279 Commits

Author SHA1 Message Date
WolverinDEV 92a3faaed7 Some small bug fixes 2021-03-07 21:25:55 +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 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
227 changed files with 29981 additions and 19780 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
+4 -1
View File
@@ -4,7 +4,10 @@
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
+7 -3
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)
@@ -61,6 +62,8 @@ 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/)
@@ -103,4 +106,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;
+42
View File
@@ -0,0 +1,42 @@
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
)
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;
};
}
+420
View File
@@ -0,0 +1,420 @@
#pragma once
#include <string>
#include <chrono>
#include <Definitions.h>
#include <condition_variable>
#include <utility>
#include <variant>
#include <deque>
#include <functional>
#include <atomic>
#include "./ExecuteResponse.h"
#define TRANSFER_KEY_LENGTH (32)
#define TRANSFER_MEDIA_BYTES_LENGTH (32)
namespace ts::server::file {
class VirtualFileServer;
namespace filesystem {
template <typename ErrorCodes>
struct DetailedError {
ErrorCodes error_type{ErrorCodes::UNKNOWN};
std::string error_message{};
DetailedError(ErrorCodes type, std::string extraMessage) : error_type{type}, error_message{std::move(extraMessage)} {}
};
enum struct DirectoryQueryErrorType {
UNKNOWN,
PATH_EXCEEDS_ROOT_PATH,
PATH_IS_A_FILE,
PATH_DOES_NOT_EXISTS,
FAILED_TO_LIST_FILES
};
constexpr std::array<std::string_view, 5> directory_query_error_messages = {
"unknown error",
"path exceeds base path",
"path is a file",
"path does not exists",
"failed to list files"
};
typedef DetailedError<DirectoryQueryErrorType> DirectoryQueryError;
struct DirectoryEntry {
enum Type {
UNKNOWN,
DIRECTORY,
FILE
};
Type type{Type::UNKNOWN};
std::string name{};
std::chrono::system_clock::time_point modified_at{};
size_t size{0}; /* file only */
bool empty{false}; /* directory only */
};
enum struct DirectoryModifyErrorType {
UNKNOWN,
PATH_EXCEEDS_ROOT_PATH,
PATH_ALREADY_EXISTS,
FAILED_TO_CREATE_DIRECTORIES
};
typedef DetailedError<DirectoryModifyErrorType> DirectoryModifyError;
enum struct FileModifyErrorType {
UNKNOWN,
PATH_EXCEEDS_ROOT_PATH,
TARGET_PATH_EXCEEDS_ROOT_PATH,
PATH_DOES_NOT_EXISTS,
TARGET_PATH_ALREADY_EXISTS,
FAILED_TO_DELETE_FILES,
FAILED_TO_RENAME_FILE,
FAILED_TO_CREATE_DIRECTORIES,
SOME_FILES_ARE_LOCKED
};
typedef DetailedError<FileModifyErrorType> FileModifyError;
enum struct FileDeleteErrorType {
UNKNOWN,
};
typedef DetailedError<FileDeleteErrorType> FileDeleteError;
struct FileDeleteResponse {
enum struct StatusType {
SUCCESS,
PATH_EXCEEDS_ROOT_PATH,
PATH_DOES_NOT_EXISTS,
FAILED_TO_DELETE_FILES,
SOME_FILES_ARE_LOCKED
};
struct DeleteResult {
StatusType status{StatusType::SUCCESS};
std::string error_detail{};
DeleteResult(StatusType status, std::string errorDetail) : status{status},
error_detail{std::move(errorDetail)} {}
};
std::vector<DeleteResult> delete_results{};
};
enum struct ServerCommandErrorType {
UNKNOWN,
FAILED_TO_CREATE_DIRECTORIES,
FAILED_TO_DELETE_DIRECTORIES
};
typedef DetailedError<ServerCommandErrorType> ServerCommandError;
struct FileInfoResponse {
enum struct StatusType {
SUCCESS,
PATH_EXCEEDS_ROOT_PATH,
PATH_DOES_NOT_EXISTS,
FAILED_TO_QUERY_INFO,
UNKNOWN_FILE_TYPE
};
struct FileInfo {
StatusType status{StatusType::SUCCESS};
std::string error_detail{};
DirectoryEntry info{};
FileInfo(StatusType status, std::string errorDetail, DirectoryEntry info) : status{status},
error_detail{std::move(errorDetail)}, info{std::move(info)} {}
};
std::vector<FileInfo> file_info{};
};
enum struct FileInfoErrorType {
UNKNOWN,
};
typedef DetailedError<FileInfoErrorType> FileInfoError;
class AbstractProvider {
public:
typedef ExecuteResponse<DirectoryQueryError, std::deque<DirectoryEntry>> directory_query_response_t;
/* server */
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<ServerCommandError>> initialize_server(const std::shared_ptr<VirtualFileServer> &/* server */) = 0;
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<ServerCommandError>> delete_server(const std::shared_ptr<VirtualFileServer> &/* server */) = 0;
/* channels */
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> query_channel_info(const std::shared_ptr<VirtualFileServer> &/* server */, const std::vector<std::tuple<ChannelId, std::string>>& /* files */) = 0;
[[nodiscard]] virtual std::shared_ptr<directory_query_response_t> query_channel_directory(const std::shared_ptr<VirtualFileServer> &/* server */, ChannelId /* channel */, const std::string& /* path */) = 0;
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<DirectoryModifyError>> create_channel_directory(const std::shared_ptr<VirtualFileServer> &/* server */, ChannelId /* channel */, const std::string& /* path */) = 0;
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> delete_channel_files(const std::shared_ptr<VirtualFileServer> &/* server */, ChannelId /* channel */, const std::vector<std::string>& /* paths */) = 0;
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileModifyError>> rename_channel_file(const std::shared_ptr<VirtualFileServer> &/* server */, ChannelId /* channel */, const std::string& /* path */, ChannelId /* target channel */, const std::string& /* target */) = 0;
/* icons */
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> query_icon_info(const std::shared_ptr<VirtualFileServer> &/* server */, const std::vector<std::string>& /* names */) = 0;
[[nodiscard]] virtual std::shared_ptr<directory_query_response_t> query_icon_directory(const std::shared_ptr<VirtualFileServer> &/* server */) = 0;
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> delete_icons(const std::shared_ptr<VirtualFileServer> &/* server */, const std::vector<std::string>& /* names */) = 0;
/* avatars */
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> query_avatar_info(const std::shared_ptr<VirtualFileServer> &/* server */, const std::vector<std::string>& /* names */) = 0;
[[nodiscard]] virtual std::shared_ptr<directory_query_response_t> query_avatar_directory(const std::shared_ptr<VirtualFileServer> &/* server */) = 0;
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> delete_avatars(const std::shared_ptr<VirtualFileServer> &/* server */, const std::vector<std::string>& /* names */) = 0;
private:
};
}
namespace transfer {
typedef uint16_t transfer_id;
struct Transfer {
transfer_id server_transfer_id{0};
transfer_id client_transfer_id{0};
std::shared_ptr<VirtualFileServer> server{nullptr};
ChannelId channel_id{0};
ClientId client_id{0};
std::string client_unique_id{};
char transfer_key[TRANSFER_KEY_LENGTH]{};
std::chrono::system_clock::time_point initialized_timestamp{};
enum Direction {
DIRECTION_UNKNOWN,
DIRECTION_UPLOAD,
DIRECTION_DOWNLOAD
} direction{DIRECTION_UNKNOWN};
struct Address {
std::string hostname{};
uint16_t port{0};
};
std::vector<Address> server_addresses{};
enum TargetType {
TARGET_TYPE_UNKNOWN,
TARGET_TYPE_CHANNEL_FILE,
TARGET_TYPE_ICON,
TARGET_TYPE_AVATAR
} target_type{TARGET_TYPE_UNKNOWN};
std::string target_file_path{};
std::string absolute_file_path{};
std::string relative_file_path{};
std::string file_name{};
int64_t max_bandwidth{-1};
size_t expected_file_size{0}; /* incl. the offset! */
size_t file_offset{0};
bool override_exiting{false};
};
struct TransferStatistics {
uint64_t network_bytes_send{0};
uint64_t network_bytes_received{0};
uint64_t delta_network_bytes_send{0};
uint64_t delta_network_bytes_received{0};
uint64_t file_bytes_transferred{0};
uint64_t delta_file_bytes_transferred{0};
size_t file_start_offset{0};
size_t file_current_offset{0};
size_t file_total_size{0};
double average_speed{0};
double current_speed{0};
};
struct TransferInitError {
enum Type {
UNKNOWN,
INVALID_FILE_TYPE,
FILE_DOES_NOT_EXISTS,
FILE_IS_NOT_A_FILE,
CLIENT_TOO_MANY_TRANSFERS,
SERVER_TOO_MANY_TRANSFERS,
SERVER_QUOTA_EXCEEDED,
CLIENT_QUOTA_EXCEEDED,
IO_ERROR
} error_type{UNKNOWN};
std::string error_message{};
TransferInitError(Type errorType, std::string errorMessage) : error_type{errorType},
error_message{std::move(errorMessage)} {}
};
struct TransferActionError {
enum Type {
UNKNOWN,
UNKNOWN_TRANSFER
} error_type{UNKNOWN};
std::string error_message{};
};
struct TransferError {
enum Type {
UNKNOWN,
TRANSFER_TIMEOUT,
DISK_IO_ERROR,
DISK_TIMEOUT,
DISK_INITIALIZE_ERROR,
NETWORK_IO_ERROR,
UNEXPECTED_CLIENT_DISCONNECT,
UNEXPECTED_DISK_EOF,
USER_REQUEST
} error_type{UNKNOWN};
std::string error_message{};
};
struct ActiveFileTransfer {
transfer_id client_transfer_id{0};
transfer_id server_transfer_id{0};
Transfer::Direction direction{Transfer::DIRECTION_UNKNOWN};
ClientId client_id{};
std::string client_unique_id{};
std::string file_path{};
std::string file_name{};
size_t expected_size{};
size_t size_done{};
enum Status {
NOT_STARTED,
RUNNING,
INACTIVE /* (not used yet) */
} status{Status::NOT_STARTED};
std::chrono::milliseconds runtime{};
double average_speed{0};
double current_speed{0};
};
enum struct TransferListError {
UNKNOWN
};
class AbstractProvider {
public:
struct TransferInfo {
std::string file_path{};
std::string client_unique_id{};
ClientId client_id{};
bool override_exiting{false}; /* only for upload valid */
size_t file_offset{0};
size_t expected_file_size{0};
int64_t max_bandwidth{-1};
int64_t max_concurrent_transfers{-1};
/* only used for upload, for download the quotas could be checked before */
size_t download_server_quota_limit{(size_t) -1};
size_t download_client_quota_limit{(size_t) -1};
};
virtual std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
initialize_channel_transfer(Transfer::Direction /* direction */, const std::shared_ptr<VirtualFileServer>& /* server */, ChannelId /* channel */, const TransferInfo& /* info */) = 0;
virtual std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
initialize_icon_transfer(Transfer::Direction /* direction */, const std::shared_ptr<VirtualFileServer>& /* server */, const TransferInfo& /* info */) = 0;
virtual std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
initialize_avatar_transfer(Transfer::Direction /* direction */, const std::shared_ptr<VirtualFileServer>& /* server */, const TransferInfo& /* info */) = 0;
virtual std::shared_ptr<ExecuteResponse<TransferListError, std::vector<ActiveFileTransfer>>> list_transfer() = 0;
virtual std::shared_ptr<ExecuteResponse<TransferActionError>> stop_transfer(const std::shared_ptr<VirtualFileServer>& /* server */, transfer_id /* id */, bool /* flush */) = 0;
std::function<void(const std::shared_ptr<Transfer>&)> callback_transfer_registered{}; /* transfer has been registered */
std::function<void(const std::shared_ptr<Transfer>&)> callback_transfer_started{}; /* transfer has been started */
std::function<void(const std::shared_ptr<Transfer>&)> callback_transfer_finished{}; /* transfer has been finished */
std::function<void(const std::shared_ptr<Transfer>&, const TransferStatistics&)> callback_transfer_statistics{};
std::function<void(const std::shared_ptr<Transfer>&, const transfer::TransferStatistics&, const TransferError&)> callback_transfer_aborted{}; /* an error happened while transferring the data */
};
}
class VirtualFileServer {
public:
explicit VirtualFileServer(ServerId server_id, std::string unique_id) : server_id_{server_id}, unique_id_{std::move(unique_id)} {}
[[nodiscard]] inline auto unique_id() const -> const std::string& { return this->unique_id_; }
[[nodiscard]] inline auto server_id() const -> ServerId { return this->server_id_; }
[[nodiscard]] inline auto max_networking_upload_bandwidth() const -> int64_t { return this->max_networking_upload_bandwidth_; }
virtual void max_networking_upload_bandwidth(int64_t value) {
this->max_networking_upload_bandwidth_ = value;
}
[[nodiscard]] inline auto max_networking_download_bandwidth() const -> int64_t { return this->max_networking_download_bandwidth_; }
virtual void max_networking_download_bandwidth(int64_t value) {
this->max_networking_download_bandwidth_ = value;
}
[[nodiscard]] inline auto generate_transfer_id() {
return ++this->current_transfer_id;
}
private:
ServerId server_id_;
std::string unique_id_;
int64_t max_networking_upload_bandwidth_{-1};
int64_t max_networking_download_bandwidth_{-1};
std::atomic<transfer::transfer_id> current_transfer_id{0};
};
class AbstractFileServer {
public:
[[nodiscard]] virtual std::string file_base_path() const = 0;
[[nodiscard]] virtual filesystem::AbstractProvider& file_system() = 0;
[[nodiscard]] virtual transfer::AbstractProvider& file_transfer() = 0;
[[nodiscard]] inline auto virtual_servers() const -> std::deque<std::shared_ptr<VirtualFileServer>> {
std::lock_guard slock{this->servers_mutex};
return this->servers_;
}
[[nodiscard]] inline auto find_virtual_server(ServerId server_id) const -> std::shared_ptr<VirtualFileServer> {
std::lock_guard slock{this->servers_mutex};
auto it = std::find_if(this->servers_.begin(), this->servers_.end(), [&](const std::shared_ptr<VirtualFileServer>& server) {
return server->server_id() == server_id;
});
return it == this->servers_.end() ? nullptr : *it;
}
virtual std::shared_ptr<VirtualFileServer> register_server(ServerId /* server id */) = 0;
virtual void unregister_server(ServerId /* server id */) = 0;
protected:
mutable std::mutex servers_mutex{};
std::deque<std::shared_ptr<VirtualFileServer>> servers_{};
};
extern bool initialize(std::string& /* error */, const std::string& /* host names */, uint16_t /* port */);
extern void finalize();
extern std::shared_ptr<AbstractFileServer> server();
}
+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 */);
}
+148
View File
@@ -0,0 +1,148 @@
//
// Created by WolverinDEV on 28/04/2020.
//
#include <netinet/in.h>
#include <log/LogUtils.h>
#include "LocalFileProvider.h"
#include "LocalFileSystem.h"
#include "LocalFileTransfer.h"
using namespace ts::server;
using LocalFileServer = file::LocalFileProvider;
using LocalVirtualFileServer = file::LocalVirtualFileServer;
std::shared_ptr<LocalFileServer> server_instance{};
bool file::initialize(std::string &error, const std::string& hostnames, uint16_t port) {
server_instance = std::make_shared<LocalFileProvider>();
if(!server_instance->initialize(error)) {
server_instance = nullptr;
return false;
}
bool any_bind{false};
for(const auto& binding : net::resolve_bindings(hostnames, port)) {
if(!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) {
auto server_unique_id = std::to_string(server_id);
std::lock_guard slock{this->servers_mutex};
auto it = std::find_if(this->servers_.begin(), this->servers_.end(), [&](const std::shared_ptr<VirtualFileServer>& server) {
return server->unique_id() == server_unique_id;
});
if(it == this->servers_.end()) return;
this->servers_.erase(it);
}
void LocalVirtualFileServer::max_networking_upload_bandwidth(int64_t value) {
VirtualFileServer::max_networking_upload_bandwidth(value);
this->upload_throttle.set_max_bandwidth(value);
}
void LocalVirtualFileServer::max_networking_download_bandwidth(int64_t value) {
VirtualFileServer::max_networking_download_bandwidth(value);
this->download_throttle.set_max_bandwidth(value);
}
+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 */) 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 */) override;
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>>
query_channel_info(const std::shared_ptr<VirtualFileServer> & /* server */, const std::vector<std::tuple<ChannelId, std::string>>& /* names */) override;
std::shared_ptr<directory_query_response_t>
query_channel_directory(const std::shared_ptr<VirtualFileServer> & id, ChannelId channelId, const std::string &string) override;
std::shared_ptr<ExecuteResponse<DirectoryModifyError>>
create_channel_directory(const std::shared_ptr<VirtualFileServer> & id, ChannelId channelId, const std::string &string) override;
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>>
delete_channel_files(const std::shared_ptr<VirtualFileServer> & id, ChannelId channelId, const std::vector<std::string> &string) override;
std::shared_ptr<ExecuteResponse<FileModifyError>>
rename_channel_file(const std::shared_ptr<VirtualFileServer> & id, ChannelId channelId, const std::string &, ChannelId, const std::string &) override;
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>>
query_icon_info(const std::shared_ptr<VirtualFileServer> & /* server */, const std::vector<std::string>& /* names */) override;
std::shared_ptr<directory_query_response_t> query_icon_directory(const std::shared_ptr<VirtualFileServer> & id) override;
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>>
delete_icons(const std::shared_ptr<VirtualFileServer> & id, const std::vector<std::string> &string) override;
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>>
query_avatar_info(const std::shared_ptr<VirtualFileServer> & /* server */, const std::vector<std::string>& /* names */) override;
std::shared_ptr<directory_query_response_t> query_avatar_directory(const std::shared_ptr<VirtualFileServer> & id) override;
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>>
delete_avatars(const std::shared_ptr<VirtualFileServer> & id, const std::vector<std::string> &string) override;
private:
#ifdef FS_INCLUDED
[[nodiscard]] fs::path server_path(const std::shared_ptr<VirtualFileServer> &);
[[nodiscard]] fs::path server_channel_path(const std::shared_ptr<VirtualFileServer> &, ChannelId);
[[nodiscard]] static bool exceeds_base_path(const fs::path& /* base */, const fs::path& /* target */);
[[nodiscard]] bool is_any_file_locked(const fs::path& /* base */, const std::string& /* path */, std::string& /* file (relative to the base) */);
[[nodiscard]] std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>>
delete_files(const fs::path& /* base */, const std::vector<std::string> &string);
[[nodiscard]] std::shared_ptr<directory_query_response_t>
query_directory(const fs::path& /* base */, const std::string &string, bool);
[[nodiscard]] std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>>
query_file_info(const std::vector<std::tuple<fs::path, std::string>> &string);
#endif
template <typename error_t, typename result_t = EmptyExecuteResponse>
std::shared_ptr<ExecuteResponse<error_t, result_t>> create_execute_response() {
return std::make_shared<ExecuteResponse<error_t, result_t>>(this->result_notify_mutex, this->result_notify_cv);
}
std::string target_file_path(FileCategory type, const std::shared_ptr<VirtualFileServer> &sid, ts::ChannelId cid, const std::string &path);
std::mutex result_notify_mutex{};
std::condition_variable result_notify_cv{};
std::string root_path_{};
std::mutex locked_files_mutex{};
std::deque<std::string> locked_files_{};
};
}
+393
View File
@@ -0,0 +1,393 @@
//
// 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"};
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);
}
+590
View File
@@ -0,0 +1,590 @@
//
// 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!
+15 -1
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,6 +26,8 @@ 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`
@@ -34,6 +36,18 @@ using namespace license;
*
* 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;
+1 -1
View File
@@ -533,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},
+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");
+18 -10
View File
@@ -297,17 +297,21 @@ void WebStatistics::handleEventWrite(int file_descriptor, short, void* ptr_serve
}
std::lock_guard<std::recursive_mutex> lock(client->execute_lock);
std::unique_lock elock(client->execute_lock);
if(client->buffer_write.empty()) return;
auto& buffer = client->buffer_write.front();
auto written = send(file_descriptor, buffer.data(), buffer.length(), MSG_DONTWAIT | MSG_NOSIGNAL);
if(written < 0){
elock.unlock();
if(errno == EWOULDBLOCK) return;
logError(LOG_LICENSE_WEB, "[{}] Invalid write: {}/{}. Closing connection.", client->client_prefix(), errno, strerror(errno));
server->close_connection(client);
return;
} else if(written == 0) {
elock.unlock();
logError(LOG_LICENSE_WEB, "[{}] Invalid write (eof). Closing connection", client->client_prefix());
server->close_connection(client);
return;
@@ -334,17 +338,20 @@ void WebStatistics::close_connection(const std::shared_ptr<license::web::WebStat
else; //TODO Error handling?
}
std::lock_guard<std::recursive_mutex> lock(client->execute_lock);
if(client->event_read) {
event_del(client->event_read);
event_free(client->event_read);
client->event_read = nullptr;
std::unique_lock elock(client->execute_lock);
auto event_read = std::exchange(client->event_read, nullptr);
auto event_write = std::exchange(client->event_write, nullptr);
elock.unlock();
if(event_read) {
event_del(event_read);
event_free(event_read);
}
if(client->event_write) {
event_del(client->event_write);
event_free(client->event_write);
client->event_write = nullptr;
if(event_write) {
event_del(event_write);
event_free(event_write);
}
elock.lock();
if(client->file_descriptor > 0) {
if(shutdown(client->file_descriptor, SHUT_RDWR) < 0); //TODO error handling
if(close(client->file_descriptor) < 0); //TODO error handling
@@ -353,6 +360,7 @@ void WebStatistics::close_connection(const std::shared_ptr<license::web::WebStat
if(client->pipe_websocket)
client->pipe_websocket = nullptr;
if(client->pipe_ssl) {
client->pipe_ssl->finalize();
client->pipe_ssl = nullptr;
-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,
+2 -2
View File
@@ -348,7 +348,7 @@ namespace license {
bool deleted{false};
uint32_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: ad24c38923...a4dabbb5c4
Submodule
+1
Submodule rtclib added at cdf42fccd3
+76 -62
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")
@@ -43,37 +43,32 @@ set(SERVER_SOURCE_FILES
# 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/POWHandler.cpp
src/client/voice/VoiceClientConnection.cpp
#src/client/ConnectedClientCommandHandler.cpp
src/client/command_handler/channel.cpp
src/client/command_handler/client.cpp
src/client/command_handler/server.cpp
src/client/command_handler/misc.cpp
src/client/command_handler/bulk_parsers.cpp
src/client/ConnectedClientNotifyHandler.cpp
src/VirtualServerManager.cpp
src/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
@@ -81,7 +76,6 @@ set(SERVER_SOURCE_FILES
src/client/query/QueryClientCommands.cpp
src/client/query/QueryClientNotify.cpp
src/manager/IpListManager.cpp
src/ConnectionStatistics.cpp
@@ -96,7 +90,6 @@ 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
@@ -130,15 +123,37 @@ 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/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)
@@ -147,51 +162,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
@@ -234,8 +216,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 "2")
if (BUILD_TYPE_NAME EQUAL OFF)
SET(CPACK_PACKAGE_VERSION_DATA "beta")
elseif (BUILD_TYPE_NAME STREQUAL "")
@@ -257,6 +239,7 @@ target_link_libraries(TeaSpeakServer
TeaLicenseHelper #Static
TeaMusic #Static
CXXTerminal::static #Static
TeaSpeak-FileServer
${StringVariable_LIBRARIES_STATIC}
${YAML_CPP_LIBRARIES}
pthread
@@ -267,22 +250,33 @@ 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/)
target_link_libraries(TeaSpeakServer
@@ -307,4 +301,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!
+204 -23
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 = "Fore 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]");
@@ -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
+3 -39
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; }
@@ -49,11 +44,6 @@ cp "${library_path}" . || { echo "failed to copy libssl.so.1.1"; exit 1; }
query_system_link "libcrypto.so.1.1"
cp "${library_path}" . || { echo "failed to copy libcrypto.so.1.1"; 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"
# Setting up Sqlite3
query_system_link "libsqlite3.so.0"
cp "${library_path}" . || { echo "failed to copy libsqlite3.so.0"; exit 1; }
@@ -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})"
+210 -69
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,8 +121,7 @@ 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::voice::events_per_server;
size_t config::threads::voice::io_min;
size_t config::threads::voice::io_per_server;
@@ -115,8 +136,12 @@ 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 +348,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 +463,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 +500,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 +509,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 +584,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 +1033,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 +1047,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 +1127,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 +1154,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 +1181,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 +1225,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 +1282,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 +1306,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 +1316,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 +1362,65 @@ 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("Chaneg 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");
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");
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 +1428,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 +1466,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)
{
@@ -1426,9 +1548,34 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
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", 0);
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", 0);
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", 0);
BIND_INTEGRAL(config::web::stun_port, 19302, 1, 0xFFFF);
ADD_DESCRIPTION("Port of the stun server");
ADD_NOTE_RELOADABLE();
}
{
CREATE_BINDING("webrtc.udp", 0);
BIND_INTEGRAL(config::web::udp_enabled, true, false, true);
ADD_DESCRIPTION("Enable UDP for theweb client");
ADD_NOTE_RELOADABLE();
}
{
CREATE_BINDING("webrtc.tcp", 0);
BIND_INTEGRAL(config::web::tcp_enabled, true, false, true);
ADD_DESCRIPTION("Enable TCP for theweb client");
ADD_NOTE_RELOADABLE();
}
}
{
@@ -1682,6 +1829,12 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
ADD_DESCRIPTION("Thread pool size for the ticking task of a VirtualServer");
ADD_SENSITIVE();
}
{
CREATE_BINDING("command_execute", 0);
BIND_INTEGRAL(config::threads::command_execute, 4, 1, 128);
ADD_DESCRIPTION("Command executors");
ADD_SENSITIVE();
}
{
BIND_GROUP(voice)
{
@@ -1690,18 +1843,6 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
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);
+43 -6
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,17 +217,22 @@ 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;
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;
+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};
};
}
}
+389 -216
View File
@@ -13,153 +13,165 @@ 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_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_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) */) {
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) */
bool is_channel) {
auto start = system_clock::now();
auto server_id = server ? server->getServerId() : 0;
@@ -213,38 +225,50 @@ inline sql::result load_permissions_v2(const std::shared_ptr<VirtualServer>& ser
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
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<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 std::shared_ptr<VirtualServer>& server, ClientDbId cldbid) {
auto server_id = server ? server->getServerId() : 0;
#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
@@ -282,20 +306,28 @@ std::shared_ptr<v2::PermissionManager> DatabaseHelper::loadClientPermissionManag
variable{":serverId", server ? server->getServerId() : 0},
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, 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 +340,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;
@@ -364,7 +396,7 @@ std::shared_ptr<permission::v2::PermissionManager> DatabaseHelper::loadGroupPerm
variable{":serverId", server ? server->getServerId() : 0},
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, result.get(), command, false, false));
return result;
}
@@ -375,7 +407,7 @@ void DatabaseHelper::saveGroupPermissions(const std::shared_ptr<ts::server::Virt
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;
@@ -436,7 +468,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, result.get(), command, false, false));
return result;
}
@@ -447,7 +479,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 +534,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, result.get(), command, false, true));
return result;
}
@@ -513,7 +545,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,9 +572,9 @@ 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){
std::shared_ptr<PropertyManager> DatabaseHelper::default_properties_client(std::shared_ptr<PropertyManager> properties, ClientType type){
if(!properties)
properties = make_shared<Properties>();
properties = make_shared<PropertyManager>();
properties->register_property_type<property::ClientProperties>();
properties->register_property_type<property::ConnectionProperties>();
@@ -555,47 +587,44 @@ 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());
return true;
}
logTrace(id, "Loaded client from database. Database id: {} Unique id: {}", cl->getClientDatabaseId(), cl->getUid());
return true;
}
@@ -617,8 +646,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 +659,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 +672,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;
@@ -705,7 +734,7 @@ std::shared_ptr<Properties> DatabaseHelper::loadServerProperties(const std::shar
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 +746,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;
@@ -790,9 +819,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 +900,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;
@@ -900,6 +930,7 @@ 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});
@@ -925,23 +956,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,16 +991,62 @@ 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;
}
} else if(prop.type().type_property == property::PROP_TYPE_CONNECTION) {
switch (prop.type().property_index) {
case property::CONNECTION_CLIENT_IP:
column = "client_ip";
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;
@@ -1013,6 +1093,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 +1208,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 +1234,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 +1243,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 +1296,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);
});
}
+101 -99
View File
@@ -8,125 +8,127 @@
#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;
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 std::shared_ptr<VirtualServer>&, 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 std::shared_ptr<VirtualServer>&, GroupId);
void saveGroupPermissions(const std::shared_ptr<VirtualServer>&, GroupId, 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 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 */);
};
}
+218
View File
@@ -0,0 +1,218 @@
//
// 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;
}
file::config::ssl_option_supplier = [&]{
return this->instance_->sslManager()->web_ssl_options();
};
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&);
};
}
+105 -53
View File
@@ -6,7 +6,6 @@
#include "VirtualServer.h"
#include "src/client/ConnectedClient.h"
#include "InstanceHandler.h"
#include "src/server/file/FileServer.h"
using namespace std;
using namespace std::chrono;
@@ -19,7 +18,7 @@ Group::Group(GroupManager* handle, GroupTarget target, GroupType type, GroupId g
memtrack::allocated<Group>(this);
this->handle = handle;
this->_properties = new Properties();
this->_properties = std::make_shared<PropertyManager>();
this->_properties->register_property_type<property::GroupProperties>();
this->setPermissionManager(make_shared<permission::v2::PermissionManager>());
@@ -84,7 +83,6 @@ void Group::apply_properties_from_permissions() {
}
Group::~Group() {
delete this->_properties;
memtrack::freed<Group>(this);
}
@@ -93,6 +91,7 @@ GroupManager::GroupManager(const shared_ptr<VirtualServer> &server, sql::SqlMana
GroupManager::~GroupManager() {}
bool GroupManager::loadGroupFormDatabase(GroupId id) {
std::lock_guard glock{this->group_lock};
if(id == 0){
this->groups.clear();
@@ -116,8 +115,13 @@ bool GroupManager::loadGroupFormDatabase(GroupId id) {
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);
{
std::lock_guard glock{this->group_lock};
for(const auto& group : this->groups)
response.push_back(group);
}
if(root && this->root){
auto elm = this->root->availableGroups();
for(const auto& e : elm)
@@ -128,9 +132,14 @@ std::vector<std::shared_ptr<Group>> GroupManager::availableGroups(bool root) {
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);
{
std::lock_guard glock{this->group_lock};
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)
@@ -141,9 +150,12 @@ std::vector<std::shared_ptr<Group>> GroupManager::availableServerGroups(bool roo
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);
{
std::lock_guard glock{this->group_lock};
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)
@@ -167,8 +179,7 @@ int GroupManager::insertGroupFromDb(int count, char **values, char **column) {
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;
//else cerr << "Invalid group table row " << column[index] << endl;
}
if((size_t) groupId == 0 || (size_t) target == 0xff || (size_t) type == 0xff || targetName.empty()) {
@@ -217,11 +228,13 @@ int GroupManager::insertGroupFromDb(int count, char **values, char **column) {
debugMessage(this->getServerId(), "Push back group -> " + to_string(group->groupId()) + " - " + group->name());
this->groups.push_back(group);
#if 0
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);
}
#endif
return 0;
}
@@ -237,6 +250,7 @@ void GroupManager::handleChannelDeleted(const ChannelId& channel_id) {
}
bool GroupManager::isLocalGroup(std::shared_ptr<Group> gr) {
std::lock_guard glock{this->group_lock};
return std::find(this->groups.begin(), this->groups.end(), gr) != this->groups.end();
}
@@ -247,14 +261,18 @@ std::shared_ptr<Group> GroupManager::defaultGroup(GroupTarget type, bool enforce
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>();
server->properties()[type == GroupTarget::GROUPTARGET_SERVER ? property::VIRTUALSERVER_DEFAULT_SERVER_GROUP
: property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP].as_or<GroupId>(0) :
serverInstance->properties()[property::SERVERINSTANCE_GUEST_SERVERQUERY_GROUP].as_or<GroupId>(0);
auto group = this->findGroupLocal(id);
if(group || enforce_property) return group;
for(auto elm : this->groups)
if(elm->target() == type)
return elm;
{
std::lock_guard glock{this->group_lock};
for(auto elm : this->groups)
if(elm->target() == type)
return elm;
}
return nullptr; //Worst case!
}
@@ -266,6 +284,7 @@ std::shared_ptr<Group> GroupManager::findGroup(GroupId groupId) {
}
std::shared_ptr<Group> GroupManager::findGroupLocal(GroupId groupId) {
std::lock_guard glock{this->group_lock};
for(const auto& elm : this->groups)
if(elm->groupId() == groupId) return elm;
return nullptr;
@@ -273,8 +292,11 @@ std::shared_ptr<Group> GroupManager::findGroupLocal(GroupId groupId) {
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);
{
std::lock_guard glock{this->group_lock};
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);
@@ -305,6 +327,8 @@ std::shared_ptr<Group> GroupManager::createGroup(GroupTarget target, GroupType t
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()));
std::lock_guard glock{this->group_lock};
this->groups.push_back(group);
return group;
}
@@ -394,7 +418,10 @@ bool GroupManager::deleteGroup(std::shared_ptr<Group> group) {
return false;
}
this->groups.erase(std::find(this->groups.begin(), this->groups.end(), group));
{
std::lock_guard glock{this->group_lock};
this->groups.erase(std::find(this->groups.begin(), this->groups.end(), group));
}
/* erase the group out of our cache */
{
@@ -424,7 +451,8 @@ bool GroupManager::deleteGroup(std::shared_ptr<Group> group) {
LOG_SQL_CMD(res);
flag_sql |= !res;
flag_sql &= serverInstance->databaseHelper()->deleteGroupPermissions(this->server.lock(), group->groupId());
serverInstance->databaseHelper()->deleteGroupArtifacts(this->getServerId(), group->groupId());
if(flag_sql)
logError(this->getServerId(), "Could not delete group {} ({}) from database. May leader to invalid data", group->name(), group->groupId());
@@ -591,41 +619,65 @@ bool GroupManager::isClientCached(const ClientDbId& client_database_id) {
return this->resolve_cached_client(client_database_id) == nullptr;
}
constexpr static auto kGroupMemberListQuery{R"(
SELECT
assignedGroups.cldbid,
clients_server.client_unique_id,
clients_server.client_nickname,
assignedGroups.channelId,
assignedGroups.until
FROM assignedGroups
INNER JOIN clients_server ON
clients_server.client_database_id = assignedGroups.cldbid AND clients_server.server_id = :sid
WHERE assignedGroups.`serverId` = :sid AND `groupId` = :gid;
)"};
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
std::deque<GroupMember> GroupManager::listGroupMembers(std::shared_ptr<Group> group, bool names) {
if(!isLocalGroup(group)){
if(this->root) return this->root->listGroupMembers(group, names);
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;
std::deque<GroupMember> result{};
size_t set_index{0};
sql::command{this->sql, std::string{kGroupMemberListQuery}, variable{":sid", this->getServerId()}, variable{":gid", group->groupId()}}
.query([&](int length, std::string* values, std::string* names) {
set_index++;
auto index{0};
try {
auto& member = result.emplace_back();
assert(names[index] == "cldbid");
member.cldbId = std::stoull(values[index++]);
assert(names[index] == "client_unique_id");
member.uid = values[index++];
assert(names[index] == "client_nickname");
member.displayName = values[index++];
assert(names[index] == "channelId");
member.channelId = std::stoull(values[index++]);
assert(names[index] == "until");
member.until = std::chrono::system_clock::time_point{} + std::chrono::milliseconds{std::stoll(values[index++])};
assert(index == length);
} catch (std::exception& ex) {
result.pop_back();
logError(this->getServerId(), "Failed to parse client group assignment for group {}: {}. Set index: {}, Column: {}",
group->groupId(),
ex.what(),
set_index - 1,
index - 1
);
return;
}
list->push_back(member);
return 0;
}, &result);
});
return result;
}
@@ -769,8 +821,8 @@ std::vector<std::shared_ptr<GroupAssignment>> GroupManager::defaultServerGroupGr
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>();
server->properties()[property::VIRTUALSERVER_DEFAULT_MUSIC_GROUP].as_or<GroupId>(0) :
serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_MUSICDEFAULT_GROUP].as_or<GroupId>(0);
auto group = this->findGroupLocal(id);
if(group) {
result.push_back(std::make_shared<GroupAssignment>(nullptr, this->getServerId(), 0, group, time_point<system_clock>()));
+6 -4
View File
@@ -83,7 +83,7 @@ namespace ts {
~Group();
std::shared_ptr<permission::v2::PermissionManager> permissions(){ return this->_permissions; }
Properties& properties(){ return *this->_properties; }
PropertyManager& properties(){ return *this->_properties; }
GroupNameMode nameMode(){ return (GroupNameMode) (uint8_t) properties()[property::GROUP_NAMEMODE]; }
@@ -137,7 +137,7 @@ namespace ts {
GroupManager* handle;
std::shared_ptr<permission::v2::PermissionManager> _permissions;
Properties* _properties;
std::shared_ptr<PropertyManager> _properties;
GroupTarget _target;
GroupType _type;
};
@@ -198,7 +198,7 @@ namespace ts {
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::deque<GroupMember> listGroupMembers(std::shared_ptr<Group>, bool names = false);
std::vector<std::shared_ptr<GroupAssignment>> listGroupAssignments(ClientDbId client);
void cleanupAssignments(ClientDbId);
@@ -215,7 +215,6 @@ namespace ts {
bool isClientCached(const ClientDbId& /* client database id */);
void clearCache();
bool isLocalGroup(std::shared_ptr<Group>);
protected:
void handleChannelDeleted(const ChannelId& /* channel id */);
@@ -225,7 +224,10 @@ namespace ts {
ServerId getServerId();
sql::SqlManager* sql;
std::mutex group_lock{};
std::vector<std::shared_ptr<Group>> groups;
threads::Mutex cacheLock;
std::vector<std::shared_ptr<CachedClient>> cachedClients;
+106 -106
View File
@@ -3,31 +3,28 @@
#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 <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>
#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;
@@ -40,9 +37,22 @@ using namespace ts::server;
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,12 +120,12 @@ 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());
@@ -122,8 +139,10 @@ 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>());
debugMessage(LOG_INSTANCE, "Instance admin group id " + to_string(
this->properties()[property::SERVERINSTANCE_ADMIN_SERVERQUERY_GROUP].as_or<GroupId>(0)));
auto instance_server_admin = this->groupManager->findGroup(
this->properties()[property::SERVERINSTANCE_ADMIN_SERVERQUERY_GROUP].as_or<GroupId>(0));
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") + ")");
@@ -131,16 +150,20 @@ InstanceHandler::InstanceHandler(SqlDataManager *sql) : sql(sql) {
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>());
debugMessage(LOG_INSTANCE, "Server guest group id " + to_string(
this->properties()[property::SERVERINSTANCE_GUEST_SERVERQUERY_GROUP].as_or<GroupId>(0)));
auto instance_server_guest = this->groupManager->findGroup(
this->properties()[property::SERVERINSTANCE_GUEST_SERVERQUERY_GROUP].as_or<GroupId>(0));
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>());
debugMessage(LOG_INSTANCE, "Server music group id " + to_string(
this->properties()[property::SERVERINSTANCE_TEMPLATE_MUSICDEFAULT_GROUP].as_or<GroupId>(0)));
auto instance_server_music = this->groupManager->findGroup(
this->properties()[property::SERVERINSTANCE_TEMPLATE_MUSICDEFAULT_GROUP].as_or<GroupId>(0));
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") + ")");
@@ -173,8 +196,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,34 +210,16 @@ 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;
@@ -219,7 +228,6 @@ InstanceHandler::~InstanceHandler() {
_musicRoot = nullptr;
statistics = nullptr;
tick_manager = nullptr;
}
inline string strip(std::string message) {
@@ -245,12 +253,14 @@ 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->permission_mapper = make_shared<permission::PermissionNameMapper>();
if(!this->permission_mapper->initialize(config::permission_mapping_file, errorMessage)) {
@@ -282,31 +292,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 +324,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;
@@ -414,7 +404,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 +422,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 +444,31 @@ 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;
}
void InstanceHandler::tickInstance() {
threads::MutexLock lock(this->lock_tick);
if(!this->active) return;
if(!this->active) {
return;
}
auto now = system_clock::now();
@@ -482,16 +488,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 +519,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 +535,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,12 +557,10 @@ 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() {
@@ -651,10 +651,10 @@ std::shared_ptr<ts::server::license::InstanceLicenseInfo> InstanceHandler::gener
request->metrics.web_clients_online = report.clients_web;
request->metrics.bots_online = report.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 +677,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);
}
}
+37 -20
View File
@@ -8,6 +8,7 @@
#include "manager/SqlDataManager.h"
#include "lincense/TeamSpeakLicense.h"
#include "server/WebIoManager.h"
#include <misc/task_executor.h>
namespace ts {
namespace weblist {
@@ -23,6 +24,16 @@ namespace ts {
class LicenseService;
}
namespace file {
class FileServerHandler;
}
namespace log {
class ActionLogger;
}
class ServerCommandExecutor;
class InstanceHandler {
public:
explicit InstanceHandler(SqlDataManager*);
@@ -31,9 +42,8 @@ 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; }
@@ -42,18 +52,16 @@ namespace ts {
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,17 +72,20 @@ 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_; }
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}; }
[[nodiscard]] inline std::shared_ptr<webio::LoopManager> getWebIoLoop() { return this->web_event_loop; }
[[nodiscard]] inline std::shared_ptr<weblist::WebListManager> getWebList() { return this->web_list; }
[[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; }
[[nodiscard]] inline const auto& server_command_executor() { return this->server_command_executor_; }
permission::v2::PermissionFlaggedValue calculate_permission(
permission::PermissionType,
@@ -100,6 +111,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,20 +123,23 @@ 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};
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::PropertyManager> default_server_properties = nullptr;
std::shared_ptr<ts::ServerChannelTree> default_tree = nullptr;
std::shared_mutex default_tree_lock;
std::shared_ptr<ts::GroupManager> groupManager = nullptr;
@@ -133,7 +149,8 @@ namespace ts {
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;
+4 -4
View File
@@ -6,7 +6,6 @@
#include "InstanceHandler.h"
#include "src/client/InternalClient.h"
#include "src/server/QueryServer.h"
#include "src/server/file/FileServer.h"
using namespace std;
using namespace std::chrono;
@@ -129,13 +128,14 @@ bool InstanceHandler::setupDefaultGroups() {
info->target == 0 ? GroupType::GROUP_TYPE_QUERY : GroupType::GROUP_TYPE_TEMPLATE,
info->name
);
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));
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();
+39 -54
View File
@@ -163,6 +163,7 @@ struct SnapshotPermissionEntry {
}
};
#if 0
std::shared_ptr<VirtualServer> VirtualServerManager::createServerFromSnapshot(shared_ptr<VirtualServer> old, std::string host,
uint16_t port, const ts::Command &arguments,
std::string &error) {
@@ -201,7 +202,7 @@ std::shared_ptr<VirtualServer> VirtualServerManager::createServerFromSnapshot(sh
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()){
for(const auto &key : arguments[index].keys()){
if(key == "end_virtualserver") continue;
if(key == "begin_virtualserver") continue;
if(snapshot_version == 0) {
@@ -559,8 +560,8 @@ std::shared_ptr<VirtualServer> VirtualServerManager::createServerFromSnapshot(sh
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) {
const auto& property = property::find<property::ClientProperties>(key);
if(property.is_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;
}
@@ -597,8 +598,8 @@ std::shared_ptr<VirtualServer> VirtualServerManager::createServerFromSnapshot(sh
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) {
const auto& property = property::find<property::ClientProperties>(key);
if(property.is_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;
}
@@ -692,6 +693,7 @@ std::shared_ptr<VirtualServer> VirtualServerManager::createServerFromSnapshot(sh
server->ensureValidDefaultGroups();
return server;
}
#endif
struct CommandTuple {
Command& cmd;
@@ -771,8 +773,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 +782,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 +803,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 +820,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 +884,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 +916,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++;
+3
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();
+48 -12
View File
@@ -1,5 +1,4 @@
#include <breakpad/client/linux/handler/exception_handler.h>
#include "VirtualServer.h"
#include "SignalHandler.h"
#include "VirtualServerManager.h"
@@ -8,16 +7,39 @@
#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);
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());
try {
std::rethrow_exception(std::current_exception());
} catch(std::exception& ex) {
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) {
logCritical(LOG_GENERAL, "The server crashed!");
try {
@@ -31,32 +53,36 @@ static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor,
} catch (...) {
logCritical(LOG_GENERAL, "Failed to write/move crash dump!");
}
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());
try {
std::rethrow_exception(std::current_exception());
} catch(std::exception& ex) {
logCritical(LOG_GENERAL, " Message: {}", ex.what());
} catch(...) {}
}
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 +124,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();
}
}
+106 -43
View File
@@ -6,6 +6,7 @@
#include <misc/timer.h>
#include <log/LogUtils.h>
#include <misc/sassert.h>
#include <src/manager/ActionLogger.h>
#include "InstanceHandler.h"
using namespace std;
@@ -75,12 +76,12 @@ bool VirtualServer::registerClient(shared_ptr<ConnectedClient> client) {
if(client->getType() == ClientType::CLIENT_TEAMSPEAK || client->getType() == ClientType::CLIENT_WEB) {
this->properties()[property::VIRTUALSERVER_CLIENT_CONNECTIONS] ++; //increase manager connections
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();
}
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
this->properties()[property::VIRTUALSERVER_QUERY_CLIENT_CONNECTIONS].increment_by<uint64_t>(1); //increase manager connections
}
return true;
@@ -173,31 +174,74 @@ void VirtualServer::unregisterInternalClient(std::shared_ptr<ConnectedClient> cl
}
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_lock server_channel_lock{this->channel_tree_lock};
std::shared_ptr<BasicChannel> channel{};
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->passwordMatch(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->passwordMatch(client->properties()[property::CLIENT_DEFAULT_CHANNEL_PASSWORD], 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:;
}
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);
@@ -247,13 +291,6 @@ bool VirtualServer::could_default_create_channel() {
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);
@@ -306,6 +343,9 @@ void VirtualServer::notify_client_kick(
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 */
@@ -323,11 +363,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;
{
@@ -358,11 +401,18 @@ void VirtualServer::delete_channel(shared_ptr<ts::ServerChannel> channel, const
tree_lock.lock(); /* no clients left within that tree */
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);
});
this->tokenManager->handle_channel_deleted(channel->channelId());
}
void VirtualServer::client_move(
@@ -375,14 +425,16 @@ void VirtualServer::client_move(
std::unique_lock<std::shared_mutex> &server_channel_write_lock) {
TIMING_START(timings);
if(server_channel_write_lock.owns_lock())
if(server_channel_write_lock.owns_lock()) {
server_channel_write_lock.unlock();
}
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->currentChannel == target_channel) {
return;
}
/* first step: resolve the target channel / or fix missing */
auto s_target_channel = dynamic_pointer_cast<ServerChannel>(target_channel);
@@ -467,9 +519,17 @@ void VirtualServer::client_move(
}
});
if(s_source_channel)
if(s_source_channel) {
s_source_channel->unregister_client(target);
}
s_target_channel->register_client(target);
if(auto client{dynamic_pointer_cast<SpeakingClient>(target)}; 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) {
/* Start normal broadcasting, what the client expects */
this->rtc_server().start_broadcast_audio(client->rtc_client_id, 1);
}
} else {
/* client left the server */
if(target->currentChannel) {
@@ -483,6 +543,9 @@ void VirtualServer::client_move(
}
s_source_channel->unregister_client(target);
if(auto client{dynamic_pointer_cast<SpeakingClient>(target)}; client) {
this->rtc_server().assign_channel(client->rtc_client_id, 0);
}
}
}
TIMING_STEP(timings, "notify view");
@@ -504,7 +567,8 @@ void VirtualServer::client_move(
this->groups->setChannelGroup(target->getClientDatabaseId(), nullptr, s_source_channel);
}
auto update = target->properties()[property::CLIENT_IS_TALKER].as<bool>() || target->properties()[property::CLIENT_TALK_REQUEST].as<int64_t>() > 0;
auto update = target->properties()[property::CLIENT_IS_TALKER].as_or<bool>(false) ||
target->properties()[property::CLIENT_TALK_REQUEST].as_or<int64_t>(0) > 0;
if(update) {
target->properties()[property::CLIENT_IS_TALKER] = 0;
target->properties()[property::CLIENT_TALK_REQUEST] = 0;
@@ -517,8 +581,7 @@ void VirtualServer::client_move(
}
if (s_target_channel) {
if(target->update_cached_permissions()) /* update cached calculated permissions */
target->sendNeededPermissions(false);
target->task_update_needed_permissions.enqueue();
TIMING_STEP(timings, "perm gr upd");
if(s_source_channel) {
+21 -31
View File
@@ -76,6 +76,9 @@ void VirtualServer::executeServerTick() {
case ClientType::CLIENT_MUSIC:
queryOnline++;
break;
case ClientType::CLIENT_INTERNAL:
case ClientType::MAX:
default:
break;
}
@@ -84,19 +87,20 @@ void VirtualServer::executeServerTick() {
properties()[property::VIRTUALSERVER_UPTIME] = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now() - 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_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();
@@ -127,7 +131,7 @@ void VirtualServer::executeServerTick() {
if(cl->floodPoints > flood_decrease)
cl->floodPoints -= flood_decrease;
else cl->floodPoints = 0;
cl->tick(tick_client_end);
cl->tick_server(tick_client_end);
auto voice = dynamic_pointer_cast<SpeakingClient>(cl);
if(flag_update_spoken && voice)
this->spoken_time += voice->takeSpokenTime();
@@ -150,9 +154,10 @@ 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());
}
@@ -171,24 +176,23 @@ void VirtualServer::executeServerTick() {
auto channels = this->channelTree->channels();
channel_lock.unlock();
for(const auto& channel : this->channelTree->channels()){
for(const auto& channel : 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())
continue;
seconds deleteTimeout(0);
if(channel->properties().hasProperty(property::CHANNEL_DELETE_DELAY))
deleteTimeout = seconds(channel->properties()[property::CHANNEL_DELETE_DELAY].as<uint64_t>());
seconds deleteTimeout{channel->properties()[property::CHANNEL_DELETE_DELAY].as_or<uint64_t>(0)};
auto last_left = time_point<system_clock>() + milliseconds(channel->properties()[property::CHANNEL_LAST_LEFT].as<int64_t>());
auto last_left = time_point<system_clock>() + milliseconds(
channel->properties()[property::CHANNEL_LAST_LEFT].as_or<int64_t>(0));
auto channel_created = channel->createdTimestamp();
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);
this->delete_channel(server_channel, this->serverRoot, "temporary auto delete", channel_lock, true);
if(channel_lock.owns_lock())
channel_lock.unlock();
}
@@ -209,21 +213,7 @@ 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)) {
@@ -263,7 +253,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,7 +261,7 @@ void VirtualServer::executeServerTick() {
{
BEGIN_TIMINGS();
this->musicManager->execute_tick();
this->music_manager_->execute_tick();
END_TIMINGS(music_manager);
}
+281 -136
View File
@@ -10,7 +10,8 @@
#include <misc/digest.h>
#include <misc/base64.h>
#include "weblist/WebListManager.h"
#include <files/FileServer.h>
#include "./client/web/WebClient.h"
#include "./client/voice/VoiceClient.h"
#include "./client/InternalClient.h"
@@ -18,13 +19,14 @@
#include "./client/query/QueryClient.h"
#include "music/MusicBotManager.h"
#include "server/VoiceServer.h"
#include "server/file/FileServer.h"
#include "server/QueryServer.h"
#include "InstanceHandler.h"
#include "Configuration.h"
#include "VirtualServer.h"
#include "./rtc/lib.h"
#include "src/manager/ConversationManager.h"
#include <misc/sassert.h>
#include <src/manager/ActionLogger.h>
using namespace std;
using namespace std::chrono;
@@ -39,7 +41,6 @@ using namespace ts::buffer;
#define BUILD_VERSION "Unknown build"
#endif
extern ts::server::InstanceHandler* serverInstance;
VirtualServer::VirtualServer(uint16_t serverId, sql::SqlManager* database) : serverId(serverId), sql(database) {
memtrack::allocated<VirtualServer>(this);
}
@@ -47,17 +48,26 @@ VirtualServer::VirtualServer(uint16_t serverId, sql::SqlManager* database) : ser
bool VirtualServer::initialize(bool test_properties) {
assert(self.lock());
this->rtc_server_ = std::make_unique<rtc::Server>();
this->_properties = serverInstance->databaseHelper()->loadServerProperties(self.lock());
this->_properties->registerNotifyHandler([&](Property& prop){
if(prop.type() == property::VIRTUALSERVER_DISABLE_IP_SAVING) {
this->_disable_ip_saving = prop.as<bool>();
this->_disable_ip_saving = prop.as_or<bool>(false);
return;
}
if(prop.type() == property::VIRTUALSERVER_CODEC_ENCRYPTION_MODE) {
this->_voice_encryption_mode = prop.as<int>();
} else if(prop.type() == property::VIRTUALSERVER_CODEC_ENCRYPTION_MODE) {
this->_voice_encryption_mode = prop.as_or<int>(0);
return;
} else if(prop.type() == property::VIRTUALSERVER_MAX_UPLOAD_TOTAL_BANDWIDTH) {
auto file_vs = file::server()->find_virtual_server(this->getServerId());
if(!file_vs) return;
file_vs->max_networking_upload_bandwidth(prop.as_or<int64_t>(-1));
} else if(prop.type() == property::VIRTUALSERVER_MAX_DOWNLOAD_TOTAL_BANDWIDTH) {
auto file_vs = file::server()->find_virtual_server(this->getServerId());
if(!file_vs) return;
file_vs->max_networking_download_bandwidth(prop.as_or<int64_t>(-1));
}
std::string sql;
std::string sql{};
if(prop.type() == property::VIRTUALSERVER_HOST)
sql = "UPDATE `servers` SET `host` = :value WHERE `serverId` = :sid";
else if(prop.type() == property::VIRTUALSERVER_PORT)
@@ -72,10 +82,65 @@ bool VirtualServer::initialize(bool test_properties) {
this->properties()[property::VIRTUALSERVER_ID] = serverId;
this->_disable_ip_saving = this->properties()[property::VIRTUALSERVER_DISABLE_IP_SAVING];
if(!properties()[property::VIRTUALSERVER_KEYPAIR].as<std::string>().empty()){
/* initialize logging */
{
auto server_id = this->serverId;
auto sync_property = [server_id](Property& prop) {
log::LoggerGroup action_type;
switch (prop.type().property_index) {
case property::VIRTUALSERVER_LOG_SERVER:
action_type = log::LoggerGroup::SERVER;
break;
case property::VIRTUALSERVER_LOG_CHANNEL:
action_type = log::LoggerGroup::CHANNEL;
break;
case property::VIRTUALSERVER_LOG_CLIENT:
action_type = log::LoggerGroup::CLIENT;
break;
case property::VIRTUALSERVER_LOG_FILETRANSFER:
action_type = log::LoggerGroup::FILES;
break;
case property::VIRTUALSERVER_LOG_PERMISSIONS:
action_type = log::LoggerGroup::PERMISSION;
break;
case property::VIRTUALSERVER_LOG_QUERY:
action_type = log::LoggerGroup::QUERY;
break;
default:
return;
}
serverInstance->action_logger()->toggle_logging_group(server_id, action_type,
prop.as_or<bool>(true));
};
for(const property::VirtualServerProperties& property : {
property::VIRTUALSERVER_LOG_SERVER,
property::VIRTUALSERVER_LOG_CHANNEL,
property::VIRTUALSERVER_LOG_CLIENT,
property::VIRTUALSERVER_LOG_FILETRANSFER,
property::VIRTUALSERVER_LOG_QUERY,
property::VIRTUALSERVER_LOG_PERMISSIONS
}) {
auto prop = this->_properties->get(property::PROP_TYPE_SERVER, property);
sync_property(prop);
}
this->_properties->registerNotifyHandler([sync_property](Property& prop){
sync_property(prop);
});
}
if(!properties()[property::VIRTUALSERVER_KEYPAIR].value().empty()){
debugMessage(this->serverId, "Importing server keypair");
this->_serverKey = new ecc_key;
auto bytes = base64::decode(properties()[property::VIRTUALSERVER_KEYPAIR].as<std::string>());
auto bytes = base64::decode(properties()[property::VIRTUALSERVER_KEYPAIR].value());
int err;
if((err = ecc_import(reinterpret_cast<const unsigned char *>(bytes.data()), bytes.length(), this->_serverKey)) != CRYPT_OK){
logError(this->getServerId(), "Cant import key. ({} => {})", err, error_to_string(err));
@@ -117,8 +182,8 @@ bool VirtualServer::initialize(bool test_properties) {
properties()[property::VIRTUALSERVER_UNIQUE_IDENTIFIER] = base64::encode(digest::sha1(base64::encode(buffer, bufferLength)));
}
this->_conversation_manager = make_shared<conversation::ConversationManager>(this->ref());
this->_conversation_manager->initialize(this->_conversation_manager);
this->conversation_manager_ = make_shared<conversation::ConversationManager>(this->ref());
this->conversation_manager_->initialize(this->conversation_manager_);
channelTree = new ServerChannelTree(self.lock(), this->sql);
channelTree->loadChannelsFromDatabase();
@@ -129,7 +194,8 @@ bool VirtualServer::initialize(bool test_properties) {
return false;
}
if(channelTree->channel_count() == 0){
channelTree->deleteSemiPermanentChannels();
if(channelTree->channel_count() == 0) {
logMessage(this->serverId, "Creating new channel tree (Copy from server 0)");
LOG_SQL_CMD(sql::command(this->getSql(), "INSERT INTO `channels` (`serverId`, `channelId`, `type`, `parentId`) SELECT :serverId AS `serverId`, `channelId`, `type`, `parentId` FROM `channels` WHERE `serverId` = 0", variable{":serverId", this->serverId}).execute());
LOG_SQL_CMD(sql::command(this->getSql(), "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) SELECT :serverId AS `serverId`, `type`, `id`, `key`, `value` FROM `properties` WHERE `serverId` = 0 AND `type` = :type",
@@ -141,7 +207,7 @@ bool VirtualServer::initialize(bool test_properties) {
channelTree->loadChannelsFromDatabase();
if(channelTree->channel_count() == 0){
logCritical(this->serverId, "Failed to setup channel tree!");
return 0;
return false;
}
if(!channelTree->getDefaultChannel()) {
logError(this->serverId, "Missing default channel! Using first one!");
@@ -151,18 +217,18 @@ bool VirtualServer::initialize(bool test_properties) {
if(!channelTree->getDefaultChannel()) channelTree->setDefaultChannel(*channelTree->channels().begin());
auto default_channel = channelTree->getDefaultChannel();
assert(default_channel);
if(default_channel->properties()[property::CHANNEL_FLAG_PASSWORD].as<bool>())
if(default_channel->properties()[property::CHANNEL_FLAG_PASSWORD].as_or<bool>(false))
default_channel->properties()[property::CHANNEL_FLAG_PASSWORD] = false;
this->tokenManager = new token::TokenManager(this);
this->tokenManager->loadTokens();
this->tokenManager = new token::TokenManager(this->sql, this->getServerId());
this->tokenManager->initialize_cache();
this->complains = new ComplainManager(this);
if(!this->complains->loadComplains()) logError(this->serverId, "Could not load complains");
//Setup new server if needed
if(this->groups->availableServerGroups(false).empty() || this->groups->availableChannelGroups(false).empty()){
if(!this->properties()[property::VIRTUALSERVER_AUTOGENERATED_PRIVILEGEKEY].as<string>().empty()) {
if(!this->properties()[property::VIRTUALSERVER_AUTOGENERATED_PRIVILEGEKEY].value().empty()) {
logCritical(this->getServerId(), "Missing default groups. Applying permission reset!");
}
string token;
@@ -180,35 +246,67 @@ bool VirtualServer::initialize(bool test_properties) {
letters = new letter::LetterManager(this);
serverStatistics = make_shared<stats::ConnectionStatistics>(serverInstance->getStatistics(), true);
server_statistics_ = make_shared<stats::ConnectionStatistics>(serverInstance->getStatistics());
this->serverRoot = std::make_shared<InternalClient>(this->sql, self.lock(), this->properties()[property::VIRTUALSERVER_NAME].as<string>(), false);
static_pointer_cast<InternalClient>(this->serverRoot)->setSharedLock(this->serverRoot);
this->properties().registerNotifyHandler([&](Property& property) {
if(property.type() == property::VIRTUALSERVER_NAME) static_pointer_cast<InternalClient>(this->serverRoot)->properties()[property::CLIENT_NICKNAME] = property.as<string>();
this->serverRoot = std::make_shared<InternalClient>(this->sql, self.lock(),
this->properties()[property::VIRTUALSERVER_NAME].value(), false);
this->serverRoot->initialize_weak_reference(this->serverRoot);
this->properties()->registerNotifyHandler([&](Property& property) {
if(property.type() == property::VIRTUALSERVER_NAME) {
static_pointer_cast<InternalClient>(this->serverRoot)->properties()[property::CLIENT_NICKNAME] = property.value();
}
});
this->serverRoot->server = nullptr;
this->serverAdmin = std::make_shared<InternalClient>(this->sql, self.lock(), "serveradmin", true);
static_pointer_cast<InternalClient>(this->serverAdmin)->setSharedLock(this->serverAdmin);
this->serverAdmin->initialize_weak_reference(this->serverAdmin);
DatabaseHelper::assignDatabaseId(this->sql, this->serverId, this->serverAdmin);
this->serverAdmin->server = nullptr;
this->registerInternalClient(this->serverAdmin); /* lets assign server id 0 */
if(serverInstance->getFileServer())
serverInstance->getFileServer()->setupServer(self.lock());
{
using ErrorType = file::filesystem::ServerCommandErrorType;
auto file_vs = file::server()->register_server(this->getServerId());
auto initialize_result = file::server()->file_system().initialize_server(file_vs);
if(!initialize_result->wait_for(std::chrono::seconds{5})) {
logError(this->getServerId(), "Failed to wait for file directory initialisation.");
} else if(!initialize_result->succeeded()) {
switch (initialize_result->error().error_type) {
case ErrorType::FAILED_TO_CREATE_DIRECTORIES:
logError(this->getServerId(), "Failed to create server file directories ({}).", initialize_result->error().error_message);
break;
case ErrorType::UNKNOWN:
case ErrorType::FAILED_TO_DELETE_DIRECTORIES:
logError(this->getServerId(), "Failed to initialize server file directory due to an unknown error: {}/{}",
(int) initialize_result->error().error_type, initialize_result->error().error_message);
break;
}
}
this->properties()[property::VIRTUALSERVER_FILEBASE] = file::server()->file_base_path();
file_vs->max_networking_download_bandwidth(
this->properties()[property::VIRTUALSERVER_MAX_DOWNLOAD_TOTAL_BANDWIDTH].as_or<int64_t>(-1));
file_vs->max_networking_upload_bandwidth(
this->properties()[property::VIRTUALSERVER_MAX_UPLOAD_TOTAL_BANDWIDTH].as_or<int64_t>(-1));
}
this->channelTree->printChannelTree([&](std::string msg){ debugMessage(this->serverId, msg); });
this->musicManager = make_shared<music::MusicBotManager>(self.lock());
this->musicManager->_self = this->musicManager;
this->musicManager->load_playlists();
this->musicManager->load_bots();
this->music_manager_ = make_shared<music::MusicBotManager>(self.lock());
this->music_manager_->_self = this->music_manager_;
this->music_manager_->load_playlists();
this->music_manager_->load_bots();
#if 0
if(this->properties()[property::VIRTUALSERVER_ICON_ID] != (IconId) 0)
if(!serverInstance->getFileServer()->iconExists(self.lock(), this->properties()[property::VIRTUALSERVER_ICON_ID])) {
debugMessage(this->getServerId(), "Removing invalid icon id of server");
this->properties()[property::VIRTUALSERVER_ICON_ID] = 0;
}
}
#endif
for(const auto& type : vector<property::VirtualServerProperties>{
property::VIRTUALSERVER_DOWNLOAD_QUOTA,
@@ -216,20 +314,18 @@ bool VirtualServer::initialize(bool test_properties) {
property::VIRTUALSERVER_MAX_UPLOAD_TOTAL_BANDWIDTH,
property::VIRTUALSERVER_MAX_DOWNLOAD_TOTAL_BANDWIDTH,
}) {
auto info = property::impl::info(type);
const auto& info = property::describe(type);
auto prop = this->properties()[type];
if(prop.default_value() == prop.value()) continue;
if(!info->validate_input(this->properties()[type].value())) {
this->properties()[type] = info->default_value;
logMessage(this->getServerId(), "Server property " + info->name + " contains an invalid value! Resetting it.");
if(!info.validate_input(this->properties()[type].value())) {
this->properties()[type] = info.default_value;
logMessage(this->getServerId(), "Server property " + std::string{info.name} + " contains an invalid value! Resetting it.");
}
}
if(this->properties()[property::VIRTUALSERVER_FILEBASE].value().empty())
this->properties()[property::VIRTUALSERVER_FILEBASE] = serverInstance->getFileServer()->server_file_base(self.lock());
/* lets cleanup the conversations for not existent channels */
this->_conversation_manager->synchronize_channels();
this->conversation_manager_->synchronize_channels();
return true;
}
@@ -240,7 +336,7 @@ VirtualServer::~VirtualServer() {
delete this->channelTree;
delete this->letters;
delete this->complains;
this->_conversation_manager.reset();
this->conversation_manager_.reset();
if(this->_serverKey) ecc_free(this->_serverKey);
delete this->_serverKey;
@@ -313,7 +409,7 @@ bool VirtualServer::start(std::string& error) {
}
}
auto host = this->properties()[property::VIRTUALSERVER_HOST].as<string>();
auto host = this->properties()[property::VIRTUALSERVER_HOST].value();
if(config::binding::enforce_default_voice_host)
host = config::binding::DefaultVoiceHost;
@@ -322,7 +418,7 @@ bool VirtualServer::start(std::string& error) {
this->stop("failed to start", true);
return false;
}
if(this->properties()[property::VIRTUALSERVER_PORT].as<uint16_t>() <= 0){
if(this->properties()[property::VIRTUALSERVER_PORT].as_or<uint16_t>(0) <= 0){
error = "invalid port";
this->stop("failed to start", true);
return false;
@@ -335,7 +431,7 @@ bool VirtualServer::start(std::string& error) {
sockaddr_in addr{};
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(this->properties()[property::VIRTUALSERVER_PORT].as<uint16_t>());
addr.sin_port = htons(this->properties()[property::VIRTUALSERVER_PORT].as_or<uint16_t>(0));
if(!evaluateAddress4(address, addr.sin_addr)) {
logError(this->serverId, "Fail to resolve v4 address info for \"{}\"", address);
continue;
@@ -346,7 +442,7 @@ bool VirtualServer::start(std::string& error) {
sockaddr_in6 addr{};
memset(&addr, 0, sizeof(addr));
addr.sin6_family = AF_INET6;
addr.sin6_port = htons(this->properties()[property::VIRTUALSERVER_PORT].as<uint16_t>());
addr.sin6_port = htons(this->properties()[property::VIRTUALSERVER_PORT].as_or<uint16_t>(0));
if(!evaluateAddress6(address, addr.sin6_addr)) {
logError(this->serverId, "Fail to resolve v6 address info for \"{}\"", address);
continue;
@@ -376,11 +472,11 @@ bool VirtualServer::start(std::string& error) {
if(ts::config::web::activated && serverInstance->sslManager()->web_ssl_options()) {
string web_host_string = this->properties()[property::VIRTUALSERVER_WEB_HOST];
if(web_host_string.empty())
web_host_string = this->properties()[property::VIRTUALSERVER_HOST].as<string>();
web_host_string = this->properties()[property::VIRTUALSERVER_HOST].as_or<string>(0);
auto web_port = this->properties()[property::VIRTUALSERVER_WEB_PORT].as<uint16_t>();
auto web_port = this->properties()[property::VIRTUALSERVER_WEB_PORT].as_or<uint16_t>(0);
if(web_port == 0)
web_port = this->properties()[property::VIRTUALSERVER_PORT].as<uint16_t>();
web_port = this->properties()[property::VIRTUALSERVER_PORT].as_or<uint16_t>(0);
startTimestamp = std::chrono::system_clock::now();
#ifdef COMPILE_WEB_CLIENT
@@ -412,11 +508,18 @@ bool VirtualServer::start(std::string& error) {
#endif
}
//Startup ticking
serverInstance->executeTick(this);
if(this->properties()[property::VIRTUALSERVER_WEBLIST_ENABLED].as<bool>())
serverInstance->getWebList()->enable_report(this->self.lock());
auto weak_this = this->self;
serverInstance->general_task_executor()->schedule_repeating(
this->tick_task_id,
"server tick " + std::to_string(this->serverId),
std::chrono::milliseconds {500},
[weak_this](const auto& scheduled){
auto ref_self = weak_this.lock();
if(ref_self) {
ref_self->executeServerTick();
}
}
);
properties()[property::VIRTUALSERVER_CLIENTS_ONLINE] = 0;
properties()[property::VIRTUALSERVER_QUERYCLIENTS_ONLINE] = 0;
@@ -424,8 +527,8 @@ bool VirtualServer::start(std::string& error) {
properties()[property::VIRTUALSERVER_UPTIME] = 0;
this->startTimestamp = system_clock::now();
this->musicManager->cleanup_semi_bots();
this->musicManager->connectBots();
this->music_manager_->cleanup_semi_bots();
this->music_manager_->connectBots();
{
threads::MutexLock lock(this->stateLock);
@@ -487,7 +590,7 @@ void VirtualServer::stop(const std::string& reason, bool disconnect_query) {
if(disconnect_query) {
auto qc = dynamic_pointer_cast<QueryClient>(cl);
qc->disconnect_from_virtual_server();
qc->disconnect_from_virtual_server("server disconnect");
}
} else if(cl->getType() == CLIENT_MUSIC) {
cl->disconnect("");
@@ -498,9 +601,10 @@ void VirtualServer::stop(const std::string& reason, bool disconnect_query) {
logError(this->serverId, "Got client with unknown type: " + to_string(cl->getType()));
}
}
this->musicManager->disconnectBots();
this->music_manager_->disconnectBots();
serverInstance->cancelExecute(this);
serverInstance->general_task_executor()->cancel_task(this->tick_task_id);
this->tick_task_id = 0;
if(this->udpVoiceServer) this->udpVoiceServer->stop();
this->udpVoiceServer = nullptr;
@@ -511,12 +615,6 @@ void VirtualServer::stop(const std::string& reason, bool disconnect_query) {
this->webControlServer = nullptr;
#endif
{
auto list = serverInstance->getWebList();
if(list)
list->disable_report(self_lock);
}
if(this->groups) {
this->groups->clearCache();
}
@@ -557,6 +655,7 @@ OnlineClientReport VirtualServer::onlineStats() {
switch (cl->getType()) {
case CLIENT_TEAMSPEAK:
case CLIENT_TEASPEAK:
response.clients_ts++;
break;
case CLIENT_WEB:
@@ -568,6 +667,8 @@ OnlineClientReport VirtualServer::onlineStats() {
case CLIENT_MUSIC:
response.bots++;
break;
case CLIENT_INTERNAL:
case MAX:
default:
break;
}
@@ -711,12 +812,12 @@ bool VirtualServer::notifyServerEdited(std::shared_ptr<ConnectedClient> invoker,
cmd["invokeruid"] = invoker->getUid();
cmd["reasonid"] = ViewReasonId::VREASON_EDITED;
for(const auto& key : keys) {
auto info = property::impl::info<property::VirtualServerProperties>(key);
if(*info == property::VIRTUALSERVER_UNDEFINED) {
const auto& info = property::find<property::VirtualServerProperties>(key);
if(info == property::VIRTUALSERVER_UNDEFINED) {
logError(this->getServerId(), "Tried to broadcast a server update with an unknown info: " + key);
continue;
}
cmd[key] = properties()[info].as<std::string>();
cmd[key] = properties()[info].value();
}
this->forEachClient([&cmd](shared_ptr<ConnectedClient> client){
client->sendCommand(cmd);
@@ -724,10 +825,10 @@ bool VirtualServer::notifyServerEdited(std::shared_ptr<ConnectedClient> invoker,
return true;
}
bool VirtualServer::notifyClientPropertyUpdates(std::shared_ptr<ConnectedClient> client, const deque<shared_ptr<property::PropertyDescription>>& keys, bool selfNotify) {
if(keys.empty()) return false;
bool VirtualServer::notifyClientPropertyUpdates(std::shared_ptr<ConnectedClient> client, const deque<const property::PropertyDescription*>& keys, bool selfNotify) {
if(keys.empty() || !client) return false;
this->forEachClient([&](const shared_ptr<ConnectedClient>& cl) {
shared_lock client_channel_lock(client->channel_lock);
shared_lock client_channel_lock(cl->channel_lock);
if(cl->isClientVisible(client, false) || (cl == client && selfNotify))
cl->notifyClientUpdated(client, keys, false);
});
@@ -798,8 +899,6 @@ vector<pair<ts::permission::PermissionType, ts::permission::v2::PermissionFlagge
bool have_skip_permission = false;
int skip_permission_type = -1; /* -1 := unset | 0 := skip, not explicit | 1 := skip, explicit */
bool have_skip;
/*
* server_group_data[0] := Server group id
* server_group_data[1] := Skip flag
@@ -843,10 +942,10 @@ vector<pair<ts::permission::PermissionType, ts::permission::v2::PermissionFlagge
server_group_data_initialized = true;
active_server_group = nullptr;
auto groups = cache->getGroupAssignments(this, client_dbid, client_type);
server_group_data.resize(groups.size());
auto assigned_groups = cache->getGroupAssignments(this, client_dbid, client_type);
server_group_data.resize(assigned_groups.size());
auto it = server_group_data.begin();
for(auto& group : groups) {
for(auto& group : assigned_groups) {
auto group_permissions = group->group->permissions();
auto permission_flags = group_permissions->permission_flags(permission_type);
@@ -896,13 +995,6 @@ vector<pair<ts::permission::PermissionType, ts::permission::v2::PermissionFlagge
};
for(const auto& permission : permissions) {
if(permission == permission::b_client_skip_channelgroup_permissions) {
if(skip_permission_type == -1) /* initialize skip flag */
calculate_skip();
result.push_back({permission, {have_skip_permission, skip_permission_type == 1}});
continue;
}
server_group_data_initialized = false; /* reset all group data */
auto client_permission_flags = cache->client_permissions->permission_flags(permission);
/* lets try to resolve the channel specific permission */
@@ -916,27 +1008,29 @@ vector<pair<ts::permission::PermissionType, ts::permission::v2::PermissionFlagge
}
have_skip = channel_id == 0;
if(!have_skip) {
bool skip_channel_permissions = channel_id == 0;
if(!skip_channel_permissions) {
/* look if somewhere is the skip permission flag set */
if(skip_permission_type == -1) /* initialize skip flag */
if(skip_permission_type == -1) {/* initialize skip flag */
calculate_skip();
have_skip = have_skip_permission;
}
skip_channel_permissions = have_skip_permission;
}
if(!have_skip) {
if(!skip_channel_permissions) {
/* okey we've no global skip. Then now lookup the groups and the client permissions */
if(calculate_granted ? client_permission_flags.grant_set : client_permission_flags.value_set) {
/* okey the client has the permission, this counts */
have_skip = client_permission_flags.skip;
skip_channel_permissions = client_permission_flags.skip;
} else {
if(!server_group_data_initialized)
initialize_group_data(permission);
if(active_server_group)
have_skip = std::get<1>(*active_server_group);
skip_channel_permissions = std::get<1>(*active_server_group);
}
}
if(!have_skip) {
if(!skip_channel_permissions) {
/* lookup the channel group */
{
auto channel_assignment = cache->getChannelAssignment(this, client_dbid, channel_id);
@@ -1005,7 +1099,7 @@ permission::v2::PermissionFlaggedValue VirtualServer::calculate_permission(
}
bool VirtualServer::verifyServerPassword(std::string password, bool hashed) {
if(!this->properties()[property::VIRTUALSERVER_FLAG_PASSWORD].as<bool>()) return true;
if(!this->properties()[property::VIRTUALSERVER_FLAG_PASSWORD].as_or<bool>(false)) return true;
if(password.empty()) return false;
if(!hashed){
@@ -1014,38 +1108,36 @@ bool VirtualServer::verifyServerPassword(std::string password, bool hashed) {
password = base64_encode(string(buffer, SHA_DIGEST_LENGTH));
}
return password == this->properties()[property::VIRTUALSERVER_PASSWORD].as<std::string>();
return password == this->properties()[property::VIRTUALSERVER_PASSWORD].value();
}
float VirtualServer::averagePacketLoss() {
//TODO Average packet loss
return 0.f;
}
VirtualServer::NetworkReport VirtualServer::generate_network_report() {
double total_ping{0}, total_loss{0};
size_t pings_counted{0}, loss_counted{0};
float VirtualServer::averagePing() {
float count = 0;
float sum = 0;
this->forEachClient([&count, &sum](shared_ptr<ConnectedClient> client) {
auto type = client->getType();
if(type == ClientType::CLIENT_TEAMSPEAK || type == ClientType::CLIENT_TEASPEAK) {
count++;
sum += duration_cast<milliseconds>(dynamic_pointer_cast<VoiceClient>(client)->calculatePing()).count();
this->forEachClient([&](const std::shared_ptr<ConnectedClient>& client) {
if(auto vc = dynamic_pointer_cast<VoiceClient>(client); vc) {
total_ping += vc->current_ping().count();
total_loss += vc->current_packet_loss();
pings_counted++;
loss_counted++;
}
#ifdef COMPILE_WEB_CLIENT
else if(type == ClientType::CLIENT_WEB) {
count++;
sum += duration_cast<milliseconds>(dynamic_pointer_cast<WebClient>(client)->client_ping()).count();
else if(client->getType() == ClientType::CLIENT_WEB) {
pings_counted++;
total_ping += duration_cast<milliseconds>(dynamic_pointer_cast<WebClient>(client)->client_ping()).count();
}
#endif
});
if(count == 0) return 0;
return sum / count;
VirtualServer::NetworkReport result{};
if(loss_counted) result.average_loss = total_loss / loss_counted;
if(pings_counted) result.average_ping = total_ping / pings_counted;
return result;
}
bool VirtualServer::resetPermissions(std::string& token) {
LOG_SQL_CMD(sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :serverId", variable{":serverId", this->serverId}).execute());
bool VirtualServer::resetPermissions(std::string& new_permission_token) {
LOG_SQL_CMD(sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :serverId AND `type` != :channel_type", variable{":serverId", this->serverId}, variable{":channel_type", permission::SQL_PERM_CHANNEL}).execute());
LOG_SQL_CMD(sql::command(this->sql, "DELETE FROM `assignedGroups` WHERE `serverId` = :serverId", variable{":serverId", this->serverId}).execute());
LOG_SQL_CMD(sql::command(this->sql, "DELETE FROM `groups` WHERE `serverId` = :serverId", variable{":serverId", this->serverId}).execute());
@@ -1063,12 +1155,17 @@ bool VirtualServer::resetPermissions(std::string& token) {
}
//Server admin
auto default_server_admin = serverInstance->getGroupManager()->findGroup(serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_SERVERADMIN_GROUP].as<GroupId>());
auto default_server_music = serverInstance->getGroupManager()->findGroup(serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_MUSICDEFAULT_GROUP].as<GroupId>());
auto default_server_guest = serverInstance->getGroupManager()->findGroup(serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_SERVERDEFAULT_GROUP].as<GroupId>());
auto default_server_admin = serverInstance->getGroupManager()->findGroup(
serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_SERVERADMIN_GROUP].as_or<GroupId>(0));
auto default_server_music = serverInstance->getGroupManager()->findGroup(
serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_MUSICDEFAULT_GROUP].as_or<GroupId>(0));
auto default_server_guest = serverInstance->getGroupManager()->findGroup(
serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_SERVERDEFAULT_GROUP].as_or<GroupId>(0));
auto default_channel_admin = serverInstance->getGroupManager()->findGroup(serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_CHANNELADMIN_GROUP].as<GroupId>());
auto default_channel_guest = serverInstance->getGroupManager()->findGroup(serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_CHANNELDEFAULT_GROUP].as<GroupId>());
auto default_channel_admin = serverInstance->getGroupManager()->findGroup(
serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_CHANNELADMIN_GROUP].as_or<GroupId>(0));
auto default_channel_guest = serverInstance->getGroupManager()->findGroup(
serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_CHANNELDEFAULT_GROUP].as_or<GroupId>(0));
if(!default_server_guest) {
logCritical(0, "Missing default server guest template group!");
@@ -1107,23 +1204,26 @@ bool VirtualServer::resetPermissions(std::string& token) {
this->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP] = this->getGroupManager()->findGroup(GroupTarget::GROUPTARGET_CHANNEL, default_channel_admin->name()).front()->groupId();
this->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP] = this->getGroupManager()->findGroup(GroupTarget::GROUPTARGET_CHANNEL, default_channel_guest->name()).front()->groupId();
auto token_admin = this->getGroupManager()->findGroup(GroupTarget::GROUPTARGET_SERVER, default_server_admin->name()).front()->groupId();
auto created = this->tokenManager->createToken(token::TOKEN_SERVER, token_admin, "Default server token for the server admin.");
if(!created) {
logCritical(this->serverId, "Failed to generate default serveradmin token!");
auto server_admin_group_id = this->getGroupManager()->findGroup(GroupTarget::GROUPTARGET_SERVER, default_server_admin->name()).front()->groupId();
auto token = this->tokenManager->create_token(0, "Default server admin token", 1, std::chrono::system_clock::time_point{});
if(!token) {
logCritical(this->serverId, "Failed to register the default server admin token.");
this->properties()[property::VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY] = false;
} else {
token = created->token;
this->properties()[property::VIRTUALSERVER_AUTOGENERATED_PRIVILEGEKEY] = token;
std::vector<token::TokenAction> actions{};
actions.push_back(token::TokenAction{
.id = 0,
.type = token::ActionType::AddServerGroup,
.id1 = server_admin_group_id,
.id2 = 0
});
this->tokenManager->add_token_actions(token->id, actions);
new_permission_token = token->token;
this->properties()[property::VIRTUALSERVER_AUTOGENERATED_PRIVILEGEKEY] = token->token;
this->properties()[property::VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY] = true;
}
if(this->properties()[property::VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY].as<bool>()) {
auto requested_token = this->tokenManager->findToken(this->properties()[property::VIRTUALSERVER_AUTOGENERATED_PRIVILEGEKEY]);
if(!requested_token) {
logError(this->serverId, "Failed to resolve default token! Don't ask for privilege key anymore.");
this->properties()[property::VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY] = false;
this->properties()[property::VIRTUALSERVER_AUTOGENERATED_PRIVILEGEKEY] = "";
}
}
this->ensureValidDefaultGroups();
for(const auto& client : this->getClients()) {
@@ -1132,10 +1232,9 @@ bool VirtualServer::resetPermissions(std::string& token) {
client->notifyChannelGroupList();
}
if(this->notifyClientPropertyUpdates(client, this->getGroupManager()->update_server_group_property(client, true, client->getChannel()))) {
if(client->update_cached_permissions()) /* update cached calculated permissions */
client->sendNeededPermissions(false); /* cached permissions had changed, notify the client */
client->task_update_needed_permissions.enqueue();
}
client->updateChannelClientProperties(true, true);
client->task_update_channel_client_properties.enqueue();
}
return true;
}
@@ -1168,7 +1267,8 @@ void VirtualServer::ensureValidDefaultGroups() {
this->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP] = default_channel_group->groupId();
}
auto admin_channel_group = this->getGroupManager()->findGroupLocal(this->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP].as_save<GroupId>());
auto admin_channel_group = this->getGroupManager()->findGroupLocal(
this->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP].as_or<GroupId>(0));
if(!admin_channel_group) {
logError(this->serverId, "Missing server's default channel admin group! (Id: {})", this->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP].value());
@@ -1186,8 +1286,16 @@ void VirtualServer::send_text_message(const std::shared_ptr<BasicChannel> &chann
auto channel_id = channel->channelId();
auto now = chrono::system_clock::now();
bool conversation_private = channel->properties()[property::CHANNEL_FLAG_CONVERSATION_PRIVATE].as<bool>();
auto flag_password = channel->properties()[property::CHANNEL_FLAG_PASSWORD].as<bool>();
bool conversation_private;
auto conversation_mode = channel->properties()[property::CHANNEL_CONVERSATION_MODE].as_or<ChannelConversationMode>(ChannelConversationMode::CHANNELCONVERSATIONMODE_PRIVATE);
if(conversation_mode == ChannelConversationMode::CHANNELCONVERSATIONMODE_NONE) {
/* nothing to do */
return;
} else {
conversation_private = conversation_mode == ChannelConversationMode::CHANNELCONVERSATIONMODE_PRIVATE;
}
auto flag_password = channel->properties()[property::CHANNEL_FLAG_PASSWORD].as_or<bool>(false);
for(const auto& client : this->getClients()) {
if(client->connectionState() != ConnectionState::CONNECTED)
continue;
@@ -1217,4 +1325,41 @@ void VirtualServer::send_text_message(const std::shared_ptr<BasicChannel> &chann
auto conversation = conversations->get_or_create(channel->channelId());
conversation->register_message(sender->getClientDatabaseId(), sender->getUid(), sender->getDisplayName(), now, message);
}
}
void VirtualServer::update_channel_from_permissions(const std::shared_ptr<BasicChannel> &channel, const std::shared_ptr<ConnectedClient>& issuer) {
bool require_view_update;
auto property_updates = channel->update_properties_from_permissions(require_view_update);
if(!property_updates.empty()) {
this->forEachClient([&](const std::shared_ptr<ConnectedClient>& cl) {
shared_lock client_channel_lock(cl->channel_lock);
cl->notifyChannelEdited(channel, property_updates, issuer, false);
});
}
if(require_view_update) {
auto l_source = this->channelTree->findLinkedChannel(channel->channelId());
this->forEachClient([&](const shared_ptr<ConnectedClient>& cl) {
/* server tree read lock still active */
auto l_target = !cl->currentChannel ? nullptr : cl->server->channelTree->findLinkedChannel(cl->currentChannel->channelId());
sassert(l_source);
if(cl->currentChannel) sassert(l_target);
{
unique_lock client_channel_lock(cl->channel_lock);
deque<ChannelId> deleted;
for(const auto& [flag_visible, channel] : cl->channels->update_channel(l_source, l_target)) {
if(flag_visible) {
cl->notifyChannelShow(channel->channel(), channel->previous_channel);
} else {
deleted.push_back(channel->channelId());
}
}
if(!deleted.empty())
cl->notifyChannelHide(deleted, false);
}
});
}
}
+36 -14
View File
@@ -21,6 +21,7 @@
#include "manager/LetterManager.h"
#include "Configuration.h"
#include "protocol/ringbuffer.h"
#include <misc/task_executor.h>
#include <tomcrypt.h>
#undef byte
@@ -48,6 +49,10 @@ namespace ts {
class MusicBotManager;
}
namespace rtc {
class Server;
}
namespace server {
class ConnectedClient;
class VoiceClient;
@@ -58,7 +63,6 @@ namespace ts {
class InstanceHandler;
class VoiceServer;
class QueryServer;
class FileServer;
class SpeakingClient;
class WebControlServer;
@@ -136,6 +140,11 @@ namespace ts {
friend class InstanceHandler;
friend class VirtualServerManager;
public:
struct NetworkReport {
float average_ping{0};
float average_loss{0};
};
VirtualServer(ServerId serverId, sql::SqlManager*);
~VirtualServer();
@@ -172,7 +181,8 @@ namespace ts {
ecc_key* serverKey(){ return _serverKey; }
std::string publicServerKey();
Properties& properties(){ return *this->_properties; }
inline PropertyWrapper properties() { return PropertyWrapper{this->_properties}; }
inline const PropertyWrapper properties() const { return PropertyWrapper{this->_properties}; }
inline sql::SqlManager * getSql(){ return this->sql; }
sql::AsyncSqlPool* getSqlPool(){ return this->sql->pool; }
@@ -180,13 +190,18 @@ namespace ts {
inline ServerId getServerId(){ return this->serverId; }
inline ServerChannelTree* getChannelTree(){ return this->channelTree; }
inline GroupManager* getGroupManager() { return this->groups; }
inline rtc::Server& rtc_server() { return *this->rtc_server_; }
[[nodiscard]] inline auto& getTokenManager() {
return *this->tokenManager;
}
bool notifyServerEdited(std::shared_ptr<ConnectedClient>, std::deque<std::string> keys);
bool notifyClientPropertyUpdates(std::shared_ptr<ConnectedClient>, const std::deque<std::shared_ptr<property::PropertyDescription>>& keys, bool selfNotify = true); /* execute only with at least channel tree read lock! */
bool notifyClientPropertyUpdates(std::shared_ptr<ConnectedClient>, const std::deque<const property::PropertyDescription*>& keys, bool selfNotify = true); /* execute only with at least channel tree read lock! */
inline bool notifyClientPropertyUpdates(const std::shared_ptr<ConnectedClient>& client, const std::deque<property::ClientProperties>& keys, bool selfNotify = true) {
if(keys.empty()) return false;
std::deque<std::shared_ptr<property::PropertyDescription>> _keys;
for(const auto& key : keys) _keys.push_back(property::impl::info<property::ClientProperties>(key));
std::deque<const property::PropertyDescription*> _keys{};
for(const auto& key : keys) _keys.push_back(&property::describe(key));
return this->notifyClientPropertyUpdates(client, _keys, selfNotify);
};
@@ -202,7 +217,7 @@ namespace ts {
std::string getDisplayName(){ return properties()[property::VIRTUALSERVER_NAME]; }
std::shared_ptr<stats::ConnectionStatistics> getServerStatistics(){ return serverStatistics; }
std::shared_ptr<stats::ConnectionStatistics> getServerStatistics(){ return server_statistics_; }
std::shared_ptr<VoiceServer> getVoiceServer(){ return this->udpVoiceServer; }
WebControlServer* getWebServer(){ return this->webControlServer; }
@@ -230,8 +245,7 @@ namespace ts {
void testBanStateChange(const std::shared_ptr<ConnectedClient>& invoker);
float averagePing();
float averagePacketLoss();
[[nodiscard]] NetworkReport generate_network_report();
bool resetPermissions(std::string&);
void ensureValidDefaultGroups();
@@ -267,13 +281,18 @@ namespace ts {
std::shared_ptr<ServerChannel> /* target channel */,
const std::shared_ptr<ConnectedClient>& /* invoker */,
const std::string& /* kick message */,
std::unique_lock<std::shared_mutex>& /* tree lock */
std::unique_lock<std::shared_mutex>& /* tree lock */,
bool temporary_auto_delete
);
void send_text_message(const std::shared_ptr<BasicChannel>& /* channel */, const std::shared_ptr<ConnectedClient>& /* sender */, const std::string& /* message */);
inline int voice_encryption_mode() { return this->_voice_encryption_mode; }
inline std::shared_ptr<conversation::ConversationManager> conversation_manager() { return this->_conversation_manager; }
inline std::shared_ptr<conversation::ConversationManager> conversation_manager() { return this->conversation_manager_; }
inline auto& get_channel_tree_lock() { return this->channel_tree_lock; }
void update_channel_from_permissions(const std::shared_ptr<BasicChannel>& /* channel */, const std::shared_ptr<ConnectedClient>& /* issuer */);
protected:
bool registerClient(std::shared_ptr<ConnectedClient>);
bool unregisterClient(std::shared_ptr<ConnectedClient>, std::string, std::unique_lock<std::shared_mutex>& channel_tree_lock);
@@ -285,6 +304,8 @@ namespace ts {
//Locks by tick, start and stop
threads::Mutex stateLock;
ServerState::value state = ServerState::OFFLINE;
task_id tick_task_id{};
std::chrono::system_clock::time_point lastTick;
void executeServerTick();
@@ -293,9 +314,10 @@ namespace ts {
token::TokenManager* tokenManager = nullptr;
ComplainManager* complains = nullptr;
letter::LetterManager* letters = nullptr;
std::shared_ptr<music::MusicBotManager> musicManager;
std::shared_ptr<stats::ConnectionStatistics> serverStatistics;
std::shared_ptr<conversation::ConversationManager> _conversation_manager;
std::shared_ptr<music::MusicBotManager> music_manager_;
std::shared_ptr<stats::ConnectionStatistics> server_statistics_;
std::shared_ptr<conversation::ConversationManager> conversation_manager_;
std::unique_ptr<rtc::Server> rtc_server_;
sql::SqlManager* sql;
@@ -316,7 +338,7 @@ namespace ts {
//General server properties
ecc_key* _serverKey = nullptr;
std::shared_ptr<Properties> _properties;
std::shared_ptr<PropertyManager> _properties;
int _voice_encryption_mode = 2; /* */
ServerChannelTree* channelTree = nullptr;
+142 -44
View File
@@ -4,18 +4,17 @@
#include "src/server/VoiceServer.h"
#include "src/client/query/QueryClient.h"
#include "InstanceHandler.h"
#include "src/server/file/FileServer.h"
#include "src/client/ConnectedClient.h"
#include <ThreadPool/ThreadHelper.h>
#include <files/FileServer.h>
using namespace std;
using namespace std::chrono;
using namespace ts::server;
VirtualServerManager::VirtualServerManager(InstanceHandler* handle) : handle(handle) {
this->puzzles = new protocol::PuzzleManager();
this->puzzles = new udp::PuzzleManager{};
this->handshakeTickers = new threads::Scheduler(1, "handshake ticker");
this->execute_loop = new event::EventExecutor("executor #");
//this->join_loop = new event::EventExecutor("joiner #");
this->_ioManager = new io::VoiceIOManager();
@@ -40,11 +39,6 @@ VirtualServerManager::~VirtualServerManager() {
delete this->puzzles;
this->puzzles = nullptr;
if(this->execute_loop)
this->execute_loop->shutdown();
delete this->execute_loop;
this->execute_loop = nullptr;
if(this->join_loop)
this->join_loop->shutdown();
delete this->join_loop;
@@ -62,12 +56,11 @@ VirtualServerManager::~VirtualServerManager() {
}
bool VirtualServerManager::initialize(bool autostart) {
this->execute_loop->initialize(1);
this->state = State::STARTING;
logMessage(LOG_INSTANCE, "Generating server puzzles...");
auto start = system_clock::now();
this->puzzles->precomputePuzzles(config::voice::DefaultPuzzlePrecomputeSize);
if(!this->puzzles->precompute_puzzles(config::voice::DefaultPuzzlePrecomputeSize))
logCritical(LOG_INSTANCE, "Failed to precompute RSA puzzles");
logMessage(LOG_INSTANCE, "Puzzles generated! Time required: " + to_string(duration_cast<milliseconds>(system_clock::now() - start).count()) + "ms");
size_t serverCount = 0;
@@ -109,6 +102,9 @@ bool VirtualServerManager::initialize(bool autostart) {
if(id == 0) {
logError(LOG_INSTANCE, "Failed to load virtual server from database. Server id is zero!");
return 0;
} else if(id == 0xFFFF) {
/* snapshot server */
return 0;
}
if(host.empty()) {
@@ -135,7 +131,7 @@ bool VirtualServerManager::initialize(bool autostart) {
this->instances.push_back(server);
}
if(autostart && server->properties()[property::VIRTUALSERVER_AUTOSTART].as<bool>()) {
if(autostart && server->properties()[property::VIRTUALSERVER_AUTOSTART].as_or<bool>(false)) {
logMessage(server->getServerId(), "Starting server");
string msg;
try {
@@ -158,7 +154,6 @@ bool VirtualServerManager::initialize(bool autostart) {
(float) server_count / (time / 1024 == 0 ? 1 : time / 1024)
);
this->handle->databaseHelper()->clearStartupCache(0);
this->adjust_executor_threads();
{
this->acknowledge.executor = std::thread([&]{
@@ -200,11 +195,13 @@ shared_ptr<VirtualServer> VirtualServerManager::findServerByPort(uint16_t port)
return nullptr;
}
uint16_t VirtualServerManager::next_available_port() {
uint16_t VirtualServerManager::next_available_port(const std::string& host_string) {
auto instances = this->serverInstances();
deque<uint16_t> unallowed_ports;
std::vector<uint16_t> unallowed_ports{};
unallowed_ports.reserve(instances.size());
for(const auto& instance : instances) {
unallowed_ports.push_back(instance->properties()[property::VIRTUALSERVER_PORT].as<uint16_t>());
unallowed_ports.push_back(instance->properties()[property::VIRTUALSERVER_PORT].as_or<uint16_t>(0));
auto vserver = instance->getVoiceServer();
if(instance->running() && vserver) {
@@ -213,25 +210,53 @@ uint16_t VirtualServerManager::next_available_port() {
}
}
}
auto bindings = net::resolve_bindings(host_string, 0);
uint16_t port = config::voice::default_voice_port;
while(true) {
if(port < 1024) goto c;
if(port < 1024) goto next_port;
for(auto& p : unallowed_ports) {
if(p == port)
goto c;
goto next_port;
}
for(auto& binding : bindings) {
if(!std::get<2>(binding).empty()) continue; /* error on that */
auto& baddress = std::get<1>(binding);
auto& raw_port = baddress.ss_family == AF_INET ? ((sockaddr_in*) &baddress)->sin_port : ((sockaddr_in6*) &baddress)->sin6_port;
raw_port = htons(port);
switch (net::address_available(baddress, net::binding_type::TCP)) {
case net::binding_result::ADDRESS_USED:
goto next_port;
case net::binding_result::ADDRESS_FREE:
case net::binding_result::INTERNAL_ERROR:
default:
break; /* if we've an internal error we ignore it */
}
switch (net::address_available(baddress, net::binding_type::UDP)) {
case net::binding_result::ADDRESS_USED:
goto next_port;
case net::binding_result::ADDRESS_FREE:
case net::binding_result::INTERNAL_ERROR:
default:
break; /* if we've an internal error we ignore it */
}
}
break;
c:
next_port:
port++;
}
return port;
}
ts::ServerId VirtualServerManager::next_available_server_id(bool& success) {
auto server_id_base = this->handle->properties()[property::SERVERINSTANCE_VIRTUAL_SERVER_ID_INDEX].as<ServerId>();
auto server_id_base = this->handle->properties()[property::SERVERINSTANCE_VIRTUAL_SERVER_ID_INDEX].as_or<ServerId>(0);
/* ensure we're not using 0xFFFF (This is the snapshot server) */
if(server_id_base > 65530) {
success = false;
return 0;
@@ -273,7 +298,7 @@ ServerReport VirtualServerManager::report() {
result.avariable++;
if(sr->running()) {
result.online++;
result.slots += sr->properties()[property::VIRTUALSERVER_MAXCLIENTS].as<size_t>();
result.slots += sr->properties()[property::VIRTUALSERVER_MAXCLIENTS].as_or<size_t>(0);
result.onlineClients += sr->onlineClients();
result.onlineChannels += sr->onlineChannels();
}
@@ -305,7 +330,7 @@ size_t VirtualServerManager::runningServers() {
size_t VirtualServerManager::usedSlots() {
size_t res = 0;
for(const auto& sr : this->serverInstances())
res += sr->properties()[property::VIRTUALSERVER_MAXCLIENTS].as<size_t>();
res += sr->properties()[property::VIRTUALSERVER_MAXCLIENTS].as_or<size_t>(0);
return res;
}
@@ -316,8 +341,9 @@ shared_ptr<VirtualServer> VirtualServerManager::create_server(std::string hosts,
if(!sid_success)
return nullptr;
this->delete_server_in_db(serverId, false); /* just to ensure */
sql::command(this->handle->getSql(), "INSERT INTO `servers` (`serverId`, `host`, `port`) VALUES (:sid, :host, :port)", variable{":sid", serverId}, variable{":host", hosts}, variable{":port", port}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
//`serverId` INTEGER DEFAULT -1, `type` INTEGER, `id` INTEGER, `key` VARCHAR(" UNKNOWN_KEY_LENGTH "), `value` TEXT
auto prop_copy = sql::command(this->handle->getSql(), "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) SELECT :target_sid AS `serverId`, `type`, `id`, `key`, `value` FROM `properties` WHERE `type` = :type AND `id` = 0 AND `serverId` = 0;",
variable{":target_sid", serverId},
variable{":type", property::PROP_TYPE_SERVER}).execute();
@@ -332,7 +358,7 @@ shared_ptr<VirtualServer> VirtualServerManager::create_server(std::string hosts,
server->properties()[property::VIRTUALSERVER_HOST] = hosts;
server->properties()[property::VIRTUALSERVER_PORT] = port;
if(config::server::default_music_bot) {
auto bot = server->musicManager->createBot(0);
auto bot = server->music_manager_->createBot(0);
if(!bot) {
logCritical(server->getServerId(), "Failed to create default music bot!");
}
@@ -341,7 +367,6 @@ shared_ptr<VirtualServer> VirtualServerManager::create_server(std::string hosts,
threads::MutexLock l(this->instanceLock);
this->instances.push_back(server);
}
this->adjust_executor_threads();
return server;
}
@@ -359,7 +384,6 @@ bool VirtualServerManager::deleteServer(shared_ptr<VirtualServer> server) {
return s == server;
}), this->instances.end());
}
this->adjust_executor_threads();
if(server->getState() != ServerState::OFFLINE)
server->stop("server deleted", true);
@@ -368,7 +392,7 @@ bool VirtualServerManager::deleteServer(shared_ptr<VirtualServer> server) {
cl->close_connection(chrono::system_clock::now());
} else if(cl->getType() == CLIENT_QUERY){
auto qc = dynamic_pointer_cast<QueryClient>(cl);
qc->disconnect_from_virtual_server();
qc->disconnect_from_virtual_server("server delete");
} else if(cl->getType() == CLIENT_MUSIC) {
cl->disconnect("");
cl->currentChannel = nullptr;
@@ -392,22 +416,30 @@ bool VirtualServerManager::deleteServer(shared_ptr<VirtualServer> server) {
server->state = ServerState::DELETING;
}
this->handle->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED] += server->properties()[property::VIRTUALSERVER_SPOKEN_TIME].as<uint64_t>();
sql::command(this->handle->getSql(), "DELETE FROM `tokens` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
sql::command(this->handle->getSql(), "DELETE FROM `properties` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
sql::command(this->handle->getSql(), "DELETE FROM `permissions` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
sql::command(this->handle->getSql(), "DELETE FROM `groups` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
sql::command(this->handle->getSql(), "DELETE FROM `clients` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
sql::command(this->handle->getSql(), "DELETE FROM `channels` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
sql::command(this->handle->getSql(), "DELETE FROM `bannedClients` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
sql::command(this->handle->getSql(), "DELETE FROM `assignedGroups` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
sql::command(this->handle->getSql(), "DELETE FROM `servers` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
sql::command(this->handle->getSql(), "DELETE FROM `musicbots` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
this->handle->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED].increment_by(server->properties()[property::VIRTUALSERVER_SPOKEN_TIME].as_or<uint64_t>(0));
this->delete_server_in_db(server->serverId, false);
this->handle->databaseHelper()->handleServerDelete(server->serverId);
sql::command(this->handle->getSql(), "DELETE FROM `bannedClients` WHERE `serverId` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
sql::command(this->handle->getSql(), "DELETE FROM `ban_trigger` WHERE `server_id` = :sid", variable{":sid", server->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
this->handle->getFileServer()->deleteServer(server);
if(auto file_vs = file::server()->find_virtual_server(server->getServerId()); file_vs) {
using ErrorType = file::filesystem::ServerCommandErrorType;
auto delete_result = file::server()->file_system().delete_server(file_vs);
if(!delete_result->wait_for(std::chrono::seconds{5})) {
logError(LOG_INSTANCE, "Failed to wait for file directory initialisation.");
} 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->getServerId(), 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->getServerId(), (int) delete_result->error().error_type, delete_result->error().error_message);
break;
}
}
}
return true;
}
@@ -415,7 +447,7 @@ void VirtualServerManager::executeAutostart() {
threads::MutexLock l(this->instanceLock);
auto lastStart = system_clock::time_point();
for(const auto& server : this->instances){
if(!server->running() && server->properties()[property::VIRTUALSERVER_AUTOSTART].as<bool>()){
if(!server->running() && server->properties()[property::VIRTUALSERVER_AUTOSTART].as_or<bool>(false)){
threads::self::sleep_until(lastStart + milliseconds(10)); //Don't start all server at the same point (otherwise all servers would tick at the same moment)
lastStart = system_clock::now();
logMessage(server->getServerId(), "Starting server");
@@ -436,8 +468,6 @@ void VirtualServerManager::shutdownAll(const std::string& msg) {
for(const auto &server : this->serverInstances()){
if(server->running()) server->stop(msg, true);
}
this->execute_loop->shutdown();
}
void VirtualServerManager::tickHandshakeClients() {
@@ -446,4 +476,72 @@ void VirtualServerManager::tickHandshakeClients() {
if(vserver)
vserver->tickHandshakingClients();
}
}
void VirtualServerManager::delete_server_in_db(ts::ServerId server_id, bool data_only) {
#define execute_delete(statement) \
result = sql::command(this->handle->getSql(), statement, variable{":sid", server_id}).execute(); \
if(!result) { \
logWarning(LOG_INSTANCE, "Failed to execute SQL command {}: {}", statement, result.fmtStr()); \
result = sql::result{}; \
}
sql::result result{};
if(!data_only) {
execute_delete("DELETE FROM `servers` WHERE `serverId` = :sid");
}
execute_delete("DELETE FROM `tokens` WHERE `serverId` = :sid");
execute_delete("DELETE FROM `properties` WHERE `serverId` = :sid");
execute_delete("DELETE FROM `permissions` WHERE `serverId` = :sid");
execute_delete("DELETE FROM `channels` WHERE `serverId` = :sid");
execute_delete("DELETE FROM `bannedClients` WHERE `serverId` = :sid");
execute_delete("DELETE FROM `groups` WHERE `serverId` = :sid");
execute_delete("DELETE FROM `assignedGroups` WHERE `serverId` = :sid");
execute_delete("DELETE FROM `complains` WHERE `serverId` = :sid");
execute_delete("DELETE FROM `letters` WHERE `serverId` = :sid");
execute_delete("DELETE FROM `musicbots` WHERE `serverId` = :sid");
execute_delete("DELETE FROM `playlists` WHERE `serverId` = :sid");
execute_delete("DELETE FROM `playlist_songs` WHERE `serverId` = :sid");
execute_delete("DELETE FROM `conversations` WHERE `server_id` = :sid");
execute_delete("DELETE FROM `conversation_blocks` WHERE `server_id` = :sid");
execute_delete("DELETE FROM `ban_trigger` WHERE `server_id` = :sid");
execute_delete("DELETE FROM `clients_server` WHERE `server_id` = :sid");
}
#define execute_change(table, column) \
result = sql::command(this->handle->getSql(), "UPDATE `" table "` SET `" column "` = :nsid WHERE `" column "` = :osid;", \
variable{":osid", old_id}, variable{":nsid", new_id}).execute(); \
if(!result) { \
logWarning(LOG_INSTANCE, "Failed to execute server id change on table {} (column {}): {}", table, column, result.fmtStr()); \
result = sql::result{}; \
}
void VirtualServerManager::change_server_id_in_db(ts::ServerId old_id, ts::ServerId new_id) {
sql::result result{};
execute_change("tokens", "serverId");
execute_change("properties", "serverId");
execute_change("permissions", "serverId");
execute_change("channels", "serverId");
execute_change("bannedClients", "serverId");
execute_change("groups", "serverId");
execute_change("assignedGroups", "serverId");
execute_change("complains", "serverId");
execute_change("letters", "serverId");
execute_change("musicbots", "serverId");
execute_change("playlists", "serverId");
execute_change("playlist_songs", "serverId");
execute_change("conversations", "server_id");
execute_change("conversation_blocks", "server_id");
execute_change("ban_trigger", "server_id");
execute_change("clients_server", "server_id");
execute_change("servers", "serverId");
}
+79 -73
View File
@@ -2,98 +2,104 @@
#include <deque>
#include <EventLoop.h>
#include "client/voice/PrecomputedPuzzles.h"
#include "src/server/PrecomputedPuzzles.h"
#include "server/VoiceIOManager.h"
#include "VirtualServer.h"
#include <query/command3.h>
#include "snapshots/snapshot.h"
namespace ts {
namespace server {
class InstanceHandler;
namespace ts::server {
class InstanceHandler;
struct ServerReport {
size_t avariable;
size_t online;
struct ServerReport {
size_t avariable;
size_t online;
size_t slots;
size_t onlineClients;
size_t onlineChannels;
};
class VirtualServerManager {
public:
enum State {
STOPPED,
STARTING,
STARTED,
STOPPING
};
size_t slots;
size_t onlineClients;
size_t onlineChannels;
};
class VirtualServerManager {
public:
enum State {
STOPPED,
STARTING,
STARTED,
STOPPING
};
explicit VirtualServerManager(InstanceHandler*);
~VirtualServerManager();
enum struct SnapshotDeployResult {
SUCCESS,
bool initialize(bool execute_autostart = true);
REACHED_SOFTWARE_SERVER_LIMIT,
REACHED_CONFIG_SERVER_LIMIT,
REACHED_SERVER_ID_LIMIT,
std::shared_ptr<VirtualServer> create_server(std::string hosts, uint16_t port);
bool deleteServer(std::shared_ptr<VirtualServer>);
CUSTOM_ERROR /* error message set */
};
std::shared_ptr<VirtualServer> findServerById(ServerId);
std::shared_ptr<VirtualServer> findServerByPort(uint16_t);
uint16_t next_available_port();
ServerId next_available_server_id(bool& /* success */);
std::deque<std::shared_ptr<VirtualServer>> serverInstances(){
threads::MutexLock l(this->instanceLock);
return instances;
}
explicit VirtualServerManager(InstanceHandler*);
~VirtualServerManager();
ServerReport report();
OnlineClientReport clientReport();
size_t runningServers();
size_t usedSlots();
bool initialize(bool execute_autostart = true);
void executeAutostart();
void shutdownAll(const std::string&);
std::shared_ptr<VirtualServer> create_server(std::string hosts, uint16_t port);
bool deleteServer(std::shared_ptr<VirtualServer>);
//Dotn use shared_ptr references to keep sure that they be hold in memory
bool createServerSnapshot(Command &cmd, std::shared_ptr<VirtualServer> server, int version, std::string &error);
std::shared_ptr<VirtualServer> createServerFromSnapshot(std::shared_ptr<VirtualServer> old, std::string, uint16_t, const ts::Command &, std::string &);
std::shared_ptr<VirtualServer> findServerById(ServerId);
std::shared_ptr<VirtualServer> findServerByPort(uint16_t);
uint16_t next_available_port(const std::string& /* host string */);
ServerId next_available_server_id(bool& /* success */);
protocol::PuzzleManager* rsaPuzzles() { return this->puzzles; }
std::deque<std::shared_ptr<VirtualServer>> serverInstances(){
threads::MutexLock l(this->instanceLock);
return instances;
}
event::EventExecutor* get_join_loop() { return this->join_loop; }
event::EventExecutor* get_executor_loop() { return this->execute_loop; }
ServerReport report();
OnlineClientReport clientReport();
size_t runningServers();
size_t usedSlots();
inline void adjust_executor_threads() {
std::unique_lock instance_lock(this->instanceLock);
auto instance_count = this->instances.size();
instance_lock.unlock();
void executeAutostart();
void shutdownAll(const std::string&);
auto threads = std::min(config::threads::voice::execute_per_server * instance_count, config::threads::voice::execute_limit);
this->execute_loop->threads(threads);
}
io::VoiceIOManager* ioManager(){ return this->_ioManager; }
//Don't use shared_ptr references to keep sure that they be hold in memory
bool createServerSnapshot(Command &cmd, std::shared_ptr<VirtualServer> server, int version, std::string &error);
SnapshotDeployResult deploy_snapshot(std::string& /* error */, std::shared_ptr<VirtualServer>& /* target server */, const command_parser& /* source */);
threads::Mutex server_create_lock;
udp::PuzzleManager* rsaPuzzles() { return this->puzzles; }
State getState() { return this->state; }
private:
State state = State::STOPPED;
InstanceHandler* handle;
threads::Mutex instanceLock;
std::deque<std::shared_ptr<VirtualServer>> instances;
protocol::PuzzleManager* puzzles = nullptr;
event::EventExecutor* get_join_loop() { return this->join_loop; }
event::EventExecutor* execute_loop = nullptr;
event::EventExecutor* join_loop = nullptr;
threads::Scheduler* handshakeTickers = nullptr;
io::VoiceIOManager* _ioManager = nullptr;
io::VoiceIOManager* ioManager(){ return this->_ioManager; }
struct {
std::thread executor{};
std::condition_variable condition;
std::mutex lock;
} acknowledge;
/* This must be recursive */
threads::Mutex server_create_lock;
void tickHandshakeClients();
};
}
State getState() { return this->state; }
private:
State state = State::STOPPED;
InstanceHandler* handle;
threads::Mutex instanceLock;
std::deque<std::shared_ptr<VirtualServer>> instances;
udp::PuzzleManager* puzzles{nullptr};
event::EventExecutor* join_loop = nullptr;
threads::Scheduler* handshakeTickers = nullptr;
io::VoiceIOManager* _ioManager = nullptr;
struct {
std::thread executor{};
std::condition_variable condition;
std::mutex lock;
} acknowledge;
void tickHandshakeClients();
void delete_server_in_db(ServerId /* server id */, bool /* data only */);
void change_server_id_in_db(ServerId /* old id */, ServerId /* new id */);
bool try_deploy_snapshot(std::string& /* error */, ServerId /* target server id */, ServerId /* logging server id */, const command_parser& /* source */);
};
}
File diff suppressed because it is too large Load Diff
+280
View File
@@ -0,0 +1,280 @@
/*
* Copyright (c) 2019 German Mendez Bravo (Kronuz)
* Copyright (c) 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*
* A btree::map<> implements the STL unique sorted associative container
* interface and the pair associative container interface (a.k.a map<>) using a
* btree. See btree.h for details of the btree implementation and caveats.
*/
#ifndef BTREE_MAP_H__
#define BTREE_MAP_H__
#include "btree.h"
#include <stdexcept>
namespace btree {
// A common base class for map and safe_map.
template <typename Tree>
class btree_map_container : public btree_unique_container<Tree> {
typedef btree_map_container<Tree> self_type;
typedef btree_unique_container<Tree> super_type;
public:
typedef typename Tree::key_type key_type;
typedef typename Tree::data_type data_type;
typedef typename Tree::value_type value_type;
typedef typename Tree::mapped_type mapped_type;
typedef typename Tree::key_compare key_compare;
typedef typename Tree::allocator_type allocator_type;
typedef typename Tree::iterator iterator;
typedef typename Tree::const_iterator const_iterator;
public:
// Default constructor.
btree_map_container(const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type())
: super_type(comp, alloc) {
}
// Copy constructor.
btree_map_container(const self_type& x)
: super_type(x) {
}
// Range constructor.
template <class InputIterator>
btree_map_container(InputIterator b, InputIterator e,
const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type())
: super_type(b, e, comp, alloc) {
}
template <typename... Args>
std::pair<iterator, bool> try_emplace(const key_type& key, Args&&... args) {
return this->__tree.emplace_unique_key_args(key,
std::piecewise_construct,
std::forward_as_tuple(key),
std::forward_as_tuple(std::forward<Args>(args)...));
}
template <typename... Args>
std::pair<iterator, bool> try_emplace(key_type&& key, Args&&... args) {
return this->__tree.emplace_unique_key_args(key,
std::piecewise_construct,
std::forward_as_tuple(std::move(key)),
std::forward_as_tuple(std::forward<Args>(args)...));
}
template <typename... Args>
iterator try_emplace(const_iterator hint, const key_type& key, Args&&... args) {
return this->__tree.emplace_hint_unique_key_args(hint, key,
std::piecewise_construct,
std::forward_as_tuple(key),
std::forward_as_tuple(std::forward<Args>(args)...));
}
template <typename... Args>
iterator try_emplace(const_iterator hint, key_type&& key, Args&&... args) {
return this->__tree.emplace_hint_unique_key_args(hint, key,
std::piecewise_construct,
std::forward_as_tuple(std::move(key)),
std::forward_as_tuple(std::forward<Args>(args)...));
}
// Access specified element with bounds checking.
mapped_type& at(const key_type& key) {
auto it = this->find(key);
if (it == this->end()) {
throw std::out_of_range("map::at: key not found");
}
return it->second;
}
const mapped_type& at(const key_type& key) const {
auto it = this->find(key);
if (it == this->end()) {
throw std::out_of_range("map::at: key not found");
}
return it->second;
}
// Insertion routines.
data_type& operator[](const key_type& key) {
return this->try_emplace(key).first->second;
}
data_type& operator[](key_type&& key) {
return this->try_emplace(std::move(key)).first->second;
}
};
// The map class is needed mainly for its constructors.
template <typename Key, typename Value,
typename Compare = std::less<Key>,
typename Alloc = std::allocator<std::pair<const Key, Value>>,
int TargetNodeSize = 256>
class map : public btree_map_container<
btree<btree_map_params<Key, Value, Compare, Alloc, TargetNodeSize>>> {
typedef map<Key, Value, Compare, Alloc, TargetNodeSize> self_type;
typedef btree_map_params<Key, Value, Compare, Alloc, TargetNodeSize> params_type;
typedef btree<params_type> btree_type;
typedef btree_map_container<btree_type> super_type;
public:
typedef typename btree_type::key_compare key_compare;
typedef typename btree_type::allocator_type allocator_type;
public:
// Default constructor.
map(const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type())
: super_type(comp, alloc) {
}
// Copy constructor.
map(const self_type& x)
: super_type(x) {
}
// Range constructor.
template <class InputIterator>
map(InputIterator b, InputIterator e,
const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type())
: super_type(b, e, comp, alloc) {
}
};
} // namespace btree
template <typename K, typename V, typename C, typename A, int N>
bool operator==(const btree::map<K, V, C, A, N>& lhs, const btree::map<K, V, C, A, N>& rhs) {
return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin());
}
template <typename K, typename V, typename C, typename A, int N>
bool operator<(const btree::map<K, V, C, A, N>& lhs, const btree::map<K, V, C, A, N>& rhs) {
return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
}
template <typename K, typename V, typename C, typename A, int N>
bool operator!=(const btree::map<K, V, C, A, N>& lhs, const btree::map<K, V, C, A, N>& rhs) {
return !(lhs == rhs);
}
template <typename K, typename V, typename C, typename A, int N>
bool operator>(const btree::map<K, V, C, A, N>& lhs, const btree::map<K, V, C, A, N>& rhs) {
return rhs < lhs;
}
template <typename K, typename V, typename C, typename A, int N>
bool operator>=(const btree::map<K, V, C, A, N>& lhs, const btree::map<K, V, C, A, N>& rhs) {
return !(lhs < rhs);
}
template <typename K, typename V, typename C, typename A, int N>
bool operator<=(const btree::map<K, V, C, A, N>& lhs, const btree::map<K, V, C, A, N>& rhs) {
return !(rhs < lhs);
}
template <typename K, typename V, typename C, typename A, int N>
inline void swap(btree::map<K, V, C, A, N>& x, btree::map<K, V, C, A, N>& y) {
x.swap(y);
}
namespace btree {
// The multimap class is needed mainly for its constructors.
template <typename Key, typename Value,
typename Compare = std::less<Key>,
typename Alloc = std::allocator<std::pair<const Key, Value> >,
int TargetNodeSize = 256>
class multimap : public btree_multi_container<
btree<btree_map_params<Key, Value, Compare, Alloc, TargetNodeSize> > > {
typedef multimap<Key, Value, Compare, Alloc, TargetNodeSize> self_type;
typedef btree_map_params< Key, Value, Compare, Alloc, TargetNodeSize> params_type;
typedef btree<params_type> btree_type;
typedef btree_multi_container<btree_type> super_type;
public:
typedef typename btree_type::key_compare key_compare;
typedef typename btree_type::allocator_type allocator_type;
typedef typename btree_type::data_type data_type;
typedef typename btree_type::mapped_type mapped_type;
public:
// Default constructor.
multimap(const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type())
: super_type(comp, alloc) {
}
// Copy constructor.
multimap(const self_type& x)
: super_type(x) {
}
// Range constructor.
template <class InputIterator>
multimap(InputIterator b, InputIterator e,
const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type())
: super_type(b, e, comp, alloc) {
}
};
} // namespace btree
template <typename K, typename V, typename C, typename A, int N>
bool operator==(const btree::multimap<K, V, C, A, N>& lhs, const btree::multimap<K, V, C, A, N>& rhs) {
return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin());
}
template <typename K, typename V, typename C, typename A, int N>
bool operator<(const btree::multimap<K, V, C, A, N>& lhs, const btree::multimap<K, V, C, A, N>& rhs) {
return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
}
template <typename K, typename V, typename C, typename A, int N>
bool operator!=(const btree::multimap<K, V, C, A, N>& lhs, const btree::multimap<K, V, C, A, N>& rhs) {
return !(lhs == rhs);
}
template <typename K, typename V, typename C, typename A, int N>
bool operator>(const btree::multimap<K, V, C, A, N>& lhs, const btree::multimap<K, V, C, A, N>& rhs) {
return rhs < lhs;
}
template <typename K, typename V, typename C, typename A, int N>
bool operator>=(const btree::multimap<K, V, C, A, N>& lhs, const btree::multimap<K, V, C, A, N>& rhs) {
return !(lhs < rhs);
}
template <typename K, typename V, typename C, typename A, int N>
bool operator<=(const btree::multimap<K, V, C, A, N>& lhs, const btree::multimap<K, V, C, A, N>& rhs) {
return !(rhs < lhs);
}
template <typename K, typename V, typename C, typename A, int N>
inline void swap(btree::multimap<K, V, C, A, N>& x, btree::multimap<K, V, C, A, N>& y) {
x.swap(y);
}
#endif // BTREE_MAP_H__
+183
View File
@@ -0,0 +1,183 @@
/*
* Copyright (c) 2019 German Mendez Bravo (Kronuz)
* Copyright (c) 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*
* A btree::set<> implements the STL unique sorted associative container
* interface (a.k.a set<>) using a btree. See btree.h for details of the btree
* implementation and caveats.
*/
#ifndef BTREE_SET_H__
#define BTREE_SET_H__
#include "btree.h"
namespace btree {
// The set class is needed mainly for its constructors.
template <typename Key,
typename Compare = std::less<Key>,
typename Alloc = std::allocator<Key>,
int TargetNodeSize = 256>
class set : public btree_unique_container<
btree<btree_set_params<Key, Compare, Alloc, TargetNodeSize> > > {
typedef set<Key, Compare, Alloc, TargetNodeSize> self_type;
typedef btree_set_params<Key, Compare, Alloc, TargetNodeSize> params_type;
typedef btree<params_type> btree_type;
typedef btree_unique_container<btree_type> super_type;
public:
typedef typename btree_type::key_compare key_compare;
typedef typename btree_type::allocator_type allocator_type;
public:
// Default constructor.
set(const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type())
: super_type(comp, alloc) {
}
// Copy constructor.
set(const self_type& x)
: super_type(x) {
}
// Range constructor.
template <class InputIterator>
set(InputIterator b, InputIterator e,
const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type())
: super_type(b, e, comp, alloc) {
}
};
} // namespace btree
template <typename K, typename C, typename A, int N>
bool operator==(const btree::set<K, C, A, N>& lhs, const btree::set<K, C, A, N>& rhs) {
return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin());
}
template <typename K, typename C, typename A, int N>
bool operator<(const btree::set<K, C, A, N>& lhs, const btree::set<K, C, A, N>& rhs) {
return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
}
template <typename K, typename C, typename A, int N>
bool operator!=(const btree::set<K, C, A, N>& lhs, const btree::set<K, C, A, N>& rhs) {
return !(lhs == rhs);
}
template <typename K, typename C, typename A, int N>
bool operator>(const btree::set<K, C, A, N>& lhs, const btree::set<K, C, A, N>& rhs) {
return rhs < lhs;
}
template <typename K, typename C, typename A, int N>
bool operator>=(const btree::set<K, C, A, N>& lhs, const btree::set<K, C, A, N>& rhs) {
return !(lhs < rhs);
}
template <typename K, typename C, typename A, int N>
bool operator<=(const btree::set<K, C, A, N>& lhs, const btree::set<K, C, A, N>& rhs) {
return !(rhs < lhs);
}
template <typename K, typename C, typename A, int N>
inline void swap(btree::set<K, C, A, N>& x, btree::set<K, C, A, N>& y) {
x.swap(y);
}
namespace btree {
// The multiset class is needed mainly for its constructors.
template <typename Key,
typename Compare = std::less<Key>,
typename Alloc = std::allocator<Key>,
int TargetNodeSize = 256>
class multiset : public btree_multi_container<
btree<btree_set_params<Key, Compare, Alloc, TargetNodeSize> > > {
typedef multiset<Key, Compare, Alloc, TargetNodeSize> self_type;
typedef btree_set_params<Key, Compare, Alloc, TargetNodeSize> params_type;
typedef btree<params_type> btree_type;
typedef btree_multi_container<btree_type> super_type;
public:
typedef typename btree_type::key_compare key_compare;
typedef typename btree_type::allocator_type allocator_type;
public:
// Default constructor.
multiset(const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type())
: super_type(comp, alloc) {
}
// Copy constructor.
multiset(const self_type& x)
: super_type(x) {
}
// Range constructor.
template <class InputIterator>
multiset(InputIterator b, InputIterator e,
const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type())
: super_type(b, e, comp, alloc) {
}
};
} // namespace btree
template <typename K, typename C, typename A, int N>
bool operator==(const btree::multiset<K, C, A, N>& lhs, const btree::multiset<K, C, A, N>& rhs) {
return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin());
}
template <typename K, typename C, typename A, int N>
bool operator<(const btree::multiset<K, C, A, N>& lhs, const btree::multiset<K, C, A, N>& rhs) {
return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
}
template <typename K, typename C, typename A, int N>
bool operator!=(const btree::multiset<K, C, A, N>& lhs, const btree::multiset<K, C, A, N>& rhs) {
return !(lhs == rhs);
}
template <typename K, typename C, typename A, int N>
bool operator>(const btree::multiset<K, C, A, N>& lhs, const btree::multiset<K, C, A, N>& rhs) {
return rhs < lhs;
}
template <typename K, typename C, typename A, int N>
bool operator>=(const btree::multiset<K, C, A, N>& lhs, const btree::multiset<K, C, A, N>& rhs) {
return !(lhs < rhs);
}
template <typename K, typename C, typename A, int N>
bool operator<=(const btree::multiset<K, C, A, N>& lhs, const btree::multiset<K, C, A, N>& rhs) {
return !(rhs < lhs);
}
template <typename K, typename C, typename A, int N>
inline void swap(btree::multiset<K, C, A, N>& x, btree::multiset<K, C, A, N>& y) {
x.swap(y);
}
#endif // BTREE_SET_H__
+1 -1
View File
@@ -8,7 +8,7 @@ namespace build {
enum BuildType {
STABLE,
BETA,
ALPHA,
NIGHTLY,
PRIVATE
};
+3 -3
View File
@@ -98,7 +98,7 @@ std::shared_ptr<ViewEntry> ClientChannelView::find_channel(const std::shared_ptr
while(heads.front()) {
auto parent = heads.front()->parent();
if(!parent && heads.front()->properties()[property::CHANNEL_PID].as<ChannelId>() != 0) {
if(!parent && heads.front()->properties()[property::CHANNEL_PID].as_or<ChannelId>(0) != 0) {
head = this->find_linked_entry(channel->channelId(), nullptr);//We're searching for a deleted head! So lets iterate over everything
deep_search = true;
break;
@@ -172,7 +172,7 @@ std::deque<std::shared_ptr<ViewEntry>> ClientChannelView::insert_channels(shared
continue;
};
auto now_prv = this->find_channel(entry->previousChannelId());
debugMessage(this->getServerId(), "{}[CHANNELS] Insert channel {} ({}) after {} ({}). Original view prv: {} ({}). Original prv: {} ({})",
logTrace(this->getServerId(), "{}[CHANNELS] Insert channel {} ({}) after {} ({}). Original view prv: {} ({}). Original prv: {} ({})",
CLIENT_STR_LOG_PREFIX_(this->owner),
channel->channelId(), channel->name(),
entry->previousChannelId(), now_prv ? now_prv->channel()->name() : "",
@@ -225,7 +225,7 @@ std::deque<std::shared_ptr<ViewEntry>> ClientChannelView::show_channel(std::shar
remote_previous = remote_previous->previous;
}
auto previous_channel = previous ? previous->channel() : nullptr; //weak could be may nullptr
debugMessage(this->getServerId(), "{}[CHANNELS] Insert channel {} ({}) after {} ({})",
logTrace(this->getServerId(), "{}[CHANNELS] Insert channel {} ({}) after {} ({})",
CLIENT_STR_LOG_PREFIX_(this->owner),
channel->channelId(), channel->name(),
previous ? previous->channelId() : 0, previous_channel ? previous_channel->name() : ""
+55 -28
View File
@@ -5,7 +5,6 @@
#include "misc/rnd.h"
#include "src/VirtualServer.h"
#include "src/client/ConnectedClient.h"
#include "src/server/file/FileServer.h"
#include "src/InstanceHandler.h"
#include "../manager/ConversationManager.h"
@@ -13,9 +12,8 @@ using namespace std;
using namespace ts;
using namespace ts::server;
extern InstanceHandler* serverInstance;
ServerChannel::ServerChannel(ChannelId parentId, ChannelId channelId) : BasicChannel(parentId, channelId) {
ServerChannel::ServerChannel(uint32_t rtc_channel_id, ChannelId parentId, ChannelId channelId) : BasicChannel(parentId, channelId),
rtc_channel_id{rtc_channel_id} {
memtrack::allocated<ServerChannel>(this);
}
@@ -56,22 +54,23 @@ size_t ServerChannel::client_count() {
return result;
}
void ServerChannel::setProperties(const std::shared_ptr<Properties> &ptr) {
void ServerChannel::setProperties(const std::shared_ptr<PropertyManager> &ptr) {
BasicChannel::setProperties(ptr);
}
ServerChannelTree::ServerChannelTree(const std::shared_ptr<server::VirtualServer>& server, sql::SqlManager* sql) : sql(sql), server(server) { }
ServerChannelTree::ServerChannelTree(const std::shared_ptr<server::VirtualServer>& server, sql::SqlManager* sql) : sql(sql), server_ref(server) { }
ServerChannelTree::~ServerChannelTree() { }
void ServerChannelTree::deleteSemiPermanentChannels() {
loop:
for(const auto& ch : this->channels())
for(const auto& ch : this->channels()) {
if(ch->channelType() == ChannelType::semipermanent || ch->channelType() == ChannelType::temporary){ //We also delete private channels
this->delete_channel_root(ch);
goto loop;
}
}
}
ChannelId ServerChannelTree::generateChannelId() {
@@ -91,18 +90,22 @@ std::shared_ptr<BasicChannel> ServerChannelTree::createChannel(ChannelId parentI
std::shared_ptr<BasicChannel> channel = BasicChannelTree::createChannel(parentId, orderId, name);
if(!channel) return channel;
auto properties = serverInstance->databaseHelper()->loadChannelProperties(this->server.lock(), channel->channelId());
for(const auto& prop : channel->properties().list_properties())
if(prop.isModified()) //Copy the already set properties
/* TODO: Speed up (skip the database query) */
auto properties = serverInstance->databaseHelper()->loadChannelProperties(this->server_ref.lock(), channel->channelId());
for(const auto& prop : channel->properties()->list_properties()) {
if(prop.isModified()) { //Copy the already set properties
(*properties)[prop.type()] = prop.value();
}
}
static_pointer_cast<ServerChannel>(channel)->setProperties(properties);
static_pointer_cast<ServerChannel>(channel)->setPermissionManager(serverInstance->databaseHelper()->loadChannelPermissions(this->server.lock(), channel->channelId()));
static_pointer_cast<ServerChannel>(channel)->setPermissionManager(serverInstance->databaseHelper()->loadChannelPermissions(this->server_ref.lock(), channel->channelId()));
channel->properties()[property::CHANNEL_CREATED_AT] = chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now().time_since_epoch()).count();
channel->properties()[property::CHANNEL_LAST_LEFT] = chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now().time_since_epoch()).count();
auto result = sql::command(this->sql, "INSERT INTO `channels` (`serverId`, `channelId`, `type`, `parentId`) VALUES(:sid, :chid, :type, :parent);", variable{":sid", this->getServerId()}, variable{":chid", channel->channelId()}, variable{":type", channel->channelType()}, variable{":parent", channel->parent() ? channel->parent()->channelId() : 0}).execute();
auto result = sql::command(this->sql, "INSERT INTO `channels` (`serverId`, `channelId`, `parentId`) VALUES(:sid, :chid, :parent);", variable{":sid", this->getServerId()}, variable{":chid", channel->channelId()}, variable{":parent", channel->parent() ? channel->parent()->channelId() : 0}).execute();
auto pf = LOG_SQL_CMD;
pf(result);
@@ -122,7 +125,7 @@ inline std::shared_ptr<TreeView::LinkedTreeEntry> findLinkedChannelByPool(const
}
ServerId ServerChannelTree::getServerId() {
auto s = this->server.lock();
auto s = this->server_ref.lock();
return s ? s->getServerId() : 0UL;
}
@@ -132,11 +135,12 @@ bool ServerChannelTree::initializeTempParents() {
auto channel = dynamic_pointer_cast<BasicChannel>(linked_channel->entry);
assert(channel);
if(channel->properties().hasProperty(property::CHANNEL_PID) && channel->properties()[property::CHANNEL_PID].as<ChannelId>() != 0){
if(channel->properties()[property::CHANNEL_PID].as_or<ChannelId>(0) != 0){
if(!channel->parent())
linked_channel->parent = findLinkedChannelByPool(this->tmpChannelList, channel->properties()[property::CHANNEL_PID]);
if(!channel->parent()){
logError(this->getServerId(), "Invalid channel parent (Channel does not exists). Channel id: {} ({}) Missing parent id: {}", channel->channelId(), channel->name(), channel->properties()[property::CHANNEL_PID].as<ChannelId>());
logError(this->getServerId(), "Invalid channel parent (Channel does not exists). Channel id: {} ({}) Missing parent id: {}", channel->channelId(), channel->name(),
channel->properties()[property::CHANNEL_PID].as_or<ChannelId>(0));
logError(this->getServerId(), "Resetting parent");
channel->properties()[property::CHANNEL_PID] = 0;
}
@@ -311,7 +315,7 @@ inline std::shared_ptr<TreeView::LinkedTreeEntry> buildChannelTree(ServerId serv
}
auto evaluated_parent_id = channel->parent() ? channel->parent()->channelId() : 0;
if(evaluated_parent_id != channel->properties()[property::CHANNEL_PID].as<ChannelId>()) {
if(evaluated_parent_id != channel->properties()[property::CHANNEL_PID].as_or<ChannelId>(0)) {
debugMessage(serverId, "Fixed parent id for channel {} ({}). New parent channel {}", entry->entry->channelId(), channel->name(), evaluated_parent_id);
channel->properties()[property::CHANNEL_PID] = evaluated_parent_id;
}
@@ -481,6 +485,7 @@ bool ServerChannelTree::validateChannelNames() {
bool ServerChannelTree::validateChannelIcons() {
for(const auto &channel : this->channels()) {
auto iconId = (IconId) channel->properties()[property::CHANNEL_ICON_ID];
#if 0
if(iconId != 0 && !serverInstance->getFileServer()->iconExists(this->server.lock(), iconId)) {
logMessage(this->getServerId(), "[FILE] Missing channel icon (" + to_string(iconId) + ").");
if(config::server::delete_missing_icon_permissions) {
@@ -488,12 +493,13 @@ bool ServerChannelTree::validateChannelIcons() {
channel->permissions()->set_permission(permission::i_icon_id, {0, 0}, permission::v2::PermissionUpdateType::set_value, permission::v2::PermissionUpdateType::do_nothing);
}
}
#endif
}
return true;
}
void ServerChannelTree::loadChannelsFromDatabase() {
auto res = sql::command(this->sql, "SELECT * FROM `channels` WHERE `serverId` = :sid", variable{":sid", this->getServerId()}).query(&ServerChannelTree::loadChannelFromData, this);
auto res = sql::command(this->sql, "SELECT `channelId`, `parentId` FROM `channels` WHERE `serverId` = :sid", variable{":sid", this->getServerId()}).query(&ServerChannelTree::loadChannelFromData, this);
(LOG_SQL_CMD)(res);
if(!res){
logError(this->getServerId(), "Could not load channel tree from database");
@@ -512,7 +518,6 @@ void ServerChannelTree::loadChannelsFromDatabase() {
int ServerChannelTree::loadChannelFromData(int argc, char **data, char **column) {
ChannelId channelId = 0;
ChannelId parentId = 0;
auto type = static_cast<ChannelType::ChannelType>(0xFF);
int index = 0;
@@ -520,8 +525,6 @@ int ServerChannelTree::loadChannelFromData(int argc, char **data, char **column)
for(index = 0; index < argc; index++){
if(strcmp(column[index], "channelId") == 0) channelId = static_cast<ChannelId>(stoll(data[index]));
else if(strcmp(column[index], "parentId") == 0) parentId = static_cast<ChannelId>(stoll(data[index]));
else if(strcmp(column[index], "type") == 0) type = static_cast<ChannelType::ChannelType>(stoll(data[index]));
else if(strcmp(column[index], "serverId") == 0) {}
else logError(this->getServerId(), "ServerChannelTree::loadChannelFromData called with invalid column from sql \"{}\"", column[index]);
}
} catch (std::exception& ex) {
@@ -531,9 +534,19 @@ int ServerChannelTree::loadChannelFromData(int argc, char **data, char **column)
//assert(type != 0xFF);
assert(channelId != 0);
if(channelId == 0)
return 0;
auto channel = std::make_shared<ServerChannel>(parentId, channelId);
auto server = this->server.lock();
auto server = this->server_ref.lock();
std::shared_ptr<ServerChannel> channel;
if(server) {
auto rtc_channel_id = server->rtc_server().create_channel();
channel = std::make_shared<ServerChannel>(rtc_channel_id, parentId, channelId);
} else {
channel = std::make_shared<ServerChannel>(0, parentId, channelId);
}
static_pointer_cast<ServerChannel>(channel)->setProperties(serverInstance->databaseHelper()->loadChannelProperties(server, channelId));
static_pointer_cast<ServerChannel>(channel)->setPermissionManager(serverInstance->databaseHelper()->loadChannelPermissions(server, channel->channelId()));
@@ -544,24 +557,30 @@ int ServerChannelTree::loadChannelFromData(int argc, char **data, char **column)
}
deque<ChannelId> ServerChannelTree::deleteChannelRoot(const std::shared_ptr<BasicChannel> &channel) {
auto server = this->server.lock();
auto server = this->server_ref.lock();
auto channels = this->delete_channel_root(channel);
deque<ChannelId> channel_ids;
for(const auto& channel : channels)
for(const auto& channel : channels) {
channel_ids.push_back(channel->channelId());
}
return channel_ids;
}
void ServerChannelTree::on_channel_entry_deleted(const shared_ptr<BasicChannel> &channel) {
BasicChannelTree::on_channel_entry_deleted(channel);
auto server = this->server.lock();
auto server_channel = dynamic_pointer_cast<ServerChannel>(channel);
assert(server_channel);
auto server = this->server_ref.lock();
if(server) {
server->getGroupManager()->handleChannelDeleted(channel->channelId());
server->conversation_manager()->delete_conversation(channel->channelId());
} else
server->rtc_server().destroy_channel(server_channel->rtc_channel_id);
} else {
serverInstance->getGroupManager()->handleChannelDeleted(channel->channelId());
}
auto sql_result = sql::command(this->sql, "DELETE FROM `channels` WHERE `serverId` = '" + to_string(this->getServerId()) + "' AND `channelId` = '" + to_string(channel->channelId()) + "'").execute();
@@ -570,11 +589,19 @@ void ServerChannelTree::on_channel_entry_deleted(const shared_ptr<BasicChannel>
sql_result = sql::command(this->sql, "DELETE FROM `properties` WHERE `serverId` = '" + to_string(this->getServerId()) + "' AND `id` = '" + to_string(channel->channelId()) + "' AND `type` = " + to_string(property::PropertyType::PROP_TYPE_CHANNEL)).execute();
LOG_SQL_CMD(sql_result);
serverInstance->databaseHelper()->deleteChannelPermissions(this->server.lock(), channel->channelId());
serverInstance->databaseHelper()->deleteChannelPermissions(this->server_ref.lock(), channel->channelId());
sql_result = sql::command(this->sql, "DELETE FROM `assignedGroups` WHERE `serverId` = '" + to_string(this->getServerId()) + "' AND `channelId` = '" + to_string(channel->channelId()) + "'").execute();
LOG_SQL_CMD(sql_result);
}
std::shared_ptr<BasicChannel> ServerChannelTree::allocateChannel(const shared_ptr<BasicChannel> &parent, ChannelId channelId) {
return std::make_shared<ServerChannel>(parent ? parent->channelId() : 0, channelId);
auto server = this->server_ref.lock();
auto parent_channel_id = parent ? parent->channelId() : 0;
if(server) {
auto rtc_channel_id = server->rtc_server().create_channel();
return std::make_shared<ServerChannel>(rtc_channel_id, parent_channel_id, channelId);
} else {
return std::make_shared<ServerChannel>(0, parent_channel_id, channelId);
}
}
+11 -9
View File
@@ -1,11 +1,11 @@
#pragma once
#include <stdint.h>
#include <cstdlib>
#include "Properties.h"
#include "PermissionManager.h"
#include "BasicChannel.h"
#include "../Group.h"
#include "../rtc/lib.h"
#include <memory>
#include <sql/SqlQuery.h>
@@ -19,10 +19,12 @@ namespace ts {
class ServerChannel : public BasicChannel {
friend class ServerChannelTree;
public:
ServerChannel(ChannelId parentId, ChannelId channelId);
ServerChannel(uint32_t rtc_channel_id, ChannelId parentId, ChannelId channelId);
~ServerChannel() override;
~ServerChannel();
void setProperties(const std::shared_ptr<Properties> &ptr) override;
void setProperties(const std::shared_ptr<PropertyManager> &ptr) override;
uint32_t rtc_channel_id;
std::shared_mutex client_lock;
std::deque<std::weak_ptr<server::ConnectedClient>> clients;
@@ -37,24 +39,24 @@ namespace ts {
class ServerChannelTree : public BasicChannelTree {
public:
ServerChannelTree(const std::shared_ptr<server::VirtualServer>&, sql::SqlManager*);
virtual ~ServerChannelTree();
~ServerChannelTree() override;
void loadChannelsFromDatabase();
virtual std::shared_ptr<BasicChannel> createChannel(ChannelId parentId, ChannelId orderId, const std::string &name) override;
std::shared_ptr<BasicChannel> createChannel(ChannelId parentId, ChannelId orderId, const std::string &name) override;
virtual std::deque<ChannelId> deleteChannelRoot(const std::shared_ptr<BasicChannel> &channel);
void deleteSemiPermanentChannels();
std::shared_ptr<LinkedTreeEntry> tree_head() { return this->head; }
protected:
virtual ChannelId generateChannelId() override;
ChannelId generateChannelId() override;
virtual void on_channel_entry_deleted(const std::shared_ptr<BasicChannel> &channel) override;
void on_channel_entry_deleted(const std::shared_ptr<BasicChannel> &channel) override;
std::shared_ptr<BasicChannel> allocateChannel(const std::shared_ptr<BasicChannel> &parent, ChannelId channelId) override;
private:
std::weak_ptr<server::VirtualServer> server;
std::weak_ptr<server::VirtualServer> server_ref;
ServerId getServerId();
sql::SqlManager* sql;
+355 -159
View File
@@ -10,18 +10,13 @@
#include "src/VirtualServer.h"
#include "voice/VoiceClient.h"
#include "../server/VoiceServer.h"
#include "../server/file/FileServer.h"
#include "../InstanceHandler.h"
#include "ConnectedClient.h"
#include <event.h>
using namespace std;
using namespace std::chrono;
using namespace ts;
using namespace ts::server;
using namespace ts::token;
extern ts::server::InstanceHandler* serverInstance;
@@ -29,9 +24,7 @@ ConnectedClient::ConnectedClient(sql::SqlManager* db, const std::shared_ptr<Virt
memtrack::allocated<ConnectedClient>(this);
memset(&this->remote_address, 0, sizeof(this->remote_address));
connectionStatistics = make_shared<stats::ConnectionStatistics>(server ? server->getServerStatistics() : nullptr, false);
this->connectionStatistics->measure_bandwidths(false); /* done by the client and we trust this */
connectionStatistics = make_shared<stats::ConnectionStatistics>(server ? server->getServerStatistics() : nullptr);
channels = make_shared<ClientChannelView>(this);
}
@@ -39,6 +32,40 @@ ConnectedClient::~ConnectedClient() {
memtrack::freed<ConnectedClient>(this);
}
void ConnectedClient::initialize_weak_reference(const std::shared_ptr<ConnectedClient> &self) {
assert(this == &*self);
this->_this = self;
auto weak_self = std::weak_ptr{self};
this->task_update_needed_permissions = multi_shot_task{serverInstance->general_task_executor(), "update permissions for " + this->getLoggingPeerIp(), [weak_self]{
auto self = weak_self.lock();
if(self) {
auto permissions_changed = self->update_client_needed_permissions();
logTrace(self->getServerId(), "{} Updated client permissions. Permissions changed: {}", CLIENT_STR_LOG_PREFIX_(self), permissions_changed);
if(permissions_changed) {
self->sendNeededPermissions(true);
}
}
}};
this->task_update_channel_client_properties = multi_shot_task{serverInstance->general_task_executor(), "update channel properties for " + this->getLoggingPeerIp(), [weak_self]{
auto self = weak_self.lock();
if(self) {
self->updateChannelClientProperties(true, true);
}
}};
}
bool ConnectedClient::loadDataForCurrentServer() {
auto result = DataClient::loadDataForCurrentServer();
if(!result) {
return false;
}
this->properties()[property::CONNECTION_CLIENT_IP] = this->getLoggingPeerIp();
return true;
}
std::shared_ptr<ConnectionInfoData> ConnectedClient::request_connection_info(const std::shared_ptr<ConnectedClient> &receiver, bool& send_temp) {
auto& info = this->connection_info;
@@ -82,96 +109,117 @@ std::shared_ptr<ConnectionInfoData> ConnectedClient::request_connection_info(con
return info.data;
}
//Attention the client should be only read only locked!
void ConnectedClient::updateChannelClientProperties(bool lock_channel_tree, bool notify_self) {
/* this->server may be null! */
shared_ptr<VirtualServer> server_ref = this->server;
/* The server and the current channel might change while executing this method! */
auto server_ref = this->server;
auto channel = this->currentChannel;
auto permissions = this->calculate_permissions({
permission::i_client_talk_power,
permission::b_client_ignore_antiflood,
permission::i_channel_view_power,
permission::b_channel_ignore_view_power
}, this->currentChannel ? this->currentChannel->channelId() : 0);
permission::b_channel_ignore_view_power,
}, channel ? channel->channelId() : 0);
permission::v2::PermissionFlaggedValue
permission_talk_power{0, false},
permission_ignore_antiflood{0, false},
permission_channel_view_power{0, false},
permission_channel_ignore_view_power{0, false};
for(const auto& perm : permissions) {
if(perm.first == permission::i_client_talk_power)
if(perm.first == permission::i_client_talk_power) {
permission_talk_power = perm.second;
else if(perm.first == permission::b_client_ignore_antiflood)
} else if(perm.first == permission::b_client_ignore_antiflood) {
permission_ignore_antiflood = perm.second;
else if(perm.first == permission::i_channel_view_power)
} else if(perm.first == permission::i_channel_view_power) {
permission_channel_view_power = perm.second;
else if(perm.first == permission::b_channel_ignore_view_power)
} else if(perm.first == permission::b_channel_ignore_view_power) {
permission_channel_ignore_view_power = perm.second;
else sassert(false);
} else {
sassert(false);
}
}
deque<property::ClientProperties> notifyList;
debugMessage(this->getServerId(), "{} Got a channel talk power of {} Talk power set is {}", CLIENT_STR_LOG_PREFIX, permission_talk_power, this->properties()[property::CLIENT_TALK_POWER].as<uint64_t>());
if((permission_talk_power.has_value ? permission_talk_power.value : 0) != this->properties()[property::CLIENT_TALK_POWER].as<uint64_t>()) { //We do not have to update tp if there's no channel
this->properties()[property::CLIENT_TALK_POWER] = (permission_talk_power.has_value ? permission_talk_power.value : 0);
notifyList.emplace_back(property::CLIENT_TALK_POWER);
std::deque<property::ClientProperties> updated_client_properties;
{
auto old_talk_power = this->properties()[property::CLIENT_TALK_POWER].as_or<int64_t>(0);
auto new_talk_power = permission_talk_power.has_value ? permission_talk_power.value : 0;
auto update = this->properties()[property::CLIENT_IS_TALKER].as<bool>() || this->properties()[property::CLIENT_TALK_REQUEST].as<int64_t>() > 0;
if(update && this->currentChannel) {
if(this->currentChannel->talk_power_granted(permission_talk_power)) {
this->properties()[property::CLIENT_IS_TALKER] = 0;
this->properties()[property::CLIENT_TALK_REQUEST] = 0;
this->properties()[property::CLIENT_TALK_REQUEST_MSG] = "";
notifyList.emplace_back(property::CLIENT_IS_TALKER);
notifyList.emplace_back(property::CLIENT_TALK_REQUEST);
notifyList.emplace_back(property::CLIENT_TALK_REQUEST_MSG);
debugMessage(this->getServerId(), "{} Recalculated talk power. New value: {} Old value: {}", CLIENT_STR_LOG_PREFIX, new_talk_power, old_talk_power);
if(old_talk_power != new_talk_power) {
this->properties()[property::CLIENT_TALK_POWER] = new_talk_power;
updated_client_properties.emplace_back(property::CLIENT_TALK_POWER);
auto retract_request = this->properties()[property::CLIENT_IS_TALKER].as_or<bool>(false);
if(!retract_request && channel) {
retract_request = channel->talk_power_granted(permission_talk_power);
}
if(retract_request) {
if(this->properties()[property::CLIENT_IS_TALKER].update_value(0)) {
updated_client_properties.emplace_back(property::CLIENT_IS_TALKER);
}
if(this->properties()[property::CLIENT_TALK_REQUEST].update_value(0)) {
updated_client_properties.emplace_back(property::CLIENT_TALK_REQUEST);
}
if(this->properties()[property::CLIENT_TALK_REQUEST_MSG].update_value("")) {
updated_client_properties.emplace_back(property::CLIENT_TALK_REQUEST_MSG);
}
}
}
}
IconId iconId = 0;
auto local_permissions = this->clientPermissions;
if(local_permissions) {
permission::v2::PermissionFlaggedValue value{0, false};
auto permission_flags = local_permissions->permission_flags(permission::i_icon_id);
if(permission_flags.channel_specific && this->currentChannel) {
auto val = local_permissions->channel_permission(permission::i_icon_id, this->currentChannel->channelId());
value = {val.values.value, val.flags.value_set};
}
if(!value.has_value)
value = local_permissions->permission_value_flagged(permission::i_icon_id);
if(value.has_value)
iconId = value.value;
}
logTrace(this->getServerId(), "[CLIENT] Updating client icon from " + to_string(this->properties()[property::CLIENT_ICON_ID].as<IconId>()) + " to " + to_string(iconId));
if(this->properties()[property::CLIENT_ICON_ID].as<IconId>() != iconId){
if(server_ref && iconId != 0) {
auto dir = serverInstance->getFileServer()->iconDirectory(server_ref);
if(!serverInstance->getFileServer()->iconExists(server_ref, iconId)) {
logMessage(this->getServerId(), "[FILE] Missing client icon (" + to_string(iconId) + ").");
iconId = 0;
{
IconId current_icon_id = this->properties()[property::CLIENT_ICON_ID].as_or<IconId>(0);
IconId new_icon_id{ 0};
auto local_permissions = this->clientPermissions;
if(local_permissions) {
permission::v2::PermissionFlaggedValue value{0, false};
auto permission_flags = local_permissions->permission_flags(permission::i_icon_id);
if(permission_flags.channel_specific && this->currentChannel) {
auto val = local_permissions->channel_permission(permission::i_icon_id, this->currentChannel->channelId());
value = { val.values.value, val.flags.value_set };
}
if(!value.has_value) {
value = local_permissions->permission_value_flagged(permission::i_icon_id);
}
if(value.has_value) {
new_icon_id = value.value;
}
}
if(this->properties()[property::CLIENT_ICON_ID].as<IconId>() != iconId) {
this->properties()[property::CLIENT_ICON_ID] = (IconId) iconId;
notifyList.emplace_back(property::CLIENT_ICON_ID);
if(this->properties()[property::CLIENT_ICON_ID].update_value(new_icon_id)) {
logTrace(this->getServerId(), "{} Updating client icon from {} to {}", CLIENT_STR_LOG_PREFIX, current_icon_id, new_icon_id);
updated_client_properties.emplace_back(property::CLIENT_ICON_ID);
}
}
auto pSpeaker = this->clientPermissions ? this->clientPermissions->channel_permission(permission::b_client_is_priority_speaker, this->getChannelId()) : permission::v2::empty_channel_permission;
auto pSpeakerGranted = permission::v2::permission_granted(1, {pSpeaker.values.value, pSpeaker.flags.value_set});
if(properties()[property::CLIENT_IS_PRIORITY_SPEAKER].as<bool>() != pSpeakerGranted){
properties()[property::CLIENT_IS_PRIORITY_SPEAKER] = pSpeakerGranted;
notifyList.emplace_back(property::CLIENT_IS_PRIORITY_SPEAKER);
{
auto local_permissions = this->clientPermissions;
auto permission_speaker = local_permissions ?
local_permissions->channel_permission(permission::b_client_is_priority_speaker, channel ? channel->channelId() : 0) :
permission::v2::empty_channel_permission;
auto speaker_granted = permission::v2::permission_granted(1, { permission_speaker.values.value, permission_speaker.flags.value_set });
if(properties()[property::CLIENT_IS_PRIORITY_SPEAKER].update_value(speaker_granted)){
updated_client_properties.emplace_back(property::CLIENT_IS_PRIORITY_SPEAKER);
}
}
block_flood = !permission::v2::permission_granted(1, permission_ignore_antiflood);
if(server_ref)
server_ref->notifyClientPropertyUpdates(_this.lock(), notifyList, notify_self);
this->updateTalkRights(permission_talk_power.has_value ? permission_talk_power.value : 0);
if(server_ref) {
server_ref->notifyClientPropertyUpdates(this->ref(), updated_client_properties, notify_self);
}
if((this->channels_view_power != permission_channel_view_power || this->channels_ignore_view != permission_channel_ignore_view_power) && notify_self && this->currentChannel && server_ref) {
this->updateTalkRights(permission_talk_power);
if((this->channels_view_power != permission_channel_view_power || this->channels_ignore_view != permission_channel_ignore_view_power) && notify_self && channel && server_ref) {
this->channels_view_power = permission_channel_view_power;
this->channels_ignore_view = permission_channel_ignore_view_power;
@@ -184,22 +232,30 @@ void ConnectedClient::updateChannelClientProperties(bool lock_channel_tree, bool
client_channel_lock.lock();
}
deque<ChannelId> deleted;
for(const auto& update_entry : this->channels->update_channel_path(server_ref->channelTree->tree_head(), server_ref->channelTree->find_linked_entry(this->currentChannel->channelId()))) {
if(update_entry.first)
this->notifyChannelShow(update_entry.second->channel(), update_entry.second->previous_channel);
else deleted.push_back(update_entry.second->channelId());
/* might have been changed since we locked the tree */
if(channel) {
deque<ChannelId> deleted;
for(const auto& update_entry : this->channels->update_channel_path(server_ref->channelTree->tree_head(), server_ref->channelTree->find_linked_entry(channel->channelId()))) {
if(update_entry.first) {
this->notifyChannelShow(update_entry.second->channel(), update_entry.second->previous_channel);
} else {
deleted.push_back(update_entry.second->channelId());
}
}
if(!deleted.empty()) {
this->notifyChannelHide(deleted, false); /* we've locked the tree before */
}
}
if(!deleted.empty())
this->notifyChannelHide(deleted, false); /* we've locked the tree before */
}
}
void ConnectedClient::updateTalkRights(permission::PermissionValue talk_power) {
void ConnectedClient::updateTalkRights(permission::v2::PermissionFlaggedValue talk_power) {
bool flag = false;
flag |= this->properties()[property::CLIENT_IS_TALKER].as<bool>();
if(!flag && this->currentChannel) {
flag = this->currentChannel->talk_power_granted({talk_power, talk_power != permNotGranted});
flag |= this->properties()[property::CLIENT_IS_TALKER].as_or<bool>(false);
auto current_channel = this->currentChannel;
if(!flag && current_channel) {
flag = current_channel->talk_power_granted(talk_power);
}
this->allowedToTalk = flag;
}
@@ -215,7 +271,8 @@ void ConnectedClient::increaseFloodPoints(uint16_t num) {
bool ConnectedClient::shouldFloodBlock() {
if(!this->server) return false;
if(!this->block_flood) return false;
return this->floodPoints > this->server->properties()[property::VIRTUALSERVER_ANTIFLOOD_POINTS_NEEDED_COMMAND_BLOCK].as<uint16_t>();
return this->floodPoints >
this->server->properties()[property::VIRTUALSERVER_ANTIFLOOD_POINTS_NEEDED_COMMAND_BLOCK].as_or<uint16_t>(150);
}
std::deque<std::shared_ptr<BasicChannel>> ConnectedClient::subscribeChannel(const std::deque<std::shared_ptr<BasicChannel>>& targets, bool lock_channel, bool enforce) {
@@ -381,7 +438,7 @@ bool ConnectedClient::notifyClientLeftView(
std::shared_ptr<ConnectedClient> invoker,
bool lock_channel_tree) {
assert(!lock_channel_tree); /* not supported yet! */
assert(client && client->getClientId() != 0);
assert(client == this || (client && client->getClientId() != 0));
assert(client->currentChannel || &*client == this);
if(client != this) {
@@ -555,13 +612,17 @@ bool ConnectedClient::notifyClientNeededPermissions() {
Command cmd("notifyclientneededpermissions");
int index = 0;
unique_lock cache_lock(this->cached_permissions_lock);
auto permissions = this->cached_permissions;
unique_lock cache_lock(this->client_needed_permissions_lock);
auto permissions = this->client_needed_permissions;
cache_lock.unlock();
for(const auto& value : permissions) {
cmd[index]["permid"] = value.first;
cmd[index++]["permvalue"] = value.second.has_value ? value.second.value : 0;
for(const auto& [ key, value ] : permissions) {
if(!value.has_value) {
continue;
}
cmd[index]["permid"] = key;
cmd[index++]["permvalue"] = value.value;
}
if(index == 0) {
@@ -574,29 +635,20 @@ bool ConnectedClient::notifyClientNeededPermissions() {
}
bool ConnectedClient::notifyError(const command_result& result, const std::string& retCode) {
Command cmd("error");
ts::command_builder command{"error"};
if(result.is_detailed()) {
auto detailed = result.details();
cmd["id"] = (int) detailed->error_id;
cmd["msg"] = findError(detailed->error_id).message;
this->writeCommandResult(command, result);
if(!retCode.empty())
command.put_unchecked(0, "return_code", retCode);
for(const auto& extra : detailed->extra_properties)
cmd[extra.first] = extra.second;
} else {
cmd["id"] = (int) result.error_code();
cmd["msg"] = findError(result.error_code()).message;
if(result.is_permission_error())
cmd["failed_permid"] = result.permission_id();
}
if(retCode.length() > 0)
cmd["return_code"] = retCode;
this->sendCommand(cmd);
this->sendCommand(command);
return true;
}
void ConnectedClient::writeCommandResult(ts::command_builder &cmd_builder, const command_result &result, const std::string& errorCodeKey) {
result.build_error_response(cmd_builder, errorCodeKey);
}
inline std::shared_ptr<ViewEntry> pop_view_entry(std::deque<std::shared_ptr<ViewEntry>>& pool, ChannelId id) {
for(auto it = pool.begin(); it != pool.end(); it++) {
if((*it)->channelId() == id) {
@@ -623,11 +675,11 @@ inline void send_channels(ConnectedClient* client, ChannelIT begin, const Channe
continue;
}
for (const auto &elm : channel->properties().list_properties(property::FLAG_CHANNEL_VIEW, client->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) {
for (const auto &elm : channel->properties()->list_properties(property::FLAG_CHANNEL_VIEW, client->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) {
if(elm.type() == property::CHANNEL_ORDER)
builder.put_unchecked(index, elm.type().name, override_orderid ? 0 : (*begin)->previous_channel);
else
builder.put_unchecked(index, elm.type().name, elm.as<string>());
builder.put_unchecked(index, elm.type().name, elm.value());
}
begin++;
@@ -635,12 +687,7 @@ inline void send_channels(ConnectedClient* client, ChannelIT begin, const Channe
break;
}
if(dynamic_cast<VoiceClient*>(client)) {
auto vc = dynamic_cast<VoiceClient*>(client);
vc->sendCommand0(builder.build(), false, true); /* we need to process this command directly so it will be processed before the channellistfinished stuff */
} else {
client->sendCommand(builder);
}
client->sendCommand(builder);
if(begin != end)
send_channels(client, begin, end, override_orderid);
}
@@ -694,7 +741,7 @@ void ConnectedClient::sendChannelList(bool lock_channel_tree) {
logCritical(this->getServerId(), "ConnectedClient::sendChannelList => invalid (empty) own channel path!");
send_channels(this, entry_channels.begin(), entry_channels.end(), false);
this->notifyClientEnterView(_this.lock(), nullptr, "", this->currentChannel, ViewReasonId::VREASON_SYSTEM, nullptr, false); //Notify self after path is send
this->notifyClientEnterView(this->ref(), nullptr, "", this->currentChannel, ViewReasonId::VREASON_SYSTEM, nullptr, false); //Notify self after path is send
send_channels(this, channels.begin(), channels.end(), false);
//this->notifyClientEnterView(_this.lock(), nullptr, "", this->currentChannel, ViewReasonId::VREASON_SYSTEM, nullptr, false); //Notify self after path is send
this->sendCommand(Command("channellistfinished"));
@@ -709,7 +756,7 @@ void ConnectedClient::sendChannelDescription(const std::shared_ptr<BasicChannel>
auto limit = this->getType() == CLIENT_TEAMSPEAK ? 8192 : 131130;
auto description = channel->properties()[property::CHANNEL_DESCRIPTION].as<std::string>();
auto description = channel->properties()[property::CHANNEL_DESCRIPTION].value();
Command cmd("notifychanneledited");
cmd["cid"] = channel->channelId();
cmd["reasonid"] = 9;
@@ -717,7 +764,7 @@ void ConnectedClient::sendChannelDescription(const std::shared_ptr<BasicChannel>
this->sendCommand(cmd, true);
}
void ConnectedClient::tick(const std::chrono::system_clock::time_point &time) {
void ConnectedClient::tick_server(const std::chrono::system_clock::time_point &time) {
ALARM_TIMER(A1, "ConnectedClient::tick", milliseconds(2));
if(this->state == ConnectionState::CONNECTED) {
if(this->requireNeededPermissionResend)
@@ -725,44 +772,41 @@ void ConnectedClient::tick(const std::chrono::system_clock::time_point &time) {
if(this->lastOnlineTimestamp.time_since_epoch().count() == 0) {
this->lastOnlineTimestamp = time;
} else if(time - this->lastOnlineTimestamp > seconds(120)) {
this->properties()[property::CLIENT_MONTH_ONLINE_TIME] += duration_cast<seconds>(time - lastOnlineTimestamp).count();
this->properties()[property::CLIENT_TOTAL_ONLINE_TIME] += duration_cast<seconds>(time - lastOnlineTimestamp).count();
this->properties()[property::CLIENT_MONTH_ONLINE_TIME].increment_by<uint64_t>(duration_cast<seconds>(time - lastOnlineTimestamp).count());
this->properties()[property::CLIENT_TOTAL_ONLINE_TIME].increment_by<uint64_t>(duration_cast<seconds>(time - lastOnlineTimestamp).count());
lastOnlineTimestamp = time;
}
if(time - this->lastTransfareTimestamp > seconds(5)) {
lastTransfareTimestamp = time;
auto update = this->connectionStatistics->mark_file_bytes();
if(update.first > 0) {
this->properties()[property::CLIENT_MONTH_BYTES_DOWNLOADED] += update.first;
this->properties()[property::CLIENT_TOTAL_BYTES_DOWNLOADED] += update.first;
this->properties()[property::CLIENT_MONTH_BYTES_DOWNLOADED].increment_by<uint64_t>(update.first);
this->properties()[property::CLIENT_TOTAL_BYTES_DOWNLOADED].increment_by<uint64_t>(update.first);
}
if(update.second > 0) {
this->properties()[property::CLIENT_MONTH_BYTES_UPLOADED] += update.second;
this->properties()[property::CLIENT_TOTAL_BYTES_UPLOADED] += update.second;
this->properties()[property::CLIENT_MONTH_BYTES_UPLOADED].increment_by<uint64_t>(update.second);
this->properties()[property::CLIENT_TOTAL_BYTES_UPLOADED].increment_by<uint64_t>(update.second);
}
}
}
if(this->last_statistics_tick + seconds(5) < time) {
this->last_statistics_tick = time;
this->connectionStatistics->tick();
}
this->connectionStatistics->tick();
}
void ConnectedClient::sendServerInit() {
Command command("initserver");
for(const auto& prop : this->server->properties().list_properties(property::FLAG_SERVER_VIEW, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) {
command[prop.type().name] = prop.value();
for(const auto& prop : this->server->properties()->list_properties(property::FLAG_SERVER_VIEW, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) {
command[std::string{prop.type().name}] = prop.value();
}
command["virtualserver_maxclients"] = 32;
//Server stuff
command["client_talk_power"] = this->properties()[property::CLIENT_TALK_POWER].as<string>();
command["client_needed_serverquery_view_power"] = this->properties()[property::CLIENT_NEEDED_SERVERQUERY_VIEW_POWER].as<string>();
command["client_myteamspeak_id"] = this->properties()[property::CLIENT_MYTEAMSPEAK_ID].as<string>();
command["client_integrations"] = this->properties()[property::CLIENT_INTEGRATIONS].as<string>();
command["client_talk_power"] = this->properties()[property::CLIENT_TALK_POWER].value();
command["client_needed_serverquery_view_power"] = this->properties()[property::CLIENT_NEEDED_SERVERQUERY_VIEW_POWER].value();
command["client_myteamspeak_id"] = this->properties()[property::CLIENT_MYTEAMSPEAK_ID].value();
command["client_integrations"] = this->properties()[property::CLIENT_INTEGRATIONS].value();
if(ts::config::server::DefaultServerLicense == LicenseType::LICENSE_AUTOMATIC_INSTANCE){
if(serverInstance->getVoiceServerManager()->usedSlots() <= 32)
@@ -772,9 +816,9 @@ void ConnectedClient::sendServerInit() {
else
command["lt"] = LicenseType::LICENSE_HOSTING;
} else if(ts::config::server::DefaultServerLicense == LicenseType::LICENSE_AUTOMATIC_SERVER){
if(this->server->properties()[property::VIRTUALSERVER_MAXCLIENTS].as<size_t>() <= 32)
if(this->server->properties()[property::VIRTUALSERVER_MAXCLIENTS].as_or<size_t>(0) <= 32)
command["lt"] = LicenseType::LICENSE_NONE;
else if(this->server->properties()[property::VIRTUALSERVER_MAXCLIENTS].as<size_t>() <= 512)
else if(this->server->properties()[property::VIRTUALSERVER_MAXCLIENTS].as_or<size_t>(0) <= 512)
command["lt"] = LicenseType::LICENSE_NPL;
else
command["lt"] = LicenseType::LICENSE_HOSTING;
@@ -784,11 +828,7 @@ void ConnectedClient::sendServerInit() {
command["pv"] = 6; //Protocol version
command["acn"] = this->getDisplayName();
command["aclid"] = this->getClientId();
if(dynamic_cast<VoiceClient*>(this)) {
dynamic_cast<VoiceClient*>(this)->sendCommand0(command.build(), false, true); /* process it directly so the order for the channellist entries is ensured. (First serverinit then everything else) */
} else {
this->sendCommand(command);
}
this->sendCommand(command);
}
bool ConnectedClient::handleCommandFull(Command& cmd, bool disconnectOnFail) {
@@ -800,14 +840,38 @@ bool ConnectedClient::handleCommandFull(Command& cmd, bool disconnectOnFail) {
command_result result;
try {
result = this->handleCommand(cmd);
result.reset(this->handleCommand(cmd));
} catch(command_value_cast_failed& ex){
auto message = ex.key() + " at " + std::to_string(ex.index()) + " could not be casted to " + ex.target_type().name();
if(disconnectOnFail) {
this->disconnect(message);
return false;
} else {
result.reset(command_result{error::parameter_convert, message});
}
} catch(command_bulk_index_out_of_bounds_exception& ex){
auto message = "missing bulk for index " + std::to_string(ex.index());
if(disconnectOnFail) {
this->disconnect(message);
return false;
} else {
result.reset(command_result{error::parameter_invalid_count, message});
}
} catch(command_value_missing_exception& ex){
auto message = "missing value for " + ex.key() + (ex.bulk_index() == std::string::npos ? "" : " at " + std::to_string(ex.bulk_index()));
if(disconnectOnFail) {
this->disconnect(message);
return false;
} else {
result.reset(command_result{error::parameter_missing, message});
}
} catch(invalid_argument& ex){
logWarning(this->getServerId(), "{}[Command] Failed to handle command. Received invalid argument exception: {}", CLIENT_STR_LOG_PREFIX, ex.what());
if(disconnectOnFail) {
this->disconnect("Invalid argument (" + string(ex.what()) + ")");
return false;
} else {
result = command_result{error::parameter_convert};
result.reset(command_result{error::parameter_convert, ex.what()});
}
} catch (exception& ex) {
logWarning(this->getServerId(), "{}[Command] Failed to handle command. Received exception with message: {}", CLIENT_STR_LOG_PREFIX, ex.what());
@@ -815,7 +879,7 @@ bool ConnectedClient::handleCommandFull(Command& cmd, bool disconnectOnFail) {
this->disconnect("Error while command handling (" + string(ex.what()) + ")!");
return false;
} else {
result = command_result{error::vs_critical};
result.reset(command_result{error::vs_critical});
}
} catch (...) {
this->disconnect("Error while command handling! (unknown)");
@@ -823,7 +887,7 @@ bool ConnectedClient::handleCommandFull(Command& cmd, bool disconnectOnFail) {
}
bool generateReturnStatus = false;
if(result.error_code() != error::ok || this->getType() == ClientType::CLIENT_QUERY){
if(result.has_error() || this->getType() == ClientType::CLIENT_QUERY){
generateReturnStatus = true;
} else if(cmd["return_code"].size() > 0) {
generateReturnStatus = !cmd["return_code"].string().empty();
@@ -832,8 +896,9 @@ bool ConnectedClient::handleCommandFull(Command& cmd, bool disconnectOnFail) {
if(generateReturnStatus)
this->notifyError(result, cmd["return_code"].size() > 0 ? cmd["return_code"].first().as<std::string>() : "");
if(result.error_code() != error::ok && this->state == ConnectionState::INIT_HIGH)
if(result.has_error() && this->state == ConnectionState::INIT_HIGH) {
this->close_connection(system_clock::now()); //Disconnect now
}
for (const auto& handler : postCommandHandler)
handler();
@@ -846,7 +911,7 @@ bool ConnectedClient::handleCommandFull(Command& cmd, bool disconnectOnFail) {
else
logWarning(this->getServerId(), "Command handling of command {} needs {}ms.", cmd.command(), duration_cast<milliseconds>(end - start).count());
}
result.release_details();
result.release_data();
return true;
}
@@ -900,22 +965,28 @@ std::shared_ptr<BanRecord> ConnectedClient::resolveActiveBan(const std::string&
return banEntry;
}
bool ConnectedClient::update_cached_permissions() {
auto values = this->calculate_permissions(permission::neededPermissions, this->currentChannel? this->currentChannel->channelId() : 0); /* copy the channel here so it does not change */
bool ConnectedClient::update_client_needed_permissions() {
if(this->getType() == ClientType::CLIENT_QUERY) {
/* Query clients are not interested in their permissions */
return true;
}
/* The server and/or the channel might change while we're executing this method */
auto currentChannel = this->currentChannel;
auto values = this->calculate_permissions(permission::neededPermissions, currentChannel ? currentChannel->channelId() : 0);
auto updated = false;
{
lock_guard cached_lock(this->cached_permissions_lock);
lock_guard cached_lock(this->client_needed_permissions_lock);
auto old_cached_permissions{this->cached_permissions};
this->cached_permissions = values;
std::sort(this->cached_permissions.begin(), this->cached_permissions.end(), [](const auto& a, const auto& b) { return a.first < b.first; });
auto old_cached_permissions{this->client_needed_permissions};
this->client_needed_permissions = values;
std::sort(this->client_needed_permissions.begin(), this->client_needed_permissions.end(), [](const auto& a, const auto& b) { return a.first < b.first; });
if(this->cached_permissions.size() != old_cached_permissions.size())
if(this->client_needed_permissions.size() != old_cached_permissions.size())
updated = true;
else {
for(auto oit = old_cached_permissions.begin(), nit = this->cached_permissions.begin(); oit != old_cached_permissions.end(); oit++, nit++) {
for(auto oit = old_cached_permissions.begin(), nit = this->client_needed_permissions.begin(); oit != old_cached_permissions.end(); oit++, nit++) {
if(oit->first != nit->first || oit->second != nit->second) {
updated = true;
break;
@@ -954,39 +1025,164 @@ do { \
} while(0)
permission::PermissionType ConnectedClient::calculate_and_get_join_state(const std::shared_ptr<BasicChannel>& channel) {
shared_ptr<ViewEntry> ventry;
std::shared_ptr<ViewEntry> ventry;
{
shared_lock view_lock(this->channel_lock);
ventry = this->channel_view()->find_channel(channel);
if(!ventry)
if(!ventry) {
return permission::i_channel_view_power;
}
}
if(ventry->join_state_id == this->join_state_id)
if(ventry->join_state_id == this->join_state_id) {
return ventry->join_permission_error;
}
auto channel_id = channel->channelId();
auto permission_cache = make_shared<CalculateCache>();
switch(channel->channelType()) {
case ChannelType::permanent:
if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_join_permanent, channel_id)))
if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_join_permanent, channel_id))) {
RESULT(permission::b_channel_join_permanent);
}
break;
case ChannelType::semipermanent:
if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_join_semi_permanent, channel_id)))
if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_join_semi_permanent, channel_id))) {
RESULT(permission::b_channel_join_semi_permanent);
}
break;
case ChannelType::temporary:
if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_join_temporary, channel_id)))
if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_join_temporary, channel_id))) {
RESULT(permission::b_channel_join_temporary);
}
break;
}
if(!channel->permission_granted(permission::i_channel_needed_join_power, this->calculate_permission(permission::i_channel_join_power, channel_id), false)) {
if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_ignore_join_power, channel_id)))
if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_channel_ignore_join_power, channel_id))) {
RESULT(permission::i_channel_join_power);
}
}
if(permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_is_sticky, this->currentChannel ? this->currentChannel->channelId() : 0)))
if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_ignore_sticky, channel_id)))
if(permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_is_sticky, this->currentChannel ? this->currentChannel->channelId() : 0))) {
if(!permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_ignore_sticky, channel_id))) {
RESULT(permission::b_client_is_sticky);
}
}
RESULT(permission::ok);
}
void ConnectedClient::useToken(token::TokenId token_id) {
auto server_ref = this->server;
if(!server_ref) {
return;
}
std::deque<token::TokenAction> actions{};
if(!server_ref->getTokenManager().query_token_actions(token_id, actions)) {
return;
}
if(actions.empty()) {
return;
}
bool tree_registered = !!this->currentChannel;
bool server_groups_changed{false}, channel_group_changed{false};
std::deque<std::shared_ptr<Group>> added_server_groups{};
std::deque<std::shared_ptr<Group>> removed_server_groups{};
for(const auto& action : actions) {
switch(action.type) {
case token::ActionType::AddServerGroup:
case token::ActionType::RemoveServerGroup: {
auto group = server->getGroupManager()->findGroup(action.id1);
if(!group || group->target() != GroupTarget::GROUPTARGET_SERVER) {
debugMessage(this->getServerId(), "{} Skipping token action add/remove server group for group {} because the group does not exists anymore.", CLIENT_STR_LOG_PREFIX, action.id1);
break;
}
if(action.type == token::ActionType::AddServerGroup) {
if(this->server->groups->hasServerGroupAssigned(this->getClientDatabaseId(), group)) {
debugMessage(this->getServerId(), "{} Skipping token action add server group for group {} because client is already member of that group.", CLIENT_STR_LOG_PREFIX, action.id1);
} else {
debugMessage(this->getServerId(), "{} Executing token action add server group for group {}.", CLIENT_STR_LOG_PREFIX, action.id1);
this->server->groups->addServerGroup(this->getClientDatabaseId(), group);
added_server_groups.push_back(group);
server_groups_changed = true;
}
} else {
if(!this->server->groups->hasServerGroupAssigned(this->getClientDatabaseId(), group)) {
debugMessage(this->getServerId(), "{} Skipping token action remove server group for group {} because client is not a member of that group.", CLIENT_STR_LOG_PREFIX, action.id1);
} else {
debugMessage(this->getServerId(), "{} Executing token action remove server group for group {}.", CLIENT_STR_LOG_PREFIX, action.id1);
this->server->groups->removeServerGroup(this->getClientDatabaseId(), group);
removed_server_groups.push_back(group);
server_groups_changed = true;
}
}
break;
}
case token::ActionType::SetChannelGroup: {
auto group = server->getGroupManager()->findGroup(action.id1);
if(!group || group->target() != GroupTarget::GROUPTARGET_CHANNEL) {
debugMessage(this->getServerId(), "{} Skipping token action set channel group for group {} at channel {} because the group does not exists anymore.", CLIENT_STR_LOG_PREFIX, action.id1, action.id2);
break;
}
auto channel = this->server->channelTree->findChannel(action.id2);
if (!channel) {
debugMessage(this->getServerId(), "{} Skipping token action set channel group for group {} at channel {} because the channel does not exists anymore.", CLIENT_STR_LOG_PREFIX, action.id1, action.id2);
break;
}
channel_group_changed = true;
this->server->groups->setChannelGroup(this->getClientDatabaseId(), group, channel);
break;
}
case token::ActionType::AllowChannelJoin: {
auto speaking_client = dynamic_cast<SpeakingClient*>(this);
if(speaking_client) {
speaking_client->join_whitelisted_channel.emplace_back(action.id2, action.text);
}
break;
}
case token::ActionType::ActionSqlFailed:
case token::ActionType::ActionDeleted:
case token::ActionType::ActionIgnore:
default:
break;
}
}
if(this->state > ConnectionState::INIT_HIGH) {
this->task_update_channel_client_properties.enqueue();
this->task_update_needed_permissions.enqueue();
}
if(tree_registered && (server_groups_changed || channel_group_changed)) {
auto updated_properties = this->getServer()->getGroupManager()->update_server_group_property(this->ref(), true, this->currentChannel);
if(!updated_properties.empty()) {
this->getServer()->notifyClientPropertyUpdates(this->ref(), updated_properties);
}
}
if(tree_registered && server_groups_changed) {
for(const auto &viewer : this->server->getClients()) {
if(viewer->isClientVisible(this->ref(), true)) {
for(const auto& group : added_server_groups) {
viewer->notifyServerGroupClientAdd(this->server->serverRoot, this->ref(), group);
}
for(const auto& group : removed_server_groups) {
viewer->notifyServerGroupClientRemove(this->server->serverRoot, this->ref(), group);
}
}
}
}
}
+106 -48
View File
@@ -4,16 +4,21 @@
#include <misc/net.h>
#include <cstdint>
#include <src/music/PlayablePlaylist.h>
#include <misc/task_executor.h>
#include "music/Song.h"
#include "../channel/ClientChannelView.h"
#include "DataClient.h"
#include "query/command3.h"
#define CLIENT_STR_LOG_PREFIX_(this) (std::string("[") + this->getLoggingPeerIp() + ":" + std::to_string(this->getPeerPort()) + "/" + this->getDisplayName() + " | " + std::to_string(this->getClientId()) + "]")
#define CLIENT_STR_LOG_PREFIX_(this) (this->getLoggingPrefix())
#define CLIENT_STR_LOG_PREFIX CLIENT_STR_LOG_PREFIX_(this)
#define CMD_REQ_SERVER \
if(!this->server) return command_result{error::server_invalid_id};
do { \
if(!this->server) { \
return command_result{error::server_invalid_id}; \
} \
} while(0)
/* TODO: Play lock the server here with read? So the client dosn't get kicked within that moment */
#define CMD_REF_SERVER(variable_name) \
@@ -33,8 +38,10 @@ if(!cmd[0].has(parm)) return command_result{error::parameter_not_found};
//the message here is show to the manager!
#define CMD_CHK_AND_INC_FLOOD_POINTS(num) \
this->increaseFloodPoints(num); \
if(this->shouldFloodBlock()) return command_result{error::ban_flooding};
do {\
this->increaseFloodPoints(num); \
if(this->shouldFloodBlock()) return command_result{error::ban_flooding}; \
} while(0)
#define CMD_CHK_PARM_COUNT(count) \
if(cmd.bulkCount() != count) return command_result{error::parameter_invalid_count};
@@ -76,10 +83,14 @@ namespace ts {
ConnectionState connectionState(){ return this->state; }
std::string getLoggingPeerIp() { return config::server::disable_ip_saving || (this->server && this->server->disable_ip_saving()) ? "X.X.X.X" : this->getPeerIp(); }
std::string getPeerIp(){ return this->isAddressV4() ? net::to_string(this->getAddressV4()->sin_addr) : this->isAddressV6() ? net::to_string(this->getAddressV6()->sin6_addr) : "localhost"; }
uint16_t getPeerPort(){ return ntohs(this->isAddressV4() ? this->getAddressV4()->sin_port : this->isAddressV6() ? this->getAddressV6()->sin6_port : (uint16_t) 0); }
std::string getPeerIp(){ return net::to_string(this->remote_address, false); }
uint16_t getPeerPort(){ return net::port(this->remote_address); }
std::string getHardwareId(){ return properties()[property::CLIENT_HARDWARE_ID]; }
[[nodiscard]] inline std::string getLoggingPrefix() {
return std::string{"["} + this->getLoggingPeerIp() + ":" + std::to_string(this->getPeerPort()) + "/" + this->getDisplayName() + " | " + std::to_string(this->getClientId()) + "]";
}
//General connection stuff
bool isAddressV4() { return this->remote_address.ss_family == AF_INET; }
const sockaddr_in* getAddressV4(){ return (sockaddr_in*) &this->remote_address; }
@@ -116,14 +127,15 @@ namespace ts {
/** Notifies general stuff **/
virtual bool notifyError(const command_result&, const std::string& retCode = "");
virtual void writeCommandResult(ts::command_builder&, const command_result&, const std::string& errorCodeKey = "id");
/** Notifies (after request) */
bool sendNeededPermissions(bool /* force an update */); /* invoke this because it dosn't spam the client */
virtual bool notifyClientNeededPermissions();
virtual bool notifyServerGroupList();
virtual bool notifyServerGroupList(bool as_notify = true);
virtual bool notifyGroupPermList(const std::shared_ptr<Group>&, bool);
virtual bool notifyClientPermList(ClientDbId, const std::shared_ptr<permission::v2::PermissionManager>&, bool);
virtual bool notifyChannelGroupList();
virtual bool notifyChannelGroupList(bool as_notify = true);
virtual bool notifyConnectionInfo(const std::shared_ptr<ConnectedClient> &target, const std::shared_ptr<ConnectionInfoData> &info);
virtual bool notifyChannelSubscribed(const std::deque<std::shared_ptr<BasicChannel>> &);
virtual bool notifyChannelUnsubscribed(const std::deque<std::shared_ptr<BasicChannel>> &);
@@ -135,7 +147,7 @@ namespace ts {
virtual bool notifyClientPoke(std::shared_ptr<ConnectedClient> invoker, std::string msg);
virtual bool notifyClientUpdated(
const std::shared_ptr<ConnectedClient> &,
const std::deque<std::shared_ptr<property::PropertyDescription>> &,
const std::deque<const property::PropertyDescription*> &,
bool lock_channel_tree
); /* invalid client id causes error: invalid clientID */
@@ -233,7 +245,16 @@ namespace ts {
virtual bool notifyConversationMessageDelete(const ChannelId /* conversation id */, const std::chrono::system_clock::time_point& /* begin timestamp */, const std::chrono::system_clock::time_point& /* begin end */, ClientDbId /* client id */, size_t /* messages */);
/* this method should be callable from everywhere; the method is non blocking! */
/**
* Close the network connection.
*
* Note:
* This method could be called from any thread with any locks in hold.
* It's not blocking.
*
* @param timeout The timestamp when to drop the client if not all data has been send.
* @returns `true` if the connection has been closed and `false` if the connection is already closed.
*/
virtual bool close_connection(const std::chrono::system_clock::time_point &timeout) = 0;
/* this method should be callable from everywhere; the method is non blocking! */
@@ -246,8 +267,7 @@ namespace ts {
virtual bool ignoresFlood() { return !this->block_flood; }
std::shared_ptr<ConnectionInfoData> request_connection_info(const std::shared_ptr<ConnectedClient> & /* receiver */, bool& /* send temporary (no client response yet) */);
virtual void updateChannelClientProperties(bool /* lock channel tree */, bool /* notify our self */);
void updateTalkRights(permission::PermissionValue talk_power);
void updateTalkRights(permission::v2::PermissionFlaggedValue talk_power);
virtual std::shared_ptr<BanRecord> resolveActiveBan(const std::string& ip_address);
@@ -257,25 +277,30 @@ namespace ts {
inline std::shared_ptr<ClientChannelView> channel_view() { return this->channels; }
inline std::shared_ptr<ConnectedClient> ref() { return _this.lock(); }
[[nodiscard]] inline std::shared_ptr<ConnectedClient> ref() { return this->_this.lock(); }
[[nodiscard]] inline std::weak_ptr<ConnectedClient> weak_ref() { return this->_this; }
std::shared_mutex& get_channel_lock() { return this->channel_lock; }
/* Attention: Ensure that channel_lock has been locked */
[[nodiscard]] inline std::vector<GroupId>& current_server_groups() { return this->cached_server_groups; }
[[nodiscard]] inline GroupId& current_channel_group() { return this->cached_channel_group; }
/**
* Attention: This method should never be called directly (except in some edge cases)!
* Use `task_update_channel_client_properties` instead to schedule an update.
*/
virtual void updateChannelClientProperties(bool /* lock channel tree */, bool /* notify our self */);
/*
* permission stuff
*/
/*
inline permission::PermissionValue cached_permission_value(permission::PermissionType type) const {
std::lock_guard lock(this->cached_permissions_lock);
auto index = this->cached_permissions.find(type);
if(index != this->cached_permissions.end())
return index->second;
We're only caching permissions which are granted to reduce memory
//logError(this->getServerId(), "{} Looked up cached permission, which hasn't been cached!", CLIENT_STR_LOG_PREFIX);
return permNotGranted;
}
/**
* Attention: This method should never be called directly!
* Use `task_update_needed_permissions` instead to schedule an update.
* @returns `true` is a permission updated happened.
*/
bool update_cached_permissions();
bool update_client_needed_permissions();
std::shared_lock<std::shared_mutex> require_connected_state(bool blocking = false) {
//try_to_lock_t
@@ -297,8 +322,10 @@ namespace ts {
}
inline bool playlist_subscribed(const std::shared_ptr<ts::music::Playlist>& playlist) const {
return this->_subscribed_playlist.lock() == playlist;
return this->subscribed_playlist_.lock() == playlist;
}
permission::PermissionType calculate_and_get_join_state(const std::shared_ptr<BasicChannel>&);
protected:
std::weak_ptr<ConnectedClient> _this;
sockaddr_storage remote_address;
@@ -307,7 +334,7 @@ namespace ts {
std::mutex state_lock;
ConnectionState state{ConnectionState::UNKNWON};
bool allowedToTalk = false;
bool allowedToTalk{false};
std::shared_mutex finalDisconnectLock; /* locked before state lock! */
@@ -316,7 +343,7 @@ namespace ts {
std::deque<std::weak_ptr<ConnectedClient>> visibleClients{}; /* variable locked with channel_lock */
std::deque<std::weak_ptr<ConnectedClient>> mutedClients{}; /* variable locked with channel_lock */
std::deque<std::weak_ptr<ConnectedClient>> openChats{}; /* variable locked with channel_lock */
std::deque<std::weak_ptr<ConnectedClient>> open_private_conversations{}; /* variable locked with channel_lock */
std::chrono::system_clock::time_point lastNeededNotify;
std::shared_ptr<BasicChannel> lastNeededPermissionNotifyChannel = nullptr;
@@ -326,7 +353,6 @@ namespace ts {
std::chrono::system_clock::time_point lastOnlineTimestamp;
std::chrono::system_clock::time_point lastTransfareTimestamp;
std::chrono::system_clock::time_point idleTimestamp;
std::chrono::system_clock::time_point last_statistics_tick;
struct {
std::mutex lock;
@@ -353,23 +379,32 @@ namespace ts {
std::shared_ptr<ClientChannelView> channels;
std::shared_mutex channel_lock;
std::mutex cached_permissions_lock;
std::vector<std::pair<permission::PermissionType, permission::v2::PermissionFlaggedValue>> cached_permissions; /* contains all needed permissions which are set */
/* The permission overview which the client itself has (for basic client actions ) */
std::mutex client_needed_permissions_lock;
std::vector<std::pair<permission::PermissionType, permission::v2::PermissionFlaggedValue>> client_needed_permissions;
permission::v2::PermissionFlaggedValue channels_view_power{0, false};
permission::v2::PermissionFlaggedValue channels_ignore_view{0, false};
permission::v2::PermissionFlaggedValue cpmerission_whisper_power{0, false};
permission::v2::PermissionFlaggedValue cpmerission_needed_whisper_power{0, false};
bool subscribeToAll = false;
uint16_t join_state_id = 1; /* default channel value is 0 and by default we need to calculate at least once, so we use 1 */
permission::PermissionType calculate_and_get_join_state(const std::shared_ptr<BasicChannel>&);
virtual void initialize_weak_reference(const std::shared_ptr<ConnectedClient>& /* self reference */);
bool subscribeToAll{false};
uint16_t join_state_id{1}; /* default channel value is 0 and by default we need to calculate at least once, so we use 1 */
/* (ChannelId, ChannelPasswordHash!) (If empty no password/permissions, if "ignore" ignore permissions granted) */
std::vector<std::pair<ChannelId, std::string>> join_whitelisted_channel{}; /* Access only when the command mutex is acquired */
std::weak_ptr<MusicClient> selectedBot;
std::weak_ptr<MusicClient> subscribed_bot;
std::weak_ptr<ts::music::Playlist> _subscribed_playlist{};
std::weak_ptr<ts::music::Playlist> subscribed_playlist_{};
virtual void tick(const std::chrono::system_clock::time_point &time);
multi_shot_task task_update_needed_permissions{};
multi_shot_task task_update_channel_client_properties{};
bool loadDataForCurrentServer() override;
virtual void tick_server(const std::chrono::system_clock::time_point &time);
//Locked by everything who has something todo with command handling
threads::Mutex command_lock; /* Note: This mutex must be recursive! */
std::vector<std::function<void()>> postCommandHandler;
@@ -411,13 +446,13 @@ namespace ts {
command_result handleCommandChannelDelPerm(Command&);
//Server group manager management
command_result handleCommandServerGroupCopy(Command&);
command_result handleCommandServerGroupAdd(Command&);
command_result handleCommandServerGroupCopy(Command&);
command_result handleCommandServerGroupRename(Command&);
command_result handleCommandServerGroupDel(Command&);
command_result handleCommandServerGroupClientList(Command&);
command_result handleCommandServerGroupDelClient(Command&);
command_result handleCommandServerGroupAddClient(Command&);
command_result handleCommandServerGroupDelClient(Command&);
command_result handleCommandServerGroupPermList(Command&);
command_result handleCommandServerGroupAddPerm(Command&);
command_result handleCommandServerGroupDelPerm(Command&);
@@ -454,11 +489,10 @@ namespace ts {
command_result handleCommandFTDeleteFile(Command&);
command_result handleCommandFTInitUpload(Command&);
command_result handleCommandFTInitDownload(Command&);
command_result handleCommandFTGetFileInfo(Command&);
//CMD_TODO handleCommandFTGetFileInfo -> 5 points
//CMD_TODO handleCommandFTStop -> 5 points
//CMD_TODO handleCommandFTRenameFile -> 5 points
//CMD_TODO handleCommandFTList -> 5 points
command_result handleCommandFTGetFileInfo(Command&);
command_result handleCommandFTRenameFile(Command&);
command_result handleCommandFTList(Command&);
command_result handleCommandFTStop(Command&);
command_result handleCommandBanList(Command&);
command_result handleCommandBanAdd(Command&);
@@ -469,7 +503,9 @@ namespace ts {
command_result handleCommandBanTriggerList(Command&);
command_result handleCommandTokenList(Command&);
command_result handleCommandTokenActionList(Command&);
command_result handleCommandTokenAdd(Command&);
command_result handleCommandTokenEdit(Command&);
command_result handleCommandTokenUse(Command&);
command_result handleCommandTokenDelete(Command&);
@@ -529,7 +565,7 @@ namespace ts {
command_result handleCommandMusicBotQueueList(Command&);
command_result handleCommandMusicBotQueueAdd(Command&);
command_result handleCommandMusicBotQueueRemove(Command&);
command_result handleCommandMusicBotQueueRemove(Command&);
command_result handleCommandMusicBotQueueReorder(Command&);
command_result handleCommandMusicBotPlaylistAssign(Command&);
@@ -593,7 +629,10 @@ namespace ts {
command_result handleCommandConversationMessageDelete(Command&);
command_result handleCommandLogView(Command&);
//CMD_TODO handleCommandLogAdd
command_result handleCommandLogQuery(Command&);
command_result handleCommandLogAdd(Command&);
command_result handleCommandListFeatureSupport(Command &cmd);
//handleCommandClientSiteReport() -> return findError(0x00)
//handleCommandChannelCreatePrivate() -> return findError(0x02)
@@ -604,7 +643,7 @@ namespace ts {
//handleCommandDummy_ConnectFailed
//handleCommandDummy_ConnectionLost
//Not needed - completly useless
//Not needed - completely useless
//CMD_TODO handleCommandCustomInfo
//CMD_TODO handleCommandCustomSearch
//CMD_TODO serverquerycmd
@@ -616,6 +655,16 @@ namespace ts {
bool handleTextMessage(ChatMessageMode, std::string, const std::shared_ptr<ConnectedClient>& /* sender target */);
/**
* Call this method only when command handling is locked (aka the client can't do anything).
* All other locks shall be free.
*
* Note: This will not increase the token use count.
* The callee will have to do so.
*
*/
void useToken(token::TokenId);
typedef std::function<void(const std::shared_ptr<ConnectedClient>& /* sender */, const std::string& /* message */)> handle_text_command_fn_t;
bool handle_text_command(
ChatMessageMode,
@@ -625,6 +674,13 @@ namespace ts {
const std::shared_ptr<ConnectedClient>& /* sender target */
);
/* Function to execute the channel edit. We're not checking for any permissions */
ts::command_result execute_channel_edit(
ChannelId& /* channel id */,
const std::map<property::ChannelProperties, std::string>& /* values */,
bool /* is channel create */
);
inline std::string notify_response_command(const std::string_view& notify) {
if(this->getExternalType() == ClientType::CLIENT_TEAMSPEAK)
return std::string(notify);
@@ -634,18 +690,20 @@ namespace ts {
template <typename T>
struct ConnectedLockedClient {
ConnectedLockedClient() {}
explicit ConnectedLockedClient(std::shared_ptr<T> client) : client{std::move(client)} {
if(this->client)
this->connection_lock = this->client->require_connected_state();
}
explicit ConnectedLockedClient(ConnectedLockedClient&& client) : client{std::move(client.client)}, connection_lock{std::move(client.connection_lock)} { }
inline ConnectedLockedClient<T> &operator=(const ConnectedLockedClient& other) {
inline ConnectedLockedClient &operator=(const ConnectedLockedClient& other) {
this->client = other.client;
if(other)
this->connection_lock = std::shared_lock{*other.connection_lock.mutex()}; /* if the other is true (state locked & client) than we could easily acquire a shared lock */
}
inline ConnectedLockedClient<T> &operator=(ConnectedLockedClient&& other) {
inline ConnectedLockedClient &operator=(ConnectedLockedClient&& other) {
this->client = std::move(other.client);
this->connection_lock = std::move(other.connection_lock);
}
File diff suppressed because it is too large Load Diff
+114 -108
View File
@@ -1,29 +1,21 @@
#include <iostream>
#include <bitset>
#include <algorithm>
#include "ConnectedClient.h"
#include "voice/VoiceClient.h"
#include "../server/file/FileServer.h"
#include "../server/VoiceServer.h"
#include "../InstanceHandler.h"
#include "../server/QueryServer.h"
#include "../manager/PermissionNameMapper.h"
#include "music/MusicClient.h"
#include <log/LogUtils.h>
#include <misc/sassert.h>
#include <misc/timer.h>
#include <log/LogUtils.h>
#include "./web/WebClient.h"
#include "query/command3.h"
using namespace std::chrono;
using namespace std;
using namespace ts;
using namespace ts::server;
using namespace ts::token;
extern ts::server::InstanceHandler* serverInstance;
#define INVOKER(command, invoker) \
# define INVOKER(command, invoker) \
do { \
if(invoker) { \
command["invokerid"] = invoker->getClientId(); \
@@ -49,8 +41,8 @@ do { \
} \
} while(0)
bool ConnectedClient::notifyServerGroupList() {
Command cmd(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifyservergrouplist" : "");
bool ConnectedClient::notifyServerGroupList(bool as_notify) {
Command cmd(as_notify ? "notifyservergrouplist" : "");
int index = 0;
for (const auto& group : (this->server ? this->server->groups : serverInstance->getGroupManager().get())->availableServerGroups(true)) {
@@ -60,7 +52,7 @@ bool ConnectedClient::notifyServerGroupList() {
cmd[index]["sgid"] = group->groupId();
}
for (const auto &prop : group->properties().list_properties(property::FLAG_GROUP_VIEW, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0))
cmd[index][prop.type().name] = prop.value();
cmd[index][std::string{prop.type().name}] = prop.value();
auto modify_power = group->permissions()->permission_value_flagged(permission::i_displayed_group_needed_modify_power);
@@ -176,8 +168,8 @@ bool ConnectedClient::notifyClientPermList(ClientDbId cldbid, const std::shared_
return true;
}
bool ConnectedClient::notifyChannelGroupList() {
Command cmd(this->getExternalType() == CLIENT_TEAMSPEAK ? "notifychannelgrouplist" : "");
bool ConnectedClient::notifyChannelGroupList(bool as_notify) {
Command cmd(as_notify ? "notifychannelgrouplist" : "");
int index = 0;
for (const auto &group : (this->server ? this->server->groups : serverInstance->getGroupManager().get())->availableChannelGroups(true)) {
if(group->target() == GroupTarget::GROUPTARGET_CHANNEL) {
@@ -186,7 +178,7 @@ bool ConnectedClient::notifyChannelGroupList() {
cmd[index]["sgid"] = group->groupId();
}
for (auto &prop : group->properties().list_properties(property::FLAG_GROUP_VIEW, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0))
cmd[index][prop.type().name] = prop.value();
cmd[index][std::string{prop.type().name}] = prop.value();
auto modify_power = group->permissions()->permission_value_flagged(permission::i_displayed_group_needed_modify_power);
@@ -271,108 +263,116 @@ bool ConnectedClient::notifyClientChannelGroupChanged(
}
bool ConnectedClient::notifyConnectionInfo(const shared_ptr<ConnectedClient> &target, const shared_ptr<ConnectionInfoData> &info) {
Command notify("notifyconnectioninfo");
notify["clid"] = target->getClientId();
command_builder notify{"notifyconnectioninfo"};
auto bulk = notify.bulk(0);
bulk.put_unchecked("clid", target->getClientId());
auto not_set = this->getType() == CLIENT_TEAMSPEAK ? 0 : -1;
/* we deliver data to the web client as well, because its a bit dump :D */
if(target->getClientId() != this->getClientId()) {
auto report = target->connectionStatistics->full_report();
auto file_stats = target->connectionStatistics->file_stats();
/* default values which normally sets the client */
notify["connection_bandwidth_received_last_minute_control"] = not_set;
notify["connection_bandwidth_received_last_minute_keepalive"] = not_set;
notify["connection_bandwidth_received_last_minute_speech"] = not_set;
notify["connection_bandwidth_received_last_second_control"] = not_set;
notify["connection_bandwidth_received_last_second_keepalive"] = not_set;
notify["connection_bandwidth_received_last_second_speech"] = not_set;
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_RECEIVED_LAST_MINUTE_CONTROL, not_set);
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_RECEIVED_LAST_MINUTE_KEEPALIVE, not_set);
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_RECEIVED_LAST_MINUTE_SPEECH, not_set);
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_RECEIVED_LAST_SECOND_CONTROL, not_set);
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_RECEIVED_LAST_SECOND_KEEPALIVE, not_set);
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_RECEIVED_LAST_SECOND_SPEECH, not_set);
notify["connection_bandwidth_sent_last_minute_control"] = not_set;
notify["connection_bandwidth_sent_last_minute_keepalive"] = not_set;
notify["connection_bandwidth_sent_last_minute_speech"] = not_set;
notify["connection_bandwidth_sent_last_second_control"] = not_set;
notify["connection_bandwidth_sent_last_second_keepalive"] = not_set;
notify["connection_bandwidth_sent_last_second_speech"] = not_set;
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_SENT_LAST_MINUTE_CONTROL, not_set);
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_SENT_LAST_MINUTE_KEEPALIVE, not_set);
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_SENT_LAST_MINUTE_SPEECH, not_set);
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_SENT_LAST_SECOND_CONTROL, not_set);
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_SENT_LAST_SECOND_KEEPALIVE, not_set);
bulk.put_unchecked(property::CONNECTION_BANDWIDTH_SENT_LAST_SECOND_SPEECH, not_set);
/* its flipped here because the report is out of the clients view */
notify["connection_bytes_received_control"] = not_set;
notify["connection_bytes_received_keepalive"] = not_set;
notify["connection_bytes_received_speech"] = not_set;
notify["connection_bytes_sent_control"] = not_set;
notify["connection_bytes_sent_keepalive"] = not_set;
notify["connection_bytes_sent_speech"] = not_set;
bulk.put_unchecked(property::CONNECTION_BYTES_RECEIVED_CONTROL, not_set);
bulk.put_unchecked(property::CONNECTION_BYTES_RECEIVED_KEEPALIVE, not_set);
bulk.put_unchecked(property::CONNECTION_BYTES_RECEIVED_SPEECH, not_set);
bulk.put_unchecked(property::CONNECTION_BYTES_SENT_CONTROL, not_set);
bulk.put_unchecked(property::CONNECTION_BYTES_SENT_KEEPALIVE, not_set);
bulk.put_unchecked(property::CONNECTION_BYTES_SENT_SPEECH, not_set);
/* its flipped here because the report is out of the clients view */
notify["connection_packets_received_control"] = not_set;
notify["connection_packets_received_keepalive"] = not_set;
notify["connection_packets_received_speech"] = not_set;
notify["connection_packets_sent_control"] = not_set;
notify["connection_packets_sent_keepalive"] = not_set;
notify["connection_packets_sent_speech"] = not_set;
bulk.put_unchecked(property::CONNECTION_PACKETS_RECEIVED_CONTROL, not_set);
bulk.put_unchecked(property::CONNECTION_PACKETS_RECEIVED_KEEPALIVE, not_set);
bulk.put_unchecked(property::CONNECTION_PACKETS_RECEIVED_SPEECH, not_set);
bulk.put_unchecked(property::CONNECTION_PACKETS_SENT_CONTROL, not_set);
bulk.put_unchecked(property::CONNECTION_PACKETS_SENT_KEEPALIVE, not_set);
bulk.put_unchecked(property::CONNECTION_PACKETS_SENT_SPEECH, not_set);
notify["connection_server2client_packetloss_control"] = not_set;
notify["connection_server2client_packetloss_keepalive"] = not_set;
notify["connection_server2client_packetloss_speech"] = not_set;
notify["connection_server2client_packetloss_total"] = not_set;
bulk.put_unchecked(property::CONNECTION_SERVER2CLIENT_PACKETLOSS_CONTROL, not_set);
bulk.put_unchecked(property::CONNECTION_SERVER2CLIENT_PACKETLOSS_KEEPALIVE, not_set);
bulk.put_unchecked(property::CONNECTION_SERVER2CLIENT_PACKETLOSS_SPEECH, not_set);
bulk.put_unchecked(property::CONNECTION_SERVER2CLIENT_PACKETLOSS_TOTAL, not_set);
notify["connection_ping"] = 0;
notify["connection_ping_deviation"] = 0;
bulk.put_unchecked(property::CONNECTION_CLIENT2SERVER_PACKETLOSS_SPEECH, not_set);
bulk.put_unchecked(property::CONNECTION_CLIENT2SERVER_PACKETLOSS_KEEPALIVE, not_set);
bulk.put_unchecked(property::CONNECTION_CLIENT2SERVER_PACKETLOSS_CONTROL, not_set);
bulk.put_unchecked(property::CONNECTION_CLIENT2SERVER_PACKETLOSS_TOTAL, not_set);
notify["connection_connected_time"] = 0;
notify["connection_idle_time"] = 0;
bulk.put_unchecked(property::CONNECTION_PING, 0);
bulk.put_unchecked(property::CONNECTION_PING_DEVIATION, 0);
bulk.put_unchecked(property::CONNECTION_CONNECTED_TIME, 0);
bulk.put_unchecked(property::CONNECTION_IDLE_TIME, 0);
/* its flipped here because the report is out of the clients view */
notify["connection_filetransfer_bandwidth_sent"] = report.file_bytes_received;
notify["connection_filetransfer_bandwidth_received"] = report.file_bytes_sent;
bulk.put_unchecked(property::CONNECTION_FILETRANSFER_BANDWIDTH_SENT, file_stats.bytes_received);
bulk.put_unchecked(property::CONNECTION_FILETRANSFER_BANDWIDTH_RECEIVED, file_stats.bytes_sent);
}
if(info) {
for(const auto& elm : info->properties) {
notify[elm.first] = elm.second;
}
for(const auto& [key, value] : info->properties)
bulk.put(key, value);
} else {
//Fill in some server stuff
if(dynamic_pointer_cast<VoiceClient>(target))
notify["connection_ping"] = floor<milliseconds>(dynamic_pointer_cast<VoiceClient>(target)->calculatePing()).count();
//Fill in what we can, else we trust the client
if(target->getType() == ClientType::CLIENT_TEASPEAK || target->getType() == ClientType::CLIENT_TEAMSPEAK || target->getType() == ClientType::CLIENT_WEB) {
auto& stats = target->connectionStatistics->total_stats();
/* its flipped here because the report is out of the clients view */
bulk.put(property::CONNECTION_BYTES_RECEIVED_CONTROL, stats.connection_bytes_received[stats::ConnectionStatistics::category::COMMAND]);
bulk.put(property::CONNECTION_BYTES_RECEIVED_KEEPALIVE, stats.connection_bytes_received[stats::ConnectionStatistics::category::KEEP_ALIVE]);
bulk.put(property::CONNECTION_BYTES_RECEIVED_SPEECH, stats.connection_bytes_received[stats::ConnectionStatistics::category::VOICE]);
bulk.put(property::CONNECTION_BYTES_SENT_CONTROL, stats.connection_bytes_sent[stats::ConnectionStatistics::category::COMMAND]);
bulk.put(property::CONNECTION_BYTES_SENT_KEEPALIVE, stats.connection_bytes_sent[stats::ConnectionStatistics::category::KEEP_ALIVE]);
bulk.put(property::CONNECTION_BYTES_SENT_SPEECH, stats.connection_bytes_sent[stats::ConnectionStatistics::category::VOICE]);
/* its flipped here because the report is out of the clients view */
bulk.put(property::CONNECTION_PACKETS_RECEIVED_CONTROL, stats.connection_packets_received[stats::ConnectionStatistics::category::COMMAND]);
bulk.put(property::CONNECTION_PACKETS_RECEIVED_KEEPALIVE, stats.connection_packets_received[stats::ConnectionStatistics::category::KEEP_ALIVE]);
bulk.put(property::CONNECTION_PACKETS_RECEIVED_SPEECH, stats.connection_packets_received[stats::ConnectionStatistics::category::VOICE]);
bulk.put(property::CONNECTION_PACKETS_SENT_CONTROL, stats.connection_packets_sent[stats::ConnectionStatistics::category::COMMAND]);
bulk.put(property::CONNECTION_PACKETS_SENT_KEEPALIVE, stats.connection_packets_sent[stats::ConnectionStatistics::category::KEEP_ALIVE]);
bulk.put(property::CONNECTION_PACKETS_SENT_SPEECH, stats.connection_packets_sent[stats::ConnectionStatistics::category::VOICE]);
}
}
if(auto vc = dynamic_pointer_cast<VoiceClient>(target); vc) {
bulk.put(property::CONNECTION_PING, floor<milliseconds>(vc->current_ping()).count());
bulk.put(property::CONNECTION_PING_DEVIATION, vc->current_ping_deviation());
}
#ifdef COMPILE_WEB_CLIENT
else if(dynamic_pointer_cast<WebClient>(target))
notify["connection_ping"] = floor<milliseconds>(dynamic_pointer_cast<WebClient>(target)->client_ping()).count();
else if(dynamic_pointer_cast<WebClient>(target))
bulk.put(property::CONNECTION_PING, floor<milliseconds>(dynamic_pointer_cast<WebClient>(target)->client_ping()).count());
#endif
if(target->getType() == ClientType::CLIENT_TEASPEAK || target->getType() == ClientType::CLIENT_TEAMSPEAK || target->getType() == ClientType::CLIENT_WEB) {
auto report = target->connectionStatistics->full_report();
/* its flipped here because the report is out of the clients view */
notify["connection_bytes_received_control"] = report.connection_bytes_sent[stats::ConnectionStatistics::category::COMMAND];
notify["connection_bytes_received_keepalive"] = report.connection_bytes_sent[stats::ConnectionStatistics::category::ACK];
notify["connection_bytes_received_speech"] = report.connection_bytes_sent[stats::ConnectionStatistics::category::VOICE];
notify["connection_bytes_sent_control"] = report.connection_bytes_sent[stats::ConnectionStatistics::category::COMMAND];
notify["connection_bytes_sent_keepalive"] = report.connection_bytes_sent[stats::ConnectionStatistics::category::ACK];
notify["connection_bytes_sent_speech"] = report.connection_bytes_sent[stats::ConnectionStatistics::category::VOICE];
/* its flipped here because the report is out of the clients view */
notify["connection_packets_received_control"] = report.connection_packets_sent[stats::ConnectionStatistics::category::COMMAND];
notify["connection_packets_received_keepalive"] = report.connection_packets_sent[stats::ConnectionStatistics::category::ACK];
notify["connection_packets_received_speech"] = report.connection_packets_sent[stats::ConnectionStatistics::category::VOICE];
notify["connection_packets_sent_control"] = report.connection_packets_sent[stats::ConnectionStatistics::category::COMMAND];
notify["connection_packets_sent_keepalive"] = report.connection_packets_sent[stats::ConnectionStatistics::category::ACK];
notify["connection_packets_sent_speech"] = report.connection_packets_sent[stats::ConnectionStatistics::category::VOICE];
}
if(auto vc = dynamic_pointer_cast<VoiceClient>(target); vc){
auto& calculator = vc->connection->packet_statistics();
auto report = calculator.loss_report();
bulk.put(property::CONNECTION_CLIENT2SERVER_PACKETLOSS_SPEECH, std::to_string(report.voice_loss()));
bulk.put(property::CONNECTION_CLIENT2SERVER_PACKETLOSS_KEEPALIVE, std::to_string(report.keep_alive_loss()));
bulk.put(property::CONNECTION_CLIENT2SERVER_PACKETLOSS_CONTROL, std::to_string(report.control_loss()));
bulk.put(property::CONNECTION_CLIENT2SERVER_PACKETLOSS_TOTAL, std::to_string(report.total_loss()));
}
if(target->getClientId() == this->getClientId() || permission::v2::permission_granted(1, this->calculate_permission(permission::b_client_remoteaddress_view, this->getChannelId()))) {
notify["connection_client_ip"] = target->getLoggingPeerIp();
notify["connection_client_port"] = target->getPeerPort();
bulk.put(property::CONNECTION_CLIENT_IP, target->getLoggingPeerIp());
bulk.put(property::CONNECTION_CLIENT_PORT, target->getPeerPort());
}
//Needs to be filled out
notify["connection_client2server_packetloss_speech"] = not_set;
notify["connection_client2server_packetloss_keepalive"] = not_set;
notify["connection_client2server_packetloss_control"] = not_set;
notify["connection_client2server_packetloss_total"] = not_set;
notify["connection_connected_time"] = chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now() - target->connectTimestamp).count();
notify["connection_idle_time"] = chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now() - target->idleTimestamp).count();
bulk.put(property::CONNECTION_CONNECTED_TIME, chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now() - target->connectTimestamp).count());
bulk.put(property::CONNECTION_IDLE_TIME, chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now() - target->idleTimestamp).count());
this->sendCommand(notify);
return true;
}
@@ -404,10 +404,11 @@ bool ConnectedClient::notifyClientMoved(const shared_ptr<ConnectedClient> &clien
return true;
}
bool ConnectedClient::notifyClientUpdated(const std::shared_ptr<ConnectedClient> &client, const deque<shared_ptr<property::PropertyDescription>> &props, bool lock) {
shared_lock channel_lock(this->channel_lock, defer_lock);
if(lock)
bool ConnectedClient::notifyClientUpdated(const std::shared_ptr<ConnectedClient> &client, const deque<const property::PropertyDescription*> &props, bool lock) {
std::shared_lock channel_lock(this->channel_lock, defer_lock);
if(lock) {
channel_lock.lock();
}
if(!this->isClientVisible(client, false) && client != this)
return false;
@@ -420,8 +421,8 @@ bool ConnectedClient::notifyClientUpdated(const std::shared_ptr<ConnectedClient>
Command response("notifyclientupdated");
response["clid"] = client_id;
for (const auto &prop : props) {
if(lastOnlineTimestamp.time_since_epoch().count() > 0 && (prop->property_index == property::CLIENT_TOTAL_ONLINE_TIME || prop->property_index == property::CLIENT_MONTH_ONLINE_TIME))
response[prop->name] = client->properties()[prop].as<int64_t>() + duration_cast<seconds>(system_clock::now() - client->lastOnlineTimestamp).count();
if(lastOnlineTimestamp.time_since_epoch().count() > 0 && (*prop == property::CLIENT_TOTAL_ONLINE_TIME || *prop == property::CLIENT_MONTH_ONLINE_TIME))
response[prop->name] = client->properties()[prop].as_or<int64_t>(0) + duration_cast<seconds>(system_clock::now() - client->lastOnlineTimestamp).count();
else
response[prop->name] = client->properties()[prop].value();
}
@@ -470,7 +471,7 @@ bool ConnectedClient::notifyChannelMoved(const std::shared_ptr<BasicChannel> &ch
bool ConnectedClient::notifyChannelCreate(const std::shared_ptr<BasicChannel> &channel, ChannelId orderId, const std::shared_ptr<ConnectedClient> &invoker) {
Command notify("notifychannelcreated");
for (auto &prop : channel->properties().list_properties(property::FLAG_CHANNEL_VARIABLE | property::FLAG_CHANNEL_VIEW, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) {
for (auto &prop : channel->properties()->list_properties(property::FLAG_CHANNEL_VARIABLE | property::FLAG_CHANNEL_VIEW, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) {
if(prop.type() == property::CHANNEL_ORDER)
notify[prop.type().name] = orderId;
else if(prop.type() == property::CHANNEL_DESCRIPTION)
@@ -535,7 +536,7 @@ bool ConnectedClient::notifyChannelShow(const std::shared_ptr<ts::BasicChannel>
result = this->notifyChannelCreate(channel, orderId, this->server->serverRoot);
} else {
Command notify("notifychannelshow");
for (auto &prop : channel->properties().list_properties(property::FLAG_CHANNEL_VARIABLE | property::FLAG_CHANNEL_VIEW, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) {
for (auto &prop : channel->properties()->list_properties(property::FLAG_CHANNEL_VARIABLE | property::FLAG_CHANNEL_VIEW, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) {
if(prop.type() == property::CHANNEL_ORDER) {
notify[prop.type().name] = orderId;
} else if(prop.type() == property::CHANNEL_DESCRIPTION) {
@@ -650,7 +651,7 @@ bool ConnectedClient::notifyClientEnterView(const std::deque<std::shared_ptr<Con
this->visibleClients.push_back(client);
for (const auto &elm : client->properties()->list_properties(property::FLAG_CLIENT_VIEW, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) {
cmd[index][elm.type().name] = elm.value();
cmd[index][std::string{elm.type().name}] = elm.value();
}
index++;
@@ -675,25 +676,30 @@ bool ConnectedClient::notifyChannelEdited(
if(!v_channel) return false; //Not visible? Important do not remove!
bool send_description_change{false};
size_t property_count{0};
Command notify("notifychanneledited");
for(auto prop : properties) {
const auto& prop_info = property::impl::info(prop);
const auto& prop_info = property::describe(prop);
if(prop == property::CHANNEL_ORDER)
notify[prop_info->name] = v_channel->previous_channel;
else if(prop == property::CHANNEL_DESCRIPTION) {
if(prop == property::CHANNEL_ORDER) {
notify[prop_info.name] = v_channel->previous_channel;
property_count++;
} else if(prop == property::CHANNEL_DESCRIPTION) {
send_description_change = true;
} else {
notify[prop_info->name] = channel->properties()[prop].as<string>();
notify[prop_info.name] = channel->properties()[prop].value();
property_count++;
}
}
notify["cid"] = channel->channelId();
notify["reasonid"] = ViewReasonId::VREASON_EDITED;
if(property_count > 0) {
notify["cid"] = channel->channelId();
notify["reasonid"] = ViewReasonId::VREASON_EDITED;
INVOKER(notify, invoker);
this->sendCommand(notify);
INVOKER(notify, invoker);
this->sendCommand(notify);
}
if(send_description_change) {
Command notify_dchange{"notifychanneldescriptionchanged"};
@@ -724,7 +730,7 @@ bool ConnectedClient::notifyChannelDeleted(const deque<ChannelId>& channel_ids,
bool ConnectedClient::notifyServerUpdated(std::shared_ptr<ConnectedClient> invoker) {
Command response("notifyserverupdated");
for (const auto& elm : this->server->properties().list_properties(property::FLAG_SERVER_VARIABLE, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) {
for (const auto& elm : this->server->properties()->list_properties(property::FLAG_SERVER_VARIABLE, this->getType() == CLIENT_TEAMSPEAK ? property::FLAG_NEW : (uint16_t) 0)) {
if(elm.type() == property::VIRTUALSERVER_MIN_WINPHONE_VERSION)
continue;

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