355 Commits

Author SHA1 Message Date
WolverinDEV b54c6d4e04 Some minor updates 2021-07-23 14:59:44 +02:00
WolverinDEV 1e7738bd24 Fixed missing join permission recalculation after server group update 2021-05-26 13:48:42 +02:00
WolverinDEV bfeee49507 Small improvement of permission performance 2021-05-26 12:39:55 +02:00
WolverinDEV 03bad9e6f5 Using proper permission inheritance 2021-05-26 12:32:03 +02:00
WolverinDEV f059e6b3c8 Fixed a query rename bug 2021-05-26 11:49:07 +02:00
WolverinDEV 053eb1a64e Updated the unsupported message 2021-05-23 13:41:57 +02:00
WolverinDEV fefe550805 Fixed a client disconnect bug 2021-05-19 11:42:10 +02:00
WolverinDEV 521b6fa111 Reverted the change with the no permission checking for server group assignments 2021-05-17 22:54:57 +02:00
WolverinDEV bebcb9bfb2 Removed a stupid permission check 2021-05-15 12:40:14 +02:00
WolverinDEV 92fcfbecca Bumped version 2021-05-09 18:29:49 +02:00
WolverinDEV a888b3aff7 Fixed some bugs 2021-05-09 18:28:27 +02:00
WolverinDEV fb28e613cd Fixed error spam and a memory leak 2021-05-03 10:42:04 +02:00
WolverinDEV 707ede7a6f Updated revs 2021-04-28 18:53:30 +02:00
WolverinDEV 19b7a9702a Sending an error for TS3 clients even though they didn't send a return code 2021-04-28 18:51:05 +02:00
WolverinDEV 7753c95906 Fixed permissions 2021-04-28 18:43:30 +02:00
WolverinDEV cdf8aa1d69 Updated revs 2021-04-28 18:32:16 +02:00
WolverinDEV 25d57ce825 Properly counting group name lengths and fixed a crash on server create when the group creation process failed 2021-04-28 18:32:00 +02:00
WolverinDEV 8710f0c31b Updated revs 2021-04-27 22:36:42 +02:00
WolverinDEV ae4f890574 Fixed some bugs 2021-04-27 22:35:15 +02:00
WolverinDEV 03312b89c7 Releaxed clientinit values 2021-04-27 15:50:08 +02:00
WolverinDEV 0aad154c15 Some minor updates 2021-04-27 15:46:19 +02:00
WolverinDEV bc2c951dde Adding support for the video broadcaster to trace his followers 2021-04-26 15:14:09 +02:00
WolverinDEV 89f4ed05b2 Fixed the missing file SSL option 2021-04-24 13:00:59 +02:00
WolverinDEV 6631cd26c5 Fixed the client badges 2021-04-23 11:16:27 +02:00
WolverinDEV 356687b0be Fixed a crash which could cause a double free call 2021-04-23 11:11:41 +02:00
WolverinDEV 05f834b47d Some minor optimizations 2021-04-22 10:50:48 +02:00
WolverinDEV 03a0cec60e Don't write shutdown crashes 2021-04-22 00:17:51 +02:00
WolverinDEV e99c1b442a Some minor changes 2021-04-22 00:14:35 +02:00
WolverinDEV f7039187b2 Starting attempt to implement the new rust time transfer server 2021-04-21 13:19:46 +02:00
WolverinDEV 45229e1e8e Fixed bug which prevented clients from kicking client from channel 2021-04-18 21:36:35 +02:00
WolverinDEV db8e232bfa Fixed recurring tasks sometimes getting totally dropped when canceling a recurring task 2021-04-18 21:20:12 +02:00
WolverinDEV 08cd7e9d9e Fixed disconnect due to auto update 2021-04-17 00:17:31 +02:00
WolverinDEV 31fd33513f Fixed a crash on startup 2021-04-17 00:06:28 +02:00
WolverinDEV 2c66e9022f Fixed the whole ed255 stuff 2021-04-15 22:43:48 +02:00
WolverinDEV 1e1227773a Bumped the client version 2021-04-15 19:14:57 +02:00
WolverinDEV 9428c411ae Some minor compile warning fixing 2021-04-15 19:14:21 +02:00
WolverinDEV 63eccff578 Removed the old voice io 2021-04-15 17:34:44 +02:00
WolverinDEV cd5804125d Reworked the voice IP part 2021-04-15 17:14:47 +02:00
WolverinDEV abeeae4ed5 Using one global event loop for the query and web client 2021-04-15 12:54:52 +02:00
WolverinDEV 6e2e005ed7 Heavily improving the clients join and leave performance 2021-04-15 03:28:08 +02:00
WolverinDEV f41cfb8c30 Some general code cleanup 2021-04-14 23:12:51 +02:00
WolverinDEV 25b454ad0e Fixed the query client view power 2021-04-14 16:00:02 +02:00
WolverinDEV 6b774d3a80 Some code refactoring 2021-04-14 14:57:04 +02:00
WolverinDEV bf643b4797 Fixed the default channel group assignment 2021-04-12 19:12:02 +02:00
WolverinDEV 3aa50a2c1a Fixed channel update 2021-03-23 17:26:16 +01:00
WolverinDEV f410c9d467 Fixed typo 2021-03-22 20:52:44 +01:00
WolverinDEV d6ec52b25b Fixed query permission calculation 2021-03-22 20:46:19 +01:00
WolverinDEV 968cd7172b Fixed another small compile error 2021-03-22 20:23:34 +01:00
WolverinDEV ea0f75756d Fixed compile errors 2021-03-22 20:20:36 +01:00
WolverinDEV 90fcc074f2 Fixed guest server queries 2021-03-22 20:14:15 +01:00
WolverinDEV 41f5b30f56 Fixed some bugs related to the new server setup 2021-03-22 18:43:00 +01:00
WolverinDEV ceb77a2c85 SOme updates 2021-03-21 23:51:37 +01:00
WolverinDEV fb517f0b72 Removed the sanitizer 2021-03-11 14:41:19 +01:00
WolverinDEV 82aa59fdf9 Removed old junk 2021-03-11 14:35:15 +01:00
WolverinDEV 5991e5f4ad Finalizing the new group manager 2021-03-11 14:12:12 +01:00
WolverinDEV 061ce4c284 Fixed missing header 2021-03-10 20:33:47 +01:00
WolverinDEV 7604b03f02 Fixed the license upgrades 2021-03-10 20:31:25 +01:00
WolverinDEV 5dd3fd3e13 Merge branch '1.5.0' into new-groups 2021-03-08 13:00:17 +01:00
WolverinDEV 92a3faaed7 Some small bug fixes 2021-03-07 21:25:55 +01:00
WolverinDEV 2d0b9da08d Some more work for the new group manager 2021-03-07 19:17:20 +01:00
WolverinDEV 449df23a4b Merge branch '1.5.0' into new-groups
# Conflicts:
#	server/src/Group.h
#	server/src/InstanceHandler.cpp
#	server/src/InstanceHandler.h
#	server/src/VirtualServer.cpp
#	server/src/client/DataClient.cpp
#	server/src/client/DataClient.h
2021-03-01 19:22:15 +01:00
WolverinDEV f213b199fc Updated the git teaspeak rev 2021-03-01 14:54:01 +01:00
WolverinDEV c8438c5867 Fixed some compile errors 2021-03-01 14:50:57 +01:00
WolverinDEV db71461646 Using property wrapper for all property accesses 2021-03-01 14:37:03 +01:00
WolverinDEV 1bdb178a3f Reworked the property system and fixed a crash 2021-03-01 14:16:44 +01:00
WolverinDEV 6a7e29b1eb Temporary stashing group changes 2021-02-28 19:03:41 +01:00
WolverinDEV 6f2d8ab6e4 Introduced the new group manager 2021-02-26 21:32:57 +01:00
WolverinDEV ee52f4b8d9 Fixed the client icon id 2021-02-26 10:16:39 +01:00
WolverinDEV 1ea630b326 Removed the whole TS3 WebList feature 2021-02-25 11:54:47 +01:00
WolverinDEV 2c7e7e43d4 Removed the WebList feature 2021-02-25 11:52:58 +01:00
WolverinDEV 7325caa7e8 Updates for the new token system 2021-02-25 11:13:30 +01:00
WolverinDEV 22d366edc7 Updates for the 1.5.1 version 2021-02-25 11:13:10 +01:00
WolverinDEV 863bf0df65 Updating the ChangeLog.md and some small documentation changes 2021-02-24 14:57:50 +01:00
WolverinDEV 0033f40eca Updating files for the new token system 2021-02-22 22:04:37 +01:00
WolverinDEV b40e1326cc Using tasks instead of inlining certain client updates 2021-02-21 21:56:52 +01:00
WolverinDEV 71b2d734bd Using this->ref() and this->weak_ref() instead of direct member access 2021-02-21 20:28:59 +01:00
WolverinDEV e9445d6568 Some more improvements 2021-02-14 20:41:10 +01:00
WolverinDEV be1a189076 Fixed an internal command handler hangup per client 2021-02-14 20:36:56 +01:00
WolverinDEV 61b6f13bbd Updating rev 2021-02-12 14:52:19 +01:00
WolverinDEV 58691a23b8 Fixed compile errors 2021-02-12 14:44:31 +01:00
WolverinDEV 84f4885466 Adding a flag to the packet decoder whatever we're on the server or client side 2021-02-07 18:04:56 +01:00
WolverinDEV 2b38287317 Some code refactoring 2021-02-07 12:27:40 +01:00
WolverinDEV c31eb18849 Moved some voice udp utils to the shared teaspeak part 2021-02-07 12:27:28 +01:00
WolverinDEV 7dd8513f62 Made all non required libraries for the client optional 2021-02-06 21:00:06 +01:00
WolverinDEV 54e0571132 Some smaller changes and code cleanups 2021-02-05 17:20:39 +01:00
WolverinDEV a608b52269 Fixed 32 bit related error 2021-02-05 15:14:46 +01:00
WolverinDEV 37b3101561 Fixed security level calculcation for too long numbers 2021-02-05 14:23:52 +01:00
WolverinDEV 9a523f1525 Some minor code refactors and fixed converter for 332bit builds 2021-02-05 14:20:11 +01:00
WolverinDEV 9af0a76ed6 Reschedule packet send on EAGAIN 2021-02-01 20:59:03 +01:00
WolverinDEV e2335becd7 Fixed query disconnect and log message 2021-01-31 18:51:07 +01:00
WolverinDEV bb3cc465d7 Disabled libasan 2021-01-30 13:29:33 +01:00
WolverinDEV c28f779484 Fixed while true loop 2021-01-29 09:47:19 +01:00
WolverinDEV 678f5e0a6b Some minor changes 2021-01-28 21:07:10 +01:00
WolverinDEV 902b1f3511 A lot of query reworking 2021-01-28 20:59:15 +01:00
WolverinDEV 9a5aa1d42b Using static asan 2021-01-24 11:26:43 +01:00
WolverinDEV ffd5b64177 Some minor refactoring, removed jemalloc and added the address sanitize 2021-01-24 10:34:19 +01:00
WolverinDEV 9784ce9351 Fixed a crash 2021-01-22 19:59:01 +01:00
WolverinDEV 660fe3317f Some minor changes 2021-01-20 17:44:44 +01:00
WolverinDEV e75b2342e2 Fixed missing method 2021-01-14 22:27:54 +01:00
WolverinDEV 94c7eb2f39 Improved abort signal printing and some reformats 2021-01-14 22:16:56 +01:00
WolverinDEV f2c5b5d750 Fixed the auto keyframe interval 2021-01-04 20:36:33 +01:00
WolverinDEV 8be83fc51d Added support for controlling the streams max bps 2021-01-04 20:32:29 +01:00
WolverinDEV a233915064 Fixed a crash 2021-01-04 19:45:48 +01:00
WolverinDEV 3c0c48c00c Some minor compile fixes 2021-01-03 17:20:43 +01:00
WolverinDEV 960186d55e Fixed a crash related to file transfers 2021-01-03 17:16:23 +01:00
WolverinDEV 6a502e23f2 Using VP8 as video codec and reqesting keyframes 2020-12-30 17:32:05 +01:00
WolverinDEV 6ee02a6a17 Fixed some compile errors 2020-12-29 18:31:25 +01:00
WolverinDEV 1ece4bca8b Removed deleted file 2020-12-29 18:27:48 +01:00
WolverinDEV 25504abe7e Some minor changes 2020-12-29 18:26:09 +01:00
WolverinDEV c6ed584d97 Updating shared rev 2020-12-19 10:52:44 +01:00
WolverinDEV ccd0f7fcfd Fixed a crash related to the whisper system and some minor updates 2020-12-19 10:52:24 +01:00
WolverinDEV 0cff59328f Some minor changes and fixes 2020-12-17 12:00:27 +01:00
WolverinDEV 6605a440bd Fixed a possible crash 2020-12-16 19:46:33 +01:00
WolverinDEV e80afb1145 Fixed crash 2020-12-16 08:45:10 +01:00
WolverinDEV 4fddc87866 Fixed an abort 2020-12-15 23:21:20 +01:00
WolverinDEV 644391f58a Added a stack protector 2020-12-14 20:34:01 +01:00
WolverinDEV 9b8d1d4d65 Fixed a compile bug and added the address santiser 2020-12-14 20:33:36 +01:00
WolverinDEV 835471c2dd Updating rev 2020-12-12 21:36:03 +01:00
WolverinDEV 4849e17221 Updating revs 2020-12-12 21:34:17 +01:00
WolverinDEV f240402113 Using 32 bit return codes and not 8bit 2020-12-12 21:33:55 +01:00
WolverinDEV 93db485d78 Added a critical error 2020-12-12 21:32:43 +01:00
WolverinDEV 4e8f7c7669 Some fixes 2020-12-12 20:38:55 +01:00
WolverinDEV 26f90efcbf Updated the ChangeLog.md 2020-12-12 20:37:35 +01:00
WolverinDEV c15595d66e Fixed libnice 2020-12-12 20:37:14 +01:00
WolverinDEV fcac375dc3 Don't log to stdout/stderr and some minor video improvements 2020-12-12 15:23:00 +01:00
WolverinDEV da159c1ba5 Fixed some minor issues 2020-12-12 13:17:37 +01:00
WolverinDEV 5398ca65f2 A lot of video related changes 2020-12-12 00:16:40 +01:00
WolverinDEV 99e68cd5c4 Video now has to be manually activated in order to watch it 2020-12-10 13:33:09 +01:00
WolverinDEV 0d976ec0b8 A small fix 2020-12-07 22:40:22 +01:00
WolverinDEV 4249b4fe82 Updating revs 2020-12-05 21:50:09 +01:00
WolverinDEV 88c4485278 Fixed some minor speaking bugs 2020-12-05 21:50:02 +01:00
WolverinDEV da5f5bfebe Removing upnp 2020-12-05 16:34:35 +01:00
WolverinDEV f2e5387318 Some minor and small fixes 2020-12-05 16:27:41 +01:00
WolverinDEV 1c99f093c8 Improved the db info command performance 2020-12-05 10:26:12 +01:00
WolverinDEV 8ca58056d4 Updating the ChangeLog.md 2020-12-05 10:09:50 +01:00
WolverinDEV 065dac2caf Removing one permission 2020-12-05 10:09:34 +01:00
WolverinDEV 912d5e2c3e Some minor changes 2020-12-04 19:34:53 +01:00
WolverinDEV 80370ff73e Updated the rtc lib 2020-12-04 15:17:48 +01:00
WolverinDEV 7d66afc24c Some minor temporary client move bug fix 2020-12-04 14:52:32 +01:00
WolverinDEV 75b6d0c677 Some minor fixes 2020-12-04 12:37:52 +01:00
WolverinDEV 97e2ded4fb Some minor changes 2020-12-04 12:24:07 +01:00
WolverinDEV 4c7a70bc81 Some minor changes 2020-12-04 12:23:12 +01:00
WolverinDEV c21838d4f6 Added the possibility to depricate TS3 clients 2020-12-03 11:47:37 +01:00
WolverinDEV 5ee731bc92 Added missing permissions 2020-12-03 11:12:18 +01:00
WolverinDEV 364b3781b1 Added a new channel property 2020-12-03 10:49:21 +01:00
WolverinDEV db63248677 Some build file fixes 2020-12-02 23:40:36 +01:00
WolverinDEV 4ca32f4453 Some minor fixes 2020-12-02 21:43:03 +01:00
WolverinDEV 64106c83eb Fixed a small bug 2020-12-02 18:20:49 +01:00
WolverinDEV a21a28fede Updated the channel edit and create functions 2020-12-01 10:07:37 +01:00
WolverinDEV 706cecb66c Fixed crash 2020-11-30 19:06:18 +01:00
WolverinDEV 8714761afa Some minor changes 2020-11-28 15:42:24 +01:00
WolverinDEV 5cf3b0b75c Allowing the webclient to whisper 2020-11-28 11:27:31 +01:00
WolverinDEV 5ed16d3756 Allowing the webclient to signal whisper 2020-11-28 11:27:06 +01:00
WolverinDEV f0b094d7e4 Supporting voice whisper again 2020-11-28 11:09:25 +01:00
WolverinDEV ccc3bad705 Added the possibility for voice whisper 2020-11-28 11:09:07 +01:00
WolverinDEV 73cf372a84 Fixed a mass disconnect due to too long channel names 2020-11-26 10:47:16 +01:00
WolverinDEV 818cffd368 Merge branch '1.4.15' into 1.5.0 2020-11-26 10:46:31 +01:00
WolverinDEV eaca25ea30 Fixed disconnect due to max characters 2020-11-26 10:45:30 +01:00
WolverinDEV 76f22ff337 Improved the code quality and TeaSpeak now builds with clang 2020-11-26 10:35:01 +01:00
WolverinDEV 39d1959dde Allowing screen & camara sharing simultaneously 2020-11-22 19:09:02 +01:00
WolverinDEV 6e93c5e25a Some minor changes 2020-11-22 16:11:46 +01:00
WolverinDEV 225c006140 Added possibilities to configure the ICE agent 2020-11-16 14:50:54 +01:00
WolverinDEV 83fa79a51d Added possibilities to configure the ICE agent 2020-11-16 14:50:33 +01:00
WolverinDEV b438684f20 Some webclient fixes 2020-11-15 23:30:51 +01:00
WolverinDEV 9b1de50f1b Fixed firefox 2020-11-15 23:29:46 +01:00
WolverinDEV dd4a3f65a4 Fixed crash and build type 2020-11-10 17:02:31 +01:00
WolverinDEV e925c80991 Updated build scripts 2020-11-10 15:59:54 +00:00
WolverinDEV be58f24196 Updated the server CMakeLists.txt 2020-11-10 15:44:07 +01:00
WolverinDEV 1ad3187a86 Some minor changes 2020-11-10 15:39:30 +01:00
WolverinDEV 9544657f3b Adding a build script 2020-11-10 15:39:15 +01:00
WolverinDEV 7fb5d2f8f1 Adding the RTC lib for teaspeak 2020-11-07 13:18:18 +01:00
WolverinDEV a37ba81a4f Initial video commit 2020-11-07 13:17:51 +01:00
WolverinDEV 6cd481e824 Bumped version 2020-10-04 15:05:54 +02:00
WolverinDEV 1686ce095f Updated the property list command and added a channel description only mode 2020-10-04 15:04:25 +02:00
WolverinDEV 6150957689 Added a property documentation and updated the ChangeLog.md 2020-10-04 14:59:40 +02:00
WolverinDEV 3b4d519178 Minor fix 2020-09-25 00:49:20 +02:00
WolverinDEV 35c852b2cd Fixed missing import 2020-09-24 23:00:58 +02:00
WolverinDEV bd7ff3e4e0 Some updates 2020-09-24 22:57:10 +02:00
WolverinDEV f5d5766644 Fixed enforced permissions 2020-09-16 21:03:07 +02:00
WolverinDEV 0a3585f4f8 Fixed the server group create command 2020-09-16 14:27:27 +02:00
WolverinDEV a8bd42fd3f Bumping the server version 2020-09-16 14:16:21 +02:00
WolverinDEV 38d6ad4920 Fixed a bug related to the file transfer 2020-09-16 14:15:06 +02:00
WolverinDEV 2a1f0187ac Adding server log options 2020-09-15 01:18:35 +02:00
WolverinDEV 4f5a4dc993 Some more updates 2020-09-06 21:00:27 +02:00
WolverinDEV 7d0db0dea0 Some minor snapshot updates 2020-08-23 22:23:12 +02:00
WolverinDEV add439de00 Fixed a crash 2020-08-22 21:57:53 +02:00
WolverinDEV 4a7a9e7228 Fixed MySQL 2020-08-22 21:34:35 +02:00
WolverinDEV a78c36c999 Fixed MySQL 2020-08-19 20:11:09 +02:00
WolverinDEV a80c90f025 Some minor fixes 2020-08-19 14:50:42 +02:00
WolverinDEV 28b13093f6 Fixing some bugs for 1.4.19b1 2020-08-18 22:03:07 +02:00
WolverinDEV 7dcf4a54ef Some changes 2020-08-13 12:58:19 +02:00
WolverinDEV 7fdd272d76 Fixed the virtual server snapshot port persistance 2020-08-07 21:05:19 +02:00
WolverinDEV d5ce71b769 Updated music ref 2020-08-03 14:04:14 +02:00
WolverinDEV fa7a390fe3 Adding a basic skillet to the new voice server - fixed some connection issues 2020-08-03 13:51:47 +02:00
WolverinDEV e49b091b92 Fixed server group client list 2020-08-01 16:02:03 +02:00
WolverinDEV 8c3d756842 Omitting the finished notification 2020-08-01 12:36:43 +02:00
WolverinDEV 97cf371362 Adding TCP no delay 2020-08-01 12:24:59 +02:00
WolverinDEV f14e5e0148 Not using a non blocking socket, instead using the non blocking write methods 2020-08-01 12:07:52 +02:00
WolverinDEV e9388e5e5e Some minor transfer updates 2020-08-01 11:34:24 +02:00
WolverinDEV 669b3ae349 Fixed last user nickname 2020-08-01 00:09:01 +02:00
WolverinDEV bd9cca6fb1 Updating submodules 2020-07-31 23:21:08 +02:00
WolverinDEV 8284748381 Adding terminal pipes 2020-07-31 23:18:01 +02:00
WolverinDEV 7aa37e40b9 Fixed upgrade command 2020-07-31 19:42:40 +02:00
WolverinDEV eab2155384 Added some reload flags 2020-07-31 17:44:16 +02:00
WolverinDEV 3e36d70164 Increased server version 2020-07-31 17:35:41 +02:00
WolverinDEV 3dea4906e1 Updating the deploy algorithm 2020-07-31 17:35:14 +02:00
WolverinDEV 72abd7e20e First draft of the new snapshot system and database changes 2020-07-30 20:25:45 +02:00
WolverinDEV c60119af00 Fixed unique ID generation 2020-07-30 14:00:25 +02:00
WolverinDEV 1e0c9eabe3 Fixed some too long messages 2020-07-30 11:50:31 +02:00
WolverinDEV 3f700e79d3 Some file server related things 2020-07-30 11:40:03 +02:00
WolverinDEV 7a974677fb Finalizing the voice connection cleanup 2020-07-29 22:53:40 +02:00
WolverinDEV 4c91a7a3bf reworked the voice client connection part 1 2020-07-29 19:05:38 +02:00
WolverinDEV ad51f8118d A lot of changes to 1.4.17 2020-07-28 18:36:32 +02:00
WolverinDEV 071a6533e0 Reworked the YT-DL provider and fixed a FFMPEG crash 2020-07-28 18:25:31 +02:00
WolverinDEV 9f24a71aed Fixed some basic stuff 2020-07-23 20:28:43 +02:00
WolverinDEV 94453894e9 Updating the ChangeLog.md 2020-07-13 11:17:57 +02:00
WolverinDEV 9c7223d016 Some minor bugfixes 2020-07-13 11:13:09 +02:00
WolverinDEV e01811e20e Increased server version to 1.4.16 2020-06-28 14:07:49 +02:00
WolverinDEV 6e6eee39d4 Added feature "log-query" 2020-06-28 14:07:07 +02:00
WolverinDEV 68cfab1ac9 Added the action logging system 2020-06-28 14:01:14 +02:00
WolverinDEV 8e16309930 Updated the ChangeLog.md 2020-06-28 14:00:42 +02:00
WolverinDEV b7953f5535 Some minor bug fixes 2020-06-25 18:15:15 +02:00
WolverinDEV ce5d5c5cfa Increased sleep max 2020-06-16 19:59:44 +02:00
WolverinDEV b0c8f04a53 Updated the sleep stuff 2020-06-16 19:51:06 +02:00
WolverinDEV c59346ea68 Fix for some VM's 2020-06-16 19:40:32 +02:00
WolverinDEV 14d2578a67 Some minor fixes and added the listfeaturesupport command 2020-06-16 12:57:20 +02:00
WolverinDEV f03af7a9bf Added documentation for listfeaturesupport 2020-06-16 12:56:31 +02:00
WolverinDEV a23002ce66 File transfer server now respects the port and host settings set via the config.yml 2020-06-13 01:08:49 +02:00
WolverinDEV ed7cbd38e8 Appropriate linking some libraries 2020-06-11 13:24:07 +02:00
WolverinDEV cbb0bd6864 Fixed networking flush algorithm for the file transfer 2020-06-11 13:08:45 +02:00
WolverinDEV 6e1323cc23 Small fixes 2020-06-11 11:37:04 +02:00
WolverinDEV 006fd6ebec Some minor changes 2020-06-10 18:17:32 +02:00
WolverinDEV abcd35e443 Updating the changelog to 1.4.15 2020-06-10 18:16:23 +02:00
WolverinDEV 9e964b3ea8 Some bug fixes and final version before release 2020-06-10 18:13:14 +02:00
WolverinDEV bfdf940dbf Merge branch '1.4.10-openssl' into 1.4.15 2020-05-21 16:53:00 +02:00
WolverinDEV 4015f11718 Fixed declaration 2020-05-21 09:52:05 +02:00
WolverinDEV cd0ef02ab1 Fixed some minor issues 2020-05-21 09:48:11 +02:00
WolverinDEV 68716f38e0 Merge branch '1.4.10-openssl' into 1.4.15 2020-05-19 15:12:51 +02:00
WolverinDEV f7924d29df Fixed YatQa rror 2020-05-19 09:51:54 +02:00
WolverinDEV c7f989da8b Fixed server statistics 2020-05-18 17:30:44 +02:00
WolverinDEV 14870efc11 Fixed permission assignments 2020-05-14 15:18:39 +02:00
WolverinDEV c7751efa71 Some updates 2020-05-14 15:08:28 +02:00
WolverinDEV b987583770 Added file transfer status 2020-05-13 18:03:14 +02:00
WolverinDEV 5245e4ffc1 Fixed stats overflow 2020-05-13 12:22:12 +02:00
WolverinDEV 1a3235697e Some more comments 2020-05-13 12:19:49 +02:00
WolverinDEV f6058d9ac0 Some more overflow fixes 2020-05-13 12:19:19 +02:00
WolverinDEV ffa691ac78 Fixed integer overflow 2020-05-13 12:18:27 +02:00
WolverinDEV 1d413f9b76 Merge branch '1.4.10-openssl' into 1.4.15 2020-05-13 11:53:51 +02:00
WolverinDEV e3bf46a89b Channel move improvements 2020-05-13 11:51:01 +02:00
WolverinDEV 90b1646876 A lot of file transfer updates 2020-05-13 11:32:08 +02:00
WolverinDEV 1a2dd4a008 Fixed broken link 2020-05-10 16:27:55 +02:00
WolverinDEV 4e3921502d Some file transfer updates 2020-05-10 16:23:02 +02:00
WolverinDEV dd4a871bf0 Fixed query 2020-05-09 19:56:10 +02:00
WolverinDEV 5e8ed17ef7 Fixed update 2020-05-09 19:53:46 +02:00
WolverinDEV 885cf52bdc Fixed offline messages bug 2020-05-09 16:55:15 +02:00
WolverinDEV 9ef9ce2b22 Fixed the default MOTD 2020-05-07 22:09:51 +02:00
WolverinDEV 92bb168b4e Fixed server not starting after license expires 2020-05-07 21:57:04 +02:00
WolverinDEV dbca214ef2 Some minimal changes 2020-05-07 21:39:26 +02:00
WolverinDEV 48326bd102 A lot of updates 2020-05-07 21:28:15 +02:00
WolverinDEV fd256411d1 Update 2020-05-03 14:06:34 +02:00
WolverinDEV f3441e0115 Fixed some license server hangups 2020-05-01 10:48:29 +02:00
WolverinDEV cd8e2974f2 Minor bug fixes 2020-04-29 10:34:22 +02:00
WolverinDEV dfd33eb674 Fixed ft stats 2020-04-28 19:59:05 +02:00
WolverinDEV ff88705f09 Some updates 2020-04-28 18:27:49 +02:00
WolverinDEV 633fe10821 Updated shared rev 2020-04-26 19:42:19 +02:00
WolverinDEV 58aa7fe9bc Fixed some bugs 2020-04-26 19:41:41 +02:00
WolverinDEV 7d4df36049 Using dynamid gllib2.0 resolution 2020-04-26 19:06:56 +02:00
WolverinDEV 004ec89f44 Fixed server crash 2020-04-26 11:41:35 +02:00
WolverinDEV cbfd27b954 Fixed the broken pipe issue 2020-04-26 11:29:20 +02:00
WolverinDEV ac89b3a423 Fixed the invalid packet splitting algorithm 2020-04-25 11:26:00 +02:00
WolverinDEV 40dfbd64fa Updated reb 2020-04-24 22:04:24 +02:00
WolverinDEV 3e787a1d9f A lot of updates (Speed improvement) 2020-04-24 22:04:07 +02:00
WolverinDEV 0a2c1bf3d9 Updated revs 2020-04-23 15:39:01 +02:00
WolverinDEV f6932f0512 1.4.14 ;) 2020-04-23 15:36:58 +02:00
WolverinDEV 3f98bcf9cf Updated the ChaneLog 2020-04-23 15:35:46 +02:00
WolverinDEV 09d5e97d5d Fixed database stuff 2020-04-20 12:51:50 +02:00
WolverinDEV eeca625af6 Some more settings 2020-04-19 19:46:43 +02:00
WolverinDEV 512aa54700 Fixed invalid range 2020-04-19 18:40:40 +02:00
WolverinDEV 85df6e096f Changed stun 2020-04-19 18:20:45 +02:00
WolverinDEV dbde035d77 Removed STUN need 2020-04-19 18:09:05 +02:00
WolverinDEV ee00935cfc Fixed WebRTC stuff 2020-04-19 17:21:30 +02:00
WolverinDEV 4de657a9a2 Merge remote-tracking branch 'origin/1.4.10-openssl' into 1.4.10-openssl 2020-04-19 00:23:28 +02:00
WolverinDEV 1c225c0e10 Fixed permission stuff 2020-04-19 00:23:18 +02:00
WolverinDEV c50aa0f862 Fixed providers 2020-04-18 13:24:01 +00:00
WolverinDEV 2bdead3676 Increased server version 2020-04-18 13:39:02 +02:00
WolverinDEV 099a041ed8 Updated shared rev 2020-04-18 12:56:11 +02:00
WolverinDEV 4914b1fbd3 Some updated for 1.4.13 2020-04-18 12:54:29 +02:00
WolverinDEV 271d79bb64 U 2020-04-16 14:52:08 +02:00
WolverinDEV b6fdcbebfd Fixed the build script a dozen times 2020-04-16 14:47:53 +02:00
WolverinDEV b79b496ad1 Updated to 1.4.13 ;) 2020-04-16 14:05:58 +02:00
WolverinDEV 9705f84bc0 Merge remote-tracking branch 'remotes/origin/1.4.12' into 1.4.10-openssl 2020-04-16 13:38:16 +02:00
WolverinDEV 6d19526458 Fixed build script 2020-04-15 15:56:16 +02:00
WolverinDEV b7cbf4b20a Updated env build script 2020-04-15 15:16:42 +02:00
WolverinDEV afa2b40b50 Fixed build script 2020-04-15 15:14:00 +02:00
WolverinDEV 10280da419 Merge branch '1.4.12' of https://git.did.science/WolverinDEV/TeaSpeak into 1.4.12 2020-04-15 15:03:40 +02:00
WolverinDEV bb935dd214 Fixed some crashes (1.4.12b4) 2020-04-15 15:02:59 +02:00
WolverinDEV d92f5d4bb5 Removed unneeded files 2020-04-14 11:53:38 +02:00
WolverinDEV ee69287813 Merge remote-tracking branch 'origin/1.4.10-openssl' into 1.4.10-openssl 2020-04-14 11:49:43 +02:00
WolverinDEV f205075d97 Changed for new deploy algorithm 2020-04-14 11:49:07 +02:00
WolverinDEV d570d03307 Updated music rev 2020-04-12 12:38:28 +00:00
WolverinDEV 483e188c37 New snapshot system proposal 2020-04-11 20:41:09 +02:00
WolverinDEV 0f1665c97d Fixed invalid channel flags for changing the default channel 2020-04-11 12:31:07 +02:00
WolverinDEV 7ef77c3160 Some updates 2020-04-10 23:29:51 +02:00
WolverinDEV 74fa735004 Some smaller updates 2020-04-08 13:50:03 +02:00
WolverinDEV eb61daab43 A lot of updates for 1.4.12 2020-04-08 13:01:41 +02:00
WolverinDEV a2f52d98db Deleted the finally obsolete big handler file 2020-04-08 03:23:21 +02:00
WolverinDEV b0f0710b5b Updated the property system and added a packet loss calculator 2020-04-08 02:56:08 +02:00
WolverinDEV 421f04fe60 Fixed some small stuff 2020-04-04 12:22:20 +02:00
WolverinDEV 3d90e8b57a Removed a debugging message 2020-04-04 12:01:10 +02:00
WolverinDEV a1ea11a196 Music bot fix 2020-04-04 11:59:33 +02:00
WolverinDEV f830a8023d Updated some small stuff 2020-04-04 01:38:37 +02:00
WolverinDEV a36f0dbf02 Fixed log 2020-04-03 19:46:18 +02:00
WolverinDEV 824aec6322 Fixed some stuff 2020-04-03 19:26:22 +02:00
WolverinDEV 8e4d52ddd2 License server memory validation 2020-04-03 19:07:03 +02:00
WolverinDEV 240052da3a Fixed a missing ! for the web client 2020-04-03 14:27:02 +02:00
WolverinDEV 8d42156383 Fixed TeaClient join 2020-04-03 13:56:39 +02:00
WolverinDEV 95d52e4997 Updating some revs 2020-04-03 13:50:20 +02:00
WolverinDEV e439d4bc39 Fixed web hangup 2020-04-02 20:12:29 +02:00
WolverinDEV 702dd87c41 Updating rev 2020-04-02 19:29:05 +02:00
WolverinDEV ca2de244d8 Added another debug possibility 2020-04-02 19:24:57 +02:00
WolverinDEV b594c9566c Added some debug info 2020-04-02 19:18:58 +02:00
WolverinDEV 1865a3b20d Fixed the permfind command 2020-04-02 14:10:33 +02:00
WolverinDEV 924e553664 Updated revs 2020-04-01 20:13:35 +02:00
WolverinDEV b005016c48 Some updates 2020-03-31 22:01:47 +02:00
WolverinDEV c4eff7c743 Fixing zombie processes after a stream has been closed 2020-03-31 21:51:58 +02:00
WolverinDEV d669708989 Updated the changelog & command docs 2020-03-30 22:53:47 +02:00
WolverinDEV 90353c2bc5 Fixed some stuff 2020-03-30 22:53:15 +02:00
WolverinDEV 5c408948f6 Fixed stuff attempt 3 2020-03-28 23:11:41 +01:00
WolverinDEV 63219434d3 Tryfix again 2020-03-28 23:08:11 +01:00
WolverinDEV f596151c42 Fixed command dropping 2020-03-28 22:49:22 +01:00
WolverinDEV b7b22dc89e Fixed two music bot hangups 2020-03-26 17:34:31 +01:00
WolverinDEV 0778b04b6b Fixed lib gen script 2020-03-25 20:48:35 +01:00
WolverinDEV c627690011 Optimized 1.4.11 2020-03-25 20:36:44 +01:00
WolverinDEV d52496600f Some fixes 2020-03-23 10:58:07 +01:00
WolverinDEV 1a4a6721a1 Added messages 2020-03-20 17:58:58 +01:00
WolverinDEV 124844b9d4 Fixed the web client 2020-03-20 17:00:15 +01:00
WolverinDEV c74804ccf5 Fixed that music bots now not appear anymore... 2020-03-20 01:05:39 +01:00
WolverinDEV e5f7b3bd32 Some changes 2020-03-20 00:56:36 +01:00
WolverinDEV 0705c68b7b Fixed some missing permissions 2020-03-19 01:47:48 +01:00
WolverinDEV 82e65c712b Updating git rev 2020-03-11 10:36:55 +01:00
WolverinDEV 3f9ee1c444 Fixed the query account password change parameter 2020-03-11 10:33:39 +01:00
WolverinDEV d6314f22b2 Fixed license shutdown issue 2020-03-10 17:04:30 +01:00
WolverinDEV 3aafe342c7 Fixed some bugs 2020-03-05 14:33:28 +01:00
WolverinDEV 383fab15a6 Fixed permission notify client disconnect 2020-03-04 16:31:03 +01:00
WolverinDEV e47d38da79 Added a RW lock 2020-03-03 20:57:35 +01:00
WolverinDEV f48fa94b82 Using for logview command bash 2020-03-02 23:44:38 +01:00
WolverinDEV 7e82f4c8ea Fixed license check interval 2020-03-02 21:03:37 +01:00
WolverinDEV dfb61b9fea Some small updates 2020-03-02 21:00:18 +01:00
WolverinDEV 12d86cbfea Counting empty instances as well 2020-03-02 20:40:48 +01:00
WolverinDEV 67becd9496 Printing eror message on ssl init failed 2020-03-02 20:22:11 +01:00
WolverinDEV 8de9bc921f Fixed missing rt lib 2020-03-02 20:04:37 +01:00
WolverinDEV 96bb7ff56f Improved license server libraries 2020-03-02 19:57:56 +01:00
WolverinDEV 7cbe5717ce Fixed missing library for old clib versions 2020-03-02 19:30:50 +01:00
WolverinDEV e78b900937 Fixed openssl incompatibility 2020-03-02 19:29:34 +01:00
WolverinDEV 5a740e1853 Fixed TeaSpeak for openssl 2020-03-02 18:45:53 +01:00
259 changed files with 37858 additions and 25808 deletions
+66
View File
@@ -0,0 +1,66 @@
# Generated from CLion C/C++ Code Style settings
BasedOnStyle: LLVM
AccessModifierOffset: 0
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: false
AlignOperands: true
AllowAllArgumentsOnNextLine: false
AllowAllConstructorInitializersOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: Always
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Always
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterReturnType: None
AlwaysBreakTemplateDeclarations: Yes
BreakBeforeBraces: Custom
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterUnion: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
SplitEmptyFunction: false
SplitEmptyRecord: true
BreakBeforeBinaryOperators: None
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeColon
BreakInheritanceList: BeforeColon
ColumnLimit: 0
CompactNamespaces: false
ContinuationIndentWidth: 8
IndentCaseLabels: true
IndentPPDirectives: None
IndentWidth: 4
KeepEmptyLinesAtTheStartOfBlocks: true
MaxEmptyLinesToKeep: 2
NamespaceIndentation: All
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PointerAlignment: Right
ReflowComments: false
SpaceAfterCStyleCast: true
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 0
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
TabWidth: 4
UseTab: Never
+7 -1
View File
@@ -4,7 +4,13 @@
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
[submodule "rtclib"]
path = rtclib
url = https://git.did.science/TeaSpeak/Server/rtc.git
[submodule "file-rust"]
path = file-rust
url = https://git.did.science/TeaSpeak/Server/teafile.git
+8 -6
View File
@@ -8,7 +8,9 @@ set(TEASPEAK_SERVER ON)
#end now
#set(MEMORY_DEBUG_FLAGS " -fsanitize=leak -fsanitize=address -fstack-protector-all ")
#set(MEMORY_DEBUG_FLAGS "-fsanitize=address")
#set(MEMORY_DEBUG_FLAGS "-fsanitize=address -fstack-protector-all")
#set(MEMORY_DEBUG_FLAGS "-fstack-protector-all")
#set(MEMORY_DEBUG_FLAGS " -fsanitize=address -static-libasan")
if (NOT BUILD_OS_ARCH)
set(BUILD_OS_ARCH $ENV{build_os_arch})
@@ -23,7 +25,6 @@ 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)
@@ -49,18 +50,19 @@ find_package(TomMath REQUIRED)
find_package(TomCrypt REQUIRED)
find_package(Breakpad REQUIRED)
find_package(Protobuf REQUIRED)
find_package(Boringssl 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)
include_directories(${StringVariable_INCLUDE_DIR})
add_subdirectory(music/)
@@ -92,7 +94,6 @@ 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})
@@ -103,4 +104,5 @@ add_definitions(-DINET -DINET6)
add_subdirectory(shared/)
add_subdirectory(server/)
add_subdirectory(license/)
add_subdirectory(MusicBot/)
add_subdirectory(MusicBot/)
add_subdirectory(file/)
+12 -4
View File
@@ -30,9 +30,12 @@ void AbstractMusicPlayer::unregisterEventHandler(const std::string& string) {
}
void AbstractMusicPlayer::fireEvent(MusicEvent event) {
std::lock_guard 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);
}
@@ -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;
Submodule
+1
Submodule file-rust added at 29f9579435
+50
View File
@@ -0,0 +1,50 @@
cmake_minimum_required(VERSION 3.6)
project(TeaSpeak-Files)
#set(CMAKE_CXX_STANDARD 20)
#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
)
add_library(TeaSpeak-FileRust STATIC
local_rust/provider.cpp
local_rust/lib.cpp
)
target_include_directories(TeaSpeak-FileRust PUBLIC include/)
target_link_libraries(TeaSpeak-FileRust PUBLIC TeaSpeak ${CMAKE_SOURCE_DIR}/file-rust/target/debug/libteaspeak_file_capi.so)
# target_link_directories(TeaSpeak-FileRust PUBLIC ${CMAKE_SOURCE_DIR}/file-rust/target/debug/)
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")
target_compile_features(TeaSpeak-FileServer PUBLIC cxx_std_20)
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)
+13
View File
@@ -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;
}
+77
View File
@@ -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;
};
}
+424
View File
@@ -0,0 +1,424 @@
#pragma once
#include <string>
#include <chrono>
#include <utility>
#include <variant>
#include <deque>
#include <functional>
#include <atomic>
#include <condition_variable>
#include "./ExecuteResponse.h"
#define TRANSFER_KEY_LENGTH (32)
#define TRANSFER_MEDIA_BYTES_LENGTH (32)
namespace ts::server::file {
class VirtualFileServer;
typedef uint64_t ChannelId;
typedef uint64_t ServerId;
typedef uint64_t ClientId;
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)} {}
DeleteResult() = default;
};
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)} {}
FileInfo() = default;
};
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;
/* 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{};
std::string transfer_key{};
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 */, bool /* delete tiles */) = 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();
}
+54
View File
@@ -0,0 +1,54 @@
//
// Created by WolverinDEV on 20/04/2021.
//
#pragma once
#include <cstdint>
#include <cstddef>
#ifdef __cplusplus
extern "C" {
#endif
/* Attention: Do not call any libteaspeak_file_* functions within being in the callback! */
struct TeaFileNativeCallbacks {
uint32_t version;
void(*log)(uint8_t /* level */, const void* /* callback data */, const char* /* message */, uint32_t /* length */);
};
struct TeaFilePath {
uint32_t type;
uint64_t channel_id;
const char* path;
};
struct TeaFilePathInfo {
int32_t query_result;
uint32_t file_type;
const char* name;
uint64_t modify_timestamp;
uint64_t file_size;
bool directory_empty;
};
extern const char* libteaspeak_file_version();
extern void libteaspeak_free_str(const char* /* ptr */);
extern const char* libteaspeak_file_initialize(const TeaFileNativeCallbacks* /* */, size_t /* size of the callback struct */);
extern void libteaspeak_file_finalize();
extern void libteaspeak_file_system_register_server(const char* /* server unique id */);
extern void libteaspeak_file_system_unregister_server(const char* /* server unique id */, bool /* delete files */);
extern void libteaspeak_file_free_file_info(const TeaFilePathInfo*);
extern const char* libteaspeak_file_system_query_file_info(const char* /* server unique id */, const TeaFilePath* /* file path */, size_t /* path count */, const TeaFilePathInfo ** /* result */);
extern const char* libteaspeak_file_system_query_directory(const char* /* server unique id */, const TeaFilePath* /* file path */, const TeaFilePathInfo ** /* result */);
extern const char* libteaspeak_file_system_delete_files(const char* /* server unique id */, const TeaFilePath* /* file path */, size_t /* path count */, const TeaFilePathInfo ** /* result */);
extern uint32_t libteaspeak_file_system_create_channel_directory(const char* /* server unique id */, uint32_t /* channel id */, const char* /* path */);
extern uint32_t libteaspeak_file_system_rename_channel_file(const char* /* server unique id */, uint32_t /* old channel id */, const char* /* old path */, uint32_t /* new channel id */, const char* /* new path */);
#ifdef __cplusplus
}
#endif
+6
View File
@@ -0,0 +1,6 @@
//
// Created by WolverinDEV on 20/04/2021.
//
#include "./lib.h"
#include "./imports.h"
+5
View File
@@ -0,0 +1,5 @@
//
// Created by WolverinDEV on 20/04/2021.
//
#pragma once
+587
View File
@@ -0,0 +1,587 @@
//
// Created by WolverinDEV on 20/04/2021.
//
#include <cassert>
#include <log/LogUtils.h>
#include "./provider.h"
#include "./imports.h"
using namespace ts::server;
using namespace ts::server::file;
using namespace ts::server::file::transfer;
using namespace ts::server::file::filesystem;
RustFileSystem::~RustFileSystem() = default;
std::shared_ptr<ExecuteResponse<ServerCommandError>> RustFileSystem::initialize_server(
const std::shared_ptr<VirtualFileServer> &) {
/* Nothing to do. */
auto response = this->create_execute_response<ServerCommandError>();
response->emplace_success();
return response;
}
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> RustFileSystem::query_channel_info(
const std::shared_ptr<VirtualFileServer> &virtual_server, const std::vector<std::tuple<ChannelId, std::string>> &files) {
std::vector<TeaFilePath> file_paths{};
file_paths.reserve(files.size());
for(const auto& [ channel_id, path ] : files) {
file_paths.push_back(TeaFilePath{
.type = 0,
.channel_id = channel_id,
.path = path.c_str()
});
}
return this->execute_info(virtual_server->unique_id().c_str(), file_paths.data(), file_paths.size());
}
std::shared_ptr<RustFileSystem::directory_query_response_t> RustFileSystem::query_channel_directory(
const std::shared_ptr<VirtualFileServer> &virtual_server, ChannelId channelId, const std::string &directory) {
TeaFilePath path{
.type = 0,
.channel_id = channelId,
.path = directory.c_str()
};
return this->execute_query(virtual_server->unique_id().c_str(), &path);
}
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> RustFileSystem::query_icon_info(
const std::shared_ptr<VirtualFileServer> &virtual_server, const std::vector<std::string> &files) {
std::vector<TeaFilePath> file_paths{};
file_paths.reserve(files.size());
for(const auto& path : files) {
file_paths.push_back(TeaFilePath{
.type = 1,
.channel_id = 0,
.path = path.c_str()
});
}
return this->execute_info(virtual_server->unique_id().c_str(), file_paths.data(), file_paths.size());
}
std::shared_ptr<RustFileSystem::directory_query_response_t> RustFileSystem::query_icon_directory(
const std::shared_ptr<VirtualFileServer> &virtual_server) {
TeaFilePath path{
.type = 1,
.channel_id = 0,
.path = nullptr
};
return this->execute_query(virtual_server->unique_id().c_str(), &path);
}
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> RustFileSystem::query_avatar_info(
const std::shared_ptr<VirtualFileServer> &virtual_server, const std::vector<std::string> &files) {
std::vector<TeaFilePath> file_paths{};
file_paths.reserve(files.size());
for(const auto& path : files) {
file_paths.push_back(TeaFilePath{
.type = 2,
.channel_id = 0,
.path = path.c_str()
});
}
return this->execute_info(virtual_server->unique_id().c_str(), file_paths.data(), file_paths.size());
}
std::shared_ptr<RustFileSystem::directory_query_response_t> RustFileSystem::query_avatar_directory(
const std::shared_ptr<VirtualFileServer> &virtual_server) {
TeaFilePath path{
.type = 2,
.channel_id = 0,
.path = nullptr
};
return this->execute_query(virtual_server->unique_id().c_str(), &path);
}
inline void path_info_to_directory_entry(DirectoryEntry& target, const TeaFilePathInfo& info) {
switch (info.file_type) {
case 1:
target.type = DirectoryEntry::FILE;
break;
case 2:
target.type = DirectoryEntry::DIRECTORY;
break;
default:
target.type = DirectoryEntry::UNKNOWN;
return;
}
assert(info.name);
target.name = std::string{info.name};
target.modified_at = std::chrono::system_clock::time_point{} + std::chrono::seconds{info.modify_timestamp};
target.size = info.file_size;
target.empty = info.directory_empty;
}
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> RustFileSystem::execute_info(const char *server_unique_id, void *paths,
size_t paths_count) {
auto response = this->create_execute_response<FileInfoError, FileInfoResponse>();
const TeaFilePathInfo* result{nullptr};
auto error_ptr = libteaspeak_file_system_query_file_info(server_unique_id, (const TeaFilePath*) paths, paths_count, &result);
if(error_ptr) {
libteaspeak_file_free_file_info(result);
response->emplace_fail(FileInfoErrorType::UNKNOWN, error_ptr);
libteaspeak_free_str(error_ptr);
} else {
assert(result);
std::vector<FileInfoResponse::FileInfo> file_infos{};
file_infos.reserve(paths_count);
auto info_ptr{result};
while(info_ptr->query_result >= 0) {
auto& info = file_infos.emplace_back();
switch (info_ptr->query_result) {
case 0:
/* success */
info.status = FileInfoResponse::StatusType::SUCCESS;
path_info_to_directory_entry(info.info, *info_ptr);
break;
case 1:
info.status = FileInfoResponse::StatusType::PATH_EXCEEDS_ROOT_PATH;
break;
case 2:
info.status = FileInfoResponse::StatusType::PATH_DOES_NOT_EXISTS;
break;
case 3:
info.status = FileInfoResponse::StatusType::UNKNOWN_FILE_TYPE;
break;
default:
info.status = FileInfoResponse::StatusType::FAILED_TO_QUERY_INFO;
info.error_detail = std::string{"invalid query result "} + std::to_string(info_ptr->query_result);
break;
}
info_ptr++;
}
assert(file_infos.size() == paths_count);
libteaspeak_file_free_file_info(result);
response->emplace_success(FileInfoResponse{file_infos});
}
return response;
}
std::shared_ptr<RustFileSystem::directory_query_response_t> RustFileSystem::execute_query(const char *server_unique_id, void *path) {
auto response = this->create_execute_response<DirectoryQueryError, std::deque<DirectoryEntry>>();
const TeaFilePathInfo* result{nullptr};
auto error_ptr = libteaspeak_file_system_query_directory(server_unique_id, (const TeaFilePath*) path, &result);
if(error_ptr) {
libteaspeak_file_free_file_info(result);
response->emplace_fail(DirectoryQueryErrorType::UNKNOWN, error_ptr);
libteaspeak_free_str(error_ptr);
} else {
assert(result);
/* result->query_result could be zero or minus one in both cases we're iterating the query result */
if(result->query_result > 0) {
/* An error occurred */
switch(result->query_result) {
case 1:
response->emplace_fail(DirectoryQueryErrorType::PATH_EXCEEDS_ROOT_PATH, "");
break;
case 2:
response->emplace_fail(DirectoryQueryErrorType::PATH_DOES_NOT_EXISTS, "");
break;
case 4:
response->emplace_fail(DirectoryQueryErrorType::PATH_IS_A_FILE, "");
break;
case 5:
response->emplace_fail(DirectoryQueryErrorType::UNKNOWN, "io error");
break;
default:
response->emplace_fail(DirectoryQueryErrorType::UNKNOWN, std::string{"unknown error result "} + std::to_string(result->query_result));
break;
}
} else {
std::deque<DirectoryEntry> entries{};
auto info_ptr{result};
while(info_ptr->query_result >= 0) {
assert(info_ptr->query_result == 0);
path_info_to_directory_entry(entries.emplace_back(), *info_ptr);
info_ptr++;
}
response->emplace_success(std::move(entries));
}
libteaspeak_file_free_file_info(result);
}
return response;
}
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> RustFileSystem::delete_channel_files(
const std::shared_ptr<VirtualFileServer> &virtual_server, ChannelId channel_id, const std::vector<std::string> &files) {
std::vector<TeaFilePath> file_paths{};
file_paths.reserve(files.size());
for(const auto& path : files) {
file_paths.push_back(TeaFilePath{
.type = 0,
.channel_id = channel_id,
.path = path.c_str()
});
}
return this->execute_delete(virtual_server->unique_id().c_str(), file_paths.data(), file_paths.size());
}
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> RustFileSystem::delete_icons(
const std::shared_ptr<VirtualFileServer> &virtual_server, const std::vector<std::string> &files) {
std::vector<TeaFilePath> file_paths{};
file_paths.reserve(files.size());
for(const auto& path : files) {
file_paths.push_back(TeaFilePath{
.type = 1,
.channel_id = 0,
.path = path.c_str()
});
}
return this->execute_delete(virtual_server->unique_id().c_str(), file_paths.data(), file_paths.size());
}
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> RustFileSystem::delete_avatars(
const std::shared_ptr<VirtualFileServer> &virtual_server, const std::vector<std::string> &files) {
std::vector<TeaFilePath> file_paths{};
file_paths.reserve(files.size());
for(const auto& path : files) {
file_paths.push_back(TeaFilePath{
.type = 2,
.channel_id = 0,
.path = path.c_str()
});
}
return this->execute_delete(virtual_server->unique_id().c_str(), file_paths.data(), file_paths.size());
}
inline void path_info_to_delete_result(FileDeleteResponse::DeleteResult& result, const TeaFilePathInfo& info) {
switch (info.query_result) {
case 0:
result.status = FileDeleteResponse::StatusType::SUCCESS;
break;
case 1:
result.status = FileDeleteResponse::StatusType::PATH_EXCEEDS_ROOT_PATH;
break;
case 2:
result.status = FileDeleteResponse::StatusType::PATH_DOES_NOT_EXISTS;
break;
case 5:
result.status = FileDeleteResponse::StatusType::FAILED_TO_DELETE_FILES;
break;
case 6:
result.status = FileDeleteResponse::StatusType::SOME_FILES_ARE_LOCKED;
break;
default:
result.status = FileDeleteResponse::StatusType::FAILED_TO_DELETE_FILES;
result.error_detail = std::string{"unknown delete error "} + std::to_string(info.query_result);
break;
}
}
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> RustFileSystem::execute_delete(const char *server_unique_id, void *paths,
size_t paths_count) {
auto response = this->create_execute_response<FileDeleteError, FileDeleteResponse>();
const TeaFilePathInfo* result{nullptr};
auto error_ptr = libteaspeak_file_system_delete_files(server_unique_id, (const TeaFilePath*) paths, paths_count, &result);
if(error_ptr) {
libteaspeak_file_free_file_info(result);
response->emplace_fail(FileDeleteErrorType::UNKNOWN, error_ptr);
libteaspeak_free_str(error_ptr);
} else {
assert(result);
std::vector<FileDeleteResponse::DeleteResult> results{};
results.reserve(paths_count);
auto info_ptr{result};
while(info_ptr->query_result >= 0) {
path_info_to_delete_result(results.emplace_back(), *info_ptr);
info_ptr++;
}
assert(results.size() == paths_count);
libteaspeak_file_free_file_info(result);
response->emplace_success(FileDeleteResponse{results});
}
return response;
}
std::shared_ptr<ExecuteResponse<DirectoryModifyError>> RustFileSystem::create_channel_directory(
const std::shared_ptr<VirtualFileServer> &virtual_server, ChannelId channelId, const std::string &path) {
auto response = this->create_execute_response<DirectoryModifyError>();
auto result = libteaspeak_file_system_create_channel_directory(virtual_server->unique_id().c_str(), channelId, path.c_str());
switch (result) {
case 0:
response->emplace_success();
break;
case 1:
response->emplace_fail(DirectoryModifyErrorType::UNKNOWN, "server not found");
break;
case 2:
response->emplace_fail(DirectoryModifyErrorType::PATH_EXCEEDS_ROOT_PATH, "");
break;
case 3:
response->emplace_fail(DirectoryModifyErrorType::PATH_ALREADY_EXISTS, "");
break;
case 4:
response->emplace_fail(DirectoryModifyErrorType::FAILED_TO_CREATE_DIRECTORIES, "");
break;
default:
response->emplace_fail(DirectoryModifyErrorType::UNKNOWN, std::string{"invalid return code "} + std::to_string(result));
break;
}
return response;
}
std::shared_ptr<ExecuteResponse<FileModifyError>> RustFileSystem::rename_channel_file(
const std::shared_ptr<VirtualFileServer> &virtual_server, ChannelId old_channel_id, const std::string &old_path, ChannelId new_channel_id,
const std::string &new_path) {
auto response = this->create_execute_response<FileModifyError>();
auto result = libteaspeak_file_system_rename_channel_file(virtual_server->unique_id().c_str(), old_channel_id, old_path.c_str(), new_channel_id, new_path.c_str());
switch (result) {
case 0:
response->emplace_success();
break;
case 1:
response->emplace_fail(FileModifyErrorType::UNKNOWN, "server not found");
break;
case 2:
response->emplace_fail(FileModifyErrorType::UNKNOWN, "invalid path types");
break;
case 3:
response->emplace_fail(FileModifyErrorType::PATH_EXCEEDS_ROOT_PATH, "");
break;
case 4:
response->emplace_fail(FileModifyErrorType::TARGET_PATH_EXCEEDS_ROOT_PATH, "");
break;
case 5:
response->emplace_fail(FileModifyErrorType::PATH_DOES_NOT_EXISTS, "");
break;
case 6:
response->emplace_fail(FileModifyErrorType::SOME_FILES_ARE_LOCKED, "");
break;
case 7:
response->emplace_fail(FileModifyErrorType::TARGET_PATH_ALREADY_EXISTS, "");
break;
case 8:
response->emplace_fail(FileModifyErrorType::FAILED_TO_RENAME_FILE, "");
break;
default:
response->emplace_fail(FileModifyErrorType::UNKNOWN, std::string{"invalid return code "} + std::to_string(result));
break;
}
return response;
}
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
RustFileTransfer::initialize_channel_transfer(transfer::Transfer::Direction direction,
const std::shared_ptr<VirtualFileServer> &ptr, ChannelId id,
const transfer::AbstractProvider::TransferInfo &info) {
auto result = this->create_execute_response<TransferInitError, std::shared_ptr<Transfer>>();
result->emplace_fail(TransferInitError::Type::UNKNOWN, "");
return result;
}
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
RustFileTransfer::initialize_icon_transfer(transfer::Transfer::Direction direction,
const std::shared_ptr<VirtualFileServer> &ptr,
const transfer::AbstractProvider::TransferInfo &info) {
auto result = this->create_execute_response<TransferInitError, std::shared_ptr<Transfer>>();
result->emplace_fail(TransferInitError::Type::UNKNOWN, "");
return result;
}
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
RustFileTransfer::initialize_avatar_transfer(transfer::Transfer::Direction direction,
const std::shared_ptr<VirtualFileServer> &ptr,
const transfer::AbstractProvider::TransferInfo &info) {
auto result = this->create_execute_response<TransferInitError, std::shared_ptr<Transfer>>();
result->emplace_fail(TransferInitError::Type::UNKNOWN, "");
return result;
}
std::shared_ptr<ExecuteResponse<TransferListError, std::vector<ActiveFileTransfer>>>
RustFileTransfer::list_transfer() {
auto result = this->create_execute_response<TransferListError, std::vector<ActiveFileTransfer>>();
result->emplace_fail(TransferListError::UNKNOWN);
return result;
}
std::shared_ptr<ExecuteResponse<TransferActionError>>
RustFileTransfer::stop_transfer(const std::shared_ptr<VirtualFileServer> &ptr, transfer::transfer_id id,
bool b) {
auto result = this->create_execute_response<TransferActionError>();
result->emplace_fail(TransferActionError{TransferActionError::Type::UNKNOWN_TRANSFER});
return result;
}
RustFileProvider::RustFileProvider() :
file_system_{new RustFileSystem{}},
file_transfer_{new RustFileTransfer{}}
{}
RustFileProvider::~RustFileProvider() {
delete this->file_transfer_;
delete this->file_system_;
}
std::string RustFileProvider::file_base_path() const {
return "TODO!"; /* FIXME! */
}
filesystem::AbstractProvider & RustFileProvider::file_system() {
return *this->file_system_;
}
transfer::AbstractProvider & RustFileProvider::file_transfer() {
return *this->file_transfer_;
}
std::shared_ptr<VirtualFileServer> RustFileProvider::register_server(ServerId server_id) {
auto server = this->find_virtual_server(server_id);
if(server) return server;
server = std::make_shared<file::RustVirtualFileServer>(server_id, std::to_string(server_id));
libteaspeak_file_system_register_server(server->unique_id().c_str());
{
std::lock_guard slock{this->servers_mutex};
this->servers_.push_back(server);
}
return server;
}
void RustFileProvider::unregister_server(ServerId server_id, bool delete_files) {
auto server_unique_id = std::to_string(server_id);
std::shared_ptr<VirtualFileServer> server{};
{
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;
}
server = *it;
this->servers_.erase(it);
}
libteaspeak_file_system_unregister_server(server->unique_id().c_str(), delete_files);
}
void RustVirtualFileServer::max_networking_download_bandwidth(int64_t value) {
VirtualFileServer::max_networking_download_bandwidth(value);
}
void RustVirtualFileServer::max_networking_upload_bandwidth(int64_t value) {
VirtualFileServer::max_networking_upload_bandwidth(value);
}
#define log(...) \
switch(level) { \
case 0: \
logTrace(__VA_ARGS__); \
break; \
case 1: \
debugMessage(__VA_ARGS__); \
break; \
case 2: \
logMessage(__VA_ARGS__); \
break; \
case 3: \
logWarning(__VA_ARGS__); \
break; \
case 4: \
logError(__VA_ARGS__); \
break; \
case 5: \
logCritical(__VA_ARGS__); \
break; \
default: \
debugMessage(__VA_ARGS__); \
break; \
}
void libteaspeak_file_callback_log(uint8_t level, const void* callback_data_ptr, const char* message_ptr, uint32_t length) {
std::string_view message{message_ptr, length};
log(LOG_GENERAL, "{}", message);
}
#undef log
static TeaFileNativeCallbacks native_callbacks{
.version = 1,
.log = libteaspeak_file_callback_log,
};
std::shared_ptr<AbstractFileServer> file_instance{};
bool file::initialize(std::string& error, const std::string& host_names, uint16_t port) {
logMessage(LOG_FT, "Initializing file server with version {}", libteaspeak_file_version());
auto error_ptr = libteaspeak_file_initialize(&native_callbacks, sizeof native_callbacks);
if(error_ptr) {
error = error_ptr;
libteaspeak_free_str(error_ptr);
return false;
}
/* FIXME: Start server */
file_instance = std::make_shared<RustFileProvider>();
return true;
}
void file::finalize() {
file_instance = nullptr;
libteaspeak_file_finalize();
}
std::shared_ptr<AbstractFileServer> file::server() {
return file_instance;
}
+123
View File
@@ -0,0 +1,123 @@
//
// Created by WolverinDEV on 20/04/2021.
//
#pragma once
#include <files/FileServer.h>
namespace ts::server::file {
namespace filesystem {
class RustFileSystem : public filesystem::AbstractProvider {
using FileModifyError = filesystem::FileModifyError;
using DirectoryModifyError = filesystem::DirectoryModifyError;
public:
virtual ~RustFileSystem();
std::shared_ptr<ExecuteResponse<ServerCommandError>> initialize_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:
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<FileInfoError, FileInfoResponse>> execute_info(const char* /* server unique id */, void* /* query */, size_t /* query count */);
std::shared_ptr<directory_query_response_t> execute_query(const char* /* server unique id */, void* /* directory */);
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> execute_delete(const char* /* server unique id */, void* /* files */, size_t /* file count */);
std::mutex result_notify_mutex{};
std::condition_variable result_notify_cv{};
};
}
namespace transfer {
class RustFileTransfer : public AbstractProvider {
public:
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
initialize_channel_transfer(Transfer::Direction direction,
const std::shared_ptr<VirtualFileServer> &ptr, ChannelId id,
const TransferInfo &info) override;
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
initialize_icon_transfer(Transfer::Direction direction, const std::shared_ptr<VirtualFileServer> &ptr,
const TransferInfo &info) override;
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
initialize_avatar_transfer(Transfer::Direction direction, const std::shared_ptr<VirtualFileServer> &ptr,
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> &ptr, transfer_id id, bool b) override;
private:
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::mutex result_notify_mutex{};
std::condition_variable result_notify_cv{};
};
}
class RustVirtualFileServer : public VirtualFileServer {
public:
explicit RustVirtualFileServer(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;
};
class RustFileProvider : public AbstractFileServer {
public:
RustFileProvider();
virtual ~RustFileProvider();
[[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 */, bool /* delete files */) override;
private:
filesystem::RustFileSystem* file_system_;
transfer::RustFileTransfer* file_transfer_;
};
}
+13
View File
@@ -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 */
}
+35
View File
@@ -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;
}
+7
View File
@@ -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 */);
}
+174
View File
@@ -0,0 +1,174 @@
//
// 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(!std::get<2>(binding).empty()) {
logError(LOG_FT, "Failed to resolve binding for {}: {}", std::get<0>(binding), std::get<2>(binding));
continue;
}
auto result = dynamic_cast<transfer::LocalFileTransfer&>(server_instance->file_transfer()).add_network_binding({ std::get<0>(binding), std::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", std::get<0>(binding));
continue;
case transfer::NetworkingBindResult::FAILED_TO_LISTEN:
logWarning(LOG_FT, "Failed to listen on {}: {}/{}", std::get<0>(binding), errno, strerror(errno));
continue;
case transfer::NetworkingBindResult::FAILED_TO_BIND:
logWarning(LOG_FT, "Failed to bind on {}: {}/{}", std::get<0>(binding), errno, strerror(errno));
continue;
case transfer::NetworkingBindResult::BINDING_ALREADY_EXISTS:
logWarning(LOG_FT, "Failed to bind on {}: binding already exists", std::get<0>(binding));
continue;
case transfer::NetworkingBindResult::NETWORKING_NOT_INITIALIZED:
logWarning(LOG_FT, "Failed to bind on {}: networking not initialized", std::get<0>(binding));
continue;
case transfer::NetworkingBindResult::FAILED_TO_ALLOCATE_SOCKET:
logWarning(LOG_FT, "Failed to allocate a socket for {}: {}/{}", std::get<0>(binding), errno, strerror(errno));
continue;
}
}
return any_bind;
}
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, bool delete_files) {
auto server_unique_id = std::to_string(server_id);
std::shared_ptr<VirtualFileServer> server{};
{
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;
}
server = *it;
this->servers_.erase(it);
}
using ErrorType = file::filesystem::ServerCommandErrorType;
auto delete_result = this->file_system_->delete_server(server);
if(!delete_result->wait_for(std::chrono::seconds{5})) {
logError(LOG_INSTANCE, "Failed to wait for file directory deletion.");
} else if(!delete_result->succeeded()) {
switch (delete_result->error().error_type) {
case ErrorType::FAILED_TO_DELETE_DIRECTORIES:
logError(LOG_INSTANCE, "Failed to delete server {} file directories ({}).", server->server_id(), delete_result->error().error_message);
break;
case ErrorType::UNKNOWN:
case ErrorType::FAILED_TO_CREATE_DIRECTORIES:
logError(LOG_INSTANCE, "Failed to delete server {} file directory due to an unknown error: {}/{}",
server->server_id(), (int) delete_result->error().error_type, delete_result->error().error_message);
break;
}
}
}
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);
}
+52
View File
@@ -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 */, bool /* delete server */) override;
private:
filesystem::LocalFileSystem* file_system_;
transfer::LocalFileTransfer* file_transfer_;
};
}
+457
View File
@@ -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 &current_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);
}
+117
View File
@@ -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 */);
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_{};
};
}
+395
View File
@@ -0,0 +1,395 @@
//
// 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;
buffer->ref_count = 1;
return buffer;
}
Buffer* transfer::ref_buffer(Buffer *buffer) {
buffer->ref_count++;
return buffer;
}
void transfer::deref_buffer(Buffer *buffer) {
if(--buffer->ref_count == 0) {
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"};
transfer->transfer_key.resize(TRANSFER_KEY_LENGTH);
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;
}
+449
View File
@@ -0,0 +1,449 @@
//
// 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};
std::atomic_uint32_t ref_count{0};
uint32_t capacity{0};
uint32_t length{0};
uint32_t offset{0};
char data[1]{};
};
[[nodiscard]] extern Buffer* allocate_buffer(size_t);
[[nodiscard]] extern Buffer* ref_buffer(Buffer*);
extern void deref_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(deref_buffer)*> http_header_buffer{nullptr, deref_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);
}
+591
View File
@@ -0,0 +1,591 @@
//
// 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:
deref_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;
deref_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};
if(!client->disk_buffer.buffer_head) {
assert(client->disk_buffer.bytes == 0);
buffer_left_size = 0;
break;
}
buffer_left_size = client->disk_buffer.bytes;
buffer = ref_buffer(client->disk_buffer.buffer_head);
}
assert(buffer->offset < buffer->length);
auto written = ::write(client->file.file_descriptor, buffer->data + buffer->offset, buffer->length - buffer->offset);
if(written <= 0) {
deref_buffer(buffer);
if(errno == EAGAIN) {
//TODO: Timeout?
this->enqueue_disk_io(client);
break;
}
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 {
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};
if(client->disk_buffer.buffer_head == buffer) {
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;
/* We have to deref the buffer twice since we've removed it from the list which owns us one reference */
/* Will not trigger a memory free since we're still holding onto one reference */
deref_buffer(buffer);
} else {
/* The buffer got removed */
}
}
} else {
std::lock_guard block{client->disk_buffer.mutex};
if(client->disk_buffer.buffer_head == buffer) {
assert(client->disk_buffer.bytes >= written);
client->disk_buffer.bytes -= written;
buffer_left_size = client->disk_buffer.bytes;
} else {
/* The buffer got removed */
}
}
deref_buffer(buffer);
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(errno == EAGAIN) {
this->enqueue_disk_io(client);
return;
}
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 {
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
+9
View File
@@ -0,0 +1,9 @@
//
// Created by WolverinDEV on 12/05/2020.
//
#include "./NetTools.h"
using namespace ts::server::file::networking;
NetworkThrottle NetworkThrottle::kNoThrottle{-1};
+169
View File
@@ -0,0 +1,169 @@
#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);
}
};
}
+283
View File
@@ -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
+6
View File
@@ -0,0 +1,6 @@
#pragma once
#include <string>
#include <string_view>
extern std::string clnpath(const std::string_view&);
+35
View File
@@ -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);
}
+242
View File
@@ -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;
}
+2
View File
@@ -0,0 +1,2 @@
Hello World
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+1
View File
@@ -0,0 +1 @@
Test HTTPS file upload!
+9 -7
View File
@@ -29,12 +29,13 @@ target_link_libraries(TeaLicenseHelper PUBLIC
TeaSpeak
protobuf::libprotobuf
libevent::core libevent::pthreads
openssl::ssl::shared
openssl::crypto::shared
# 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})
@@ -49,7 +50,7 @@ add_executable(TeaLicenseServer ${LICENCE_SOURCE_FILES}
server/StatisticManager.cpp
server/UserManager.cpp
LicenseServerMain.cpp
MySQLLibSSLFix.c
# Only for boringssl: MySQLLibSSLFix.c
)
target_link_libraries(TeaLicenseServer
@@ -61,13 +62,14 @@ target_link_libraries(TeaLicenseServer
mysqlclient
jsoncpp_lib
DataPipes::core::shared
openssl::ssl::shared
openssl::crypto::shared
DataPipes::core::static
openssl::ssl::static
openssl::crypto::static
pthread
dl
z
z
rt
)
#The test license client
+47 -9
View File
@@ -18,7 +18,7 @@ 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`
*
@@ -26,12 +26,28 @@ using namespace license;
* //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::database::DatabaseHandler> license_manager;
@@ -64,10 +80,21 @@ inline std::shared_ptr<WebCertificate> load_web_certificate() {
return nullptr;
}
if(!BIO_mem_contents(&*bio, &mem_ptr, &length)) {
logError(0, "Failed to receive memptr to private key");
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);
}
@@ -78,10 +105,21 @@ inline std::shared_ptr<WebCertificate> load_web_certificate() {
logError(0, "Failed to export certificate");
return nullptr;
}
if(!BIO_mem_contents(&*bio, &mem_ptr, &length)) {
logError(0, "Failed to receive memptr to 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);
}
+5 -3
View File
@@ -117,12 +117,12 @@ bool DatabaseHandler::setup(std::string& error) {
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);");
CTBL("CREATE TABLE license_upgrade_log (`upgrade_id` INT, `timestamp` INT, `unique_id` VARCHAR(64), `server_ip` VARCHAR(32), `succeeded` TINYINT);");
CIDX("CREATE INDEX `upgrade_id_timestamp` ON `license_upgrade_log` (`upgrade_id`, `timestamp`)");
SET_VERSION(7);
default:;
}
}
return true;
}
@@ -408,6 +408,7 @@ std::shared_ptr<DatabaseHandler::UserHistory> DatabaseHandler::list_statistics_u
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;
@@ -441,6 +442,7 @@ std::shared_ptr<DatabaseHandler::UserHistory> DatabaseHandler::list_statistics_u
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;
@@ -531,7 +533,7 @@ std::deque<std::unique_ptr<DatabaseHandler::GlobalVersionsStatistic>> DatabaseHa
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::system_clock::now().time_since_epoch().count();
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},
+4 -3
View File
@@ -5,8 +5,8 @@
#include <LicenseRequest.pb.h>
namespace license::server::database {
typedef size_t license_key_id_t;
typedef size_t upgrade_id_t;
typedef uint64_t license_key_id_t;
typedef uint64_t upgrade_id_t;
class DatabaseHandler;
class KeyIdCache {
@@ -70,7 +70,8 @@ namespace license::server::database {
};
struct GlobalUserStatistics : public UserStatistics {
uint64_t instance_online = 0;
uint64_t instance_online{0};
uint64_t instance_empty{0};
};
struct DatabaseUserStatistics : public UserStatistics {
+30 -9
View File
@@ -134,16 +134,19 @@ void LicenseServer::handleEventWrite(int fd, short, void* ptrServer) {
if(!client) return;
buffer::RawBuffer* write_buffer{nullptr};
std::unique_lock write_lock(client->network.write_queue_lock);
while(true) { //TODO: May add some kind of timeout?
std::lock_guard lock(client->network.write_queue_lock);
write_buffer = TAILQ_FIRST(&client->network.write_queue);
if(!write_buffer) return;
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;
}
@@ -187,7 +190,7 @@ void ConnectedClient::init() {
TAILQ_INIT(&network.write_queue);
}
void ConnectedClient::uninit() {
void ConnectedClient::uninit(bool blocking) {
{
lock_guard queue_lock{this->network.write_queue_lock};
ts::buffer::RawBuffer* buffer;
@@ -201,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) {
@@ -221,12 +234,20 @@ void LicenseServer::handleEventRead(int fd, short, void* ptrServer) {
if(read < 0){
if(errno == EWOULDBLOCK) return;
logError(LOG_LICENSE_CONTROLL, "Invalid read. Disconnecting remote client. Message: {}/{}", errno, strerror(errno));
event_del_noblock(client->network.readEvent);
{
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) {
logMessage(LOG_LICENSE_CONTROLL, "[CLIENT][" + client->address() + "] Received EOF for client. Removing client.");
event_del_noblock(client->network.readEvent);
{
std::lock_guard elock{client->network.event_mutex};
if(client->network.readEvent)
event_del_noblock(client->network.readEvent);
}
server->closeConnection(client);
return;
}
@@ -312,7 +333,7 @@ void LicenseServer::unregisterClient(const std::shared_ptr<ConnectedClient> &cli
std::lock_guard state_lock{client->protocol.state_lock};
client->protocol.state = protocol::UNCONNECTED;
}
client->uninit();
client->uninit(this_thread::get_id() != this->event_base_dispatch.get_id());
}
void LicenseServer::cleanup_clients() {
+3 -1
View File
@@ -31,6 +31,8 @@ namespace license {
struct {
sockaddr_in remoteAddr;
int fileDescriptor = 0;
std::mutex event_mutex{};
event* readEvent = nullptr; /* protected via state_lock (check state and the use these variables) */
event* writeEvent = nullptr;
@@ -57,7 +59,7 @@ namespace license {
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); }
+14 -8
View File
@@ -188,7 +188,7 @@ bool LicenseServer::handleServerValidation(shared_ptr<ConnectedClient> &client,
logMessage(LOG_GENERAL, "[CLIENT][{}] Remote license has been deleted! Shutting down server!", client->address());
} else {
fill_info(response.mutable_license_info(), info, remote_license->data.licenceKey);
auto is_invalid = !info->isValid();
auto is_invalid = !info->isNotExpired();
if(is_invalid) {
response.set_invalid_reason("license is invalid");
response.set_valid(false);
@@ -198,9 +198,15 @@ bool LicenseServer::handleServerValidation(shared_ptr<ConnectedClient> &client,
}
}
this->manager->logRequest(remote_license->key(), client->unique_identifier, client->address(), pkt.info().version(), response.valid());
} else {
} 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())
@@ -289,17 +295,17 @@ bool LicenseServer::handlePacketPropertyUpdate(shared_ptr<ConnectedClient> &clie
this->manager->logStatistic(client->key, client->unique_identifier, client->address(), pkt);
//TODO test stuff if its possible!
ts::proto::license::WebCertificate* web_certificate{nullptr};
ts::proto::license::WebCertificate* proto_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);
proto_web_certificate = new ts::proto::license::WebCertificate{};
proto_web_certificate->set_key(cert->key);
proto_web_certificate->set_certificate(cert->certificate);
proto_web_certificate->set_revision(cert->revision);
}
}
@@ -308,7 +314,7 @@ bool LicenseServer::handlePacketPropertyUpdate(shared_ptr<ConnectedClient> &clie
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);
response.set_allocated_web_certificate(proto_web_certificate);
client->sendPacket(protocol::packet{protocol::PACKET_SERVER_PROPERTY_ADJUSTMENT, response});
this->disconnectClient(client, "finished");
+2
View File
@@ -139,6 +139,7 @@ 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);
@@ -148,6 +149,7 @@ std::shared_ptr<GeneralStatistics> StatisticManager::general_statistics() {
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();
+57 -58
View File
@@ -5,70 +5,69 @@
#include <chrono>
#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 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);
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::shared_ptr<server::database::DatabaseHandler::UserHistory> statistics;
};
std::shared_ptr<server::database::DatabaseHandler::UserHistory> statistics;
};
class StatisticManager {
public:
explicit StatisticManager(std::shared_ptr<server::database::DatabaseHandler> /* 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::database::DatabaseHandler> 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;
};
}
+38 -22
View File
@@ -7,6 +7,7 @@
#include <misc/net.h>
#include <misc/endianness.h>
#include <json/json.h>
#include <log/LogUtils.h>
#include "StatisticManager.h"
#include "WebAPI.h"
@@ -197,14 +198,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);
}
}
@@ -296,17 +298,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;
@@ -333,17 +339,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
@@ -352,6 +361,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;
@@ -472,6 +482,7 @@ bool WebStatistics::handle_message(const std::shared_ptr<license::web::WebStatis
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;
@@ -519,6 +530,7 @@ bool WebStatistics::handle_message(const std::shared_ptr<license::web::WebStatis
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;
@@ -555,16 +567,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;
-1
View File
@@ -92,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 -1
View File
@@ -8,7 +8,7 @@
#include "license.h"
namespace license::client {
class LicenseServerClient {
class LicenseServerClient : public std::enable_shared_from_this<LicenseServerClient> {
public:
enum ConnectionState {
CONNECTING,
+3 -3
View File
@@ -346,9 +346,9 @@ namespace license {
std::chrono::system_clock::time_point creation;
bool deleted{false};
uint32_t upgrade_id{0};
uint64_t upgrade_id{0};
inline bool isValid() { return (end.time_since_epoch().count() == 0 || std::chrono::system_clock::now() < this->end); }
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 &);
@@ -422,7 +422,7 @@ namespace license {
#ifdef GOOGLE_PROTOBUF_MESSAGE_H__
packet(PacketType packetId, const ::google::protobuf::Message&);
#endif
packet(PacketType packetId, nullptr_t);
packet(PacketType packetId, std::nullptr_t);
};
}
}
+7 -5
View File
@@ -41,15 +41,17 @@ message ServerValidation {
required bool license_info = 2;
optional bytes license = 3;
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 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 available when ServerValidation::license_info = true
optional bool update_pending = 4; /* if an update is pending */
required Blacklist blacklist = 2;
optional LicenseInfo license_info = 3; //Only available when ServerValidation::license_info = true
optional bool update_pending = 4; /* if an update is pending */
optional bool deprecate_third_party_clients = 6;
}
message PropertyUpdateRequest {
+28 -9
View File
@@ -7,6 +7,8 @@
#include <event.h>
#include <ThreadPool/ThreadHelper.h>
#include <misc/endianness.h>
#include <shared/include/license/client.h>
#include <log/LogUtils.h>
#include "shared/include/license/client.h"
#include "crypt.h"
@@ -42,11 +44,16 @@ LicenseServerClient::LicenseServerClient(const sockaddr_in &address, int pversio
}
LicenseServerClient::~LicenseServerClient() {
this->close_connection();
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);
threads::save_join(this->network.event_dispatch, false);
}
bool LicenseServerClient::start_connection(std::string &error) {
@@ -88,14 +95,25 @@ bool LicenseServerClient::start_connection(std::string &error) {
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);
event_dispatch_spawned = true;
this->network.event_dispatch = std::thread([&] {
signal(SIGPIPE, SIG_IGN);
@@ -114,10 +132,6 @@ bool LicenseServerClient::start_connection(std::string &error) {
return true;
error_cleanup:
this->cleanup_network_resources();
if(!event_dispatch_spawned) {
event_base_free(this->network.event_base);
this->network.event_base = nullptr;
}
this->connection_state = ConnectionState::UNCONNECTED;
return false;
}
@@ -164,7 +178,7 @@ void LicenseServerClient::cleanup_network_resources() {
auto buffer = TAILQ_FIRST(&this->buffers.write);
while(buffer) {
auto next = TAILQ_NEXT(buffer, tail);
Buffer::free(next);
Buffer::free(buffer);
buffer = next;
}
TAILQ_INIT(&this->buffers.write);
@@ -243,8 +257,11 @@ void LicenseServerClient::callback_write(short events) {
}
if(events & EV_WRITE) {
if(this->connection_state == ConnectionState::CONNECTING)
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};
@@ -365,6 +382,8 @@ void LicenseServerClient::handle_data(void *recv_buffer, size_t length) {
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);
}
+1 -1
Submodule music updated: ae7f8c7f3d...a4dabbb5c4
Submodule
+1
Submodule rtclib added at 6bb3ce6050
+90 -70
View File
@@ -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 -pthread ${MEMORY_DEBUG_FLAGS} -Werror=return-type")
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 -Werror=delete-non-virtual-dtor")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3")
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -O3")
@@ -39,40 +39,36 @@ set(CMAKE_VERBOSE_MAKEFILE ON)
set(SERVER_SOURCE_FILES
main.cpp
tomcryptTest.cpp
MySQLLibSSLFix.c
# Only needed for boringssl
# MySQLLibSSLFix.c
src/client/ConnectedClient.cpp
src/client/voice/PrecomputedPuzzles.cpp
src/server/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/client/voice/VoiceClientConnectionPacketHandler.cpp
src/TS3ServerClientManager.cpp
src/VirtualServer.cpp
src/FileServerHandler.cpp
src/TS3ServerHeartbeat.cpp
src/SignalHandler.cpp
src/server/VoiceServer.cpp
src/server/VoiceServerSocket.cpp
src/server/POWHandler.cpp
src/client/voice/VoiceClientConnection.cpp
#src/client/ConnectedClientCommandHandler.cpp
src/client/command_handler/groups.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/server/file/FileServer.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/client/DataClient.cpp
src/server/QueryServer.cpp
@@ -80,12 +76,12 @@ set(SERVER_SOURCE_FILES
src/client/query/QueryClientCommands.cpp
src/client/query/QueryClientNotify.cpp
src/manager/IpListManager.cpp
src/server/GlobalNetworkEvents.cpp
src/ConnectionStatistics.cpp
src/manager/TokeManager.cpp
src/manager/TokenManager.cpp
src/terminal/CommandHandler.cpp
@@ -95,13 +91,14 @@ set(SERVER_SOURCE_FILES
src/manager/LetterManager.cpp
src/manager/PermissionNameManager.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/InstanceHandlerPermissions.cpp
src/PermissionCalculator.cpp
src/Configuration.cpp
@@ -120,8 +117,6 @@ set(SERVER_SOURCE_FILES
src/client/query/XMacroEventTypes.h
src/server/VoiceIOManager.cpp
src/server/WebIoManager.cpp
src/client/SpeakingClient.cpp
../shared/src/ssl/SSLManager.cpp
@@ -129,15 +124,41 @@ set(SERVER_SOURCE_FILES
src/manager/SqlDataManager.cpp
src/ShutdownHelper.cpp
src/client/music/MusicQueue.cpp
src/lincense/TeamSpeakLicense.cpp
src/weblist/WebListManager.cpp
src/weblist/TeamSpeakWebClient.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/groups/Group.cpp
src/groups/GroupManager.cpp
src/groups/GroupAssignmentManager.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/command_handler/music.cpp src/client/command_handler/file.cpp
src/client/voice/PacketEncoder.cpp
src/client/shared/ServerCommandExecutor.cpp
src/client/voice/CryptSetupHandler.cpp
src/client/shared/WhisperHandler.cpp
src/terminal/PipedTerminal.cpp
src/server/voice/UDPVoiceServer.cpp
src/server/voice/DatagramPacket.cpp
src/rtc/lib.cpp
)
if (COMPILE_WEB_CLIENT)
add_definitions(-DCOMPILE_WEB_CLIENT)
@@ -146,51 +167,18 @@ if (COMPILE_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/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}
TeaSpeak #Static
TeaLicenseHelper #Static
TeaMusic #Static
${LIBRARY_PATH_THREAD_POOL} #Static
${LIBRARY_PATH_TERMINAL} #Static
${LIBRARY_PATH_VARIBALES}
${LIBRARY_PATH_YAML}
pthread
TeaSpeak
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}
#We're forsed to use boringssl caused by the fact that boringssl is already within webrtc!
#Require a so
sqlite3
${LIBRARY_PATH_BREAKPAD}
${LIBRARY_PATH_JDBC}
${LIBRARY_PATH_PROTOBUF}
${LIBRARY_PATH_DATA_PIPES}
${LIBRARY_PATH_BORINGSSL_SSL}
${LIBRARY_PATH_BORINGSSL_CRYPTO}
dl
jemalloc
)
CXXTerminal::static
libevent::core libevent::pthreads
${StringVariable_LIBRARIES_STATIC}
)
add_executable(PermMapHelper helpers/PermMapGen.cpp)
target_link_libraries(PermMapHelper
@@ -233,8 +221,8 @@ target_link_libraries(PermMapHelper
SET(CPACK_PACKAGE_VERSION_MAJOR "1")
SET(CPACK_PACKAGE_VERSION_MINOR "4")
SET(CPACK_PACKAGE_VERSION_PATCH "10")
SET(CPACK_PACKAGE_VERSION_MINOR "5")
SET(CPACK_PACKAGE_VERSION_PATCH "6")
if (BUILD_TYPE_NAME EQUAL OFF)
SET(CPACK_PACKAGE_VERSION_DATA "beta")
elseif (BUILD_TYPE_NAME STREQUAL "")
@@ -256,6 +244,8 @@ target_link_libraries(TeaSpeakServer
TeaLicenseHelper #Static
TeaMusic #Static
CXXTerminal::static #Static
TeaSpeak-FileServer
# TeaSpeak-FileRust
${StringVariable_LIBRARIES_STATIC}
${YAML_CPP_LIBRARIES}
pthread
@@ -266,24 +256,34 @@ target_link_libraries(TeaSpeakServer
#Require a so
sqlite3
DataPipes::rtc::shared
DataPipes::core::static
breakpad::static
protobuf::libprotobuf
jemalloc::shared
#jemalloc::shared
tomcrypt::static
tommath::static
jsoncpp_lib
${ed25519_LIBRARIES_STATIC}
zstd::libzstd_static
)
if (COMPILE_WEB_CLIENT)
target_link_libraries(TeaSpeakServer ${glib20_DIR}/lib/x86_64-linux-gnu/libffi.so.7 ${nice_DIR}/lib/libnice.so.10)
endif ()
if(EXISTS "${CMAKE_SOURCE_DIR}/rtclib/libteaspeak_rtc.so" AND NOT NO_RELEASE_RTC)
message("Linking to release librtc file")
target_link_libraries(TeaSpeakServer
${CMAKE_SOURCE_DIR}/rtclib/libteaspeak_rtc.so
)
elseif(EXISTS "${CMAKE_SOURCE_DIR}/rtclib/libteaspeak_rtc_development.so")
message("Linkding against debug libteaspeak_rtc_development.so")
target_link_libraries(TeaSpeakServer
${CMAKE_SOURCE_DIR}/rtclib/libteaspeak_rtc_development.so
)
else()
message(FATAL_ERROR "Missing librtc library file")
endif()
include_directories(${LIBRARY_PATH}/boringssl/include/)
# include_directories(${LIBRARY_PATH}/boringssl/include/)
target_link_libraries(TeaSpeakServer
openssl::ssl::shared
openssl::crypto::shared
@@ -306,4 +306,24 @@ if (NOT DISABLE_JEMALLOC)
jemalloc
)
add_definitions(-DHAVE_JEMALLOC)
endif ()
endif ()
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::core::static
tomcrypt::static
tommath::static
)
target_include_directories(Snapshots-Permissions-Test PUBLIC ${CMAKE_SOURCE_DIR}/server/src/)
-1
View File
@@ -1 +0,0 @@
../repro/env/geoloc/
-1
View File
@@ -1 +0,0 @@
../../music/bin/providers/
-1
View File
@@ -1 +0,0 @@
../repro/env/resources/
+2 -5
View File
@@ -1,10 +1,7 @@
#include <fstream>
#include <query/Command.h>
#include <cstring>
#include <utility>
#include <functional> /* required from permission manager */
#include "log/LogUtils.h"
#include "Definitions.h"
#include "PermissionManager.h"
@@ -135,7 +132,7 @@ int main(int argc, char** argv) {
group.target = line == "2" ? TARGET_QUERY : TARGET_SERVER;
read_line(file, line);
auto data = "perms " + line;
ts::Command group_parms = ts::Command::parse(pipes::buffer_view(data.data(), data.length()));
ts::Command group_parms = ts::Command::parse(data);
map<permission::PermissionType, permission::update::UpdatePermission> grantMapping;
for (int index = 0; index < group_parms.bulkCount(); index++) {
@@ -184,7 +181,7 @@ int main(int argc, char** argv) {
group.target = TARGET_CHANNEL;
read_line(file, line);
auto data = "perms " + line;
ts::Command group_parms = ts::Command::parse(pipes::buffer_view(data.data(), data.length()));
ts::Command group_parms = ts::Command::parse(data);
map<permission::PermissionType, permission::update::UpdatePermission> grantMapping;
for (int index = 0; index < group_parms.bulkCount(); index++) {
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+7 -1
View File
@@ -1,3 +1,9 @@
Locking Order:
Channel Tree:
1. Server Channel Tree
2. Client Channel Tree
CommandResult handleCommandClientUpdate(Command&);
CommandResult handleCommandClientEdit(Command&);
CommandResult handleCommandClientEdit(Command&, const std::shared_ptr<ConnectedClient>& /* target */);
@@ -55,4 +61,4 @@ Move client acts like access server channel tree: write lock channel_tree_lock =
TODO: Some kind of perm channel lock
TODO: Fix handleCommandChannelEdit
Test: Channel hide & show with clients! Multible clients as well!
Test: Channel hide & show with clients! Multiple clients as well!
+205 -24
View File
@@ -1,4 +1,6 @@
#include <client/linux/handler/exception_handler.h>
//include <iterator> /* Required for breakpad */
//#include <client/linux/handler/exception_handler.h>
#include <iostream>
#include <misc/strobf.h>
#include <CXXTerminal/QuickTerminal.h>
@@ -10,11 +12,13 @@
#include "src/VirtualServer.h"
#include "src/InstanceHandler.h"
#include "src/server/QueryServer.h"
#include "src/server/file/FileServer.h"
#include "src/terminal/CommandHandler.h"
#include "src/client/InternalClient.h"
#include "src/SignalHandler.h"
#include "src/build.h"
#include <dlfcn.h>
#include <sys/time.h>
#include <sys/resource.h>
using namespace std;
using namespace std::chrono;
@@ -44,11 +48,12 @@ extern void testTomMath();
#define DB_NAME "TeaData.sqlite"
#endif
#include <regex>
#include <codecvt>
#include "src/client/music/internal_provider/channel_replay/ChannelProvider.h"
#include <codecvt>
#include <src/rtc/lib.h>
#include <src/terminal/PipedTerminal.h>
class CLIParser{
class CLIParser {
public:
CLIParser (int &argc, char **argv){
for (int i = 1; i < argc; i++)
@@ -114,6 +119,82 @@ int main(int argc, char** argv) {
(void*) malloc_conf;
#endif
#if 0
{
//ts::property::list<ts::property::InstanceProperties>()
std::cout << "| Name | Type | Flags | Default Value | Description | \n";
std::cout << "|:-- | -- | -- | -- |:-- | \n";
for(const auto& property : ts::property::list<ts::property::VirtualServerProperties>()) {
std::cout << "| `" << property->name << "` | ";
switch(property->type_value) {
case ts::property::TYPE_STRING:
std::cout << "String | ";
break;
case ts::property::TYPE_BOOL:
std::cout << "Boolean | ";
break;
case ts::property::TYPE_SIGNED_NUMBER:
std::cout << "Signed number | ";
break;
case ts::property::TYPE_UNSIGNED_NUMBER:
std::cout << "Unsigned number | ";
break;
case ts::property::TYPE_FLOAT:
std::cout << "Float | ";
break;
default:
std::cout << "Unknown | ";
break;
}
std::string flags{};
if(property->flags & ts::property::FLAG_INTERNAL) { flags += ", internal"; }
if(property->flags & ts::property::FLAG_GLOBAL) { flags += ", global"; }
if(property->flags & ts::property::FLAG_SNAPSHOT) { flags += ", snapshot"; }
if(property->flags & ts::property::FLAG_SAVE) { flags += ", saved"; }
if(property->flags & ts::property::FLAG_SAVE_MUSIC) { flags += ", saved (music)"; }
if(property->flags & ts::property::FLAG_NEW) { flags += ", new"; }
if(property->flags & ts::property::FLAG_SERVER_VARIABLE) { flags += ", server variable"; }
if(property->flags & ts::property::FLAG_SERVER_VIEW) { flags += ", server view variable"; }
if(property->flags & ts::property::FLAG_CLIENT_VARIABLE) { flags += ", client variable"; }
if(property->flags & ts::property::FLAG_CLIENT_VIEW) { flags += ", client view variable"; }
if(property->flags & ts::property::FLAG_CLIENT_INFO) { flags += ", client info variable"; }
if(property->flags & ts::property::FLAG_CHANNEL_VARIABLE) { flags += ", channel variable"; }
if(property->flags & ts::property::FLAG_CHANNEL_VIEW) { flags += ", channel view variable"; }
// FLAG_GROUP_VIEW = FLAG_CHANNEL_VIEW << 1UL,
if(property->flags & ts::property::FLAG_INSTANCE_VARIABLE) { flags += ", instance variable"; }
if(property->flags & ts::property::FLAG_PLAYLIST_VARIABLE) { flags += ", playlist variable"; }
if(property->flags & ts::property::FLAG_USER_EDITABLE) { flags += ", editable"; }
if(!flags.empty()) {
std::cout << flags.substr(2);
}
std::cout << "| ";
if(property->default_value.empty()) {
std::cout << "empty";
} else {
std::cout << "`" << property->default_value << "` ";
}
std::cout << "| ";
std::cout << "No description ";
std::cout << "| ";
std::cout << " \n";
}
return 0;
}
#endif
CLIParser arguments(argc, argv);
SSL_load_error_strings();
OpenSSL_add_ssl_algorithms();
@@ -127,9 +208,8 @@ int main(int argc, char** argv) {
if(!arguments.cmdOptionExists("--no-terminal")) {
terminal::install();
if(!terminal::active()){ cerr << "could not setup terminal!" << endl; return -1; }
if(!terminal::active()) { cerr << "could not setup terminal!" << endl; return -1; }
}
assert(ts::property::impl::validateUnique());
if(arguments.cmdOptionExists("--help") || arguments.cmdOptionExists("-h")) {
#define HELP_FMT " {} {} | {}"
@@ -218,6 +298,21 @@ int main(int argc, char** argv) {
}
}
/*
{
auto a = malloc(10); // 0xa04010
auto b = malloc(10); // 0xa04030
auto c = malloc(10); // 0xa04050
free(a);
free(b); // To bypass "double free or corruption (fasttop)" check
free(a); // Double Free !!
auto d = malloc(10); // 0xa04010
auto e = malloc(10); // 0xa04030
auto f = malloc(10); // 0xa04010 - Same as 'd' !
}
*/
/*
std::string error;
if(!interaction::waitForAttach(error)){
cerr << "Rsult: " << error << endl;
@@ -232,14 +327,16 @@ int main(int argc, char** argv) {
if(true) return 0;
*/
//debugMessage(LOG_GENERAL, "Sizeof ViewEntry {} Sizeof LinkedTreeEntry {} Sizeof shared_ptr<ViewEntry> {} Sizeof ClientChannelView {}", sizeof(ts::ViewEntry), sizeof(ts::TreeView::LinkedTreeEntry), sizeof(shared_ptr<ts::ViewEntry>), sizeof(ts::ClientChannelView));
{
//http://git.mcgalaxy.de/WolverinDEV/tomcrypt/blob/develop/src/misc/crypt/crypt_inits.c#L40-86
std::string descriptors = "LTGE";
bool crypt_init = false;
for(const auto& c : descriptors)
if((crypt_init |= crypt_mp_init(&c)))
for(const auto& c : descriptors) {
if((crypt_init |= crypt_mp_init(&c))) {
break;
}
}
if(!crypt_init) {
logCritical(LOG_GENERAL, "Could not initialise libtomcrypt mp descriptors!");
return 1;
@@ -265,7 +362,7 @@ int main(int argc, char** argv) {
if(!cfgErrors.empty()){
logErrorFmt(true, LOG_GENERAL, "Could not load configuration. Errors: (" + to_string(cfgErrors.size()) + ")");
for(const auto& entry : cfgErrors)
logError(true, LOG_GENERAL, " - {}", entry);
logErrorFmt(true, LOG_GENERAL, " - {}", entry);
logErrorFmt(true, LOG_GENERAL, "Stopping server...");
goto stopApp;
}
@@ -289,6 +386,18 @@ int main(int argc, char** argv) {
};
logger::updateLogLevels();
logMessage(LOG_GENERAL, "Starting TeaSpeak-Server v{}", build::version()->string(true));
{
debugMessage(LOG_GENERAL, "Initializing RTP library version {}", ts::rtc::version());
std::string error;
if(!ts::rtc::initialize(error)) {
logCritical(LOG_GENERAL, "Failed to initialize RTC library: {}", error);
return EXIT_FAILURE;
}
}
if(ts::config::license_original && ts::config::license_original->data.type != license::LicenseType::DEMO){
logMessageFmt(true, LOG_GENERAL, strobf("[]---------------------------------------------------------[]").string());
logMessageFmt(true, LOG_GENERAL, strobf(" §aThank you for buying the TeaSpeak-§lPremium-§aSoftware! ").string());
@@ -309,7 +418,32 @@ int main(int argc, char** argv) {
logMessageFmt(true, LOG_GENERAL, strobf("[]---------------------------------------------------------[]").string());
}
logMessage(LOG_GENERAL, "Starting TeaSpeak-Server v{}", build::version()->string(true));
{
rlimit rlimit{0, 0};
//forum.teaspeak.de/index.php?threads/2570/
constexpr auto seek_help_message = "For more help visit the forum and read this thread (https://forum.teaspeak.de/index.php?threads/2570/).";
if(getrlimit(RLIMIT_NOFILE, &rlimit) != 0) {
//prlimit -n4096 -p pid_of_process
logWarningFmt(true, LOG_INSTANCE, "Failed to get open file rlimit ({}). Please ensure its over 16384.", strerror(errno));
logWarningFmt(true, LOG_INSTANCE, seek_help_message);
} else {
const auto original = rlimit.rlim_cur;
rlimit.rlim_cur = std::max(rlimit.rlim_cur, std::min(rlimit.rlim_max, (rlim_t) 16384));
if(original != rlimit.rlim_cur) {
if(setrlimit(RLIMIT_NOFILE, &rlimit) != 0) {
logErrorFmt(true, LOG_INSTANCE, "Failed to set open file rlimit to {} ({}). Please ensure its over 16384.", rlimit.rlim_cur, strerror(errno));
logWarningFmt(true, LOG_INSTANCE, seek_help_message);
goto rlimit_updates;
}
}
if(rlimit.rlim_cur < 16384) {
logWarningFmt(true, LOG_INSTANCE, "Open file rlimit is bellow 16384 ({}). Please increase the system file descriptor limits.", rlimit.rlim_cur);
logWarningFmt(true, LOG_INSTANCE, seek_help_message);
}
}
rlimit_updates:;
}
logMessage(LOG_GENERAL, "Starting music providers");
if(terminal::instance()) terminal::instance()->setPrompt("§aStarting server. §7[§aloading music§7]");
@@ -352,7 +486,7 @@ int main(int argc, char** argv) {
logCriticalFmt(true, LOG_GENERAL, "Could not initialize SQL!");
if(errorMessage.find("database is locked") != string::npos) {
logCriticalFmt(true, LOG_GENERAL, "----------------------------[ ATTENTION ]----------------------------");
logCriticalFmt(true, LOG_GENERAL, "{:^69}", "You're database is already in use!");
logCriticalFmt(true, LOG_GENERAL, "{:^69}", "Your database is already in use!");
logCriticalFmt(true, LOG_GENERAL, "{:^69}", "Stop the other instance first!");
logCriticalFmt(true, LOG_GENERAL, "----------------------------[ ATTENTION ]----------------------------");
} else {
@@ -371,22 +505,18 @@ int main(int argc, char** argv) {
auto password = arguments.cmdOptionExists("-q") ? arguments.get_option("-q") : arguments.get_option("--set_query_password");
if(!password.empty()) {
logMessageFmt(true, LOG_GENERAL, "Updating server admin query password to \"{}\"", password);
auto accounts = serverInstance->getQueryServer()->find_query_accounts_by_unique_id(serverInstance->getInitialServerAdmin()->getUid());
bool found = false;
for(const auto& account : accounts) {
if(account->bound_server != 0) continue;
auto account = serverInstance->getQueryServer()->find_query_account_by_name("serveradmin");
if(!account) {
logErrorFmt(true, LOG_GENERAL, "Failed to update server admin query password! Login does not exists!");
} else {
if(!serverInstance->getQueryServer()->change_query_password(account, password)) {
logErrorFmt(true, LOG_GENERAL, "Failed to update server admin query password! (Internal error)");
}
found = true;
break;
}
if(!found) {
logErrorFmt(true, LOG_GENERAL, "Failed to update server admin query password! Login does not exists!");
}
}
}
terminal::initialize_pipe(arguments.get_option("--pipe-path"));
if(terminal::instance()) terminal::instance()->setPrompt("§7> §f");
while(mainThreadActive) {
usleep(5 * 1000);
@@ -394,11 +524,23 @@ int main(int argc, char** argv) {
if(terminal::instance()) {
if(terminal::instance()->linesAvailable() > 0){
while(!(line = terminal::instance()->readLine("§7> §f")).empty())
threads::Thread(THREAD_DETACHED, [line](){ terminal::chandler::handleCommand(line); });
threads::Thread(THREAD_DETACHED, [line]{
terminal::chandler::CommandHandle handle{};
handle.command = line;
if(!terminal::chandler::handleCommand(handle)) {
for(const auto& response : handle.response)
logErrorFmt(true, LOG_GENERAL, "{}", response);
} else {
for(const auto& response : handle.response)
logMessageFmt(true, LOG_GENERAL, "{}", response);
}
});
}
}
}
terminal::finalize_pipe();
stopApp:
logMessageFmt(true, LOG_GENERAL, "Stopping application");
@@ -420,4 +562,43 @@ int main(int argc, char** argv) {
terminal::uninstall();
mainThreadDone = true;
return 0;
}
/* Fix for Virtuzzo 7 where sometimes the pthread create fails! */
typedef int (*pthread_create_t)(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
pthread_create_t original_pthread_create{nullptr};
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg) {
if(!original_pthread_create) {
original_pthread_create = (pthread_create_t) dlsym(RTLD_NEXT, "pthread_create");
if(!original_pthread_create) {
std::cerr << "[CRITICAL] Missing original pthread_create function. Aborting execution!" << std::endl;
std::abort();
}
}
int result, attempt{0}, sleep{5};
while((result = original_pthread_create(thread, attr, start_routine, arg)) != 0 && errno == EAGAIN) {
if(attempt > 55) {
std::cerr << "[CRITICAL] pthread_create(...) cause EAGAIN for the last 50 attempts (~4.7seconds)! Aborting application execution!" << std::endl;
std::abort();
} else if(attempt > 5) {
/* let some other threads do work */
pthread_yield();
} else if(attempt == 0) {
std::string message{"[CRITICAL] Failed to spawn thread (Resource temporarily unavailable). Trying to recover."};
std::cerr << message << std::endl;
}
//std::string message{"[CRITICAL] pthread_create(...) cause EAGAIN! Trying again in " + std::to_string(sleep) + "usec (Attempt: " + std::to_string(attempt) + ")"};
//std::cerr << message << std::endl;
usleep(sleep);
attempt++;
sleep = (int) (sleep * 1.25);
}
if(attempt > 0) {
std::string message{"[CRITICAL] Successfully recovered from pthread_create() EAGAIN error. Took " + std::to_string(attempt) + " attempts."};
std::cerr << message << std::endl;
}
return result;
}
+41 -1
View File
@@ -6,6 +6,40 @@ if [[ -z "${BUILD_PATH}" ]]; then
exit 1
fi
rm buildVersion.txt
cp env/buildVersion.txt .
rm -r env
mkdir env
cd env
[[ $? -ne 0 ]] && {
echo "Failed to create the env"
exit 1
}
cp -rf ../../../git-teaspeak/default_files/{certs,commanddocs,geoloc,resources,*.sh} .
[[ $? -ne 0 ]] && {
echo "Failed to copy env"
exit 1
}
cp -rf ../../../music/bin/providers .
[[ $? -ne 0 ]] && {
echo "Failed to copy providers"
exit 1
}
#
cp ../../environment/TeaSpeakServer .
[[ $? -ne 0 ]] && {
echo "Failed to copy server"
exit 1
}
cd ..
mv buildVersion.txt env/buildVersion.txt
[[ $? -ne 0 ]] && {
echo "Failed to move the build version back"
exit 1
}
./generate_version.sh "${BUILD_PATH}" || {
echo "Failed to generate version! ($?)"
exit 1
@@ -16,6 +50,12 @@ fi
exit 1
}
./make_symbol.sh || {
echo "Failed to generate debug symbols"
exit 1
}
./package_server.sh "${BUILD_PATH}" || {
echo "Failed to package server! ($?)"
exit 1
@@ -24,4 +64,4 @@ fi
./deploy_build.sh "${BUILD_PATH}" || {
echo "Failed to deploy package! ($?)"
exit 1
}
}
-1
View File
@@ -1 +0,0 @@
../../environment/TeaSpeakServer
-1
View File
@@ -1 +0,0 @@
../../../git-teaspeak/default_files/certs/
-1
View File
@@ -1 +0,0 @@
../../../git-teaspeak/default_files/commanddocs/
-1
View File
@@ -1 +0,0 @@
../../../git-teaspeak/default_files/geoloc/
-1
View File
@@ -1 +0,0 @@
../../../git-teaspeak/default_files/install_music.sh
-1
View File
@@ -1 +0,0 @@
../../../music/bin/providers/
-1
View File
@@ -1 +0,0 @@
../../../git-teaspeak/default_files/resources/
-1
View File
@@ -1 +0,0 @@
../../../git-teaspeak/default_files/tealoop.sh
-1
View File
@@ -1 +0,0 @@
../../../git-teaspeak/default_files/teastart.sh
-1
View File
@@ -1 +0,0 @@
../../../git-teaspeak/default_files/teastart_autorestart.sh
-1
View File
@@ -1 +0,0 @@
../../../git-teaspeak/default_files/teastart_minimal.sh
+7 -43
View File
@@ -3,15 +3,10 @@
#Required libraries:
# "libssl.so"
# "libcrypto.so"
# "libDataPipes.so"
# "libjemalloc.so.2"
# "libsqlite3.so.0"
# "libTeaMusic.so"
# "libnice.so.10"
# "libpcre.so.3" (only for web)
# "libgobject-2.0.so.0" (only for web)
# "libglib-2.0.so.0" (only for web)
# "libffi.so.7"
# "libteaspeak_rtc.so"
[[ -z "${build_os_type}" ]] && { echo "missing build os type"; exit 1; }
[[ -z "${build_os_arch}" ]] && { echo "missing build os arch"; exit 1; }
@@ -42,17 +37,12 @@ cd libs || { echo "failed to enter lib directory"; exit 1; }
library_base=$(realpath ../../../../../libraries)
# Setting up ssl
library_path=$(realpath "${library_base}/boringssl/${build_path}/ssl/libssl.so")
cp "$library_path" . || { echo "failed to copy libssl.so"; exit 1; }
query_system_link "libssl.so.1.1"
cp "${library_path}" . || { echo "failed to copy libssl.so.1.1"; exit 1; }
# Setting up crypto
library_path=$(realpath "${library_base}/boringssl/${build_path}/crypto/libcrypto.so")
cp "$library_path" . || { echo "failed to copy libcrypto.so"; exit 1; }
# Setting up DataPipes
library_path=$(realpath "${library_base}/DataPipes/${build_path}/lib/libDataPipes-RTC.so")
cp "$library_path" . || { echo "failed to copy libDataPipes-RTC.so"; exit 1; }
_dp_path="$library_path"
query_system_link "libcrypto.so.1.1"
cp "${library_path}" . || { echo "failed to copy libcrypto.so.1.1"; exit 1; }
# Setting up Sqlite3
query_system_link "libsqlite3.so.0"
@@ -66,35 +56,9 @@ cp "${library_path}" . || { echo "failed to copy libjemalloc.so.2"; exit 1; }
library_path=$(realpath "../../../../MusicBot/libs/libTeaMusic.so")
cp "$library_path" . || { echo "failed to copy libTeaMusic.so"; exit 1; }
if ldd "../../../environment/TeaSpeakServer" | grep -q "libnice.so.10"; then
echo "Adding web libraries"
# Setting up libnice
library_path=$(realpath "${library_base}/libnice/${build_os_type}_${build_os_arch}/lib/libnice.so.10")
cp "$library_path" libnice.so.10 || { echo "failed to copy libnice.so.10"; exit 1; }
glib_libs=$(realpath "${library_base}//glibc/${build_os_type}_${build_os_arch}/lib/"*"/")
cp "$glib_libs/libgobject-2.0.so.0" . || { echo "failed to copy libgobject-2.0.so.0"; exit 1; }
cp "$glib_libs/libgmodule-2.0.so.0" . || { echo "failed to copy libgmodule-2.0.so.0"; exit 1; }
cp "$glib_libs/libglib-2.0.so.0" . || { echo "failed to copy libglib-2.0.so.0"; exit 1; }
cp "$glib_libs/libgio-2.0.so.0" . || { echo "failed to copy libgio-2.0.so.0"; exit 1; }
cp "$glib_libs/libffi.so.7" . || { echo "failed to copy libffi.so.7"; exit 1; }
# "libgobject-2.0.so.0" (only for web)
# "libglib-2.0.so.0" (only for web)
# Setting up libpcre
query_system_link "libpcre.so.3" "$_dp_path"
cp "${library_path}" . || { echo "failed to copy libpcre.so.3"; exit 1; }
fi
query_system_link "libteaspeak_rtc.so"
cp "${library_path}" . || { echo "failed to copy libteaspeak_rtc.so"; exit 1; }
# Doing some prostprocessing
chmod 755 *
for file in *.so*; do
echo "Editing rpath for $file"
strip -s "$file"
patchelf --set-rpath "./libs/:./" "$file"
done
echo "All libraries have been copied successfully"
+1
View File
@@ -38,4 +38,5 @@ function create_dump() {
create_dump "env" "TeaSpeakServer"
create_dump "env/providers" "000ProviderFFMpeg.so"
create_dump "env/providers" "001ProviderYT.so"
create_dump "env/libs/" "libteaspeak_rtc.so"
echo "Created dump symbols!"
+11 -6
View File
@@ -1,5 +1,6 @@
#!/usr/bin/env bash
# shellcheck disable=SC2207
BUILD_INFO=($(cat build_version.txt))
BUILD_FULL_NAME=${BUILD_INFO[0]}
BUILD_NAME=${BUILD_INFO[1]}
@@ -22,18 +23,22 @@ echo -e "# Version: ${BUILD_FULL_NAME}
{\"build_name\": \"${BUILD_FULL_NAME}\", \"build_version\": \"${BUILD_NAME}\", \"build_index\": ${BUILD_VERSION}}" > buildVersion.txt
#Create a copy and save unstripped
cp TeaSpeakServer TeaSpeakServerTmp
rm TeaSpeakServer
mv TeaSpeakServerTmp TeaSpeakServer
echo "Stripping symbols"
strip -s -p -v TeaSpeakServer || { echo "failed to strip symbols!"; exit 1; }
patchelf --set-rpath ./libs/ TeaSpeakServer || { echo "failed to set rpath!"; exit 1; }
cd libs/ || exit 1
for file in *.so*; do
echo "Editing rpath for $file"
strip --strip-all "$file"
patchelf --set-rpath "./libs/:./" "$file"
done
cd ..
tar --dereference -cvf - * | gzip -f -9 > "../${BUILD_FILENAME}"
[[ $? -ne 0 ]] && { echo "failed to package server"; exit 1; }
cd ..
rm -r finalenv
./make_symbol.sh
echo "Package created (${BUILD_FILENAME})"
+221 -105
View File
@@ -1,5 +1,6 @@
#include <utility>
#include <log/LogUtils.h>
#include "Configuration.h"
#include "build.h"
#include "../../license/shared/include/license/license.h"
@@ -41,19 +42,39 @@ uint16_t ts::config::binding::DefaultFilePort;
std::string config::server::DefaultServerVersion;
std::string config::server::DefaultServerPlatform;
LicenseType config::server::DefaultServerLicense;
bool config::server::enable_teamspeak_weblist;
bool config::server::strict_ut8_mode;
bool config::server::show_invisible_clients_as_online;
bool config::server::disable_ip_saving;
bool config::server::default_music_bot;
/*
* namespace limits {
extern size_t poke_message_length;
extern size_t talk_power_request_message_length;
}
*/
size_t config::server::limits::poke_message_length;
size_t config::server::limits::talk_power_request_message_length;
size_t config::server::limits::afk_message_length;
ssize_t config::server::max_virtual_server;
bool config::server::badges::allow_badges;
bool config::server::badges::allow_overwolf;
bool config::server::authentication::name;
bool config::server::clients::teamspeak;
std::string config::server::clients::extra_welcome_message_teamspeak;
std::string config::server::clients::teamspeak_not_allowed_message;
config::server::clients::WelcomeMessageType config::server::clients::extra_welcome_message_type_teamspeak;
bool config::server::clients::teaweb;
bool config::server::clients::teaspeak;
std::string config::server::clients::extra_welcome_message_teaweb;
std::string config::server::clients::teaweb_not_allowed_message;
config::server::clients::WelcomeMessageType config::server::clients::extra_welcome_message_type_teaweb;
std::string config::server::clients::extra_welcome_message_teaspeak;
config::server::clients::WelcomeMessageType config::server::clients::extra_welcome_message_type_teaspeak;
bool config::server::clients::ignore_max_clone_permissions;
uint16_t config::voice::default_voice_port;
size_t config::voice::DefaultPuzzlePrecomputeSize;
@@ -70,6 +91,7 @@ bool config::voice::allow_session_reinitialize;
std::string config::query::motd;
std::string config::query::newlineCharacter;
size_t config::query::max_line_buffer;
int config::query::sslMode;
std::string config::query::ssl::certFile;
std::string config::query::ssl::keyFile;
@@ -99,24 +121,23 @@ std::string config::messages::timeout::packet_resend_failed;
std::string config::messages::timeout::connection_reinitialized;
size_t config::threads::ticking;
size_t config::threads::voice::execute_limit;
size_t config::threads::voice::execute_per_server;
size_t config::threads::command_execute;
size_t config::threads::network_events;
size_t config::threads::voice::events_per_server;
size_t config::threads::voice::io_min;
size_t config::threads::voice::io_per_server;
size_t config::threads::voice::io_limit;
bool config::threads::voice::bind_io_thread_to_kernel_thread;
size_t config::threads::music::execute_limit;
size_t config::threads::music::execute_per_bot;
size_t config::threads::web::io_loops;
std::string config::messages::teamspeak_permission_editor;
bool config::web::activated;
std::deque<std::tuple<std::string, std::string, std::string>> config::web::ssl::certificates;
uint16_t config::web::webrtc_port_max;
uint16_t config::web::webrtc_port_min;
deque<string> config::web::ice_servers;
bool config::web::stun_enabled;
std::string config::web::stun_host;
uint16_t config::web::stun_port;
bool config::web::enable_upnp;
bool config::web::udp_enabled;
bool config::web::tcp_enabled;
size_t config::log::vs_size;
std::string config::log::path;
@@ -323,7 +344,7 @@ void read_bindings(YAML::Node& root, const std::deque<std::shared_ptr<EntryBindi
inline string apply_comments(stringstream &in, map<string, deque<string>>& comments);
std::deque<std::shared_ptr<EntryBinding>> create_local_bindings(int& version, std::string& license);
#define CURRENT_CONFIG_VERSION 15
#define CURRENT_CONFIG_VERSION 16
static std::string _config_path;
vector<string> config::parseConfig(const std::string& path) {
_config_path = path;
@@ -438,6 +459,13 @@ vector<string> config::parseConfig(const std::string& path) {
nodes_key = "2";
}
}
case 15: {
auto nodes_key = resolveNode(config, "web.webrtc.stun.ip").back();
if(nodes_key.IsDefined() && nodes_key.as<std::string>() == "127.0.0.1") {
nodes_key.reset();
resolveNode(config, "web.webrtc.stun.enabled").back() = "1";
}
}
default:
break;
}
@@ -468,7 +496,6 @@ vector<string> config::parseConfig(const std::string& path) {
goto license_parsing;
}
/*
if(!config::license->isValid()) {
if(config::license->data.type == license::LicenseType::INVALID) {
errors.emplace_back(strobf("Give license isn't valid!").string());
@@ -478,38 +505,18 @@ vector<string> config::parseConfig(const std::string& path) {
logErrorFmt(true, LOG_GENERAL, strobf("The given license isn't valid!").string());
logErrorFmt(true, LOG_GENERAL, strobf("Falling back to the default license.").string());
teaspeak_license = "none";
goto license_parsing;
}
*/
}
{
auto bindings = create_bindings();
read_bindings(config, bindings);
build_comments(comments, bindings);
for(const auto& entry : config::web::ice_servers) {
auto dp = entry.find(':');
if(dp == string::npos) {
errors.emplace_back("Invalid ice server entry! Missing port");
continue;
}
auto host = entry.substr(0, dp);
auto port = entry.substr(dp + 1);
if(port.find_last_not_of("0123456789") != string::npos) {
errors.push_back("Invalid ice server entry! Invalid port (" + port + ")");
continue;
}
try {
stoi(port);
} catch(std::exception& ex) {
errors.push_back("Invalid ice server entry! Invalid port (" + port + ")");
}
}
}
auto currentVersion = strobf("TeaSpeak ").string() + build::version()->string(true);
auto currentVersion = config::server::default_version();
if(currentVersion != config::server::DefaultServerVersion) {
auto ref = config::server::DefaultServerVersion;
try {
@@ -573,6 +580,14 @@ std::vector<std::string> config::reload() {
auto bindings = create_bindings();
read_bindings(config, bindings, FLAG_RELOADABLE);
const auto& logConfig = logger::currentConfig();
if(logConfig) {
logConfig->logfileLevel = (spdlog::level::level_enum) ts::config::log::logfileLevel;
logConfig->terminalLevel = (spdlog::level::level_enum) ts::config::log::terminalLevel;
logger::updateLogLevels();
}
} catch(const YAML::Exception& ex) {
errors.emplace_back("Could not read config: " + ex.msg + " @" + to_string(ex.mark.line) + ":" + to_string(ex.mark.column));
return errors;
@@ -1014,8 +1029,9 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
{
BIND_GROUP(log)
{
CREATE_BINDING("level", 0);
CREATE_BINDING("level", FLAG_RELOADABLE);
BIND_INTEGRAL(config::log::logfileLevel, spdlog::level::debug, spdlog::level::trace, spdlog::level::off);
ADD_NOTE_RELOADABLE();
ADD_DESCRIPTION("The log level within the log files");
ADD_DESCRIPTION("Available types:");
ADD_DESCRIPTION(" 0: Trace");
@@ -1027,8 +1043,9 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
ADD_DESCRIPTION(" 6: Off");
}
{
CREATE_BINDING("terminal_level", 0);
CREATE_BINDING("terminal_level", FLAG_RELOADABLE);
BIND_INTEGRAL(config::log::terminalLevel, spdlog::level::info, spdlog::level::trace, spdlog::level::off);
ADD_NOTE_RELOADABLE();
ADD_DESCRIPTION("The log level within the TeaSpeak server terminal");
ADD_DESCRIPTION("Available types:");
ADD_DESCRIPTION(" 0: Trace");
@@ -1106,18 +1123,24 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
{
BIND_GROUP(query);
{
CREATE_BINDING("nl_char", 0);
BIND_STRING(config::query::newlineCharacter, "\r\n");
CREATE_BINDING("nl_char", FLAG_RELOADABLE);
BIND_STRING(config::query::newlineCharacter, "\n");
ADD_DESCRIPTION("Change the query newline character");
ADD_NOTE("NOTE: TS3 - Compatible bots may require \"\n\r\"");
}
{
CREATE_BINDING("motd", 0);
BIND_STRING(config::query::motd, "TeaSpeak\r\nWelcome on the TeaSpeak ServerQuery interface.\r\n");
CREATE_BINDING("max_line_buffer", FLAG_RELOADABLE);
BIND_INTEGRAL(config::query::max_line_buffer, 1024 * 1024, 1024 * 8, 1024 * 1024 * 512);
ADD_DESCRIPTION("Max number of characters one query command could contain.");
}
{
CREATE_BINDING("motd", FLAG_RELOADABLE);
BIND_STRING(config::query::motd, "TeaSpeak\nWelcome on the TeaSpeak ServerQuery interface.\n");
ADD_DESCRIPTION("The query welcome message");
ADD_NOTE("If not like TeamSpeak then some applications may not recognize the Query");
ADD_NOTE("Default TeamSpeak 3 MOTD:");
ADD_NOTE("TS3\r\nWelcome to the TeamSpeak 3 ServerQuery interface, type \"help\" for a list of commands and \"help <command>\" for information on a specific command.\r\n");
ADD_NOTE("TS3\n\rWelcome to the TeamSpeak 3 ServerQuery interface, type \"help\" for a list of commands and \"help <command>\" for information on a specific command.\n\r");
ADD_NOTE("NOTE: Sometimes you have to append one \r\n more!");
}
{
@@ -1127,7 +1150,7 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
ADD_DESCRIPTION("Available modes:");
ADD_DESCRIPTION(" 0: Disabled");
ADD_DESCRIPTION(" 1: Enabled (Enforced encryption)");
ADD_DESCRIPTION(" 2: Hybrid (Prefer encryption but fallback when it isnt available)");
ADD_DESCRIPTION(" 2: Hybrid (Prefer encryption but fallback when it isn't available)");
}
{
BIND_GROUP(ssl);
@@ -1154,17 +1177,17 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
"The start point for the server creation still apply.");
}
{
CREATE_BINDING("notifymute", 0);
CREATE_BINDING("notifymute", FLAG_RELOADABLE);
BIND_BOOL(config::voice::notifyMuted, false);
ADD_DESCRIPTION("Enable/disable the mute notify");
}
{
CREATE_BINDING("suppress_myts_warnings", 0);
CREATE_BINDING("suppress_myts_warnings", FLAG_RELOADABLE);
BIND_BOOL(config::voice::suppress_myts_warnings, true);
ADD_DESCRIPTION("Suppress the MyTS integration warnings");
}
{
CREATE_BINDING("allow_session_reinitialize", 0);
CREATE_BINDING("allow_session_reinitialize", FLAG_RELOADABLE);
BIND_BOOL(config::voice::allow_session_reinitialize, true);
ADD_DESCRIPTION("Enable/disable fast session reinitialisation.");
ADD_SENSITIVE();
@@ -1198,19 +1221,19 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
}
}
{
CREATE_BINDING("connect_limit", 0);
CREATE_BINDING("connect_limit", FLAG_RELOADABLE);
BIND_INTEGRAL(config::voice::connectLimit, 10, 0, 1024);
ADD_DESCRIPTION("Maximum amount of join attempts per second.");
ADD_NOTE("A value of zero means unlimited");
}
{
CREATE_BINDING("client_connect_limit", 0);
CREATE_BINDING("client_connect_limit", FLAG_RELOADABLE);
BIND_INTEGRAL(config::voice::clientConnectLimit, 3, 0, 1024);
ADD_DESCRIPTION("Maximum amount of join attempts per second per ip.");
ADD_NOTE("A value of zero means unlimited");
}
{
CREATE_BINDING("protocol.experimental_31", 0);
CREATE_BINDING("protocol.experimental_31", FLAG_RELOADABLE);
BIND_BOOL(config::experimental_31, false);
ADD_DESCRIPTION("Enables the newer and safer protocol based on TeamSpeak's documented standard");
ADD_NOTE("An invalid protocol chain could lead clients to calculate a wrong shared secret result");
@@ -1255,24 +1278,21 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
BIND_BOOL(config::server::delete_old_bans, true);
ADD_DESCRIPTION("Enable/disable the deletion of old bans within the database");
}
#if 0
{
CREATE_BINDING("delete_missing_icon_permissions", 0);
BIND_BOOL(config::server::delete_missing_icon_permissions, true);
ADD_DESCRIPTION("Enable/disable the deletion of invalid icon id permissions");
}
#endif
{
CREATE_BINDING("allow_weblist", 0);
BIND_BOOL(config::server::enable_teamspeak_weblist, true);
ADD_DESCRIPTION("Enable/disable weblist reports globally! (Server setting wount be disabled, they will be just not send)");
}
{
CREATE_BINDING("strict_ut8_mode", 0);
CREATE_BINDING("strict_ut8_mode", FLAG_RELOADABLE);
BIND_BOOL(config::server::strict_ut8_mode, false);
ADD_DESCRIPTION("If enabled an error will be throws on invalid UTF-8 characters within the protocol (Query & Client).");
ADD_DESCRIPTION("Else the property pair will be dropped silently!");
}
{
CREATE_BINDING("show_invisible_clients", 0);
CREATE_BINDING("show_invisible_clients", FLAG_RELOADABLE);
BIND_BOOL(config::server::show_invisible_clients_as_online, true);
ADD_DESCRIPTION("Allow anybody to send text messages to clients which are in invisible channels");
}
@@ -1282,7 +1302,7 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
ADD_DESCRIPTION("Disable the saving of IP addresses within the database.");
}
{
CREATE_BINDING("default_music_bot", 0);
CREATE_BINDING("default_music_bot", FLAG_RELOADABLE);
BIND_BOOL(config::server::default_music_bot, true);
ADD_DESCRIPTION("Add by default a new music bot to each created virtual server.");
}
@@ -1292,6 +1312,27 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
ADD_DESCRIPTION("Set the limit for maximal virtual servers. -1 means unlimited.");
ADD_NOTE_RELOADABLE();
}
{
BIND_GROUP(limits);
{
CREATE_BINDING("poke_message_length", FLAG_RELOADABLE);
BIND_INTEGRAL(config::server::limits::poke_message_length, 1024, 1, 262144);
ADD_NOTE_RELOADABLE();
}
{
CREATE_BINDING("talk_power_request_message_length", FLAG_RELOADABLE);
BIND_INTEGRAL(config::server::limits::talk_power_request_message_length, 50, 1, 262144);
ADD_NOTE_RELOADABLE();
}
{
CREATE_BINDING("afk_message_length", FLAG_RELOADABLE);
BIND_INTEGRAL(config::server::limits::afk_message_length, 50, 1, 262144);
ADD_NOTE_RELOADABLE();
}
}
{
/*
BIND_GROUP(badges);
@@ -1317,13 +1358,67 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
}
}
{
using WelcomeMessageType = config::server::clients::WelcomeMessageType;
BIND_GROUP(clients);
/* TeamSpeak */
{
CREATE_BINDING("teamspeak", FLAG_RELOADABLE);
BIND_BOOL(config::server::clients::teamspeak, true);
ADD_DESCRIPTION("Allow/disallow the TeamSpeak 3 client to join the server.");
ADD_NOTE_RELOADABLE();
}
{
CREATE_BINDING("not_allowed_message", FLAG_RELOADABLE);
BIND_STRING(config::server::clients::teamspeak_not_allowed_message, "");
ADD_DESCRIPTION("Change the message, displayed when denying the access to the server");
ADD_NOTE_RELOADABLE();
}
{
CREATE_BINDING("teamspeak_message", 0); /* No reload flag else we could just manipulate the licensing thing */
BIND_STRING(config::server::clients::extra_welcome_message_teamspeak, "");
ADD_DESCRIPTION("Add an extra welcome message for TeamSpeak client users");
/* Don't make the reloadable else you could just disable the outdated client server join message */
//ADD_NOTE_RELOADABLE();
}
{
CREATE_BINDING("teamspeak_message_type", 0); /* No reload flag else we could just manipulate the licensing thing */
BIND_INTEGRAL(config::server::clients::extra_welcome_message_type_teamspeak, WelcomeMessageType::WELCOME_MESSAGE_TYPE_NONE, WelcomeMessageType::WELCOME_MESSAGE_TYPE_MIN, WelcomeMessageType::WELCOME_MESSAGE_TYPE_MAX);
ADD_DESCRIPTION("The welcome message type modes");
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_NONE) + " - None, do nothing");
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_CHAT) + " - Message, sends this message before the server welcome message");
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_POKE) + " - Message, pokes the client with the message when he enters the server");
/* Don't make the reloadable else you could just disable the outdated client server join message */
//ADD_NOTE_RELOADABLE();
}
/* TeaSpeak */
/*
{
CREATE_BINDING("teaspeak", FLAG_RELOADABLE);
BIND_BOOL(config::server::clients::teaspeak, true);
ADD_DESCRIPTION("Allow/disallow the TeaSpeak - Client to join the server.");
ADD_NOTE_RELOADABLE();
}
*/
{
CREATE_BINDING("teaspeak_message", FLAG_RELOADABLE);
BIND_STRING(config::server::clients::extra_welcome_message_teaspeak, "");
ADD_DESCRIPTION("Add an extra welcome message for the TeaSpeak - Client users");
ADD_NOTE_RELOADABLE();
}
{
CREATE_BINDING("teaspeak_message_type", FLAG_RELOADABLE);
BIND_INTEGRAL(config::server::clients::extra_welcome_message_type_teaspeak, WelcomeMessageType::WELCOME_MESSAGE_TYPE_NONE, WelcomeMessageType::WELCOME_MESSAGE_TYPE_MIN, WelcomeMessageType::WELCOME_MESSAGE_TYPE_MAX);
ADD_DESCRIPTION("The welcome message type modes");
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_NONE) + " - None, do nothing");
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_CHAT) + " - Message, sends this message before the server welcome message");
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_POKE) + " - Message, pokes the client with the message when he enters the server");
ADD_NOTE_RELOADABLE();
}
/* TeaWeb */
{
CREATE_BINDING("teaweb", FLAG_RELOADABLE);
BIND_BOOL(config::server::clients::teaweb, true);
@@ -1331,9 +1426,32 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
ADD_NOTE_RELOADABLE();
}
{
CREATE_BINDING("teaspeak", FLAG_RELOADABLE);
BIND_BOOL(config::server::clients::teaspeak, true);
ADD_DESCRIPTION("Allow/disallow the TeaSpeak - Client to join the server.");
CREATE_BINDING("not_allowed_message", FLAG_RELOADABLE);
BIND_STRING(config::server::clients::teaweb_not_allowed_message, "");
ADD_DESCRIPTION("Chaneg the message, displayed when denying the access to the server");
ADD_NOTE_RELOADABLE();
}
{
CREATE_BINDING("teaweb_message", FLAG_RELOADABLE);
BIND_STRING(config::server::clients::extra_welcome_message_teaweb, "");
ADD_DESCRIPTION("Add an extra welcome message for the TeaSpeak - Web client users");
ADD_NOTE_RELOADABLE();
}
{
CREATE_BINDING("teaweb_message_type", FLAG_RELOADABLE);
BIND_INTEGRAL(config::server::clients::extra_welcome_message_type_teaweb, WelcomeMessageType::WELCOME_MESSAGE_TYPE_NONE, WelcomeMessageType::WELCOME_MESSAGE_TYPE_MIN, WelcomeMessageType::WELCOME_MESSAGE_TYPE_MAX);
ADD_DESCRIPTION("The welcome message type modes");
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_NONE) + " - None, do nothing");
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_CHAT) + " - Message, sends this message before the server welcome message");
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_POKE) + " - Message, pokes the client with the message when he enters the server");
ADD_NOTE_RELOADABLE();
}
{
CREATE_BINDING("ignore_max_clone_permissions", FLAG_RELOADABLE);
BIND_BOOL(config::server::clients::ignore_max_clone_permissions, false);
ADD_DESCRIPTION("Allows you to disable the permission checks for i_client_max_clones_uid, i_client_max_clones_ip and i_client_max_clones_hwid");
ADD_SENSITIVE();
ADD_NOTE_RELOADABLE();
}
}
@@ -1346,12 +1464,14 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
ADD_DESCRIPTION("Disable/enable the possibility to connect via the TeaSpeak web client");
ADD_NOTE("If you've disabled this feature the TeaClient wound be able to join too.");
}
/* LibNice has been build without this
{
CREATE_BINDING("upnp", 0);
BIND_BOOL(config::web::enable_upnp, false);
ADD_DESCRIPTION("Disable/enable UPNP support");
ADD_SENSITIVE();
}
*/
{
BIND_GROUP(ssl)
{
@@ -1412,23 +1532,48 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
}
{
CREATE_BINDING("webrtc.port_min", 0);
CREATE_BINDING("webrtc.port_min", FLAG_RELOADABLE);
BIND_INTEGRAL(config::web::webrtc_port_min, 50000, 0, 65535);
ADD_DESCRIPTION("Define the port range within the web client and TeaClient operates in");
ADD_DESCRIPTION("A port of zero stands for no limit");
ADD_NOTE("These ports must opened to use the voice bridge (Protocol: UDP)");
}
{
CREATE_BINDING("webrtc.port_max", 0);
CREATE_BINDING("webrtc.port_max", FLAG_RELOADABLE);
BIND_INTEGRAL(config::web::webrtc_port_max, 56000, 0, 65535);
ADD_DESCRIPTION("Define the port range within the web client and TeaClient operates in");
ADD_DESCRIPTION("A port of zero stands for no limit");
ADD_NOTE("These ports must opened to use the voice bridge (Protocol: UDP)");
}
{
CREATE_BINDING("webrtc.ice", 0);
BIND_VECTOR(config::web::ice_servers, {"stun.l.google.com:19302"});
ADD_DESCRIPTION("A list of possible offered ice servers");
CREATE_BINDING("webrtc.stun.enabled", FLAG_RELOADABLE);
BIND_INTEGRAL(config::web::stun_enabled, true, false, true);
ADD_DESCRIPTION("Whatever to use a STUN server");
ADD_NOTE_RELOADABLE();
}
{
CREATE_BINDING("webrtc.stun.host", FLAG_RELOADABLE);
BIND_STRING(config::web::stun_host, "stun.l.google.com");
ADD_DESCRIPTION("The address of the stun server to use.");
ADD_NOTE_RELOADABLE();
}
{
CREATE_BINDING("webrtc.stun.port", FLAG_RELOADABLE);
BIND_INTEGRAL(config::web::stun_port, 19302, 1, 0xFFFF);
ADD_DESCRIPTION("Port of the stun server");
ADD_NOTE_RELOADABLE();
}
{
CREATE_BINDING("webrtc.udp", FLAG_RELOADABLE);
BIND_INTEGRAL(config::web::udp_enabled, true, false, true);
ADD_DESCRIPTION("Enable UDP for theweb client");
ADD_NOTE_RELOADABLE();
}
{
CREATE_BINDING("webrtc.tcp", FLAG_RELOADABLE);
BIND_INTEGRAL(config::web::tcp_enabled, true, false, true);
ADD_DESCRIPTION("Enable TCP for theweb client");
ADD_NOTE_RELOADABLE();
}
}
{
@@ -1677,53 +1822,24 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
}
}
{
CREATE_BINDING("web.io_loops", 0);
BIND_INTEGRAL(config::threads::web::io_loops, 2, 1, 128);
ADD_DESCRIPTION("Thread pool size for the ticking task of a VirtualServer");
CREATE_BINDING("command_execute", 0);
BIND_INTEGRAL(config::threads::command_execute, 4, 1, 128);
ADD_DESCRIPTION("Command executors");
ADD_SENSITIVE();
}
{
CREATE_BINDING("network_events", 0);
BIND_INTEGRAL(config::threads::network_events, 4, 1, 128);
ADD_DESCRIPTION("Network event loops");
ADD_SENSITIVE();
}
{
BIND_GROUP(voice)
{
CREATE_BINDING("events_per_server", 0);
BIND_INTEGRAL(config::threads::voice::events_per_server, 2, 1, 16);
ADD_DESCRIPTION("Kernel events per server");
ADD_SENSITIVE();
}
{
CREATE_BINDING("execute_per_server", 0);
BIND_INTEGRAL(config::threads::voice::execute_per_server, 2, 1, 128);
ADD_DESCRIPTION("Threads per server for command executing");
ADD_SENSITIVE();
}
{
CREATE_BINDING("execute_limit", 0);
BIND_INTEGRAL(config::threads::voice::execute_limit, 5, 1, 1024);
ADD_DESCRIPTION("Max number of threads for command handling threads within the instance");
ADD_SENSITIVE();
}
{
CREATE_BINDING("io_min", 0);
BIND_INTEGRAL(config::threads::voice::io_min, 2, 1, 1024);
ADD_DESCRIPTION("Minimum IO threads");
ADD_SENSITIVE();
}
{
CREATE_BINDING("io_per_server", 0);
BIND_INTEGRAL(config::threads::voice::io_per_server, 2, 1, 64);
ADD_DESCRIPTION("IO Thread increase per server");
ADD_SENSITIVE();
}
{
CREATE_BINDING("io_limit", 0);
BIND_INTEGRAL(config::threads::voice::io_limit, 5, 1, 1024);
ADD_DESCRIPTION("Max IO threads");
ADD_SENSITIVE();
}
{
CREATE_BINDING("bind_io_thread_to_kernel_thread", 0);
BIND_BOOL(config::threads::voice::bind_io_thread_to_kernel_thread, false);
ADD_DESCRIPTION("Bind each IO thread to one kernel thread to improve socket IO performance");
BIND_INTEGRAL(config::threads::voice::events_per_server, 4, 1, 128);
ADD_DESCRIPTION("Kernel events per server socket");
ADD_DESCRIPTION("This value is upper bound to threads.network_events and should not exceed it.");
ADD_SENSITIVE();
}
}
+44 -15
View File
@@ -7,8 +7,10 @@
#undef byte
#endif
#include <spdlog/common.h>
#include <misc/strobf.h>
#include "geo/GeoLocation.h"
#include "../../license/shared/include/license/license.h"
#include "build.h"
namespace YAML {
class Node;
@@ -71,11 +73,16 @@ namespace ts::config {
extern bool strict_ut8_mode;
extern bool enable_teamspeak_weblist;
extern bool show_invisible_clients_as_online;
extern bool disable_ip_saving;
extern bool default_music_bot;
namespace limits {
extern size_t poke_message_length;
extern size_t talk_power_request_message_length;
extern size_t afk_message_length;
}
namespace badges {
extern bool allow_overwolf;
extern bool allow_badges;
@@ -86,12 +93,36 @@ namespace ts::config {
}
namespace clients {
enum WelcomeMessageType {
WELCOME_MESSAGE_TYPE_MIN,
WELCOME_MESSAGE_TYPE_NONE = WELCOME_MESSAGE_TYPE_MIN,
WELCOME_MESSAGE_TYPE_CHAT,
WELCOME_MESSAGE_TYPE_POKE,
WELCOME_MESSAGE_TYPE_MAX
};
extern bool teamspeak;
extern bool teaspeak;
extern std::string teamspeak_not_allowed_message;
extern std::string extra_welcome_message_teamspeak;
extern WelcomeMessageType extra_welcome_message_type_teamspeak;
extern std::string extra_welcome_message_teaspeak;
extern WelcomeMessageType extra_welcome_message_type_teaspeak;
extern bool teaweb;
extern std::string teaweb_not_allowed_message;
extern std::string extra_welcome_message_teaweb;
extern WelcomeMessageType extra_welcome_message_type_teaweb;
extern bool ignore_max_clone_permissions;
}
extern ssize_t max_virtual_server;
__attribute__((always_inline)) inline std::string default_version() { return strobf("TeaSpeak ").string() + build::version()->string(true); }
__attribute__((always_inline)) inline bool check_server_version_with_license() {
return default_version() == DefaultServerVersion || (license->isPremium() && license->isValid());
}
}
namespace voice {
@@ -125,6 +156,7 @@ namespace ts::config {
namespace query {
extern std::string motd;
extern std::string newlineCharacter;
extern size_t max_line_buffer;
extern int sslMode;
namespace ssl {
@@ -185,33 +217,30 @@ namespace ts::config {
extern uint16_t webrtc_port_min;
extern uint16_t webrtc_port_max;
extern std::deque<std::string> ice_servers;
extern bool enable_upnp;
extern bool stun_enabled;
extern std::string stun_host;
extern uint16_t stun_port;
extern bool tcp_enabled;
extern bool udp_enabled;
}
namespace threads {
extern size_t ticking;
extern size_t command_execute;
extern size_t network_events;
namespace voice {
extern size_t execute_per_server;
extern size_t execute_limit;
extern size_t events_per_server;
extern size_t io_min;
extern size_t io_per_server;
extern size_t io_limit;
extern bool bind_io_thread_to_kernel_thread;
}
namespace music {
extern size_t execute_per_bot;
extern size_t execute_limit;
}
namespace web {
extern size_t io_loops;
}
}
namespace log {
+46 -265
View File
@@ -3,8 +3,9 @@
//
#include <misc/memtracker.h>
#include <utility>
#include "ConnectionStatistics.h"
#include "VirtualServer.h"
using namespace std;
using namespace std::chrono;
@@ -13,321 +14,101 @@ using namespace ts::server;
using namespace ts::stats;
using namespace ts::protocol;
ConnectionStatistics::ConnectionStatistics(const shared_ptr<ConnectionStatistics>& handle, bool properties) : handle(handle) {
ConnectionStatistics::ConnectionStatistics(shared_ptr<ConnectionStatistics> handle) : handle(std::move(handle)) {
memtrack::allocated<ConnectionStatistics>(this);
if(properties) {
this->properties = make_shared<Properties>(); //TODO load etc?
this->properties->register_property_type<property::ConnectionProperties>();
}
/*
this->properties->registerProperty("connection_packets_sent_speech", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_bytes_sent_speech", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_packets_received_speech", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_bytes_received_speech", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_packets_sent_keepalive", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_bytes_sent_keepalive", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_packets_received_keepalive", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_bytes_received_keepalive", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_packets_sent_control", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_bytes_sent_control", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_packets_received_control", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_bytes_received_control", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_packets_sent_total", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_bytes_sent_total", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_packets_received_total", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_bytes_received_total", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_bandwidth_sent_last_second_total", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_bandwidth_sent_last_minute_total", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_bandwidth_received_last_second_total", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_bandwidth_received_last_minute_total", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_filetransfer_bandwidth_sent", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_filetransfer_bandwidth_received", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_filetransfer_bytes_sent_total", 0, PROP_STATISTIC);
this->properties->registerProperty("connection_filetransfer_bytes_received_total", 0, PROP_STATISTIC);
*/
}
ConnectionStatistics::~ConnectionStatistics() {
memtrack::freed<ConnectionStatistics>(this);
{
lock_guard lock(this->history_lock_incoming);
for(auto entry : this->history_incoming)
if(entry->use_count.fetch_sub(1) == 1)
delete entry;
for(auto entry : this->history_file_incoming)
if(entry->use_count.fetch_sub(1) == 1)
delete entry;
this->history_incoming.clear();
this->history_file_incoming.clear();
}
{
lock_guard lock(this->history_lock_outgoing);
for(auto entry : this->history_outgoing)
if(entry->use_count.fetch_sub(1) == 1)
delete entry;
for(auto entry : this->history_file_outgoing)
if(entry->use_count.fetch_sub(1) == 1)
delete entry;
this->history_outgoing.clear();
this->history_file_outgoing.clear();
}
}
std::shared_ptr<Properties> ConnectionStatistics::statistics() {
return this->properties;
}
void ConnectionStatistics::logIncomingPacket(const category::value &category, size_t size) {
auto info_entry = new StatisticEntry{};
info_entry->timestamp = system_clock::now();
info_entry->size = uint16_t(size);
assert(category >= 0 && category <= 2);
this->statistics_second_current.connection_bytes_received[category] += size;
this->statistics_second_current.connection_packets_received[category] += 1;
this->_log_incoming_packet(info_entry, category);
}
void ConnectionStatistics::_log_incoming_packet(ts::stats::StatisticEntry *info_entry, int8_t index) {
if(index >= 0 && index <= 3) {
this->connection_packets_received[index] ++;
this->connection_bytes_received[index] += info_entry->size;
}
this->connection_packets_received[0] ++;
this->connection_bytes_received[0] += info_entry->size;
if(this->_measure_bandwidths) {
auto lock_count = info_entry->use_count++;
assert(lock_count >= 0);
(void) lock_count;
lock_guard lock(this->history_lock_incoming);
this->history_incoming.push_back(info_entry);
}
if(this->handle)
this->handle->_log_incoming_packet(info_entry, index);
this->handle->logIncomingPacket(category, size);
}
void ConnectionStatistics::logOutgoingPacket(const category::value &category, size_t size) {
auto info_entry = new StatisticEntry{};
info_entry->timestamp = system_clock::now();
info_entry->size = uint16_t(size);
assert(category >= 0 && category <= 2);
this->statistics_second_current.connection_bytes_sent[category] += size;
this->statistics_second_current.connection_packets_sent[category] += 1;
this->_log_outgoing_packet(info_entry, category);
}
void ConnectionStatistics::_log_outgoing_packet(ts::stats::StatisticEntry *info_entry, int8_t index) {
if(index >= 0 && index <= 3) {
this->connection_packets_sent[index] ++;
this->connection_bytes_sent[index] += info_entry->size;
if(this->handle) {
this->handle->logOutgoingPacket(category, size);
}
this->connection_packets_sent[0] ++;
this->connection_bytes_sent[0] += info_entry->size;
if(this->_measure_bandwidths) {
auto lock_count = info_entry->use_count++;
assert(lock_count >= 0);
(void) lock_count;
lock_guard lock(this->history_lock_outgoing);
this->history_outgoing.push_back(info_entry);
}
if(this->handle)
this->handle->_log_outgoing_packet(info_entry, index);
}
/* file transfer */
void ConnectionStatistics::logFileTransferIn(uint64_t bytes) {
auto info_entry = new StatisticEntry{};
info_entry->timestamp = system_clock::now();
info_entry->size = bytes;
this->_log_incoming_file_packet(info_entry);
}
void ConnectionStatistics::_log_incoming_file_packet(ts::stats::StatisticEntry *info_entry) {
this->file_bytes_received += info_entry->size;
if(this->_measure_bandwidths) {
auto lock_count = info_entry->use_count++;
assert(lock_count >= 0);
(void) lock_count;
lock_guard lock(this->history_lock_incoming);
this->history_file_incoming.push_back(info_entry);
}
void ConnectionStatistics::logFileTransferIn(uint32_t bytes) {
this->statistics_second_current.file_bytes_received += bytes;
this->file_bytes_received += bytes;
if(this->handle)
this->handle->_log_incoming_file_packet(info_entry);
this->handle->logFileTransferIn(bytes);
}
void ConnectionStatistics::logFileTransferOut(uint64_t bytes) {
auto info_entry = new StatisticEntry{};
info_entry->timestamp = system_clock::now();
info_entry->size = bytes;
this->_log_outgoing_file_packet(info_entry);
}
void ConnectionStatistics::_log_outgoing_file_packet(ts::stats::StatisticEntry *info_entry) {
this->file_bytes_sent += info_entry->size;
if(this->_measure_bandwidths) {
auto lock_count = info_entry->use_count++;
assert(lock_count >= 0);
(void) lock_count;
lock_guard lock(this->history_lock_outgoing);
this->history_file_outgoing.push_back(info_entry);
}
void ConnectionStatistics::logFileTransferOut(uint32_t bytes) {
this->statistics_second_current.file_bytes_sent += bytes;
this->file_bytes_sent += bytes;
if(this->handle)
this->handle->_log_outgoing_file_packet(info_entry);
this->handle->logFileTransferOut(bytes);
}
void ConnectionStatistics::tick() {
StatisticEntry* entry;
{
auto timeout_min = system_clock::now() - minutes(1);
auto now = std::chrono::system_clock::now();
auto time_difference = this->last_second_tick.time_since_epoch().count() > 0 ? now - this->last_second_tick : std::chrono::seconds{1};
if(time_difference >= std::chrono::seconds{1}) {
BandwidthEntry<uint32_t> current{};
current.atomic_exchange(this->statistics_second_current);
lock_guard lock(this->history_lock_incoming);
auto period_ms = std::chrono::floor<std::chrono::milliseconds>(time_difference).count();
auto current_normalized = current.mul<long double>(1000.0 / period_ms);
while(!this->history_incoming.empty() && (entry = this->history_incoming[0])->timestamp < timeout_min) {
if(entry->use_count.fetch_sub(1) == 1)
delete entry;
this->statistics_second = this->statistics_second.mul<long double>(.2) + current_normalized.mul<long double>(.8);
this->total_statistics += current;
this->history_incoming.pop_front();
auto current_second = std::chrono::floor<std::chrono::seconds>(now.time_since_epoch()).count();
if(statistics_minute_offset == 0) {
statistics_minute_offset = current_second;
}
while(!this->history_file_incoming.empty() && (entry = this->history_file_incoming[0])->timestamp < timeout_min) {
if(entry->use_count.fetch_sub(1) == 1)
delete entry;
this->history_file_incoming.pop_front();
/* fill all "lost" with the current bandwidth as well */
while(statistics_minute_offset <= current_second) {
this->statistics_minute[statistics_minute_offset++ % this->statistics_minute.size()] = current_normalized;
}
}
{
auto timeout_min = system_clock::now() - minutes(1);
lock_guard lock(this->history_lock_outgoing);
while(!this->history_outgoing.empty() && (entry = this->history_outgoing[0])->timestamp < timeout_min) {
if(entry->use_count.fetch_sub(1) == 1)
delete entry;
this->history_outgoing.pop_front();
}
while(!this->history_file_outgoing.empty() && (entry = this->history_file_outgoing[0])->timestamp < timeout_min) {
if(entry->use_count.fetch_sub(1) == 1)
delete entry;
this->history_file_outgoing.pop_front();
}
}
if(this->properties) {
auto& _properties = *this->properties;
#define M(type, index) \
_properties[property::CONNECTION_BYTES_SENT_ ##type] = (uint64_t) this->connection_bytes_sent[index]; \
_properties[property::CONNECTION_PACKETS_SENT_ ##type] = (uint64_t) this->connection_packets_sent[index]; \
_properties[property::CONNECTION_BYTES_RECEIVED_ ##type] = (uint64_t) this->connection_bytes_received[index]; \
_properties[property::CONNECTION_PACKETS_RECEIVED_ ##type] = (uint64_t) this->connection_packets_received[index]; \
M(TOTAL, 0);
M(CONTROL, 1);
M(KEEPALIVE, 2);
M(SPEECH, 3);
_properties[property::CONNECTION_FILETRANSFER_BYTES_RECEIVED_TOTAL] = (uint64_t) this->file_bytes_received;
_properties[property::CONNECTION_FILETRANSFER_BYTES_SENT_TOTAL] = (uint64_t) this->file_bytes_sent;
_properties[property::CONNECTION_FILETRANSFER_BYTES_RECEIVED_TOTAL] = (uint64_t) this->file_bytes_received;
_properties[property::CONNECTION_FILETRANSFER_BYTES_SENT_TOTAL] = (uint64_t) this->file_bytes_sent;
this->last_second_tick = now;
}
}
DataSummery ConnectionStatistics::dataReport() {
DataSummery report{};
auto minTimeout = system_clock::now() - seconds(1);
{
lock_guard lock(this->history_lock_incoming);
for(const auto& elm : this->history_incoming){
if(elm->timestamp >= minTimeout) {
report.recv_second += elm->size;
}
report.recv_minute += elm->size;
}
for(const auto& elm : this->history_file_incoming) {
report.file_recv += elm->size;
}
}
{
lock_guard lock(this->history_lock_outgoing);
for(const auto& elm : this->history_outgoing){
if(elm->timestamp >= minTimeout) {
report.send_second += elm->size;
}
report.send_minute += elm->size;
}
for(const auto& elm : this->history_file_outgoing) {
report.file_send += elm->size;
}
}
report.recv_minute /= 60;
report.send_minute /= 60;
return report;
BandwidthEntry<uint32_t> ConnectionStatistics::minute_stats() const {
BandwidthEntry<uint32_t> result{};
for(const auto& second : this->statistics_minute)
result += second;
return result.mul<uint32_t>(1. / (double) this->statistics_minute.size());
}
FullReport ConnectionStatistics::full_report() {
FullReport report{};
FileTransferStatistics ConnectionStatistics::file_stats() {
FileTransferStatistics result{};
for(size_t index = 0 ; index < 4; index++) {
report.connection_bytes_sent[index] = (uint64_t) this->connection_bytes_sent[index];
report.connection_packets_sent[index] = (uint64_t) this->connection_packets_sent[index];
report.connection_bytes_received[index] = (uint64_t) this->connection_bytes_received[index];
report.connection_packets_received[index] = (uint64_t) this->connection_packets_received[index];
}
result.bytes_received = this->file_bytes_received;
result.bytes_sent = this->file_bytes_sent;
report.file_bytes_sent = this->file_bytes_sent;
report.file_bytes_received = this->file_bytes_received;
return report;
return result;
}
std::pair<uint64_t, uint64_t> ConnectionStatistics::mark_file_bytes() {
std::pair<uint64_t, uint64_t> result;
{
lock_guard lock(this->history_lock_incoming);
if(this->mark_file_bytes_received < this->file_bytes_received)
result.second = this->file_bytes_received - this->mark_file_bytes_received;
this->mark_file_bytes_received = (uint64_t) this->file_bytes_received;
}
{
lock_guard lock(this->history_lock_outgoing);
if(this->mark_file_bytes_sent < this->file_bytes_sent)
result.first = this->file_bytes_sent - this->mark_file_bytes_sent;
this->mark_file_bytes_sent = (uint64_t) this->file_bytes_sent;
+102 -70
View File
@@ -11,41 +11,97 @@ namespace ts {
}
namespace stats {
struct StatisticEntry {
std::atomic<int8_t> use_count{0};
std::chrono::time_point<std::chrono::system_clock> timestamp;
uint16_t size = 0;
template <typename value_t>
struct BandwidthEntry {
std::array<value_t, 3> connection_packets_sent{};
std::array<value_t, 3> connection_bytes_sent{};
std::array<value_t, 3> connection_packets_received{};
std::array<value_t, 3> connection_bytes_received{};
value_t file_bytes_sent{0};
value_t file_bytes_received{0};
template <typename other_type>
inline BandwidthEntry& operator=(const BandwidthEntry<other_type>& other) {
for(size_t index{0}; index < this->connection_packets_sent.size(); index++)
this->connection_packets_sent[index] = other.connection_packets_sent[index];
for(size_t index{0}; index < this->connection_bytes_sent.size(); index++)
this->connection_bytes_sent[index] = other.connection_bytes_sent[index];
for(size_t index{0}; index < this->connection_packets_received.size(); index++)
this->connection_packets_received[index] = other.connection_packets_received[index];
for(size_t index{0}; index < this->connection_bytes_received.size(); index++)
this->connection_bytes_received[index] = other.connection_bytes_received[index];
this->file_bytes_sent = other.file_bytes_sent;
this->file_bytes_received = other.file_bytes_received;
return *this;
}
template <typename target_t>
inline BandwidthEntry<target_t> mul(double factor) const {
BandwidthEntry<target_t> result{};
result = *this;
for(auto& val : result.connection_packets_sent) val *= factor;
for(auto& val : result.connection_bytes_sent) val *= factor;
for(auto& val : result.connection_packets_received) val *= factor;
for(auto& val : result.connection_bytes_received) val *= factor;
result.file_bytes_sent *= factor;
result.file_bytes_received *= factor;
return result;
}
template <typename other_type>
inline BandwidthEntry& operator+=(const BandwidthEntry<other_type>& other) {
for(size_t index{0}; index < this->connection_packets_sent.size(); index++)
this->connection_packets_sent[index] += other.connection_packets_sent[index];
for(size_t index{0}; index < this->connection_bytes_sent.size(); index++)
this->connection_bytes_sent[index] += other.connection_bytes_sent[index];
for(size_t index{0}; index < this->connection_packets_received.size(); index++)
this->connection_packets_received[index] += other.connection_packets_received[index];
for(size_t index{0}; index < this->connection_bytes_received.size(); index++)
this->connection_bytes_received[index] += other.connection_bytes_received[index];
this->file_bytes_sent += other.file_bytes_sent;
this->file_bytes_received += other.file_bytes_received;
return *this;
}
template <typename other_type>
inline BandwidthEntry operator+(const BandwidthEntry<other_type>& other) {
return BandwidthEntry{*this} += other;
}
template <typename atomic_t>
inline void atomic_exchange(BandwidthEntry<std::atomic<atomic_t>>& source) {
for(size_t index{0}; index < this->connection_packets_sent.size(); index++)
this->connection_packets_sent[index] = source.connection_packets_sent[index].exchange(0);
for(size_t index{0}; index < this->connection_bytes_sent.size(); index++)
this->connection_bytes_sent[index] = source.connection_bytes_sent[index].exchange(0);
for(size_t index{0}; index < this->connection_packets_received.size(); index++)
this->connection_packets_received[index] = source.connection_packets_received[index].exchange(0);
for(size_t index{0}; index < this->connection_bytes_received.size(); index++)
this->connection_bytes_received[index] = source.connection_bytes_received[index].exchange(0);
this->file_bytes_sent = source.file_bytes_sent.exchange(0);
this->file_bytes_received = source.file_bytes_received.exchange(0);
}
};
struct DataSummery {
uint32_t send_minute;
uint32_t send_second;
uint32_t recv_minute;
uint32_t recv_second;
uint32_t file_recv;
uint32_t file_send;
};
struct FullReport {
uint64_t connection_packets_sent[4]{0, 0, 0, 0};
uint64_t connection_bytes_sent[4]{0, 0, 0, 0};
uint64_t connection_packets_received[4]{0, 0, 0, 0};
uint64_t connection_bytes_received[4]{0, 0, 0, 0};
uint64_t file_bytes_sent = 0;
uint64_t file_bytes_received = 0;
struct FileTransferStatistics {
uint64_t bytes_received{0};
uint64_t bytes_sent{0};
};
class ConnectionStatistics {
public:
struct category {
/* Only three categories. Map unknown to category 0 */
enum value {
COMMAND,
ACK,
KEEP_ALIVE,
VOICE,
UNKNOWN
UNKNOWN = COMMAND
};
constexpr static std::array<category::value, 16> lookup_table{
@@ -53,10 +109,10 @@ namespace ts {
VOICE, /* VoiceWhisper */
COMMAND, /* Command */
COMMAND, /* CommandLow */
ACK, /* Ping */
ACK, /* Pong */
ACK, /* Ack */
ACK, /* AckLow */
KEEP_ALIVE, /* Ping */
KEEP_ALIVE, /* Pong */
COMMAND, /* Ack */
COMMAND, /* AckLow */
COMMAND, /* */
UNKNOWN,
@@ -71,63 +127,39 @@ namespace ts {
inline static category::value from_type(uint8_t type){
return lookup_table[type & 0xFU];
}
inline static category::value from_type(const protocol::PacketTypeInfo& type){
return from_type(type.type());
}
};
explicit ConnectionStatistics(const std::shared_ptr<ConnectionStatistics>& /* root */, bool /* spawn properties */);
explicit ConnectionStatistics(std::shared_ptr<ConnectionStatistics> /* root */);
~ConnectionStatistics();
std::shared_ptr<Properties> statistics();
inline void logIncomingPacket(const protocol::ClientPacket& packet) { this->logIncomingPacket(category::from_type(packet.type()), packet.length()); }
void logIncomingPacket(const category::value& /* category */, size_t /* length */);
inline void logOutgoingPacket(const protocol::ServerPacket& packet) { this->logOutgoingPacket(category::from_type(packet.type()), packet.length()); }
void logOutgoingPacket(const category::value& /* category */, size_t /* length */);
void logFileTransferIn(uint64_t);
void logFileTransferOut(uint64_t);
void logFileTransferIn(uint32_t);
void logFileTransferOut(uint32_t);
void tick();
DataSummery dataReport();
FullReport full_report();
[[nodiscard]] inline const BandwidthEntry<uint64_t>& total_stats() const { return this->total_statistics; }
[[nodiscard]] inline BandwidthEntry<uint32_t> second_stats() const { return this->statistics_second; }
[[nodiscard]] BandwidthEntry<uint32_t> minute_stats() const;
FileTransferStatistics file_stats();
std::pair<uint64_t, uint64_t> mark_file_bytes();
inline bool measure_bandwidths() { return this->_measure_bandwidths; }
void measure_bandwidths(bool flag) { this->_measure_bandwidths = flag; }
inline bool has_properties() { return !!this->properties; }
private:
bool _measure_bandwidths = true;
std::shared_ptr<ConnectionStatistics> handle;
std::shared_ptr<Properties> properties;
BandwidthEntry<uint64_t> total_statistics{};
std::atomic<uint64_t> connection_packets_sent[4]{0, 0, 0, 0};
std::atomic<uint64_t> connection_bytes_sent[4]{0, 0, 0, 0};
std::atomic<uint64_t> connection_packets_received[4]{0, 0, 0, 0};
std::atomic<uint64_t> connection_bytes_received[4]{0, 0, 0, 0};
BandwidthEntry<std::atomic<uint64_t>> statistics_second_current{};
BandwidthEntry<uint32_t> statistics_second{}; /* will be updated every second by the stats from the "current_second" */
std::array<BandwidthEntry<uint32_t>, 60> statistics_minute{};
uint32_t statistics_minute_offset{0}; /* pointing to the upcoming minute */
std::chrono::system_clock::time_point last_second_tick{};
std::atomic<uint64_t> file_bytes_sent = 0;
std::atomic<uint64_t> file_bytes_received = 0;
std::atomic<uint64_t> file_bytes_sent{0};
std::atomic<uint64_t> file_bytes_received{0};
std::atomic<uint64_t> mark_file_bytes_sent = 0;
std::atomic<uint64_t> mark_file_bytes_received = 0;
spin_lock history_lock_outgoing;
spin_lock history_lock_incoming;
std::deque<StatisticEntry*> history_file_incoming{};
std::deque<StatisticEntry*> history_file_outgoing{};
std::deque<StatisticEntry*> history_incoming{};
std::deque<StatisticEntry*> history_outgoing{};
void _log_incoming_packet(StatisticEntry */* statistics */, int8_t /* type index */);
void _log_outgoing_packet(StatisticEntry* /* statistics */, int8_t /* type index */);
void _log_incoming_file_packet(StatisticEntry */* statistics */);
void _log_outgoing_file_packet(StatisticEntry* /* statistics */);
uint64_t mark_file_bytes_sent{0};
uint64_t mark_file_bytes_received{0};
};
}
}
+419 -259
View File
@@ -13,156 +13,168 @@ using namespace ts::permission;
//#define DISABLE_CACHING
struct ts::server::CachedPermissionManager {
ServerId server_id{0};
ClientDbId client_database_id{0};
std::weak_ptr<permission::v2::PermissionManager> instance{};
std::shared_ptr<permission::v2::PermissionManager> instance_ref{}; /* reference to the current instance, will be refreshed every time the instance gets accessed */
std::chrono::time_point<std::chrono::system_clock> last_access{};
};
struct ts::server::StartupCacheEntry {
ServerId sid{0};
std::deque<std::unique_ptr<StartupPermissionEntry>> permissions{};
std::deque<std::unique_ptr<StartupPropertyEntry>> properties{};
};
DatabaseHelper::DatabaseHelper(sql::SqlManager* srv) : sql(srv) {}
DatabaseHelper::~DatabaseHelper() {
for(const auto& elm : cachedPermissionManagers)
delete elm;
cachedPermissionManagers.clear();
this->cached_permission_managers.clear();
}
void DatabaseHelper::tick() {
auto cache_timeout = std::chrono::system_clock::now() - std::chrono::minutes{10};
{
threads::MutexLock l(this->permManagerLock);
auto cpy = this->cachedPermissionManagers;
for(const auto& mgr : cpy){
//if(mgr->ownLock && system_clock::now() - mgr->lastAccess > minutes(5)) //TODO remove instand delete!
mgr->ownLock.reset();
if(mgr->manager.expired()){
this->cachedPermissionManagers.erase(std::find(this->cachedPermissionManagers.begin(), this->cachedPermissionManagers.end(), mgr));
delete mgr;
}
}
}
std::lock_guard cp_lock{this->cached_permission_manager_lock};
{
threads::MutexLock l(this->propsLock);
auto pcpy = this->cachedProperties;
for(const auto& mgr : pcpy){
if(mgr->ownLock && system_clock::now() - mgr->lastAccess > minutes(5))
mgr->ownLock.reset();
if(mgr->properties.expired()) {
this->cachedProperties.erase(std::find(this->cachedProperties.begin(), this->cachedProperties.end(), mgr));
delete mgr;
}
}
this->cached_permission_managers.erase(std::remove_if(this->cached_permission_managers.begin(), this->cached_permission_managers.end(), [&](const std::unique_ptr<CachedPermissionManager>& manager) {
if(manager->last_access < cache_timeout)
manager->instance_ref = nullptr;
if(manager->instance.expired())
return true;
return false;
}), this->cached_permission_managers.end());
}
}
int collectData(deque<shared_ptr<ClientDatabaseInfo>>* list, int length, char** values, char** columns){
shared_ptr<ClientDatabaseInfo> entry = std::make_shared<ClientDatabaseInfo>();
constexpr static std::string_view kSqlBase{"SELECT `client_unique_id`, `client_database_id`, `client_nickname`, `client_created`, `client_last_connected`, `client_ip`, `client_total_connections` FROM `clients_server`"};
inline std::deque<std::shared_ptr<ClientDatabaseInfo>> query_database_client_info(sql::SqlManager* sql_manager, ServerId server_id, const std::string& query, const std::vector<variable>& variables) {
std::deque<std::shared_ptr<ClientDatabaseInfo>> result{};
for(int index = 0; index < length; index++)
if(strcmp(columns[index], "cldbid") == 0)
entry->cldbid = static_cast<ClientDbId>(stol(values[index]));
else if(strcmp(columns[index], "clientUid") == 0)
entry->uniqueId = values[index];
else if(strcmp(columns[index], "firstConnect") == 0)
entry->created = time_point<system_clock>() + seconds(stoll(values[index]));
else if(strcmp(columns[index], "lastConnect") == 0)
entry->lastjoin = time_point<system_clock>() + seconds(stoll(values[index]));
else if(strcmp(columns[index], "connections") == 0)
entry->connections = static_cast<uint32_t>(stoi(values[index]));
else if(strcmp(columns[index], "lastName") == 0)
entry->lastName = values[index];
else if(strcmp(columns[index], "serverId") == 0);
else logError(LOG_GENERAL, "Invalid db key for manager data. Key: {}", columns[index]);
sql::command command{sql_manager, query};
for(const auto& variable : variables)
command.value(variable);
auto sql_result = command.query([&](int length, std::string* values, std::string* names) {
auto entry = std::make_shared<ClientDatabaseInfo>();
list->push_back(entry);
return 0;
}
auto index{0};
try {
assert(names[index] == "client_unique_id");
entry->client_unique_id = values[index++];
#define MAX_QUERY 32
std::deque<std::shared_ptr<ClientDatabaseInfo>> DatabaseHelper::queryDatabaseInfo(const std::shared_ptr<VirtualServer>& server, const std::deque<ClientDbId>& list) {
if(list.empty()) return {};
assert(names[index] == "client_database_id");
entry->client_database_id = std::stoull(values[index++]);
deque<shared_ptr<ClientDatabaseInfo>> result;
assert(names[index] == "client_nickname");
entry->client_nickname = values[index++];
if(list.size() <= MAX_QUERY){
std::string query = "SELECT * FROM `clients` WHERE `serverId` = :serverId AND (";
for(auto elm : list)
query += " `cldbid` = " + to_string(elm) + " OR";
query = query.substr(0, query.length() - 3) + ")";
logTrace(server ? server->getServerId() : 0, "[SQL] queryDatabseInfo(...) -> {}", query);
auto state = sql::command(this->sql, query, variable{":serverId", server ? server->getServerId() : 0}).query(std::function<decltype(collectData)>(collectData), &result);
auto pf = LOG_SQL_CMD;
pf(state);
if(!state) return {};
} else {
std::deque<ClientDbId> sub;
do {
sub.insert(sub.begin(), list.begin(), list.begin() + min(list.size(), (size_t) MAX_QUERY));
list.erase(list.begin(), list.begin() + min(list.size(), (size_t) MAX_QUERY));
assert(names[index] == "client_created");
entry->client_created = std::chrono::system_clock::time_point{} + std::chrono::seconds{std::stoull(values[index++])};
auto res = this->queryDatabaseInfo(server, sub);
result.insert(result.end(), res.begin(), res.end());
sub.clear();
} while(!list.empty());
assert(names[index] == "client_last_connected");
entry->client_last_connected = std::chrono::system_clock::time_point{} + std::chrono::seconds{std::stoull(values[index++])};
assert(names[index] == "client_ip");
entry->client_ip = values[index++];
assert(names[index] == "client_total_connections");
entry->client_total_connections = std::stoull(values[index++]);
assert(index == length);
} catch (std::exception& ex) {
logError(server_id, "Failed to parse client base properties at index {}: {}. Query: {}",
index - 1,
ex.what(),
query
);
}
result.push_back(std::move(entry));
});
if(!sql_result) {
logError(server_id, "Failed to query client database infos: {}; Query: {}", sql_result.fmtStr(), query);
return result;
}
return result;
}
std::deque<std::shared_ptr<ClientDatabaseInfo>> DatabaseHelper::queryDatabaseInfo(const std::shared_ptr<VirtualServer>& server, const std::deque<ClientDbId>& list) {
if(list.empty())
return {};
std::string valueList{};
for(const auto& element : list)
valueList += ", " + std::to_string(element);
valueList = valueList.substr(2);
auto serverId = server ? server->getServerId() : 0;
return query_database_client_info(this->sql, serverId, std::string{kSqlBase} + "WHERE `server_id` = :sid AND `client_database_id` IN (" + valueList + ")", {variable{":sid", serverId}});
}
std::deque<std::shared_ptr<ClientDatabaseInfo>> DatabaseHelper::queryDatabaseInfoByUid(const std::shared_ptr<VirtualServer> &server, std::deque<std::string> list) {
if(list.empty()) return {};
if(list.empty())
return {};
deque<shared_ptr<ClientDatabaseInfo>> result;
std::string valueList{};
for(size_t value_index{0}; value_index < list.size(); value_index++)
valueList += ", :v" + std::to_string(value_index);
valueList = valueList.substr(2);
if(list.size() <= MAX_QUERY){
std::string query = "SELECT * FROM `clients` WHERE `serverId` = :serverId AND (";
for(const auto &elm : list)
query += " `clientUid` = '" + elm + "' OR";
query = query.substr(0, query.length() - 3) + ")";
logTrace(server ? server->getServerId() : 0, "[SQL] queryDatabseInfoByUid(...) -> {}", query);
auto state = sql::command(this->sql, query, variable{":serverId", server ? server->getServerId() : 0}).query(function<decltype(collectData)>(collectData), &result);
auto pf = LOG_SQL_CMD;
pf(state);
if(!state) return {};
} else {
std::deque<std::string> sub;
do {
sub.insert(sub.begin(), list.begin(), list.begin() + min(list.size(), (size_t) MAX_QUERY));
list.erase(list.begin(), list.begin() + min(list.size(), (size_t) MAX_QUERY));
auto serverId = server ? server->getServerId() : 0;
auto res = this->queryDatabaseInfoByUid(server, sub);
result.insert(result.end(), res.begin(), res.end());
sub.clear();
} while(!list.empty());
}
std::vector<variable> values{};
values.reserve(list.size() + 1);
return result;
values.emplace_back(":sid", serverId);
for(size_t value_index{0}; value_index < list.size(); value_index++)
values.emplace_back(":v" + std::to_string(value_index), list[value_index]);
return query_database_client_info(this->sql, serverId, std::string{kSqlBase} + "WHERE `server_id` = :sid AND `client_unique_id` IN (" + valueList + ")", values);
}
bool DatabaseHelper::validClientDatabaseId(const std::shared_ptr<VirtualServer>& server, ClientDbId cldbid) { return cldbid > 0; } //TODO here check
void DatabaseHelper::deleteClient(const std::shared_ptr<VirtualServer>& server, ClientDbId cldbid) {
ServerId sid = static_cast<ServerId>(server ? server->getServerId() : 0);
auto serverId = (ServerId) (server ? server->getServerId() : 0);
{
lock_guard<threads::Mutex> lock(permManagerLock);
for(auto permMgr : this->cachedPermissionManagers)
if(permMgr->cldbid == cldbid && permMgr->sid == sid) {
this->cachedPermissionManagers.erase(std::find(this->cachedPermissionManagers.begin(), this->cachedPermissionManagers.end(), permMgr));
delete permMgr;
break;
}
lock_guard lock{cached_permission_manager_lock};
this->cached_permission_managers.erase(std::remove_if(this->cached_permission_managers.begin(), this->cached_permission_managers.end(), [&](const auto& entry) {
return entry->server_id == serverId && entry->client_database_id == cldbid;
}), this->cached_permission_managers.end());
}
//TODO remove from props cache?
sql::result state{};
auto state = sql::command(this->sql, "DELETE FROM `properties` WHERE `serverId` = :sid AND (`type` = :type1 OR `type` = :type2) AND `id` = :id", variable{":sid", sid}, variable{":type1", property::PROP_TYPE_CONNECTION}, variable{":type2", property::PROP_TYPE_CLIENT}, variable{":id", cldbid}).execute();
state = sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :sid AND `type` = :type AND `id` = :id", variable{":sid", sid}, variable{":type", permission::SQL_PERM_USER}, variable{":id", cldbid}).execute();
state = sql::command(this->sql, "DELETE FROM `clients` WHERE `serverId` = :sid AND `cldbid` = :id", variable{":sid", sid}, variable{":id", cldbid}).execute();
state = sql::command(this->sql, "DELETE FROM `bannedClients` WHERE `serverId` = :sid AND `invokerDbid` = :id", variable{":sid", sid}, variable{":id", cldbid}).execute();
state = sql::command(this->sql, "DELETE FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :id", variable{":sid", sid}, variable{":id", cldbid}).execute();
state = sql::command(this->sql, "DELETE FROM `properties` WHERE `serverId` = :sid AND (`type` = :type1 OR `type` = :type2) AND `id` = :id", variable{":sid", serverId}, variable{":type1", property::PROP_TYPE_CONNECTION}, variable{":type2", property::PROP_TYPE_CLIENT}, variable{":id", cldbid}).execute();
state = sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :sid AND `type` = :type AND `id` = :id", variable{":sid", serverId}, variable{":type", permission::SQL_PERM_USER}, variable{":id", cldbid}).execute();
state = sql::command(this->sql, "DELETE FROM `bannedClients` WHERE `serverId` = :sid AND `invokerDbid` = :id", variable{":sid", serverId}, variable{":id", cldbid}).execute();
state = sql::command(this->sql, "DELETE FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :id", variable{":sid", serverId}, variable{":id", cldbid}).execute();
if(serverId == 0) {
state = sql::command(this->sql, "DELETE FROM `clients_server` WHERE `client_database_id` = :id", variable{":id", cldbid}).execute();
state = sql::command(this->sql, "DELETE FROM `clients` WHERE `client_database_id` = :id", variable{":id", cldbid}).execute();
} else {
state = sql::command(this->sql, "DELETE FROM `clients_server` WHERE `server_id` = :sid AND `client_database_id` = :id", variable{":sid", serverId}, variable{":id", cldbid}).execute();
}
//TODO delete letters
//TODO delete query
//TODO delete complains
}
inline sql::result load_permissions_v2(const std::shared_ptr<VirtualServer>& server, v2::PermissionManager* manager, sql::command& command, bool test_channel /* only used for client permissions (client channel permissions) */) {
auto start = system_clock::now();
auto server_id = server ? server->getServerId() : 0;
inline sql::result load_permissions_v2(
const ServerId& server_id,
v2::PermissionManager* manager,
sql::command& command,
bool test_channel, /* only used for client permissions (client channel permissions) */
bool is_channel) {
return command.query([&](int length, char** values, char** names){
permission::PermissionType key = permission::PermissionType::undefined;
permission::PermissionValue value = permNotGranted, granted = permNotGranted;
@@ -197,66 +209,68 @@ inline sql::result load_permissions_v2(const std::shared_ptr<VirtualServer>& ser
logError(server_id, "[SQL] Cant load permissions! Failed to parse value! Command: {} Message: {} Key: {} Value: {}", command.sqlCommand(), ex.what(),names[index], values[index]);
return 0;
}
if(channel_id > 0 && test_channel) {
if(!server)
logError(server_id, "[SQL] Cant find channel for channel bound permission (No server given)! Command: {} ChannelID: {}", command.sqlCommand(), values[index]);
else {
auto channel = server->getChannelTree()->findChannel(channel_id);
if(!channel)
logError(server_id, "[SQL] Cant find channel for channel bound permission! Command: {} ChannelID: {}", command.sqlCommand(), values[index]);
}
}
if(key == permission::undefined) {
debugMessage(server_id, "[SQL] Permission entry misses permission type! Command: {}", command.sqlCommand());
return 0;
}
if(channel_id == 0)
if(channel_id == 0 || is_channel) {
manager->load_permission(key, {value, granted}, skipped, negated, value != permNotGranted, granted != permNotGranted);
else
} else {
manager->load_permission(key, {value, granted}, channel_id, skipped, negated, value != permNotGranted, granted != permNotGranted);
}
return 0;
});
auto end = system_clock::now();
auto time = end - start;
logTrace(server_id, "[SQL] load_permissions(\"{}\") took {}ms", command.sqlCommand(), duration_cast<milliseconds>(time).count());
//auto end = system_clock::now();
//auto time = end - start;
//logTrace(server_id, "[SQL] load_permissions(\"{}\") took {}ms", command.sqlCommand(), duration_cast<milliseconds>(time).count());
}
#define UPDATE_COMMAND "UPDATE `permissions` SET `value` = :value, `grant` = :grant, `flag_skip` = :flag_skip, `flag_negate` = :flag_negate WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `permId` = :permId AND `channelId` = :chId"
#define INSERT_COMMAND "INSERT INTO `permissions` (`serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate`) VALUES (:serverId, :type, :id, :chId, :permId, :value, :grant, :flag_skip, :flag_negate)"
#define DELETE_COMMAND "DELETE FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `permId` = :permId AND `channelId` = :chId"
constexpr static std::string_view kPermissionUpdateCommand{"UPDATE `permissions` SET `value` = :value, `grant` = :grant, `flag_skip` = :flag_skip, `flag_negate` = :flag_negate WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `permId` = :permId AND `channelId` = :chId"};
constexpr static std::string_view kPermissionInsertCommand{"INSERT INTO `permissions` (`serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate`) VALUES (:serverId, :type, :id, :chId, :permId, :value, :grant, :flag_skip, :flag_negate)"};
constexpr static std::string_view kPermissionDeleteCommand{"DELETE FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `permId` = :permId AND `channelId` = :chId"};
std::shared_ptr<v2::PermissionManager> DatabaseHelper::loadClientPermissionManager(const std::shared_ptr<VirtualServer>& server, ClientDbId cldbid) {
auto server_id = server ? server->getServerId() : 0;
std::shared_ptr<permission::v2::PermissionManager> DatabaseHelper::find_cached_permission_manager(ServerId server_id,
ClientDbId client_database_id) {
for(auto it = this->cached_permission_managers.begin(); it != this->cached_permission_managers.end(); it++) {
auto& cached_manager = *it;
if(cached_manager->client_database_id == client_database_id && cached_manager->server_id == server_id) {
auto manager = cached_manager->instance.lock();
if(!manager){
this->cached_permission_managers.erase(it);
break;
}
cached_manager->last_access = system_clock::now();
cached_manager->instance_ref = manager;
return manager;
}
}
return nullptr;
}
std::shared_ptr<v2::PermissionManager> DatabaseHelper::loadClientPermissionManager(const ServerId& server_id, ClientDbId cldbid) {
#ifndef DISABLE_CACHING
{
lock_guard<threads::Mutex> lock(permManagerLock);
for(auto permMgr : this->cachedPermissionManagers)
if(permMgr->cldbid == cldbid && permMgr->sid == (server ? server->getServerId() : 0)) {
auto ptr = permMgr->manager.lock();
if(!ptr){
this->cachedPermissionManagers.erase(std::find(this->cachedPermissionManagers.begin(), this->cachedPermissionManagers.end(), permMgr));
delete permMgr;
break;
}
permMgr->lastAccess = system_clock::now();
return ptr;
}
std::lock_guard lock{cached_permission_manager_lock};
auto manager = this->find_cached_permission_manager(server_id, cldbid);
if(manager) return manager;
}
#endif
logTrace(server_id, "[Permission] Loading client permission manager for client {}", cldbid);
auto permission_manager = std::make_shared<v2::PermissionManager>();
bool loaded = false;
if(this->use_startup_cache && server) {
if(this->use_startup_cache && server_id > 0) {
shared_ptr<StartupCacheEntry> entry;
{
threads::MutexLock lock(this->startup_lock);
for(const auto& entries : this->startup_entries) {
if(entries->sid == server->getServerId()) {
if(entries->sid == server_id) {
entry = entries;
break;
}
@@ -265,12 +279,11 @@ std::shared_ptr<v2::PermissionManager> DatabaseHelper::loadClientPermissionManag
if(entry) {
for(const auto& perm : entry->permissions) {
if(perm->type == permission::SQL_PERM_USER && perm->id == cldbid) {
auto channel = perm->channelId > 0 ? server->getChannelTree()->findChannel(perm->channelId) : nullptr;
if(channel)
permission_manager->load_permission(perm->permission->type, {perm->value, perm->grant}, channel->channelId(), perm->flag_skip, perm->flag_negate, perm->value != permNotGranted, perm->grant != permNotGranted);
else
if(perm->channelId > 0) {
permission_manager->load_permission(perm->permission->type, {perm->value, perm->grant}, perm->channelId, perm->flag_skip, perm->flag_negate, perm->value != permNotGranted, perm->grant != permNotGranted);
} else {
permission_manager->load_permission(perm->permission->type, {perm->value, perm->grant}, perm->flag_skip, perm->flag_negate, perm->value != permNotGranted, perm->grant != permNotGranted);
}
}
}
loaded = true;
@@ -279,23 +292,31 @@ std::shared_ptr<v2::PermissionManager> DatabaseHelper::loadClientPermissionManag
if(!loaded) {
auto command = sql::command(this->sql, "SELECT `permId`, `value`, `channelId`, `grant`, `flag_skip`, `flag_negate` FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id",
variable{":serverId", server ? server->getServerId() : 0},
variable{":serverId", server_id},
variable{":type", permission::SQL_PERM_USER},
variable{":id", cldbid});
LOG_SQL_CMD(load_permissions_v2(server, permission_manager.get(), command, true));
LOG_SQL_CMD(load_permissions_v2(server_id, permission_manager.get(), command, true, false));
}
#ifndef DISABLE_CACHING
this->permManagerLock.lock();
auto entry = new CachedPermissionManager();
entry->sid = server_id;
entry->manager = permission_manager;
entry->ownLock = permission_manager;
entry->cldbid = cldbid;
entry->lastAccess = system_clock::now();
this->cachedPermissionManagers.push_back(entry);
this->permManagerLock.unlock();
auto cache_entry = std::make_unique<CachedPermissionManager>();
cache_entry->server_id = server_id;
cache_entry->instance = permission_manager;
cache_entry->instance_ref = permission_manager;
cache_entry->client_database_id = cldbid;
cache_entry->last_access = system_clock::now();
{
std::lock_guard cache_lock{this->cached_permission_manager_lock};
/* test if we might not got a second instance */
auto manager = this->find_cached_permission_manager(server_id, cldbid);
if(manager) return manager;
this->cached_permission_managers.push_back(std::move(cache_entry));
}
#endif
return permission_manager;
}
@@ -308,7 +329,7 @@ void DatabaseHelper::saveClientPermissions(const std::shared_ptr<ts::server::Vir
auto server_id = server ? server->getServerId() : 0;
for(auto& update : updates) {
std::string query = update.flag_delete ? DELETE_COMMAND : (update.flag_db ? UPDATE_COMMAND : INSERT_COMMAND);
std::string query{update.flag_delete ? kPermissionDeleteCommand : (update.flag_db ? kPermissionUpdateCommand : kPermissionInsertCommand)};
auto permission_data = permission::resolvePermissionData(update.permission);
auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value;
@@ -336,14 +357,14 @@ void DatabaseHelper::saveClientPermissions(const std::shared_ptr<ts::server::Vir
}
std::shared_ptr<permission::v2::PermissionManager> DatabaseHelper::loadGroupPermissions(const std::shared_ptr<VirtualServer>& server, ts::GroupId group_id) {
std::shared_ptr<permission::v2::PermissionManager> DatabaseHelper::loadGroupPermissions(const ServerId& server_id, ts::GroupId group_id, uint8_t /* target */) {
auto result = std::make_shared<v2::PermissionManager>();
if(this->use_startup_cache && server) {
if(this->use_startup_cache && server_id > 0) {
shared_ptr<StartupCacheEntry> entry;
{
threads::MutexLock lock(this->startup_lock);
for(const auto& entries : this->startup_entries) {
if(entries->sid == server->getServerId()) {
if(entries->sid == server_id) {
entry = entries;
break;
}
@@ -361,34 +382,33 @@ std::shared_ptr<permission::v2::PermissionManager> DatabaseHelper::loadGroupPerm
//7931
auto command = sql::command(this->sql, "SELECT `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate` FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id",
variable{":serverId", server ? server->getServerId() : 0},
variable{":serverId", server_id},
variable{":type", permission::SQL_PERM_GROUP},
variable{":id", group_id});
LOG_SQL_CMD(load_permissions_v2(server, result.get(), command, false));
LOG_SQL_CMD(load_permissions_v2(server_id, result.get(), command, false, false));
return result;
}
void DatabaseHelper::saveGroupPermissions(const std::shared_ptr<ts::server::VirtualServer> &server, ts::GroupId group_id, const std::shared_ptr<ts::permission::v2::PermissionManager> &permissions) {
void DatabaseHelper::saveGroupPermissions(const ServerId &server_id, ts::GroupId group_id, uint8_t target, const std::shared_ptr<ts::permission::v2::PermissionManager> &permissions) {
const auto updates = permissions->flush_db_updates();
if(updates.empty())
return;
auto server_id = server ? server->getServerId() : 0;
for(auto& update : updates) {
std::string query = update.flag_delete ? DELETE_COMMAND : (update.flag_db ? UPDATE_COMMAND : INSERT_COMMAND);
std::string query{update.flag_delete ? kPermissionDeleteCommand : (update.flag_db ? kPermissionUpdateCommand : kPermissionInsertCommand)};
auto permission_data = permission::resolvePermissionData(update.permission);
auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value;
auto grant = update.update_grant == v2::delete_value ? permNotGranted : update.values.grant;
logTrace(server_id, "[CHANNEL] Updating group permission for group {}: {}. New value: {}. New grant: {}. Query: {}",
group_id,
logTrace(server_id, "Updating group permission for group {}/{}: {}. New value: {}. New grant: {}. Query: {}",
target, group_id,
permission_data->name,
value,
grant,
query
);
sql::command(this->sql, query,
variable{":serverId", server ? server->getServerId() : 0},
variable{":serverId", server_id},
variable{":id", group_id},
variable{":chId", 0},
variable{":type", permission::SQL_PERM_GROUP},
@@ -436,7 +456,7 @@ std::shared_ptr<permission::v2::PermissionManager> DatabaseHelper::loadPlaylistP
variable{":serverId", server ? server->getServerId() : 0},
variable{":type", permission::SQL_PERM_PLAYLIST},
variable{":id", playlist_id});
LOG_SQL_CMD(load_permissions_v2(server, result.get(), command, false));
LOG_SQL_CMD(load_permissions_v2(server ? server->getServerId() : 0, result.get(), command, false, false));
return result;
}
@@ -447,7 +467,7 @@ void DatabaseHelper::savePlaylistPermissions(const std::shared_ptr<VirtualServer
auto server_id = server ? server->getServerId() : 0;
for(auto& update : updates) {
std::string query = update.flag_delete ? DELETE_COMMAND : (update.flag_db ? UPDATE_COMMAND : INSERT_COMMAND);
std::string query{update.flag_delete ? kPermissionDeleteCommand : (update.flag_db ? kPermissionUpdateCommand : kPermissionInsertCommand)};
auto permission_data = permission::resolvePermissionData(update.permission);
auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value;
@@ -502,7 +522,7 @@ std::shared_ptr<permission::v2::PermissionManager> DatabaseHelper::loadChannelPe
variable{":chid", channel},
variable{":id", 0},
variable{":type", permission::SQL_PERM_CHANNEL});
LOG_SQL_CMD(load_permissions_v2(server, result.get(), command, false));
LOG_SQL_CMD(load_permissions_v2(server ? server->getServerId() : 0, result.get(), command, false, true));
return result;
}
@@ -513,7 +533,7 @@ void DatabaseHelper::saveChannelPermissions(const std::shared_ptr<ts::server::Vi
auto server_id = server ? server->getServerId() : 0;
for(auto& update : updates) {
std::string query = update.flag_delete ? DELETE_COMMAND : (update.flag_db ? UPDATE_COMMAND : INSERT_COMMAND);
std::string query{update.flag_delete ? kPermissionDeleteCommand : (update.flag_db ? kPermissionUpdateCommand : kPermissionInsertCommand)};
auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value;
auto grant = update.update_grant == v2::delete_value ? permNotGranted : update.values.grant;
@@ -540,12 +560,12 @@ void DatabaseHelper::saveChannelPermissions(const std::shared_ptr<ts::server::Vi
}
}
std::shared_ptr<Properties> DatabaseHelper::default_properties_client(std::shared_ptr<Properties> properties, ClientType type){
if(!properties)
properties = make_shared<Properties>();
std::shared_ptr<PropertyManager> DatabaseHelper::default_properties_client(std::shared_ptr<PropertyManager> properties, ClientType type){
if(!properties) {
properties = std::make_shared<PropertyManager>();
}
properties->register_property_type<property::ClientProperties>();
properties->register_property_type<property::ConnectionProperties>();
if(type == ClientType::CLIENT_MUSIC || type == ClientType::CLIENT_QUERY){
(*properties)[property::CLIENT_INPUT_HARDWARE] = true;
@@ -555,47 +575,42 @@ std::shared_ptr<Properties> DatabaseHelper::default_properties_client(std::share
return properties;
}
bool DatabaseHelper::assignDatabaseId(sql::SqlManager *sql, ServerId id, std::shared_ptr<DataClient> cl) {
bool DatabaseHelper::assignDatabaseId(sql::SqlManager *sql, ServerId serverId, std::shared_ptr<DataClient> cl) {
cl->loadDataForCurrentServer();
if(cl->getClientDatabaseId() == 0){ //Client does not exist
ClientDbId cldbid = 0;
auto res = sql::command(sql, "SELECT `cldbid` FROM `clients` WHERE `serverId` = 0 AND `clientUid` = :cluid", variable{":cluid", cl->getUid()}).query([](ClientDbId* ptr, int length, char** values, char** names){
*ptr = static_cast<ClientDbId>(stoll(values[0]));
return 0;
}, &cldbid);
auto pf = LOG_SQL_CMD;
pf(res);
if(!res) return false;
if(cl->getClientDatabaseId() == 0) {
/* client does not exists, create a new one */
auto insertTemplate = sql::model(sql, "INSERT INTO `clients` (`serverId`, `cldbId`, `clientUid`, `lastName`,`firstConnect`,`lastConnect`, `connections`) VALUES (:serverId, :cldbid, :cluid, :name, :fconnect, :lconnect, :connections)",
variable{":cluid", cl->getUid()}, variable{":name", cl->getDisplayName()}, variable{":fconnect", duration_cast<seconds>(system_clock::now().time_since_epoch()).count()}, variable{":lconnect", 0}, variable{":connections", 0});
if(cldbid == 0){ //Completly new user
res = sql::command(sql, "SELECT `cldbid` FROM `clients` WHERE `serverId` = 0 ORDER BY `cldbid` DESC LIMIT 1").query([](ClientDbId* ptr, int length, char** values, char** names){
*ptr = static_cast<ClientDbId>(stoll(values[0]));
return 0;
}, &cldbid);
pf(res);
if(!res) return false;
sql::result sql_result{};
cldbid += 1;
res = insertTemplate.command().values(variable{":serverId", 0}, variable{":cldbid", cldbid}).execute(); //Insert global
pf(res);
if(!res) return false;
debugMessage(id, "Having new instance user on server {}. (Database ID: {} Name: {})", id, cldbid, cl->getDisplayName());
} else {
debugMessage(id, "Having new server user on server {}. (Database ID: {} Name: {})", id, cldbid, cl->getDisplayName());
std::string insert_or_ignore{sql->getType() == sql::TYPE_SQLITE ? "INSERT OR IGNORE" : "INSERT IGNORE"};
auto currentTimeSeconds = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
sql_result = sql::command{sql, insert_or_ignore + " INTO `clients` (`client_unique_id`, `client_created`) VALUES (:uniqueId, :now)",
variable{":uniqueId", cl->getUid()},
variable{":now", currentTimeSeconds}
}.execute();
if(!sql_result) {
logCritical(LOG_INSTANCE, "Failed to execute client insert command for {}: {}", cl->getUid(), sql_result.fmtStr());
return false;
}
if(id != 0){ //Else already inserted
res = insertTemplate.command().values(variable{":serverId", id}, variable{":cldbid", cldbid}).execute();
pf(res);
if(!res) return false;
sql_result = sql::command{sql, "INSERT INTO `clients_server` (`server_id`, `client_unique_id`, `client_database_id`, `client_created`) SELECT :serverId, :uniqueId, `client_database_id`, :now FROM `clients` WHERE `client_unique_id` = :uniqueId;",
variable{":serverId", serverId},
variable{":uniqueId", cl->getUid()},
variable{":now", currentTimeSeconds}
}.execute();
if(!sql_result) {
logCritical(LOG_INSTANCE, "Failed to execute client server insert command for {}: {}", cl->getUid(), sql_result.fmtStr());
return false;
}
return assignDatabaseId(sql, id, cl);
if(!cl->loadDataForCurrentServer())
return false;
debugMessage(serverId, "Successfully registered client {} for server {} with database id {}.", cl->getUid(), serverId, cl->getClientDatabaseId());
}
logTrace(id, "Loaded client from database. Database id: {} Unique id: {}", cl->getClientDatabaseId(), cl->getUid());
return true;
}
@@ -617,8 +632,8 @@ inline sql::result load_properties(ServerId sid, deque<unique_ptr<FastPropertyEn
}
}
const auto &info = property::impl::info_key(type, key);
if(info->name == "undefined") {
const auto &info = property::find(type, key);
if(info.name == "undefined") {
logError(sid, "Found unknown property in database! ({})", key);
return 0;
}
@@ -630,9 +645,9 @@ inline sql::result load_properties(ServerId sid, deque<unique_ptr<FastPropertyEn
prop.setDbReference(true);
*/
auto data = make_unique<FastPropertyEntry>();
auto data = std::make_unique<FastPropertyEntry>();
data->type = &info;
data->value = value;
data->type = info;
properties.push_back(move(data));
return 0;
});
@@ -643,8 +658,8 @@ inline sql::result load_properties(ServerId sid, deque<unique_ptr<FastPropertyEn
return result;
}
std::shared_ptr<Properties> DatabaseHelper::loadServerProperties(const std::shared_ptr<ts::server::VirtualServer>& server) {
auto props = std::make_shared<Properties>();
std::shared_ptr<PropertyManager> DatabaseHelper::loadServerProperties(const std::shared_ptr<ts::server::VirtualServer>& server) {
auto props = std::make_shared<PropertyManager>();
props->register_property_type<property::VirtualServerProperties>();
(*props)[property::VIRTUALSERVER_HOST] = config::binding::DefaultVoiceHost;
@@ -695,17 +710,19 @@ std::shared_ptr<Properties> DatabaseHelper::loadServerProperties(const std::shar
return;
}
auto weak_server = weak.lock();
if(!weak_server && serverId != 0) return;
if(!weak_server && serverId != 0) {
return;
}
string sql;
if(prop.hasDbReference())
if(prop.hasDbReference()) {
sql = "UPDATE `properties` SET `value` = :value WHERE `serverId` = :sid AND `type` = :type AND `id` = :id AND `key` = :key";
else {
} else {
prop.setDbReference(true);
sql = "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) VALUES (:sid, :type, :id, :key, :value)";
}
logTrace(serverId, "Updating server property: " + prop.type().name + ". New value: " + prop.value() + ". Query: " + sql);
logTrace(serverId, "Updating server property: " + std::string{prop.type().name} + ". New value: " + prop.value() + ". Query: " + sql);
sql::command(this->sql, sql,
variable{":sid", serverId},
variable{":type", property::PropertyType::PROP_TYPE_SERVER},
@@ -717,8 +734,8 @@ std::shared_ptr<Properties> DatabaseHelper::loadServerProperties(const std::shar
return props;
}
std::shared_ptr<Properties> DatabaseHelper::loadPlaylistProperties(const std::shared_ptr<ts::server::VirtualServer>& server, PlaylistId id) {
auto props = std::make_shared<Properties>();
std::shared_ptr<PropertyManager> DatabaseHelper::loadPlaylistProperties(const std::shared_ptr<ts::server::VirtualServer>& server, PlaylistId id) {
auto props = std::make_shared<PropertyManager>();
props->register_property_type<property::PlaylistProperties>();
(*props)[property::PLAYLIST_ID] = id;
@@ -771,9 +788,9 @@ std::shared_ptr<Properties> DatabaseHelper::loadPlaylistProperties(const std::sh
if(!weak_server && serverId != 0) return;
string sql;
if(prop.hasDbReference())
if(prop.hasDbReference()) {
sql = "UPDATE `properties` SET `value` = :value WHERE `serverId` = :sid AND `type` = :type AND `id` = :id AND `key` = :key";
else {
} else {
prop.setDbReference(true);
sql = "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) VALUES (:sid, :type, :id, :key, :value)";
}
@@ -790,9 +807,9 @@ std::shared_ptr<Properties> DatabaseHelper::loadPlaylistProperties(const std::sh
return props;
}
std::shared_ptr<Properties> DatabaseHelper::loadChannelProperties(const shared_ptr<VirtualServer>& server, ChannelId channel) {
std::shared_ptr<PropertyManager> DatabaseHelper::loadChannelProperties(const shared_ptr<VirtualServer>& server, ChannelId channel) {
ServerId serverId = server ? server->getServerId() : 0U;
auto props = std::make_shared<Properties>();
auto props = std::make_shared<PropertyManager>();
props->register_property_type<property::ChannelProperties>();
if(server) {
@@ -871,11 +888,12 @@ std::shared_ptr<Properties> DatabaseHelper::loadChannelProperties(const shared_p
return props;
}
std::shared_ptr<Properties> DatabaseHelper::loadClientProperties(const std::shared_ptr<VirtualServer>& server, ClientDbId cldbid, ClientType type) {
std::shared_ptr<PropertyManager> DatabaseHelper::loadClientProperties(const std::shared_ptr<VirtualServer>& server, ClientDbId cldbid, ClientType type) {
auto props = DatabaseHelper::default_properties_client(nullptr, type);
if(server) {
props->operator[](property::CLIENT_DESCRIPTION) = server->properties()[property::VIRTUALSERVER_DEFAULT_CLIENT_DESCRIPTION].value();
}
bool loaded = false;
if(use_startup_cache && server) {
shared_ptr<StartupCacheEntry> entry;
@@ -890,7 +908,7 @@ std::shared_ptr<Properties> DatabaseHelper::loadClientProperties(const std::shar
}
if(entry) {
for(const auto& prop : entry->properties) {
if(prop->id == cldbid && (prop->type == property::PROP_TYPE_CLIENT || prop->type == property::PROP_TYPE_CONNECTION)) {
if(prop->id == cldbid && (prop->type == property::PROP_TYPE_CLIENT)) {
auto p = (*props)[prop->info];
p = prop->value;
p.setModified(true);
@@ -900,8 +918,9 @@ std::shared_ptr<Properties> DatabaseHelper::loadClientProperties(const std::shar
loaded = true;
}
}
if(!loaded) {
auto command = sql::command(this->sql, "SELECT `key`, `value`, `type` FROM properties WHERE `serverId` = :serverId AND (`type` = :type1 OR `type` = :type2) AND `id` = :id", variable{":serverId", server ? server->getServerId() : 0}, variable{":type1", property::PropertyType::PROP_TYPE_CONNECTION}, variable{":type2", property::PropertyType::PROP_TYPE_CLIENT}, variable{":id", cldbid});
auto command = sql::command(this->sql, "SELECT `key`, `value`, `type` FROM properties WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id", variable{":serverId", server ? server->getServerId() : 0}, variable{":type", property::PropertyType::PROP_TYPE_CLIENT}, variable{":id", cldbid});
deque<unique_ptr<FastPropertyEntry>> property_list;
LOG_SQL_CMD(load_properties(server ? server->getServerId() : 0, property_list, command));
@@ -925,23 +944,26 @@ std::shared_ptr<Properties> DatabaseHelper::loadClientProperties(const std::shar
if(!prop.isModified()) return;
if((prop.type().flags & property::FLAG_SAVE) == 0 && (type != ClientType::CLIENT_MUSIC || (prop.type().flags & property::FLAG_SAVE_MUSIC) == 0)) {
logTrace(server ? server->getServerId() : 0, "[Property] Not saving property '" + prop.type().name + "', changed for " + to_string(cldbid) + " (New value: " + prop.value() + ")");
logTrace(server ? server->getServerId() : 0, "[Property] Not saving property '" + std::string{prop.type().name} + "', changed for " + to_string(cldbid) + " (New value: " + prop.value() + ")");
return;
}
auto handle = prop.get_handle();
if(!handle || !handle->isSaveEnabled()) {
return;
}
if(!prop.get_handle()) return;
if(!prop.get_handle()->isSaveEnabled()) return;
if(!prop.hasDbReference() && (prop.default_value() == prop.value())) return; //No changes to default value
prop.setModified(false);
std::string sql;
std::string sqlCommand;
if(prop.hasDbReference())
sql = "UPDATE `properties` SET `value` = :value WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `key` = :key";
sqlCommand = "UPDATE `properties` SET `value` = :value WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `key` = :key";
else {
prop.setDbReference(true);
sql = "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) VALUES (:serverId, :type, :id, :key, :value)";
sqlCommand = "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) VALUES (:serverId, :type, :id, :key, :value)";
}
logTrace(server ? server->getServerId() : 0, "[Property] Changed property in db key: " + prop.type().name + " value: " + prop.value());
sql::command(this->sql, sql,
logTrace(server ? server->getServerId() : 0, "[Property] Changed property in db key: " + std::string{prop.type().name} + " value: " + prop.value());
sql::command(this->sql, sqlCommand,
variable{":serverId", server ? server->getServerId() : 0},
variable{":type", prop.type().type_property},
variable{":id", cldbid},
@@ -957,21 +979,66 @@ std::shared_ptr<Properties> DatabaseHelper::loadClientProperties(const std::shar
return;
}
std::string query;
if(prop.type() == property::CLIENT_TOTALCONNECTIONS)
query = "UPDATE `clients` SET `connections` = :value WHERE `serverId` = :serverId AND `cldbid` = :cldbid";
else if(prop.type() == property::CLIENT_NICKNAME)
query = "UPDATE `clients` SET `lastName` = :value WHERE `serverId` = :serverId AND `cldbid` = :cldbid";
else if(prop.type() == property::CLIENT_LASTCONNECTED)
query = "UPDATE `clients` SET `lastConnect` = :value WHERE `serverId` = :serverId AND `cldbid` = :cldbid";
if(query.empty()) return;
debugMessage(server ? server->getServerId() : 0, "[Property] Changing client property '" + prop.type().name + "' for " + to_string(cldbid) + " (New value: " + prop.value() + ", SQL: " + query + ")");
sql::command(this->sql, query, variable{":serverId", server ? server->getServerId() : 0}, variable{":cldbid", cldbid}, variable{":value", prop.value()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
std::string column;
if(prop.type().type_property == property::PROP_TYPE_CLIENT) {
switch (prop.type().property_index) {
case property::CLIENT_NICKNAME:
column = "client_nickname";
break;
case property::CLIENT_LASTCONNECTED:
column = "client_last_connected";
break;
case property::CLIENT_TOTALCONNECTIONS:
column = "client_total_connections";
break;
case property::CLIENT_MONTH_BYTES_UPLOADED:
column = "client_month_upload";
break;
case property::CLIENT_TOTAL_BYTES_UPLOADED:
column = "client_total_upload";
break;
case property::CLIENT_MONTH_BYTES_DOWNLOADED:
column = "client_month_download";
break;
case property::CLIENT_TOTAL_BYTES_DOWNLOADED:
column = "client_total_download";
break;
default:
return;
}
}
debugMessage(server ? server->getServerId() : 0, "[Property] Changing client property '{}' for {} (New value: {}, Column: {})",
prop.type().name,
cldbid,
prop.value(),
column
);
sql::command(this->sql, "UPDATE `clients_server` SET `" + column + "` = :value WHERE `server_id` = :serverId AND `client_database_id` = :cldbid",
variable{":serverId", server ? server->getServerId() : 0},
variable{":cldbid", cldbid},
variable{":value", prop.value()}
).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
});
return props;
}
void DatabaseHelper::updateClientIpAddress(const ServerId &server_id, ClientDbId client_database_id, const std::string &ip_address) {
sql::command(this->sql, "UPDATE `clients_server` SET `client_ip` = :value WHERE `server_id` = :serverId AND `client_database_id` = :cldbid",
variable{":serverId", server_id},
variable{":cldbid", client_database_id},
variable{":value", ip_address}
).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
}
void DatabaseHelper::loadStartupCache() {
this->loadStartupPermissionCache();
this->loadStartupPropertyCache();
@@ -1013,6 +1080,15 @@ void DatabaseHelper::clearStartupCache(ts::ServerId sid) {
}
}
void DatabaseHelper::handleServerDelete(ServerId server_id) {
{
std::lock_guard pm_lock{this->cached_permission_manager_lock};
this->cached_permission_managers.erase(std::remove_if(this->cached_permission_managers.begin(), this->cached_permission_managers.end(), [&](const auto& entry) {
return entry->server_id == server_id;
}), this->cached_permission_managers.end());
}
}
//SELECT `serverId`, `type`, `id`, `key`, `value` FROM properties ORDER BY `serverId`
//SELECT `serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate` FROM permissions ORDER BY `serverId`
struct StartupPermissionArgument {
@@ -1119,8 +1195,8 @@ void DatabaseHelper::loadStartupPropertyCache() {
}
}
auto info = property::impl::info_key(type, key);
if(info == property::PropertyDescription::unknown) {
const auto& info = property::find(type, key);
if(info.is_undefined()) {
logError(serverId, "Invalid property ({} | {})", key, type);
return 0;
}
@@ -1145,7 +1221,7 @@ void DatabaseHelper::loadStartupPropertyCache() {
}
auto entry = make_unique<StartupPropertyEntry>();
entry->info = info;
entry->info = &info;
entry->value = value;
entry->id = id;
entry->type = type;
@@ -1154,13 +1230,19 @@ void DatabaseHelper::loadStartupPropertyCache() {
}, &arg);
}
bool DatabaseHelper::deleteGroupPermissions(const std::shared_ptr<ts::server::VirtualServer> &server, ts::GroupId group_id) {
auto command = sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id",
variable{":serverId", server ? server->getServerId() : 0},
variable{":type", permission::SQL_PERM_GROUP},
variable{":id", group_id}).execute();
LOG_SQL_CMD(command);
return !!command;
void DatabaseHelper::deleteGroupArtifacts(ServerId server_id, GroupId group_id) {
sql::result result{};
result = sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id",
variable{":serverId", server_id},
variable{":type", permission::SQL_PERM_GROUP},
variable{":id", group_id}).execute();
LOG_SQL_CMD(result);
result = sql::command(this->sql, "DELETE FROM `tokens` WHERE `serverId` = :serverId AND `targetGroup` = :id",
variable{":serverId", server_id},
variable{":id", group_id}).execute();
LOG_SQL_CMD(result);
}
bool DatabaseHelper::deleteChannelPermissions(const std::shared_ptr<ts::server::VirtualServer> &server, ts::ChannelId channel_id) {
@@ -1201,4 +1283,82 @@ bool DatabaseHelper::deletePlaylist(const std::shared_ptr<ts::server::VirtualSer
).executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed to delete playlist properties for playlist " + to_string(playlist_id)});
return true;
}
constexpr static auto kDBListQuery{R"(
SELECT `clients`.*, `properties`.`value` as `client_description` FROM (
SELECT
`clients_server`.`client_database_id`,
`clients_server`.`client_unique_id`,
`clients_server`.`client_nickname`,
`clients_server`.`client_ip`,
`clients_server`.`client_created`,
`clients_server`.`client_last_connected`,
`clients_server`.`client_total_connections`,
`clients`.`client_login_name` FROM `clients_server`
INNER JOIN `clients` ON `clients`.`client_database_id` = `clients_server`.`client_database_id`
WHERE `server_id` = :serverId LIMIT :offset, :limit
) AS `clients`
LEFT JOIN `properties` ON `properties`.serverId = :serverId AND `properties`.key = 'client_description' AND `properties`.`id` = `clients`.`client_database_id`
)"};
void DatabaseHelper::listDatabaseClients(
ServerId server_id,
const std::optional<int64_t>& offset,
const std::optional<int64_t>& limit,
void (* callback)(void *, const DatabaseClient &),
void *user_argument) {
DatabaseClient client;
size_t set_index{0};
auto sqlResult = sql::command{this->sql, kDBListQuery,
variable{":serverId", server_id},
variable{":offset", offset.has_value() ? *offset : 0},
variable{":limit", limit.has_value() ? *limit : -1}
}.query([&](int length, std::string* values, std::string* names) {
set_index++;
auto index{0};
try {
assert(names[index] == "client_database_id");
client.client_database_id = std::stoull(values[index++]);
assert(names[index] == "client_unique_id");
client.client_unique_id = values[index++];
assert(names[index] == "client_nickname");
client.client_nickname = values[index++];
assert(names[index] == "client_ip");
client.client_ip = values[index++];
assert(names[index] == "client_created");
client.client_created = values[index++];
assert(names[index] == "client_last_connected");
client.client_last_connected = values[index++];
assert(names[index] == "client_total_connections");
client.client_total_connections = values[index++];
assert(names[index] == "client_login_name");
client.client_login_name = values[index++];
assert(names[index] == "client_description");
client.client_description = values[index++];
assert(index == length);
} catch (std::exception& ex) {
logError(server_id, "Failed to parse client base properties at index {}: {}. Offset: {} Limit: {} Set: {}",
index - 1,
ex.what(),
offset.has_value() ? std::to_string(*offset) : "not given",
limit.has_value() ? std::to_string(*limit) : "not given",
set_index - 1
);
return;
}
callback(user_argument, client);
});
}
+103 -99
View File
@@ -8,125 +8,129 @@
#include <Properties.h>
#include <cstdint>
namespace ts {
namespace server {
class VirtualServer;
class DataClient;
namespace ts::server {
class VirtualServer;
class DataClient;
struct ClientDatabaseInfo {
ServerId sid;
ClientDbId cldbid;
std::chrono::time_point<std::chrono::system_clock> created;
std::chrono::time_point<std::chrono::system_clock> lastjoin;
std::string uniqueId;
std::string lastName;
uint32_t connections;
};
struct ClientDatabaseInfo {
ServerId server_id;
struct CachedPermissionManager {
ServerId sid;
ClientDbId cldbid;
std::weak_ptr<permission::v2::PermissionManager> manager;
std::shared_ptr<permission::v2::PermissionManager> ownLock;
std::chrono::time_point<std::chrono::system_clock> lastAccess;
};
ClientDbId client_database_id;
std::string client_unique_id;
std::string client_nickname;
std::string client_ip;
struct CachedProperties {
ServerId sid;
ClientDbId cldbid;
std::weak_ptr<Properties> properties;
std::shared_ptr<Properties> ownLock;
std::chrono::time_point<std::chrono::system_clock> lastAccess;
};
std::chrono::time_point<std::chrono::system_clock> client_created;
std::chrono::time_point<std::chrono::system_clock> client_last_connected;
/*
CREATE_TABLE("properties", "`serverId` INTEGER DEFAULT -1, `type` INTEGER, `id` INTEGER, `key` VARCHAR(" UNKNOWN_KEY_LENGTH "), `value` TEXT", command_append_utf8);
CREATE_TABLE("permissions", "`serverId` INT NOT NULL, `type` INT, `id` INT, `channelId` INT, `permId` VARCHAR(" UNKNOWN_KEY_LENGTH "), `value` INT, `grant` INT", command_append_utf8);
*/
struct StartupPermissionEntry {
permission::PermissionSqlType type = permission::SQL_PERM_CHANNEL;
uint64_t id = 0;
ChannelId channelId = 0;
std::shared_ptr<permission::PermissionTypeEntry> permission = permission::PermissionTypeEntry::unknown;
permission::PermissionValue value = 0;
permission::PermissionValue grant = 0;
uint32_t client_total_connections;
};
bool flag_skip = false;
bool flag_negate = false;
};
struct StartupPermissionEntry {
permission::PermissionSqlType type = permission::SQL_PERM_CHANNEL;
uint64_t id = 0;
ChannelId channelId = 0;
std::shared_ptr<permission::PermissionTypeEntry> permission = permission::PermissionTypeEntry::unknown;
permission::PermissionValue value = 0;
permission::PermissionValue grant = 0;
struct StartupPropertyEntry {
property::PropertyType type = property::PropertyType::PROP_TYPE_UNKNOWN;
uint64_t id = 0;
std::shared_ptr<property::PropertyDescription> info = property::PropertyDescription::unknown;
std::string value;
};
bool flag_skip = false;
bool flag_negate = false;
};
struct StartupCacheEntry {
ServerId sid;
struct StartupPropertyEntry {
property::PropertyType type = property::PropertyType::PROP_TYPE_UNKNOWN;
uint64_t id{0};
const property::PropertyDescription* info{&property::undefined_property_description};
std::string value;
};
std::deque<std::unique_ptr<StartupPermissionEntry>> permissions;
std::deque<std::unique_ptr<StartupPropertyEntry>> properties;
};
struct DatabaseClient {
ClientDbId client_database_id;
std::string client_unique_id;
struct FastPropertyEntry {
std::shared_ptr<property::PropertyDescription> type;
std::string value;
};
std::string client_nickname;
std::string client_ip;
class DatabaseHelper {
public:
static std::shared_ptr<Properties> default_properties_client(std::shared_ptr<Properties> /* properties */, ClientType /* type */);
static bool assignDatabaseId(sql::SqlManager *, ServerId id, std::shared_ptr<DataClient>);
std::string client_created; /* seconds since epoch */
std::string client_last_connected; /* seconds since epoch */
std::string client_total_connections;
explicit DatabaseHelper(sql::SqlManager*);
~DatabaseHelper();
std::string client_login_name;
std::string client_description; /* optional and only given sometimes */
};
void loadStartupCache();
size_t cacheBinarySize();
void clearStartupCache(ServerId sid = 0);
struct FastPropertyEntry {
const property::PropertyDescription* type;
std::string value;
};
void deleteClient(const std::shared_ptr<VirtualServer>&,ClientDbId);
bool validClientDatabaseId(const std::shared_ptr<VirtualServer>&, ClientDbId);
std::deque<std::shared_ptr<ClientDatabaseInfo>> queryDatabaseInfo(const std::shared_ptr<VirtualServer>&, const std::deque<ClientDbId>&);
std::deque<std::shared_ptr<ClientDatabaseInfo>> queryDatabaseInfoByUid(const std::shared_ptr<VirtualServer> &, std::deque<std::string>);
struct CachedPermissionManager;
struct StartupCacheEntry;
class DatabaseHelper {
public:
static std::shared_ptr<PropertyManager> default_properties_client(std::shared_ptr<PropertyManager> /* properties */, ClientType /* type */);
static bool assignDatabaseId(sql::SqlManager *, ServerId serverId, std::shared_ptr<DataClient>);
std::shared_ptr<permission::v2::PermissionManager> loadClientPermissionManager(const std::shared_ptr<VirtualServer>&, ClientDbId);
void saveClientPermissions(const std::shared_ptr<VirtualServer>&, ClientDbId , const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
explicit DatabaseHelper(sql::SqlManager*);
~DatabaseHelper();
std::shared_ptr<permission::v2::PermissionManager> loadChannelPermissions(const std::shared_ptr<VirtualServer>&, ChannelId);
void saveChannelPermissions(const std::shared_ptr<VirtualServer>&, ChannelId, const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
void loadStartupCache();
size_t cacheBinarySize();
void clearStartupCache(ServerId sid = 0);
std::shared_ptr<permission::v2::PermissionManager> loadGroupPermissions(const std::shared_ptr<VirtualServer>&, GroupId);
void saveGroupPermissions(const std::shared_ptr<VirtualServer>&, GroupId, const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
void handleServerDelete(ServerId /* server id */);
std::shared_ptr<permission::v2::PermissionManager> loadPlaylistPermissions(const std::shared_ptr<VirtualServer>&, PlaylistId /* playlist id */);
void savePlaylistPermissions(const std::shared_ptr<VirtualServer>&, PlaylistId, const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
void listDatabaseClients(
ServerId /* server id */,
const std::optional<int64_t>& offset,
const std::optional<int64_t>& limit,
void(* /* callback */)(void* /* user argument */, const DatabaseClient& /* client */),
void* /* user argument */);
std::shared_ptr<Properties> loadServerProperties(const std::shared_ptr<VirtualServer>&); //Read and write
std::shared_ptr<Properties> loadPlaylistProperties(const std::shared_ptr<VirtualServer>&, PlaylistId); //Read and write
std::shared_ptr<Properties> loadChannelProperties(const std::shared_ptr<VirtualServer>&, ChannelId); //Read and write
std::shared_ptr<Properties> loadClientProperties(const std::shared_ptr<VirtualServer>&, ClientDbId, ClientType);
void deleteClient(const std::shared_ptr<VirtualServer>&,ClientDbId);
bool validClientDatabaseId(const std::shared_ptr<VirtualServer>&, ClientDbId);
std::deque<std::shared_ptr<ClientDatabaseInfo>> queryDatabaseInfo(const std::shared_ptr<VirtualServer>&, const std::deque<ClientDbId>&);
std::deque<std::shared_ptr<ClientDatabaseInfo>> queryDatabaseInfoByUid(const std::shared_ptr<VirtualServer> &, std::deque<std::string>);
bool deleteGroupPermissions(const std::shared_ptr<VirtualServer>&, GroupId);
bool deleteChannelPermissions(const std::shared_ptr<VirtualServer>&, ChannelId);
bool deletePlaylist(const std::shared_ptr<VirtualServer>&, PlaylistId /* playlist id */);
std::deque<std::unique_ptr<FastPropertyEntry>> query_properties(ServerId /* server */, property::PropertyType /* type */, uint64_t /* id */); /* required for server snapshots */
std::shared_ptr<permission::v2::PermissionManager> loadClientPermissionManager(const ServerId&, ClientDbId);
void saveClientPermissions(const std::shared_ptr<VirtualServer>&, ClientDbId , const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
void tick();
private:
void loadStartupPermissionCache();
void loadStartupPropertyCache();
std::shared_ptr<permission::v2::PermissionManager> loadChannelPermissions(const std::shared_ptr<VirtualServer>&, ChannelId);
void saveChannelPermissions(const std::shared_ptr<VirtualServer>&, ChannelId, const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
bool use_startup_cache = false;
threads::Mutex startup_lock;
std::deque<std::shared_ptr<StartupCacheEntry>> startup_entries;
std::shared_ptr<permission::v2::PermissionManager> loadGroupPermissions(const ServerId& server_id, GroupId, uint8_t /* target */);
void saveGroupPermissions(const ServerId&, GroupId, uint8_t /* target */, const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
sql::SqlManager* sql = nullptr;
threads::Mutex permManagerLock;
std::deque<CachedPermissionManager*> cachedPermissionManagers;
threads::Mutex propsLock;
std::deque<CachedProperties*> cachedProperties;
};
}
std::shared_ptr<permission::v2::PermissionManager> loadPlaylistPermissions(const std::shared_ptr<VirtualServer>&, PlaylistId /* playlist id */);
void savePlaylistPermissions(const std::shared_ptr<VirtualServer>&, PlaylistId, const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
std::shared_ptr<PropertyManager> loadServerProperties(const std::shared_ptr<VirtualServer>&); //Read and write
std::shared_ptr<PropertyManager> loadPlaylistProperties(const std::shared_ptr<VirtualServer>&, PlaylistId); //Read and write
std::shared_ptr<PropertyManager> loadChannelProperties(const std::shared_ptr<VirtualServer>&, ChannelId); //Read and write
std::shared_ptr<PropertyManager> loadClientProperties(const std::shared_ptr<VirtualServer>&, ClientDbId, ClientType);
void updateClientIpAddress(const ServerId& /* server id */, ClientDbId /* target database id */, const std::string& /* ip address */);
void deleteGroupArtifacts(ServerId, GroupId);
bool deleteChannelPermissions(const std::shared_ptr<VirtualServer>&, ChannelId);
bool deletePlaylist(const std::shared_ptr<VirtualServer>&, PlaylistId /* playlist id */);
std::deque<std::unique_ptr<FastPropertyEntry>> query_properties(ServerId /* server */, property::PropertyType /* type */, uint64_t /* id */); /* required for server snapshots */
void tick();
private:
void loadStartupPermissionCache();
void loadStartupPropertyCache();
bool use_startup_cache = false;
threads::Mutex startup_lock;
std::deque<std::shared_ptr<StartupCacheEntry>> startup_entries;
sql::SqlManager* sql = nullptr;
threads::Mutex cached_permission_manager_lock;
std::deque<std::unique_ptr<CachedPermissionManager>> cached_permission_managers;
/* Attention: cached_permission_manager_lock should be locked! */
[[nodiscard]] inline std::shared_ptr<permission::v2::PermissionManager> find_cached_permission_manager(ServerId /* server id */, ClientDbId /* client id */);
};
}
+220
View File
@@ -0,0 +1,220 @@
//
// Created by WolverinDEV on 12/05/2020.
//
#include <files/FileServer.h>
#include <files/Config.h>
#include "./client/ConnectedClient.h"
#include "FileServerHandler.h"
using namespace ts::server;
using namespace ts::server::file;
FileServerHandler::FileServerHandler(ts::server::InstanceHandler *instance) : instance_{instance} {}
bool FileServerHandler::initialize(std::string &error) {
if(!file::initialize(error,
serverInstance->properties()[property::SERVERINSTANCE_FILETRANSFER_HOST].value(),
serverInstance->properties()[property::SERVERINSTANCE_FILETRANSFER_PORT].as_or<uint16_t>(30303))) {
return false;
}
#if 1
file::config::ssl_option_supplier = [&]{
return this->instance_->sslManager()->web_ssl_options();
};
#endif
auto server = file::server();
assert(server);
auto& transfer = server->file_transfer();
transfer.callback_transfer_registered = std::bind(&FileServerHandler::callback_transfer_registered, this, std::placeholders::_1);
transfer.callback_transfer_started = std::bind(&FileServerHandler::callback_transfer_started, this, std::placeholders::_1);
transfer.callback_transfer_finished = std::bind(&FileServerHandler::callback_transfer_finished, this, std::placeholders::_1);
transfer.callback_transfer_aborted = std::bind(&FileServerHandler::callback_transfer_aborted, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
transfer.callback_transfer_statistics = std::bind(&FileServerHandler::callback_transfer_statistics, this, std::placeholders::_1, std::placeholders::_2);
return true;
}
void FileServerHandler::finalize() {
file::finalize();
}
void FileServerHandler::callback_transfer_registered(const std::shared_ptr<transfer::Transfer> &transfer) {
auto server = this->instance_->getVoiceServerManager()->findServerById(transfer->server->server_id());
if(!server) return; /* well that's bad */
const auto bytes = transfer->expected_file_size - transfer->file_offset;
if(transfer->direction == transfer::Transfer::DIRECTION_UPLOAD) {
server->properties()[property::VIRTUALSERVER_TOTAL_BYTES_UPLOADED].increment_by<int64_t>(bytes);
server->properties()[property::VIRTUALSERVER_MONTH_BYTES_UPLOADED].increment_by<int64_t>(bytes);
} else {
server->properties()[property::VIRTUALSERVER_TOTAL_BYTES_DOWNLOADED].increment_by<int64_t>(bytes);
server->properties()[property::VIRTUALSERVER_MONTH_BYTES_DOWNLOADED].increment_by<int64_t>(bytes);
}
auto client = server->find_client_by_id(transfer->client_id);
if(client && client->getUid() == transfer->client_unique_id) {
if(transfer->direction == transfer::Transfer::DIRECTION_UPLOAD) {
client->properties()[property::CLIENT_TOTAL_BYTES_UPLOADED].increment_by<int64_t>(bytes);
client->properties()[property::CLIENT_MONTH_BYTES_UPLOADED].increment_by<int64_t>(bytes);
} else {
client->properties()[property::CLIENT_MONTH_BYTES_DOWNLOADED].increment_by<int64_t>(bytes);
client->properties()[property::CLIENT_MONTH_BYTES_DOWNLOADED].increment_by<int64_t>(bytes);
}
}
}
void FileServerHandler::callback_transfer_aborted(const std::shared_ptr<transfer::Transfer> &transfer,
const transfer::TransferStatistics &statistics,
const ts::server::file::transfer::TransferError &error) {
auto server = this->instance_->getVoiceServerManager()->findServerById(transfer->server->server_id());
if(!server) return; /* well that's bad */
if(statistics.file_total_size < statistics.file_current_offset)
return;
const int64_t bytes_left = statistics.file_total_size - statistics.file_current_offset;
if(transfer->direction == transfer::Transfer::DIRECTION_UPLOAD) {
server->properties()[property::VIRTUALSERVER_TOTAL_BYTES_UPLOADED].increment_by<int64_t>(-bytes_left);
server->properties()[property::VIRTUALSERVER_MONTH_BYTES_UPLOADED].increment_by<int64_t>(-bytes_left);
} else {
server->properties()[property::VIRTUALSERVER_TOTAL_BYTES_DOWNLOADED].increment_by<int64_t>(-bytes_left);
server->properties()[property::VIRTUALSERVER_MONTH_BYTES_DOWNLOADED].increment_by<int64_t>(-bytes_left);
}
auto client = server->find_client_by_id(transfer->client_id);
if(client && client->getUid() == transfer->client_unique_id) {
if(transfer->direction == transfer::Transfer::DIRECTION_UPLOAD) {
client->properties()[property::CLIENT_TOTAL_BYTES_UPLOADED].increment_by<int64_t>(-bytes_left);
client->properties()[property::CLIENT_MONTH_BYTES_UPLOADED].increment_by<int64_t>(-bytes_left);
} else {
client->properties()[property::CLIENT_MONTH_BYTES_DOWNLOADED].increment_by<int64_t>(-bytes_left);
client->properties()[property::CLIENT_MONTH_BYTES_DOWNLOADED].increment_by<int64_t>(-bytes_left);
}
ts::command_builder notify{"notifystatusfiletransfer"};
notify.put_unchecked(0, "clientftfid", transfer->client_transfer_id);
notify.put(0, "size", 0);
ts::command_result status{};
using ErrorType = ts::server::file::transfer::TransferError::Type;
switch (error.error_type) {
case ErrorType::TRANSFER_TIMEOUT:
status.reset(ts::command_result{error::file_transfer_connection_timeout});
break;
case ErrorType::DISK_IO_ERROR:
case ErrorType::DISK_TIMEOUT:
case ErrorType::DISK_INITIALIZE_ERROR:
status.reset(ts::command_result{error::file_io_error});
break;
case ErrorType::UNKNOWN:
case ErrorType::NETWORK_IO_ERROR:
status.reset(ts::command_result{error::file_connection_lost});
break;
case ErrorType::UNEXPECTED_CLIENT_DISCONNECT:
case ErrorType::UNEXPECTED_DISK_EOF:
status.reset(ts::command_result{error::file_transfer_interrupted});
case ErrorType::USER_REQUEST:
status.reset(ts::command_result{error::file_transfer_canceled});
break;
}
client->writeCommandResult(notify, status, "status");
client->sendCommand(notify);
}
}
void FileServerHandler::callback_transfer_statistics(const std::shared_ptr<transfer::Transfer> &transfer,
const ts::server::file::transfer::TransferStatistics &statistics) {
auto server = this->instance_->getVoiceServerManager()->findServerById(transfer->server->server_id());
if(!server) return; /* well that's bad */
auto client = server->find_client_by_id(transfer->client_id);
if(!client || client->getUid() != transfer->client_unique_id) {
/* client not online anymore, but we could still log this as server traffic */
if(transfer->direction == transfer::Transfer::DIRECTION_UPLOAD) {
server->getServerStatistics()->logFileTransferIn(statistics.delta_file_bytes_transferred);
} else {
server->getServerStatistics()->logFileTransferOut(statistics.delta_file_bytes_transferred);
}
return;
}
if(transfer->direction == transfer::Transfer::DIRECTION_UPLOAD) {
client->getConnectionStatistics()->logFileTransferIn(statistics.delta_file_bytes_transferred);
} else {
client->getConnectionStatistics()->logFileTransferOut(statistics.delta_file_bytes_transferred);
}
if(client->getType() == ClientType::CLIENT_TEAMSPEAK) {
return; /* TS3 does not know this notify */
}
ts::command_builder notify{"notifyfiletransferprogress"};
notify.put_unchecked(0, "clientftfid", transfer->client_transfer_id);
notify.put_unchecked(0, "file_bytes_transferred", statistics.file_bytes_transferred);
notify.put_unchecked(0, "network_bytes_send", statistics.network_bytes_send);
notify.put_unchecked(0, "network_bytes_received", statistics.network_bytes_received);
notify.put_unchecked(0, "file_start_offset", statistics.file_start_offset);
notify.put_unchecked(0, "file_current_offset", statistics.file_current_offset);
notify.put_unchecked(0, "file_total_size", statistics.file_total_size);
notify.put_unchecked(0, "network_current_speed", statistics.current_speed);
notify.put_unchecked(0, "network_average_speed", statistics.average_speed);
client->sendCommand(notify);
}
void FileServerHandler::callback_transfer_started(const std::shared_ptr<transfer::Transfer> &transfer) {
auto server = this->instance_->getVoiceServerManager()->findServerById(transfer->server->server_id());
if(!server) return; /* well that's bad */
auto client = server->find_client_by_id(transfer->client_id);
if(!client || client->getUid() != transfer->client_unique_id) {
return;
}
ts::command_builder notify{"notifyfiletransferstarted"};
notify.put_unchecked(0, "clientftfid", transfer->client_transfer_id);
client->sendCommand(notify);
}
void FileServerHandler::callback_transfer_finished(const std::shared_ptr<transfer::Transfer> &transfer) {
auto server = this->instance_->getVoiceServerManager()->findServerById(transfer->server->server_id());
if(!server) {
return; /* well that's bad */
}
auto client = server->find_client_by_id(transfer->client_id);
if(!client || client->getUid() != transfer->client_unique_id) {
return;
}
if(client->getType() == ClientType::CLIENT_TEAMSPEAK) {
return;
}
ts::command_builder notify{"notifystatusfiletransfer"};
notify.put_unchecked(0, "clientftfid", transfer->client_transfer_id);
notify.put(0, "size", transfer->expected_file_size); /* not sure where TeamSpeak counts from */
notify.put_unchecked(0, "status", (int) error::file_transfer_complete);
notify.put_unchecked(0, "msg", findError(error::file_transfer_complete).message);
/* TODO: Some stats? */
client->sendCommand(notify);
}
+25
View File
@@ -0,0 +1,25 @@
#pragma once
#include <string>
#include <files/FileServer.h>
#include "./InstanceHandler.h"
namespace ts::server::file {
class FileServerHandler {
public:
explicit FileServerHandler(InstanceHandler*);
bool initialize(std::string& /* error */);
void finalize();
private:
InstanceHandler* instance_;
void callback_transfer_registered(const std::shared_ptr<transfer::Transfer>&);
void callback_transfer_started(const std::shared_ptr<transfer::Transfer>&);
void callback_transfer_finished(const std::shared_ptr<transfer::Transfer>&);
void callback_transfer_aborted(const std::shared_ptr<transfer::Transfer>&, const transfer::TransferStatistics&, const transfer::TransferError&);
void callback_transfer_statistics(const std::shared_ptr<transfer::Transfer>&, const transfer::TransferStatistics&);
};
}
-920
View File
@@ -1,920 +0,0 @@
#include <algorithm>
#include <utility>
#include <log/LogUtils.h>
#include <misc/memtracker.h>
#include "Group.h"
#include "VirtualServer.h"
#include "src/client/ConnectedClient.h"
#include "InstanceHandler.h"
#include "src/server/file/FileServer.h"
using namespace std;
using namespace std::chrono;
using namespace ts;
using namespace ts::server;
using namespace ts::permission;
extern InstanceHandler* serverInstance;
Group::Group(GroupManager* handle, GroupTarget target, GroupType type, GroupId groupId) : _target(target), _type(type) {
memtrack::allocated<Group>(this);
this->handle = handle;
this->_properties = new Properties();
this->_properties->register_property_type<property::GroupProperties>();
this->setPermissionManager(make_shared<permission::v2::PermissionManager>());
this->properties()[property::GROUP_ID] = groupId;
this->properties()[property::GROUP_TYPE] = type;
/*
this->_properties->registerProperty("sgid", groupId, PROP_GROUP_INIT_SERVER);
this->_properties->registerProperty("cgid", groupId, PROP_GROUP_INIT_CHANNEL);
this->_properties->registerProperty("gid", groupId, PROP_PRIVATE_TEMP);
this->_properties->registerProperty("type", (uint8_t) type, PROP_GROUP_INIT);
this->_properties->registerProperty("name", "Undefined group #" + to_string(groupId), PROP_GROUP_INIT | PROP_SNAPSHOT);
this->_properties->registerProperty("sortid", 0, PROP_GROUP_INIT);
this->_properties->registerProperty("savedb", 0, PROP_GROUP_INIT);
this->_properties->registerProperty("namemode", 0, PROP_GROUP_INIT);
this->_properties->registerProperty("iconid", 0, PROP_GROUP_INIT);
this->_properties->registerProperty("default_group", false, PROP_PRIVATE_TEMP);
*/
this->_properties->registerNotifyHandler([&](Property& prop){
if((prop.type().flags & property::FLAG_SAVE) == 0) return;
if(!this->handle) return;
std::string sql;
if(prop.hasDbReference()){
sql = "UPDATE `properties` SET `value` = :value WHERE `serverId` = :sid AND `type` = :type AND `id` = :gid AND `key` = :key";
} else {
prop.setDbReference(true);
sql = "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) VALUES (:sid, :type, :gid, :key, :value)";
}
sql::command(this->handle->sql, sql,
variable{":sid", this->handle->getServerId()}, variable{":type", property::PropertyType::PROP_TYPE_GROUP}, variable{":gid", this->groupId()}, variable{":key", prop.type().name}, variable{":value", prop.value()})
.executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
});
}
void Group::setPermissionManager(const std::shared_ptr<permission::v2::PermissionManager> &manager) {
this->_permissions = manager;
this->apply_properties_from_permissions();
}
void Group::apply_properties_from_permissions() {
{
auto permission = this->_permissions->permission_value_flagged(permission::i_icon_id);
this->properties()[property::GROUP_ICONID] = permission.has_value ? permission.value : 0;
}
{
auto permission = this->_permissions->permission_value_flagged(permission::i_group_show_name_in_tree);
this->properties()[property::GROUP_NAMEMODE] = permission.has_value ? permission.value : 0;
}
{
auto permission = this->_permissions->permission_value_flagged(permission::i_group_sort_id);
this->properties()[property::GROUP_SORTID] = permission.has_value ? permission.value : 0;
}
{
auto permission = this->_permissions->permission_value_flagged(permission::b_group_is_permanent);
this->properties()[property::GROUP_SAVEDB] = permission.has_value ? permission.value : 0;
}
}
Group::~Group() {
delete this->_properties;
memtrack::freed<Group>(this);
}
GroupManager::GroupManager(const shared_ptr<VirtualServer> &server, sql::SqlManager *sql, std::shared_ptr<GroupManager> root) : server(server), sql(sql), root(std::move(root)) { }
GroupManager::~GroupManager() {}
bool GroupManager::loadGroupFormDatabase(GroupId id) {
if(id == 0){
this->groups.clear();
auto res = sql::command(this->sql, "SELECT * FROM `groups` WHERE `serverId` = :sid", variable{":sid", this->getServerId()}).query(&GroupManager::insertGroupFromDb, this);
LOG_SQL_CMD(res);
return true;
} else {
for(const auto &e : this->groups)
if(e->groupId() == id){
this->groups.erase(find(this->groups.begin(), this->groups.end(), e));
break;
}
auto res = sql::command(this->sql, "SELECT * FROM `groups` WHERE `serverId` = :sid AND `groupId` = :gid",
variable{":sid", this->getServerId()}, variable{":gid", id}).query(&GroupManager::insertGroupFromDb, this);
auto fn = LOG_SQL_CMD;
fn(res);
return res;
}
}
std::vector<std::shared_ptr<Group>> GroupManager::availableGroups(bool root) {
std::vector<std::shared_ptr<Group>> response;
for(const auto& group : this->groups)
response.push_back(group);
if(root && this->root){
auto elm = this->root->availableGroups();
for(const auto& e : elm)
response.push_back(e);
}
return response;
}
std::vector<std::shared_ptr<Group>> GroupManager::availableServerGroups(bool root){
std::vector<std::shared_ptr<Group>> response;
for(const auto& group : this->groups)
if(group->target() == GroupTarget::GROUPTARGET_SERVER)
response.push_back(group);
if(root && this->root){
auto elm = this->root->availableServerGroups();
for(const auto& e : elm)
response.push_back(e);
}
return response;
}
std::vector<std::shared_ptr<Group>> GroupManager::availableChannelGroups(bool root) {
std::vector<std::shared_ptr<Group>> response;
for(const auto& group : this->groups)
if(group->target() == GroupTarget::GROUPTARGET_CHANNEL)
response.push_back(group);
if(root && this->root){
auto elm = this->root->availableChannelGroups(true);
for(const auto& e : elm)
response.push_back(e);
}
return response;
}
int GroupManager::insertGroupFromDb(int count, char **values, char **column) {
GroupId groupId = 0;
auto target = (GroupTarget) 0xff;
auto type = (GroupType) 0xff;
std::string targetName;
for(int index = 0; index < count; index++){
if(strcmp(column[index], "target") == 0)
target = (GroupTarget) stoll(values[index]);
else if(strcmp(column[index], "type") == 0)
type = (GroupType) stoll(values[index]);
else if(strcmp(column[index], "groupId") == 0)
groupId = (GroupType) stoll(values[index]);
else if(strcmp(column[index], "displayName") == 0)
targetName = values[index];
else if(strcmp(column[index], "serverId") == 0);
else cerr << "Invalid group table row " << column[index] << endl;
}
if((size_t) groupId == 0 || (size_t) target == 0xff || (size_t) type == 0xff || targetName.empty()) {
logCritical(this->getServerId(), "Found invalid group ad database! (GroupId " + to_string(groupId) + ", Target " + to_string(target) + ", Type " + to_string(type) + ", Name '" + targetName + "')");
return 0;
}
/*
assert(groupId != 0);
assert(target != 0xff);
assert(type != 0xff);
assert(!targetName.empty());
*/
shared_ptr<Group> group = std::make_shared<Group>(this, target, type, groupId);
/*
auto res = sql::command(this->sql, "SELECT `key`, `value` FROM `properties` WHERE `serverId` = :sid AND `type` = :type AND `id` = :gid",
variable{":sid", this->getServerId()}, variable{":type", property::PROP_TYPE_GROUP}, variable{":gid", group->groupId()}).query([this](Group* g, int length, char** values, char** columns){
string key, value;
for(int index = 0; index < length; index++)
if(strcmp(columns[index], "key") == 0)
key = values[index];
else if(strcmp(columns[index], "value") == 0)
value = values[index];
auto info = property::info<property::GroupProperties>(key);
if(info == property::GROUP_UNDEFINED) {
logError(this->getServerId(), "Invalid property for group: " + key);
return 0;
}
auto prop = g->properties()[info.property];
prop.setDbReference(true);
prop.value(value, false);
return 0;
}, group.get());
auto print = LOG_SQL_CMD;
print(res);
*/
//FIXME load group properties view database helper (or drop it full because no saved properties)
group->properties()[property::GROUP_NAME] = targetName;
group->setPermissionManager(serverInstance->databaseHelper()->loadGroupPermissions(this->server.lock(), group->groupId()));
debugMessage(this->getServerId(), "Push back group -> " + to_string(group->groupId()) + " - " + group->name());
this->groups.push_back(group);
auto iconId = (IconId) group->icon_id();
if(iconId != 0 && serverInstance && serverInstance->getFileServer() && !serverInstance->getFileServer()->iconExists(this->server.lock(), iconId)) {
logMessage(this->getServerId(), "[FILE] Missing group icon (" + to_string(iconId) + ").");
if(config::server::delete_missing_icon_permissions) group->permissions()->set_permission(permission::i_icon_id, {0, 0}, permission::v2::delete_value, permission::v2::do_nothing);
}
return 0;
}
void GroupManager::handleChannelDeleted(const ChannelId& channel_id) {
unique_lock cache_lock(this->cacheLock);
auto cached_clients = std::vector<shared_ptr<CachedClient>>{this->cachedClients.begin(), this->cachedClients.end()};
cache_lock.unlock();
for(auto& entry : cached_clients) {
lock_guard entry_lock(entry->lock);
entry->channel_groups.erase(channel_id);
}
}
bool GroupManager::isLocalGroup(std::shared_ptr<Group> gr) {
return std::find(this->groups.begin(), this->groups.end(), gr) != this->groups.end();
}
std::shared_ptr<Group> GroupManager::defaultGroup(GroupTarget type, bool enforce_property) {
threads::MutexLock lock(this->cacheLock);
if(this->groups.empty()) return nullptr;
auto server = this->server.lock();
auto id =
server ?
server->properties()[type == GroupTarget::GROUPTARGET_SERVER ? property::VIRTUALSERVER_DEFAULT_SERVER_GROUP : property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP].as_save<GroupId>() :
serverInstance->properties()[property::SERVERINSTANCE_GUEST_SERVERQUERY_GROUP].as_save<GroupId>();
auto group = this->findGroupLocal(id);
if(group || enforce_property) return group;
for(auto elm : this->groups)
if(elm->target() == type)
return elm;
return nullptr; //Worst case!
}
std::shared_ptr<Group> GroupManager::findGroup(GroupId groupId) {
auto result = this->findGroupLocal(groupId);
if(!result && this->root) result = this->root->findGroup(groupId);
return result;
}
std::shared_ptr<Group> GroupManager::findGroupLocal(GroupId groupId) {
for(const auto& elm : this->groups)
if(elm->groupId() == groupId) return elm;
return nullptr;
}
std::vector<std::shared_ptr<Group>> GroupManager::findGroup(GroupTarget target, std::string name) {
vector<shared_ptr<Group>> res;
for(const auto &elm : this->groups)
if(elm->name() == name && elm->target() == target) res.push_back(elm);
if(this->root) {
auto r = root->findGroup(target, name);
for(const auto &e : r) res.push_back(e);
}
return res;
}
ServerId GroupManager::getServerId() {
auto l = this->server.lock();
return l ? l->getServerId() : 0;
}
std::shared_ptr<Group> GroupManager::createGroup(GroupTarget target, GroupType type, std::string name) {
if(type != GROUP_TYPE_NORMAL && this->root) return root->createGroup(target, type, name);
auto rawId = generateGroupId(this->sql);
if(rawId <= 0) {
logError(this->getServerId(), "Could not create a new group! ({})", "Could not generate group id");
return nullptr;
}
auto groupId = (GroupId) rawId;
auto res = sql::command(this->sql, "INSERT INTO `groups` (`serverId`, `groupId`, `target`, `type`, `displayName`) VALUES (:sid, :gid, :target, :type, :name)",
variable{":sid", this->getServerId()}, variable{":gid", groupId}, variable{":target", target}, variable{":type", type}, variable{":name", name}).execute();
auto print = LOG_SQL_CMD;
print(res);
if(!res) return nullptr;
std::shared_ptr<Group> group = std::make_shared<Group>(this, target, type, groupId);
group->properties()[property::GROUP_NAME] = name;
group->setPermissionManager(serverInstance->databaseHelper()->loadGroupPermissions(this->server.lock(), group->groupId()));
this->groups.push_back(group);
return group;
}
GroupId GroupManager::copyGroup(std::shared_ptr<Group> group, GroupType type, std::string name, ServerId targetServerId) {
auto group_server = group->handle->getServerId();
auto groupId = generateGroupId(this->sql);
auto print = LOG_SQL_CMD;
auto res = sql::command(this->sql, "INSERT INTO `permissions` (`serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate`) SELECT :tsid AS `serverId`, `type`, :target AS `id`, 0 AS `channelId`, `permId`, `value`,`grant`,`flag_skip`, `flag_negate` FROM `permissions` WHERE `serverId` = :ssid AND `type` = :type AND `id` = :source",
variable{":target", groupId}, variable{":ssid", group_server}, variable{":tsid", targetServerId}, variable{":source", group->groupId()}, variable{":type", SQL_PERM_GROUP}).execute();
print(res);
//Properties not used currently
res = sql::command(this->sql, "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) SELECT :tsid AS `serverId`, `type`, :target AS `id`, `key`, `value` FROM `properties` WHERE `serverId` = :ssid AND `type` = :type AND `id` = :source",
variable{":target", groupId}, variable{":ssid", group_server}, variable{":tsid", targetServerId}, variable{":source", group->groupId()}, variable{":type", property::PropertyType::PROP_TYPE_GROUP}).execute();
print(res);
res = sql::command(this->sql, "INSERT INTO `groups` (`serverId`, `groupId`, `target`, `type`, `displayName`) VALUES (:sid, :gid, :target, :type, :name)",
variable{":sid", targetServerId}, variable{":gid", groupId}, variable{":target", group->target()}, variable{":type", type}, variable{":name", name}).execute();
print(res);
if(targetServerId == (this->getServerId()))
this->loadGroupFormDatabase(groupId);
else if(this->root)
this->root->loadGroupFormDatabase(groupId);
return groupId;
}
bool GroupManager::copyGroupPermissions(const shared_ptr<Group> &source, const shared_ptr<Group> &target) {
auto targetServer = target->handle->getServerId();
auto sourceServer = source->handle->getServerId();
auto res = sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :sid AND `type` = :type AND `id` = :id", variable{":sid", targetServer}, variable{":type", SQL_PERM_GROUP}, variable{":id", target->groupId()}).execute();
LOG_SQL_CMD(res);
res = sql::command(this->sql, "INSERT INTO `permissions` (`serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`) SELECT :tsid AS `serverId`, `type`, :target AS `id`, 0 AS `channelId`, `permId`, `value`,`grant` FROM `permissions` WHERE `serverId` = :ssid AND `type` = :type AND `id` = :source",
variable{":ssid", sourceServer}, variable{":tsid", targetServer}, variable{":type", SQL_PERM_GROUP}, variable{":source", source->groupId()}, variable{":target", target->groupId()}).execute();
target->setPermissionManager(serverInstance->databaseHelper()->loadGroupPermissions(target->handle->server.lock(), target->groupId()));
LOG_SQL_CMD(res);
return true;
}
bool GroupManager::reloadGroupPermissions(std::shared_ptr<Group> group) {
if(!isLocalGroup(group)){
if(this->root) return this->root->reloadGroupPermissions(group);
return false;
}
group->setPermissionManager(serverInstance->databaseHelper()->loadGroupPermissions(this->server.lock(), group->groupId()));
return true;
}
bool GroupManager::renameGroup(std::shared_ptr<Group> group, std::string name) {
if(!isLocalGroup(group)){
if(this->root) return this->root->renameGroup(group, name);
return false;
}
// CREATE_TABLE("groups", "`serverId` INT NOT NULL, `groupId` INTEGER, `target` INT, `type` INT, `displayName` TEXT");
group->properties()[property::GROUP_NAME] = name;
sql::command(this->sql, "UPDATE `groups` SET `displayName` = :name WHERE `serverId` = :sid AND `groupId` = :gid AND `target` = :target",
variable{":name", name}, variable{":gid", group->groupId()}, variable{":target", group->target()}, variable{":sid", this->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed"});
return true;
}
bool GroupManager::deleteAllGroups() {
LOG_SQL_CMD(sql::command(this->sql, "DELETE FROM `groups` WHERE `serverId` = :sid", variable{":sid", this->getServerId()}).execute());
LOG_SQL_CMD(sql::command(this->sql, "DELETE FROM `assignedGroups` WHERE `serverId` = :sid", variable{":sid", this->getServerId()}).execute());
LOG_SQL_CMD(sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :sid AND `type` = :type", variable{":sid", this->getServerId()}, variable{":type", SQL_PERM_GROUP}).execute());
{
lock_guard cache_lock(this->cacheLock);
for(const auto& entry : this->cachedClients) {
lock_guard entry_lock(entry->lock);
entry->server_groups.clear();
entry->channel_groups.clear();
}
}
this->groups.clear();
return true;
}
bool GroupManager::deleteGroup(std::shared_ptr<Group> group) {
if(!isLocalGroup(group)){
if(this->root) return this->root->deleteGroup(group);
return false;
}
this->groups.erase(std::find(this->groups.begin(), this->groups.end(), group));
/* erase the group out of our cache */
{
lock_guard cache_lock(this->cacheLock);
for(auto& entry : this->cachedClients) {
lock_guard entry_lock(entry->lock);
entry->server_groups.erase(std::remove_if(entry->server_groups.begin(), entry->server_groups.end(), [&](const std::shared_ptr<GroupAssignment>& group_assignment) {
return group_assignment->group == group;
}), entry->server_groups.end());
for(auto it = entry->channel_groups.begin(); it != entry->channel_groups.end();) {
if(it->second->group == group)
it = entry->channel_groups.erase(it);
else
it++;
}
}
}
bool flag_sql = false;
auto res = sql::command(this->sql, "DELETE FROM `groups` WHERE `serverId` = :sid AND `groupId` = :gid", variable{":sid", this->getServerId()}, variable{":gid", group->groupId()}).execute();
LOG_SQL_CMD(res);
flag_sql |= !res;
res = sql::command(this->sql, "DELETE FROM `assignedGroups` WHERE `serverId` = :sid AND `groupId` = :gid", variable{":sid", this->getServerId()}, variable{":gid", group->groupId()}).execute();
LOG_SQL_CMD(res);
flag_sql |= !res;
flag_sql &= serverInstance->databaseHelper()->deleteGroupPermissions(this->server.lock(), group->groupId());
if(flag_sql)
logError(this->getServerId(), "Could not delete group {} ({}) from database. May leader to invalid data", group->name(), group->groupId());
return true;
}
int64_t GroupManager::generateGroupId(sql::SqlManager* sql) {
int64_t hightestGroupId = 0;
sql::command(sql, "SELECT `groupId` FROM `groups` ORDER BY `groupId` DESC LIMIT 1").query([](int64_t* ptr, int, char** values, char**){
*ptr = stoul(values[0]);
return 0;
}, &hightestGroupId);
return hightestGroupId + 1;
}
std::deque<property::ClientProperties> GroupManager::update_server_group_property(const shared_ptr<server::ConnectedClient> &client, bool channel_lock, const std::shared_ptr<BasicChannel>& channel) {
std::deque<property::ClientProperties> changed;
//Server groups
{
auto groups = this->getServerGroups(client->getClientDatabaseId(), client->getType());
string group_string;
for(const auto& group : groups)
if(group_string.empty()) group_string += to_string(group->group->groupId());
else group_string += "," + to_string(group->group->groupId());
if(client->properties()[property::CLIENT_SERVERGROUPS] != group_string) {
client->properties()[property::CLIENT_SERVERGROUPS] = group_string;
changed.push_back(property::CLIENT_SERVERGROUPS);
unique_lock chan_lock(client->channel_lock, defer_lock);
if(channel_lock)
chan_lock.lock();
client->cached_server_groups.clear();
client->cached_server_groups.reserve(groups.size());
for(const auto& group : groups)
client->cached_server_groups.push_back(group->group->groupId());
}
}
//Channel groups
if(channel){
shared_ptr<GroupAssignment> group = this->getChannelGroup(client->getClientDatabaseId(), channel, true);
if(client->properties()[property::CLIENT_CHANNEL_GROUP_INHERITED_CHANNEL_ID] != group->channelId) {
client->properties()[property::CLIENT_CHANNEL_GROUP_INHERITED_CHANNEL_ID] = group->channelId;
changed.push_back(property::CLIENT_CHANNEL_GROUP_INHERITED_CHANNEL_ID);
}
if(client->properties()[property::CLIENT_CHANNEL_GROUP_ID] != group->group->groupId()) {
client->properties()[property::CLIENT_CHANNEL_GROUP_ID] = group->group->groupId();
changed.push_back(property::CLIENT_CHANNEL_GROUP_ID);
unique_lock chan_lock(client->channel_lock, defer_lock);
if(channel_lock)
chan_lock.lock();
client->cached_channel_group = group->group->groupId();
}
}
if(!changed.empty())
client->join_state_id++; /* groups have changed :) */
return changed;
}
void GroupManager::cleanupAssignments(ClientDbId client) {
if(this->root)
this->root->cleanupAssignments(client);
for(const auto& assignment : this->getAssignedServerGroups(client)) {
if(!assignment->group->is_permanent())
this->removeServerGroup(client, assignment->group);
}
}
void GroupManager::enableCache(const ClientDbId& client_database_id) {
if(this->root)
this->root->enableCache(client_database_id);
unique_lock cache_lock(this->cacheLock);
/* test if we're already having the client */
for(auto& entry : this->cachedClients)
if(entry->client_database_id == client_database_id) {
entry->use_count++;
return; /* client already cached, no need to cache client */
}
auto entry = std::make_shared<CachedClient>();
entry->client_database_id = client_database_id;
entry->use_count++;
this->cachedClients.push_back(entry);
lock_guard client_cache_lock(entry->lock); /* lock the client because we're currently loading the cache. */
cache_lock.unlock();
auto res = sql::command(this->sql, "SELECT `groupId`, `channelId`, `until` FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :cldbid", variable{":sid", this->getServerId()}, variable{":cldbid", client_database_id})
.query([&](int length, std::string* value, std::string* column) {
shared_ptr<Group> group = nullptr;
time_point<system_clock> until;
ChannelId channelId = 0;
for(int index = 0; index < length; index++){
try {
if(column[index] == "groupId"){
group = this->findGroup(stoll(value[index]));
} else if(column[index] == "until"){
until = time_point<system_clock>() + milliseconds(stoll(value[index]));
} else if(column[index] == "channelId"){
channelId = stoll(value[index]);
} else {
logError(this->getServerId(), "Unknown column in group assignment query: {}", column[index]);
continue;
}
} catch(std::exception& ex) {
logError(this->getServerId(), "Failed to load group assignment from database for client {}. Column {} contains an invalid value: {}", client_database_id, column[index], value[index]);
return 0;
}
}
if(!group) {
return 0;
}
auto assignment = std::make_shared<GroupAssignment>();
assignment->group = group;
assignment->until = until;
assignment->parent = &*entry;
assignment->channelId = channelId;
assignment->server = this->getServerId();
if(channelId == 0)
entry->server_groups.push_back(assignment);
else
entry->channel_groups[channelId] = assignment;
return 0;
});
}
//FIXME: This method till get far more often then it should be. We should add a flag if the group cache is loaded for each std::shared_ptr<ConnectedClient> instance
void GroupManager::disableCache(const ClientDbId& client_database_id) {
if(this->root) {
this->root->disableCache(client_database_id);
}
lock_guard cache_lock(this->cacheLock);
this->cachedClients.erase(std::remove_if(this->cachedClients.begin(), this->cachedClients.end(), [&](const std::shared_ptr<CachedClient>& client) {
if(client->client_database_id != client_database_id)
return false;
lock_guard client_lock{client->lock};
return (--client->use_count) == 0;
}), this->cachedClients.end());
}
void GroupManager::clearCache() {
if(this->root) this->root->clearCache();
lock_guard lock(this->cacheLock);
this->cachedClients.clear();
}
bool GroupManager::isClientCached(const ClientDbId& client_database_id) {
return this->resolve_cached_client(client_database_id) == nullptr;
}
typedef std::vector<std::shared_ptr<GroupMember>> ResList;
std::vector<std::shared_ptr<GroupMember>> GroupManager::listGroupMembers(std::shared_ptr<Group> group, bool names) { //TODO juse inner join only on names = true
if(!isLocalGroup(group)){
if(this->root) return this->root->listGroupMembers(group, names);
return {};
}
ResList result;
sql::command(this->sql,
"SELECT assignedGroups.cldbid, assignedGroups.channelId, assignedGroups.until, clients.clientUid, clients.lastName FROM assignedGroups INNER JOIN clients ON (clients.cldbid = assignedGroups.cldbid AND clients.serverId = assignedGroups.serverId) WHERE assignedGroups.`serverId` = :sid AND `groupId` = :gid;",
variable{":sid", this->getServerId()}, variable{":gid", group->groupId()})
.query([&](ResList* list, int columnCount, char** values, char** columnName){
std::shared_ptr<GroupMember> member = std::make_shared<GroupMember>();
member->displayName = "undefined";
member->uid = "undefined";
for(int index = 0; index < columnCount; index++){
if(values[index] == nullptr) {
logError(this->getServerId(), string() + "Invalid value at " + columnName[index]);
continue;
}
if(strcmp(columnName[index], "cldbid") == 0)
member->cldbId = stoll(values[index]);
else if(strcmp(columnName[index], "until") == 0)
member->until = time_point<system_clock>() + milliseconds(stoll(values[index]));
else if(strcmp(columnName[index], "clientUid") == 0)
member->uid = values[index];
else if(strcmp(columnName[index], "lastName") == 0)
member->displayName = values[index];
else if(strcmp(columnName[index], "channelId") == 0)
member->channelId = stoll(values[index]);
else cerr << "Invalid column name " << columnName[index] << endl;
}
list->push_back(member);
return 0;
}, &result);
return result;
}
vector<shared_ptr<GroupAssignment>> GroupManager::listGroupAssignments(ClientDbId cldbId) {
vector<std::shared_ptr<GroupAssignment>> result;
sql::result res;
auto cached = resolve_cached_client(cldbId);
if(cached) {
{
lock_guard lock{cached->lock};
for(const auto &serverGroup : cached->server_groups)
result.push_back(serverGroup);
for(auto& channelGroup : cached->channel_groups)
result.push_back(channelGroup.second);
}
if(this->root){
auto append = this->root->listGroupAssignments(cldbId);
for(const auto &elm : append)
result.push_back(elm);
}
return result;
}
res = sql::command(this->sql, "SELECT `groupId`, `until`, `channelId` FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :cldbid", variable{":sid", this->getServerId()}, variable{":cldbid", cldbId})
.query([&](int length, char** value, char** column){
shared_ptr<Group> group = nullptr;
time_point<system_clock> until;
uint64_t channelId = 0;
for(int index = 0; index < length; index++){
if(value[index] == nullptr) {
logError(this->getServerId(), string() + "Invalid value at " + column[index]);
continue;
}
if(strcmp(column[index], "groupId") == 0){
group = this->findGroup(stoll(value[index]));
} else if(strcmp(column[index], "until") == 0){
until = time_point<system_clock>() + milliseconds(stoll(value[index]));
} else if(strcmp(column[index], "channelId") == 0){
channelId = stoll(value[index]);
} else cerr << "Invalid column " << column[index] << endl;
}
if(!group)
return 0;
shared_ptr<GroupAssignment> assignment = std::make_shared<GroupAssignment>();
assignment->parent = nullptr;
assignment->group = group;
assignment->until = until;
assignment->channelId = channelId;
assignment->server = this->getServerId();
result.push_back(assignment);
return 0;
});
(LOG_SQL_CMD)(res);
if(this->root){
auto append = this->root->listGroupAssignments(cldbId);
for(const auto &elm : append)
result.push_back(elm);
}
return result;
}
std::shared_ptr<CachedClient> GroupManager::resolve_cached_client(ClientDbId client_database_id) {
{
lock_guard lock(this->cacheLock);
for(auto& entry : this->cachedClients)
if(entry->client_database_id == client_database_id)
return entry;
}
return nullptr;
}
std::vector<std::shared_ptr<GroupAssignment>> GroupManager::getAssignedServerGroups(ClientDbId cldbid) {
auto cached = this->resolve_cached_client(cldbid);
sql::result res;
std::vector<std::shared_ptr<GroupAssignment>> result;
if(this->root) {
auto client_groups = this->root->getAssignedServerGroups(cldbid);
result.insert(result.begin(), client_groups.begin(), client_groups.end());
}
if(cached) {
lock_guard cache_lock{cached->lock};
result.insert(result.end(), cached->server_groups.begin(), cached->server_groups.end());
return result;
}
debugMessage(this->getServerId(), "Query client groups for client {} on server {}.", cldbid, this->getServerId());
res = sql::command(this->sql, "SELECT `groupId`, `until` FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :cldbid AND `channelId` = 0", variable{":sid", this->getServerId()}, variable{":cldbid", cldbid}).query([&](int length, char** value, char** column){
shared_ptr<Group> group = nullptr;
time_point<system_clock> until;
for(int index = 0; index < length; index++) {
if(value[index] == nullptr) {
logError(this->getServerId(), string() + "Invalid value at " + column[index]);
continue;
}
if(strcmp(column[index], "groupId") == 0 && value[index] != nullptr){
group = this->findGroup(stoll(value[index]));
} else if(strcmp(column[index], "until") == 0){
until = time_point<system_clock>() + milliseconds(stoll(value[index] == nullptr ? "0" : value[index]));
} else cerr << "Invalid column " << column[index] << endl;
}
if(!group)
return 0;
shared_ptr<GroupAssignment> assignment = std::make_shared<GroupAssignment>();
assignment->parent = nullptr;
assignment->group = group;
assignment->until = until;
assignment->server = this->getServerId();
result.push_back(assignment);
return 0;
});
LOG_SQL_CMD(res);
return result;
}
std::vector<std::shared_ptr<GroupAssignment>> GroupManager::getServerGroups(ClientDbId cldbid, server::ClientType type) {
auto result = this->getAssignedServerGroups(cldbid);
if(result.empty()) return this->defaultServerGroupGroupAssignments(cldbid, type);
return result;
}
std::vector<std::shared_ptr<GroupAssignment>> GroupManager::defaultServerGroupGroupAssignments(ClientDbId client, ClientType type) {
std::vector<std::shared_ptr<GroupAssignment>> result;
if(type == ClientType::CLIENT_QUERY && this->root) {
auto root = this->root->defaultServerGroupGroupAssignments(client, type);
result.insert(result.begin(), root.begin(), root.end());
} else if(type == ClientType::CLIENT_MUSIC) {
threads::MutexLock lock(this->cacheLock);
auto server = this->server.lock();
auto id =
server ?
server->properties()[property::VIRTUALSERVER_DEFAULT_MUSIC_GROUP].as_save<GroupId>() :
serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_MUSICDEFAULT_GROUP].as_save<GroupId>();
auto group = this->findGroupLocal(id);
if(group) {
result.push_back(std::make_shared<GroupAssignment>(nullptr, this->getServerId(), 0, group, time_point<system_clock>()));
return result;
}
}
result.push_back(std::make_shared<GroupAssignment>(nullptr, this->getServerId(), 0, this->defaultGroup(GroupTarget::GROUPTARGET_SERVER), time_point<system_clock>()));
return result;
}
std::shared_ptr<GroupAssignment> GroupManager::getChannelGroupExact(ClientDbId cldbId, const std::shared_ptr<BasicChannel>& channel, bool assign_default) {
auto cached = resolve_cached_client(cldbId);
if(cached) {
lock_guard cache_lock(cached->lock);
if(cached->channel_groups.count(channel->channelId()) > 0) {
return cached->channel_groups[channel->channelId()];
} else
return assign_default ? this->defaultChannelGroupAssignment(cldbId, channel) : nullptr;
}
std::shared_ptr<GroupAssignment> result;
auto res = sql::command(this->sql, "SELECT `groupId`, `until` FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :cldbid AND `channelId` = :chid", variable{":sid", this->getServerId()}, variable{":cldbid", cldbId}, variable{":chid", channel->channelId()}).query([&](int length, char** value, char** column){
shared_ptr<Group> group = nullptr;
time_point<system_clock> until;
for(int index = 0; index < length; index++){
if(value[index] == nullptr) {
logError(this->getServerId(), string() + "Invalid value at " + column[index]);
continue;
}
if(strcmp(column[index], "groupId") == 0){
group = this->findGroup(stoll(value[index]));
} else if(strcmp(column[index], "until") == 0){
until = time_point<system_clock>() + milliseconds(stoll(value[index]));
} else cerr << "Invalid column " << column[index] << endl;
}
if(!group)
return 0;
shared_ptr<GroupAssignment> assignment = std::make_shared<GroupAssignment>();
assignment->parent = nullptr;
assignment->group = group;
assignment->until = until;
assignment->server = this->getServerId();
assignment->channelId = channel->channelId();
result = std::move(assignment);
return 0;
});
(LOG_SQL_CMD)(res);
return !result && assign_default ? this->defaultChannelGroupAssignment(cldbId, channel) : result;
}
std::shared_ptr<GroupAssignment> GroupManager::getChannelGroup(ClientDbId cldbId, const shared_ptr<BasicChannel> &channel, bool assign_default) {
shared_ptr<GroupAssignment> group;
std::shared_ptr<BasicChannel> inheritance_channel = channel;
while(inheritance_channel && !group) {
group = this->getChannelGroupExact(cldbId, inheritance_channel, false);
if(!group) {
auto inheritance = inheritance_channel->permissions()->permission_value_flagged(permission::b_channel_group_inheritance_end);
if(inheritance.has_value && inheritance.value == 1)
break;
inheritance_channel = inheritance_channel->parent();
}
}
return !group && assign_default ? this->defaultChannelGroupAssignment(cldbId, channel) : group;
}
std::shared_ptr<GroupAssignment> GroupManager::defaultChannelGroupAssignment(ClientDbId cldbId, const std::shared_ptr<BasicChannel> &channel) {
return std::make_shared<GroupAssignment>(nullptr, this->getServerId(), channel->channelId(), this->defaultGroup(GroupTarget::GROUPTARGET_CHANNEL), time_point<system_clock>());
}
void GroupManager::addServerGroup(ClientDbId cldbId, std::shared_ptr<Group> group, time_point<system_clock> until) {
/*
if(!this->isLocalGroup(group)) {
if(this->root) this->root->addServerGroup(cldbId, group, until);
return;
}
if(hasServerGroup(cldbId, group)) return;
*/
auto cached = resolve_cached_client(cldbId);
if(cached) {
lock_guard cache_lock(cached->lock);
cached->server_groups.push_back(std::make_shared<GroupAssignment>(cached.get(), this->getServerId(), 0, group, until));
}
sql::command(this->sql, "INSERT INTO `assignedGroups` (`serverId`, `cldbid`, `groupId`, `channelId`, `until`) VALUES (:sid, :cldbid, :gid, :chid, :until)",
variable{":sid", this->getServerId()},
variable{":cldbid", cldbId},
variable{":gid", group->groupId()},
variable{":chid", 0},
variable{":until", time_point_cast<milliseconds>(until).time_since_epoch().count()})
.executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
}
void GroupManager::removeServerGroup(ClientDbId cldbId, std::shared_ptr<Group> group) {
if(!this->hasServerGroupAssigned(cldbId, group)) return;
auto cached = resolve_cached_client(cldbId);
if(cached) {
lock_guard cache_lock(cached->lock);
cached->server_groups.erase(std::remove_if(cached->server_groups.begin(), cached->server_groups.end(), [&](const std::shared_ptr<GroupAssignment>& group_assignment) {
return group_assignment->group == group;
}), cached->server_groups.end());
}
sql::command(this->sql, "DELETE FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :cldbid AND `groupId` = :gid AND `channelId` = :chid",
variable{":sid", this->getServerId()},
variable{":cldbid", cldbId},
variable{":gid", group->groupId()},
variable{":chid", 0})
.executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
}
void GroupManager::setChannelGroup(ClientDbId cldbId, std::shared_ptr<Group> group, std::shared_ptr<BasicChannel> channel, time_point<system_clock> until) {
auto old_group = getChannelGroupExact(cldbId, channel, false);
if(old_group) {
if(old_group->group == group) return;
} else if(!group) return;
auto default_group = !group || group == this->defaultGroup(GroupTarget::GROUPTARGET_CHANNEL);
auto cached = resolve_cached_client(cldbId);
if(cached) {
lock_guard cache_lock(cached->lock);
if(default_group)
cached->channel_groups.erase(channel->channelId());
else
cached->channel_groups[channel->channelId()] = std::make_shared<GroupAssignment>(cached.get(), this->getServerId(), channel->channelId(), group, until);
}
sql::command(this->sql, "DELETE FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :cldbid AND `channelId` = :chid",
variable{":sid", this->getServerId()},
variable{":cldbid", cldbId},
variable{":chid", channel->channelId()})
.executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
if(!default_group) {
sql::command(this->sql, "INSERT INTO `assignedGroups` (`serverId`, `cldbid`, `groupId`, `channelId`, `until`) VALUES (:sid, :cldbid, :gid, :chid, :until)",
variable{":sid", this->getServerId()},
variable{":cldbid", cldbId},
variable{":gid", group->groupId()},
variable{":chid", channel->channelId()},
variable{":until", time_point_cast<milliseconds>(until).time_since_epoch().count()})
.executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
}
}
+1 -217
View File
@@ -12,225 +12,9 @@
#include <sql/SqlQuery.h>
namespace ts {
namespace server {
class VirtualServer;
class ConnectedClient;
}
struct CachedClient;
class GroupManager;
class Group;
enum GroupType {
GROUP_TYPE_TEMPLATE,
GROUP_TYPE_NORMAL,
GROUP_TYPE_QUERY //Only aplies for servergroups
};
enum GroupTarget {
GROUPTARGET_SERVER,
GROUPTARGET_CHANNEL
};
enum GroupNameMode {
GROUP_NAMEMODE_HIDDEN,
GROUP_NAMEMODE_BEFORE,
GROUP_NAMEMODE_BEHIND
};
}
DEFINE_TRANSFORMS(ts::GroupType, uint8_t);
DEFINE_TRANSFORMS(ts::GroupTarget, uint8_t);
DEFINE_TRANSFORMS(ts::GroupNameMode, uint8_t);
namespace ts {
struct GroupMember {
std::string uid;
uint64_t cldbId;
uint64_t channelId;
std::string displayName;
std::chrono::time_point<std::chrono::system_clock> until;
};
struct GroupAssignment {
GroupAssignment() {}
GroupAssignment(CachedClient *parent, ServerId server, uint64_t channelId, const std::shared_ptr<Group> &group, const std::chrono::time_point<std::chrono::system_clock> &until) : parent(parent), server(server), channelId(channelId), group(group), until(until) {}
CachedClient* parent = nullptr; //Could be null!
ServerId server = 0;
uint64_t channelId = 0;
std::shared_ptr<Group> group;
std::chrono::time_point<std::chrono::system_clock> until;
inline bool isPermanent(){ return std::chrono::time_point_cast<std::chrono::milliseconds>(until).time_since_epoch().count() == 0; }
};
struct CachedClient {
ClientDbId client_database_id;
std::vector<std::shared_ptr<GroupAssignment>> server_groups;
std::map<ts::ChannelId, std::shared_ptr<GroupAssignment>> channel_groups;
size_t use_count = 0;
std::mutex lock; /* never lock this lock before the general client cache! */
};
class Group {
friend class GroupManager;
public:
Group(GroupManager* handle, GroupTarget target, GroupType type, GroupId groupId);
~Group();
std::shared_ptr<permission::v2::PermissionManager> permissions(){ return this->_permissions; }
Properties& properties(){ return *this->_properties; }
GroupNameMode nameMode(){ return (GroupNameMode) (uint8_t) properties()[property::GROUP_NAMEMODE]; }
std::string name(){ return properties()[property::GROUP_NAME]; }
GroupId groupId(){ return properties()[property::GROUP_ID]; }
GroupTarget target() { return _target; }
GroupType type() { return _type; }
void apply_properties_from_permissions();
inline permission::PermissionValue updateType() {
auto permission_manager = this->permissions(); /* copy the manager */
assert(permission_manager);
const auto data = permission_manager->permission_value_flagged(permission::i_group_auto_update_type);
return data.has_value ? data.value : 0;
}
inline bool permission_granted(const permission::PermissionType& permission, const permission::v2::PermissionFlaggedValue& granted_value, bool require_granted_value) {
auto permission_manager = this->permissions(); /* copy the manager */
assert(permission_manager);
const auto data = permission_manager->permission_value_flagged(permission);
if(!data.has_value) {
return !require_granted_value || granted_value.has_value;
}
if(!granted_value.has_value) {
return false;
}
if(data.value == -1) {
return granted_value.value == -1;
}
return granted_value.value >= data.value;
}
inline bool is_permanent() {
auto permission_manager = this->permissions(); /* copy the manager */
assert(permission_manager);
const auto data = permission_manager->permission_value_flagged(permission::b_group_is_permanent);
return data.has_value ? data.value == 1 : false;
}
inline IconId icon_id() {
auto permission_manager = this->permissions(); /* copy the manager */
assert(permission_manager);
const auto data = permission_manager->permission_value_flagged(permission::i_icon_id);
return data.has_value ? data.value : 0;
}
private:
void setPermissionManager(const std::shared_ptr<permission::v2::PermissionManager>& manager);
GroupManager* handle;
std::shared_ptr<permission::v2::PermissionManager> _permissions;
Properties* _properties;
GroupTarget _target;
GroupType _type;
};
class GroupManager {
friend class ServerChannelTree;
friend class Group;
friend class server::VirtualServer;
public:
static int64_t generateGroupId(sql::SqlManager* sql);
GroupManager(const std::shared_ptr<server::VirtualServer> &, sql::SqlManager *, std::shared_ptr<GroupManager> root = nullptr);
~GroupManager();
bool loadGroupFormDatabase(GroupId id = 0);
std::vector<std::shared_ptr<Group>> availableGroups(bool root = true);
std::vector<std::shared_ptr<Group>> availableServerGroups(bool root = true);
std::vector<std::shared_ptr<Group>> availableChannelGroups(bool root = true);
std::vector<std::shared_ptr<GroupAssignment>> getAssignedServerGroups(ClientDbId cldbid);
inline bool hasServerGroupAssigned(uint64_t cldbId, const std::shared_ptr<Group>& group){
for(const auto& assign : this->getAssignedServerGroups(cldbId)) if(assign->group == group) return true;
return false;
}
std::shared_ptr<GroupAssignment> get_group_assignment(const ClientDbId& client_database_id, const std::shared_ptr<Group>& group) {
for(const auto& assign : this->getAssignedServerGroups(client_database_id))
if(assign->group == group) return assign;
return nullptr;
}
std::vector<std::shared_ptr<GroupAssignment>> getServerGroups(ClientDbId cldbid, server::ClientType type);
inline bool hasServerGroup(uint64_t cldbId, server::ClientType type, const std::shared_ptr<Group>& group){
for(const auto& assign : this->getServerGroups(cldbId, type)) if(assign->group == group) return true;
return false;
}
//Gets the channel group (may inherited)
std::shared_ptr<GroupAssignment> getChannelGroup(ClientDbId cldbId, const std::shared_ptr<BasicChannel> &, bool assign_default);
//Gets the channel group within the channel
std::shared_ptr<GroupAssignment> getChannelGroupExact(ClientDbId cldbId, const std::shared_ptr<BasicChannel>&, bool assign_default);
std::vector<std::shared_ptr<GroupAssignment>> defaultServerGroupGroupAssignments(ClientDbId, server::ClientType);
std::shared_ptr<GroupAssignment> defaultChannelGroupAssignment(ClientDbId cldbId, const std::shared_ptr<BasicChannel> &);
void addServerGroup(ClientDbId cldbId, std::shared_ptr<Group>, std::chrono::time_point<std::chrono::system_clock> until = std::chrono::time_point<std::chrono::system_clock>());
void removeServerGroup(ClientDbId cldbId, std::shared_ptr<Group>);
void setChannelGroup(ClientDbId cldbId, std::shared_ptr<Group>, std::shared_ptr<BasicChannel> , std::chrono::time_point<std::chrono::system_clock> until = std::chrono::time_point<std::chrono::system_clock>());
std::shared_ptr<Group> createGroup(GroupTarget target, GroupType type, std::string name);
GroupId copyGroup(std::shared_ptr<Group> group, GroupType type, std::string name, ServerId targetServerId);
bool copyGroupPermissions(const std::shared_ptr<Group>& source, const std::shared_ptr<Group>& target);
bool reloadGroupPermissions(std::shared_ptr<Group>);
bool renameGroup(std::shared_ptr<Group>, std::string);
bool deleteGroup(std::shared_ptr<Group>);
bool deleteAllGroups();
std::vector<std::shared_ptr<GroupMember>> listGroupMembers(std::shared_ptr<Group>, bool names = false);
std::vector<std::shared_ptr<GroupAssignment>> listGroupAssignments(ClientDbId client);
void cleanupAssignments(ClientDbId);
std::shared_ptr<Group> findGroup(GroupId);
std::shared_ptr<Group> findGroupLocal(GroupId);
std::vector<std::shared_ptr<Group>> findGroup(GroupTarget target, std::string);
std::shared_ptr<Group> defaultGroup(GroupTarget type, bool enforce_property = false);
std::deque<property::ClientProperties> update_server_group_property(const std::shared_ptr<server::ConnectedClient> &client, bool channel_lock, const std::shared_ptr<BasicChannel>& channel);
void enableCache(const ClientDbId& /* client database id */); /* if this called disableCache(...) MUST be called to decrease the reference count */
void disableCache(const ClientDbId& /* client database id */);
bool isClientCached(const ClientDbId& /* client database id */);
void clearCache();
bool isLocalGroup(std::shared_ptr<Group>);
protected:
void handleChannelDeleted(const ChannelId& /* channel id */);
private:
std::shared_ptr<GroupManager> root = nullptr;
std::weak_ptr<server::VirtualServer> server;
ServerId getServerId();
sql::SqlManager* sql;
std::vector<std::shared_ptr<Group>> groups;
threads::Mutex cacheLock;
std::vector<std::shared_ptr<CachedClient>> cachedClients;
int insertGroupFromDb(int count, char** values, char** column);
inline std::shared_ptr<CachedClient> resolve_cached_client(ClientDbId client_database_id);
};
}
DEFINE_TRANSFORMS(ts::GroupTarget, uint8_t);
+205 -171
View File
@@ -3,31 +3,30 @@
#define XFREE undefined_free
#define XREALLOC undefined_realloc
#include <netdb.h>
#include "src/weblist/WebListManager.h"
#include <log/LogUtils.h>
#include "InstanceHandler.h"
#include "src/client/InternalClient.h"
#include "src/server/QueryServer.h"
#include "src/server/file/FileServer.h"
#include "SignalHandler.h"
#include "src/manager/PermissionNameMapper.h"
#include "./FileServerHandler.h"
#include "./server/GlobalNetworkEvents.h"
#include <ThreadPool/Timer.h>
#include "ShutdownHelper.h"
#include <sys/utsname.h>
#include "build.h"
#include <misc/digest.h>
#include <misc/base64.h>
#include <misc/hex.h>
#include <misc/rnd.h>
#include <misc/strobf.h>
#include <jemalloc/jemalloc.h>
#include <protocol/buffers.h>
#include "./groups/GroupManager.h"
#ifndef _POSIX_SOURCE
#define _POSIX_SOURCE
#endif
#include <unistd.h>
#include "./manager/ActionLogger.h"
#include "./client/shared/ServerCommandExecutor.h"
#undef _POSIX_SOURCE
using namespace std;
@@ -35,14 +34,25 @@ using namespace std::chrono;
using namespace ts;
using namespace ts::server;
#define INSTANCE_TICK_NAME "instance"
extern bool mainThreadActive;
InstanceHandler::InstanceHandler(SqlDataManager *sql) : sql(sql) {
serverInstance = this;
this->tick_manager = make_shared<threads::Scheduler>(config::threads::ticking, "tick task ");
this->statistics = make_shared<stats::ConnectionStatistics>(nullptr, true);
this->statistics->measure_bandwidths(true);
this->general_task_executor_ = std::make_shared<task_executor>(ts::config::threads::ticking, "instance tick ");
this->general_task_executor_->set_exception_handler([](const std::string& task_name, const std::exception_ptr& exception) {
std::string message{};
try {
std::rethrow_exception(exception);
} catch (const std::exception& ex) {
message = "std::exception::what() -> " + std::string{ex.what()};
} catch(...) {
message = "unknown exception";
}
logCritical(LOG_INSTANCE, "Instance task executor received exception: {}", message);
});
this->statistics = make_shared<stats::ConnectionStatistics>(nullptr);
std::string error_message{};
this->license_service_ = std::make_shared<license::LicenseService>();
@@ -51,7 +61,14 @@ InstanceHandler::InstanceHandler(SqlDataManager *sql) : sql(sql) {
}
this->dbHelper = new DatabaseHelper(this->getSql());
this->_properties = new Properties();
this->action_logger_ = std::make_unique<log::ActionLogger>();
if(!this->action_logger_->initialize(error_message)) {
logCritical(LOG_INSTANCE, "Failed to initialize instance action logs: {}", error_message);
logCritical(LOG_INSTANCE, "Action log has been disabled.");
this->action_logger_->finalize();
}
this->_properties = std::make_shared<PropertyManager>();
this->_properties->register_property_type<property::InstanceProperties>();
this->properties()[property::SERVERINSTANCE_FILETRANSFER_PORT] = ts::config::binding::DefaultFilePort;
this->properties()[property::SERVERINSTANCE_FILETRANSFER_HOST] = ts::config::binding::DefaultFileHost;
@@ -70,8 +87,8 @@ InstanceHandler::InstanceHandler(SqlDataManager *sql) : sql(sql) {
}
}
const auto &info = property::impl::info<property::InstanceProperties>(key);
if(*info == property::SERVERINSTANCE_UNDEFINED) {
const auto &info = property::find<property::InstanceProperties>(key);
if(info == property::SERVERINSTANCE_UNDEFINED) {
logError(0, "Got an unknown instance property " + key);
return 0;
}
@@ -103,18 +120,57 @@ InstanceHandler::InstanceHandler(SqlDataManager *sql) : sql(sql) {
this->properties()[property::SERVERINSTANCE_PERMISSIONS_VERSION] = this->sql->get_permissions_version();
globalServerAdmin = std::make_shared<ts::server::InternalClient>(this->getSql(), nullptr, "serveradmin", true);
static_pointer_cast<ts::server::InternalClient>(globalServerAdmin)->setSharedLock(globalServerAdmin);
this->globalServerAdmin = std::make_shared<ts::server::InternalClient>(this->getSql(), nullptr, "serveradmin", true);
this->globalServerAdmin->initialize_weak_reference(this->globalServerAdmin);
ts::server::DatabaseHelper::assignDatabaseId(this->getSql(), 0, globalServerAdmin);
this->_musicRoot = std::make_shared<InternalClient>(this->getSql(), nullptr, "Music Manager", false);
static_pointer_cast<InternalClient>(this->_musicRoot)->setSharedLock(this->_musicRoot);
dynamic_pointer_cast<InternalClient>(this->_musicRoot)->initialize_weak_reference(this->_musicRoot);
{
this->groupManager = std::make_shared<GroupManager>(nullptr, this->getSql());
this->groupManager->loadGroupFormDatabase();
using GroupLoadResult = groups::GroupLoadResult;
if (this->groupManager->availableServerGroups(false).empty()) {
this->group_manager_ = std::make_shared<groups::GroupManager>(this->getSql(), 0, nullptr);
if(!this->group_manager_->initialize(this->group_manager_, error_message)) {
logCritical(LOG_INSTANCE, "Failed to initialize instance group manager: {}", error_message);
mainThreadActive = false;
return;
}
bool initialize_groups{false};
switch(this->group_manager_->server_groups()->load_data(true)) {
case GroupLoadResult::SUCCESS:
break;
case GroupLoadResult::NO_GROUPS:
initialize_groups = true;
break;
case GroupLoadResult::DATABASE_ERROR:
logCritical(LOG_INSTANCE, "Failed to load instance server groups (Database error)");
mainThreadActive = false;
return;
}
switch(this->group_manager_->channel_groups()->load_data(true)) {
case GroupLoadResult::SUCCESS:
break;
case GroupLoadResult::NO_GROUPS:
initialize_groups = true;
break;
case GroupLoadResult::DATABASE_ERROR:
logCritical(LOG_INSTANCE, "Failed to load instance channel groups (Database error)");
mainThreadActive = false;
return;
}
if(!this->group_manager_->assignments().load_data(error_message)) {
logCritical(LOG_INSTANCE, "Failed to load instance group assignments: {}", error_message);
mainThreadActive = false;
return;
}
if (initialize_groups) {
if(!this->setupDefaultGroups()){
logCritical(LOG_INSTANCE, "Could not setup server instance! Stopping...");
mainThreadActive = false;
@@ -122,30 +178,7 @@ InstanceHandler::InstanceHandler(SqlDataManager *sql) : sql(sql) {
}
}
debugMessage(LOG_INSTANCE, "Instance admin group id " + to_string(this->properties()[property::SERVERINSTANCE_ADMIN_SERVERQUERY_GROUP].as<GroupId>()));
auto instance_server_admin = this->groupManager->findGroup(this->properties()[property::SERVERINSTANCE_ADMIN_SERVERQUERY_GROUP].as<GroupId>());
if (!instance_server_admin) {
instance_server_admin = this->groupManager->availableServerGroups(false).front();
logCritical(LOG_INSTANCE, "Missing instance server admin group! Using first available (" + (instance_server_admin ? instance_server_admin->name() : "nil") + ")");
}
if(this->groupManager->listGroupAssignments(this->globalServerAdmin->getClientDatabaseId()).empty())
this->groupManager->addServerGroup(this->globalServerAdmin->getClientDatabaseId(), instance_server_admin);
debugMessage(LOG_INSTANCE, "Server guest group id " + to_string(this->properties()[property::SERVERINSTANCE_GUEST_SERVERQUERY_GROUP].as<GroupId>()));
auto instance_server_guest = this->groupManager->findGroup(this->properties()[property::SERVERINSTANCE_GUEST_SERVERQUERY_GROUP].as_save<GroupId>());
if (!instance_server_guest) {
instance_server_guest = this->groupManager->availableServerGroups(false).front();
logCritical(LOG_INSTANCE, "Missing instance server guest group! Using first available (" + (instance_server_guest ? instance_server_guest->name() : "nil") + ")");
}
this->properties()[property::SERVERINSTANCE_GUEST_SERVERQUERY_GROUP] = instance_server_guest->groupId();
debugMessage(LOG_INSTANCE, "Server music group id " + to_string(this->properties()[property::SERVERINSTANCE_TEMPLATE_MUSICDEFAULT_GROUP].as<GroupId>()));
auto instance_server_music = this->groupManager->findGroup(this->properties()[property::SERVERINSTANCE_TEMPLATE_MUSICDEFAULT_GROUP].as_save<GroupId>());
if (!instance_server_music) {
instance_server_music = instance_server_guest;
logCritical(LOG_INSTANCE, "Missing instance server music group! Using serverguest (" + (instance_server_music ? instance_server_music->name() : "nil") + ")");
}
this->properties()[property::SERVERINSTANCE_TEMPLATE_MUSICDEFAULT_GROUP] = instance_server_music->groupId();
this->validate_default_groups();
}
{
@@ -173,8 +206,12 @@ InstanceHandler::InstanceHandler(SqlDataManager *sql) : sql(sql) {
this->save_channel_permissions();
}
}
if(!this->default_tree->getDefaultChannel()) this->default_tree->setDefaultChannel(this->default_tree->findChannel("[cspacer04]Default Channel", nullptr));
if(!this->default_tree->getDefaultChannel()) this->default_tree->setDefaultChannel(*this->default_tree->channels().begin());
if(!this->default_tree->getDefaultChannel()) {
this->default_tree->setDefaultChannel(this->default_tree->findChannel("[cspacer04]Default Channel", nullptr));
}
if(!this->default_tree->getDefaultChannel()) {
this->default_tree->setDefaultChannel(*this->default_tree->channels().begin());
}
assert(this->default_tree->getDefaultChannel());
}
@@ -183,43 +220,24 @@ InstanceHandler::InstanceHandler(SqlDataManager *sql) : sql(sql) {
}
if(this->properties()[property::SERVERINSTANCE_MONTHLY_TIMESTAMP].as<int64_t>() == 0) {
if(this->properties()[property::SERVERINSTANCE_MONTHLY_TIMESTAMP].as_or<int64_t>(0) == 0) {
debugMessage(LOG_INSTANCE, "Setting up monthly reset timestamp!");
this->properties()[property::SERVERINSTANCE_MONTHLY_TIMESTAMP] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
}
this->banMgr = new BanManager(this->getSql());
this->banMgr->loadBans();
this->web_list = make_shared<weblist::WebListManager>();
}
void InstanceHandler::executeTick(VirtualServer* server) {
auto str = "server_" + to_string(server->getServerId());
if(!this->tick_manager->schedule(str, std::bind(&VirtualServer::executeServerTick, server), milliseconds(500))) {
logCritical(LOG_INSTANCE, "Could not schedule server ticking task!");
}
}
void InstanceHandler::cancelExecute(VirtualServer* server) {
auto str = "server_" + to_string(server->getServerId());
if(!this->tick_manager->cancelTask(str)){
logError(LOG_INSTANCE, "Could not stop server tick task!");
}
}
InstanceHandler::~InstanceHandler() {
delete this->_properties;
delete this->banMgr;
delete this->dbHelper;
groupManager = nullptr;
group_manager_ = nullptr;
globalServerAdmin = nullptr;
_musicRoot = nullptr;
statistics = nullptr;
tick_manager = nullptr;
}
inline string strip(std::string message) {
@@ -245,12 +263,21 @@ inline vector<string> split_hosts(const std::string& message, char delimiter) {
}
bool InstanceHandler::startInstance() {
if (this->active)
if (this->active) {
return false;
}
active = true;
string errorMessage;
this->web_list->enabled = ts::config::server::enable_teamspeak_weblist;
this->server_command_executor_ = std::make_shared<ServerCommandExecutor>(ts::config::threads::command_execute);
this->network_event_loop_ = std::make_unique<NetworkEventLoop>(ts::config::threads::network_events);
if(!this->network_event_loop_->initialize()) {
this->server_command_executor_ = nullptr;
this->network_event_loop_ = nullptr;
logCritical(LOG_INSTANCE, "Failed to initialize network event loop");
return false;
}
this->permission_mapper = make_shared<permission::PermissionNameMapper>();
if(!this->permission_mapper->initialize(config::permission_mapping_file, errorMessage)) {
@@ -282,31 +309,11 @@ bool InstanceHandler::startInstance() {
}
this->loadWebCertificate();
fileServer = new ts::server::FileServer();
{
auto bindings_string = this->properties()[property::SERVERINSTANCE_FILETRANSFER_HOST].as<string>();
auto port = this->properties()[property::SERVERINSTANCE_FILETRANSFER_PORT].as<uint16_t>();
auto ft_bindings = net::resolve_bindings(bindings_string, port);
deque<shared_ptr<FileServer::Binding>> bindings;
for(auto& binding : ft_bindings) {
if(!get<2>(binding).empty()) {
logError(LOG_FT, "Failed to resolve binding for {}: {}", get<0>(binding), get<2>(binding));
continue;
}
auto entry = make_shared<FileServer::Binding>();
memcpy(&entry->address, &get<1>(binding), sizeof(sockaddr_storage));
entry->file_descriptor = -1;
entry->event_accept = nullptr;
bindings.push_back(entry);
}
logMessage(LOG_FT, "Starting server on {}:{}", bindings_string, port);
if(!fileServer->start(bindings, errorMessage)) {
logCritical(LOG_FT, "Failed to start server: {}", errorMessage);
return false;
}
this->file_server_handler_ = new file::FileServerHandler{this};
if(!this->file_server_handler_->initialize(errorMessage)) {
logCritical(LOG_FT, "Failed to initialize server: {}", errorMessage);
return false;
}
if(config::query::sslMode > 0) {
@@ -334,8 +341,8 @@ bool InstanceHandler::startInstance() {
}
{
auto query_bindings_string = this->properties()[property::SERVERINSTANCE_QUERY_HOST].as<string>();
auto query_port = this->properties()[property::SERVERINSTANCE_QUERY_PORT].as<uint16_t>();
auto query_bindings_string = this->properties()[property::SERVERINSTANCE_QUERY_HOST].value();
auto query_port = this->properties()[property::SERVERINSTANCE_QUERY_PORT].as_or<uint16_t>(0);
auto query_bindings = net::resolve_bindings(query_bindings_string, query_port);
deque<shared_ptr<QueryServer::Binding>> bindings;
@@ -377,7 +384,6 @@ FwIDAQAB
logCritical(LOG_GENERAL, "Failed to initialize WebClient TeaForum key! ({})", error);
return false;
}
this->web_event_loop = make_shared<webio::LoopManager>();
}
#endif
@@ -414,7 +420,14 @@ FwIDAQAB
startTimestamp = system_clock::now();
this->voiceServerManager->executeAutostart();
this->scheduler()->schedule(INSTANCE_TICK_NAME, bind(&InstanceHandler::tickInstance, this), milliseconds(100));
this->general_task_executor()->schedule_repeating(
this->tick_task_id,
"instance ticker",
std::chrono::milliseconds{500},
[&](const auto&) {
this->tickInstance();
}
);
return true;
}
@@ -425,10 +438,13 @@ void InstanceHandler::stopInstance() {
this->active = false;
this->activeCon.notify_all();
}
this->web_list->enabled = false;
this->server_command_executor_->shutdown();
/* TODO: Block on canceling. */
this->general_task_executor()->cancel_task(this->tick_task_id);
this->tick_task_id = 0;
threads::MutexLock lock_tick(this->lock_tick);
this->scheduler()->cancelTask(INSTANCE_TICK_NAME);
debugMessage(LOG_INSTANCE, "Stopping all virtual servers");
if (this->voiceServerManager)
@@ -444,25 +460,32 @@ void InstanceHandler::stopInstance() {
debugMessage(LOG_QUERY, "Query server stopped");
debugMessage(LOG_FT, "Stopping file server");
if (this->fileServer) this->fileServer->stop();
delete this->fileServer;
this->fileServer = nullptr;
file::finalize();
debugMessage(LOG_FT, "File server stopped");
this->save_channel_permissions();
this->save_group_permissions();
if(this->file_server_handler_) {
this->file_server_handler_->finalize();
delete std::exchange(this->file_server_handler_, nullptr);
}
delete this->sslMgr;
this->sslMgr = nullptr;
this->web_event_loop = nullptr;
this->license_service_->shutdown();
this->server_command_executor_ = nullptr;
this->network_event_loop_->shutdown();
this->network_event_loop_ = nullptr;
}
void InstanceHandler::tickInstance() {
threads::MutexLock lock(this->lock_tick);
if(!this->active) return;
if(!this->active) {
return;
}
auto now = system_clock::now();
@@ -482,16 +505,17 @@ void InstanceHandler::tickInstance() {
ALARM_TIMER(t, "InstanceHandler::tickInstance -> flush", milliseconds(5));
//logger::flush();
}
if(statisticsUpdateTimestamp + seconds(5) < now) {
{
ALARM_TIMER(t, "InstanceHandler::tickInstance -> statistics tick", milliseconds(5));
this->statistics->tick();
}
if(statisticsUpdateTimestamp + seconds(1) < now) {
statisticsUpdateTimestamp = now;
{
ALARM_TIMER(t, "InstanceHandler::tickInstance -> statistics tick", milliseconds(5));
this->statistics->tick();
}
{
ALARM_TIMER(t, "InstanceHandler::tickInstance -> statistics tick [monthly]", milliseconds(2));
auto month_timestamp = system_clock::time_point() + seconds(this->properties()[property::SERVERINSTANCE_MONTHLY_TIMESTAMP].as<int64_t>());
auto month_timestamp = system_clock::time_point() + seconds(
this->properties()[property::SERVERINSTANCE_MONTHLY_TIMESTAMP].as_or<int64_t>(0));
auto time_t_old = system_clock::to_time_t(month_timestamp);
auto time_t_new = system_clock::to_time_t(system_clock::now());
@@ -512,18 +536,13 @@ void InstanceHandler::tickInstance() {
if(memcleanTimestamp + minutes(10) < now) {
memcleanTimestamp = now;
{
{
ALARM_TIMER(t, "InstanceHandler::tickInstance -> mem cleanup -> buffer cleanup", milliseconds(5));
auto info = buffer::cleanup_buffers(buffer::cleanmode::CHUNKS_BLOCKS);
if(info.bytes_freed_buffer != 0 || info.bytes_freed_internal != 0)
logMessage(LOG_INSTANCE, "Cleanupped buffers. (Buffer: {}, Internal: {})", info.bytes_freed_buffer, info.bytes_freed_internal);
ALARM_TIMER(t, "InstanceHandler::tickInstance -> mem cleanup -> buffer cleanup", milliseconds(5));
auto info = buffer::cleanup_buffers(buffer::cleanmode::CHUNKS_BLOCKS);
if(info.bytes_freed_buffer != 0 || info.bytes_freed_internal != 0) {
logMessage(LOG_INSTANCE, "Cleanupped buffers. (Buffer: {}, Internal: {})", info.bytes_freed_buffer, info.bytes_freed_internal);
}
}
}
{
ALARM_TIMER(t, "InstanceHandler::tickInstance -> fileserver tick", milliseconds(5));
if(this->fileServer) this->fileServer->instanceTick();
}
{
ALARM_TIMER(t, "InstanceHandler::tickInstance -> sql_test tick", milliseconds(5));
if(this->sql && this->active) {
@@ -533,7 +552,7 @@ void InstanceHandler::tickInstance() {
auto command = this->sql->sql()->getType() == sql::TYPE_SQLITE ? "SELECT * FROM `sqlite_master`" : "SHOW TABLES";
auto result = sql::command(this->getSql(), command).query([command](int, string*, string*){ return 0; });
if(!result) {
logCritical(LOG_INSTANCE, "Dummy sql connection test faild! (Failed to execute command \"{}\". Error message: {})", command, result.fmtStr());
logCritical(LOG_INSTANCE, "Dummy sql connection test failed! (Failed to execute command \"{}\". Error message: {})", command, result.fmtStr());
logCritical(LOG_INSTANCE, "Stopping instance!");
ts::server::shutdownInstance("invalid sql connection!");
}
@@ -555,25 +574,14 @@ void InstanceHandler::tickInstance() {
this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_ALIVE] = this->calculateSpokenTime().count();
this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_TOTAL] =
this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_ALIVE].as<uint64_t>() +
this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED].as<uint64_t>() +
this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_VARIANZ].as<uint64_t>();
this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_ALIVE].as_or<uint64_t>(0) +
this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED].as_or<uint64_t>(0) +
this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_VARIANZ].as_or<uint64_t>(0);
}
this->web_list->tick();
}
void InstanceHandler::save_group_permissions() {
auto groups = this->getGroupManager()->availableGroups(false);
for(auto& group : groups) {
auto permissions = group->permissions();
if(permissions->require_db_updates()) {
auto begin = system_clock::now();
serverInstance->databaseHelper()->saveGroupPermissions(nullptr, group->groupId(), permissions);
auto end = system_clock::now();
debugMessage(0, "Saved instance group permissions for group {} ({}) in {}ms", group->groupId(), group->name(), duration_cast<milliseconds>(end - begin).count());
}
}
this->group_manager()->save_permissions();
}
void InstanceHandler::save_channel_permissions() {
@@ -646,15 +654,15 @@ std::shared_ptr<ts::server::license::InstanceLicenseInfo> InstanceHandler::gener
auto request = std::make_shared<license::InstanceLicenseInfo>();
request->license = config::license;
request->metrics.servers_online = this->voiceServerManager->runningServers();
auto report = this->voiceServerManager->clientReport();
request->metrics.client_online = report.clients_ts;
request->metrics.web_clients_online = report.clients_web;
request->metrics.bots_online = report.bots;
auto report = this->voiceServerManager->instanceSlotUsageReport();
request->metrics.client_online = report.clients_teamspeak;
request->metrics.web_clients_online = report.clients_teaweb;
request->metrics.bots_online = report.music_bots;
request->metrics.queries_online = report.queries;
request->metrics.speech_total = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_TOTAL].as<uint64_t>();
request->metrics.speech_varianz = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_VARIANZ].as<uint64_t>();
request->metrics.speech_online = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_ALIVE].as<uint64_t>();
request->metrics.speech_dead = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED].as<uint64_t>();
request->metrics.speech_total = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_TOTAL].as_or<uint64_t>(0);
request->metrics.speech_varianz = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_VARIANZ].as_or<uint64_t>(0);
request->metrics.speech_online = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_ALIVE].as_or<uint64_t>(0);
request->metrics.speech_dead = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED].as_or<uint64_t>(0);
static std::string null_str{"\0\0\0\0\0\0\0\0", 8}; /* we need at least some characters */
request->web_certificate_revision = this->web_cert_revision.empty() ? null_str : this->web_cert_revision;
@@ -677,11 +685,11 @@ std::shared_ptr<ts::server::license::InstanceLicenseInfo> InstanceHandler::gener
{ /* unique id */
auto property_unique_id = this->properties()[property::SERVERINSTANCE_UNIQUE_ID];
if(property_unique_id.as<string>().empty())
if(property_unique_id.value().empty())
property_unique_id = rnd_string(64);
auto hash = digest::sha256(request->info.uname);
hash = digest::sha256(hash + property_unique_id.as<string>() + get_mac_address());
hash = digest::sha256(hash + property_unique_id.value() + get_mac_address());
request->info.unique_id = base64::encode(hash);
}
}
@@ -894,35 +902,61 @@ void InstanceHandler::loadWebCertificate() {
this->web_cert_revision = revision;
}
bool InstanceHandler::validate_default_groups() {
using groups::GroupCalculateMode;
using groups::GroupAssignmentCalculateMode;
permission::v2::PermissionFlaggedValue InstanceHandler::calculate_permission(permission::PermissionType permission,
ClientDbId cldbid, ClientType type, ChannelId channel, bool granted, std::shared_ptr<CalculateCache> cache) {
auto result = this->calculate_permissions({permission}, cldbid, type, channel, granted, cache);
if(result.empty()) return {0, false};
return result.front().second;
}
{
auto property = this->properties()[property::SERVERINSTANCE_ADMIN_SERVERQUERY_GROUP];
auto group_id = property.as_or<GroupId>(0);
debugMessage(LOG_INSTANCE, "Instance admin query group id {}", group_id);
std::vector<std::pair<permission::PermissionType, permission::v2::PermissionFlaggedValue> > InstanceHandler::calculate_permissions(
const std::deque<permission::PermissionType> &permissions, ClientDbId cldbid, ClientType type, ChannelId channel, bool granted, std::shared_ptr<CalculateCache> cache) {
std::vector<std::pair<permission::PermissionType, permission::v2::PermissionFlaggedValue>> result{};
auto group_instance = this->group_manager_->server_groups()->find_group(GroupCalculateMode::GLOBAL, group_id);
if(!group_instance) {
auto available_groups = this->group_manager_->server_groups()->available_groups(GroupCalculateMode::GLOBAL);
if(available_groups.empty()) {
logCritical(LOG_INSTANCE, "Missing instance server groups.");
return false;
}
//TODO: Negate?
//TODO: May move this part to the instance?
auto server_groups = this->getGroupManager()->getServerGroups(cldbid, type);
for(const auto& permission : permissions) {
permission::v2::PermissionFlaggedValue value{0, false};
for(const auto &gr : server_groups){
auto group_permissions = gr->group->permissions();
auto flagged_permissions = granted ? group_permissions->permission_granted_flagged(permission) : group_permissions->permission_value_flagged(permission);
if(flagged_permissions.has_value)
if(!value.has_value || flagged_permissions.value > value.value || flagged_permissions.value == -1)
value = flagged_permissions;
group_instance = available_groups.front();
logCritical(LOG_INSTANCE, "Missing instance server admin query group. Using first available ({})", group_instance->group_id());
}
result.emplace_back(permission, value);
property.update_value(group_instance->group_id());
{
auto& assignments = this->group_manager_->assignments();
auto client_assignments = assignments.server_groups_of_client(GroupAssignmentCalculateMode::GLOBAL, this->globalServerAdmin->getClientDatabaseId());
if(client_assignments.empty()) {
assignments.add_server_group(this->globalServerAdmin->getClientDatabaseId(), group_instance->group_id(), false);
}
}
}
return result;
{
auto property = this->properties()[property::SERVERINSTANCE_GUEST_SERVERQUERY_GROUP];
auto group_id = property.as_or<GroupId>(0);
debugMessage(LOG_INSTANCE, "Instance guest query group id {}", group_id);
auto group_instance = this->group_manager_->server_groups()->find_group(GroupCalculateMode::GLOBAL, group_id);
if(!group_instance) {
auto available_groups = this->group_manager_->server_groups()->available_groups(GroupCalculateMode::GLOBAL);
if(available_groups.empty()) {
logCritical(LOG_INSTANCE, "Missing instance server groups.");
return false;
}
group_instance = available_groups.front();
logCritical(LOG_INSTANCE, "Missing instance server guest query group. Using first available ({})", group_instance->group_id());
}
property.update_value(group_instance->group_id());
}
return true;
}
std::shared_ptr<groups::ServerGroup> InstanceHandler::guest_query_group() {
auto group_id = this->properties()[property::SERVERINSTANCE_GUEST_SERVERQUERY_GROUP].as_or(0);
return this->group_manager_->server_groups()->find_group(groups::GroupCalculateMode::GLOBAL, group_id);
}
+52 -46
View File
@@ -7,13 +7,9 @@
#include <src/lincense/LicenseService.h>
#include "manager/SqlDataManager.h"
#include "lincense/TeamSpeakLicense.h"
#include "server/WebIoManager.h"
#include <misc/task_executor.h>
namespace ts {
namespace weblist {
class WebListManager;
}
namespace permission {
class PermissionNameMapper;
}
@@ -23,6 +19,22 @@ namespace ts {
class LicenseService;
}
namespace file {
class FileServerHandler;
}
namespace log {
class ActionLogger;
}
namespace groups {
class GroupManager;
}
class NetworkEventLoop;
class ServerCommandExecutor;
class InstanceHandler;
class InstanceHandler {
public:
explicit InstanceHandler(SqlDataManager*);
@@ -31,29 +43,32 @@ namespace ts {
bool startInstance();
void stopInstance();
ts::Properties& properties(){
return *_properties;
}
inline PropertyWrapper properties() { return PropertyWrapper{this->_properties}; }
inline const PropertyWrapper properties() const { return PropertyWrapper{this->_properties}; }
std::shared_ptr<ts::server::InternalClient> getInitialServerAdmin(){ return globalServerAdmin; }
std::shared_ptr<ts::GroupManager> getGroupManager(){ return groupManager; }
const auto& group_manager(){ return this->group_manager_; }
/**
* Get the default instance server query group.
* @return the default group or `nullptr` if the group doesn't exists any more.
*/
[[nodiscard]] std::shared_ptr<groups::ServerGroup> guest_query_group();
std::shared_ptr<ts::ServerChannelTree> getChannelTree() { return this->default_tree; }
std::shared_mutex& getChannelTreeLock() { return this->default_tree_lock; }
VirtualServerManager* getVoiceServerManager(){ return this->voiceServerManager; }
FileServer* getFileServer(){ return fileServer; }
QueryServer* getQueryServer(){ return queryServer; }
DatabaseHelper* databaseHelper(){ return this->dbHelper; }
BanManager* banManager(){ return this->banMgr; }
ssl::SSLManager* sslManager(){ return this->sslMgr; }
sql::SqlManager* getSql(){ return sql->sql(); }
log::ActionLogger* action_logger() { return &*this->action_logger_; }
file::FileServerHandler* getFileServerHandler() { return this->file_server_handler_; }
std::chrono::time_point<std::chrono::system_clock> getStartTimestamp(){ return startTimestamp; }
void executeTick(VirtualServer*);
void cancelExecute(VirtualServer*);
bool reloadConfig(std::vector<std::string>& /* errors */, bool /* reload file */);
void setWebCertRoot(const std::string& /* key */, const std::string& /* certificate */, const std::string& /* revision */);
@@ -64,35 +79,19 @@ namespace ts {
bool resetMonthlyStats();
std::shared_ptr<stats::ConnectionStatistics> getStatistics(){ return statistics; }
std::shared_ptr<threads::Scheduler> scheduler(){ return this->tick_manager; }
std::shared_ptr<license::InstanceLicenseInfo> generateLicenseData();
[[nodiscard]] inline const auto& general_task_executor(){ return this->general_task_executor_; }
[[nodiscard]] inline const auto& network_event_loop(){ return this->network_event_loop_; }
std::shared_ptr<TeamSpeakLicense> getTeamSpeakLicense() { return this->teamspeak_license; }
std::shared_ptr<ts::Properties> getDefaultServerProperties() { return this->default_server_properties; }
std::shared_ptr<webio::LoopManager> getWebIoLoop() { return this->web_event_loop; }
std::shared_ptr<weblist::WebListManager> getWebList() { return this->web_list; }
[[nodiscard]] inline std::shared_ptr<stats::ConnectionStatistics> getStatistics(){ return statistics; }
[[nodiscard]] std::shared_ptr<license::InstanceLicenseInfo> generateLicenseData();
std::shared_ptr<permission::PermissionNameMapper> getPermissionMapper() { return this->permission_mapper; }
std::shared_ptr<ts::event::EventExecutor> getConversationIo() { return this->conversation_io; }
[[nodiscard]] inline std::shared_ptr<TeamSpeakLicense> getTeamSpeakLicense() { return this->teamspeak_license; }
[[nodiscard]] inline PropertyWrapper getDefaultServerProperties() { return PropertyWrapper{this->default_server_properties}; }
permission::v2::PermissionFlaggedValue calculate_permission(
permission::PermissionType,
ClientDbId,
ClientType type,
ChannelId channel,
bool granted = false,
std::shared_ptr<CalculateCache> cache = nullptr
);
[[nodiscard]] inline std::shared_ptr<permission::PermissionNameMapper> getPermissionMapper() { return this->permission_mapper; }
[[nodiscard]] inline std::shared_ptr<ts::event::EventExecutor> getConversationIo() { return this->conversation_io; }
std::vector<std::pair<permission::PermissionType, permission::v2::PermissionFlaggedValue>> calculate_permissions(
const std::deque<permission::PermissionType>&,
ClientDbId,
ClientType type,
ChannelId channel,
bool granted = false,
std::shared_ptr<CalculateCache> cache = nullptr
);
[[nodiscard]] inline const auto& server_command_executor() { return this->server_command_executor_; }
[[nodiscard]] inline std::shared_ptr<license::LicenseService> license_service() { return this->license_service_; }
private:
@@ -100,6 +99,8 @@ namespace ts {
std::condition_variable activeCon;
bool active = false;
task_id tick_task_id{};
std::chrono::system_clock::time_point startTimestamp;
std::chrono::system_clock::time_point sqlTestTimestamp;
std::chrono::system_clock::time_point speachUpdateTimestamp;
@@ -110,30 +111,34 @@ namespace ts {
std::chrono::system_clock::time_point memcleanTimestamp;
SqlDataManager* sql;
FileServer* fileServer = nullptr;
QueryServer* queryServer = nullptr;
VirtualServerManager* voiceServerManager = nullptr;
DatabaseHelper* dbHelper = nullptr;
BanManager* banMgr = nullptr;
ssl::SSLManager* sslMgr = nullptr;
file::FileServerHandler* file_server_handler_{nullptr};
std::unique_ptr<log::ActionLogger> action_logger_{nullptr};
std::unique_ptr<NetworkEventLoop> network_event_loop_{nullptr};
ts::Properties* _properties = nullptr;
std::shared_ptr<ts::PropertyManager> _properties{};
std::shared_ptr<ServerCommandExecutor> server_command_executor_{};
std::shared_ptr<ts::event::EventExecutor> conversation_io = nullptr;
std::shared_ptr<webio::LoopManager> web_event_loop = nullptr;
std::shared_ptr<weblist::WebListManager> web_list = nullptr;
std::shared_ptr<ts::Properties> default_server_properties = nullptr;
std::shared_ptr<ts::ServerChannelTree> default_tree = nullptr;
std::shared_ptr<ts::PropertyManager> default_server_properties = nullptr;
std::shared_mutex default_tree_lock;
std::shared_ptr<ts::GroupManager> groupManager = nullptr;
std::shared_ptr<ts::ServerChannelTree> default_tree = nullptr;
std::shared_ptr<groups::GroupManager> group_manager_{nullptr};
std::shared_ptr<ts::server::InternalClient> globalServerAdmin = nullptr;
std::shared_ptr<ConnectedClient> _musicRoot = nullptr;
std::shared_ptr<license::LicenseService> license_service_{nullptr};
std::shared_ptr<stats::ConnectionStatistics> statistics = nullptr;
std::shared_ptr<threads::Scheduler> tick_manager = nullptr;
std::shared_ptr<task_executor> general_task_executor_{nullptr};
std::shared_ptr<permission::PermissionNameMapper> permission_mapper = nullptr;
std::shared_ptr<TeamSpeakLicense> teamspeak_license = nullptr;
@@ -149,6 +154,7 @@ namespace ts {
void save_channel_permissions();
void loadWebCertificate();
bool validate_default_groups();
};
}
}
+10
View File
@@ -0,0 +1,10 @@
//
// Created by WolverinDEV on 26/02/2021.
//
#include "./InstanceHandler.h"
#include "./groups/GroupManager.h"
#include "./PermissionCalculator.h"
using namespace ts;
using namespace ts::server;
+41 -15
View File
@@ -1,12 +1,10 @@
#include <netdb.h>
#include <deque>
#include <tuple>
#include <fstream>
#include <log/LogUtils.h>
#include "InstanceHandler.h"
#include "src/client/InternalClient.h"
#include "src/server/QueryServer.h"
#include "src/server/file/FileServer.h"
#include "./groups/GroupManager.h"
using namespace std;
using namespace std::chrono;
@@ -24,9 +22,9 @@ struct GroupInfo {
*/
int target;
std::deque<string> properties;
string name;
std::string name;
/* permission type, value, granted, skip, negate */
deque<tuple<permission::PermissionType, permission::PermissionValue, permission::PermissionValue, bool, bool>> permissions;
std::deque<std::tuple<permission::PermissionType, permission::PermissionValue, permission::PermissionValue, bool, bool>> permissions;
};
/* TODO may use a transaction here? */
@@ -120,25 +118,53 @@ bool InstanceHandler::setupDefaultGroups() {
groups.push_back(group);
}
debugMessage(LOG_INSTANCE, "Read " + to_string(groups.size()) + " default groups");
debugMessage(LOG_INSTANCE, "Read {} default groups", groups.size());
for(const auto& info : groups) {
debugMessage(LOG_INSTANCE, "Creating default group {} with type {}", info->name, to_string(info->target));
//Query groups
auto group = this->groupManager->createGroup(
info->target != 2 ? GroupTarget::GROUPTARGET_SERVER : GroupTarget::GROUPTARGET_CHANNEL,
info->target == 0 ? GroupType::GROUP_TYPE_QUERY : GroupType::GROUP_TYPE_TEMPLATE,
info->name
);
std::shared_ptr<groups::Group> created_group{};
groups::GroupCreateResult create_result{};
if(info->target == 2) {
std::shared_ptr<groups::ChannelGroup> c_group{};
create_result = serverInstance->group_manager()->channel_groups()->create_group(groups::GroupType::GROUP_TYPE_TEMPLATE, info->name, c_group);
created_group = c_group;
} else {
std::shared_ptr<groups::ServerGroup> s_group{};
create_result = serverInstance->group_manager()->server_groups()->create_group(info->target == 0 ? groups::GroupType::GROUP_TYPE_QUERY : groups::GroupType::GROUP_TYPE_TEMPLATE, info->name, s_group);
created_group = s_group;
}
switch(create_result) {
case groups::GroupCreateResult::SUCCESS:
break;
case groups::GroupCreateResult::DATABASE_ERROR:
logCritical(LOG_INSTANCE, "Failed to insert template group {} (Database error)", info->name);
return false;
case groups::GroupCreateResult::NAME_TOO_LONG:
case groups::GroupCreateResult::NAME_ALREADY_IN_USED:
case groups::GroupCreateResult::NAME_TOO_SHORT:
logCritical(LOG_INSTANCE, "Failed to insert template group {} (Name issue)", info->name);
return false;
default:
logCritical(LOG_INSTANCE, "Failed to insert template group {} (Unkown error)", info->name);
return false;
}
for(auto perm : info->permissions) {
group->permissions()->set_permission(get<0>(perm), {get<1>(perm), get<2>(perm)}, permission::v2::set_value, permission::v2::set_value, get<3>(perm), get<4>(perm));
created_group->permissions()->set_permission(get<0>(perm), {get<1>(perm), get<2>(perm)}, get<1>(perm) == permNotGranted ? permission::v2::do_nothing : permission::v2::set_value, get<2>(perm) == permNotGranted ? permission::v2::do_nothing : permission::v2::set_value, get<3>(perm), get<4>(perm));
}
for(const auto& property : info->properties) {
const auto& prop = property::impl::info<property::InstanceProperties>(property);
if(*prop == property::SERVERINSTANCE_UNDEFINED) {
const auto& prop = property::find<property::InstanceProperties>(property);
if(prop.is_undefined()) {
logCritical(LOG_INSTANCE, "Invalid template property name: " + property);
} else {
this->properties()[prop] = group->groupId();
this->properties()[prop] = created_group->group_id();
}
}
}
+408
View File
@@ -0,0 +1,408 @@
//
// Created by WolverinDEV on 27/02/2021.
//
#include "./PermissionCalculator.h"
#include "./InstanceHandler.h"
#include "./groups/Group.h"
#include "./client/DataClient.h"
#include <PermissionManager.h>
#include <log/LogUtils.h>
#include <src/groups/GroupManager.h>
using namespace ts::server;
using ts::permission::PermissionType;
using ts::permission::v2::PermissionFlaggedValue;
ClientPermissionCalculator::ClientPermissionCalculator(DataClient *client, ChannelId channel_id) {
/* Note: Order matters! */
this->initialize_client(client);
this->initialize_default_groups(client->getServer());
auto server = client->getServer();
if(server && channel_id > 0) {
std::shared_ptr<BasicChannel> channel{};
try {
std::shared_lock channel_lock{server->get_channel_tree_lock()};
this->channel_ = server->getChannelTree()->findChannel(channel_id);
} catch (std::system_error& e) {
if(e.code() != std::errc::resource_deadlock_would_occur) {
throw;
}
/* tree already write locked, no need to lock it again */
this->channel_ = server->getChannelTree()->findChannel(channel_id);
}
}
}
ClientPermissionCalculator::ClientPermissionCalculator(DataClient *client, const std::shared_ptr<BasicChannel> &channel) {
/* Note: Order matters! */
this->initialize_client(client);
this->initialize_default_groups(client->getServer());
this->channel_ = channel;
}
ClientPermissionCalculator::ClientPermissionCalculator(
const std::shared_ptr<VirtualServer> &server,
ClientDbId client_database_id,
ClientType client_type,
ChannelId channel_id) {
this->client_database_id = client_database_id;
this->client_type = client_type;
if(server) {
this->virtual_server_id = server->getServerId();
this->group_manager_ = server->group_manager();
try {
std::shared_lock channel_lock{server->get_channel_tree_lock()};
this->channel_ = server->getChannelTree()->findChannel(channel_id);
} catch (std::system_error& e) {
if(e.code() != std::errc::resource_deadlock_would_occur) {
throw;
}
/* tree already write locked, no need to lock it again */
this->channel_ = server->getChannelTree()->findChannel(channel_id);
}
} else {
this->virtual_server_id = 0;
this->group_manager_ = serverInstance->group_manager();
}
this->initialize_default_groups(server);
}
void ClientPermissionCalculator::initialize_client(DataClient* client) {
this->virtual_server_id = client->getServerId();
this->client_database_id = client->getClientDatabaseId();
this->client_type = client->getType();
this->client_permissions_ = client->permissions();
auto server = client->getServer();
if(server) {
this->group_manager_ = server->group_manager();
} else {
this->group_manager_ = serverInstance->group_manager();
}
}
void ClientPermissionCalculator::initialize_default_groups(const std::shared_ptr<VirtualServer> &server) {
if(this->client_type == ClientType::CLIENT_QUERY) {
this->default_server_group = []{ return serverInstance->guest_query_group(); };
} else if(server) {
this->default_server_group = [server]{ return server->default_server_group(); };
}
if(server) {
this->default_channel_group = [server]{ return server->default_channel_group(); };
}
}
PermissionFlaggedValue ClientPermissionCalculator::calculate_permission(permission::PermissionType permission,
bool granted) {
auto result = this->calculate_permissions({permission}, granted);
if(result.empty()) {
return { permNotGranted, false };
}
return result.front().second;
}
std::vector<std::pair<PermissionType, PermissionFlaggedValue>> ClientPermissionCalculator::calculate_permissions(
const std::deque<permission::PermissionType> &permissions,
bool calculate_granted
) {
if(permissions.empty()) {
return {};
}
std::vector<std::pair<PermissionType, PermissionFlaggedValue>> result;
result.reserve(permissions.size());
auto client_permissions = this->client_permissions();
assert(client_permissions);
/*
* server_group_data[0] := Server group id
* server_group_data[1] := Skip flag
* server_group_data[2] := Negate flag
* server_group_data[3] := Permission value
*/
typedef std::tuple<GroupId, bool, bool, permission::PermissionValue> GroupData;
bool server_group_data_initialized = false;
std::vector<GroupData> server_group_data;
GroupData* active_server_group;
auto initialize_group_data = [&](const permission::PermissionType& permission_type) {
server_group_data_initialized = true;
active_server_group = nullptr;
auto assigned_groups = this->assigned_server_groups();
server_group_data.resize(assigned_groups.size());
auto it = server_group_data.begin();
for(auto& group : assigned_groups) {
auto group_permissions = group->permissions();
auto permission_flags = group_permissions->permission_flags(permission_type);
auto flag_set = calculate_granted ? permission_flags.grant_set : permission_flags.value_set;
if(!flag_set) {
continue;
}
//TODO: Test if there is may a group channel permissions
auto value = group_permissions->permission_values(permission_type);
*it = std::make_tuple(group->group_id(), (bool) permission_flags.skip, (bool) permission_flags.negate, calculate_granted ? value.grant : value.value);
it++;
}
if(it == server_group_data.begin()) {
return; /* no server group has that permission */
}
server_group_data.erase(it, server_group_data.end()); /* remove unneeded */
auto found_negate = false;
for(auto& group : server_group_data) {
if(std::get<2>(group)) {
found_negate = true;
break;
}
}
if(found_negate) {
server_group_data.erase(remove_if(server_group_data.begin(), server_group_data.end(), [](auto data) { return !std::get<2>(data); }), server_group_data.end());
logTrace(this->virtual_server_id, "[Permission] Found negate flag within server groups. Groups left: {}", server_group_data.size());
if(server_group_data.empty()) {
logTrace(this->virtual_server_id, "[Permission] After non negated groups have been kicked out the negated groups are empty! This should not happen! Permission: {}, Client ID: {}", permission_type, this->client_database_id);
}
permission::PermissionValue current_lowest = 0;
for(auto& group : server_group_data) {
if(!active_server_group || (std::get<3>(group) < current_lowest && std::get<3>(group) != -1)) {
current_lowest = std::get<3>(group);
active_server_group = &group;
}
}
} else {
permission::PermissionValue current_highest = 0;
for(auto& group : server_group_data) {
if(!active_server_group || (std::get<3>(group) > current_highest || std::get<3>(group) == -1)) {
current_highest = std::get<3>(group);
active_server_group = &group;
}
}
}
};
for(const auto& permission : permissions) {
server_group_data_initialized = false; /* reset all group data */
auto client_permission_flags = client_permissions->permission_flags(permission);
/* lets try to resolve the channel specific permission */
if(this->channel_ && client_permission_flags.channel_specific) {
auto data = client_permissions->channel_permission(permission, this->channel_->channelId());
if(calculate_granted ? data.flags.grant_set : data.flags.value_set) {
result.push_back({permission, {calculate_granted ? data.values.grant : data.values.value, true}});
logTrace(this->virtual_server_id, "[Permission] Calculation for client {} of permission {} returned {} (Client channel permission)", this->client_database_id, permission::resolvePermissionData(permission)->name, data.values.value);
continue;
}
}
bool skip_channel_permissions = !this->channel_ || this->has_global_skip_permission();
if(!skip_channel_permissions) {
/* We dont have a global skip flag. Lets see if the target permission has skip enabled */
if(calculate_granted ? client_permission_flags.grant_set : client_permission_flags.value_set) {
/* okey the client has the permission, this counts */
skip_channel_permissions = client_permission_flags.skip;
} else {
if(!server_group_data_initialized) {
initialize_group_data(permission);
}
if(active_server_group) {
skip_channel_permissions = std::get<1>(*active_server_group);
}
}
}
if(!skip_channel_permissions) {
/* lookup the channel group */
{
auto channel_assignment = this->assigned_channel_group();
if(channel_assignment) {
auto group_permissions = channel_assignment->permissions();
auto permission_flags = group_permissions->permission_flags(permission);
auto flag_set = calculate_granted ? permission_flags.grant_set : permission_flags.value_set;
if(flag_set) {
auto value = group_permissions->permission_values(permission);
result.push_back({permission, {calculate_granted ? value.grant : value.value, true}});
logTrace(this->virtual_server_id, "[Permission] Calculation for client {} of permission {} returned {} (Channel group permission)", this->client_database_id, permission::resolvePermissionData(permission)->name, calculate_granted ? value.grant : value.value);
continue;
}
}
}
/* lookup the channel permissions. Whyever? */
if(this->channel_) {
auto channel_permissions = this->channel_->permissions();
auto data = calculate_granted ? channel_permissions->permission_granted_flagged(permission) : channel_permissions->permission_value_flagged(permission);
if(data.has_value) {
result.push_back({permission, {data.value, true}});
logTrace(this->virtual_server_id, "[Permission] Calculation for client {} of permission {} returned {} (Channel permission)", this->client_database_id, permission::resolvePermissionData(permission)->name, data.value);
continue;
}
}
}
if(calculate_granted ? client_permission_flags.grant_set : client_permission_flags.value_set) {
auto client_value = client_permissions->permission_values(permission);
result.push_back({permission, {calculate_granted ? client_value.grant : client_value.value, true}});
logTrace(this->virtual_server_id, "[Permission] Calculation for client {} of permission {} returned {} (Client permission)", this->client_database_id, permission::resolvePermissionData(permission)->name, client_value.value);
continue;
}
if(!server_group_data_initialized) {
initialize_group_data(permission);
}
if(active_server_group) {
result.push_back({permission, {get<3>(*active_server_group), true}});
logTrace(this->virtual_server_id, "[Permission] Calculation for client {} of permission {} returned {} (Server group permission of group {})", this->client_database_id, permission::resolvePermissionData(permission)->name, get<3>(*active_server_group), get<0>(*active_server_group));
continue;
}
logTrace(this->virtual_server_id, "[Permission] Calculation for client {} of permission {} returned in no permission.", this->client_database_id, permission::resolvePermissionData(permission)->name);
result.push_back({permission, { permNotGranted, false }});
}
return result;
}
const std::shared_ptr<ts::permission::v2::PermissionManager> & ClientPermissionCalculator::client_permissions() {
if(!this->client_permissions_) {
this->client_permissions_ = serverInstance->databaseHelper()->loadClientPermissionManager(this->virtual_server_id, this->client_database_id);
if(!this->client_permissions_) {
logCritical(this->virtual_server_id, "Failed to load client permissions for client {}. Using empty permission set.", this->client_database_id);
this->client_permissions_ = std::make_shared<permission::v2::PermissionManager>();
}
}
return this->client_permissions_;
}
const std::vector<std::shared_ptr<groups::ServerGroup>>& ClientPermissionCalculator::assigned_server_groups() {
if(this->assigned_server_groups_.has_value()) {
return *this->assigned_server_groups_;
}
auto assignments = this->group_manager_->assignments().server_groups_of_client(groups::GroupAssignmentCalculateMode::GLOBAL, this->client_database_id);
this->assigned_server_groups_.emplace();
this->assigned_server_groups_->reserve(assignments.size());
for(const auto& group_id : assignments) {
auto group = this->group_manager_->server_groups()->find_group(groups::GroupCalculateMode::GLOBAL, group_id);
if(group) {
this->assigned_server_groups_->push_back(group);
}
}
if(this->assigned_server_groups_->empty() && this->default_server_group) {
auto default_group = this->default_server_group();
if(default_group) {
this->assigned_server_groups_->push_back(default_group);
}
}
return *this->assigned_server_groups_;
}
const std::shared_ptr<groups::ChannelGroup>& ClientPermissionCalculator::assigned_channel_group() {
if(this->assigned_channel_group_.has_value()) {
return *this->assigned_channel_group_;
}
this->assigned_channel_group_.emplace();
if(!this->channel_) {
return *this->assigned_channel_group_;
}
std::shared_ptr<BasicChannel> inherited_channel{this->channel_};
auto channel_group_assignment = this->group_manager_->assignments().calculate_channel_group_of_client(
groups::GroupAssignmentCalculateMode::GLOBAL,
this->client_database_id,
inherited_channel
);
if(channel_group_assignment.has_value()) {
assert(inherited_channel);
auto channel_group = this->group_manager_->channel_groups()->find_group(groups::GroupCalculateMode::GLOBAL, *channel_group_assignment);
if(channel_group) {
this->assigned_channel_group_.emplace(std::move(channel_group));
logTrace(this->virtual_server_id, "[Permission] Using calculated channel group with id {} (Channel id: {}, Inherited channel id: {}).",
*channel_group_assignment, this->channel_->channelId(), inherited_channel->channelId());
} else {
logTrace(this->virtual_server_id, "[Permission] Missing calculated channel group with id {} (Channel id: {}, Inherited channel id: {}). Using default channel group.",
*channel_group_assignment, this->channel_->channelId(), inherited_channel->channelId());
}
} else {
logTrace(this->virtual_server_id, "[Permission] Using default channel group.");
}
if(!this->assigned_channel_group_.has_value()) {
assigned_channel_group_.emplace(
this->default_channel_group()
);
}
return *this->assigned_channel_group_;
}
bool ClientPermissionCalculator::has_global_skip_permission() {
if(this->skip_enabled.has_value()) {
return *this->skip_enabled;
}
/* test for skip permission within the client permission manager */
{
auto client_permissions = this->client_permissions();
auto skip_value = client_permissions->permission_value_flagged(permission::b_client_skip_channelgroup_permissions);
if(skip_value.has_value) {
this->skip_enabled = std::make_optional(permission::v2::permission_granted(1, skip_value));
logTrace(this->virtual_server_id, "[Permission] Found skip permission in client permissions. Value: {}", *this->skip_enabled);
}
}
/* test for skip permission within all server groups */
if(!this->skip_enabled.has_value()) {
for(const auto& assignment : this->assigned_server_groups()) {
auto group_permissions = assignment->permissions();
auto flagged_value = group_permissions->permission_value_flagged(permission::b_client_skip_channelgroup_permissions);
if(flagged_value.has_value) {
this->skip_enabled = std::make_optional(permission::v2::permission_granted(1, flagged_value));
if(*this->skip_enabled) {
logTrace(this->virtual_server_id, "[Permission] Found skip permission in client server group. Group: {} ({})", assignment->group_id(), assignment->display_name());
break;
}
}
}
}
if(!this->skip_enabled.has_value()) {
this->skip_enabled = std::make_optional(false);
}
return *this->skip_enabled;
}
bool ClientPermissionCalculator::permission_granted(const permission::PermissionType &permission,
const permission::PermissionValue &required_value, bool granted) {
return this->permission_granted(permission, { required_value, true }, granted);
}
bool ClientPermissionCalculator::permission_granted(const permission::PermissionType &permission,
const permission::v2::PermissionFlaggedValue &required_value, bool granted) {
return permission::v2::permission_granted(required_value, this->calculate_permission(permission, granted));
}
+114
View File
@@ -0,0 +1,114 @@
#pragma once
#include <Definitions.h>
#include <PermissionManager.h>
#include <vector>
#include <memory>
namespace ts {
class BasicChannel;
}
namespace ts::server {
class DataClient;
class VirtualServer;
namespace groups {
class ChannelGroup;
class ServerGroup;
class GroupManager;
}
/**
* Helper for calculating the client permissions for a certain channel.
* Note: All functions are not thread save!
*/
class ClientPermissionCalculator {
public:
/* When providing the pointer to the channel the channel tree **should** not be locked in any way! */
explicit ClientPermissionCalculator(DataClient* /* client */, const std::shared_ptr<BasicChannel>& /* target channel */);
explicit ClientPermissionCalculator(DataClient* /* client */, ChannelId /* target channel id */);
explicit ClientPermissionCalculator(
const std::shared_ptr<VirtualServer>& /* server */,
ClientDbId /* client database id */,
ClientType /* client type */,
ChannelId /* target channel id */
);
/**
* Calculate the given permissions.
* This method can be called from everywhere without any locking needed.
* @param granted
* @return
*/
[[nodiscard]] permission::v2::PermissionFlaggedValue calculate_permission(
permission::PermissionType,
bool granted = false
);
/**
* Calculate the given permissions.
* This method can be called from everywhere without any locking needed.
* @param channel
* @param calculate_granted
* @return
*/
[[nodiscard]] std::vector<std::pair<permission::PermissionType, permission::v2::PermissionFlaggedValue>> calculate_permissions(
const std::deque<permission::PermissionType>&,
bool calculate_granted = false
);
/**
* Test if the target has the target permission granted.
* If the client does not have any value assigned for the permission `false` will be returned.
*/
[[nodiscard]] bool permission_granted(
const permission::PermissionType& /* target permission */,
const permission::PermissionValue& /* required value */,
bool /* granted permission */ = false
);
/**
* Test if the target has the target permission granted.
* If the client does not have any value assigned for the permission,
* `true` will be returned if the `required value` contains no value
* otherwise false will be returned.
*
* This method should be used when testing permissions which are allowed by default except if they're
* specified otherwise. An example permission would be if we're testing against the channel needed join power.
*/
[[nodiscard]] bool permission_granted(
const permission::PermissionType& /* target permission */,
const permission::v2::PermissionFlaggedValue& /* required value */,
bool /* granted permission */ = false
);
//const PermissionValue& required, const PermissionFlaggedValue& given, bool requires_given = true
private:
/* given fields */
ServerId virtual_server_id;
ClientDbId client_database_id;
ClientType client_type;
std::shared_ptr<BasicChannel> channel_;
std::shared_ptr<groups::GroupManager> group_manager_{};
std::function<std::shared_ptr<groups::ChannelGroup>()> default_channel_group{[]{ return nullptr; }};
std::function<std::shared_ptr<groups::ServerGroup>()> default_server_group{[]{ return nullptr; }};
/* fields which will be set when calculating permissions */
std::shared_ptr<permission::v2::PermissionManager> client_permissions_{};
std::optional<bool> skip_enabled{};
std::optional<std::shared_ptr<groups::ChannelGroup>> assigned_channel_group_{};
std::optional<std::vector<std::shared_ptr<groups::ServerGroup>>> assigned_server_groups_{};
void initialize_client(DataClient* /* client */);
void initialize_default_groups(const std::shared_ptr<VirtualServer>& /* server */);
[[nodiscard]] const std::vector<std::shared_ptr<groups::ServerGroup>>& assigned_server_groups();
[[nodiscard]] const std::shared_ptr<groups::ChannelGroup>& assigned_channel_group();
[[nodiscard]] const std::shared_ptr<permission::v2::PermissionManager>& client_permissions();
[[nodiscard]] bool has_global_skip_permission();
};
}
+47 -697
View File
@@ -1,12 +1,11 @@
#include <algorithm>
#include <utility>
#include <log/LogUtils.h>
#include <misc/std_unique_ptr.h>
#include <Properties.h>
#include "VirtualServerManager.h"
#include "src/server/VoiceServer.h"
#include "InstanceHandler.h"
#include "InstanceHandler.h"
#include "./groups/GroupManager.h"
//TODO: When using the new command builder make sure you're using a std::deque as the underlying bulk type!
@@ -16,13 +15,6 @@ using namespace ts::server;
#define PREFIX string("[SNAPSHOT] ")
inline std::string avArguments(const ts::Command& cmd, int index){
stringstream res;
for(const auto &key : cmd[index].keys())
res << key << "=" << cmd[index][key].as<std::string>() << " ";
return res.str();
}
//TeamSpeak: permid=i_channel_needed_permission_modify_power permvalue=75 permskip=0 permnegated=0
//TeaSpeak: perm=i_channel_needed_permission_modify_power value=75 granted=75 flag_skip=0 flag_negated=0
struct SnapshotPermissionEntry {
@@ -32,104 +24,9 @@ struct SnapshotPermissionEntry {
bool negated = false;
bool skipped = false;
SnapshotPermissionEntry(const std::shared_ptr<permission::PermissionTypeEntry>& type, permission::PermissionValue value, permission::PermissionValue grant) : type(std::move(type)), value(value), grant(grant) {}
SnapshotPermissionEntry(const std::shared_ptr<permission::PermissionTypeEntry>& type, permission::PermissionValue value, permission::PermissionValue grant, bool negated, bool skipped) : type(type), value(value), grant(grant), negated(negated), skipped(skipped) {}
SnapshotPermissionEntry() = default;
static deque<std::unique_ptr<SnapshotPermissionEntry>> parse(const Command& cmd, int& index, permission::teamspeak::GroupType type, int version, const std::vector<std::string>& ends, bool ignore_first = false) {
deque<std::unique_ptr<SnapshotPermissionEntry>> result{};
while(true) {
bool end_reached = false;
for(const auto& e : ends)
if(cmd[index].has(e)) { end_reached = true; break; }
if(end_reached || cmd.bulkCount() < index) {
if(ignore_first) ignore_first = false;
else break;
}
if(version == 0) {
auto permission_name = cmd[index]["permid"].string();
for(const auto& mapped : permission::teamspeak::map_key(permission_name, type)) {
auto permission = permission::resolvePermissionData(mapped);
if(permission->type == permission::unknown) {
logError(0, "Failed to parse permission {}. Original: {}", mapped, permission_name);
index++;
continue;
}
bool found = false;
for(auto& e : result)
if(e->type->type == permission->type) {
found = true;
if(permission->grantName() == mapped)
e->grant = cmd[index]["permvalue"];
else {
e->value = cmd[index]["permvalue"];
e->negated = cmd[index]["permnegated"];
e->skipped = cmd[index]["permskip"];
}
}
if(!found) {
auto entry = make_unique<SnapshotPermissionEntry>();
entry->type = permission;
if(permission->grantName() == mapped)
entry->grant = cmd[index]["permvalue"];
else {
entry->value = cmd[index]["permvalue"];
entry->negated = cmd[index]["permnegated"];
entry->skipped = cmd[index]["permskip"];
}
result.push_back(std::move(entry));
}
}
} else if(version >= 1) {
auto permission_name = cmd[index]["perm"].string();
auto permission = permission::resolvePermissionData(permission_name);
if(permission->type == permission::unknown) {
logError(0, "Failed to parse permission {}", permission_name);
index++;
continue;
}
auto entry = make_unique<SnapshotPermissionEntry>();
entry->type = permission;
entry->value = cmd[index]["value"];
entry->grant = cmd[index]["grant"];
entry->skipped = cmd[index]["flag_skip"];
entry->negated = cmd[index]["flag_negated"];
result.push_back(std::move(entry));
} else {
logError(0, "Failed to parse snapshot permission entry! Invalid version!");
index++;
continue;
}
index++;
}
deque<std::unique_ptr<SnapshotPermissionEntry>> addings{};
for(const auto& perm : result) {
if(perm->type->type == permission::i_group_auto_update_type) { //Migrate this type
for(const auto& e : permission::update::migrate)
if(e.type == perm->value) {
auto _type = permission::resolvePermissionData(e.permission.name);
if(_type->type == permission::unknown) {
logError(0, "Invalid update permission for update group {}. Permission name: {}", e.type, e.permission.name);
} else {
addings.push_back(make_unique<SnapshotPermissionEntry>(_type, e.permission.value, e.permission.granted, e.permission.negated, e.permission.skipped));
}
}
}
}
for(auto& a : addings)
result.push_back(move(a));
return result;
}
void write(Command& cmd, int& index, permission::teamspeak::GroupType type, int version) {
if(version == 0) {
if(this->value != permNotGranted) {
@@ -163,542 +60,6 @@ struct SnapshotPermissionEntry {
}
};
std::shared_ptr<VirtualServer> VirtualServerManager::createServerFromSnapshot(shared_ptr<VirtualServer> old, std::string host,
uint16_t port, const ts::Command &arguments,
std::string &error) {
ServerId serverId = 0;
map<ClientDbId, map<ChannelId, ClientDbId>> channelGroupRelations; //cid is the new cgid
map<GroupId, GroupId> channelGroupMapping;
map<ClientDbId, vector<GroupId>> serverGroupRelations; //gid is the new gid
map<GroupId, GroupId> serverGroupMapping;
map<ClientDbId, ClientDbId> db_mapping_client_id;
deque<pair<std::string, threads::Future<sql::result>>> futures;
bool sid_success = false;
serverId = this->next_available_server_id(sid_success);
if(!sid_success) {
error = "failed to generate new sid";
return nullptr;
}
ServerId log_server_id = old ? old->getServerId() : serverId;
futures.emplace_back(
"server id register",
sql::command(this->handle->getSql(), "INSERT INTO `servers` (`serverId`, `host`, `port`) VALUES (:sid, :host, :port)", variable{":sid", serverId}, variable{":host", host}, variable{":port", port}).executeLater()
);
sql::model sql_insert_channel(this->handle->getSql(), "INSERT INTO `channels` (`serverId`, `channelId`, `type`, `parentId`) VALUES (:sid, :id, :type, :pid)", variable{":sid", serverId});
sql::model sql_insert_group_assignment(this->handle->getSql(), "INSERT INTO `assignedGroups` (`serverId`, `cldbid`, `groupId`, `channelId`, `until`) VALUES (:sid, :cldbid, :gid, :chid, :until)", variable{":sid", serverId}, variable{":until", 0});
sql::model sql_insert_permission(this->handle->getSql(), "INSERT INTO `permissions` (`serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate`) VALUES (:serverId, :type, :id, :chId, :permId, :value, :grant, :flag_skip, :flag_negate)",
variable{":serverId", serverId});
sql::model sql_update_permission_grant(this->handle->getSql(), "UPDATE `permissions` SET `grant` = :grant WHERE `serverId` = :sid AND `type` = :type AND `id` = :id AND `channelId` = :chid AND `permId` = :key", variable{":sid", serverId});
sql::model sql_insert_property(this->handle->getSql(), "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) VALUES (:sid, :type, :id, :key, :value)", variable{":sid", serverId});
sql::model sql_insert_bot(this->handle->getSql(), "INSERT INTO `musicbots` (`serverId`, `botId`, `uniqueId`, `owner`) VALUES (:server_id, :bot_id, :unique_id, :owner)", variable{":server_id", serverId});
sql::model sql_insert_playlist(this->handle->getSql(), "INSERT INTO `playlists` (`serverId`, `playlist_id`) VALUES (:server_id, :playlist_id)", variable{":server_id", serverId});
sql::model sql_insert_playlist_song(this->handle->getSql(), "INSERT INTO `playlist_songs` (`serverId`, `playlist_id`, `song_id`, `order_id`, `invoker_dbid`, `url`, `url_loader`, `loaded`, `metadata`) VALUES (:server_id, :playlist_id, :song_id, :order_id, :invoker_dbid, :url, :url_loader, :loaded, :metadata)", variable{":server_id", serverId});
int index = 0;
auto snapshot_version = arguments[index].has("snapshot_version") ? arguments[index++]["snapshot_version"] : 0;
debugMessage(0, "Got server snapshot with version {}", snapshot_version);
while(true){
for(auto &key : arguments[index].keys()){
if(key == "end_virtualserver") continue;
if(key == "begin_virtualserver") continue;
if(snapshot_version == 0) {
if(key == "virtualserver_download_quota" || key == "virtualserver_upload_quota" || key == "virtualserver_max_download_total_bandwidth" || key == "virtualserver_max_upload_total_bandwidth") {
auto value = arguments[index][key].string();
try {
arguments[index][key] = to_string((int64_t) std::stoull(value));
} catch(const std::exception& ex) {
continue;
}
}
}
debugMessage(LOG_GENERAL, PREFIX + " Having server key {} = {}", key, arguments[index][key].as<std::string>());
futures.emplace_back(
"server property import proprty=" + key,
sql_insert_property.command().values(
variable{":type", property::PropertyType::PROP_TYPE_SERVER},
variable{":id", 0},
variable{":key", key},
variable{":value", arguments[index][key].string()}
).executeLater()
);
}
if(arguments[index].has("end_virtualserver")) break;
index++;
}
//`serverId` INT NOT NULL, `playlist_id` INT, `song_id` INT, `order_id` INT, `invoker_dbid` INT, `url` TEXT, `url_loader` TEXT
/*
* sql::command(this->handle->getSql(), "INSERT INTO `playlists` (`serverId`, `playlist_id`) VALUES (:server_id, :playlist_id)",
variable{":server_id", this->ref_server()->getServerId()},
variable{":playlist_id", playlist_id}
)
*/
index++;
for(;index < arguments.bulkCount(); index++) {
if (arguments[index].has("begin_channels")) {
while (true) {
if (arguments[index].has("end_channels")) break;
auto channel_id = arguments[index]["channel_id"].string();
futures.emplace_back(
"channel register cid=" + channel_id,
sql_insert_channel.command().values(variable{":id", channel_id}, variable{":type", -1}, variable{":pid", arguments[index]["channel_pid"].string()}).executeLater()
); //TODO findout type!
for (const auto &key : arguments[index].keys()) {
if(key == "channel_id") continue;
if(key == "channel_pid") continue;
if(key == "channel_security_salt") continue;
if(key == "channel_filepath") continue; //TODO implement channel_filepath?
if(key == "begin_channels") continue;
debugMessage(log_server_id, PREFIX + "Having channel key " + key + "=" + arguments[index][key].as<std::string>() + " for channel " + arguments[index]["channel_id"].as<std::string>());
futures.push_back({
"channel prop register cid=" + channel_id + " property=" + key,
sql_insert_property.command().values(variable{":sid", serverId},
variable{":type", property::PropertyType::PROP_TYPE_CHANNEL},
variable{":id", channel_id},
variable{":key", key},
variable{":value", arguments[index][key].as<std::string>()}
).executeLater()
});
}
index++;
}
} else if (arguments[index].has("begin_clients")) {
while (true) {
if (arguments[index].has("end_clients")) break;
debugMessage(log_server_id, PREFIX + "Create client {}/{} -> end: {}",arguments[index]["client_id"].as<std::string>(), arguments[index]["client_nickname"].as<std::string>(), to_string(arguments[index].has("end_clients")));
ClientDbId oldId = 0;
auto res = sql::command(this->handle->getSql(), "SELECT `cldbid` FROM `clients` WHERE `clientUid` = :uid AND `serverId` = 0", variable{":uid", arguments[index]["client_unique_id"].as<std::string>()}).query([](ClientDbId* ptr, int, char** v, char**){ *ptr = stoll(v[0]); return 0;}, &oldId);
LOG_SQL_CMD(res);
if(oldId == 0){
res = sql::command(this->handle->getSql(), "SELECT `cldbid` FROM `clients` WHERE `serverId` = 0 ORDER BY `cldbid` DESC LIMIT 1").query([](ClientDbId* ptr, int length, char** values, char** names){
*ptr = static_cast<ClientDbId>(stoll(values[0]));
return 0;
}, &oldId);
LOG_SQL_CMD(res);
oldId += 1;
//Create a new client
sql::command(this->handle->getSql(), "INSERT INTO `clients` (`serverId`, `cldbid`, `clientUid`, `firstConnect`, `lastConnect`, `connections`, `lastName`) VALUES (:sid, :cldbid, :cluid, :firstConnect, :lastConnect, :connections, :name)",
variable{":sid", 0}, variable{":cldbid", oldId}, variable{":cluid", arguments[index]["client_unique_id"].as<std::string>()},
variable{":firstConnect", arguments[index]["client_created"].as<std::string>()},
variable{":lastConnect", "0"}, variable{":connections", "0"}, variable{":name", arguments[index]["client_nickname"].as<std::string>()}).execute(); //TODO log error
}
db_mapping_client_id[arguments[index]["client_id"].as<ClientDbId>()] = oldId;
sql::command(this->handle->getSql(), "INSERT INTO `clients` (`serverId`, `cldbid`, `clientUid`, `firstConnect`, `lastConnect`, `connections`, `lastName`) VALUES (:sid, :cldbid, :cluid, :firstConnect, :lastConnect, :connections, :name)",
variable{":sid", serverId}, variable{":cldbid", oldId}, variable{":cluid", arguments[index]["client_unique_id"].as<std::string>()},
variable{":firstConnect", arguments[index]["client_created"].as<std::string>()},
variable{":lastConnect", "0"}, variable{":connections", "0"}, variable{":name", arguments[index]["client_nickname"].as<std::string>()}).execute(); //TODO log error
//TODO here
index++;
}
} else if (arguments[index].has("begin_permissions")) {
while (true) {
index++;
if (arguments[index].has("end_permissions")) break;
else if (arguments[index].has("server_groups")) {
GroupId currentGroupId = 1;
while (true) {
if (arguments[index].has("end_groups")) break;
if(!arguments[index].has("id")){ //new group
logError(0, "Invalid server group permission entry! Missing group id!");
index++;
continue;
}
currentGroupId = static_cast<GroupId>(GroupManager::generateGroupId(this->handle->getSql()));
debugMessage(log_server_id, PREFIX + "Insert group: " + to_string(currentGroupId) + " - " + arguments[index]["name"].as<string>());
serverGroupMapping[arguments[index]["id"].as<GroupId>()] = currentGroupId;
LOG_SQL_CMD(sql::command(this->handle->getSql(), "INSERT INTO `groups` (`serverId`, `groupId`, `target`, `type`, `displayName`) VALUES (:sid, :gid, :target, :type, :name)",
variable{":sid", serverId},
variable{":gid", currentGroupId},
variable{":target", GROUPTARGET_SERVER},
variable{":type", GroupType::GROUP_TYPE_NORMAL},
variable{":name", arguments[index]["name"].as<string>()})
.execute());
auto permissions = SnapshotPermissionEntry::parse(arguments, index, permission::teamspeak::SERVER, snapshot_version, {"end_group"});
for(const auto& entry : permissions) {
futures.push_back({
"server group permission sgid=" + to_string(currentGroupId) + " permId=" + entry->type->name,
sql_insert_permission.command().values(
variable{":id", currentGroupId},
variable{":type", permission::SQL_PERM_GROUP},
variable{":chId", 0},
variable{":permId", entry->type->name},
variable{":value", entry->value},
variable{":grant", entry->grant},
variable{":flag_skip", entry->skipped},
variable{":flag_negate", entry->negated}
).executeLater()
});
}
index++;
}
//Relations for sgroups after list
index++;
while (true) {
if (arguments[index].has("end_relations")) break;
serverGroupRelations[db_mapping_client_id[arguments[index]["cldbid"].as<ClientDbId>()]].push_back(serverGroupMapping[arguments[index]["gid"].as<GroupId>()]);
futures.push_back({
"server group relation",
sql_insert_group_assignment.command().values(variable{":cldbid", db_mapping_client_id[arguments[index]["cldbid"].as<ClientDbId>()]}, variable{":gid", serverGroupMapping[arguments[index]["gid"].as<GroupId>()]}, variable{":chid", "0"}).executeLater()
});
index++;
}
} else if(arguments[index].has("channel_groups")){
GroupId currentGroupId = 1;
while (true) {
if (arguments[index].has("end_groups")) break;
if(!arguments[index].has("id")){ //new group
logError(0, "Invalid server group permission entry! Missing group id!");
index++;
continue;
}
currentGroupId = static_cast<GroupId>(GroupManager::generateGroupId(this->handle->getSql()));
debugMessage(log_server_id, PREFIX + "Insert channel group: " + to_string(currentGroupId) + " - " + arguments[index]["name"].as<string>());
channelGroupMapping[arguments[index]["id"].as<GroupId>()] = currentGroupId;
LOG_SQL_CMD(sql::command(this->handle->getSql(), "INSERT INTO `groups` (`serverId`, `groupId`, `target`, `type`, `displayName`) VALUES (:sid, :gid, :target, :type, :name)",
variable{":sid", serverId},
variable{":gid", currentGroupId},
variable{":target", GROUPTARGET_CHANNEL},
variable{":type", GroupType::GROUP_TYPE_NORMAL},
variable{":name", arguments[index]["name"].as<string>()})
.execute());
auto permissions = SnapshotPermissionEntry::parse(arguments, index, permission::teamspeak::SERVER, snapshot_version, {"end_group"});
for(const auto& entry : permissions) {
futures.push_back({
"channel group property insert cgid=" + to_string(currentGroupId) + " property=" + entry->type->name,
sql_insert_permission.command().values(
variable{":id", currentGroupId},
variable{":type", permission::SQL_PERM_GROUP},
variable{":chId", 0},
variable{":permId", entry->type->name},
variable{":value", entry->value},
variable{":grant", entry->grant},
variable{":flag_skip", entry->skipped},
variable{":flag_negate", entry->negated}
).executeLater()
});
}
index++;
}
//Relations for sgroups after list
ChannelId chId = 0;
index++;
while (true) {
if (arguments[index].has("end_relations")) break;
if(arguments[index].has("iid")) chId = arguments[index]["iid"];
channelGroupRelations[db_mapping_client_id[arguments[index]["cldbid"].as<ClientDbId>()]][chId] = channelGroupMapping[arguments[index]["gid"].as<GroupId>()];
futures.push_back({
"channel group relation",
sql_insert_group_assignment.command().values(variable{":cldbid", db_mapping_client_id[arguments[index]["cldbid"].as<ClientDbId>()]}, variable{":gid", channelGroupMapping[arguments[index]["gid"].as<GroupId>()]}, variable{":chid", chId}).executeLater()
});
index++;
}
} else if (arguments[index].has("client_flat")) {
ClientDbId currentId = 0;
while (true) {
if (arguments[index].has("end_flat")) break;
if(arguments[index].has("id1"))
currentId = arguments[index]["id1"];
auto permissions = SnapshotPermissionEntry::parse(arguments, index, permission::teamspeak::CLIENT, snapshot_version, {"id1", "end_flat"}, true);
for(const auto& entry : permissions) {
futures.push_back({
"client permission insert clientId=" + to_string(currentId) + " permId=" + entry->type->name,
sql_insert_permission.command().values(
variable{":id", currentId},
variable{":type", permission::SQL_PERM_USER},
variable{":chId", 0},
variable{":permId", entry->type->name},
variable{":value", entry->value},
variable{":grant", entry->grant},
variable{":flag_skip", entry->skipped},
variable{":flag_negate", entry->negated}
).executeLater()
});
}
}
} else if (arguments[index].has("channel_flat")) {
ChannelId currentChannelId = 0;
while (true) {
if (arguments[index].has("end_flat")) break;
if (arguments[index].has("id1"))
currentChannelId = arguments[index]["id1"]; //We also have id2 unknown for what this is. Maybe parent?
auto permissions = SnapshotPermissionEntry::parse(arguments, index, permission::teamspeak::CHANNEL, snapshot_version, {"id1", "end_flat"}, true);
for(const auto& entry : permissions) {
futures.push_back({
"channel permission insert channelId=" + to_string(currentChannelId) + " permId=" + entry->type->name,
sql_insert_permission.command().values(
variable{":id", 0},
variable{":type", permission::SQL_PERM_CHANNEL},
variable{":chId", currentChannelId},
variable{":permId", entry->type->name},
variable{":value", entry->value},
variable{":grant", entry->grant},
variable{":flag_skip", entry->skipped},
variable{":flag_negate", entry->negated}
).executeLater()
});
}
}
} else if (arguments[index].has("channel_client_flat")) {
ClientDbId currentClientId = 0;
ChannelId currentChannelId = 0;
while (true) {
if (arguments[index].has("end_flat")) break;
//id1 = channel id
//id2 = client id
if(arguments[index].has("id1"))
currentChannelId = arguments[index]["id1"];
if(arguments[index].has("id2"))
currentClientId = db_mapping_client_id[arguments[index]["id2"]];
if(currentChannelId > 0 && currentClientId > 0){
auto permissions = SnapshotPermissionEntry::parse(arguments, index, permission::teamspeak::CLIENT, snapshot_version, {"id1", "id2", "end_flat"}, true);
for(const auto& entry : permissions) {
futures.push_back({
"client channel permission insert",
sql_insert_permission.command().values(
variable{":id", currentClientId},
variable{":type", permission::SQL_PERM_CHANNEL},
variable{":chId", currentChannelId},
variable{":permId", entry->type->name},
variable{":value", entry->value},
variable{":grant", entry->grant},
variable{":flag_skip", entry->skipped},
variable{":flag_negate", entry->negated}
).executeLater()
});
}
} else index++;
}
} else {
error = "Invalid tag in permissions. Available: " + avArguments(arguments, index);
return nullptr;
}
}
} else if(arguments[index].has("begin_music")) {
PlaylistId playlist_index = 0;
PlaylistId playlist_error = 0xFFFFFFFF;
map<PlaylistId, PlaylistId> db_mapping_playlist;
/*
//gather static info (not needed because its a new server)
{
auto sql_result = sql::command(this->handle->getSql(), "SELECT `playlist_id` FROM `playlists` WHERE `serverId` = :server_id ORDER BY `playlist_id` DESC", variable{":server_id", serverId}).query([&](int length, string* values, string* names){
if(length != 1) return;
try {
playlist_index = (PlaylistId) stoll(values[0]);
} catch(const std::exception& ex) {
error = "Failed to parse playlist id from database. ID: " + values[0];
return;
}
});
LOG_SQL_CMD(sql_result);
if(playlist_index == 0) {
error = "failed to gather info";
return nullptr;
}
}
*/
while(!arguments[index].has("end_music")) {
if(arguments[index].has("begin_bots")) {
while(!arguments[index].has("end_bots")) {
ClientDbId bot_id = arguments[index]["bot_id"];
if(db_mapping_client_id.find(bot_id) == db_mapping_client_id.end()) {
logError(log_server_id, PREFIX + "Failed to insert music bot within id {}. (Mapping not found)", bot_id);
index++;
continue;
}
ClientDbId new_bot_id = db_mapping_client_id[bot_id];
//begin_bots
auto sql_result = sql_insert_bot.command().values(
variable{":bot_id", new_bot_id},
variable{":unique_id", arguments[index]["bot_unique_id"].string()},
variable{":owner", arguments[index]["bot_owner_id"].string()}
).execute();
if(!sql_result) {
logError(log_server_id, PREFIX + "Failed to insert music bot with id {} (old: {}). ({})", new_bot_id, bot_id, sql_result.fmtStr());
index++;
continue;
}
for(const auto& key : arguments[index].keys()) {
if(key == "begin_music") continue;
if(key == "begin_bots") continue;
if(key == "bot_unique_id") continue;
if(key == "bot_owner_id") continue;
if(key == "bot_id") continue;
const auto& property = property::info<property::ClientProperties>(key);
if(property->property_index == property::CLIENT_UNDEFINED) {
debugMessage(log_server_id, PREFIX + "Failed to parse give music bot property {} for bot {} (old: {}). Value: {}", key, new_bot_id, bot_id, arguments[index][key].string());
continue;
}
futures.push_back({
"music bot insert",
sql_insert_property.command().values(
variable{":type", property::PropertyType::PROP_TYPE_CLIENT},
variable{":id", new_bot_id},
variable{":key", key},
variable{":value", arguments[index][key].string()}
).executeLater()
});
}
index++;
}
} else if(arguments[index].has("begin_playlist")) {
while(!arguments[index].has("end_playlist")) {
PlaylistId playlist_id = arguments[index]["playlist_id"];
db_mapping_playlist[++playlist_index] = playlist_id;
auto sql_result = sql_insert_playlist.command().values(variable{":playlist_id", playlist_index}).execute();
if(!sql_result) {
db_mapping_playlist[playlist_index] = playlist_error;
logError(log_server_id, PREFIX + "Failed to insert playlist with id {} (old: {}). ({})", playlist_index, playlist_id, sql_result.fmtStr());
index++;
continue;
}
for(const auto& key : arguments[index].keys()) {
if(key == "begin_music") continue;
if(key == "begin_playlist") continue;
if(key == "playlist_id") continue;
const auto& property = property::info<property::ClientProperties>(key);
if(property->property_index == property::CLIENT_UNDEFINED) {
debugMessage(log_server_id, PREFIX + "Failed to parse given playlist property {} for playlist {} (old: {}). Value: {}", key, playlist_index, playlist_id, arguments[index][key].string());
continue;
}
futures.push_back({
"playlist insert",
sql_insert_property.command().values(
variable{":type", property::PropertyType::PROP_TYPE_PLAYLIST},
variable{":id", playlist_index},
variable{":key", key},
variable{":value", arguments[index][key].string()}
).executeLater()
});
}
index++;
}
} else if(arguments[index].has("begin_playlist_songs")) {
PlaylistId current_playlist = 0;
PlaylistId current_playlist_mapped = 0;
while(!arguments[index].has("end_playlist_songs")) {
if(arguments[index].has("song_playlist_id")) {
current_playlist = arguments[index]["song_playlist_id"];
current_playlist_mapped = (PlaylistId) db_mapping_client_id[current_playlist];
if(current_playlist_mapped == 0) {
logError(log_server_id, PREFIX + "Failed to insert playlist song entry {}. Playlist id {} hasn't been mapped", arguments[index]["song_id"].value(), current_playlist);
current_playlist_mapped = playlist_error;
}
}
if(current_playlist_mapped == playlist_error) { /* current list is an error */
index++;
continue;
}
auto sql_future = sql_insert_playlist_song.command().values(
variable{":playlist_id", current_playlist_mapped},
variable{":song_id", arguments[index]["song_id"].value()},
variable{":order_id", arguments[index]["song_order"].value()},
variable{":invoker_dbid", arguments[index]["song_invoker"].value()},
variable{":url", arguments[index]["song_url"].value()},
variable{":url_loader", arguments[index]["song_url_loader"].value()},
variable{":loaded", arguments[index]["song_loaded"].value()},
variable{":metadata", arguments[index]["song_metadata"].value()}
).executeLater();
futures.push_back({
"song insert",
sql_future
});
index++;
}
}
index++;
}
} else {
error = "Invalid root tag. Available: " + avArguments(arguments, index);
return nullptr;
}
}
debugMessage(serverId, "Wait for success!");
for(const auto& future : futures) {
auto result = future.second.waitAndGet({-1, "timeout"}, chrono::system_clock::now() + chrono::seconds(5));
if(!result) {
logError(serverId, "Failed to execute snapshotdeploy command {}: {}", future.first, result.fmtStr());
}
}
if(old)
this->deleteServer(old);
//Now we load the server
auto server = make_shared<VirtualServer>(serverId, this->handle->getSql());
server->self = server;
if(!server->initialize(false)) {
//FIXME Error handling!
}
server->properties()[property::VIRTUALSERVER_HOST] = host;
server->properties()[property::VIRTUALSERVER_PORT] = port;
server->properties()[property::VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY] = false;
{
threads::MutexLock l(this->instanceLock);
this->instances.push_back(server);
}
this->adjust_executor_threads();
server->properties()[property::VIRTUALSERVER_DEFAULT_SERVER_GROUP] = serverGroupMapping[server->properties()[property::VIRTUALSERVER_DEFAULT_SERVER_GROUP].as<GroupId>()];
server->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP] = channelGroupMapping[server->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP].as<GroupId>()];
server->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP] = channelGroupMapping[server->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP].as<GroupId>()];
server->ensureValidDefaultGroups();
return server;
}
struct CommandTuple {
Command& cmd;
int& index;
int version;
};
struct PermissionCommandTuple {
Command& cmd;
int& index;
@@ -771,8 +132,8 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
int index = 0;
if(version == -1) version = 2; //Auto versioned
if(version < 0 || version > 2) {
if(version == -1) version = 3; //Auto versioned
if(version < 0 || version > 3) {
error = "Invalid snapshot version!";
return false;
}
@@ -780,19 +141,19 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
//Server
{
cmd[index]["begin_virtualserver"] = "";
for(const auto& serverProperty : server->properties().list_properties(property::FLAG_SNAPSHOT)) {
for(const auto& serverProperty : server->properties()->list_properties(property::FLAG_SNAPSHOT)) {
if(version == 0) {
switch (serverProperty.type().property_index) {
case property::VIRTUALSERVER_DOWNLOAD_QUOTA:
case property::VIRTUALSERVER_UPLOAD_QUOTA:
case property::VIRTUALSERVER_MAX_DOWNLOAD_TOTAL_BANDWIDTH:
case property::VIRTUALSERVER_MAX_UPLOAD_TOTAL_BANDWIDTH:
cmd[index][serverProperty.type().name] = (uint64_t) serverProperty.as_save<int64_t>();
cmd[index][std::string{serverProperty.type().name}] = (uint64_t) serverProperty.as_or<int64_t>(0);
default:
break;
}
}
cmd[index][serverProperty.type().name] = serverProperty.value();
cmd[index][std::string{serverProperty.type().name}] = serverProperty.value();
}
cmd[index++]["end_virtualserver"] = "";
}
@@ -801,13 +162,14 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
{
cmd[index]["begin_channels"] = "";
for(const auto& channel : server->getChannelTree()->channels()) {
for(const auto& channelProperty : channel->properties().list_properties(property::FLAG_SNAPSHOT)) {
if(channelProperty.type() == property::CHANNEL_ID)
cmd[index]["channel_id"] = channelProperty.as<string>();
else if(channelProperty.type() == property::CHANNEL_PID)
cmd[index]["channel_pid"] = channelProperty.as<string>();
else
cmd[index][channelProperty.type().name] = channelProperty.as<string>();
for(const auto& channelProperty : channel->properties()->list_properties(property::FLAG_SNAPSHOT)) {
if(channelProperty.type() == property::CHANNEL_ID) {
cmd[index]["channel_id"] = channelProperty.value();
} else if(channelProperty.type() == property::CHANNEL_PID) {
cmd[index]["channel_pid"] = channelProperty.value();
} else {
cmd[index][std::string{channelProperty.type().name}] = channelProperty.value();
}
}
index++;
}
@@ -817,44 +179,26 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
//Clients
{
cmd[index]["begin_clients"] = "";
CommandTuple parm{cmd, index, version};
auto res = sql::command(server->getSql(), "SELECT `cldbid`, `clientUid`, `firstConnect`, `lastName` FROM `clients` WHERE `serverId` = :sid",
variable{":sid", server->getServerId()}
).query([&](CommandTuple* commandIndex, int length, char** value, char** name) {
ClientDbId cldbid = 0;
int64_t clientCreated = 0;
string clientUid;
string lastName;
string description; //TODO description
for(int idx = 0; idx < length; idx++) {
try {
if(strcmp(name[idx], "cldbid") == 0)
cldbid = value[idx] && strlen(value[idx]) > 0 ? stoul(value[idx]) : 0;
else if(strcmp(name[idx], "clientUid") == 0)
clientUid = value[idx];
else if(strcmp(name[idx], "firstConnect") == 0)
clientCreated = value[idx] && strlen(value[idx]) > 0 ? stoll(value[idx]) : 0L;
else if(strcmp(name[idx], "lastName") == 0)
lastName = value[idx];
} catch (std::exception& ex) {
logError(0, "Failed to write snapshot client (Skipping it)! Message: {} @ {} => {}", ex.what(), name[idx], value[idx]);
return 0;
}
}
struct CallbackArgument {
Command& command;
int& index;
int version;
};
CallbackArgument callback_argument{cmd, index, version};
this->handle->databaseHelper()->listDatabaseClients(server->getServerId(), std::nullopt, std::nullopt, [](void* ptr_argument, const DatabaseClient& client) {
auto argument = (CallbackArgument*) ptr_argument;
commandIndex->cmd[commandIndex->index]["client_id"] = cldbid;
commandIndex->cmd[commandIndex->index]["client_unique_id"] = clientUid;
commandIndex->cmd[commandIndex->index]["client_nickname"] = lastName;
commandIndex->cmd[commandIndex->index]["client_created"] = clientCreated;
commandIndex->cmd[commandIndex->index]["client_description"] = description;
if(commandIndex->version == 0)
commandIndex->cmd[commandIndex->index]["client_unread_messages"] = 0;
commandIndex->index++;
return 0;
}, &parm);
LOG_SQL_CMD(res);
argument->command[argument->index]["client_id"] = client.client_database_id;
argument->command[argument->index]["client_unique_id"] = client.client_unique_id;
argument->command[argument->index]["client_nickname"] = client.client_nickname;
argument->command[argument->index]["client_created"] = client.client_created;
argument->command[argument->index]["client_description"] = client.client_description;
if(argument->version == 0)
argument->command[argument->index]["client_unread_messages"] = 0;
argument->index++;
}, &callback_argument);
cmd[index++]["end_clients"] = "";
}
@@ -899,7 +243,7 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
if((property->type->flags & (property::FLAG_SAVE_MUSIC | property::FLAG_SAVE)) == 0) continue;
if(property->value == property->type->default_value) continue;
cmd[index][property->type->name] = property->value;
cmd[index][std::string{property->type->name}] = property->value;
}
index++;
@@ -931,7 +275,7 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
if((property->type->flags & (property::FLAG_SAVE_MUSIC | property::FLAG_SAVE)) == 0) continue;
if(property->value == property->type->default_value) continue;
cmd[index][property->type->name] = property->value;
cmd[index][std::string{property->type->name}] = property->value;
}
index++;
@@ -994,10 +338,13 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
//List groups
{
cmd[index]["server_groups"] = "";
for(const auto& group : server->getGroupManager()->availableServerGroups(false)) {
cmd[index]["id"] = group->groupId();
cmd[index]["name"] = group->name();
if(!writePermissions(group->permissions(), cmd, index, version, permission::teamspeak::SERVER, error)) break;
auto server_groups = server->group_manager()->server_groups();
for(const auto& group : server_groups->available_groups(groups::GroupCalculateMode::LOCAL)) {
cmd[index]["id"] = group->group_id();
cmd[index]["name"] = group->display_name();
if(!writePermissions(group->permissions(), cmd, index, version, permission::teamspeak::SERVER, error)) {
break;
}
cmd[index++]["end_group"] = "";
}
cmd[index++]["end_groups"] = "";
@@ -1016,10 +363,13 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
//List groups
{
cmd[index]["channel_groups"] = "";
for(const auto& group : server->getGroupManager()->availableChannelGroups(false)) {
cmd[index]["id"] = group->groupId();
cmd[index]["name"] = group->name();
if(!writePermissions(group->permissions(), cmd, index, version, permission::teamspeak::SERVER, error)) break;
auto server_groups = server->group_manager()->channel_groups();
for(const auto& group : server_groups->available_groups(groups::GroupCalculateMode::LOCAL)) {
cmd[index]["id"] = group->group_id();
cmd[index]["name"] = group->display_name();
if(!writePermissions(group->permissions(), cmd, index, version, permission::teamspeak::SERVER, error)) {
break;
}
cmd[index++]["end_group"] = "";
}
cmd[index++]["end_groups"] = "";
+7
View File
@@ -1,6 +1,7 @@
#include <log/LogUtils.h>
#include <StringVariable.h>
#include <ThreadPool/ThreadHelper.h>
#include <src/terminal/PipedTerminal.h>
#include "ShutdownHelper.h"
#include "InstanceHandler.h"
@@ -21,6 +22,7 @@ void ts::server::shutdownInstance(const std::string& message) {
logCriticalFmt(true, 0, "Could not shutdown server within 30 seconds! (Hangup!)");
logCriticalFmt(true, 0, "Killing server!");
terminal::finalize_pipe();
auto force_kill = std::thread([]{
threads::self::sleep_for(chrono::seconds(5));
logCriticalFmt(true, 0, "Failed to exit normally!");
@@ -31,6 +33,7 @@ void ts::server::shutdownInstance(const std::string& message) {
force_kill.detach();
exit(2);
//kill(0, SIGKILL);
});
threads::name(hangup_controller, "stop controller");
hangup_controller.detach();
@@ -42,6 +45,10 @@ void ts::server::shutdownInstance(const std::string& message) {
mainThreadActive = false;
}
bool ts::server::isShuttingDown() {
return shuttingDown;
}
std::shared_ptr<server::ShutdownData> currentShutdown = nullptr;
std::shared_ptr<server::ShutdownData> server::scheduledShutdown() { return currentShutdown; }
+1 -1
View File
@@ -5,7 +5,7 @@
namespace ts {
namespace server {
extern void shutdownInstance(const std::string& reason = ts::config::messages::applicationStopped);
extern bool isShuttingDown();
struct ShutdownData {
std::string reason;
+61 -17
View File
@@ -1,5 +1,4 @@
#include <breakpad/client/linux/handler/exception_handler.h>
#include "VirtualServer.h"
#include "SignalHandler.h"
#include "VirtualServerManager.h"
@@ -8,29 +7,26 @@
#include <csignal>
#include <log/LogUtils.h>
#include <experimental/filesystem>
#include <src/terminal/PipedTerminal.h>
#include <iterator>
#define BREAKPAD_EXCEPTION_HANDLER 1
#ifdef BREAKPAD_EXCEPTION_HANDLER
#include <breakpad/client/linux/handler/exception_handler.h>
#endif
using namespace std;
namespace fs = std::experimental::filesystem;
#ifdef BREAKPAD_EXCEPTION_HANDLER
google_breakpad::ExceptionHandler* globalExceptionHandler = nullptr;
#endif
#define SIG(s, c) \
if(signal(s, c) != nullptr) logError(LOG_GENERAL, "Cant setup signal handler for " #s);
extern bool mainThreadDone;
static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context, bool succeeded) {
logCritical(LOG_GENERAL, "The server crashed!");
try {
if(!fs::exists(fs::u8path(ts::config::crash_path)))
fs::create_directories(fs::u8path(ts::config::crash_path));
auto path = fs::u8path(descriptor.path());
path = fs::u8path(ts::config::crash_path + "crash_dump_" + path.filename().string());
fs::rename(fs::u8path(descriptor.path()), path);
logCritical(LOG_GENERAL, "Wrote crash dump to " + path.relative_path().string());
} catch (...) {
logCritical(LOG_GENERAL, "Failed to write/move crash dump!");
}
void print_current_exception() {
if(std::current_exception()) {
logCritical(LOG_GENERAL, "Exception reached stack root and cause the server to crash!");
logCritical(LOG_GENERAL, " Type: {}", std::current_exception().__cxa_exception_type()->name());
@@ -40,23 +36,61 @@ static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor,
logCritical(LOG_GENERAL, " Message: {}", ex.what());
} catch(...) {}
}
}
extern bool mainThreadDone;
#ifdef BREAKPAD_EXCEPTION_HANDLER
static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context, bool succeeded) {
if(ts::server::isShuttingDown()) {
/* We don't care about this crash dump. Remove it. */
std::error_code error_code{};
fs::remove(fs::u8path(descriptor.path()), error_code);
return true;
}
logCritical(LOG_GENERAL, "The server crashed!");
try {
if(!fs::exists(fs::u8path(ts::config::crash_path))) {
fs::create_directories(fs::u8path(ts::config::crash_path));
}
auto path = fs::u8path(descriptor.path());
path = fs::u8path(ts::config::crash_path + "crash_dump_" + path.filename().string());
fs::rename(fs::u8path(descriptor.path()), path);
logCritical(LOG_GENERAL, "Wrote crash dump to " + path.relative_path().string());
} catch (...) {
logCritical(LOG_GENERAL, "Failed to write/move crash dump!");
}
print_current_exception();
logCritical(LOG_GENERAL, "Please report this crash to the TeaSpeak maintainer WolverinDEV");
logCritical(LOG_GENERAL, "Official issue and bug tracker url: https://github.com/TeaSpeak/TeaSpeak/issues");
logCritical(LOG_GENERAL, "Any reports of crashes are useless if you not provide the above generated crashlog!");
logCritical(LOG_GENERAL, "Stopping server");
terminal::finalize_pipe();
ts::server::shutdownInstance(ts::config::messages::applicationCrashed);
while(!mainThreadDone) threads::self::sleep_for(chrono::seconds(1));
while(!mainThreadDone) {
threads::self::sleep_for(chrono::seconds(1));
}
return succeeded;
}
#endif
std::atomic spawn_failed_count = 0;
bool ts::syssignal::setup() {
logMessage(LOG_GENERAL, "Setting up exception handler");
#ifdef BREAKPAD_EXCEPTION_HANDLER
globalExceptionHandler = new google_breakpad::ExceptionHandler(google_breakpad::MinidumpDescriptor("."), nullptr, dumpCallback, nullptr, true, -1);
#endif
SIG(SIGTERM, &ts::syssignal::handleStopSignal);
if(isatty(fileno(stdin))) //We cant listen for this signal if stdin ist a atty
if(isatty(fileno(stdin))) {
//We cant listen for this signal if stdin ist a atty
SIG(SIGINT, &ts::syssignal::handleStopSignal);
}
//SIG(SIGABRT, &ts::syssignal::handleAbortSignal);
std::set_terminate(ts::syssignal::handleTerminate);
return true;
}
@@ -98,4 +132,14 @@ void ts::syssignal::handleStopSignal(int signal) {
raise(SIGKILL);
}
ts::server::shutdownInstance();
}
void ts::syssignal::handleAbortSignal(int) {
logCritical(0, "The server crashed (Abort signal received)!");
print_current_exception();
}
void ts::syssignal::handleTerminate() {
logCritical(0, "The server crashed (Received a terminate signal)!");
print_current_exception();
}
+2
View File
@@ -8,5 +8,7 @@ namespace ts {
extern bool setup();
extern bool setup_threads();
extern void handleStopSignal(int);
extern void handleAbortSignal(int);
extern void handleTerminate();
}
}
+337 -275
View File
@@ -1,12 +1,15 @@
#include <cstring>
#include <protocol/buffers.h>
#include "./PermissionCalculator.h"
#include "client/voice/VoiceClient.h"
#include "client/InternalClient.h"
#include "VirtualServer.h"
#include <misc/timer.h>
#include <log/LogUtils.h>
#include <misc/sassert.h>
#include <src/manager/ActionLogger.h>
#include "InstanceHandler.h"
#include "./groups/GroupManager.h"
using namespace std;
using namespace ts::server;
@@ -19,192 +22,234 @@ bool VirtualServer::registerClient(shared_ptr<ConnectedClient> client) {
sassert(client);
{
lock_guard lock(this->clients.lock);
std::lock_guard clients_lock{this->clients_mutex};
if(client->getClientId() > 0) {
logCritical(this->getServerId(), "Client {} ({}|{}) has been already registered!", client->getDisplayName(), client->getClientId(), client->getUid());
return false;
}
ClientId client_id = 0;
ClientId max_client_id = this->clients.clients.size();
while(client_id < max_client_id && this->clients.clients[client_id])
ClientId client_id{0};
while(this->clients.count(client_id)) {
client_id++;
if(client_id == max_client_id)
this->clients.clients.push_back(client);
else
this->clients.clients[client_id] = client;
this->clients.count++;
}
this->clients.emplace(client_id, client);
client->setClientId(client_id);
}
{
lock_guard lock(this->client_nickname_lock);
std::lock_guard lock{this->client_nickname_lock};
auto login_name = client->getDisplayName();
while(login_name.length() < 3)
login_name += ".";
if(client->getExternalType() == ClientType::CLIENT_TEAMSPEAK)
if(client->getExternalType() == ClientType::CLIENT_TEAMSPEAK) {
client->properties()[property::CLIENT_LOGIN_NAME] = login_name;
}
std::shared_ptr<ConnectedClient> found_client = nullptr;
while(login_name.length() < 3) {
login_name += ".";
}
auto client_name = login_name;
size_t counter = 0;
std::shared_ptr<ConnectedClient> found_client{nullptr};
auto registered_clients = this->getClients();
auto client_name{login_name};
size_t counter{0};
{
lock_guard clients_lock(this->clients.lock);
while(true) {
for(auto& _client : this->clients.clients) {
if(!_client) continue;
if(_client->getDisplayName() == client_name && _client != client)
for(auto& _client : registered_clients) {
if(_client->getDisplayName() == client_name && _client != client) {
goto increase_name;
}
}
goto nickname_valid;
increase_name:
client_name = login_name + to_string(++counter);
client_name = login_name + std::to_string(++counter);
}
}
nickname_valid:
client->setDisplayName(client_name);
}
switch(client->getType()) {
case ClientType::CLIENT_TEAMSPEAK:
case ClientType::CLIENT_TEASPEAK:
case ClientType::CLIENT_WEB:
this->properties()[property::VIRTUALSERVER_CLIENT_CONNECTIONS].increment_by<uint64_t>(1); //increase manager connections
this->properties()[property::VIRTUALSERVER_LAST_CLIENT_CONNECT] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
break;
case ClientType::CLIENT_QUERY:
this->properties()[property::VIRTUALSERVER_LAST_QUERY_CONNECT] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
this->properties()[property::VIRTUALSERVER_QUERY_CLIENT_CONNECTIONS].increment_by<uint64_t>(1); //increase manager connections
break;
case ClientType::CLIENT_MUSIC:
case ClientType::CLIENT_INTERNAL:
break;
if(client->getType() == ClientType::CLIENT_TEAMSPEAK || client->getType() == ClientType::CLIENT_WEB) {
this->properties()[property::VIRTUALSERVER_CLIENT_CONNECTIONS] ++; //increase manager connections
this->properties()[property::VIRTUALSERVER_LAST_CLIENT_CONNECT] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
}
else if(client->getType() == ClientType::CLIENT_QUERY) {
this->properties()[property::VIRTUALSERVER_LAST_QUERY_CONNECT] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
this->properties()[property::VIRTUALSERVER_QUERY_CLIENT_CONNECTIONS] ++; //increase manager connections
case ClientType::MAX:
default:
assert(false);
break;
}
return true;
}
bool VirtualServer::unregisterClient(shared_ptr<ConnectedClient> cl, std::string reason, std::unique_lock<std::shared_mutex>& chan_tree_lock) {
if(cl->getType() == ClientType::CLIENT_TEAMSPEAK && cl->getType() == ClientType::CLIENT_WEB) {
sassert(cl->state == ConnectionState::DISCONNECTED);
bool VirtualServer::unregisterClient(shared_ptr<ConnectedClient> client, std::string reason, std::unique_lock<std::shared_mutex>& chan_tree_lock) {
/* FIXME: Reenable this for the web client as soon we've fixed the web client disconnect method */
if(client->getType() == ClientType::CLIENT_TEAMSPEAK || client->getType() == ClientType::CLIENT_TEASPEAK/* || client->getType() == ClientType::CLIENT_WEB */) {
sassert(client->state == ConnectionState::DISCONNECTED);
}
auto client_id = cl->getClientId();
if(client_id == 0) return false; /* not registered */
{
lock_guard lock(this->clients.lock);
if(client_id >= this->clients.clients.size()) {
logCritical(this->getServerId(), "Client {} ({}|{}) has been registered, but client id exceed client id! Failed to unregister client.", cl->getDisplayName(), client_id, cl->getUid());
} else {
auto& client_container = this->clients.clients[client_id];
if(client_container != cl) {
logCritical(this->getServerId(), "Client {} ({}|{}) has been registered, but container hasn't client set! Failed to unregister client.", cl->getDisplayName(), client_id, cl->getUid());
} else {
client_container.reset();
this->clients.count--;
}
}
}
if(cl->getType() == ClientType::CLIENT_TEAMSPEAK || cl->getType() == ClientType::CLIENT_WEB)
this->properties()[property::VIRTUALSERVER_LAST_CLIENT_DISCONNECT] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
else if(cl->getType() == ClientType::CLIENT_QUERY)
this->properties()[property::VIRTUALSERVER_LAST_QUERY_DISCONNECT] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
{
if(!chan_tree_lock.owns_lock())
if(!chan_tree_lock.owns_lock()) {
chan_tree_lock.lock();
}
if(cl->currentChannel) //We dont have to make him invisible if he hasnt even a channel
this->client_move(cl, nullptr, nullptr, reason, ViewReasonId::VREASON_SERVER_LEFT, false, chan_tree_lock);
if(client->currentChannel) {
//We dont have to make him invisible if he hasnt even a channel
this->client_move(client, nullptr, nullptr, reason, ViewReasonId::VREASON_SERVER_LEFT, false, chan_tree_lock);
}
chan_tree_lock.unlock();
}
serverInstance->databaseHelper()->saveClientPermissions(this->ref(), cl->getClientDatabaseId(), cl->clientPermissions);
cl->setClientId(0);
{
std::lock_guard clients_lock{this->clients_mutex};
auto client_id = client->getClientId();
if(client_id == 0) {
return false; /* not registered */
}
if(!this->clients.erase(client_id)) {
client->setClientId(0);
logError(this->getServerId(), "Tried to unregister a not registered client {}/{} ({})", client->getDisplayName(), client->getUid(), client_id);
return false;
}
client->setClientId(0);
}
auto current_time_seconds = std::chrono::duration_cast<seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
switch(client->getType()) {
case ClientType::CLIENT_TEAMSPEAK:
case ClientType::CLIENT_TEASPEAK:
case ClientType::CLIENT_WEB:
this->properties()[property::VIRTUALSERVER_LAST_CLIENT_DISCONNECT] = current_time_seconds;
break;
case ClientType::CLIENT_QUERY:
this->properties()[property::VIRTUALSERVER_LAST_QUERY_DISCONNECT] = current_time_seconds;
break;
case ClientType::CLIENT_MUSIC:
case ClientType::CLIENT_INTERNAL:
case ClientType::MAX:
default:
break;
}
serverInstance->databaseHelper()->saveClientPermissions(this->ref(), client->getClientDatabaseId(), client->clientPermissions);
return true;
}
void VirtualServer::registerInternalClient(std::shared_ptr<ConnectedClient> client) {
client->state = ConnectionState::CONNECTED;
{
lock_guard lock(this->clients.lock);
if(client->getClientId() > 0) {
logCritical(this->getServerId(), "Internal client {} ({}|{}) has been already registered!", client->getDisplayName(), client->getClientId(), client->getUid());
return;
}
ClientId client_id = 0;
ClientId max_client_id = this->clients.clients.size();
while(client_id < max_client_id && this->clients.clients[client_id])
client_id++;
if(client_id == max_client_id)
this->clients.clients.push_back(client);
else
this->clients.clients[client_id] = client;
this->clients.clients[client_id] = client;
this->clients.count++;
client->setClientId(client_id);
}
this->registerClient(client);
}
void VirtualServer::unregisterInternalClient(std::shared_ptr<ConnectedClient> client) {
client->state = ConnectionState::DISCONNECTED;
{
auto client_id = client->getClientId();
lock_guard lock(this->clients.lock);
if(client_id >= this->clients.clients.size()) {
logCritical(this->getServerId(), "Client {} ({}|{}) has been registered, but client id exceed client id! Failed to unregister internal client.", client->getDisplayName(), client_id, client->getUid());
} else {
auto& client_container = this->clients.clients[client_id];
if(client_container != client) {
logCritical(this->getServerId(), "Client {} ({}|{}) has been registered, but container hasn't client set! Failed to unregister internal client.", client->getDisplayName(), client_id, client->getUid());
} else {
this->clients.count--;
client_container.reset();
}
}
}
std::unique_lock tree_lock{this->channel_tree_mutex};
this->unregisterClient(client, "internal disconnect", tree_lock);
}
bool VirtualServer::assignDefaultChannel(const shared_ptr<ConnectedClient>& client, bool join) {
shared_lock server_channel_lock(this->channel_tree_lock);
std::shared_ptr<BasicChannel> channel = nullptr;
if(client->properties()->hasProperty(property::CLIENT_DEFAULT_CHANNEL) && !client->properties()[property::CLIENT_DEFAULT_CHANNEL].as<string>().empty()) {
auto str = client->properties()[property::CLIENT_DEFAULT_CHANNEL].as<std::string>();
if(str[0] == '/' && str.find_first_not_of("0123456789", 1) == string::npos)
channel = this->channelTree->findChannel(static_cast<ChannelId>(stoll(str.substr(1))));
else
channel = this->channelTree->findChannelByPath(str);
if (channel) {
if(!channel->permission_granted(permission::i_channel_needed_join_power, client->calculate_permission(permission::i_channel_join_power, channel->channelId()), false)) {
logMessage(this->serverId, "{} Client tried to connect to a channel which he hasn't permission for. Channel: {} ({})", CLIENT_STR_LOG_PREFIX_(client), channel->channelId(), channel->name());
channel = nullptr;
} else if (!channel->passwordMatch(client->properties()[property::CLIENT_DEFAULT_CHANNEL_PASSWORD], true) && !permission::v2::permission_granted(1, client->calculate_permission(permission::b_channel_join_ignore_password, channel->channelId()))) {
logMessage(this->serverId, "{} Client tried to connect to a channel which is password protected and he hasn't the right password. Channel: {} ({})", CLIENT_STR_LOG_PREFIX_(client), channel->channelId(), channel->name());
channel = nullptr;
}
} else
logMessage(this->serverId, "{} Client {}/{} tried to join on a not existing channel. Name: {}",
CLIENT_STR_LOG_PREFIX_(client),
client->getDisplayName(), client->getUid(),
client->properties()[property::CLIENT_DEFAULT_CHANNEL].as<std::string>());
}
if(!channel) channel = this->channelTree->getDefaultChannel();
if(!channel) return false;
std::shared_ptr<BasicChannel> channel{};
std::unique_lock server_channel_lock{this->channel_tree_mutex};
auto requested_channel_path = client->properties()[property::CLIENT_DEFAULT_CHANNEL].value();
if(!requested_channel_path.empty()) {
if (requested_channel_path[0] == '/' && requested_channel_path.find_first_not_of("0123456789", 1) == std::string::npos) {
ChannelId channel_id{0};
try {
channel_id = std::stoull(requested_channel_path.substr(1));
} catch (std::exception&) {
logTrace(this->getServerId(), "{} Failed to parse provided channel path as channel id.");
}
if(channel_id > 0) {
channel = this->channelTree->findChannel(channel_id);
}
} else {
channel = this->channelTree->findChannelByPath(requested_channel_path);
}
}
if(channel) {
/* Client proposes a target channel */
auto& channel_whitelist = client->join_whitelisted_channel;
auto whitelist_entry = std::find_if(channel_whitelist.begin(), channel_whitelist.end(), [&](const auto& entry) { return entry.first == channel->channelId(); });
auto client_channel_password = client->properties()[property::CLIENT_DEFAULT_CHANNEL_PASSWORD].value();
if(whitelist_entry != channel_whitelist.end()) {
debugMessage(this->getServerId(), "{} Allowing client to join channel {} because the token he used explicitly allowed it.", client->getLoggingPrefix(), channel->channelId());
if(whitelist_entry->second != "ignore") {
if (!channel->verify_password(std::make_optional(client_channel_password), true)) {
if (!permission::v2::permission_granted(1, client->calculate_permission(permission::b_channel_join_ignore_password, channel->channelId()))) {
channel = nullptr;
goto skip_permissions;
}
}
}
goto skip_permissions;
}
if(!channel->permission_granted(permission::i_channel_needed_join_power, client->calculate_permission(permission::i_channel_join_power, channel->channelId()), false)) {
debugMessage(this->getServerId(), "{} Tried to join channel {} but hasn't enough join power.", client->getLoggingPrefix(), channel->channelId());
channel = nullptr;
goto skip_permissions;
}
if (!channel->verify_password(std::make_optional(client->properties()[property::CLIENT_DEFAULT_CHANNEL_PASSWORD].value()), true)) {
if(!permission::v2::permission_granted(1, client->calculate_permission(permission::b_channel_join_ignore_password, channel->channelId()))) {
debugMessage(this->getServerId(), "{} Tried to join channel {} but hasn't given the right channel password.", client->getLoggingPrefix(), channel->channelId());
channel = nullptr;
goto skip_permissions;
}
}
skip_permissions:;
}
/* Clear these parameters. We don't need them any more after we initially payed attention. */
client->properties()[property::CLIENT_DEFAULT_CHANNEL] = "";
client->properties()[property::CLIENT_DEFAULT_CHANNEL_PASSWORD] = "";
if(!channel) {
/* Client did not propose a channel or the proposed channel got rejected */
channel = this->channelTree->getDefaultChannel();
if(!channel) {
logCritical(this->getServerId(), "Channel tree is missing the default channel.");
return false;
}
}
debugMessage(this->getServerId(), "{} Using channel {} as default client channel.", client->getLoggingPrefix(), channel->channelId());
if(join) {
server_channel_lock.unlock();
unique_lock server_channel_w_lock(this->channel_tree_lock);
this->client_move(client, channel, nullptr, "", ViewReasonId::VREASON_USER_ACTION, false, server_channel_w_lock);
this->client_move(client, channel, nullptr, "", ViewReasonId::VREASON_USER_ACTION, false, server_channel_lock);
} else {
client->currentChannel = channel;
}
return true;
}
@@ -220,51 +265,17 @@ void VirtualServer::testBanStateChange(const std::shared_ptr<ConnectedClient>& i
});
}
bool VirtualServer::could_default_create_channel() {
{
auto default_group = this->getGroupManager()->defaultGroup(GroupTarget::GROUPTARGET_SERVER);
if(default_group) {
auto flag = default_group->permissions()->permission_value_flagged(permission::b_channel_create_temporary).value == 1;
flag = flag ? flag : default_group->permissions()->permission_value_flagged(permission::b_channel_create_semi_permanent).value == 1;
flag = flag ? flag : default_group->permissions()->permission_value_flagged(permission::b_channel_create_permanent).value == 1;
if(flag)
return true;
}
}
{
auto default_group = this->getGroupManager()->defaultGroup(GroupTarget::GROUPTARGET_CHANNEL);
if(default_group) {
auto flag = default_group->permissions()->permission_value_flagged(permission::b_channel_create_temporary).value == 1;
flag = flag ? flag : default_group->permissions()->permission_value_flagged(permission::b_channel_create_semi_permanent).value == 1;
flag = flag ? flag : default_group->permissions()->permission_value_flagged(permission::b_channel_create_permanent).value == 1;
if(flag)
return true;
}
}
return false;
}
/*
*
for (auto &cl : this->server->getClients())
if (cl->isClientVisible(client) || client == cl)
cl->notifyClientLeftViewKicked(client, client->currentChannel, nullptr, cmd["reasonmsg"].as<std::string>(), this);
*/
void VirtualServer::notify_client_ban(const shared_ptr<ConnectedClient> &target, const std::shared_ptr<ts::server::ConnectedClient> &invoker, const std::string &reason, size_t time) {
/* the target is not allowed to execute anything; Must before channel tree lock because the target may waits for us to finish the channel stuff */
lock_guard command_lock(target->command_lock);
unique_lock server_channel_lock(this->channel_tree_lock); /* we're "moving" a client! */
unique_lock server_channel_lock(this->channel_tree_mutex); /* we're "moving" a client! */
if(target->currentChannel) {
for(const auto& client : this->getClients()) {
if(!client || client == target)
continue;
unique_lock client_channel_lock(client->channel_lock);
unique_lock client_channel_lock(client->channel_tree_mutex);
if(client->isClientVisible(target, false))
client->notifyClientLeftViewBanned(target, reason, invoker, time, false);
}
@@ -274,7 +285,7 @@ void VirtualServer::notify_client_ban(const shared_ptr<ConnectedClient> &target,
}
/* now disconnect the target itself */
unique_lock client_channel_lock(target->channel_lock);
unique_lock client_channel_lock(target->channel_tree_mutex);
target->notifyClientLeftViewBanned(target, reason, invoker, time, false);
target->currentChannel = nullptr;
}
@@ -287,29 +298,32 @@ void VirtualServer::notify_client_kick(
if(target_channel) {
/* use the move! */
unique_lock server_channel_lock(this->channel_tree_lock, defer_lock);
unique_lock server_channel_lock(this->channel_tree_mutex, defer_lock);
this->client_move(target, target_channel, invoker, reason, ViewReasonId::VREASON_CHANNEL_KICK, true, server_channel_lock);
} else {
/* the target is not allowed to execute anything; Must before channel tree lock because the target may waits for us to finish the channel stuff */
lock_guard command_lock(target->command_lock);
unique_lock server_channel_lock(this->channel_tree_lock); /* we're "moving" a client! */
unique_lock server_channel_lock(this->channel_tree_mutex); /* we're "moving" a client! */
if(target->currentChannel) {
for(const auto& client : this->getClients()) {
if(!client || client == target)
continue;
unique_lock client_channel_lock(client->channel_lock);
unique_lock client_channel_lock(client->channel_tree_mutex);
if(client->isClientVisible(target, false))
client->notifyClientLeftViewKicked(target, nullptr, reason, invoker, false);
}
auto s_channel = dynamic_pointer_cast<ServerChannel>(target->currentChannel);
s_channel->unregister_client(target);
if(auto client{dynamic_pointer_cast<SpeakingClient>(target)}; client) {
this->rtc_server().assign_channel(client->rtc_client_id, 0);
}
}
/* now disconnect the target itself */
unique_lock client_channel_lock(target->channel_lock);
unique_lock client_channel_lock(target->channel_tree_mutex);
target->notifyClientLeftViewKicked(target, nullptr, reason, invoker, false);
target->currentChannel = nullptr;
}
@@ -323,11 +337,14 @@ void VirtualServer::notify_client_kick(
*
* Note: channel cant be a ref because the channel itself gets deleted!
*/
void VirtualServer::delete_channel(shared_ptr<ts::ServerChannel> channel, const shared_ptr<ConnectedClient> &invoker, const std::string& kick_message, unique_lock<std::shared_mutex> &tree_lock) {
if(!tree_lock.owns_lock())
void VirtualServer::delete_channel(shared_ptr<ts::ServerChannel> channel, const shared_ptr<ConnectedClient> &invoker, const std::string& kick_message, unique_lock<std::shared_mutex> &tree_lock, bool temp_delete) {
if(!tree_lock.owns_lock()) {
tree_lock.lock();
if(channel->deleted)
}
if(channel->deleted) {
return;
}
deque<std::shared_ptr<ConnectedClient>> clients;
{
@@ -344,29 +361,61 @@ void VirtualServer::delete_channel(shared_ptr<ts::ServerChannel> channel, const
channel->deleted = true;
}
auto default_channel = this->channelTree->getDefaultChannel();
tree_lock.unlock();
deque<unique_lock<threads::Mutex>> command_locks;
for(const auto& client : clients) {
command_locks.push_back(move(unique_lock(client->command_lock)));
}
for(const auto& client : clients)
for(const auto& client : clients) {
this->client_move(client, default_channel, invoker, kick_message, ViewReasonId::VREASON_CHANNEL_KICK, true, tree_lock);
}
if(!tree_lock.owns_lock())
tree_lock.lock(); /* no clients left within that tree */
if(!tree_lock.owns_lock()) {
/* This case should never happen. client_move should never unlock the tree lock! */
tree_lock.lock();
}
command_locks.clear();
auto channel_ids = this->channelTree->delete_channel_root(channel);
auto deleted_channels = this->channelTree->delete_channel_root(channel);
log::ChannelDeleteReason delete_reason{temp_delete ? log::ChannelDeleteReason::EMPTY : log::ChannelDeleteReason::USER_ACTION};
for(const auto& deleted_channel : deleted_channels) {
serverInstance->action_logger()->channel_logger.log_channel_delete(this->serverId, invoker, deleted_channel->channelId(), channel == deleted_channel ? delete_reason : log::ChannelDeleteReason::PARENT_DELETED);
}
this->forEachClient([&](const shared_ptr<ConnectedClient>& client) {
unique_lock client_channel_lock(client->channel_lock);
client->notifyChannelDeleted(client->channels->delete_channel_root(channel), invoker);
unique_lock client_channel_lock(client->channel_tree_mutex);
client->notifyChannelDeleted(client->channel_tree->delete_channel_root(channel), invoker);
});
{
std::vector<ChannelId> deleted_channel_ids{};
deleted_channel_ids.reserve(deleted_channels.size());
for(const auto& deleted_channel : deleted_channels) {
deleted_channel_ids.push_back(deleted_channel->channelId());
}
auto ref_self = this->ref();
task_id task_id{};
serverInstance->general_task_executor()->schedule(task_id, "database cleanup after channel delete", [ref_self, deleted_channel_ids]{
for(const auto& deleted_channel_id : deleted_channel_ids) {
ref_self->tokenManager->handle_channel_deleted(deleted_channel_id);
}
for(const auto& deleted_channel_id : deleted_channel_ids) {
ref_self->group_manager()->assignments().handle_channel_deleted(deleted_channel_id);
}
});
}
}
/*
* This method had previously owned the clients command lock but that's not really needed.
* Everything which is related to the server channel tree or the client channel tree should be locked with
* the appropriate mutexes.
*/
void VirtualServer::client_move(
const shared_ptr<ts::server::ConnectedClient> &target,
const shared_ptr<ts::server::ConnectedClient> &target_client,
shared_ptr<ts::BasicChannel> target_channel,
const std::shared_ptr<ts::server::ConnectedClient> &invoker,
const std::string &reason_message,
@@ -375,171 +424,183 @@ void VirtualServer::client_move(
std::unique_lock<std::shared_mutex> &server_channel_write_lock) {
TIMING_START(timings);
if(server_channel_write_lock.owns_lock())
server_channel_write_lock.unlock();
if(!server_channel_write_lock.owns_lock()) {
server_channel_write_lock.lock();
}
lock_guard client_command_lock(target->command_lock);
server_channel_write_lock.lock();
TIMING_STEP(timings, "chan tree l");
if(target->currentChannel == target_channel)
if(target_client->currentChannel == target_channel) {
return;
}
/* first step: resolve the target channel / or fix missing */
/* first step: verify thew source and target channel */
auto s_target_channel = dynamic_pointer_cast<ServerChannel>(target_channel);
auto s_source_channel = dynamic_pointer_cast<ServerChannel>(target->currentChannel);
assert(!target->currentChannel || s_source_channel != nullptr);
auto s_source_channel = dynamic_pointer_cast<ServerChannel>(target_client->currentChannel);
assert(!target_client->currentChannel || s_source_channel != nullptr);
deque<property::ClientProperties> client_updates;
std::deque<property::ClientProperties> changed_groups{};
std::deque<property::ClientProperties> updated_client_properties{};
if(target_channel) {
assert(s_target_channel);
if(s_target_channel->deleted) {
target_channel = this->channelTree->getDefaultChannel();
s_target_channel = dynamic_pointer_cast<ServerChannel>(target_channel);
assert(s_target_channel);
return;
}
/* update the group properties here already, so for all enter views we could just send the new props directly */
changed_groups = this->groups->update_server_group_property(target, true, s_target_channel);
client_updates.insert(client_updates.end(), changed_groups.begin(), changed_groups.end()); //TODO: Only update for clients which have no enter?
}
auto l_target_channel = s_target_channel ? this->channelTree->findLinkedChannel(s_target_channel->channelId()) : nullptr;
auto l_source_channel = s_source_channel ? this->channelTree->findLinkedChannel(s_source_channel->channelId()) : nullptr;
TIMING_STEP(timings, "channel res");
/* second step: show the target channel to the client if its not shown and let him subscibe to the channel */
/* second step: show the target channel to the client if its not shown and let him subscribe to the channel */
if(target_channel && notify_client) {
unique_lock client_channel_lock(target->channel_lock);
std::unique_lock client_channel_lock{target_client->channel_tree_mutex};
bool success = false;
bool success{false};
/* TODO: Use a bunk here and not a notify for every single */
for(const auto& channel : target->channels->show_channel(l_target_channel, success))
target->notifyChannelShow(channel->channel(), channel->previous_channel);
sassert(success);
if(!success)
return;
for(const auto& channel : target_client->channel_tree->show_channel(l_target_channel, success)) {
target_client->notifyChannelShow(channel->channel(), channel->previous_channel);
}
target->subscribeChannel({target_channel}, false, true);
sassert(success);
if(!success) {
return;
}
target_client->subscribeChannel({ target_channel }, false, true);
}
TIMING_STEP(timings, "target show");
if(s_source_channel) {
s_source_channel->unregister_client(target_client);
}
if(target_channel) {
this->forEachClient([&](const shared_ptr<ConnectedClient>& client) {
if (!notify_client && client == target) return;
ClientPermissionCalculator target_client_permissions{&*target_client, target_channel};
auto needed_view_power = target_client_permissions.calculate_permission(permission::i_client_needed_serverquery_view_power);
unique_lock client_channel_lock(client->channel_lock);
auto chan_target = client->channels->find_channel(target_channel);
/* ct_... is for client channel tree */
this->forEachClient([&](const std::shared_ptr<ConnectedClient>& client) {
if (!notify_client && client == target_client) {
return;
}
if(chan_target) {
auto chan_source = client->channels->find_channel(s_source_channel);
if(chan_source) {
if (chan_target->subscribed || client == target) {
if (client == target || client->isClientVisible(target, false)) {
client->notifyClientMoved(target, s_target_channel, reason_id, reason_message, invoker, false);
bool move_target_client_visible{true};
if(target_client->getType() == ClientType::CLIENT_QUERY) {
auto query_view_power = client->calculate_permission(permission::i_client_serverquery_view_power, target_channel->channelId());
move_target_client_visible = permission::v2::permission_granted(needed_view_power, query_view_power);
}
std::unique_lock client_channel_lock{client->channel_tree_mutex};
auto ct_target_channel = move_target_client_visible ? client->channel_tree->find_channel(target_channel) : nullptr;
if(ct_target_channel) {
auto ct_source_channel = client->channel_tree->find_channel(s_source_channel);
if(ct_source_channel) {
/* Source and target channel are visible for the client. Just a "normal" move. */
if (ct_target_channel->subscribed || client == target_client) {
if (client == target_client || client->isClientVisible(target_client, false)) {
client->notifyClientMoved(target_client, s_target_channel, reason_id, reason_message, invoker, false);
} else {
client->notifyClientEnterView(target, invoker, reason_message, s_target_channel, reason_id, s_source_channel, false);
client->notifyClientEnterView(target_client, invoker, reason_message, s_target_channel, reason_id, s_source_channel, false);
}
} else if(client->isClientVisible(target, false)){
//Client got out of view
client->notifyClientLeftView(target, s_target_channel, reason_id, reason_message.empty() ? string("view left") : reason_message, invoker, false);
} else if(client->isClientVisible(target_client, false)){
/* Client has been moved into an unsubscribed channel */
client->notifyClientLeftView(target_client, s_target_channel, reason_id, reason_message.empty() ? string("view left") : reason_message, invoker, false);
}
} else {
if(client == target && client->getType() != ClientType::CLIENT_INTERNAL && client->getType() != ClientType::CLIENT_MUSIC)
logCritical(this->getServerId(), "{} Client enters visibility twice!", CLIENT_STR_LOG_PREFIX_(client));
//Client entered view
if(chan_target->subscribed)
client->notifyClientEnterView(target, invoker, reason_message, s_target_channel, ViewReasonId::VREASON_USER_ACTION, nullptr, false);
} else if(ct_target_channel->subscribed) {
/* Target client entered the view from an invisible channel */
client->notifyClientEnterView(target_client, invoker, reason_message, s_target_channel, ViewReasonId::VREASON_USER_ACTION, nullptr, false);
}
} else {
/* target channel isn't visible => so client gone out of view */
if(client == target && client->getType() != ClientType::CLIENT_INTERNAL && client->getType() != ClientType::CLIENT_MUSIC)
logCritical(this->getServerId(), "{} Moving own client into a not visible channel! This shall not happen!", CLIENT_STR_LOG_PREFIX_(client));
//Test for in view? (Notify already does but nvm)
if(client->isClientVisible(target, false)){
//Client got out of view
if(reason_id == ViewReasonId::VREASON_USER_ACTION)
client->notifyClientLeftView(target, nullptr, ViewReasonId::VREASON_SERVER_LEFT, reason_message.empty() ? "joined a hidden channel" : reason_message, invoker, false);
else
client->notifyClientLeftView(target, nullptr, ViewReasonId::VREASON_SERVER_LEFT, reason_message.empty() ? "moved to a hidden channel" : reason_message, invoker, false);
if(client->isClientVisible(target_client, false)) {
/* Client has been moved out of view into an invisible channel */
if(reason_id == ViewReasonId::VREASON_USER_ACTION) {
client->notifyClientLeftView(target_client, nullptr, ViewReasonId::VREASON_SERVER_LEFT, reason_message.empty() ? "joined a hidden channel" : reason_message, invoker, false);
} else {
client->notifyClientLeftView(target_client, nullptr, ViewReasonId::VREASON_SERVER_LEFT, reason_message.empty() ? "moved to a hidden channel" : reason_message, invoker, false);
}
}
}
});
if(s_source_channel)
s_source_channel->unregister_client(target);
s_target_channel->register_client(target);
s_target_channel->register_client(target_client);
if(auto client{dynamic_pointer_cast<SpeakingClient>(target_client)}; client) {
this->rtc_server().assign_channel(client->rtc_client_id, s_target_channel->rtc_channel_id);
}
if(auto client{dynamic_pointer_cast<VoiceClient>(target_client)}; client) {
/* Start normal broadcasting, what the client expects */
this->rtc_server().start_broadcast_audio(client->rtc_client_id, 1);
client->clear_video_unsupported_message_flag();
}
} else {
/* client left the server */
if(target->currentChannel) {
if(target_client->currentChannel) {
for(const auto& client : this->getClients()) {
if(!client || client == target)
if(!client || client == target_client)
continue;
unique_lock client_channel_lock(client->channel_lock);
if(client->isClientVisible(target, false))
client->notifyClientLeftView(target, nullptr, reason_id, reason_message, invoker, false);
unique_lock client_channel_lock(client->channel_tree_mutex);
if(client->isClientVisible(target_client, false)) {
client->notifyClientLeftView(target_client, nullptr, reason_id, reason_message, invoker, false);
}
}
s_source_channel->unregister_client(target);
if(auto client{dynamic_pointer_cast<SpeakingClient>(target_client)}; client) {
this->rtc_server().assign_channel(client->rtc_client_id, 0);
}
}
}
TIMING_STEP(timings, "notify view");
target->currentChannel = target_channel;
server_channel_write_lock.unlock();
target_client->currentChannel = target_channel;
/* third step: update stuff for the client (remember: the client cant execute anything at the moment!) */
shared_lock server_channel_read_lock(this->channel_tree_lock);
unique_lock client_channel_lock(target->channel_lock);
unique_lock client_channel_lock{target_client->channel_tree_mutex};
TIMING_STEP(timings, "lock own tr");
if (s_source_channel) {
s_source_channel->properties()[property::CHANNEL_LAST_LEFT] = chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now().time_since_epoch()).count();
auto source_channel_group = this->groups->getChannelGroupExact(target->getClientDatabaseId(), s_source_channel, false);
if(source_channel_group) {
auto default_data = source_channel_group->group->permissions()->permission_value_flagged(permission::b_group_is_permanent);
if(!default_data.has_value || default_data.value != 1)
this->groups->setChannelGroup(target->getClientDatabaseId(), nullptr, s_source_channel);
s_source_channel->properties()[property::CHANNEL_LAST_LEFT] = std::chrono::duration_cast<chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
this->group_manager()->assignments().cleanup_temporary_channel_assignment(target_client->getClientDatabaseId(), s_source_channel->channelId());
if(target_client->properties()[property::CLIENT_IS_TALKER].update_value("0")) {
updated_client_properties.push_back(property::CLIENT_IS_TALKER);
}
auto update = target->properties()[property::CLIENT_IS_TALKER].as<bool>() || target->properties()[property::CLIENT_TALK_REQUEST].as<int64_t>() > 0;
if(update) {
target->properties()[property::CLIENT_IS_TALKER] = 0;
target->properties()[property::CLIENT_TALK_REQUEST] = 0;
target->properties()[property::CLIENT_TALK_REQUEST_MSG] = "";
client_updates.push_back(property::CLIENT_IS_TALKER);
client_updates.push_back(property::CLIENT_TALK_REQUEST);
client_updates.push_back(property::CLIENT_TALK_REQUEST_MSG);
if(target_client->properties()[property::CLIENT_TALK_REQUEST].update_value("0")) {
updated_client_properties.push_back(property::CLIENT_TALK_REQUEST);
}
if(target_client->properties()[property::CLIENT_TALK_REQUEST_MSG].update_value("")) {
updated_client_properties.push_back(property::CLIENT_TALK_REQUEST_MSG);
}
TIMING_STEP(timings, "src chan up");
}
if (s_target_channel) {
if(target->update_cached_permissions()) /* update cached calculated permissions */
target->sendNeededPermissions(false);
target_client->task_update_needed_permissions.enqueue();
target_client->task_update_displayed_groups.enqueue();
TIMING_STEP(timings, "perm gr upd");
if(s_source_channel) {
deque<ChannelId> deleted;
for(const auto& channel : target->channels->test_channel(l_source_channel, l_target_channel))
for(const auto& channel : target_client->channel_tree->test_channel(l_source_channel, l_target_channel)) {
deleted.push_back(channel->channelId());
}
if(!deleted.empty())
target->notifyChannelHide(deleted, false);
if(!deleted.empty()) {
target_client->notifyChannelHide(deleted, false);
}
auto i_source_channel = s_source_channel->channelId();
if(std::find(deleted.begin(), deleted.end(), i_source_channel) == deleted.end()) {
auto source_channel_sub_power = target->calculate_permission(permission::i_channel_subscribe_power, i_source_channel);
auto source_channel_sub_power = target_client->calculate_permission(permission::i_channel_subscribe_power, i_source_channel);
if(!s_source_channel->permission_granted(permission::i_channel_needed_subscribe_power, source_channel_sub_power, false)) {
auto source_channel_sub_power_ignore = target->calculate_permission(permission::b_channel_ignore_subscribe_power, i_source_channel);
if(!permission::v2::permission_granted(1, source_channel_sub_power_ignore, true)) {
auto source_channel_sub_power_ignore = target_client->calculate_permission(permission::b_channel_ignore_subscribe_power, i_source_channel);
if(!permission::v2::permission_granted(1, source_channel_sub_power_ignore)) {
logTrace(this->serverId, "Force unsubscribing of client {} for channel {}/{}. (Channel switch and no permissions)",
CLIENT_STR_LOG_PREFIX_(target), s_source_channel->name(),
CLIENT_STR_LOG_PREFIX_(target_client), s_source_channel->name(),
i_source_channel
);
target->unsubscribeChannel({s_source_channel}, false); //Unsubscribe last channel (hasn't permissions)
target_client->unsubscribeChannel({ s_source_channel }, false); //Unsubscribe last channel (hasn't permissions)
}
}
}
@@ -547,12 +608,13 @@ void VirtualServer::client_move(
}
}
client_channel_lock.unlock();
/* both methods lock if they require stuff */
this->notifyClientPropertyUpdates(target, client_updates, s_source_channel ? true : false);
this->notifyClientPropertyUpdates(target_client, updated_client_properties, s_source_channel ? true : false);
TIMING_STEP(timings, "notify cpro");
if(s_target_channel) {
target->updateChannelClientProperties(false, s_source_channel ? true : false);
target_client->updateChannelClientProperties(false, s_source_channel ? true : false);
TIMING_STEP(timings, "notify_t_pr");
}
debugMessage(this->getServerId(), "{} Client move timings: {}", CLIENT_STR_LOG_PREFIX_(target), TIMING_FINISH(timings));
debugMessage(this->getServerId(), "{} Client move timings: {}", CLIENT_STR_LOG_PREFIX_(target_client), TIMING_FINISH(timings));
}
+94 -101
View File
@@ -6,6 +6,7 @@
#include "VirtualServer.h"
#include "./manager/ConversationManager.h"
#include "./music/MusicBotManager.h"
#include "./groups/GroupManager.h"
using namespace std;
using namespace std::chrono;
@@ -16,10 +17,10 @@ using namespace ts::buffer;
inline void banClientFlood(VirtualServer* server, const shared_ptr<ConnectedClient>& cl, time_point<system_clock> until){
auto time = until.time_since_epoch().count() == 0 ? 0L : chrono::ceil<chrono::seconds>(until - system_clock::now()).count();
std::string reason = "You're flooding too much";
std::string reason{"You're flooding too much"};
serverInstance->banManager()->registerBan(server->getServerId(), cl->getClientDatabaseId(), reason, cl->getUid(), cl->getLoggingPeerIp(), "", "", until);
for(const auto &client : server->findClientsByUid(cl->getUid())) {
for(const auto &client : server->findClientsByUid(cl->getUid())) {
server->notify_client_ban(client, server->getServerRoot(), reason, time);
client->close_connection(system_clock::now() + seconds(1));
}
@@ -31,29 +32,27 @@ timing_end = system_clock::now(); \
variable = duration_cast<decltype(variable)>(timing_end - timing_begin);
void VirtualServer::executeServerTick() {
threads::MutexTryLock l(this->stateLock); //Should not attempt to shutdown or start the server while ticking
if(!l) {
if(this->running())
logError(this->getServerId(), "Failed to lock tick mutex!");
std::shared_lock state_lock{this->state_mutex};
if(!this->running()) {
return;
}
if(!this->running()) return;
auto tick_timestamp = std::chrono::system_clock::now();
try {
if(lastTick.time_since_epoch().count() != 0) {
auto delay = system_clock::now() - lastTick;
auto delay_ms = duration_cast<milliseconds>(delay).count();
if(this->lastTick.time_since_epoch().count() > 0) {
auto delay = tick_timestamp - this->lastTick;
auto delay_ms = std::chrono::duration_cast<std::chrono::milliseconds>(delay).count();
if(delay_ms > 510) {
if(delay_ms < 750) {
logWarning(this->getServerId(),
"Found varianzes within the server tick! (Supposed: 500ms Hold: {}ms)", delay_ms);
"Found variances within the server tick! (Supposed: 500ms Hold: {}ms)", delay_ms);
} else {
logError(this->getServerId(),
"Found varianzes within the server tick! This long delay could be an issue. (Supposed: 500ms Hold: {}ms)", delay_ms);
"Found variances within the server tick! This long delay could be an issue. (Supposed: 500ms Hold: {}ms)", delay_ms);
}
}
}
lastTick = system_clock::now();
this->lastTick = tick_timestamp;
system_clock::time_point timing_begin, timing_end;
milliseconds timing_update_states, timing_client_tick, timing_channel, timing_statistic, timing_groups, timing_ccache, music_manager;
@@ -63,40 +62,62 @@ void VirtualServer::executeServerTick() {
{
BEGIN_TIMINGS();
size_t clientOnline = 0;
size_t queryOnline = 0;
size_t clientOnline{0};
size_t queryOnline{0};
for(const auto& conn : client_list){
switch (conn->getType()){
switch(conn->connectionState()) {
case ConnectionState::CONNECTED:
case ConnectionState::INIT_HIGH:
break;
case ConnectionState::INIT_LOW:
case ConnectionState::DISCONNECTING:
case ConnectionState::DISCONNECTED:
continue;
case ConnectionState::UNKNWON:
default:
assert(false);
continue;
}
switch (conn->getType()) {
case ClientType::CLIENT_TEAMSPEAK:
case ClientType::CLIENT_TEASPEAK:
case ClientType::CLIENT_WEB:
clientOnline++;
break;
case ClientType::CLIENT_QUERY:
case ClientType::CLIENT_MUSIC:
queryOnline++;
break;
case ClientType::CLIENT_INTERNAL:
case ClientType::MAX:
default:
break;
}
}
properties()[property::VIRTUALSERVER_UPTIME] = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now() - this->startTimestamp).count();
properties()[property::VIRTUALSERVER_UPTIME] = std::chrono::duration_cast<std::chrono::seconds>(tick_timestamp - this->startTimestamp).count();
properties()[property::VIRTUALSERVER_CLIENTS_ONLINE] = clientOnline + queryOnline;
properties()[property::VIRTUALSERVER_QUERYCLIENTS_ONLINE] = queryOnline;
if(clientOnline + queryOnline == 0) //We don't need to tick, when server is empty!
if(clientOnline + queryOnline == 0) {
//We don't need to tick, when server is empty!
return;
properties()[property::VIRTUALSERVER_CHANNELS_ONLINE] = this->channelTree->channel_count();
properties()[property::VIRTUALSERVER_TOTAL_PING] = this->averagePing();
}
properties()[property::VIRTUALSERVER_CHANNELS_ONLINE] = this->channelTree->channel_count();
properties()[property::VIRTUALSERVER_TOTAL_PING] = this->generate_network_report().average_ping;
END_TIMINGS(timing_update_states);
}
{
BEGIN_TIMINGS();
auto flood_decrease = this->properties()[property::VIRTUALSERVER_ANTIFLOOD_POINTS_TICK_REDUCE].as<FloodPoints>();
auto flood_block = this->properties()[property::VIRTUALSERVER_ANTIFLOOD_POINTS_NEEDED_IP_BLOCK].as<FloodPoints>();
auto flood_decrease = this->properties()[property::VIRTUALSERVER_ANTIFLOOD_POINTS_TICK_REDUCE].as_or<FloodPoints>(0);
auto flood_block = this->properties()[property::VIRTUALSERVER_ANTIFLOOD_POINTS_NEEDED_IP_BLOCK].as_or<FloodPoints>(0);
bool flag_update_spoken = this->spoken_time_timestamp + seconds(30) < system_clock::now();
@@ -105,37 +126,34 @@ void VirtualServer::executeServerTick() {
tick_client_begin = tick_client_end;
if(cl->server != this) {
logError(this->getServerId(), "Got registered client, but client does not think hes bound to this server!");
{
lock_guard lock(this->clients.lock);
for(auto& client : this->clients.clients) {
if(client != cl) continue;
client.reset();
this->clients.count--;
break;
}
}
continue; //Fully ha?
std::unique_lock tree_lock{this->channel_tree_mutex};
this->unregisterClient(cl, "invalid server handle", tree_lock);
continue;
}
if(cl->floodPoints > flood_block){
if(!cl->ignoresFlood()) {
banClientFlood(this, cl, system_clock::now() + minutes(10));
continue;
}
}
if(cl->floodPoints > flood_decrease)
if(cl->floodPoints > flood_decrease) {
cl->floodPoints -= flood_decrease;
else cl->floodPoints = 0;
cl->tick(tick_client_end);
} else {
cl->floodPoints = 0;
}
cl->tick_server(tick_client_end);
auto voice = dynamic_pointer_cast<SpeakingClient>(cl);
if(flag_update_spoken && voice)
if(flag_update_spoken && voice) {
this->spoken_time += voice->takeSpokenTime();
}
tick_client_end = system_clock::now();
auto passed_time = tick_client_end - tick_client_begin;
if(passed_time > microseconds(2500)) {
if(passed_time > milliseconds(10)) {
if(passed_time > microseconds{2500}) {
if(passed_time > milliseconds{10}) {
logError(this->serverId, "Ticking of client {:1} ({:2}) needs more that 2500 microseconds! ({:3} microseconds)",
cl->getLoggingPeerIp() + ":" + to_string(cl->getPeerPort()),
cl->getDisplayName(),
@@ -150,15 +168,18 @@ void VirtualServer::executeServerTick() {
}
}
if(cl->clientPermissions->require_db_updates()) {
auto client_permissions = cl->clientPermissions;
if(client_permissions->require_db_updates()) {
auto begin = system_clock::now();
serverInstance->databaseHelper()->saveClientPermissions(this->ref(), cl->getClientDatabaseId(), cl->clientPermissions);
serverInstance->databaseHelper()->saveClientPermissions(this->ref(), cl->getClientDatabaseId(), client_permissions);
auto end = system_clock::now();
debugMessage(this->serverId, "Saved client permissions for client {} ({}) in {}ms", cl->getClientDatabaseId(), cl->getDisplayName(), duration_cast<milliseconds>(end - begin).count());
}
}
if(flag_update_spoken)
if(flag_update_spoken) {
this->spoken_time_timestamp = system_clock::now();
}
END_TIMINGS(timing_client_tick);
}
@@ -167,31 +188,33 @@ void VirtualServer::executeServerTick() {
{
BEGIN_TIMINGS();
unique_lock channel_lock(this->channel_tree_lock);
std::unique_lock channel_lock{this->channel_tree_mutex};
auto channels = this->channelTree->channels();
channel_lock.unlock();
for(const auto& channel : channels) {
auto server_channel = dynamic_pointer_cast<ServerChannel>(channel);
assert(server_channel);
for(const auto& channel : this->channelTree->channels()){
if(channel->channelType() == ChannelType::temporary) {
auto server_channel = dynamic_pointer_cast<ServerChannel>(channel);
assert(server_channel);
if(server_channel->client_count() > 0 || !this->getClientsByChannelRoot(channel, true).empty())
if(server_channel->client_count() > 0 || !this->isChannelRootEmpty(channel, false)) {
continue;
}
seconds deleteTimeout(0);
if(channel->properties().hasProperty(property::CHANNEL_DELETE_DELAY))
deleteTimeout = seconds(channel->properties()[property::CHANNEL_DELETE_DELAY].as<uint64_t>());
/* seconds */
auto channel_delete_timeout = channel->properties()[property::CHANNEL_DELETE_DELAY].as_or<uint64_t>(0);
auto empty_seconds = channel->empty_seconds();
auto last_left = time_point<system_clock>() + milliseconds(channel->properties()[property::CHANNEL_LAST_LEFT].as<int64_t>());
auto channel_created = channel->createdTimestamp();
if(empty_seconds > channel_delete_timeout) {
this->delete_channel(server_channel, this->serverRoot, "temporary auto delete", channel_lock, true);
if(!channel_lock.owns_lock()) {
channel_lock.lock();
}
if(system_clock::now() - last_left < deleteTimeout) continue; //One second stay
if(system_clock::now() - channel_created < deleteTimeout + seconds(1)) continue; //One second stay
this->delete_channel(server_channel, this->serverRoot, "temporary autodelete", channel_lock);
if(channel_lock.owns_lock())
channel_lock.unlock();
/* no need to tick the channel any more since it has been deleted */
continue;
}
}
{
auto permission_manager = channel->permissions();
if(permission_manager->require_db_updates()) {
@@ -209,34 +232,15 @@ void VirtualServer::executeServerTick() {
{
BEGIN_TIMINGS();
this->serverStatistics->tick();
if(fileStatisticsTimestamp + seconds(5) < system_clock::now()) {
fileStatisticsTimestamp = system_clock::now();
auto update = this->serverStatistics->mark_file_bytes();
if(update.first > 0) {
this->properties()[property::VIRTUALSERVER_MONTH_BYTES_DOWNLOADED] += update.first;
this->properties()[property::VIRTUALSERVER_TOTAL_BYTES_DOWNLOADED] += update.first;
}
if(update.second > 0) {
this->properties()[property::VIRTUALSERVER_MONTH_BYTES_UPLOADED] += update.second;
this->properties()[property::VIRTUALSERVER_TOTAL_BYTES_UPLOADED] += update.second;
}
}
this->server_statistics_->tick();
{
lock_guard<threads::Mutex> lock(this->join_attempts_lock);
if(system_clock::now() > this->join_last_decrease + seconds(5)) {
for(auto& elm : this->join_attempts)
if(elm.second > 0) elm.second--;
lock_guard lock{this->join_attempts_lock};
if(tick_timestamp > this->join_last_decrease + seconds(5)) {
std::erase_if(this->join_attempts, [](auto& entry) {
return --entry.second <= 0;
});
auto copy = this->join_attempts;
for(const auto& elm : copy)
if(elm.second == 0){
auto found = find(this->join_attempts.begin(), this->join_attempts.end(), elm);
if(found != this->join_attempts.end()) this->join_attempts.erase(found);
}
this->join_last_decrease = system_clock::now();
this->join_last_decrease = tick_timestamp;
}
}
END_TIMINGS(timing_statistic);
@@ -244,18 +248,7 @@ void VirtualServer::executeServerTick() {
{
BEGIN_TIMINGS();
auto groups = this->getGroupManager()->availableGroups(false);
for(auto& group : groups) {
auto permissions = group->permissions();
if(permissions->require_db_updates()) {
auto begin = system_clock::now();
serverInstance->databaseHelper()->saveGroupPermissions(this->ref(), group->groupId(), permissions);
auto end = system_clock::now();
debugMessage(this->serverId, "Saved group permissions for group {} ({}) in {}ms", group->groupId(), group->name(), duration_cast<milliseconds>(end - begin).count());
}
}
this->group_manager()->save_permissions();
END_TIMINGS(timing_groups);
}
@@ -263,7 +256,7 @@ void VirtualServer::executeServerTick() {
BEGIN_TIMINGS();
if(this->conversation_cache_cleanup_timestamp + minutes(15) < system_clock::now()) {
debugMessage(this->serverId, "Cleaning up conversation cache.");
this->_conversation_manager->cleanup_cache();
this->conversation_manager_->cleanup_cache();
conversation_cache_cleanup_timestamp = system_clock::now();
}
END_TIMINGS(timing_ccache);
@@ -271,13 +264,13 @@ void VirtualServer::executeServerTick() {
{
BEGIN_TIMINGS();
this->musicManager->execute_tick();
this->music_manager_->execute_tick();
END_TIMINGS(music_manager);
}
if(system_clock::now() - lastTick > milliseconds(100)) {
//milliseconds timing_update_states, timing_client_tick, timing_channel, timing_statistic;
logError(this->serverId, "Server tick tooks to long ({}ms => Status updates: {}ms Client tick: {}ms, Channel tick: {}ms, Statistic tick: {}ms, Groups: {}ms, Conversation cache: {}ms)",
logError(this->serverId, "Server tick took to long ({}ms => Status updates: {}ms Client tick: {}ms, Channel tick: {}ms, Statistic tick: {}ms, Groups: {}ms, Conversation cache: {}ms)",
duration_cast<milliseconds>(system_clock::now() - lastTick).count(),
timing_update_states.count(),
timing_client_tick.count(),
File diff suppressed because it is too large Load Diff

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