Compare commits
355 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| eaca25ea30 | |||
| 6cd481e824 | |||
| 1686ce095f | |||
| 6150957689 | |||
| 3b4d519178 | |||
| 35c852b2cd | |||
| bd7ff3e4e0 | |||
| f5d5766644 | |||
| 0a3585f4f8 | |||
| a8bd42fd3f | |||
| 38d6ad4920 | |||
| 2a1f0187ac | |||
| 4f5a4dc993 | |||
| 7d0db0dea0 | |||
| add439de00 | |||
| 4a7a9e7228 | |||
| a78c36c999 | |||
| a80c90f025 | |||
| 28b13093f6 | |||
| 7dcf4a54ef | |||
| 7fdd272d76 | |||
| d5ce71b769 | |||
| fa7a390fe3 | |||
| e49b091b92 | |||
| 8c3d756842 | |||
| 97cf371362 | |||
| f14e5e0148 | |||
| e9388e5e5e | |||
| 669b3ae349 | |||
| bd9cca6fb1 | |||
| 8284748381 | |||
| 7aa37e40b9 | |||
| eab2155384 | |||
| 3e36d70164 | |||
| 3dea4906e1 | |||
| 72abd7e20e | |||
| c60119af00 | |||
| 1e0c9eabe3 | |||
| 3f700e79d3 | |||
| 7a974677fb | |||
| 4c91a7a3bf | |||
| ad51f8118d | |||
| 071a6533e0 | |||
| 9f24a71aed | |||
| 94453894e9 | |||
| 9c7223d016 | |||
| e01811e20e | |||
| 6e6eee39d4 | |||
| 68cfab1ac9 | |||
| 8e16309930 | |||
| b7953f5535 | |||
| ce5d5c5cfa | |||
| b0c8f04a53 | |||
| c59346ea68 | |||
| 14d2578a67 | |||
| f03af7a9bf | |||
| a23002ce66 | |||
| ed7cbd38e8 | |||
| cbb0bd6864 | |||
| 6e1323cc23 | |||
| 006fd6ebec | |||
| abcd35e443 | |||
| 9e964b3ea8 | |||
| bfdf940dbf | |||
| 4015f11718 | |||
| cd0ef02ab1 | |||
| 68716f38e0 | |||
| f7924d29df | |||
| c7f989da8b | |||
| 14870efc11 | |||
| c7751efa71 | |||
| b987583770 | |||
| 5245e4ffc1 | |||
| 1a3235697e | |||
| f6058d9ac0 | |||
| ffa691ac78 | |||
| 1d413f9b76 | |||
| e3bf46a89b | |||
| 90b1646876 | |||
| 1a2dd4a008 | |||
| 4e3921502d | |||
| dd4a871bf0 | |||
| 5e8ed17ef7 | |||
| 885cf52bdc | |||
| 9ef9ce2b22 | |||
| 92bb168b4e | |||
| dbca214ef2 | |||
| 48326bd102 | |||
| fd256411d1 | |||
| f3441e0115 | |||
| cd8e2974f2 | |||
| dfd33eb674 | |||
| ff88705f09 | |||
| 633fe10821 | |||
| 58aa7fe9bc | |||
| 7d4df36049 | |||
| 004ec89f44 | |||
| cbfd27b954 | |||
| ac89b3a423 | |||
| 40dfbd64fa | |||
| 3e787a1d9f | |||
| 0a2c1bf3d9 | |||
| f6932f0512 | |||
| 3f98bcf9cf | |||
| 09d5e97d5d | |||
| eeca625af6 | |||
| 512aa54700 | |||
| 85df6e096f | |||
| dbde035d77 | |||
| ee00935cfc | |||
| 4de657a9a2 | |||
| 1c225c0e10 | |||
| c50aa0f862 | |||
| 2bdead3676 | |||
| 099a041ed8 | |||
| 4914b1fbd3 | |||
| 271d79bb64 | |||
| b6fdcbebfd | |||
| b79b496ad1 | |||
| 9705f84bc0 | |||
| 6d19526458 | |||
| b7cbf4b20a | |||
| afa2b40b50 | |||
| 10280da419 | |||
| bb935dd214 | |||
| d92f5d4bb5 | |||
| ee69287813 | |||
| f205075d97 | |||
| d570d03307 | |||
| 483e188c37 | |||
| 0f1665c97d | |||
| 7ef77c3160 | |||
| 74fa735004 | |||
| eb61daab43 | |||
| a2f52d98db | |||
| b0f0710b5b | |||
| 421f04fe60 | |||
| 3d90e8b57a | |||
| a1ea11a196 | |||
| f830a8023d | |||
| a36f0dbf02 | |||
| 824aec6322 | |||
| 8e4d52ddd2 | |||
| 240052da3a | |||
| 8d42156383 | |||
| 95d52e4997 | |||
| e439d4bc39 | |||
| 702dd87c41 | |||
| ca2de244d8 | |||
| b594c9566c | |||
| 1865a3b20d | |||
| 924e553664 | |||
| b005016c48 | |||
| c4eff7c743 | |||
| d669708989 | |||
| 90353c2bc5 | |||
| 5c408948f6 | |||
| 63219434d3 | |||
| f596151c42 | |||
| b7b22dc89e | |||
| 0778b04b6b | |||
| c627690011 | |||
| d52496600f | |||
| 1a4a6721a1 | |||
| 124844b9d4 | |||
| c74804ccf5 | |||
| e5f7b3bd32 | |||
| 0705c68b7b | |||
| 82e65c712b | |||
| 3f9ee1c444 | |||
| d6314f22b2 | |||
| 3aafe342c7 | |||
| 383fab15a6 | |||
| e47d38da79 | |||
| f48fa94b82 | |||
| 7e82f4c8ea | |||
| dfb61b9fea | |||
| 12d86cbfea | |||
| 67becd9496 | |||
| 8de9bc921f | |||
| 96bb7ff56f | |||
| 7cbe5717ce | |||
| e78b900937 | |||
| 5a740e1853 | |||
| 15502b5ae5 | |||
| a2bd25f5b7 | |||
| 98265d4dc8 | |||
| 25dda4cb4c | |||
| 93d2b765ef | |||
| 25c507c699 | |||
| 504c842499 | |||
| d3ee202313 | |||
| 571eaa0fa5 | |||
| 62e41685f1 | |||
| 2acef8214d | |||
| 6510c0e85e | |||
| fa40e79a5d | |||
| e81e21f00e | |||
| 0d456eea5d | |||
| 2b929fd3c0 | |||
| 86942e45c9 | |||
| 41d3d30386 | |||
| ec10d01971 | |||
| 41c7551048 | |||
| 34e97b75aa | |||
| fd36ce87cf | |||
| aa08bfbf1b | |||
| 0fccd957b6 | |||
| 6479e31435 | |||
| b98c5f8907 | |||
| 96b5ccd17d | |||
| ec2da18050 | |||
| c9d96bbdca | |||
| f2a80275eb | |||
| 29da611864 | |||
| e243f1bd94 | |||
| ec1205c313 | |||
| 371ca06bfa | |||
| 64b4a7118b | |||
| 035f23ef23 | |||
| 423f7a7303 | |||
| 817c8f0c87 | |||
| 3e39bcc38c | |||
| 14d9936d90 | |||
| 8021c3f334 | |||
| 76342bbb96 | |||
| 6297b4cba7 | |||
| 402467fa69 | |||
| 1c7183d7f2 | |||
| c6080c175c | |||
| e0e1431745 | |||
| 8baaf1de60 | |||
| 57693f25c9 | |||
| d28b0ba316 | |||
| cb15bea748 | |||
| a42c0869bf | |||
| 73ed3a23c6 | |||
| 543e6b1abc | |||
| fe05c63882 | |||
| 1c710e2aef | |||
| 80a36e7c39 | |||
| 7c1e6d9364 | |||
| 8d186c18cd | |||
| 7693f962ca | |||
| 81e86b0df1 | |||
| 9a030bedf6 | |||
| 619e560152 | |||
| 431e2c28f2 | |||
| 8acd2396d3 | |||
| aaea9b9339 | |||
| 1de3eee117 | |||
| 12aee2c958 | |||
| 2dc48747ed | |||
| 8fc8e43b2c | |||
| 87d607335e | |||
| d8337214f0 | |||
| f5c321dd9a | |||
| a69100f689 | |||
| c7b6c0a3ba | |||
| bb2e7699dc | |||
| 10092cfab0 | |||
| 9a0fc705e1 | |||
| 66ad46cef6 | |||
| 52c20efff5 | |||
| 0ef580477f | |||
| f180f62f2c | |||
| 77a7c23a62 | |||
| 43c4277b04 | |||
| 9e41b825c0 | |||
| 158c9ea196 | |||
| b9238f1e36 | |||
| 60a12b4085 | |||
| 6564969a28 | |||
| 268dfb5bcf | |||
| 3869788b66 | |||
| b099578957 | |||
| c3a654efe9 | |||
| dfde7591cb | |||
| d5d3867456 | |||
| 4bab4ede7a | |||
| 3141a4566d | |||
| 45a32496ba | |||
| a651eb879e | |||
| b2199c8d56 | |||
| 4fa9123a31 | |||
| bf689eca59 | |||
| 14bfda467f | |||
| fb5dc72970 | |||
| b7d60361c0 | |||
| 867012d47b | |||
| e18b5e5eaf | |||
| 5efe5037c5 | |||
| 755183a144 | |||
| b9a6b1d546 | |||
| c4795f5f74 | |||
| 0d3d3cfc53 | |||
| 44fe958f31 | |||
| b68da43daf | |||
| 8a083180c1 | |||
| 55631bfb82 | |||
| 7cf7995371 | |||
| 2bb9310f78 | |||
| 84c1f94f30 | |||
| 7cf8f6c2c3 | |||
| 6fae66001e | |||
| dbb7c322a2 | |||
| 988ed00ff6 | |||
| ca00f690fd | |||
| 3e88ae46b6 | |||
| 971a84105b | |||
| b778421593 | |||
| 5cfd2c4586 | |||
| 1cd1221217 | |||
| 2d52fb9915 | |||
| fc7df4bf2a | |||
| fc365736ed | |||
| 0b2ec5ac0a | |||
| 7f1254b05e | |||
| 0bedf6c42a | |||
| 0c6fd1f864 | |||
| bf9637a82d | |||
| 46753d7a3f | |||
| b3347cfa8e | |||
| c256dd3d60 | |||
| 08ba917d02 | |||
| e46e521e9b | |||
| 1f8d51454e | |||
| 23afbf477e | |||
| 46714ae57f | |||
| e929419585 | |||
| e39f01bde5 | |||
| 7822d20efe | |||
| 155bfb3185 | |||
| 647248009d | |||
| 244b7dc0cb | |||
| 2ae00f07cd | |||
| 709d2c0f5d | |||
| de01ee9387 | |||
| 79f8c91157 | |||
| 787f911b6f | |||
| 7c490d2180 | |||
| aa3de59652 | |||
| bae6a56ed3 | |||
| f7cb93d768 | |||
| b07933ea08 | |||
| e9d4b4c6a2 | |||
| 2ac8d26687 | |||
| dcd55275b5 | |||
| e38199e6c7 | |||
| af4106614c | |||
| c720a80bc0 | |||
| 345da8394b | |||
| d465b0a4be | |||
| d4d6978d5f | |||
| e507d1f75d |
+1
-1
@@ -4,7 +4,7 @@
|
||||
branch = master
|
||||
[submodule "shared"]
|
||||
path = shared
|
||||
url = https://git.did.science/WolverinDEV/TeaSpeak-SharedLib.git
|
||||
url = https://git.did.science/TeaSpeak/TeaSpeakLibrary.git
|
||||
[submodule "music"]
|
||||
path = music
|
||||
url = https://github.com/TeaSpeak/TeaMusic-Providers.git
|
||||
|
||||
+83
-41
@@ -1,67 +1,109 @@
|
||||
cmake_minimum_required(VERSION 3.6)
|
||||
#project(TeamSpeak)
|
||||
project(TeaSpeak-Parent)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17")
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake/Modules")
|
||||
set(TEASPEAK_SERVER ON)
|
||||
|
||||
include_directories(/usr/local/mysql/connector-c++-8.0/include/jdbc/)
|
||||
#end now
|
||||
#set(MEMORY_DEBUG_FLAGS " -fsanitize=leak -fsanitize=address -fstack-protector-all ")
|
||||
#set(MEMORY_DEBUG_FLAGS "-fsanitize=address")
|
||||
|
||||
if (NOT BUILD_OS_ARCH)
|
||||
set(BUILD_OS_ARCH $ENV{build_os_arch})
|
||||
endif ()
|
||||
|
||||
if (NOT BUILD_OS_ARCH)
|
||||
set(BUILD_OS_ARCH $ENV{build_os_arch})
|
||||
endif ()
|
||||
|
||||
set(LIBRARY_PATH "${CMAKE_SOURCE_DIR}/../libraries/")
|
||||
if (BUILD_INCLUDE_FILE)
|
||||
include(${BUILD_INCLUDE_FILE})
|
||||
endif ()
|
||||
|
||||
set(CMAKE_PREFIX_PATH "/home/wolverindev/clib/qt/5.6.1/5.6/gcc_64/lib/cmake")
|
||||
set(LIBEVENT_PATH "${LIBRARY_PATH}/event/build/lib/")
|
||||
|
||||
function(resolve_library VARIABLE FALLBACK PATHS)
|
||||
set( _PATHS ${PATHS} ${ARGN} ) # Merge them together
|
||||
set( _PATHS ${PATHS} ${ARGN} ) # Merge them together
|
||||
|
||||
foreach(PATH IN ITEMS ${_PATHS})
|
||||
message(STATUS "Try to use path ${PATH} for ${VARIABLE}")
|
||||
if(EXISTS ${PATH})
|
||||
message(STATUS "Setting ${VARIABLE} to ${PATH}")
|
||||
set(${VARIABLE} ${PATH} PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
endforeach()
|
||||
foreach(PATH IN ITEMS ${_PATHS})
|
||||
message(STATUS "Try to use path ${PATH} for ${VARIABLE}")
|
||||
if(EXISTS ${PATH})
|
||||
message(STATUS "Setting ${VARIABLE} to ${PATH}")
|
||||
set(${VARIABLE} ${PATH} PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
if(FALLBACK)
|
||||
message(WARNING "Failed to resolve library path for ${VARIABLE}. Using default ${VARIABLE}")
|
||||
else()
|
||||
message(FATAL_ERROR "Failed to find requited library. Variable: ${VARIABLE} Paths: ${_PATHS}")
|
||||
endif()
|
||||
if(FALLBACK)
|
||||
message(WARNING "Failed to resolve library path for ${VARIABLE}. Using default ${VARIABLE}")
|
||||
else()
|
||||
message(FATAL_ERROR "Failed to find requited library. Variable: ${VARIABLE} Paths: ${_PATHS}")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
resolve_library(LIBRARY_TOM_MATH OFF "${LIBRARY_PATH}/tommath/build/libtommathStatic.a")
|
||||
resolve_library(LIBRARY_TOM_CRYPT OFF "${LIBRARY_PATH}/tomcrypt/libtomcrypt.a")
|
||||
resolve_library(LIBRARY_PATH_BREAKPAD OFF "${LIBRARY_PATH}/breakpad/build/src/client/linux/libbreakpad_client.a")
|
||||
resolve_library(LIBRARY_PATH_PROTOBUF OFF "${LIBRARY_PATH}/protobuf/build/libprotobuf.a")
|
||||
resolve_library(LIBRARY_PATH_BORINGSSL_SSL OFF "${LIBRARY_PATH}/boringssl/build/ssl/libssl.so")
|
||||
resolve_library(LIBRARY_PATH_BORINGSSL_CRYPTO OFF "${LIBRARY_PATH}/boringssl/build/crypto/libcrypto.so")
|
||||
resolve_library(LIBRARY_PATH_THREAD_POOL OFF "${LIBRARY_PATH}/Thread-Pool/build/libThreadPoolStatic.a")
|
||||
resolve_library(LIBRARY_PATH_TERMINAL OFF "${LIBRARY_PATH}/CXXTerminal/build/libCXXTerminalStatic.a")
|
||||
resolve_library(LIBRARY_PATH_VARIBALES OFF "${LIBRARY_PATH}/StringVariable/build/libStringVariablesStatic.a")
|
||||
resolve_library(LIBRARY_PATH_YAML OFF "${LIBRARY_PATH}/yaml-cpp/build/libyaml-cpp.a")
|
||||
resolve_library(LIBRARY_PATH_JSON OFF "${LIBRARY_PATH}/jsoncpp/build/src/lib_json/libjsoncpp.a")
|
||||
resolve_library(LIBRARY_PATH_ED255 OFF "${LIBRARY_PATH}/ed25519/build/libed25519.a")
|
||||
resolve_library(LIBRARY_PATH_DATA_PIPES OFF "${LIBRARY_PATH}/DataPipes/build/libDataPipes.so" "${LIBRARY_PATH}/DataPipes/cmake-build-release/libDataPipes.so" "${LIBRARY_PATH}/DataPipes/cmake-build-debug/libDataPipes.so")
|
||||
resolve_library(LIBRARY_PATH_OPUS OFF "${LIBRARY_PATH}/opus/build/.libs/libopus.a")
|
||||
find_package(TomMath REQUIRED)
|
||||
find_package(TomCrypt REQUIRED)
|
||||
find_package(Breakpad REQUIRED)
|
||||
find_package(Protobuf REQUIRED)
|
||||
find_package(Crypto REQUIRED)
|
||||
find_package(ThreadPool REQUIRED)
|
||||
find_package(CXXTerminal REQUIRED)
|
||||
find_package(StringVariable REQUIRED)
|
||||
find_package(yaml-cpp REQUIRED)
|
||||
find_package(jsoncpp REQUIRED)
|
||||
find_package(Ed25519 REQUIRED)
|
||||
find_package(DataPipes REQUIRED)
|
||||
find_package(Opus REQUIRED)
|
||||
find_package(spdlog REQUIRED)
|
||||
find_package(Jemalloc REQUIRED)
|
||||
find_package(Protobuf REQUIRED)
|
||||
message("${zstd_DIR}")
|
||||
find_package(zstd REQUIRED)
|
||||
|
||||
#if(EXISTS ${CMAKE_SOURCE_DIR}/../libraries/mysqlconnector/build/libmysqlcppconn8.so.1)
|
||||
# set(LIBRARY_PATH_MYSQL "${CMAKE_SOURCE_DIR}/../libraries/mysqlconnector/build/libmysqlcppconn8.so.1")
|
||||
#else()
|
||||
# set(LIBRARY_PATH_MYSQL "libmysqlcppconn8-static.a")
|
||||
# message(WARNING "Could not resolve mysql! Using default resolver")
|
||||
#endif()
|
||||
include_directories(${StringVariable_INCLUDE_DIR})
|
||||
add_subdirectory(music/)
|
||||
|
||||
# LibEvent fucks up the CMAKE_FIND_LIBRARY_SUFFIXES variable
|
||||
macro(find_event static)
|
||||
set(_CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES})
|
||||
if (${static})
|
||||
set(LIBEVENT_STATIC_LINK TRUE)
|
||||
message("Use static libevent")
|
||||
endif ()
|
||||
find_package(Libevent 2.2 REQUIRED COMPONENTS core pthreads)
|
||||
if (NOT Libevent_FOUND)
|
||||
message(FATAL_ERROR "Failed to find libevent (Variable: ${LIBEVENT_FOUND})")
|
||||
endif ()
|
||||
message("Libevent version: ${Libevent_FOUND}")
|
||||
endmacro()
|
||||
find_event(TRUE)
|
||||
|
||||
check_include_file(mysql/mysql.h HAVE_MYSQL_MYSQL_H)
|
||||
if(HAVE_MYSQL_MYSQL_H)
|
||||
add_definitions(-DHAVE_MYSQL_MYSQL_H)
|
||||
endif()
|
||||
|
||||
check_include_file(mysql.h HAVE_MYSQL_H)
|
||||
if(HAVE_MYSQL_H)
|
||||
add_definitions(-DHAVE_MYSQL_H)
|
||||
endif()
|
||||
|
||||
#FIXME: Use module for this
|
||||
include_directories(${breakpad_INCLUDE_DIR})
|
||||
include_directories(${ed25519_INCLUDE_DIR})
|
||||
include_directories(${ThreadPool_INCLUDE_DIR})
|
||||
include_directories(${DataPipes_INCLUDE_DIR})
|
||||
include_directories(${LIBEVENT_INCLUDE_DIRS})
|
||||
include_directories(${StringVariable_INCLUDE_DIR})
|
||||
|
||||
add_definitions(-DINET -DINET6)
|
||||
|
||||
add_subdirectory(shared/)
|
||||
add_subdirectory(client/)
|
||||
add_subdirectory(server/)
|
||||
add_subdirectory(license/)
|
||||
add_subdirectory(flooder/)
|
||||
add_subdirectory(MusicBot/)
|
||||
add_subdirectory(music/)
|
||||
add_subdirectory(file/)
|
||||
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.6)
|
||||
project(TeaMusic)
|
||||
|
||||
set(CMAKE_VERBOSE_MAKEFILE ON)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fpermissive -Wall -Wno-sign-compare -Wno-reorder -static-libgcc -static-libstdc++ -Wl,--enable-new-dtags,--export-dynamic")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fpermissive -Wall -Wno-sign-compare -Wno-reorder -static-libgcc -static-libstdc++")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3")
|
||||
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELEASE} -O3")
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/enviroment/)
|
||||
@@ -10,12 +10,13 @@ set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/libs/)
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
|
||||
include_directories(../music/include/)
|
||||
include_directories(../shared/src)
|
||||
add_definitions(-DLTM_DESC)
|
||||
|
||||
#The basic library
|
||||
add_library(TeaMusic SHARED src/MusicPlayer.cpp)
|
||||
target_link_libraries(TeaMusic PUBLIC TeaSpeak libevent::core libevent::pthreads dl)
|
||||
|
||||
#The test file
|
||||
add_executable(TeaMusicTest ${MUSIC_SOURCE_FILES} main.cpp)
|
||||
target_link_libraries(TeaMusicTest ProviderFFMpeg ProviderYT ProviderOpus TeaMusic pthread ThreadPool opus asound opusfile stdc++fs dl mpg123 avformat avcodec avutil TeaSpeak CXXTerminal event jsoncpp)
|
||||
target_link_libraries(TeaMusicTest ProviderFFMpeg ProviderYT TeaMusic pthread asound dl stdc++fs)
|
||||
#ThreadPool opus asound opusfile stdc++fs dl TeaSpeak CXXTerminal event jsoncpp
|
||||
@@ -1,249 +0,0 @@
|
||||
# Musik bot websocket protocol
|
||||
## General structure
|
||||
Transmitted data is in json format
|
||||
The json format has the structure as following:
|
||||
```
|
||||
{
|
||||
"type": <PacketType>,
|
||||
"data": [
|
||||
{
|
||||
<key>: value
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
Example:
|
||||
```
|
||||
{
|
||||
"type": "showMessage",
|
||||
"data": [
|
||||
{
|
||||
"message": "A simple info modal",
|
||||
"type": "info"
|
||||
},
|
||||
{
|
||||
"message": "A simple error modal",
|
||||
"type": "error"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
## TODO list
|
||||
* Music bot queue
|
||||
* Music bot ts3 access rights (allow other clients to use this music bot etc.)
|
||||
|
||||
## Packet types
|
||||
### General types
|
||||
#### Server `showMessage`:
|
||||
This packet should show up a message modal.
|
||||
* **[~]**
|
||||
* `message`: <msg>
|
||||
* `type`: {info|error}
|
||||
|
||||
|
||||
#### Server `reqError`:
|
||||
The server sends this if you applay a invalid request
|
||||
* **[1]**
|
||||
* `message`: <msg>
|
||||
* `requestId`: <reqestId>
|
||||
|
||||
|
||||
#### Server `disconnect`
|
||||
I send this packet before i close the comunication
|
||||
* **[1]**
|
||||
* `message`
|
||||
|
||||
___
|
||||
### Login packets
|
||||
#### Client `login`
|
||||
Try login
|
||||
* **[1]**
|
||||
* `username`
|
||||
* `password`
|
||||
* `requestId`
|
||||
|
||||
|
||||
#### Server `notifylogin`
|
||||
* **[1]**
|
||||
* `requestId`
|
||||
* `succeeded`: {0|1}
|
||||
* `uid` own uid. only set if login failed
|
||||
* `message` only set if login failed
|
||||
|
||||
#### Client `logout`
|
||||
* **[1]**
|
||||
* `requestId`
|
||||
|
||||
#### Server `notifylogout`
|
||||
Could be send at any time (force logout)
|
||||
* **[1]**
|
||||
* `requestId` (empty of not requested)
|
||||
* `succeeded`: {0|1}
|
||||
|
||||
___
|
||||
### Server Management
|
||||
#### Client `serverlist`
|
||||
* **[1]**
|
||||
* `requestId`
|
||||
|
||||
#### Server `notifyserverlist`
|
||||
Sends when requested or list updated
|
||||
(Lists online avariable server for the client view)
|
||||
* **[~]**
|
||||
* [1] `requestId` (empty of not requested)
|
||||
* `name`
|
||||
* `uid`
|
||||
* `serverId`
|
||||
* `status`: {online|offline}
|
||||
* `clientOnline`
|
||||
* `maxClients`
|
||||
|
||||
#### Server `notifyserverupdate`
|
||||
Sends when a server changes display properties
|
||||
* **[1]**
|
||||
* `serverId`
|
||||
* `key`: {name|onlineClients|maxClients}
|
||||
* `value`
|
||||
|
||||
___
|
||||
### Channel Management
|
||||
#### Client `channellist`
|
||||
Request a channel list
|
||||
* **[1]**
|
||||
* `requestId`
|
||||
* `serverId`
|
||||
|
||||
#### Server `notifychannellist`
|
||||
The channel response is ordered:
|
||||
This packet would also be send if the channel tree gets updated
|
||||
```
|
||||
root
|
||||
- sub 1
|
||||
- sub sub 1
|
||||
- sub sub 2
|
||||
- sub 2
|
||||
root 2
|
||||
...
|
||||
```
|
||||
* **[~]**
|
||||
* [1] `requestId` (empty of not requested)
|
||||
* [1] `serverId`
|
||||
* `name`
|
||||
* `channelId`
|
||||
* `channelParent`
|
||||
* `channelOrder`
|
||||
|
||||
___
|
||||
### Music bot management
|
||||
#### Client `musicbotlist`
|
||||
* **[1]**
|
||||
* `requestId`
|
||||
* `serverId`
|
||||
|
||||
#### Server `notifymusikmusicbotlist`
|
||||
* **[~]**
|
||||
* [1] `requestId` (empty of not requested)
|
||||
* [1] `serverId`
|
||||
* `id`
|
||||
* `connected`: {1|0}
|
||||
* `name`
|
||||
* `channelId`
|
||||
* `ownerUid` (its your own if its matching with our own id)
|
||||
* `ownerCldbid`
|
||||
|
||||
#### Client `musicbotcreate`
|
||||
* **[1]**
|
||||
* `requestId`
|
||||
* `serverId`
|
||||
* `name`
|
||||
* `channelId`
|
||||
|
||||
#### Server `notifymusikbotcreated`
|
||||
* **[1]**
|
||||
* `requestId` (empty of not requested)
|
||||
* `serverId`
|
||||
* `id`
|
||||
* `connected`: {1|0}
|
||||
* `name`
|
||||
* `channelId`
|
||||
* `ownerUid` (its your own if its matching with our own id)
|
||||
* `ownerCldbid`
|
||||
|
||||
#### Client `musicbotdelete`
|
||||
* **[1]**
|
||||
* `requestId`
|
||||
* `serverId`
|
||||
* `name`
|
||||
* `channelId`
|
||||
|
||||
#### Server `notifymusikbotdelete`
|
||||
* **[1]**
|
||||
* `requestId` (empty of not requested)
|
||||
* `serverId`
|
||||
* `id`
|
||||
|
||||
#### Client `musicbotinfo`
|
||||
Request music bot info
|
||||
* **[1]**
|
||||
* `requestId`
|
||||
* `serverId`
|
||||
* `id`
|
||||
|
||||
#### Server `notifymusicbotinfo`
|
||||
* **[1]**
|
||||
* `requestId`
|
||||
* `serverId`
|
||||
* `id`
|
||||
* `name`
|
||||
* `connected`
|
||||
* `phoeticName`
|
||||
* `channelId`
|
||||
* `playing`
|
||||
* `playingInfo`: <string|Current playing title etc. May need to be inproved> Empty if noting selected
|
||||
* `description`
|
||||
* `textCurrentSong`
|
||||
|
||||
#### Client `musicbotedit`
|
||||
* **[1]**
|
||||
* `requestId`
|
||||
* `serverId`
|
||||
* `id`
|
||||
* `key`
|
||||
* `value`
|
||||
|
||||
#### Server `notifymusicbotedit`
|
||||
* **[~]**
|
||||
* [1] `requestId` (empty of not requested)
|
||||
* [1] `serverId`
|
||||
* `id`
|
||||
* `key`: {connected|name|channelId|description|playing|playingInfo}
|
||||
|
||||
#### Client `musicbotplay`
|
||||
* **[1]**
|
||||
* `requestId`
|
||||
* `serverId`
|
||||
* `id`
|
||||
* `type`: {yt|file}
|
||||
* `value`
|
||||
|
||||
#### Server `notifymusicbotplay`
|
||||
Send only as answer for `musicbotplay`
|
||||
You would recive the play state update via `notifymusicbotedit`
|
||||
* **[1]**
|
||||
* `requestId`
|
||||
* `succeeded`
|
||||
*
|
||||
#### Client `musicbotstop`
|
||||
* **[1]**
|
||||
* `requestId`
|
||||
* `serverId`
|
||||
* `id`
|
||||
* `paused`: {1|0}
|
||||
|
||||
#### Server `notifymusicbotstop`
|
||||
Send only as answer for `musicbotstop`
|
||||
You would recive the play state update via `notifymusicbotedit`
|
||||
* **[1]**
|
||||
* `requestId`
|
||||
* `succeeded`
|
||||
+48
-100
@@ -3,13 +3,8 @@
|
||||
#include <alsa/asoundlib.h>
|
||||
#include <fstream>
|
||||
#include <chrono>
|
||||
#include <MusicPlayer.h>
|
||||
#include <teaspeak/MusicPlayer.h>
|
||||
|
||||
extern "C" {
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavutil/avutil.h>
|
||||
}
|
||||
#include <log/LogUtils.h>
|
||||
#include <CXXTerminal/Terminal.h>
|
||||
|
||||
@@ -18,18 +13,17 @@ using namespace std;
|
||||
using namespace std::chrono;
|
||||
using namespace music;
|
||||
|
||||
void die(const char* message)
|
||||
{
|
||||
void die(const char *message) {
|
||||
fprintf(stderr, "%s\n", message);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
int main(int, char**){
|
||||
logger::config::logLevel = spdlog::level::trace;
|
||||
logger::config::terminalLevel = spdlog::level::trace;
|
||||
//terminal::install();
|
||||
|
||||
logger::setup();
|
||||
int main(int, char **) {
|
||||
auto config = std::make_shared<logger::LoggerConfig>();
|
||||
config->logfileLevel = spdlog::level::off;
|
||||
config->terminalLevel = spdlog::level::trace;
|
||||
config->sync = true;
|
||||
logger::setup(config);
|
||||
logger::updateLogLevels();
|
||||
|
||||
//youtube-dl -s --dump-json https://www.youtube.com/watch?v=1ifTLj_glhc
|
||||
@@ -37,126 +31,77 @@ int main(int, char**){
|
||||
//
|
||||
|
||||
//printf("Length: %d\n", codec_context->frame_size / codec_context->framerate.den / codec_context->channels); //576 | 3
|
||||
/*
|
||||
// To initalize libao for playback
|
||||
ao_initialize();
|
||||
|
||||
int driver = ao_default_driver_id();
|
||||
music::manager::loadProviders("../../music/bin/providers/");
|
||||
|
||||
// The format of the decoded PCM samples
|
||||
ao_sample_format sample_format;
|
||||
sample_format.bits = 16;
|
||||
sample_format.channels = 2;
|
||||
sample_format.rate = 44100;
|
||||
sample_format.byte_format = AO_FMT_NATIVE;
|
||||
sample_format.matrix = 0;
|
||||
|
||||
ao_device* device = ao_open_live(driver, &sample_format, NULL);
|
||||
|
||||
AVPacket packet;
|
||||
int buffer_size = AVCODEC_MAX_AUDIO_FRAME_SIZE;
|
||||
int8_t buffer[AVCODEC_MAX_AUDIO_FRAME_SIZE];
|
||||
|
||||
while (1) {
|
||||
|
||||
buffer_size = AVCODEC_MAX_AUDIO_FRAME_SIZE;
|
||||
|
||||
// Read one packet into `packet`
|
||||
if (av_read_frame(container, &packet) < 0) {
|
||||
break; // End of lstream. Done decoding.
|
||||
}
|
||||
|
||||
// Decodes from `packet` into the buffer
|
||||
if (avcodec_decode_audio3(codec_context, (int16_t*)buffer, &buffer_size, &packet) < 1) {
|
||||
break; // Error in decoding
|
||||
}
|
||||
|
||||
// Send the buffer contents to the audio device
|
||||
ao_play(device, (char*)buffer, buffer_size);
|
||||
}
|
||||
|
||||
av_close_input_file(container);
|
||||
|
||||
ao_shutdown();
|
||||
|
||||
fprintf(stdout, "Done playing. Exiting...");
|
||||
*/
|
||||
|
||||
music::manager::loadProviders("providers/");
|
||||
#if false
|
||||
std::string file = "https://www.youtube.com/watch?v=GVC5adzPpiE"; //https://www.youtube.com/watch?v=eBlg2oX0Z0Q
|
||||
auto provider = music::manager::resolveProvider("YouTube", file); //test.mp3
|
||||
if(!provider) return 0;
|
||||
cout << "Using provider -> " << provider->providerName << endl;
|
||||
cout << "Using provider -> " << provider->providerDescription << endl;
|
||||
if(true) return 0;
|
||||
/*
|
||||
auto fut = provider->createPlayer(file);
|
||||
fut.waitAndGet(nullptr);
|
||||
if(fut.failed()){
|
||||
log::log(log::err, "Could not create: " + fut.errorMegssage());
|
||||
} else log::log(log::info, "Got!");
|
||||
//VALID
|
||||
/*
|
||||
auto player = provider->createPlayer("https://r5---sn-4g5edned.googlevideo.com/videoplayback?lmt=1491636391724572&key=yt6&itag=251&keepalive=yes&signature=8220B0B2D85AFE6A44CAE901A9C5A5D9A2564DA4.59183C41417E91E04552835738ED0AD1592BD05E&source=youtube&clen=4017657&sparams=clen%2Cdur%2Cei%2Cgir%2Cid%2Cinitcwndbps%2Cip%2Cipbits%2Citag%2Ckeepalive%2Clmt%2Cmime%2Cmm%2Cmn%2Cms%2Cmv%2Cpl%2Crequiressl%2Csource%2Cexpire&gir=yes&expire=1516917262&initcwndbps=856250&ei=rv1pWuvYBI-AV7SlqaAE&dur=234.061&mv=m&mt=1516895537&ms=au&requiressl=yes&ip=93.230.21.99&ipbits=0&mn=sn-4g5edned&mm=31&pl=26&mime=audio%2Fwebm&id=o-ABQnAlwQxtPQJTqHMc9SiTD-bGc1YR2yogmPcVUy1Xxp&ratebypass=yes").waitAndGet(nullptr);
|
||||
/*
|
||||
auto player = provider->createPlayer("https://r5---sn-4g5edned.googlevideo.com/videoplayback?mn=sn-4g5edned&mm=31&gir=yes&clen=4017657&requiressl=yes&mv=m&mt=1516820571&ms=au&ei=tdhoWtv0Fd5bY1wLY5qVA&lmt=1491636391724572&key=yt6&ip=80.133.238.232&expire=1516842261&dur=234.061&beids=%5B9466594%5D&id=o-AKeOawWpz0BT9Qxuqria6qw1_eTa3is7UXit4BLIC8re&initcwndbps=591250&source=youtube&sparams=clen%2Cdur%2Cei%2Cgir%2Cid%2Cinitcwndbps%2Cip%2Cipbits%2Citag%2Ckeepalive%2Clmt%2Cmime%2Cmm%2Cmn%2Cms%2Cmv%2Cpl%2Crequiressl%2Csource%2Cexpire&ipbits=0&signature=230305B8592A57E939FDBF2D4C19C16DC2FE0D68.AC475B47E4533071F4A6EA96960384398A15D801&pl=26&itag=251&mime=audio%2Fwebm&keepalive=yes&ratebypass=yes").waitAndGet(nullptr);
|
||||
*/
|
||||
auto player = provider->createPlayer(file).waitAndGet(nullptr);
|
||||
if(!player){
|
||||
#endif
|
||||
constexpr auto url = "https://streams.ilovemusic.de/iloveradio1.mp3";
|
||||
auto provider = music::manager::resolveProvider("ffmpeg", url);
|
||||
if (!provider) return 0;
|
||||
|
||||
auto player = provider->createPlayer(url, nullptr, nullptr).waitAndGet(nullptr);
|
||||
if (!player) {
|
||||
cerr << "Could not load youtube video" << endl;
|
||||
return -1;
|
||||
}
|
||||
if(!player->initialize()){
|
||||
if (!player->initialize(2)) {
|
||||
log::log(log::err, "Could not inizalisze ffmpeg player -> " + player->error());
|
||||
return 1;
|
||||
}
|
||||
player->registerEventHandler("main", [player](music::MusicEvent event){ //FIXME weak ptr
|
||||
player->registerEventHandler("main", [player](music::MusicEvent event) { //FIXME weak ptr
|
||||
log::log(log::info, "Got event " + to_string(event));
|
||||
if(event == music::EVENT_ERROR) {
|
||||
if (event == music::EVENT_ERROR) {
|
||||
log::log(log::err, "Recived error: " + player->error());
|
||||
player->clearError();
|
||||
}
|
||||
});
|
||||
player->play();
|
||||
player->forward(chrono::minutes(0));
|
||||
//player->forward(chrono::minutes(0));
|
||||
cout << "Song length " << duration_cast<seconds>(player->length()).count() << " seconds" << endl;
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::seconds{600});
|
||||
|
||||
unsigned int pcm, tmp, dir;
|
||||
#if false
|
||||
snd_pcm_t *pcm{nullptr};
|
||||
int err, tmp, dir;
|
||||
snd_pcm_t *pcm_handle;
|
||||
snd_pcm_hw_params_t *params;
|
||||
snd_pcm_uframes_t frames;
|
||||
int loops;
|
||||
|
||||
int channels = 2;
|
||||
int rate = 48000;
|
||||
unsigned int rate = 48000;
|
||||
int seconds = duration_cast<std::chrono::seconds>(player->length()).count() + 1;
|
||||
seconds = 10000;
|
||||
|
||||
if (pcm = snd_pcm_open(&pcm_handle, PCM_DEVICE, SND_PCM_STREAM_PLAYBACK, 0) < 0)
|
||||
printf("ERROR: Can't open \"%s\" PCM device. %s\n",
|
||||
PCM_DEVICE, snd_strerror(pcm));
|
||||
if (err = snd_pcm_open(&pcm_handle, PCM_DEVICE, SND_PCM_STREAM_PLAYBACK, 0); err < 0)
|
||||
printf("ERROR: Can't open \"%s\" PCM device. %s\n", PCM_DEVICE, snd_strerror(err));
|
||||
|
||||
snd_pcm_hw_params_alloca(¶ms);
|
||||
|
||||
snd_pcm_hw_params_any(pcm_handle, params);
|
||||
|
||||
if (pcm = snd_pcm_hw_params_set_access(pcm_handle, params,
|
||||
SND_PCM_ACCESS_RW_INTERLEAVED) < 0)
|
||||
printf("ERROR: Can't set interleaved mode. %s\n", snd_strerror(pcm));
|
||||
if (err = snd_pcm_hw_params_set_access(pcm_handle, params, SND_PCM_ACCESS_RW_INTERLEAVED); err < 0)
|
||||
printf("ERROR: Can't set interleaved mode. %s\n", snd_strerror(err));
|
||||
|
||||
if (pcm = snd_pcm_hw_params_set_format(pcm_handle, params,
|
||||
SND_PCM_FORMAT_S16_LE) < 0)
|
||||
printf("ERROR: Can't set format. %s\n", snd_strerror(pcm));
|
||||
if (err = snd_pcm_hw_params_set_format(pcm_handle, params, SND_PCM_FORMAT_S16_LE); err < 0)
|
||||
printf("ERROR: Can't set format. %s\n", snd_strerror(err));
|
||||
|
||||
if (pcm = snd_pcm_hw_params_set_channels(pcm_handle, params, channels) < 0)
|
||||
printf("ERROR: Can't set channels number. %s\n", snd_strerror(pcm));
|
||||
if (err = snd_pcm_hw_params_set_channels(pcm_handle, params, channels); err < 0)
|
||||
printf("ERROR: Can't set channels number. %s\n", snd_strerror(err));
|
||||
|
||||
if (pcm = snd_pcm_hw_params_set_rate_near(pcm_handle, params, &rate, 0) < 0)
|
||||
printf("ERROR: Can't set rate. %s\n", snd_strerror(pcm));
|
||||
if (err = snd_pcm_hw_params_set_rate_near(pcm_handle, params, &rate, 0); err < 0)
|
||||
printf("ERROR: Can't set rate. %s\n", snd_strerror(err));
|
||||
|
||||
if (pcm = snd_pcm_hw_params(pcm_handle, params) < 0)
|
||||
printf("ERROR: Can't set harware parameters. %s\n", snd_strerror(pcm));
|
||||
if (err = snd_pcm_hw_params(pcm_handle, params); err < 0)
|
||||
printf("ERROR: Can't set harware parameters. %s\n", snd_strerror(err));
|
||||
|
||||
printf("PCM name: '%s'\n", snd_pcm_name(pcm_handle));
|
||||
printf("PCM state: %s\n", snd_pcm_state_name(snd_pcm_state(pcm_handle)));
|
||||
@@ -174,9 +119,9 @@ int main(int, char**){
|
||||
|
||||
printf("seconds: %d\n", seconds);
|
||||
|
||||
snd_pcm_hw_params_get_period_size(params, &frames, 0);
|
||||
snd_pcm_hw_params_get_period_size(params, &frames, nullptr);
|
||||
cout << "perd size: " << frames << endl;
|
||||
snd_pcm_hw_params_get_period_time(params, &tmp, NULL);
|
||||
snd_pcm_hw_params_get_period_time(params, &tmp, nullptr);
|
||||
player->preferredSampleCount(frames);
|
||||
|
||||
auto last = system_clock::now();
|
||||
@@ -184,27 +129,30 @@ int main(int, char**){
|
||||
//cout << " dur: " << duration_cast<microseconds>(system_clock::now() - last).count() << endl;
|
||||
|
||||
auto next = player->popNextSegment();
|
||||
if(!next) {
|
||||
if (!next) {
|
||||
log::log(log::info, "END!");
|
||||
continue;
|
||||
}
|
||||
retry:
|
||||
if (pcm = snd_pcm_writei(pcm_handle, next->segments, next->segmentLength) == -EPIPE) {
|
||||
if (err = snd_pcm_writei(pcm_handle, next->segments, next->segmentLength); err == -EPIPE) {
|
||||
printf("XRUN.\n");
|
||||
snd_pcm_prepare(pcm_handle);
|
||||
goto retry;
|
||||
} else if (pcm < 0) {
|
||||
printf("ERROR. Can't write to PCM device. %s\n", snd_strerror(pcm));
|
||||
printf("ERROR. Can't write to PCM device. %s\n", snd_strerror(err));
|
||||
}
|
||||
//if((system_clock::now() - last) > ::seconds(1)) {
|
||||
log::log(log::debug, "Time: " + to_string(duration_cast<milliseconds>(system_clock::now() - last).count()) + " | " + to_string(duration_cast<milliseconds>(player->currentIndex()).count()));
|
||||
last = system_clock::now();
|
||||
log::log(log::debug,
|
||||
"Time: " + to_string(duration_cast<milliseconds>(system_clock::now() - last).count()) + " | " +
|
||||
to_string(duration_cast<milliseconds>(player->currentIndex()).count()));
|
||||
last = system_clock::now();
|
||||
//}
|
||||
//TODO!
|
||||
}
|
||||
|
||||
snd_pcm_drain(pcm_handle);
|
||||
snd_pcm_close(pcm_handle);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
#include <algorithm>
|
||||
#include <log/LogUtils.h>
|
||||
#include <experimental/filesystem>
|
||||
#include "MusicPlayer.h"
|
||||
#include "teaspeak/MusicPlayer.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace music;
|
||||
@@ -15,14 +15,14 @@ void log::log(const Level& lvl, const std::string& msg) {
|
||||
}
|
||||
|
||||
void AbstractMusicPlayer::registerEventHandler(const std::string& key, const std::function<void(MusicEvent)>& function) {
|
||||
threads::MutexLock lock(this->eventLock);
|
||||
this->eventHandlers.push_back({key, function});
|
||||
std::lock_guard lock(this->eventLock);
|
||||
this->eventHandlers.emplace_back(key, function);
|
||||
}
|
||||
|
||||
void AbstractMusicPlayer::unregisterEventHandler(const std::string& string) {
|
||||
threads::MutexLock lock(this->eventLock);
|
||||
std::lock_guard lock(this->eventLock);
|
||||
for(const auto& entry : this->eventHandlers){
|
||||
if(entry.first == string){
|
||||
if(entry.first == string) {
|
||||
this->eventHandlers.erase(find_if(this->eventHandlers.begin(), this->eventHandlers.end(), [string](const std::pair<std::string, std::function<void(MusicEvent)>>& elm){ return elm.first == string; }));
|
||||
break;
|
||||
}
|
||||
@@ -30,26 +30,29 @@ void AbstractMusicPlayer::unregisterEventHandler(const std::string& string) {
|
||||
}
|
||||
|
||||
void AbstractMusicPlayer::fireEvent(MusicEvent event) {
|
||||
threads::MutexLock lock(this->eventLock);
|
||||
auto listCopy = this->eventHandlers; //Copy for remove while fire
|
||||
for(const auto& entry : listCopy)
|
||||
decltype(this->eventHandlers) handlers{};
|
||||
{
|
||||
std::lock_guard lock(this->eventLock);
|
||||
handlers = this->eventHandlers; //Copy for remove while fire
|
||||
}
|
||||
for(const auto& entry : handlers)
|
||||
entry.second(event);
|
||||
}
|
||||
|
||||
const char* music::stateNames[] = {"uninitialised", "playing", "paused", "stopped"};
|
||||
|
||||
static threads::Mutex staticLock;
|
||||
static std::mutex staticLock;
|
||||
static std::deque<std::shared_ptr<PlayerProvider>> types;
|
||||
|
||||
std::deque<std::shared_ptr<PlayerProvider>> manager::registeredTypes(){ return types; }
|
||||
void registerType(const std::shared_ptr<PlayerProvider>& provider) {
|
||||
threads::MutexLock l(staticLock);
|
||||
std::lock_guard l(staticLock);
|
||||
types.push_back(provider);
|
||||
}
|
||||
|
||||
//empty for not set
|
||||
std::shared_ptr<PlayerProvider> manager::resolveProvider(const std::string& provName, const std::string& str) {
|
||||
threads::MutexLock l(staticLock);
|
||||
std::lock_guard l(staticLock);
|
||||
vector<std::shared_ptr<PlayerProvider>> provs;
|
||||
for(const auto& prov : types){
|
||||
auto p = prov.get();
|
||||
@@ -75,11 +78,16 @@ void manager::loadProviders(const std::string& path) {
|
||||
}
|
||||
|
||||
deque<fs::path> paths;
|
||||
for(const auto& entry : fs::directory_iterator(dir)){
|
||||
error_code error_code{};
|
||||
for(const auto& entry : fs::directory_iterator(dir, error_code)){
|
||||
if(!entry.path().has_extension()) continue;
|
||||
if(entry.path().extension().string() == ".so")
|
||||
paths.push_back(entry.path());
|
||||
}
|
||||
if(error_code) {
|
||||
log::log(log::err, "Failed to scan the target directory (" + dir.string() + "): " + error_code.message());
|
||||
return;
|
||||
}
|
||||
std::sort(paths.begin(), paths.end(), [](const fs::path& a, const fs::path& b){ return a.filename().string() < b.filename().string(); });
|
||||
|
||||
int index = 0;
|
||||
@@ -111,5 +119,11 @@ void manager::loadProviders(const std::string& path) {
|
||||
}
|
||||
|
||||
void manager::register_provider(const std::shared_ptr<music::manager::PlayerProvider> &provider) {
|
||||
std::lock_guard l(staticLock);
|
||||
types.push_back(provider);
|
||||
}
|
||||
|
||||
void manager::finalizeProviders() {
|
||||
std::lock_guard l(staticLock);
|
||||
types.clear();
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.6)
|
||||
project(TeamSpeak)
|
||||
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -static-libgcc -static-libstdc++ -pthread ${MEMORY_DEBUG_FLAGS}")
|
||||
|
||||
include_directories(../shared/src)
|
||||
add_definitions(-DLTM_DESC)
|
||||
|
||||
set(SOURCE_FILES
|
||||
main.cpp
|
||||
src/protocol/Connection.cpp
|
||||
src/protocol/ConnectionHandschake.cpp
|
||||
src/protocol/ConnectionPacketHandler.cpp
|
||||
|
||||
src/protocol/socket/FilteredUDPSocket.cpp
|
||||
src/protocol/socket/RawUDPSocket.cpp
|
||||
|
||||
src/Identity.cpp
|
||||
src/MultithreadedIdentity.cpp
|
||||
src/protocol/HandshakeNew.cpp
|
||||
|
||||
../shared/src/License.cpp
|
||||
)
|
||||
|
||||
find_package(Protobuf REQUIRED)
|
||||
include_directories(${Protobuf_INCLUDE_DIRS})
|
||||
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS proto/LicenseKey.proto)
|
||||
|
||||
add_executable(TeamSpeakClient ${SOURCE_FILES} ${PROTO_SRCS})
|
||||
target_link_libraries(TeamSpeakClient
|
||||
${LIBRARY_PATH_THREAD_POOL} #Static
|
||||
TeaSpeak #Static
|
||||
TeaLicenseHelper #Static
|
||||
TeaMusic #Static
|
||||
${LIBRARY_PATH_TERMINAL} #Static
|
||||
${LIBRARY_PATH_VARIBALES}
|
||||
${LIBRARY_PATH_YAML}
|
||||
pthread
|
||||
stdc++fs
|
||||
${LIBEVENT_PATH}/libevent.a
|
||||
${LIBEVENT_PATH}/libevent_pthreads.a
|
||||
opus.a
|
||||
${LIBRARY_PATH_JSON}
|
||||
${LIBRARY_PATH_PROTOBUF}
|
||||
|
||||
DataPipes
|
||||
#${LIBWEBRTC_LIBRARIES} #ATTENTIAN! WebRTC does not work with crypto! (Already contains a crypto version)
|
||||
${LIBRARY_TOM_CRYPT}
|
||||
${LIBRARY_TOM_MATH}
|
||||
|
||||
#We're forsed to use boringssl caused by the fact that boringssl is already within webrtc!
|
||||
|
||||
#Require a so
|
||||
sqlite3
|
||||
${LIBRARY_PATH_ED255}
|
||||
|
||||
${LIBRARY_PATH_BREAKPAD}
|
||||
${LIBRARY_PATH_JDBC}
|
||||
${LIBRARY_PATH_PROTOBUF}
|
||||
|
||||
${LIBRARY_PATH_BORINGSSL_SSL}
|
||||
${LIBRARY_PATH_BORINGSSL_CRYPTO}
|
||||
)
|
||||
|
||||
#strip -s -p -v TeamSpeakHash
|
||||
add_executable(TeamSpeakHash src/Identity.cpp src/MultithreadedIdentity.cpp identityHash.cpp)
|
||||
#set_target_properties(TeamSpeakHash PROPERTIES CMAKE_CXX_FLAGS "-o1")
|
||||
|
||||
target_link_libraries(TeamSpeakHash
|
||||
TeaSpeak
|
||||
ThreadPoolStatic
|
||||
pthread
|
||||
${TOM_LIBRARIES}
|
||||
${LIBRARY_TOM_CRYPT}
|
||||
${LIBRARY_TOM_MATH}
|
||||
${LIBRARY_PATH_BORINGSSL_SSL}
|
||||
${LIBRARY_PATH_BORINGSSL_CRYPTO}
|
||||
)
|
||||
@@ -1,29 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/preprocessor/cat.hpp>
|
||||
#include <boost/preprocessor/seq/for_each_i.hpp>
|
||||
#include <boost/preprocessor/seq/enum.hpp>
|
||||
|
||||
#define CRYPT_MACRO(r, d, i, elem) ( elem ^ ( d - i ) )
|
||||
|
||||
#define DEFINE_HIDDEN_STRING(NAME, SEED, SEQ)\
|
||||
static const char* BOOST_PP_CAT(Get, NAME)()\
|
||||
{\
|
||||
static char data[] = {\
|
||||
BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_FOR_EACH_I(CRYPT_MACRO, SEED, SEQ)),\
|
||||
'\0'\
|
||||
};\
|
||||
\
|
||||
static bool isEncrypted = true;\
|
||||
if ( isEncrypted )\
|
||||
{\
|
||||
for (unsigned i = 0; i < ( sizeof(data) / sizeof(data[0]) ) - 1; ++i)\
|
||||
{\
|
||||
data[i] = CRYPT_MACRO(_, SEED, i, data[i]);\
|
||||
}\
|
||||
\
|
||||
isEncrypted = false;\
|
||||
}\
|
||||
\
|
||||
return data;\
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
[OUT] clientinitiv alpha=kXZSTZ7qaOhblA== omega=MEwDAgcAAgEgAiA5+ycSoMRRdugndbohYtH7PqS6Q6h3TV1VnGRKoZNMsQIhANn2rszHVipB13LBcY2Zf3APg7HX7ix3WWiaT5hjQrUv ot=1 ip=84.200.62.248
|
||||
[ IN] initivexpand2 l=AQCBIiOUkbXLeQ\/6UhNB+8iJnwLYGZJglZoDFSTt+7awWgAJFtlwLxmo8AAAACVUZWFtU3BlYWsgU3lzdGVtcyBHbWJIAADtwskuiFg0FkafvNvS1uwz67b2lEhMnOkAygPvo0waGAAJQrh2HBxqvQAAACRUZWFtU3BlYWsgc3lzdGVtcyBHbWJIAADd85KDiA8UeqB5G9MBtNZ879CYBnfbjc\/OVj0j0xMcqQIJQ7qACi5lgAYAAAAAVGVhbVNwZWFrIFN5c3RlbXMgR21iSAAAitZK9P6JEXiiy+uERaapfw\/I5na7S5M+Se5IoOb\/24kgCV6OnAlfN1w= beta=5cB\/vUHLQ7GbVPNRStMLnZlyo572IFPzdgTTgxJ3zF4JBehh7nLudpdzk1hdbcglx4n3JmqX omega=MEwDAgcAAgEgAiEA83r2Er9vHr5sSEcm9g\/\/rdLjOdTMtwX\/Lkbi02+24FkCIB2P804l26yketcG8WxZQJ1z4ukU0e3SqEzm4pKHzMRY ot=1 proof=MEUCIAgYSvrPo0OKKD8aRmjeKVS1EVF6L\/DXLBT+XFkZlMVPAiEAnSMVOp8DOEMzcE42hna72+aWr+Z4+a76x23hmDJa4Uk= tvd=AQAAAABaQjKEblQQgGczKfB+CSS7IY5qli8LRS9jDltkab8leo6ltwQH82VUU\/WwnpCbazVbnh4iNbcobdxv\/MTX5Y3+6xpqDgEAEa1\/SLpE\/CIg2X7GX9mtDCCvP1KdQGaT\/RKoofPSsTkACRbZcAr4DPAAAAASVGVhbVNwZWFrIFN5c3RlbXMgR21iSAAAPgFaQLsu0h3FTQax+i0qXFXX\/Bn7VcGJH8SE5AOAiuQECVvUYwlenQk= time=1514211836
|
||||
[OUT] clientek ek=n4+LesWAJf+8ha6LlF5kWilUbj59qX+zV31asaniK8U= proof=MEQCIC+NxeqTNUQWqHz914BHkFfC3LTF8BkYAYktMB62sET7AiBPmnfN6yoB381g\/GAnZVwMkeCPIjKi9vHUk15yOdT5FQ==
|
||||
[OUT] clientinit client_nickname=P3nisBaum client_version=3.1.7\s[Build:\s1513163251] client_platform=Windows client_input_hardware=1 client_output_hardware=1 client_default_channel client_default_channel_password client_server_password client_meta_data client_version_sign=tdNngCAZ1ImAf7BxJzO4RXv5nBRsUERsrSOnMKVUFNQg6BS4Bzag0RFgLVzs2DRj19AC8+q5cXgH+5Ms50mTCA== client_key_offset=15848377 client_nickname_phonetic client_default_token client_badges=Overwolf=0 hwid=3036580947951600a7de06cd796bb0f1,7209e96ac1f60ff7772b7301919e0878
|
||||
[OUT] clientinit client_nickname=P3nisBaum client_version=3.1.7\s[Build:\s1513163251] client_platform=Windows client_input_hardware=1 client_output_hardware=1 client_default_channel client_default_channel_password client_server_password client_meta_data client_version_sign=tdNngCAZ1ImAf7BxJzO4RXv5nBRsUERsrSOnMKVUFNQg6BS4Bzag0RFgLVzs2DRj19AC8+q5cXgH+5Ms50mTCA== client_key_offset=15848377 client_nickname_phonetic client_default_token client_badges=Overwolf=0 hwid=3036580947951600a7de06cd796bb0f1,7209e96ac1f60ff7772b7301919e0878
|
||||
[ IN] error id=521 msg=too\smany\sclones\salready\sconnected
|
||||
|
||||
|
||||
|
||||
-> ot & ip needs to be set
|
||||
-> ack needs to be unencripted as look clientinitiv not done
|
||||
|
||||
|
||||
initivexpand2 l=AQCBIiOUkbXLeQ\/6UhNB+8iJnwLYGZJglZoDFSTt+7awWgAJFtlwLxmo8AAAACVUZWFtU3BlYWsgU3lzdGVtcyBHbWJIAADtwskuiFg0FkafvNvS1uwz67b2lEhMnOkAygPvo0waGAAJQrh2HBxqvQAAACRUZWFtU3BlYWsgc3lzdGVtcyBHbWJIAADd85KDiA8UeqB5G9MBtNZ879CYBnfbjc\/OVj0j0xMcqQIJQ7qACi5lgAYAAAAAVGVhbVNwZWFrIFN5c3RlbXMgR21iSAAA5Szd\/YkuL5NUF7SeHfvSKi5byOOMvYME0\/WRwlTHK8ogCV6hYglfSiI= beta=W\/D9NTwuZi6L1sbFpA+KiDBMSgl8y6Jy6vFVSGszJy1BPy8zEhNDQ62msazyAgk0CdbY0B+E omega=MEwDAgcAAgEgAiEA83r2Er9vHr5sSEcm9g\/\/rdLjOdTMtwX\/Lkbi02+24FkCIB2P804l26yketcG8WxZQJ1z4ukU0e3SqEzm4pKHzMRY ot=1 proof=MEUCIA3dgrIlXFIjkLrxJ1Yni7OqQYwPing03Xz\/zkQFrmdHAiEA8a0delCktJ21TIrj91QZbA76vtZUL38XVm+P2bR9M7c= tvd=AQAAAABaQjKEblQQgGczKfB+CSS7IY5qli8LRS9jDltkab8leo6ltwQH82VUU\/WwnpCbazVbnh4iNbcobdxv\/MTX5Y3+6xpqDgEAEa1\/SLpE\/CIg2X7GX9mtDCCvP1KdQGaT\/RKoofPSsTkACRbZcAr4DPAAAAASVGVhbVNwZWFrIFN5c3RlbXMgR21iSAAAPgFaQLsu0h3FTQax+i0qXFXX\/Bn7VcGJH8SE5AOAiuQECVvUYwlenQk= time=1514216642
|
||||
|
||||
|
||||
Nr 1
|
||||
[OUT] clientinitiv alpha=DBiidVqi8kTYAQ== omega=MEwDAgcAAgEgAiA5+ycSoMRRdugndbohYtH7PqS6Q6h3TV1VnGRKoZNMsQIhANn2rszHVipB13LBcY2Zf3APg7HX7ix3WWiaT5hjQrUv ot=1 ip=84.200.62.248
|
||||
[ IN] initivexpand2 l=AQCBIiOUkbXLeQ\/6UhNB+8iJnwLYGZJglZoDFSTt+7awWgAJFtlwLxmo8AAAACVUZWFtU3BlYWsgU3lzdGVtcyBHbWJIAADtwskuiFg0FkafvNvS1uwz67b2lEhMnOkAygPvo0waGAAJQrh2HBxqvQAAACRUZWFtU3BlYWsgc3lzdGVtcyBHbWJIAADd85KDiA8UeqB5G9MBtNZ879CYBnfbjc\/OVj0j0xMcqQIJQ7qACi5lgAYAAAAAVGVhbVNwZWFrIFN5c3RlbXMgR21iSAAALYVev\/rqwinxd2zrGZla8+69t03410Iyo9N6lV7bajMgCV6ioQlfS2E= beta=\/Eg8SA2j45sSVi28kVwBVpxT98G1w0tlenDVYuKy\/7Cn1ZZiNiwu8Z9XhGBxwrzPXrMHPEMJ omega=MEwDAgcAAgEgAiEA83r2Er9vHr5sSEcm9g\/\/rdLjOdTMtwX\/Lkbi02+24FkCIB2P804l26yketcG8WxZQJ1z4ukU0e3SqEzm4pKHzMRY ot=1 proof=MEUCIFFa+q1zdgA0OtXgdgn9gxSOMp7GBvc3vPW0YDTU2+e6AiEA8sjW3XDikxQADPoSaSw7lwL6gfXr6gwO0Hvro3RZBHU= tvd=AQAAAABaQjKEblQQgGczKfB+CSS7IY5qli8LRS9jDltkab8leo6ltwQH82VUU\/WwnpCbazVbnh4iNbcobdxv\/MTX5Y3+6xpqDgEAEa1\/SLpE\/CIg2X7GX9mtDCCvP1KdQGaT\/RKoofPSsTkACRbZcAr4DPAAAAASVGVhbVNwZWFrIFN5c3RlbXMgR21iSAAAPgFaQLsu0h3FTQax+i0qXFXX\/Bn7VcGJH8SE5AOAiuQECVvUYwlenQk= time=1514216961
|
||||
[OUT] clientek ek=19doGY4WwwAeW\/zHnmFJTpnE8h9MUJZSYFNpz4aUabs= proof=MEUCIGTwtZ1E+HjDPLqlyTuaHtvWmjYW\/\/tnQgugaC5u1swtAiEA9DN+kuwxuXG80FDzKnICGQYQGZZBySQv8Nhflg\/1FwQ=
|
||||
|
||||
|
||||
[OUT] clientinitiv alpha=3Pcn19zISkIB5g== omega=MEwDAgcAAgEgAiA5+ycSoMRRdugndbohYtH7PqS6Q6h3TV1VnGRKoZNMsQIhANn2rszHVipB13LBcY2Zf3APg7HX7ix3WWiaT5hjQrUv ot=1 ip=84.200.62.248
|
||||
[ IN] initivexpand2 l=AQCBIiOUkbXLeQ\/6UhNB+8iJnwLYGZJglZoDFSTt+7awWgAJFtlwLxmo8AAAACVUZWFtU3BlYWsgU3lzdGVtcyBHbWJIAADtwskuiFg0FkafvNvS1uwz67b2lEhMnOkAygPvo0waGAAJQrh2HBxqvQAAACRUZWFtU3BlYWsgc3lzdGVtcyBHbWJIAADd85KDiA8UeqB5G9MBtNZ879CYBnfbjc\/OVj0j0xMcqQIJQ7qACi5lgAYAAAAAVGVhbVNwZWFrIFN5c3RlbXMgR21iSAAASpEEQfPJOP8aeecPLtzcu8iYmtqBJn8MWq+8lSQljYcgCV6iyAlfS4g= beta=JLFcgyWwlYNzy5lsTGJKGz6rvmShHrb6YEZfq4TXwTLSiWDGqckf6ywiMOP2MGAc1y1yQsEi omega=MEwDAgcAAgEgAiEA83r2Er9vHr5sSEcm9g\/\/rdLjOdTMtwX\/Lkbi02+24FkCIB2P804l26yketcG8WxZQJ1z4ukU0e3SqEzm4pKHzMRY ot=1 proof=MEYCIQCpKZJCmQCHyC2PLIleZVAl1fSTdnzqcNxhiXxVAHtUHgIhAPvPfziHtiqn36nB7KSVuKV+gMA29OL87bI1HP6CJNw2 tvd=AQAAAABaQjKEblQQgGczKfB+CSS7IY5qli8LRS9jDltkab8leo6ltwQH82VUU\/WwnpCbazVbnh4iNbcobdxv\/MTX5Y3+6xpqDgEAEa1\/SLpE\/CIg2X7GX9mtDCCvP1KdQGaT\/RKoofPSsTkACRbZcAr4DPAAAAASVGVhbVNwZWFrIFN5c3RlbXMgR21iSAAAPgFaQLsu0h3FTQax+i0qXFXX\/Bn7VcGJH8SE5AOAiuQECVvUYwlenQk= time=1514217000
|
||||
[OUT] clientek ek=yGZb1Qp0Y\/V7T+rMwcqtzUeOTIGgE6+7gcKxAw2OMPU= proof=MEQCIHdgsGlufstf\/ab1TvvM740Azs56jyGFmo93ijr53k1FAiBHNhWkV9xLUG19jpZGGe86SgCDli7GdSrv61ZGr3jD9g==
|
||||
|
||||
bM5HB+Ox/Wht38dd6/VeB7L76ebcpP9YgKzWAk4lmQ8=
|
||||
@@ -1,63 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <src/Identity.h>
|
||||
#include <sstream>
|
||||
#include "HideString.h"
|
||||
DEFINE_HIDDEN_STRING(HeaderMessage, 0x44, ('#')('#')('#')('#')('#')('#')('#')('#')('#')('#')('#')('#')('#')('#')('#')('#')('#')(' ')('T')('S')('3')(' ')('H')('a')('s')('h')(' ')('b')('y')(' ')('W')('o')('l')('v')('e')('r')('i')('n')('D')('E')('V')(' ')('#')('#')('#')('#')('#')('#')('#')('#')('#')('#')('#')('#')('#')('#')('#')('#')('#'));
|
||||
|
||||
using namespace ts;
|
||||
using namespace std;
|
||||
|
||||
inline string crypt(string in){
|
||||
stringstream result;
|
||||
result << "DEFINE_HIDDEN_STRING(EncryptionKey, 0xA5, ";
|
||||
|
||||
for(char c : in)
|
||||
result << "('" << c << "')";
|
||||
|
||||
result << ");";
|
||||
return result.str();
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
cout << GetHeaderMessage() << endl;
|
||||
if(argc < 3) {
|
||||
cerr << "Invalid arguments. ./TSHash <threads> <MaxLevel> [<BlockSize> = 1.000.000]" << endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
init_LTM();
|
||||
if(register_prng(&sprng_desc) == -1) {
|
||||
cerr << "could not setup prng" << endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (register_cipher(&rijndael_desc) == -1) {
|
||||
cerr << "could not setup rijndael" << endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
int threads = atoi(argv[1]);
|
||||
int level = atoi(argv[2]);
|
||||
int blockSize = argc > 3 ? atoi(argv[3]) : 1 * 1000 * 1000;
|
||||
|
||||
cout << "# Creating new identity (Threads: " << threads << " Level: " << level << " Size: " << blockSize << ")" << endl;
|
||||
auto identity = Identity::createNew();
|
||||
cout << "IDENTITY: " << identity->exportIdentity() << endl;
|
||||
cout << "# Start hashing" << endl;
|
||||
if(!identity->improveSecurityLevelMultithreaded(level, threads, blockSize, 0, true)) {
|
||||
cout << "ERROR: Could not improve identity!" << endl;
|
||||
return 1;
|
||||
}
|
||||
cout << "INFO: Found identity!" << endl;
|
||||
cout << "IDENTITY: " << identity->exportIdentity() << endl;
|
||||
cout << "LEVEL: " << identity->getSecurityLevel() << endl;
|
||||
return 0;
|
||||
}
|
||||
/*
|
||||
#IDENTITY: 5513931022VPXfjXSN1qRa2IZvTpS3xs+L7YvcBI2lTfXgOWAkaAQwQVgR8BXtWJ1MKUSh/HGNGNgdVGWYHcCg1BgQie3pValZZB39hVE4HBStcAA5SUz9Tf1JbXVABBVUJUwFmFkJZLlZUfCtaZXFBaUVBZ1poOXlQd09KSzN6ejYxMjVnaGN3bmtSKzRPOXdvZWJObVpaT3gyOTA3ND0=
|
||||
#LEVEL: 36
|
||||
|
||||
#IDENTITY: 0VdYzXyIWNlPR991zQZLQoGLDYA4JvCX9GQ3MAYzpBRABWAHZTAmJvNQUAMxEEKGQDJVlnanF1YDdQQn0GewlXVWoAQmVMLX9FVyZaMAI7KlAIeXpPKGMCJ3BTVAJHLGlUHARbBlpqSUNJUURISXY1UXJCYmlBMHlZOVV3dFdRRXovM0lkU1hzNFJvWTkwT1JienA5bVRRPT0=
|
||||
Current level = 41 at 1233527956186
|
||||
Current offset index: 3750576000000 block size: 1000000
|
||||
|
||||
*/
|
||||
-1176
File diff suppressed because it is too large
Load Diff
@@ -1,17 +0,0 @@
|
||||
syntax = "proto2";
|
||||
|
||||
package ts.proto.license.teamspeak;
|
||||
|
||||
message LicenseData { //_ZTIN3com10teamspeak310accounting8protobuf19Signed_License_InfoE => Signed_License_Info
|
||||
required bytes chain = 1;
|
||||
required bytes root = 2;
|
||||
required int32 slots = 3;
|
||||
required int32 servers = 4;
|
||||
required string type = 5;
|
||||
}
|
||||
|
||||
message License {
|
||||
required LicenseData license = 1;
|
||||
required bytes sign_chain = 2;
|
||||
required bytes license_sign = 3;
|
||||
}
|
||||
@@ -1,218 +0,0 @@
|
||||
//
|
||||
// Created by wolverindev on 07.10.17.
|
||||
//
|
||||
|
||||
#include <iostream>
|
||||
#include <openssl/sha.h>
|
||||
#include "misc/base64.h"
|
||||
#include "Identity.h"
|
||||
|
||||
#define ECC_TYPE_INDEX 5
|
||||
|
||||
using namespace std;
|
||||
|
||||
static const char *TSKEY =
|
||||
"b9dfaa7bee6ac57ac7b65f1094a1c155"
|
||||
"e747327bc2fe5d51c512023fe54a2802"
|
||||
"01004e90ad1daaae1075d53b7d571c30"
|
||||
"e063b5a62a4a017bb394833aa0983e6e";
|
||||
|
||||
static int obfuscateInplace(char *data, uint32_t length) {
|
||||
int dataSize = min((uint32_t) 100, length);
|
||||
for (int i = 0; i < dataSize; i++) {
|
||||
data[i] ^= TSKEY[i];
|
||||
}
|
||||
|
||||
char hash[20];
|
||||
hash_state ctx;
|
||||
if (sha1_init(&ctx) != CRYPT_OK)
|
||||
{ return -1; }
|
||||
if (sha1_process(&ctx, (uint8_t*)data + 20, strlen(data + 20)) != CRYPT_OK)
|
||||
{ return -1; }
|
||||
if (sha1_done(&ctx, (uint8_t*)hash) != CRYPT_OK)
|
||||
{ return -1; }
|
||||
|
||||
for (int i = 0; i < 20; i++) {
|
||||
data[i] ^= hash[i];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int deObfuscateInplace(char *data, uint32_t length) {
|
||||
char hash[20];
|
||||
hash_state ctx;
|
||||
if (sha1_init(&ctx) != CRYPT_OK)
|
||||
{ return -1; }
|
||||
if (sha1_process(&ctx, (uint8_t*)data + 20, strlen(data + 20)) != CRYPT_OK)
|
||||
{ return -1; }
|
||||
if (sha1_done(&ctx, (uint8_t*)hash) != CRYPT_OK)
|
||||
{ return -1; }
|
||||
|
||||
for (int i = 0; i < 20; i++) {
|
||||
data[i] ^= hash[i];
|
||||
}
|
||||
|
||||
int dataSize = min((uint32_t) 100, length);
|
||||
for (int i = 0; i < dataSize; i++) {
|
||||
data[i] ^= TSKEY[i];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
namespace ts {
|
||||
Identity* Identity::createNew() {
|
||||
auto result = new Identity();
|
||||
|
||||
prng_state rndState{};
|
||||
memset(&rndState, 0, sizeof(prng_state));
|
||||
int err;
|
||||
|
||||
result->keyPair = new ecc_key;
|
||||
|
||||
cout << " -> " << find_prng("sprng") << endl;
|
||||
if((err = ecc_make_key_ex(&rndState, find_prng("sprng"), result->keyPair, <c_ecc_sets[ECC_TYPE_INDEX])) != CRYPT_OK) {
|
||||
cerr << "Cant create a new identity (Keygen)" << endl;
|
||||
cerr << "Message: " << error_to_string(err) << endl;
|
||||
delete result;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Identity::Identity(std::string asnStruct, int64_t keyOffset, int64_t lastCheckedOffset) {
|
||||
this->keyOffset = keyOffset;
|
||||
this->lastCheckedOffset = lastCheckedOffset;
|
||||
importKey(asnStruct);
|
||||
}
|
||||
|
||||
Identity::Identity(std::string data) : Identity() {
|
||||
int vindex = data.find('V');
|
||||
assert(vindex > 0);
|
||||
|
||||
auto slevel = data.substr(0, vindex);
|
||||
assert(slevel.find_first_not_of("0123456789") == std::string::npos);
|
||||
this->keyOffset = stol(slevel);
|
||||
|
||||
data = data.substr(vindex + 1);
|
||||
data = base64::decode(data);
|
||||
if(deObfuscateInplace((char *) data.data(), data.length()) < 0) {
|
||||
cerr << "Cand decript identitry data" << endl;
|
||||
return;
|
||||
}
|
||||
importKey(base64::decode(data));
|
||||
}
|
||||
|
||||
Identity::Identity() {
|
||||
this->keyOffset = 0;
|
||||
this->lastCheckedOffset = 0;
|
||||
this->keyPair = nullptr;
|
||||
}
|
||||
|
||||
Identity::~Identity() {
|
||||
delete this->keyPair;
|
||||
this->keyPair = nullptr;
|
||||
}
|
||||
|
||||
void Identity::importKey(std::string asnStruct) {
|
||||
this->keyPair = new ecc_key;
|
||||
int err;
|
||||
if((err = ecc_import_ex((const unsigned char *) asnStruct.data(), asnStruct.length(), this->keyPair, <c_ecc_sets[ECC_TYPE_INDEX])) != CRYPT_OK){
|
||||
delete this->keyPair;
|
||||
this->keyPair = nullptr;
|
||||
|
||||
cerr << "Cant import identity from asn structure" << endl;
|
||||
cerr << "Message: " << error_to_string(err) << endl;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::string Identity::exportIdentity() {
|
||||
string data = privateKey();
|
||||
obfuscateInplace((char *) data.data(), data.length());
|
||||
return to_string(this->lastValidKeyOffset()) + "V" + base64_encode(data);
|
||||
}
|
||||
|
||||
std::string Identity::publicKey() {
|
||||
assert(this->keyPair);
|
||||
|
||||
size_t bufferLength = 1028;
|
||||
char buffer[bufferLength];
|
||||
ecc_export((unsigned char *) buffer, &bufferLength, PK_PUBLIC, this->keyPair);
|
||||
|
||||
return base64_encode(string(buffer, bufferLength));
|
||||
}
|
||||
|
||||
std::string Identity::privateKey() {
|
||||
assert(this->keyPair);
|
||||
|
||||
size_t bufferLength = 1028;
|
||||
char buffer[bufferLength];
|
||||
ecc_export((unsigned char *) buffer, &bufferLength, PK_PRIVATE, this->keyPair);
|
||||
|
||||
return base64_encode(string(buffer, bufferLength));
|
||||
}
|
||||
|
||||
ecc_key& Identity::getPrivateKey() {
|
||||
return *keyPair;
|
||||
}
|
||||
|
||||
#define MaxUlongString 20
|
||||
|
||||
bool Identity::improveSecurityLevel(int target) {
|
||||
auto publicKey = this->publicKey();
|
||||
char hashBuffer[publicKey.length() + MaxUlongString];
|
||||
memcpy(hashBuffer, publicKey.data(), publicKey.length());
|
||||
|
||||
this->lastCheckedOffset = max(this->lastCheckedOffset, this->keyOffset);
|
||||
int best = getSecurityLevel(hashBuffer, publicKey.length(), this->lastCheckedOffset);
|
||||
while(true){
|
||||
if(best >= target) return true;
|
||||
|
||||
int currentLevel = getSecurityLevel(hashBuffer, publicKey.length(), this->lastCheckedOffset);
|
||||
if(currentLevel >= best){
|
||||
this->keyOffset = this->lastCheckedOffset;
|
||||
best = currentLevel;
|
||||
}
|
||||
this->lastCheckedOffset++;
|
||||
}
|
||||
}
|
||||
|
||||
int Identity::getSecurityLevel() {
|
||||
auto length = publicKey().length();
|
||||
char hashBuffer[length + MaxUlongString];
|
||||
|
||||
auto publicKey = this->publicKey();
|
||||
memcpy(hashBuffer, publicKey.data(), publicKey.length());
|
||||
|
||||
return getSecurityLevel(hashBuffer, publicKey.length(), this->keyOffset);
|
||||
}
|
||||
|
||||
int Identity::getSecurityLevel(char *hashBuffer, size_t keyLength, int64_t offset) {
|
||||
char numBuffer[MaxUlongString];
|
||||
int numLen = 0;
|
||||
do {
|
||||
numBuffer[numLen] = '0' + (offset % 10);
|
||||
offset /= 10;
|
||||
numLen++;
|
||||
} while(offset > 0);
|
||||
for(int i = 0; i < numLen; i++)
|
||||
hashBuffer[keyLength + i] = numBuffer[numLen - (i + 1)];
|
||||
|
||||
char shaBuffer[SHA_DIGEST_LENGTH];
|
||||
SHA1((const unsigned char *) hashBuffer, keyLength + numLen, (unsigned char *) shaBuffer);
|
||||
|
||||
//Leading zero bits
|
||||
register int zeroBits = 0;
|
||||
register int i;
|
||||
for(i = 0; i < SHA_DIGEST_LENGTH; i++)
|
||||
if(shaBuffer[i] == 0) zeroBits += 8;
|
||||
else break;
|
||||
if(i < SHA_DIGEST_LENGTH)
|
||||
for(int bit = 0; bit < 8; bit++)
|
||||
if((shaBuffer[i] & (1 << bit)) == 0) zeroBits++;
|
||||
else break;
|
||||
return zeroBits;
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <tomcrypt.h>
|
||||
#include <string>
|
||||
|
||||
namespace ts {
|
||||
class Identity {
|
||||
public:
|
||||
static Identity* createNew();
|
||||
|
||||
explicit Identity(std::string data);
|
||||
Identity(std::string asnStruct,int64_t keyOffset,int64_t lastCheckedOffset);
|
||||
~Identity();
|
||||
|
||||
bool valid(){ return keyPair != nullptr; }
|
||||
|
||||
std::string publicKey();
|
||||
std::string privateKey();
|
||||
std::string exportIdentity();
|
||||
|
||||
ecc_key* getKeyPair(){
|
||||
return keyPair;
|
||||
}
|
||||
|
||||
ecc_key& getPrivateKey();
|
||||
|
||||
bool improveSecurityLevel(int target);
|
||||
bool improveSecurityLevelMultithreaded(int target, int nthread = 4, size_t nblockSize = 1000, size_t baseOffset = 0, bool verbose = false);
|
||||
|
||||
int getSecurityLevel();
|
||||
|
||||
int64_t lastValidKeyOffset(){ return keyOffset; }
|
||||
int64_t lastTestedKeyOffset(){ return lastCheckedOffset; }
|
||||
private:
|
||||
Identity();
|
||||
|
||||
int getSecurityLevel(char* hasBuffer, size_t keyLength, int64_t offset);
|
||||
void importKey(std::string asn1);
|
||||
|
||||
ecc_key* keyPair = nullptr;
|
||||
size_t keyOffset;
|
||||
size_t lastCheckedOffset;
|
||||
};
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
//
|
||||
// Created by wolverindev on 14.10.17.
|
||||
//
|
||||
|
||||
#include <iostream>
|
||||
#include <openssl/sha.h>
|
||||
#include <ThreadPool/Thread.h>
|
||||
#include <vector>
|
||||
#include <ThreadPool/Mutex.h>
|
||||
#include <zconf.h>
|
||||
#include "misc/base64.h"
|
||||
#include "Identity.h"
|
||||
|
||||
#define ECC_TYPE_INDEX 5
|
||||
|
||||
#define USE_OPENSSL_SHA1
|
||||
using namespace std::chrono;
|
||||
using namespace std;
|
||||
namespace ts {
|
||||
|
||||
inline int calculateSecutityLevel(uint8_t* hashBuffer, uint8_t* bufferToHash, int length){
|
||||
register int zeroBits = 0;
|
||||
register int bit;
|
||||
register int i;
|
||||
|
||||
SHA1(bufferToHash, length, hashBuffer);
|
||||
|
||||
//Leading zero bits
|
||||
for(i = 0; i < SHA_DIGEST_LENGTH; i++)
|
||||
if(hashBuffer[i] == 0) zeroBits += 8;
|
||||
else break;
|
||||
if(i < SHA_DIGEST_LENGTH)
|
||||
for(bit = 0; bit < 8; bit++)
|
||||
if((hashBuffer[i] & (1 << bit)) == 0) zeroBits++;
|
||||
else break;
|
||||
|
||||
return zeroBits;
|
||||
}
|
||||
|
||||
inline void increaseNumBuffer(char* buffer, int numOffset, int* length){
|
||||
int index = *length - 1;
|
||||
|
||||
while(true){
|
||||
if(buffer[index] == '9'){
|
||||
if(index - 1 < numOffset) {
|
||||
buffer[index] = '1';
|
||||
buffer[*length] = '0';
|
||||
*length += 1;
|
||||
return;
|
||||
}
|
||||
buffer[index--] = '0';
|
||||
} else {
|
||||
buffer[index] += 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define MaxUlongString 20
|
||||
bool Identity::improveSecurityLevelMultithreaded(int target, int nthread, size_t nblockSize, size_t baseOffset, bool verbose) {
|
||||
if(this->getSecurityLevel() >= target)
|
||||
return false;
|
||||
|
||||
vector<threads::Thread*> threads;
|
||||
|
||||
auto publicKey = this->publicKey();
|
||||
|
||||
size_t currentBlockIndex = max(max(this->lastCheckedOffset, this->keyOffset), baseOffset);
|
||||
threads::Mutex blockIndexLock;
|
||||
|
||||
auto activeDigging = new bool(true);
|
||||
threads::Thread* thread;
|
||||
for(int threadId = 0; threadId < nthread; threadId++){
|
||||
thread = new threads::Thread([&](){
|
||||
size_t offset = 0;
|
||||
size_t endOffset = 0;
|
||||
size_t bestOffset = 0;
|
||||
|
||||
char shaHashBuffer[SHA_DIGEST_LENGTH];
|
||||
char hashBuffer[publicKey.length() + MaxUlongString];
|
||||
memcpy(hashBuffer, publicKey.data(), publicKey.length());
|
||||
|
||||
#ifndef SLOW
|
||||
//Setup some stuff
|
||||
size_t pubKeyLength = publicKey.length();
|
||||
|
||||
#endif
|
||||
while(*activeDigging){
|
||||
//Get next range
|
||||
blockIndexLock.lock();
|
||||
offset = currentBlockIndex;
|
||||
endOffset = currentBlockIndex + nblockSize;
|
||||
currentBlockIndex += nblockSize;
|
||||
blockIndexLock.unlock();
|
||||
|
||||
int bestLevel = this->getSecurityLevel(hashBuffer, publicKey.length(), this->keyOffset);
|
||||
#ifdef SLOW
|
||||
while(offset < endOffset){
|
||||
int currentLevel = getSecurityLevel(hashBuffer, publicKey.length(), offset);
|
||||
if(currentLevel >= best){
|
||||
bestOffset = offset;
|
||||
best = currentLevel;
|
||||
}
|
||||
offset++;
|
||||
}
|
||||
blockIndexLock.lock();
|
||||
if(best > this->getSecurityLevel()){
|
||||
if(verbose) cout << "Improved -> " << best << "/" << target << endl;
|
||||
this->lastCheckedOffset = bestOffset;
|
||||
this->keyOffset = bestOffset;
|
||||
cout << "Got level: " << best << endl;
|
||||
|
||||
if(best >= target){
|
||||
cout << "Done!" << endl;
|
||||
*activeDigging = false;
|
||||
}
|
||||
}
|
||||
blockIndexLock.unlock();
|
||||
if(verbose) cout << "round done: highest -> " << best << endl;
|
||||
#else
|
||||
|
||||
string strStartOffset = to_string(offset);
|
||||
ssize_t roundsLeft = nblockSize;
|
||||
|
||||
auto hashBufferLength = static_cast<int>(publicKey.length() + strStartOffset.length());
|
||||
memcpy(&hashBuffer[pubKeyLength], strStartOffset.c_str(), strStartOffset.length());
|
||||
|
||||
while(roundsLeft-- >= 0){
|
||||
auto level = calculateSecutityLevel(reinterpret_cast<uint8_t *>(shaHashBuffer), reinterpret_cast<uint8_t *>(hashBuffer), hashBufferLength);
|
||||
if(level > bestLevel){
|
||||
{
|
||||
threads::MutexLock l(blockIndexLock);
|
||||
|
||||
auto strOffset = string(&hashBuffer[pubKeyLength], hashBufferLength - pubKeyLength);
|
||||
bestOffset = stoull(strOffset);
|
||||
auto got = this->getSecurityLevel();
|
||||
if(got >= level) {
|
||||
cout << "Already got bedder level! (" << got << ")" << endl;
|
||||
bestLevel = got;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(verbose) cout << "Improved -> " << level << "/" << target << " [" << strOffset << "]" << endl;
|
||||
this->lastCheckedOffset = bestOffset;
|
||||
this->keyOffset = bestOffset;
|
||||
bestLevel = level;
|
||||
|
||||
if(bestLevel >= target){
|
||||
cout << "Done! (" << bestLevel << ")" << endl;
|
||||
*activeDigging = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
increaseNumBuffer(hashBuffer, pubKeyLength + 1, &hashBufferLength);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
});
|
||||
thread->name("IdentityHashDigger #" + to_string(threadId));
|
||||
threads.push_back(thread);
|
||||
}
|
||||
|
||||
if(verbose){
|
||||
thread = new threads::Thread([&](){
|
||||
auto lastIndex = currentBlockIndex;
|
||||
while(*activeDigging){
|
||||
for(int count = 0; count < 1000 && *activeDigging; count++)
|
||||
usleep(1000);
|
||||
|
||||
blockIndexLock.lock();
|
||||
cout << "Current level = " << this->getSecurityLevel() << " at " << this->keyOffset << endl;
|
||||
cout << "Current offset index: " << currentBlockIndex << " block size: " << nblockSize << endl;
|
||||
cout << "speed: " << (currentBlockIndex - lastIndex) / 1000 / 1000.f << " Mio. attemps/sec" << endl;
|
||||
lastIndex = currentBlockIndex;
|
||||
blockIndexLock.unlock();
|
||||
}
|
||||
});
|
||||
thread->name("Status printer");
|
||||
threads.push_back(thread);
|
||||
}
|
||||
|
||||
for(auto elm : threads){
|
||||
if(elm->state() == threads::ThreadState::RUNNING)
|
||||
elm->join();
|
||||
delete elm;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* TinySHA1 - a header only implementation of the SHA1 algorithm in C++. Based
|
||||
* on the implementation in boost::uuid::details.
|
||||
*
|
||||
* SHA1 Wikipedia Page: http://en.wikipedia.org/wiki/SHA-1
|
||||
*
|
||||
* Copyright (c) 2012-22 SAURAV MOHAPATRA <mohaps@gmail.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
#ifndef _TINY_SHA1_HPP_
|
||||
#define _TINY_SHA1_HPP_
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <stdint.h>
|
||||
namespace sha1
|
||||
{
|
||||
class SHA1
|
||||
{
|
||||
public:
|
||||
typedef uint32_t digest32_t[5];
|
||||
typedef uint8_t digest8_t[20];
|
||||
inline static uint32_t LeftRotate(uint32_t value, size_t count) {
|
||||
return (value << count) ^ (value >> (32-count));
|
||||
}
|
||||
SHA1(){ reset(); }
|
||||
virtual ~SHA1() {}
|
||||
SHA1(const SHA1& s) { *this = s; }
|
||||
const SHA1& operator = (const SHA1& s) {
|
||||
memcpy(m_digest, s.m_digest, 5 * sizeof(uint32_t));
|
||||
memcpy(m_block, s.m_block, 64);
|
||||
m_blockByteIndex = s.m_blockByteIndex;
|
||||
m_byteCount = s.m_byteCount;
|
||||
return *this;
|
||||
}
|
||||
SHA1& reset() {
|
||||
m_digest[0] = 0x67452301;
|
||||
m_digest[1] = 0xEFCDAB89;
|
||||
m_digest[2] = 0x98BADCFE;
|
||||
m_digest[3] = 0x10325476;
|
||||
m_digest[4] = 0xC3D2E1F0;
|
||||
m_blockByteIndex = 0;
|
||||
m_byteCount = 0;
|
||||
return *this;
|
||||
}
|
||||
SHA1& processByte(uint8_t octet) {
|
||||
this->m_block[this->m_blockByteIndex++] = octet;
|
||||
++this->m_byteCount;
|
||||
if(m_blockByteIndex == 64) {
|
||||
this->m_blockByteIndex = 0;
|
||||
processBlock();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
SHA1& processBlock(const void* const start, const void* const end) {
|
||||
const uint8_t* begin = static_cast<const uint8_t*>(start);
|
||||
const uint8_t* finish = static_cast<const uint8_t*>(end);
|
||||
while(begin != finish) {
|
||||
processByte(*begin);
|
||||
begin++;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
SHA1& processBytes(const void* const data, size_t len) {
|
||||
const uint8_t* block = static_cast<const uint8_t*>(data);
|
||||
processBlock(block, block + len);
|
||||
return *this;
|
||||
}
|
||||
const uint32_t* getDigest(digest32_t digest) {
|
||||
size_t bitCount = this->m_byteCount * 8;
|
||||
processByte(0x80);
|
||||
if (this->m_blockByteIndex > 56) {
|
||||
while (m_blockByteIndex != 0) {
|
||||
processByte(0);
|
||||
}
|
||||
while (m_blockByteIndex < 56) {
|
||||
processByte(0);
|
||||
}
|
||||
} else {
|
||||
while (m_blockByteIndex < 56) {
|
||||
processByte(0);
|
||||
}
|
||||
}
|
||||
processByte(0);
|
||||
processByte(0);
|
||||
processByte(0);
|
||||
processByte(0);
|
||||
processByte( static_cast<unsigned char>((bitCount>>24) & 0xFF));
|
||||
processByte( static_cast<unsigned char>((bitCount>>16) & 0xFF));
|
||||
processByte( static_cast<unsigned char>((bitCount>>8 ) & 0xFF));
|
||||
processByte( static_cast<unsigned char>((bitCount) & 0xFF));
|
||||
|
||||
memcpy(digest, m_digest, 5 * sizeof(uint32_t));
|
||||
return digest;
|
||||
}
|
||||
const uint8_t* getDigestBytes(digest8_t digest) {
|
||||
digest32_t d32;
|
||||
getDigest(d32);
|
||||
size_t di = 0;
|
||||
digest[di++] = ((d32[0] >> 24) & 0xFF);
|
||||
digest[di++] = ((d32[0] >> 16) & 0xFF);
|
||||
digest[di++] = ((d32[0] >> 8) & 0xFF);
|
||||
digest[di++] = ((d32[0]) & 0xFF);
|
||||
|
||||
digest[di++] = ((d32[1] >> 24) & 0xFF);
|
||||
digest[di++] = ((d32[1] >> 16) & 0xFF);
|
||||
digest[di++] = ((d32[1] >> 8) & 0xFF);
|
||||
digest[di++] = ((d32[1]) & 0xFF);
|
||||
|
||||
digest[di++] = ((d32[2] >> 24) & 0xFF);
|
||||
digest[di++] = ((d32[2] >> 16) & 0xFF);
|
||||
digest[di++] = ((d32[2] >> 8) & 0xFF);
|
||||
digest[di++] = ((d32[2]) & 0xFF);
|
||||
|
||||
digest[di++] = ((d32[3] >> 24) & 0xFF);
|
||||
digest[di++] = ((d32[3] >> 16) & 0xFF);
|
||||
digest[di++] = ((d32[3] >> 8) & 0xFF);
|
||||
digest[di++] = ((d32[3]) & 0xFF);
|
||||
|
||||
digest[di++] = ((d32[4] >> 24) & 0xFF);
|
||||
digest[di++] = ((d32[4] >> 16) & 0xFF);
|
||||
digest[di++] = ((d32[4] >> 8) & 0xFF);
|
||||
digest[di++] = ((d32[4]) & 0xFF);
|
||||
return digest;
|
||||
}
|
||||
|
||||
protected:
|
||||
void processBlock() {
|
||||
uint32_t w[80];
|
||||
for (size_t i = 0; i < 16; i++) {
|
||||
w[i] = (m_block[i*4 + 0] << 24);
|
||||
w[i] |= (m_block[i*4 + 1] << 16);
|
||||
w[i] |= (m_block[i*4 + 2] << 8);
|
||||
w[i] |= (m_block[i*4 + 3]);
|
||||
}
|
||||
for (size_t i = 16; i < 80; i++) {
|
||||
w[i] = LeftRotate((w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16]), 1);
|
||||
}
|
||||
|
||||
uint32_t a = m_digest[0];
|
||||
uint32_t b = m_digest[1];
|
||||
uint32_t c = m_digest[2];
|
||||
uint32_t d = m_digest[3];
|
||||
uint32_t e = m_digest[4];
|
||||
|
||||
for (std::size_t i=0; i<80; ++i) {
|
||||
uint32_t f = 0;
|
||||
uint32_t k = 0;
|
||||
|
||||
if (i<20) {
|
||||
f = (b & c) | (~b & d);
|
||||
k = 0x5A827999;
|
||||
} else if (i<40) {
|
||||
f = b ^ c ^ d;
|
||||
k = 0x6ED9EBA1;
|
||||
} else if (i<60) {
|
||||
f = (b & c) | (b & d) | (c & d);
|
||||
k = 0x8F1BBCDC;
|
||||
} else {
|
||||
f = b ^ c ^ d;
|
||||
k = 0xCA62C1D6;
|
||||
}
|
||||
uint32_t temp = LeftRotate(a, 5) + f + e + k + w[i];
|
||||
e = d;
|
||||
d = c;
|
||||
c = LeftRotate(b, 30);
|
||||
b = a;
|
||||
a = temp;
|
||||
}
|
||||
|
||||
m_digest[0] += a;
|
||||
m_digest[1] += b;
|
||||
m_digest[2] += c;
|
||||
m_digest[3] += d;
|
||||
m_digest[4] += e;
|
||||
}
|
||||
private:
|
||||
digest32_t m_digest;
|
||||
uint8_t m_block[64];
|
||||
size_t m_blockByteIndex;
|
||||
size_t m_byteCount;
|
||||
};
|
||||
}
|
||||
#endif
|
||||
@@ -1,454 +0,0 @@
|
||||
//
|
||||
// Created by wolverindev on 07.10.17.
|
||||
//
|
||||
|
||||
#include <log/LogUtils.h>
|
||||
#include "Connection.h"
|
||||
#include "misc/base64.h"
|
||||
#include <misc/endianness.h>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <poll.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <bitset>
|
||||
#include <protocol/Packet.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace ts;
|
||||
using namespace ts::connection;
|
||||
using namespace ts::protocol;
|
||||
|
||||
ServerConnection::ServerConnection() {
|
||||
cryptionHandler = new CryptionHandler();
|
||||
cryptionHandler->reset();
|
||||
|
||||
compressionHandler = new CompressionHandler();
|
||||
|
||||
readQueue = (buffer::SortedBufferQueue<ServerPacket> **) malloc(16 * sizeof(void*));
|
||||
for(int i = 0; i < 16; i++) {
|
||||
auto type = ts::protocol::PacketTypeInfo::fromid(i);
|
||||
if(type != PacketTypeInfo::Undefined){
|
||||
readQueue[i] = new buffer::SortedBufferQueue<ServerPacket>(ts::protocol::PacketTypeInfo::fromid(i), PacketTypeInfo::Command != type); //Ignore command low
|
||||
} else {
|
||||
readQueue[i] = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ServerConnection::~ServerConnection() {
|
||||
for(int i = 0; i < 16; i++)
|
||||
if(readQueue[i])
|
||||
delete readQueue[i];
|
||||
free(readQueue);
|
||||
this->rwThread->join();
|
||||
}
|
||||
|
||||
static int sourcePort = 50000;
|
||||
|
||||
void ServerConnection::disconnect() {
|
||||
//this->rwThread->cancel();
|
||||
//this->handleThread->cancel();
|
||||
this->connected = false;
|
||||
if(this->socket) this->socket->close();
|
||||
}
|
||||
|
||||
bool ServerConnection::connect(std::string host, std::string port, Identity *identity) {
|
||||
this->clientIdentity = identity;
|
||||
|
||||
memset(&remoteAddress, 0, sizeof(remoteAddress));
|
||||
remoteAddress.sin_family = AF_INET;
|
||||
remoteAddress.sin_port = htons((uint16_t) std::stoi(port));
|
||||
remoteAddress.sin_addr.s_addr = inet_addr(host.c_str());
|
||||
#ifdef NoQt
|
||||
/*
|
||||
this->socketfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
int allow = 1;
|
||||
setsockopt(this->socketfd, SOL_SOCKET, SO_REUSEADDR, &allow, sizeof(int));
|
||||
|
||||
memset(&localAddress, 0, sizeof(localAddress));
|
||||
localAddress.sin_family = AF_INET;
|
||||
localAddress.sin_addr.s_addr = htonl (INADDR_ANY);
|
||||
localAddress.sin_port = htons (sourcePort++);
|
||||
::connect(this->socketfd, (const sockaddr *) &remoteHost, sizeof(this->remoteAddress));
|
||||
//bind(this->socketfd, (struct sockaddr *) &localAddress, sizeof(localAddress));
|
||||
|
||||
*/
|
||||
this->socket = new UdpSocket;
|
||||
if(!this->socket->setup(&remoteAddress)){
|
||||
cerr << "Invalid socket setup" << endl;
|
||||
}
|
||||
#else
|
||||
|
||||
#endif
|
||||
this->rwThread = new threads::Thread(THREAD_SAVE_OPERATIONS, [&]() {
|
||||
#ifndef NoQt
|
||||
this->qtSocket = new QUdpSocket();
|
||||
|
||||
QObject::connect(qtSocket,SIGNAL(bytesWritten(qint64)),this,SLOT(bytesWritten(qint64)));
|
||||
QObject::connect(this->qtSocket, SIGNAL(readyRead()), this, SLOT(attempDatagramRead()));
|
||||
this->qtSocket->bind(QHostAddress::Any, 23111);
|
||||
|
||||
this->socketfd = qtSocket->socketDescriptor();
|
||||
cout << "Sock fd: " << this->socketfd << endl;
|
||||
#endif
|
||||
/*
|
||||
auto cthread = QThread::currentThread();
|
||||
cout << "ex" << endl;
|
||||
runOnThread(qtSocket->thread(), [&](){
|
||||
cout << "try" << endl;
|
||||
qtSocket->moveToThread(cthread);
|
||||
cout << "Moved" << endl;
|
||||
});
|
||||
cout << "Start rw" << endl;
|
||||
*/
|
||||
|
||||
this->rwExecutor();
|
||||
});
|
||||
|
||||
/*
|
||||
this->handleThread = new threads::Thread([&]() {
|
||||
this->handleExecutor();
|
||||
});
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifndef NoQt
|
||||
void ServerConnection::bytesWritten(qint64 b) {
|
||||
cout << "written " << b << endl;
|
||||
}
|
||||
|
||||
void ServerConnection::attempDatagramRead() {
|
||||
cout << "Data " << endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
//this->socket->socketDescriptor()
|
||||
void ServerConnection::rwExecutor() {
|
||||
pollfd pollData = {this->socket->getSocketDescriptor(), POLLRDHUP | POLLIN | POLLOUT, 0};
|
||||
buffer::RawBuffer readBuffer(512);
|
||||
std::shared_ptr<protocol::ServerPacket> readedPacket;
|
||||
while (socket->getSocketDescriptor() > 0 && this->connected) {
|
||||
int rfds = poll(&pollData, 1, -1);
|
||||
bool select = false;
|
||||
if(rfds == 0) {
|
||||
usleep(5 * 1000);;
|
||||
continue;
|
||||
} else if(rfds < 0) {
|
||||
break;
|
||||
}
|
||||
if (pollData.revents & POLLRDHUP || pollData.revents & POLLHUP) {
|
||||
select = 1;
|
||||
cerr << "Connection hang up!" << endl;
|
||||
return;
|
||||
}
|
||||
|
||||
if (pollData.revents & POLLIN) {
|
||||
select = 1;
|
||||
ssize_t readedBytes = -1;
|
||||
#ifdef NoQt
|
||||
readedBytes = socket->read(readBuffer.buffer, readBuffer.length);
|
||||
#ifdef DEBUG
|
||||
cout << "Read bytes (" << readedBytes << ")" << endl;
|
||||
#endif
|
||||
#else
|
||||
QHostAddress senderAddr;
|
||||
quint16 senderPort;
|
||||
readedBytes = this->qtSocket->readDatagram(readBuffer.buffer, readBuffer.length, &senderAddr, &senderPort);
|
||||
#endif
|
||||
if (readedBytes < 0) {
|
||||
cout << "fatal read error: " << errno << "/" << strerror(errno) << endl;
|
||||
return;
|
||||
}
|
||||
|
||||
readedPacket = std::make_shared<ServerPacket>(pipes::buffer_view(readBuffer.buffer, readedBytes));
|
||||
if(!preProgressPacket(readedPacket)){
|
||||
cerr << "Invalid packet preprocess!" << endl;
|
||||
readedPacket = nullptr;
|
||||
goto exitRead;
|
||||
}
|
||||
|
||||
if(readedPacket->type().type() < 0 || readedPacket->type().type() > 16){
|
||||
cerr << "Invalid packet id!" << endl;
|
||||
readedPacket = nullptr;
|
||||
goto exitRead;
|
||||
}
|
||||
//Deserelize packet
|
||||
this->bufferQueueLock.lock();
|
||||
if(!this->readQueue[readedPacket->type().type()]->push_pack(readedPacket)){
|
||||
//TODO error handling
|
||||
cout << "pkId: " << be2le16((char*) readedPacket->data().data_ptr()) << " -> " << readedPacket->type().name() << endl;
|
||||
}
|
||||
this->bufferQueueLock.unlock();
|
||||
while(this->handleNextPacket());
|
||||
exitRead:;
|
||||
}
|
||||
|
||||
if (pollData.revents & POLLOUT) {
|
||||
this->bufferQueueLock.lock();
|
||||
if (!this->writeQueue.empty()) {
|
||||
select = 1;
|
||||
buffer::RawBuffer buffer = this->writeQueue.front();
|
||||
|
||||
#ifdef NoQt
|
||||
auto res = this->socket->write(buffer.buffer, buffer.length);
|
||||
if (res == -1) {
|
||||
cout << "having write error: " << errno << "/" << strerror(errno) << " -> " << buffer.length << endl;
|
||||
}
|
||||
//cout << string() + "Write: " + PacketType::fromid(buffer.type()).name() << endl;
|
||||
#else
|
||||
this->qtSocket->writeDatagram(buffer.buffer, buffer.length, QHostAddress("localhost"), htons(this->remoteAddress.sin_port));
|
||||
#endif
|
||||
|
||||
/*
|
||||
if(!PacketTypeInfo::fromid(buffer.type()).requireAcknowledge()){ //Than we need a ack!
|
||||
//Wait for acknowlage
|
||||
this->acknowlageQueueLock.lock();
|
||||
this->acknowlageQueue.push_back(buffer);
|
||||
this->acknowlageQueueLock.unlock();
|
||||
}
|
||||
*/
|
||||
|
||||
this->writeQueue.pop_front();
|
||||
}
|
||||
this->bufferQueueLock.unlock();
|
||||
}
|
||||
if (!select) {
|
||||
usleep(5 * 10000);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
cerr << "rw loop broken!" << endl;
|
||||
}
|
||||
|
||||
bool ServerConnection::preProgressPacket(std::shared_ptr<protocol::ServerPacket> packet){
|
||||
packet->setEncrypted(!packet->hasFlag(PacketFlag::Unencrypted));
|
||||
packet->setCompressed(packet->hasFlag(PacketFlag::Compressed));
|
||||
packet->setFragmentedEntry(packet->hasFlag(PacketFlag::Fragmented));
|
||||
|
||||
if(packet->type() == PacketTypeInfo::Init1){
|
||||
|
||||
}
|
||||
if (packet->isEncrypted()) {
|
||||
string error = "success";
|
||||
if (!cryptionHandler->progressPacketIn(packet.get(), error, false)) {
|
||||
cerr << "Cant decript packet! Message: " << error << endl;
|
||||
cerr << "Dropping it!" << endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
cout << "[IN] Packet type -> " << packet->type().name() << " flags " << packet->flags() << " Length: " << packet->data().length() << endl;
|
||||
#endif
|
||||
if(packet->type() == PacketTypeInfo::Command || packet->type() == PacketTypeInfo::CommandLow){ //needs an acknowledge
|
||||
sendAcknowledge(packet->packetId(), packet->type() == PacketTypeInfo::CommandLow);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//TODO right packet recive order!
|
||||
void ServerConnection::handleExecutor() {
|
||||
shared_ptr<protocol::ServerPacket> packet = nullptr;
|
||||
string error = "success";
|
||||
while(this->connected){
|
||||
while(this->handleNextPacket());
|
||||
usleep(10 * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
bool ServerConnection::handleNextPacket() {
|
||||
shared_ptr<protocol::ServerPacket> packet = nullptr;
|
||||
string error = "success";
|
||||
|
||||
if(this->autoHandle){
|
||||
handleQueueLock.lock();
|
||||
if(!this->handleQueue.empty()) {
|
||||
packet = this->handleQueue.front();
|
||||
this->handleQueue.pop_front();
|
||||
}
|
||||
handleQueueLock.unlock();
|
||||
|
||||
if(packet){
|
||||
if(packet->type() == PacketTypeInfo::Ack || packet->type() == PacketTypeInfo::AckLow){
|
||||
handlePacketAck(packet);
|
||||
} else if(packet->type() == PacketTypeInfo::Command || packet->type() == PacketTypeInfo::CommandLow){
|
||||
handlePacketCommand(packet);
|
||||
} else if(packet->type() == PacketTypeInfo::Ping || packet->type() == PacketTypeInfo::Pong){
|
||||
handlePacketPing(packet);
|
||||
} else if(packet->type() == PacketTypeInfo::Voice || packet->type() == PacketTypeInfo::VoiceWhisper){
|
||||
handlePacketVoice(packet);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for(int index = 0; index < 16; index++){
|
||||
if(this->readQueue[index]) {
|
||||
if(this->readQueue[index]->available() > 0){
|
||||
auto npacket = this->readQueue[index]->peekNext(0);
|
||||
packet = make_shared<ServerPacket>(npacket->buffer());
|
||||
packet->setEncrypted(npacket->isEncrypted());
|
||||
packet->setCompressed(npacket->isCompressed());
|
||||
packet->setFragmentedEntry(npacket->isFragmentEntry());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if(!packet) return false;
|
||||
if(packet->isFragmentEntry()){
|
||||
packet->setFragmentedEntry(false);
|
||||
int deltaPacketIndex = 0;
|
||||
while(this->connected){
|
||||
std::shared_ptr<protocol::ServerPacket> nextElm = this->readQueue[packet->type().type()]->peekNext(++deltaPacketIndex);
|
||||
if(!nextElm)
|
||||
return false;
|
||||
|
||||
if(!nextElm) {
|
||||
cerr << "Dropped fragment?" << endl;
|
||||
packet = nullptr;
|
||||
break;
|
||||
}
|
||||
packet->append_data({nextElm->data()});
|
||||
if(nextElm->hasFlag(protocol::PacketFlag::Fragmented)) break; //Tail end
|
||||
nextElm = nullptr;
|
||||
}
|
||||
this->readQueue[packet->type().type()]->pop_packets(deltaPacketIndex);
|
||||
}
|
||||
this->readQueue[packet->type().type()]->pop_packets(1);
|
||||
|
||||
if(packet->type() != PacketTypeInfo::Init1 && !this->compressionHandler->progressPacketIn(packet.get(), error)){
|
||||
cerr << "Cant decompress packet! (" << error << ")" << endl;
|
||||
packet = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
#if defined(DEBUG_PACKET_LOG)
|
||||
cout << "Parsed packet " << packet->type().name() << " with id " << packet->packetId() << ". Data:" << endl;
|
||||
hexDump((void *) packet->data().data_ptr(), packet->data().length(), 16, 8, [](std::string line) {
|
||||
cout << "[IN] " << line << endl;
|
||||
});
|
||||
#endif
|
||||
handleQueueLock.lock();
|
||||
this->handleQueue.push_back(packet);
|
||||
handleQueueLock.unlock();
|
||||
return true;
|
||||
}
|
||||
|
||||
using namespace std::chrono;
|
||||
std::shared_ptr<protocol::ServerPacket> ServerConnection::readNextPacket(bool block) {
|
||||
auto start = system_clock::now();
|
||||
attempGet:
|
||||
if(system_clock::now() - start > seconds(5)) return nullptr;
|
||||
|
||||
this->handleQueueLock.lock();
|
||||
if (this->handleQueue.empty()) {
|
||||
this->handleQueueLock.unlock();
|
||||
if (!block) return nullptr;
|
||||
usleep(5 * 1000);
|
||||
goto attempGet;
|
||||
}
|
||||
|
||||
std::shared_ptr<protocol::ServerPacket> packet = std::move(this->handleQueue.front());
|
||||
this->handleQueue.pop_front();
|
||||
this->handleQueueLock.unlock();
|
||||
return packet;
|
||||
}
|
||||
|
||||
bool ServerConnection::setupSharedSecret(std::string alpha, std::string beta, std::string sharedKey, std::string &error) {
|
||||
return this->cryptionHandler->setupSharedSecret(alpha, beta, sharedKey, error);
|
||||
}
|
||||
|
||||
//Packet splitting not working correctly! (On clientinit dosnt wait for the second)
|
||||
void ServerConnection::sendPacket(ts::protocol::ClientPacket &packet) {
|
||||
int maxDataLength = 500 - packet.header().length();
|
||||
|
||||
if(packet.data().length() > maxDataLength){
|
||||
string error;
|
||||
/*
|
||||
packet.enableFlag(PacketFlag::Compressed);
|
||||
if(!this->compressionHandler->progressPacketOut(&packet, error)){
|
||||
cerr << "Compress error!" << endl;
|
||||
return;
|
||||
}
|
||||
packet.enableFlag(PacketFlag::Compressed);
|
||||
*/
|
||||
if(packet.data().length() > maxDataLength){
|
||||
std::vector<shared_ptr<ClientPacket>> siblings;
|
||||
siblings.reserve(8);
|
||||
|
||||
|
||||
{ //Split packets
|
||||
auto buffer = packet.data();
|
||||
|
||||
const auto max_length = packet.type().max_length();
|
||||
while(buffer.length() > max_length * 2) {
|
||||
siblings.push_back(make_shared<ClientPacket>(packet.type(), buffer.view(0, max_length)));
|
||||
buffer = buffer.range(max_length);
|
||||
}
|
||||
|
||||
if(buffer.length() > max_length) { //Divide rest by 2
|
||||
siblings.push_back(make_shared<ClientPacket>(packet.type(), buffer.view(0, buffer.length() / 2)));
|
||||
buffer = buffer.range(buffer.length() / 2);
|
||||
}
|
||||
siblings.push_back(make_shared<ClientPacket>(packet.type(), buffer));
|
||||
|
||||
for(const auto& frag : siblings) {
|
||||
frag->setFragmentedEntry(true);
|
||||
frag->enableFlag(PacketFlag::NewProtocol);
|
||||
}
|
||||
}
|
||||
|
||||
for(const auto& entry : siblings)
|
||||
this->sendPacket(*entry);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(!packet.memory_state.id_branded)
|
||||
packet.applyPacketId(idManager);
|
||||
packet.clientId(this->clientId);
|
||||
|
||||
string error = "success";
|
||||
if (!this->cryptionHandler->progressPacketOut(&packet, error, false)) {
|
||||
cerr << "Invalid crypt -> " << error << endl;
|
||||
return;
|
||||
}
|
||||
|
||||
buffer::RawBuffer buffer(packet.buffer().length());
|
||||
|
||||
memcpy(&buffer.buffer[0], packet.buffer().data_ptr(), packet.buffer().length());
|
||||
|
||||
this->bufferQueueLock.lock();
|
||||
this->writeQueue.push_back(buffer);
|
||||
#if defined(DEBUG_PACKET_LOG)
|
||||
cout << "Send packet " << packet.type().name() << " fragmented -> " << packet.isFragmentEntry() << " length " << packet.data().length() << " flags " << packet.flags() << " ID: " << packet.packetId() << endl;
|
||||
hexDump(buffer.buffer, buffer.length, buffer.length, buffer.length);
|
||||
#endif
|
||||
this->bufferQueueLock.unlock();
|
||||
}
|
||||
|
||||
void ServerConnection::sendCommand(ts::Command command, bool low) {
|
||||
auto data = command.build();
|
||||
protocol::ClientPacket pkt(low ? protocol::PacketTypeInfo::CommandLow : protocol::PacketTypeInfo::Command, pipes::buffer_view{(void*) data.data(), data.length()});
|
||||
#ifdef DEBUG
|
||||
cout << "[Client -> Server][" << pkt.type().name() << "] " << pkt.data() << endl;
|
||||
#endif
|
||||
if(!low) pkt.enableFlag(PacketFlag::NewProtocol);
|
||||
sendPacket(pkt);
|
||||
}
|
||||
|
||||
void ServerConnection::sendAcknowledge(uint16_t packetId, bool low) {
|
||||
if(breakAck) return;
|
||||
char buffer[2];
|
||||
le2be16(packetId, buffer);
|
||||
protocol::ClientPacket pkt(low ? protocol::PacketTypeInfo::AckLow : protocol::PacketTypeInfo::Ack, pipes::buffer_view(buffer, 2));
|
||||
#ifdef DEBUG
|
||||
cout << "Sending packet acknowledge for " << packetId << " (Encrypt: " << encriptAck << ")" << endl;
|
||||
#endif
|
||||
if(!encriptAck)
|
||||
pkt.enableFlag(PacketFlag::Unencrypted);
|
||||
if(!low) pkt.toggle(protocol::PacketFlag::NewProtocol, true);
|
||||
sendPacket(pkt);
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <ThreadPool/Thread.h>
|
||||
#include <ThreadPool/Mutex.h>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <netinet/in.h>
|
||||
#include <deque>
|
||||
#include <list>
|
||||
#include <protocol/Packet.h>
|
||||
#include <protocol/buffers.h>
|
||||
#include <src/protocol/socket/FilteredUDPSocket.h>
|
||||
#include <src/Identity.h>
|
||||
#include <protocol/CryptionHandler.h>
|
||||
#include <protocol/CompressionHandler.h>
|
||||
|
||||
#define NoQt
|
||||
#ifndef NoQt
|
||||
#include <QUdpSocket>
|
||||
#endif
|
||||
|
||||
#define DEBUG_PACKET_LOG
|
||||
//#define LOG_CMD
|
||||
namespace ts {
|
||||
namespace connection {
|
||||
namespace ConnectionState {
|
||||
enum ConnectionState {
|
||||
UNCONNECTED,
|
||||
LLHANDSCHAKE,
|
||||
HANDSCHAKE,
|
||||
CONNECTED,
|
||||
DISCONNECTED
|
||||
};
|
||||
}
|
||||
|
||||
class ServerConnection
|
||||
#ifndef NoQt
|
||||
: public QObject {
|
||||
Q_OBJECT
|
||||
#else
|
||||
{
|
||||
#endif
|
||||
public:
|
||||
ServerConnection();
|
||||
~ServerConnection();
|
||||
|
||||
bool connect(std::string host, std::string port, Identity* identity);
|
||||
void disconnect();
|
||||
|
||||
ConnectionState::ConnectionState getConnectionState(){ return cstate; }
|
||||
|
||||
bool handshake(std::string &errorMessage);
|
||||
bool handshakeNew(Command &cmd, const std::string& alpha, std::string &errorMessage);
|
||||
|
||||
void sendPacket(ts::protocol::ClientPacket& packet);
|
||||
void sendCommand(ts::Command command, bool low = false);
|
||||
void sendAcknowledge(uint16_t packetId, bool low = false);
|
||||
|
||||
std::shared_ptr<protocol::ServerPacket> readNextPacket(bool block = true);
|
||||
|
||||
uint16_t getClientId(){
|
||||
return this->clientId;
|
||||
}
|
||||
|
||||
void setClientId(uint16_t id){
|
||||
this->clientId = id;
|
||||
}
|
||||
|
||||
#ifndef NoQt
|
||||
public slots:
|
||||
void attempDatagramRead();
|
||||
void bytesWritten(qint64);
|
||||
#endif
|
||||
private:
|
||||
bool encriptAck = false;
|
||||
|
||||
bool preProgressPacket(std::shared_ptr<protocol::ServerPacket> packet);
|
||||
|
||||
void rwExecutor();
|
||||
void handleExecutor();
|
||||
bool handleNextPacket();
|
||||
|
||||
bool setupSharedSecret(std::string alpha, std::string beta, std::string sharedKey, std::string& error);
|
||||
|
||||
|
||||
void handlePacketPing(std::shared_ptr<protocol::ServerPacket> packet);
|
||||
void handlePacketCommand(std::shared_ptr<protocol::ServerPacket> packet);
|
||||
void handlePacketAck(std::shared_ptr<protocol::ServerPacket> packet);
|
||||
void handlePacketVoice(std::shared_ptr<protocol::ServerPacket> packet);
|
||||
|
||||
bool connected = true;
|
||||
|
||||
sockaddr_in remoteAddress;
|
||||
sockaddr_in localAddress;
|
||||
|
||||
|
||||
UdpSocket* socket;
|
||||
threads::Thread* rwThread = nullptr;
|
||||
std::deque<buffer::RawBuffer> writeQueue;
|
||||
#ifndef NoQt
|
||||
QUdpSocket* qtSocket = nullptr;
|
||||
#endif
|
||||
|
||||
threads::Mutex bufferQueueLock;
|
||||
buffer::SortedBufferQueue<protocol::ServerPacket>** readQueue = nullptr;
|
||||
|
||||
//std::deque<RawBuffer> readQueue;
|
||||
|
||||
std::deque<buffer::RawBuffer> acknowlageQueue;
|
||||
threads::Mutex acknowlageQueueLock;
|
||||
|
||||
protocol::PacketIdManager idManager;
|
||||
threads::Thread* handleThread = nullptr;
|
||||
std::list<std::shared_ptr<protocol::ServerPacket>> handleQueue; //Parsed packets
|
||||
threads::Mutex handleQueueLock;
|
||||
bool autoHandle = false;
|
||||
|
||||
ts::connection::CryptionHandler* cryptionHandler = nullptr;
|
||||
ts::connection::CompressionHandler* compressionHandler = nullptr;
|
||||
|
||||
std::string remoteHost;
|
||||
uint16_t remotePort;
|
||||
|
||||
Identity* clientIdentity;
|
||||
|
||||
ConnectionState::ConnectionState cstate = ConnectionState::UNCONNECTED;
|
||||
|
||||
|
||||
bool breakAck = false;
|
||||
/**
|
||||
* TS3 Client data
|
||||
*/
|
||||
|
||||
uint16_t clientId = 0;
|
||||
|
||||
std::deque<ChannelId> channels;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,361 +0,0 @@
|
||||
//
|
||||
// Created by wolverindev on 08.10.17.
|
||||
//
|
||||
|
||||
#include <tommath.h>
|
||||
#include <bitset>
|
||||
#include <openssl/sha.h>
|
||||
#include "Connection.h"
|
||||
#include "misc/base64.h"
|
||||
#include "misc/endianness.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
using namespace ts;
|
||||
using namespace ts::connection;
|
||||
using namespace ts::protocol;
|
||||
|
||||
const int InitVersionLength = 4;
|
||||
const uint8_t InitVersion[InitVersionLength] = {0x09, 0x83, 0x8C, 0xCF};
|
||||
|
||||
/**
|
||||
* Maybe memset to 0 for security?
|
||||
*/
|
||||
#define RESET_DATA \
|
||||
bufferIndex = 0; \
|
||||
delete pkt; \
|
||||
pkt = nullptr;
|
||||
|
||||
|
||||
inline ClientPacket *solvePuzzle(shared_ptr<ServerPacket> response, Identity *, std::string &);
|
||||
|
||||
inline std::string toString(mp_int* num){
|
||||
char buffer[2048];
|
||||
memset(buffer, 0, 2048);
|
||||
auto len = mp_todecimal(num, buffer);
|
||||
return string(buffer);
|
||||
}
|
||||
|
||||
|
||||
extern void hexdump(std::ostream& outs, const std::string& s, size_t line_len = 16);
|
||||
bool ServerConnection::handshake(std::string &errorMessage) {
|
||||
//setup the init mac
|
||||
/**
|
||||
* Low level
|
||||
*/
|
||||
ts::protocol::ClientPacket *pkt;
|
||||
shared_ptr<ServerPacket> response;
|
||||
int maxBufferSize = 512;
|
||||
size_t bufferIndex = 0;
|
||||
uint8_t buffer[maxBufferSize];
|
||||
memset(buffer, 0, maxBufferSize);
|
||||
int err = 0;
|
||||
string error = "success";
|
||||
|
||||
beginCoocie:
|
||||
memcpy(buffer, InitVersion, InitVersionLength);
|
||||
bufferIndex += InitVersionLength;
|
||||
buffer[bufferIndex++] = 0x00; //Login state
|
||||
|
||||
auto millis = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
|
||||
memcpy(&buffer[bufferIndex], &millis, 4);
|
||||
bufferIndex += 4;
|
||||
//generate the alpha key
|
||||
for (int i = 0; i < 4; i++) buffer[bufferIndex++] = (uint8_t) std::rand();
|
||||
bufferIndex += 8; //Reserved bytes
|
||||
|
||||
pkt = new ts::protocol::ClientPacket(ts::protocol::PacketTypeInfo::Init1, pipes::buffer_view((void *) buffer, bufferIndex));
|
||||
pkt->clientId(0);
|
||||
pkt->toggle(ts::protocol::PacketFlag::Unencrypted, true);
|
||||
pkt->applyPacketId(101, 0);
|
||||
this->sendPacket(*pkt);
|
||||
RESET_DATA;
|
||||
|
||||
response = readNextPacket();
|
||||
if (!response) {
|
||||
errorMessage = "could not get a valid response!";
|
||||
return false;
|
||||
}
|
||||
if (response->type() != protocol::PacketTypeInfo::Init1) {
|
||||
errorMessage = "invalid response type. Got: " + response->type().name();
|
||||
return false;
|
||||
}
|
||||
if (response->data()[0] != 1) {
|
||||
errorMessage = "iInvalid requested login type (" + to_string((int) response->data()[0]) + " == 1)";
|
||||
return false;
|
||||
}
|
||||
|
||||
//the second request of the manager
|
||||
memcpy(buffer, InitVersion, InitVersionLength);
|
||||
bufferIndex += InitVersionLength;
|
||||
buffer[bufferIndex++] = 0x02; //Login state
|
||||
if(response)
|
||||
memcpy(&buffer[bufferIndex], response->data().string().substr(1, 16).data(), 16);
|
||||
bufferIndex += 16; //Servers 16 bytes
|
||||
if(response)
|
||||
memcpy(&buffer[bufferIndex], response->data().string().substr(17, 4).data(), 4);
|
||||
bufferIndex += 4; //My own 16 bytes, reversed
|
||||
|
||||
pkt = new ts::protocol::ClientPacket(ts::protocol::PacketTypeInfo::Init1, pipes::buffer_view((void *) buffer, bufferIndex));
|
||||
pkt->clientId(0);
|
||||
pkt->toggle(ts::protocol::PacketFlag::Unencrypted, true);
|
||||
pkt->applyPacketId(101, 0);
|
||||
this->sendPacket(*pkt);
|
||||
RESET_DATA;
|
||||
|
||||
//We got the RSA challenge
|
||||
response = readNextPacket();
|
||||
if (!response) {
|
||||
errorMessage = "could not get a valid response!";
|
||||
return false;
|
||||
}
|
||||
if (response->type() != protocol::PacketTypeInfo::Init1) {
|
||||
errorMessage = "invalid response type";
|
||||
return false;
|
||||
}
|
||||
if (response->data()[0] != 3) {
|
||||
if(response->data()[0] == 127) {
|
||||
cout << "COOCIE RESET!" << endl;
|
||||
goto beginCoocie;
|
||||
}
|
||||
hexdump(cout, response->data().string());
|
||||
errorMessage = "Invalid requested login type (" + to_string((int) response->data()[0]) + " == 3 | unencripted -> " + (response->hasFlag(PacketFlag::Unencrypted) ? "true" : "false") + ")";
|
||||
return false;
|
||||
}
|
||||
|
||||
//Generate puzzel response
|
||||
std::string alpha;
|
||||
pkt = solvePuzzle(response, this->clientIdentity, alpha);
|
||||
pkt->applyPacketId(101, 0);
|
||||
this->sendPacket(*pkt);
|
||||
RESET_DATA;
|
||||
|
||||
cout << "manager init done" << endl;
|
||||
this->encriptAck = true;
|
||||
|
||||
response = readNextPacket();
|
||||
if (!response) {
|
||||
errorMessage = "could not get a valid response!";
|
||||
return false;
|
||||
}
|
||||
if (response->type() != protocol::PacketTypeInfo::Command) {
|
||||
errorMessage = "invalid response type: " + response->type().name();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
auto command = response->asCommand();
|
||||
if (command.getCommand().compare("initivexpand") != 0) {
|
||||
// errorMessage = "invalid response command. Got: " + command.getCommand() + " Expected: initivexpand";
|
||||
return this->handshakeNew(command, alpha, errorMessage);
|
||||
}
|
||||
|
||||
//std::string alpha = base64::decode(command[0]["alpha"]);
|
||||
std::string beta = base64::decode(command[0]["beta"]);
|
||||
std::string omega = base64::decode(command[0]["omega"]); //Remotes public key
|
||||
cout << "RESPONSE! -> " << command.build() << endl;
|
||||
//Read public key
|
||||
ecc_key remotePublicKey{};
|
||||
if ((err = ecc_import((const unsigned char *) omega.data(), omega.length(), &remotePublicKey)) != CRYPT_OK) {
|
||||
errorMessage = "ecc_import(...) returned " + to_string(err) + "/" + error_to_string(err);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(strcmp(remotePublicKey.dp->name, "ECC-256") != 0){
|
||||
errorMessage = "invalid imported public key! Curve found " + string(remotePublicKey.dp->name);
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t sharedSecretLength = 32;
|
||||
char sharedSecret[sharedSecretLength];
|
||||
|
||||
if ((err = ecc_shared_secret(clientIdentity->getKeyPair(), &remotePublicKey, (unsigned char *) sharedSecret, &sharedSecretLength)) != CRYPT_OK) {
|
||||
errorMessage = "ecc_shared_secret(...) returned " + to_string(err) + "/" + error_to_string(err);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!setupSharedSecret(alpha, beta, string(sharedSecret, sharedSecretLength), error)) {
|
||||
errorMessage = "setupSharedSecret(...) failed: " + error;
|
||||
return false;
|
||||
}
|
||||
|
||||
//this->readQueue[PacketType::Command.type()]->reset();
|
||||
|
||||
//TS 3.1
|
||||
/*
|
||||
response = readNextPacket();
|
||||
if (!response) {
|
||||
errorMessage = "could not get a valid response!";
|
||||
return false;
|
||||
}
|
||||
if (response->type() != protocol::PacketType::Command) {
|
||||
errorMessage = "invalid response type: " + response->type().name();
|
||||
return false;
|
||||
}
|
||||
command = response->asCommand();
|
||||
cout << "Having initiv2 -> " << response->data() << endl;
|
||||
*/
|
||||
|
||||
this->idManager.nextPacketId(PacketTypeInfo::Command);
|
||||
Command clientinit("clientinit");
|
||||
//94ec66de-5940-4e38-b002-970df0cf6c94,62444179-0d99-42ba-a45c-c6b1557d079a,d95f9901-c42d-4bac-8849-7164fd9e2310
|
||||
//clientinit["client_badges"] = "badges=450f81c1-ab41-4211-a338-222fa94ed157,c9e97536-5a2d-4c8e-a135-af404587a472,94ec66de-5940-4e38-b002-970df0cf6c94"; //,62444179-0d99-42ba-a45c-c6b1557d079a
|
||||
clientinit["client_nickname"] = "Wolf C++ XXXX";
|
||||
clientinit["client_version"] = "3.1 [Build: 1471417187]";
|
||||
clientinit["client_platform"] = "Windows";
|
||||
clientinit["client_version_sign"] = "Vr9F7kbVorcrkV5b/Iw+feH9qmDGvfsW8tpa737zhc1fDpK5uaEo6M5l2DzgaGqqOr3GKl5A7PF9Sj6eTM26Aw==";
|
||||
clientinit["client_input_hardware"] = true;
|
||||
clientinit["client_output_hardware"] = true;
|
||||
clientinit["client_default_channel"] = "";
|
||||
clientinit["client_default_channel_password"] = "";
|
||||
|
||||
string password;
|
||||
if(!password.empty()){
|
||||
char passwordBuffer[SHA_DIGEST_LENGTH];
|
||||
SHA1((const unsigned char *) password.data(), password.length(), (unsigned char *) passwordBuffer);
|
||||
password = base64_encode(string(passwordBuffer, SHA_DIGEST_LENGTH));
|
||||
}
|
||||
clientinit["client_server_password"] = password;
|
||||
clientinit["client_meta_data"] = "";
|
||||
clientinit["client_key_offset"] = this->clientIdentity->lastValidKeyOffset();
|
||||
clientinit["client_nickname_phonetic"] = "";
|
||||
clientinit["client_default_token"] = "";
|
||||
clientinit["hwid"] = "123,456123123123";
|
||||
sendCommand(clientinit);
|
||||
|
||||
while(true){
|
||||
response = readNextPacket();
|
||||
if (!response) {
|
||||
errorMessage = "could not get a valid response!";
|
||||
return false;
|
||||
}
|
||||
if(response->type() == PacketTypeInfo::Ack) continue;
|
||||
break;
|
||||
}
|
||||
|
||||
//TODO check ack id
|
||||
|
||||
if (!response) {
|
||||
errorMessage = "could not get a valid response!";
|
||||
return false;
|
||||
}
|
||||
if (response->type() != protocol::PacketTypeInfo::Command) {
|
||||
errorMessage = "invalid response type: " + response->type().name();
|
||||
return false;
|
||||
}
|
||||
if(response->asCommand().getCommand() == "initserver"){ //Got success
|
||||
this->handleQueueLock.lock();
|
||||
this->handleQueue.push_front(response);
|
||||
this->handleQueueLock.unlock();
|
||||
|
||||
this->setClientId(response->asCommand()["aclid"]);
|
||||
|
||||
this->autoHandle = true;
|
||||
cout << "Successfull connected!" << endl;
|
||||
|
||||
/*
|
||||
std::thread([&](){
|
||||
usleep(1000 * 1000);
|
||||
cout << " -> send extra command" << endl;
|
||||
//this->sendCommand(Command("channelsubscribeall return_code=1:i"));
|
||||
|
||||
while(true){
|
||||
//this->sendCommand(Command("getconnectioninfo clid=320 return_code=1:112"));
|
||||
//this->sendCommand(Command("ftgetfilelist cid=0 cpw path=\\/icons return_code=1:z0"));
|
||||
this->sendCommand(Command("servergrouppermlist sgid=6 return_code=1:112"));
|
||||
usleep(10 * 1000 * 1000);
|
||||
}
|
||||
//Command cmd("channelgetdescription cid=1 return_code=1:3o");
|
||||
//Command cmd("clientupdate client_nickname=WolverinDEV22 return_code=__1_");
|
||||
//Command cmd("clientdisconnect reasonid=8 reasonmsg=leaving");
|
||||
//this->sendCommand(Command("permissionlist return_code=__1_"));
|
||||
//this->sendCommand(Command("clientgetvariables clid=" + to_string(this->clientId)));
|
||||
}).detach();
|
||||
*/
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
cout << "Invalid connect: " << response->data() << endl;
|
||||
//TODO error handling
|
||||
return true;
|
||||
}
|
||||
|
||||
inline ClientPacket* solvePuzzle(shared_ptr<ServerPacket> response, Identity *identity, std::string &alpha) {
|
||||
uint32_t puzzelLength = be2le32(&((char*) response->data().data_ptr())[1 + 128]); //1 for the first byte (the state byte)
|
||||
|
||||
auto buffer = (char*) response->data().data_ptr();
|
||||
|
||||
char alphaBuffer[10];
|
||||
for (int index = 0; index < 10; index++)
|
||||
alphaBuffer[index] = 0; //rand();
|
||||
alpha = string(alphaBuffer, 10);
|
||||
|
||||
//Generating command
|
||||
auto pkey = identity->publicKey();
|
||||
ts::Command command("clientinitiv", {
|
||||
{"alpha", base64_encode(alphaBuffer, 10)},
|
||||
{"omega", pkey},
|
||||
{"ip", ""},
|
||||
{"ot", 1} //Required by 3.1
|
||||
});
|
||||
std::string cmd = command.build();
|
||||
|
||||
//Sloving puzzel
|
||||
mp_int x{};
|
||||
mp_int n{};
|
||||
mp_int result{};
|
||||
|
||||
//mp_init_multi(&x, &n, &result);
|
||||
mp_init(&x);
|
||||
mp_init(&n);
|
||||
mp_init(&result);
|
||||
|
||||
char numBuffer[2048];
|
||||
mp_read_unsigned_bin(&x, (const unsigned char *) &response->data()[1], 64); //One offset
|
||||
mp_read_unsigned_bin(&n, (const unsigned char *) &response->data()[1 + 64], 64); //1 + 64 offset
|
||||
|
||||
cout << "X: " << toString(&x) << endl;
|
||||
cout << "N: " << toString(&n) << endl;
|
||||
cout << "Length: " << puzzelLength << endl;
|
||||
|
||||
mp_int exp{};
|
||||
mp_init(&exp);
|
||||
mp_2expt(&exp, puzzelLength);
|
||||
|
||||
|
||||
//x ** (2 ** puzzelLength) mod n
|
||||
int err = 0;
|
||||
if ((err = mp_exptmod(&x, &exp, &n, &result)) != CRYPT_OK) {
|
||||
cerr << "Invalid crypt: " << err << "/" << error_to_string(err) << endl;
|
||||
}
|
||||
|
||||
int resultBufferLength = mp_unsigned_bin_size(&result);
|
||||
char resultBuffer[resultBufferLength];
|
||||
mp_to_unsigned_bin(&result, (unsigned char *) resultBuffer);
|
||||
|
||||
|
||||
//mp_clear_multi(&x, &n, &exp, &result);
|
||||
mp_clear(&x);
|
||||
mp_clear(&n);
|
||||
mp_clear(&exp);
|
||||
mp_clear(&result);
|
||||
|
||||
size_t packetBufferLength = InitVersionLength + 1 + 232 + 64 + cmd.length();
|
||||
char packetBuffer[packetBufferLength];
|
||||
memset(packetBuffer, 0, packetBufferLength);
|
||||
|
||||
memcpy(packetBuffer, InitVersion, InitVersionLength);
|
||||
packetBuffer[InitVersionLength] = 0x04;
|
||||
|
||||
//Copy old data
|
||||
memcpy(&packetBuffer[InitVersionLength + 1], &response->data()[1], 232);
|
||||
memcpy(&packetBuffer[InitVersionLength + 1 + 232 + (64 - resultBufferLength)], resultBuffer, resultBufferLength);
|
||||
memcpy(&packetBuffer[InitVersionLength + 1 + 232 + 64], cmd.data(), cmd.length());
|
||||
|
||||
cout << "sending puzzel sulution" << endl;
|
||||
auto pkt = new ts::protocol::ClientPacket(ts::protocol::PacketTypeInfo::Init1, pipes::buffer_view((void *) packetBuffer, packetBufferLength));
|
||||
pkt->clientId(0);
|
||||
pkt->toggle(ts::protocol::PacketFlag::Unencrypted, true);
|
||||
return pkt;
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
#include <protocol/Packet.h>
|
||||
#include <arpa/inet.h>
|
||||
#include "Connection.h"
|
||||
#include "misc/base64.h"
|
||||
#include "misc/endianness.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace ts;
|
||||
using namespace ts::connection;
|
||||
using namespace ts::protocol;
|
||||
|
||||
//notifystatusfiletransfer clientftfid=4093 status=2063 msg=lost\sfile\stransfer\sconnection size=16384
|
||||
|
||||
extern void hexdump(std::ostream& outs, const std::string& s, size_t line_len = 16);
|
||||
inline void downloadStuff(std::string key, uint16_t port, uint64_t size){
|
||||
threads::Thread([key, port, size](){
|
||||
int socketId = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
assert(socketId > 1);
|
||||
|
||||
sockaddr_in server;
|
||||
server.sin_addr.s_addr = inet_addr("127.0.0.1");
|
||||
server.sin_family = AF_INET;
|
||||
server.sin_port = htons( port );
|
||||
|
||||
//Connect to remote server
|
||||
if (connect(socketId , (struct sockaddr *)&server , sizeof(server)) < 0)
|
||||
{
|
||||
perror("connect failed. Error");
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t readed = 0;
|
||||
assert(send(socketId, key.data(), key.length(), 0) > 0);
|
||||
|
||||
while(readed < size + 3){
|
||||
char buffer[size];
|
||||
auto readedBytes = recv(socketId, buffer, size - readed, MSG_DONTWAIT);
|
||||
if(readedBytes < 0) {
|
||||
//cerr << "Invalid ft read" << endl;
|
||||
continue;
|
||||
}
|
||||
if(readedBytes == 0){
|
||||
continue;
|
||||
}
|
||||
hexdump(cout, string(buffer, readedBytes));
|
||||
readed += readedBytes;
|
||||
}
|
||||
cout << "File downloaded!" << endl;
|
||||
}).detach();
|
||||
}
|
||||
|
||||
void ServerConnection::handlePacketAck(std::shared_ptr<protocol::ServerPacket> packet) {
|
||||
auto packetId = be2le16((const char*) packet->data().data_ptr());
|
||||
#if defined(DEBUG_PACKET_LOG) || defined(LOG_ACK)
|
||||
cout << "Got ack for " << packetId << endl;
|
||||
#endif
|
||||
}
|
||||
|
||||
void ServerConnection::handlePacketCommand(std::shared_ptr<protocol::ServerPacket> packet) {
|
||||
auto command = packet->asCommand();
|
||||
#if defined(DEBUG_PACKET_LOG) || defined(LOG_CMD)
|
||||
cout << "[Server -> Client][" << packet->type().name() << "] " << packet->data() << endl;
|
||||
#endif
|
||||
if (command.getCommand().compare("notifyconnectioninforequest") == 0) { //TODO
|
||||
cout << "Send response" << endl;
|
||||
Command cmd(
|
||||
string("setconnectioninfo"), {
|
||||
{"connection_ping", 10000000},
|
||||
{"connection_ping_deviation", 10000000},
|
||||
{"connection_packets_sent_speech", 0},
|
||||
{"connection_packets_sent_keepalive", 0},
|
||||
{"connection_packets_sent_control", rand()},
|
||||
{"connection_bytes_sent_speech", 0},
|
||||
{"connection_bytes_sent_keepalive", 0},
|
||||
{"connection_bytes_sent_control", 0},
|
||||
{"connection_packets_received_speech", 0},
|
||||
{"connection_packets_received_keepalive", 0},
|
||||
{"connection_packets_received_control", 0},
|
||||
{"connection_bytes_received_speech", 0},
|
||||
{"connection_bytes_received_keepalive", 0},
|
||||
{"connection_bytes_received_control", 0},
|
||||
{"connection_server2client_packetloss_speech", 10000000},
|
||||
{"connection_server2client_packetloss_keepalive", 10000000},
|
||||
{"connection_server2client_packetloss_control", 10000000},
|
||||
{"connection_server2client_packetloss_total", 10000000},
|
||||
{"connection_bandwidth_sent_last_second_speech", 0},
|
||||
{"connection_bandwidth_sent_last_second_keepalive", 0},
|
||||
{"connection_bandwidth_sent_last_second_control", 0},
|
||||
{"connection_bandwidth_sent_last_minute_speech", 0},
|
||||
{"connection_bandwidth_sent_last_minute_keepalive", 0},
|
||||
{"connection_bandwidth_sent_last_minute_control", 0},
|
||||
{"connection_bandwidth_received_last_second_speech", 0},
|
||||
{"connection_bandwidth_received_last_second_keepalive", 0},
|
||||
{"connection_bandwidth_received_last_second_control", 0},
|
||||
{"connection_bandwidth_received_last_minute_speech", 0},
|
||||
{"connection_bandwidth_received_last_minute_keepalive", 0},
|
||||
{"connection_bandwidth_received_last_minute_control", 0}
|
||||
}
|
||||
);
|
||||
sendCommand(cmd, true);
|
||||
} else if (command.command() == "notifyserverupdated") {
|
||||
#if defined(DEBUG_PACKET_LOG) || defined(LOG_CMD)
|
||||
cout << "notifyserverupdated -> " << endl;
|
||||
cout << "Last data: " << packet->data().string().substr(packet->data().length() - 10) << endl;
|
||||
#endif
|
||||
} else if (command.command() == "notifystartdownload") {
|
||||
cout << "Client download: " << command.build() << endl;
|
||||
auto port = command["port"].as<uint16_t>();
|
||||
auto key = command["ftkey"].string();
|
||||
auto size = command["size"].as<uint64_t>();
|
||||
downloadStuff(key, port, size);
|
||||
} else if (command.command() == "channellist") {
|
||||
cout << "Breaking ack" << endl;
|
||||
for (int index = 0; index < command.bulkCount(); index++) {
|
||||
this->channels.push_back(command[index]["cid"].as<ChannelId>());
|
||||
}
|
||||
}
|
||||
}
|
||||
void ServerConnection::handlePacketVoice(std::shared_ptr<protocol::ServerPacket> packet) {}
|
||||
|
||||
static int pingIndex = 0;
|
||||
void ServerConnection::handlePacketPing(std::shared_ptr<protocol::ServerPacket> packet) {
|
||||
if(packet->type() == PacketTypeInfo::Pong){
|
||||
//cout << "[PING] gota " << be2le16(packet->data().data()) << endl;
|
||||
return;
|
||||
}
|
||||
|
||||
char buffer[2];
|
||||
le2be16(packet->packetId(), buffer);
|
||||
|
||||
ClientPacket pkt(PacketTypeInfo::Pong, pipes::buffer_view{buffer, 2});
|
||||
pkt.enableFlag(PacketFlag::Unencrypted);
|
||||
sendPacket(pkt);
|
||||
|
||||
ClientPacket ping(PacketTypeInfo::Ping, pipes::buffer_view{buffer, 0});
|
||||
ping.enableFlag(PacketFlag::Unencrypted);
|
||||
sendPacket(ping);
|
||||
//cout << "[PING] Reqe " << ping.packetId() << endl;
|
||||
|
||||
//cout << "[PONG] Send " << packet->packetId() << endl;
|
||||
|
||||
if(this->clientId > 0 && this->channels.size() > 0) {
|
||||
Command command("clientmove");
|
||||
command["clid"] = this->clientId;
|
||||
|
||||
auto idx = rand() % this->channels.size();
|
||||
command["cid"] = this->channels[idx];
|
||||
this->sendCommand(command);
|
||||
|
||||
std::thread([&] {
|
||||
threads::self::sleep_for(chrono::seconds(1));
|
||||
|
||||
Command cmd("channelcreate");
|
||||
cmd["channel_name"] = to_string(rand()) + "_" + to_string(rand());
|
||||
//this->sendCommand(cmd);
|
||||
}).detach();
|
||||
}
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
#include <ed25519/ed25519.h>
|
||||
#include <ed25519/sha512.h>
|
||||
#include <misc/base64.h>
|
||||
#include <misc/digest.h>
|
||||
#include "Connection.h"
|
||||
#include "License.h"
|
||||
#include <log/LogUtils.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
using namespace ts;
|
||||
using namespace license::teamspeak;
|
||||
using namespace ts::connection;
|
||||
using namespace ts::protocol;
|
||||
|
||||
|
||||
int __ed_sha512_init(sha512_context* ctx) {
|
||||
//assert(!ctx->context);
|
||||
|
||||
ctx->context = new hash_state{};
|
||||
return sha512_init((hash_state*) ctx->context) == CRYPT_OK;
|
||||
}
|
||||
|
||||
int __ed_sha512_final(sha512_context* ctx, unsigned char *out) {
|
||||
assert(ctx->context);
|
||||
|
||||
auto result = sha512_done((hash_state*) ctx->context, out) == CRYPT_OK;
|
||||
delete (hash_state*) ctx->context;
|
||||
return result;
|
||||
}
|
||||
int __ed_sha512_update(sha512_context* ctx, const unsigned char *msg, size_t len) {
|
||||
assert(ctx->context);
|
||||
return sha512_process((hash_state*) ctx->context, msg, len) == CRYPT_OK;
|
||||
}
|
||||
|
||||
static sha512_functions __ed_sha512_functions {
|
||||
__ed_sha512_init,
|
||||
__ed_sha512_final,
|
||||
__ed_sha512_update
|
||||
};
|
||||
|
||||
|
||||
bool ServerConnection::handshakeNew(Command &initivexpand2, const std::string& alpha, std::string &errorMessage) {
|
||||
if(&__ed_sha512_functions != &_ed_sha512_functions)
|
||||
_ed_sha512_functions = __ed_sha512_functions;
|
||||
|
||||
cout << initivexpand2.build() << endl;
|
||||
u_char seed[32 * 2];
|
||||
u_char clientPrivateKey[32];
|
||||
u_char clientPublicKey[32];
|
||||
ed25519_create_keypair(clientPublicKey, clientPrivateKey, seed);
|
||||
cout << "Client key: " << base64::encode((char*) clientPrivateKey, 32) << endl;
|
||||
cout << "Privet key:" << endl;
|
||||
hexDump(clientPrivateKey, 32);
|
||||
cout << "Public key:" << endl;
|
||||
hexDump(clientPublicKey, 32);
|
||||
auto license = base64::decode(initivexpand2["l"]);
|
||||
|
||||
auto licensestream = stringstream(license);
|
||||
auto chain = LicenseChain::parse(licensestream, errorMessage);
|
||||
if(!chain) return false;
|
||||
chain->print();
|
||||
|
||||
unique_ptr<ecc_key> serverPublic(new ecc_key{});
|
||||
auto omega = base64::decode(initivexpand2["omega"]);
|
||||
ecc_import((u_char*) omega.data(), omega.length(), serverPublic.get());
|
||||
|
||||
//7B 1E AC 02 CE 77 35 0E EF C4 5C 1C F7 54 04 87 A9 A7 64 A7 8F 04 F7 53 58 64 84 D7 0A 97 F2 63
|
||||
//[0x7b, 0x1e, 0xac, 0x2, 0xce, 0x77, 0x35, 0xe, 0xef, 0xc4, 0x5c, 0x1c, 0xf7, 0x54, 0x4, 0x87, 0xa9, 0xa7, 0x64, 0xa7, 0x8f, 0x4, 0xf7, 0x53, 0x58, 0x64, 0x84, 0xd7, 0xa, 0x97, 0xf2, 0xe3]
|
||||
//License signed from server :)
|
||||
auto licenseHash = digest::sha256(license);
|
||||
auto licenseSign = base64::decode(initivexpand2["proof"]);
|
||||
|
||||
int state;
|
||||
assert(ecc_verify_hash((u_char*) licenseSign.c_str(), licenseSign.length(), (u_char*) licenseHash.c_str(), licenseHash.length(), &state, serverPublic.get()) == CRYPT_OK);
|
||||
cout << "State: " << state << endl;
|
||||
assert(state == 1);
|
||||
|
||||
//EK!
|
||||
this->idManager.nextPacketId(PacketTypeInfo::Command);
|
||||
cout << this->idManager.currentPacketId(PacketTypeInfo::Command) << endl;
|
||||
Command clientek("clientek");
|
||||
clientek["ek"] = base64::encode((char*) clientPublicKey, 32);
|
||||
auto rawProof = string((char*) clientPublicKey, 32) + base64::decode(initivexpand2["beta"]);
|
||||
cout << " -> " << rawProof.length() << endl;
|
||||
size_t signBufferLength = 120;
|
||||
char signBuffer[signBufferLength];
|
||||
prng_state prngState{};
|
||||
memset(&prngState, 0, sizeof(prngState));
|
||||
|
||||
cout << "Data: " << base64::encode(rawProof) << endl;
|
||||
cout << "KEY: " << this->clientIdentity->privateKey() << endl;
|
||||
rawProof = digest::sha256(rawProof);
|
||||
assert(ecc_sign_hash((u_char*) rawProof.data(), rawProof.length(), (u_char*) signBuffer, &signBufferLength, &prngState, find_prng("sprng"), this->clientIdentity->getKeyPair()) == CRYPT_OK);
|
||||
cout << "ecc_sign_hash() -> " << base64::encode(signBuffer, signBufferLength) << endl;
|
||||
clientek["proof"] = base64::encode(signBuffer, signBufferLength);
|
||||
this->sendCommand(clientek, false);
|
||||
|
||||
//TODO magic stuff
|
||||
shared_ptr<ServerPacket> response;
|
||||
response = readNextPacket();
|
||||
if (!response) {
|
||||
errorMessage = "could not get a valid response!";
|
||||
return false;
|
||||
}
|
||||
cout << "Type: " << response->type().name() << endl;
|
||||
cout << "ID: " << (int) response->data()[0] << " " << (int) response->data()[1] << endl;
|
||||
|
||||
|
||||
LicensePublicKey serverroot;
|
||||
memcpy(serverroot, public_root, 32);
|
||||
if(initivexpand2[0].has("root")) {
|
||||
cout << "Cot costume server root!" << endl;
|
||||
auto root = base64::decode(initivexpand2["root"]);
|
||||
memcpy(serverroot, root.data(), 32);
|
||||
}
|
||||
cout << "Public root key: " << endl;
|
||||
for(const auto& e : serverroot)
|
||||
cout << hex << "0x" << (int) (uint8_t) e << " " << endl;
|
||||
|
||||
string sharedData;
|
||||
this->cryptionHandler->setupSharedSecretNew(alpha, base64::decode(initivexpand2["beta"]), (char*) clientPrivateKey, (char*) chain->generatePublicKey(serverroot).data());
|
||||
//this->cryptionHandler->setupSharedSecretNew(alpha, base64::decode(initivexpand2["beta"]), (char*) clientPrivateKey, (char*) public_tea_root);
|
||||
|
||||
threads::self::sleep_for(milliseconds(250));
|
||||
Command clientinit("clientinit");
|
||||
//94ec66de-5940-4e38-b002-970df0cf6c94,62444179-0d99-42ba-a45c-c6b1557d079a,d95f9901-c42d-4bac-8849-7164fd9e2310
|
||||
//clientinit["client_badges"] = "badges=450f81c1-ab41-4211-a338-222fa94ed157,c9e97536-5a2d-4c8e-a135-af404587a472,94ec66de-5940-4e38-b002-970df0cf6c94"; //,62444179-0d99-42ba-a45c-c6b1557d079a
|
||||
clientinit["client_nickname"] = "Wolf C++ XX";
|
||||
clientinit["client_version"] = "3.1.8 [Build: 1516614607]";
|
||||
clientinit["client_platform"] = "Windows";
|
||||
clientinit["client_version_sign"] = "gDEgQf/BiOQZdAheKccM1XWcMUj2OUQqt75oFuvF2c0MQMXyv88cZQdUuckKbcBRp7RpmLInto4PIgd7mPO7BQ==";
|
||||
clientinit["client_input_hardware"] = true;
|
||||
clientinit["client_output_hardware"] = true;
|
||||
clientinit["client_default_channel"] = "";
|
||||
clientinit["client_default_channel_password"] = "";
|
||||
clientinit["client_server_password"] = "";
|
||||
clientinit["client_meta_data"] = "";
|
||||
clientinit["client_key_offset"] = this->clientIdentity->lastValidKeyOffset();
|
||||
clientinit["client_nickname_phonetic"] = "";
|
||||
clientinit["client_default_token"] = "";
|
||||
clientinit["hwid"] = "123,456123123123";
|
||||
sendCommand(clientinit);
|
||||
|
||||
this->autoHandle = true;
|
||||
/*
|
||||
response = readNextPacket();
|
||||
if (!response) {
|
||||
errorMessage = "could not get a valid response!";
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
//
|
||||
// Created by root on 13.10.17.
|
||||
//
|
||||
|
||||
#include "FilteredUDPSocket.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
#include <ifaddrs.h>
|
||||
#include <netinet/udp.h> //Provides declarations for udp header
|
||||
#include <netinet/ip.h> //Provides declarations for ip header
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <linux/if_ether.h>
|
||||
#include <linux/filter.h>
|
||||
#include <chrono>
|
||||
#include <zconf.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
using namespace ts::connection;
|
||||
|
||||
FilteredUdpSocket::FilteredUdpSocket() {}
|
||||
FilteredUdpSocket::~FilteredUdpSocket() {}
|
||||
|
||||
|
||||
bool FilteredUdpSocket::setup(sockaddr_in * remoteAddr) {
|
||||
srand(system_clock::now().time_since_epoch().count()); // should only be called once
|
||||
int r = (lrand48() % (50 * 1000)) + 1000; // returns a pseudo-random integer between 0 and RAND_MAX
|
||||
|
||||
|
||||
this->remoteAdress = new sockaddr_in;
|
||||
memcpy(this->remoteAdress, remoteAddr, sizeof(sockaddr_in));
|
||||
|
||||
this->socketDescriptor = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if(this->socketDescriptor < 0){
|
||||
cerr << "Invalid socket create: " << errno << " - " << this->socketDescriptor << " -> " << strerror(errno) << endl;
|
||||
}
|
||||
int allow = 1;
|
||||
setsockopt(this->socketDescriptor, SOL_SOCKET, SO_REUSEADDR, &allow, sizeof(int));
|
||||
|
||||
this->localAdress = new sockaddr_in;
|
||||
memset((char *) this->localAdress, 0, sizeof(sockaddr_in));
|
||||
localAdress->sin_family = AF_INET;
|
||||
localAdress->sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
localAdress->sin_port = htons(r);
|
||||
|
||||
|
||||
/* sudo tcpdump -q udp port 256 -dd */
|
||||
struct sock_filter code[ ] = {
|
||||
{ 0x28, 0, 0, 0x0000000c },
|
||||
{ 0x15, 0, 8, 0x000086dd },
|
||||
{ 0x30, 0, 0, 0x00000014 },
|
||||
{ 0x15, 2, 0, 0x00000084 },
|
||||
{ 0x15, 1, 0, 0x00000006 },
|
||||
{ 0x15, 0, 17, 0x00000011 },
|
||||
{ 0x28, 0, 0, 0x00000036 },
|
||||
{ 0x15, 14, 0, 0x00002fbd },
|
||||
{ 0x28, 0, 0, 0x00000038 },
|
||||
{ 0x15, 12, 13, 0x00002fbd },
|
||||
{ 0x15, 0, 12, 0x00000800 },
|
||||
{ 0x30, 0, 0, 0x00000017 },
|
||||
{ 0x15, 2, 0, 0x00000084 },
|
||||
{ 0x15, 1, 0, 0x00000006 },
|
||||
{ 0x15, 0, 8, 0x00000011 },
|
||||
{ 0x28, 0, 0, 0x00000014 },
|
||||
{ 0x45, 6, 0, 0x00001fff },
|
||||
{ 0xb1, 0, 0, 0x0000000e },
|
||||
{ 0x48, 0, 0, 0x0000000e },
|
||||
{ 0x15, 2, 0, 0x00002fbd },
|
||||
{ 0x48, 0, 0, 0x00000010 },
|
||||
{ 0x15, 0, 1, 0x00002fbd },
|
||||
{ 0x6, 0, 0, 0x00040000 },
|
||||
{ 0x6, 0, 0, 0x00000000 },
|
||||
|
||||
};
|
||||
|
||||
struct sock_fprog bpf = {
|
||||
.len = sizeof(code) / sizeof(*code),
|
||||
.filter = code,
|
||||
};
|
||||
|
||||
//auto response = setsockopt(this->socketDescriptor, SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf));
|
||||
//if (response < 0) cerr << "Invalid attach!" << endl;
|
||||
/* ... bail out ... */
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
if(connect(this->socketDescriptor, (const sockaddr *) remoteAddr, sizeof(sockaddr_in)) < 0){
|
||||
cerr << "Invalid connect" << endl;
|
||||
}
|
||||
*/
|
||||
if(bind(this->socketDescriptor, (const sockaddr *) localAdress, sizeof(sockaddr_in)) < 0) cout << "XXX" << endl;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void FilteredUdpSocket::close() {
|
||||
if(this->socketDescriptor > 0) {
|
||||
shutdown(this->socketDescriptor, SHUT_RDWR);
|
||||
::close(this->socketDescriptor);
|
||||
this->socketDescriptor = 0;
|
||||
}
|
||||
}
|
||||
|
||||
ssize_t FilteredUdpSocket::write(const char *buffer, size_t size) {
|
||||
return sendto(this->socketDescriptor, buffer, size, 0, (const sockaddr *) this->remoteAdress, sizeof(sockaddr_in));
|
||||
}
|
||||
|
||||
ssize_t FilteredUdpSocket::read(char *buffer, size_t size) {
|
||||
return recv(this->socketDescriptor, (void *) buffer, size, 0);
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <netinet/in.h>
|
||||
|
||||
namespace ts {
|
||||
namespace connection {
|
||||
class FilteredUdpSocket {
|
||||
public:
|
||||
FilteredUdpSocket();
|
||||
~FilteredUdpSocket();
|
||||
|
||||
bool setup(sockaddr_in*);
|
||||
void close();
|
||||
|
||||
ssize_t read(char* buffer, size_t size);
|
||||
ssize_t write(const char* buffer, size_t size);
|
||||
|
||||
int getSocketDescriptor(){ return socketDescriptor; }
|
||||
private:
|
||||
int socketDescriptor;
|
||||
sockaddr_in* remoteAdress = nullptr;
|
||||
sockaddr_in* localAdress = nullptr;
|
||||
};
|
||||
|
||||
typedef FilteredUdpSocket UdpSocket;
|
||||
}
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
//
|
||||
// Created by wolverindev on 12.10.17.
|
||||
//
|
||||
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
#include <ifaddrs.h>
|
||||
#include <netinet/udp.h> //Provides declarations for udp header
|
||||
#include <netinet/ip.h> //Provides declarations for ip header
|
||||
#include <arpa/inet.h>
|
||||
#include "RawUDPSocket.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace ts::connection;
|
||||
|
||||
/*
|
||||
96 bit (12 bytes) pseudo header needed for udp header checksum calculation
|
||||
*/
|
||||
struct pseudo_header
|
||||
{
|
||||
u_int32_t source_address;
|
||||
u_int32_t dest_address;
|
||||
u_int8_t placeholder;
|
||||
u_int8_t protocol;
|
||||
u_int16_t udp_length;
|
||||
};
|
||||
|
||||
RawUdpSocket::RawUdpSocket() {}
|
||||
|
||||
RawUdpSocket::~RawUdpSocket() {}
|
||||
|
||||
uint16_t RawUdpSocket::buildCheckSum(uint16_t* buffer, size_t size) {
|
||||
register long sum;
|
||||
unsigned short oddbyte;
|
||||
register short answer;
|
||||
|
||||
sum = 0;
|
||||
while (size > 1) {
|
||||
sum += *buffer++;
|
||||
size -= 2;
|
||||
}
|
||||
if (size == 1) {
|
||||
oddbyte = 0;
|
||||
*((u_char *) &oddbyte) = *(u_char *) buffer;
|
||||
sum += oddbyte;
|
||||
}
|
||||
|
||||
sum = (sum >> 16) + (sum & 0xffff);
|
||||
sum = sum + (sum >> 16);
|
||||
answer = (short) ~sum;
|
||||
|
||||
return (answer);
|
||||
}
|
||||
|
||||
int RawUdpSocket::read(char *buffer, size_t size) {}
|
||||
|
||||
int RawUdpSocket::write(const char *buffer, size_t size) {
|
||||
char datagram[4096];
|
||||
memset(datagram, 0, 4096);
|
||||
memcpy(&datagram[sizeof(iphdr) + sizeof(udphdr)], buffer, size);
|
||||
|
||||
//IP header
|
||||
struct iphdr *iph = (struct iphdr *) datagram;
|
||||
//UDP header
|
||||
struct udphdr *udph = (struct udphdr *) (datagram + sizeof(struct ip));
|
||||
|
||||
//Setup the ip header
|
||||
iph->ihl = 5;
|
||||
iph->version = 4;
|
||||
iph->tos = 0;
|
||||
iph->tot_len = sizeof(iphdr) + sizeof(udphdr) + size; //Total length
|
||||
iph->id = htonl(12); //TODO increase
|
||||
iph->frag_off = 0;
|
||||
iph->ttl = 255; //Max 255 hops maybe change it to default 64
|
||||
iph->protocol = IPPROTO_UDP;
|
||||
iph->saddr = localAdress->sin_addr.s_addr;
|
||||
iph->daddr = remoteAdress->sin_addr.s_addr;
|
||||
iph->check = this->buildCheckSum ((unsigned short *) datagram, iph->tot_len);
|
||||
|
||||
udph->source = localAdress->sin_port;
|
||||
udph->dest = remoteAdress->sin_port;
|
||||
udph->len = htons(8 + size);
|
||||
|
||||
size_t csumLength = sizeof(struct pseudo_header) + sizeof(struct udphdr) + size;
|
||||
char csumData[csumLength];
|
||||
|
||||
pseudo_header psh;
|
||||
//Now the UDP checksum using the pseudo header
|
||||
psh.source_address = localAdress->sin_addr.s_addr;
|
||||
psh.dest_address = remoteAdress->sin_addr.s_addr;
|
||||
psh.placeholder = 0;
|
||||
psh.protocol = IPPROTO_UDP;
|
||||
psh.udp_length = htons(sizeof(struct udphdr) + size);
|
||||
memcpy(csumData , (char*) &psh , sizeof (struct pseudo_header));
|
||||
memcpy(csumData + sizeof(struct pseudo_header) , udph , sizeof(struct udphdr) + size);
|
||||
|
||||
udph->check = buildCheckSum((uint16_t *) csumData, csumLength);
|
||||
|
||||
auto written = sendto(this->socketDescriptor, datagram, iph->tot_len, 0, (struct sockaddr *) this->remoteAdress, sizeof(sockaddr));
|
||||
if(written != iph->tot_len){
|
||||
cerr << "Invalid write: " << written << endl;
|
||||
return -1;
|
||||
}
|
||||
cout << "Write: " << written << endl;
|
||||
return size;
|
||||
}
|
||||
|
||||
bool RawUdpSocket::setup(sockaddr_in *remoteAdress) {
|
||||
this->remoteAdress = new sockaddr_in;
|
||||
memcpy(this->remoteAdress, remoteAdress, sizeof(sockaddr_in));
|
||||
this->socketDescriptor = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
|
||||
if(this->socketDescriptor < 0){
|
||||
if(this->socketDescriptor == EPERM){
|
||||
cerr << "Invalid permission. Dont have permission to create a new RAW socket!";
|
||||
return false;
|
||||
}
|
||||
cerr << "Invalid socket create: " << errno << " - " << this->socketDescriptor << " -> " << strerror(errno) << endl;
|
||||
}
|
||||
|
||||
//get local addr
|
||||
this->localAdress = new sockaddr_in;
|
||||
struct ifaddrs *ifAddrStruct = NULL;
|
||||
getifaddrs(&ifAddrStruct);
|
||||
|
||||
for (ifaddrs* ifa = ifAddrStruct; ifa != NULL; ifa = ifa->ifa_next) {
|
||||
if (!ifa->ifa_addr) {
|
||||
continue;
|
||||
}
|
||||
if (ifa->ifa_addr->sa_family == AF_INET) { // check it is IP4
|
||||
// is a valid IP4 Address
|
||||
memcpy(this->localAdress, ifa->ifa_addr, sizeof(sockaddr_in));
|
||||
/*
|
||||
tmpAddrPtr=&((struct sockaddr_in *)ifa->ifa_addr)->sin_addr;
|
||||
char addressBuffer[INET_ADDRSTRLEN];
|
||||
inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN);
|
||||
*/
|
||||
}
|
||||
/*
|
||||
else if (ifa->ifa_addr->sa_family == AF_INET6) { // check it is IP6
|
||||
// is a valid IP6 Address
|
||||
tmpAddrPtr=&((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
|
||||
char addressBuffer[INET6_ADDRSTRLEN];
|
||||
inet_ntop(AF_INET6, tmpAddrPtr, addressBuffer, INET6_ADDRSTRLEN);
|
||||
}
|
||||
*/
|
||||
}
|
||||
if (ifAddrStruct != NULL) freeifaddrs(ifAddrStruct);
|
||||
this->localAdress->sin_port = 1232;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <netinet/in.h>
|
||||
|
||||
namespace ts {
|
||||
namespace connection {
|
||||
class RawUdpSocket {
|
||||
public:
|
||||
RawUdpSocket();
|
||||
~RawUdpSocket();
|
||||
|
||||
bool setup(sockaddr_in*);
|
||||
|
||||
int read(char* buffer, size_t size);
|
||||
int write(const char* buffer, size_t size);
|
||||
uint16_t buildCheckSum(uint16_t* buffer, size_t size);
|
||||
|
||||
int getSocketDescriptor(){ return socketDescriptor; }
|
||||
private:
|
||||
int socketDescriptor;
|
||||
sockaddr_in* remoteAdress = nullptr;
|
||||
sockaddr_in* localAdress = nullptr;
|
||||
};
|
||||
|
||||
|
||||
typedef RawUdpSocket UdpSocket;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
if (NOT TARGET Gabime::Spdlog)
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_path(SPDLOG_INCLUDE_DIR NAMES spdlog/spdlog.h)
|
||||
find_package_handle_standard_args(Spdlog DEFAULT_MSG SPDLOG_INCLUDE_DIR)
|
||||
add_library(spdlog INTERFACE)
|
||||
target_include_directories(spdlog INTERFACE ${SPDLOG_INCLUDE_DIR})
|
||||
add_library(Gabime::Spdlog ALIAS spdlog)
|
||||
endif ()
|
||||
@@ -0,0 +1,57 @@
|
||||
# - Try to find mysql include dirs and libraries
|
||||
#
|
||||
# Usage of this module as follows:
|
||||
#
|
||||
# find_package(mysql)
|
||||
#
|
||||
# Variables used by this module, they can change the default behaviour and need
|
||||
# to be set before calling find_package:
|
||||
#
|
||||
# mysql_ROOT_DIR Set this variable to the root installation of
|
||||
# mysql if the module has problems finding the
|
||||
# proper installation path.
|
||||
#
|
||||
# Variables defined by this module:
|
||||
#
|
||||
# mysql_FOUND System has mysql, include and library dirs found
|
||||
# mysql_INCLUDE_DIR The mysql include directories.
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
|
||||
function(find_mysql)
|
||||
find_path(mysql_ROOT_DIR
|
||||
NAMES include/mysql.h include/mysql_version.h
|
||||
HINTS ${mysql_ROOT_DIR}
|
||||
)
|
||||
|
||||
find_path(mysql_INCLUDE_DIR
|
||||
NAMES mysql.h mysql_version.h
|
||||
HINTS ${mysql_ROOT_DIR}/include/
|
||||
)
|
||||
|
||||
if (NOT TARGET mysql::client::static)
|
||||
find_library(MYSQL_CLIENT_STATIC
|
||||
NAMES mysql.lib libmysqlclient.a
|
||||
HINTS ${mysql_ROOT_DIR} ${mysql_ROOT_DIR}/lib
|
||||
)
|
||||
|
||||
if(MYSQL_CLIENT_STATIC)
|
||||
add_library(mysql::client::static STATIC IMPORTED)
|
||||
set_target_properties(mysql::client::static PROPERTIES
|
||||
IMPORTED_LOCATION ${MYSQL_CLIENT_STATIC}
|
||||
INTERFACE_INCLUDE_DIRECTORIES ${mysql_INCLUDE_DIR}
|
||||
)
|
||||
endif()
|
||||
endif ()
|
||||
|
||||
find_package_handle_standard_args(mysql DEFAULT_MSG
|
||||
mysql_INCLUDE_DIR
|
||||
)
|
||||
|
||||
mark_as_advanced(
|
||||
mysql_ROOT_DIR
|
||||
mysql_INCLUDE_DIR
|
||||
MYSQL_CLIENT_STATIC
|
||||
)
|
||||
endfunction()
|
||||
find_mysql()
|
||||
@@ -0,0 +1,41 @@
|
||||
cmake_minimum_required(VERSION 3.6)
|
||||
project(TeaSpeak-Files)
|
||||
|
||||
#set(CMAKE_CXX_STANDARD 17)
|
||||
#set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
add_library(TeaSpeak-FileServer STATIC
|
||||
local_server/LocalFileProvider.cpp
|
||||
local_server/LocalFileSystem.cpp
|
||||
local_server/LocalFileTransfer.cpp
|
||||
local_server/LocalFileTransferClientWorker.cpp
|
||||
local_server/LocalFileTransferDisk.cpp
|
||||
local_server/LocalFileTransferNetwork.cpp
|
||||
local_server/clnpath.cpp
|
||||
local_server/NetTools.cpp
|
||||
local_server/Config.cpp
|
||||
local_server/HTTPUtils.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(TeaSpeak-FileServer PUBLIC TeaSpeak ${StringVariable_LIBRARIES_STATIC} stdc++fs
|
||||
libevent::core libevent::pthreads
|
||||
# We're not linking this here, since we may later use DataPipes::shared linking
|
||||
# DataPipes::core::static
|
||||
openssl::ssl::shared
|
||||
openssl::crypto::shared
|
||||
)
|
||||
|
||||
target_include_directories(TeaSpeak-FileServer PUBLIC include/)
|
||||
target_compile_options(TeaSpeak-FileServer PUBLIC "-Wswitch-enum")
|
||||
|
||||
add_executable(TeaSpeak-FileServerTest test/main.cpp)
|
||||
target_link_libraries(TeaSpeak-FileServerTest PUBLIC TeaSpeak-FileServer
|
||||
TeaMusic #Static (Must be in here, so we link against TeaMusic which uses C++11. That forbids GCC to use the newer glibc version)
|
||||
CXXTerminal::static
|
||||
DataPipes::core::static
|
||||
stdc++fs
|
||||
)
|
||||
target_compile_options(TeaSpeak-FileServerTest PUBLIC -static-libgcc -static-libstdc++)
|
||||
|
||||
add_executable(FileServer-CLNText local_server/clnpath.cpp)
|
||||
target_compile_definitions(FileServer-CLNText PUBLIC -DCLN_EXEC)
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <pipes/ssl.h>
|
||||
|
||||
namespace ts::server::file::config {
|
||||
enum struct Key {
|
||||
SSL_OPTION_SUPPLIER
|
||||
};
|
||||
|
||||
extern void value_updated(Key /* value */);
|
||||
extern std::function<std::shared_ptr<pipes::SSL::Options>()> ssl_option_supplier;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
namespace ts::server::file {
|
||||
enum struct ExecuteStatus {
|
||||
UNKNOWN,
|
||||
WAITING,
|
||||
SUCCESS,
|
||||
ERROR
|
||||
};
|
||||
|
||||
template<typename VariantType, typename T, std::size_t index = 0>
|
||||
constexpr std::size_t variant_index() {
|
||||
if constexpr (index == std::variant_size_v<VariantType>) {
|
||||
return index;
|
||||
} else if constexpr (std::is_same_v<std::variant_alternative_t<index, VariantType>, T>) {
|
||||
return index;
|
||||
} else {
|
||||
return variant_index<VariantType, T, index + 1>();
|
||||
}
|
||||
}
|
||||
|
||||
struct EmptyExecuteResponse { };
|
||||
template <class error_t, class response_t = EmptyExecuteResponse>
|
||||
class ExecuteResponse {
|
||||
typedef std::variant<EmptyExecuteResponse, error_t, response_t> variant_t;
|
||||
public:
|
||||
ExecuteStatus status{ExecuteStatus::WAITING};
|
||||
|
||||
[[nodiscard]] inline auto response() const -> const response_t& { return std::get<response_t>(this->response_); }
|
||||
|
||||
template <typename = std::enable_if_t<!std::is_void<error_t>::value>>
|
||||
[[nodiscard]] inline const error_t& error() const { return std::get<error_t>(this->response_); }
|
||||
|
||||
inline void wait() const {
|
||||
std::unique_lock nlock{this->notify_mutex};
|
||||
this->notify_cv.wait(nlock, [&]{ return this->status != ExecuteStatus::WAITING; });
|
||||
}
|
||||
|
||||
template<typename _Rep, typename _Period>
|
||||
[[nodiscard]] inline bool wait_for(const std::chrono::duration<_Rep, _Period>& time) const {
|
||||
std::unique_lock nlock{this->notify_mutex};
|
||||
return this->notify_cv.wait_for(nlock, time, [&]{ return this->status != ExecuteStatus::WAITING; });
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
inline void emplace_success(Args&&... args) {
|
||||
constexpr auto success_index = variant_index<variant_t, response_t>();
|
||||
|
||||
std::lock_guard rlock{this->notify_mutex};
|
||||
this->response_.template emplace<success_index, Args...>(std::forward<Args>(args)...);
|
||||
this->status = ExecuteStatus::SUCCESS;
|
||||
this->notify_cv.notify_all();
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
inline void emplace_fail(Args&&... args) {
|
||||
constexpr auto error_index = variant_index<variant_t, error_t>();
|
||||
|
||||
std::lock_guard rlock{this->notify_mutex};
|
||||
this->response_.template emplace<error_index, Args...>(std::forward<Args>(args)...);
|
||||
this->status = ExecuteStatus::ERROR;
|
||||
this->notify_cv.notify_all();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool succeeded() const {
|
||||
return this->status == ExecuteStatus::SUCCESS;
|
||||
}
|
||||
|
||||
ExecuteResponse(std::mutex& notify_mutex, std::condition_variable& notify_cv)
|
||||
: notify_mutex{notify_mutex}, notify_cv{notify_cv} {}
|
||||
private:
|
||||
variant_t response_{}; /* void* as default value so we don't initialize error_t or response_t */
|
||||
|
||||
std::mutex& notify_mutex;
|
||||
std::condition_variable& notify_cv;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,420 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
#include <Definitions.h>
|
||||
#include <condition_variable>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <atomic>
|
||||
|
||||
#include "./ExecuteResponse.h"
|
||||
|
||||
#define TRANSFER_KEY_LENGTH (32)
|
||||
#define TRANSFER_MEDIA_BYTES_LENGTH (32)
|
||||
|
||||
namespace ts::server::file {
|
||||
class VirtualFileServer;
|
||||
|
||||
namespace filesystem {
|
||||
template <typename ErrorCodes>
|
||||
struct DetailedError {
|
||||
ErrorCodes error_type{ErrorCodes::UNKNOWN};
|
||||
std::string error_message{};
|
||||
|
||||
DetailedError(ErrorCodes type, std::string extraMessage) : error_type{type}, error_message{std::move(extraMessage)} {}
|
||||
};
|
||||
|
||||
enum struct DirectoryQueryErrorType {
|
||||
UNKNOWN,
|
||||
PATH_EXCEEDS_ROOT_PATH,
|
||||
PATH_IS_A_FILE,
|
||||
PATH_DOES_NOT_EXISTS,
|
||||
FAILED_TO_LIST_FILES
|
||||
};
|
||||
constexpr std::array<std::string_view, 5> directory_query_error_messages = {
|
||||
"unknown error",
|
||||
"path exceeds base path",
|
||||
"path is a file",
|
||||
"path does not exists",
|
||||
"failed to list files"
|
||||
};
|
||||
|
||||
typedef DetailedError<DirectoryQueryErrorType> DirectoryQueryError;
|
||||
|
||||
struct DirectoryEntry {
|
||||
enum Type {
|
||||
UNKNOWN,
|
||||
DIRECTORY,
|
||||
FILE
|
||||
};
|
||||
|
||||
Type type{Type::UNKNOWN};
|
||||
std::string name{};
|
||||
std::chrono::system_clock::time_point modified_at{};
|
||||
|
||||
size_t size{0}; /* file only */
|
||||
bool empty{false}; /* directory only */
|
||||
};
|
||||
|
||||
enum struct DirectoryModifyErrorType {
|
||||
UNKNOWN,
|
||||
PATH_EXCEEDS_ROOT_PATH,
|
||||
PATH_ALREADY_EXISTS,
|
||||
FAILED_TO_CREATE_DIRECTORIES
|
||||
};
|
||||
typedef DetailedError<DirectoryModifyErrorType> DirectoryModifyError;
|
||||
|
||||
enum struct FileModifyErrorType {
|
||||
UNKNOWN,
|
||||
PATH_EXCEEDS_ROOT_PATH,
|
||||
TARGET_PATH_EXCEEDS_ROOT_PATH,
|
||||
PATH_DOES_NOT_EXISTS,
|
||||
TARGET_PATH_ALREADY_EXISTS,
|
||||
FAILED_TO_DELETE_FILES,
|
||||
FAILED_TO_RENAME_FILE,
|
||||
FAILED_TO_CREATE_DIRECTORIES,
|
||||
|
||||
SOME_FILES_ARE_LOCKED
|
||||
};
|
||||
typedef DetailedError<FileModifyErrorType> FileModifyError;
|
||||
|
||||
enum struct FileDeleteErrorType {
|
||||
UNKNOWN,
|
||||
};
|
||||
typedef DetailedError<FileDeleteErrorType> FileDeleteError;
|
||||
|
||||
struct FileDeleteResponse {
|
||||
enum struct StatusType {
|
||||
SUCCESS,
|
||||
|
||||
PATH_EXCEEDS_ROOT_PATH,
|
||||
PATH_DOES_NOT_EXISTS,
|
||||
FAILED_TO_DELETE_FILES,
|
||||
SOME_FILES_ARE_LOCKED
|
||||
};
|
||||
|
||||
struct DeleteResult {
|
||||
StatusType status{StatusType::SUCCESS};
|
||||
std::string error_detail{};
|
||||
|
||||
DeleteResult(StatusType status, std::string errorDetail) : status{status},
|
||||
error_detail{std::move(errorDetail)} {}
|
||||
};
|
||||
|
||||
std::vector<DeleteResult> delete_results{};
|
||||
};
|
||||
|
||||
enum struct ServerCommandErrorType {
|
||||
UNKNOWN,
|
||||
FAILED_TO_CREATE_DIRECTORIES,
|
||||
FAILED_TO_DELETE_DIRECTORIES
|
||||
};
|
||||
typedef DetailedError<ServerCommandErrorType> ServerCommandError;
|
||||
|
||||
struct FileInfoResponse {
|
||||
enum struct StatusType {
|
||||
SUCCESS,
|
||||
|
||||
PATH_EXCEEDS_ROOT_PATH,
|
||||
PATH_DOES_NOT_EXISTS,
|
||||
|
||||
FAILED_TO_QUERY_INFO,
|
||||
UNKNOWN_FILE_TYPE
|
||||
};
|
||||
|
||||
struct FileInfo {
|
||||
StatusType status{StatusType::SUCCESS};
|
||||
std::string error_detail{};
|
||||
|
||||
DirectoryEntry info{};
|
||||
|
||||
FileInfo(StatusType status, std::string errorDetail, DirectoryEntry info) : status{status},
|
||||
error_detail{std::move(errorDetail)}, info{std::move(info)} {}
|
||||
};
|
||||
|
||||
std::vector<FileInfo> file_info{};
|
||||
};
|
||||
|
||||
enum struct FileInfoErrorType {
|
||||
UNKNOWN,
|
||||
};
|
||||
typedef DetailedError<FileInfoErrorType> FileInfoError;
|
||||
|
||||
class AbstractProvider {
|
||||
public:
|
||||
typedef ExecuteResponse<DirectoryQueryError, std::deque<DirectoryEntry>> directory_query_response_t;
|
||||
|
||||
/* server */
|
||||
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<ServerCommandError>> initialize_server(const std::shared_ptr<VirtualFileServer> &/* server */) = 0;
|
||||
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<ServerCommandError>> delete_server(const std::shared_ptr<VirtualFileServer> &/* server */) = 0;
|
||||
|
||||
/* channels */
|
||||
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> query_channel_info(const std::shared_ptr<VirtualFileServer> &/* server */, const std::vector<std::tuple<ChannelId, std::string>>& /* files */) = 0;
|
||||
[[nodiscard]] virtual std::shared_ptr<directory_query_response_t> query_channel_directory(const std::shared_ptr<VirtualFileServer> &/* server */, ChannelId /* channel */, const std::string& /* path */) = 0;
|
||||
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<DirectoryModifyError>> create_channel_directory(const std::shared_ptr<VirtualFileServer> &/* server */, ChannelId /* channel */, const std::string& /* path */) = 0;
|
||||
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> delete_channel_files(const std::shared_ptr<VirtualFileServer> &/* server */, ChannelId /* channel */, const std::vector<std::string>& /* paths */) = 0;
|
||||
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileModifyError>> rename_channel_file(const std::shared_ptr<VirtualFileServer> &/* server */, ChannelId /* channel */, const std::string& /* path */, ChannelId /* target channel */, const std::string& /* target */) = 0;
|
||||
|
||||
/* icons */
|
||||
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> query_icon_info(const std::shared_ptr<VirtualFileServer> &/* server */, const std::vector<std::string>& /* names */) = 0;
|
||||
[[nodiscard]] virtual std::shared_ptr<directory_query_response_t> query_icon_directory(const std::shared_ptr<VirtualFileServer> &/* server */) = 0;
|
||||
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> delete_icons(const std::shared_ptr<VirtualFileServer> &/* server */, const std::vector<std::string>& /* names */) = 0;
|
||||
|
||||
/* avatars */
|
||||
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> query_avatar_info(const std::shared_ptr<VirtualFileServer> &/* server */, const std::vector<std::string>& /* names */) = 0;
|
||||
[[nodiscard]] virtual std::shared_ptr<directory_query_response_t> query_avatar_directory(const std::shared_ptr<VirtualFileServer> &/* server */) = 0;
|
||||
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> delete_avatars(const std::shared_ptr<VirtualFileServer> &/* server */, const std::vector<std::string>& /* names */) = 0;
|
||||
private:
|
||||
};
|
||||
}
|
||||
|
||||
namespace transfer {
|
||||
typedef uint16_t transfer_id;
|
||||
|
||||
struct Transfer {
|
||||
transfer_id server_transfer_id{0};
|
||||
transfer_id client_transfer_id{0};
|
||||
|
||||
std::shared_ptr<VirtualFileServer> server{nullptr};
|
||||
ChannelId channel_id{0};
|
||||
|
||||
ClientId client_id{0};
|
||||
std::string client_unique_id{};
|
||||
|
||||
char transfer_key[TRANSFER_KEY_LENGTH]{};
|
||||
std::chrono::system_clock::time_point initialized_timestamp{};
|
||||
enum Direction {
|
||||
DIRECTION_UNKNOWN,
|
||||
DIRECTION_UPLOAD,
|
||||
DIRECTION_DOWNLOAD
|
||||
} direction{DIRECTION_UNKNOWN};
|
||||
|
||||
struct Address {
|
||||
std::string hostname{};
|
||||
uint16_t port{0};
|
||||
};
|
||||
std::vector<Address> server_addresses{};
|
||||
|
||||
enum TargetType {
|
||||
TARGET_TYPE_UNKNOWN,
|
||||
TARGET_TYPE_CHANNEL_FILE,
|
||||
TARGET_TYPE_ICON,
|
||||
TARGET_TYPE_AVATAR
|
||||
} target_type{TARGET_TYPE_UNKNOWN};
|
||||
std::string target_file_path{};
|
||||
std::string absolute_file_path{};
|
||||
|
||||
std::string relative_file_path{};
|
||||
std::string file_name{};
|
||||
|
||||
int64_t max_bandwidth{-1};
|
||||
size_t expected_file_size{0}; /* incl. the offset! */
|
||||
size_t file_offset{0};
|
||||
bool override_exiting{false};
|
||||
};
|
||||
|
||||
struct TransferStatistics {
|
||||
uint64_t network_bytes_send{0};
|
||||
uint64_t network_bytes_received{0};
|
||||
|
||||
uint64_t delta_network_bytes_send{0};
|
||||
uint64_t delta_network_bytes_received{0};
|
||||
|
||||
uint64_t file_bytes_transferred{0};
|
||||
uint64_t delta_file_bytes_transferred{0};
|
||||
|
||||
size_t file_start_offset{0};
|
||||
size_t file_current_offset{0};
|
||||
size_t file_total_size{0};
|
||||
|
||||
double average_speed{0};
|
||||
double current_speed{0};
|
||||
};
|
||||
|
||||
struct TransferInitError {
|
||||
enum Type {
|
||||
UNKNOWN,
|
||||
|
||||
INVALID_FILE_TYPE,
|
||||
FILE_DOES_NOT_EXISTS,
|
||||
FILE_IS_NOT_A_FILE,
|
||||
|
||||
CLIENT_TOO_MANY_TRANSFERS,
|
||||
SERVER_TOO_MANY_TRANSFERS,
|
||||
|
||||
SERVER_QUOTA_EXCEEDED,
|
||||
CLIENT_QUOTA_EXCEEDED,
|
||||
|
||||
IO_ERROR
|
||||
} error_type{UNKNOWN};
|
||||
std::string error_message{};
|
||||
|
||||
TransferInitError(Type errorType, std::string errorMessage) : error_type{errorType},
|
||||
error_message{std::move(errorMessage)} {}
|
||||
};
|
||||
|
||||
struct TransferActionError {
|
||||
enum Type {
|
||||
UNKNOWN,
|
||||
|
||||
UNKNOWN_TRANSFER
|
||||
} error_type{UNKNOWN};
|
||||
std::string error_message{};
|
||||
};
|
||||
|
||||
struct TransferError {
|
||||
enum Type {
|
||||
UNKNOWN,
|
||||
|
||||
TRANSFER_TIMEOUT,
|
||||
|
||||
DISK_IO_ERROR,
|
||||
DISK_TIMEOUT,
|
||||
DISK_INITIALIZE_ERROR,
|
||||
|
||||
NETWORK_IO_ERROR,
|
||||
|
||||
UNEXPECTED_CLIENT_DISCONNECT,
|
||||
UNEXPECTED_DISK_EOF,
|
||||
|
||||
USER_REQUEST
|
||||
} error_type{UNKNOWN};
|
||||
std::string error_message{};
|
||||
};
|
||||
|
||||
struct ActiveFileTransfer {
|
||||
transfer_id client_transfer_id{0};
|
||||
transfer_id server_transfer_id{0};
|
||||
|
||||
Transfer::Direction direction{Transfer::DIRECTION_UNKNOWN};
|
||||
|
||||
ClientId client_id{};
|
||||
std::string client_unique_id{};
|
||||
|
||||
std::string file_path{};
|
||||
std::string file_name{};
|
||||
|
||||
size_t expected_size{};
|
||||
size_t size_done{};
|
||||
|
||||
enum Status {
|
||||
NOT_STARTED,
|
||||
RUNNING,
|
||||
INACTIVE /* (not used yet) */
|
||||
} status{Status::NOT_STARTED};
|
||||
|
||||
std::chrono::milliseconds runtime{};
|
||||
|
||||
double average_speed{0};
|
||||
double current_speed{0};
|
||||
};
|
||||
|
||||
enum struct TransferListError {
|
||||
UNKNOWN
|
||||
};
|
||||
|
||||
class AbstractProvider {
|
||||
public:
|
||||
struct TransferInfo {
|
||||
std::string file_path{};
|
||||
std::string client_unique_id{};
|
||||
ClientId client_id{};
|
||||
|
||||
bool override_exiting{false}; /* only for upload valid */
|
||||
|
||||
size_t file_offset{0};
|
||||
size_t expected_file_size{0};
|
||||
|
||||
int64_t max_bandwidth{-1};
|
||||
int64_t max_concurrent_transfers{-1};
|
||||
|
||||
/* only used for upload, for download the quotas could be checked before */
|
||||
size_t download_server_quota_limit{(size_t) -1};
|
||||
size_t download_client_quota_limit{(size_t) -1};
|
||||
};
|
||||
|
||||
virtual std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
initialize_channel_transfer(Transfer::Direction /* direction */, const std::shared_ptr<VirtualFileServer>& /* server */, ChannelId /* channel */, const TransferInfo& /* info */) = 0;
|
||||
|
||||
virtual std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
initialize_icon_transfer(Transfer::Direction /* direction */, const std::shared_ptr<VirtualFileServer>& /* server */, const TransferInfo& /* info */) = 0;
|
||||
|
||||
virtual std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
initialize_avatar_transfer(Transfer::Direction /* direction */, const std::shared_ptr<VirtualFileServer>& /* server */, const TransferInfo& /* info */) = 0;
|
||||
|
||||
virtual std::shared_ptr<ExecuteResponse<TransferListError, std::vector<ActiveFileTransfer>>> list_transfer() = 0;
|
||||
|
||||
virtual std::shared_ptr<ExecuteResponse<TransferActionError>> stop_transfer(const std::shared_ptr<VirtualFileServer>& /* server */, transfer_id /* id */, bool /* flush */) = 0;
|
||||
|
||||
std::function<void(const std::shared_ptr<Transfer>&)> callback_transfer_registered{}; /* transfer has been registered */
|
||||
std::function<void(const std::shared_ptr<Transfer>&)> callback_transfer_started{}; /* transfer has been started */
|
||||
std::function<void(const std::shared_ptr<Transfer>&)> callback_transfer_finished{}; /* transfer has been finished */
|
||||
std::function<void(const std::shared_ptr<Transfer>&, const TransferStatistics&)> callback_transfer_statistics{};
|
||||
std::function<void(const std::shared_ptr<Transfer>&, const transfer::TransferStatistics&, const TransferError&)> callback_transfer_aborted{}; /* an error happened while transferring the data */
|
||||
};
|
||||
}
|
||||
|
||||
class VirtualFileServer {
|
||||
public:
|
||||
explicit VirtualFileServer(ServerId server_id, std::string unique_id) : server_id_{server_id}, unique_id_{std::move(unique_id)} {}
|
||||
|
||||
[[nodiscard]] inline auto unique_id() const -> const std::string& { return this->unique_id_; }
|
||||
[[nodiscard]] inline auto server_id() const -> ServerId { return this->server_id_; }
|
||||
|
||||
[[nodiscard]] inline auto max_networking_upload_bandwidth() const -> int64_t { return this->max_networking_upload_bandwidth_; }
|
||||
virtual void max_networking_upload_bandwidth(int64_t value) {
|
||||
this->max_networking_upload_bandwidth_ = value;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline auto max_networking_download_bandwidth() const -> int64_t { return this->max_networking_download_bandwidth_; }
|
||||
virtual void max_networking_download_bandwidth(int64_t value) {
|
||||
this->max_networking_download_bandwidth_ = value;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline auto generate_transfer_id() {
|
||||
return ++this->current_transfer_id;
|
||||
}
|
||||
private:
|
||||
ServerId server_id_;
|
||||
std::string unique_id_;
|
||||
|
||||
int64_t max_networking_upload_bandwidth_{-1};
|
||||
int64_t max_networking_download_bandwidth_{-1};
|
||||
|
||||
std::atomic<transfer::transfer_id> current_transfer_id{0};
|
||||
};
|
||||
|
||||
class AbstractFileServer {
|
||||
public:
|
||||
[[nodiscard]] virtual std::string file_base_path() const = 0;
|
||||
[[nodiscard]] virtual filesystem::AbstractProvider& file_system() = 0;
|
||||
[[nodiscard]] virtual transfer::AbstractProvider& file_transfer() = 0;
|
||||
|
||||
[[nodiscard]] inline auto virtual_servers() const -> std::deque<std::shared_ptr<VirtualFileServer>> {
|
||||
std::lock_guard slock{this->servers_mutex};
|
||||
return this->servers_;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline auto find_virtual_server(ServerId server_id) const -> std::shared_ptr<VirtualFileServer> {
|
||||
std::lock_guard slock{this->servers_mutex};
|
||||
auto it = std::find_if(this->servers_.begin(), this->servers_.end(), [&](const std::shared_ptr<VirtualFileServer>& server) {
|
||||
return server->server_id() == server_id;
|
||||
});
|
||||
return it == this->servers_.end() ? nullptr : *it;
|
||||
}
|
||||
|
||||
virtual std::shared_ptr<VirtualFileServer> register_server(ServerId /* server id */) = 0;
|
||||
virtual void unregister_server(ServerId /* server id */) = 0;
|
||||
protected:
|
||||
mutable std::mutex servers_mutex{};
|
||||
std::deque<std::shared_ptr<VirtualFileServer>> servers_{};
|
||||
};
|
||||
|
||||
extern bool initialize(std::string& /* error */, const std::string& /* host names */, uint16_t /* port */);
|
||||
extern void finalize();
|
||||
|
||||
extern std::shared_ptr<AbstractFileServer> server();
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// Created by WolverinDEV on 21/05/2020.
|
||||
//
|
||||
|
||||
#include "files/Config.h"
|
||||
|
||||
using namespace ts::server::file;
|
||||
|
||||
std::function<std::shared_ptr<pipes::SSL::Options>()> config::ssl_option_supplier{nullptr};
|
||||
|
||||
void config::value_updated(ts::server::file::config::Key) {
|
||||
/* we currently do nothing */
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// Created by WolverinDEV on 22/05/2020.
|
||||
//
|
||||
|
||||
#include <pipes/misc/http.h>
|
||||
#include "HTTPUtils.h"
|
||||
|
||||
bool http::parse_url_parameters(const std::string_view &query, std::map<std::string, std::string>& result) {
|
||||
const auto query_offset = query.find('?');
|
||||
if(query_offset == std::string::npos) return false;
|
||||
|
||||
const auto query_end_offset = query.find('#', query_offset); /* fragment (if there is any) */
|
||||
|
||||
auto offset = query_offset + 1;
|
||||
size_t next_param;
|
||||
while(offset > 0) {
|
||||
next_param = query.find('&', offset);
|
||||
if(next_param >= query_end_offset)
|
||||
next_param = query_end_offset;
|
||||
|
||||
if(offset >= next_param)
|
||||
break;
|
||||
|
||||
/* parameter: [offset;next_param) */
|
||||
const auto param_view = query.substr(offset, next_param - offset);
|
||||
const auto assignment_index = param_view.find('=');
|
||||
if(assignment_index == std::string::npos)
|
||||
result[std::string{param_view}] = "";
|
||||
else
|
||||
result[std::string{param_view.substr(0, assignment_index)}] = http::decode_url(std::string{param_view.substr(assignment_index + 1)});
|
||||
|
||||
offset = next_param + 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace http {
|
||||
bool parse_url_parameters(const std::string_view& /* url */, std::map<std::string, std::string>& /* result */);
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
//
|
||||
// Created by WolverinDEV on 28/04/2020.
|
||||
//
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <log/LogUtils.h>
|
||||
#include "LocalFileProvider.h"
|
||||
#include "LocalFileSystem.h"
|
||||
#include "LocalFileTransfer.h"
|
||||
|
||||
using namespace ts::server;
|
||||
using LocalFileServer = file::LocalFileProvider;
|
||||
using LocalVirtualFileServer = file::LocalVirtualFileServer;
|
||||
|
||||
std::shared_ptr<LocalFileServer> server_instance{};
|
||||
bool file::initialize(std::string &error, const std::string& hostnames, uint16_t port) {
|
||||
server_instance = std::make_shared<LocalFileProvider>();
|
||||
|
||||
if(!server_instance->initialize(error)) {
|
||||
server_instance = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool any_bind{false};
|
||||
for(const auto& binding : net::resolve_bindings(hostnames, port)) {
|
||||
if(!get<2>(binding).empty()) {
|
||||
logError(LOG_FT, "Failed to resolve binding for {}: {}", get<0>(binding), get<2>(binding));
|
||||
continue;
|
||||
}
|
||||
|
||||
auto result = dynamic_cast<transfer::LocalFileTransfer&>(server_instance->file_transfer()).add_network_binding({ get<0>(binding), get<1>(binding) });
|
||||
switch (result) {
|
||||
case transfer::NetworkingBindResult::SUCCESS:
|
||||
any_bind = true;
|
||||
break;
|
||||
|
||||
case transfer::NetworkingBindResult::OUT_OF_MEMORY:
|
||||
logWarning(LOG_FT, "Failed to listen to address {}: Out of memory", get<0>(binding));
|
||||
continue;
|
||||
|
||||
case transfer::NetworkingBindResult::FAILED_TO_LISTEN:
|
||||
logWarning(LOG_FT, "Failed to listen on {}: {}/{}", get<0>(binding), errno, strerror(errno));
|
||||
continue;
|
||||
|
||||
case transfer::NetworkingBindResult::FAILED_TO_BIND:
|
||||
logWarning(LOG_FT, "Failed to bind on {}: {}/{}", get<0>(binding), errno, strerror(errno));
|
||||
continue;
|
||||
|
||||
case transfer::NetworkingBindResult::BINDING_ALREADY_EXISTS:
|
||||
logWarning(LOG_FT, "Failed to bind on {}: binding already exists", get<0>(binding));
|
||||
continue;
|
||||
|
||||
case transfer::NetworkingBindResult::NETWORKING_NOT_INITIALIZED:
|
||||
logWarning(LOG_FT, "Failed to bind on {}: networking not initialized", get<0>(binding));
|
||||
continue;
|
||||
|
||||
case transfer::NetworkingBindResult::FAILED_TO_ALLOCATE_SOCKET:
|
||||
logWarning(LOG_FT, "Failed to allocate a socket for {}: {}/{}", get<0>(binding), errno, strerror(errno));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void file::finalize() {
|
||||
auto server = std::exchange(server_instance, nullptr);
|
||||
if(!server) return;
|
||||
|
||||
server->finalize();
|
||||
}
|
||||
|
||||
std::shared_ptr<file::AbstractFileServer> file::server() {
|
||||
return server_instance;
|
||||
}
|
||||
|
||||
LocalFileServer::LocalFileProvider() {
|
||||
this->file_system_ = new filesystem::LocalFileSystem();
|
||||
this->file_transfer_ = new transfer::LocalFileTransfer(this->file_system_);
|
||||
}
|
||||
LocalFileServer::~LocalFileProvider() {
|
||||
delete this->file_transfer_;
|
||||
delete this->file_system_;
|
||||
};
|
||||
|
||||
bool LocalFileServer::initialize(std::string &error) {
|
||||
if(!this->file_system_->initialize(error, "files/"))
|
||||
return false;
|
||||
|
||||
if(!this->file_transfer_->start()) {
|
||||
error = "transfer server startup failed";
|
||||
this->file_system_->finalize();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void LocalFileServer::finalize() {
|
||||
this->file_transfer_->stop();
|
||||
this->file_system_->finalize();
|
||||
}
|
||||
|
||||
file::filesystem::AbstractProvider &LocalFileServer::file_system() {
|
||||
return *this->file_system_;
|
||||
}
|
||||
|
||||
file::transfer::AbstractProvider &LocalFileServer::file_transfer() {
|
||||
return *this->file_transfer_;
|
||||
}
|
||||
|
||||
std::string file::LocalFileProvider::file_base_path() const {
|
||||
return this->file_system_->root_path();
|
||||
}
|
||||
|
||||
std::shared_ptr<file::VirtualFileServer> LocalFileServer::register_server(ServerId server_id) {
|
||||
auto server = this->find_virtual_server(server_id);
|
||||
if(server) return server;
|
||||
|
||||
server = std::make_shared<file::LocalVirtualFileServer>(server_id, std::to_string(server_id));
|
||||
{
|
||||
std::lock_guard slock{this->servers_mutex};
|
||||
this->servers_.push_back(server);
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
void LocalFileServer::unregister_server(ServerId server_id) {
|
||||
auto server_unique_id = std::to_string(server_id);
|
||||
|
||||
std::lock_guard slock{this->servers_mutex};
|
||||
auto it = std::find_if(this->servers_.begin(), this->servers_.end(), [&](const std::shared_ptr<VirtualFileServer>& server) {
|
||||
return server->unique_id() == server_unique_id;
|
||||
});
|
||||
|
||||
if(it == this->servers_.end()) return;
|
||||
this->servers_.erase(it);
|
||||
}
|
||||
|
||||
void LocalVirtualFileServer::max_networking_upload_bandwidth(int64_t value) {
|
||||
VirtualFileServer::max_networking_upload_bandwidth(value);
|
||||
this->upload_throttle.set_max_bandwidth(value);
|
||||
}
|
||||
|
||||
void LocalVirtualFileServer::max_networking_download_bandwidth(int64_t value) {
|
||||
VirtualFileServer::max_networking_download_bandwidth(value);
|
||||
this->download_throttle.set_max_bandwidth(value);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <files/FileServer.h>
|
||||
#include <deque>
|
||||
#include <utility>
|
||||
#include <thread>
|
||||
#include <shared_mutex>
|
||||
#include <sys/socket.h>
|
||||
#include <pipes/ws.h>
|
||||
#include <pipes/ssl.h>
|
||||
#include <misc/net.h>
|
||||
#include <misc/spin_mutex.h>
|
||||
#include <random>
|
||||
#include <misc/memtracker.h>
|
||||
#include "./NetTools.h"
|
||||
|
||||
namespace ts::server::file {
|
||||
namespace filesystem { class LocalFileSystem; }
|
||||
namespace transfer { class LocalFileTransfer; }
|
||||
|
||||
class LocalVirtualFileServer : public VirtualFileServer {
|
||||
public:
|
||||
explicit LocalVirtualFileServer(ServerId server_id, std::string unique_id) : VirtualFileServer{server_id, std::move(unique_id)} {}
|
||||
|
||||
void max_networking_upload_bandwidth(int64_t value) override;
|
||||
void max_networking_download_bandwidth(int64_t value) override;
|
||||
|
||||
networking::NetworkThrottle upload_throttle{};
|
||||
networking::NetworkThrottle download_throttle{};
|
||||
};
|
||||
|
||||
class LocalFileProvider : public AbstractFileServer {
|
||||
public:
|
||||
LocalFileProvider();
|
||||
virtual ~LocalFileProvider();
|
||||
|
||||
[[nodiscard]] bool initialize(std::string& /* error */);
|
||||
void finalize();
|
||||
|
||||
[[nodiscard]] std::string file_base_path() const override;
|
||||
|
||||
filesystem::AbstractProvider &file_system() override;
|
||||
transfer::AbstractProvider &file_transfer() override;
|
||||
|
||||
|
||||
std::shared_ptr<VirtualFileServer> register_server(ServerId /* server id */) override;
|
||||
void unregister_server(ServerId /* server id */) override;
|
||||
private:
|
||||
filesystem::LocalFileSystem* file_system_;
|
||||
transfer::LocalFileTransfer* file_transfer_;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,457 @@
|
||||
//
|
||||
// Created by WolverinDEV on 29/04/2020.
|
||||
//
|
||||
#include <experimental/filesystem>
|
||||
#define FS_INCLUDED
|
||||
|
||||
#include <log/LogUtils.h>
|
||||
#include "./LocalFileSystem.h"
|
||||
#include "./clnpath.h"
|
||||
|
||||
using namespace ts::server::file;
|
||||
using namespace ts::server::file::filesystem;
|
||||
namespace fs = std::experimental::filesystem;
|
||||
using directory_query_response_t = AbstractProvider::directory_query_response_t;
|
||||
|
||||
LocalFileSystem::~LocalFileSystem() = default;
|
||||
|
||||
bool LocalFileSystem::initialize(std::string &error_message, const std::string &root_path_string) {
|
||||
auto root_path = fs::u8path(root_path_string);
|
||||
std::error_code error{};
|
||||
|
||||
if(!fs::exists(root_path, error)) {
|
||||
if(error)
|
||||
logWarning(LOG_FT, "Failed to check root path existence. Assuming it does not exist. ({}/{})", error.value(), error.message());
|
||||
if(!fs::create_directories(root_path, error) || error) {
|
||||
error_message = "Failed to create root file system at " + root_path_string + ": " + std::to_string(error.value()) + "/" + error.message();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto croot = clnpath(fs::absolute(root_path).string());
|
||||
logMessage(LOG_FT, "Started file system root at {}", croot);
|
||||
this->root_path_ = croot;
|
||||
return true;
|
||||
}
|
||||
|
||||
void LocalFileSystem::finalize() {}
|
||||
|
||||
fs::path LocalFileSystem::server_path(const std::shared_ptr<VirtualFileServer> &server) {
|
||||
return fs::u8path(this->root_path_) / fs::u8path("server_" + std::to_string(server->server_id()));
|
||||
}
|
||||
|
||||
fs::path LocalFileSystem::server_channel_path(const std::shared_ptr<VirtualFileServer> &server, ts::ChannelId cid) {
|
||||
return this->server_path(server) / fs::u8path("channel_" + std::to_string(cid));
|
||||
}
|
||||
|
||||
bool LocalFileSystem::exceeds_base_path(const fs::path &base, const fs::path &target) {
|
||||
auto rel_target = clnpath(target.string());
|
||||
if(rel_target.starts_with("..")) return true;
|
||||
|
||||
auto base_string = clnpath(fs::absolute(base).string());
|
||||
auto target_string = clnpath(fs::absolute(target).string());
|
||||
return !target_string.starts_with(base_string);
|
||||
}
|
||||
|
||||
bool LocalFileSystem::is_any_file_locked(const fs::path &base, const std::string &path, std::string &locked_file) {
|
||||
auto c_path = clnpath(fs::absolute(base / fs::u8path(path)).string());
|
||||
|
||||
std::lock_guard lock{this->locked_files_mutex};
|
||||
for(const auto& lfile : this->locked_files_) {
|
||||
if(lfile.starts_with(c_path)) {
|
||||
locked_file = lfile.substr(base.string().length());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string LocalFileSystem::target_file_path(FileCategory type, const std::shared_ptr<VirtualFileServer> &server, ts::ChannelId cid, const std::string &path) {
|
||||
fs::path target_path{};
|
||||
switch (type) {
|
||||
case FileCategory::CHANNEL:
|
||||
target_path = this->server_channel_path(server, cid) / path;
|
||||
break;
|
||||
case FileCategory::ICON:
|
||||
target_path = this->server_path(server) / "icons" / path;
|
||||
break;
|
||||
case FileCategory::AVATAR:
|
||||
target_path = this->server_path(server) / "avatars" / path;
|
||||
break;
|
||||
}
|
||||
|
||||
return clnpath(fs::absolute(target_path).string());
|
||||
}
|
||||
|
||||
std::string LocalFileSystem::absolute_avatar_path(const std::shared_ptr<VirtualFileServer> &sid, const std::string &path) {
|
||||
return this->target_file_path(FileCategory::AVATAR, sid, 0, path);
|
||||
}
|
||||
|
||||
std::string LocalFileSystem::absolute_icon_path(const std::shared_ptr<VirtualFileServer> &sid, const std::string &path) {
|
||||
return this->target_file_path(FileCategory::ICON, sid, 0, path);
|
||||
}
|
||||
|
||||
std::string LocalFileSystem::absolute_channel_path(const std::shared_ptr<VirtualFileServer> &sid, ChannelId cid, const std::string &path) {
|
||||
return this->target_file_path(FileCategory::CHANNEL, sid, cid, path);
|
||||
}
|
||||
|
||||
void LocalFileSystem::lock_file(const std::string &c_path) {
|
||||
std::lock_guard lock{this->locked_files_mutex};
|
||||
this->locked_files_.push_back(c_path);
|
||||
}
|
||||
|
||||
void LocalFileSystem::unlock_file(const std::string &c_path) {
|
||||
std::lock_guard lock{this->locked_files_mutex};
|
||||
|
||||
this->locked_files_.erase(std::remove_if(this->locked_files_.begin(), this->locked_files_.end(), [&](const auto& p) { return p == c_path; }), this->locked_files_.end());
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<ServerCommandError>> LocalFileSystem::initialize_server(const std::shared_ptr<VirtualFileServer> &id) {
|
||||
auto path = this->server_path(id);
|
||||
std::error_code error{};
|
||||
|
||||
auto response = this->create_execute_response<ServerCommandError>();
|
||||
|
||||
if(!fs::exists(path, error)) {
|
||||
if(!fs::create_directories(path, error) || error) {
|
||||
response->emplace_fail(ServerCommandErrorType::FAILED_TO_CREATE_DIRECTORIES, std::to_string(error.value()) + "/" + error.message());
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Copy the default icon
|
||||
|
||||
response->emplace_success();
|
||||
return response;
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<ServerCommandError>> LocalFileSystem::delete_server(const std::shared_ptr<VirtualFileServer> &id) {
|
||||
auto path = this->server_path(id);
|
||||
std::error_code error{};
|
||||
|
||||
auto response = this->create_execute_response<ServerCommandError>();
|
||||
|
||||
//TODO: Stop all running file transfers!
|
||||
|
||||
if(fs::exists(path, error)) {
|
||||
if(!fs::remove_all(path, error) || error) {
|
||||
response->emplace_fail(ServerCommandErrorType::FAILED_TO_DELETE_DIRECTORIES, std::to_string(error.value()) + "/" + error.message());
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
response->emplace_success();
|
||||
return response;
|
||||
}
|
||||
|
||||
std::shared_ptr<directory_query_response_t> LocalFileSystem::query_directory(const fs::path &base,
|
||||
const std::string &path,
|
||||
bool allow_non_existance) {
|
||||
std::error_code error{};
|
||||
auto response = this->create_execute_response<DirectoryQueryError, std::deque<DirectoryEntry>>();
|
||||
auto target_path = base / fs::u8path(path);
|
||||
if(this->exceeds_base_path(base, target_path)) {
|
||||
response->emplace_fail(DirectoryQueryErrorType::PATH_EXCEEDS_ROOT_PATH, "");
|
||||
return response;
|
||||
}
|
||||
|
||||
if(!fs::exists(target_path, error)) {
|
||||
if(allow_non_existance)
|
||||
response->emplace_success();
|
||||
else
|
||||
response->emplace_fail(DirectoryQueryErrorType::PATH_DOES_NOT_EXISTS, "");
|
||||
return response;
|
||||
} else if(error) {
|
||||
logWarning(LOG_FT, "Failed to check for file at {}: {}. Assuming it does not exists.", target_path.string(), error.value(), error.message());
|
||||
response->emplace_fail(DirectoryQueryErrorType::PATH_DOES_NOT_EXISTS, "");
|
||||
return response;
|
||||
}
|
||||
|
||||
if(!fs::is_directory(target_path, error)) {
|
||||
response->emplace_fail(DirectoryQueryErrorType::PATH_IS_A_FILE, "");
|
||||
return response;
|
||||
} else if(error) {
|
||||
logWarning(LOG_FT, "Failed to check for directory at {}: {}. Assuming its not a directory.", target_path.string(), error.value(), error.message());
|
||||
response->emplace_fail(DirectoryQueryErrorType::PATH_IS_A_FILE, "");
|
||||
return response;
|
||||
}
|
||||
|
||||
std::deque<DirectoryEntry> entries{};
|
||||
for(auto& entry : fs::directory_iterator(target_path, error)) {
|
||||
auto status = entry.status(error);
|
||||
if(error) {
|
||||
logWarning(LOG_FT, "Failed to query file status for {} ({}/{}). Skipping entry for directory query.", entry.path().string(), error.value(), error.message());
|
||||
continue;
|
||||
}
|
||||
|
||||
if(status.type() == fs::file_type::directory) {
|
||||
auto& dentry = entries.emplace_back();
|
||||
dentry.type = DirectoryEntry::DIRECTORY;
|
||||
dentry.name = entry.path().filename();
|
||||
|
||||
dentry.empty = fs::is_empty(entry.path(), error);
|
||||
if(error)
|
||||
logWarning(LOG_FT, "Failed to query directory empty state for directory {} ({}/{})", target_path.string(), error.value(), error.message());
|
||||
|
||||
dentry.modified_at = fs::last_write_time(entry.path(), error);
|
||||
if(error)
|
||||
logWarning(LOG_FT, "Failed to query last write time for directory {} ({}/{})", entry.path().string(), error.value(), error.message());
|
||||
dentry.size = 0;
|
||||
} else if(status.type() == fs::file_type::regular) {
|
||||
auto& dentry = entries.emplace_back();
|
||||
dentry.type = DirectoryEntry::FILE;
|
||||
dentry.name = entry.path().filename();
|
||||
|
||||
dentry.modified_at = fs::last_write_time(entry.path(), error);
|
||||
if(error)
|
||||
logWarning(LOG_FT, "Failed to query last write time for file {} ({}/{}).", entry.path().string(), error.value(), error.message());
|
||||
dentry.size = fs::file_size(entry.path(), error);
|
||||
if(error)
|
||||
logWarning(LOG_FT, "Failed to query size for file {} ({}/{}).", entry.path().string(), error.value(), error.message());
|
||||
} else {
|
||||
logWarning(LOG_FT, "Directory query listed an unknown file type for file {} ({}).", entry.path().string(), (int) status.type());
|
||||
}
|
||||
}
|
||||
if(error && entries.empty()) {
|
||||
response->emplace_fail(DirectoryQueryErrorType::FAILED_TO_LIST_FILES, std::to_string(error.value()) + "/" + error.message());
|
||||
return response;
|
||||
}
|
||||
response->emplace_success(std::forward<decltype(entries)>(entries));
|
||||
return response;
|
||||
}
|
||||
|
||||
std::shared_ptr<directory_query_response_t> LocalFileSystem::query_icon_directory(const std::shared_ptr<VirtualFileServer> &id) {
|
||||
return this->query_directory(this->server_path(id) / fs::u8path("icons"), "/", true);
|
||||
}
|
||||
|
||||
std::shared_ptr<directory_query_response_t> LocalFileSystem::query_avatar_directory(const std::shared_ptr<VirtualFileServer> &id) {
|
||||
return this->query_directory(this->server_path(id) / fs::u8path("avatars"), "/", true);
|
||||
}
|
||||
|
||||
std::shared_ptr<directory_query_response_t> LocalFileSystem::query_channel_directory(const std::shared_ptr<VirtualFileServer> &id, ChannelId channelId, const std::string &path) {
|
||||
return this->query_directory(this->server_channel_path(id, channelId), path, false);
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<DirectoryModifyError>> LocalFileSystem::create_channel_directory(const std::shared_ptr<VirtualFileServer> &id, ChannelId channelId, const std::string &path) {
|
||||
auto channel_path_root = this->server_channel_path(id, channelId);
|
||||
std::error_code error{};
|
||||
|
||||
auto response = this->create_execute_response<DirectoryModifyError>();
|
||||
auto target_path = channel_path_root / fs::u8path(path);
|
||||
if(this->exceeds_base_path(channel_path_root, target_path)) {
|
||||
response->emplace_fail(DirectoryModifyErrorType::PATH_EXCEEDS_ROOT_PATH, "");
|
||||
return response;
|
||||
}
|
||||
|
||||
if(fs::exists(target_path, error)) {
|
||||
response->emplace_fail(DirectoryModifyErrorType::PATH_ALREADY_EXISTS, "");
|
||||
return response;
|
||||
} else if(error) {
|
||||
logWarning(LOG_FT, "Failed to check for file at {}: {}. Assuming it does not exists.", target_path.string(), error.value(), error.message());
|
||||
}
|
||||
|
||||
if(!fs::create_directories(target_path, error) || error) {
|
||||
response->emplace_fail(DirectoryModifyErrorType::FAILED_TO_CREATE_DIRECTORIES, std::to_string(error.value()) + "/" + error.message());
|
||||
return response;
|
||||
}
|
||||
|
||||
response->emplace_success();
|
||||
return response;
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileModifyError>> LocalFileSystem::rename_channel_file(const std::shared_ptr<VirtualFileServer> &id, ChannelId channelId, const std::string ¤t_path_string, ChannelId targetChannelId, const std::string &new_path_string) {
|
||||
auto channel_path_root = this->server_channel_path(id, channelId);
|
||||
auto target_path_root = this->server_channel_path(id, targetChannelId);
|
||||
std::error_code error{};
|
||||
std::string locked_file{};
|
||||
|
||||
auto response = this->create_execute_response<FileModifyError>();
|
||||
auto current_path = channel_path_root / fs::u8path(current_path_string);
|
||||
auto target_path = target_path_root / fs::u8path(new_path_string);
|
||||
|
||||
if(this->exceeds_base_path(channel_path_root, current_path)) {
|
||||
response->emplace_fail(FileModifyErrorType::PATH_EXCEEDS_ROOT_PATH, "");
|
||||
return response;
|
||||
}
|
||||
if(this->exceeds_base_path(target_path_root, target_path)) {
|
||||
response->emplace_fail(FileModifyErrorType::TARGET_PATH_EXCEEDS_ROOT_PATH, "");
|
||||
return response;
|
||||
}
|
||||
|
||||
if(!fs::exists(current_path, error)) {
|
||||
response->emplace_fail(FileModifyErrorType::PATH_DOES_NOT_EXISTS, "");
|
||||
return response;
|
||||
} else if(error) {
|
||||
logWarning(LOG_FT, "Failed to check for file at {}: {}. Assuming it does not exists.", current_path.string(), error.value(), error.message());
|
||||
response->emplace_fail(FileModifyErrorType::PATH_DOES_NOT_EXISTS, "");
|
||||
return response;
|
||||
}
|
||||
|
||||
if(!fs::exists(target_path.parent_path(), error)) {
|
||||
if(!fs::create_directories(target_path.parent_path(), error)) {
|
||||
response->emplace_fail(FileModifyErrorType::FAILED_TO_CREATE_DIRECTORIES, std::to_string(error.value()) + "/" + error.message());
|
||||
return response;
|
||||
}
|
||||
} else if(error) {
|
||||
logWarning(LOG_FT, "Failed to test for target directory existence for {}: {}/{}. Assuming it does exists", target_path.parent_path().string(), error.value(), error.message());
|
||||
}
|
||||
|
||||
if(fs::exists(target_path, error)) {
|
||||
response->emplace_fail(FileModifyErrorType::TARGET_PATH_ALREADY_EXISTS, "");
|
||||
return response;
|
||||
} else if(error) {
|
||||
logWarning(LOG_FT, "Failed to check for file at {}: {}. Assuming it does exists.", current_path.string(), error.value(), error.message());
|
||||
response->emplace_fail(FileModifyErrorType::TARGET_PATH_ALREADY_EXISTS, "");
|
||||
return response;
|
||||
}
|
||||
|
||||
if(this->is_any_file_locked(channel_path_root, current_path, locked_file)) {
|
||||
response->emplace_fail(FileModifyErrorType::SOME_FILES_ARE_LOCKED, locked_file);
|
||||
return response;
|
||||
}
|
||||
|
||||
fs::rename(current_path, target_path, error);
|
||||
if(error) {
|
||||
response->emplace_fail(FileModifyErrorType::FAILED_TO_RENAME_FILE, std::to_string(error.value()) + "/" + error.message());
|
||||
return response;
|
||||
}
|
||||
|
||||
response->emplace_success();
|
||||
return response;
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> LocalFileSystem::delete_files(const fs::path &base,
|
||||
const std::vector<std::string> &paths) {
|
||||
std::error_code error{};
|
||||
std::string locked_file{};
|
||||
auto response = this->create_execute_response<FileDeleteError, FileDeleteResponse>();
|
||||
|
||||
std::vector<FileDeleteResponse::DeleteResult> delete_results{};
|
||||
for(const auto& path : paths) {
|
||||
auto target_path = base / fs::u8path(path);
|
||||
|
||||
if(!fs::exists(target_path, error)) {
|
||||
delete_results.emplace_back(FileDeleteResponse::StatusType::PATH_DOES_NOT_EXISTS, "");
|
||||
continue;
|
||||
} else if(error) {
|
||||
logWarning(LOG_FT, "Failed to check for file at {}: {}. Assuming it does exists.", target_path.string(), error.value(), error.message());
|
||||
delete_results.emplace_back(FileDeleteResponse::StatusType::PATH_DOES_NOT_EXISTS, "");
|
||||
continue;
|
||||
}
|
||||
|
||||
if(this->is_any_file_locked(base, path, locked_file)) {
|
||||
delete_results.emplace_back(FileDeleteResponse::StatusType::SOME_FILES_ARE_LOCKED, locked_file);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!fs::remove_all(target_path, error) || error) {
|
||||
delete_results.emplace_back(FileDeleteResponse::StatusType::FAILED_TO_DELETE_FILES, std::to_string(error.value()) + "/" + error.message());
|
||||
continue;
|
||||
}
|
||||
|
||||
delete_results.emplace_back(FileDeleteResponse::StatusType::SUCCESS, "");
|
||||
}
|
||||
|
||||
response->emplace_success(FileDeleteResponse{delete_results});
|
||||
return response;
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> LocalFileSystem::delete_channel_files(const std::shared_ptr<VirtualFileServer> &id, ChannelId channelId, const std::vector<std::string> &path) {
|
||||
return this->delete_files(this->server_channel_path(id, channelId), path);
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> LocalFileSystem::delete_icons(const std::shared_ptr<VirtualFileServer> &id, const std::vector<std::string> &icon) {
|
||||
return this->delete_files(this->server_path(id) / fs::u8path("icons"), icon);
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> LocalFileSystem::delete_avatars(const std::shared_ptr<VirtualFileServer> &id, const std::vector<std::string> &avatar) {
|
||||
return this->delete_files(this->server_path(id) / fs::u8path("avatars"), avatar);
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> LocalFileSystem::query_file_info(const std::vector<std::tuple<fs::path, std::string>> &paths) {
|
||||
std::error_code error{};
|
||||
auto response = this->create_execute_response<FileInfoError, FileInfoResponse>();
|
||||
std::vector<FileInfoResponse::FileInfo> file_infos{};
|
||||
file_infos.reserve(paths.size());
|
||||
|
||||
for(const auto& [base, path] : paths) {
|
||||
auto target_path = base / fs::u8path(path);
|
||||
if(this->exceeds_base_path(base, target_path)) {
|
||||
file_infos.emplace_back(FileInfoResponse::StatusType::PATH_EXCEEDS_ROOT_PATH, "", DirectoryEntry{});
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!fs::exists(target_path, error)) {
|
||||
file_infos.emplace_back(FileInfoResponse::StatusType::PATH_DOES_NOT_EXISTS, "", DirectoryEntry{});
|
||||
continue;
|
||||
} else if(error) {
|
||||
logWarning(LOG_FT, "Failed to check for file at {}: {}. Assuming it does not exists.", target_path.string(), error.value(), error.message());
|
||||
|
||||
file_infos.emplace_back(FileInfoResponse::StatusType::PATH_DOES_NOT_EXISTS, "", DirectoryEntry{});
|
||||
continue;
|
||||
}
|
||||
|
||||
auto status = fs::status(target_path, error);
|
||||
if(error) {
|
||||
logWarning(LOG_FT, "Failed to query file status for {} ({}/{}). Skipping entry for file info query.", target_path.string(), error.value(), error.message());
|
||||
|
||||
file_infos.emplace_back(FileInfoResponse::StatusType::FAILED_TO_QUERY_INFO, "", DirectoryEntry{});
|
||||
continue;
|
||||
}
|
||||
|
||||
if(status.type() == fs::file_type::directory) {
|
||||
DirectoryEntry dentry{};
|
||||
dentry.type = DirectoryEntry::DIRECTORY;
|
||||
dentry.name = target_path.filename();
|
||||
dentry.empty = fs::is_empty(target_path, error);
|
||||
if(error)
|
||||
logWarning(LOG_FT, "Failed to query directory empty state for directory {} ({}/{})", target_path.string(), error.value(), error.message());
|
||||
|
||||
dentry.modified_at = fs::last_write_time(target_path, error);
|
||||
if(error)
|
||||
logWarning(LOG_FT, "Failed to query last write time for directory {} ({}/{})", target_path.string(), error.value(), error.message());
|
||||
dentry.size = 0;
|
||||
file_infos.emplace_back(FileInfoResponse::StatusType::SUCCESS, "", dentry);
|
||||
} else if(status.type() == fs::file_type::regular) {
|
||||
DirectoryEntry dentry{};
|
||||
dentry.type = DirectoryEntry::FILE;
|
||||
dentry.name = target_path.filename();
|
||||
|
||||
dentry.modified_at = fs::last_write_time(target_path, error);
|
||||
if(error)
|
||||
logWarning(LOG_FT, "Failed to query last write time for file {} ({}/{}).", target_path.string(), error.value(), error.message());
|
||||
dentry.size = fs::file_size(target_path, error);
|
||||
if(error)
|
||||
logWarning(LOG_FT, "Failed to query size for file {} ({}/{}).", target_path.string(), error.value(), error.message());
|
||||
file_infos.emplace_back(FileInfoResponse::StatusType::SUCCESS, "", dentry);
|
||||
} else {
|
||||
logWarning(LOG_FT, "File info query contains an unknown file type for file {} ({}).", target_path.string(), (int) status.type());
|
||||
file_infos.emplace_back(FileInfoResponse::StatusType::UNKNOWN_FILE_TYPE, "", DirectoryEntry{});
|
||||
}
|
||||
}
|
||||
|
||||
response->emplace_success(FileInfoResponse{file_infos});
|
||||
return response;
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> LocalFileSystem::query_channel_info(const std::shared_ptr<VirtualFileServer> &sid, const std::vector<std::tuple<ChannelId, std::string>>& files) {
|
||||
std::vector<std::tuple<fs::path, std::string>> file_paths{};
|
||||
for(const auto& [channelId, path] : files)
|
||||
file_paths.emplace_back(this->server_channel_path(sid, channelId), path);
|
||||
return this->query_file_info(file_paths);
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> LocalFileSystem::query_icon_info(const std::shared_ptr<VirtualFileServer> &sid, const std::vector<std::string> &paths) {
|
||||
std::vector<std::tuple<fs::path, std::string>> file_paths{};
|
||||
for(const auto& path : paths)
|
||||
file_paths.emplace_back(this->server_path(sid) / fs::u8path("icons"), path);
|
||||
return this->query_file_info(file_paths);
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse> > LocalFileSystem::query_avatar_info(const std::shared_ptr<VirtualFileServer> &sid, const std::vector<std::string> &paths) {
|
||||
std::vector<std::tuple<fs::path, std::string>> file_paths{};
|
||||
for(const auto& path : paths)
|
||||
file_paths.emplace_back(this->server_path(sid) / fs::u8path("avatars"), path);
|
||||
return this->query_file_info(file_paths);
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
//
|
||||
// Created by WolverinDEV on 16/09/2020.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <files/FileServer.h>
|
||||
#include <deque>
|
||||
#include <utility>
|
||||
#include <thread>
|
||||
#include <shared_mutex>
|
||||
#include <sys/socket.h>
|
||||
#include <pipes/ws.h>
|
||||
#include <pipes/ssl.h>
|
||||
#include <misc/net.h>
|
||||
#include <misc/spin_mutex.h>
|
||||
#include <random>
|
||||
#include <misc/memtracker.h>
|
||||
#include "./NetTools.h"
|
||||
|
||||
namespace ts::server::file::filesystem {
|
||||
#ifdef FS_INCLUDED
|
||||
namespace fs = std::experimental::filesystem;
|
||||
#endif
|
||||
|
||||
class LocalFileSystem : public filesystem::AbstractProvider {
|
||||
using FileModifyError = filesystem::FileModifyError;
|
||||
using DirectoryModifyError = filesystem::DirectoryModifyError;
|
||||
public:
|
||||
enum struct FileCategory {
|
||||
ICON,
|
||||
AVATAR,
|
||||
CHANNEL
|
||||
};
|
||||
|
||||
virtual ~LocalFileSystem();
|
||||
|
||||
bool initialize(std::string & /* error */, const std::string & /* root path */);
|
||||
void finalize();
|
||||
|
||||
void lock_file(const std::string& /* absolute path */);
|
||||
void unlock_file(const std::string& /* absolute path */);
|
||||
|
||||
[[nodiscard]] inline const auto &root_path() const { return this->root_path_; }
|
||||
|
||||
[[nodiscard]] std::string absolute_avatar_path(const std::shared_ptr<VirtualFileServer> &, const std::string&);
|
||||
[[nodiscard]] std::string absolute_icon_path(const std::shared_ptr<VirtualFileServer> &, const std::string&);
|
||||
[[nodiscard]] std::string absolute_channel_path(const std::shared_ptr<VirtualFileServer> &, ChannelId, const std::string&);
|
||||
|
||||
std::shared_ptr<ExecuteResponse<ServerCommandError>> initialize_server(const std::shared_ptr<VirtualFileServer> & /* server */) override;
|
||||
std::shared_ptr<ExecuteResponse<ServerCommandError>> delete_server(const std::shared_ptr<VirtualFileServer> & /* server */) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>>
|
||||
query_channel_info(const std::shared_ptr<VirtualFileServer> & /* server */, const std::vector<std::tuple<ChannelId, std::string>>& /* names */) override;
|
||||
|
||||
std::shared_ptr<directory_query_response_t>
|
||||
query_channel_directory(const std::shared_ptr<VirtualFileServer> & id, ChannelId channelId, const std::string &string) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<DirectoryModifyError>>
|
||||
create_channel_directory(const std::shared_ptr<VirtualFileServer> & id, ChannelId channelId, const std::string &string) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>>
|
||||
delete_channel_files(const std::shared_ptr<VirtualFileServer> & id, ChannelId channelId, const std::vector<std::string> &string) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileModifyError>>
|
||||
rename_channel_file(const std::shared_ptr<VirtualFileServer> & id, ChannelId channelId, const std::string &, ChannelId, const std::string &) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>>
|
||||
query_icon_info(const std::shared_ptr<VirtualFileServer> & /* server */, const std::vector<std::string>& /* names */) override;
|
||||
|
||||
std::shared_ptr<directory_query_response_t> query_icon_directory(const std::shared_ptr<VirtualFileServer> & id) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>>
|
||||
delete_icons(const std::shared_ptr<VirtualFileServer> & id, const std::vector<std::string> &string) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>>
|
||||
query_avatar_info(const std::shared_ptr<VirtualFileServer> & /* server */, const std::vector<std::string>& /* names */) override;
|
||||
|
||||
std::shared_ptr<directory_query_response_t> query_avatar_directory(const std::shared_ptr<VirtualFileServer> & id) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>>
|
||||
delete_avatars(const std::shared_ptr<VirtualFileServer> & id, const std::vector<std::string> &string) override;
|
||||
|
||||
private:
|
||||
#ifdef FS_INCLUDED
|
||||
[[nodiscard]] fs::path server_path(const std::shared_ptr<VirtualFileServer> &);
|
||||
[[nodiscard]] fs::path server_channel_path(const std::shared_ptr<VirtualFileServer> &, ChannelId);
|
||||
[[nodiscard]] static bool exceeds_base_path(const fs::path& /* base */, const fs::path& /* target */);
|
||||
[[nodiscard]] bool is_any_file_locked(const fs::path& /* base */, const std::string& /* path */, std::string& /* file (relative to the base) */);
|
||||
|
||||
[[nodiscard]] std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>>
|
||||
delete_files(const fs::path& /* base */, const std::vector<std::string> &string);
|
||||
|
||||
[[nodiscard]] std::shared_ptr<directory_query_response_t>
|
||||
query_directory(const fs::path& /* base */, const std::string &string, bool);
|
||||
|
||||
[[nodiscard]] std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>>
|
||||
query_file_info(const std::vector<std::tuple<fs::path, std::string>> &string);
|
||||
#endif
|
||||
|
||||
template <typename error_t, typename result_t = EmptyExecuteResponse>
|
||||
std::shared_ptr<ExecuteResponse<error_t, result_t>> create_execute_response() {
|
||||
return std::make_shared<ExecuteResponse<error_t, result_t>>(this->result_notify_mutex, this->result_notify_cv);
|
||||
}
|
||||
std::string target_file_path(FileCategory type, const std::shared_ptr<VirtualFileServer> &sid, ts::ChannelId cid, const std::string &path);
|
||||
|
||||
std::mutex result_notify_mutex{};
|
||||
std::condition_variable result_notify_cv{};
|
||||
|
||||
std::string root_path_{};
|
||||
|
||||
std::mutex locked_files_mutex{};
|
||||
std::deque<std::string> locked_files_{};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,385 @@
|
||||
//
|
||||
// Created by WolverinDEV on 04/05/2020.
|
||||
//
|
||||
|
||||
#include <cassert>
|
||||
#include <event2/event.h>
|
||||
#include <log/LogUtils.h>
|
||||
#include <random>
|
||||
#include "./LocalFileProvider.h"
|
||||
#include "./LocalFileTransfer.h"
|
||||
#include <experimental/filesystem>
|
||||
|
||||
namespace fs = std::experimental::filesystem;
|
||||
using namespace ts::server::file;
|
||||
using namespace ts::server::file::transfer;
|
||||
|
||||
Buffer* transfer::allocate_buffer(size_t size) {
|
||||
auto total_size = sizeof(Buffer) + size;
|
||||
auto buffer = (Buffer*) malloc(total_size);
|
||||
new (buffer) Buffer{};
|
||||
buffer->capacity = size;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void transfer::free_buffer(Buffer* buffer) {
|
||||
buffer->~Buffer();
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
FileClient::~FileClient() {
|
||||
this->flush_network_buffer();
|
||||
this->flush_disk_buffer();
|
||||
|
||||
assert(!this->disk_buffer.buffer_head);
|
||||
assert(!this->network_buffer.buffer_head);
|
||||
|
||||
assert(!this->file.file_descriptor);
|
||||
assert(!this->file.currently_processing);
|
||||
assert(!this->file.next_client);
|
||||
|
||||
assert(!this->networking.event_read);
|
||||
assert(!this->networking.event_write);
|
||||
|
||||
assert(this->state == STATE_DISCONNECTED);
|
||||
memtrack::freed<FileClient>(this);
|
||||
}
|
||||
|
||||
LocalFileTransfer::LocalFileTransfer(filesystem::LocalFileSystem *fs) : file_system_{fs} {}
|
||||
LocalFileTransfer::~LocalFileTransfer() = default;
|
||||
|
||||
bool LocalFileTransfer::start() {
|
||||
(void) this->start_client_worker();
|
||||
|
||||
{
|
||||
auto start_result = this->start_disk_io();
|
||||
switch (start_result) {
|
||||
case DiskIOStartResult::SUCCESS:
|
||||
break;
|
||||
case DiskIOStartResult::OUT_OF_MEMORY:
|
||||
logError(LOG_FT, "Failed to start disk worker (Out of memory)");
|
||||
goto error_exit_disk;
|
||||
default:
|
||||
logError(LOG_FT, "Failed to start disk worker ({})", (int) start_result);
|
||||
goto error_exit_disk;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
auto start_result = this->start_networking();
|
||||
switch (start_result) {
|
||||
case NetworkingStartResult::SUCCESS:
|
||||
break;
|
||||
|
||||
case NetworkingStartResult::OUT_OF_MEMORY:
|
||||
logError(LOG_FT, "Failed to start networking (Out of memory)");
|
||||
goto error_exit_network;
|
||||
|
||||
default:
|
||||
logError(LOG_FT, "Failed to start networking ({})", (int) start_result);
|
||||
goto error_exit_network;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
error_exit_network:
|
||||
this->shutdown_networking();
|
||||
|
||||
error_exit_disk:
|
||||
this->shutdown_disk_io();
|
||||
this->shutdown_client_worker();
|
||||
return false;
|
||||
}
|
||||
|
||||
void LocalFileTransfer::stop() {
|
||||
this->shutdown_networking();
|
||||
this->shutdown_disk_io();
|
||||
this->shutdown_client_worker();
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
LocalFileTransfer::initialize_icon_transfer(Transfer::Direction direction, const std::shared_ptr<VirtualFileServer> &server, const TransferInfo &info) {
|
||||
return this->initialize_transfer(direction, server, 0, Transfer::TARGET_TYPE_ICON, info);
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
LocalFileTransfer::initialize_avatar_transfer(Transfer::Direction direction, const std::shared_ptr<VirtualFileServer> &server, const TransferInfo &info) {
|
||||
return this->initialize_transfer(direction, server, 0, Transfer::TARGET_TYPE_AVATAR, info);
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
LocalFileTransfer::initialize_channel_transfer(Transfer::Direction direction, const std::shared_ptr<VirtualFileServer> &server, ChannelId cid, const TransferInfo &info) {
|
||||
return this->initialize_transfer(direction, server, cid, Transfer::TARGET_TYPE_CHANNEL_FILE, info);
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>> LocalFileTransfer::initialize_transfer(
|
||||
Transfer::Direction direction, const std::shared_ptr<VirtualFileServer> &server, ChannelId cid,
|
||||
Transfer::TargetType ttype,
|
||||
const TransferInfo &info) {
|
||||
auto response = this->create_execute_response<TransferInitError, std::shared_ptr<Transfer>>();
|
||||
|
||||
std::lock_guard clock{this->transfer_create_mutex};
|
||||
if(info.max_concurrent_transfers > 0) {
|
||||
std::unique_lock tlock{this->transfers_mutex};
|
||||
{
|
||||
auto transfers = std::count_if(this->transfers_.begin(), this->transfers_.end(), [&](const std::shared_ptr<FileClient>& client) {
|
||||
return client->transfer && client->transfer->client_unique_id == info.client_unique_id && client->state < FileClient::STATE_FLUSHING;
|
||||
});
|
||||
transfers += std::count_if(this->pending_transfers.begin(), this->pending_transfers.end(), [&](const std::shared_ptr<Transfer>& transfer) {
|
||||
return transfer->client_unique_id == info.client_unique_id;
|
||||
});
|
||||
|
||||
if(transfers >= info.max_concurrent_transfers) {
|
||||
response->emplace_fail(TransferInitError::CLIENT_TOO_MANY_TRANSFERS, std::to_string(transfers));
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
auto server_transfers = this->pending_transfers.size();
|
||||
server_transfers += std::count_if(this->transfers_.begin(), this->transfers_.end(), [&](const std::shared_ptr<FileClient>& client) {
|
||||
return client->transfer;
|
||||
});
|
||||
if(server_transfers >= this->max_concurrent_transfers) {
|
||||
response->emplace_fail(TransferInitError::SERVER_TOO_MANY_TRANSFERS, std::to_string(server_transfers));
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto transfer = std::make_shared<Transfer>();
|
||||
transfer->server_transfer_id = server->generate_transfer_id();
|
||||
transfer->server = server;
|
||||
transfer->channel_id = cid;
|
||||
transfer->target_type = ttype;
|
||||
transfer->direction = direction;
|
||||
|
||||
transfer->client_id = 0; /* must be provided externally */
|
||||
transfer->client_transfer_id = 0; /* must be provided externally */
|
||||
|
||||
transfer->server_addresses.reserve(this->network.bindings.size());
|
||||
for(auto& binding : this->network.bindings) {
|
||||
if(!binding->file_descriptor) continue;
|
||||
|
||||
transfer->server_addresses.emplace_back(Transfer::Address{binding->hostname, net::port(binding->address)});
|
||||
}
|
||||
|
||||
transfer->target_file_path = info.file_path;
|
||||
transfer->file_offset = info.file_offset;
|
||||
transfer->expected_file_size = info.expected_file_size;
|
||||
transfer->max_bandwidth = info.max_bandwidth;
|
||||
transfer->client_unique_id = info.client_unique_id;
|
||||
transfer->client_id = info.client_id;
|
||||
|
||||
constexpr static std::string_view kTokenCharacters{"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"};
|
||||
for(auto& c : transfer->transfer_key)
|
||||
c = kTokenCharacters[transfer_random_token_generator() % kTokenCharacters.length()];
|
||||
transfer->transfer_key[0] = (char) 'r'; /* (114) */ /* a non valid SSL header type to indicate that we're using a file transfer key and not doing a SSL handshake */
|
||||
transfer->transfer_key[1] = (char) 'a'; /* ( 97) */
|
||||
transfer->transfer_key[2] = (char) 'w'; /* (119) */
|
||||
|
||||
transfer->initialized_timestamp = std::chrono::system_clock::now();
|
||||
|
||||
{
|
||||
std::string absolute_path{};
|
||||
switch (transfer->target_type) {
|
||||
case Transfer::TARGET_TYPE_AVATAR:
|
||||
absolute_path = this->file_system_->absolute_avatar_path(transfer->server, transfer->target_file_path);
|
||||
break;
|
||||
case Transfer::TARGET_TYPE_ICON:
|
||||
absolute_path = this->file_system_->absolute_icon_path(transfer->server, transfer->target_file_path);
|
||||
break;
|
||||
case Transfer::TARGET_TYPE_CHANNEL_FILE:
|
||||
absolute_path = this->file_system_->absolute_channel_path(transfer->server, transfer->channel_id, transfer->target_file_path);
|
||||
break;
|
||||
case Transfer::TARGET_TYPE_UNKNOWN:
|
||||
default:
|
||||
response->emplace_fail(TransferInitError::INVALID_FILE_TYPE, "");
|
||||
return response;
|
||||
}
|
||||
transfer->absolute_file_path = absolute_path;
|
||||
|
||||
const auto root_path_length = this->file_system_->root_path().size();
|
||||
if(root_path_length < absolute_path.size())
|
||||
transfer->relative_file_path = absolute_path.substr(root_path_length);
|
||||
else
|
||||
transfer->relative_file_path = "error";
|
||||
transfer->file_name = fs::u8path(absolute_path).filename();
|
||||
}
|
||||
|
||||
if(direction == Transfer::DIRECTION_DOWNLOAD) {
|
||||
auto path = fs::u8path(transfer->absolute_file_path);
|
||||
std::error_code error{};
|
||||
if(!fs::exists(path, error)) {
|
||||
response->emplace_fail(TransferInitError::FILE_DOES_NOT_EXISTS, "");
|
||||
return response;
|
||||
} else if(error) {
|
||||
logWarning(LOG_FT, "Failed to check for file at {}: {}. Assuming it does not exists.", transfer->absolute_file_path, error.value(), error.message());
|
||||
response->emplace_fail(TransferInitError::FILE_DOES_NOT_EXISTS, "");
|
||||
return response;
|
||||
}
|
||||
|
||||
auto status = fs::status(path, error);
|
||||
if(error) {
|
||||
logWarning(LOG_FT, "Failed to status for file at {}: {}. Ignoring file transfer.", transfer->absolute_file_path, error.value(), error.message());
|
||||
response->emplace_fail(TransferInitError::IO_ERROR, "stat");
|
||||
return response;
|
||||
}
|
||||
|
||||
if(status.type() != fs::file_type::regular) {
|
||||
response->emplace_fail(TransferInitError::FILE_IS_NOT_A_FILE, "");
|
||||
return response;
|
||||
}
|
||||
|
||||
transfer->expected_file_size = fs::file_size(path, error);
|
||||
if(error) {
|
||||
logWarning(LOG_FT, "Failed to get file size for file at {}: {}. Ignoring file transfer.", transfer->absolute_file_path, error.value(), error.message());
|
||||
response->emplace_fail(TransferInitError::IO_ERROR, "file_size");
|
||||
return response;
|
||||
}
|
||||
|
||||
if(info.download_client_quota_limit > 0 && info.download_client_quota_limit <= transfer->expected_file_size) {
|
||||
response->emplace_fail(TransferInitError::CLIENT_QUOTA_EXCEEDED, "");
|
||||
return response;
|
||||
}
|
||||
if(info.download_server_quota_limit > 0 && info.download_server_quota_limit <= transfer->expected_file_size) {
|
||||
response->emplace_fail(TransferInitError::SERVER_QUOTA_EXCEEDED, "");
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard tlock{this->transfers_mutex};
|
||||
this->pending_transfers.push_back(transfer);
|
||||
}
|
||||
|
||||
switch (transfer->target_type) {
|
||||
case Transfer::TARGET_TYPE_AVATAR:
|
||||
logMessage(LOG_FT, "Initialized avatar transfer for avatar \"{}\" ({} bytes, transferring {} bytes).", transfer->target_file_path, transfer->expected_file_size, transfer->expected_file_size - transfer->file_offset);
|
||||
break;
|
||||
case Transfer::TARGET_TYPE_ICON:
|
||||
logMessage(LOG_FT, "Initialized icon transfer for icon \"{}\" ({} bytes, transferring {} bytes).",
|
||||
transfer->target_file_path, transfer->expected_file_size, transfer->expected_file_size - transfer->file_offset);
|
||||
break;
|
||||
case Transfer::TARGET_TYPE_CHANNEL_FILE:
|
||||
logMessage(LOG_FT, "Initialized channel transfer for file \"{}/{}\" ({} bytes, transferring {} bytes).",
|
||||
transfer->channel_id, transfer->target_file_path, transfer->expected_file_size, transfer->expected_file_size - transfer->file_offset);
|
||||
break;
|
||||
case Transfer::TARGET_TYPE_UNKNOWN:
|
||||
default:
|
||||
response->emplace_fail(TransferInitError::INVALID_FILE_TYPE, "");
|
||||
return response;
|
||||
}
|
||||
|
||||
if(auto callback{this->callback_transfer_registered}; callback)
|
||||
callback(transfer);
|
||||
|
||||
response->emplace_success(std::move(transfer));
|
||||
return response;
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferActionError>> LocalFileTransfer::stop_transfer(const std::shared_ptr<VirtualFileServer>& server, transfer_id id, bool flush) {
|
||||
auto response = this->create_execute_response<TransferActionError>();
|
||||
|
||||
std::shared_ptr<Transfer> transfer{};
|
||||
std::shared_ptr<FileClient> connected_transfer{};
|
||||
|
||||
{
|
||||
std::lock_guard tlock{this->transfers_mutex};
|
||||
|
||||
auto ct_it = std::find_if(this->transfers_.begin(), this->transfers_.end(), [&](const std::shared_ptr<FileClient>& t) {
|
||||
return t->transfer && t->transfer->server_transfer_id == id && t->transfer->server == server;
|
||||
});
|
||||
if(ct_it != this->transfers_.end())
|
||||
connected_transfer = *ct_it;
|
||||
else {
|
||||
auto t_it = std::find_if(this->pending_transfers.begin(), this->pending_transfers.end(), [&](const std::shared_ptr<Transfer>& t) {
|
||||
return t->server_transfer_id == id && t->server == server;
|
||||
});
|
||||
if(t_it != this->pending_transfers.end()) {
|
||||
transfer = *t_it;
|
||||
this->pending_transfers.erase(t_it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!transfer) {
|
||||
if(connected_transfer)
|
||||
transfer = connected_transfer->transfer;
|
||||
else {
|
||||
response->emplace_fail(TransferActionError{TransferActionError::UNKNOWN_TRANSFER, ""});
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
if(connected_transfer) {
|
||||
this->invoke_aborted_callback(connected_transfer, { TransferError::USER_REQUEST, "" });
|
||||
logMessage(LOG_FT, "{} Stopping transfer due to an user request.", connected_transfer->log_prefix());
|
||||
|
||||
std::unique_lock slock{connected_transfer->state_mutex};
|
||||
this->disconnect_client(connected_transfer, slock, flush);
|
||||
} else {
|
||||
this->invoke_aborted_callback(transfer, { TransferError::USER_REQUEST, "" });
|
||||
logMessage(LOG_FT, "Removing pending file transfer for id {}", id);
|
||||
}
|
||||
|
||||
response->emplace_success();
|
||||
return response;
|
||||
}
|
||||
|
||||
inline void apply_transfer_info(const std::shared_ptr<Transfer>& transfer, ActiveFileTransfer& info) {
|
||||
info.server_transfer_id = transfer->server_transfer_id;
|
||||
info.client_transfer_id = transfer->client_transfer_id;
|
||||
info.direction = transfer->direction;
|
||||
info.client_id = transfer->client_id;
|
||||
info.client_unique_id = transfer->client_unique_id;
|
||||
|
||||
info.file_path = transfer->relative_file_path;
|
||||
info.file_name = transfer->file_name;
|
||||
|
||||
info.expected_size = transfer->expected_file_size;
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferListError, std::vector<ActiveFileTransfer>>> LocalFileTransfer::list_transfer() {
|
||||
std::vector<ActiveFileTransfer> transfer_infos{};
|
||||
auto response = this->create_execute_response<TransferListError, std::vector<ActiveFileTransfer>>();
|
||||
|
||||
std::unique_lock tlock{this->transfers_mutex};
|
||||
auto awaiting_transfers = this->pending_transfers;
|
||||
auto running_transfers = this->transfers_;
|
||||
tlock.unlock();
|
||||
|
||||
transfer_infos.reserve(awaiting_transfers.size() + running_transfers.size());
|
||||
for(const auto& transfer : awaiting_transfers) {
|
||||
ActiveFileTransfer info{};
|
||||
apply_transfer_info(transfer, info);
|
||||
info.size_done = transfer->file_offset;
|
||||
|
||||
info.status = ActiveFileTransfer::NOT_STARTED;
|
||||
info.runtime = std::chrono::milliseconds{0};
|
||||
|
||||
info.average_speed = 0;
|
||||
info.current_speed = 0;
|
||||
transfer_infos.push_back(info);
|
||||
}
|
||||
|
||||
for(const auto& client : running_transfers) {
|
||||
auto transfer = client->transfer;
|
||||
if(!transfer) continue;
|
||||
|
||||
ActiveFileTransfer info{};
|
||||
apply_transfer_info(transfer, info);
|
||||
info.size_done = transfer->file_offset + client->statistics.file_transferred.total_bytes;
|
||||
|
||||
info.status = ActiveFileTransfer::RUNNING;
|
||||
info.runtime = std::chrono::floor<std::chrono::milliseconds>(std::chrono::system_clock::now() - client->timings.key_received);
|
||||
|
||||
info.average_speed = client->statistics.file_transferred.average_bandwidth();
|
||||
info.current_speed = client->statistics.file_transferred.current_bandwidth();
|
||||
transfer_infos.push_back(info);
|
||||
}
|
||||
|
||||
response->emplace_success(std::move(transfer_infos));
|
||||
return response;
|
||||
}
|
||||
@@ -0,0 +1,446 @@
|
||||
//
|
||||
// Created by WolverinDEV on 16/09/2020.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <files/FileServer.h>
|
||||
#include <deque>
|
||||
#include <utility>
|
||||
#include <thread>
|
||||
#include <shared_mutex>
|
||||
#include <sys/socket.h>
|
||||
#include <pipes/ws.h>
|
||||
#include <pipes/ssl.h>
|
||||
#include <misc/net.h>
|
||||
#include <misc/spin_mutex.h>
|
||||
#include <random>
|
||||
#include <misc/memtracker.h>
|
||||
#include "./NetTools.h"
|
||||
#include "LocalFileSystem.h"
|
||||
|
||||
#define TRANSFER_MAX_CACHED_BYTES (1024 * 1024 * 1) // Buffer up to 1mb
|
||||
|
||||
namespace ts::server::file::transfer {
|
||||
class LocalFileTransfer;
|
||||
|
||||
struct Buffer {
|
||||
Buffer* next{nullptr};
|
||||
|
||||
size_t capacity{0};
|
||||
size_t length{0};
|
||||
size_t offset{0};
|
||||
|
||||
char data[1]{};
|
||||
};
|
||||
[[nodiscard]] extern Buffer* allocate_buffer(size_t);
|
||||
extern void free_buffer(Buffer*);
|
||||
|
||||
/* all variables are locked via the state_mutex */
|
||||
struct FileClient : std::enable_shared_from_this<FileClient> {
|
||||
LocalFileTransfer* handle;
|
||||
std::shared_ptr<Transfer> transfer{nullptr};
|
||||
|
||||
std::shared_mutex state_mutex{};
|
||||
enum {
|
||||
STATE_AWAITING_KEY, /* includes SSL/HTTP init */
|
||||
STATE_TRANSFERRING,
|
||||
STATE_FLUSHING,
|
||||
STATE_DISCONNECTED
|
||||
} state{STATE_AWAITING_KEY};
|
||||
|
||||
bool finished_signal_send{false};
|
||||
|
||||
enum NetworkingProtocol {
|
||||
PROTOCOL_UNKNOWN,
|
||||
PROTOCOL_HTTPS,
|
||||
PROTOCOL_TS_V1
|
||||
};
|
||||
|
||||
enum HTTPUploadState {
|
||||
HTTP_AWAITING_HEADER,
|
||||
HTTP_STATE_AWAITING_BOUNDARY,
|
||||
HTTP_STATE_AWAITING_BOUNDARY_END,
|
||||
HTTP_STATE_UPLOADING,
|
||||
HTTP_STATE_DOWNLOADING
|
||||
};
|
||||
|
||||
struct {
|
||||
bool file_locked{false};
|
||||
int file_descriptor{0};
|
||||
|
||||
bool currently_processing{false};
|
||||
FileClient* next_client{nullptr};
|
||||
|
||||
bool query_media_bytes{false};
|
||||
uint8_t media_bytes[TRANSFER_MEDIA_BYTES_LENGTH]{0};
|
||||
uint8_t media_bytes_length{0};
|
||||
} file{};
|
||||
|
||||
struct {
|
||||
size_t provided_bytes{0};
|
||||
char key[TRANSFER_KEY_LENGTH]{0};
|
||||
} transfer_key{};
|
||||
|
||||
struct {
|
||||
std::mutex mutex{};
|
||||
size_t bytes{0};
|
||||
|
||||
bool buffering_stopped{false};
|
||||
bool write_disconnected{false};
|
||||
|
||||
Buffer* buffer_head{nullptr};
|
||||
Buffer** buffer_tail{&buffer_head};
|
||||
} network_buffer{};
|
||||
|
||||
struct {
|
||||
std::mutex mutex{};
|
||||
size_t bytes{0};
|
||||
|
||||
bool buffering_stopped{false};
|
||||
bool write_disconnected{false};
|
||||
|
||||
Buffer* buffer_head{nullptr};
|
||||
Buffer** buffer_tail{&buffer_head};
|
||||
} disk_buffer{};
|
||||
|
||||
struct {
|
||||
sockaddr_storage address{};
|
||||
int file_descriptor{0};
|
||||
|
||||
NetworkingProtocol protocol{PROTOCOL_UNKNOWN};
|
||||
|
||||
struct event* event_read{nullptr};
|
||||
struct event* event_write{nullptr};
|
||||
struct event* event_throttle{nullptr};
|
||||
|
||||
bool add_event_write{false}, add_event_read{false};
|
||||
|
||||
std::chrono::system_clock::time_point disconnect_timeout{};
|
||||
|
||||
networking::NetworkThrottle client_throttle{};
|
||||
/* the right side is the server throttle */
|
||||
networking::DualNetworkThrottle throttle{&client_throttle, &networking::NetworkThrottle::kNoThrottle};
|
||||
|
||||
pipes::SSL pipe_ssl{};
|
||||
bool pipe_ssl_init{false};
|
||||
std::unique_ptr<Buffer, decltype(free_buffer)*> http_header_buffer{nullptr, free_buffer};
|
||||
HTTPUploadState http_state{HTTPUploadState::HTTP_AWAITING_HEADER};
|
||||
|
||||
std::string http_boundary{};
|
||||
|
||||
/* Only read the transfer key length at the beginning. We than have the actual limit which will be set via throttle */
|
||||
size_t max_read_buffer_size{TRANSFER_KEY_LENGTH};
|
||||
} networking{};
|
||||
|
||||
struct {
|
||||
networking::TransferStatistics network_send{};
|
||||
networking::TransferStatistics network_received{};
|
||||
|
||||
networking::TransferStatistics file_transferred{};
|
||||
|
||||
networking::TransferStatistics disk_bytes_read{};
|
||||
networking::TransferStatistics disk_bytes_write{};
|
||||
} statistics{};
|
||||
|
||||
struct {
|
||||
std::chrono::system_clock::time_point last_write{};
|
||||
std::chrono::system_clock::time_point last_read{};
|
||||
|
||||
std::chrono::system_clock::time_point connected{};
|
||||
std::chrono::system_clock::time_point key_received{};
|
||||
std::chrono::system_clock::time_point disconnecting{};
|
||||
} timings;
|
||||
|
||||
explicit FileClient(LocalFileTransfer* handle) : handle{handle} { memtrack::allocated<FileClient>(this); }
|
||||
~FileClient();
|
||||
|
||||
void add_network_write_event(bool /* ignore bandwidth limits */);
|
||||
void add_network_write_event_nolock(bool /* ignore bandwidth limits */);
|
||||
|
||||
/* will check if we've enough space in out read buffer again */
|
||||
void add_network_read_event(bool /* ignore bandwidth limits */);
|
||||
|
||||
bool send_file_bytes(const void* /* buffer */, size_t /* length */);
|
||||
bool enqueue_network_buffer_bytes(const void* /* buffer */, size_t /* length */);
|
||||
bool enqueue_disk_buffer_bytes(const void* /* buffer */, size_t /* length */);
|
||||
|
||||
/* these function clear the buffers and set the write disconnected flags to true so no new buffers will be enqueued */
|
||||
size_t flush_network_buffer();
|
||||
void flush_disk_buffer();
|
||||
|
||||
[[nodiscard]] bool buffers_flushed();
|
||||
[[nodiscard]] inline std::string log_prefix() const { return "[" + net::to_string(this->networking.address) + "]"; }
|
||||
};
|
||||
|
||||
enum struct DiskIOStartResult {
|
||||
SUCCESS,
|
||||
OUT_OF_MEMORY
|
||||
};
|
||||
|
||||
enum struct NetworkingStartResult {
|
||||
SUCCESS,
|
||||
OUT_OF_MEMORY
|
||||
};
|
||||
|
||||
enum struct NetworkingBindResult {
|
||||
SUCCESS,
|
||||
|
||||
BINDING_ALREADY_EXISTS,
|
||||
NETWORKING_NOT_INITIALIZED,
|
||||
FAILED_TO_ALLOCATE_SOCKET, /* errno is set */
|
||||
FAILED_TO_BIND,
|
||||
FAILED_TO_LISTEN,
|
||||
|
||||
OUT_OF_MEMORY,
|
||||
};
|
||||
|
||||
enum struct NetworkingUnbindResult {
|
||||
SUCCESS,
|
||||
UNKNOWN_BINDING
|
||||
};
|
||||
|
||||
enum struct ClientWorkerStartResult {
|
||||
SUCCESS
|
||||
};
|
||||
|
||||
enum struct NetworkInitializeResult {
|
||||
SUCCESS,
|
||||
OUT_OF_MEMORY
|
||||
};
|
||||
|
||||
enum struct FileInitializeResult {
|
||||
SUCCESS,
|
||||
|
||||
INVALID_TRANSFER_DIRECTION,
|
||||
OUT_OF_MEMORY,
|
||||
|
||||
PROCESS_FILE_LIMIT_REACHED,
|
||||
SYSTEM_FILE_LIMIT_REACHED,
|
||||
|
||||
FILE_IS_BUSY,
|
||||
FILE_DOES_NOT_EXISTS,
|
||||
FILE_SYSTEM_ERROR,
|
||||
FILE_IS_A_DIRECTORY,
|
||||
|
||||
FILE_TOO_LARGE,
|
||||
DISK_IS_READ_ONLY,
|
||||
|
||||
FILE_SEEK_FAILED,
|
||||
FILE_SIZE_MISMATCH,
|
||||
|
||||
FILE_IS_NOT_ACCESSIBLE,
|
||||
|
||||
FAILED_TO_READ_MEDIA_BYTES,
|
||||
COUNT_NOT_CREATE_DIRECTORIES,
|
||||
|
||||
MAX
|
||||
};
|
||||
|
||||
constexpr static std::array<std::string_view, (size_t) FileInitializeResult::MAX> kFileInitializeResultMessages{
|
||||
/* SUCCESS */ "success",
|
||||
|
||||
/* INVALID_TRANSFER_DIRECTION */ "invalid file transfer direction",
|
||||
/* OUT_OF_MEMORY */ "out of memory",
|
||||
|
||||
/* PROCESS_FILE_LIMIT_REACHED */ "process file limit reached",
|
||||
/* SYSTEM_FILE_LIMIT_REACHED */ "system file limit reached",
|
||||
|
||||
/* FILE_IS_BUSY */ "target file is busy",
|
||||
/* FILE_DOES_NOT_EXISTS */ "target file does not exists",
|
||||
/* FILE_SYSTEM_ERROR */ "internal file system error",
|
||||
/* FILE_IS_A_DIRECTORY */ "target file is a directory",
|
||||
|
||||
/* FILE_TOO_LARGE */ "file is too large",
|
||||
/* DISK_IS_READ_ONLY */ "disk is in read only mode",
|
||||
|
||||
/* FILE_SEEK_FAILED */ "failed to seek to target file offset",
|
||||
/* FILE_SIZE_MISMATCH */ "file size miss match",
|
||||
/* FILE_IS_NOT_ACCESSIBLE */ "file is not accessible",
|
||||
/* FAILED_TO_READ_MEDIA_BYTES */ "failed to read file media bytes",
|
||||
/* COUNT_NOT_CREATE_DIRECTORIES */ "could not create required directories"
|
||||
};
|
||||
|
||||
enum struct TransferKeyApplyResult {
|
||||
SUCCESS,
|
||||
FILE_ERROR,
|
||||
UNKNOWN_KEY,
|
||||
|
||||
INTERNAL_ERROR
|
||||
};
|
||||
|
||||
enum struct TransferUploadRawResult {
|
||||
MORE_DATA_TO_RECEIVE,
|
||||
FINISH,
|
||||
FINISH_OVERFLOW,
|
||||
|
||||
/* UNKNOWN ERROR */
|
||||
};
|
||||
|
||||
enum struct TransferUploadHTTPResult {
|
||||
MORE_DATA_TO_RECEIVE,
|
||||
FINISH,
|
||||
|
||||
BOUNDARY_MISSING,
|
||||
BOUNDARY_TOKEN_INVALID,
|
||||
BOUNDARY_INVALID,
|
||||
|
||||
MISSING_CONTENT_TYPE,
|
||||
INVALID_CONTENT_TYPE
|
||||
/* UNKNOWN ERROR */
|
||||
};
|
||||
|
||||
struct NetworkBinding {
|
||||
std::string hostname{};
|
||||
sockaddr_storage address{};
|
||||
};
|
||||
|
||||
struct ActiveNetworkBinding : std::enable_shared_from_this<ActiveNetworkBinding> {
|
||||
std::string hostname{};
|
||||
sockaddr_storage address{};
|
||||
|
||||
int file_descriptor{-1};
|
||||
struct event* accept_event{nullptr};
|
||||
|
||||
LocalFileTransfer* handle{nullptr};
|
||||
};
|
||||
|
||||
class LocalFileTransfer : public AbstractProvider {
|
||||
public:
|
||||
explicit LocalFileTransfer(filesystem::LocalFileSystem*);
|
||||
~LocalFileTransfer();
|
||||
|
||||
[[nodiscard]] bool start();
|
||||
void stop();
|
||||
|
||||
[[nodiscard]] NetworkingBindResult add_network_binding(const NetworkBinding& /* binding */);
|
||||
[[nodiscard]] std::vector<NetworkBinding> active_network_bindings();
|
||||
[[nodiscard]] NetworkingUnbindResult remove_network_binding(const NetworkBinding& /* binding */);
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
initialize_channel_transfer(Transfer::Direction direction, const std::shared_ptr<VirtualFileServer>& server, ChannelId channelId,
|
||||
const TransferInfo &info) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
initialize_icon_transfer(Transfer::Direction direction, const std::shared_ptr<VirtualFileServer>& server, const TransferInfo &info) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
initialize_avatar_transfer(Transfer::Direction direction, const std::shared_ptr<VirtualFileServer>& server, const TransferInfo &info) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferListError, std::vector<ActiveFileTransfer>>> list_transfer() override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferActionError>> stop_transfer(const std::shared_ptr<VirtualFileServer>& /* server */, transfer_id id, bool) override;
|
||||
private:
|
||||
enum struct DiskIOLoopState {
|
||||
STOPPED,
|
||||
RUNNING,
|
||||
|
||||
STOPPING,
|
||||
FORCE_STOPPING
|
||||
};
|
||||
filesystem::LocalFileSystem* file_system_;
|
||||
|
||||
size_t max_concurrent_transfers{1024};
|
||||
std::mt19937 transfer_random_token_generator{std::random_device{}()};
|
||||
|
||||
std::mutex result_notify_mutex{};
|
||||
std::condition_variable result_notify_cv{};
|
||||
|
||||
std::mutex transfers_mutex{};
|
||||
std::mutex transfer_create_mutex{};
|
||||
std::deque<std::shared_ptr<FileClient>> transfers_{};
|
||||
std::deque<std::shared_ptr<Transfer>> pending_transfers{};
|
||||
|
||||
enum ServerState {
|
||||
STOPPED,
|
||||
RUNNING
|
||||
} state{ServerState::STOPPED};
|
||||
|
||||
struct {
|
||||
bool active{false};
|
||||
|
||||
std::thread dispatch_thread{};
|
||||
std::mutex mutex{};
|
||||
std::condition_variable notify_cv{};
|
||||
} disconnect;
|
||||
|
||||
struct {
|
||||
std::mutex mutex;
|
||||
|
||||
bool active{false};
|
||||
std::thread dispatch_thread{};
|
||||
struct event_base* event_base{nullptr};
|
||||
|
||||
std::deque<std::shared_ptr<ActiveNetworkBinding>> bindings{};
|
||||
} network{};
|
||||
|
||||
struct {
|
||||
DiskIOLoopState state{DiskIOLoopState::STOPPED};
|
||||
std::thread dispatch_thread{};
|
||||
std::mutex queue_lock{};
|
||||
std::condition_variable notify_work_awaiting{};
|
||||
std::condition_variable notify_client_processed{};
|
||||
|
||||
FileClient* queue_head{nullptr};
|
||||
FileClient** queue_tail{&queue_head};
|
||||
} disk_io{};
|
||||
|
||||
template <typename error_t, typename result_t = EmptyExecuteResponse>
|
||||
std::shared_ptr<ExecuteResponse<error_t, result_t>> create_execute_response() {
|
||||
return std::make_shared<ExecuteResponse<error_t, result_t>>(this->result_notify_mutex, this->result_notify_cv);
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
initialize_transfer(Transfer::Direction, const std::shared_ptr<VirtualFileServer> &, ChannelId, Transfer::TargetType, const TransferInfo &info);
|
||||
|
||||
[[nodiscard]] NetworkingStartResult start_networking();
|
||||
[[nodiscard]] DiskIOStartResult start_disk_io();
|
||||
[[nodiscard]] ClientWorkerStartResult start_client_worker();
|
||||
|
||||
void shutdown_networking();
|
||||
void shutdown_disk_io();
|
||||
void shutdown_client_worker();
|
||||
|
||||
void disconnect_client(const std::shared_ptr<FileClient>& /* client */, std::unique_lock<std::shared_mutex>& /* state lock */, bool /* flush network */);
|
||||
|
||||
[[nodiscard]] NetworkInitializeResult initialize_networking(const std::shared_ptr<FileClient>& /* client */, int /* file descriptor */);
|
||||
/* might block 'till all IO operations have been succeeded */
|
||||
void finalize_networking(const std::shared_ptr<FileClient>& /* client */, std::unique_lock<std::shared_mutex>& /* state lock */);
|
||||
|
||||
[[nodiscard]] FileInitializeResult initialize_file_io(const std::shared_ptr<FileClient>& /* client */);
|
||||
void finalize_file_io(const std::shared_ptr<FileClient>& /* client */, std::unique_lock<std::shared_mutex>& /* state lock */);
|
||||
|
||||
[[nodiscard]] bool initialize_client_ssl(const std::shared_ptr<FileClient>& /* client */);
|
||||
void finalize_client_ssl(const std::shared_ptr<FileClient>& /* client */);
|
||||
|
||||
void enqueue_disk_io(const std::shared_ptr<FileClient>& /* client */);
|
||||
void execute_disk_io(const std::shared_ptr<FileClient>& /* client */);
|
||||
|
||||
void test_disconnecting_state(const std::shared_ptr<FileClient>& /* client */);
|
||||
|
||||
[[nodiscard]] TransferUploadRawResult handle_transfer_upload_raw(const std::shared_ptr<FileClient>& /* client */, const char * /* buffer */, size_t /* length */, size_t* /* bytes written */);
|
||||
[[nodiscard]] TransferUploadHTTPResult handle_transfer_upload_http(const std::shared_ptr<FileClient>& /* client */, const char * /* buffer */, size_t /* length */);
|
||||
|
||||
void send_http_response(const std::shared_ptr<FileClient>& /* client */, http::HttpResponse& /* response */);
|
||||
|
||||
static void callback_transfer_network_write(int, short, void*);
|
||||
static void callback_transfer_network_read(int, short, void*);
|
||||
static void callback_transfer_network_throttle(int, short, void*);
|
||||
static void callback_transfer_network_accept(int, short, void*);
|
||||
|
||||
static void dispatch_loop_client_worker(void*);
|
||||
static void dispatch_loop_network(void*);
|
||||
static void dispatch_loop_disk_io(void*);
|
||||
|
||||
void report_transfer_statistics(const std::shared_ptr<FileClient>& /* client */);
|
||||
[[nodiscard]] TransferStatistics generate_transfer_statistics_report(const std::shared_ptr<FileClient>& /* client */);
|
||||
void invoke_aborted_callback(const std::shared_ptr<FileClient>& /* client */, const TransferError& /* error */);
|
||||
void invoke_aborted_callback(const std::shared_ptr<Transfer>& /* pending transfer */, const TransferError& /* error */);
|
||||
|
||||
size_t handle_transfer_read(const std::shared_ptr<FileClient>& /* client */, const char* /* buffer */, size_t /* bytes */);
|
||||
size_t handle_transfer_read_raw(const std::shared_ptr<FileClient>& /* client */, const char* /* buffer */, size_t /* bytes */);
|
||||
[[nodiscard]] TransferKeyApplyResult handle_transfer_key_provided(const std::shared_ptr<FileClient>& /* client */, std::string& /* error */);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
//
|
||||
// Created by WolverinDEV on 04/05/2020.
|
||||
//
|
||||
|
||||
#include <cassert>
|
||||
#include <event2/event.h>
|
||||
#include <log/LogUtils.h>
|
||||
#include "./LocalFileProvider.h"
|
||||
#include "./LocalFileTransfer.h"
|
||||
|
||||
using namespace ts::server::file;
|
||||
using namespace ts::server::file::transfer;
|
||||
|
||||
ClientWorkerStartResult LocalFileTransfer::start_client_worker() {
|
||||
assert(!this->disconnect.active);
|
||||
this->disconnect.active = true;
|
||||
|
||||
this->disconnect.dispatch_thread = std::thread(&LocalFileTransfer::dispatch_loop_client_worker, this);
|
||||
return ClientWorkerStartResult::SUCCESS;
|
||||
}
|
||||
|
||||
void LocalFileTransfer::shutdown_client_worker() {
|
||||
if(!this->disconnect.active) return;
|
||||
this->disconnect.active = false;
|
||||
|
||||
this->disconnect.notify_cv.notify_all();
|
||||
if(this->disconnect.dispatch_thread.joinable())
|
||||
this->disconnect.dispatch_thread.join();
|
||||
|
||||
{
|
||||
std::unique_lock tlock{this->transfers_mutex};
|
||||
if(!this->transfers_.empty())
|
||||
logWarning(LOG_FT, "Shutting down disconnect worker even thou we still have some active clients. This could cause memory leaks.");
|
||||
}
|
||||
}
|
||||
|
||||
void LocalFileTransfer::disconnect_client(const std::shared_ptr<FileClient> &client, std::unique_lock<std::shared_mutex>& state_lock, bool flush) {
|
||||
assert(state_lock.owns_lock());
|
||||
|
||||
if(client->state == FileClient::STATE_DISCONNECTED || (client->state == FileClient::STATE_FLUSHING && flush)) {
|
||||
return; /* shall NOT happen */
|
||||
}
|
||||
|
||||
#define del_ev_noblock(event) if(event) event_del_noblock(event)
|
||||
|
||||
client->state = flush ? FileClient::STATE_FLUSHING : FileClient::STATE_DISCONNECTED;
|
||||
client->timings.disconnecting = std::chrono::system_clock::now();
|
||||
if(flush) {
|
||||
const auto network_flush_time = client->networking.throttle.expected_writing_time(client->network_buffer.bytes) + std::chrono::seconds{10};
|
||||
|
||||
del_ev_noblock(client->networking.event_read);
|
||||
|
||||
client->networking.disconnect_timeout = std::chrono::system_clock::now() + network_flush_time;
|
||||
debugMessage(LOG_FT, "{} Disconnecting client. Flushing pending bytes (max {} seconds)", client->log_prefix(), std::chrono::floor<std::chrono::seconds>(network_flush_time).count());
|
||||
|
||||
client->add_network_write_event_nolock(false);
|
||||
this->enqueue_disk_io(client);
|
||||
} else {
|
||||
del_ev_noblock(client->networking.event_read);
|
||||
del_ev_noblock(client->networking.event_write);
|
||||
del_ev_noblock(client->networking.event_throttle);
|
||||
|
||||
this->disconnect.notify_cv.notify_one();
|
||||
}
|
||||
|
||||
#undef del_ev_noblock
|
||||
}
|
||||
|
||||
void LocalFileTransfer::test_disconnecting_state(const std::shared_ptr<FileClient> &client) {
|
||||
if(client->state != FileClient::STATE_FLUSHING)
|
||||
return;
|
||||
|
||||
if(!client->buffers_flushed())
|
||||
return;
|
||||
|
||||
debugMessage(LOG_FT, "{} Disk and network buffers are flushed. Closing connection.", client->log_prefix());
|
||||
std::unique_lock s_lock{client->state_mutex};
|
||||
this->disconnect_client(client, s_lock, false);
|
||||
}
|
||||
|
||||
void LocalFileTransfer::dispatch_loop_client_worker(void *ptr_transfer) {
|
||||
auto provider = reinterpret_cast<LocalFileTransfer*>(ptr_transfer);
|
||||
|
||||
while(provider->disconnect.active) {
|
||||
{
|
||||
std::unique_lock dlock{provider->disconnect.mutex};
|
||||
provider->disconnect.notify_cv.wait_for(dlock, std::chrono::milliseconds {500}); /* report all 500ms the statistics */
|
||||
}
|
||||
/* run the disconnect worker at least once before exiting */
|
||||
|
||||
/* transfer statistics */
|
||||
{
|
||||
std::unique_lock tlock{provider->transfers_mutex};
|
||||
auto transfers = provider->transfers_;
|
||||
tlock.unlock();
|
||||
for(const auto& transfer : transfers) {
|
||||
switch(transfer->state) {
|
||||
case FileClient::STATE_TRANSFERRING:
|
||||
break;
|
||||
case FileClient::STATE_FLUSHING:
|
||||
if(!transfer->transfer)
|
||||
continue;
|
||||
|
||||
if(transfer->transfer->direction != Transfer::DIRECTION_DOWNLOAD)
|
||||
continue;
|
||||
|
||||
if(transfer->buffers_flushed())
|
||||
continue;
|
||||
|
||||
break; /* we're still transferring (sending data) */
|
||||
case FileClient::STATE_AWAITING_KEY:
|
||||
case FileClient::STATE_DISCONNECTED:
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
provider->report_transfer_statistics(transfer);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::deque<std::shared_ptr<Transfer>> timeouted_transfers{};
|
||||
|
||||
{
|
||||
std::unique_lock tlock{provider->transfers_mutex};
|
||||
|
||||
auto now = std::chrono::system_clock::now();
|
||||
std::copy_if(provider->pending_transfers.begin(), provider->pending_transfers.end(), std::back_inserter(timeouted_transfers), [&](const std::shared_ptr<Transfer>& t) {
|
||||
return t->initialized_timestamp + std::chrono::seconds{10} < now;
|
||||
});
|
||||
provider->pending_transfers.erase(std::remove_if(provider->pending_transfers.begin(), provider->pending_transfers.end(), [&](const auto& t) {
|
||||
return std::find(timeouted_transfers.begin(), timeouted_transfers.end(), t) != timeouted_transfers.end();
|
||||
}), provider->pending_transfers.end());
|
||||
}
|
||||
|
||||
for(const auto& pt : timeouted_transfers)
|
||||
provider->invoke_aborted_callback(pt, { TransferError::TRANSFER_TIMEOUT, "" });
|
||||
|
||||
if(!timeouted_transfers.empty())
|
||||
logMessage(LOG_FT, "Removed {} pending transfers because no request has been made for them.", timeouted_transfers.size());
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
std::deque<std::shared_ptr<FileClient>> disconnected_clients{};
|
||||
{
|
||||
std::unique_lock tlock{provider->transfers_mutex};
|
||||
|
||||
auto now = std::chrono::system_clock::now();
|
||||
std::copy_if(provider->transfers_.begin(), provider->transfers_.end(), std::back_inserter(disconnected_clients), [&](const std::shared_ptr<FileClient>& t) {
|
||||
std::shared_lock slock{t->state_mutex};
|
||||
if(t->state == FileClient::STATE_DISCONNECTED) {
|
||||
return true;
|
||||
} else if(t->state == FileClient::STATE_AWAITING_KEY) {
|
||||
return t->timings.connected + std::chrono::seconds{10} < now;
|
||||
} else if(t->state == FileClient::STATE_TRANSFERRING) {
|
||||
assert(t->transfer);
|
||||
if(t->transfer->direction == Transfer::DIRECTION_UPLOAD) {
|
||||
return t->timings.last_read + std::chrono::seconds{5} < now;
|
||||
} else if(t->transfer->direction == Transfer::DIRECTION_DOWNLOAD) {
|
||||
return t->timings.last_write + std::chrono::seconds{5} < now;
|
||||
}
|
||||
} else if(t->state == FileClient::STATE_FLUSHING) {
|
||||
if(t->networking.disconnect_timeout.time_since_epoch().count() > 0)
|
||||
return t->networking.disconnect_timeout + std::chrono::seconds{5} < now;
|
||||
return t->timings.disconnecting + std::chrono::seconds{30} < now;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
provider->transfers_.erase(std::remove_if(provider->transfers_.begin(), provider->transfers_.end(), [&](const auto& t) {
|
||||
return std::find(disconnected_clients.begin(), disconnected_clients.end(), t) != disconnected_clients.end();
|
||||
}), provider->transfers_.end());
|
||||
}
|
||||
|
||||
for(auto& client : disconnected_clients) {
|
||||
switch(client->state) {
|
||||
case FileClient::STATE_AWAITING_KEY:
|
||||
logMessage(LOG_FT, "{} Received no key. Dropping client.", client->log_prefix());
|
||||
break;
|
||||
case FileClient::STATE_TRANSFERRING:
|
||||
logMessage(LOG_FT, "{} Networking timeout. Dropping client", client->log_prefix());
|
||||
provider->invoke_aborted_callback(client, { TransferError::TRANSFER_TIMEOUT, "" });
|
||||
break;
|
||||
case FileClient::STATE_FLUSHING:
|
||||
if(!client->buffers_flushed())
|
||||
logMessage(LOG_FT, "{} Failed to flush connection. Dropping client", client->log_prefix());
|
||||
else
|
||||
; /* we just awaited a client disconnect */
|
||||
break;
|
||||
case FileClient::STATE_DISCONNECTED:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
{
|
||||
std::unique_lock slock{client->state_mutex};
|
||||
client->state = FileClient::STATE_DISCONNECTED;
|
||||
/*
|
||||
* First of all disconnect the client from the network so no actions could be triggered by that way.
|
||||
* Secondly finalize all network components, so no data is pending anywhere
|
||||
* Thirdly drop the client's disk worker (if it's an upload the data should be written already, else we don't care anyways)
|
||||
*/
|
||||
provider->finalize_networking(client, slock);
|
||||
provider->finalize_client_ssl(client);
|
||||
provider->finalize_file_io(client, slock);
|
||||
}
|
||||
|
||||
debugMessage(LOG_FT, "{} Destroying transfer.", client->log_prefix());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LocalFileTransfer::report_transfer_statistics(const std::shared_ptr<FileClient> &client) {
|
||||
auto callback{this->callback_transfer_statistics};
|
||||
if(!callback) return;
|
||||
|
||||
callback(client->transfer, this->generate_transfer_statistics_report(client));
|
||||
}
|
||||
|
||||
TransferStatistics LocalFileTransfer::generate_transfer_statistics_report(const std::shared_ptr<FileClient> &client) {
|
||||
TransferStatistics stats{};
|
||||
|
||||
stats.network_bytes_send = client->statistics.network_send.total_bytes;
|
||||
stats.network_bytes_received = client->statistics.network_received.total_bytes;
|
||||
stats.file_bytes_transferred = client->statistics.file_transferred.total_bytes;
|
||||
|
||||
stats.delta_network_bytes_received = client->statistics.network_received.take_delta();
|
||||
stats.delta_network_bytes_send = client->statistics.network_received.take_delta();
|
||||
|
||||
stats.delta_file_bytes_transferred = client->statistics.file_transferred.take_delta();
|
||||
|
||||
stats.file_start_offset = client->transfer->file_offset;
|
||||
stats.file_current_offset = client->statistics.file_transferred.total_bytes + client->transfer->file_offset;
|
||||
stats.file_total_size = client->transfer->expected_file_size;
|
||||
|
||||
stats.average_speed = client->statistics.file_transferred.average_bandwidth();
|
||||
stats.current_speed = client->statistics.file_transferred.current_bandwidth();
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
void LocalFileTransfer::invoke_aborted_callback(const std::shared_ptr<FileClient> &client,
|
||||
const ts::server::file::transfer::TransferError &error) {
|
||||
auto callback{this->callback_transfer_aborted};
|
||||
if(!callback || !client->transfer) return;
|
||||
|
||||
callback(client->transfer, this->generate_transfer_statistics_report(client), error);
|
||||
}
|
||||
|
||||
void LocalFileTransfer::invoke_aborted_callback(const std::shared_ptr<Transfer> &transfer,
|
||||
const ts::server::file::transfer::TransferError &error) {
|
||||
auto callback{this->callback_transfer_aborted};
|
||||
if(!callback) return;
|
||||
|
||||
TransferStatistics stats{};
|
||||
|
||||
stats.network_bytes_send = 0;
|
||||
stats.network_bytes_received = 0;
|
||||
stats.file_bytes_transferred = 0;
|
||||
|
||||
stats.delta_network_bytes_received = 0;
|
||||
stats.delta_network_bytes_send = 0;
|
||||
|
||||
stats.delta_file_bytes_transferred = 0;
|
||||
|
||||
stats.file_start_offset = transfer->file_offset;
|
||||
stats.file_current_offset = transfer->file_offset;
|
||||
stats.file_total_size = transfer->expected_file_size;
|
||||
|
||||
callback(transfer, stats, error);
|
||||
}
|
||||
@@ -0,0 +1,566 @@
|
||||
//
|
||||
// Created by WolverinDEV on 04/05/2020.
|
||||
//
|
||||
|
||||
#include <cassert>
|
||||
#include <event.h>
|
||||
#include <experimental/filesystem>
|
||||
#include <log/LogUtils.h>
|
||||
#include "./LocalFileProvider.h"
|
||||
#include "./LocalFileTransfer.h"
|
||||
#include "duration_utils.h"
|
||||
|
||||
using namespace ts::server::file;
|
||||
using namespace ts::server::file::transfer;
|
||||
namespace fs = std::experimental::filesystem;
|
||||
|
||||
DiskIOStartResult LocalFileTransfer::start_disk_io() {
|
||||
assert(this->disk_io.state == DiskIOLoopState::STOPPED);
|
||||
|
||||
this->disk_io.state = DiskIOLoopState::RUNNING;
|
||||
this->disk_io.dispatch_thread = std::thread(&LocalFileTransfer::dispatch_loop_disk_io, this);
|
||||
return DiskIOStartResult::SUCCESS;
|
||||
}
|
||||
|
||||
void LocalFileTransfer::shutdown_disk_io() {
|
||||
if(this->disk_io.state == DiskIOLoopState::STOPPED) return;
|
||||
|
||||
this->disk_io.state = DiskIOLoopState::STOPPING;
|
||||
{
|
||||
std::unique_lock qlock{this->disk_io.queue_lock};
|
||||
this->disk_io.notify_work_awaiting.notify_all();
|
||||
while(this->disk_io.queue_head)
|
||||
this->disk_io.notify_client_processed.wait_for(qlock, std::chrono::seconds{10});
|
||||
|
||||
if(this->disk_io.queue_head) {
|
||||
logWarning(0, "Failed to flush disk IO. Force aborting.");
|
||||
this->disk_io.state = DiskIOLoopState::FORCE_STOPPING;
|
||||
this->disk_io.notify_work_awaiting.notify_all();
|
||||
this->disk_io.notify_client_processed.wait(qlock);
|
||||
}
|
||||
}
|
||||
|
||||
if(this->disk_io.dispatch_thread.joinable())
|
||||
this->disk_io.dispatch_thread.join();
|
||||
|
||||
this->disk_io.state = DiskIOLoopState::STOPPED;
|
||||
}
|
||||
|
||||
void LocalFileTransfer::dispatch_loop_disk_io(void *provider_ptr) {
|
||||
auto provider = reinterpret_cast<LocalFileTransfer*>(provider_ptr);
|
||||
|
||||
std::shared_ptr<FileClient> client{};
|
||||
while(true) {
|
||||
{
|
||||
std::unique_lock qlock{provider->disk_io.queue_lock};
|
||||
if(client) {
|
||||
client->file.currently_processing = false;
|
||||
provider->disk_io.notify_client_processed.notify_all();
|
||||
client = nullptr;
|
||||
}
|
||||
|
||||
provider->disk_io.notify_work_awaiting.wait(qlock, [&]{ return provider->disk_io.state != DiskIOLoopState::RUNNING || provider->disk_io.queue_head != nullptr; });
|
||||
|
||||
if(provider->disk_io.queue_head) {
|
||||
try {
|
||||
client = provider->disk_io.queue_head->shared_from_this();
|
||||
} catch (std::bad_weak_ptr& ex) {
|
||||
logCritical(LOG_FT, "Disk worker encountered a bad weak ptr. This indicated something went horribly wrong! Please submit this on https://forum.teaspeak.de !!!");
|
||||
client.reset();
|
||||
continue;
|
||||
}
|
||||
|
||||
provider->disk_io.queue_head = provider->disk_io.queue_head->file.next_client;
|
||||
if(!provider->disk_io.queue_head)
|
||||
provider->disk_io.queue_tail = &provider->disk_io.queue_head;
|
||||
}
|
||||
|
||||
if(provider->disk_io.state != DiskIOLoopState::RUNNING) {
|
||||
if(provider->disk_io.state == DiskIOLoopState::STOPPING) {
|
||||
if(!client)
|
||||
break;
|
||||
/* break only if all clients have been flushed */
|
||||
} else {
|
||||
/* force stopping without any flushing */
|
||||
auto fclient = &*client;
|
||||
while(fclient)
|
||||
fclient = std::exchange(fclient->file.next_client, nullptr);
|
||||
|
||||
provider->disk_io.queue_head = nullptr;
|
||||
provider->disk_io.queue_tail = &provider->disk_io.queue_head;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!client)
|
||||
continue;
|
||||
|
||||
client->file.currently_processing = true;
|
||||
client->file.next_client = nullptr;
|
||||
}
|
||||
|
||||
provider->execute_disk_io(client);
|
||||
}
|
||||
provider->disk_io.notify_client_processed.notify_all();
|
||||
}
|
||||
|
||||
bool FileClient::enqueue_disk_buffer_bytes(const void *snd_buffer, size_t size) {
|
||||
auto tbuffer = allocate_buffer(size);
|
||||
tbuffer->length = size;
|
||||
tbuffer->offset = 0;
|
||||
memcpy(tbuffer->data, snd_buffer, size);
|
||||
|
||||
size_t buffer_size;
|
||||
{
|
||||
std::lock_guard block{this->disk_buffer.mutex};
|
||||
if(this->disk_buffer.write_disconnected)
|
||||
goto write_disconnected;
|
||||
|
||||
*this->disk_buffer.buffer_tail = tbuffer;
|
||||
this->disk_buffer.buffer_tail = &tbuffer->next;
|
||||
buffer_size = (this->disk_buffer.bytes += size);
|
||||
}
|
||||
|
||||
return buffer_size > TRANSFER_MAX_CACHED_BYTES;
|
||||
|
||||
write_disconnected:
|
||||
free_buffer(tbuffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
void FileClient::flush_disk_buffer() {
|
||||
Buffer* current_head;
|
||||
{
|
||||
std::lock_guard block{this->disk_buffer.mutex};
|
||||
|
||||
this->disk_buffer.write_disconnected = true;
|
||||
this->disk_buffer.bytes = 0;
|
||||
current_head = std::exchange(this->disk_buffer.buffer_head, nullptr);
|
||||
this->disk_buffer.buffer_tail = &this->disk_buffer.buffer_head;
|
||||
}
|
||||
|
||||
while(current_head) {
|
||||
auto next = current_head->next;
|
||||
free_buffer(current_head);
|
||||
current_head = next;
|
||||
}
|
||||
}
|
||||
|
||||
bool FileClient::buffers_flushed() {
|
||||
{
|
||||
std::lock_guard db_lock{this->disk_buffer.mutex};
|
||||
if(this->disk_buffer.bytes > 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard nb_lock{this->network_buffer.mutex};
|
||||
if(this->network_buffer.bytes > 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
FileInitializeResult LocalFileTransfer::initialize_file_io(const std::shared_ptr<FileClient> &transfer) {
|
||||
FileInitializeResult result{FileInitializeResult::SUCCESS};
|
||||
assert(transfer->transfer);
|
||||
|
||||
std::shared_lock slock{transfer->state_mutex};
|
||||
auto& file_data = transfer->file;
|
||||
assert(!file_data.file_descriptor);
|
||||
assert(!file_data.next_client);
|
||||
|
||||
auto absolute_path = transfer->transfer->absolute_file_path;
|
||||
{
|
||||
unsigned int open_flags{0};
|
||||
if(transfer->transfer->direction == Transfer::DIRECTION_DOWNLOAD) {
|
||||
open_flags = O_RDONLY;
|
||||
|
||||
std::error_code fs_error{};
|
||||
if(absolute_path.empty() || !fs::exists(absolute_path, fs_error)) {
|
||||
result = FileInitializeResult::FILE_DOES_NOT_EXISTS;
|
||||
goto error_exit;
|
||||
} else if(fs_error) {
|
||||
logWarning(LOG_FT, "{} Failed to check for file existence of {}: {}/{}", transfer->log_prefix(), absolute_path, fs_error.value(), fs_error.message());
|
||||
result = FileInitializeResult::FILE_SYSTEM_ERROR;
|
||||
goto error_exit;
|
||||
}
|
||||
} else if(transfer->transfer->direction == Transfer::DIRECTION_UPLOAD) {
|
||||
std::error_code fs_error{};
|
||||
auto parent_path = fs::u8path(absolute_path).parent_path();
|
||||
if(!fs::exists(parent_path)) {
|
||||
if(!fs::create_directories(parent_path, fs_error) || fs_error) {
|
||||
logError(LOG_FT, "{} Failed to create required directories for file upload for {}: {}/{}", transfer->log_prefix(), absolute_path, fs_error.value(), fs_error.message());
|
||||
result = FileInitializeResult::COUNT_NOT_CREATE_DIRECTORIES;
|
||||
goto error_exit;
|
||||
}
|
||||
} else if(fs_error) {
|
||||
logWarning(LOG_FT, "{} Failed to check for directory existence of {}: {}/{}. Assuming it exists", transfer->log_prefix(), parent_path.string(), fs_error.value(), fs_error.message());
|
||||
}
|
||||
|
||||
open_flags = (unsigned) O_WRONLY | (unsigned) O_CREAT;
|
||||
if(transfer->transfer->override_exiting)
|
||||
open_flags |= (unsigned) O_TRUNC;
|
||||
} else {
|
||||
return FileInitializeResult::INVALID_TRANSFER_DIRECTION;
|
||||
}
|
||||
|
||||
file_data.file_descriptor = open(absolute_path.c_str(), (int) open_flags, 0644);
|
||||
if(file_data.file_descriptor <= 0) {
|
||||
const auto errno_ = errno;
|
||||
switch (errno_) {
|
||||
case EACCES:
|
||||
result = FileInitializeResult::FILE_IS_NOT_ACCESSIBLE;
|
||||
break;
|
||||
case EDQUOT:
|
||||
logWarning(LOG_FT, "{} Disk inode limit has been reached. Failed to start file transfer for file {}", transfer->log_prefix(), absolute_path);
|
||||
result = FileInitializeResult::FILE_SYSTEM_ERROR;
|
||||
break;
|
||||
case EISDIR:
|
||||
result = FileInitializeResult::FILE_IS_A_DIRECTORY;
|
||||
break;
|
||||
case EMFILE:
|
||||
result = FileInitializeResult::PROCESS_FILE_LIMIT_REACHED;
|
||||
break;
|
||||
case ENFILE:
|
||||
result = FileInitializeResult::SYSTEM_FILE_LIMIT_REACHED;
|
||||
break;
|
||||
case ETXTBSY:
|
||||
result = FileInitializeResult::FILE_IS_BUSY;
|
||||
break;
|
||||
case EROFS:
|
||||
result = FileInitializeResult::DISK_IS_READ_ONLY;
|
||||
break;
|
||||
default:
|
||||
logWarning(LOG_FT, "{} Failed to start file {} for file {}: {}/{}", transfer->log_prefix(),
|
||||
transfer->transfer->direction == Transfer::DIRECTION_DOWNLOAD ? "download" : "upload", absolute_path, errno_, strerror(errno_));
|
||||
result = FileInitializeResult::FILE_SYSTEM_ERROR;
|
||||
break;
|
||||
}
|
||||
goto error_exit;
|
||||
}
|
||||
}
|
||||
|
||||
this->file_system_->lock_file(absolute_path);
|
||||
transfer->file.file_locked = true;
|
||||
|
||||
if(transfer->transfer->direction == Transfer::DIRECTION_UPLOAD) {
|
||||
if(ftruncate(file_data.file_descriptor, transfer->transfer->expected_file_size) != 0) {
|
||||
const auto errno_ = errno;
|
||||
switch (errno_) {
|
||||
case EACCES:
|
||||
logWarning(LOG_FT, "{} File {} got inaccessible on truncating, but not on opening.", transfer->log_prefix(), absolute_path);
|
||||
result = FileInitializeResult::FILE_IS_NOT_ACCESSIBLE;
|
||||
goto error_exit;
|
||||
|
||||
case EFBIG:
|
||||
result = FileInitializeResult::FILE_TOO_LARGE;
|
||||
goto error_exit;
|
||||
|
||||
case EIO:
|
||||
logWarning(LOG_FT, "{} A disk IO error occurred while resizing file {}.", transfer->log_prefix(), absolute_path);
|
||||
result = FileInitializeResult::FILE_IS_NOT_ACCESSIBLE;
|
||||
goto error_exit;
|
||||
|
||||
case EROFS:
|
||||
logWarning(LOG_FT, "{} Failed to resize file {} because disk is in read only mode.", transfer->log_prefix(), absolute_path);
|
||||
result = FileInitializeResult::FILE_IS_NOT_ACCESSIBLE;
|
||||
goto error_exit;
|
||||
default:
|
||||
debugMessage(LOG_FT, "{} Failed to truncate file {}: {}/{}. Trying to upload file anyways.", transfer->log_prefix(), absolute_path, errno_, strerror(errno_));
|
||||
}
|
||||
}
|
||||
} else if(transfer->transfer->direction == Transfer::DIRECTION_DOWNLOAD) {
|
||||
auto file_size = lseek(file_data.file_descriptor, 0, SEEK_END);
|
||||
if(file_size != transfer->transfer->expected_file_size) {
|
||||
logWarning(LOG_FT, "{} Expected target file to be of size {}, but file is actually of size {}", transfer->log_prefix(), transfer->transfer->expected_file_size, file_size);
|
||||
result = FileInitializeResult::FILE_SIZE_MISMATCH;
|
||||
goto error_exit;
|
||||
}
|
||||
if(transfer->file.query_media_bytes && file_size > 0) {
|
||||
auto new_pos = lseek(file_data.file_descriptor, 0, SEEK_SET);
|
||||
if(new_pos < 0) {
|
||||
logWarning(LOG_FT, "{} Failed to seek to file start: {}/{}", transfer->log_prefix(), errno, strerror(errno));
|
||||
result = FileInitializeResult::FILE_SEEK_FAILED;
|
||||
goto error_exit;
|
||||
}
|
||||
|
||||
auto read = ::read(file_data.file_descriptor, transfer->file.media_bytes, TRANSFER_MEDIA_BYTES_LENGTH);
|
||||
if(read <= 0) {
|
||||
logWarning(LOG_FT, "{} Failed to read file media bytes: {}/{}", transfer->log_prefix(), errno, strerror(errno));
|
||||
result = FileInitializeResult::FAILED_TO_READ_MEDIA_BYTES;
|
||||
goto error_exit;
|
||||
}
|
||||
transfer->file.media_bytes_length = read;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
auto new_pos = lseek(file_data.file_descriptor, transfer->transfer->file_offset, SEEK_SET);
|
||||
if(new_pos < 0) {
|
||||
logWarning(LOG_FT, "{} Failed to seek to target file offset ({}): {}/{}", transfer->log_prefix(), transfer->transfer->file_offset, errno, strerror(errno));
|
||||
result = FileInitializeResult::FILE_SEEK_FAILED;
|
||||
goto error_exit;
|
||||
} else if(new_pos != transfer->transfer->file_offset) {
|
||||
logWarning(LOG_FT, "{} File rw offset mismatch after seek. Expected {} but received {}", transfer->log_prefix(), transfer->transfer->file_offset, new_pos);
|
||||
result = FileInitializeResult::FILE_SEEK_FAILED;
|
||||
goto error_exit;
|
||||
}
|
||||
logTrace(LOG_FT, "{} Seek to file offset {}. New actual offset is {}", transfer->log_prefix(), transfer->transfer->file_offset, new_pos);
|
||||
}
|
||||
|
||||
return FileInitializeResult::SUCCESS;
|
||||
error_exit:
|
||||
if(std::exchange(transfer->file.file_locked, false))
|
||||
this->file_system_->unlock_file(absolute_path);
|
||||
|
||||
if(file_data.file_descriptor > 0)
|
||||
::close(file_data.file_descriptor);
|
||||
file_data.file_descriptor = 0;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void LocalFileTransfer::finalize_file_io(const std::shared_ptr<FileClient> &transfer,
|
||||
std::unique_lock<std::shared_mutex> &state_lock) {
|
||||
assert(state_lock.owns_lock());
|
||||
if(!transfer->transfer) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto absolute_path = transfer->transfer->absolute_file_path;
|
||||
|
||||
auto& file_data = transfer->file;
|
||||
|
||||
state_lock.unlock();
|
||||
{
|
||||
std::unique_lock dlock{this->disk_io.queue_lock};
|
||||
while(true) {
|
||||
if(file_data.currently_processing) {
|
||||
this->disk_io.notify_client_processed.wait(dlock);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(this->disk_io.queue_head == &*transfer) {
|
||||
this->disk_io.queue_head = file_data.next_client;
|
||||
if (!this->disk_io.queue_head) {
|
||||
this->disk_io.queue_tail = &this->disk_io.queue_head;
|
||||
}
|
||||
} else if(file_data.next_client || this->disk_io.queue_tail == &file_data.next_client) {
|
||||
FileClient* head{this->disk_io.queue_head};
|
||||
while(head->file.next_client != &*transfer) {
|
||||
assert(head->file.next_client);
|
||||
head = head->file.next_client;
|
||||
}
|
||||
|
||||
head->file.next_client = file_data.next_client;
|
||||
if(!file_data.next_client)
|
||||
this->disk_io.queue_tail = &head->file.next_client;
|
||||
}
|
||||
file_data.next_client = nullptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
state_lock.lock();
|
||||
|
||||
if(std::exchange(file_data.file_locked, false)) {
|
||||
this->file_system_->unlock_file(absolute_path);
|
||||
}
|
||||
|
||||
if(file_data.file_descriptor > 0)
|
||||
::close(file_data.file_descriptor);
|
||||
file_data.file_descriptor = 0;
|
||||
}
|
||||
|
||||
void LocalFileTransfer::enqueue_disk_io(const std::shared_ptr<FileClient> &client) {
|
||||
if(!client->file.file_descriptor) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!client->transfer) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(client->transfer->direction == Transfer::DIRECTION_DOWNLOAD) {
|
||||
if(client->state != FileClient::STATE_TRANSFERRING) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(client->disk_buffer.bytes > TRANSFER_MAX_CACHED_BYTES) {
|
||||
return;
|
||||
}
|
||||
} else if(client->transfer->direction == Transfer::DIRECTION_UPLOAD) {
|
||||
/* we don't do this check because this might be a flush instruction, where the buffer is actually zero bytes filled */
|
||||
/*
|
||||
if(client->buffer.bytes == 0)
|
||||
return;
|
||||
*/
|
||||
}
|
||||
|
||||
std::lock_guard dlock{this->disk_io.queue_lock};
|
||||
if(client->file.next_client || this->disk_io.queue_tail == &client->file.next_client) {
|
||||
return;
|
||||
}
|
||||
|
||||
*this->disk_io.queue_tail = &*client;
|
||||
this->disk_io.queue_tail = &client->file.next_client;
|
||||
|
||||
this->disk_io.notify_work_awaiting.notify_all();
|
||||
}
|
||||
|
||||
void LocalFileTransfer::execute_disk_io(const std::shared_ptr<FileClient> &client) {
|
||||
if(!client->transfer) return;
|
||||
|
||||
if(client->transfer->direction == Transfer::DIRECTION_UPLOAD) {
|
||||
Buffer* buffer;
|
||||
size_t buffer_left_size;
|
||||
|
||||
while(true) {
|
||||
{
|
||||
std::lock_guard block{client->disk_buffer.mutex};
|
||||
buffer = client->disk_buffer.buffer_head;
|
||||
buffer_left_size = client->disk_buffer.bytes;
|
||||
}
|
||||
if(!buffer) {
|
||||
assert(buffer_left_size == 0);
|
||||
break;
|
||||
}
|
||||
|
||||
assert(buffer->offset < buffer->length);
|
||||
auto written = ::write(client->file.file_descriptor, buffer->data + buffer->offset, buffer->length - buffer->offset);
|
||||
if(written <= 0) {
|
||||
if(written == 0) {
|
||||
/* EOF, how the hell is this event possible?! */
|
||||
auto offset_written = client->statistics.disk_bytes_write.total_bytes + client->transfer->file_offset;
|
||||
auto aoffset = lseek(client->file.file_descriptor, 0, SEEK_CUR);
|
||||
logError(LOG_FT, "{} Received unexpected file write EOF. EOF received at {} but expected {}. Actual file offset: {}. Closing transfer.",
|
||||
client->log_prefix(), offset_written, client->transfer->expected_file_size, aoffset);
|
||||
|
||||
|
||||
this->invoke_aborted_callback(client, { TransferError::UNEXPECTED_DISK_EOF, strerror(errno) });
|
||||
|
||||
client->flush_disk_buffer();
|
||||
{
|
||||
std::unique_lock slock{client->state_mutex};
|
||||
client->handle->disconnect_client(client, slock, true);
|
||||
}
|
||||
} else {
|
||||
if(errno == EAGAIN) {
|
||||
//TODO: Timeout?
|
||||
this->enqueue_disk_io(client);
|
||||
break;
|
||||
}
|
||||
|
||||
auto offset_written = client->statistics.disk_bytes_write.total_bytes + client->transfer->file_offset;
|
||||
auto aoffset = lseek(client->file.file_descriptor, 0, SEEK_CUR);
|
||||
logError(LOG_FT, "{} Received write to disk IO error. Write pointer is at {} of {}. Actual file offset: {}. Closing transfer.",
|
||||
client->log_prefix(), offset_written, client->transfer->expected_file_size, aoffset);
|
||||
|
||||
this->invoke_aborted_callback(client, { TransferError::DISK_IO_ERROR, strerror(errno) });
|
||||
|
||||
client->flush_disk_buffer();
|
||||
{
|
||||
std::unique_lock slock{client->state_mutex};
|
||||
client->handle->disconnect_client(client, slock, true);
|
||||
}
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
buffer->offset += written;
|
||||
assert(buffer->offset <= buffer->length);
|
||||
if(buffer->length == buffer->offset) {
|
||||
{
|
||||
std::lock_guard block{client->disk_buffer.mutex};
|
||||
client->disk_buffer.buffer_head = buffer->next;
|
||||
if(!buffer->next)
|
||||
client->disk_buffer.buffer_tail = &client->disk_buffer.buffer_head;
|
||||
|
||||
assert(client->disk_buffer.bytes >= written);
|
||||
client->disk_buffer.bytes -= written;
|
||||
buffer_left_size = client->disk_buffer.bytes;
|
||||
(void) buffer_left_size; /* trick my IDE here a bit */
|
||||
}
|
||||
|
||||
free_buffer(buffer);
|
||||
} else {
|
||||
std::lock_guard block{client->disk_buffer.mutex};
|
||||
assert(client->disk_buffer.bytes >= written);
|
||||
client->disk_buffer.bytes -= written;
|
||||
buffer_left_size = client->disk_buffer.bytes;
|
||||
(void) buffer_left_size; /* trick my IDE here a bit */
|
||||
}
|
||||
|
||||
client->statistics.disk_bytes_write.increase_bytes(written);
|
||||
}
|
||||
}
|
||||
|
||||
if(buffer_left_size > 0) {
|
||||
this->enqueue_disk_io(client);
|
||||
} else if(client->state == FileClient::STATE_FLUSHING) {
|
||||
this->test_disconnecting_state(client);
|
||||
}
|
||||
|
||||
if(client->state == FileClient::STATE_TRANSFERRING && buffer_left_size < TRANSFER_MAX_CACHED_BYTES / 2) {
|
||||
if(client->disk_buffer.buffering_stopped)
|
||||
logMessage(LOG_FT, "{} Starting network read, buffer is capable for reading again.", client->log_prefix());
|
||||
client->add_network_read_event(false);
|
||||
}
|
||||
} else if(client->transfer->direction == Transfer::DIRECTION_DOWNLOAD) {
|
||||
if(client->state == FileClient::STATE_FLUSHING) {
|
||||
client->flush_disk_buffer(); /* just in case, file download usually don't write to the disk */
|
||||
return;
|
||||
}
|
||||
|
||||
while(true) {
|
||||
constexpr auto buffer_capacity{4096};
|
||||
char buffer[buffer_capacity];
|
||||
|
||||
auto read = ::read(client->file.file_descriptor, buffer, buffer_capacity);
|
||||
if(read <= 0) {
|
||||
if(read == 0) {
|
||||
/* EOF */
|
||||
auto offset_send = client->statistics.disk_bytes_read.total_bytes + client->transfer->file_offset;
|
||||
if(client->transfer->expected_file_size == offset_send) {
|
||||
debugMessage(LOG_FT, "{} Finished file reading. Flushing and disconnecting transfer. Reading took {} seconds.",
|
||||
client->log_prefix(), duration_to_string(std::chrono::system_clock::now() - client->timings.key_received));
|
||||
} else {
|
||||
auto aoffset = lseek(client->file.file_descriptor, 0, SEEK_CUR);
|
||||
logError(LOG_FT, "{} Received unexpected read EOF. EOF received at {} but expected {}. Actual file offset: {}. Disconnecting client.",
|
||||
client->log_prefix(), offset_send, client->transfer->expected_file_size, aoffset);
|
||||
|
||||
this->invoke_aborted_callback(client, { TransferError::UNEXPECTED_DISK_EOF, "" });
|
||||
}
|
||||
} else {
|
||||
if(errno == EAGAIN) {
|
||||
this->enqueue_disk_io(client);
|
||||
return;
|
||||
}
|
||||
|
||||
logWarning(LOG_FT, "{} Failed to read from file {} ({}/{}). Aborting transfer.", client->log_prefix(), client->transfer->absolute_file_path, errno, strerror(errno));
|
||||
|
||||
this->invoke_aborted_callback(client, { TransferError::DISK_IO_ERROR, strerror(errno) });
|
||||
}
|
||||
|
||||
std::unique_lock slock{client->state_mutex};
|
||||
client->handle->disconnect_client(client, slock, true);
|
||||
return;
|
||||
} else {
|
||||
auto buffer_full = client->send_file_bytes(buffer, read);
|
||||
client->statistics.disk_bytes_read.increase_bytes(read);
|
||||
client->statistics.file_transferred.increase_bytes(read);
|
||||
|
||||
std::shared_lock slock{client->state_mutex};
|
||||
if(buffer_full) {
|
||||
//logTrace(LOG_FT, "{} Stopping buffering from disk. Buffer full ({}bytes)", client->log_prefix(), client->buffer.bytes);
|
||||
break;
|
||||
}
|
||||
|
||||
/* we've stuff to write again, yeahr */
|
||||
client->add_network_write_event(false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logError(LOG_FT, "{} Disk IO scheduled, but transfer direction is unknown.", client->log_prefix());
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,9 @@
|
||||
//
|
||||
// Created by WolverinDEV on 12/05/2020.
|
||||
//
|
||||
|
||||
#include "./NetTools.h"
|
||||
|
||||
using namespace ts::server::file::networking;
|
||||
|
||||
NetworkThrottle NetworkThrottle::kNoThrottle{-1};
|
||||
@@ -0,0 +1,166 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
#include <cassert>
|
||||
#include <array>
|
||||
|
||||
#include <misc/spin_mutex.h>
|
||||
#include <numeric>
|
||||
|
||||
namespace ts::server::file::networking {
|
||||
struct NetworkThrottle {
|
||||
constexpr static auto kThrottleTimespanMs{250};
|
||||
|
||||
typedef uint8_t span_t;
|
||||
|
||||
static NetworkThrottle kNoThrottle;
|
||||
|
||||
ssize_t max_bytes{0};
|
||||
|
||||
span_t current_index{0};
|
||||
size_t bytes_send{0};
|
||||
|
||||
mutable spin_mutex mutex{};
|
||||
|
||||
inline bool increase_bytes(size_t bytes) {
|
||||
auto current_ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
auto current_span = (span_t) (current_ms / kThrottleTimespanMs);
|
||||
|
||||
std::lock_guard slock{this->mutex};
|
||||
if(this->current_index != current_span) {
|
||||
this->current_index = current_span;
|
||||
this->bytes_send = bytes;
|
||||
} else {
|
||||
this->bytes_send += bytes;
|
||||
}
|
||||
return this->max_bytes > 0 && this->bytes_send >= this->max_bytes;
|
||||
}
|
||||
|
||||
inline void set_max_bandwidth(ssize_t bytes_per_second) {
|
||||
std::lock_guard slock{this->mutex};
|
||||
if(bytes_per_second <= 0)
|
||||
this->max_bytes = -1;
|
||||
else
|
||||
this->max_bytes = bytes_per_second * kThrottleTimespanMs / 1000;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool should_throttle(timeval& next_timestamp) {
|
||||
if(this->max_bytes <= 0) return false;
|
||||
|
||||
auto current_ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
auto current_span = (span_t) (current_ms / kThrottleTimespanMs);
|
||||
|
||||
std::lock_guard slock{this->mutex};
|
||||
if(this->max_bytes <= 0) return false; /* we've to test here again, else out arithmetic will fail */
|
||||
if(this->current_index != current_span) return false;
|
||||
if(this->bytes_send < this->max_bytes) return false;
|
||||
|
||||
next_timestamp.tv_usec = (kThrottleTimespanMs - current_ms % kThrottleTimespanMs) * 1000;
|
||||
next_timestamp.tv_sec = next_timestamp.tv_usec / 1000000;
|
||||
next_timestamp.tv_usec -= next_timestamp.tv_sec * 1000000;
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline size_t bytes_left() const {
|
||||
if(this->max_bytes <= 0) return (size_t) -1;
|
||||
|
||||
auto current_ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
auto current_span = (span_t) (current_ms / kThrottleTimespanMs);
|
||||
|
||||
std::lock_guard slock{this->mutex};
|
||||
if(this->max_bytes <= 0) return false; /* we've to test here again, else out arithmetic will fail */
|
||||
if(this->current_index != current_span) return this->max_bytes;
|
||||
if(this->bytes_send < this->max_bytes) return this->max_bytes - this->bytes_send;
|
||||
return 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline std::chrono::milliseconds expected_writing_time(size_t bytes) const {
|
||||
std::lock_guard slock{this->mutex};
|
||||
|
||||
if(this->max_bytes <= 0) return std::chrono::milliseconds{0};
|
||||
return std::chrono::seconds{bytes / (this->max_bytes * (1000 / kThrottleTimespanMs))};
|
||||
}
|
||||
};
|
||||
|
||||
struct DualNetworkThrottle {
|
||||
NetworkThrottle *left, *right;
|
||||
|
||||
explicit DualNetworkThrottle(NetworkThrottle* left, NetworkThrottle* right) : left{left}, right{right} {
|
||||
assert(left);
|
||||
assert(right);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline size_t bytes_left() const {
|
||||
return std::min(this->left->bytes_left(), this->right->bytes_left());
|
||||
}
|
||||
|
||||
[[nodiscard]] inline std::chrono::milliseconds expected_writing_time(size_t bytes) const {
|
||||
return std::max(this->left->expected_writing_time(bytes), this->right->expected_writing_time(bytes));
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool should_throttle(timeval& next_timestamp) const {
|
||||
bool throttle = false;
|
||||
timeval right_timestamp{};
|
||||
throttle |= this->left->should_throttle(next_timestamp);
|
||||
throttle |= this->right->should_throttle(right_timestamp);
|
||||
if(!throttle) return false;
|
||||
|
||||
if(right_timestamp.tv_sec > next_timestamp.tv_sec || (right_timestamp.tv_sec == next_timestamp.tv_sec && right_timestamp.tv_usec > next_timestamp.tv_usec))
|
||||
next_timestamp = right_timestamp;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool increase_bytes(size_t bytes) {
|
||||
bool result = false;
|
||||
result |= this->left->increase_bytes(bytes);
|
||||
result |= this->right->increase_bytes(bytes);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
struct TransferStatistics {
|
||||
constexpr static auto kMeasureTimespanMs{1000};
|
||||
constexpr static auto kAverageTimeCount{60};
|
||||
typedef uint8_t span_t;
|
||||
|
||||
size_t total_bytes{0};
|
||||
size_t delta_bytes{0}; /* used for statistics propagation */
|
||||
|
||||
span_t span_index{0};
|
||||
size_t span_bytes{0};
|
||||
std::array<size_t, kAverageTimeCount> history{};
|
||||
|
||||
spin_mutex mutex{};
|
||||
|
||||
inline void increase_bytes(size_t bytes) {
|
||||
auto current_ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
auto current_span = (span_t) (current_ms / kMeasureTimespanMs);
|
||||
|
||||
std::lock_guard slock{this->mutex};
|
||||
this->total_bytes += bytes;
|
||||
|
||||
if(this->span_index != current_span)
|
||||
this->history[this->span_index % kAverageTimeCount] = std::exchange(this->span_bytes, 0);
|
||||
this->span_index = current_span;
|
||||
this->span_bytes += bytes;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline size_t take_delta() {
|
||||
std::lock_guard slock{this->mutex};
|
||||
assert(this->delta_bytes <= this->total_bytes);
|
||||
auto delta = this->total_bytes - this->delta_bytes;
|
||||
this->delta_bytes = this->total_bytes;
|
||||
return delta;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline double current_bandwidth() const {
|
||||
return (this->history[(this->span_index - 1) % kAverageTimeCount] * (double) 1000) / (double) kMeasureTimespanMs;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline double average_bandwidth() const {
|
||||
return (std::accumulate(this->history.begin(), this->history.end(), 0UL) * (double) 1000) / (double) (kMeasureTimespanMs * kAverageTimeCount);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
//
|
||||
// Created by WolverinDEV on 29/04/2020.
|
||||
//
|
||||
|
||||
#include "clnpath.h"
|
||||
#include <cstring>
|
||||
|
||||
#define MAX_PATH_ELEMENTS 128 /* Number of levels of directory */
|
||||
void ts_clnpath(char *path)
|
||||
{
|
||||
char *src;
|
||||
char *dst;
|
||||
char c;
|
||||
int slash = 0;
|
||||
|
||||
/* Convert multiple adjacent slashes to single slash */
|
||||
src = dst = path;
|
||||
while ((c = *dst++ = *src++) != '\0')
|
||||
{
|
||||
if (c == '/')
|
||||
{
|
||||
slash = 1;
|
||||
while (*src == '/')
|
||||
src++;
|
||||
}
|
||||
}
|
||||
|
||||
if (slash == 0)
|
||||
return;
|
||||
|
||||
/* Remove "./" from "./xxx" but leave "./" alone. */
|
||||
/* Remove "/." from "xxx/." but reduce "/." to "/". */
|
||||
/* Reduce "xxx/./yyy" to "xxx/yyy" */
|
||||
src = dst = (*path == '/') ? path + 1 : path;
|
||||
while (src[0] == '.' && src[1] == '/' && src[2] != '\0')
|
||||
src += 2;
|
||||
while ((c = *dst++ = *src++) != '\0')
|
||||
{
|
||||
if (c == '/' && src[0] == '.' && (src[1] == '\0' || src[1] == '/'))
|
||||
{
|
||||
src++;
|
||||
dst--;
|
||||
}
|
||||
}
|
||||
if (path[0] == '/' && path[1] == '.' &&
|
||||
(path[2] == '\0' || (path[2] == '/' && path[3] == '\0')))
|
||||
path[1] = '\0';
|
||||
|
||||
/* Remove trailing slash, if any. There is at most one! */
|
||||
/* dst is pointing one beyond terminating null */
|
||||
if ((dst -= 2) > path && *dst == '/')
|
||||
*dst++ = '\0';
|
||||
}
|
||||
|
||||
bool ts_strequal(const char* a, const char* b) {
|
||||
return strcmp(a, b) == 0;
|
||||
}
|
||||
|
||||
int ts_tokenise(char* ostring, const char* del, char** result, int max_tokens) {
|
||||
int num_tokens{0};
|
||||
|
||||
char* token, *string, *tofree;
|
||||
tofree = string = strdup(ostring);
|
||||
while ((token = strsep(&string, del)) != nullptr) {
|
||||
result[num_tokens++] = strdup(token);
|
||||
if(num_tokens > max_tokens)
|
||||
break;
|
||||
}
|
||||
|
||||
free(tofree);
|
||||
return num_tokens;
|
||||
}
|
||||
|
||||
/*
|
||||
** clnpath2() is not part of the basic clnpath() function because it can
|
||||
** change the meaning of a path name if there are symbolic links on the
|
||||
** system. For example, suppose /usr/tmp is a symbolic link to /var/tmp.
|
||||
** If the user supplies /usr/tmp/../abcdef as the directory name, clnpath
|
||||
** would transform that to /usr/abcdef, not to /var/abcdef which is what
|
||||
** the kernel would interpret it as.
|
||||
*/
|
||||
void ts_clnpath2(char *path)
|
||||
{
|
||||
char *token[MAX_PATH_ELEMENTS], *otoken[MAX_PATH_ELEMENTS];
|
||||
int ntok, ontok;
|
||||
|
||||
ts_clnpath(path);
|
||||
|
||||
/* Reduce "<name>/.." to "/" */
|
||||
ntok = ontok = ts_tokenise(path, "/", otoken, MAX_PATH_ELEMENTS);
|
||||
memcpy(token, otoken, sizeof(char*) * ntok);
|
||||
|
||||
if (ntok > 1) {
|
||||
for (int i = 0; i < ntok - 1; i++)
|
||||
{
|
||||
if (!ts_strequal(token[i], "..") && ts_strequal(token[i + 1], ".."))
|
||||
{
|
||||
if (*token[i] == '\0')
|
||||
continue;
|
||||
while (i < ntok - 1)
|
||||
{
|
||||
token[i] = token[i + 2];
|
||||
i++;
|
||||
}
|
||||
ntok -= 2;
|
||||
i = -1; /* Restart enclosing for loop */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Reassemble string */
|
||||
char *dst = path;
|
||||
if (ntok == 0)
|
||||
{
|
||||
*dst++ = '.';
|
||||
*dst = '\0';
|
||||
}
|
||||
else
|
||||
{
|
||||
if (token[0][0] == '\0')
|
||||
{
|
||||
int i;
|
||||
for (i = 1; i < ntok && ts_strequal(token[i], ".."); i++)
|
||||
;
|
||||
if (i > 1)
|
||||
{
|
||||
int j;
|
||||
for (j = 1; i < ntok; i++)
|
||||
token[j++] = token[i];
|
||||
ntok = j;
|
||||
}
|
||||
}
|
||||
if (ntok == 1 && token[0][0] == '\0')
|
||||
{
|
||||
*dst++ = '/';
|
||||
*dst = '\0';
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < ntok; i++)
|
||||
{
|
||||
char *src = token[i];
|
||||
while ((*dst++ = *src++) != '\0')
|
||||
;
|
||||
*(dst - 1) = '/';
|
||||
}
|
||||
*(dst - 1) = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
for(int i{0}; i < ontok; i++)
|
||||
::free(otoken[i]);
|
||||
}
|
||||
|
||||
std::string clnpath(const std::string_view& data) {
|
||||
std::string result{data};
|
||||
ts_clnpath2(result.data());
|
||||
auto index = result.find((char) 0);
|
||||
if(index != std::string::npos)
|
||||
result.resize(index);
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifdef CLN_EXEC
|
||||
typedef struct p1_test_case
|
||||
{
|
||||
const char *input;
|
||||
const char *output;
|
||||
} p1_test_case;
|
||||
|
||||
/* This stress tests the cleaning, concentrating on the boundaries. */
|
||||
static const p1_test_case p1_tests[] =
|
||||
{
|
||||
{ "/", "/", },
|
||||
{ "//", "/", },
|
||||
{ "///", "/", },
|
||||
{ "/.", "/", },
|
||||
{ "/./", "/", },
|
||||
{ "/./.", "/", },
|
||||
{ "/././.profile", "/.profile", },
|
||||
{ "./", ".", },
|
||||
{ "./.", ".", },
|
||||
{ "././", ".", },
|
||||
{ "./././.profile", ".profile", },
|
||||
{ "abc/.", "abc", },
|
||||
{ "abc/./def", "abc/def", },
|
||||
{ "./abc", "abc", },
|
||||
|
||||
{ "//abcd///./abcd////", "/abcd/abcd", },
|
||||
{ "//abcd///././../defg///ddd//.", "/abcd/../defg/ddd", },
|
||||
{ "/abcd/./../././defg/./././ddd", "/abcd/../defg/ddd", },
|
||||
{ "//abcd//././../defg///ddd//.///", "/abcd/../defg/ddd", },
|
||||
|
||||
/* Most of these are minimal interest in phase 1 */
|
||||
{ "/usr/tmp/clnpath.c", "/usr/tmp/clnpath.c", },
|
||||
{ "/usr/tmp/", "/usr/tmp", },
|
||||
{ "/bin/..", "/bin/..", },
|
||||
{ "bin/..", "bin/..", },
|
||||
{ "/bin/.", "/bin", },
|
||||
{ "sub/directory", "sub/directory", },
|
||||
{ "sub/directory/file", "sub/directory/file", },
|
||||
{ "/part1/part2/../.././../", "/part1/part2/../../..", },
|
||||
{ "/.././../usr//.//bin/./cc", "/../../usr/bin/cc", },
|
||||
};
|
||||
|
||||
static void p1_tester(const void *data)
|
||||
{
|
||||
const p1_test_case *test = (const p1_test_case *)data;
|
||||
char buffer[256];
|
||||
|
||||
strcpy(buffer, test->input);
|
||||
ts_clnpath(buffer);
|
||||
if (strcmp(buffer, test->output) == 0)
|
||||
printf("<<%s>> cleans to <<%s>>\n", test->input, buffer);
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "<<%s>> - unexpected output from clnpath()\n", test->input);
|
||||
fprintf(stderr, "Wanted <<%s>>\n", test->output);
|
||||
fprintf(stderr, "Actual <<%s>>\n", buffer);
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct p2_test_case
|
||||
{
|
||||
const char *input;
|
||||
const char *output;
|
||||
} p2_test_case;
|
||||
|
||||
static const p2_test_case p2_tests[] =
|
||||
{
|
||||
{ "/abcd/../defg/ddd", "/defg/ddd" },
|
||||
{ "/bin/..", "/" },
|
||||
{ "bin/..", "." },
|
||||
{ "/usr/bin/..", "/usr" },
|
||||
{ "/usr/bin/../..", "/" },
|
||||
{ "usr/bin/../..", "." },
|
||||
{ "../part/of/../the/way", "../part/the/way" },
|
||||
{ "/../part/of/../the/way", "/part/the/way" },
|
||||
{ "part1/part2/../../part3", "part3" },
|
||||
{ "part1/part2/../../../part3", "../part3" },
|
||||
{ "/part1/part2/../../../part3", "/part3" },
|
||||
{ "/part1/part2/../../../", "/" },
|
||||
{ "/../../usr/bin/cc", "/usr/bin/cc" },
|
||||
{ "../../usr/bin/cc", "../../usr/bin/cc" },
|
||||
{ "part1/./part2/../../part3", "part3" },
|
||||
{ "./part1/part2/../../../part3", "../part3" },
|
||||
{ "/part1/part2/.././../../part3", "/part3" },
|
||||
{ "/part1/part2/../.././../", "/" },
|
||||
{ "/.././..//./usr///bin/cc/", "/usr/bin/cc" },
|
||||
{nullptr, nullptr}
|
||||
};
|
||||
|
||||
static void p2_tester(const void *data)
|
||||
{
|
||||
auto test = (const p2_test_case *)data;
|
||||
char buffer[256];
|
||||
|
||||
strcpy(buffer, test->input);
|
||||
ts_clnpath2(buffer);
|
||||
if (strcmp(buffer, test->output) == 0)
|
||||
printf("<<%s>> cleans to <<%s>>\n", test->input, buffer);
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "<<%s>> - unexpected output from clnpath2()\n", test->input);
|
||||
fprintf(stderr, "Wanted <<%s>>\n", test->output);
|
||||
fprintf(stderr, "Actual <<%s>>\n", buffer);
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
for(const auto& test : p1_tests) {
|
||||
if(!test.input) break;
|
||||
p1_tester(&test);
|
||||
}
|
||||
|
||||
printf("------------------------------\n");
|
||||
for(const auto& test : p2_tests) {
|
||||
if(!test.input) break;
|
||||
p2_tester(&test);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
extern std::string clnpath(const std::string_view&);
|
||||
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
template <typename Rep, typename Per>
|
||||
inline std::string duration_to_string(std::chrono::duration<Rep, Per> ms) {
|
||||
std::string result{};
|
||||
|
||||
{
|
||||
auto hours = std::chrono::duration_cast<std::chrono::hours>(ms);
|
||||
if(hours.count())
|
||||
result += std::to_string(hours.count()) + " hours ";
|
||||
ms -= hours;
|
||||
}
|
||||
{
|
||||
auto minutes = std::chrono::duration_cast<std::chrono::minutes>(ms);
|
||||
if(minutes.count())
|
||||
result += std::to_string(minutes.count()) + " minutes ";
|
||||
ms -= minutes;
|
||||
}
|
||||
{
|
||||
auto seconds = std::chrono::duration_cast<std::chrono::seconds>(ms);
|
||||
if(seconds.count())
|
||||
result += std::to_string(seconds.count()) + " seconds ";
|
||||
ms -= seconds;
|
||||
}
|
||||
if(result.empty()) {
|
||||
auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(ms);
|
||||
if(milliseconds.count())
|
||||
result = std::to_string(milliseconds.count()) + " milliseconds ";
|
||||
}
|
||||
if(result.empty()) {
|
||||
auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(ms);
|
||||
result = std::to_string(microseconds.count()) + " microseconds ";
|
||||
}
|
||||
return result.substr(0, result.length() - 1);
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
//
|
||||
// Created by WolverinDEV on 29/04/2020.
|
||||
//
|
||||
|
||||
#include <files/FileServer.h>
|
||||
#include <log/LogUtils.h>
|
||||
|
||||
#include <experimental/filesystem>
|
||||
#include <local_server/clnpath.h>
|
||||
#include <event2/thread.h>
|
||||
#include <include/files/Config.h>
|
||||
#include <local_server/HTTPUtils.h>
|
||||
|
||||
namespace fs = std::experimental::filesystem;
|
||||
|
||||
using namespace ts::server;
|
||||
|
||||
struct Nothing {};
|
||||
|
||||
template <typename ErrorType, typename ResponseType>
|
||||
inline void print_fs_response(const std::string& message, const std::shared_ptr<file::ExecuteResponse<file::filesystem::DetailedError<ErrorType>, ResponseType>>& response) {
|
||||
if(response->status == file::ExecuteStatus::ERROR)
|
||||
logError(LOG_FT, "{}: {} => {}", message, (int) response->error().error_type, response->error().error_message);
|
||||
else if(response->status == file::ExecuteStatus::SUCCESS)
|
||||
logMessage(LOG_FT, "{}: success", message);
|
||||
else
|
||||
logWarning(LOG_FT, "Unknown response state ({})!", (int) response->status);
|
||||
}
|
||||
|
||||
template <typename ErrorType, typename ResponseType>
|
||||
inline void print_ft_response(const std::string& message, const std::shared_ptr<file::ExecuteResponse<ErrorType, ResponseType>>& response) {
|
||||
if(response->status == file::ExecuteStatus::ERROR)
|
||||
logError(LOG_FT, "{}: {} => {}", message, (int) response->error().error_type, response->error().error_message);
|
||||
else if(response->status == file::ExecuteStatus::SUCCESS)
|
||||
logMessage(LOG_FT, "{}: success", message);
|
||||
else
|
||||
logWarning(LOG_FT, "Unknown response state ({})!", (int) response->status);
|
||||
}
|
||||
|
||||
inline void print_query(const std::string& message, const file::filesystem::AbstractProvider::directory_query_response_t& response) {
|
||||
if(response.status == file::ExecuteStatus::ERROR)
|
||||
logError(LOG_FT, "{}: {} => {}", message, (int) response.error().error_type, response.error().error_message);
|
||||
else if(response.status == file::ExecuteStatus::SUCCESS) {
|
||||
const auto& entries = response.response();
|
||||
logMessage(LOG_FT, "{}: Found {} entries", message, entries.size());
|
||||
for(auto& entry : entries) {
|
||||
if(entry.type == file::filesystem::DirectoryEntry::FILE)
|
||||
logMessage(LOG_FT, " - File {}", entry.name);
|
||||
else if(entry.type == file::filesystem::DirectoryEntry::DIRECTORY)
|
||||
logMessage(LOG_FT, " - Directory {}", entry.name);
|
||||
else
|
||||
logMessage(LOG_FT, " - Unknown {}", entry.name);
|
||||
logMessage(LOG_FT, " Write timestamp: {}", std::chrono::floor<std::chrono::seconds>(entry.modified_at.time_since_epoch()).count());
|
||||
logMessage(LOG_FT, " Size: {}", entry.size);
|
||||
}
|
||||
} else
|
||||
logWarning(LOG_FT, "{}: Unknown response state ({})!", message, (int) response.status);
|
||||
}
|
||||
|
||||
EVP_PKEY* ssl_generate_key() {
|
||||
auto key = std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>(EVP_PKEY_new(), ::EVP_PKEY_free);
|
||||
|
||||
auto rsa = RSA_new();
|
||||
auto e = std::unique_ptr<BIGNUM, decltype(&BN_free)>(BN_new(), ::BN_free);
|
||||
BN_set_word(e.get(), RSA_F4);
|
||||
if(!RSA_generate_key_ex(rsa, 2048, e.get(), nullptr)) return nullptr;
|
||||
EVP_PKEY_assign_RSA(key.get(), rsa);
|
||||
return key.release();
|
||||
}
|
||||
|
||||
X509* ssl_generate_certificate(EVP_PKEY* key) {
|
||||
auto cert = X509_new();
|
||||
X509_set_pubkey(cert, key);
|
||||
|
||||
ASN1_INTEGER_set(X509_get_serialNumber(cert), 3);
|
||||
X509_gmtime_adj(X509_get_notBefore(cert), 0);
|
||||
X509_gmtime_adj(X509_get_notAfter(cert), 31536000L);
|
||||
|
||||
X509_NAME* name = nullptr;
|
||||
name = X509_get_subject_name(cert);
|
||||
//for(const auto& subject : this->subjects)
|
||||
// X509_NAME_add_entry_by_txt(name, subject.first.c_str(), MBSTRING_ASC, (unsigned char *) subject.second.c_str(), subject.second.length(), -1, 0);
|
||||
X509_set_subject_name(cert, name);
|
||||
|
||||
name = X509_get_issuer_name(cert);
|
||||
//for(const auto& subject : this->issues)
|
||||
// X509_NAME_add_entry_by_txt(name, subject.first.c_str(), MBSTRING_ASC, (unsigned char *) subject.second.c_str(), subject.second.length(), -1, 0);
|
||||
|
||||
X509_set_issuer_name(cert, name);
|
||||
|
||||
X509_sign(cert, key, EVP_sha512());
|
||||
return cert;
|
||||
}
|
||||
|
||||
int main() {
|
||||
evthread_use_pthreads();
|
||||
{
|
||||
std::map<std::string, std::string> query{};
|
||||
http::parse_url_parameters("http://www.example.org/suche?stichwort=wiki&no-arg&1arg=&ausgabe=liste&test=test#bla=d&blub=c", query);
|
||||
for(const auto& [key, value] : query)
|
||||
std::cout << key << " => " << value << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto log_config = std::make_shared<logger::LoggerConfig>();
|
||||
log_config->terminalLevel = spdlog::level::trace;
|
||||
logger::setup(log_config);
|
||||
|
||||
std::string error{};
|
||||
if(!file::initialize(error)) {
|
||||
logError(LOG_FT, "Failed to initialize file server: {}", error);
|
||||
return 0;
|
||||
}
|
||||
logMessage(LOG_FT, "File server started");
|
||||
auto instance = file::server();
|
||||
|
||||
|
||||
{
|
||||
auto options = std::make_shared<pipes::SSL::Options>();
|
||||
options->verbose_io = true;
|
||||
options->context_method = SSLv23_method();
|
||||
options->free_unused_keypairs = false;
|
||||
|
||||
{
|
||||
std::shared_ptr<EVP_PKEY> pkey{ssl_generate_key(), ::EVP_PKEY_free};
|
||||
std::shared_ptr<X509> cert{ssl_generate_certificate(&*pkey), ::X509_free};
|
||||
|
||||
options->default_keypair({pkey, cert});
|
||||
}
|
||||
file::config::ssl_option_supplier = [options]{
|
||||
return options;
|
||||
};
|
||||
}
|
||||
|
||||
#if 0
|
||||
auto& fs = instance->file_system();
|
||||
{
|
||||
auto response = fs.initialize_server(0);
|
||||
response->wait();
|
||||
print_fs_response("Server init result", response);
|
||||
if(response->status != file::ExecuteStatus::SUCCESS)
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
auto response = fs.create_channel_directory(0, 2, "/");
|
||||
response->wait();
|
||||
print_fs_response("Channel dir create A", response);
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
auto response = fs.create_channel_directory(0, 2, "/test-folder/");
|
||||
response->wait();
|
||||
print_fs_response("Channel dir create B", response);
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
auto response = fs.create_channel_directory(0, 2, "../test-folder/");
|
||||
response->wait();
|
||||
print_fs_response("Channel dir create C", response);
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
auto response = fs.create_channel_directory(0, 2, "./test-folder/../test-folder-2");
|
||||
response->wait();
|
||||
print_fs_response("Channel dir create D", response);
|
||||
}
|
||||
|
||||
{
|
||||
auto response = fs.query_channel_directory(0, 2, "/");
|
||||
response->wait();
|
||||
print_query("Channel query", *response);
|
||||
}
|
||||
|
||||
{
|
||||
auto response = fs.query_icon_directory(0);
|
||||
response->wait();
|
||||
print_query("Icons", *response);
|
||||
}
|
||||
|
||||
{
|
||||
auto response = fs.query_avatar_directory(0);
|
||||
response->wait();
|
||||
print_query("Avatars", *response);
|
||||
}
|
||||
|
||||
{
|
||||
auto response = fs.rename_channel_file(0, 2, "./test-folder/../test-folder-2", "./test-folder/../test-folder-3");
|
||||
response->wait();
|
||||
print_fs_response("Folder rename A", response);
|
||||
}
|
||||
|
||||
{
|
||||
auto response = fs.rename_channel_file(0, 2, "./test-folder/../test-folder-3", "./test-folder/../test-folder-2");
|
||||
response->wait();
|
||||
print_fs_response("Folder rename B", response);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
auto& ft = instance->file_transfer();
|
||||
|
||||
ft.callback_transfer_finished = [](const std::shared_ptr<file::transfer::Transfer>& transfer) {
|
||||
logMessage(0, "Transfer finished");
|
||||
};
|
||||
|
||||
ft.callback_transfer_started = [](const std::shared_ptr<file::transfer::Transfer>& transfer) {
|
||||
logMessage(0, "Transfer started");
|
||||
};
|
||||
|
||||
ft.callback_transfer_aborted = [](const std::shared_ptr<file::transfer::Transfer>& transfer, const transfer::TransferStatistics&, const file::transfer::TransferError& error) {
|
||||
logMessage(0, "Transfer aborted ({}/{})", (int) error.error_type, error.error_message);
|
||||
};
|
||||
|
||||
ft.callback_transfer_statistics = [](const std::shared_ptr<file::transfer::Transfer>& transfer, const file::transfer::TransferStatistics& stats) {
|
||||
logMessage(0, "Transfer stats. New file bytes: {}, delta bytes send {}", stats.delta_file_bytes_transferred, stats.delta_network_bytes_send);
|
||||
};
|
||||
|
||||
{
|
||||
auto response = ft.initialize_channel_transfer(file::transfer::Transfer::DIRECTION_UPLOAD, 0, 2, {
|
||||
"test2.txt",
|
||||
false,
|
||||
4,
|
||||
120,
|
||||
32
|
||||
});
|
||||
response->wait();
|
||||
print_ft_response("Download test.txt", response);
|
||||
if(response->succeeded())
|
||||
logMessage(LOG_FT, "Download key: {}", std::string{response->response()->transfer_key, TRANSFER_KEY_LENGTH});
|
||||
}
|
||||
#endif
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::seconds{120});
|
||||
//TODO: Test file locking
|
||||
file::finalize();
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
Hello World
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
@@ -0,0 +1 @@
|
||||
Test HTTPS file upload!
|
||||
@@ -1,25 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.6)
|
||||
project(TeamSpeak)
|
||||
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
|
||||
|
||||
include_directories(../shared/src)
|
||||
add_definitions(-DLTM_DESC)
|
||||
|
||||
set(SOURCE_FILES
|
||||
main.cpp
|
||||
src/ProxiedClient.cpp
|
||||
src/PorxiedClientSock5.cpp
|
||||
src/TSClient.cpp
|
||||
)
|
||||
|
||||
add_executable(TeamSpeakFloodClient ${SOURCE_FILES})
|
||||
target_link_libraries(TeamSpeakFloodClient
|
||||
TeaSpeak
|
||||
pthread ThreadPool
|
||||
${TOM_LIBRARIES}
|
||||
crypto
|
||||
event
|
||||
event_pthreads
|
||||
/usr/local/lib/libjsoncpp.so
|
||||
)
|
||||
@@ -1,81 +0,0 @@
|
||||
#include <arpa/inet.h>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <src/ProxiedClient.h>
|
||||
#include <event2/thread.h>
|
||||
#include <iomanip>
|
||||
|
||||
using namespace std;
|
||||
using namespace ts::flood;
|
||||
|
||||
void hexout(std::ostream& os, unsigned char c)
|
||||
{
|
||||
unsigned char uc = static_cast<unsigned char>(c);
|
||||
os << std::setw(2) << std::setfill('0') << (unsigned int)uc << ' ';
|
||||
}
|
||||
|
||||
void hexdump(std::ostream& outs, const std::string& s, size_t line_len = 16)
|
||||
{
|
||||
std::ostringstream os;
|
||||
const std::string::size_type slen(s.size());
|
||||
int i(0);
|
||||
std::string::size_type pos(0);
|
||||
const std::streamsize lines(slen / line_len);
|
||||
const std::streamsize chars(slen % line_len);
|
||||
std::ios::fmtflags f(os.flags());
|
||||
|
||||
os << "Length: " << s.length() << "/" << std::hex << "0x" << s.length() << endl;
|
||||
for(std::streamsize line = 0; line <= lines - (chars == 0 ? 1 : 0); ++line)
|
||||
{
|
||||
os << std::hex << setfill('0') << setw(3) << line * line_len << " | ";
|
||||
for(i = 0; i < line_len; ++i)
|
||||
{
|
||||
if(pos < s.length())
|
||||
hexout(os, s[pos]);
|
||||
else os << " ";
|
||||
pos++;
|
||||
}
|
||||
os << " | ";
|
||||
if(pos - line_len < s.length()){
|
||||
auto av = s.substr(pos - line_len);
|
||||
for(char c : av.substr(0, min(av.length(), line_len))){
|
||||
if(isprint(c))
|
||||
os << c << " ";
|
||||
else
|
||||
os << "." << " ";
|
||||
}
|
||||
}
|
||||
os << '\n';
|
||||
}
|
||||
os.flags(f);
|
||||
outs << os.str() << endl;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv){
|
||||
sockaddr_in remoteAddress{}, proxyAddress{};
|
||||
memset(&remoteAddress, 0, sizeof(remoteAddress));
|
||||
memset(&proxyAddress, 0, sizeof(proxyAddress));
|
||||
|
||||
|
||||
proxyAddress.sin_family = AF_INET;
|
||||
proxyAddress.sin_port = htons(1085);
|
||||
proxyAddress.sin_addr.s_addr = inet_addr("185.89.100.17");
|
||||
|
||||
/*
|
||||
proxyAddress.sin_family = AF_INET;
|
||||
proxyAddress.sin_port = htons(1080);
|
||||
proxyAddress.sin_addr.s_addr = inet_addr("54.38.22.7");
|
||||
*/
|
||||
|
||||
remoteAddress.sin_family = AF_INET;
|
||||
remoteAddress.sin_port = htons(1100);
|
||||
remoteAddress.sin_addr.s_addr = inet_addr("87.106.252.164");
|
||||
|
||||
assert(evthread_use_pthreads() == 0);
|
||||
event_base* evBase = event_base_new();
|
||||
|
||||
ProxiedClient client(evBase, proxyAddress, remoteAddress);
|
||||
client.connect();
|
||||
|
||||
event_base_dispatch(evBase);
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
#include <mutex>
|
||||
#include <arpa/inet.h>
|
||||
#include <ThreadPool/Thread.h>
|
||||
#include "ProxiedClient.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
using namespace ts::flood;
|
||||
|
||||
#define CERROR(msg) \
|
||||
do { \
|
||||
cerr << msg << endl;\
|
||||
this->disconnect();\
|
||||
return;\
|
||||
} while(0)
|
||||
|
||||
static int port = 10000;
|
||||
static threads::Mutex portLock;
|
||||
void ProxiedClient::handleProxyMessage(const std::string &msg) {
|
||||
if(this->state == PROXY_INIT_METHODS){
|
||||
if(msg[0] != 0x05) CERROR("Invalid proxy version response (methode exchange)");
|
||||
if(msg[1] != 0x00) CERROR("Invalid respond methode");
|
||||
this->state = PROXY_INIT_CONNECTION;
|
||||
|
||||
char buffer[128];
|
||||
int index = 0;
|
||||
buffer[index++] = 0x05; //Version
|
||||
buffer[index++] = 0x03; //Udp weiterleitung
|
||||
buffer[index++] = 0x00; //Resv
|
||||
buffer[index++] = 0x01; //Addr type = IPv4
|
||||
|
||||
auto addr = IPv4{this->remoteAddr->sin_addr.s_addr};
|
||||
buffer[index++] = addr._1;
|
||||
buffer[index++] = addr._2;
|
||||
buffer[index++] = addr._3;
|
||||
buffer[index++] = addr._4;
|
||||
|
||||
buffer[index++] = (ntohs(this->remoteAddr->sin_port) >> 8) & 0xFF;
|
||||
buffer[index++] = (ntohs(this->remoteAddr->sin_port) >> 0) & 0xFF;
|
||||
this->sendMessage(string(buffer, index));
|
||||
} else if(this->state = PROXY_INIT_CONNECTION){
|
||||
cout << "res!" << endl;
|
||||
|
||||
int index = 0;
|
||||
if(msg[index++] != 0x05) CERROR("Invalid proxy version response (connection request)");
|
||||
if(msg[index++] != 0x00) CERROR("Could not create connection (" + to_string((int) msg[1]) + ")");
|
||||
if(msg[index++] != 0x00) CERROR("Invalid proxy rsv response (connection request)");
|
||||
if(msg[index++] != 0x01) CERROR("Invalid proxy ip response type");
|
||||
|
||||
auto rAddr = IPv4{};
|
||||
rAddr._1 = msg[index++];
|
||||
rAddr._2 = msg[index++];
|
||||
rAddr._3 = msg[index++];
|
||||
rAddr._4 = msg[index++];
|
||||
|
||||
uint16_t pHigh = ((uint16_t) msg[index++]) & 0xFF;
|
||||
uint16_t pLow = ((uint16_t) msg[index++]) & 0xFF;
|
||||
uint16_t rPort = (pHigh << 8) | pLow;
|
||||
cout << "Got udp relay " << rAddr.string() << ":" << rPort << endl;
|
||||
|
||||
//Delete old connection
|
||||
//shutdown(this->fileDescriptor, SHUT_RDWR);
|
||||
|
||||
event_del(this->wEvent);
|
||||
event_del(this->rEvent);
|
||||
this->fileDescriptor = 0;
|
||||
|
||||
//Setup relay
|
||||
this->relayAddr = new sockaddr_in{};
|
||||
memset(relayAddr, 0, sizeof(*relayAddr));
|
||||
relayAddr->sin_family = AF_INET;
|
||||
relayAddr->sin_port = htons(rPort);
|
||||
relayAddr->sin_addr.s_addr = rAddr.addr;
|
||||
cout << "Relay addr: " << inet_ntoa(relayAddr->sin_addr) << ":" << ntohs(relayAddr->sin_port) << endl;
|
||||
|
||||
this->localAddr = new sockaddr_in{};
|
||||
memset(localAddr, 0, sizeof(*localAddr));
|
||||
localAddr->sin_family = AF_INET;
|
||||
{
|
||||
lock_guard<threads::Mutex> l(portLock);
|
||||
localAddr->sin_port = this->remoteAddr->sin_port; // = htons(10000 + (port++ % 30000));
|
||||
}
|
||||
localAddr->sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
|
||||
this->fileDescriptor = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
cout << "fd -> " << this->fileDescriptor << endl;
|
||||
int allow = 1;
|
||||
if(setsockopt(this->fileDescriptor, SOL_SOCKET, SO_REUSEADDR, &allow, sizeof(int)) < 0) CERROR("Could not enable reuse addr");
|
||||
if(bind(this->fileDescriptor, reinterpret_cast<const sockaddr *>(this->localAddr), sizeof(*this->localAddr)) < 0) CERROR("Could nto bind to relay");
|
||||
cout << "Bind on " << inet_ntoa(this->localAddr->sin_addr) << ":" << ntohs(this->localAddr->sin_port) << endl;
|
||||
this->state = PROXY_CONNECTED;
|
||||
|
||||
this->rEvent = event_new(this->evBase, this->fileDescriptor, EV_READ | EV_PERSIST, ProxiedClient::handleEventRead, this);
|
||||
this->wEvent = event_new(this->evBase, this->fileDescriptor, EV_WRITE, ProxiedClient::handleEventWrite, this);
|
||||
event_add(rEvent, nullptr);
|
||||
|
||||
threads::Thread([&](){
|
||||
threads::self::sleep_for(seconds(1));
|
||||
this->proxyInizalisized();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void ProxiedClient::requestProxyConnection() {
|
||||
char buffer[3];
|
||||
buffer[0] = 0x05; //Version
|
||||
buffer[1] = 1; //One methode
|
||||
buffer[2] = 0x00; //No auth required
|
||||
|
||||
this->sendMessage(string(buffer, 3));
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
#include <mutex>
|
||||
#include <netinet/tcp.h>
|
||||
#include <arpa/inet.h>
|
||||
#include "ProxiedClient.h"
|
||||
#include "TSClient.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace ts::flood;
|
||||
|
||||
ProxiedClient::ProxiedClient(event_base* base, const sockaddr_in &proxyAddr, const sockaddr_in &remoteAddr) : evBase(base) {
|
||||
this->proxyAddr = new sockaddr_in{};
|
||||
this->remoteAddr = new sockaddr_in{};
|
||||
|
||||
memcpy(this->proxyAddr, &proxyAddr, sizeof(proxyAddr));
|
||||
memcpy(this->remoteAddr, &remoteAddr, sizeof(proxyAddr));
|
||||
|
||||
this->client = new TSClient(this);
|
||||
}
|
||||
|
||||
ProxiedClient::~ProxiedClient() {
|
||||
delete this->proxyAddr;
|
||||
delete this->remoteAddr;
|
||||
}
|
||||
|
||||
#define CERR(msg) \
|
||||
do { \
|
||||
cerr << "Could not connect: " << msg << "(" << errno << "/" << strerror(errno) << ")" << endl; \
|
||||
return false; \
|
||||
} while(0)
|
||||
|
||||
#if defined(TCP_CORK) && !defined(TCP_NOPUSH)
|
||||
#define TCP_NOPUSH TCP_CORK
|
||||
#endif
|
||||
|
||||
static int enabled = 1;
|
||||
static int disabled = 0;
|
||||
|
||||
bool ProxiedClient::connect() {
|
||||
assert(this->state == ProxyState::PROXY_UNCONNECTED);
|
||||
TAILQ_INIT(&this->writeQueue);
|
||||
|
||||
this->fileDescriptor = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if(this->fileDescriptor < 0) CERR("Socket setup failed");
|
||||
if(::connect(this->fileDescriptor, reinterpret_cast<const sockaddr *>(this->proxyAddr), sizeof(*this->proxyAddr)) < 0) CERR("connect() failed");
|
||||
if(setsockopt(this->fileDescriptor, SOL_SOCKET, SO_REUSEADDR, &enabled, sizeof(enabled)) < 0) CERR("could not set reuse addr");
|
||||
if(setsockopt(this->fileDescriptor, IPPROTO_TCP, TCP_NOPUSH, &disabled, sizeof(disabled)) < 0) CERR("could not set no push");
|
||||
|
||||
cout << "Connected to " << inet_ntoa(this->proxyAddr->sin_addr) << endl;
|
||||
|
||||
this->rEvent = event_new(this->evBase, this->fileDescriptor, EV_READ | EV_PERSIST, ProxiedClient::handleEventRead, this);
|
||||
this->wEvent = event_new(this->evBase, this->fileDescriptor, EV_WRITE, ProxiedClient::handleEventWrite, this);
|
||||
|
||||
event_add(rEvent, nullptr);
|
||||
this->state = ProxyState::PROXY_INIT_METHODS;
|
||||
|
||||
this->requestProxyConnection();
|
||||
return true;
|
||||
}
|
||||
|
||||
void ProxiedClient::disconnect() {
|
||||
this->closeConnection();
|
||||
}
|
||||
|
||||
void ProxiedClient::closeConnection() {
|
||||
{
|
||||
lock_guard<threads::Mutex> lock(this->stateLock);
|
||||
if(this->state == PROXY_UNCONNECTED) return;
|
||||
this->state = PROXY_UNCONNECTED;
|
||||
}
|
||||
|
||||
event_del(this->wEvent);
|
||||
event_del(this->rEvent);
|
||||
|
||||
this->wEvent = nullptr;
|
||||
this->rEvent = nullptr;
|
||||
}
|
||||
|
||||
void ProxiedClient::sendMessage(const std::string& message) {
|
||||
buffer::RawBuffer* buffer;
|
||||
if(this->state != PROXY_CONNECTED){
|
||||
buffer = new buffer::RawBuffer{message.length()};
|
||||
memcpy(buffer->buffer, message.data(), message.length());
|
||||
} else {
|
||||
cout << "Send " << message.length() << " bytes with relay" << endl;
|
||||
int relayHeaderLength = 2 + 1 + 1 + 4 + 2;
|
||||
buffer = new buffer::RawBuffer{relayHeaderLength + message.length()};
|
||||
buffer->index = 0;
|
||||
char preBuffer[relayHeaderLength];
|
||||
preBuffer[0] = 0x00;
|
||||
preBuffer[1] = 0x00;
|
||||
preBuffer[2] = 0x00;
|
||||
preBuffer[3] = 0x01;
|
||||
|
||||
IPv4 addr{this->relayAddr->sin_addr.s_addr};
|
||||
preBuffer[4] = addr._1;
|
||||
preBuffer[5] = addr._2;
|
||||
preBuffer[6] = addr._3;
|
||||
preBuffer[7] = addr._4;
|
||||
|
||||
preBuffer[8] = (ntohs(this->relayAddr->sin_port) >> 8) & 0xFF;
|
||||
preBuffer[9] = (ntohs(this->relayAddr->sin_port) >> 0) & 0xFF;
|
||||
//memset(&preBuffer[4], 0, 6);
|
||||
|
||||
memcpy(&buffer->buffer[0], preBuffer, relayHeaderLength);
|
||||
memcpy(&buffer->buffer[relayHeaderLength], message.data(), message.length());
|
||||
}
|
||||
|
||||
{
|
||||
lock_guard<threads::Mutex> lock(this->queueLock);
|
||||
TAILQ_INSERT_TAIL(&this->writeQueue, buffer, tail);
|
||||
}
|
||||
event_add(this->wEvent, nullptr);
|
||||
}
|
||||
|
||||
void ProxiedClient::handleMessage(const std::string &message) {
|
||||
if(this->state == PROXY_UNCONNECTED) return;
|
||||
if(this->state == PROXY_CONNECTED) return; //TODO
|
||||
this->handleProxyMessage(message);
|
||||
}
|
||||
|
||||
extern void hexdump(std::ostream& outs, const std::string& s, size_t line_len = 16);
|
||||
void ProxiedClient::handleEventWrite(int fd, short, void* ptrClient) {
|
||||
auto* client = static_cast<ProxiedClient *>(ptrClient);
|
||||
|
||||
buffer::RawBuffer* buffer = nullptr;
|
||||
{
|
||||
lock_guard<threads::Mutex> lock(client->queueLock);
|
||||
buffer = TAILQ_FIRST(&client->writeQueue);
|
||||
if(!buffer) return;
|
||||
|
||||
ssize_t writtenBytes = 0;
|
||||
if(client->state == PROXY_CONNECTED){
|
||||
cout << "Write bytes to relay - " << fd << " - " << inet_ntoa(client->relayAddr->sin_addr) << ":" << ntohs(client->relayAddr->sin_port) << endl;
|
||||
hexdump(cout, string((const char*) buffer->buffer, buffer->length));
|
||||
writtenBytes = sendto(fd, buffer->buffer, buffer->length, 0, (const sockaddr *) client->relayAddr, sizeof(*client->relayAddr));
|
||||
} else
|
||||
writtenBytes = send(fd, &buffer->buffer[buffer->index], buffer->length - buffer->index, 0);
|
||||
buffer->index += writtenBytes;
|
||||
cout << "Written: " << writtenBytes << endl;
|
||||
|
||||
if(buffer->index >= buffer->length || client->state == PROXY_CONNECTED) {
|
||||
TAILQ_REMOVE(&client->writeQueue, buffer, tail);
|
||||
delete buffer;
|
||||
}
|
||||
if(!TAILQ_EMPTY(&client->writeQueue))
|
||||
event_add(client->wEvent, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void ProxiedClient::handleEventRead(int fd, short, void* ptrClient) {
|
||||
auto* client = static_cast<ProxiedClient *>(ptrClient);
|
||||
|
||||
char buffer[1024];
|
||||
sockaddr_in remoteAddr{};
|
||||
socklen_t remoteAddrSize = sizeof(remoteAddr);
|
||||
auto read = recvfrom(fd, buffer, 1024, MSG_DONTWAIT, reinterpret_cast<sockaddr *>(&remoteAddr), &remoteAddrSize);
|
||||
cout << "Read " << read << " bytes" << endl;
|
||||
if(read < 0){
|
||||
if(errno == EWOULDBLOCK) return;
|
||||
cerr << "Invalid read: " << errno << "/" << strerror(errno) << endl;
|
||||
client->disconnect();
|
||||
return;
|
||||
} else if(read == 0){
|
||||
cerr << "Client hangs up!" << endl;
|
||||
client->closeConnection();
|
||||
return;
|
||||
}
|
||||
|
||||
hexdump(cout, string(buffer, read));
|
||||
client->handleMessage(string(buffer, read));
|
||||
}
|
||||
|
||||
void ProxiedClient::proxyInizalisized() {
|
||||
this->client->startConnect();
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <netinet/in.h>
|
||||
#include <misc/queue.h>
|
||||
#include <protocol/buffers.h>
|
||||
#include <event.h>
|
||||
|
||||
namespace ts {
|
||||
namespace flood {
|
||||
union IPv4 {
|
||||
uint32_t addr;
|
||||
struct __attribute__ ((__packed__)) {
|
||||
uint8_t _1;
|
||||
uint8_t _2;
|
||||
uint8_t _3;
|
||||
uint8_t _4;
|
||||
};
|
||||
|
||||
inline std::string string(){
|
||||
std::stringstream ss;
|
||||
ss << (int) _1 << "." << (int) _2 << "." << (int) _3 << "." << (int) _4;
|
||||
return ss.str();
|
||||
}
|
||||
};
|
||||
|
||||
class TSClient;
|
||||
enum ProxyState {
|
||||
PROXY_UNCONNECTED,
|
||||
PROXY_INIT_METHODS,
|
||||
PROXY_INIT_CONNECTION,
|
||||
PROXY_CONNECTED
|
||||
};
|
||||
|
||||
class ProxiedClient {
|
||||
public:
|
||||
ProxiedClient(event_base*,const sockaddr_in& proxyAddr, const sockaddr_in& remoteAddr);
|
||||
~ProxiedClient();
|
||||
|
||||
bool connect();
|
||||
void disconnect();
|
||||
void closeConnection();
|
||||
|
||||
void handleMessage(const std::string& message);
|
||||
void sendMessage(const std::string &);
|
||||
private:
|
||||
static void handleEventRead(int, short, void*);
|
||||
static void handleEventWrite(int, short, void*);
|
||||
|
||||
void requestProxyConnection();
|
||||
void handleProxyMessage(const std::string &);
|
||||
|
||||
void proxyInizalisized();
|
||||
|
||||
event_base* evBase = nullptr;
|
||||
event* rEvent = nullptr;
|
||||
event* wEvent = nullptr;
|
||||
int fileDescriptor;
|
||||
ProxyState state = ProxyState::PROXY_UNCONNECTED;
|
||||
threads::Mutex stateLock;
|
||||
sockaddr_in* proxyAddr = nullptr;
|
||||
sockaddr_in* relayAddr = nullptr;
|
||||
sockaddr_in* localAddr = nullptr;
|
||||
sockaddr_in* remoteAddr = nullptr;
|
||||
|
||||
TAILQ_HEAD(, buffer::RawBuffer) writeQueue;
|
||||
threads::Mutex queueLock;
|
||||
|
||||
TSClient* client = nullptr;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
//
|
||||
// Created by wolverindev on 06.01.18.
|
||||
//
|
||||
|
||||
#include "TSClient.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
using namespace ts::flood;
|
||||
using namespace ts::protocol;
|
||||
|
||||
TSClient::TSClient(ProxiedClient* con) : connection(con) {
|
||||
this->cryptionHandler = new connection::CryptionHandler();
|
||||
this->cryptionHandler->reset();
|
||||
}
|
||||
|
||||
TSClient::~TSClient() {}
|
||||
|
||||
void TSClient::handleMessageRead(const std::string& message) {
|
||||
if(message.length() < MAC_SIZE + SERVER_HEADER_SIZE) {
|
||||
cerr << "Invalid pkt length!" << endl;
|
||||
return;
|
||||
}
|
||||
|
||||
shared_ptr<ServerPacket> packet = make_shared<ServerPacket>(message);
|
||||
cout << "Having packet " << packet->type().name() << endl;
|
||||
}
|
||||
|
||||
void TSClient::sendPacket(ts::protocol::ClientPacket &packet, int32_t packetId) {
|
||||
size_t maxDataLength = 500 - packet.header().length();
|
||||
|
||||
if(packet.data().length() > maxDataLength){
|
||||
cout << "Split packet" << endl;
|
||||
string error;
|
||||
|
||||
|
||||
/*
|
||||
if(!compressPacket(&packet, error)){
|
||||
cerr << "Compress error!" << endl;
|
||||
return;
|
||||
}
|
||||
packet.enableFlag(PacketFlag::Compressed);
|
||||
*/
|
||||
if(packet.data().length() > maxDataLength){
|
||||
std::vector<ClientPacket*> siblings;
|
||||
|
||||
ClientPacket* root = new ClientPacket(packet.type(), packet.flagMask(), packet.data());
|
||||
root->enableFlag(PacketFlag::Fragmented);
|
||||
//assert(root->hasFlag(PacketFlag::Compressed));
|
||||
siblings.push_back(root);
|
||||
|
||||
//Max len - mac - header
|
||||
while(siblings.back()->data().length() > maxDataLength){
|
||||
auto overhead = siblings.back()->data().substr(maxDataLength);
|
||||
siblings.back()->data(siblings.back()->data().substr(0, maxDataLength));
|
||||
|
||||
ClientPacket* sib = new ClientPacket(packet.type(), packet.flagMask(), overhead);
|
||||
sib->toggle(PacketFlag::Fragmented, false);
|
||||
siblings.push_back(sib);
|
||||
}
|
||||
siblings.back()->enableFlag(PacketFlag::Fragmented);
|
||||
for(auto elm : siblings){
|
||||
sendPacket(*elm);
|
||||
delete elm;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (packetId == -1)
|
||||
packet.applyPacketId(pktIdManager);
|
||||
else packet.applyPacketId((uint16_t) packetId, 0);
|
||||
packet.clientId(this->clientId);
|
||||
|
||||
string error = "success";
|
||||
if (!this->cryptionHandler->progressPacketOut(&packet, error)) {
|
||||
cerr << "Invalid crypt -> " << error << endl;
|
||||
return;
|
||||
}
|
||||
|
||||
this->connection->sendMessage(packet.mac() + packet.header() + packet.data());
|
||||
}
|
||||
|
||||
const int InitVersionLength = 4;
|
||||
const uint8_t InitVersion[InitVersionLength] = {0x06, 0x3b, 0xEC, 0xE9};
|
||||
|
||||
void TSClient::startConnect() {
|
||||
int maxBufferSize = 512;
|
||||
size_t bufferIndex = 0;
|
||||
uint8_t buffer[maxBufferSize];
|
||||
string error = "success";
|
||||
|
||||
memcpy(buffer, InitVersion, InitVersionLength);
|
||||
bufferIndex += InitVersionLength;
|
||||
buffer[bufferIndex++] = 0x00; //Login state
|
||||
|
||||
int64_t millis = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
|
||||
memcpy(&buffer[5], &millis, 4);
|
||||
bufferIndex += 4;
|
||||
//generate the alpha key
|
||||
for (int i = 0; i < 4; i++) buffer[bufferIndex++] = (uint8_t) std::rand();
|
||||
bufferIndex += 8; //Reserved bytes
|
||||
|
||||
ClientPacket pkt(ts::protocol::PacketTypeInfo::Init1, string((char *) buffer, bufferIndex));
|
||||
pkt.clientId(0);
|
||||
pkt.toggle(ts::protocol::PacketFlag::Unencrypted, true);
|
||||
this->sendPacket(pkt, 101);
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "ProxiedClient.h"
|
||||
#include <string>
|
||||
#include <Definitions.h>
|
||||
#include <protocol/CryptionHandler.h>
|
||||
|
||||
namespace ts {
|
||||
namespace flood {
|
||||
enum TSClientConnectionState {
|
||||
TSC_UNCONNECTED,
|
||||
TSC_PRE,
|
||||
TSC_RSA,
|
||||
TSC_HIGH,
|
||||
TSC_CONNECTED
|
||||
};
|
||||
|
||||
class TSClient {
|
||||
public:
|
||||
TSClient(ProxiedClient*);
|
||||
~TSClient();
|
||||
|
||||
void startConnect();
|
||||
void handleMessageRead(const std::string&);
|
||||
|
||||
void sendPacket(ts::protocol::ClientPacket &packet, int32_t packetId = -1);
|
||||
private:
|
||||
ProxiedClient* connection;
|
||||
TSClientConnectionState state = TSClientConnectionState::TSC_UNCONNECTED;
|
||||
protocol::PacketIdManager pktIdManager;
|
||||
ts::connection::CryptionHandler* cryptionHandler = nullptr;
|
||||
|
||||
ClientId clientId = 0;
|
||||
};
|
||||
}
|
||||
}
|
||||
+1
-1
Submodule git-teaspeak updated: 819ba6d090...3133288d21
+40
-68
@@ -14,86 +14,67 @@ include_directories(../shared/src)
|
||||
add_definitions(-DLTM_DESC)
|
||||
|
||||
set(LICENCE_SOURCE_FILES
|
||||
shared/LicenseRequest.cpp
|
||||
shared/LicenseRequestHandler.cpp
|
||||
shared/License.cpp
|
||||
../shared/src/log/LogUtils.cpp
|
||||
shared/src/license.cpp
|
||||
shared/src/client.cpp
|
||||
)
|
||||
|
||||
#Protobuf
|
||||
find_package(Protobuf REQUIRED)
|
||||
include_directories(${Protobuf_INCLUDE_DIRS})
|
||||
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS packets/LicenseRequest.proto packets/LicenseManager.proto)
|
||||
protobuf_generate_cpp(PROTO_SRCS TeaLicenseHelper_PROTOCOL_HEADERS shared/packets/LicenseRequest.proto shared/packets/LicenseManager.proto)
|
||||
|
||||
#The actual librarie
|
||||
add_library(TeaLicenseHelper STATIC ${LICENCE_SOURCE_FILES} ${PROTO_SRCS} ${PROTO_HDRS})
|
||||
#The actual shared library
|
||||
add_library(TeaLicenseHelper STATIC ${LICENCE_SOURCE_FILES} ${PROTO_SRCS})
|
||||
target_link_libraries(TeaLicenseHelper PUBLIC
|
||||
TeaSpeak
|
||||
protobuf::libprotobuf
|
||||
libevent::core libevent::pthreads
|
||||
# openssl::ssl::shared
|
||||
# openssl::crypto::shared
|
||||
|
||||
${StringVariable_LIBRARIES_STATIC}
|
||||
${ed25519_LIBRARIES_STATIC}
|
||||
stdc++fs
|
||||
rt
|
||||
)
|
||||
target_include_directories(TeaLicenseHelper PUBLIC shared/include/)
|
||||
target_include_directories(TeaLicenseHelper PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
#The license server
|
||||
add_executable(TeaLicenseServer ${LICENCE_SOURCE_FILES} ${PROTO_SRCS} ${PROTO_HDRS}
|
||||
add_executable(TeaLicenseServer ${LICENCE_SOURCE_FILES}
|
||||
server/KeyIdCache.cpp
|
||||
server/LicenseServer.cpp
|
||||
server/LicenseServerHandler.cpp
|
||||
server/LicenseManager.cpp
|
||||
LicenseServerMain.cpp
|
||||
server/DatabaseHandler.cpp
|
||||
server/WebAPI.cpp
|
||||
server/StatisticManager.cpp
|
||||
server/UserManager.cpp
|
||||
LicenseServerMain.cpp
|
||||
# Only for boringssl: MySQLLibSSLFix.c
|
||||
)
|
||||
|
||||
target_link_libraries(TeaLicenseServer
|
||||
TeaSpeak #Static
|
||||
${LIBRARY_PATH_DATA_PIPES}
|
||||
${LIBRARY_PATH_TERMINAL} #Static
|
||||
${LIBRARY_PATH_THREAD_POOL} #Static
|
||||
${PROJECT_SOURCE_DIR}/../../libraries/event/build/lib/libevent.a
|
||||
${PROJECT_SOURCE_DIR}/../../libraries/event/build/lib/libevent_pthreads.a
|
||||
pthread
|
||||
${LIBRARY_PATH_BORINGSSL_SSL}
|
||||
${LIBRARY_PATH_BORINGSSL_CRYPTO}
|
||||
${LIBRARY_PATH_VARIBALES}
|
||||
${LIBRARY_PATH_BREAKPAD}
|
||||
${LIBRARY_PATH_PROTOBUF}
|
||||
TeaSpeak
|
||||
TeaLicenseHelper #Static
|
||||
threadpool::static #Static
|
||||
CXXTerminal::static #Static
|
||||
libevent::core libevent::pthreads
|
||||
|
||||
${LIBRARY_TOM_MATH}
|
||||
${LIBRARY_TOM_CRYPT}
|
||||
mysqlclient
|
||||
jsoncpp_lib
|
||||
DataPipes::core::static
|
||||
openssl::ssl::static
|
||||
openssl::crypto::static
|
||||
|
||||
${LIBRARY_PATH_BREAKPAD}
|
||||
${TOM_LIBRARIES}
|
||||
${LIBRARY_PATH_JDBC}
|
||||
jsoncpp.a
|
||||
stdc++fs.a
|
||||
pthread
|
||||
dl
|
||||
z
|
||||
rt
|
||||
)
|
||||
|
||||
#The test license client
|
||||
add_executable(TeaLicenseClient
|
||||
LicenseClientMain.cpp
|
||||
${LICENCE_SOURCE_FILES} ${PROTO_SRCS} ${PROTO_HDRS}
|
||||
)
|
||||
target_link_libraries(TeaLicenseClient
|
||||
${LIBRARY_PATH_DATA_PIPES}
|
||||
${LIBRARY_PATH_TERMINAL} #Static
|
||||
${LIBRARY_PATH_THREAD_POOL} #Static
|
||||
${PROJECT_SOURCE_DIR}/../../libraries/event/build/lib/libevent.a
|
||||
${PROJECT_SOURCE_DIR}/../../libraries/event/build/lib/libevent_pthreads.a
|
||||
pthread
|
||||
${LIBRARY_PATH_VARIBALES}
|
||||
${LIBRARY_PATH_BREAKPAD}
|
||||
${LIBRARY_PATH_PROTOBUF}
|
||||
|
||||
${LIBRARY_TOM_MATH}
|
||||
${LIBRARY_TOM_CRYPT}
|
||||
stdc++fs.a
|
||||
|
||||
${LIBRARY_PATH_BORINGSSL_SSL}
|
||||
${LIBRARY_PATH_BORINGSSL_CRYPTO}
|
||||
${LIBRARY_PATH_BREAKPAD}
|
||||
${TOM_LIBRARIES}
|
||||
${LIBRARY_PATH_JDBC}
|
||||
TeaSpeak #Static
|
||||
jsoncpp.a
|
||||
)
|
||||
add_executable(TeaLicenseClient LicenseClientMain.cpp)
|
||||
target_link_libraries(TeaLicenseClient TeaLicenseHelper)
|
||||
|
||||
#The license manager
|
||||
if(NOT DISABLE_QT)
|
||||
@@ -134,6 +115,7 @@ if(NOT DISABLE_QT)
|
||||
|
||||
${LIBRARY_TOM_MATH}
|
||||
${LIBRARY_TOM_CRYPT}
|
||||
${LIBRARY_PATH_ED255}
|
||||
stdc++fs
|
||||
jsoncpp.a
|
||||
${LIBRARY_PATH_DATA_PIPES}
|
||||
@@ -147,16 +129,6 @@ add_executable(LicenseCLI
|
||||
manager/ServerConnection.cpp
|
||||
manager/ServerConnectionExecutor.cpp
|
||||
manager/ServerConnectionHandler.cpp
|
||||
shared/License.cpp
|
||||
${PROTO_SRCS} ${PROTO_HDRS}
|
||||
)
|
||||
|
||||
target_link_libraries(LicenseCLI
|
||||
${LIBRARY_PATH_THREAD_POOL} #Static
|
||||
${PROJECT_SOURCE_DIR}/../../libraries/event/build/lib/libevent.a
|
||||
${PROJECT_SOURCE_DIR}/../../libraries/event/build/lib/libevent_pthreads.a
|
||||
${LIBRARY_PATH_PROTOBUF}
|
||||
${LIBRARY_TOM_MATH} #Static
|
||||
${LIBRARY_TOM_CRYPT} #Static
|
||||
pthread
|
||||
)
|
||||
target_link_libraries(LicenseCLI TeaLicenseHelper)
|
||||
+116
-25
@@ -1,7 +1,12 @@
|
||||
#include <iostream>
|
||||
#include <shared/License.h>
|
||||
#include <shared/LicenseRequest.h>
|
||||
#include <shared/include/license/license.h>
|
||||
#include <shared/include/license/LicenseRequest.h>
|
||||
#include <event2/thread.h>
|
||||
#include "shared/include/license/client.h"
|
||||
|
||||
#include <random>
|
||||
#include <ed25519/ed25519.h>
|
||||
#include <misc/base64.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
@@ -21,10 +26,68 @@ using namespace license;
|
||||
inline bool isValid() { return (end.time_since_epoch().count() == 0 || std::chrono::system_clock::now() < this->end); }
|
||||
};
|
||||
*/
|
||||
|
||||
std::array<uint8_t, 195> intermediate_key{0x02, 0x00, 0x2d, 0x6b, 0xe3, 0x4d, 0x3c, 0xbb, 0x19, 0x1e, 0x46, 0x25, 0x72, 0x22, 0xa3, 0x53, 0x6d, 0x2d, 0xc3, 0xd1, 0x2c, 0xc8, 0xea, 0xf2, 0xf8, 0xe5, 0xd5, 0x0f, 0x6c, 0x7f, 0xeb, 0x63, 0x8a, 0xc4, 0x37, 0xb2, 0x33, 0x5e, 0x06, 0x31, 0xcf, 0x2c, 0xc6, 0xbe, 0x5e, 0x9c, 0xf1, 0xe6, 0xb3, 0xc3, 0x69, 0xd7, 0xf0, 0x05, 0x90, 0x2f, 0x65, 0x03, 0x60, 0x12, 0xa0, 0x20, 0x83, 0xe2, 0x6b, 0x4f, 0x46, 0xab, 0xfd, 0xa6, 0x22, 0x0b, 0x11, 0x9a, 0x34, 0xfe, 0x7b, 0xa3, 0x1b, 0x12, 0xce, 0x30, 0xf7, 0x0a, 0x32, 0x26, 0x23, 0x10, 0xfe, 0x58, 0xb6, 0xc7, 0x5d, 0x3d, 0xc6, 0x14, 0xdc, 0x10, 0xa0, 0xc0, 0x78, 0x10, 0x45, 0x41, 0x36, 0x1a, 0x1c, 0x9f, 0x60, 0x25, 0xd9, 0x69, 0xc9, 0x0b, 0xb7, 0xb4, 0x64, 0xab, 0x6c, 0x22, 0xff, 0xaf, 0x86, 0x26, 0x94, 0xcc, 0x7f, 0x59, 0x52, 0xa3, 0x56, 0x7f, 0x3e, 0x86, 0x04, 0x4c, 0xe0, 0x0e, 0xb3, 0xb1, 0x23, 0x51, 0xf7, 0xf0, 0x14, 0x5d, 0xfd, 0x48, 0xfb, 0x16, 0xe6, 0xc6, 0xca, 0xf2, 0x8d, 0xc8, 0xce, 0xf1, 0x2b, 0x12, 0x9e, 0xd1, 0x7a, 0x80, 0x3a, 0x9b, 0x46, 0xe7, 0xca, 0x34, 0x04, 0xae, 0x3d, 0x12, 0xcd, 0x4a, 0xc6, 0xe1, 0xf1, 0xe4, 0xd8, 0xca, 0x68, 0x36, 0xd3, 0x94, 0x0d, 0xef, 0x93, 0x86, 0xb9, 0x3a, 0xa4, 0x10, 0x52};
|
||||
int main(int ac, char** av){
|
||||
auto state = evthread_use_pthreads();
|
||||
assert(state == 0);
|
||||
string error;
|
||||
uint8_t errc = 0;
|
||||
|
||||
#if 0
|
||||
std::array<uint8_t, 32> prv_key{};
|
||||
auto license = v2::License::create({}, prv_key);
|
||||
assert(license->private_data_editable());
|
||||
|
||||
license->push_entry<v2::hierarchy::Intermediate>(system_clock::now(), system_clock::now() + hours{24 * 365 * 1000}, "TeaSpeak (Test)");
|
||||
assert(license->write_private_data({0}));
|
||||
error = license->write(errc);
|
||||
assert(errc == 0);
|
||||
|
||||
cout << "Intermediate: " << hex;
|
||||
for(size_t index = 0; index < error.length(); index++) {
|
||||
uint8_t e = error[index];
|
||||
cout << " 0x" << (e <= 0xF ? "0" : "") << (uint32_t) e << ",";
|
||||
}
|
||||
cout << endl;
|
||||
cout << "Intermediate: " << base64::encode(error) << endl;
|
||||
#endif
|
||||
#if 0
|
||||
auto license = v2::License::read(intermediate_key.data(), intermediate_key.size(), errc);
|
||||
|
||||
license->push_entry<v2::hierarchy::Server>(system_clock::now(), system_clock::now() + hours{24 * 365 * 1000}, "TeaSpeak Official server", "contact@teaspeak.de");
|
||||
assert(license->write_private_data({0}));
|
||||
error = license->write(errc);
|
||||
assert(errc == 0);
|
||||
__asm__("nop");
|
||||
cout << "Errc: " << errc << endl;
|
||||
cout << "Write: " << base64::encode(error) << endl;
|
||||
#endif
|
||||
#if 0
|
||||
std::array<uint8_t, 32> private_key, public_key;
|
||||
|
||||
std::random_device rd;
|
||||
std::uniform_int_distribution<uint8_t> d;
|
||||
|
||||
uint8_t root_seed[64];
|
||||
for(auto& e : root_seed)
|
||||
e = d(rd);
|
||||
ed25519_create_keypair(public_key.data(), private_key.data(), root_seed);
|
||||
|
||||
cout << "Key Pair generated:" << endl;
|
||||
cout << "Private Key:" << hex;
|
||||
for(auto& e : private_key)
|
||||
cout << " 0x" << (e <= 0xF ? "0" : "") << (uint32_t) e << ",";
|
||||
cout << endl;
|
||||
|
||||
cout << "Public Key :" << hex;
|
||||
for(auto& e : public_key)
|
||||
cout << " 0x" << (e <= 0xF ? "0" : "") << (uint32_t) e << ",";
|
||||
cout << endl;
|
||||
|
||||
return true;
|
||||
#endif
|
||||
#if 0
|
||||
srand(system_clock::now().time_since_epoch().count());
|
||||
cout << "Generating new license" << endl;
|
||||
|
||||
@@ -60,28 +123,56 @@ int main(int ac, char** av){
|
||||
auto data = make_shared<LicenseRequestData>();
|
||||
data->license = license;
|
||||
data->info = make_shared<ServerInfo>();
|
||||
while(true) {
|
||||
LicenceRequest request(data, serv_addr);
|
||||
try {
|
||||
cout << "Requesting license" << endl;
|
||||
auto info = request.requestInfo().waitAndGet(nullptr);
|
||||
if(!info) {
|
||||
cout << "Invalid result! Error: " << (request.exception() ? "yes => " + string(request.exception()->what()) : "no") << endl;
|
||||
throw *request.exception();
|
||||
}
|
||||
cout << "Got result!" << endl;
|
||||
cout << "Valid: " << info->license_valid << endl;
|
||||
if(info->license) {
|
||||
cout << "License:" << endl;
|
||||
cout << " Type: " << info->license->type << endl;
|
||||
cout << " User name: " << info->license->username << endl;
|
||||
cout << " First name: " << info->license->first_name << endl;
|
||||
cout << " Last name: " << info->license->last_name << endl;
|
||||
cout << " EMail: " << info->license->email << endl;
|
||||
} else cout << "License: none";
|
||||
} catch (const std::exception& ex){
|
||||
cerr << "Could not load info after throwing: " << endl << ex.what() << endl;
|
||||
}
|
||||
}
|
||||
|
||||
LicenceRequest request(data, serv_addr);
|
||||
try {
|
||||
cout << "Requesting license" << endl;
|
||||
auto info = request.requestInfo().waitAndGet(nullptr);
|
||||
if(!info) {
|
||||
cout << "Invalid result! Error: " << (request.exception() ? "yes => " + string(request.exception()->what()) : "no") << endl;
|
||||
throw *request.exception();
|
||||
}
|
||||
cout << "Got result!" << endl;
|
||||
cout << "Valid: " << info->license_valid << endl;
|
||||
if(info->license) {
|
||||
cout << "License:" << endl;
|
||||
cout << " Type: " << info->license->type << endl;
|
||||
cout << " User name: " << info->license->username << endl;
|
||||
cout << " First name: " << info->license->first_name << endl;
|
||||
cout << " Last name: " << info->license->last_name << endl;
|
||||
cout << " EMail: " << info->license->email << endl;
|
||||
} else cout << "License: none";
|
||||
} catch (const std::exception& ex){
|
||||
cerr << "Could not load info after throwing: " << endl << ex.what() << endl;
|
||||
}
|
||||
#endif
|
||||
#if 1
|
||||
sockaddr_in serv_addr{};
|
||||
serv_addr.sin_family = AF_INET;
|
||||
|
||||
serv_addr.sin_addr.s_addr = ((in_addr*) gethostbyname("localhost")->h_addr)->s_addr;
|
||||
serv_addr.sin_port = htons(27786);
|
||||
|
||||
client::LicenseServerClient client{serv_addr, 3};
|
||||
client.callback_connected = [&]{
|
||||
std::cout << "Connected" << std::endl;
|
||||
client.disconnect("client closed", std::chrono::system_clock::now() + std::chrono::seconds{5});
|
||||
};
|
||||
client.callback_message = [&](auto type, const void* buffer, size_t length) {
|
||||
std::cout << "Received an message" << std::endl;
|
||||
};
|
||||
client.callback_disconnected = [&](bool expected, const std::string& reason) {
|
||||
std::cout << "Received disconnect (expected: " << expected << "): " << reason << std::endl;
|
||||
};
|
||||
|
||||
if(!client.start_connection(error)) {
|
||||
std::cout << "Failed to start connection" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::seconds{2});
|
||||
client.disconnect("client closed", std::chrono::system_clock::now() + std::chrono::seconds{5});
|
||||
std::cout << "Disconnect result: " << client.await_disconnect() << std::endl;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <event2/thread.h>
|
||||
#include <misc/base64.h>
|
||||
|
||||
#include "manager/ServerConnection.h"
|
||||
|
||||
@@ -63,16 +64,18 @@ class CLIParser {
|
||||
std::vector<std::string> tokens;
|
||||
};
|
||||
|
||||
#define REQ_CMD(variable, message, ...) \
|
||||
if(!options.option_exists({__VA_ARGS__}, false)) { \
|
||||
cerr << "missing user" << endl; \
|
||||
return 1; \
|
||||
} \
|
||||
auto variable = url_decode(options.get_option({__VA_ARGS__})) \
|
||||
#define _str(x) #x
|
||||
|
||||
#define REQ_CMD(variable, message, skey, lkey, ...) \
|
||||
if(!options.option_exists({skey, lkey, #__VA_ARGS__}, false)) { \
|
||||
cerr << message << " (" << _str(skey) << " or " << _str(lkey) << ")" << endl; \
|
||||
return 1; \
|
||||
} \
|
||||
auto variable = url_decode(options.get_option({skey, lkey, #__VA_ARGS__})) \
|
||||
|
||||
#define NO_OPEN_SSL
|
||||
#include <misc/digest.h>
|
||||
#include <misc/base64.h>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
CLIParser options(argc, argv);
|
||||
|
||||
@@ -84,11 +87,13 @@ int main(int argc, char **argv) {
|
||||
REQ_CMD(email, "missing email", "-m", "--email");
|
||||
REQ_CMD(license_type, "missing license_type", "-l", "--license-type");
|
||||
|
||||
REQ_CMD(auth_user, "missing authentification user", "-au", "--auth-user");
|
||||
REQ_CMD(auth_pass, "missing authentification user", "-ap", "--auth-pass");
|
||||
REQ_CMD(auth_user, "missing authentication user", "-au", "--auth-user");
|
||||
REQ_CMD(auth_pass, "missing authentication user", "-ap", "--auth-pass");
|
||||
|
||||
REQ_CMD(server_host, "missing server host", "-h", "--server-host");
|
||||
REQ_CMD(server_port, "missing server port", "-p", "--server-port");
|
||||
REQ_CMD(server_port, "missing server port", "-p", "--server-port");
|
||||
|
||||
REQ_CMD(old_key, "missing old license key", "-ol", "--old-license");
|
||||
|
||||
auto state = evthread_use_pthreads();
|
||||
if(state != 0) {
|
||||
@@ -96,6 +101,7 @@ int main(int argc, char **argv) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string error{};
|
||||
ServerConnection connection;
|
||||
connection.verbose = false;
|
||||
try {
|
||||
@@ -112,15 +118,24 @@ int main(int argc, char **argv) {
|
||||
{
|
||||
auto future = connection.login(auth_user, auth_pass);
|
||||
if(!future.waitAndGet(false, system_clock::now() + seconds(5))) {
|
||||
cerr << "failed to athentificate (" << future.errorMegssage() << ")" << endl;
|
||||
cerr << "failed to authenticate (" << future.errorMegssage() << ")" << endl;
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
std::shared_ptr<License> old_license{nullptr};
|
||||
if(!old_key.empty() && old_key != "none") {
|
||||
old_license = license::readLocalLicence(old_key, error);
|
||||
if(!old_license) {
|
||||
cerr << "failed to decode old license: " << error << endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
system_clock::time_point timestamp_begin = system_clock::time_point() + seconds(stoll(begin));
|
||||
system_clock::time_point timestamp_end = system_clock::time_point() + seconds(stoll(end));
|
||||
auto future = connection.registerLicense(first_name, last_name, user, email, (LicenseType) stoll(license_type), timestamp_end, timestamp_begin);
|
||||
auto future = connection.registerLicense(first_name, last_name, user, email, (LicenseType) stoll(license_type), timestamp_end, timestamp_begin, old_license ? old_license->key() : "none");
|
||||
auto result = future.waitAndGet({nullptr, nullptr}, system_clock::now() + seconds(5));
|
||||
if(!result.first || !result.second) {
|
||||
cerr << "failed to create license! (" << future.errorMegssage() << ")" << endl;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include <iostream>
|
||||
#include <shared/License.h>
|
||||
#include <shared/LicenseRequest.h>
|
||||
#include <shared/include/license/license.h>
|
||||
#include <shared/include/license/LicenseRequest.h>
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <manager/ui/LicenseGenerator.h>
|
||||
#include <manager/ServerConnection.h>
|
||||
@@ -43,7 +43,7 @@ int main(int ac, char** av){
|
||||
auto license = license::readLocalLicence(data, error);
|
||||
cout << "Key: " << base64::encode(license->key()) << endl;
|
||||
cout << "Key: " << string_to_hex(license->key()) << endl;
|
||||
return false;
|
||||
//return false;
|
||||
|
||||
connection = new ServerConnection();
|
||||
if(!connection->connect("mcgalaxy.de", 27786).waitAndGet(false)) {
|
||||
|
||||
+145
-33
@@ -1,17 +1,16 @@
|
||||
#include <iostream>
|
||||
#include <shared/License.h>
|
||||
#include <openssl/bio.h>
|
||||
#include <server/LicenseServer.h>
|
||||
#include <log/LogUtils.h>
|
||||
#include <CXXTerminal/Terminal.h>
|
||||
#include <sql/mysql/MySQL.h>
|
||||
#include <pipes/misc/http.h>
|
||||
#include "server/WebAPI.h"
|
||||
#include "server/StatisticManager.h"
|
||||
#include <event2/thread.h>
|
||||
#include "server/UserManager.h"
|
||||
|
||||
#include <misc/base64.h>
|
||||
#include <misc/digest.h>
|
||||
#include <misc/hex.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
@@ -19,28 +18,120 @@ using namespace license;
|
||||
|
||||
/*
|
||||
* Requests/license: SELECT `tmp`.`keyId`, `tmp`.`key`, `tmp`.`ip`, `tmp`.`count`, `license_info`.`username`, `tmp`.`type` FROM (SELECT DISTINCT `license_request`.`keyId`, `key`, `license_request`.`ip`, COUNT(`license_request`.`keyId`) AS `count`, `license`.`type` FROM `license_request` INNER JOIN `license` ON `license`.`keyId` = `license_request`.`keyId` GROUP BY (`license_request`.`ip`)) AS `tmp` INNER JOIN `license_info` ON `license_info`.`keyId` = `tmp`.`keyId`
|
||||
* Different IP's: SELECT `tmp`.`keyId`, `license_info`.`username`, COUNT(`ip`) FROM (SELECT DISTINCT `ip`, `keyId` FROM `license_request`) AS `tmp` LEFT JOIN `license_info` ON `license_info`.`keyId` = `tmp`.`keyId` GROUP BY (`tmp`.`keyId`)
|
||||
* Different IP's: SELECT `tmp`.`keyId`, `license_info`.`username`, COUNT(`ip`) FROM (SELECT DISTINCT `ip`, `keyId` FROM `license_request`) AS `tmp` LEFT JOIN `license_info` ON `license_info`.`keyId` = `tmp`.`keyId` GROUP BY (`tmp`.`keyId`) ORDER BY COUNT(`ip`) DESC
|
||||
*
|
||||
* Requests (license) / ip: SELECT DISTINCT `ip`, COUNT(`ip`) FROM `license_request` WHERE `keyId` = ? GROUP BY `ip`
|
||||
*
|
||||
* SELECT DISTINCT(`ip`), `keyId` FROM `license_request` WHERE `timestamp` > (UNIX_TIMESTAMP() - 2 * 60 * 60) * 1000
|
||||
* //462
|
||||
*
|
||||
* SELECT DISTINCT(`ip`), `license_request`.`keyId`, `license_info`.`username` FROM `license_request` LEFT JOIN `license_info` ON `license_info`.`keyId` = `license_request`.`keyId` WHERE `timestamp` > (UNIX_TIMESTAMP() - 2 * 60 * 60) * 1000
|
||||
* License too many request looking: SELECT `keyId`, `username`, COUNT(`ip`) FROM `unique_license_requests` GROUP BY `keyId` ORDER BY COUNT(`ip`) DESC
|
||||
*
|
||||
*
|
||||
*
|
||||
* Online clients: SELECT SUM(`clients`) FROM (SELECT DISTINCT(`ip`), `clients` FROM `history_online` WHERE `timestamp` > (UNIX_TIMESTAMP() - 60 * 60 * 2) * 1000) AS `a`
|
||||
* Online bots: SELECT SUM(`clients`) FROM (SELECT DISTINCT(`ip`), `clients` FROM `history_online` WHERE `timestamp` > (UNIX_TIMESTAMP() - 60 * 60 * 2) * 1000) AS `a`
|
||||
* Online VS Server: SELECT SUM(`music`) FROM (SELECT DISTINCT(`ip`), `music` FROM `history_online` WHERE `timestamp` > (UNIX_TIMESTAMP() - 60 * 60 * 2) * 1000) AS `a`
|
||||
*
|
||||
* Empty instances: curl -ik "https://stats.teaspeak.de:27788?type=request&request_type=general" -X GET 2>&1 | grep "data:"
|
||||
*/
|
||||
//SELECT SUM(`clients`) FROM `history_online` WHERE `keyId` = 701 OR `keyId` = 795 OR `keyId` = 792 OR `keyId` = 582 OR `keyId` = 753 OR `keyId` = 764 OR `keyId` = 761 OR `keyId` = 796 WHERE `timestamp` > (UNIX_TIMESTAMP() - 2.1 * 60 * 60) * 1000
|
||||
|
||||
/*
|
||||
* Extra users:
|
||||
* - ServerSponsoring
|
||||
* - Davide550
|
||||
* - xDeyego?
|
||||
* - latters
|
||||
* - Pamonha
|
||||
* - vova3639 (5 licenses)
|
||||
*/
|
||||
|
||||
bool handle_command(string& line);
|
||||
|
||||
shared_ptr<server::LicenseManager> license_manager;
|
||||
shared_ptr<server::database::DatabaseHandler> license_manager;
|
||||
shared_ptr<stats::StatisticManager> statistic_manager;
|
||||
shared_ptr<ts::ssl::SSLManager> ssl_manager;
|
||||
shared_ptr<license::web::WebStatistics> web_server;
|
||||
shared_ptr<LicenseServer> license_server;
|
||||
shared_ptr<UserManager> user_manager;
|
||||
|
||||
inline std::shared_ptr<WebCertificate> load_web_certificate() {
|
||||
std::string certificate_file{"web_certificate.txt"}, certificate{};
|
||||
std::string key_file{"web_key.txt"}, key{};
|
||||
std::string error{};
|
||||
|
||||
auto context = ssl_manager->initializeContext("web_shared_cert", key_file, certificate_file, error);
|
||||
if(!context) {
|
||||
logError(0, "Failed to load web certificated: {}", error);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
std::shared_ptr<BIO> bio{nullptr};
|
||||
const uint8_t* mem_ptr{nullptr};
|
||||
size_t length{0};
|
||||
|
||||
{
|
||||
bio = shared_ptr<BIO>(BIO_new(BIO_s_mem()), ::BIO_free);
|
||||
if(PEM_write_bio_PrivateKey(&*bio, &*context->privateKey, nullptr, nullptr, 0, nullptr, nullptr) != 1) {
|
||||
logError(0, "Failed to export certificate");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#ifdef CRYPTO_BORINGSSL
|
||||
if(!BIO_mem_contents(&*bio_private_key, &mem_ptr, &length)) {
|
||||
logError(0, "Failed to receive memptr to private key");
|
||||
return nullptr;
|
||||
}
|
||||
#else
|
||||
BUF_MEM* memory{nullptr};
|
||||
if(!BIO_get_mem_ptr(&*bio, &memory) || !memory) {
|
||||
logError(0, "Failed to receive memptr to private key");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
mem_ptr = (uint8_t*) memory->data;
|
||||
length = memory->length;
|
||||
#endif
|
||||
key.resize(length);
|
||||
memcpy(key.data(), mem_ptr, length);
|
||||
}
|
||||
|
||||
{
|
||||
bio = shared_ptr<BIO>(BIO_new(BIO_s_mem()), ::BIO_free);
|
||||
if(PEM_write_bio_X509(&*bio, &*context->certificate) != 1) {
|
||||
logError(0, "Failed to export certificate");
|
||||
return nullptr;
|
||||
}
|
||||
#ifdef CRYPTO_BORINGSSL
|
||||
if(!BIO_mem_contents(&*bio_private_key, &mem_ptr, &length)) {
|
||||
logError(0, "Failed to receive memptr to public key");
|
||||
return nullptr;
|
||||
}
|
||||
#else
|
||||
BUF_MEM* memory{nullptr};
|
||||
if(!BIO_get_mem_ptr(&*bio, &memory) || !memory) {
|
||||
logError(0, "Failed to receive memptr to public key");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
mem_ptr = (uint8_t*) memory->data;
|
||||
length = memory->length;
|
||||
#endif
|
||||
certificate.resize(length);
|
||||
memcpy(certificate.data(), mem_ptr, length);
|
||||
}
|
||||
|
||||
auto response = std::make_shared<WebCertificate>();
|
||||
response->key = key;
|
||||
response->certificate = certificate;
|
||||
response->revision = digest::sha512(response->key + response->certificate);
|
||||
logMessage(0, "Web certificate revision: {}", hex::hex(response->revision));
|
||||
return response;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if(argc < 2) {
|
||||
cerr << "Invalid arguments! Need MySQL connection" << endl;
|
||||
@@ -48,16 +139,17 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
|
||||
evthread_use_pthreads();
|
||||
http::decode_url("xxx");
|
||||
srand(system_clock::now().time_since_epoch().count());
|
||||
terminal::install();
|
||||
if(!terminal::active()){ cerr << "could not setup terminal!" << endl; return -1; }
|
||||
|
||||
//terminal::install();
|
||||
//if(!terminal::active()){ cerr << "could not setup terminal!" << endl; return -1; }
|
||||
|
||||
auto config = std::make_shared<logger::LoggerConfig>();
|
||||
config->vs_group_size = 0;
|
||||
config->logfileLevel = spdlog::level::trace;
|
||||
config->terminalLevel = spdlog::level::trace;
|
||||
config->logPath = "logs/log_${time}(%Y-%m-%d_%H:%M:%S).log";
|
||||
config->sync = !terminal::active();
|
||||
logger::setup(config);
|
||||
|
||||
string error;
|
||||
@@ -65,8 +157,8 @@ int main(int argc, char** argv) {
|
||||
bool db_connected = true;
|
||||
((sql::mysql::MySQLManager*) database)->listener_disconnected = [&](bool wanted){
|
||||
if(wanted) return;
|
||||
logCritical("Lost connection to MySQL server!");
|
||||
logCritical("Stopping server!");
|
||||
logCritical(LOG_GENERAL, "Lost connection to MySQL server!");
|
||||
logCritical(LOG_GENERAL, "Stopping server!");
|
||||
db_connected = false;
|
||||
};
|
||||
|
||||
@@ -74,7 +166,7 @@ int main(int argc, char** argv) {
|
||||
logMessage(LOG_GENERAL, "Connecting to {}", argv[1]);
|
||||
auto connect_result = database->connect(string(argv[1]));
|
||||
if(!connect_result) {
|
||||
logError("Could not connect to mysql server! (" + connect_result.fmtStr() + ")");
|
||||
logError(LOG_GENERAL, "Could not connect to mysql server! (" + connect_result.fmtStr() + ")");
|
||||
return 0;
|
||||
}
|
||||
#if false
|
||||
@@ -86,15 +178,16 @@ int main(int argc, char** argv) {
|
||||
}, (void*) nullptr) << endl;
|
||||
#endif
|
||||
|
||||
license_manager = make_shared<server::LicenseManager>(database);
|
||||
license_manager = make_shared<server::database::DatabaseHandler>(database);
|
||||
if(!license_manager->setup(error)) {
|
||||
logError("Could not start license manager! (" +error + ")");
|
||||
logError(LOG_GENERAL, "Could not start license manager! (" +error + ")");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
statistic_manager = make_shared<stats::StatisticManager>(license_manager);
|
||||
|
||||
#if false
|
||||
/*
|
||||
{
|
||||
auto _now = system_clock::now();
|
||||
@@ -110,9 +203,18 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
{
|
||||
*/
|
||||
#endif
|
||||
|
||||
#if false
|
||||
/*
|
||||
{
|
||||
ofstream _file_out("version_history.txt");
|
||||
|
||||
auto _now = system_clock::now();
|
||||
auto statistics = license_manager->list_statistics_version(_now - hours(24) * 9, _now, duration_cast<milliseconds>(hours(1)));
|
||||
cout << "Getting statistics" << endl;
|
||||
auto statistics = license_manager->list_statistics_version(_now - hours(24) * 32 * 12, _now, duration_cast<milliseconds>(hours(1)));
|
||||
cout << "Grouping statistics" << endl;
|
||||
std::deque<std::string> versions;
|
||||
const auto version_name = [](const std::string& key) {
|
||||
auto space = key.find(' ');
|
||||
@@ -130,6 +232,7 @@ int main(int argc, char** argv) {
|
||||
versions.push_back(name);
|
||||
}
|
||||
}
|
||||
cout << "Sorting statistics" << endl;
|
||||
sort(versions.begin(), versions.end(), [](const std::string& a, const std::string& b) {
|
||||
const auto index_a = a.find_last_of('.');
|
||||
const auto index_b = b.find_last_of('.');
|
||||
@@ -141,10 +244,11 @@ int main(int argc, char** argv) {
|
||||
return patch_a > patch_b;
|
||||
});
|
||||
|
||||
cout << "Date";
|
||||
for(auto it = versions.begin(); it != versions.end(); it++)
|
||||
cout << "," << *it;
|
||||
cout << endl;
|
||||
cout << "Writing statistics" << endl;
|
||||
_file_out << "Date";
|
||||
for(auto & version : versions)
|
||||
_file_out << "," << version;
|
||||
_file_out << endl;
|
||||
|
||||
for(const auto& entry : statistics) {
|
||||
auto time = system_clock::to_time_t(entry->timestamp);
|
||||
@@ -158,14 +262,18 @@ int main(int argc, char** argv) {
|
||||
version_count[name] += version.second;
|
||||
}
|
||||
|
||||
cout << string_time;
|
||||
_file_out << string_time;
|
||||
for(const auto& name : versions) {
|
||||
cout << "," << version_count[name];
|
||||
_file_out << "," << version_count[name];
|
||||
}
|
||||
cout << endl;
|
||||
}
|
||||
}
|
||||
*/
|
||||
_file_out << endl;
|
||||
}s
|
||||
cout << "Done statistics" << endl;
|
||||
_file_out.flush();
|
||||
}
|
||||
return 0;
|
||||
*/
|
||||
#endif
|
||||
|
||||
ssl_manager = make_shared<ts::ssl::SSLManager>();
|
||||
{
|
||||
@@ -181,7 +289,7 @@ int main(int argc, char** argv) {
|
||||
|
||||
{
|
||||
web_server = make_shared<license::web::WebStatistics>(license_manager, statistic_manager);
|
||||
logMessage("Starting web server on [:::]:27788");
|
||||
logMessage(LOG_GENERAL, "Starting web server on [:::]:27788");
|
||||
if(!web_server->start(error, 27788, ssl_manager->getContext("web_stats"))) {
|
||||
logError(LOG_GENERAL, "Failed to start web statistics server!");
|
||||
return 0;
|
||||
@@ -192,7 +300,7 @@ int main(int argc, char** argv) {
|
||||
user_manager = make_shared<UserManager>(database);
|
||||
}
|
||||
{
|
||||
logMessage("Starting license server on [:::]:27786");
|
||||
logMessage(LOG_GENERAL, "Starting license server on [:::]:27786");
|
||||
struct sockaddr_in listen_addr{};
|
||||
memset(&listen_addr, 0, sizeof(listen_addr));
|
||||
listen_addr.sin_family = AF_INET;
|
||||
@@ -200,23 +308,27 @@ int main(int argc, char** argv) {
|
||||
listen_addr.sin_port = htons(27786);
|
||||
|
||||
license_server = make_shared<LicenseServer>(listen_addr, license_manager, statistic_manager, web_server, user_manager);
|
||||
license_server->startServer();
|
||||
license_server->web_certificate = load_web_certificate();
|
||||
license_server->start();
|
||||
}
|
||||
|
||||
while(db_connected && web_server->running() && license_server->isRunning()) {
|
||||
if(!terminal::instance()) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds{10});
|
||||
continue;
|
||||
}
|
||||
auto line = terminal::instance()->readLine("§a> §f");
|
||||
if(line.empty()){
|
||||
usleep(500);
|
||||
continue;
|
||||
}
|
||||
if(!handle_command(line)) {
|
||||
terminal::instance()->writeMessage("§aStopping server...");
|
||||
if(!handle_command(line))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
terminal::instance()->writeMessage("§aStopping server...");
|
||||
web_server->stop();
|
||||
license_server->stopServer();
|
||||
license_server->stop();
|
||||
if(database) database->disconnect();
|
||||
|
||||
logger::uninstall();
|
||||
@@ -235,6 +347,6 @@ bool handle_command(string& line) {
|
||||
logMessage(LOG_LICENSE_WEB, " {} clients are currently connected!", clients.size());
|
||||
return true;
|
||||
}
|
||||
logError("Invalid command: " + line);
|
||||
logError(LOG_GENERAL, "Invalid command: " + line);
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
#include <openssl/aes.h>
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
const EVP_CIPHER *EVP_aes_128_cfb1(void){ return 0; }
|
||||
const EVP_CIPHER *EVP_aes_192_cfb1(void){ return 0; }
|
||||
const EVP_CIPHER *EVP_aes_256_cfb1(void){ return 0; }
|
||||
|
||||
const EVP_CIPHER *EVP_aes_128_cfb8(void){ return 0; }
|
||||
const EVP_CIPHER *EVP_aes_192_cfb8(void){ return 0; }
|
||||
const EVP_CIPHER *EVP_aes_256_cfb8(void){ return 0; }
|
||||
|
||||
const EVP_CIPHER *EVP_aes_128_cfb128(void){ return 0; }
|
||||
const EVP_CIPHER *EVP_aes_192_cfb128(void){ return 0; }
|
||||
const EVP_CIPHER *EVP_aes_256_cfb128(void){ return 0; }
|
||||
|
||||
int EVP_EncryptFinal(EVP_CIPHER_CTX *ctx, uint8_t *out, int *out_len) {
|
||||
return EVP_EncryptFinal_ex(ctx, out, out_len);
|
||||
}
|
||||
|
||||
int EVP_DecryptFinal(EVP_CIPHER_CTX *ctx, unsigned char *out, int *out_len) {
|
||||
return EVP_DecryptFinal_ex(ctx, out, out_len);
|
||||
}
|
||||
|
||||
int SSL_CTX_set_ciphersuites(SSL_CTX *ctx, const char *str) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define DTLSv1_get_timeout DTLSv1_get_timeout
|
||||
#define DTLSv1_handle_timeout DTLSv1_handle_timeout
|
||||
#define SSL_CTX_add0_chain_cert SSL_CTX_add0_chain_cert
|
||||
#define SSL_CTX_add1_chain_cert SSL_CTX_add1_chain_cert
|
||||
#define SSL_CTX_add_extra_chain_cert SSL_CTX_add_extra_chain_cert
|
||||
#define SSL_CTX_clear_extra_chain_certs SSL_CTX_clear_extra_chain_certs
|
||||
#define SSL_CTX_clear_chain_certs SSL_CTX_clear_chain_certs
|
||||
#define SSL_CTX_clear_mode SSL_CTX_clear_mode
|
||||
#define SSL_CTX_clear_options SSL_CTX_clear_options
|
||||
#define SSL_CTX_get0_chain_certs SSL_CTX_get0_chain_certs
|
||||
#define SSL_CTX_get_extra_chain_certs SSL_CTX_get_extra_chain_certs
|
||||
#define SSL_CTX_get_max_cert_list SSL_CTX_get_max_cert_list
|
||||
#define SSL_CTX_get_mode SSL_CTX_get_mode
|
||||
#define SSL_CTX_get_options SSL_CTX_get_options
|
||||
#define SSL_CTX_get_read_ahead SSL_CTX_get_read_ahead
|
||||
#define SSL_CTX_get_session_cache_mode SSL_CTX_get_session_cache_mode
|
||||
#define SSL_CTX_get_tlsext_ticket_keys SSL_CTX_get_tlsext_ticket_keys
|
||||
#define SSL_CTX_need_tmp_RSA SSL_CTX_need_tmp_RSA
|
||||
#define SSL_CTX_sess_get_cache_size SSL_CTX_sess_get_cache_size
|
||||
#define SSL_CTX_sess_number SSL_CTX_sess_number
|
||||
#define SSL_CTX_sess_set_cache_size SSL_CTX_sess_set_cache_size
|
||||
#define SSL_CTX_set0_chain SSL_CTX_set0_chain
|
||||
#define SSL_CTX_set1_chain SSL_CTX_set1_chain
|
||||
#define SSL_CTX_set1_curves SSL_CTX_set1_curves
|
||||
#define SSL_CTX_set_max_cert_list SSL_CTX_set_max_cert_list
|
||||
#define SSL_CTX_set_max_send_fragment SSL_CTX_set_max_send_fragment
|
||||
#define SSL_CTX_set_mode SSL_CTX_set_mode
|
||||
#define SSL_CTX_set_msg_callback_arg SSL_CTX_set_msg_callback_arg
|
||||
#define SSL_CTX_set_options SSL_CTX_set_options
|
||||
#define SSL_CTX_set_read_ahead SSL_CTX_set_read_ahead
|
||||
#define SSL_CTX_set_session_cache_mode SSL_CTX_set_session_cache_mode
|
||||
#define SSL_CTX_set_tlsext_servername_arg SSL_CTX_set_tlsext_servername_arg
|
||||
#define SSL_CTX_set_tlsext_servername_callback \
|
||||
SSL_CTX_set_tlsext_servername_callback
|
||||
#define SSL_CTX_set_tlsext_ticket_key_cb SSL_CTX_set_tlsext_ticket_key_cb
|
||||
#define SSL_CTX_set_tlsext_ticket_keys SSL_CTX_set_tlsext_ticket_keys
|
||||
#define SSL_CTX_set_tmp_dh SSL_CTX_set_tmp_dh
|
||||
#define SSL_CTX_set_tmp_ecdh SSL_CTX_set_tmp_ecdh
|
||||
#define SSL_CTX_set_tmp_rsa SSL_CTX_set_tmp_rsa
|
||||
#define SSL_add0_chain_cert SSL_add0_chain_cert
|
||||
#define SSL_add1_chain_cert SSL_add1_chain_cert
|
||||
#define SSL_clear_chain_certs SSL_clear_chain_certs
|
||||
#define SSL_clear_mode SSL_clear_mode
|
||||
#define SSL_clear_options SSL_clear_options
|
||||
#define SSL_get0_certificate_types SSL_get0_certificate_types
|
||||
#define SSL_get0_chain_certs SSL_get0_chain_certs
|
||||
#define SSL_get_max_cert_list SSL_get_max_cert_list
|
||||
#define SSL_get_mode SSL_get_mode
|
||||
#define SSL_get_options SSL_get_options
|
||||
#define SSL_get_secure_renegotiation_support \
|
||||
SSL_get_secure_renegotiation_support
|
||||
#define SSL_need_tmp_RSA SSL_need_tmp_RSA
|
||||
#define SSL_num_renegotiations SSL_num_renegotiations
|
||||
#define SSL_session_reused SSL_session_reused
|
||||
#define SSL_set0_chain SSL_set0_chain
|
||||
#define SSL_set1_chain SSL_set1_chain
|
||||
#define SSL_set1_curves SSL_set1_curves
|
||||
#define SSL_set_max_cert_list SSL_set_max_cert_list
|
||||
#define SSL_set_max_send_fragment SSL_set_max_send_fragment
|
||||
#define SSL_set_mode SSL_set_mode
|
||||
#define SSL_set_msg_callback_arg SSL_set_msg_callback_arg
|
||||
#define SSL_set_mtu SSL_set_mtu
|
||||
#define SSL_set_options SSL_set_options
|
||||
#define SSL_set_tlsext_host_name SSL_set_tlsext_host_name
|
||||
#define SSL_set_tmp_dh SSL_set_tmp_dh
|
||||
#define SSL_set_tmp_ecdh SSL_set_tmp_ecdh
|
||||
#define SSL_set_tmp_rsa SSL_set_tmp_rsa
|
||||
#define SSL_total_renegotiations SSL_total_renegotiations
|
||||
|
||||
long SSL_CTX_ctrl(SSL_CTX *ctx, int cmd, long larg, void *parg) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
};
|
||||
#endif
|
||||
@@ -3,10 +3,11 @@
|
||||
//
|
||||
|
||||
#include <netinet/tcp.h>
|
||||
#include <shared/crypt.h>
|
||||
#include <shared/License.h>
|
||||
#include <shared/src/crypt.h>
|
||||
#include <shared/include/license/license.h>
|
||||
#include <memory>
|
||||
#include <misc/std_unique_ptr.h>
|
||||
#include <ThreadPool/ThreadHelper.h>
|
||||
#include "ServerConnection.h"
|
||||
|
||||
using namespace std;
|
||||
@@ -49,21 +50,21 @@ threads::Future<bool> ServerConnection::connect(const std::string &host, uint16_
|
||||
if(this->network.file_descriptor < 0) CERR("Socket setup failed");
|
||||
if(::connect(this->network.file_descriptor, reinterpret_cast<const sockaddr *>(&this->network.address_remote), sizeof(this->network.address_remote)) < 0) CERR("connect() failed (" + to_string(errno) + " | " + strerror(errno) + ")");
|
||||
int enabled = 1, disabled = 0;
|
||||
if(setsockopt(this->network.file_descriptor, SOL_SOCKET, SO_REUSEADDR, &enabled, sizeof(enabled)) < 0) CERR("could not set reuse addr");
|
||||
if(setsockopt(this->network.file_descriptor, IPPROTO_TCP, TCP_CORK, &disabled, sizeof(disabled)) < 0) CERR("could not set no push");
|
||||
if(setsockopt(this->network.file_descriptor, SOL_SOCKET, SO_REUSEADDR, &enabled, sizeof(enabled)) < 0); // CERR("could not set reuse addr");
|
||||
if(setsockopt(this->network.file_descriptor, IPPROTO_TCP, TCP_CORK, &disabled, sizeof(disabled)) < 0); // CERR("could not set no push");
|
||||
|
||||
this->network.event_base = event_base_new();
|
||||
this->network.event_read = event_new(this->network.event_base, this->network.file_descriptor, EV_READ | EV_PERSIST, ServerConnection::handleEventRead, this);
|
||||
this->network.event_write = event_new(this->network.event_base, this->network.file_descriptor, EV_WRITE, ServerConnection::handleEventWrite, this);
|
||||
event_add(this->network.event_read, nullptr);
|
||||
|
||||
this->network.event_base_dispatch = new threads::Thread(THREAD_SAVE_OPERATIONS, [&](){
|
||||
this->network.event_base_dispatch = std::thread{[&]{
|
||||
event_base_dispatch(this->network.event_base);
|
||||
if(this->verbose)
|
||||
cout << "ev ended!" << endl;
|
||||
});
|
||||
}};
|
||||
this->network.state = ConnectionState::CONNECTED;
|
||||
this->protocol.state = protocol::HANDSCAKE;
|
||||
this->protocol.state = protocol::HANDSCHAKE;
|
||||
this->protocol.ping_thread = thread([&]{
|
||||
while(true) {
|
||||
{
|
||||
@@ -82,7 +83,7 @@ threads::Future<bool> ServerConnection::connect(const std::string &host, uint16_
|
||||
handshakeBuffer[0] = 0xC0;
|
||||
handshakeBuffer[1] = 0xFF;
|
||||
handshakeBuffer[2] = 0xEE;
|
||||
handshakeBuffer[3] = LICENSE_PROT_VERSION;
|
||||
handshakeBuffer[3] = 2;
|
||||
handshakeBuffer[4] = 1; //Im a manager
|
||||
this->sendPacket(protocol::packet{protocol::PACKET_CLIENT_HANDSHAKE, string((const char*) handshakeBuffer, 5)}); //Initialise packet
|
||||
}).detach();
|
||||
@@ -104,7 +105,7 @@ void ServerConnection::closeConnection() {
|
||||
if(this->network.state == ConnectionState::UNCONNECTED) return;
|
||||
this->network.state = ConnectionState::DISCONNECTING;
|
||||
|
||||
if(this->network.event_base_dispatch && *this->network.event_base_dispatch == threads::self::id()) {
|
||||
if(this->network.event_base_dispatch.get_id() == this_thread::get_id()) {
|
||||
this->network.flush_thread = new threads::Thread(THREAD_SAVE_OPERATIONS, [&](){ this->closeConnection(); });
|
||||
return;
|
||||
}
|
||||
@@ -126,11 +127,7 @@ void ServerConnection::closeConnection() {
|
||||
cerr << "could not stop event loop!" << endl;
|
||||
}
|
||||
}
|
||||
if(this->network.event_base_dispatch) {
|
||||
this->network.event_base_dispatch->join();
|
||||
delete this->network.event_base_dispatch;
|
||||
this->network.event_base_dispatch = nullptr;
|
||||
}
|
||||
threads::save_join(this->network.event_base_dispatch);
|
||||
if(this->network.event_base) {
|
||||
event_base_free(this->network.event_base);
|
||||
this->network.event_base = nullptr;
|
||||
@@ -159,8 +156,8 @@ void ServerConnection::handleEventRead(int fd, short, void* _connection) {
|
||||
auto connection = (ServerConnection*) _connection;
|
||||
|
||||
char buffer[1024];
|
||||
auto read = recv(fd, buffer, 1024, SOCK_NONBLOCK);
|
||||
if(read < 0) {
|
||||
auto read = recv(fd, buffer, 1024, MSG_DONTWAIT);
|
||||
if(read <= 0) {
|
||||
if(connection->verbose)
|
||||
cout << "Invalid read: " << strerror(errno) << endl;
|
||||
connection->local_disconnect_message = "invalid read";
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
#include <event.h>
|
||||
#include <ThreadPool/Future.h>
|
||||
#include <deque>
|
||||
#include "shared/License.h"
|
||||
#include <thread>
|
||||
#include <ThreadPool/Thread.h>
|
||||
#include "shared/include/license/license.h"
|
||||
|
||||
#define FLSUCCESS(listener, object) \
|
||||
do { \
|
||||
@@ -23,91 +25,93 @@ do { \
|
||||
} while(0)
|
||||
|
||||
|
||||
namespace license {
|
||||
namespace manager {
|
||||
enum ConnectionState {
|
||||
UNCONNECTED,
|
||||
CONNECTING,
|
||||
CONNECTED,
|
||||
DISCONNECTING
|
||||
};
|
||||
class ServerConnection {
|
||||
public:
|
||||
ServerConnection();
|
||||
~ServerConnection();
|
||||
namespace license::manager {
|
||||
enum ConnectionState {
|
||||
UNCONNECTED,
|
||||
CONNECTING,
|
||||
CONNECTED,
|
||||
DISCONNECTING
|
||||
};
|
||||
class ServerConnection {
|
||||
public:
|
||||
ServerConnection();
|
||||
~ServerConnection();
|
||||
|
||||
threads::Future<bool> connect(const std::string& host, uint16_t port);
|
||||
void disconnect(const std::string&);
|
||||
threads::Future<bool> connect(const std::string& host, uint16_t port);
|
||||
void disconnect(const std::string&);
|
||||
|
||||
void ping();
|
||||
void ping();
|
||||
|
||||
threads::Future<bool> login(const std::string&, const std::string&);
|
||||
threads::Future<std::pair<std::shared_ptr<license::License>, std::shared_ptr<license::LicenseInfo>>> registerLicense(
|
||||
const std::string& first_name,
|
||||
const std::string& last_name,
|
||||
const std::string& username,
|
||||
const std::string& email,
|
||||
license::LicenseType type,
|
||||
const std::chrono::system_clock::time_point& end,
|
||||
const std::chrono::system_clock::time_point& begin = std::chrono::system_clock::now()
|
||||
);
|
||||
threads::Future<bool> login(const std::string&, const std::string&);
|
||||
threads::Future<std::pair<std::shared_ptr<license::License>, std::shared_ptr<license::LicenseInfo>>> registerLicense(
|
||||
const std::string& first_name,
|
||||
const std::string& last_name,
|
||||
const std::string& username,
|
||||
const std::string& email,
|
||||
license::LicenseType type,
|
||||
const std::chrono::system_clock::time_point& end,
|
||||
const std::chrono::system_clock::time_point& begin,
|
||||
const std::string& old_license
|
||||
);
|
||||
|
||||
threads::Future<std::map<std::string, std::shared_ptr<license::LicenseInfo>>> list(int offset, int count);
|
||||
threads::Future<bool> deleteLicense(const std::string& key, bool full = false);
|
||||
threads::Future<std::map<std::string, std::shared_ptr<license::LicenseInfo>>> list(int offset, int count);
|
||||
threads::Future<bool> deleteLicense(const std::string& key, bool full = false);
|
||||
|
||||
bool verbose = true;
|
||||
private:
|
||||
struct {
|
||||
ConnectionState state = ConnectionState::UNCONNECTED;
|
||||
sockaddr_in address_remote;
|
||||
int file_descriptor = 0;
|
||||
bool verbose = true;
|
||||
private:
|
||||
struct {
|
||||
ConnectionState state = ConnectionState::UNCONNECTED;
|
||||
sockaddr_in address_remote;
|
||||
int file_descriptor = 0;
|
||||
|
||||
event* event_read = nullptr;
|
||||
event* event_write = nullptr;
|
||||
struct event_base* event_base = nullptr;
|
||||
threads::Thread* event_base_dispatch = nullptr;
|
||||
event* event_read = nullptr;
|
||||
event* event_write = nullptr;
|
||||
struct event_base* event_base = nullptr;
|
||||
std::thread event_base_dispatch;
|
||||
|
||||
threads::Thread* flush_thread = nullptr;
|
||||
threads::Thread* flush_thread = nullptr;
|
||||
|
||||
threads::Mutex queue_lock;
|
||||
std::deque<std::string> queue_write;
|
||||
threads::Mutex queue_lock;
|
||||
std::deque<std::string> queue_write;
|
||||
|
||||
std::unique_ptr<protocol::packet> current_packet;
|
||||
} network;
|
||||
std::unique_ptr<protocol::packet> current_packet;
|
||||
|
||||
|
||||
struct {
|
||||
protocol::RequestState state;
|
||||
std::string crypt_key = "";
|
||||
std::string overhead;
|
||||
} network;
|
||||
|
||||
std::mutex ping_lock;
|
||||
std::condition_variable ping_notify;
|
||||
std::thread ping_thread;
|
||||
} protocol;
|
||||
|
||||
struct {
|
||||
std::unique_ptr<threads::Future<bool>> future_connect;
|
||||
std::unique_ptr<threads::Future<bool>> future_login;
|
||||
std::unique_ptr<threads::Future<std::pair<std::shared_ptr<license::License>, std::shared_ptr<license::LicenseInfo>>>> future_register;
|
||||
std::unique_ptr<threads::Future<std::map<std::string, std::shared_ptr<license::LicenseInfo>>>> future_list;
|
||||
std::unique_ptr<threads::Future<bool>> future_delete;
|
||||
} listener;
|
||||
struct {
|
||||
protocol::RequestState state;
|
||||
std::string crypt_key = "";
|
||||
|
||||
std::string local_disconnect_message;
|
||||
std::mutex ping_lock;
|
||||
std::condition_variable ping_notify;
|
||||
std::thread ping_thread;
|
||||
} protocol;
|
||||
|
||||
static void handleEventRead(int, short, void*);
|
||||
static void handleEventWrite(int, short, void*);
|
||||
struct {
|
||||
std::unique_ptr<threads::Future<bool>> future_connect;
|
||||
std::unique_ptr<threads::Future<bool>> future_login;
|
||||
std::unique_ptr<threads::Future<std::pair<std::shared_ptr<license::License>, std::shared_ptr<license::LicenseInfo>>>> future_register;
|
||||
std::unique_ptr<threads::Future<std::map<std::string, std::shared_ptr<license::LicenseInfo>>>> future_list;
|
||||
std::unique_ptr<threads::Future<bool>> future_delete;
|
||||
} listener;
|
||||
|
||||
void closeConnection();
|
||||
void sendPacket(const protocol::packet&);
|
||||
void handleMessage(const std::string&);
|
||||
std::string local_disconnect_message;
|
||||
|
||||
void handlePacketDisconnect(const std::string&);
|
||||
void handlePacketHandshake(const std::string&);
|
||||
void handlePacketAuthResponse(const std::string&);
|
||||
void handlePacketCreateResponse(const std::string&);
|
||||
void handlePacketListResponse(const std::string&);
|
||||
void handlePacketDeleteResponse(const std::string&);
|
||||
};
|
||||
}
|
||||
static void handleEventRead(int, short, void*);
|
||||
static void handleEventWrite(int, short, void*);
|
||||
|
||||
void closeConnection();
|
||||
void sendPacket(const protocol::packet&);
|
||||
void handleMessage(const std::string&);
|
||||
|
||||
void handlePacketDisconnect(const std::string&);
|
||||
void handlePacketHandshake(const std::string&);
|
||||
void handlePacketAuthResponse(const std::string&);
|
||||
void handlePacketCreateResponse(const std::string&);
|
||||
void handlePacketListResponse(const std::string&);
|
||||
void handlePacketDeleteResponse(const std::string&);
|
||||
};
|
||||
}
|
||||
@@ -35,7 +35,8 @@ threads::Future<std::pair<std::shared_ptr<license::License>, std::shared_ptr<lic
|
||||
const std::string &email,
|
||||
license::LicenseType type,
|
||||
const std::chrono::system_clock::time_point& end,
|
||||
const std::chrono::system_clock::time_point& start
|
||||
const std::chrono::system_clock::time_point& start,
|
||||
const std::string& old_license
|
||||
) {
|
||||
|
||||
this->listener.future_register = std::make_unique<threads::Future<std::pair<std::shared_ptr<license::License>, std::shared_ptr<license::LicenseInfo>>>>();
|
||||
@@ -48,6 +49,9 @@ threads::Future<std::pair<std::shared_ptr<license::License>, std::shared_ptr<lic
|
||||
request.set_type(type);
|
||||
request.set_begin(duration_cast<milliseconds>(start.time_since_epoch()).count());
|
||||
request.set_end(duration_cast<milliseconds>(end.time_since_epoch()).count());
|
||||
if(!old_license.empty() && old_license != "none")
|
||||
request.set_old_key(old_license);
|
||||
|
||||
this->sendPacket({protocol::PACKET_CLIENT_LICENSE_CREATE_REQUEST, request});
|
||||
|
||||
if(this->network.state != ConnectionState::CONNECTED)
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
#include <misc/endianness.h>
|
||||
#include <LicenseRequest.pb.h>
|
||||
#include <LicenseManager.pb.h>
|
||||
#include <shared/crypt.h>
|
||||
#include <shared/License.h>
|
||||
#include <shared/src/crypt.h>
|
||||
#include <shared/include/license/license.h>
|
||||
#include <misc/std_unique_ptr.h>
|
||||
#include "ServerConnection.h"
|
||||
|
||||
@@ -27,8 +27,7 @@ void ServerConnection::handleMessage(const std::string& message) {
|
||||
packet->data += message;
|
||||
} else {
|
||||
packet->data += message.substr(0, left);
|
||||
if(this->verbose)
|
||||
cerr << "Dropping overhead! FIXME!" << endl;
|
||||
this->network.overhead = message.substr(left);
|
||||
}
|
||||
} else {
|
||||
if(message.length() < sizeof(protocol::packet::header)) {
|
||||
@@ -41,7 +40,8 @@ void ServerConnection::handleMessage(const std::string& message) {
|
||||
memcpy(packet.get(), message.data(), sizeof(protocol::packet::header));
|
||||
packet->data = message.substr(sizeof(protocol::packet::header));
|
||||
}
|
||||
if(packet->data.length() < packet->header.length) return;
|
||||
if(packet->data.length() < packet->header.length)
|
||||
return;
|
||||
|
||||
if(!this->protocol.crypt_key.empty())
|
||||
xorBuffer((char*) packet->data.data(), packet->data.length(), this->protocol.crypt_key.data(), this->protocol.crypt_key.length());
|
||||
@@ -70,6 +70,11 @@ void ServerConnection::handleMessage(const std::string& message) {
|
||||
cout << "Invalid packet type: " << packet->header.packetId << endl;
|
||||
}
|
||||
packet.reset();
|
||||
if(!this->network.overhead.empty()) {
|
||||
auto oh = this->network.overhead;
|
||||
this->network.overhead = "";
|
||||
this->handleMessage(oh);
|
||||
}
|
||||
}
|
||||
|
||||
void ServerConnection::handlePacketDisconnect(const std::string& message) {
|
||||
@@ -79,11 +84,11 @@ void ServerConnection::handlePacketDisconnect(const std::string& message) {
|
||||
}
|
||||
|
||||
void ServerConnection::handlePacketHandshake(const std::string& data) {
|
||||
if(this->protocol.state != protocol::HANDSCAKE) LERROR("Protocol state mismatch");
|
||||
if(this->protocol.state != protocol::HANDSCHAKE) LERROR("Protocol state mismatch");
|
||||
if(data.length() < 3) LERROR("Invalid packet size");
|
||||
|
||||
if((uint8_t) data[0] != 0xAF || (uint8_t) data[1] != 0xFE) LERROR("Invalid handshake");
|
||||
if((uint8_t) data[2] != LICENSE_PROT_VERSION) LERROR("Invalid license protocol version. Please update TeaSpeak!");
|
||||
if((uint8_t) data[2] != 2) LERROR("Invalid license protocol version. Please update this client!");
|
||||
|
||||
auto key_length = be2le16(data.data(), 3);
|
||||
if(data.length() < key_length + 3) LERROR("Invalid packet size");
|
||||
|
||||
@@ -158,7 +158,7 @@ void Overview::btn_refresh_clicked() {
|
||||
this->ui.licenses->removeRow(0);
|
||||
this->entries.clear();
|
||||
|
||||
auto fut = connection->list(0, 0);
|
||||
auto fut = connection->list(0, 100);
|
||||
fut.waitAndGetLater([&, fut](std::map<std::string, std::shared_ptr<license::LicenseInfo>> response) {
|
||||
if(!fut.succeeded()) {
|
||||
runOnThread(this->thread(), [&, fut](){
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
#include "LicenseManager.h"
|
||||
#include "DatabaseHandler.h"
|
||||
#include <misc/base64.h>
|
||||
#include <log/LogUtils.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
using namespace license;
|
||||
using namespace license::server;
|
||||
using namespace license::server::database;
|
||||
using namespace sql;
|
||||
|
||||
LicenseManager::LicenseManager(sql::SqlManager* database) : database(database){
|
||||
DatabaseHandler::DatabaseHandler(sql::SqlManager* database) : database(database){
|
||||
this->id_cache = make_shared<KeyIdCache>(this);
|
||||
}
|
||||
LicenseManager::~LicenseManager() { }
|
||||
DatabaseHandler::~DatabaseHandler() { }
|
||||
|
||||
/*
|
||||
LicenseType type;
|
||||
@@ -40,7 +41,7 @@ res = command(this->database, cmd).execute(); \
|
||||
version = ver; \
|
||||
sql::command(this->database, "UPDATE `general` SET `value` = :version WHERE `key` = 'version'", variable{":version", version}).execute();
|
||||
|
||||
bool LicenseManager::setup(std::string& error) {
|
||||
bool DatabaseHandler::setup(std::string& error) {
|
||||
result res;
|
||||
int version = -1;
|
||||
sql::command(this->database, "SELECT `value` FROM `general` WHERE `key` = 'version'").query([](int* version, int lnegth, string* values, string* names) {
|
||||
@@ -59,7 +60,7 @@ bool LicenseManager::setup(std::string& error) {
|
||||
|
||||
SET_VERSION(0);
|
||||
case 0:
|
||||
logMessage("Updating database! To version 1");
|
||||
logMessage(LOG_GENERAL, "Updating database! To version 1");
|
||||
CTBL("CREATE TABLE IF NOT EXISTS `history_speach` (`keyId` INT, `timestamp` BIGINT, `total` BIGINT, `dead` BIGINT, `online` BIGINT, `varianz` BIGINT)");
|
||||
CTBL("CREATE TABLE IF NOT EXISTS `history_online` (`keyId` INT, `timestamp` BIGINT, `server` INT, `clients` INT, `web` INT, `music` INT, `queries` INT)");
|
||||
|
||||
@@ -106,20 +107,33 @@ bool LicenseManager::setup(std::string& error) {
|
||||
|
||||
case 4:
|
||||
CTBL("CREATE TABLE IF NOT EXISTS `users` (`username` VARCHAR(64) NOT NULL PRIMARY KEY, `password_hash` VARCHAR(128), `status` INT, `owner` VARCHAR(64))");
|
||||
SET_VERSION(5);
|
||||
|
||||
case 5:
|
||||
CTBL("CREATE TABLE `license_upgrades` (`upgrade_id` INT PRIMARY KEY NOT NULL, `old_key_id` INT, `new_key_id` INT, `timestamp_begin` BIGINT, `timestamp_end` BIGINT, `valid` INT, `use_count` INT, `license` BLOB);");
|
||||
CTBL("ALTER TABLE `license_upgrades` ADD INDEX(`old_key_id`)");
|
||||
CTBL("ALTER TABLE `license` ADD INDEX(`keyId`);");
|
||||
CTBL("ALTER TABLE `license` ADD COLUMN `upgrade_id` INT DEFAULT 0;");
|
||||
SET_VERSION(6);
|
||||
|
||||
case 6:
|
||||
CTBL("CREATE TABLE license_upgrade_log (`upgrade_id` INT, `timestamp` INT, `unique_id` VARCHAR(64), `server_ip` INT, `succeeded` TINYINT);");
|
||||
CIDX("CREATE INDEX `upgrade_id_timestamp` ON `license_upgrade_log` (`upgrade_id`, `timestamp`)");
|
||||
SET_VERSION(7);
|
||||
|
||||
default:;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LicenseManager::registerLicense(const std::string& key, const shared_ptr<LicenseInfo>& info, const std::string& issuer) {
|
||||
bool DatabaseHandler::register_license(const std::string& key, const shared_ptr<LicenseInfo>& info, const std::string& issuer) {
|
||||
result res;
|
||||
res = command(this->database, "INSERT INTO `license` (`key`, `type`, `deleted`, `issuer`) VALUES (:key, :type, :deleted, :issuer)", variable{":key", key}, variable{":type", (uint32_t) info->type}, variable{":deleted", false}, variable{":issuer", issuer}).execute();
|
||||
if(!res) {
|
||||
logError("Could not register new license (" + res.fmtStr() + ")");
|
||||
logError(LOG_GENERAL, "Could not register new license (" + res.fmtStr() + ")");
|
||||
return false;
|
||||
}
|
||||
auto keyId = this->id_cache->getKeyId(key);
|
||||
auto keyId = this->id_cache->get_key_id_from_key(key);
|
||||
if(keyId == 0) return false;
|
||||
|
||||
res = command(this->database, "INSERT INTO `license_info` (`keyId`, `username`, `first_name`, `last_name`, `email`, `begin`, `end`, `generated`) VALUES (:key, :username, :first_name, :last_name, :email, :begin, :end, :generated)",
|
||||
@@ -133,43 +147,43 @@ bool LicenseManager::registerLicense(const std::string& key, const shared_ptr<Li
|
||||
variable{":end", duration_cast<milliseconds>(info->end.time_since_epoch()).count()}
|
||||
).execute();
|
||||
if(!res) {
|
||||
logError("Could not register new license info (" + res.fmtStr() + ")");
|
||||
logError(LOG_GENERAL, "Could not register new license info (" + res.fmtStr() + ")");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LicenseManager::deleteLicense(const std::string& key, bool full) {
|
||||
bool DatabaseHandler::delete_license(const std::string& key, bool full) {
|
||||
if(full) {
|
||||
auto keyId = this->id_cache->getKeyId(key);
|
||||
auto keyId = this->id_cache->get_key_id_from_key(key);
|
||||
if(keyId == 0) return false; //Never exists
|
||||
|
||||
auto res = command(this->database, "DELETE FROM `license` WHERE `key` = :key", variable{":key", key}).execute();
|
||||
if(!res) logError("Could not delete license (" + res.fmtStr() + ")");
|
||||
if(!res) logError(LOG_GENERAL, "Could not delete license (" + res.fmtStr() + ")");
|
||||
res = command(this->database, "DELETE FROM `license_info` WHERE `keyId` = :key", variable{":keyId", keyId}).execute();
|
||||
if(!res) logError("Could not delete license (" + res.fmtStr() + ")");
|
||||
if(!res) logError(LOG_GENERAL, "Could not delete license (" + res.fmtStr() + ")");
|
||||
return !!res;
|
||||
} else {
|
||||
auto res = command(this->database, "UPDATE `license` SET `deleted` = :true WHERE `key` = :key", variable{":true", true}, variable{":key", key}).execute();
|
||||
if(!res) logError("Could not delete license (" + res.fmtStr() + ")");
|
||||
if(!res) logError(LOG_GENERAL, "Could not delete license (" + res.fmtStr() + ")");
|
||||
return !!res;
|
||||
}
|
||||
}
|
||||
|
||||
bool LicenseManager::validLicenseKey(const std::string& key) {
|
||||
bool DatabaseHandler::validLicenseKey(const std::string& key) {
|
||||
bool valid = false;
|
||||
auto res = command(this->database, "SELECT * FROM `license` WHERE `key` = :key AND `deleted` = :false LIMIT 1", variable{":false", false}, variable{":key", key}).query([](bool* flag, int, char**, char**) {
|
||||
*flag = true;
|
||||
return 0;
|
||||
}, &valid);
|
||||
if(!res) logError("Could not validate license (" + res.fmtStr() + ")");
|
||||
if(!res) logError(LOG_GENERAL, "Could not validate license (" + res.fmtStr() + ")");
|
||||
return !!res && valid;
|
||||
}
|
||||
|
||||
inline std::map<std::string, std::shared_ptr<LicenseInfo>> query_license(SqlManager* mgr, std::string key, int offset, int length) {
|
||||
inline std::map<std::string, std::shared_ptr<LicenseInfo>> query_license(SqlManager* mgr, const std::string& key, int offset, int length) {
|
||||
std::map<std::string, std::shared_ptr<LicenseInfo>> result;
|
||||
|
||||
auto query = string() + "SELECT `key`, `username`, `first_name`, `last_name`, `email`, `begin`, `end`, `generated`, `deleted` FROM `license_info` INNER JOIN `license` ON `license_info`.`keyId` = `license`.`keyId`";
|
||||
auto query = string() + "SELECT `key`, `username`, `first_name`, `last_name`, `email`, `begin`, `end`, `generated`, `deleted`, `upgrade_id` FROM `license_info` INNER JOIN `license` ON `license_info`.`keyId` = `license`.`keyId`";
|
||||
if(!key.empty())
|
||||
query += "WHERE `key` = :key";
|
||||
else
|
||||
@@ -203,6 +217,8 @@ inline std::map<std::string, std::shared_ptr<LicenseInfo>> query_license(SqlMana
|
||||
info->creation = system_clock::time_point() + milliseconds(stoll(values[index]));
|
||||
else if(names[index] == "deleted")
|
||||
info->deleted = values[index] == "1" || values[index] == "true";
|
||||
else if(names[index] == "upgrade_id")
|
||||
info->upgrade_id = std::stol(values[index]);
|
||||
else
|
||||
logError(LOG_GENERAL, "Unknown field {}", names[index]);
|
||||
} catch (std::exception& ex) {
|
||||
@@ -214,24 +230,24 @@ inline std::map<std::string, std::shared_ptr<LicenseInfo>> query_license(SqlMana
|
||||
return 0;
|
||||
}, &result);
|
||||
logTrace(LOG_GENERAL, "Query returned {} results", result.size());
|
||||
if(!res) logError("Could not query license (" + res.fmtStr() + ")");
|
||||
if(!res) logError(LOG_GENERAL, "Could not query license (" + res.fmtStr() + ")");
|
||||
return result;
|
||||
}
|
||||
|
||||
std::shared_ptr<LicenseInfo> LicenseManager::licenseInfo(const std::string& key) {
|
||||
std::shared_ptr<LicenseInfo> DatabaseHandler::query_license_info(const std::string& key) {
|
||||
auto result = query_license(this->database, key, 0, 0);
|
||||
if(result.empty()) return nullptr;
|
||||
return result.begin()->second;
|
||||
}
|
||||
|
||||
std::map<std::string, std::shared_ptr<LicenseInfo>> LicenseManager::listLicenses(int offset, int limit) {
|
||||
std::map<std::string, std::shared_ptr<LicenseInfo>> DatabaseHandler::list_licenses(int offset, int limit) {
|
||||
return query_license(this->database, "", offset, limit);
|
||||
}
|
||||
|
||||
bool LicenseManager::logRequest(const std::string& key, const std::string& unique_id, const std::string& ip, const std::string& version, int state) {
|
||||
bool DatabaseHandler::logRequest(const std::string& key, const std::string& unique_id, const std::string& ip, const std::string& version, int state) {
|
||||
result res;
|
||||
|
||||
auto keyId = this->id_cache->getKeyId(key);
|
||||
auto keyId = this->id_cache->get_key_id_from_key(key);
|
||||
if(keyId == 0) {
|
||||
logError(LOG_GENERAL, "Failed to log license request (could not resolve key id)");
|
||||
return false;
|
||||
@@ -246,7 +262,7 @@ bool LicenseManager::logRequest(const std::string& key, const std::string& uniqu
|
||||
variable{":unique_id", unique_id},
|
||||
variable{":result", state}).execute();
|
||||
if(!res) {
|
||||
logError("Could not log license validation (" + res.fmtStr() + ")");
|
||||
logError(LOG_GENERAL, "Could not log license validation (" + res.fmtStr() + ")");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -258,17 +274,17 @@ bool LicenseManager::logRequest(const std::string& key, const std::string& uniqu
|
||||
variable{":version", version}
|
||||
).execute();
|
||||
if(!res)
|
||||
logError("Could not log license version statistic (" + res.fmtStr() + ")");
|
||||
logError(LOG_GENERAL, "Could not log license version statistic (" + res.fmtStr() + ")");
|
||||
res = {};
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LicenseManager::logStatistic(const std::string &key, const std::string& unique_id, const std::string &ip,
|
||||
const ts::proto::license::PropertyUpdateRequest &data) {
|
||||
bool DatabaseHandler::logStatistic(const std::string &key, const std::string& unique_id, const std::string &ip,
|
||||
const ts::proto::license::PropertyUpdateRequest &data) {
|
||||
result res;
|
||||
|
||||
auto keyId = this->id_cache->getKeyId(key);
|
||||
auto keyId = this->id_cache->get_key_id_from_key(key);
|
||||
if(keyId == 0) return false;
|
||||
|
||||
auto time = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
|
||||
@@ -284,7 +300,7 @@ bool LicenseManager::logStatistic(const std::string &key, const std::string& uni
|
||||
variable{":query", data.queries_online()}
|
||||
).execute();
|
||||
if(!res)
|
||||
logError("Could not log license statistics (" + res.fmtStr() + ")");
|
||||
logError(LOG_GENERAL, "Could not log license statistics (" + res.fmtStr() + ")");
|
||||
res = {};
|
||||
}
|
||||
//SELECT * FROM `license_info` WHERE `keyId` IN (SELECT `keyId` FROM `license` WHERE `key` = '000')
|
||||
@@ -299,107 +315,160 @@ bool LicenseManager::logStatistic(const std::string &key, const std::string& uni
|
||||
variable{":varianz", data.speach_varianz()}
|
||||
).execute();
|
||||
if(!res)
|
||||
logError("Could not log license statistics (" + res.fmtStr() + ")");
|
||||
logError(LOG_GENERAL, "Could not log license statistics (" + res.fmtStr() + ")");
|
||||
res = {};
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::deque<std::unique_ptr<LicenseManager::GlobalUserStatistics>> LicenseManager::list_statistics_user(const system_clock::time_point &begin, const system_clock::time_point &end, const milliseconds &interval) {
|
||||
map<std::string, deque<unique_ptr<LicenseManager::UserStatistics>>> server_statistics;
|
||||
std::shared_ptr<DatabaseHandler::UserHistory> DatabaseHandler::list_statistics_user(const system_clock::time_point &begin, const system_clock::time_point &end, const milliseconds &interval) {
|
||||
auto timeout = hours(2) + minutes(10); //TODO timeout configurable?
|
||||
|
||||
auto state = command(this->database,"SELECT * FROM `history_online` WHERE `timestamp` >= :timestamp_start AND `timestamp` <= :timestamp_end ORDER BY `timestamp` ASC",
|
||||
/* initialize the result */
|
||||
auto allocated_records = (size_t) floor((end - begin) / interval) + 1;
|
||||
auto _result = (UserHistory*) malloc(sizeof(UserHistory) + sizeof(GlobalUserStatistics) * allocated_records);
|
||||
new (_result) UserHistory();
|
||||
|
||||
auto result = shared_ptr<UserHistory>(_result, [](UserHistory* ptr){
|
||||
ptr->~UserHistory();
|
||||
free(ptr);
|
||||
});
|
||||
|
||||
|
||||
result->record_count = 0;
|
||||
result->begin = begin;
|
||||
result->end = end;
|
||||
result->interval = interval;
|
||||
|
||||
memset(&result->history[0], 0, allocated_records * sizeof(GlobalUserStatistics));
|
||||
auto info = &_result->history[0];
|
||||
|
||||
/* temp db variables */
|
||||
map<std::string, DatabaseHandler::DatabaseUserStatistics> server_statistics;
|
||||
|
||||
bool have_key, have_uid;
|
||||
DatabaseHandler::DatabaseUserStatistics temp_stats; /* temp struct for stats parsing */
|
||||
DatabaseHandler::DatabaseUserStatistics* stats_ptr; /* pointer to the target stats */
|
||||
std::chrono::system_clock::time_point current_timestamp = begin + interval; /* upper limit of the current interval */
|
||||
|
||||
auto state = command(this->database, "SELECT * FROM `history_online` WHERE `timestamp` >= :timestamp_start AND `timestamp` <= :timestamp_end ORDER BY `timestamp` ASC",
|
||||
variable{":timestamp_start", duration_cast<milliseconds>(begin.time_since_epoch() - timeout).count()},
|
||||
variable{":timestamp_end", duration_cast<milliseconds>(end.time_since_epoch()).count()}
|
||||
).query([&server_statistics](int columns, std::string* values, std::string* names){
|
||||
size_t key_id = 0;
|
||||
std::string unique_id;
|
||||
auto info = make_unique<LicenseManager::UserStatistics>();
|
||||
for(int index = 0; index < columns; index++) {
|
||||
try {
|
||||
if(names[index] == "keyId")
|
||||
key_id = stoull(values[index]);
|
||||
else if(names[index] == "unique_id")
|
||||
unique_id = values[index];
|
||||
else if(names[index] == "timestamp")
|
||||
info->timestamp = system_clock::time_point() + milliseconds(stoll(values[index]));
|
||||
else if(names[index] == "server")
|
||||
info->servers_online = stoull(values[index]);
|
||||
else if(names[index] == "clients")
|
||||
info->clients_online = stoull(values[index]);
|
||||
else if(names[index] == "web")
|
||||
info->web_clients_online = stoull(values[index]);
|
||||
else if(names[index] == "music")
|
||||
info->bots_online = stoull(values[index]);
|
||||
else if(names[index] == "queries")
|
||||
info->queries_online = stoull(values[index]);
|
||||
} catch (std::exception& ex) {
|
||||
logError("Failed to parse column " + names[index] + " => " + ex.what() + " (Value: " + values[index] + ")");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if(key_id == 0) return 0;
|
||||
).query([&](int columns, std::string* values, std::string* names) {
|
||||
/* find the user statistic ptr */
|
||||
{
|
||||
size_t key_id = 0;
|
||||
std::string unique_id;
|
||||
|
||||
server_statistics[to_string(key_id) + "_" + unique_id].push_back(std::move(info));
|
||||
have_key = false;
|
||||
have_uid = false;
|
||||
for(int index = 0; index < columns; index++) {
|
||||
if(names[index] == "keyId") {
|
||||
key_id = strtol(values[index].c_str(), nullptr, 10);
|
||||
if(key_id == 0) return 0; /* invalid key id */
|
||||
|
||||
have_key = true;
|
||||
if(have_key && have_uid) goto process_tag;
|
||||
} else if(names[index] == "unique_id") {
|
||||
unique_id = values[index];
|
||||
have_uid = true;
|
||||
|
||||
if(have_key && have_uid) goto process_tag;
|
||||
}
|
||||
}
|
||||
return 0; /* key or uid haven't been found */
|
||||
|
||||
process_tag:
|
||||
stats_ptr = &server_statistics[to_string(key_id) + "_" + unique_id];
|
||||
}
|
||||
|
||||
for(int index = 0; index < columns; index++) {
|
||||
if(names[index] == "timestamp")
|
||||
temp_stats.timestamp = system_clock::time_point() + milliseconds(stoll(values[index]));
|
||||
else if(names[index] == "server")
|
||||
temp_stats.servers_online = strtol(values[index].c_str(), nullptr, 10);
|
||||
else if(names[index] == "clients")
|
||||
temp_stats.clients_online = strtol(values[index].c_str(), nullptr, 10);
|
||||
else if(names[index] == "web")
|
||||
temp_stats.web_clients_online = strtol(values[index].c_str(), nullptr, 10);
|
||||
else if(names[index] == "music")
|
||||
temp_stats.bots_online = strtol(values[index].c_str(), nullptr, 10);
|
||||
else if(names[index] == "queries")
|
||||
temp_stats.queries_online = strtol(values[index].c_str(), nullptr, 10);
|
||||
}
|
||||
|
||||
/* because the query could only be oldest to newest */
|
||||
while(temp_stats.timestamp > current_timestamp) {
|
||||
assert(_result->record_count < allocated_records); /* ensure we write to valid memory */
|
||||
|
||||
auto min_timestamp = current_timestamp - timeout;
|
||||
for(auto& server : server_statistics) {
|
||||
auto& second = server.second;
|
||||
if(second.timestamp < min_timestamp || second.timestamp.time_since_epoch().count() == 0)
|
||||
continue; /* last server request is too old to be counted */
|
||||
|
||||
info->instance_online++;
|
||||
info->instance_empty += second.web_clients_online == 0 && second.clients_online == 0;
|
||||
info->queries_online += second.queries_online;
|
||||
info->bots_online += second.bots_online;
|
||||
info->web_clients_online += second.web_clients_online;
|
||||
info->clients_online += second.clients_online;
|
||||
info->servers_online += second.servers_online;
|
||||
}
|
||||
|
||||
info++;
|
||||
_result->record_count++;
|
||||
current_timestamp += interval; /* lets gather for the next interval */
|
||||
}
|
||||
|
||||
/* write the "new" statistic */
|
||||
memcpy(stats_ptr, &temp_stats, sizeof(temp_stats));
|
||||
return 0;
|
||||
});
|
||||
|
||||
if(!state) {
|
||||
logError("Could not read license statistics (" + state.fmtStr() + ")");
|
||||
logError(LOG_GENERAL, "Could not read license statistics (" + state.fmtStr() + ")");
|
||||
return {};
|
||||
}
|
||||
|
||||
std::deque<std::unique_ptr<LicenseManager::GlobalUserStatistics>> result;
|
||||
system_clock::time_point current_timestamp = begin;
|
||||
while(current_timestamp <= end) {
|
||||
auto info = make_unique<LicenseManager::GlobalUserStatistics>();
|
||||
info->timestamp = current_timestamp;
|
||||
/* flush the last record */
|
||||
do {
|
||||
assert(_result->record_count < allocated_records); /* ensure we write to valid memory */
|
||||
|
||||
auto min_timestamp = current_timestamp - timeout;
|
||||
for(auto& server : server_statistics) {
|
||||
while(!server.second.empty()) {
|
||||
auto& first = *server.second.begin();
|
||||
if(first->timestamp > current_timestamp) break; //Entry within the future
|
||||
if(first->timestamp + timeout < current_timestamp) { //Entry within the past
|
||||
server.second.pop_front();
|
||||
continue;
|
||||
}
|
||||
if(server.second.size() > 1) {
|
||||
auto& second = *(server.second.begin() + 1);
|
||||
if(second->timestamp <= current_timestamp) {
|
||||
server.second.pop_front(); //The next entry is more up 2 date
|
||||
continue;
|
||||
}
|
||||
}
|
||||
auto& second = server.second;
|
||||
if(second.timestamp < min_timestamp)
|
||||
continue; /* last server request is too old to be counted */
|
||||
|
||||
info->instance_online++;
|
||||
info->queries_online += first->queries_online;
|
||||
info->bots_online += first->bots_online;
|
||||
info->web_clients_online += first->web_clients_online;
|
||||
info->clients_online += first->clients_online;
|
||||
info->servers_online += first->servers_online;
|
||||
break;
|
||||
}
|
||||
info->instance_online++;
|
||||
info->instance_empty += second.web_clients_online == 0 && second.clients_online == 0;
|
||||
info->queries_online += second.queries_online;
|
||||
info->bots_online += second.bots_online;
|
||||
info->web_clients_online += second.web_clients_online;
|
||||
info->clients_online += second.clients_online;
|
||||
info->servers_online += second.servers_online;
|
||||
}
|
||||
|
||||
result.push_back(std::move(info));
|
||||
info++;
|
||||
_result->record_count++;
|
||||
current_timestamp += interval;
|
||||
}
|
||||
} while(current_timestamp < end);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::deque<std::unique_ptr<LicenseManager::GlobalVersionsStatistic>> LicenseManager::list_statistics_version(const std::chrono::system_clock::time_point &begin, const std::chrono::system_clock::time_point &end, const std::chrono::milliseconds &interval) {
|
||||
map<std::string, deque<unique_ptr<LicenseManager::GlobalVersionsStatistic>>> server_statistics;
|
||||
std::deque<std::unique_ptr<DatabaseHandler::GlobalVersionsStatistic>> DatabaseHandler::list_statistics_version(const std::chrono::system_clock::time_point &begin, const std::chrono::system_clock::time_point &end, const std::chrono::milliseconds &interval) {
|
||||
map<std::string, deque<unique_ptr<DatabaseHandler::GlobalVersionsStatistic>>> server_statistics;
|
||||
auto timeout = hours(2) + minutes(10); //TODO timeout configurable?
|
||||
|
||||
auto state = command(this->database,"SELECT * FROM `history_version` WHERE `timestamp` >= :timestamp_start AND `timestamp` <= :timestamp_end ORDER BY `timestamp` ASC",
|
||||
auto state = command(this->database, "SELECT * FROM `history_version` WHERE `timestamp` >= :timestamp_start AND `timestamp` <= :timestamp_end ORDER BY `timestamp` ASC",
|
||||
variable{":timestamp_start", duration_cast<milliseconds>(begin.time_since_epoch() - timeout).count()},
|
||||
variable{":timestamp_end", duration_cast<milliseconds>(end.time_since_epoch()).count()}
|
||||
).query([&server_statistics](int columns, std::string* values, std::string* names){
|
||||
size_t key_id = 0;
|
||||
std::string unique_id;
|
||||
auto info = make_unique<LicenseManager::GlobalVersionsStatistic>();
|
||||
auto info = make_unique<DatabaseHandler::GlobalVersionsStatistic>();
|
||||
for(int index = 0; index < columns; index++) {
|
||||
try {
|
||||
if(names[index] == "keyId")
|
||||
@@ -411,7 +480,7 @@ std::deque<std::unique_ptr<LicenseManager::GlobalVersionsStatistic>> LicenseMana
|
||||
else if(names[index] == "version")
|
||||
info->versions[values[index]] = 1;
|
||||
} catch (std::exception& ex) {
|
||||
logError("Failed to parse column " + names[index] + " => " + ex.what() + " (Value: " + values[index] + ")");
|
||||
logError(LOG_GENERAL, "Failed to parse column " + names[index] + " => " + ex.what() + " (Value: " + values[index] + ")");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -422,14 +491,14 @@ std::deque<std::unique_ptr<LicenseManager::GlobalVersionsStatistic>> LicenseMana
|
||||
});
|
||||
|
||||
if(!state) {
|
||||
logError("Could not read license statistics (" + state.fmtStr() + ")");
|
||||
logError(LOG_GENERAL, "Could not read license statistics (" + state.fmtStr() + ")");
|
||||
return {};
|
||||
}
|
||||
|
||||
std::deque<std::unique_ptr<LicenseManager::GlobalVersionsStatistic>> result;
|
||||
std::deque<std::unique_ptr<DatabaseHandler::GlobalVersionsStatistic>> result;
|
||||
system_clock::time_point current_timestamp = begin;
|
||||
while(current_timestamp <= end) {
|
||||
auto info = make_unique<LicenseManager::GlobalVersionsStatistic>();
|
||||
auto info = make_unique<DatabaseHandler::GlobalVersionsStatistic>();
|
||||
info->timestamp = current_timestamp;
|
||||
|
||||
for(auto& server : server_statistics) {
|
||||
@@ -460,4 +529,87 @@ std::deque<std::unique_ptr<LicenseManager::GlobalVersionsStatistic>> LicenseMana
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool DatabaseHandler::register_license_upgrade(license_key_id_t old_key_id, license_key_id_t new_key_id,
|
||||
const std::chrono::system_clock::time_point &begin_timestamp, const std::chrono::system_clock::time_point &end_timestamp, const std::string &license_key) {
|
||||
auto upgrade_id = std::chrono::ceil<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
auto sql_result = sql::command(this->sql(), "INSERT INTO `license_upgrades` (`upgrade_id`, `old_key_id`, `new_key_id`, `timestamp_begin`, `timestamp_end`, `valid`, `use_count`, `license`) VALUES"
|
||||
"(:upgrade_id, :old_key_id, :new_key_id, :timestamp_begin, :timestamp_end, 1, 0, :license)",
|
||||
variable{":upgrade_id", upgrade_id},
|
||||
variable{":old_key_id", old_key_id},
|
||||
variable{":new_key_id", new_key_id},
|
||||
variable{":timestamp_begin", std::chrono::duration_cast<std::chrono::milliseconds>(begin_timestamp.time_since_epoch()).count()},
|
||||
variable{":timestamp_end", std::chrono::duration_cast<std::chrono::milliseconds>(end_timestamp.time_since_epoch()).count()},
|
||||
variable{":license", base64::decode(license_key)}).execute();
|
||||
if(!sql_result) {
|
||||
logError(LOG_GENERAL, "Failed to insert license upgrade: {}", sql_result.fmtStr());
|
||||
return false;
|
||||
}
|
||||
|
||||
sql_result = sql::command(this->sql(), "UPDATE `license` SET `upgrade_id` = :upgrade_id WHERE `keyId` = :key_id",
|
||||
variable{":upgrade_id", upgrade_id},
|
||||
variable{":key_id", old_key_id}).execute();
|
||||
if(!sql_result) {
|
||||
logError(LOG_GENERAL, "Failed to set license upgrade: {}", sql_result.fmtStr());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<LicenseUpgrade> DatabaseHandler::query_license_upgrade(upgrade_id_t id) {
|
||||
std::unique_ptr<LicenseUpgrade> result{};
|
||||
auto sql_result = sql::command(this->sql(), "SELECT `upgrade_id`, `old_key_id`, `new_key_id`, `timestamp_begin`, `timestamp_end`, `valid`, `use_count`, `license` FROM `license_upgrades` WHERE `upgrade_id` = :upgrade_id LIMIT 1",
|
||||
variable{":upgrade_id", id}).query([&](int length, std::string* values, std::string* names) {
|
||||
result = std::make_unique<LicenseUpgrade>();
|
||||
for(size_t index = 0; index < length; index++) {
|
||||
try {
|
||||
if(names[index] == "upgrade_id")
|
||||
result->upgrade_id = std::stoull(values[index]);
|
||||
else if(names[index] == "old_key_id")
|
||||
result->old_license_key_id = std::stoull(values[index]);
|
||||
else if(names[index] == "new_key_id")
|
||||
result->new_license_key_id = std::stoull(values[index]);
|
||||
else if(names[index] == "timestamp_begin")
|
||||
result->begin_timestamp = std::chrono::system_clock::time_point{} + std::chrono::milliseconds{std::stoull(values[index])};
|
||||
else if(names[index] == "timestamp_end")
|
||||
result->end_timestamp = std::chrono::system_clock::time_point{} + std::chrono::milliseconds{std::stoull(values[index])};
|
||||
else if(names[index] == "use_count")
|
||||
result->update_count = std::stoull(values[index]);
|
||||
else if(names[index] == "valid")
|
||||
result->valid = std::stoull(values[index]) > 0;
|
||||
else if(names[index] == "license")
|
||||
result->license_key = base64::encode(values[index]);
|
||||
} catch(std::exception& ex) {
|
||||
result = nullptr;
|
||||
logWarning(LOG_GENERAL, "Failed to parse column {} for upgrade id {}. (Value: {})", names[index], id, values[index]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
if(!sql_result) {
|
||||
logWarning(LOG_GENERAL, "Failed to query license upgrade info for upgrade {}: {}", id, sql_result.fmtStr());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void DatabaseHandler::log_license_upgrade_attempt(upgrade_id_t upgrade_id, bool succeeded, const std::string &unique_id, const std::string &ip) {
|
||||
auto result = sql::command(this->sql(), "INSERT INTO `license_upgrade_log` (`upgrade_id`, `timestamp`, `unique_id`, `server_ip`, `succeeded`) VALUES (:upgrade_id, :timestamp, :unique_id, :server_ip, :succeeded);",
|
||||
variable{":upgrade_id", upgrade_id},
|
||||
variable{":timestamp", std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count()},
|
||||
variable{":unique_id", unique_id},
|
||||
variable{":server_ip", ip},
|
||||
variable{":succeeded", succeeded}).execute();
|
||||
if(!result)
|
||||
logWarning(LOG_GENERAL, "Failed to insert upgrade log into database ({})", result.fmtStr());
|
||||
|
||||
if(succeeded) {
|
||||
result = sql::command(this->sql(), "UPDATE `license_upgrades` SET `use_count` = `use_count` + 1 WHERE `upgrade_id` = :upgrade_id",
|
||||
variable{":upgrade_id", upgrade_id}).execute();
|
||||
if(!result)
|
||||
logWarning(LOG_GENERAL, "Failed to increase upgrade use count MySQL ({})", result.fmtStr());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
#pragma once
|
||||
|
||||
#include <sql/SqlQuery.h>
|
||||
#include <shared/include/license/license.h>
|
||||
#include <LicenseRequest.pb.h>
|
||||
|
||||
namespace license::server::database {
|
||||
typedef size_t license_key_id_t;
|
||||
typedef size_t upgrade_id_t;
|
||||
|
||||
class DatabaseHandler;
|
||||
class KeyIdCache {
|
||||
public:
|
||||
explicit KeyIdCache(DatabaseHandler*);
|
||||
|
||||
std::string get_key_from_id(license_key_id_t keyId);
|
||||
size_t get_key_id_from_key(const std::string &key);
|
||||
|
||||
void clear_cache();
|
||||
private:
|
||||
struct CacheEntry {
|
||||
std::string key;
|
||||
size_t keyId;
|
||||
std::chrono::system_clock::time_point age;
|
||||
};
|
||||
|
||||
int insert_entry(int, std::string*, std::string*);
|
||||
|
||||
DatabaseHandler* handle;
|
||||
|
||||
std::mutex entry_lock{};
|
||||
std::deque<std::unique_ptr<CacheEntry>> entries;
|
||||
};
|
||||
|
||||
struct LicenseUpgrade {
|
||||
upgrade_id_t upgrade_id{0};
|
||||
|
||||
license_key_id_t old_license_key_id{0};
|
||||
license_key_id_t new_license_key_id{0};
|
||||
|
||||
std::chrono::system_clock::time_point begin_timestamp{};
|
||||
std::chrono::system_clock::time_point end_timestamp{};
|
||||
|
||||
bool valid{false};
|
||||
size_t update_count{0};
|
||||
|
||||
std::string license_key{};
|
||||
|
||||
[[nodiscard]] inline bool not_yet_available() const {
|
||||
return std::chrono::system_clock::now() < this->begin_timestamp && this->begin_timestamp.time_since_epoch().count() != 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool is_expired() const {
|
||||
return std::chrono::system_clock::now() > this->end_timestamp && this->end_timestamp.time_since_epoch().count() != 0;
|
||||
}
|
||||
};
|
||||
|
||||
class DatabaseHandler {
|
||||
public:
|
||||
struct UserStatistics {
|
||||
uint64_t clients_online = 0;
|
||||
uint64_t web_clients_online = 0;
|
||||
uint64_t bots_online = 0;
|
||||
uint64_t queries_online = 0;
|
||||
uint64_t servers_online = 0;
|
||||
};
|
||||
|
||||
struct ExtendedUserStatistics : public UserStatistics {
|
||||
std::string ip_address;
|
||||
};
|
||||
|
||||
struct GlobalUserStatistics : public UserStatistics {
|
||||
uint64_t instance_online{0};
|
||||
uint64_t instance_empty{0};
|
||||
};
|
||||
|
||||
struct DatabaseUserStatistics : public UserStatistics {
|
||||
std::chrono::system_clock::time_point timestamp{};
|
||||
};
|
||||
|
||||
struct GlobalVersionsStatistic {
|
||||
std::chrono::system_clock::time_point timestamp;
|
||||
std::map<std::string, uint32_t> versions;
|
||||
};
|
||||
|
||||
struct UserHistory {
|
||||
std::chrono::system_clock::time_point begin{};
|
||||
std::chrono::system_clock::time_point end{};
|
||||
std::chrono::milliseconds interval{0};
|
||||
|
||||
size_t record_count = 0;
|
||||
GlobalUserStatistics history[0];
|
||||
};
|
||||
static_assert(sizeof(UserHistory) == 32);
|
||||
|
||||
public:
|
||||
explicit DatabaseHandler(sql::SqlManager* database);
|
||||
~DatabaseHandler();
|
||||
|
||||
bool setup(std::string&);
|
||||
|
||||
[[nodiscard]] inline std::shared_ptr<KeyIdCache> key_id_cache() { return this->id_cache; }
|
||||
|
||||
bool validLicenseKey(const std::string& /* key */);
|
||||
std::shared_ptr<LicenseInfo> query_license_info(const std::string&);
|
||||
std::map<std::string, std::shared_ptr<LicenseInfo>> list_licenses(int offset = 0, int limit = -1);
|
||||
|
||||
bool register_license_upgrade(license_key_id_t /* old key */, license_key_id_t /* new key */, const std::chrono::system_clock::time_point& /* begin */, const std::chrono::system_clock::time_point& /* end */, const std::string& /* key */);
|
||||
std::unique_ptr<LicenseUpgrade> query_license_upgrade(upgrade_id_t /* upgrade id */);
|
||||
void log_license_upgrade_attempt(upgrade_id_t /* upgrade id */, bool /* succeeded */, const std::string& /* server unique id */, const std::string& /* ip address */);
|
||||
|
||||
bool register_license(const std::string& /* key */, const std::shared_ptr<LicenseInfo>& /* info */, const std::string& /* issuer */);
|
||||
bool delete_license(const std::string& /* key */, bool /* full */ = false);
|
||||
|
||||
bool logRequest(const std::string& /* key */, const std::string& /* unique_id */, const std::string& /* ip */, const std::string& /* version */, int /* result */);
|
||||
bool logStatistic(const std::string& /* key */, const std::string& /* unique_id */, const std::string& /* ip */, const ts::proto::license::PropertyUpdateRequest&);
|
||||
//std::deque<std::unique_ptr<ExtendedUserStatistics>> list_statistics_user(const std::string& key, const std::chrono::system_clock::time_point& begin, const std::chrono::system_clock::time_point& end);
|
||||
std::shared_ptr<UserHistory> list_statistics_user(const std::chrono::system_clock::time_point& begin, const std::chrono::system_clock::time_point& end, const std::chrono::milliseconds& /* interval */);
|
||||
std::deque<std::unique_ptr<GlobalVersionsStatistic>> list_statistics_version(const std::chrono::system_clock::time_point& begin, const std::chrono::system_clock::time_point& end, const std::chrono::milliseconds& /* interval */);
|
||||
|
||||
inline sql::SqlManager* sql() { return this->database; }
|
||||
private:
|
||||
std::shared_ptr<KeyIdCache> id_cache;
|
||||
sql::SqlManager* database;
|
||||
};
|
||||
}
|
||||
@@ -1,26 +1,30 @@
|
||||
#include "log/LogUtils.h"
|
||||
#include "LicenseManager.h"
|
||||
#include "DatabaseHandler.h"
|
||||
|
||||
using namespace license;
|
||||
using namespace license::server;
|
||||
using namespace license::server::database;
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
using KeyIdCache = LicenseManager::KeyIdCache;
|
||||
|
||||
KeyIdCache::KeyIdCache(license::server::LicenseManager *handle) : handle(handle) {}
|
||||
KeyIdCache::KeyIdCache(DatabaseHandler *handle) : handle(handle) {}
|
||||
|
||||
std::string KeyIdCache::getKey(size_t keyId) {
|
||||
void KeyIdCache::clear_cache() {
|
||||
std::lock_guard elock{this->entry_lock};
|
||||
this->entries.clear();
|
||||
}
|
||||
|
||||
std::string KeyIdCache::get_key_from_id(size_t keyId) {
|
||||
{
|
||||
threads::MutexLock lock(this->entry_lock);
|
||||
std::lock_guard elock{this->entry_lock};
|
||||
|
||||
for(const auto& entry : this->entries)
|
||||
if(entry->keyId == keyId) return entry->key;
|
||||
}
|
||||
|
||||
sql::command(this->handle->database, "SELECT `key`, `keyId` FROM `license` WHERE `keyId` = :key", variable{":key", keyId})
|
||||
sql::command(this->handle->sql(), "SELECT `key`, `keyId` FROM `license` WHERE `keyId` = :key", variable{":key", keyId})
|
||||
.query(&KeyIdCache::insert_entry, this);
|
||||
{
|
||||
threads::MutexLock lock(this->entry_lock);
|
||||
std::lock_guard elock{this->entry_lock};
|
||||
|
||||
for(const auto& entry : this->entries)
|
||||
if(entry->keyId == keyId) return entry->key;
|
||||
@@ -28,20 +32,20 @@ std::string KeyIdCache::getKey(size_t keyId) {
|
||||
}
|
||||
}
|
||||
|
||||
size_t KeyIdCache::getKeyId(const std::string &key) {
|
||||
size_t KeyIdCache::get_key_id_from_key(const std::string &key) {
|
||||
{
|
||||
threads::MutexLock lock(this->entry_lock);
|
||||
std::lock_guard elock{this->entry_lock};
|
||||
|
||||
for(const auto& entry : this->entries)
|
||||
if(entry->key == key) return entry->keyId;
|
||||
}
|
||||
|
||||
auto result = sql::command(this->handle->database, "SELECT `key`, `keyId` FROM `license` WHERE `key` = :key", variable{":key", key})
|
||||
auto result = sql::command(this->handle->sql(), "SELECT `key`, `keyId` FROM `license` WHERE `key` = :key", variable{":key", key})
|
||||
.query(&KeyIdCache::insert_entry, this);
|
||||
if(!result)
|
||||
logError(LOG_GENERAL, "Failed to query key id for license. Query resulted in {}", result.fmtStr());
|
||||
{
|
||||
threads::MutexLock lock(this->entry_lock);
|
||||
std::lock_guard elock{this->entry_lock};
|
||||
|
||||
for(const auto& entry : this->entries)
|
||||
if(entry->key == key)
|
||||
@@ -52,16 +56,24 @@ size_t KeyIdCache::getKeyId(const std::string &key) {
|
||||
}
|
||||
|
||||
int KeyIdCache::insert_entry(int length, std::string *value, std::string *names) {
|
||||
string key;
|
||||
size_t keyId = 0;
|
||||
for(int index = 0; index < length; index++)
|
||||
string key{"unknown"};
|
||||
size_t keyId{0};
|
||||
|
||||
for(int index = 0; index < length; index++) {
|
||||
if(names[index] == "key")
|
||||
key = value[index];
|
||||
else if(names[index] == "keyId")
|
||||
keyId = stoll(value[index]);
|
||||
keyId = std::strtoll(value[index].c_str(), nullptr, 10);
|
||||
}
|
||||
if(!keyId) {
|
||||
logWarning(LOG_GENERAL, "Failed to parse key id for key {}", key);
|
||||
return 0;
|
||||
}
|
||||
|
||||
{
|
||||
threads::MutexLock lock(this->entry_lock);
|
||||
this->entries.push_back(new KeyIdCache::CacheEntry{key, keyId, system_clock::now()});
|
||||
auto entry = new KeyIdCache::CacheEntry{key, keyId, system_clock::now()};
|
||||
std::lock_guard elock{this->entry_lock};
|
||||
this->entries.emplace_back(entry);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <sql/SqlQuery.h>
|
||||
#include <shared/License.h>
|
||||
#include <LicenseRequest.pb.h>
|
||||
|
||||
namespace license {
|
||||
namespace server {
|
||||
class LicenseManager {
|
||||
public:
|
||||
class KeyIdCache {
|
||||
struct CacheEntry {
|
||||
std::string key;
|
||||
size_t keyId;
|
||||
std::chrono::system_clock::time_point age;
|
||||
};
|
||||
|
||||
public:
|
||||
KeyIdCache(LicenseManager*);
|
||||
|
||||
void cache_keys(const std::deque<std::string>& /* keys */);
|
||||
std::string getKey(size_t keyId);
|
||||
size_t getKeyId(const std::string&);
|
||||
private:
|
||||
int insert_entry(int, std::string*, std::string*);
|
||||
|
||||
LicenseManager* handle;
|
||||
threads::Mutex entry_lock;
|
||||
threads::Mutex entry_update_lock;
|
||||
std::deque<CacheEntry*> entries;
|
||||
};
|
||||
struct UserStatistics {
|
||||
std::chrono::system_clock::time_point timestamp;
|
||||
uint64_t clients_online;
|
||||
uint64_t web_clients_online;
|
||||
uint64_t bots_online;
|
||||
uint64_t queries_online;
|
||||
uint64_t servers_online;
|
||||
};
|
||||
|
||||
struct ExtendedUserStatistics : public UserStatistics{
|
||||
std::string ip_address;
|
||||
};
|
||||
|
||||
struct GlobalUserStatistics : public UserStatistics {
|
||||
uint64_t instance_online;
|
||||
};
|
||||
|
||||
struct GlobalVersionsStatistic {
|
||||
std::chrono::system_clock::time_point timestamp;
|
||||
std::map<std::string, uint32_t> versions;
|
||||
};
|
||||
public:
|
||||
LicenseManager(sql::SqlManager* database);
|
||||
~LicenseManager();
|
||||
|
||||
bool setup(std::string&);
|
||||
|
||||
bool validLicenseKey(const std::string& /* key */);
|
||||
std::shared_ptr<LicenseInfo> licenseInfo(const std::string&);
|
||||
|
||||
std::map<std::string, std::shared_ptr<LicenseInfo>> listLicenses(int offset = 0, int limit = -1);
|
||||
|
||||
bool registerLicense(const std::string& /* key */, const std::shared_ptr<LicenseInfo>& /* info */, const std::string& /* issuer */);
|
||||
bool updateLicenseInfo(const std::string&, const std::shared_ptr<LicenseInfo>&);
|
||||
bool deleteLicense(const std::string& /* key */, bool /* full */ = false);
|
||||
|
||||
bool logRequest(const std::string& /* key */, const std::string& /* unique_id */, const std::string& /* ip */, const std::string& /* version */, int /* result */);
|
||||
bool logStatistic(const std::string& /* key */, const std::string& /* unique_id */, const std::string& /* ip */, const ts::proto::license::PropertyUpdateRequest&);
|
||||
//std::deque<std::unique_ptr<ExtendedUserStatistics>> list_statistics_user(const std::string& key, const std::chrono::system_clock::time_point& begin, const std::chrono::system_clock::time_point& end);
|
||||
std::deque<std::unique_ptr<GlobalUserStatistics>> list_statistics_user(const std::chrono::system_clock::time_point& begin, const std::chrono::system_clock::time_point& end, const std::chrono::milliseconds& /* interval */);
|
||||
std::deque<std::unique_ptr<GlobalVersionsStatistic>> list_statistics_version(const std::chrono::system_clock::time_point& begin, const std::chrono::system_clock::time_point& end, const std::chrono::milliseconds& /* interval */);
|
||||
|
||||
inline sql::SqlManager* sql() { return this->database; }
|
||||
private:
|
||||
std::shared_ptr<KeyIdCache> id_cache;
|
||||
sql::SqlManager* database;
|
||||
};
|
||||
}
|
||||
}
|
||||
+162
-112
@@ -1,16 +1,17 @@
|
||||
#include <unistd.h>
|
||||
#include <csignal>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <arpa/inet.h>
|
||||
#include <log/LogUtils.h>
|
||||
#include <misc/endianness.h>
|
||||
#include <LicenseRequest.pb.h>
|
||||
#include <shared/License.h>
|
||||
#include <shared/crypt.h>
|
||||
#include <misc/base64.h>
|
||||
#include <shared/include/license/license.h>
|
||||
#include <shared/src/crypt.h>
|
||||
#include <ThreadPool/ThreadHelper.h>
|
||||
#include "LicenseServer.h"
|
||||
#include "crypt.h"
|
||||
#include "UserManager.h"
|
||||
|
||||
using namespace std;
|
||||
@@ -19,98 +20,100 @@ using namespace license;
|
||||
using namespace ts;
|
||||
|
||||
LicenseServer::LicenseServer(const sockaddr_in& addr,
|
||||
const std::shared_ptr<server::LicenseManager>& manager,
|
||||
const shared_ptr<license::stats::StatisticManager> &stats,
|
||||
const shared_ptr<license::web::WebStatistics> &wstats,
|
||||
const std::shared_ptr<UserManager>& user_manager) : manager(manager), statistics(stats), web_statistics(wstats), user_manager(user_manager) {
|
||||
this->localAddr = new sockaddr_in{};
|
||||
memcpy(this->localAddr, &addr, sizeof(addr));
|
||||
std::shared_ptr<server::database::DatabaseHandler> manager,
|
||||
shared_ptr<license::stats::StatisticManager> stats,
|
||||
shared_ptr<license::web::WebStatistics> wstats,
|
||||
std::shared_ptr<UserManager> user_manager) : manager{std::move(manager)}, statistics{std::move(stats)}, web_statistics{std::move(wstats)}, user_manager{std::move(user_manager)} {
|
||||
memcpy(&this->localAddr, &addr, sizeof(addr));
|
||||
}
|
||||
|
||||
LicenseServer::~LicenseServer() {
|
||||
this->stopServer();
|
||||
delete this->localAddr;
|
||||
this->stop();
|
||||
}
|
||||
|
||||
#define SFAIL(message) \
|
||||
do { \
|
||||
logError(lstream << (message) << " Message: " << errno << "/" << strerror(errno)); \
|
||||
this->stopServer(); \
|
||||
logError(LOG_GENERAL, " Message: {} ({}/{})", message, errno, strerror(errno)); \
|
||||
this->stop(); \
|
||||
return false; \
|
||||
} while(0)
|
||||
|
||||
static int enabled = 1;
|
||||
static int disabled = 0;
|
||||
bool LicenseServer::startServer() {
|
||||
bool LicenseServer::start() {
|
||||
{
|
||||
lock_guard lock(this->lock);
|
||||
lock_guard lock(this->client_lock);
|
||||
if(this->running) return false;
|
||||
this->running = true;
|
||||
}
|
||||
|
||||
fileDescriptor = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (fileDescriptor < 0) SFAIL("Could not create new socket");
|
||||
server_socket = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (server_socket < 0) SFAIL("Could not create new socket");
|
||||
|
||||
if(setsockopt(this->fileDescriptor, SOL_SOCKET, SO_REUSEADDR, &enabled, sizeof(enabled)) < 0) SFAIL("could not set reuse address");
|
||||
if(setsockopt(this->fileDescriptor, IPPROTO_TCP, TCP_CORK, &disabled, sizeof(disabled)) < 0) SFAIL("could not set no push");
|
||||
if(bind(this->fileDescriptor, (struct sockaddr *) this->localAddr, sizeof(sockaddr_in)) < 0) SFAIL("Could not bind socket on " + string(inet_ntoa(this->localAddr->sin_addr)));
|
||||
if(setsockopt(this->server_socket, SOL_SOCKET, SO_REUSEADDR, &enabled, sizeof(enabled)) < 0) SFAIL("could not set reuse address");
|
||||
if(setsockopt(this->server_socket, IPPROTO_TCP, TCP_CORK, &disabled, sizeof(disabled)) < 0) SFAIL("could not set no push");
|
||||
if(bind(this->server_socket, (struct sockaddr *) &this->localAddr, sizeof(sockaddr_in)) < 0) SFAIL("Could not bind socket on " + string(inet_ntoa(this->localAddr.sin_addr)));
|
||||
|
||||
if(listen(this->fileDescriptor, 32) < 0) SFAIL("Could not listen on socket");
|
||||
if(listen(this->server_socket, 32) < 0) SFAIL("Could not listen on socket");
|
||||
|
||||
this->evBase = event_base_new();
|
||||
this->acceptEvent = event_new(this->evBase, this->fileDescriptor, EV_READ | EV_PERSIST, LicenseServer::handleEventAccept, this);
|
||||
this->client_cleanup = evtimer_new(this->evBase, LicenseServer::handleEventCleanup, this);
|
||||
this->event_accept = event_new(this->evBase, this->server_socket, EV_READ | EV_PERSIST, LicenseServer::handleEventAccept, this);
|
||||
this->event_cleanup = evtimer_new(this->evBase, LicenseServer::handleEventCleanup, this);
|
||||
|
||||
event_add(this->acceptEvent, nullptr);
|
||||
event_add(this->event_accept, nullptr);
|
||||
{
|
||||
timeval now{1, 0};
|
||||
evtimer_add(this->client_cleanup, &now);
|
||||
evtimer_add(this->event_cleanup, &now);
|
||||
}
|
||||
evBaseDispatch = new threads::Thread(THREAD_SAVE_OPERATIONS, [&](){
|
||||
|
||||
event_base_dispatch = std::thread([&]{
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
event_base_dispatch(this->evBase);
|
||||
::event_base_dispatch(this->evBase);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
void LicenseServer::stopServer() {
|
||||
void LicenseServer::stop() {
|
||||
{
|
||||
lock_guard lock(this->lock);
|
||||
lock_guard lock(this->client_lock);
|
||||
if(!this->running) return;
|
||||
this->running = false;
|
||||
}
|
||||
|
||||
/* first unregister the accept event so we don't get new clients */
|
||||
if(this->event_accept) {
|
||||
event_del(this->event_accept);
|
||||
event_free(this->event_accept);
|
||||
}
|
||||
this->event_accept = nullptr;
|
||||
|
||||
/* disconnect all clients */
|
||||
for(const auto& client : this->getClients())
|
||||
this->closeConnection(client);
|
||||
|
||||
if(this->acceptEvent) {
|
||||
event_del(this->acceptEvent);
|
||||
event_free(this->acceptEvent);
|
||||
}
|
||||
this->acceptEvent = nullptr;
|
||||
|
||||
if(this->client_cleanup) {
|
||||
event_del_block(this->client_cleanup);
|
||||
event_free(this->client_cleanup);
|
||||
this->client_cleanup = nullptr;
|
||||
}
|
||||
|
||||
if(this->evBase)
|
||||
event_base_loopbreak(this->evBase);
|
||||
|
||||
if(this->evBaseDispatch)
|
||||
this->evBaseDispatch->join();
|
||||
delete this->evBaseDispatch;
|
||||
this->evBaseDispatch = nullptr;
|
||||
if(this->evBase) {
|
||||
event_base_loopbreak(this->evBase); /* again for some reason */
|
||||
if(!threads::timed_join(this->event_base_dispatch, std::chrono::seconds{2})) {
|
||||
this->event_base_dispatch.detach();
|
||||
logCritical(LOG_GENERAL, "Failed to join event base dispatch thread. This will cause memory leaks.");
|
||||
}
|
||||
|
||||
/* Needs to be cleaned up after event loop has been destroyed. Because its used within the event loop. */
|
||||
if(this->event_cleanup) {
|
||||
event_del_block(this->event_cleanup);
|
||||
event_free(this->event_cleanup);
|
||||
this->event_cleanup = nullptr;
|
||||
}
|
||||
|
||||
if(this->evBase)
|
||||
event_base_free(this->evBase);
|
||||
}
|
||||
this->evBase = nullptr;
|
||||
|
||||
if(this->fileDescriptor != 0) {
|
||||
shutdown(this->fileDescriptor, SHUT_RDWR);
|
||||
close(this->fileDescriptor);
|
||||
this->fileDescriptor = 0;
|
||||
if(this->server_socket != 0) {
|
||||
shutdown(this->server_socket, SHUT_RDWR);
|
||||
close(this->server_socket);
|
||||
this->server_socket = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,8 +123,8 @@ void LicenseServer::handleEventCleanup(int, short, void* ptrServer) {
|
||||
server->cleanup_clients();
|
||||
timeval next{1, 0};
|
||||
|
||||
if(server->client_cleanup)
|
||||
event_add(server->client_cleanup, &next);
|
||||
if(server->event_cleanup)
|
||||
event_add(server->event_cleanup, &next);
|
||||
}
|
||||
|
||||
//Basic IO
|
||||
@@ -129,20 +132,30 @@ void LicenseServer::handleEventWrite(int fd, short, void* ptrServer) {
|
||||
auto server = static_cast<LicenseServer *>(ptrServer);
|
||||
auto client = server->findClient(fd);
|
||||
if(!client) return;
|
||||
buffer::RawBuffer* buffer = nullptr;
|
||||
{
|
||||
threads::MutexLock lock(client->network.lock);
|
||||
buffer = TAILQ_FIRST(&client->network.writeQueue);
|
||||
if(!buffer) return;
|
||||
|
||||
auto writtenBytes = send(fd, &buffer->buffer[buffer->index], buffer->length - buffer->index, 0);
|
||||
buffer->index += writtenBytes;
|
||||
buffer::RawBuffer* write_buffer{nullptr};
|
||||
std::unique_lock write_lock(client->network.write_queue_lock);
|
||||
while(true) { //TODO: May add some kind of timeout?
|
||||
write_buffer = TAILQ_FIRST(&client->network.write_queue);
|
||||
if(!write_buffer) return;
|
||||
|
||||
if(buffer->index >= buffer->length) {
|
||||
TAILQ_REMOVE(&client->network.writeQueue, buffer, tail);
|
||||
delete buffer;
|
||||
auto writtenBytes = send(fd, &write_buffer->buffer[write_buffer->index], write_buffer->length - write_buffer->index, 0);
|
||||
if(writtenBytes <= 0) {
|
||||
write_lock.unlock();
|
||||
if(writtenBytes == -1 && errno == EAGAIN)
|
||||
return;
|
||||
logError(LOG_LICENSE_CONTROLL, "Invalid write. Disconnecting remote client. Message: {}/{}", errno, strerror(errno));
|
||||
server->unregisterClient(client);
|
||||
return;
|
||||
} else {
|
||||
write_buffer->index += writtenBytes;
|
||||
}
|
||||
if(!TAILQ_EMPTY(&client->network.writeQueue))
|
||||
|
||||
if(write_buffer->index >= write_buffer->length) {
|
||||
TAILQ_REMOVE(&client->network.write_queue, write_buffer, tail);
|
||||
delete write_buffer;
|
||||
}
|
||||
if(!TAILQ_EMPTY(&client->network.write_queue))
|
||||
event_add(client->network.writeEvent, nullptr);
|
||||
}
|
||||
}
|
||||
@@ -158,23 +171,31 @@ void ConnectedClient::sendPacket(const protocol::packet& packet) {
|
||||
xorBuffer(&buffer->buffer[sizeof(packet.header)], packet.data.length(), this->protocol.cryptKey.data(), this->protocol.cryptKey.length());
|
||||
|
||||
{
|
||||
threads::MutexLock lock(this->network.lock);
|
||||
TAILQ_INSERT_TAIL(&this->network.writeQueue, buffer, tail);
|
||||
lock_guard queue_lock{this->network.write_queue_lock};
|
||||
TAILQ_INSERT_TAIL(&this->network.write_queue, buffer, tail);
|
||||
}
|
||||
event_add(this->network.writeEvent, nullptr);
|
||||
{
|
||||
lock_guard state_lock{this->protocol.state_lock};
|
||||
if(this->protocol.state == protocol::UNCONNECTED) goto error_cleanup;
|
||||
|
||||
event_add(this->network.writeEvent, nullptr);
|
||||
return;
|
||||
}
|
||||
error_cleanup:
|
||||
delete buffer;
|
||||
}
|
||||
|
||||
void ConnectedClient::init() {
|
||||
protocol.last_read = std::chrono::system_clock::now();
|
||||
TAILQ_INIT(&network.writeQueue);
|
||||
TAILQ_INIT(&network.write_queue);
|
||||
}
|
||||
|
||||
void ConnectedClient::uninit() {
|
||||
void ConnectedClient::uninit(bool blocking) {
|
||||
{
|
||||
threads::MutexLock lock(this->network.lock);
|
||||
lock_guard queue_lock{this->network.write_queue_lock};
|
||||
ts::buffer::RawBuffer* buffer;
|
||||
while ((buffer = TAILQ_FIRST(&this->network.writeQueue))) {
|
||||
TAILQ_REMOVE(&this->network.writeQueue, buffer, tail);
|
||||
while ((buffer = TAILQ_FIRST(&this->network.write_queue))) {
|
||||
TAILQ_REMOVE(&this->network.write_queue, buffer, tail);
|
||||
delete buffer;
|
||||
}
|
||||
}
|
||||
@@ -183,11 +204,21 @@ void ConnectedClient::uninit() {
|
||||
close(this->network.fileDescriptor);
|
||||
network.fileDescriptor = 0;
|
||||
}
|
||||
if(this->network.readEvent) event_del(this->network.readEvent);
|
||||
this->network.readEvent = nullptr;
|
||||
|
||||
if(this->network.writeEvent) event_del(this->network.writeEvent);
|
||||
this->network.writeEvent = nullptr;
|
||||
std::unique_lock elock{this->network.event_mutex};
|
||||
auto read_event = std::exchange(this->network.readEvent, nullptr);
|
||||
auto write_event = std::exchange(this->network.writeEvent, nullptr);
|
||||
elock.unlock();
|
||||
|
||||
if(blocking) {
|
||||
if(read_event) event_del_block(read_event);
|
||||
if(write_event) event_del_block(write_event);
|
||||
} else {
|
||||
if(read_event) event_del_noblock(read_event);
|
||||
if(write_event) event_del_noblock(write_event);
|
||||
}
|
||||
if(read_event) event_free(read_event);
|
||||
if(write_event) event_free(write_event);
|
||||
}
|
||||
|
||||
void LicenseServer::handleEventRead(int fd, short, void* ptrServer) {
|
||||
@@ -202,13 +233,21 @@ void LicenseServer::handleEventRead(int fd, short, void* ptrServer) {
|
||||
|
||||
if(read < 0){
|
||||
if(errno == EWOULDBLOCK) return;
|
||||
logError(LOG_LICENSE_CONTROLL, "Invalid read. Disconnecting remote manager. Message: {}/{}", errno, strerror(errno));
|
||||
event_del_noblock(client->network.readEvent);
|
||||
logError(LOG_LICENSE_CONTROLL, "Invalid read. Disconnecting remote client. Message: {}/{}", errno, strerror(errno));
|
||||
{
|
||||
std::lock_guard elock{client->network.event_mutex};
|
||||
if(client->network.readEvent)
|
||||
event_del_noblock(client->network.readEvent);
|
||||
}
|
||||
server->closeConnection(client);
|
||||
return;
|
||||
} else if(read == 0) {
|
||||
logError(LOG_LICENSE_CONTROLL, "Invalid read. Disconnecting remote client");
|
||||
event_del_noblock(client->network.readEvent);
|
||||
logMessage(LOG_LICENSE_CONTROLL, "[CLIENT][" + client->address() + "] Received EOF for client. Removing client.");
|
||||
{
|
||||
std::lock_guard elock{client->network.event_mutex};
|
||||
if(client->network.readEvent)
|
||||
event_del_noblock(client->network.readEvent);
|
||||
}
|
||||
server->closeConnection(client);
|
||||
return;
|
||||
}
|
||||
@@ -230,21 +269,21 @@ void LicenseServer::handleEventAccept(int fd, short, void* ptrServer) {
|
||||
if(setsockopt(client->network.fileDescriptor, IPPROTO_TCP, TCP_CORK, &disabled, sizeof(disabled)) < 0);// CERR("could not set no push");
|
||||
|
||||
if (client->network.fileDescriptor < 0) {
|
||||
logCritical("Could not accept new client! (" + to_string(client->network.fileDescriptor) + "|" + to_string(errno) + "|" + strerror(errno) + ")");
|
||||
logCritical(LOG_GENERAL, "Could not accept new client! (" + to_string(client->network.fileDescriptor) + "|" + to_string(errno) + "|" + strerror(errno) + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
client->protocol.state = protocol::HANDSCAKE;
|
||||
client->protocol.state = protocol::HANDSCHAKE;
|
||||
{
|
||||
lock_guard lock(server->lock);
|
||||
server->currentClients.push_back(client);
|
||||
lock_guard lock(server->client_lock);
|
||||
server->clients.push_back(client);
|
||||
}
|
||||
|
||||
client->network.readEvent = event_new(server->evBase, client->network.fileDescriptor, EV_READ | EV_PERSIST, LicenseServer::handleEventRead, server);
|
||||
client->network.writeEvent = event_new(server->evBase, client->network.fileDescriptor, EV_WRITE, LicenseServer::handleEventWrite, server);
|
||||
event_add(client->network.readEvent, nullptr);
|
||||
|
||||
logMessage(lstream << "Got new client from " << inet_ntoa(client->network.remoteAddr.sin_addr));
|
||||
logMessage(LOG_GENERAL, "Accepted new client from {}", inet_ntoa(client->network.remoteAddr.sin_addr));
|
||||
}
|
||||
|
||||
void LicenseServer::disconnectClient(const std::shared_ptr<ConnectedClient>& client, const std::string &reason) {
|
||||
@@ -252,15 +291,15 @@ void LicenseServer::disconnectClient(const std::shared_ptr<ConnectedClient>& cli
|
||||
}
|
||||
|
||||
void LicenseServer::closeConnection(const std::shared_ptr<ConnectedClient> &client, bool blocking) {
|
||||
if(this->evBaseDispatch && threads::self::id() == *this->evBaseDispatch) {
|
||||
if(this_thread::get_id() == this->event_base_dispatch.get_id()) {
|
||||
std::thread(std::bind(&LicenseServer::closeConnection, this, client, true)).detach();
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
unique_lock lock(client->network.lock);
|
||||
if(!TAILQ_EMPTY(&client->network.writeQueue)) {
|
||||
unique_lock lock(client->network.write_queue_lock);
|
||||
if(!TAILQ_EMPTY(&client->network.write_queue)) {
|
||||
lock.unlock();
|
||||
|
||||
if(!blocking) {
|
||||
@@ -271,7 +310,7 @@ void LicenseServer::closeConnection(const std::shared_ptr<ConnectedClient> &clie
|
||||
while(system_clock::now() - start < seconds(5)){
|
||||
{
|
||||
lock.lock();
|
||||
if(TAILQ_EMPTY(&client->network.writeQueue)) break;
|
||||
if(TAILQ_EMPTY(&client->network.write_queue)) break;
|
||||
lock.unlock();
|
||||
}
|
||||
threads::self::sleep_for(milliseconds(5));
|
||||
@@ -283,49 +322,57 @@ void LicenseServer::closeConnection(const std::shared_ptr<ConnectedClient> &clie
|
||||
|
||||
void LicenseServer::unregisterClient(const std::shared_ptr<ConnectedClient> &client) {
|
||||
{
|
||||
lock_guard lock(this->lock);
|
||||
lock_guard lock(this->client_lock);
|
||||
|
||||
auto it = find(this->currentClients.begin(), this->currentClients.end(), client);
|
||||
if(it != this->currentClients.end())
|
||||
this->currentClients.erase(it);
|
||||
auto it = find(this->clients.begin(), this->clients.end(), client);
|
||||
if(it != this->clients.end())
|
||||
this->clients.erase(it);
|
||||
}
|
||||
|
||||
client->protocol.state = protocol::UNCONNECTED;
|
||||
client->uninit();
|
||||
{
|
||||
std::lock_guard state_lock{client->protocol.state_lock};
|
||||
client->protocol.state = protocol::UNCONNECTED;
|
||||
}
|
||||
client->uninit(this_thread::get_id() != this->event_base_dispatch.get_id());
|
||||
}
|
||||
|
||||
void LicenseServer::cleanup_clients() {
|
||||
unique_lock lock(this->lock);
|
||||
auto clients = this->currentClients;
|
||||
unique_lock lock(this->client_lock);
|
||||
auto clients = this->clients;
|
||||
lock.unlock();
|
||||
|
||||
size_t cleanup_count{0};
|
||||
for(const auto& client : clients) {
|
||||
if(client->protocol.last_read + minutes(1) < system_clock::now()) {
|
||||
cleanup_count++;
|
||||
if(client->protocol.state != protocol::DISCONNECTING && client->protocol.state != protocol::UNCONNECTED) {
|
||||
this->disconnectClient(client, "timeout");
|
||||
this->closeConnection(client);
|
||||
client->protocol.state = protocol::DISCONNECTING;
|
||||
|
||||
std::lock_guard state_lock{client->protocol.state_lock};
|
||||
client->protocol.state = protocol::UNCONNECTED;
|
||||
} else {
|
||||
auto it = find(this->currentClients.begin(), this->currentClients.end(), client);
|
||||
if(it != this->currentClients.end())
|
||||
this->currentClients.erase(it);
|
||||
auto it = find(this->clients.begin(), this->clients.end(), client);
|
||||
if(it != this->clients.end())
|
||||
this->clients.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
debugMessage("Client's cleaned up");
|
||||
if(cleanup_count)
|
||||
debugMessage(LOG_GENERAL, "{} clients have been cleaned up due to a read timeout.", cleanup_count);
|
||||
}
|
||||
|
||||
std::shared_ptr<ConnectedClient> LicenseServer::findClient(int fileDescriptor) {
|
||||
lock_guard lock(this->lock);
|
||||
for(const auto& cl : this->currentClients)
|
||||
if(cl->network.fileDescriptor == fileDescriptor)
|
||||
std::shared_ptr<ConnectedClient> LicenseServer::findClient(int fd) {
|
||||
lock_guard lock(this->client_lock);
|
||||
for(const auto& cl : this->clients)
|
||||
if(cl->network.fileDescriptor == fd)
|
||||
return cl;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#define ERR(message) \
|
||||
do { \
|
||||
logError(lstream << message); \
|
||||
logError(LOG_GENERAL, message); \
|
||||
this->closeConnection(client); \
|
||||
return; \
|
||||
} while(0)
|
||||
@@ -337,10 +384,11 @@ void LicenseServer::handleMessage(shared_ptr<ConnectedClient>& client, const std
|
||||
memcpy(&packet.header, message.data(), sizeof(protocol::packet::header));
|
||||
packet.data = message.substr(sizeof(protocol::packet::header), packet.header.length);
|
||||
|
||||
if(!client->protocol.cryptKey.empty())
|
||||
if(!client->protocol.cryptKey.empty()) {
|
||||
xorBuffer((char*) packet.data.data(), packet.data.length(), client->protocol.cryptKey.data(), client->protocol.cryptKey.length());
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
bool success{false};
|
||||
string error;
|
||||
try {
|
||||
if(packet.header.packetId == protocol::PACKET_CLIENT_HANDSHAKE) {
|
||||
@@ -351,6 +399,8 @@ void LicenseServer::handleMessage(shared_ptr<ConnectedClient>& client, const std
|
||||
success = this->handleServerValidation(client, packet, error);
|
||||
} else if(packet.header.packetId == protocol::PACKET_CLIENT_PROPERTY_ADJUSTMENT) {
|
||||
success = this->handlePacketPropertyUpdate(client, packet, error);
|
||||
} else if(packet.header.packetId == protocol::PACKET_CLIENT_LICENSE_UPGRADE) {
|
||||
success = this->handlePacketLicenseUpgrade(client, packet, error);
|
||||
} else if(packet.header.packetId == protocol::PACKET_CLIENT_AUTH_REQUEST) {
|
||||
success = this->handlePacketAuth(client, packet, error);
|
||||
} else if(packet.header.packetId == protocol::PACKET_CLIENT_LICENSE_CREATE_REQUEST) {
|
||||
@@ -368,7 +418,7 @@ void LicenseServer::handleMessage(shared_ptr<ConnectedClient>& client, const std
|
||||
}
|
||||
|
||||
if(!success) {
|
||||
logError("[CLIENT][" + client->address() + "] Failed to handle packet. message: " + error);
|
||||
logError(LOG_GENERAL, "[CLIENT][" + client->address() + "] Failed to handle packet. message: " + error);
|
||||
this->disconnectClient(client, error);
|
||||
}
|
||||
}
|
||||
@@ -9,9 +9,9 @@
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <ThreadPool/Thread.h>
|
||||
#include <shared/License.h>
|
||||
#include <shared/include/license/license.h>
|
||||
#include <arpa/inet.h>
|
||||
#include "LicenseManager.h"
|
||||
#include "DatabaseHandler.h"
|
||||
|
||||
namespace license {
|
||||
namespace web {
|
||||
@@ -31,69 +31,86 @@ namespace license {
|
||||
struct {
|
||||
sockaddr_in remoteAddr;
|
||||
int fileDescriptor = 0;
|
||||
event* readEvent = nullptr;
|
||||
|
||||
std::mutex event_mutex{};
|
||||
event* readEvent = nullptr; /* protected via state_lock (check state and the use these variables) */
|
||||
event* writeEvent = nullptr;
|
||||
|
||||
threads::Mutex lock;
|
||||
TAILQ_HEAD(, ts::buffer::RawBuffer) writeQueue;
|
||||
std::mutex write_queue_lock{};
|
||||
TAILQ_HEAD(, ts::buffer::RawBuffer) write_queue;
|
||||
} network;
|
||||
|
||||
struct {
|
||||
std::mutex state_lock{};
|
||||
protocol::RequestState state = protocol::UNCONNECTED;
|
||||
|
||||
std::chrono::system_clock::time_point last_read;
|
||||
std::string cryptKey = "";
|
||||
|
||||
int version{2}; /* current version is 3 */
|
||||
} protocol;
|
||||
|
||||
ClientType type = ClientType::SERVER;
|
||||
std::string username;
|
||||
std::string key;
|
||||
uint64_t key_pending_upgrade{0};
|
||||
std::string unique_identifier;
|
||||
|
||||
bool invalid_license = false;
|
||||
|
||||
void init();
|
||||
void uninit();
|
||||
void uninit(bool /* blocking */);
|
||||
void sendPacket(const protocol::packet&);
|
||||
|
||||
inline std::string address() { return inet_ntoa(network.remoteAddr.sin_addr); }
|
||||
};
|
||||
|
||||
struct WebCertificate {
|
||||
std::string revision;
|
||||
std::string key;
|
||||
std::string certificate;
|
||||
};
|
||||
|
||||
class LicenseServer {
|
||||
public:
|
||||
explicit LicenseServer(const sockaddr_in&, const std::shared_ptr<server::LicenseManager>&, const std::shared_ptr<stats::StatisticManager>& /* stats */, const std::shared_ptr<web::WebStatistics>& /* web stats */, const std::shared_ptr<UserManager>& /* user manager */);
|
||||
explicit LicenseServer(const sockaddr_in&, std::shared_ptr<server::database::DatabaseHandler> , std::shared_ptr<stats::StatisticManager> /* stats */, std::shared_ptr<web::WebStatistics> /* web stats */, std::shared_ptr<UserManager> /* user manager */);
|
||||
~LicenseServer();
|
||||
|
||||
bool startServer();
|
||||
bool start();
|
||||
bool isRunning(){ return this->running; }
|
||||
void stopServer();
|
||||
void stop();
|
||||
|
||||
std::shared_ptr<ConnectedClient> findClient(int fileDescriptor);
|
||||
void disconnectClient(const std::shared_ptr<ConnectedClient>&, const std::string& reason);
|
||||
void closeConnection(const std::shared_ptr<ConnectedClient>&, bool blocking = false);
|
||||
|
||||
std::deque<std::shared_ptr<ConnectedClient>> getClients() {
|
||||
std::lock_guard lock(this->lock);
|
||||
return currentClients;
|
||||
std::lock_guard lock(this->client_lock);
|
||||
return clients;
|
||||
}
|
||||
|
||||
std::shared_ptr<WebCertificate> web_certificate{nullptr};
|
||||
private:
|
||||
void unregisterClient(const std::shared_ptr<ConnectedClient>&);
|
||||
void cleanup_clients();
|
||||
|
||||
std::shared_ptr<web::WebStatistics> web_statistics;
|
||||
std::shared_ptr<stats::StatisticManager> statistics;
|
||||
std::shared_ptr<server::LicenseManager> manager;
|
||||
std::shared_ptr<server::database::DatabaseHandler> manager;
|
||||
std::shared_ptr<UserManager> user_manager;
|
||||
|
||||
bool running = false;
|
||||
std::mutex client_lock;
|
||||
std::deque<std::shared_ptr<ConnectedClient>> clients;
|
||||
|
||||
std::mutex lock;
|
||||
std::deque<std::shared_ptr<ConnectedClient>> currentClients;
|
||||
bool running = false; /* also secured by client_lock */
|
||||
|
||||
sockaddr_in* localAddr = nullptr;
|
||||
int fileDescriptor = 0;
|
||||
sockaddr_in localAddr{};
|
||||
int server_socket = 0;
|
||||
event_base* evBase = nullptr;
|
||||
event* acceptEvent = nullptr;
|
||||
event* client_cleanup = nullptr;
|
||||
event* event_accept = nullptr;
|
||||
event* event_cleanup = nullptr;
|
||||
|
||||
threads::Thread* evBaseDispatch = nullptr;
|
||||
std::thread event_base_dispatch{};
|
||||
|
||||
static void handleEventCleanup(int, short, void*);
|
||||
static void handleEventAccept(int, short, void*);
|
||||
@@ -105,6 +122,7 @@ namespace license {
|
||||
bool handleDisconnect(std::shared_ptr<ConnectedClient>&, protocol::packet&, std::string& error);
|
||||
bool handleHandshake(std::shared_ptr<ConnectedClient>&, protocol::packet&, std::string& error);
|
||||
bool handleServerValidation(std::shared_ptr<ConnectedClient> &, protocol::packet &, std::string &error);
|
||||
bool handlePacketLicenseUpgrade(std::shared_ptr<ConnectedClient> &client, protocol::packet &packet, std::string &error);
|
||||
bool handlePacketPropertyUpdate(std::shared_ptr<ConnectedClient> &, protocol::packet &, std::string &error);
|
||||
|
||||
bool handlePacketAuth(std::shared_ptr<ConnectedClient> &, protocol::packet &, std::string &error);
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
#include <misc/endianness.h>
|
||||
#include <misc/base64.h>
|
||||
#include <misc/hex.h>
|
||||
#include <log/LogUtils.h>
|
||||
#include <LicenseManager.pb.h>
|
||||
#include <shared/License.h>
|
||||
#include <shared/include/license/license.h>
|
||||
#include "LicenseRequest.pb.h"
|
||||
#include "shared/License.h"
|
||||
#include "shared/include/license/license.h"
|
||||
#include "LicenseServer.h"
|
||||
#include "WebAPI.h"
|
||||
#include "StatisticManager.h"
|
||||
@@ -21,6 +22,8 @@ inline void generate(char* buffer, size_t length){
|
||||
buffer[index] = rand();
|
||||
}
|
||||
|
||||
#define _str(x) #x
|
||||
|
||||
#define TEST_PROTOCOL_STATE(expected) \
|
||||
if(client->protocol.state != protocol::expected) { \
|
||||
error = "invalid protocol state"; \
|
||||
@@ -36,21 +39,23 @@ if(packet.data.length() < (expected)) { \
|
||||
#define PARSE_PROTO(class, var) \
|
||||
ts::proto::license::class var; \
|
||||
if(!var.ParseFromString(packet.data)) { \
|
||||
error = "invalid data!"; \
|
||||
error = "invalid data (" _str(class) ")!"; \
|
||||
return false; \
|
||||
}
|
||||
|
||||
#define CRYPT_KEY_LENGTH 32
|
||||
bool LicenseServer::handleHandshake(shared_ptr<ConnectedClient>& client, protocol::packet& packet, std::string &error) {
|
||||
TEST_PROTOCOL_STATE(HANDSCAKE);
|
||||
TEST_PROTOCOL_STATE(HANDSCHAKE);
|
||||
ENSURE_PACKET_SIZE(4);
|
||||
if((uint8_t) packet.data[0] != 0xC0 || (uint8_t) packet.data[1] != 0xFF || (uint8_t) packet.data[2] != 0xEE) {
|
||||
error = "invalid magic!";
|
||||
return false;
|
||||
}
|
||||
if((uint8_t) packet.data[3] != LICENSE_PROT_VERSION) {
|
||||
error = "invalid version!";
|
||||
return false;
|
||||
|
||||
client->protocol.version = (uint8_t) packet.data[3];
|
||||
if(client->protocol.version < 2 || client->protocol.version > 3) {
|
||||
error = "unsupported version";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool manager = false;
|
||||
@@ -66,7 +71,7 @@ bool LicenseServer::handleHandshake(shared_ptr<ConnectedClient>& client, protoco
|
||||
size_t buffer_index = 0;
|
||||
buffer[buffer_index++] = 0xAF;
|
||||
buffer[buffer_index++] = 0xFE;
|
||||
buffer[buffer_index++] = LICENSE_PROT_VERSION;
|
||||
buffer[buffer_index++] = client->protocol.version;
|
||||
le2be16(CRYPT_KEY_LENGTH, buffer, buffer_index, &buffer_index);
|
||||
memcpy(&buffer[buffer_index], buffer_cryptkey, CRYPT_KEY_LENGTH);
|
||||
buffer_index += CRYPT_KEY_LENGTH;
|
||||
@@ -88,21 +93,32 @@ bool LicenseServer::handleHandshake(shared_ptr<ConnectedClient>& client, protoco
|
||||
}
|
||||
|
||||
bool LicenseServer::handleDisconnect(shared_ptr<ConnectedClient>& client, protocol::packet& packet, std::string &error) {
|
||||
logMessage("[CLIENT][" + client->address() + "] Remote disconnect. Reason: " + packet.data);
|
||||
logMessage(LOG_GENERAL, "[CLIENT][" + client->address() + "] Remote disconnect. Reason: " + packet.data);
|
||||
this->closeConnection(client);
|
||||
return true;
|
||||
}
|
||||
|
||||
inline void fill_info(proto::license::LicenseInfo* proto, const shared_ptr<LicenseInfo>& info, const std::string& key) {
|
||||
proto->set_key(key);
|
||||
proto->set_username(info->username);
|
||||
proto->set_first_name(info->first_name);
|
||||
proto->set_last_name(info->last_name);
|
||||
proto->set_email(info->email);
|
||||
proto->set_type(info->type);
|
||||
proto->set_created(duration_cast<milliseconds>(info->creation.time_since_epoch()).count());
|
||||
proto->set_begin(duration_cast<milliseconds>(info->start.time_since_epoch()).count());
|
||||
proto->set_end(duration_cast<milliseconds>(info->end.time_since_epoch()).count());
|
||||
if(info) {
|
||||
proto->set_username(info->username);
|
||||
proto->set_first_name(info->first_name);
|
||||
proto->set_last_name(info->last_name);
|
||||
proto->set_email(info->email);
|
||||
proto->set_type(info->type);
|
||||
proto->set_created(duration_cast<milliseconds>(info->creation.time_since_epoch()).count());
|
||||
proto->set_begin(duration_cast<milliseconds>(info->start.time_since_epoch()).count());
|
||||
proto->set_end(duration_cast<milliseconds>(info->end.time_since_epoch()).count());
|
||||
} else {
|
||||
proto->set_username("invalid (null)");
|
||||
proto->set_first_name("invalid (null)");
|
||||
proto->set_last_name("invalid (null)");
|
||||
proto->set_email("invalid (null)");
|
||||
proto->set_type(0);
|
||||
proto->set_created(0);
|
||||
proto->set_begin(0);
|
||||
proto->set_end(0);
|
||||
}
|
||||
}
|
||||
|
||||
std::string string_to_hex(const std::string& input)
|
||||
@@ -122,109 +138,189 @@ std::string string_to_hex(const std::string& input)
|
||||
}
|
||||
|
||||
bool LicenseServer::handleServerValidation(shared_ptr<ConnectedClient> &client, protocol::packet &packet, std::string &error) {
|
||||
TEST_PROTOCOL_STATE(SERVER_VALIDATION);
|
||||
if(client->protocol.state != protocol::LICENSE_UPGRADE) /* server may wants to verify new license */
|
||||
TEST_PROTOCOL_STATE(SERVER_VALIDATION);
|
||||
|
||||
PARSE_PROTO(ServerValidation, pkt);
|
||||
|
||||
shared_ptr<License> remoteLicense = nullptr;
|
||||
if(pkt.licensed() && !pkt.has_license()) {
|
||||
//TODO shutdown server
|
||||
std::shared_ptr<License> remote_license{nullptr};
|
||||
if(pkt.licensed() && (!pkt.has_license() || !pkt.has_license_info())) {
|
||||
error = "invalid/missing license data";
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!pkt.has_info()) {
|
||||
error = "invalid data or missing data";
|
||||
return false;
|
||||
}
|
||||
if(pkt.has_license()){ //Client has license
|
||||
remoteLicense = readLocalLicence(pkt.license(), error);
|
||||
if(!remoteLicense) {
|
||||
error = "Could not read remote key: " + error;
|
||||
|
||||
if(pkt.licensed()){ //Client has license
|
||||
remote_license = readLocalLicence(pkt.license(), error);
|
||||
if(!remote_license) {
|
||||
error = "could not parse license (" + error + ")";
|
||||
return false;
|
||||
};
|
||||
logMessage(LOG_GENERAL, "[CLIENT][{}] Got remote license. Registered to {}. Key: {} (0x{})", client->address(), remoteLicense->owner(), base64::encode(remoteLicense->key()), string_to_hex(remoteLicense->key()));
|
||||
client->key = remoteLicense->key();
|
||||
} else { }
|
||||
if(pkt.licensed() && !pkt.has_license_info()) {
|
||||
error = "Invalid content!";
|
||||
return false;
|
||||
}
|
||||
|
||||
logMessage(LOG_GENERAL, "[CLIENT][{}] Got remote license. Registered to {}. Key: {} (0x{})", client->address(), remote_license->owner(), base64::encode(remote_license->key()), string_to_hex(remote_license->key()));
|
||||
client->key = remote_license->key();
|
||||
}
|
||||
|
||||
logMessage(LOG_GENERAL, "[CLIENT][{}] Got some server information. TeaSpeak-Version: {} uname: {}", client->address(), pkt.info().version(), pkt.info().uname());
|
||||
ts::proto::license::LicenseResponse response;
|
||||
|
||||
//Forces
|
||||
|
||||
ts::proto::license::LicenseResponse response{};
|
||||
client->unique_identifier = pkt.info().has_unique_id() ? pkt.info().unique_id() : client->address();
|
||||
if(remoteLicense && pkt.licensed()) {
|
||||
auto info = this->manager->licenseInfo(remoteLicense->key());
|
||||
if(!info) {
|
||||
response.set_valid(false);
|
||||
|
||||
/*
|
||||
logMessage(LOG_GENERAL, "[CLIENT][{}] Unknown license! Adding it to database!", client->address());
|
||||
auto db_info = make_shared<LicenseInfo>();
|
||||
db_info->start = system_clock::now();
|
||||
db_info->end = remoteLicense->end();
|
||||
db_info->last_name = "unknown";
|
||||
db_info->first_name = "unknown";
|
||||
db_info->username = remoteLicense->owner();
|
||||
db_info->email = "unknonw@unknown";
|
||||
db_info->creation = system_clock::now();
|
||||
db_info->type = remoteLicense->data.type;
|
||||
this->manager->registerLicense(remoteLicense->key(), db_info, "teaforo-fix");
|
||||
info = this->manager->licenseInfo(remoteLicense->key());
|
||||
if(!info) {
|
||||
error = "could not insert key!";
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
if(remote_license) {
|
||||
auto info = this->manager->query_license_info(remote_license->key());
|
||||
|
||||
if(!info) {
|
||||
response.set_invalid_reason("license has not been found");
|
||||
response.set_valid(false);
|
||||
|
||||
logMessage(LOG_GENERAL, "[CLIENT][{}] Remote license hasn't been found in database. Shutting down server!", client->address());
|
||||
} else {
|
||||
response.set_update_pending(info->upgrade_id > 0);
|
||||
client->key_pending_upgrade = info->upgrade_id;
|
||||
if(info->deleted) {
|
||||
response.set_invalid_reason("license has been deleted");
|
||||
response.set_valid(false);
|
||||
logMessage(LOG_GENERAL, "[CLIENT][{}] Remote license has been deleted! Shutting down server!", client->address());
|
||||
} else {
|
||||
fill_info(response.mutable_license_info(), info, remoteLicense->data.licenceKey);
|
||||
response.set_valid(info->isValid());
|
||||
fill_info(response.mutable_license_info(), info, remote_license->data.licenceKey);
|
||||
auto is_invalid = !info->isNotExpired();
|
||||
if(is_invalid) {
|
||||
response.set_invalid_reason("license is invalid");
|
||||
response.set_valid(false);
|
||||
} else {
|
||||
response.set_valid(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
this->manager->logRequest(remoteLicense->key(), client->unique_identifier, client->address(), pkt.info().version(), response.valid());
|
||||
} else {
|
||||
this->manager->logRequest(remote_license->key(), client->unique_identifier, client->address(), pkt.info().version(), response.valid());
|
||||
} else {
|
||||
/* shall never happen, by default each server has the default license */
|
||||
response.set_valid(true);
|
||||
}
|
||||
if(pkt.has_memory_valid() && !pkt.memory_valid()) {
|
||||
response.set_invalid_reason("server memory seems to be invalid");
|
||||
response.set_valid(false);
|
||||
logError(LOG_GENERAL, "Server {} has patched license memory!", client->address());
|
||||
}
|
||||
|
||||
if(client->protocol.version == 2) {
|
||||
if(response.valid())
|
||||
response.mutable_blacklist()->set_state(ts::proto::license::VALID);
|
||||
else {
|
||||
response.mutable_blacklist()->set_reason(response.invalid_reason());
|
||||
response.mutable_blacklist()->set_state(ts::proto::license::BLACKLISTED); /* "Hack" for all old clients */
|
||||
|
||||
if(!response.has_license_info()) fill_info(response.mutable_license_info(), nullptr, ""); /* "Hack" for old clients which require a license. Else the server would not be stopped */
|
||||
client->invalid_license = true;
|
||||
}
|
||||
} else {
|
||||
if(!response.has_blacklist())
|
||||
response.mutable_blacklist()->set_state(ts::proto::license::VALID);
|
||||
}
|
||||
client->invalid_license = !response.valid();
|
||||
|
||||
response.mutable_blacklist()->set_state(ts::proto::license::VALID);
|
||||
client->sendPacket(protocol::packet{protocol::PACKET_SERVER_VALIDATION_RESPONSE, response});
|
||||
client->protocol.state = protocol::PROPERTY_ADJUSTMENT;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LicenseServer::handlePacketLicenseUpgrade(shared_ptr<ConnectedClient> &client, license::protocol::packet &packet,
|
||||
std::string &error) {
|
||||
TEST_PROTOCOL_STATE(PROPERTY_ADJUSTMENT);
|
||||
PARSE_PROTO(RequestLicenseUpgrade, pkt);
|
||||
|
||||
if(!client->key_pending_upgrade) {
|
||||
error = "no update pending";
|
||||
return false;
|
||||
}
|
||||
ts::proto::license::LicenseUpgradeResponse response;
|
||||
auto license_upgrade = this->manager->query_license_upgrade(client->key_pending_upgrade);
|
||||
response.set_valid(false);
|
||||
if(license_upgrade) {
|
||||
if(!license_upgrade->valid) {
|
||||
response.set_error_message("upgrade has been invalidated");
|
||||
} else if(license_upgrade->is_expired()) {
|
||||
response.set_error_message("upgrade has been expired");
|
||||
} else if(license_upgrade->not_yet_available()) {
|
||||
response.set_error_message("upgrade is not yet active.");
|
||||
} else {
|
||||
response.set_valid(true);
|
||||
response.set_license_key(license_upgrade->license_key);
|
||||
|
||||
}
|
||||
this->manager->log_license_upgrade_attempt(license_upgrade->upgrade_id, response.valid(), client->unique_identifier, client->address());
|
||||
logMessage(LOG_GENERAL, "[CLIENT][{}] Client requested license upgrade {}. Result: {}", client->key_pending_upgrade, response.valid() ? "granted" : "denied (" + response.error_message() + ")");
|
||||
} else {
|
||||
response.set_error_message("failed to find upgrade");
|
||||
}
|
||||
|
||||
client->sendPacket(protocol::packet{protocol::PACKET_SERVER_LICENSE_UPGRADE_RESPONSE, response});
|
||||
client->protocol.state = protocol::LICENSE_UPGRADE;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LicenseServer::handlePacketPropertyUpdate(shared_ptr<ConnectedClient> &client, protocol::packet &packet, std::string &error) {
|
||||
TEST_PROTOCOL_STATE(PROPERTY_ADJUSTMENT);
|
||||
if(client->protocol.state != protocol::LICENSE_UPGRADE) /* LICENSE_UPGRADE could be skipped */
|
||||
TEST_PROTOCOL_STATE(PROPERTY_ADJUSTMENT);
|
||||
if(client->invalid_license) {
|
||||
ts::proto::license::PropertyUpdateResponse response;
|
||||
response.set_accepted(true);
|
||||
response.set_reset_speach(false);
|
||||
response.set_speach_total_remote(0);
|
||||
response.set_speach_varianz_corrector(0);
|
||||
client->sendPacket(protocol::packet{protocol::PACKET_SERVER_PROPERTY_ADJUSTMENT, response});
|
||||
this->disconnectClient(client, "finished");
|
||||
|
||||
/* Not sure if we really want here to log these data. May put a lvalid=false within the db? */
|
||||
return true;
|
||||
}
|
||||
PARSE_PROTO(PropertyUpdateRequest, pkt);
|
||||
|
||||
logMessage("[CLIENT][" + client->address() + "] Got server statistics:");
|
||||
logMessage("[CLIENT][" + client->address() + "] Spoken total : " + to_string(pkt.speach_total()));
|
||||
logMessage("[CLIENT][" + client->address() + "] Spoken dead : " + to_string(pkt.speach_dead()));
|
||||
logMessage("[CLIENT][" + client->address() + "] Spoken online : " + to_string(pkt.speach_online()));
|
||||
logMessage("[CLIENT][" + client->address() + "] Spoken varianz : " + to_string(pkt.speach_varianz()));
|
||||
logMessage("[CLIENT][" + client->address() + "] -------------------------------");
|
||||
logMessage("[CLIENT][" + client->address() + "] Users online : " + to_string(pkt.clients_online()));
|
||||
logMessage("[CLIENT][" + client->address() + "] Web Users online : " + to_string(pkt.web_clients_online()));
|
||||
logMessage("[CLIENT][" + client->address() + "] Queries online : " + to_string(pkt.queries_online()));
|
||||
logMessage("[CLIENT][" + client->address() + "] Bots online : " + to_string(pkt.bots_online()));
|
||||
logMessage("[CLIENT][" + client->address() + "] Servers : " + to_string(pkt.servers_online()));
|
||||
logMessage(LOG_GENERAL, "[CLIENT][" + client->address() + "] Got server statistics:");
|
||||
logMessage(LOG_GENERAL, "[CLIENT][" + client->address() + "] Spoken total : " + to_string(pkt.speach_total()));
|
||||
logMessage(LOG_GENERAL, "[CLIENT][" + client->address() + "] Spoken dead : " + to_string(pkt.speach_dead()));
|
||||
logMessage(LOG_GENERAL, "[CLIENT][" + client->address() + "] Spoken online : " + to_string(pkt.speach_online()));
|
||||
logMessage(LOG_GENERAL, "[CLIENT][" + client->address() + "] Spoken varianz : " + to_string(pkt.speach_varianz()));
|
||||
logMessage(LOG_GENERAL, "[CLIENT][" + client->address() + "] -------------------------------");
|
||||
logMessage(LOG_GENERAL, "[CLIENT][" + client->address() + "] Users online : " + to_string(pkt.clients_online()));
|
||||
logMessage(LOG_GENERAL, "[CLIENT][" + client->address() + "] Web Users online : " + to_string(pkt.web_clients_online()));
|
||||
logMessage(LOG_GENERAL, "[CLIENT][" + client->address() + "] Queries online : " + to_string(pkt.queries_online()));
|
||||
logMessage(LOG_GENERAL, "[CLIENT][" + client->address() + "] Bots online : " + to_string(pkt.bots_online()));
|
||||
logMessage(LOG_GENERAL, "[CLIENT][" + client->address() + "] Servers : " + to_string(pkt.servers_online()));
|
||||
this->manager->logStatistic(client->key, client->unique_identifier, client->address(), pkt);
|
||||
//TODO test stuff!
|
||||
//TODO test stuff if its possible!
|
||||
|
||||
ts::proto::license::WebCertificate* web_certificate{nullptr};
|
||||
if(pkt.has_web_cert_revision()) {
|
||||
logMessage(LOG_GENERAL, "[CLIENT][" + client->address() + "] -------------------------------");
|
||||
logMessage(LOG_GENERAL, "[CLIENT][" + client->address() + "] Web cert revision : " + hex::hex(pkt.web_cert_revision()));
|
||||
|
||||
auto cert = this->web_certificate;
|
||||
if(cert && cert->revision != pkt.web_cert_revision()) {
|
||||
web_certificate = new ts::proto::license::WebCertificate{};
|
||||
web_certificate->set_key(cert->key);
|
||||
web_certificate->set_certificate(cert->certificate);
|
||||
web_certificate->set_revision(cert->revision);
|
||||
}
|
||||
}
|
||||
|
||||
ts::proto::license::PropertyUpdateResponse response;
|
||||
response.set_accepted(true);
|
||||
response.set_reset_speach(pkt.speach_total() < 0);
|
||||
response.set_speach_total_remote(pkt.speach_total());
|
||||
response.set_speach_varianz_corrector(0);
|
||||
response.set_allocated_web_certificate(web_certificate);
|
||||
client->sendPacket(protocol::packet{protocol::PACKET_SERVER_PROPERTY_ADJUSTMENT, response});
|
||||
this->disconnectClient(client, "finished");
|
||||
|
||||
if(this->statistics)
|
||||
this->statistics->reset_cache_general();
|
||||
|
||||
if(this->web_statistics)
|
||||
this->web_statistics->async_broadcast_notify_general_update();
|
||||
return true;
|
||||
@@ -234,7 +330,7 @@ bool LicenseServer::handlePacketAuth(shared_ptr<ConnectedClient> &client, protoc
|
||||
TEST_PROTOCOL_STATE(MANAGER_AUTHORIZATION);
|
||||
PARSE_PROTO(AuthorizationRequest, pkt);
|
||||
|
||||
logMessage("[MANAGER][" + client->address() + "] Got login. User: " + pkt.username() + " Password: " + pkt.password());
|
||||
logMessage(LOG_GENERAL, "[MANAGER][" + client->address() + "] Got login. User: " + pkt.username() + " Password: " + pkt.password());
|
||||
|
||||
ts::proto::license::AuthorizationResponse response;
|
||||
response.set_success(false);
|
||||
@@ -265,11 +361,11 @@ bool LicenseServer::handlePacketAuth(shared_ptr<ConnectedClient> &client, protoc
|
||||
response.set_message("username or password mismatch");
|
||||
|
||||
if(response.success()) {
|
||||
logMessage("[MANAGER][" + client->address() + "] Got succeeded user login. User: " + pkt.username() + " Password: " + pkt.password());
|
||||
logMessage(LOG_GENERAL, "[MANAGER][" + client->address() + "] Got succeeded user login. User: " + pkt.username() + " Password: " + pkt.password());
|
||||
client->username = pkt.username();
|
||||
client->protocol.state = protocol::MANAGER_CONNECTED;
|
||||
} else
|
||||
logMessage("[MANAGER][" + client->address() + "] Got failed user login. User: " + pkt.username() + " Password: " + pkt.password());
|
||||
logMessage(LOG_GENERAL, "[MANAGER][" + client->address() + "] Got failed user login. User: " + pkt.username() + " Password: " + pkt.password());
|
||||
|
||||
client->sendPacket(protocol::packet{protocol::PACKET_SERVER_AUTH_RESPONSE, response});
|
||||
return true;
|
||||
@@ -279,33 +375,58 @@ bool LicenseServer::handlePacketLicenseCreate(shared_ptr<ConnectedClient> &clien
|
||||
TEST_PROTOCOL_STATE(MANAGER_CONNECTED);
|
||||
PARSE_PROTO(LicenseCreateRequest, pkt);
|
||||
|
||||
logMessage(LOG_GENERAL, "[MANAGER][" + client->address() + "] Register new license to {} {} ({}). E-Mail: {}", pkt.issuer_first_name(), pkt.issuer_last_name(), pkt.issuer_username(), pkt.issuer_email());
|
||||
auto old_license = pkt.has_old_key() ? hex::hex(pkt.old_key()) : "none";
|
||||
logMessage(LOG_GENERAL, "[MANAGER][" + client->address() + "] Register new license to {} {} ({}). E-Mail: {}. Old license: {}", pkt.issuer_first_name(), pkt.issuer_last_name(), pkt.issuer_username(), pkt.issuer_email(), old_license);
|
||||
|
||||
ts::proto::license::LicenseCreateResponse response;
|
||||
auto old_key_id{0};
|
||||
ts::proto::license::LicenseCreateResponse response{};
|
||||
if(pkt.has_old_key()) {
|
||||
old_key_id = this->manager->key_id_cache()->get_key_id_from_key(pkt.old_key());
|
||||
if(old_key_id == 0) {
|
||||
response.set_error("failed to find old license key in database");
|
||||
goto _send_response;
|
||||
}
|
||||
}
|
||||
|
||||
auto db_info = make_shared<LicenseInfo>();
|
||||
db_info->start = system_clock::time_point() + milliseconds(pkt.begin());
|
||||
db_info->end = system_clock::time_point() + milliseconds(pkt.end());
|
||||
db_info->last_name = pkt.issuer_last_name();
|
||||
db_info->first_name = pkt.issuer_first_name();
|
||||
db_info->username = pkt.issuer_username();
|
||||
db_info->email = pkt.issuer_email();
|
||||
db_info->creation = system_clock::now();
|
||||
db_info->type = static_cast<LicenseType>(pkt.type());
|
||||
{
|
||||
auto db_info = make_shared<LicenseInfo>();
|
||||
db_info->start = system_clock::time_point() + milliseconds(pkt.begin());
|
||||
db_info->end = system_clock::time_point() + milliseconds(pkt.end());
|
||||
db_info->last_name = pkt.issuer_last_name();
|
||||
db_info->first_name = pkt.issuer_first_name();
|
||||
db_info->username = pkt.issuer_username();
|
||||
db_info->email = pkt.issuer_email();
|
||||
db_info->creation = system_clock::now();
|
||||
db_info->type = static_cast<LicenseType>(pkt.type());
|
||||
|
||||
auto license = license::createLocalLicence(db_info->type, db_info->end, db_info->first_name + db_info->last_name);
|
||||
auto parsed_license = license::readLocalLicence(license, error);
|
||||
if(!parsed_license) {
|
||||
response.set_error("failed to register license (parse)");
|
||||
} else {
|
||||
auto license = license::createLocalLicence(db_info->type, db_info->end, db_info->first_name + db_info->last_name);
|
||||
auto parsed_license = license::readLocalLicence(license, error);
|
||||
if(!parsed_license) {
|
||||
response.set_error("failed to register license (parse)");
|
||||
} else {
|
||||
if(!this->manager->register_license(parsed_license->key(), db_info, client->username)) {
|
||||
response.set_error("failed to register license");
|
||||
goto _send_response;
|
||||
}
|
||||
|
||||
if(!this->manager->registerLicense(parsed_license->key(), db_info, client->username)) {
|
||||
response.set_error("failed to register license");
|
||||
} else {
|
||||
fill_info(response.mutable_license(), db_info, parsed_license->key());
|
||||
response.set_exported_key(license);
|
||||
}
|
||||
}
|
||||
if(old_key_id) {
|
||||
auto new_key_id = this->manager->key_id_cache()->get_key_id_from_key(parsed_license->key());
|
||||
if(!new_key_id) {
|
||||
response.set_error("failed to find new license in database");
|
||||
goto _send_response;
|
||||
}
|
||||
|
||||
if(!this->manager->register_license_upgrade(old_key_id, new_key_id, std::chrono::system_clock::now(), db_info->end, license)) {
|
||||
response.set_error("failed to register license upgrade");
|
||||
goto _send_response;
|
||||
}
|
||||
}
|
||||
fill_info(response.mutable_license(), db_info, parsed_license->key());
|
||||
response.set_exported_key(license);
|
||||
}
|
||||
}
|
||||
|
||||
_send_response:
|
||||
client->sendPacket(protocol::packet{protocol::PACKET_SERVER_LICENSE_CREATE_RESPONSE, response});
|
||||
return true;
|
||||
}
|
||||
@@ -317,7 +438,7 @@ bool LicenseServer::handlePacketLicenseList(shared_ptr<ConnectedClient> &client,
|
||||
proto::license::LicenseListResponse response;
|
||||
response.set_end(false);
|
||||
|
||||
for(const auto& info : this->manager->listLicenses(pkt.offset(), pkt.count())) {
|
||||
for(const auto& info : this->manager->list_licenses(pkt.offset(), pkt.count())) {
|
||||
auto entry = response.add_entries();
|
||||
fill_info(entry, info.second, info.first);
|
||||
}
|
||||
@@ -331,7 +452,7 @@ bool LicenseServer::handlePacketLicenseDelete(shared_ptr<ConnectedClient> &clien
|
||||
PARSE_PROTO(LicenseDeleteRequest, pkt);
|
||||
|
||||
proto::license::LicenseDeleteResponse response;
|
||||
response.set_succeed(this->manager->deleteLicense(pkt.key(), pkt.full()));
|
||||
response.set_succeed(this->manager->delete_license(pkt.key(), pkt.full()));
|
||||
client->sendPacket(protocol::packet{protocol::PACKET_CLIENT_DELETE_RESPONSE, response});
|
||||
|
||||
return true;
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
|
||||
#include <sql/SqlQuery.h>
|
||||
#include <misc/std_unique_ptr.h>
|
||||
|
||||
#include <utility>
|
||||
#include "StatisticManager.h"
|
||||
#include "LicenseManager.h"
|
||||
#include "DatabaseHandler.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
@@ -76,16 +78,16 @@ system_clock::time_point HistoryStatistics::align_type(license::stats::HistorySt
|
||||
}
|
||||
}
|
||||
|
||||
StatisticManager::StatisticManager(const std::shared_ptr<license::server::LicenseManager> &manager) : license_manager(manager) {}
|
||||
StatisticManager::~StatisticManager() {}
|
||||
StatisticManager::StatisticManager(std::shared_ptr<license::server::database::DatabaseHandler> manager) : license_manager{std::move(manager)} {}
|
||||
StatisticManager::~StatisticManager() = default;
|
||||
|
||||
struct GeneralStatisticEntry {
|
||||
std::chrono::system_clock::time_point age;
|
||||
string unique_id = "";
|
||||
uint64_t key_id = 0;
|
||||
uint64_t servers = 0;
|
||||
uint64_t clients = 0;
|
||||
uint64_t bots = 0;
|
||||
string unique_id{""};
|
||||
uint64_t key_id{0};
|
||||
uint64_t servers{0};
|
||||
uint64_t clients{0};
|
||||
uint64_t bots{0};
|
||||
};
|
||||
|
||||
void StatisticManager::reset_cache_general() {
|
||||
@@ -93,7 +95,7 @@ void StatisticManager::reset_cache_general() {
|
||||
this->_general_statistics = nullptr;
|
||||
}
|
||||
|
||||
void parse_general_entry(deque<unique_ptr<GeneralStatisticEntry>>& entries, bool unique, int length, string* values, string* names) {
|
||||
void parse_general_entry(std::deque<std::unique_ptr<GeneralStatisticEntry>>& entries, bool unique, int length, string* values, string* names) {
|
||||
auto entry = make_unique<GeneralStatisticEntry>();
|
||||
for(int index = 0; index < length; index++) {
|
||||
if(names[index] == "keyId") {
|
||||
@@ -137,15 +139,17 @@ std::shared_ptr<GeneralStatistics> StatisticManager::general_statistics() {
|
||||
|
||||
deque<unique_ptr<GeneralStatisticEntry>> entries;
|
||||
|
||||
//TODO: Calculate web clients!
|
||||
auto result = sql::command(this->license_manager->sql(), "SELECT `keyId`, `unique_id`, `timestamp`,`server`,`clients`,`music` FROM `history_online` WHERE `timestamp` > :time ORDER BY `timestamp` ASC",
|
||||
variable{":time", duration_cast<milliseconds>(system_clock::now().time_since_epoch() - hours(2) - minutes(10)).count()}) //10min as buffer
|
||||
.query(std::function<decltype(parse_general_entry)>(parse_general_entry), entries, true);
|
||||
.query(std::function<decltype(parse_general_entry)>{parse_general_entry}, entries, true);
|
||||
|
||||
auto stats = make_shared<GeneralStatistics>();
|
||||
for(auto& entry : entries) {
|
||||
stats->bots += entry->bots;
|
||||
stats->clients += entry->clients;
|
||||
stats->servers += entry->servers;
|
||||
stats->empty_instances += entry->clients == 0;
|
||||
stats->instances++;
|
||||
}
|
||||
stats->age = system_clock::now();
|
||||
|
||||
@@ -3,59 +3,71 @@
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include <chrono>
|
||||
#include "LicenseManager.h"
|
||||
#include "DatabaseHandler.h"
|
||||
|
||||
namespace license {
|
||||
namespace stats {
|
||||
struct GeneralStatistics {
|
||||
std::chrono::system_clock::time_point age;
|
||||
namespace license::stats {
|
||||
struct GeneralStatistics {
|
||||
std::chrono::system_clock::time_point age;
|
||||
|
||||
uint64_t instances = 0;
|
||||
uint64_t servers = 0;
|
||||
uint64_t clients = 0;
|
||||
uint64_t bots = 0;
|
||||
};
|
||||
uint64_t empty_instances{0};
|
||||
uint64_t instances{0};
|
||||
uint64_t servers{0};
|
||||
uint64_t clients{0};
|
||||
uint64_t bots{0};
|
||||
};
|
||||
|
||||
struct HistoryStatistics {
|
||||
enum HistoryType {
|
||||
LAST_DAY,
|
||||
DAY_YESTERDAY,
|
||||
DAY_7DAYS_AGO,
|
||||
LAST_WEEK,
|
||||
LAST_MONTH,
|
||||
LAST_HALF_YEAR
|
||||
};
|
||||
static std::chrono::system_clock::time_point align_type(HistoryType type, const std::chrono::system_clock::time_point&);
|
||||
static std::chrono::milliseconds time_period(HistoryType type);
|
||||
static std::chrono::milliseconds cache_timeout(HistoryType type);
|
||||
static std::chrono::milliseconds type_duration(HistoryType type);
|
||||
struct HistoryStatistics {
|
||||
enum HistoryPeriod {
|
||||
DAY,
|
||||
WEEK,
|
||||
MONTH,
|
||||
HALF_YEAR,
|
||||
YEAR
|
||||
};
|
||||
enum HistoryOffset {
|
||||
NOW,
|
||||
ONE_BEFORE,
|
||||
SEVEN_BEFORE,
|
||||
TWELVE_BEFORE
|
||||
};
|
||||
enum HistoryType {
|
||||
LAST_DAY,
|
||||
DAY_YESTERDAY,
|
||||
DAY_7DAYS_AGO,
|
||||
LAST_WEEK,
|
||||
LAST_MONTH,
|
||||
LAST_HALF_YEAR
|
||||
};
|
||||
static std::chrono::system_clock::time_point align_type(HistoryType type, const std::chrono::system_clock::time_point&);
|
||||
static std::chrono::milliseconds time_period(HistoryType type);
|
||||
static std::chrono::milliseconds cache_timeout(HistoryType type);
|
||||
static std::chrono::milliseconds type_duration(HistoryType type);
|
||||
|
||||
std::chrono::system_clock::time_point evaluated;
|
||||
std::chrono::system_clock::time_point begin;
|
||||
std::chrono::system_clock::time_point end;
|
||||
std::chrono::milliseconds period;
|
||||
HistoryType type;
|
||||
std::chrono::system_clock::time_point evaluated;
|
||||
std::chrono::system_clock::time_point begin;
|
||||
std::chrono::system_clock::time_point end;
|
||||
std::chrono::milliseconds period;
|
||||
HistoryType type;
|
||||
|
||||
std::deque<std::unique_ptr<server::LicenseManager::GlobalUserStatistics>> statistics;
|
||||
};
|
||||
std::shared_ptr<server::database::DatabaseHandler::UserHistory> statistics;
|
||||
};
|
||||
|
||||
class StatisticManager {
|
||||
public:
|
||||
explicit StatisticManager(const std::shared_ptr<server::LicenseManager>& /* manager */);
|
||||
virtual ~StatisticManager();
|
||||
class StatisticManager {
|
||||
public:
|
||||
explicit StatisticManager(std::shared_ptr<server::database::DatabaseHandler> /* manager */);
|
||||
virtual ~StatisticManager();
|
||||
|
||||
void reset_cache_general();
|
||||
std::shared_ptr<GeneralStatistics> general_statistics();
|
||||
std::shared_ptr<HistoryStatistics> history(HistoryStatistics::HistoryType);
|
||||
private:
|
||||
std::shared_ptr<server::LicenseManager> license_manager;
|
||||
void reset_cache_general();
|
||||
std::shared_ptr<GeneralStatistics> general_statistics();
|
||||
std::shared_ptr<HistoryStatistics> history(HistoryStatistics::HistoryType);
|
||||
private:
|
||||
std::shared_ptr<server::database::DatabaseHandler> license_manager;
|
||||
|
||||
std::recursive_mutex _general_statistics_lock;
|
||||
std::recursive_mutex _general_statistics_generate_lock;
|
||||
std::shared_ptr<GeneralStatistics> _general_statistics;
|
||||
std::recursive_mutex _general_statistics_lock;
|
||||
std::recursive_mutex _general_statistics_generate_lock;
|
||||
std::shared_ptr<GeneralStatistics> _general_statistics;
|
||||
|
||||
std::map<HistoryStatistics::HistoryType, std::recursive_mutex> _history_locks;
|
||||
std::map<HistoryStatistics::HistoryType, std::shared_ptr<HistoryStatistics>> _history;
|
||||
};
|
||||
}
|
||||
std::map<HistoryStatistics::HistoryType, std::recursive_mutex> _history_locks;
|
||||
std::map<HistoryStatistics::HistoryType, std::shared_ptr<HistoryStatistics>> _history;
|
||||
};
|
||||
}
|
||||
+119
-52
@@ -17,7 +17,7 @@ using namespace ts::ssl;
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
|
||||
WebStatistics::WebStatistics(const shared_ptr<LicenseManager> &manager, const std::shared_ptr<stats::StatisticManager>& stats) : license_manager(manager), statistics_manager(stats) {}
|
||||
WebStatistics::WebStatistics(const shared_ptr<database::DatabaseHandler> &manager, const std::shared_ptr<stats::StatisticManager>& stats) : license_manager(manager), statistics_manager(stats) {}
|
||||
WebStatistics::~WebStatistics() {}
|
||||
|
||||
#define SFAIL(message) \
|
||||
@@ -38,11 +38,10 @@ bool WebStatistics::start(std::string &error, uint16_t port, const std::shared_p
|
||||
this->ssl = ssl;
|
||||
|
||||
{
|
||||
this->socket.local_address = make_unique<sockaddr_in>();
|
||||
memset(this->socket.local_address.get(), 0, sizeof(sockaddr_in));
|
||||
this->socket.local_address->sin_family = AF_INET;
|
||||
this->socket.local_address->sin_addr.s_addr = INADDR_ANY;
|
||||
this->socket.local_address->sin_port = htons(port);
|
||||
memset(&this->socket.address, 0, sizeof(sockaddr_in));
|
||||
this->socket.address.sin_family = AF_INET;
|
||||
this->socket.address.sin_addr.s_addr = INADDR_ANY;
|
||||
this->socket.address.sin_port = htons(port);
|
||||
}
|
||||
|
||||
this->socket.file_descriptor = ::socket(AF_INET, SOCK_STREAM, 0);
|
||||
@@ -50,7 +49,7 @@ bool WebStatistics::start(std::string &error, uint16_t port, const std::shared_p
|
||||
|
||||
if(setsockopt(this->socket.file_descriptor, SOL_SOCKET, SO_REUSEADDR, &enabled, sizeof(enabled)) < 0) SFAIL("could not set reuse address");
|
||||
if(setsockopt(this->socket.file_descriptor, IPPROTO_TCP, TCP_CORK, &disabled, sizeof(disabled)) < 0) SFAIL("could not set no push");
|
||||
if(bind(this->socket.file_descriptor, (struct sockaddr *) this->socket.local_address.get(), sizeof(sockaddr_in)) < 0) SFAIL("Could not bind socket on " + string(inet_ntoa(this->socket.local_address->sin_addr)));
|
||||
if(bind(this->socket.file_descriptor, (struct sockaddr *) &this->socket.address, sizeof(sockaddr_in)) < 0) SFAIL("Could not bind socket on " + string(inet_ntoa(this->socket.address.sin_addr)));
|
||||
|
||||
if(listen(this->socket.file_descriptor, 32) < 0) SFAIL("Could not listen on socket");
|
||||
|
||||
@@ -162,13 +161,11 @@ void WebStatistics::initialize_client(const std::shared_ptr<license::web::WebSta
|
||||
|
||||
auto lmethod = request.method;
|
||||
transform(lmethod.begin(), lmethod.end(), lmethod.begin(), ::tolower);
|
||||
if(lmethod == "get" && !request.parameters["type"].empty())
|
||||
if(lmethod == "get" && request.parameters.count("type") && !request.parameters.at("type").empty())
|
||||
this->handle_request(_client, request, response);
|
||||
};
|
||||
|
||||
client->pipe_websocket->initialize();
|
||||
|
||||
//FIXME Setup ssl
|
||||
}
|
||||
|
||||
{
|
||||
@@ -200,14 +197,15 @@ void WebStatistics::initialize_client(const std::shared_ptr<license::web::WebSta
|
||||
});
|
||||
|
||||
{
|
||||
std::string error{};
|
||||
auto options = make_shared<pipes::SSL::Options>();
|
||||
options->type = pipes::SSL::SERVER;
|
||||
options->context_method = TLS_method();
|
||||
options->free_unused_keypairs = false; /* we dont want our keys get removed */
|
||||
|
||||
options->default_keypair({this->ssl->privateKey, this->ssl->certificate});
|
||||
if(!client->pipe_ssl->initialize(options)) {
|
||||
logError(LOG_LICENSE_WEB, "[{}][SSL] Failed to setup ssl! Disconnecting client", client->client_prefix());
|
||||
if(!client->pipe_ssl->initialize(options, error)) {
|
||||
logError(LOG_LICENSE_WEB, "[{}][SSL] Failed to setup ssl ({})! Disconnecting client", client->client_prefix(), error);
|
||||
this->close_connection(client);
|
||||
}
|
||||
}
|
||||
@@ -299,17 +297,21 @@ void WebStatistics::handleEventWrite(int file_descriptor, short, void* ptr_serve
|
||||
}
|
||||
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lock(client->execute_lock);
|
||||
std::unique_lock elock(client->execute_lock);
|
||||
if(client->buffer_write.empty()) return;
|
||||
auto& buffer = client->buffer_write.front();
|
||||
|
||||
auto written = send(file_descriptor, buffer.data(), buffer.length(), MSG_DONTWAIT | MSG_NOSIGNAL);
|
||||
if(written < 0){
|
||||
elock.unlock();
|
||||
|
||||
if(errno == EWOULDBLOCK) return;
|
||||
logError(LOG_LICENSE_WEB, "[{}] Invalid write: {}/{}. Closing connection.", client->client_prefix(), errno, strerror(errno));
|
||||
server->close_connection(client);
|
||||
return;
|
||||
} else if(written == 0) {
|
||||
elock.unlock();
|
||||
|
||||
logError(LOG_LICENSE_WEB, "[{}] Invalid write (eof). Closing connection", client->client_prefix());
|
||||
server->close_connection(client);
|
||||
return;
|
||||
@@ -336,17 +338,20 @@ void WebStatistics::close_connection(const std::shared_ptr<license::web::WebStat
|
||||
else; //TODO Error handling?
|
||||
}
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lock(client->execute_lock);
|
||||
if(client->event_read) {
|
||||
event_del(client->event_read);
|
||||
event_free(client->event_read);
|
||||
client->event_read = nullptr;
|
||||
std::unique_lock elock(client->execute_lock);
|
||||
auto event_read = std::exchange(client->event_read, nullptr);
|
||||
auto event_write = std::exchange(client->event_write, nullptr);
|
||||
elock.unlock();
|
||||
if(event_read) {
|
||||
event_del(event_read);
|
||||
event_free(event_read);
|
||||
}
|
||||
if(client->event_write) {
|
||||
event_del(client->event_write);
|
||||
event_free(client->event_write);
|
||||
client->event_write = nullptr;
|
||||
if(event_write) {
|
||||
event_del(event_write);
|
||||
event_free(event_write);
|
||||
}
|
||||
|
||||
elock.lock();
|
||||
if(client->file_descriptor > 0) {
|
||||
if(shutdown(client->file_descriptor, SHUT_RDWR) < 0); //TODO error handling
|
||||
if(close(client->file_descriptor) < 0); //TODO error handling
|
||||
@@ -355,6 +360,7 @@ void WebStatistics::close_connection(const std::shared_ptr<license::web::WebStat
|
||||
|
||||
if(client->pipe_websocket)
|
||||
client->pipe_websocket = nullptr;
|
||||
|
||||
if(client->pipe_ssl) {
|
||||
client->pipe_ssl->finalize();
|
||||
client->pipe_ssl = nullptr;
|
||||
@@ -369,6 +375,15 @@ std::shared_ptr<WebStatistics::Client> WebStatistics::find_client_by_fd(int file
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool WebStatistics::send_message(const std::shared_ptr<Client> &client, const pipes::buffer_view &buffer) {
|
||||
lock_guard<recursive_mutex> lock(client->execute_lock);
|
||||
if(client->pipe_websocket) {
|
||||
client->pipe_websocket->send({pipes::TEXT, buffer.own_buffer()});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#define HERR(message, ...) \
|
||||
do {\
|
||||
logError(LOG_LICENSE_WEB, "[{}] " message, client->client_prefix(), ##__VA_ARGS__); \
|
||||
@@ -382,9 +397,6 @@ inline pipes::buffer json_dump(const Json::Value& value) {
|
||||
return pipes::buffer((void*) json.c_str(), json.length());
|
||||
}
|
||||
|
||||
Json::Value::Value(long value) : Value(to_string(value)) {}
|
||||
Json::Value::Value(unsigned long value) : Value(to_string(value)) {}
|
||||
|
||||
bool WebStatistics::handle_message(const std::shared_ptr<license::web::WebStatistics::Client> &client, const pipes::WSMessage &raw_message) {
|
||||
if(this->update_flood(client, 10)) {
|
||||
static pipes::buffer _response;
|
||||
@@ -396,7 +408,7 @@ bool WebStatistics::handle_message(const std::shared_ptr<license::web::WebStatis
|
||||
_response = json_dump(response);
|
||||
}
|
||||
|
||||
client->pipe_websocket->send({pipes::TEXT, _response});
|
||||
this->send_message(client, _response);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -429,9 +441,9 @@ bool WebStatistics::handle_message(const std::shared_ptr<license::web::WebStatis
|
||||
response["statistics"]["clients"] = to_string(stats->clients);
|
||||
response["statistics"]["music"] = to_string(stats->bots);
|
||||
|
||||
client->pipe_websocket->send({pipes::TEXT, json_dump(response)});
|
||||
this->send_message(client, json_dump(response));
|
||||
return true;
|
||||
} else if(message["request_type"].asString() == "history") {
|
||||
} else if(message["request_type"].asString() == "history" ) {
|
||||
auto type = message["history_type"].asInt();
|
||||
if(type < 0 || type > stats::HistoryStatistics::LAST_HALF_YEAR)
|
||||
__throw_range_error("invalid range!");
|
||||
@@ -450,6 +462,7 @@ bool WebStatistics::handle_message(const std::shared_ptr<license::web::WebStatis
|
||||
this->update_flood(client, 80);
|
||||
|
||||
|
||||
//TODO: Some kind of handle else this could crash the server on shutdown
|
||||
std::thread([&, client, type, message]() {
|
||||
auto history = this->statistics_manager->history((stats::HistoryStatistics::HistoryType) type);
|
||||
|
||||
@@ -462,20 +475,70 @@ bool WebStatistics::handle_message(const std::shared_ptr<license::web::WebStatis
|
||||
response["history"]["end"] = duration_cast<milliseconds>(history->end.time_since_epoch()).count();
|
||||
response["history"]["interval"] = duration_cast<milliseconds>(history->period).count();
|
||||
|
||||
int index = 0;
|
||||
for(auto& element : history->statistics) {
|
||||
response["history"]["data"][index]["instances"] = element->instance_online;
|
||||
response["history"]["data"][index]["servers"] = element->servers_online;
|
||||
response["history"]["data"][index]["clients"] = element->clients_online;
|
||||
response["history"]["data"][index]["music"] = element->bots_online;
|
||||
index++;
|
||||
}
|
||||
|
||||
lock_guard lock(client->execute_lock);
|
||||
if(client->pipe_websocket)
|
||||
client->pipe_websocket->send({pipes::TEXT, json_dump(response)});
|
||||
int index;
|
||||
auto stats = history->statistics;
|
||||
auto& history_data = response["history"]["data"];
|
||||
for(index = 0; index < stats->record_count; index++) {
|
||||
auto& indexed_data = history_data[index];
|
||||
indexed_data["instances_empty"] = stats->history[index].instance_empty;
|
||||
indexed_data["instances"] = stats->history[index].instance_online;
|
||||
indexed_data["servers"] = stats->history[index].servers_online;
|
||||
indexed_data["clients"] = stats->history[index].clients_online;
|
||||
indexed_data["music"] = stats->history[index].bots_online;
|
||||
}
|
||||
|
||||
this->send_message(client, json_dump(response));
|
||||
}).detach();
|
||||
return true;
|
||||
} else if(message["request_type"].asString() == "history_custom") {
|
||||
auto begin = std::chrono::milliseconds{message["history_begin"].asInt64()};
|
||||
auto end = std::chrono::milliseconds{message["history_end"].asInt64()};
|
||||
auto interval = std::chrono::milliseconds{message["history_interval"].asInt64()};
|
||||
auto code = message["code"].asString();
|
||||
|
||||
auto token = message["token"].asString();
|
||||
if(token != "blubalutsch") {
|
||||
Json::Value response;
|
||||
response["type"] = "error";
|
||||
response["code"] = code;
|
||||
response["message"] = "invalid token";
|
||||
this->send_message(client, json_dump(response));
|
||||
return true;
|
||||
}
|
||||
|
||||
//TODO: Some kind of handle else this could crash the server on shutdown
|
||||
std::thread([&, client, begin, end, interval, code]{
|
||||
auto data = this->license_manager->list_statistics_user(
|
||||
std::chrono::system_clock::time_point{} + begin,
|
||||
std::chrono::system_clock::time_point{} + end,
|
||||
interval
|
||||
);
|
||||
|
||||
Json::Value response;
|
||||
response["type"] = "response";
|
||||
response["code"] = code;
|
||||
|
||||
response["history"]["timestamp"] = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
|
||||
response["history"]["begin"] = duration_cast<milliseconds>(begin).count();
|
||||
response["history"]["end"] = duration_cast<milliseconds>(end).count();
|
||||
response["history"]["interval"] = duration_cast<milliseconds>(interval).count();
|
||||
|
||||
|
||||
int index;
|
||||
auto& history_data = response["history"]["data"];
|
||||
for(index = 0; index < data->record_count; index++) {
|
||||
auto& indexed_data = history_data[index];
|
||||
indexed_data["instances_empty"] = data->history[index].instance_empty;
|
||||
indexed_data["instances"] = data->history[index].instance_online;
|
||||
indexed_data["servers"] = data->history[index].servers_online;
|
||||
indexed_data["clients"] = data->history[index].clients_online;
|
||||
indexed_data["music"] = data->history[index].bots_online;
|
||||
}
|
||||
|
||||
this->send_message(client, json_dump(response));
|
||||
}).detach();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& ex) {
|
||||
@@ -484,8 +547,8 @@ bool WebStatistics::handle_message(const std::shared_ptr<license::web::WebStatis
|
||||
Json::Value response;
|
||||
response["type"] = "error";
|
||||
response["code"] = message["code"];
|
||||
response["message"] = "could not assign action";
|
||||
client->pipe_websocket->send({pipes::TEXT, json_dump(response)});
|
||||
response["message"] = "could not execute action";
|
||||
this->send_message(client, json_dump(response));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -494,7 +557,7 @@ bool WebStatistics::handle_message(const std::shared_ptr<license::web::WebStatis
|
||||
response["type"] = "error";
|
||||
response["code"] = message["code"];
|
||||
response["message"] = "could not assign action";
|
||||
client->pipe_websocket->send({pipes::TEXT, json_dump(response)});
|
||||
this->send_message(client, json_dump(response));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -503,16 +566,20 @@ bool WebStatistics::handle_request(const std::shared_ptr<license::web::WebStatis
|
||||
auto type = request.parameters.at("type");
|
||||
logMessage(LOG_LICENSE_WEB, "[{}] Received HTTP status request of type {}", client->client_prefix(), type);
|
||||
|
||||
if(type == "request" && request.parameters.at("request_type") == "general") {
|
||||
Json::Value json;
|
||||
json["type"] = "response";
|
||||
auto stats = this->statistics_manager->general_statistics();
|
||||
json["statistics"]["instances"] = to_string(stats->instances);
|
||||
json["statistics"]["servers"] = to_string(stats->servers);
|
||||
json["statistics"]["clients"] = to_string(stats->clients);
|
||||
json["statistics"]["music"] = to_string(stats->bots);
|
||||
response.setHeader("data", {json_dump(json).string()});
|
||||
response.code = http::code::_200;
|
||||
if(type == "request" && request.parameters.count("request_type") > 0) {
|
||||
const auto& type = request.parameters.at("request_type");
|
||||
if(type == "general") {
|
||||
Json::Value json;
|
||||
json["type"] = "response";
|
||||
auto stats = this->statistics_manager->general_statistics();
|
||||
json["statistics"]["instances_empty"] = to_string(stats->empty_instances);
|
||||
json["statistics"]["instances"] = to_string(stats->instances);
|
||||
json["statistics"]["servers"] = to_string(stats->servers);
|
||||
json["statistics"]["clients"] = to_string(stats->clients);
|
||||
json["statistics"]["music"] = to_string(stats->bots);
|
||||
response.setHeader("data", {json_dump(json).string()});
|
||||
response.code = http::code::_200;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
+10
-9
@@ -9,7 +9,7 @@
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <ThreadPool/Thread.h>
|
||||
#include <shared/License.h>
|
||||
#include <shared/include/license/license.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <misc/net.h>
|
||||
#include <json/json.h>
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
namespace license {
|
||||
namespace server {
|
||||
class LicenseManager;
|
||||
class DatabaseHandler;
|
||||
}
|
||||
namespace stats {
|
||||
class StatisticManager;
|
||||
@@ -31,6 +31,7 @@ namespace license {
|
||||
struct Client {
|
||||
std::unique_ptr<sockaddr_in> peer_address;
|
||||
int file_descriptor = 0;
|
||||
|
||||
event* event_read = nullptr;
|
||||
event* event_write = nullptr;
|
||||
|
||||
@@ -46,7 +47,7 @@ namespace license {
|
||||
inline std::string client_prefix() { return peer_address ? net::to_string(peer_address->sin_addr) : "unconnected"; }
|
||||
};
|
||||
public:
|
||||
WebStatistics(const std::shared_ptr<server::LicenseManager>& /* license manager */, const std::shared_ptr<stats::StatisticManager>& /* stats manager */);
|
||||
WebStatistics(const std::shared_ptr<server::database::DatabaseHandler>& /* license manager */, const std::shared_ptr<stats::StatisticManager>& /* stats manager */);
|
||||
virtual ~WebStatistics();
|
||||
|
||||
bool start(std::string& /* error */, uint16_t /* port */, const std::shared_ptr<ts::ssl::SSLContext>& /* ssl */);
|
||||
@@ -63,6 +64,7 @@ namespace license {
|
||||
|
||||
void close_connection(const std::shared_ptr<Client>& /* client */);
|
||||
std::shared_ptr<Client> find_client_by_fd(int /* file descriptor */);
|
||||
bool send_message(const std::shared_ptr<Client>& /* client */, const pipes::buffer_view& /* data */);
|
||||
|
||||
void broadcast_message(const Json::Value& /* message */);
|
||||
|
||||
@@ -73,14 +75,14 @@ namespace license {
|
||||
bool _running = false;
|
||||
std::recursive_mutex running_lock;
|
||||
|
||||
std::shared_ptr<server::LicenseManager> license_manager;
|
||||
std::shared_ptr<server::database::DatabaseHandler> license_manager;
|
||||
std::shared_ptr<stats::StatisticManager> statistics_manager;
|
||||
|
||||
struct {
|
||||
std::unique_ptr<sockaddr_in> local_address;
|
||||
int file_descriptor = 0;
|
||||
event* event_accept = nullptr;
|
||||
event_base* event_base = nullptr;
|
||||
sockaddr_in address{};
|
||||
int file_descriptor{0};
|
||||
event* event_accept{nullptr};
|
||||
struct event_base* event_base{nullptr};
|
||||
std::unique_ptr<threads::Thread> event_base_dispatch;
|
||||
} socket;
|
||||
|
||||
@@ -90,7 +92,6 @@ namespace license {
|
||||
std::deque<std::shared_ptr<Client>> clients;
|
||||
|
||||
threads::ThreadPool scheduler{1, "WebStatistics #"};
|
||||
|
||||
protected:
|
||||
static void handleEventAccept(int, short, void*);
|
||||
static void handleEventRead(int, short, void*);
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
#include <google/protobuf/message.h>
|
||||
#include <misc/base64.h>
|
||||
|
||||
//#define NO_OPEN_SSL
|
||||
#include <misc/digest.h>
|
||||
#include <cstring>
|
||||
#include <cassert>
|
||||
#include "crypt.h"
|
||||
#include "License.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
|
||||
inline void generate(char* buffer, size_t length){
|
||||
for(int index = 0; index < length; index++)
|
||||
buffer[index] = rand();
|
||||
}
|
||||
|
||||
namespace license {
|
||||
std::string LicenseTypeNames[] = LT_NAMES;
|
||||
|
||||
std::shared_ptr<License> readLocalLicence(const std::string& buffer, std::string& error){
|
||||
string bbuffer = base64::decode(buffer);
|
||||
if(bbuffer.length() < sizeof(License)) {
|
||||
error = "Invalid license size";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto license = static_cast<License *>(malloc(sizeof(License)));
|
||||
memcpy(license, bbuffer.data(), sizeof(License));
|
||||
|
||||
if(license->header.version != LICENSE_VERSION){
|
||||
error = "Invalid license version (" + to_string(license->header.version) + ")";
|
||||
return nullptr;
|
||||
}
|
||||
xorBuffer(&((char*) license)[sizeof(License::header)], sizeof(License::data), license->header.cryptKey, sizeof(license->header.cryptKey));
|
||||
|
||||
auto hash = digest::sha1(reinterpret_cast<const char *>(&license->data), sizeof(license->data));
|
||||
|
||||
uint64_t checkSum = 0;
|
||||
for(int i = 0; i < SHA_DIGEST_LENGTH; i++)
|
||||
checkSum += (uint8_t) hash[i] << (i % 8);
|
||||
|
||||
if((checkSum ^ *(uint64_t*) &license->header.cryptKey) != MAGIC_NUMER) {
|
||||
error = "invalid check sum";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return shared_ptr<License>(license, [](License* l){
|
||||
if(l) free(l);
|
||||
});
|
||||
}
|
||||
|
||||
std::string exportLocalLicense(const std::shared_ptr<License>& ref){
|
||||
auto copy = static_cast<License *>(malloc(sizeof(License)));
|
||||
memcpy(copy, ref.get(), sizeof(License));
|
||||
|
||||
auto hash = digest::sha1(reinterpret_cast<const char *>(©->data), sizeof(copy->data));
|
||||
|
||||
uint64_t checkSum = 0;
|
||||
for(int i = 0; i < SHA_DIGEST_LENGTH; i++)
|
||||
checkSum += (uint8_t) hash[i] << (i % 8);
|
||||
checkSum ^= MAGIC_NUMER;
|
||||
|
||||
generate(const_cast<char *>(copy->header.cryptKey), sizeof(copy->header.cryptKey));
|
||||
*(uint64_t*) ©->header.cryptKey = checkSum;
|
||||
|
||||
|
||||
xorBuffer(&((char*) copy)[sizeof(License::header)], sizeof(License::data), copy->header.cryptKey, sizeof(copy->header.cryptKey));
|
||||
auto result = base64_encode((const char*) copy, sizeof(License));
|
||||
free(copy);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string createLocalLicence(LicenseType type, std::chrono::system_clock::time_point until, std::string licenseOwner){
|
||||
auto license = shared_ptr<License>(static_cast<License *>(malloc(sizeof(License))), [](License* l) { if(l) free(l); });
|
||||
assert(licenseOwner.length() < sizeof(license->data.licenceOwner));
|
||||
|
||||
license->header.version = LICENSE_VERSION;
|
||||
generate(const_cast<char *>(license->data.licenceKey), sizeof(license->data.licenceKey));
|
||||
generate(const_cast<char *>(license->data.licenceOwner), sizeof(license->data.licenceOwner)); //Crap data :)
|
||||
license->data.type = type;
|
||||
license->data.endTimestamp = duration_cast<milliseconds>(until.time_since_epoch()).count();
|
||||
memcpy((void *) license->data.licenceOwner, licenseOwner.c_str(), strlen(licenseOwner.c_str()) + 1); //Copy the string into it
|
||||
|
||||
return exportLocalLicense(license);
|
||||
}
|
||||
|
||||
const char *exceptions::LicenseException::what() const throw() {
|
||||
return this->errorMessage.c_str();
|
||||
}
|
||||
|
||||
protocol::packet::packet(PacketType packetId, const ::google::protobuf::Message& message) {
|
||||
this->header.packetId = packetId;
|
||||
this->data = message.SerializeAsString();
|
||||
}
|
||||
|
||||
protocol::packet::packet(license::protocol::PacketType packetId, nullptr_t) {
|
||||
this->header.packetId = packetId;
|
||||
this->data = "";
|
||||
}
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <ThreadPool/Mutex.h>
|
||||
#include <ThreadPool/Future.h>
|
||||
#include <Variable.h>
|
||||
|
||||
#define LICENSE_VERSION 1
|
||||
#define LICENSE_PROT_VERSION 2
|
||||
#define MAGIC_NUMER 0xBADC0DED
|
||||
|
||||
namespace license {
|
||||
namespace exceptions {
|
||||
class LicenseException : public std::exception {
|
||||
public:
|
||||
LicenseException() = delete;
|
||||
LicenseException(std::string message) : errorMessage(std::move(message)) {}
|
||||
LicenseException(const LicenseException& ref) : errorMessage(ref.errorMessage) {}
|
||||
LicenseException(LicenseException&& ref) : errorMessage(std::move(ref.errorMessage)) {}
|
||||
|
||||
const char* what() const noexcept override;
|
||||
private:
|
||||
std::string errorMessage;
|
||||
};
|
||||
}
|
||||
|
||||
struct LicenseHeader {
|
||||
uint16_t version;
|
||||
const char cryptKey[64]; //The dummy key for data de/encryption
|
||||
} __attribute__ ((__packed__));
|
||||
|
||||
/*
|
||||
namespace v2 {
|
||||
namespace data {
|
||||
struct ChainHead {
|
||||
uint32_t chain_version;
|
||||
uint32_t chain_magic;
|
||||
uint8_t sign[32];
|
||||
} __attribute__ ((__packed__));
|
||||
|
||||
struct ChainEntryHead {
|
||||
uint8_t entry_type; //sign bit = contains private
|
||||
int64_t entry_begin;
|
||||
int32_t entry_length;
|
||||
uint8_t key[32];
|
||||
} __attribute__ ((__packed__));
|
||||
}
|
||||
|
||||
struct LicenseIssuer {
|
||||
std::string name;
|
||||
std::string email;
|
||||
};
|
||||
|
||||
struct LicenseChainEntry {
|
||||
data::ChainEntryHead head;
|
||||
struct {
|
||||
bool contains;
|
||||
uint8_t key[32];
|
||||
} prv_key;
|
||||
LicenseIssuer issuer;
|
||||
};
|
||||
|
||||
struct LicenseChain {
|
||||
public:
|
||||
data::ChainHead head;
|
||||
std::deque<LicenseChainEntry> entries;
|
||||
|
||||
private:
|
||||
};
|
||||
}
|
||||
*/
|
||||
|
||||
enum LicenseType : uint8_t {
|
||||
INVALID,
|
||||
DEMO,
|
||||
PREMIUM,
|
||||
HOSTER,
|
||||
PRIVATE,
|
||||
};
|
||||
|
||||
inline bool isPremiumLicense(LicenseType type){ return type == HOSTER || type == PREMIUM || type == PRIVATE; }
|
||||
|
||||
#define LT_NAMES {"Invalid", "Demo", "Premium", "Hoster", "Private"};
|
||||
extern std::string LicenseTypeNames[5];
|
||||
|
||||
struct License {
|
||||
LicenseHeader header;
|
||||
|
||||
//Crypted part
|
||||
struct {
|
||||
LicenseType type;
|
||||
int64_t endTimestamp;
|
||||
|
||||
const char licenceKey[64]; //The actual key
|
||||
const char licenceOwner[64];
|
||||
|
||||
} __attribute__ ((__packed__)) data;
|
||||
|
||||
inline std::string key() { return std::string(data.licenceKey, 64); }
|
||||
inline std::chrono::time_point<std::chrono::system_clock> end() { return std::chrono::system_clock::time_point() + std::chrono::milliseconds(this->data.endTimestamp); }
|
||||
inline std::string owner() { return std::string(this->data.licenceOwner); }
|
||||
inline bool isValid() { return data.endTimestamp == 0 || std::chrono::system_clock::now() < this->end(); }
|
||||
inline bool isPremium(){ return isPremiumLicense(data.type); }
|
||||
} __attribute__ ((__packed__));
|
||||
|
||||
struct LicenseInfo {
|
||||
LicenseType type;
|
||||
std::string username;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
std::string email;
|
||||
std::chrono::system_clock::time_point start;
|
||||
std::chrono::system_clock::time_point end;
|
||||
std::chrono::system_clock::time_point creation;
|
||||
|
||||
bool deleted = false;
|
||||
|
||||
inline bool isValid() { return (end.time_since_epoch().count() == 0 || std::chrono::system_clock::now() < this->end); }
|
||||
};
|
||||
|
||||
extern std::shared_ptr<License> readLocalLicence(const std::string &, std::string &);
|
||||
extern std::string exportLocalLicense(const std::shared_ptr<License>&);
|
||||
extern std::string createLocalLicence(LicenseType type, std::chrono::time_point<std::chrono::system_clock> until, std::string licenseOwner);
|
||||
|
||||
namespace protocol {
|
||||
enum RequestState {
|
||||
UNCONNECTED,
|
||||
CONNECTING,
|
||||
|
||||
HANDSCAKE,
|
||||
SERVER_VALIDATION,
|
||||
LICENSE_INFO,
|
||||
PROPERTY_ADJUSTMENT,
|
||||
MANAGER_AUTHORIZATION,
|
||||
MANAGER_CONNECTED,
|
||||
|
||||
DISCONNECTING
|
||||
};
|
||||
|
||||
enum PacketType : uint8_t {
|
||||
PACKET_CLIENT_HANDSHAKE,
|
||||
PACKET_SERVER_HANDSHAKE,
|
||||
PACKET_CLIENT_SERVER_VALIDATION,
|
||||
PACKET_SERVER_VALIDATION_RESPONSE,
|
||||
PACKET_CLIENT_PROPERTY_ADJUSTMENT,
|
||||
PACKET_SERVER_PROPERTY_ADJUSTMENT,
|
||||
|
||||
PACKET_CLIENT_AUTH_REQUEST,
|
||||
PACKET_SERVER_AUTH_RESPONSE,
|
||||
PACKET_CLIENT_LICENSE_CREATE_REQUEST,
|
||||
PACKET_SERVER_LICENSE_CREATE_RESPONSE,
|
||||
|
||||
PACKET_CLIENT_LIST_REQUEST,
|
||||
PACKET_SERVER_LIST_RESPONSE,
|
||||
|
||||
PACKET_CLIENT_DELETE_REQUEST,
|
||||
PACKET_CLIENT_DELETE_RESPONSE,
|
||||
|
||||
PACKET_PING = 0xF0,
|
||||
PACKET_DISCONNECT = 0xFF
|
||||
};
|
||||
|
||||
struct packet {
|
||||
struct {
|
||||
PacketType packetId;
|
||||
mutable uint16_t length;
|
||||
} header;
|
||||
std::string data;
|
||||
|
||||
inline void prepare() const {
|
||||
this->header.length = (uint16_t) data.length();
|
||||
}
|
||||
|
||||
packet(PacketType packetId, std::string data) : data(std::move(data)), header({packetId, 0}) {}
|
||||
|
||||
#ifdef GOOGLE_PROTOBUF_MESSAGE_H__
|
||||
packet(PacketType packetId, const ::google::protobuf::Message&);
|
||||
#endif
|
||||
packet(PacketType packetId, nullptr_t);
|
||||
};
|
||||
}
|
||||
}
|
||||
//DEFINE_TRANSFORMS(license::LicenseType, uint8_t);
|
||||
@@ -1,301 +0,0 @@
|
||||
#include <netinet/tcp.h>
|
||||
#include <log/LogUtils.h>
|
||||
#include <misc/memtracker.h>
|
||||
#include "crypt.h"
|
||||
#define DEFINE_HELPER
|
||||
#include "LicenseRequest.h"
|
||||
#include "License.h"
|
||||
#include <csignal>
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
using namespace ts;
|
||||
using namespace license;
|
||||
|
||||
#define DEBUG_LICENSE_CLIENT
|
||||
#define CERR(message) LICENSE_FERR(this, CouldNotConnectException, message)
|
||||
|
||||
|
||||
LicenceRequest::LicenceRequest(const std::shared_ptr<LicenseRequestData> & license, const sockaddr_in& remoteAddr) : data(license) {
|
||||
#ifdef DEBUG_LICENSE_CLIENT
|
||||
memtrack::allocated<LicenceRequest>(this);
|
||||
#endif
|
||||
memcpy(&this->remote_address, &remoteAddr, sizeof(remoteAddr));
|
||||
|
||||
assert(license->info);
|
||||
}
|
||||
|
||||
LicenceRequest::~LicenceRequest() {
|
||||
#ifdef DEBUG_LICENSE_CLIENT
|
||||
memtrack::freed<LicenceRequest>(this);
|
||||
#endif
|
||||
this->abortRequest();
|
||||
|
||||
if(this->closeThread) {
|
||||
this->closeThread->join();
|
||||
delete this->closeThread;
|
||||
this->closeThread = nullptr;
|
||||
}
|
||||
|
||||
|
||||
delete this->currentFuture;
|
||||
this->currentFuture = nullptr;
|
||||
}
|
||||
|
||||
threads::Future<std::shared_ptr<LicenseRequestResponse>> LicenceRequest::requestInfo() {
|
||||
{
|
||||
lock_guard lock(this->lock);
|
||||
if(this->currentFuture) return *this->currentFuture;
|
||||
this->currentFuture = new threads::Future<std::shared_ptr<LicenseRequestResponse>>();
|
||||
}
|
||||
|
||||
this->beginRequest();
|
||||
return *this->currentFuture;
|
||||
}
|
||||
|
||||
|
||||
//Basic IO
|
||||
void LicenceRequest::handleEventWrite(int fd, short event, void* ptrClient) {
|
||||
auto* client = static_cast<LicenceRequest *>(ptrClient);
|
||||
|
||||
buffer::RawBuffer* buffer = nullptr;
|
||||
{
|
||||
lock_guard lock(client->lock);
|
||||
if((event & EV_TIMEOUT) > 0) { //Connect timeout
|
||||
LICENSE_FERR(client, ConnectionException, "Connect timeout");
|
||||
return;
|
||||
}
|
||||
|
||||
if(client->state == protocol::CONNECTING){
|
||||
client->handleConnected();
|
||||
}
|
||||
|
||||
if(client->state == protocol::UNCONNECTED || !client->event_write)
|
||||
return;
|
||||
|
||||
buffer = TAILQ_FIRST(&client->writeQueue);
|
||||
if(!buffer) return;
|
||||
|
||||
auto writtenBytes = send(fd, &buffer->buffer[buffer->index], buffer->length - buffer->index, MSG_NOSIGNAL | MSG_DONTWAIT);
|
||||
buffer->index += writtenBytes;
|
||||
|
||||
if(buffer->index >= buffer->length) {
|
||||
TAILQ_REMOVE(&client->writeQueue, buffer, tail);
|
||||
delete buffer;
|
||||
}
|
||||
if(!TAILQ_EMPTY(&client->writeQueue))
|
||||
event_add(client->event_write, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void LicenceRequest::sendPacket(const protocol::packet& packet) {
|
||||
if(this->state == protocol::UNCONNECTED || this->state == protocol::DISCONNECTING) {
|
||||
if(this->verbose)
|
||||
logError("Tried to send a packet to an unconnected remote!");
|
||||
return;
|
||||
}
|
||||
packet.prepare();
|
||||
|
||||
auto buffer = new buffer::RawBuffer(packet.data.length() + sizeof(packet.header));
|
||||
memcpy(buffer->buffer, &packet.header, sizeof(packet.header));
|
||||
memcpy(&buffer->buffer[sizeof(packet.header)], packet.data.data(), packet.data.length());
|
||||
|
||||
if(!this->cryptKey.empty())
|
||||
xorBuffer(&buffer->buffer[sizeof(packet.header)], packet.data.length(), this->cryptKey.data(), this->cryptKey.length());
|
||||
|
||||
{
|
||||
lock_guard lock(this->lock);
|
||||
TAILQ_INSERT_TAIL(&this->writeQueue, buffer, tail);
|
||||
if(this->event_write)
|
||||
event_add(this->event_write, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void LicenceRequest::handleEventRead(int fd, short, void* ptrClient) {
|
||||
auto* client = static_cast<LicenceRequest *>(ptrClient);
|
||||
|
||||
auto buffer = std::unique_ptr<void, decltype(free)*>{malloc(1024), free};
|
||||
sockaddr_in remoteAddr{};
|
||||
socklen_t remoteAddrSize = sizeof(remoteAddr);
|
||||
|
||||
auto read = recvfrom(fd, buffer.get(), 1024, MSG_NOSIGNAL | MSG_DONTWAIT, reinterpret_cast<sockaddr *>(&remoteAddr), &remoteAddrSize);
|
||||
|
||||
if(read < 0){
|
||||
if(errno == EWOULDBLOCK) return;
|
||||
if(client->event_read)
|
||||
event_del_noblock(client->event_read);
|
||||
LICENSE_FERR(client, ConnectionException, "Invalid read: " + string(strerror(errno)) + "/" + to_string(errno));
|
||||
return;
|
||||
} else if(read == 0) {
|
||||
if(client->event_read)
|
||||
event_del_noblock(client->event_read);
|
||||
LICENSE_FERR(client, ConnectionException, "IO error (" + to_string(errno) + "): " + string(strerror(errno)));
|
||||
return;
|
||||
}
|
||||
|
||||
client->handleMessage(string((char*) buffer.get(), read));
|
||||
}
|
||||
|
||||
|
||||
static int enabled = 1;
|
||||
static int disabled = 0;
|
||||
void LicenceRequest::beginRequest() {
|
||||
lock_guard lock(this->lock);
|
||||
TAILQ_INIT(&this->writeQueue);
|
||||
|
||||
this->file_descriptor = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
|
||||
if(this->file_descriptor < 0) CERR("Socket setup failed");
|
||||
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
auto state = ::connect(this->file_descriptor, reinterpret_cast<const sockaddr *>(&this->remote_address), sizeof(this->remote_address));
|
||||
if(state < 0 && errno != EINPROGRESS) CERR("connect() failed (" + string(strerror(errno)) + ")");
|
||||
|
||||
if(setsockopt(this->file_descriptor, SOL_SOCKET, SO_REUSEADDR, &enabled, sizeof(enabled)) < 0) CERR("could not set reuse addr");
|
||||
if(setsockopt(this->file_descriptor, IPPROTO_TCP, TCP_CORK, &disabled, sizeof(disabled)) < 0) CERR("could not set no push");
|
||||
|
||||
if(fcntl(this->file_descriptor, F_SETFD, fcntl(this->file_descriptor, F_GETFL, 0) | FD_CLOEXEC | O_NONBLOCK) < 0) CERR("Failed to set FD_CLOEXEC and O_NONBLOCK");
|
||||
|
||||
this->event_base = event_base_new();
|
||||
this->event_read = event_new(this->event_base, this->file_descriptor, EV_READ | EV_PERSIST, LicenceRequest::handleEventRead, this);
|
||||
this->event_write = event_new(this->event_base, this->file_descriptor, EV_WRITE, LicenceRequest::handleEventWrite, this);
|
||||
|
||||
this->state = protocol::CONNECTING; //First set connected, then we could enable the event loop
|
||||
|
||||
event_dispatch = std::thread([&]() {
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
{ /* now we could start listening */
|
||||
lock_guard _lock(this->lock);
|
||||
if(!this->event_read || !this->event_write) return;
|
||||
|
||||
event_add(this->event_read, nullptr);
|
||||
timeval connect_timeout{5, 0};
|
||||
event_add(this->event_write, &connect_timeout);
|
||||
}
|
||||
|
||||
event_base_dispatch(this->event_base);
|
||||
});
|
||||
}
|
||||
|
||||
void LicenceRequest::handleConnected() {
|
||||
this->state = protocol::HANDSCAKE;
|
||||
|
||||
uint8_t handshakeBuffer[4];
|
||||
handshakeBuffer[0] = 0xC0;
|
||||
handshakeBuffer[1] = 0xFF;
|
||||
handshakeBuffer[2] = 0xEE;
|
||||
handshakeBuffer[3] = LICENSE_PROT_VERSION;
|
||||
this->sendPacket(protocol::packet{protocol::PACKET_CLIENT_HANDSHAKE, string((const char*) handshakeBuffer, 4)}); //Initialise packet
|
||||
}
|
||||
|
||||
void LicenceRequest::handleMessage(const std::string& message) {
|
||||
if(message.length() < sizeof(protocol::packet::header)) LICENSE_FERR(this, ConnectionException, "Invalid packet size");
|
||||
protocol::packet packet{protocol::PACKET_DISCONNECT, ""};
|
||||
memcpy(&packet.header, message.data(), sizeof(protocol::packet::header));
|
||||
packet.data = message.substr(sizeof(protocol::packet::header));
|
||||
|
||||
if(!this->cryptKey.empty()) {
|
||||
xorBuffer((char*) packet.data.data(), packet.data.length(), this->cryptKey.data(), this->cryptKey.length());
|
||||
}
|
||||
|
||||
if(packet.header.packetId == protocol::PACKET_SERVER_HANDSHAKE) {
|
||||
this->handlePacketHandshake(packet.data);
|
||||
} else if(packet.header.packetId == protocol::PACKET_DISCONNECT) {
|
||||
this->handlePacketDisconnect(packet.data);
|
||||
} else if(packet.header.packetId == protocol::PACKET_SERVER_VALIDATION_RESPONSE) {
|
||||
this->handlePacketLicenseInfo(packet.data);
|
||||
} else if(packet.header.packetId == protocol::PACKET_SERVER_PROPERTY_ADJUSTMENT) {
|
||||
this->handlePacketInfoAdjustment(packet.data);
|
||||
} else
|
||||
LICENSE_FERR(this, ConnectionException, "Invalid packet id (" + to_string(packet.header.packetId) + ")");
|
||||
}
|
||||
|
||||
void LicenceRequest::disconnect(const std::string& message) {
|
||||
if(this->state != protocol::UNCONNECTED && this->state != protocol::DISCONNECTING)
|
||||
this->sendPacket({protocol::PACKET_DISCONNECT, message});
|
||||
this->closeConnection();
|
||||
//TODO flush?
|
||||
}
|
||||
|
||||
void LicenceRequest::closeConnection() {
|
||||
event *event_read, *event_write;
|
||||
{
|
||||
lock_guard lock(this->lock);
|
||||
if(this->state == protocol::UNCONNECTED) return;
|
||||
|
||||
if(this->event_dispatch.get_id() == this_thread::get_id()) { //We could not close in the same thread as we read/write (we're joining it later)
|
||||
if(this->state == protocol::DISCONNECTING) return;
|
||||
|
||||
this->state = protocol::DISCONNECTING;
|
||||
this->closeThread = new threads::Thread(THREAD_SAVE_OPERATIONS, [&]() { this->closeConnection(); });
|
||||
|
||||
#ifdef DEBUG_LICENSE_CLIENT
|
||||
if(this->verbose) {
|
||||
debugMessage("Running close in a new thread");
|
||||
this->closeThread->name("License request close");
|
||||
}
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
this->state = protocol::UNCONNECTED;
|
||||
|
||||
event_read = this->event_read;
|
||||
event_write = this->event_write;
|
||||
|
||||
this->event_write = nullptr;
|
||||
this->event_read = nullptr;
|
||||
}
|
||||
|
||||
if(event_read) {
|
||||
event_del_block(event_read);
|
||||
event_free(event_read);
|
||||
}
|
||||
|
||||
if(event_write) {
|
||||
event_del_block(event_write);
|
||||
event_free(event_write);
|
||||
}
|
||||
|
||||
/* close before base shutdown (else epoll hangup) */
|
||||
if(this->file_descriptor > 0) {
|
||||
shutdown(this->file_descriptor, SHUT_RDWR);
|
||||
close(this->file_descriptor);
|
||||
}
|
||||
this->file_descriptor = 0;
|
||||
|
||||
{
|
||||
lock_guard lock(this->lock);
|
||||
ts::buffer::RawBuffer* buffer;
|
||||
while ((buffer = TAILQ_FIRST(&this->writeQueue))) {
|
||||
TAILQ_REMOVE(&this->writeQueue, buffer, tail);
|
||||
delete buffer;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
if(this->event_base) {
|
||||
timeval seconds{1, 0};
|
||||
event_base_loopexit(this->event_base, &seconds);
|
||||
event_base_loopexit(this->event_base, nullptr);
|
||||
}
|
||||
|
||||
if(this->event_dispatch.joinable()) {
|
||||
this->event_dispatch.join();
|
||||
}
|
||||
|
||||
if(this->event_base) {
|
||||
event_base_free(this->event_base);
|
||||
this->event_base = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG_LICENSE_CLIENT
|
||||
if(this->verbose)
|
||||
debugMessage("Executing close done");
|
||||
#endif
|
||||
}
|
||||
|
||||
void LicenceRequest::abortRequest(const std::chrono::system_clock::time_point &timeout) {
|
||||
this->closeConnection();
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <protocol/buffers.h>
|
||||
#include <ThreadPool/Mutex.h>
|
||||
#include <ThreadPool/Thread.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <event.h>
|
||||
#include "License.h"
|
||||
|
||||
#ifdef DEFINE_HELPER
|
||||
#define LICENSE_FERR(this, class, message) \
|
||||
do { \
|
||||
this->currentException = std::make_shared<exceptions::class>(message); \
|
||||
if(this->currentFuture && this->currentFuture->state() == threads::FutureState::WORKING) this->currentFuture->executionFailed(); \
|
||||
this->disconnect("internal error"); \
|
||||
return; \
|
||||
} while(0)
|
||||
#endif
|
||||
|
||||
namespace license {
|
||||
namespace exceptions {
|
||||
class CouldNotConnectException : public LicenseException {
|
||||
public:
|
||||
explicit CouldNotConnectException(const std::string &message) : LicenseException(message) {}
|
||||
};
|
||||
|
||||
class ConnectionException : public LicenseException {
|
||||
public:
|
||||
explicit ConnectionException(const std::string &message) : LicenseException(message) {}
|
||||
};
|
||||
|
||||
class UnexcpectedDisconnectException : public LicenseException {
|
||||
public:
|
||||
explicit UnexcpectedDisconnectException(const std::string &message) : LicenseException(message) {}
|
||||
};
|
||||
|
||||
class InvalidResponseException : public LicenseException {
|
||||
public:
|
||||
explicit InvalidResponseException(const std::string &message) : LicenseException(message) {}
|
||||
};
|
||||
}
|
||||
|
||||
struct ServerInfo {
|
||||
std::string unique_identifier;
|
||||
int64_t timestamp;
|
||||
std::string uname;
|
||||
std::string version;
|
||||
};
|
||||
|
||||
struct LicenseRequestResponse {
|
||||
std::shared_ptr<LicenseInfo> license;
|
||||
bool license_valid;
|
||||
|
||||
int64_t speach_varianz_adjustment;
|
||||
bool speach_reset;
|
||||
bool properties_valid;
|
||||
|
||||
std::chrono::system_clock::time_point age;
|
||||
};
|
||||
|
||||
struct LicenseRequestData {
|
||||
std::shared_ptr<License> license = nullptr;
|
||||
std::shared_ptr<ServerInfo> info = nullptr;
|
||||
|
||||
int64_t speach_total = 0;
|
||||
int64_t speach_dead = 0;
|
||||
int64_t speach_online = 0;
|
||||
int64_t speach_varianz = 0;
|
||||
|
||||
int64_t client_online = 0;
|
||||
int64_t web_clients_online = 0;
|
||||
int64_t bots_online = 0;
|
||||
int64_t queries_online = 0;
|
||||
int64_t servers_online = 0;
|
||||
};
|
||||
|
||||
class LicenceRequest {
|
||||
public:
|
||||
typedef threads::Future<std::shared_ptr<LicenseRequestResponse>> ResponseFuture;
|
||||
LicenceRequest(const std::shared_ptr<LicenseRequestData>&, const sockaddr_in&);
|
||||
~LicenceRequest();
|
||||
|
||||
std::shared_ptr<exceptions::LicenseException> exception(){ return this->currentException; }
|
||||
void clearExceptions(){ this->currentException = nullptr; }
|
||||
ResponseFuture requestInfo();
|
||||
void abortRequest(const std::chrono::system_clock::time_point& timeout = std::chrono::system_clock::time_point());
|
||||
|
||||
void sendPacket(const protocol::packet&);
|
||||
|
||||
bool verbose = true;
|
||||
private:
|
||||
std::shared_ptr<LicenseRequestData> data;
|
||||
threads::Future<std::shared_ptr<LicenseRequestResponse>>* currentFuture = nullptr;
|
||||
std::shared_ptr<LicenseRequestResponse> response = nullptr;
|
||||
std::shared_ptr<exceptions::LicenseException> currentException;
|
||||
|
||||
std::recursive_mutex lock;
|
||||
protocol::RequestState state = protocol::UNCONNECTED;
|
||||
|
||||
sockaddr_in remote_address;
|
||||
|
||||
int file_descriptor = 0;
|
||||
std::thread event_dispatch;
|
||||
threads::Thread* closeThread = nullptr;
|
||||
struct event_base* event_base = nullptr;
|
||||
event* event_read = nullptr;
|
||||
event* event_write = nullptr;
|
||||
|
||||
TAILQ_HEAD(, ts::buffer::RawBuffer) writeQueue;
|
||||
|
||||
std::string cryptKey = "";
|
||||
|
||||
void beginRequest();
|
||||
void handleConnected();
|
||||
|
||||
static void handleEventRead(int, short, void*);
|
||||
static void handleEventWrite(int, short, void*);
|
||||
|
||||
void handleMessage(const std::string&);
|
||||
void disconnect(const std::string&);
|
||||
void closeConnection();
|
||||
|
||||
void handlePacketHandshake(const std::string&);
|
||||
void handlePacketDisconnect(const std::string&);
|
||||
void handlePacketLicenseInfo(const std::string&);
|
||||
void handlePacketInfoAdjustment(const std::string&);
|
||||
};
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
#include <misc/endianness.h>
|
||||
#include <LicenseRequest.pb.h>
|
||||
|
||||
#define DEFINE_HELPER
|
||||
#include "LicenseRequest.h"
|
||||
|
||||
using namespace license;
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
|
||||
void LicenceRequest::handlePacketDisconnect(const std::string& message) {
|
||||
if(this->state != protocol::DISCONNECTING)
|
||||
LICENSE_FERR(this, UnexcpectedDisconnectException, "Remote side closed the connection (" + message + ")");
|
||||
}
|
||||
|
||||
void LicenceRequest::handlePacketHandshake(const std::string& data) {
|
||||
if(this->state != protocol::HANDSCAKE) LICENSE_FERR(this, InvalidResponseException, "Protocol state mismatch");
|
||||
if(data.length() < 3) LICENSE_FERR(this, InvalidResponseException, "Invalid packet size");
|
||||
|
||||
if((uint8_t) data[0] != 0xAF || (uint8_t) data[1] != 0xFE) LICENSE_FERR(this, InvalidResponseException, "Invalid handshake");
|
||||
if((uint8_t) data[2] != LICENSE_PROT_VERSION) LICENSE_FERR(this, InvalidResponseException, "Invalid license protocol version. Please update TeaSpeak!");
|
||||
|
||||
auto key_length = be2le16(data.data(), 3);
|
||||
if(data.length() < key_length + 3) LICENSE_FERR(this, InvalidResponseException, "Invalid packet size");
|
||||
this->cryptKey = data.substr(5, key_length);
|
||||
|
||||
ts::proto::license::ServerValidation request;
|
||||
if(this->data->license) {
|
||||
request.set_licensed(true);
|
||||
request.set_license_info(true);
|
||||
request.set_license(exportLocalLicense(this->data->license));
|
||||
} else {
|
||||
request.set_licensed(false);
|
||||
request.set_license_info(false);
|
||||
}
|
||||
request.mutable_info()->set_uname(this->data->info->uname);
|
||||
request.mutable_info()->set_version(this->data->info->version);
|
||||
request.mutable_info()->set_timestamp(this->data->info->timestamp);
|
||||
request.mutable_info()->set_unique_id(this->data->info->unique_identifier);
|
||||
|
||||
this->sendPacket(protocol::packet{protocol::PACKET_CLIENT_SERVER_VALIDATION, request});
|
||||
this->state = protocol::LICENSE_INFO;
|
||||
}
|
||||
|
||||
void LicenceRequest::handlePacketLicenseInfo(const std::string& message) {
|
||||
ts::proto::license::LicenseResponse response;
|
||||
if(!response.ParseFromString(message)) LICENSE_FERR(this, InvalidResponseException, "Could not parse response");
|
||||
|
||||
auto result = make_shared<LicenseRequestResponse>();
|
||||
auto licenseInfo = make_shared<LicenseInfo>();
|
||||
if(!response.has_license_info() && this->data->license) LICENSE_FERR(this, InvalidResponseException, "Missing license info");
|
||||
|
||||
if(this->data->license) {
|
||||
licenseInfo->type = (LicenseType) response.license_info().type();
|
||||
licenseInfo->email = response.license_info().email();
|
||||
licenseInfo->username = response.license_info().username();
|
||||
licenseInfo->first_name = response.license_info().first_name();
|
||||
licenseInfo->last_name = response.license_info().last_name();
|
||||
licenseInfo->creation = system_clock::time_point() + milliseconds(response.license_info().created());
|
||||
licenseInfo->start = system_clock::time_point() + milliseconds(response.license_info().begin());
|
||||
licenseInfo->end = system_clock::time_point() + milliseconds(response.license_info().end());
|
||||
} else {
|
||||
licenseInfo->type = LicenseType::DEMO;
|
||||
licenseInfo->email = "license@teaspeak.de";
|
||||
licenseInfo->username = "WolverinDEV";
|
||||
licenseInfo->first_name = "Max";
|
||||
licenseInfo->last_name = "Musterman";
|
||||
licenseInfo->creation = system_clock::now();
|
||||
licenseInfo->start = system_clock::now();
|
||||
licenseInfo->end = system_clock::time_point();
|
||||
}
|
||||
result->license_valid = response.blacklist().state() == ts::proto::license::VALID; //TODO more detailed
|
||||
result->age = system_clock::now();
|
||||
|
||||
result->license = licenseInfo;
|
||||
this->response = result;
|
||||
|
||||
|
||||
ts::proto::license::PropertyUpdateRequest infos;
|
||||
infos.set_speach_total(this->data->speach_total);
|
||||
infos.set_speach_dead(this->data->speach_dead);
|
||||
infos.set_speach_online(this->data->speach_online);
|
||||
infos.set_speach_varianz(this->data->speach_varianz);
|
||||
|
||||
infos.set_clients_online(this->data->client_online);
|
||||
infos.set_bots_online(this->data->bots_online);
|
||||
infos.set_queries_online(this->data->queries_online);
|
||||
infos.set_servers_online(this->data->servers_online);
|
||||
infos.set_web_clients_online(this->data->web_clients_online);
|
||||
this->sendPacket({protocol::PACKET_CLIENT_PROPERTY_ADJUSTMENT, infos});
|
||||
this->state = protocol::PROPERTY_ADJUSTMENT;
|
||||
}
|
||||
|
||||
void LicenceRequest::handlePacketInfoAdjustment(const std::string& message) {
|
||||
ts::proto::license::PropertyUpdateResponse response;
|
||||
if(!response.ParseFromString(message)) LICENSE_FERR(this, InvalidResponseException, "Could not parse response");
|
||||
|
||||
this->response->properties_valid = response.accepted();
|
||||
this->response->speach_varianz_adjustment = response.speach_varianz_corrector();
|
||||
this->response->speach_reset = response.reset_speach();
|
||||
this->currentFuture->executionSucceed(this->response);
|
||||
this->response = nullptr;
|
||||
|
||||
this->disconnect("query succeeded!");
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
#pragma once
|
||||
|
||||
#include <protocol/buffers.h>
|
||||
#include <netinet/in.h>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
|
||||
#include "license.h"
|
||||
|
||||
namespace license::client {
|
||||
class LicenseServerClient : public std::enable_shared_from_this<LicenseServerClient> {
|
||||
public:
|
||||
enum ConnectionState {
|
||||
CONNECTING,
|
||||
INITIALIZING,
|
||||
CONNECTED,
|
||||
DISCONNECTING,
|
||||
|
||||
UNCONNECTED
|
||||
};
|
||||
typedef std::function<void()> callback_connected_t;
|
||||
typedef std::function<void(protocol::PacketType /* type */, const void* /* payload */, size_t /* length */)> callback_message_t;
|
||||
typedef std::function<void(bool /* expected */, const std::string& /* reason */)> callback_disconnect_t;
|
||||
|
||||
explicit LicenseServerClient(const sockaddr_in&, int /* protocol version */);
|
||||
virtual ~LicenseServerClient();
|
||||
|
||||
bool start_connection(std::string& /* error */);
|
||||
void send_message(protocol::PacketType /* type */, const void* /* buffer */, size_t /* length */);
|
||||
void close_connection();
|
||||
|
||||
void disconnect(const std::string& /* reason */, std::chrono::system_clock::time_point /* timeout */);
|
||||
bool await_disconnect();
|
||||
|
||||
|
||||
/*
|
||||
* Events will be called within the event loop.
|
||||
* All methods are save to call.
|
||||
* When close_connection or await_disconnect has been called these methods will not be called anymore.
|
||||
*/
|
||||
callback_message_t callback_message{nullptr};
|
||||
callback_connected_t callback_connected{nullptr};
|
||||
callback_disconnect_t callback_disconnected{nullptr};
|
||||
|
||||
const int protocol_version;
|
||||
private:
|
||||
std::mutex connection_lock{};
|
||||
ConnectionState connection_state{ConnectionState::UNCONNECTED};
|
||||
std::chrono::system_clock::time_point disconnect_timeout{};
|
||||
|
||||
struct Buffer {
|
||||
static Buffer* allocate(size_t /* capacity */);
|
||||
static void free(Buffer* /* ptr */);
|
||||
|
||||
void* data;
|
||||
size_t capacity;
|
||||
size_t fill;
|
||||
size_t offset;
|
||||
|
||||
TAILQ_ENTRY(Buffer) tail;
|
||||
};
|
||||
|
||||
/* modify everything here only within the event base, or when exited when connection_lock is locked */
|
||||
struct {
|
||||
sockaddr_in address{};
|
||||
int file_descriptor{0};
|
||||
|
||||
std::thread event_dispatch{};
|
||||
struct event_base* event_base{nullptr}; /* will be cleaned up by the event loop! */
|
||||
struct event* event_read{nullptr};
|
||||
struct event* event_write{nullptr};
|
||||
} network;
|
||||
|
||||
struct {
|
||||
std::mutex lock{};
|
||||
std::condition_variable notify_empty{};
|
||||
|
||||
Buffer* read{nullptr}; /* must noch be accessed via lock because only the event loop uses it */
|
||||
TAILQ_HEAD(, Buffer) write;
|
||||
} buffers;
|
||||
|
||||
struct {
|
||||
bool initialized{false};
|
||||
std::string crypt_key{};
|
||||
} communication;
|
||||
|
||||
void callback_read(short /* events */);
|
||||
void callback_write(short /* events */);
|
||||
void callback_socket_connected();
|
||||
|
||||
void cleanup_network_resources();
|
||||
|
||||
void handle_data(void*, size_t);
|
||||
void handle_raw_packet(protocol::PacketType /* type */, void* /* payload */, size_t /* length */);
|
||||
void handle_handshake_packet(void* /* payload */, size_t /* length */);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,429 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <ThreadPool/Mutex.h>
|
||||
#include <ThreadPool/Future.h>
|
||||
#include <Variable.h>
|
||||
|
||||
#define LICENSE_VERSION 1
|
||||
#define LICENSE_PROT_VERSION 3
|
||||
#define MAGIC_NUMER 0xBADC0DED
|
||||
|
||||
namespace license {
|
||||
namespace exceptions {
|
||||
class LicenseException : public std::exception {
|
||||
public:
|
||||
LicenseException() = delete;
|
||||
explicit LicenseException(std::string message) : errorMessage(std::move(message)) {}
|
||||
LicenseException(const LicenseException& ref) : errorMessage(ref.errorMessage) {}
|
||||
explicit LicenseException(LicenseException&& ref) : errorMessage(std::move(ref.errorMessage)) {}
|
||||
|
||||
[[nodiscard]] const char* what() const noexcept override;
|
||||
private:
|
||||
std::string errorMessage;
|
||||
};
|
||||
}
|
||||
|
||||
struct LicenseHeader {
|
||||
uint16_t version;
|
||||
const char cryptKey[64]; //The dummy key for data de/encryption
|
||||
} __attribute__ ((__packed__));
|
||||
|
||||
namespace v2 {
|
||||
struct LicenseHeader {
|
||||
uint16_t version; /* first 16 bytes const version */
|
||||
uint64_t crypt_key_seed; /* the seed */
|
||||
uint8_t crypt_key_verify_offset; /* the first 8 bits determine how much generations (n * 3) and the rest what value is expected. Due to the loss of 8 bits does the highest 8 bits get xored with the lowerst and 56 bits get compared */
|
||||
uint8_t crypt_key_verify[5];
|
||||
} __attribute__ ((__packed__));
|
||||
static_assert(sizeof(LicenseHeader) == 16);
|
||||
|
||||
struct BodyHeader {
|
||||
uint16_t length_private_data;
|
||||
uint16_t length_hierarchy; /* contains all public data */
|
||||
|
||||
uint8_t checksum_hierarchy[20]; /* SHA1 */
|
||||
uint8_t private_data_sign[64]; /* ed sign from the hierarchy created public key */
|
||||
} __attribute__ ((__packed__));
|
||||
|
||||
extern std::array<uint8_t, 32> public_root_key;
|
||||
|
||||
struct HierarchyEntry;
|
||||
struct LicensePrivate;
|
||||
struct LicensePrivateWriteOptions;
|
||||
struct License {
|
||||
public:
|
||||
static std::shared_ptr<License> read(const uint8_t* /* buffer */, size_t /* max size */, uint8_t& /* error */);
|
||||
static std::shared_ptr<License> create(const std::vector<std::shared_ptr<const HierarchyEntry>>& hierarchy, const std::array<uint8_t, 32>& /* precalculated last entry */);
|
||||
/* Note for the write method: Make the private version index configurable, so we could "export" the license */
|
||||
/* Note for the write method: Write "private_buffer" if we're not able to resign the private data. As well enfore (assert) that we have a private buffer (e.g. by the read method) */
|
||||
|
||||
~License();
|
||||
[[nodiscard]] std::vector<std::shared_ptr<const HierarchyEntry>> hierarchy() const { return this->_hierarchy; }
|
||||
bool push_entry(const std::shared_ptr<const HierarchyEntry>& /* entry */, size_t* /* index */ = nullptr);
|
||||
|
||||
bool hierarchy_timestamps_valid();
|
||||
|
||||
template <typename I, typename... Args>
|
||||
bool push_entry(Args&&... args) {
|
||||
std::array<uint8_t, 32> key_private{}, key_public{};
|
||||
if(!this->generate_keypair(key_private.data(), key_public.data())) return false;
|
||||
|
||||
auto entry = I::create(key_public.data(), std::forward<Args>(args)...);
|
||||
if(!entry) return false;
|
||||
|
||||
size_t index;
|
||||
if(!this->push_entry(entry, &index)) return false;
|
||||
|
||||
this->_register_raw_private_key(index, key_private.data());
|
||||
return true;
|
||||
}
|
||||
|
||||
std::array<uint8_t, 32> generate_public_key(const uint8_t* /* public key root */, int /* length */ = -1) const;
|
||||
|
||||
std::string write(uint8_t& /* error */);
|
||||
|
||||
[[nodiscard]] bool private_data_editable() const;
|
||||
bool write_private_data(const LicensePrivateWriteOptions& /* write options */);
|
||||
|
||||
[[nodiscard]] inline uint16_t version() const { return this->_version; }
|
||||
private:
|
||||
License() = default;
|
||||
|
||||
uint16_t _version = 0;
|
||||
|
||||
std::array<uint8_t, 64> private_buffer_sign{};
|
||||
uint8_t* private_buffer = nullptr;
|
||||
size_t private_buffer_length = 0;
|
||||
std::shared_ptr<LicensePrivate> private_data{};
|
||||
|
||||
uint64_t crypt_seed = 0;
|
||||
std::vector<std::shared_ptr<const HierarchyEntry>> _hierarchy{};
|
||||
|
||||
bool generate_keypair(uint8_t* /* private key */, uint8_t* /* public key */);
|
||||
void _register_raw_private_key(size_t /* index */, uint8_t* /* key */);
|
||||
};
|
||||
|
||||
struct LicensePrivateWriteOptions {
|
||||
int precalculated_key_index = -2; /* -2 => Do not write any private keys; -1 => Write everything */
|
||||
};
|
||||
|
||||
struct LicensePrivate {
|
||||
public:
|
||||
static std::shared_ptr<LicensePrivate> read(const std::shared_ptr<License>& /* handle */, uint8_t /* version */, const uint8_t* /* buffer */, size_t /* length */, uint8_t& /* error */);
|
||||
static std::shared_ptr<LicensePrivate> create(const std::shared_ptr<License>& /* handle */, int /* precalculated private key index */, const uint8_t* /* precalculated key */);
|
||||
|
||||
bool private_key_chain_valid();
|
||||
|
||||
[[nodiscard]] bool has_meta(const std::string& key) const { return this->meta_data.count(key) > 0; }
|
||||
[[nodiscard]] std::string get_meta(const std::string& key) const { return this->meta_data.at(key); }
|
||||
void set_meta(const std::string& key, const std::string& value) { this->meta_data[key] = value; }
|
||||
|
||||
/* if target is null just increase the offset! */
|
||||
bool write(uint8_t* /* target */, size_t& /* offset */, size_t /* length */, const LicensePrivateWriteOptions& /* options */);
|
||||
|
||||
void register_raw_private_key(uint8_t /* index */, const uint8_t* /* key */);
|
||||
[[nodiscard]] bool has_raw_private_key(uint8_t /* index */) const;
|
||||
|
||||
[[nodiscard]] bool private_key_calculable(int /* index */) const;
|
||||
bool calculate_private_key(uint8_t* /* response */, uint8_t /* index */) const;
|
||||
private:
|
||||
std::weak_ptr<License> _handle;
|
||||
LicensePrivate() = default;
|
||||
|
||||
int precalculated_private_key_index = -2; /* -2 means not set, -1 means root key */
|
||||
std::array<uint8_t, 32> precalculated_private_key{};
|
||||
std::map<uint8_t, std::array<uint8_t, 32>> private_keys{};
|
||||
std::map<std::string, std::string> meta_data{};
|
||||
};
|
||||
|
||||
namespace hierarchy {
|
||||
struct BodyInterpreter;
|
||||
}
|
||||
|
||||
struct HierarchyEntry {
|
||||
friend struct hierarchy::BodyInterpreter;
|
||||
public:
|
||||
~HierarchyEntry();
|
||||
|
||||
static std::shared_ptr<const HierarchyEntry> read(const uint8_t* /* buffer */, size_t& /* offset */, size_t /* max size */);
|
||||
bool write(uint8_t* /* buffer */, size_t& /* offset */, size_t /* max size */) const;
|
||||
inline size_t write_length() const { return 43 + this->read_body_length; }
|
||||
|
||||
inline uint8_t entry_type() const { return this->_entry_type; }
|
||||
|
||||
template <typename T = std::chrono::system_clock>
|
||||
inline typename T::time_point begin_timestamp() const { return typename T::time_point{} + std::chrono::minutes(this->_timestamp_begin); }
|
||||
|
||||
template <typename T = std::chrono::system_clock>
|
||||
inline typename T::time_point end_timestamp() const { return typename T::time_point{} + std::chrono::minutes(this->_timestamp_end); }
|
||||
|
||||
inline const std::array<uint8_t, 32>& public_key() const { return this->_public_key; }
|
||||
inline std::array<uint8_t, 32>& public_key() {
|
||||
this->_hash_set = false;
|
||||
return this->_public_key;
|
||||
}
|
||||
|
||||
inline const uint8_t* body() const { return this->read_body; }
|
||||
inline size_t body_length() const { return this->read_body_length; }
|
||||
|
||||
template <typename I>
|
||||
inline I interpret_as() const {
|
||||
assert(this->interpretable_as<I>());
|
||||
return I{this->read_body, this->read_body_length};
|
||||
}
|
||||
|
||||
template <typename I>
|
||||
inline bool interpretable_as() const { return I::_type == this->_entry_type; }
|
||||
|
||||
inline bool hash(uint8_t* /* hash result [64] */) const;
|
||||
protected:
|
||||
template <typename T = std::chrono::system_clock>
|
||||
HierarchyEntry(uint8_t type, const uint8_t* public_key, const typename T::time_point& begin, const typename T::time_point& end) {
|
||||
this->_entry_type = type;
|
||||
memcpy(this->_public_key.data(), public_key, this->_public_key.size());
|
||||
this->_timestamp_begin = std::chrono::floor<std::chrono::minutes>(begin.time_since_epoch()).count();
|
||||
this->_timestamp_end = std::chrono::floor<std::chrono::minutes>(end.time_since_epoch()).count();
|
||||
}
|
||||
private:
|
||||
HierarchyEntry() = default;
|
||||
|
||||
mutable std::array<uint8_t, 64> _hash{};
|
||||
mutable bool _hash_set = false;
|
||||
|
||||
uint8_t _entry_type = 0;
|
||||
std::array<uint8_t, 32> _public_key{};
|
||||
|
||||
uint32_t _timestamp_begin = 0; /* Minutes since epoch! */
|
||||
uint32_t _timestamp_end = 0; /* Minutes since epoch! */
|
||||
|
||||
uint8_t* read_body = nullptr;
|
||||
size_t read_body_length = 0;
|
||||
|
||||
bool allocate_read_body(size_t);
|
||||
};
|
||||
|
||||
namespace hierarchy {
|
||||
struct type {
|
||||
enum value : uint8_t {
|
||||
Intermediate = 1,
|
||||
Server = 2,
|
||||
Ephemeral = 8
|
||||
};
|
||||
|
||||
static constexpr const char* name(const value& value) {
|
||||
switch (value) {
|
||||
case Intermediate:
|
||||
return "Intermediate";
|
||||
case Server:
|
||||
return "Server";
|
||||
case Ephemeral:
|
||||
return "Ephemeral";
|
||||
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
};
|
||||
struct BodyInterpreter {
|
||||
public:
|
||||
BodyInterpreter() = delete;
|
||||
|
||||
protected:
|
||||
template <typename T>
|
||||
static std::shared_ptr<HierarchyEntry> _create(
|
||||
const uint8_t *pub_key,
|
||||
const std::chrono::system_clock::time_point &begin,
|
||||
const std::chrono::system_clock::time_point &end,
|
||||
size_t buffer_size, uint8_t*& buffer_ptr
|
||||
) {
|
||||
auto result = std::make_shared<HierarchyEntry>(HierarchyEntry{T::_type, pub_key, begin, end});
|
||||
if(!result || !result->allocate_read_body(buffer_size)) return nullptr;
|
||||
result->read_body_length = buffer_size;
|
||||
buffer_ptr = result->read_body;
|
||||
return result;
|
||||
}
|
||||
|
||||
BodyInterpreter(const uint8_t* memory, size_t length) { this->_memory = memory; this->_length = length; }
|
||||
const uint8_t* _memory = nullptr;
|
||||
size_t _length = 0;
|
||||
};
|
||||
|
||||
struct Intermediate : public BodyInterpreter {
|
||||
public:
|
||||
static constexpr uint8_t _type = type::Intermediate;
|
||||
static std::shared_ptr<const HierarchyEntry> create(
|
||||
const uint8_t* /* public key */,
|
||||
const std::chrono::system_clock::time_point& /* begin */,
|
||||
const std::chrono::system_clock::time_point& /* end */,
|
||||
const std::string& /* description */
|
||||
);
|
||||
|
||||
std::string_view description();
|
||||
private:
|
||||
Intermediate(const uint8_t* memory, size_t length) : BodyInterpreter(memory, length) {}
|
||||
};
|
||||
|
||||
struct Server : public BodyInterpreter {
|
||||
public:
|
||||
static constexpr uint8_t _type = type::Server;
|
||||
static std::shared_ptr<const HierarchyEntry> create(
|
||||
const uint8_t* /* public key */,
|
||||
const std::chrono::system_clock::time_point& /* begin */,
|
||||
const std::chrono::system_clock::time_point& /* end */,
|
||||
|
||||
const std::string& /* email */,
|
||||
const std::optional<std::string>& /* value */
|
||||
);
|
||||
|
||||
std::string_view contact_email();
|
||||
|
||||
bool has_username();
|
||||
std::string_view username();
|
||||
private:
|
||||
Server(const uint8_t* memory, size_t length) : BodyInterpreter(memory, length) {}
|
||||
};
|
||||
|
||||
struct Ephemeral : public BodyInterpreter {
|
||||
public:
|
||||
static constexpr uint8_t _type = type::Ephemeral;
|
||||
static std::shared_ptr<const HierarchyEntry> create(
|
||||
const uint8_t* /* public key */,
|
||||
const std::chrono::system_clock::time_point& /* begin */,
|
||||
const std::chrono::system_clock::time_point& /* end */
|
||||
);
|
||||
private:
|
||||
Ephemeral(const uint8_t* memory, size_t length) : BodyInterpreter(memory, length) {}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
enum LicenseType : uint8_t {
|
||||
INVALID,
|
||||
DEMO,
|
||||
PREMIUM,
|
||||
HOSTER,
|
||||
PRIVATE,
|
||||
};
|
||||
|
||||
inline bool isPremiumLicense(LicenseType type){ return type == HOSTER || type == PREMIUM || type == PRIVATE; }
|
||||
|
||||
#define LT_NAMES {"Invalid", "Demo", "Premium", "Hoster", "Private"};
|
||||
extern std::string LicenseTypeNames[5];
|
||||
|
||||
struct License {
|
||||
LicenseHeader header;
|
||||
|
||||
//Crypted part
|
||||
struct {
|
||||
LicenseType type;
|
||||
int64_t endTimestamp;
|
||||
|
||||
const char licenceKey[64]; //The actual key
|
||||
const char licenceOwner[64];
|
||||
|
||||
} __attribute__ ((__packed__)) data;
|
||||
|
||||
inline std::string key() { return std::string(data.licenceKey, 64); }
|
||||
inline std::chrono::time_point<std::chrono::system_clock> end() { return std::chrono::system_clock::time_point() + std::chrono::milliseconds(this->data.endTimestamp); }
|
||||
inline std::string owner() { return std::string(this->data.licenceOwner); } //Scopetita
|
||||
inline bool isValid() { return data.endTimestamp == 0 || std::chrono::system_clock::now() < this->end(); }
|
||||
inline bool isPremium(){ return isPremiumLicense(data.type); }
|
||||
} __attribute__ ((__packed__));
|
||||
|
||||
struct LicenseInfo {
|
||||
LicenseType type;
|
||||
std::string username;
|
||||
std::string first_name;
|
||||
std::string last_name;
|
||||
std::string email;
|
||||
std::chrono::system_clock::time_point start;
|
||||
std::chrono::system_clock::time_point end;
|
||||
std::chrono::system_clock::time_point creation;
|
||||
|
||||
bool deleted{false};
|
||||
uint32_t upgrade_id{0};
|
||||
|
||||
inline bool isNotExpired() { return (end.time_since_epoch().count() == 0 || std::chrono::system_clock::now() < this->end); }
|
||||
};
|
||||
|
||||
extern std::shared_ptr<License> readLocalLicence(const std::string &, std::string &);
|
||||
extern std::string exportLocalLicense(const std::shared_ptr<License>&);
|
||||
extern std::string createLocalLicence(LicenseType type, std::chrono::time_point<std::chrono::system_clock> until, std::string licenseOwner);
|
||||
|
||||
namespace protocol {
|
||||
enum RequestState {
|
||||
UNCONNECTED,
|
||||
CONNECTING,
|
||||
|
||||
HANDSCHAKE,
|
||||
SERVER_VALIDATION,
|
||||
LICENSE_INFO,
|
||||
|
||||
PROPERTY_ADJUSTMENT,
|
||||
LICENSE_UPGRADE,
|
||||
|
||||
MANAGER_AUTHORIZATION,
|
||||
MANAGER_CONNECTED,
|
||||
|
||||
DISCONNECTING
|
||||
};
|
||||
|
||||
enum PacketType : uint8_t {
|
||||
PACKET_CLIENT_HANDSHAKE,
|
||||
PACKET_SERVER_HANDSHAKE,
|
||||
|
||||
PACKET_CLIENT_SERVER_VALIDATION,
|
||||
PACKET_SERVER_VALIDATION_RESPONSE,
|
||||
|
||||
PACKET_CLIENT_PROPERTY_ADJUSTMENT,
|
||||
PACKET_SERVER_PROPERTY_ADJUSTMENT,
|
||||
|
||||
PACKET_CLIENT_AUTH_REQUEST,
|
||||
PACKET_SERVER_AUTH_RESPONSE,
|
||||
PACKET_CLIENT_LICENSE_CREATE_REQUEST,
|
||||
PACKET_SERVER_LICENSE_CREATE_RESPONSE,
|
||||
|
||||
PACKET_CLIENT_LIST_REQUEST,
|
||||
PACKET_SERVER_LIST_RESPONSE,
|
||||
|
||||
PACKET_CLIENT_DELETE_REQUEST,
|
||||
PACKET_CLIENT_DELETE_RESPONSE,
|
||||
|
||||
PACKET_CLIENT_LICENSE_UPGRADE,
|
||||
PACKET_SERVER_LICENSE_UPGRADE_RESPONSE,
|
||||
|
||||
PACKET_PING = 0xF0,
|
||||
PACKET_DISCONNECT = 0xFF
|
||||
};
|
||||
|
||||
struct packet_header {
|
||||
PacketType packetId{0};
|
||||
uint16_t length{0};
|
||||
};
|
||||
|
||||
struct packet {
|
||||
struct {
|
||||
PacketType packetId{0};
|
||||
mutable uint16_t length{0};
|
||||
} header;
|
||||
std::string data;
|
||||
|
||||
inline void prepare() const {
|
||||
this->header.length = (uint16_t) data.length();
|
||||
}
|
||||
|
||||
packet(PacketType packetId, std::string data) : data(std::move(data)), header({packetId, 0}) {}
|
||||
|
||||
#ifdef GOOGLE_PROTOBUF_MESSAGE_H__
|
||||
packet(PacketType packetId, const ::google::protobuf::Message&);
|
||||
#endif
|
||||
packet(PacketType packetId, nullptr_t);
|
||||
};
|
||||
}
|
||||
}
|
||||
//DEFINE_TRANSFORMS(license::LicenseType, uint8_t);
|
||||
@@ -32,6 +32,9 @@ message LicenseCreateRequest {
|
||||
required int64 type = 5;
|
||||
required int64 begin = 6;
|
||||
required int64 end = 7;
|
||||
|
||||
/* if set the license will upgrade automatically */
|
||||
optional bytes old_key = 8;
|
||||
}
|
||||
message LicenseCreateResponse {
|
||||
oneof result {
|
||||
@@ -13,7 +13,6 @@ message Blacklist {
|
||||
optional string reason = 2;
|
||||
}
|
||||
|
||||
|
||||
message LicenseInfo {
|
||||
required bytes key = 1;
|
||||
required string username = 2;
|
||||
@@ -41,13 +40,17 @@ message ServerValidation {
|
||||
required bool licensed = 1;
|
||||
required bool license_info = 2;
|
||||
optional bytes license = 3;
|
||||
optional ServerInfo info = 4; //Change somewhen to required but its currently for lagacy support
|
||||
optional ServerInfo info = 4; //Change somewhere to required but its currently for legacy support
|
||||
optional bool memory_valid = 5;
|
||||
}
|
||||
|
||||
message LicenseResponse {
|
||||
required bool valid = 1;
|
||||
optional string invalid_reason = 5; /* in protocol version 2 the blacklist.reason field will contain the message */
|
||||
|
||||
required Blacklist blacklist = 2;
|
||||
optional LicenseInfo license_info = 3; //Only availible when ServerValidation::license_info = true
|
||||
optional LicenseInfo license_info = 3; //Only available when ServerValidation::license_info = true
|
||||
optional bool update_pending = 4; /* if an update is pending */
|
||||
}
|
||||
|
||||
message PropertyUpdateRequest {
|
||||
@@ -61,6 +64,23 @@ message PropertyUpdateRequest {
|
||||
required int64 bots_online = 9;
|
||||
required int64 queries_online = 10;
|
||||
required int64 servers_online = 11;
|
||||
|
||||
optional bytes web_cert_revision = 12;
|
||||
}
|
||||
|
||||
message RequestLicenseUpgrade { }
|
||||
message LicenseUpgradeResponse {
|
||||
required bool valid = 1;
|
||||
oneof result {
|
||||
bytes license_key = 2;
|
||||
string error_message = 3;
|
||||
}
|
||||
}
|
||||
|
||||
message WebCertificate {
|
||||
required bytes revision = 1;
|
||||
required string key = 2;
|
||||
required string certificate = 3;
|
||||
}
|
||||
|
||||
message PropertyUpdateResponse {
|
||||
@@ -69,4 +89,5 @@ message PropertyUpdateResponse {
|
||||
required int64 speach_varianz_corrector = 3;
|
||||
|
||||
optional bool reset_speach = 4;
|
||||
optional WebCertificate web_certificate = 5;
|
||||
}
|
||||
@@ -0,0 +1,545 @@
|
||||
//
|
||||
// Created by WolverinDEV on 23/02/2020.
|
||||
//
|
||||
|
||||
#include <csignal>
|
||||
#include <netinet/tcp.h>
|
||||
#include <event.h>
|
||||
#include <ThreadPool/ThreadHelper.h>
|
||||
#include <misc/endianness.h>
|
||||
#include <shared/include/license/client.h>
|
||||
#include "shared/include/license/client.h"
|
||||
#include "crypt.h"
|
||||
|
||||
using namespace license::client;
|
||||
|
||||
LicenseServerClient::Buffer* LicenseServerClient::Buffer::allocate(size_t capacity) {
|
||||
static_assert(std::is_trivially_constructible<Buffer>::value);
|
||||
|
||||
const auto allocated_bytes = sizeof(LicenseServerClient::Buffer) + capacity;
|
||||
auto result = malloc(allocated_bytes);
|
||||
if(!result) return nullptr;
|
||||
|
||||
auto buffer = reinterpret_cast<LicenseServerClient::Buffer*>(result);
|
||||
buffer->capacity = capacity;
|
||||
buffer->fill = 0;
|
||||
buffer->offset = 0;
|
||||
buffer->data = (char*) result + sizeof(LicenseServerClient::Buffer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void LicenseServerClient::Buffer::free(Buffer *ptr) {
|
||||
static_assert(std::is_trivially_destructible<Buffer>::value);
|
||||
|
||||
::free(ptr);
|
||||
}
|
||||
|
||||
LicenseServerClient::LicenseServerClient(const sockaddr_in &address, int pversion) : protocol_version{pversion} {
|
||||
memcpy(&this->network.address, &address, sizeof(address));
|
||||
TAILQ_INIT(&this->buffers.write);
|
||||
|
||||
if(!this->buffers.read)
|
||||
this->buffers.read = Buffer::allocate(1024 * 8);
|
||||
}
|
||||
|
||||
LicenseServerClient::~LicenseServerClient() {
|
||||
const auto is_event_loop = this->network.event_dispatch.get_id() == std::this_thread::get_id();
|
||||
{
|
||||
std::unique_lock slock{this->connection_lock};
|
||||
this->connection_state = ConnectionState::UNCONNECTED;
|
||||
}
|
||||
this->cleanup_network_resources(); /* force cleanup ignoring the previous state */
|
||||
if(is_event_loop) this->network.event_dispatch.detach();
|
||||
|
||||
if(this->buffers.read)
|
||||
Buffer::free(this->buffers.read);
|
||||
}
|
||||
|
||||
bool LicenseServerClient::start_connection(std::string &error) {
|
||||
bool event_dispatch_spawned{false};
|
||||
|
||||
std::unique_lock slock{this->connection_lock};
|
||||
if(this->connection_state != ConnectionState::UNCONNECTED) {
|
||||
error = "invalid connection state";
|
||||
return false;
|
||||
}
|
||||
|
||||
this->connection_state = ConnectionState::CONNECTING;
|
||||
this->communication.initialized = false;
|
||||
|
||||
this->network.file_descriptor = socket(this->network.address.sin_family, SOCK_STREAM | SOCK_NONBLOCK, 0);
|
||||
if(this->network.file_descriptor < 0) {
|
||||
error = "failed to allocate socket";
|
||||
goto error_cleanup;
|
||||
}
|
||||
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
{
|
||||
auto connect_state = ::connect(this->network.file_descriptor, reinterpret_cast<const sockaddr *>(&this->network.address), sizeof(this->network.address));
|
||||
if(connect_state < 0 && errno != EINPROGRESS) {
|
||||
error = "connect() failed (" + std::string{strerror(errno)} + ")";
|
||||
goto error_cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
int enabled{1}, disabled{0};
|
||||
if(setsockopt(this->network.file_descriptor, SOL_SOCKET, SO_REUSEADDR, &enabled, sizeof(enabled)) < 0); //CERR("could not set reuse addr");
|
||||
if(setsockopt(this->network.file_descriptor, IPPROTO_TCP, TCP_CORK, &disabled, sizeof(disabled)) < 0); // CERR("could not set no push");
|
||||
|
||||
if(fcntl(this->network.file_descriptor, F_SETFD, fcntl(this->network.file_descriptor, F_GETFL, 0) | FD_CLOEXEC | O_NONBLOCK) < 0); // CERR("Failed to set FD_CLOEXEC and O_NONBLOCK (" + std::to_string(errno) + ")");
|
||||
}
|
||||
|
||||
this->network.event_base = event_base_new();
|
||||
this->network.event_read = event_new(this->network.event_base, this->network.file_descriptor, EV_READ | EV_PERSIST, [](int, short e, void* _this) {
|
||||
auto client = reinterpret_cast<LicenseServerClient*>(_this);
|
||||
auto client_ref = client->weak_from_this().lock();
|
||||
if(!client_ref) {
|
||||
logCritical(0, "Network callback for expired client (E011)!");
|
||||
return;
|
||||
}
|
||||
client->callback_read(e);
|
||||
client_ref.reset();
|
||||
}, this);
|
||||
this->network.event_write = event_new(this->network.event_base, this->network.file_descriptor, EV_WRITE, [](int, short e, void* _this) {
|
||||
auto client = reinterpret_cast<LicenseServerClient*>(_this);
|
||||
auto client_ref = client->weak_from_this().lock();
|
||||
if(!client_ref) {
|
||||
logCritical(0, "Network callback for expired client (E012)!");
|
||||
return;
|
||||
}
|
||||
client->callback_write(e);
|
||||
client_ref.reset();
|
||||
}, this);
|
||||
|
||||
this->network.event_dispatch = std::thread([&] {
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
event_add(this->network.event_read, nullptr);
|
||||
|
||||
timeval connect_timeout{5, 0};
|
||||
event_add(this->network.event_write, &connect_timeout);
|
||||
|
||||
auto event_base{this->network.event_base};
|
||||
event_base_loop(event_base, EVLOOP_NO_EXIT_ON_EMPTY);
|
||||
event_base_free(event_base);
|
||||
|
||||
//this ptr might be dangling
|
||||
});
|
||||
|
||||
return true;
|
||||
error_cleanup:
|
||||
this->cleanup_network_resources();
|
||||
this->connection_state = ConnectionState::UNCONNECTED;
|
||||
return false;
|
||||
}
|
||||
|
||||
void LicenseServerClient::close_connection() {
|
||||
std::unique_lock slock{this->connection_lock};
|
||||
if(this->connection_state == ConnectionState::UNCONNECTED) return;
|
||||
this->connection_state = ConnectionState::UNCONNECTED;
|
||||
|
||||
this->cleanup_network_resources();
|
||||
}
|
||||
|
||||
void LicenseServerClient::cleanup_network_resources() {
|
||||
const auto is_event_loop = this->network.event_dispatch.get_id() == std::this_thread::get_id();
|
||||
|
||||
if(this->network.event_read) {
|
||||
if(is_event_loop) event_del_noblock(this->network.event_read);
|
||||
else event_del_block(this->network.event_read);
|
||||
event_free(this->network.event_read);
|
||||
this->network.event_read = nullptr;
|
||||
}
|
||||
|
||||
if(this->network.event_write) {
|
||||
if(is_event_loop) event_del_noblock(this->network.event_write);
|
||||
else event_del_block(this->network.event_write);
|
||||
event_free(this->network.event_write);
|
||||
this->network.event_write = nullptr;
|
||||
}
|
||||
|
||||
if(this->network.event_base) {
|
||||
event_base_loopexit(this->network.event_base, nullptr);
|
||||
if(!is_event_loop)
|
||||
threads::save_join(this->network.event_dispatch, false);
|
||||
this->network.event_base = nullptr; /* event base has been saved by the event dispatcher and will be freed there */
|
||||
}
|
||||
|
||||
if(this->network.file_descriptor) {
|
||||
::close(this->network.file_descriptor);
|
||||
this->network.file_descriptor = 0;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard block{this->buffers.lock};
|
||||
auto buffer = TAILQ_FIRST(&this->buffers.write);
|
||||
while(buffer) {
|
||||
auto next = TAILQ_NEXT(buffer, tail);
|
||||
Buffer::free(buffer);
|
||||
buffer = next;
|
||||
}
|
||||
TAILQ_INIT(&this->buffers.write);
|
||||
this->buffers.notify_empty.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
void LicenseServerClient::callback_read(short events) {
|
||||
constexpr static auto buffer_size{1024};
|
||||
|
||||
ssize_t read_bytes{0};
|
||||
char buffer[buffer_size];
|
||||
|
||||
read_bytes = recv(this->network.file_descriptor, buffer, buffer_size, MSG_DONTWAIT);
|
||||
if(read_bytes <= 0) {
|
||||
if(errno == EAGAIN) return;
|
||||
std::unique_lock slock{this->connection_lock};
|
||||
|
||||
std::string disconnect_reason{};
|
||||
bool disconnect_expected{false};
|
||||
switch (this->connection_state) {
|
||||
case ConnectionState::CONNECTING:
|
||||
disconnect_reason = "connect error (" + std::string{strerror(errno)} + ")";
|
||||
disconnect_expected = false;
|
||||
break;
|
||||
case ConnectionState::INITIALIZING:
|
||||
case ConnectionState::CONNECTED:
|
||||
disconnect_reason = "read error (" + std::string{strerror(errno)} + ")";
|
||||
disconnect_expected = false;
|
||||
break;
|
||||
case ConnectionState::DISCONNECTING:
|
||||
disconnect_expected = true;
|
||||
break;
|
||||
case ConnectionState::UNCONNECTED:
|
||||
return; /* we're obsolete */
|
||||
}
|
||||
|
||||
if(auto callback{this->callback_disconnected}; callback) {
|
||||
slock.unlock();
|
||||
callback(disconnect_expected, disconnect_reason);
|
||||
slock.lock();
|
||||
}
|
||||
|
||||
if(this->connection_state != ConnectionState::UNCONNECTED) {
|
||||
this->cleanup_network_resources();
|
||||
this->connection_state = ConnectionState::UNCONNECTED;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this->handle_data(buffer, (size_t) read_bytes);
|
||||
}
|
||||
|
||||
void LicenseServerClient::callback_write(short events) {
|
||||
bool add_write_event{this->connection_state == ConnectionState::DISCONNECTING};
|
||||
if(events & EV_TIMEOUT) {
|
||||
std::unique_lock slock{this->connection_lock};
|
||||
if(this->connection_state == ConnectionState::CONNECTING || this->connection_state == ConnectionState::INITIALIZING) {
|
||||
/* connect timeout */
|
||||
if(auto callback{this->callback_disconnected}; callback) {
|
||||
slock.unlock();
|
||||
callback(false, "connect timeout");
|
||||
slock.lock();
|
||||
}
|
||||
|
||||
if(this->connection_state != ConnectionState::UNCONNECTED) {
|
||||
this->cleanup_network_resources();
|
||||
this->connection_state = ConnectionState::UNCONNECTED;
|
||||
}
|
||||
} else if(this->connection_state == ConnectionState::DISCONNECTING) {
|
||||
/* disconnect timeout */
|
||||
this->cleanup_network_resources();
|
||||
this->connection_state = ConnectionState::UNCONNECTED;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(events & EV_WRITE) {
|
||||
if(this->connection_state == ConnectionState::CONNECTING) {
|
||||
this->callback_socket_connected();
|
||||
if(this->connection_state == ConnectionState::UNCONNECTED) /* state may change in the callback */
|
||||
return;
|
||||
}
|
||||
|
||||
ssize_t written_bytes{0};
|
||||
|
||||
std::unique_lock block{this->buffers.lock};
|
||||
auto buffer = TAILQ_FIRST(&this->buffers.write);
|
||||
if(!buffer) {
|
||||
this->buffers.notify_empty.notify_all();
|
||||
return;
|
||||
}
|
||||
block.unlock();
|
||||
written_bytes = send(this->network.file_descriptor, (char*) buffer->data + buffer->offset, buffer->fill - buffer->offset, MSG_DONTWAIT);
|
||||
|
||||
if(written_bytes <= 0) {
|
||||
if(errno == EAGAIN) goto readd_event;
|
||||
std::unique_lock slock{this->connection_lock};
|
||||
|
||||
std::string disconnect_reason{};
|
||||
bool disconnect_expected{false};
|
||||
switch (this->connection_state) {
|
||||
case ConnectionState::CONNECTING:
|
||||
case ConnectionState::INITIALIZING:
|
||||
case ConnectionState::CONNECTED:
|
||||
disconnect_reason = "write error (" + std::string{strerror(errno)} + ")";
|
||||
disconnect_expected = false;
|
||||
break;
|
||||
case ConnectionState::DISCONNECTING:
|
||||
disconnect_expected = true;
|
||||
break;
|
||||
case ConnectionState::UNCONNECTED:
|
||||
return; /* we're obsolete */
|
||||
}
|
||||
if(auto callback{this->callback_disconnected}; callback) {
|
||||
slock.unlock();
|
||||
callback(disconnect_expected, disconnect_reason);
|
||||
slock.lock();
|
||||
}
|
||||
|
||||
if(this->connection_state != ConnectionState::UNCONNECTED) {
|
||||
this->cleanup_network_resources();
|
||||
this->connection_state = ConnectionState::UNCONNECTED;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
buffer->offset += (size_t) written_bytes;
|
||||
if(buffer->offset >= buffer->fill) {
|
||||
assert(buffer->offset == buffer->fill);
|
||||
block.lock();
|
||||
TAILQ_REMOVE(&this->buffers.write, buffer, tail);
|
||||
if(!TAILQ_FIRST(&this->buffers.write)) {
|
||||
this->buffers.notify_empty.notify_all();
|
||||
} else {
|
||||
add_write_event = true;
|
||||
}
|
||||
block.unlock();
|
||||
Buffer::free(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
if(this->network.event_write && add_write_event) {
|
||||
readd_event:
|
||||
auto timeout = this->disconnect_timeout;
|
||||
if(timeout.time_since_epoch().count() == 0)
|
||||
event_add(this->network.event_write, nullptr);
|
||||
else {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
struct timeval t{0, 1};
|
||||
if(now > timeout) {
|
||||
this->callback_write(EV_TIMEOUT);
|
||||
return;
|
||||
} else {
|
||||
auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(timeout - now);
|
||||
auto seconds = std::chrono::duration_cast<std::chrono::seconds>(microseconds);
|
||||
microseconds -= seconds;
|
||||
|
||||
t.tv_usec = microseconds.count();
|
||||
t.tv_sec = seconds.count();
|
||||
}
|
||||
event_add(this->network.event_write, &t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LicenseServerClient::handle_data(void *recv_buffer, size_t length) {
|
||||
auto& buffer = this->buffers.read;
|
||||
assert(buffer);
|
||||
|
||||
if(buffer->capacity - buffer->offset - buffer->fill < length) {
|
||||
if(buffer->capacity - buffer->fill > length) {
|
||||
memcpy(buffer->data, (char*) buffer->data + buffer->offset, buffer->fill);
|
||||
buffer->offset = 0;
|
||||
} else {
|
||||
auto new_buffer = Buffer::allocate(buffer->fill + length);
|
||||
memcpy(new_buffer->data, (char*) buffer->data + buffer->offset, buffer->fill);
|
||||
new_buffer->fill = buffer->fill;
|
||||
Buffer::free(buffer);
|
||||
buffer = new_buffer;
|
||||
}
|
||||
}
|
||||
auto buffer_ptr = (char*) buffer->data;
|
||||
auto& buffer_offset = buffer->offset;
|
||||
auto& buffer_length = buffer->fill;
|
||||
|
||||
memcpy((char*) buffer_ptr + buffer_offset + buffer_length, recv_buffer, length);
|
||||
buffer_length += length;
|
||||
|
||||
while(true) {
|
||||
if(buffer_length < sizeof(protocol::packet_header)) return;
|
||||
|
||||
auto header = reinterpret_cast<protocol::packet_header*>(buffer_ptr + buffer_offset);
|
||||
if(header->length > 1024 * 8) {
|
||||
if(auto callback{this->callback_disconnected}; callback)
|
||||
callback(false, "received a too large message");
|
||||
this->disconnect("received too large message", std::chrono::system_clock::time_point{});
|
||||
return;
|
||||
}
|
||||
|
||||
if(buffer_length < header->length + sizeof(protocol::packet_header)) return;
|
||||
|
||||
this->handle_raw_packet(header->packetId, buffer_ptr + buffer_offset + sizeof(protocol::packet_header), header->length);
|
||||
if(this->connection_state == ConnectionState::UNCONNECTED) return; /* state may change while we're handing the packet */
|
||||
|
||||
buffer_offset += header->length + sizeof(protocol::packet_header);
|
||||
buffer_length -= header->length + sizeof(protocol::packet_header);
|
||||
}
|
||||
}
|
||||
|
||||
void LicenseServerClient::send_message(protocol::PacketType type, const void *payload, size_t size) {
|
||||
const auto packet_size = size + sizeof(protocol::packet_header);
|
||||
auto buffer = Buffer::allocate(packet_size);
|
||||
buffer->fill = packet_size;
|
||||
|
||||
auto header = (protocol::packet_header*) buffer->data;
|
||||
header->length = packet_size;
|
||||
header->packetId = type;
|
||||
memcpy((char*) buffer->data + sizeof(protocol::packet_header), payload, size);
|
||||
if(this->communication.initialized)
|
||||
xorBuffer((char*) buffer->data + sizeof(protocol::packet_header), size, this->communication.crypt_key.data(), this->communication.crypt_key.length());
|
||||
|
||||
std::lock_guard clock{this->connection_lock};
|
||||
if(this->connection_state == ConnectionState::UNCONNECTED || !this->network.event_write) {
|
||||
Buffer::free(buffer);
|
||||
return;
|
||||
}
|
||||
{
|
||||
std::lock_guard block{this->buffers.lock};
|
||||
TAILQ_INSERT_TAIL(&this->buffers.write, buffer, tail);
|
||||
}
|
||||
event_add(this->network.event_write, nullptr);
|
||||
}
|
||||
|
||||
void LicenseServerClient::disconnect(const std::string &message, std::chrono::system_clock::time_point timeout) {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
if(now > timeout)
|
||||
timeout = now + std::chrono::seconds{timeout.time_since_epoch().count() ? 1 : 0};
|
||||
|
||||
std::unique_lock clock{this->connection_lock};
|
||||
if(this->connection_state == ConnectionState::DISCONNECTING) {
|
||||
this->disconnect_timeout = std::min(this->disconnect_timeout, timeout);
|
||||
if(this->network.event_write)
|
||||
event_add(this->network.event_write, nullptr); /* let the write update the timeout */
|
||||
return;
|
||||
}
|
||||
this->disconnect_timeout = timeout;
|
||||
|
||||
if(this->connection_state != ConnectionState::INITIALIZING && this->connection_state != ConnectionState::CONNECTED) {
|
||||
clock.unlock();
|
||||
this->close_connection();
|
||||
return;
|
||||
}
|
||||
|
||||
this->connection_state = ConnectionState::DISCONNECTING;
|
||||
if(this->network.event_read)
|
||||
event_del_noblock(this->network.event_read);
|
||||
clock.unlock();
|
||||
|
||||
this->send_message(protocol::PACKET_DISCONNECT, message.data(), message.length());
|
||||
}
|
||||
|
||||
bool LicenseServerClient::await_disconnect() {
|
||||
{
|
||||
std::lock_guard clock{this->connection_lock};
|
||||
if(this->connection_state != ConnectionState::DISCONNECTING)
|
||||
return this->connection_state == ConnectionState::UNCONNECTED;
|
||||
}
|
||||
/* state might change here, but when we're disconnected the write buffer will be empty */
|
||||
std::unique_lock block{this->buffers.lock};
|
||||
while(TAILQ_FIRST(&this->buffers.write))
|
||||
this->buffers.notify_empty.wait(block);
|
||||
|
||||
return std::chrono::system_clock::now() <= this->disconnect_timeout;
|
||||
}
|
||||
|
||||
void LicenseServerClient::callback_socket_connected() {
|
||||
{
|
||||
std::lock_guard clock{this->connection_lock};
|
||||
if(this->connection_state != ConnectionState::CONNECTING) return;
|
||||
this->connection_state = ConnectionState::INITIALIZING;
|
||||
}
|
||||
|
||||
uint8_t handshakeBuffer[4];
|
||||
handshakeBuffer[0] = 0xC0;
|
||||
handshakeBuffer[1] = 0xFF;
|
||||
handshakeBuffer[2] = 0xEE;
|
||||
handshakeBuffer[3] = this->protocol_version;
|
||||
|
||||
this->send_message(protocol::PACKET_CLIENT_HANDSHAKE, handshakeBuffer, 4);
|
||||
}
|
||||
|
||||
void LicenseServerClient::handle_raw_packet(license::protocol::PacketType type, void * buffer, size_t length) {
|
||||
/* decrypt packet */
|
||||
if(this->communication.initialized)
|
||||
xorBuffer((char*) buffer, length, this->communication.crypt_key.data(), this->communication.crypt_key.length());
|
||||
|
||||
if(type == protocol::PACKET_DISCONNECT) {
|
||||
if(auto callback{this->callback_disconnected}; callback)
|
||||
callback(false, std::string{(const char*) buffer, length});
|
||||
this->close_connection();
|
||||
return;
|
||||
}
|
||||
|
||||
if(!this->communication.initialized) {
|
||||
if(type != protocol::PACKET_SERVER_HANDSHAKE) {
|
||||
if(auto callback{this->callback_disconnected}; callback)
|
||||
callback(false, "expected handshake packet");
|
||||
this->disconnect("expected handshake packet", std::chrono::system_clock::time_point{});
|
||||
return;
|
||||
}
|
||||
|
||||
this->handle_handshake_packet(buffer, length);
|
||||
this->communication.initialized = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if(auto callback{this->callback_message}; callback)
|
||||
callback(type, buffer, length);
|
||||
else
|
||||
; //TODO: Print error?
|
||||
}
|
||||
|
||||
void LicenseServerClient::handle_handshake_packet(void *buffer, size_t length) {
|
||||
const auto data_ptr = (const char*) buffer;
|
||||
|
||||
std::string error{};
|
||||
if(this->connection_state != ConnectionState::INITIALIZING) {
|
||||
error = "invalid protocol state";
|
||||
goto handle_error;
|
||||
}
|
||||
|
||||
if(length < 5) {
|
||||
error = "invalid packet size";
|
||||
goto handle_error;
|
||||
}
|
||||
|
||||
if((uint8_t) data_ptr[0] != 0xAF || (uint8_t) data_ptr[1] != 0xFE) {
|
||||
error = "invalid handshake signature";
|
||||
goto handle_error;
|
||||
}
|
||||
if((uint8_t) data_ptr[2] != this->protocol_version) {
|
||||
error = "Invalid license protocol version. Please update TeaSpeak!";
|
||||
goto handle_error;
|
||||
}
|
||||
|
||||
{
|
||||
auto key_length = be2le16(data_ptr, 3);
|
||||
if(length < key_length + 5) {
|
||||
error = "invalid packet size";
|
||||
goto handle_error;
|
||||
}
|
||||
this->communication.crypt_key = std::string(data_ptr + 5, key_length);
|
||||
this->communication.initialized = true;
|
||||
}
|
||||
|
||||
if(auto callback{this->callback_connected}; callback)
|
||||
callback();
|
||||
return;
|
||||
|
||||
handle_error:
|
||||
if(auto callback{this->callback_disconnected}; callback)
|
||||
callback(false, error);
|
||||
this->disconnect(error, std::chrono::system_clock::time_point{});
|
||||
}
|
||||
@@ -0,0 +1,860 @@
|
||||
#include <google/protobuf/message.h>
|
||||
#include <misc/base64.h>
|
||||
#include <random>
|
||||
#include <ed25519/ed25519.h>
|
||||
|
||||
#define NO_OPEN_SSL
|
||||
#include <misc/digest.h>
|
||||
#include <cstring>
|
||||
#include <cassert>
|
||||
#include <ed25519/ge.h>
|
||||
#include <ed25519/sc.h>
|
||||
#include "crypt.h"
|
||||
#include "shared/include/license/license.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
|
||||
inline void generate(char* buffer, size_t length){
|
||||
for(int index = 0; index < length; index++)
|
||||
buffer[index] = (uint8_t) rand();
|
||||
}
|
||||
|
||||
namespace license {
|
||||
std::string LicenseTypeNames[] = LT_NAMES;
|
||||
|
||||
std::shared_ptr<License> readLocalLicence(const std::string& buffer, std::string& error){
|
||||
string bbuffer = base64::decode(buffer);
|
||||
if(bbuffer.length() < sizeof(License)) {
|
||||
error = "invalid size";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto license = static_cast<License *>(malloc(sizeof(License)));
|
||||
memcpy(license, bbuffer.data(), sizeof(License));
|
||||
|
||||
if(license->header.version != LICENSE_VERSION){
|
||||
error = "invalid version (" + to_string(license->header.version) + ")";
|
||||
return nullptr;
|
||||
}
|
||||
xorBuffer(&((char*) license)[sizeof(License::header)], sizeof(License::data), license->header.cryptKey, sizeof(license->header.cryptKey));
|
||||
|
||||
auto hash = digest::sha1(reinterpret_cast<const char *>(&license->data), sizeof(license->data));
|
||||
|
||||
uint64_t checkSum = 0;
|
||||
for(int i = 0; i < SHA_DIGEST_LENGTH; i++)
|
||||
checkSum += (uint8_t) hash[i] << (i % 8);
|
||||
|
||||
if((checkSum ^ *(uint64_t*) &license->header.cryptKey) != MAGIC_NUMER) {
|
||||
error = "invalid check sum";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return shared_ptr<License>(license, [](License* l){
|
||||
if(l) free(l);
|
||||
});
|
||||
}
|
||||
|
||||
std::string exportLocalLicense(const std::shared_ptr<License>& ref){
|
||||
auto copy = static_cast<License *>(malloc(sizeof(License)));
|
||||
memcpy(copy, ref.get(), sizeof(License));
|
||||
|
||||
auto hash = digest::sha1(reinterpret_cast<const char *>(©->data), sizeof(copy->data));
|
||||
|
||||
uint64_t checkSum = 0;
|
||||
for(int i = 0; i < SHA_DIGEST_LENGTH; i++)
|
||||
checkSum += (uint8_t) hash[i] << (i % 8);
|
||||
checkSum ^= MAGIC_NUMER;
|
||||
|
||||
generate(const_cast<char *>(copy->header.cryptKey), sizeof(copy->header.cryptKey));
|
||||
*(uint64_t*) ©->header.cryptKey = checkSum;
|
||||
|
||||
|
||||
xorBuffer(&((char*) copy)[sizeof(License::header)], sizeof(License::data), copy->header.cryptKey, sizeof(copy->header.cryptKey));
|
||||
auto result = base64_encode((const char*) copy, sizeof(License));
|
||||
free(copy);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string createLocalLicence(LicenseType type, std::chrono::system_clock::time_point until, std::string licenseOwner){
|
||||
auto license = shared_ptr<License>(static_cast<License *>(malloc(sizeof(License))), [](License* l) { if(l) free(l); });
|
||||
assert(licenseOwner.length() < sizeof(license->data.licenceOwner));
|
||||
|
||||
license->header.version = LICENSE_VERSION;
|
||||
generate(const_cast<char *>(license->data.licenceKey), sizeof(license->data.licenceKey));
|
||||
generate(const_cast<char *>(license->data.licenceOwner), sizeof(license->data.licenceOwner)); //Crap data :)
|
||||
license->data.type = type;
|
||||
license->data.endTimestamp = duration_cast<milliseconds>(until.time_since_epoch()).count();
|
||||
memcpy((void *) license->data.licenceOwner, licenseOwner.c_str(), strlen(licenseOwner.c_str()) + 1); //Copy the string into it
|
||||
|
||||
return exportLocalLicense(license);
|
||||
}
|
||||
|
||||
const char *exceptions::LicenseException::what() const throw() {
|
||||
return this->errorMessage.c_str();
|
||||
}
|
||||
|
||||
protocol::packet::packet(PacketType packetId, const ::google::protobuf::Message& message) {
|
||||
this->header.packetId = packetId;
|
||||
this->data = message.SerializeAsString();
|
||||
}
|
||||
|
||||
protocol::packet::packet(license::protocol::PacketType packetId, nullptr_t) {
|
||||
this->header.packetId = packetId;
|
||||
this->data = "";
|
||||
}
|
||||
}
|
||||
|
||||
std::array<uint8_t, 32> license::v2::public_root_key = {
|
||||
0x84, 0x54, 0x2c, 0x2b, 0x46, 0x19, 0x05, 0x6c,
|
||||
0x01, 0xd8, 0x61, 0x49, 0x4e, 0x48, 0x47, 0x1e,
|
||||
0x6c, 0x61, 0xfa, 0x6a, 0xde, 0x6b, 0x1c, 0x76,
|
||||
0x3a, 0xeb, 0x2f, 0x39, 0x49, 0x3d, 0x71, 0x35
|
||||
};
|
||||
|
||||
namespace license::v2 {
|
||||
License::~License() {
|
||||
if(this->private_buffer)
|
||||
::free(this->private_buffer);
|
||||
}
|
||||
|
||||
std::shared_ptr<License> License::create(const std::vector<std::shared_ptr<const license::v2::HierarchyEntry>> &hierarchy, const std::array<uint8_t, 32> &prv_key) {
|
||||
auto result = shared_ptr<License>(new License{});
|
||||
|
||||
result->_version = 2;
|
||||
result->crypt_seed = std::mt19937_64{std::random_device{}()}();
|
||||
result->_hierarchy = hierarchy;
|
||||
result->private_data = LicensePrivate::create(result, (int) hierarchy.size() - 1, prv_key.data());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::shared_ptr<License> License::read(const uint8_t *buffer, size_t length, uint8_t &error) {
|
||||
auto result = shared_ptr<License>(new License{});
|
||||
|
||||
LicenseHeader header{};
|
||||
BodyHeader body_header{};
|
||||
if(length < sizeof(header) + sizeof(body_header)) {
|
||||
error = 2; /* buffer too small */
|
||||
return nullptr;
|
||||
}
|
||||
memcpy(&header, buffer, sizeof(header));
|
||||
|
||||
if(header.version != 2) {
|
||||
error = 3; /* invalid version */
|
||||
return nullptr;
|
||||
}
|
||||
result->_version = header.version;
|
||||
|
||||
std::mt19937_64 crypt_key_gen{header.crypt_key_seed};
|
||||
|
||||
/* verify the crypt key gen */
|
||||
{
|
||||
crypt_key_gen.discard(header.crypt_key_verify_offset);
|
||||
uint64_t expected = 0;
|
||||
memcpy(&expected, header.crypt_key_verify, 5);
|
||||
|
||||
uint64_t received = crypt_key_gen();
|
||||
received = received ^ (received >> 40UL);
|
||||
received &= 0xFFFFFFFFFFULL;
|
||||
|
||||
if(expected != received) {
|
||||
error = 4; /* invalid key sequence */
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
crypt_key_gen.seed(header.crypt_key_seed);
|
||||
auto decoded_buffer_length = length - sizeof(header);
|
||||
auto decoded_buffer = unique_ptr<uint8_t, decltype(::free)*>{(uint8_t*) malloc(decoded_buffer_length), ::free};
|
||||
if(!decoded_buffer) {
|
||||
error = 1; /* out of memory */
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* "decode" the data */
|
||||
{
|
||||
auto index = sizeof(header);
|
||||
auto index_decoded = 0;
|
||||
while(index + 4 < length) {
|
||||
auto& memory = *(uint32_t*) (&*decoded_buffer + index_decoded);
|
||||
memory = *(uint32_t*) (buffer + index);
|
||||
memory ^= (uint32_t) crypt_key_gen();
|
||||
index += 4;
|
||||
index_decoded += 4;
|
||||
}
|
||||
while(index < length) {
|
||||
auto& memory = *(uint8_t*) (&*decoded_buffer + index_decoded);
|
||||
memory = *(uint8_t*) (buffer + index);
|
||||
memory ^= (uint8_t) crypt_key_gen();
|
||||
index++;
|
||||
index_decoded++;
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(&body_header, &*decoded_buffer, sizeof(body_header));
|
||||
if(decoded_buffer_length < sizeof(body_header) + body_header.length_hierarchy + body_header.length_private_data) {
|
||||
error = 2; /* buffer too small */
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto hierarchy_buffer = &*decoded_buffer + sizeof(body_header) + body_header.length_private_data;
|
||||
/* test the checksum for the hierarchy (license data indirectly verified via data_sign) */
|
||||
{
|
||||
uint8_t sha_buffer[20];
|
||||
digest::sha1((char*) hierarchy_buffer, body_header.length_hierarchy, sha_buffer);
|
||||
if(memcmp(sha_buffer, body_header.checksum_hierarchy, 20) != 0) {
|
||||
error = 5; /* checksum does not match */
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/* now lets read the hierarchy data */
|
||||
{
|
||||
size_t offset = 0, length = body_header.length_hierarchy;
|
||||
while(offset < length) {
|
||||
auto entry = HierarchyEntry::read(hierarchy_buffer, offset, length);
|
||||
if(!entry) {
|
||||
error = 6; /* failed to read an entry */
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
result->_hierarchy.push_back(entry);
|
||||
}
|
||||
}
|
||||
|
||||
/* verify the given data */
|
||||
auto public_key = result->generate_public_key(public_root_key.data());
|
||||
if(!ed25519_verify(body_header.private_data_sign, &*decoded_buffer + sizeof(body_header), body_header.length_private_data, public_key.data())) {
|
||||
error = 7; /* failed to verify private data */
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
memcpy(result->private_buffer_sign.data(), body_header.private_data_sign, 64);
|
||||
|
||||
/* copy the private data */
|
||||
result->private_buffer = (uint8_t*) malloc(body_header.length_private_data);
|
||||
result->private_buffer_length = body_header.length_private_data;
|
||||
memcpy(result->private_buffer, &*decoded_buffer + sizeof(body_header), body_header.length_private_data);
|
||||
|
||||
/* let parse the private data */
|
||||
result->private_data = LicensePrivate::read(result, result->_version, result->private_buffer, result->private_buffer_length, error);
|
||||
if(!result->private_data) {
|
||||
error = 7; /* failed to parse private data */
|
||||
return nullptr;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string License::write(uint8_t &error) {
|
||||
if(!this->private_data || !this->private_buffer_length) {
|
||||
error = 2; /* missing private data */
|
||||
return "";
|
||||
}
|
||||
|
||||
/* lets estimate a buffer size */
|
||||
auto buffer_size = sizeof(LicenseHeader) + sizeof(BodyHeader) + this->private_buffer_length;
|
||||
|
||||
for(auto& he : this->_hierarchy)
|
||||
if(!he->write(nullptr, buffer_size, 0)) {
|
||||
error = 3; /* failed to estimate buffer size */
|
||||
return "";
|
||||
}
|
||||
|
||||
auto buffer = unique_ptr<uint8_t, decltype(::free)*>{(uint8_t*) malloc(buffer_size), ::free};
|
||||
LicenseHeader license_header{};
|
||||
BodyHeader body_header{};
|
||||
|
||||
/* first copy the private data */
|
||||
{
|
||||
memcpy(body_header.private_data_sign, this->private_buffer_sign.data(), this->private_buffer_sign.size());
|
||||
memcpy(&*buffer + sizeof(license_header) + sizeof(body_header), this->private_buffer, this->private_buffer_length);
|
||||
body_header.length_private_data = this->private_buffer_length;
|
||||
}
|
||||
|
||||
/* lets write the hierarchy */
|
||||
{
|
||||
auto offset = sizeof(license_header) + sizeof(body_header) + this->private_buffer_length;
|
||||
const auto begin_offset = offset;
|
||||
for(auto& he : this->_hierarchy)
|
||||
if(!he->write(&*buffer, offset, buffer_size)) {
|
||||
error = 3; /* failed to write hierarchy */
|
||||
return "";
|
||||
}
|
||||
|
||||
body_header.length_hierarchy = offset - begin_offset;
|
||||
digest::sha1((char*) &*buffer + begin_offset, body_header.length_hierarchy, body_header.checksum_hierarchy);
|
||||
}
|
||||
|
||||
/* write the body header */
|
||||
memcpy(&*buffer + sizeof(license_header), &body_header, sizeof(body_header));
|
||||
|
||||
/* lets generate the license header */
|
||||
{
|
||||
std::mt19937_64 rnd{std::random_device{}()};
|
||||
|
||||
license_header.version = 2;
|
||||
license_header.crypt_key_seed = rnd();
|
||||
license_header.crypt_key_verify_offset = std::uniform_int_distribution<uint8_t>{}(rnd);
|
||||
|
||||
{
|
||||
rnd.seed(license_header.crypt_key_seed);
|
||||
rnd.discard(license_header.crypt_key_verify_offset);
|
||||
|
||||
uint64_t expected = rnd();
|
||||
expected = expected ^ (expected >> 40UL);
|
||||
expected &= 0xFFFFFFFFFFULL;
|
||||
|
||||
memcpy(license_header.crypt_key_verify, &expected, 5);
|
||||
}
|
||||
}
|
||||
|
||||
/* now lets "encrypt" the body */
|
||||
{
|
||||
std::mt19937_64 crypt_key_gen{license_header.crypt_key_seed};
|
||||
|
||||
auto index = sizeof(license_header);
|
||||
while(index + 4 < buffer_size) {
|
||||
auto& memory = *(uint32_t*) (&*buffer + index);
|
||||
memory = *(uint32_t*) (&*buffer + index);
|
||||
memory ^= (uint32_t) crypt_key_gen();
|
||||
index += 4;
|
||||
}
|
||||
while(index < buffer_size) {
|
||||
auto& memory = *(uint8_t*) (&*buffer + index);
|
||||
memory = *(uint8_t*) (&*buffer + index);
|
||||
memory ^= (uint8_t) crypt_key_gen();
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
/* write the license header */
|
||||
memcpy(&*buffer, &license_header, sizeof(license_header));
|
||||
return std::string((char*) &*buffer, buffer_size);
|
||||
}
|
||||
|
||||
bool License::private_data_editable() const {
|
||||
return this->private_data->private_key_calculable(this->_hierarchy.size() - 1);
|
||||
}
|
||||
|
||||
bool License::write_private_data(const LicensePrivateWriteOptions& write_options) {
|
||||
if(this->_hierarchy.empty()) return false;
|
||||
|
||||
uint8_t private_key[64]; //ed25519_sign requires 64 bytes (may it expects a public key in front?)
|
||||
if(!this->private_data->calculate_private_key(private_key, this->_hierarchy.size() - 1))
|
||||
return false;
|
||||
memcpy(private_key + 32, private_key, 32);
|
||||
|
||||
auto public_key = this->generate_public_key(public_root_key.data());
|
||||
|
||||
size_t length = 0, offset = 0;
|
||||
if(!this->private_data->write(nullptr, length, 65536, write_options))
|
||||
return false;
|
||||
|
||||
if(this->private_buffer)
|
||||
::free(this->private_buffer);
|
||||
this->private_buffer_length = length;
|
||||
this->private_buffer = (uint8_t*) malloc(length);
|
||||
if(!this->private_buffer) return false;
|
||||
|
||||
if(!this->private_data->write(this->private_buffer, offset, length, write_options))
|
||||
return false;
|
||||
|
||||
ed25519_sign(this->private_buffer_sign.data(), this->private_buffer, length, public_key.data(), private_key);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::array<uint8_t, 32> License::generate_public_key(const uint8_t* root_key, int length) const {
|
||||
uint8_t hash_buffer[64];
|
||||
|
||||
ge_p3 parent_key{};
|
||||
ge_cached parent_cached{};
|
||||
|
||||
/* import the main parent key */
|
||||
ge_frombytes_negate_vartime(&parent_key, root_key);
|
||||
|
||||
/* undo the negate */
|
||||
fe_neg(parent_key.X, parent_key.X);
|
||||
fe_neg(parent_key.T, parent_key.T);
|
||||
|
||||
for(const auto& entry : this->_hierarchy) {
|
||||
if(length-- == 0) continue;
|
||||
|
||||
ge_p3 e_pub_key{};
|
||||
ge_frombytes_negate_vartime(&e_pub_key, entry->public_key().data());
|
||||
|
||||
ge_p3_to_cached(&parent_cached, &parent_key);
|
||||
|
||||
/* malloc could fail, but we ignore this for now */
|
||||
if(!entry->hash(hash_buffer)) /* */;
|
||||
|
||||
/* import hash (convert to a valid coordinate) */
|
||||
memset(hash_buffer + 32, 0, 32); /* yes, we have to drop half of the SHA512 hash :( */
|
||||
hash_buffer[0] &= 0xF8U;
|
||||
hash_buffer[31] &= 0x3FU;
|
||||
hash_buffer[31] |= 0x40U;
|
||||
sc_reduce(hash_buffer);
|
||||
|
||||
/* import the clamp data */
|
||||
ge_p3 p3_clamp_mul_pKey{};
|
||||
ge_p2 p2_clamp_mul_pKey{};
|
||||
ge_scalarmult_vartime(&p2_clamp_mul_pKey, hash_buffer, &e_pub_key);
|
||||
ge_p2_to_p3(&p3_clamp_mul_pKey, &p2_clamp_mul_pKey);
|
||||
|
||||
/* add parent with the clamp data */
|
||||
ge_p1p1 a{};
|
||||
ge_add(&a, &p3_clamp_mul_pKey, &parent_cached);
|
||||
|
||||
/* convert stuff back */
|
||||
ge_p3 r2{};
|
||||
ge_p1p1_to_p3(&r2, &a);
|
||||
|
||||
parent_key = r2;
|
||||
}
|
||||
|
||||
std::array<uint8_t, 32> result{};
|
||||
ge_p3_tobytes((uint8_t*) result.data(), &parent_key);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool License::push_entry(const std::shared_ptr<const HierarchyEntry> &entry, size_t* index) {
|
||||
assert(this->private_data);
|
||||
|
||||
auto idx = this->_hierarchy.size();
|
||||
if(idx > 0 && !this->private_data->private_key_calculable(idx - 1))
|
||||
return false;
|
||||
|
||||
if(index)
|
||||
*index = idx;
|
||||
this->_hierarchy.push_back(entry);
|
||||
return true;
|
||||
}
|
||||
|
||||
typedef duration<int64_t, ratio<hours::period::num * 24, hours::period::den>> days;
|
||||
typedef duration<int64_t, ratio<days::period::num * 365, days::period::den>> years;
|
||||
|
||||
bool License::hierarchy_timestamps_valid() {
|
||||
system_clock::time_point time_begin{};
|
||||
system_clock::time_point time_end = system_clock::time_point{} + years{5000};
|
||||
|
||||
for(const auto& entry : this->_hierarchy) {
|
||||
auto end = entry->end_timestamp();
|
||||
auto begin = entry->begin_timestamp();
|
||||
if(begin < time_begin)
|
||||
return false;
|
||||
if(end > time_end)
|
||||
return false;
|
||||
|
||||
time_begin = begin;
|
||||
if(end.time_since_epoch().count() != 0) time_end = end;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void License::_register_raw_private_key(size_t index, uint8_t *data) {
|
||||
assert(this->private_data);
|
||||
this->private_data->register_raw_private_key(index, data);
|
||||
}
|
||||
|
||||
bool License::generate_keypair(uint8_t *prv, uint8_t *pbl) {
|
||||
std::random_device rd;
|
||||
std::uniform_int_distribution<uint8_t> d;
|
||||
|
||||
uint8_t root_seed[64];
|
||||
for(auto& e : root_seed)
|
||||
e = d(rd);
|
||||
|
||||
ed25519_create_keypair(pbl, prv, root_seed);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<LicensePrivate> LicensePrivate::create(const std::shared_ptr<license::v2::License> &handle, int key_index, const uint8_t *key) {
|
||||
auto result = shared_ptr<LicensePrivate>(new LicensePrivate{});
|
||||
result->_handle = handle;
|
||||
result->precalculated_private_key_index = key_index;
|
||||
if(key) {
|
||||
memcpy(result->precalculated_private_key.data(), key, result->precalculated_private_key.size());
|
||||
} else {
|
||||
assert(key_index < -1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::shared_ptr<LicensePrivate> LicensePrivate::read(const std::shared_ptr<License>& handle, uint8_t version, const uint8_t *buffer, size_t length, uint8_t& error) {
|
||||
if(version != 2) {
|
||||
error = 2; /* invalid version */
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto result = LicensePrivate::create(handle, -2, nullptr);
|
||||
|
||||
size_t offset = 0;
|
||||
if((offset + 1) > length)
|
||||
return nullptr;
|
||||
|
||||
/* read the precalculated private key */
|
||||
if(*(buffer + offset++)) {
|
||||
if((offset + 1 + result->precalculated_private_key.size()) > length)
|
||||
return nullptr;
|
||||
|
||||
result->precalculated_private_key_index = *(buffer + offset++);
|
||||
memcpy(result->precalculated_private_key.data(), buffer + offset, result->precalculated_private_key.size());
|
||||
offset += result->precalculated_private_key.size();
|
||||
}
|
||||
|
||||
/* read raw private keys */
|
||||
{
|
||||
if((offset + 1) > length)
|
||||
return nullptr;
|
||||
|
||||
auto private_key_count = *(buffer + offset++);
|
||||
|
||||
if((offset + private_key_count * 33) > length)
|
||||
return nullptr;
|
||||
while(private_key_count-- > 0) {
|
||||
auto index = *(buffer + offset++);
|
||||
result->register_raw_private_key(index, buffer + offset);
|
||||
offset += 32;
|
||||
}
|
||||
}
|
||||
|
||||
/* read the metadata */
|
||||
{
|
||||
if((offset + 4) > length)
|
||||
return nullptr;
|
||||
|
||||
auto meta_data_length = *(uint32_t*) (buffer + offset);
|
||||
offset += 4;
|
||||
|
||||
while(meta_data_length-- > 0) {
|
||||
if((offset + 3) > length)
|
||||
return nullptr;
|
||||
auto key_length = *(buffer + offset++);
|
||||
auto value_length = *(uint16_t*) (buffer + offset);
|
||||
offset += 2;
|
||||
|
||||
if((offset + key_length + value_length) > length)
|
||||
return nullptr;
|
||||
|
||||
result->meta_data[{(char*) buffer + offset, key_length}] = {(char*) buffer + offset + key_length, value_length};
|
||||
offset += key_length;
|
||||
offset += value_length;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool LicensePrivate::write(uint8_t *buffer, size_t &offset, size_t length, const LicensePrivateWriteOptions& options) {
|
||||
if(options.precalculated_key_index < -1) {
|
||||
if(buffer) {
|
||||
if((offset + 2) > length) return false;
|
||||
*(buffer + offset++) = 0; /* no precalculated private key */
|
||||
*(buffer + offset++) = 0; /* no raw private keys */
|
||||
} else {
|
||||
offset += 2;
|
||||
}
|
||||
} else {
|
||||
auto index = options.precalculated_key_index == -1 ? this->precalculated_private_key_index : options.precalculated_key_index;
|
||||
if(index < 0) return false; /* we will NEVER write the root key */
|
||||
|
||||
if(buffer) {
|
||||
if((offset + 2 + 32) > length) return false;
|
||||
*(buffer + offset++) = 1;
|
||||
{
|
||||
*(buffer + offset++) = index;
|
||||
|
||||
if(!this->calculate_private_key(buffer + offset, index))
|
||||
return false;
|
||||
offset += 32;
|
||||
}
|
||||
|
||||
if((offset + 1) > length) return false;
|
||||
auto& private_key_count = *(buffer + offset++);
|
||||
private_key_count = 0;
|
||||
for(auto& [key_index, key] : this->private_keys) {
|
||||
if(key_index <= index)
|
||||
continue;
|
||||
|
||||
if((offset + 1 + key.size()) > length) return false;
|
||||
*(buffer + offset++) = key_index;
|
||||
memcpy(buffer + offset, key.data(), key.size());
|
||||
|
||||
private_key_count++;
|
||||
offset += key.size();
|
||||
}
|
||||
} else {
|
||||
/* private precalc key */
|
||||
offset += 2 + 32;
|
||||
|
||||
/* raw keys */
|
||||
offset += 1;
|
||||
for(auto& [key_index, key] : this->private_keys) {
|
||||
if(key_index <= index)
|
||||
continue;
|
||||
|
||||
offset += 1 + key.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(buffer) {
|
||||
if((offset + 4) > length) return false;
|
||||
*(uint32_t*) (buffer + offset) = this->meta_data.size();
|
||||
offset += 4;
|
||||
|
||||
for(auto& [key, value] : this->meta_data) {
|
||||
if((offset + 3 + key.length() + value.length()) > length) return false;
|
||||
|
||||
*(buffer + offset++) = key.length();
|
||||
*(uint16_t*)(buffer + offset) = value.length();
|
||||
offset += 2;
|
||||
|
||||
memcpy(buffer + offset, key.data(), key.length());
|
||||
offset += key.length();
|
||||
|
||||
memcpy(buffer + offset, value.data(), value.length());
|
||||
offset += value.length();
|
||||
}
|
||||
} else {
|
||||
offset += 4;
|
||||
for(auto& [key, value] : this->meta_data)
|
||||
offset += 3 + key.length() + value.length();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LicensePrivate::private_key_chain_valid() {
|
||||
auto handle = this->_handle.lock();
|
||||
if(!handle) return false;
|
||||
|
||||
auto hierarchy = handle->hierarchy();
|
||||
|
||||
auto base_index = this->precalculated_private_key_index;
|
||||
if(base_index >= (int64_t) hierarchy.size()) return false;
|
||||
if(base_index < -1) return true; /* means we don't have a private key */
|
||||
|
||||
while(base_index < hierarchy.size()) {
|
||||
if(!this->has_raw_private_key(base_index++))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LicensePrivate::private_key_calculable(int index) const {
|
||||
auto handle = this->_handle.lock();
|
||||
if(!handle) return false;
|
||||
|
||||
auto hierarchy = handle->hierarchy();
|
||||
if(index >= (int64_t) hierarchy.size()) return false;
|
||||
|
||||
|
||||
auto base_index = this->precalculated_private_key_index;
|
||||
if(base_index > index) return false;
|
||||
if(base_index < -1) return false;
|
||||
|
||||
while(base_index < index) {
|
||||
base_index++;
|
||||
if(this->private_keys.count(base_index) < 1)
|
||||
return false; /* we're missing a private key here, how is this even possible? */
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LicensePrivate::calculate_private_key(uint8_t *buffer, uint8_t index) const {
|
||||
auto handle = this->_handle.lock();
|
||||
if(!handle) return false;
|
||||
|
||||
auto hierarchy = handle->hierarchy();
|
||||
if(index >= hierarchy.size()) return false;
|
||||
|
||||
auto base_index = this->precalculated_private_key_index;
|
||||
if(base_index > index) return false;
|
||||
if(base_index < -1) return false;
|
||||
|
||||
uint8_t hash_buffer[64];
|
||||
memcpy(buffer, this->precalculated_private_key.data(), this->precalculated_private_key.size());
|
||||
while(base_index < index) {
|
||||
base_index++;
|
||||
if(this->private_keys.count(base_index) < 1)
|
||||
return false; /* we're missing a private key here, how is this even possible? */
|
||||
|
||||
if(!hierarchy[index]->hash(hash_buffer)) return false;
|
||||
|
||||
/* import hash (convert to a valid coordinate) */
|
||||
memset(hash_buffer + 32, 0, 32); /* yes, we have to drop half of the SHA512 hash :( */
|
||||
hash_buffer[0] &= 0xF8U;
|
||||
hash_buffer[31] &= 0x3FU;
|
||||
hash_buffer[31] |= 0x40U;
|
||||
sc_reduce(hash_buffer);
|
||||
|
||||
sc_muladd(buffer, this->private_keys.at(base_index).data(), hash_buffer, buffer);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void LicensePrivate::register_raw_private_key(uint8_t index, const uint8_t *buffer) {
|
||||
auto& target = this->private_keys[index];
|
||||
memcpy(target.data(), buffer, target.size());
|
||||
}
|
||||
|
||||
bool LicensePrivate::has_raw_private_key(uint8_t index) const {
|
||||
return this->private_keys.count(index) > 0;
|
||||
}
|
||||
|
||||
HierarchyEntry::~HierarchyEntry() {
|
||||
this->allocate_read_body(0);
|
||||
}
|
||||
|
||||
std::shared_ptr<const HierarchyEntry> HierarchyEntry::read(const uint8_t *buffer, size_t &offset, size_t length) {
|
||||
auto result = shared_ptr<HierarchyEntry>(new HierarchyEntry{});
|
||||
if((offset + 43) > length) return nullptr;
|
||||
|
||||
result->_entry_type = *(buffer + offset);
|
||||
offset++;
|
||||
|
||||
memcpy(result->_public_key.data(), buffer + offset, 32);
|
||||
offset += 32;
|
||||
|
||||
memcpy(&result->_timestamp_begin, buffer + offset, 4);
|
||||
offset += 4;
|
||||
|
||||
memcpy(&result->_timestamp_end, buffer + offset, 4);
|
||||
offset += 4;
|
||||
|
||||
uint16_t body_length;
|
||||
memcpy(&body_length, buffer + offset, 2);
|
||||
offset += 2;
|
||||
|
||||
if(body_length > length) return nullptr;
|
||||
if(!result->allocate_read_body(body_length)) return nullptr;
|
||||
|
||||
if(body_length > 0) {
|
||||
result->allocate_read_body(body_length);
|
||||
if(!result->read_body) return nullptr;
|
||||
result->read_body_length = body_length;
|
||||
memcpy(result->read_body, buffer + offset, body_length);
|
||||
offset += body_length;
|
||||
}
|
||||
|
||||
result->_hash_set = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool HierarchyEntry::write(uint8_t *buffer, size_t &offset, size_t length) const {
|
||||
if(buffer && (offset + 43 + this->read_body_length) > length) return false;
|
||||
|
||||
if(buffer) *(buffer + offset) = this->_entry_type;
|
||||
offset++;
|
||||
|
||||
if(buffer) memcpy(buffer + offset, this->_public_key.data(), 32);
|
||||
offset += 32;
|
||||
|
||||
if(buffer) memcpy(buffer + offset, &this->_timestamp_begin, 4);
|
||||
offset += 4;
|
||||
|
||||
if(buffer) memcpy(buffer + offset, &this->_timestamp_end, 4);
|
||||
offset += 4;
|
||||
|
||||
if(buffer) memcpy(buffer + offset, &this->read_body_length, 2);
|
||||
offset += 2;
|
||||
|
||||
if(this->read_body_length > 0) {
|
||||
if(buffer) memcpy(buffer + offset, this->read_body, this->read_body_length);
|
||||
offset += this->read_body_length;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HierarchyEntry::allocate_read_body(size_t size) {
|
||||
if(this->read_body) {
|
||||
::free(this->read_body);
|
||||
this->read_body = nullptr;
|
||||
}
|
||||
|
||||
if(size > 0) {
|
||||
this->read_body = (uint8_t*) malloc(size);
|
||||
if(!this->read_body) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HierarchyEntry::hash(uint8_t *target_buffer) const {
|
||||
if(this->_hash_set) {
|
||||
memcpy(target_buffer, this->_hash.data(), this->_hash.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t length = 43 + this->read_body_length, offset = 0;
|
||||
auto buffer = unique_ptr<uint8_t, decltype(::free)*>((uint8_t*) malloc(length), ::free);
|
||||
if(!buffer) return false;
|
||||
if(this->write(&*buffer, offset, length)) {
|
||||
digest::sha512((char*) &*buffer, length, this->_hash.data());
|
||||
this->_hash_set = true;
|
||||
}
|
||||
|
||||
return this->_hash_set ? this->hash(target_buffer) : false;
|
||||
}
|
||||
|
||||
namespace hierarchy {
|
||||
std::string_view Intermediate::description() {
|
||||
if(this->_length == 0)
|
||||
return {};
|
||||
|
||||
return std::string_view{(const char*) this->_memory + 1, (size_t) *this->_memory};
|
||||
}
|
||||
|
||||
std::shared_ptr<const HierarchyEntry> Intermediate::create(const uint8_t *pub_key, const std::chrono::system_clock::time_point &begin, const std::chrono::system_clock::time_point & end, const std::string &description) {
|
||||
assert(description.size() < 256);
|
||||
auto buffer_length = description.size() + 1;
|
||||
|
||||
uint8_t* buffer;
|
||||
auto result = BodyInterpreter::_create<Intermediate>(pub_key, begin, end, buffer_length, buffer);
|
||||
if(!result) return nullptr;
|
||||
|
||||
memcpy(buffer + 1, description.data(), description.length());
|
||||
*buffer = (uint8_t) description.length();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Server::has_username() {
|
||||
return *(this->_memory + 1) > 0;
|
||||
}
|
||||
|
||||
std::string_view Server::contact_email() {
|
||||
return {(const char*) this->_memory + 2, *this->_memory};
|
||||
}
|
||||
|
||||
std::string_view Server::username() {
|
||||
return {(const char*) this->_memory + 2 + *this->_memory, *(this->_memory + 1)};
|
||||
}
|
||||
|
||||
std::shared_ptr<const HierarchyEntry> Server::create(const uint8_t *pub_key, const std::chrono::system_clock::time_point &begin, const std::chrono::system_clock::time_point & end, const std::string &email, const std::optional<std::string> &username) {
|
||||
assert(email.size() < 256);
|
||||
assert(!username.has_value() || username->size() < 256);
|
||||
auto buffer_length = 2 + email.size() + (username.has_value() ? username->length() : 0);
|
||||
|
||||
uint8_t* buffer;
|
||||
auto result = BodyInterpreter::_create<Intermediate>(pub_key, begin, end, buffer_length, buffer);
|
||||
if(!result) return nullptr;
|
||||
|
||||
memcpy(buffer + 2, email.data(), email.length());
|
||||
*buffer = (uint8_t) email.length();
|
||||
|
||||
if(username.has_value())
|
||||
memcpy(buffer + 2 + email.length(), username->data(), username->length());
|
||||
*(buffer + 1) = (uint8_t) (username.has_value() ? username->length() : 0);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::shared_ptr<const HierarchyEntry> Ephemeral::create(const uint8_t *pub_key, const std::chrono::system_clock::time_point &begin, const std::chrono::system_clock::time_point & end) {
|
||||
uint8_t* buffer;
|
||||
return BodyInterpreter::_create<Ephemeral>(pub_key, begin, end, 0, buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
Submodule music updated: af7918a243...a4dabbb5c4
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "TeaSpeak",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@types/node": "^14.11.2",
|
||||
"yaml": "^1.10.0"
|
||||
}
|
||||
}
|
||||
+264
-216
@@ -3,7 +3,7 @@ project(TeaSpeak-Server)
|
||||
|
||||
set(CMAKE_VERBOSE_MAKEFILE ON)
|
||||
#--allow-multiple-definition
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fpermissive -Wall -Wno-reorder -Wno-sign-compare -static-libgcc -static-libstdc++ -g -Wl,-no-whole-archive,--no-undefined -pthread ${MEMORY_DEBUG_FLAGS}")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fpermissive -Wall -Wno-reorder -Wno-sign-compare -static-libgcc -static-libstdc++ -g -Wl,--no-whole-archive -pthread ${MEMORY_DEBUG_FLAGS} -Werror=return-type")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3")
|
||||
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -O3")
|
||||
|
||||
@@ -13,14 +13,14 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/environment/)
|
||||
|
||||
#disable for debug
|
||||
#add_definitions(-DRELEASE_MODE)
|
||||
find_event(ON)
|
||||
|
||||
include_directories(../music/include/)
|
||||
include_directories(../shared/src)
|
||||
include_directories(../license/src)
|
||||
include_directories(../MusicBot/src)
|
||||
include_directories(/usr/local/include/breakpad)
|
||||
include_directories(${LIBRARY_PATH}/tomcrypt/src/headers)
|
||||
include_directories(${LIBRARY_PATH}/spdlog/include)
|
||||
# include_directories(/usr/local/include/breakpad)
|
||||
# include_directories(${LIBRARY_PATH}/tomcrypt/src/headers)
|
||||
|
||||
add_definitions(-DLTM_DESC)
|
||||
add_definitions(-DMUSIC_BOT)
|
||||
@@ -33,281 +33,329 @@ add_definitions(-DUSE_BORINGSSL)
|
||||
option(BUILD_TYPE "Sets the build type" OFF)
|
||||
option(BUILD_TYPE_NAME "Sets the build type name" OFF)
|
||||
option(COMPILE_WEB_CLIENT "Enable/Disable the web cleint future" OFF)
|
||||
set(COMPILE_WEB_CLIENT "ON")
|
||||
#set(COMPILE_WEB_CLIENT "ON")
|
||||
|
||||
set(CMAKE_VERBOSE_MAKEFILE ON)
|
||||
set(SERVER_SOURCE_FILES
|
||||
main.cpp
|
||||
src/client/ConnectedClient.cpp
|
||||
src/client/voice/PrecomputedPuzzles.cpp
|
||||
src/client/voice/VoiceClient.cpp
|
||||
src/client/voice/VoiceClientHandschake.cpp
|
||||
src/client/voice/VoiceClientCommandHandler.cpp
|
||||
src/client/voice/VoiceClientPacketHandler.cpp
|
||||
src/client/voice/VoiceClientView.cpp
|
||||
src/TS3ServerClientManager.cpp
|
||||
src/TSServer.cpp
|
||||
src/TS3ServerHeartbeat.cpp
|
||||
src/SignalHandler.cpp
|
||||
src/server/VoiceServer.cpp
|
||||
src/server/POWHandler.cpp
|
||||
src/client/voice/VoiceClientConnection.cpp
|
||||
src/client/ConnectedClientCommandHandler.cpp
|
||||
src/client/ConnectedClientNotifyHandler.cpp
|
||||
src/ServerManager.cpp
|
||||
src/server/file/FileServer.cpp
|
||||
main.cpp
|
||||
tomcryptTest.cpp
|
||||
# Only needed for boringssl
|
||||
# MySQLLibSSLFix.c
|
||||
|
||||
src/client/ConnectedClient.cpp
|
||||
src/server/PrecomputedPuzzles.cpp
|
||||
src/client/voice/VoiceClient.cpp
|
||||
src/client/voice/VoiceClientHandschake.cpp
|
||||
src/client/voice/VoiceClientCommandHandler.cpp
|
||||
src/client/voice/VoiceClientConnectionPacketHandler.cpp
|
||||
src/client/voice/PacketStatistics.cpp
|
||||
src/TS3ServerClientManager.cpp
|
||||
src/VirtualServer.cpp
|
||||
src/FileServerHandler.cpp
|
||||
src/TS3ServerHeartbeat.cpp
|
||||
src/SignalHandler.cpp
|
||||
src/server/VoiceServer.cpp
|
||||
src/server/POWHandler.cpp
|
||||
src/client/voice/VoiceClientConnection.cpp
|
||||
#src/client/ConnectedClientCommandHandler.cpp
|
||||
src/client/command_handler/channel.cpp
|
||||
src/client/command_handler/client.cpp
|
||||
src/client/command_handler/server.cpp
|
||||
src/client/command_handler/misc.cpp
|
||||
src/client/command_handler/bulk_parsers.cpp
|
||||
|
||||
src/client/ConnectedClientNotifyHandler.cpp
|
||||
src/VirtualServerManager.cpp
|
||||
src/channel/ServerChannel.cpp
|
||||
src/channel/ClientChannelView.cpp
|
||||
src/client/file/FileClient.cpp
|
||||
src/client/file/FileClientIO.cpp
|
||||
src/Group.cpp
|
||||
src/manager/BanManager.cpp
|
||||
src/client/InternalClient.cpp
|
||||
#src/weblist/WeblistClient.cpp
|
||||
#src/weblist/WebList.cpp
|
||||
src/Group.cpp
|
||||
src/manager/BanManager.cpp
|
||||
src/client/InternalClient.cpp
|
||||
#src/weblist/WeblistClient.cpp
|
||||
#src/weblist/WebList.cpp
|
||||
|
||||
src/client/DataClient.cpp
|
||||
src/server/QueryServer.cpp
|
||||
src/client/query/QueryClient.cpp
|
||||
src/client/query/QueryClientCommands.cpp
|
||||
src/client/query/QueryClientNotify.cpp
|
||||
src/client/DataClient.cpp
|
||||
src/server/QueryServer.cpp
|
||||
src/client/query/QueryClient.cpp
|
||||
src/client/query/QueryClientCommands.cpp
|
||||
src/client/query/QueryClientNotify.cpp
|
||||
|
||||
src/manager/IpListManager.cpp
|
||||
|
||||
src/manager/IpListManager.cpp
|
||||
src/ConnectionStatistics.cpp
|
||||
|
||||
src/ConnectionStatistics.cpp
|
||||
src/manager/TokeManager.cpp
|
||||
|
||||
src/manager/TokeManager.cpp
|
||||
src/terminal/CommandHandler.cpp
|
||||
|
||||
src/terminal/CommandHandler.cpp
|
||||
src/manager/ComplainManager.cpp
|
||||
src/DatabaseHelper.cpp
|
||||
|
||||
src/manager/ComplainManager.cpp
|
||||
src/DatabaseHelper.cpp
|
||||
src/manager/LetterManager.cpp
|
||||
src/manager/PermissionNameManager.cpp
|
||||
|
||||
src/manager/LetterManager.cpp
|
||||
src/manager/PermissionNameManager.cpp
|
||||
src/ServerManagerSnapshot.cpp
|
||||
src/ServerManagerSnapshotDeploy.cpp
|
||||
src/client/music/Song.cpp
|
||||
src/music/PlayablePlaylist.cpp
|
||||
src/InstanceHandler.cpp
|
||||
src/InstanceHandlerSetup.cpp
|
||||
|
||||
tomcryptTest.cpp
|
||||
src/Configuration.cpp
|
||||
|
||||
src/pinteraction/ApplicationInteraction.cpp
|
||||
src/ServerManagerSnapshot.cpp
|
||||
src/ServerManagerSnapshotDeploy.cpp
|
||||
src/client/music/Song.cpp
|
||||
src/music/PlayablePlaylist.cpp
|
||||
src/InstanceHandler.cpp
|
||||
src/InstanceHandlerSetup.cpp
|
||||
src/build.cpp
|
||||
|
||||
src/Configuration.cpp
|
||||
src/music/MusicPlaylist.cpp
|
||||
src/client/music/MusicClient.cpp
|
||||
src/client/music/MusicClientPlayer.cpp
|
||||
src/client/ConnectedClientTextCommandHandler.cpp
|
||||
src/music/MusicBotManager.cpp
|
||||
src/client/music/internal_provider/channel_replay/ChannelProvider.cpp
|
||||
|
||||
src/build.cpp
|
||||
src/geo/GeoLocation.cpp
|
||||
src/geo/IP2Location.cpp
|
||||
src/geo/VPNBlocker.cpp
|
||||
|
||||
src/music/MusicPlaylist.cpp
|
||||
src/client/music/MusicClient.cpp
|
||||
src/client/music/MusicClientPlayer.cpp
|
||||
src/client/ConnectedClientTextCommandHandler.cpp
|
||||
src/music/MusicBotManager.cpp
|
||||
src/client/music/internal_provider/channel_replay/ChannelProvider.cpp
|
||||
src/client/query/XMacroEventTypes.h
|
||||
|
||||
src/geo/GeoLocation.cpp
|
||||
src/geo/IP2Location.cpp
|
||||
src/geo/VPNBlocker.cpp
|
||||
src/server/VoiceIOManager.cpp
|
||||
src/server/WebIoManager.cpp
|
||||
src/client/SpeakingClient.cpp
|
||||
|
||||
src/client/query/XMacroEventTypes.h
|
||||
../shared/src/ssl/SSLManager.cpp
|
||||
|
||||
src/server/VoiceIOManager.cpp
|
||||
src/server/WebIoManager.cpp
|
||||
src/client/SpeakingClient.cpp
|
||||
src/manager/SqlDataManager.cpp
|
||||
|
||||
src/lincense/LicenseHelper.cpp
|
||||
../shared/src/ssl/SSLManager.cpp
|
||||
src/ShutdownHelper.cpp
|
||||
src/client/music/MusicQueue.cpp
|
||||
src/lincense/TeamSpeakLicense.cpp
|
||||
|
||||
src/manager/SqlDataManager.cpp
|
||||
src/weblist/WebListManager.cpp
|
||||
src/weblist/TeamSpeakWebClient.cpp
|
||||
|
||||
src/ShutdownHelper.cpp
|
||||
src/client/music/MusicQueue.cpp
|
||||
src/lincense/TeamSpeakLicense.cpp
|
||||
src/snapshots/permission.cpp
|
||||
src/snapshots/client.cpp
|
||||
src/snapshots/channel.cpp
|
||||
src/snapshots/server.cpp
|
||||
src/snapshots/groups.cpp
|
||||
src/snapshots/deploy.cpp
|
||||
src/snapshots/music.cpp
|
||||
src/snapshots/parser.cpp
|
||||
|
||||
src/weblist/WebListManager.cpp
|
||||
src/weblist/TeamSpeakWebClient.cpp
|
||||
src/manager/ActionLogger.cpp
|
||||
src/manager/ActionLoggerImpl.cpp
|
||||
|
||||
src/manager/ConversationManager.cpp
|
||||
src/client/SpeakingClientHandshake.cpp
|
||||
src/client/command_handler/music.cpp src/client/command_handler/file.cpp
|
||||
|
||||
src/client/voice/PacketDecoder.cpp
|
||||
src/client/voice/PacketEncoder.cpp
|
||||
src/client/voice/ServerCommandExecutor.cpp
|
||||
src/client/voice/PingHandler.cpp
|
||||
src/client/voice/CryptSetupHandler.cpp
|
||||
|
||||
src/terminal/PipedTerminal.cpp
|
||||
|
||||
src/server/voice/UDPVoiceServer.cpp
|
||||
src/server/voice/DatagramPacket.cpp
|
||||
)
|
||||
if(COMPILE_WEB_CLIENT)
|
||||
add_definitions(-DCOMPILE_WEB_CLIENT)
|
||||
|
||||
set(SERVER_SOURCE_FILES
|
||||
${SERVER_SOURCE_FILES}
|
||||
if (COMPILE_WEB_CLIENT)
|
||||
add_definitions(-DCOMPILE_WEB_CLIENT)
|
||||
|
||||
src/server/WebServer.cpp
|
||||
src/client/web/WebClient.cpp
|
||||
# src/server/web/WebRTCServer.cpp
|
||||
src/client/web/WSWebClient.cpp
|
||||
src/client/web/SampleHandler.cpp
|
||||
src/client/SpeakingClientHandshake.cpp
|
||||
src/client/web/VoiceBridge.cpp
|
||||
)
|
||||
endif()
|
||||
set(SERVER_SOURCE_FILES
|
||||
${SERVER_SOURCE_FILES}
|
||||
|
||||
src/server/WebServer.cpp
|
||||
src/client/web/WebClient.cpp
|
||||
# src/server/web/WebRTCServer.cpp
|
||||
src/client/web/WSWebClient.cpp
|
||||
src/client/web/SampleHandler.cpp
|
||||
src/client/web/VoiceBridge.cpp
|
||||
src/client/command_handler/helpers.h src/music/PlaylistPermissions.cpp src/music/PlaylistPermissions.h src/lincense/LicenseService.cpp src/lincense/LicenseService.h)
|
||||
endif ()
|
||||
|
||||
add_executable(PermHelper helpers/permgen.cpp)
|
||||
target_link_libraries(PermHelper
|
||||
${LIBRARY_PATH_ED255}
|
||||
${LIBRARY_PATH_ED255}
|
||||
|
||||
TeaSpeak #Static
|
||||
TeaLicenseHelper #Static
|
||||
TeaMusic #Static
|
||||
${LIBRARY_PATH_THREAD_POOL} #Static
|
||||
${LIBRARY_PATH_TERMINAL} #Static
|
||||
${LIBRARY_PATH_VARIBALES}
|
||||
${LIBRARY_PATH_YAML}
|
||||
pthread
|
||||
stdc++fs
|
||||
${LIBEVENT_PATH}/libevent.a
|
||||
${LIBEVENT_PATH}/libevent_pthreads.a
|
||||
${LIBRARY_PATH_OPUS}
|
||||
${LIBRARY_PATH_JSON}
|
||||
${LIBRARY_PATH_PROTOBUF}
|
||||
TeaSpeak #Static
|
||||
TeaLicenseHelper #Static
|
||||
TeaMusic #Static
|
||||
${LIBRARY_PATH_THREAD_POOL} #Static
|
||||
${LIBRARY_PATH_TERMINAL} #Static
|
||||
${LIBRARY_PATH_VARIBALES}
|
||||
${LIBRARY_PATH_YAML}
|
||||
pthread
|
||||
stdc++fs
|
||||
${LIBEVENT_PATH}/libevent.a
|
||||
${LIBEVENT_PATH}/libevent_pthreads.a
|
||||
${LIBRARY_PATH_OPUS}
|
||||
${LIBRARY_PATH_JSON}
|
||||
${LIBRARY_PATH_PROTOBUF}
|
||||
|
||||
#${LIBWEBRTC_LIBRARIES} #ATTENTIAN! WebRTC does not work with crypto! (Already contains a crypto version)
|
||||
${LIBRARY_TOM_CRYPT}
|
||||
${LIBRARY_TOM_MATH}
|
||||
#${LIBWEBRTC_LIBRARIES} #ATTENTIAN! WebRTC does not work with crypto! (Already contains a crypto version)
|
||||
${LIBRARY_TOM_CRYPT}
|
||||
${LIBRARY_TOM_MATH}
|
||||
|
||||
#We're forsed to use boringssl caused by the fact that boringssl is already within webrtc!
|
||||
#We're forsed to use boringssl caused by the fact that boringssl is already within webrtc!
|
||||
|
||||
#Require a so
|
||||
sqlite3
|
||||
#Require a so
|
||||
sqlite3
|
||||
|
||||
${LIBRARY_PATH_BREAKPAD}
|
||||
${LIBRARY_PATH_JDBC}
|
||||
${LIBRARY_PATH_PROTOBUF}
|
||||
${LIBRARY_PATH_BREAKPAD}
|
||||
${LIBRARY_PATH_JDBC}
|
||||
${LIBRARY_PATH_PROTOBUF}
|
||||
|
||||
${LIBRARY_PATH_DATA_PIPES}
|
||||
${LIBRARY_PATH_BORINGSSL_SSL}
|
||||
${LIBRARY_PATH_BORINGSSL_CRYPTO}
|
||||
dl
|
||||
jemalloc
|
||||
)
|
||||
${LIBRARY_PATH_DATA_PIPES}
|
||||
${LIBRARY_PATH_BORINGSSL_SSL}
|
||||
${LIBRARY_PATH_BORINGSSL_CRYPTO}
|
||||
dl
|
||||
jemalloc
|
||||
)
|
||||
|
||||
add_executable(PermMapHelper helpers/PermMapGen.cpp)
|
||||
target_link_libraries(PermMapHelper
|
||||
${LIBRARY_PATH_ED255}
|
||||
${LIBRARY_PATH_ED255}
|
||||
|
||||
TeaSpeak #Static
|
||||
TeaLicenseHelper #Static
|
||||
TeaMusic #Static
|
||||
${LIBRARY_PATH_THREAD_POOL} #Static
|
||||
${LIBRARY_PATH_TERMINAL} #Static
|
||||
${LIBRARY_PATH_VARIBALES}
|
||||
${LIBRARY_PATH_YAML}
|
||||
pthread
|
||||
stdc++fs
|
||||
${LIBEVENT_PATH}/libevent.a
|
||||
${LIBEVENT_PATH}/libevent_pthreads.a
|
||||
${LIBRARY_PATH_OPUS}
|
||||
${LIBRARY_PATH_JSON}
|
||||
${LIBRARY_PATH_PROTOBUF}
|
||||
TeaSpeak #Static
|
||||
TeaLicenseHelper #Static
|
||||
TeaMusic #Static
|
||||
${LIBRARY_PATH_THREAD_POOL} #Static
|
||||
${LIBRARY_PATH_TERMINAL} #Static
|
||||
${LIBRARY_PATH_VARIBALES}
|
||||
${LIBRARY_PATH_YAML}
|
||||
pthread
|
||||
stdc++fs
|
||||
${LIBEVENT_PATH}/libevent.a
|
||||
${LIBEVENT_PATH}/libevent_pthreads.a
|
||||
${LIBRARY_PATH_OPUS}
|
||||
${LIBRARY_PATH_JSON}
|
||||
${LIBRARY_PATH_PROTOBUF}
|
||||
|
||||
#${LIBWEBRTC_LIBRARIES} #ATTENTIAN! WebRTC does not work with crypto! (Already contains a crypto version)
|
||||
${LIBRARY_TOM_CRYPT}
|
||||
${LIBRARY_TOM_MATH}
|
||||
#${LIBWEBRTC_LIBRARIES} #ATTENTIAN! WebRTC does not work with crypto! (Already contains a crypto version)
|
||||
${LIBRARY_TOM_CRYPT}
|
||||
${LIBRARY_TOM_MATH}
|
||||
|
||||
#We're forsed to use boringssl caused by the fact that boringssl is already within webrtc!
|
||||
#We're forsed to use boringssl caused by the fact that boringssl is already within webrtc!
|
||||
|
||||
#Require a so
|
||||
sqlite3
|
||||
#Require a so
|
||||
sqlite3
|
||||
|
||||
${LIBRARY_PATH_BREAKPAD}
|
||||
${LIBRARY_PATH_JDBC}
|
||||
${LIBRARY_PATH_PROTOBUF}
|
||||
${LIBRARY_PATH_BREAKPAD}
|
||||
${LIBRARY_PATH_JDBC}
|
||||
${LIBRARY_PATH_PROTOBUF}
|
||||
|
||||
${LIBRARY_PATH_DATA_PIPES}
|
||||
${LIBRARY_PATH_BORINGSSL_SSL}
|
||||
${LIBRARY_PATH_BORINGSSL_CRYPTO}
|
||||
dl
|
||||
jemalloc
|
||||
)
|
||||
${LIBRARY_PATH_DATA_PIPES}
|
||||
${LIBRARY_PATH_BORINGSSL_SSL}
|
||||
${LIBRARY_PATH_BORINGSSL_CRYPTO}
|
||||
dl
|
||||
jemalloc
|
||||
)
|
||||
|
||||
|
||||
SET(CPACK_PACKAGE_VERSION_MAJOR "1")
|
||||
SET(CPACK_PACKAGE_VERSION_MINOR "3")
|
||||
SET(CPACK_PACKAGE_VERSION_PATCH "26")
|
||||
if(BUILD_TYPE_NAME EQUAL OFF)
|
||||
SET(CPACK_PACKAGE_VERSION_DATA "beta")
|
||||
elseif(BUILD_TYPE_NAME STREQUAL "")
|
||||
SET(CPACK_PACKAGE_VERSION_DATA "")
|
||||
else()
|
||||
SET(CPACK_PACKAGE_VERSION_DATA "-${BUILD_TYPE_NAME}")
|
||||
endif()
|
||||
if(BUILD_TYPE EQUAL OFF)
|
||||
SET(BUILD_TYPE "1")
|
||||
endif()
|
||||
SET(CPACK_PACKAGE_VERSION_MINOR "4")
|
||||
SET(CPACK_PACKAGE_VERSION_PATCH "22")
|
||||
if (BUILD_TYPE_NAME EQUAL OFF)
|
||||
SET(CPACK_PACKAGE_VERSION_DATA "beta")
|
||||
elseif (BUILD_TYPE_NAME STREQUAL "")
|
||||
SET(CPACK_PACKAGE_VERSION_DATA "")
|
||||
else ()
|
||||
SET(CPACK_PACKAGE_VERSION_DATA "-${BUILD_TYPE_NAME}")
|
||||
endif ()
|
||||
if (NOT BUILD_TYPE AND NOT BUILD_TYPE STREQUAL "0")
|
||||
SET(BUILD_TYPE "3")
|
||||
endif ()
|
||||
set_source_files_properties(src/build.cpp PROPERTIES
|
||||
COMPILE_FLAGS "-DBUILD_MAJOR=${CPACK_PACKAGE_VERSION_MAJOR} -DBUILD_MINOR=${CPACK_PACKAGE_VERSION_MINOR} -DBUILD_PATCH=${CPACK_PACKAGE_VERSION_PATCH} -DBUILD_DATA=\"${CPACK_PACKAGE_VERSION_DATA}\" -DBUILD_TYPE=${BUILD_TYPE} -DBUILD_COUNT=0")
|
||||
COMPILE_FLAGS "-DBUILD_MAJOR=${CPACK_PACKAGE_VERSION_MAJOR} -DBUILD_MINOR=${CPACK_PACKAGE_VERSION_MINOR} -DBUILD_PATCH=${CPACK_PACKAGE_VERSION_PATCH} -DBUILD_DATA=\"${CPACK_PACKAGE_VERSION_DATA}\" -DBUILD_TYPE=${BUILD_TYPE} -DBUILD_COUNT=0")
|
||||
file(WRITE repro/env/buildVersion.txt "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}${CPACK_PACKAGE_VERSION_DATA}")
|
||||
|
||||
add_executable(TeaSpeakServer ${SERVER_SOURCE_FILES})
|
||||
target_link_libraries(TeaSpeakServer
|
||||
${LIBRARY_PATH_THREAD_POOL} #Static
|
||||
TeaSpeak #Static
|
||||
TeaLicenseHelper #Static
|
||||
TeaMusic #Static
|
||||
${LIBRARY_PATH_TERMINAL} #Static
|
||||
${LIBRARY_PATH_VARIBALES}
|
||||
${LIBRARY_PATH_YAML}
|
||||
pthread
|
||||
stdc++fs
|
||||
${LIBEVENT_PATH}/libevent.a
|
||||
${LIBEVENT_PATH}/libevent_pthreads.a
|
||||
${LIBRARY_PATH_OPUS}
|
||||
${LIBRARY_PATH_JSON}
|
||||
${LIBRARY_PATH_PROTOBUF}
|
||||
threadpool::static #Static
|
||||
TeaSpeak #Static
|
||||
TeaLicenseHelper #Static
|
||||
TeaMusic #Static
|
||||
CXXTerminal::static #Static
|
||||
TeaSpeak-FileServer
|
||||
${StringVariable_LIBRARIES_STATIC}
|
||||
${YAML_CPP_LIBRARIES}
|
||||
pthread
|
||||
stdc++fs
|
||||
libevent::core libevent::pthreads
|
||||
opus::static
|
||||
yaml-cpp
|
||||
|
||||
#We're forsed to use boringssl caused by the fact that boringssl is already within webrtc!
|
||||
#Require a so
|
||||
sqlite3
|
||||
DataPipes::rtc::shared
|
||||
|
||||
#Require a so
|
||||
sqlite3
|
||||
breakpad::static
|
||||
protobuf::libprotobuf
|
||||
jemalloc::shared
|
||||
|
||||
${LIBRARY_PATH_BREAKPAD}
|
||||
${LIBRARY_PATH_PROTOBUF}
|
||||
tomcrypt::static
|
||||
tommath::static
|
||||
|
||||
#${LIBWEBRTC_LIBRARIES} #ATTENTIAN! WebRTC does not work with crypto! (Already contains a crypto version)
|
||||
${LIBRARY_TOM_CRYPT}
|
||||
${LIBRARY_TOM_MATH}
|
||||
|
||||
mysqlclient.a
|
||||
z
|
||||
|
||||
${LIBRARY_PATH_ED255}
|
||||
jsoncpp_lib
|
||||
${ed25519_LIBRARIES_STATIC}
|
||||
zstd::libzstd_static
|
||||
)
|
||||
|
||||
if(${COMPILE_WEB_CLIENT})
|
||||
find_package(LibNice REQUIRED)
|
||||
find_package(UsrSCTP REQUIRED)
|
||||
target_link_libraries(TeaSpeakServer
|
||||
LibNice::LibNice
|
||||
${LIBRARY_PATH_DATA_PIPES}
|
||||
)
|
||||
endif()
|
||||
include_directories(${LIBRARY_PATH}/boringssl/include/)
|
||||
if (COMPILE_WEB_CLIENT)
|
||||
file(GLOB GLIB20_ARCHS ${glib20_DIR}/lib/*)
|
||||
list(LENGTH GLIB20_ARCHS GLIB20_ARCHS_LENGTH)
|
||||
if (NOT ${GLIB20_ARCHS_LENGTH} EQUAL 1)
|
||||
message(FATAL_ERROR "Missing arch specific folder for glib2.0 in ${glib20_DIR}. Found ${GLIB20_ARCHS_LENGTH} directories, expected 1.")
|
||||
endif ()
|
||||
list(GET GLIB20_ARCHS 0 GLIB20_ARCH_DIR)
|
||||
target_link_libraries(TeaSpeakServer ${GLIB20_ARCH_DIR}/libffi.so.7 ${nice_DIR}/lib/libnice.so.10)
|
||||
endif ()
|
||||
|
||||
# include_directories(${LIBRARY_PATH}/boringssl/include/)
|
||||
target_link_libraries(TeaSpeakServer
|
||||
${LIBRARY_PATH_BORINGSSL_SSL}
|
||||
${LIBRARY_PATH_BORINGSSL_CRYPTO}
|
||||
dl
|
||||
openssl::ssl::shared
|
||||
openssl::crypto::shared
|
||||
dl
|
||||
z
|
||||
rt # For clock_gettime
|
||||
)
|
||||
|
||||
#check_include_file(mysql.h HAVE_MYSQL_MYSQL_H)
|
||||
#if (NOT HAVE_MYSQL_MYSQL_H)
|
||||
# check_include_file(mysql.h HAVE_MYSQL_H)
|
||||
# if (NOT HAVE_MYSQL_H)
|
||||
# message(FATAL_ERROR "Missing MySQL header")
|
||||
# endif ()
|
||||
#endif ()
|
||||
|
||||
set(DISABLE_JEMALLOC ON)
|
||||
if(NOT DISABLE_JEMALLOC)
|
||||
target_link_libraries(TeaSpeakServer
|
||||
jemalloc
|
||||
)
|
||||
add_definitions(-DHAVE_JEMALLOC)
|
||||
endif()
|
||||
if (NOT DISABLE_JEMALLOC)
|
||||
target_link_libraries(TeaSpeakServer
|
||||
jemalloc
|
||||
)
|
||||
add_definitions(-DHAVE_JEMALLOC)
|
||||
endif ()
|
||||
|
||||
#Fix RPATH
|
||||
#patchelf --set-rpath ./libs/ TeaSpeakServer
|
||||
#patchelf --remove-rpath TeaSpeakServer
|
||||
|
||||
#add_custom_command(
|
||||
# TARGET TeaSpeakServer
|
||||
## COMMAND bash -c "patchelf --set-rpath ./libs/ ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}TeaSpeakServer"
|
||||
# COMMAND bash -c "patchelf --remove-rpath ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}TeaSpeakServer"
|
||||
# WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
# COMMENT "Cleaning RPATH"
|
||||
#)
|
||||
add_executable(Snapshots-Permissions-Test src/snapshots/permission.cpp tests/snapshots/permission.cpp)
|
||||
target_link_libraries(Snapshots-Permissions-Test PUBLIC
|
||||
TeaSpeak
|
||||
CXXTerminal::static #Static
|
||||
${StringVariable_LIBRARIES_STATIC}
|
||||
${YAML_CPP_LIBRARIES}
|
||||
pthread
|
||||
stdc++fs
|
||||
libevent::core libevent::pthreads
|
||||
|
||||
#Require a so
|
||||
sqlite3
|
||||
DataPipes::rtc::shared
|
||||
|
||||
tomcrypt::static
|
||||
tommath::static
|
||||
${glib20_DIR}/lib/x86_64-linux-gnu/libffi.so.7 ${nice_DIR}/lib/libnice.so.10
|
||||
)
|
||||
target_include_directories(Snapshots-Permissions-Test PUBLIC ${CMAKE_SOURCE_DIR}/server/src/)
|
||||
@@ -3,3 +3,8 @@ Build opus:
|
||||
|
||||
Build libevent 2.1.8 (with -fPIC)
|
||||
|
||||
|
||||
|
||||
Local licenses test:
|
||||
Base: AQDm4ty6AAAAACnNuqvy++NGfMJU+Bvo5412Wi5jM5/JmmYyDbcxWKNaJV0FF1jpXtSrss3Gm7RUEQ6CdEEhPdyH5OLcugAAAAAp9cb+t1r6P5zGt9fi+QbAOroWM7LqY0B+iy055XrZxX+r8bfP50esuU9qypU9DUtnE9+qJ2gZmOgHcVkHJ4JhN4Db2ZmOkA4dpjGWfY2L6QJanPCy6HsVOm1/1LasZv9Om1/4uWHYLfab0n2+FNajxbcyDY3FAI6+rLBlQzGErtE=
|
||||
New: AQCJON26AAAAACnNuqvy++NGfMJU+Bvo5412Wi5jM5/JmmYyDbcxWKNaJV0FF1jpXtSrss3Gm7RUEQ6CdEEhPdyHizjdugAAAAAp6d+xUgRteE54jqv3OXoRS946xsWKUtJBFkB4pPMT1+pIrXhM3/CTWjpXG8KDLoa1DEjwguolTXJ+RCvySR7Bd4Db2ZmOkA4dpjGWfY2L6QJ3bxZH8OTPFlV/1lU9x1r5QPPOpFmZt68h6VZDz4y9AVNXO+JZPHu24Pre7hTnyhg=
|
||||
@@ -0,0 +1,108 @@
|
||||
#include <openssl/aes.h>
|
||||
#include <openssl/cipher.h>
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
const EVP_CIPHER *EVP_aes_128_cfb1(void){ return 0; }
|
||||
const EVP_CIPHER *EVP_aes_192_cfb1(void){ return 0; }
|
||||
const EVP_CIPHER *EVP_aes_256_cfb1(void){ return 0; }
|
||||
|
||||
const EVP_CIPHER *EVP_aes_128_cfb8(void){ return 0; }
|
||||
const EVP_CIPHER *EVP_aes_192_cfb8(void){ return 0; }
|
||||
const EVP_CIPHER *EVP_aes_256_cfb8(void){ return 0; }
|
||||
|
||||
const EVP_CIPHER *EVP_aes_128_cfb128(void){ return 0; }
|
||||
const EVP_CIPHER *EVP_aes_192_cfb128(void){ return 0; }
|
||||
const EVP_CIPHER *EVP_aes_256_cfb128(void){ return 0; }
|
||||
|
||||
int EVP_EncryptFinal(EVP_CIPHER_CTX *ctx, uint8_t *out, int *out_len) {
|
||||
return EVP_EncryptFinal_ex(ctx, out, out_len);
|
||||
}
|
||||
|
||||
int EVP_DecryptFinal(EVP_CIPHER_CTX *ctx, unsigned char *out, int *out_len) {
|
||||
return EVP_DecryptFinal_ex(ctx, out, out_len);
|
||||
}
|
||||
|
||||
int SSL_CTX_set_ciphersuites(SSL_CTX *ctx, const char *str) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define DTLSv1_get_timeout DTLSv1_get_timeout
|
||||
#define DTLSv1_handle_timeout DTLSv1_handle_timeout
|
||||
#define SSL_CTX_add0_chain_cert SSL_CTX_add0_chain_cert
|
||||
#define SSL_CTX_add1_chain_cert SSL_CTX_add1_chain_cert
|
||||
#define SSL_CTX_add_extra_chain_cert SSL_CTX_add_extra_chain_cert
|
||||
#define SSL_CTX_clear_extra_chain_certs SSL_CTX_clear_extra_chain_certs
|
||||
#define SSL_CTX_clear_chain_certs SSL_CTX_clear_chain_certs
|
||||
#define SSL_CTX_clear_mode SSL_CTX_clear_mode
|
||||
#define SSL_CTX_clear_options SSL_CTX_clear_options
|
||||
#define SSL_CTX_get0_chain_certs SSL_CTX_get0_chain_certs
|
||||
#define SSL_CTX_get_extra_chain_certs SSL_CTX_get_extra_chain_certs
|
||||
#define SSL_CTX_get_max_cert_list SSL_CTX_get_max_cert_list
|
||||
#define SSL_CTX_get_mode SSL_CTX_get_mode
|
||||
#define SSL_CTX_get_options SSL_CTX_get_options
|
||||
#define SSL_CTX_get_read_ahead SSL_CTX_get_read_ahead
|
||||
#define SSL_CTX_get_session_cache_mode SSL_CTX_get_session_cache_mode
|
||||
#define SSL_CTX_get_tlsext_ticket_keys SSL_CTX_get_tlsext_ticket_keys
|
||||
#define SSL_CTX_need_tmp_RSA SSL_CTX_need_tmp_RSA
|
||||
#define SSL_CTX_sess_get_cache_size SSL_CTX_sess_get_cache_size
|
||||
#define SSL_CTX_sess_number SSL_CTX_sess_number
|
||||
#define SSL_CTX_sess_set_cache_size SSL_CTX_sess_set_cache_size
|
||||
#define SSL_CTX_set0_chain SSL_CTX_set0_chain
|
||||
#define SSL_CTX_set1_chain SSL_CTX_set1_chain
|
||||
#define SSL_CTX_set1_curves SSL_CTX_set1_curves
|
||||
#define SSL_CTX_set_max_cert_list SSL_CTX_set_max_cert_list
|
||||
#define SSL_CTX_set_max_send_fragment SSL_CTX_set_max_send_fragment
|
||||
#define SSL_CTX_set_mode SSL_CTX_set_mode
|
||||
#define SSL_CTX_set_msg_callback_arg SSL_CTX_set_msg_callback_arg
|
||||
#define SSL_CTX_set_options SSL_CTX_set_options
|
||||
#define SSL_CTX_set_read_ahead SSL_CTX_set_read_ahead
|
||||
#define SSL_CTX_set_session_cache_mode SSL_CTX_set_session_cache_mode
|
||||
#define SSL_CTX_set_tlsext_servername_arg SSL_CTX_set_tlsext_servername_arg
|
||||
#define SSL_CTX_set_tlsext_servername_callback \
|
||||
SSL_CTX_set_tlsext_servername_callback
|
||||
#define SSL_CTX_set_tlsext_ticket_key_cb SSL_CTX_set_tlsext_ticket_key_cb
|
||||
#define SSL_CTX_set_tlsext_ticket_keys SSL_CTX_set_tlsext_ticket_keys
|
||||
#define SSL_CTX_set_tmp_dh SSL_CTX_set_tmp_dh
|
||||
#define SSL_CTX_set_tmp_ecdh SSL_CTX_set_tmp_ecdh
|
||||
#define SSL_CTX_set_tmp_rsa SSL_CTX_set_tmp_rsa
|
||||
#define SSL_add0_chain_cert SSL_add0_chain_cert
|
||||
#define SSL_add1_chain_cert SSL_add1_chain_cert
|
||||
#define SSL_clear_chain_certs SSL_clear_chain_certs
|
||||
#define SSL_clear_mode SSL_clear_mode
|
||||
#define SSL_clear_options SSL_clear_options
|
||||
#define SSL_get0_certificate_types SSL_get0_certificate_types
|
||||
#define SSL_get0_chain_certs SSL_get0_chain_certs
|
||||
#define SSL_get_max_cert_list SSL_get_max_cert_list
|
||||
#define SSL_get_mode SSL_get_mode
|
||||
#define SSL_get_options SSL_get_options
|
||||
#define SSL_get_secure_renegotiation_support \
|
||||
SSL_get_secure_renegotiation_support
|
||||
#define SSL_need_tmp_RSA SSL_need_tmp_RSA
|
||||
#define SSL_num_renegotiations SSL_num_renegotiations
|
||||
#define SSL_session_reused SSL_session_reused
|
||||
#define SSL_set0_chain SSL_set0_chain
|
||||
#define SSL_set1_chain SSL_set1_chain
|
||||
#define SSL_set1_curves SSL_set1_curves
|
||||
#define SSL_set_max_cert_list SSL_set_max_cert_list
|
||||
#define SSL_set_max_send_fragment SSL_set_max_send_fragment
|
||||
#define SSL_set_mode SSL_set_mode
|
||||
#define SSL_set_msg_callback_arg SSL_set_msg_callback_arg
|
||||
#define SSL_set_mtu SSL_set_mtu
|
||||
#define SSL_set_options SSL_set_options
|
||||
#define SSL_set_tlsext_host_name SSL_set_tlsext_host_name
|
||||
#define SSL_set_tmp_dh SSL_set_tmp_dh
|
||||
#define SSL_set_tmp_ecdh SSL_set_tmp_ecdh
|
||||
#define SSL_set_tmp_rsa SSL_set_tmp_rsa
|
||||
#define SSL_total_renegotiations SSL_total_renegotiations
|
||||
|
||||
long SSL_CTX_ctrl(SSL_CTX *ctx, int cmd, long larg, void *parg) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
};
|
||||
#endif
|
||||
@@ -1,11 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if [ -e "build.data" ]; then
|
||||
echo "File exists"
|
||||
DATA=$(cat "build.data")
|
||||
echo "File exists"
|
||||
DATA=$(cat "build.data")
|
||||
else
|
||||
echo "Create new file"
|
||||
echo "0" > "build.data"
|
||||
echo "Create new file"
|
||||
echo "0" > "build.data"
|
||||
fi
|
||||
|
||||
DATA=$(($DATA+1))
|
||||
|
||||
Executable
+114
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#WARNING: Any spaces within the path will cause trouble!
|
||||
|
||||
#ldd -d
|
||||
if [[ ! -f "$1" ]]; then
|
||||
echo "Missing target file ($1)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# This is a multidimensional array
|
||||
# key /* library file */ => (library name; library file; dependencies ())
|
||||
declare -A collected_libraries
|
||||
|
||||
# This is a one dimensional array
|
||||
# key /* library file */ => use count
|
||||
declare -A collected_libraries_use_count
|
||||
|
||||
declare -A libraries_of_result
|
||||
function libraries_of {
|
||||
local buffer
|
||||
local index
|
||||
local data
|
||||
|
||||
buffer=$(ldd -d "$1")
|
||||
index=0
|
||||
|
||||
libraries_of_result=()
|
||||
|
||||
IFS=$'\n'
|
||||
for line in ${buffer}; do
|
||||
index=$(($index + 1))
|
||||
[[ ${index} == 1 ]] && continue
|
||||
IFS=$' ' data=(${line})
|
||||
|
||||
# We trim the leading and tailing white spaces
|
||||
_key=$(echo "${data[0]}" | sed -e 's/^[[:space:]]*//')
|
||||
_value=$(echo "${data[2]}" | sed -e 's/^[[:space:]]*//')
|
||||
libraries_of_result["${_key}"]="${_value}"
|
||||
done
|
||||
|
||||
[[ $? -ne 0 ]] && return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
function print_lookup_stack {
|
||||
index_max=${#@}
|
||||
index_args="$@"
|
||||
|
||||
#echo -n -e "\r\033[K"
|
||||
echo ""
|
||||
for (( index = 0; index < $index_max; ++index )); do
|
||||
arg=$(eval echo \$$((${index} + 1)))
|
||||
echo -n $(basename ${arg})
|
||||
[[ $(($index + 1)) -lt ${index_max} ]] && echo -n " => "
|
||||
done
|
||||
#sleep 1
|
||||
}
|
||||
|
||||
declare -a libraries_of_deep_stack
|
||||
function libraries_of_deep {
|
||||
local IFS
|
||||
local valid_libraries
|
||||
local inner_array
|
||||
local result_array
|
||||
|
||||
libraries_of $1
|
||||
|
||||
valid_libraries=()
|
||||
#echo "Gathered libraries for $1:"
|
||||
for library_name in "${!libraries_of_result[@]}"; do
|
||||
[[ -z "${libraries_of_result[$library_name]}" ]] && {
|
||||
#echo " The dependency $library_name for $1 could not be resolved"
|
||||
continue
|
||||
}
|
||||
|
||||
#echo " $library_name at ${libraries_of_result[$library_name]}";
|
||||
valid_libraries+=("${libraries_of_result[$library_name]}")
|
||||
done
|
||||
|
||||
IFS=$';' inner_array="${valid_libraries[*]}"
|
||||
IFS=$' ' result_array=("$(basename $1)" "$1" "$inner_array")
|
||||
collected_libraries[$1]="${result_array[@]}"
|
||||
|
||||
libraries_of_deep_stack+=($1)
|
||||
print_lookup_stack ${libraries_of_deep_stack[@]}
|
||||
|
||||
for library_path in "${valid_libraries[@]}"; do
|
||||
# echo "Looking up library path $library_path"
|
||||
[[ ! -z "${collected_libraries[$library_path]}" ]] && {
|
||||
#echo "Library $library_path already resolved"
|
||||
collected_libraries_use_count[$library_path]=$((${collected_libraries_use_count[$library_path]} + 1))
|
||||
continue
|
||||
}
|
||||
#echo "Resolving libraries for path $library_path"
|
||||
collected_libraries_use_count[$library_path]=1
|
||||
libraries_of_deep ${library_path}
|
||||
#library_name
|
||||
done
|
||||
unset 'libraries_of_deep_stack[${#libraries_of_deep_stack[@]}-1]';
|
||||
}
|
||||
|
||||
libraries_of_deep $1
|
||||
echo -e -n "\r\033[K" #Clear the stack
|
||||
|
||||
for key in "${!collected_libraries[@]}"; do
|
||||
IFS=$' ' library_data=(${collected_libraries[$key]})
|
||||
IFS=$';' libraries=(${library_data[2]})
|
||||
|
||||
echo "Got library ${library_data[0]} (${library_data[1]}) directly used ${collected_libraries_use_count[$key]} times:"
|
||||
for library in "${libraries[@]}"; do
|
||||
echo " $library"
|
||||
done
|
||||
done
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user