Merge branch 'main' into singleplayer

This commit is contained in:
LAX1DUDE 2022-07-27 21:03:46 -07:00
commit d706aa9305
591 changed files with 330511 additions and 55311 deletions

5
.gitattributes vendored
View File

@ -1,6 +1,7 @@
#
# https://help.github.com/articles/dealing-with-line-endings/
#
# These are explicitly windows files and should use crlf
*.bat text eol=crlf
*.bat text eol=crlf
*.sh text eol=lf
gradlew text eol=lf

9
.gitignore vendored
View File

@ -1,9 +1,13 @@
.gradle
.settings
.idea
build
bin
eaglercraftbungee/.idea
eaglercraftbungee/bin
eaglercraftbungee/rundir
eaglercraftbungee/test
eaglercraftbungee/minecrafthtml5bungee.iml
epkcompiler/bin
spigot-server/world*
eaglercraftbungee/rundir
@ -16,4 +20,7 @@ lwjgl-rundir/_eagstorage*
sp-server/.gradle
sp-server/.settings
sp-server/build
sp-server/bin
sp-server/bin
stable-download/_repl*
lwjgl-rundir/_eagstorage*
*.bak

228
README.md
View File

@ -1,34 +1,234 @@
# Eaglercraft
![eaglercraft](https://cdn.discordapp.com/attachments/378764518081429506/932053915061587978/thumbnail2.png)
### Note: someone got eags.us suspended for copyright infringement, use [https://eaglercraft.us/](https://eaglercraft.us/) for now (until it gets deleted too)
### Official Demo URL: [https://g.eags.us/eaglercraft/](https://g.eags.us/eaglercraft/)
### Client: [https://eaglercraft.us/](https://eaglercraft.us/)
### Offline Download: [Offline_Download_Version.html](https://github.com/LAX1DUDE/eaglercraft/raw/main/stable-download/Offline_Download_Version.html)
### Minecraft Beta: [https://github.com/LAX1DUDE/eaglercraft-beta/](https://github.com/LAX1DUDE/eaglercraft-beta/)
### Note: we will be transitioning away from Eaglercraft being standalone 'free browser minecraft' to Eaglercraft being a bukkit/bungee plugin for servers to allow 'online access' to players who register with a command as their real online-mode Minecraft account on the server. Server owners can still elect to set `online-mode=false` in their server.properties to allow free login but now that's their legal screw up instead of mine
![eaglercraft](https://cdn.discordapp.com/attachments/378764518081429506/964047481849643018/new_github_screenshot.png)
### Official Demo URL: [https://eaglercraft.us/](https://eaglercraft.us/)
### Download Locally: [stable-download/Offline_Download_Version.html](https://github.com/LAX1DUDE/eaglercraft/raw/main/stable-download/Offline_Download_Version.html)
(right click the link and press 'Save link as...' to download the file)
**Here is a 50 minute source walkthrough: [https://youtu.be/QvHvSX4Th84](https://youtu.be/QvHvSX4Th84)**
### Play Minecraft Beta Singleplayer: use [https://eaglercraft.us/beta/](https://eaglercraft.us/beta/) for now until I fix eags.us
Note that the server may be down - if you want a gameplay demo download the files from here [https://github.com/LAX1DUDE/eaglercraft/tree/main/stable-download](https://github.com/LAX1DUDE/eaglercraft/tree/main/stable-download) and host the page locally yourself
### Visit this site for a list of servers: [https://g.eags.us/eaglercraft/servers/](https://g.eags.us/eaglercraft/servers/)
**For any questions you can join the discord server and hit me up there [https://discord.gg/KMQW9Uvjyq](https://discord.gg/KMQW9Uvjyq)**
**For any questions you can join the discord server and hit me up there [https://discord.gg/Ekzcgs3DKZ](https://discord.gg/Ekzcgs3DKZ)**
## What is Eaglercraft?
Eaglercraft is real Minecraft 1.5.2 that you can play in any regular web browser. That includes school chromebooks, it works on all chromebooks. You can join real Minecraft 1.5.2 servers with it through a custom proxy based on Bungeecord.
### Ayonull runs a FAQ site: [https://eagler.nully.tech/](https://eagler.nully.tech/)
## How to make a server
### If replit is acceptable, you can use this:
### [https://replit.com/@ayunami2000/eaglercraft-server](https://replit.com/@ayunami2000/eaglercraft-server)
### Manual setup instructions:
1. **Check if Java is installed.** You can download it from [https://www.java.com/en/download/](https://www.java.com/en/download/)
2. Download the [stable-download/stable-download.zip](https://github.com/LAX1DUDE/eaglercraft/raw/main/stable-download/stable-download.zip) file from this repository
4. Extract the ZIP file you downloaded to a new folder
5. Open the new folder, go into the `java/bungee_command` folder
6. In Windows, double-click `run.bat`. It should open a new terminal window
![run.bat](https://i.gyazo.com/2b0f6b3e5b2e5a5a102c62ea5b6fba3f.png)
**Some computers may just say 'run' instead of 'run.bat', both are correct**
7. On macOS or Linux, google how to open the terminal and use the `cd` command to navigate to `java/bungee_command`
Then, in that folder, run `chmod +x run_unix.sh` and then run `./run_unix.sh`. It should start the same server
8. Go to the other `java/bukkit_command` folder that was also extracted from the ZIP
9. Again, on Windows, double-click `run.bat` in the folder. It should open a second terminal window.
Keep both the first and second terminal window you opened, just minimize them don't close
10. Again, on macOS or Linux, repeat step 7 except in the `java/bukkit_command` folder
11. **Your server is now ready.** Download and open [stable-download/Offline_Download_Version.html](https://github.com/LAX1DUDE/eaglercraft/raw/main/stable-download/Offline_Download_Version.html)
12. Go to 'Multiplayer' from the main menu. Select 'Direct Connect', type `127.0.0.1:25565` and press 'Join Server'
13. **It should allow you to connect, if not, check the two terminal windows for errors**
14. If you are okay with regularly checking for updates to [Offline_Download_Version.html](https://github.com/LAX1DUDE/eaglercraft/raw/main/stable-download/Offline_Download_Version.html), you are now finished
15. If you are playing with friends and want a shared website that can be updated, see the `stable-download/web` folder
16. To install, create a website and upload the contents of `stable-download/web` to the URL you want to have Eaglercraft on
17. **The 'web' folder will not work if you open it in your browser locally! If you see 'file:///' in the URL you are doing it wrong. You need to upload the folder to an HTTP or HTTPS server and access it over the internet via http:// or https://. The game will not load otherwise, this is not a bug**
18. To modify the default list of servers, download [servers_template.dat](https://github.com/LAX1DUDE/eaglercraft/raw/main/stable-download/servers_template.dat) and open it in [NBTExplorer](https://github.com/jaquadro/NBTExplorer/releases). Make your changes and then save
19. **If you can't install NBTExplorer, try [WebNBT](https://irath96.github.io/webNBT/)**
20. Upload your modified `servers_template.dat` to [base64encode.org](https://www.base64encode.org/) and press 'Encode'.
21. Download and open the encoded file, copy and paste the text in the file back between the quotes in `index.html` at line 21 (or 22) in place of the old text that also begins with `CgAACQAHc2Vydm` between the quotes
22. **To create a link to your site that automatically joins the server,** add a `?server=` variable to the URL, like (for example): [https://g.eags.us/eaglercraft/?server=127.0.0.1:25565](https://g.eags.us/eaglercraft/?server=127.0.0.1:25565) will automatically join `ws://127.0.0.1:25565/` as soon as the player finishes setting their username and skin
23. To change your server's MOTD and icon, edit the `motd1:` tag of the listener config in `java/bungee_command/config.yml`, and replace `server-icon.png` in the folder where the config file is. Use `&` to add color/formatting codes. The server list will downscale your icon to 64x64 pixels
24. You can give your MOTD multiple lines, add a `motd2:` to define a second line
25. **For an animated MOTD and icon, install EaglerMOTD: [https://github.com/LAX1DUDE/eaglercraft-motd/](https://github.com/LAX1DUDE/eaglercraft-motd/)**
26. To add some bukkit plugins, download the plugin's JAR file for CraftBukkit 1.5.2 and place it in `java/bukkit_command/plugins`
27. To add some bungee plugins, download the plugin's JAR file and place it in `java/bungee_command/plugins`
28. See [https://github.com/LAX1DUDE/eaglercraft-plugins/](https://github.com/LAX1DUDE/eaglercraft-plugins/) to download some supported plugins
29. **To disable voice chat, set `voice_enabled: false` in the bungeecord config.yml**
30. To add `/login` and `/register`, install [AuthMe](https://github.com/LAX1DUDE/eaglercraft-plugins/tree/main/AuthMe) and carefully [read it's documentation](https://github.com/AuthMe/AuthMeReloaded/wiki) to set it up correctly
31. **To ban a username on Eaglercraftbungee, use:** `eag-ban <username>`
32. **To ban an IP on Eaglercraftbungee, use:** `eag-ban-ip <ip>`, or `eag-ban-ip <name>` to ban the IP of a player automatically
33. To ban a range of IP addresses, use slash notation to define a subnet. Example: `eag-ban-ip 192.168.0.0/8`
34. To ban users by wildcard (\*) use: `eag-ban-wildcard <text>*` or `eag-ban-wildcard *<text>` or `eag-ban-wildcard *<text>*`
35. **You can edit bans.txt in your EaglercraftBungee folder, the server automatically reloads the file when it is saved**
36. To ban users by regular expression, use: `eag-ban-regex <regex>` with a regular expression to match the username in **lowercase**
37. **If you use /op on your server, keep in mind that if you "/op LAX1DUDE", a player joining as 'laX1DUDE' or 'LaX1dUdE' or 'lax1dude' will all have /op too. To solve this problem, force all operators to only be able to join with all lowercase ('lax1dude') letters in their usernames by moving 'BitchFilterPlugin.jar" into "java/bukkit_command/plugins" and then register every op username lowercase**
38. To connect to your server through a `ws://` or `wss://` URL instead of `ip:port`, set up [nginx](https://nginx.org/) as a reverse proxy to the `ip:port` of you EaglercraftBungee server you want the URL to connect to. Use a location URL template with the `proxy_pass` directive.
39. Eaglercraft uses port 80 for IP connections by default, typing `127.0.0.1` is the same as typing `ws://127.0.0.1:80/`
40. To forward a client's remote IP address from a request on nginx to EaglercraftBungee for enforcing IP bans, set the `X-Real-IP` header on the request to websocket when it is proxied
41. To make a custom resource pack for your site, clone this repository and edit the files in [lwjgl-rundir/resources](https://github.com/LAX1DUDE/eaglercraft/tree/main/lwjgl-rundir/resources).
42. When you are done, navigate to [epkcompiler/](https://github.com/LAX1DUDE/eaglercraft/tree/main/epkcompiler) and double-click `run.bat`. Wait for the window to say `Press any key to continue...` and close it. Then, go to `../javascript` in the repository and copy `javascript/assets.epk` to the `assets.epk` on your website
43. If you're on mac or linux, navigate to the epkcompiler folder via `cd` and run `chmod +x run_unix.sh` and then `./run_unix.sh` to do this, then copy the same `javascript/assets.epk` to the `assets.epk` on your website
## Singleplayer?
I successfully created Singleplayer for this version of eaglercraft and it works 100%, **but after many rounds of very thorough testing**, I found that TeaVM is unable to optimize certain aspects of terrain generation and world ticking as well as it can optimize the rendering for a multiplayer-only build. On an [i9-11900K]( https://www.cpubenchmark.net/cpu.php?cpu=Intel+Core+i9-11900K+%40+3.50GHz&id=3904) a render distance of 'Tiny' struggles to pass 12 TPS while standing still, and drops below 1 TPS as soon as a couple new chunks have to be generated and trigger lighting updates. The playerbase of this game **will bother me every f\*\*king day if what I release as singleplayer is not perfect** and therefore singleplayer will remain private indefinetly and I will not answer any further questions about it or share the source code unless you are just looking to repurpose some of the base OS emulation code.
**You can play Minecraft Beta singleplayer here: [https://g.eags.us/eaglercraft/singleplayer_test/](https://g.eags.us/eaglercraft/singleplayer_test/)**
## How does it work?
Eaglercraft uses the decompiled source code of the official build of Minecraft 1.5.2 direct from Mojang. It is decompiled by [MCP](http://www.modcoderpack.com/) and then recompiled to Javascript using [TeaVM](https://teavm.org/). Therefore it can join real Minecraft 1.5.2 servers, as it is really running Minecraft 1.5.2 in the browser. However, due to security limitations in modern browsers, it must use javascript Websocket objects for multiplayer instead of direct TCP connections to it's servers. A modified version of Bungeecord is included with Eaglercraft which accepts browser HTTP Websocket connections from Eaglercraft clients and unwraps the streams internally to regular TCP so they can be forwarded to regular Bukkit servers with no plugins. For graphics, a custom GPU compatibility layer allows Mojang's fixed function OpenGL 1.3 based rendering engine to render directly to an HTML5 WebGL 2.0 canvas on the page with minimal changes to the source, preserving the game's graphics to look exactly the same as desktop vanilla Minecraft 1.5.2.
## Installing
## Issues?
I got tired of closing duplicate 'how to maek sever' and 'add single player' issues almost every day so I disabled it because honestly I don't really care anymore, [join discord](https://discord.gg/Ekzcgs3DKZ) if you've got an issue to report that you are confident can be backed up with source code
## EaglercraftBungee
EaglercraftBungee translates WebSockets to a raw Minecraft 1.5.2 TCP connection. It is just regular BungeeCord with more `config.yml` options, and a built in plugin for syncing people's custom skins between clients so people can see each other's skins
### If voice chat causes moderation problems, set `voice_enabled: false` in config.yml
**To enable the /login and /register commands in EaglercraftBungee, you can edit this portion of config.yml**
```yaml
authservice:
enabled: false
register_enabled: true
authfile: auths.db
ip_limit: 0
join_messages:
- '&3Welcome to my &aEaglercraftBungee &3server!'
login_timeout: 30
```
- `enable` Turns login commands on and off
- `register_enabled` Turns register command on and off
- `authfile` Sets the authentication database file, which is **compatible with AuthMe**
- `ip_limit` Sets the max number of registrations per IP, 0 = unlimited
- `join_messages` List of messages to show the player when they join
- `login_timeout` Sets how many seconds players have to log in before they are kicked
**EaglercraftBungee has a built in domain blacklist that updates automatically, you can disable it by setting this in config.yml:**
```yaml
enable_web_origin_blacklist: false
```
**To block all clients on replit from joining, set this to true in config.yml:**
```yaml
origin_blacklist_block_replit_clients: true
```
**To block all offline-download clients, set this to true in config.yml:**
```yaml
origin_blacklist_block_offline_download: true
```
**To block the debug runtime (or other desktop clients), set this to true in config.yml:**
```yaml
origin_blacklist_block_missing_origin_header: true
```
**To add your own blacklisted domains**, create a file called `origin_blacklist.txt` in your bungeecord directory and put the regular expressions inside, one on each line. There 's also a `domain` command in the console to view a player's domain, and a `block-domain` and `block-domain-name` and `unblock-domain` command to manage the local `origin_blacklist.txt` from the bungee console (if you don't know how to edit a file on your own). The list reloads automatically when changes to the file are detected.
### To configure bungee to block connections from all clients except your own, set this option:
```yaml
origin_blacklist_use_simple_whitelist: true
```
### Then, add your domain to `origin_blacklist_simple_whitelist` like this:
```yaml
origin_blacklist_simple_whitelist:
- type the name of your client's domain here
```
### Then, unless still you want it as an option for your players, disable the offline download so hackers don't use it to bypass the whitelist, as it is not blocked in whitelist mode by default:
```yaml
origin_blacklist_block_offline_download: true
```
### To stop people from using bookmarklets to load a client from a different URL onto your official URL via XXS, add these headers to NGINX:
```
add_header X-Frame-Options "SAMEORIGIN";
add_header Referrer-Policy "strict-origin";
add_header X-XSS-Protection "1; mode=block";
add_header Content-Security-Policy "default-src 'self' 'unsafe-inline'; img-src 'self' 'unsafe-inline' data: blob:; connect-src 'self' ws: wss:; upgrade-insecure-requests";
```
(not fully tested, excuse the scroll bar)
### To use IP bans and rate limiting, enable `forward_ip` and pass a X-Real-IP header from your proxy to the bungeecord's websocket port
Nginx example: add `proxy_set_header X-Real-IP $remote_addr` to your proxy configuration
**The server has built in DoS protection, reset it via typing 'eag-ratelimit reset' in the bungee console**
```yaml
ratelimit:
ip:
enable: true
period: 90
limit: 60
limit_lockout: 80
lockout_duration: 1200
exceptions: []
```
- `enable` enable rate limiting
- `period` and `limit` set the number of requests (`limit`) can be made in (`period`) number of seconds
- `limit_lockout` and `lockout_duration` set the number of requests (`limit_lockout`) that can be made in (`period`) seconds before the IP is blocked for `lockout_duration` number of seconds
- `exceptions` a list of IP addresses that should never get rate limited. **Local IPs like 127.0.0.1 and 192.168.\*.\* and such are set as exceptions by default**
**you need `forward_ip` configured to use rate limiting, otherwise it will be disabled by defualt**
**To develop a plugin, download [stable-download/java/bungee_command/bungee_dist.jar](https://github.com/LAX1DUDE/eaglercraft/blob/main/stable-download/java/bungee_command/bungee-dist.jar) and add it to the Build Path of your Java IDE. Develop the plugin just like a regular BungeeCord plugin, see [EaglerMOTD](https://github.com/LAX1DUDE/eaglercraft-motd/) for an example.**
**Test your plugin by exporting it as a jar and putting it in the '/plugins' directory of EaglercraftBungee and then clicking 'run.bat'**
### New Events:
- **[net.md_5.bungee.api.event.WebsocketMOTDEvent](https://github.com/LAX1DUDE/eaglercraft/blob/main/eaglercraftbungee/src/main/java/net/md_5/bungee/api/event/WebsocketMOTDEvent.java)**: Triggered when a client or website requests the MOTD
- **[net.md_5.bungee.api.event.WebsocketQueryEvent](https://github.com/LAX1DUDE/eaglercraft/blob/main/eaglercraftbungee/src/main/java/net/md_5/bungee/api/event/WebsocketQueryEvent.java)**: Triggered when a client or website requests a query. This happens when a site opens a text WebSocket to a listener and sends a single string `Accept: <query>` packet. Can be used to provide additional custom statistics to server list sites supporting integrated WebSocket queries
**Register event handlers using the standard BungeeCord** `@EventHandler` **annotation in your** `Listener` **class**
## Installing (outdated guide, detailed)
If you want to use this project but don't want to compile it from scratch, download [stable-download/stable-download.zip](https://github.com/LAX1DUDE/eaglercraft/raw/main/stable-download/stable-download.zip) and extract
Within stable-download.zip there is a 'java' and a 'web' folder. Upload the contents of the web folder to your web server. **The web folder will not work if it is opened locally via file:///, it needs to be opened on an http:// or https:// page. Try [this extention](https://chrome.google.com/webstore/detail/web-server-for-chrome/ofhbbkphhbklhfoeikjpcbhemlocgigb/) if you are on chrome or if that's not possible then download the alternative single-file html [offline version](https://github.com/LAX1DUDE/eaglercraft/raw/main/stable-download/Offline_Download_Version.html) that does work on file URLs.** If you use this alternative version, please make sure you and your peers keep your copies up to date by regularly downloading any newer versions of the html file at [this link](https://github.com/LAX1DUDE/eaglercraft/blob/main/stable-download/Offline_Download_Version.html) to avoid getting stuck with a version that has a game-breaking glitch or mistake. The eaglercraft bungeecord executable is in the java/bungee_command folder along with the sample configuration file and a run.bat script to launch it. CraftBukkit for minecraft 1.5.2 configured to work with the eaglercraft bungee executable is in java/bukkit_command. The available version of Spigot 1.5.2 has a bug when used with bungee so you are limited to CraftBukkit and CraftBukkit plugins only on your servers
Within stable-download.zip there is a 'java' and a 'web' folder. Upload the contents of the web folder to your web server. **The web folder will not work if it is opened locally via file:///, it needs to be opened on an http:// or https:// page. Try [this extensions](https://chrome.google.com/webstore/detail/web-server-for-chrome/ofhbbkphhbklhfoeikjpcbhemlocgigb/) if you are on chrome or if that's not possible then download the alternative single-file html [offline version](https://github.com/LAX1DUDE/eaglercraft/raw/main/stable-download/Offline_Download_Version.html) that does work on file URLs.** If you use this alternative version, please make sure you and your peers keep your copies up to date by regularly downloading any newer versions of the html file at [this link](https://github.com/LAX1DUDE/eaglercraft/blob/main/stable-download/Offline_Download_Version.html) to avoid getting stuck with a version that has a game-breaking glitch or mistake. The eaglercraft bungeecord executable is in the java/bungee_command folder along with the sample configuration file and a run.bat script to launch it. CraftBukkit for minecraft 1.5.2 configured to work with the eaglercraft bungee executable is in java/bukkit_command. The available version of Spigot 1.5.2 has a bug when used with bungee so you are limited to CraftBukkit and CraftBukkit plugins only on your servers
**Here are some Bukkit plugins compatible with Eaglercraft: [https://github.com/LAX1DUDE/eaglercraft-plugins](https://github.com/LAX1DUDE/eaglercraft-plugins)**
To play the game, launch the run.bat script in both the bungee_command and bukkit_command folders. Then navigate to the URL where the contents of the web folder ended up. The game should load without any issues. Go to the Multiplayer screen and select 'Direct Connect'. **Type 127.0.0.1:25565.** Press connect or whatever and enjoy, the default port configured in the bungeecord config.yml is 25565 instead of 80 to avoid any potential conflict with the local web server or the OS (and linux desktop users can't use port numbers under 1024 without sudo).
@ -36,11 +236,11 @@ To play the game, launch the run.bat script in both the bungee_command and bukki
If you want SSL, set up [nginx](https://www.nginx.com/) as a reverse proxy from port 443 to the port on the bungeecord server. You can very easily configure SSL on an nginx virtual host when it is in proxy mode, much more easily than you could if I created my own websocket SSL config option in bungee. To connect to a server running an SSL websocket on the multiplayer screen, use this format: `wss://[url]/`. You can also add the :port option again after the domain or ip address at the beggining of the URL to change the port and connect with SSL. **If you set up the Eaglercraft index.html on an https:// URL, Chrome will only allow you to make wss:// connections from the multiplayer screen. It is a security feature in Chrome, if you want to support both ws:// and wss:// you have to host the Eaglercraft index.html on an http:// URL**. The best advice I have for security is to use Cloudflare to proxy both the site and the websocket, because you can use http and ws on your servers locally and then you can configure cloudflare to do the SSL for you when the connections are proxied. And it conceils your IP address to the max and you can also set up a content delivery network for the big assets.epk and classes.js files all for free on their little starter package
**To change the default servers on the server list, see the base64 in the javascript at line 8 of [stable-download/web/index.html](https://github.com/LAX1DUDE/eaglercraft/tree/main/stable-download/web/index.html). Copy and decode the base64 in the quotes using [base64decode.org](base64decode.org) and open the resulting file with NBTExplorer (the minecraft one). You will see the list of default servers in a 'servers' tag stored as NBT components, and you can edit them and add more as long as you follow the same format the existing servers have. When you're done, encode the file back to base64 using [base64encode.org](base64encode.org) and replace the base64 between the quotes on line 8 in index.html with the new base64 from base64encode.org.**
**To change the default servers on the server list, download [stable-download/servers_template.dat](https://github.com/LAX1DUDE/eaglercraft/raw/main/stable-download/servers_template.dat) and open the file with NBTExplorer (the minecraft one). You will see the list of default servers in a 'servers' tag stored as NBT components, and you can edit them and add more as long as you follow the same format the existing servers have. When you're done, save the file and encode the file back to base64 using the upload option on [base64encode.org](base64encode.org), then download the encoded file and open it and replace the base64 between the quotes on line 8 of your index.html with the new base64 in the encoded file you downloaded.**
There is a plugin hard coded into the bungeecord server to synchronize the eaglercraft profile skins, and also a plugin like authme for creating a secure isolated authentication lobby on the proxy. The authentication lobby plugin has not been implemented though, it is non-functional. For now you'll just have to use a different 3rd party bungeecord lobby authentication plugin like AuthMeBungee if you want authentication, which unfortunately is a must for any public server to prevent people from stealing each other's usernames. Just ignore the existing EaglerAuth plugin and it's configuration section in the main config.yml and just install and use some 3rd party plugin like AuthMeBungee instead.
There is a plugin hard coded into the bungeecord server to auto synchronize the eaglercraft profile skins between players and worlds
Someday I'll finish implementing EaglerAuth but right now I have bigger fish to fry
You should probably use a plugin like [AuthMe](https://dev.bukkit.org/projects/authme-reloaded/files/682502) to keep griefers from logging in to other people's profiles
## Compiling
@ -52,7 +252,7 @@ To complile to regular desktop Java for quick debugging, using native OpenGL for
- Create a run configuration and add a jvm argument pointing to the lwjgl natives folder (lwjgl-rundir/natives) like this: `-Djava.library.path=natives`, and make sure the working directory for the run configuration is the lwjgl-rundir folder.
To modify the game's assets repository (javascript/assets.epk), make your changes in lwjgl-runtime/resources/ and use the Eclipse project located in epkcompiler/ to regenerate the assets.epk file and copy it to the Javascript directory.
To modify the game's resource pack (javascript/assets.epk), view the readme in the [/epkcompiler](https://github.com/LAX1DUDE/eaglercraft/tree/main/epkcompiler) directory
this project is just a proof of concept to show what can be accomplished when using TeaVM to cross compile an existing java program to javascript. It is not very fast or stable, and the only real useful portion is the emulator code which creates a makeshift fixed function OpenGL 1.3 context using webgl (based on OpenGL 3.3) operational in the browser. Maybe it can be used to port other games in the future.

View File

@ -64,7 +64,7 @@ teavm {
classesToPreserve = null;
stopOnErrors = false;
optimizationLevel = "FULL"; //org.teavm.vm.TeaVMOptimizationLevel.SIMPLE;
optimizationLevel = "ADVANCED"; //org.teavm.vm.TeaVMOptimizationLevel.SIMPLE;
fastGlobalAnalysis = false;
targetType = "JAVASCRIPT"; //org.teavm.tooling.TeaVMTargetType.JAVASCRIPT;
cacheDirectory = null;

View File

@ -4,93 +4,108 @@
package net.md_5.bungee;
import net.md_5.bungee.tab.Custom;
import net.md_5.bungee.api.tab.CustomTabList;
import net.md_5.bungee.protocol.packet.Packet3Chat;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import net.md_5.bungee.protocol.packet.PacketFAPluginMessage;
import java.util.Collections;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import java.util.MissingResourceException;
import net.md_5.bungee.protocol.packet.DefinedPacket;
import io.netty.channel.ChannelException;
import java.util.Iterator;
import io.netty.util.concurrent.GenericFutureListener;
import java.net.SocketAddress;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.ChannelHandler;
import io.netty.util.AttributeKey;
import net.md_5.bungee.netty.PipelineUtils;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.fusesource.jansi.AnsiConsole;
import com.google.common.io.ByteStreams;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.util.concurrent.Future;
import io.netty.channel.Channel;
import io.netty.channel.ChannelException;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import net.md_5.bungee.api.config.ListenerInfo;
import java.util.TimerTask;
import net.md_5.bungee.reconnect.SQLReconnectHandler;
import io.netty.channel.ChannelHandler;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.MultithreadEventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.AttributeKey;
import io.netty.util.concurrent.GenericFutureListener;
import jline.UnsupportedTerminal;
import jline.console.ConsoleReader;
import jline.internal.Log;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.command.ConsoleCommandSender;
import java.util.concurrent.TimeUnit;
import java.util.Calendar;
import java.io.IOException;
import jline.UnsupportedTerminal;
import java.io.OutputStream;
import net.md_5.bungee.log.LoggingOutputStream;
import java.util.logging.Level;
import net.md_5.bungee.log.BungeeLogger;
import org.fusesource.jansi.AnsiConsole;
import jline.internal.Log;
import java.io.PrintStream;
import com.google.common.io.ByteStreams;
import net.md_5.bungee.command.CommandFind;
import net.md_5.bungee.command.CommandSend;
import net.md_5.bungee.command.CommandPerms;
import net.md_5.bungee.command.CommandBungee;
import net.md_5.bungee.command.CommandAlert;
import net.md_5.bungee.command.CommandIP;
import net.md_5.bungee.command.CommandServer;
import net.md_5.bungee.command.CommandList;
import net.md_5.bungee.command.CommandEnd;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.ReconnectHandler;
import net.md_5.bungee.api.config.ConfigurationAdapter;
import net.md_5.bungee.api.config.ListenerInfo;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Command;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.command.CommandReload;
import java.util.concurrent.ExecutorService;
import net.md_5.bungee.scheduler.BungeeScheduler;
import net.md_5.bungee.config.YamlConfig;
import net.md_5.bungee.eaglercraft.PluginEaglerAuth;
import net.md_5.bungee.eaglercraft.PluginEaglerSkins;
import net.md_5.bungee.eaglercraft.WebSocketListener;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import net.md_5.bungee.util.CaseInsensitiveMap;
import java.util.HashSet;
import io.netty.channel.nio.NioEventLoopGroup;
import net.md_5.bungee.scheduler.BungeeThreadPool;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.logging.Logger;
import jline.console.ConsoleReader;
import net.md_5.bungee.api.scheduler.TaskScheduler;
import java.io.File;
import net.md_5.bungee.api.config.ConfigurationAdapter;
import net.md_5.bungee.api.ReconnectHandler;
import net.md_5.bungee.api.plugin.PluginManager;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.Map;
import io.netty.channel.Channel;
import java.util.Collection;
import java.util.Timer;
import io.netty.channel.MultithreadEventLoopGroup;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.ResourceBundle;
import net.md_5.bungee.api.scheduler.TaskScheduler;
import net.md_5.bungee.api.tab.CustomTabList;
import net.md_5.bungee.command.CommandAlert;
import net.md_5.bungee.command.CommandBungee;
import net.md_5.bungee.command.CommandChangePassword;
import net.md_5.bungee.command.CommandClearRatelimit;
import net.md_5.bungee.command.CommandConfirmCode;
import net.md_5.bungee.command.CommandDomain;
import net.md_5.bungee.command.CommandDomainBlock;
import net.md_5.bungee.command.CommandDomainBlockDomain;
import net.md_5.bungee.command.CommandDomainUnblock;
import net.md_5.bungee.command.CommandEnd;
import net.md_5.bungee.command.CommandFind;
import net.md_5.bungee.command.CommandGlobalBan;
import net.md_5.bungee.command.CommandGlobalBanIP;
import net.md_5.bungee.command.CommandGlobalBanRegex;
import net.md_5.bungee.command.CommandGlobalBanReload;
import net.md_5.bungee.command.CommandGlobalBanWildcard;
import net.md_5.bungee.command.CommandGlobalCheckBan;
import net.md_5.bungee.command.CommandGlobalListBan;
import net.md_5.bungee.command.CommandGlobalUnban;
import net.md_5.bungee.command.CommandIP;
import net.md_5.bungee.command.CommandList;
import net.md_5.bungee.command.CommandPerms;
import net.md_5.bungee.command.CommandReload;
import net.md_5.bungee.command.CommandSend;
import net.md_5.bungee.command.CommandServer;
import net.md_5.bungee.command.ConsoleCommandSender;
import net.md_5.bungee.config.Configuration;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.config.YamlConfig;
import net.md_5.bungee.eaglercraft.AuthHandler;
import net.md_5.bungee.eaglercraft.AuthSystem;
import net.md_5.bungee.eaglercraft.BanList;
import net.md_5.bungee.eaglercraft.DomainBlacklist;
import net.md_5.bungee.eaglercraft.PluginEaglerSkins;
import net.md_5.bungee.eaglercraft.PluginEaglerVoice;
import net.md_5.bungee.eaglercraft.WebSocketListener;
import net.md_5.bungee.log.BungeeLogger;
import net.md_5.bungee.log.LoggingOutputStream;
import net.md_5.bungee.netty.PipelineUtils;
import net.md_5.bungee.protocol.packet.DefinedPacket;
import net.md_5.bungee.protocol.packet.Packet3Chat;
import net.md_5.bungee.protocol.packet.PacketFAPluginMessage;
import net.md_5.bungee.reconnect.SQLReconnectHandler;
import net.md_5.bungee.scheduler.BungeeScheduler;
import net.md_5.bungee.scheduler.BungeeThreadPool;
import net.md_5.bungee.tab.Custom;
import net.md_5.bungee.util.CaseInsensitiveMap;
public class BungeeCord extends ProxyServer {
public volatile boolean isRunning;
@ -99,6 +114,9 @@ public class BungeeCord extends ProxyServer {
public final ScheduledThreadPoolExecutor executors;
public final MultithreadEventLoopGroup eventLoops;
private final Timer saveThread;
private final Timer reloadBanThread;
private final Timer closeInactiveSockets;
private final Timer authTimeoutTimer;
private Collection<Channel> listeners;
private Collection<WebSocketListener> wsListeners;
private final Map<String, UserConnection> connections;
@ -111,6 +129,8 @@ public class BungeeCord extends ProxyServer {
private final TaskScheduler scheduler;
private ConsoleReader consoleReader;
private final Logger logger;
private Collection<Command> banCommands;
public AuthSystem authSystem;
public static BungeeCord getInstance() {
return (BungeeCord) ProxyServer.getInstance();
@ -122,6 +142,9 @@ public class BungeeCord extends ProxyServer {
this.executors = new BungeeThreadPool(new ThreadFactoryBuilder().setNameFormat("Bungee Pool Thread #%1$d").build());
this.eventLoops = (MultithreadEventLoopGroup) new NioEventLoopGroup(Runtime.getRuntime().availableProcessors(), new ThreadFactoryBuilder().setNameFormat("Netty IO Thread #%1$d").build());
this.saveThread = new Timer("Reconnect Saver");
this.reloadBanThread = new Timer("Ban List Reload");
this.closeInactiveSockets = new Timer("Close Inactive WebSockets");
this.authTimeoutTimer = new Timer("Auth Timeout");
this.listeners = new HashSet<Channel>();
this.wsListeners = new HashSet<WebSocketListener>();
this.connections = (Map<String, UserConnection>) new CaseInsensitiveMap();
@ -131,6 +154,7 @@ public class BungeeCord extends ProxyServer {
this.pluginChannels = new HashSet<String>();
this.pluginsFolder = new File("plugins");
this.scheduler = new BungeeScheduler();
this.banCommands = new ArrayList();
this.getPluginManager().registerCommand(null, new CommandReload());
this.getPluginManager().registerCommand(null, new CommandEnd());
this.getPluginManager().registerCommand(null, new CommandList());
@ -141,6 +165,12 @@ public class BungeeCord extends ProxyServer {
this.getPluginManager().registerCommand(null, new CommandPerms());
this.getPluginManager().registerCommand(null, new CommandSend());
this.getPluginManager().registerCommand(null, new CommandFind());
this.getPluginManager().registerCommand(null, new CommandClearRatelimit());
this.getPluginManager().registerCommand(null, new CommandConfirmCode());
this.getPluginManager().registerCommand(null, new CommandDomain());
this.getPluginManager().registerCommand(null, new CommandDomainBlock());
this.getPluginManager().registerCommand(null, new CommandDomainBlockDomain());
this.getPluginManager().registerCommand(null, new CommandDomainUnblock());
this.registerChannel("BungeeCord");
Log.setOutput(new PrintStream(ByteStreams.nullOutputStream()));
AnsiConsole.systemInstall();
@ -153,6 +183,42 @@ public class BungeeCord extends ProxyServer {
this.logger.info("NOTE: This error is non crucial, and BungeeCord will still function correctly! Do not bug the author about it unless you are still unable to get it working");
}
}
public void reconfigureBanCommands(boolean replaceBukkit) {
if(banCommands.size() > 0) {
for(Command c : banCommands) {
this.getPluginManager().unregisterCommand(c);
}
banCommands.clear();
}
Command cBan = new CommandGlobalBan(replaceBukkit);
Command cUnban = new CommandGlobalUnban(replaceBukkit);
Command cBanReload = new CommandGlobalBanReload(replaceBukkit);
Command cBanIP = new CommandGlobalBanIP(replaceBukkit);
Command cBanWildcard = new CommandGlobalBanWildcard(replaceBukkit);
Command cBanRegex = new CommandGlobalBanRegex(replaceBukkit);
Command cBanCheck = new CommandGlobalCheckBan(replaceBukkit);
Command cBanList = new CommandGlobalListBan(replaceBukkit);
banCommands.add(cBan);
banCommands.add(cUnban);
banCommands.add(cBanReload);
banCommands.add(cBanIP);
banCommands.add(cBanWildcard);
banCommands.add(cBanRegex);
banCommands.add(cBanCheck);
banCommands.add(cBanList);
this.getPluginManager().registerCommand(null, cBan);
this.getPluginManager().registerCommand(null, cUnban);
this.getPluginManager().registerCommand(null, cBanReload);
this.getPluginManager().registerCommand(null, cBanIP);
this.getPluginManager().registerCommand(null, cBanWildcard);
this.getPluginManager().registerCommand(null, cBanRegex);
this.getPluginManager().registerCommand(null, cBanCheck);
this.getPluginManager().registerCommand(null, cBanList);
}
public static void main(final String[] args) throws Exception {
final BungeeCord bungee = new BungeeCord();
@ -173,7 +239,11 @@ public class BungeeCord extends ProxyServer {
this.config.load();
this.pluginManager.detectPlugins(this.pluginsFolder);
this.pluginManager.addInternalPlugin(new PluginEaglerSkins());
//if(this.config.getAuthInfo().isEnabled()) this.pluginManager.addInternalPlugin(new PluginEaglerAuth());
this.pluginManager.addInternalPlugin(new PluginEaglerVoice(this.config.getVoiceEnabled()));
if (this.config.getAuthInfo().isEnabled()) {
this.authSystem = new AuthSystem(this.config.getAuthInfo());
this.getPluginManager().registerCommand(null, new CommandChangePassword(this.authSystem));
}
if (this.reconnectHandler == null) {
this.reconnectHandler = new SQLReconnectHandler();
}
@ -186,19 +256,46 @@ public class BungeeCord extends ProxyServer {
BungeeCord.this.getReconnectHandler().save();
}
}, 0L, TimeUnit.MINUTES.toMillis(5L));
this.reloadBanThread.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
BanList.maybeReloadBans(null);
}
}, 0L, TimeUnit.SECONDS.toMillis(3L));
DomainBlacklist.init(this);
this.closeInactiveSockets.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
DomainBlacklist.update();
for(WebSocketListener lst : BungeeCord.this.wsListeners) {
lst.closeInactiveSockets();
ListenerInfo info = lst.getInfo();
if(info.getRateLimitIP() != null) info.getRateLimitIP().deleteClearLimiters();
if(info.getRateLimitLogin() != null) info.getRateLimitLogin().deleteClearLimiters();
if(info.getRateLimitMOTD() != null) info.getRateLimitMOTD().deleteClearLimiters();
if(info.getRateLimitQuery() != null) info.getRateLimitQuery().deleteClearLimiters();
}
}
}, 0L, TimeUnit.SECONDS.toMillis(10L));
final int authTimeout = this.config.getAuthInfo().getLoginTimeout();
this.authTimeoutTimer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
AuthHandler.closeInactive(authTimeout);
}
}, 0L, TimeUnit.SECONDS.toMillis(2L));
}
public void startListeners() {
for (final ListenerInfo info : this.config.getListeners()) {
InetSocketAddress sock = info.getHost();
if(info.isWebsocket()) {
int port = 0;
try {
ServerSocket s = new ServerSocket(0, 0, InetAddress.getByName("127.11.0.1"));
sock = new InetSocketAddress("127.11.0.1", s.getLocalPort());
s.close();
} catch(IOException e) {
port = (int) (System.nanoTime() % 64000L + 1025L);
sock = new InetSocketAddress("127.11.0.1",(int) (System.nanoTime() % 64000L + 1025L));
}
try {
this.wsListeners.add(new WebSocketListener(info, sock, this));
@ -275,6 +372,9 @@ public class BungeeCord extends ProxyServer {
BungeeCord.this.reconnectHandler.save();
BungeeCord.this.reconnectHandler.close();
BungeeCord.this.saveThread.cancel();
BungeeCord.this.reloadBanThread.cancel();
BungeeCord.this.closeInactiveSockets.cancel();
BungeeCord.this.authTimeoutTimer.cancel();
BungeeCord.this.getLogger().info("Disabling plugins");
for (final Plugin plugin : BungeeCord.this.pluginManager.getPlugins()) {
plugin.onDisable();

View File

@ -5,36 +5,36 @@
package net.md_5.bungee;
import java.beans.ConstructorProperties;
import java.util.LinkedList;
import java.util.ArrayList;
import io.netty.util.concurrent.GenericFutureListener;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelHandler;
import net.md_5.bungee.netty.PipelineUtils;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.Objects;
import java.util.Queue;
import com.google.common.base.Preconditions;
import io.netty.bootstrap.Bootstrap;
import io.netty.util.concurrent.Future;
import net.md_5.bungee.netty.PacketHandler;
import net.md_5.bungee.connection.PingHandler;
import net.md_5.bungee.netty.HandlerBoss;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import net.md_5.bungee.api.ServerPing;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.concurrent.GenericFutureListener;
import net.md_5.bungee.api.Callback;
import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.protocol.packet.PacketFAPluginMessage;
import java.util.Objects;
import com.google.common.base.Preconditions;
import net.md_5.bungee.api.CommandSender;
import java.util.Collections;
import net.md_5.bungee.protocol.packet.DefinedPacket;
import java.util.Queue;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import java.util.Collection;
import java.net.InetSocketAddress;
import net.md_5.bungee.api.ServerPing;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.connection.PingHandler;
import net.md_5.bungee.netty.HandlerBoss;
import net.md_5.bungee.netty.PipelineUtils;
import net.md_5.bungee.protocol.packet.DefinedPacket;
import net.md_5.bungee.protocol.packet.PacketFAPluginMessage;
public class BungeeServerInfo implements ServerInfo {
private final String name;

View File

@ -4,22 +4,24 @@
package net.md_5.bungee;
import java.security.NoSuchAlgorithmException;
import java.security.KeyPairGenerator;
import java.security.spec.X509EncodedKeySpec;
import java.security.KeyFactory;
import java.security.PublicKey;
import javax.crypto.spec.IvParameterSpec;
import java.security.GeneralSecurityException;
import javax.crypto.spec.SecretKeySpec;
import java.util.Arrays;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.Random;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import net.md_5.bungee.protocol.packet.PacketFCEncryptionResponse;
import net.md_5.bungee.protocol.packet.PacketFDEncryptionRequest;
import javax.crypto.SecretKey;
import java.security.KeyPair;
import java.util.Random;
public class EncryptionUtil
{

View File

@ -4,9 +4,9 @@
package net.md_5.bungee;
import net.md_5.bungee.protocol.packet.PacketFAPluginMessage;
import net.md_5.bungee.protocol.packet.PacketCDClientStatus;
import net.md_5.bungee.protocol.packet.Packet9Respawn;
import net.md_5.bungee.protocol.packet.PacketCDClientStatus;
import net.md_5.bungee.protocol.packet.PacketFAPluginMessage;
public class PacketConstants {
public static final Packet9Respawn DIM1_SWITCH;

View File

@ -7,12 +7,13 @@ package net.md_5.bungee;
import java.beans.ConstructorProperties;
import java.net.InetSocketAddress;
import java.util.concurrent.TimeUnit;
import net.md_5.bungee.protocol.packet.PacketFFKick;
import net.md_5.bungee.api.connection.Connection;
import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.protocol.packet.DefinedPacket;
import net.md_5.bungee.protocol.packet.PacketFAPluginMessage;
import net.md_5.bungee.api.connection.Connection;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.protocol.packet.PacketFFKick;
public class ServerConnection implements Server {
private final ChannelWrapper ch;
@ -27,7 +28,7 @@ public class ServerConnection implements Server {
@Override
public synchronized void disconnect(final String reason) {
if (!this.ch.isClosed()) {
if (this.ch != null && !this.ch.isClosed()) {
this.unsafe().sendPacket(new PacketFFKick(reason));
this.ch.getHandle().eventLoop().schedule((Runnable) new Runnable() {
@Override
@ -40,7 +41,7 @@ public class ServerConnection implements Server {
@Override
public InetSocketAddress getAddress() {
return this.getInfo().getAddress();
return this.getInfo() == null ? null : this.getInfo().getAddress();
}
@Override
@ -53,7 +54,7 @@ public class ServerConnection implements Server {
this.unsafe = new Connection.Unsafe() {
@Override
public void sendPacket(final DefinedPacket packet) {
ServerConnection.this.ch.write(packet);
if (ServerConnection.this.ch != null) ServerConnection.this.ch.write(packet);
}
};
this.ch = ch;

View File

@ -5,50 +5,48 @@
package net.md_5.bungee;
import java.beans.ConstructorProperties;
import com.google.common.io.ByteArrayDataInput;
import net.md_5.bungee.protocol.Protocol;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.event.ServerKickEvent;
import java.util.Objects;
import net.md_5.bungee.protocol.packet.PacketFFKick;
import net.md_5.bungee.netty.CipherDecoder;
import javax.crypto.Cipher;
import java.security.PublicKey;
import io.netty.channel.ChannelHandler;
import net.md_5.bungee.netty.CipherEncoder;
import net.md_5.bungee.netty.PipelineUtils;
import net.md_5.bungee.protocol.packet.PacketFCEncryptionResponse;
import java.security.Key;
import net.md_5.bungee.protocol.packet.PacketFDEncryptionRequest;
import net.md_5.bungee.api.score.Scoreboard;
import java.util.Iterator;
import java.util.Objects;
import java.util.Queue;
import net.md_5.bungee.connection.CancelSendSignal;
import net.md_5.bungee.api.event.ServerSwitchEvent;
import net.md_5.bungee.connection.DownstreamBridge;
import net.md_5.bungee.netty.HandlerBoss;
import net.md_5.bungee.protocol.packet.Packet9Respawn;
import net.md_5.bungee.protocol.packet.PacketD1Team;
import net.md_5.bungee.api.score.Team;
import net.md_5.bungee.protocol.packet.PacketCEScoreboardObjective;
import net.md_5.bungee.api.score.Objective;
import net.md_5.bungee.protocol.packet.DefinedPacket;
import net.md_5.bungee.protocol.packet.forge.Forge1Login;
import net.md_5.bungee.protocol.Forge;
import net.md_5.bungee.netty.PacketDecoder;
import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.ServerConnectedEvent;
import com.google.common.base.Preconditions;
import net.md_5.bungee.protocol.packet.Packet1Login;
import com.google.common.io.ByteArrayDataOutput;
import net.md_5.bungee.protocol.packet.PacketFAPluginMessage;
import com.google.common.io.ByteStreams;
import net.md_5.bungee.api.ChatColor;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import net.md_5.bungee.netty.ChannelWrapper;
import com.google.common.base.Preconditions;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import io.netty.channel.ChannelHandler;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.event.ServerConnectedEvent;
import net.md_5.bungee.api.event.ServerKickEvent;
import net.md_5.bungee.api.event.ServerSwitchEvent;
import net.md_5.bungee.api.score.Objective;
import net.md_5.bungee.api.score.Scoreboard;
import net.md_5.bungee.api.score.Team;
import net.md_5.bungee.connection.CancelSendSignal;
import net.md_5.bungee.connection.DownstreamBridge;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.netty.CipherDecoder;
import net.md_5.bungee.netty.CipherEncoder;
import net.md_5.bungee.netty.HandlerBoss;
import net.md_5.bungee.netty.PacketDecoder;
import net.md_5.bungee.netty.PacketHandler;
import net.md_5.bungee.netty.PipelineUtils;
import net.md_5.bungee.protocol.Forge;
import net.md_5.bungee.protocol.packet.DefinedPacket;
import net.md_5.bungee.protocol.packet.Packet1Login;
import net.md_5.bungee.protocol.packet.Packet9Respawn;
import net.md_5.bungee.protocol.packet.PacketCEScoreboardObjective;
import net.md_5.bungee.protocol.packet.PacketD1Team;
import net.md_5.bungee.protocol.packet.PacketFAPluginMessage;
import net.md_5.bungee.protocol.packet.PacketFCEncryptionResponse;
import net.md_5.bungee.protocol.packet.PacketFDEncryptionRequest;
import net.md_5.bungee.protocol.packet.PacketFFKick;
import net.md_5.bungee.protocol.packet.forge.Forge1Login;
public class ServerConnector extends PacketHandler
{
@ -59,6 +57,7 @@ public class ServerConnector extends PacketHandler
private State thisState;
private SecretKey secretkey;
private boolean sentMessages;
private boolean protocolSupport = BungeeCord.getInstance().config.getProtocolSupport();
@Override
public void exception(final Throwable t) throws Exception {
@ -74,14 +73,16 @@ public class ServerConnector extends PacketHandler
@Override
public void connected(final ChannelWrapper channel) throws Exception {
this.ch = channel;
final ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("Login");
out.writeUTF(this.user.getAddress().getHostString());
out.writeInt(this.user.getAddress().getPort());
channel.write(new PacketFAPluginMessage("BungeeCord", out.toByteArray()));
channel.write(this.user.getPendingConnection().getHandshake());
if (this.user.getPendingConnection().getForgeLogin() == null) {
channel.write(PacketConstants.CLIENT_LOGIN);
if (!protocolSupport) {
final ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("Login");
out.writeUTF(this.user.getAddress().getHostString());
out.writeInt(this.user.getAddress().getPort());
channel.write(new PacketFAPluginMessage("BungeeCord", out.toByteArray()));
if (this.user.getPendingConnection().getForgeLogin() == null) {
channel.write(PacketConstants.CLIENT_LOGIN);
}
}
}
@ -92,7 +93,8 @@ public class ServerConnector extends PacketHandler
@Override
public void handle(final Packet1Login login) throws Exception {
Preconditions.checkState(this.thisState == State.LOGIN, (Object)"Not exepcting LOGIN");
Preconditions.checkState(this.thisState == State.LOGIN || this.thisState == State.ENCRYPT_REQUEST, (Object)"Not expecting LOGIN/ENCRYPT_REQUEST");
if (this.thisState == State.ENCRYPT_REQUEST) this.thisState = State.LOGIN;
final ServerConnection server = new ServerConnection(this.ch, this.target);
final ServerConnectedEvent event = new ServerConnectedEvent(this.user, server);
this.bungee.getPluginManager().callEvent(event);
@ -161,6 +163,16 @@ public class ServerConnector extends PacketHandler
@Override
public void handle(final PacketFDEncryptionRequest encryptRequest) throws Exception {
Preconditions.checkState(this.thisState == State.ENCRYPT_REQUEST, (Object)"Not expecting ENCRYPT_REQUEST");
if (protocolSupport) {
final ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("Login");
out.writeUTF(this.user.getAddress().getHostString());
out.writeInt(this.user.getAddress().getPort());
this.user.unsafe().sendPacket(new PacketFAPluginMessage("BungeeCord", out.toByteArray()));
if (this.user.getPendingConnection().getForgeLogin() == null) {
this.user.unsafe().sendPacket(PacketConstants.CLIENT_LOGIN);
}
}
if (this.user.getPendingConnection().getForgeLogin() != null) {
final PublicKey publickey = EncryptionUtil.getPubkey(encryptRequest);
this.secretkey = EncryptionUtil.getSecret();

View File

@ -4,52 +4,50 @@
package net.md_5.bungee;
import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.api.connection.PendingConnection;
import java.beans.ConstructorProperties;
import net.md_5.bungee.util.CaseInsensitiveSet;
import java.util.HashSet;
import net.md_5.bungee.api.config.TexturePackInfo;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.event.PermissionCheckEvent;
import java.util.Collections;
import java.net.InetSocketAddress;
import net.md_5.bungee.protocol.packet.PacketFAPluginMessage;
import net.md_5.bungee.protocol.packet.Packet3Chat;
import net.md_5.bungee.protocol.packet.PacketFFKick;
import java.util.logging.Level;
import io.netty.util.concurrent.GenericFutureListener;
import io.netty.util.internal.PlatformDependent;
import java.net.SocketAddress;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelHandler;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.WeakHashMap;
import java.util.logging.Level;
import com.google.common.base.Preconditions;
import io.netty.bootstrap.Bootstrap;
import io.netty.util.concurrent.Future;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import net.md_5.bungee.netty.PacketHandler;
import net.md_5.bungee.netty.HandlerBoss;
import net.md_5.bungee.netty.PipelineUtils;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.concurrent.GenericFutureListener;
import io.netty.util.internal.PlatformDependent;
import net.md_5.bungee.api.ChatColor;
import java.util.Objects;
import net.md_5.bungee.api.event.ServerConnectEvent;
import net.md_5.bungee.protocol.packet.DefinedPacket;
import com.google.common.base.Preconditions;
import java.util.Iterator;
import net.md_5.bungee.api.connection.Connection;
import net.md_5.bungee.api.score.Scoreboard;
import net.md_5.bungee.protocol.packet.PacketCCSettings;
import net.md_5.bungee.api.tab.TabListHandler;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.config.ServerInfo;
import java.util.Collection;
import net.md_5.bungee.api.config.TexturePackInfo;
import net.md_5.bungee.api.connection.Connection;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.PermissionCheckEvent;
import net.md_5.bungee.api.event.ServerConnectEvent;
import net.md_5.bungee.api.score.Scoreboard;
import net.md_5.bungee.api.tab.TabListHandler;
import net.md_5.bungee.connection.InitialHandler;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.netty.HandlerBoss;
import net.md_5.bungee.netty.PipelineUtils;
import net.md_5.bungee.protocol.packet.DefinedPacket;
import net.md_5.bungee.protocol.packet.Packet3Chat;
import net.md_5.bungee.protocol.packet.PacketCCSettings;
import net.md_5.bungee.protocol.packet.PacketFAPluginMessage;
import net.md_5.bungee.protocol.packet.PacketFFKick;
import net.md_5.bungee.util.CaseInsensitiveSet;
public final class UserConnection implements ProxiedPlayer {
private final ProxyServer bungee;
@ -71,6 +69,7 @@ public final class UserConnection implements ProxiedPlayer {
private final Scoreboard serverSentScoreboard;
private String displayName;
private final Connection.Unsafe unsafe;
private final Map<String, Object> attachment = new WeakHashMap();
public void init() {
this.displayName = this.name;
@ -383,4 +382,9 @@ public final class UserConnection implements ProxiedPlayer {
public String getDisplayName() {
return this.displayName;
}
@Override
public Map<String, Object> getAttachment() {
return attachment;
}
}

View File

@ -4,9 +4,8 @@
package net.md_5.bungee;
import java.util.Iterator;
import java.util.Collection;
import java.net.InetSocketAddress;
import java.util.Collection;
public class Util {
private static final int DEFAULT_PORT = 25565;

View File

@ -12,7 +12,7 @@ public enum ChatColor {
BLACK('0'), DARK_BLUE('1'), DARK_GREEN('2'), DARK_AQUA('3'), DARK_RED('4'), DARK_PURPLE('5'), GOLD('6'), GRAY('7'), DARK_GRAY('8'), BLUE('9'), GREEN('a'), AQUA('b'), RED('c'), LIGHT_PURPLE('d'), YELLOW('e'), WHITE('f'), MAGIC('k'),
BOLD('l'), STRIKETHROUGH('m'), UNDERLINE('n'), ITALIC('o'), RESET('r');
public static final char COLOR_CHAR = '§';
public static final char COLOR_CHAR = '\u00A7';
private static final Pattern STRIP_COLOR_PATTERN;
private static final Map<Character, ChatColor> BY_CHAR;
private final char code;
@ -20,7 +20,7 @@ public enum ChatColor {
private ChatColor(final char code) {
this.code = code;
this.toString = new String(new char[] { '§', code });
this.toString = new String(new char[] { '\u00A7', code });
}
@Override
@ -39,7 +39,7 @@ public enum ChatColor {
final char[] b = textToTranslate.toCharArray();
for (int i = 0; i < b.length - 1; ++i) {
if (b[i] == altColorChar && "0123456789AaBbCcDdEeFfKkLlMmNnOoRr".indexOf(b[i + 1]) > -1) {
b[i] = '§';
b[i] = '\u00A7';
b[i + 1] = Character.toLowerCase(b[i + 1]);
}
}
@ -51,7 +51,7 @@ public enum ChatColor {
}
static {
STRIP_COLOR_PATTERN = Pattern.compile("(?i)" + String.valueOf('§') + "[0-9A-FK-OR]");
STRIP_COLOR_PATTERN = Pattern.compile("(?i)" + String.valueOf('\u00A7') + "[0-9A-FK-OR]");
BY_CHAR = new HashMap<Character, ChatColor>();
for (final ChatColor colour : values()) {
ChatColor.BY_CHAR.put(colour.code, colour);

View File

@ -5,6 +5,7 @@
package net.md_5.bungee.api;
import java.util.Collection;
import java.util.Map;
public interface CommandSender {
String getName();
@ -22,4 +23,6 @@ public interface CommandSender {
boolean hasPermission(final String p0);
void setPermission(final String p0, final boolean p1);
Map<String, Object> getAttachment();
}

View File

@ -0,0 +1,25 @@
package net.md_5.bungee.api;
import java.util.List;
public interface MOTD extends QueryConnection {
public void sendToUser();
public String getLine1();
public String getLine2();
public List<String> getPlayerList();
public int[] getBitmap();
public int getOnlinePlayers();
public int getMaxPlayers();
public String getSubType();
public void setLine1(String p);
public void setLine2(String p);
public void setPlayerList(List<String> p);
public void setPlayerList(String... p);
public void setBitmap(int[] p);
public void setOnlinePlayers(int i);
public void setMaxPlayers(int i);
}

View File

@ -4,19 +4,21 @@
package net.md_5.bungee.api;
import net.md_5.bungee.api.tab.CustomTabList;
import net.md_5.bungee.api.scheduler.TaskScheduler;
import java.io.File;
import java.net.InetSocketAddress;
import net.md_5.bungee.api.config.ConfigurationAdapter;
import net.md_5.bungee.api.plugin.PluginManager;
import net.md_5.bungee.api.config.ServerInfo;
import java.util.Map;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import java.util.Collection;
import java.util.Map;
import java.util.logging.Logger;
import com.google.common.base.Preconditions;
import net.md_5.bungee.api.config.ConfigurationAdapter;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.PluginManager;
import net.md_5.bungee.api.scheduler.TaskScheduler;
import net.md_5.bungee.api.tab.CustomTabList;
public abstract class ProxyServer {
private static ProxyServer instance;

View File

@ -0,0 +1,70 @@
package net.md_5.bungee.api;
import java.net.InetAddress;
import org.json.JSONObject;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.api.config.ListenerInfo;
import net.md_5.bungee.eaglercraft.EaglercraftBungee;
public interface QueryConnection {
public InetAddress getRemoteAddress();
public ListenerInfo getListener();
public String getAccept();
public void setReturnType(String type);
public String getReturnType();
public int availableRequests();
public default JSONObject readRequestData() {
String s = readRequestString();
return s == null ? null : new JSONObject(s);
}
public String readRequestString();
public long getConnectionTimestamp();
public default long getConnectionAge() {
return System.currentTimeMillis() - getConnectionTimestamp();
}
public default void writeResponse(JSONObject msg) {
JSONObject toSend = new JSONObject();
toSend.put("type", getReturnType());
toSend.put("name", BungeeCord.getInstance().config.getServerName());
toSend.put("brand", EaglercraftBungee.brand);
toSend.put("vers", EaglercraftBungee.version);
toSend.put("cracked", EaglercraftBungee.cracked);
toSend.put("secure", false);
toSend.put("time", System.currentTimeMillis());
toSend.put("uuid", BungeeCord.getInstance().config.getUuid());
toSend.put("data", msg);
writeResponseRaw(toSend.toString());
}
public default void writeResponse(String msg) {
JSONObject toSend = new JSONObject();
toSend.put("type", getReturnType());
toSend.put("name", BungeeCord.getInstance().config.getServerName());
toSend.put("brand", EaglercraftBungee.brand);
toSend.put("vers", EaglercraftBungee.version);
toSend.put("cracked", EaglercraftBungee.cracked);
toSend.put("secure", false);
toSend.put("time", System.currentTimeMillis());
toSend.put("uuid", BungeeCord.getInstance().config.getUuid());
toSend.put("data", msg);
writeResponseRaw(toSend.toString());
}
public void writeResponseRaw(String msg);
public void writeResponseBinary(byte[] blob);
public void keepAlive(boolean yes);
public boolean shouldKeepAlive();
public boolean isClosed();
public void close();
}

View File

@ -0,0 +1,66 @@
package net.md_5.bungee.api;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.InputStream;
import javax.imageio.ImageIO;
public class ServerIcon {
public static int[] createServerIcon(BufferedImage awtIcon) {
BufferedImage icon = awtIcon;
boolean gotScaled = false;
if(icon.getWidth() != 64 || icon.getHeight() != 64) {
icon = new BufferedImage(64, 64, awtIcon.getType());
Graphics2D g = (Graphics2D) icon.getGraphics();
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, (awtIcon.getWidth() < 64 || awtIcon.getHeight() < 64) ?
RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR : RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g.setBackground(new Color(0, true));
g.clearRect(0, 0, 64, 64);
int ow = awtIcon.getWidth();
int oh = awtIcon.getHeight();
int nw, nh;
float aspectRatio = (float)oh / (float)ow;
if(aspectRatio >= 1.0f) {
nw = (int)(64 / aspectRatio);
nh = 64;
}else {
nw = 64;
nh = (int)(64 * aspectRatio);
}
g.drawImage(awtIcon, (64 - nw) / 2, (64 - nh) / 2, (64 - nw) / 2 + nw, (64 - nh) / 2 + nh, 0, 0, awtIcon.getWidth(), awtIcon.getHeight(), null);
g.dispose();
gotScaled = true;
}
int[] pxls = icon.getRGB(0, 0, 64, 64, new int[4096], 0, 64);
if(gotScaled) {
for(int i = 0; i < pxls.length; ++i) {
if((pxls[i] & 0xFFFFFF) == 0) {
pxls[i] = 0;
}
}
}
return pxls;
}
public static int[] createServerIcon(InputStream f) {
try {
return createServerIcon(ImageIO.read(f));
}catch(Throwable t) {
return null;
}
}
public static int[] createServerIcon(File f) {
try {
return createServerIcon(ImageIO.read(f));
}catch(Throwable t) {
return null;
}
}
}

View File

@ -1,35 +1,48 @@
package net.md_5.bungee.api.config;
import java.io.File;
import java.util.List;
public class AuthServiceInfo {
private final boolean enabled;
private final String limbo;
private final File authfile;
private final int timeout;
private final boolean registerEnabled;
private final String authfile;
private final int ipLimit;
private final List<String> joinMessages;
private final int loginTimeout;
public AuthServiceInfo(boolean enabled, String limbo, File authfile, int timeout) {
public AuthServiceInfo(boolean enabled, boolean registerEnabled, String authfile,
int timeout, List<String> joinMessages, int loginTimeout) {
this.enabled = enabled;
this.limbo = limbo;
this.registerEnabled = registerEnabled;
this.authfile = authfile;
this.timeout = timeout;
this.ipLimit = timeout;
this.joinMessages = joinMessages;
this.loginTimeout = loginTimeout;
}
public boolean isEnabled() {
return enabled;
}
public String getLimbo() {
return limbo;
public boolean isRegisterEnabled() {
return registerEnabled;
}
public File getAuthfile() {
public String getAuthfile() {
return authfile;
}
public int getTimeout() {
return timeout;
public int getIpLimit() {
return ipLimit;
}
public List<String> getJoinMessages() {
return joinMessages;
}
public int getLoginTimeout() {
return loginTimeout;
}
}

View File

@ -23,6 +23,18 @@ public interface ConfigurationAdapter {
Collection<String> getGroups(final String p0);
Collection<String> getPermissions(final String p0);
Collection<String> getBlacklistURLs();
Collection<String> getBlacklistSimpleWhitelist();
Collection<String> getDisabledCommands();
Collection<String> getICEServers();
AuthServiceInfo getAuthSettings();
Map<String, Object> getMap();
void forceSave();
}

View File

@ -4,12 +4,16 @@
package net.md_5.bungee.api.config;
import java.beans.ConstructorProperties;
import net.md_5.bungee.api.tab.TabListHandler;
import java.util.Map;
import java.io.File;
import java.net.InetSocketAddress;
import java.util.Map;
import net.md_5.bungee.api.ServerIcon;
import net.md_5.bungee.api.tab.TabListHandler;
import net.md_5.bungee.eaglercraft.WebSocketRateLimiter;
public class ListenerInfo {
private final String hostString;
private final InetSocketAddress host;
private final String motd;
private final int maxPlayers;
@ -18,13 +22,27 @@ public class ListenerInfo {
private final String fallbackServer;
private final boolean forceDefault;
private final boolean websocket;
private final boolean forwardIp;
private final Map<String, String> forcedHosts;
private final TexturePackInfo texturePack;
private final Class<? extends TabListHandler> tabList;
private final String serverIcon;
private final int[] serverIconCache;
private boolean serverIconLoaded;
private boolean serverIconSet;
private final boolean allowMOTD;
private final boolean allowQuery;
private final MOTDCacheConfiguration cacheConfig;
private final WebSocketRateLimiter rateLimitIP;
private final WebSocketRateLimiter rateLimitLogin;
private final WebSocketRateLimiter rateLimitMOTD;
private final WebSocketRateLimiter rateLimitQuery;
@ConstructorProperties({ "host", "motd", "maxPlayers", "tabListSize", "defaultServer", "fallbackServer", "forceDefault", "websocket", "forcedHosts", "texturePack", "tabList" })
public ListenerInfo(final InetSocketAddress host, final String motd, final int maxPlayers, final int tabListSize, final String defaultServer, final String fallbackServer, final boolean forceDefault, final boolean websocket,
final Map<String, String> forcedHosts, final TexturePackInfo texturePack, final Class<? extends TabListHandler> tabList) {
public ListenerInfo(final String hostString, final InetSocketAddress host, final String motd, final int maxPlayers, final int tabListSize, final String defaultServer, final String fallbackServer, final boolean forceDefault, final boolean websocket,
final boolean forwardIp, final Map<String, String> forcedHosts, final TexturePackInfo texturePack, final Class<? extends TabListHandler> tabList, final String serverIcon, final MOTDCacheConfiguration cacheConfig,
final boolean allowMOTD, final boolean allowQuery, final WebSocketRateLimiter rateLimitIP, final WebSocketRateLimiter rateLimitLogin, final WebSocketRateLimiter rateLimitMOTD, final WebSocketRateLimiter rateLimitQuery) {
this.hostString = hostString;
this.host = host;
this.motd = motd;
this.maxPlayers = maxPlayers;
@ -33,9 +51,25 @@ public class ListenerInfo {
this.fallbackServer = fallbackServer;
this.forceDefault = forceDefault;
this.websocket = websocket;
this.forwardIp = forwardIp;
this.forcedHosts = forcedHosts;
this.texturePack = texturePack;
this.tabList = tabList;
this.serverIcon = serverIcon;
this.serverIconCache = new int[4096];
this.serverIconLoaded = false;
this.serverIconSet = false;
this.allowMOTD = allowMOTD;
this.allowQuery = allowQuery;
this.cacheConfig = cacheConfig;
this.rateLimitIP = rateLimitIP;
this.rateLimitLogin = rateLimitLogin;
this.rateLimitMOTD = rateLimitMOTD;
this.rateLimitQuery = rateLimitQuery;
}
public String getHostString() {
return this.hostString;
}
public InetSocketAddress getHost() {
@ -120,6 +154,9 @@ public class ListenerInfo {
if (this.getTabListSize() != other.getTabListSize()) {
return false;
}
if (this.isWebsocket() != other.isWebsocket()) {
return false;
}
final Object this$defaultServer = this.getDefaultServer();
final Object other$defaultServer = other.getDefaultServer();
Label_0165: {
@ -180,6 +217,15 @@ public class ListenerInfo {
} else if (this$tabList.equals(other$tabList)) {
return true;
}
final Object this$getServerIcon = this.getServerIcon();
final Object other$getServerIcon = other.getServerIcon();
if (this$getServerIcon == null) {
if (other$getServerIcon == null) {
return true;
}
} else if (this$getServerIcon.equals(other$getServerIcon)) {
return true;
}
return false;
}
@ -208,6 +254,8 @@ public class ListenerInfo {
result = result * 31 + (($texturePack == null) ? 0 : $texturePack.hashCode());
final Object $tabList = this.getTabList();
result = result * 31 + (($tabList == null) ? 0 : $tabList.hashCode());
final Object $serverIconCache = this.getTabList();
result = result * 31 + (($serverIconCache == null) ? 0 : $serverIconCache.hashCode());
return result;
}
@ -220,4 +268,76 @@ public class ListenerInfo {
public boolean isWebsocket() {
return websocket;
}
public boolean hasForwardedHeaders() {
return forwardIp;
}
public String getServerIcon() {
return serverIcon;
}
public int[] getServerIconCache() {
if(!serverIconLoaded) {
if(serverIcon != null) {
int[] img = ServerIcon.createServerIcon(new File(serverIcon));
if(img != null) {
System.arraycopy(img, 0, serverIconCache, 0, img.length);
serverIconSet = true;
}else {
serverIconSet = false;
}
}else {
serverIconSet = false;
}
serverIconLoaded = true;
}
return serverIconCache;
}
public boolean isIconSet() {
getServerIconCache();
return serverIconSet;
}
public boolean isForwardIp() {
return forwardIp;
}
public boolean isServerIconLoaded() {
return serverIconLoaded;
}
public boolean isServerIconSet() {
return serverIconSet;
}
public boolean isAllowMOTD() {
return allowMOTD;
}
public boolean isAllowQuery() {
return allowQuery;
}
public MOTDCacheConfiguration getCacheConfig() {
return cacheConfig;
}
public WebSocketRateLimiter getRateLimitIP() {
return rateLimitIP;
}
public WebSocketRateLimiter getRateLimitLogin() {
return rateLimitLogin;
}
public WebSocketRateLimiter getRateLimitMOTD() {
return rateLimitMOTD;
}
public WebSocketRateLimiter getRateLimitQuery() {
return rateLimitQuery;
}
}

View File

@ -0,0 +1,21 @@
package net.md_5.bungee.api.config;
public class MOTDCacheConfiguration {
public final int cacheTTL;
public final boolean cacheServerListAnimation;
public final boolean cacheServerListResults;
public final boolean cacheServerListTrending;
public final boolean cacheServerListPortfolios;
public MOTDCacheConfiguration(int cacheTTL, boolean cacheServerListAnimation, boolean cacheServerListResults,
boolean cacheServerListTrending, boolean cacheServerListPortfolios) {
this.cacheTTL = cacheTTL;
this.cacheServerListAnimation = cacheServerListAnimation;
this.cacheServerListResults = cacheServerListResults;
this.cacheServerListTrending = cacheServerListTrending;
this.cacheServerListPortfolios = cacheServerListPortfolios;
}
}

View File

@ -4,12 +4,13 @@
package net.md_5.bungee.api.config;
import net.md_5.bungee.api.ServerPing;
import java.net.InetSocketAddress;
import java.util.Collection;
import net.md_5.bungee.api.Callback;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ServerPing;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import java.util.Collection;
import java.net.InetSocketAddress;
public interface ServerInfo {
String getName();

View File

@ -4,9 +4,10 @@
package net.md_5.bungee.api.connection;
import net.md_5.bungee.protocol.packet.DefinedPacket;
import java.net.InetSocketAddress;
import net.md_5.bungee.protocol.packet.DefinedPacket;
public interface Connection {
InetSocketAddress getAddress();

View File

@ -4,9 +4,10 @@
package net.md_5.bungee.api.connection;
import net.md_5.bungee.api.config.ListenerInfo;
import java.net.InetSocketAddress;
import net.md_5.bungee.api.config.ListenerInfo;
public interface PendingConnection extends Connection {
String getName();

View File

@ -4,10 +4,10 @@
package net.md_5.bungee.api.connection;
import net.md_5.bungee.api.tab.TabListHandler;
import net.md_5.bungee.api.config.TexturePackInfo;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.config.TexturePackInfo;
import net.md_5.bungee.api.tab.TabListHandler;
public interface ProxiedPlayer extends Connection, CommandSender {
String getDisplayName();

View File

@ -5,16 +5,17 @@
package net.md_5.bungee.api.event;
import java.beans.ConstructorProperties;
import java.util.Map;
import java.util.Collections;
import java.util.concurrent.ConcurrentHashMap;
import com.google.common.base.Preconditions;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicBoolean;
import net.md_5.bungee.api.plugin.Plugin;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import com.google.common.base.Preconditions;
import net.md_5.bungee.api.Callback;
import net.md_5.bungee.api.plugin.Event;
import net.md_5.bungee.api.plugin.Plugin;
public class AsyncEvent<T> extends Event {
private final Callback<T> done;

View File

@ -5,6 +5,7 @@
package net.md_5.bungee.api.event;
import java.beans.ConstructorProperties;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.plugin.Event;

View File

@ -5,6 +5,7 @@
package net.md_5.bungee.api.event;
import java.beans.ConstructorProperties;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Event;

View File

@ -5,6 +5,7 @@
package net.md_5.bungee.api.event;
import java.util.Arrays;
import net.md_5.bungee.api.connection.Connection;
import net.md_5.bungee.api.plugin.Cancellable;

View File

@ -5,6 +5,7 @@
package net.md_5.bungee.api.event;
import java.beans.ConstructorProperties;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Event;

View File

@ -5,6 +5,7 @@
package net.md_5.bungee.api.event;
import java.beans.ConstructorProperties;
import net.md_5.bungee.api.ServerPing;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.plugin.Event;

View File

@ -5,8 +5,9 @@
package net.md_5.bungee.api.event;
import java.beans.ConstructorProperties;
import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.api.plugin.Event;
public class ServerConnectedEvent extends Event {

View File

@ -5,6 +5,7 @@
package net.md_5.bungee.api.event;
import java.beans.ConstructorProperties;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Event;

View File

@ -5,6 +5,7 @@
package net.md_5.bungee.api.event;
import java.beans.ConstructorProperties;
import net.md_5.bungee.api.connection.Connection;
import net.md_5.bungee.api.plugin.Event;

View File

@ -0,0 +1,15 @@
package net.md_5.bungee.api.event;
import net.md_5.bungee.api.MOTD;
public class WebsocketMOTDEvent extends WebsocketQueryEvent {
public WebsocketMOTDEvent(MOTD connection) {
super(connection);
}
public MOTD getMOTD() {
return (MOTD)connection;
}
}

View File

@ -0,0 +1,33 @@
package net.md_5.bungee.api.event;
import java.net.InetAddress;
import net.md_5.bungee.api.QueryConnection;
import net.md_5.bungee.api.config.ListenerInfo;
import net.md_5.bungee.api.plugin.Event;
public class WebsocketQueryEvent extends Event {
protected final QueryConnection connection;
public WebsocketQueryEvent(QueryConnection connection) {
this.connection = connection;
}
public InetAddress getRemoteAddress() {
return connection.getRemoteAddress();
}
public ListenerInfo getListener() {
return connection.getListener();
}
public String getAccept() {
return connection.getAccept();
}
public QueryConnection getQuery() {
return connection;
}
}

View File

@ -5,9 +5,11 @@
package net.md_5.bungee.api.plugin;
import java.util.Arrays;
import net.md_5.bungee.api.CommandSender;
import com.google.common.base.Preconditions;
import net.md_5.bungee.api.CommandSender;
public abstract class Command {
private final String name;
private final String permission;

View File

@ -4,9 +4,10 @@
package net.md_5.bungee.api.plugin;
import java.io.File;
import java.io.InputStream;
import java.util.logging.Logger;
import java.io.File;
import net.md_5.bungee.api.ProxyServer;
public class Plugin {

View File

@ -4,11 +4,10 @@
package net.md_5.bungee.api.plugin;
import java.util.HashSet;
import java.util.Iterator;
import java.net.URL;
import java.util.Set;
import java.net.URLClassLoader;
import java.util.HashSet;
import java.util.Set;
public class PluginClassloader extends URLClassLoader {
private static final Set<PluginClassloader> allLoaders;

View File

@ -5,8 +5,8 @@
package net.md_5.bungee.api.plugin;
import java.beans.ConstructorProperties;
import java.util.HashSet;
import java.io.File;
import java.util.HashSet;
import java.util.Set;
public class PluginDescription {

View File

@ -5,9 +5,10 @@
package net.md_5.bungee.api.plugin;
import java.util.logging.LogRecord;
import net.md_5.bungee.api.ProxyServer;
import java.util.logging.Logger;
import net.md_5.bungee.api.ProxyServer;
public class PluginLogger extends Logger {
private String pluginName;

View File

@ -5,33 +5,36 @@
package net.md_5.bungee.api.plugin;
import java.beans.ConstructorProperties;
import java.lang.reflect.Method;
import java.io.InputStream;
import java.util.jar.JarEntry;
import java.util.zip.ZipEntry;
import java.util.jar.JarFile;
import com.google.common.base.Preconditions;
import java.io.File;
import java.net.URLClassLoader;
import java.net.URL;
import java.util.Iterator;
import java.util.Stack;
import java.util.Collection;
import java.util.logging.Level;
import net.md_5.bungee.api.ChatColor;
import java.util.Arrays;
import net.md_5.bungee.api.CommandSender;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import net.md_5.bungee.event.EventHandler;
import com.google.common.eventbus.Subscribe;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import net.md_5.bungee.event.EventBus;
import org.yaml.snakeyaml.Yaml;
import net.md_5.bungee.api.ProxyServer;
import java.util.Stack;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Level;
import java.util.regex.Pattern;
import org.yaml.snakeyaml.Yaml;
import com.google.common.base.Preconditions;
import com.google.common.eventbus.Subscribe;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.command.ConsoleCommandSender;
import net.md_5.bungee.event.EventBus;
import net.md_5.bungee.event.EventHandler;
public class PluginManager {
private static final Pattern argsSplit;
private final ProxyServer proxy;
@ -67,6 +70,9 @@ public class PluginManager {
if (command == null) {
return false;
}
if(!(sender instanceof ConsoleCommandSender) && ((BungeeCord)proxy).config.getDisabledCommands().contains(command.getName())) {
return false;
}
final String permission = command.getPermission();
if (permission != null && !permission.isEmpty() && !sender.hasPermission(permission)) {
sender.sendMessage(this.proxy.getTranslation("no_permission"));

View File

@ -5,6 +5,7 @@
package net.md_5.bungee.api.scheduler;
import java.util.concurrent.TimeUnit;
import net.md_5.bungee.api.plugin.Plugin;
public interface ScheduledTask {

View File

@ -5,6 +5,7 @@
package net.md_5.bungee.api.scheduler;
import java.util.concurrent.TimeUnit;
import net.md_5.bungee.api.plugin.Plugin;
public interface TaskScheduler {

View File

@ -4,12 +4,13 @@
package net.md_5.bungee.api.score;
import java.util.HashMap;
import com.google.common.base.Preconditions;
import java.util.Collections;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import com.google.common.base.Preconditions;
public class Scoreboard {
private String name;
private Position position;

View File

@ -5,9 +5,9 @@
package net.md_5.bungee.api.score;
import java.beans.ConstructorProperties;
import java.util.HashSet;
import java.util.Collections;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class Team {
@ -19,7 +19,7 @@ public class Team {
private Set<String> players;
public Collection<String> getPlayers() {
return (Collection<String>) Collections.unmodifiableSet((Set<?>) this.players);
return (Collection<String>) (Collection<?>) Collections.unmodifiableSet((Set<?>) this.players);
}
public void addPlayer(final String name) {

View File

@ -4,9 +4,9 @@
package net.md_5.bungee.command;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.plugin.Command;
public class CommandAlert extends Command {

View File

@ -4,9 +4,9 @@
package net.md_5.bungee.command;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.plugin.Command;
public class CommandBungee extends Command {

View File

@ -0,0 +1,35 @@
package net.md_5.bungee.command;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Command;
import net.md_5.bungee.eaglercraft.AuthSystem;
public class CommandChangePassword extends Command {
private final AuthSystem authSystem;
public CommandChangePassword(AuthSystem authSystem) {
super("changepassword", "bungeecord.command.eag.changepassword",
new String[] { "changepwd", "changepasswd", "changepass" });
this.authSystem = authSystem;
}
@Override
public void execute(final CommandSender sender, final String[] args) {
if (!(sender instanceof ProxiedPlayer)) {
return;
}
String username = sender.getName();
if (args.length == 0 || args.length == 1) {
sender.sendMessage("\u00A7cUsage: /changepassword <oldPassword> <newPassword>");
} else if (this.authSystem.login(username, args[0])) {
if (this.authSystem.changePass(username, args[1])) {
sender.sendMessage("\u00A7cPassword changed successfully!");
} else {
sender.sendMessage("\u00A7cUnable to change your password...");
}
} else {
sender.sendMessage("\u00A7cThe old password specified is incorrect!");
}
}
}

View File

@ -0,0 +1,118 @@
package net.md_5.bungee.command;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.config.ListenerInfo;
import net.md_5.bungee.api.plugin.Command;
public class CommandClearRatelimit extends Command {
public CommandClearRatelimit() {
super("eag-ratelimit", "bungeecord.command.eag.ratelimit", "e-ratelimit", "gratelimit");
}
@Override
public void execute(CommandSender p0, String[] p1) {
if(p1.length >= 1 && ("clear".equalsIgnoreCase(p1[0]) || "reset".equalsIgnoreCase(p1[0]))) {
if(p1.length == 1 || (p1.length == 2 && "all".equalsIgnoreCase(p1[1]))) {
for(ListenerInfo l : BungeeCord.getInstance().config.getListeners()) {
if(l.getRateLimitIP() != null) l.getRateLimitIP().resetLimiters();
if(l.getRateLimitLogin() != null) l.getRateLimitLogin().resetLimiters();
if(l.getRateLimitMOTD() != null) l.getRateLimitMOTD().resetLimiters();
if(l.getRateLimitQuery() != null) l.getRateLimitQuery().resetLimiters();
}
p0.sendMessage(ChatColor.GREEN + "Reset all ratelimits");
return;
}else if(p1.length == 2 || p1.length == 3) {
ListenerInfo ll = null;
if(p1.length == 3) {
for(ListenerInfo l : BungeeCord.getInstance().config.getListeners()) {
if(l.getHostString().equalsIgnoreCase(p1[2])) {
ll = l;
break;
}
}
if(ll == null) {
p0.sendMessage(ChatColor.RED + "Listener does not exist: " + ChatColor.WHITE + p1[2]);
String accum = "";
for(ListenerInfo l : BungeeCord.getInstance().config.getListeners()) {
if(accum.length() > 0) {
accum += ", ";
}
accum += l.getHostString();
}
p0.sendMessage(ChatColor.GREEN + "Listeners Available: " + ChatColor.WHITE + accum);
return;
}
}
if("all".equalsIgnoreCase(p1[1])) {
if(ll != null) {
if(ll.getRateLimitIP() != null) ll.getRateLimitIP().resetLimiters();
if(ll.getRateLimitLogin() != null) ll.getRateLimitLogin().resetLimiters();
if(ll.getRateLimitMOTD() != null) ll.getRateLimitMOTD().resetLimiters();
if(ll.getRateLimitQuery() != null) ll.getRateLimitQuery().resetLimiters();
p0.sendMessage(ChatColor.GREEN + "Reset all ratelimits on listener: " + ChatColor.WHITE + ll.getHostString());
}else {
for(ListenerInfo l : BungeeCord.getInstance().config.getListeners()) {
if(l.getRateLimitIP() != null) l.getRateLimitIP().resetLimiters();
if(l.getRateLimitLogin() != null) l.getRateLimitLogin().resetLimiters();
if(l.getRateLimitMOTD() != null) l.getRateLimitMOTD().resetLimiters();
if(l.getRateLimitQuery() != null) l.getRateLimitQuery().resetLimiters();
}
p0.sendMessage(ChatColor.GREEN + "Reset all ratelimits");
}
return;
}else if("ip".equalsIgnoreCase(p1[1])) {
if(ll != null) {
if(ll.getRateLimitIP() != null) ll.getRateLimitIP().resetLimiters();
p0.sendMessage(ChatColor.GREEN + "Reset all IP ratelimits on listener: " + ChatColor.WHITE + ll.getHostString());
}else {
for(ListenerInfo l : BungeeCord.getInstance().config.getListeners()) {
if(l.getRateLimitIP() != null) l.getRateLimitIP().resetLimiters();
}
p0.sendMessage(ChatColor.GREEN + "Reset all IP ratelimits.");
}
return;
}else if("login".equalsIgnoreCase(p1[1])) {
if(ll != null) {
if(ll.getRateLimitLogin() != null) ll.getRateLimitLogin().resetLimiters();
p0.sendMessage(ChatColor.GREEN + "Reset all login ratelimits on listener: " + ChatColor.WHITE + ll.getHostString());
}else {
for(ListenerInfo l : BungeeCord.getInstance().config.getListeners()) {
if(l.getRateLimitLogin() != null) l.getRateLimitLogin().resetLimiters();
}
p0.sendMessage(ChatColor.GREEN + "Reset all login ratelimits.");
}
return;
}else if("motd".equalsIgnoreCase(p1[1])) {
if(ll != null) {
if(ll.getRateLimitMOTD() != null) ll.getRateLimitMOTD().resetLimiters();
p0.sendMessage(ChatColor.GREEN + "Reset all MOTD ratelimits on listener: " + ChatColor.WHITE + ll.getHostString());
}else {
for(ListenerInfo l : BungeeCord.getInstance().config.getListeners()) {
if(l.getRateLimitMOTD() != null) l.getRateLimitMOTD().resetLimiters();
}
p0.sendMessage(ChatColor.GREEN + "Reset all MOTD ratelimits.");
}
return;
}else if("query".equalsIgnoreCase(p1[1])) {
if(ll != null) {
if(ll.getRateLimitMOTD() != null) ll.getRateLimitMOTD().resetLimiters();
p0.sendMessage(ChatColor.GREEN + "Reset all query ratelimits on listener: " + ChatColor.WHITE + ll.getHostString());
}else {
for(ListenerInfo l : BungeeCord.getInstance().config.getListeners()) {
if(l.getRateLimitMOTD() != null) l.getRateLimitMOTD().resetLimiters();
}
p0.sendMessage(ChatColor.GREEN + "Reset all query ratelimits.");
}
return;
}
}
}
p0.sendMessage(ChatColor.RED + "How to reset all rate limits: " + ChatColor.WHITE + "/eag-ratelimit reset");
p0.sendMessage(ChatColor.RED + "How to reset a specific rate limit: " + ChatColor.WHITE + "/eag-ratelimit reset <ip|login|motd|query>");
p0.sendMessage(ChatColor.RED + "How to reset a specific listener: " + ChatColor.WHITE + "/eag-ratelimit reset <all|ip|login|motd|query> <host>");
}
}

View File

@ -0,0 +1,33 @@
package net.md_5.bungee.command;
import java.nio.charset.StandardCharsets;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.plugin.Command;
import net.md_5.bungee.eaglercraft.QueryConnectionImpl;
import net.md_5.bungee.eaglercraft.SHA1Digest;
public class CommandConfirmCode extends Command {
public CommandConfirmCode() {
super("confirm-code", "bungeecord.command.eag.confirmcode", "confirmcode");
}
@Override
public void execute(CommandSender p0, String[] p1) {
if(p1.length != 1) {
p0.sendMessage(ChatColor.RED + "How to use: " + ChatColor.WHITE + "/confirm-code <code>");
}else {
p0.sendMessage(ChatColor.YELLOW + "Server list 2FA code has been set to: " + ChatColor.GREEN + p1[0]);
p0.sendMessage(ChatColor.YELLOW + "You can now return to the server list site and continue");
byte[] bts = p1[0].getBytes(StandardCharsets.US_ASCII);
SHA1Digest dg = new SHA1Digest();
dg.update(bts, 0, bts.length);
byte[] f = new byte[20];
dg.doFinal(f, 0);
QueryConnectionImpl.confirmHash = SHA1Digest.hash2string(f);
}
}
}

View File

@ -0,0 +1,37 @@
package net.md_5.bungee.command;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Command;
public class CommandDomain extends Command {
public CommandDomain() {
super("domain", "bungeecord.command.eag.domain");
}
@Override
public void execute(CommandSender p0, String[] p1) {
if (p1.length < 1) {
p0.sendMessage(ChatColor.RED + "Please follow this command by a user name");
return;
}
final ProxiedPlayer user = ProxyServer.getInstance().getPlayer(p1[0]);
if (user == null) {
p0.sendMessage(ChatColor.RED + "That user is not online");
} else {
Object o = user.getAttachment().get("origin");
if(o != null) {
p0.sendMessage(ChatColor.BLUE + "Domain of " + p1[0] + " is " + o);
if(p0.hasPermission("bungeecord.command.eag.blockdomain")) {
p0.sendMessage(ChatColor.BLUE + "Type " + ChatColor.WHITE + "/block-domain " + p1[0] + ChatColor.BLUE + " to block this person");
}
}else {
p0.sendMessage(ChatColor.RED + "Domain of " + p1[0] + " is unknown");
}
}
}
}

View File

@ -0,0 +1,38 @@
package net.md_5.bungee.command;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Command;
import net.md_5.bungee.eaglercraft.DomainBlacklist;
public class CommandDomainBlock extends Command {
public CommandDomainBlock() {
super("block-domain", "bungeecord.command.eag.blockdomain");
}
@Override
public void execute(CommandSender p0, String[] p1) {
if (p1.length < 1) {
p0.sendMessage(ChatColor.RED + "Please follow this command by a username");
return;
}
final ProxiedPlayer user = ProxyServer.getInstance().getPlayer(p1[0]);
if (user == null) {
p0.sendMessage(ChatColor.RED + "That user is not online");
}else {
Object o = user.getAttachment().get("origin");
if(o != null) {
DomainBlacklist.addLocal((String)o);
p0.sendMessage(ChatColor.RED + "Domain of " + ChatColor.WHITE + p1[0] + ChatColor.RED + " is " + ChatColor.WHITE + o);
p0.sendMessage(ChatColor.RED + "It was added to the local block list.");
user.disconnect("client blocked");
}else {
p0.sendMessage(ChatColor.RED + "Domain of " + p1[0] + " is unknown");
}
}
}
}

View File

@ -0,0 +1,24 @@
package net.md_5.bungee.command;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.plugin.Command;
import net.md_5.bungee.eaglercraft.DomainBlacklist;
public class CommandDomainBlockDomain extends Command {
public CommandDomainBlockDomain() {
super("block-domain-name", "bungeecord.command.eag.blockdomainname");
}
@Override
public void execute(CommandSender p0, String[] p1) {
if (p1.length < 1) {
p0.sendMessage(ChatColor.RED + "Please follow this command by a domain");
return;
}
DomainBlacklist.addLocal(p1[0]);
p0.sendMessage(ChatColor.GREEN + "The domain '" + ChatColor.WHITE + p1[0] + ChatColor.GREEN + "' was added to the block list");
}
}

View File

@ -0,0 +1,27 @@
package net.md_5.bungee.command;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.plugin.Command;
import net.md_5.bungee.eaglercraft.DomainBlacklist;
public class CommandDomainUnblock extends Command {
public CommandDomainUnblock() {
super("unblock-domain", "bungeecord.command.eag.unblockdomain", "unblock-domain-name");
}
@Override
public void execute(CommandSender p0, String[] p1) {
if (p1.length < 1) {
p0.sendMessage(ChatColor.RED + "Please follow this command by a domain");
return;
}
if(DomainBlacklist.removeLocal(p1[0])) {
p0.sendMessage(ChatColor.GREEN + "The domain '" + p1[0] + "' was removed from the local block list");
}else {
p0.sendMessage(ChatColor.RED + "The domain was not removed, is it on the block list? Check '" + DomainBlacklist.localBlacklist.getName() + "' in your bungeecord directory");
}
}
}

View File

@ -4,10 +4,10 @@
package net.md_5.bungee.command;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Command;
public class CommandFind extends Command {

View File

@ -0,0 +1,61 @@
package net.md_5.bungee.command;
import java.util.Collection;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Command;
import net.md_5.bungee.eaglercraft.BanList;
public class CommandGlobalBan extends Command {
private final boolean replaceBukkit;
public CommandGlobalBan(boolean replaceBukkit) {
super(replaceBukkit ? "ban" : "eag-ban", "bungeecord.command.eag.ban", replaceBukkit ? new String[] { "kickban", "eag-ban", "e-ban", "gban" } : new String[] { "e-ban", "gban" });
this.replaceBukkit = replaceBukkit;
}
@Override
public void execute(CommandSender p0, String[] p1) {
if(p1.length >= 1) {
String p = p1[0].trim().toLowerCase();
if(p0.getName().equalsIgnoreCase(p)) {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "You cannot ban yourself");
return;
}
String reason = "The ban hammer has spoken!";
if(p1.length >= 2) {
reason = "";
for(int i = 1; i < p1.length; ++i) {
if(reason.length() > 0) {
reason += " ";
}
reason += p1[i];
}
}
String wasTheKick = null;
Collection<ProxiedPlayer> playerz = BungeeCord.getInstance().getPlayers();
for(ProxiedPlayer pp : playerz) {
if(pp.getName().equalsIgnoreCase(p)) {
wasTheKick = pp.getName();
pp.disconnect("" + ChatColor.RED + "You are banned.\n" + ChatColor.DARK_GRAY + "Reason: " + reason);
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.WHITE + "Kicked: " + pp.getName());
}
}
if(BanList.ban(p, reason)) {
if(wasTheKick == null) {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.YELLOW + "Warning! '" + ChatColor.WHITE + p + ChatColor.YELLOW + "' is not currently on this server");
}
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Username '" + ChatColor.WHITE + (wasTheKick == null ? p : wasTheKick) + ChatColor.GREEN + "' was added to the ban list");
}else {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Username '" + ChatColor.WHITE + p + ChatColor.RED + "' is already banned");
}
}else {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "To ban a player, use: " + ChatColor.WHITE + "/" + (replaceBukkit?"":"eag-") + "ban <player> [reason]");
}
}
}

View File

@ -0,0 +1,147 @@
package net.md_5.bungee.command;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Command;
import net.md_5.bungee.eaglercraft.BanList;
import net.md_5.bungee.eaglercraft.BanList.IPBan;
public class CommandGlobalBanIP extends Command {
private final boolean replaceBukkit;
public CommandGlobalBanIP(boolean replaceBukkit) {
super(replaceBukkit ? "ban-ip" : "eag-ban-ip", "bungeecord.command.eag.banip", (replaceBukkit ? new String[] {"eag-ban-ip", "banip", "e-ban-ip", "gban-ip"} :
new String[] {"gban-ip", "e-ban-ip", "gbanip", "e-banip"}) );
this.replaceBukkit = replaceBukkit;
}
@Override
public void execute(CommandSender p0, String[] p1) {
String w = (String) p0.getAttachment().get("banIPWaitingToAdd");
if(w != null) {
List<ProxiedPlayer> lst = (List<ProxiedPlayer>)p0.getAttachment().get("banIPWaitingToKick");
if(p1.length != 1 || (!p1[0].equalsIgnoreCase("confirm") && !p1[0].equalsIgnoreCase("cancel"))) {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Type " + ChatColor.WHITE + (replaceBukkit ? "/ban-ip" : "/eag-ban-ip") + " confirm" + ChatColor.RED + " to add IP " + ChatColor.WHITE + w +
ChatColor.RED + " and ban " + ChatColor.WHITE + lst.size() + ChatColor.RED + " players");
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Type " + ChatColor.WHITE + (replaceBukkit ? "/ban-ip" : "/eag-ban-ip") + " cancel" + ChatColor.RED + " to cancel this operation");
}else {
if(p1[0].equalsIgnoreCase("confirm")) {
try {
if(BanList.banIP(w)) {
for(ProxiedPlayer pp : lst) {
pp.disconnect("" + ChatColor.RED + "You are banned.\n" + ChatColor.DARK_GRAY + "Reason: banned by IP");
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Kicked: " + ChatColor.WHITE + pp.getName());
}
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Added IP '" + ChatColor.WHITE + w + ChatColor.GREEN + "' to the ban list");
}else {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "IP '" + ChatColor.WHITE + w + ChatColor.RED + "' is already on the ban list");
}
} catch (UnknownHostException e) {
e.printStackTrace();
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "ERROR: address '" + ChatColor.WHITE + w + ChatColor.RED + "' is suddenly invalid for some reason");
}
}else {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Canceled ban");
}
p0.getAttachment().remove("banIPWaitingToAdd");
p0.getAttachment().remove("banIPWaitingToKick");
}
return;
}
if(p1.length != 1) {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "How to use: " + ChatColor.WHITE + (replaceBukkit ? "/ban-ip" : "/eag-ban-ip") + " <addr|player>");
return;
}
boolean isPlayer = false;
IPBan p = null;
try {
p = BanList.constructIpBan(p1[0]);
}catch(Throwable t) {
for(ProxiedPlayer pp : BungeeCord.getInstance().getPlayers()) {
if(pp.getName().equalsIgnoreCase(p1[0])) {
Object addr = pp.getAttachment().get("remoteAddr");
if(addr != null) {
String newAddr = ((InetAddress)addr).getHostAddress();
isPlayer = true;
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Player '" + ChatColor.WHITE + p1[0] + ChatColor.GREEN + "' has IP " + ChatColor.WHITE + newAddr);
p1[0] = newAddr;
try {
p = BanList.constructIpBan(p1[0]);
}catch(UnknownHostException ex) {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Address '" + ChatColor.WHITE + p1[0] + "' is suddenly invalid: " + ChatColor.WHITE + p1[0]);
return;
}
}
break;
}
}
if(!isPlayer) {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Player '" + ChatColor.WHITE + p1[0] + "' is not on this server");
return;
}
}
boolean blocked = false;
for(IPBan b : BanList.blockedBans) {
if(b.checkBan(p.getBaseAddress()) || p.checkBan(b.getBaseAddress())) {
blocked = true;
}
}
if(blocked) {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Cannot ban '" + ChatColor.WHITE + p1[0] + ChatColor.RED + "', it will ban local addresses that may break your game");
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "To force, add to the " + ChatColor.WHITE + "[IPs]" + ChatColor.RED + " section of " + ChatColor.WHITE + "bans.txt" + ChatColor.RED + " in your bungee directory");
return;
}
boolean isSenderGonnaGetKicked = false;
List<ProxiedPlayer> usersThatAreGonnaBeKicked = new ArrayList();
for(ProxiedPlayer pp : BungeeCord.getInstance().getPlayers()) {
Object addr = pp.getAttachment().get("remoteAddr");
if(addr != null) {
InetAddress addrr = (InetAddress)addr;
if(p.checkBan(addrr)) {
usersThatAreGonnaBeKicked.add(pp);
if(pp.getName().equalsIgnoreCase(p0.getName())) {
isSenderGonnaGetKicked = true;
break;
}
}
}
}
if(isSenderGonnaGetKicked) {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "banning address '" + ChatColor.WHITE + p1[0] + ChatColor.RED + "' will ban you off of your own server");
return;
}
if(usersThatAreGonnaBeKicked.size() > 1) {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "WARNING: banning address '" + ChatColor.WHITE + p1[0] + ChatColor.RED + "' is gonna ban " +
ChatColor.WHITE + usersThatAreGonnaBeKicked.size() + ChatColor.RED + " players off of your server");
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Type " + ChatColor.WHITE + (replaceBukkit ? "/ban-ip" : "/eag-ban-ip") + " confirm" + ChatColor.RED + " to continue, or type " +
ChatColor.WHITE + (replaceBukkit ? "/ban-ip" : "/eag-ban-ip") + " cancel" + ChatColor.RED + " to cancel");
p0.getAttachment().put("banIPWaitingToKick", usersThatAreGonnaBeKicked);
p0.getAttachment().put("banIPWaitingToAdd", p1[0]);
}else {
try {
if(BanList.banIP(p1[0])) {
if(usersThatAreGonnaBeKicked.size() > 0) {
usersThatAreGonnaBeKicked.get(0).disconnect("" + ChatColor.RED + "You are banned.\n" + ChatColor.DARK_GRAY + "Reason: banned by IP");
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Kicked: " + ChatColor.WHITE + usersThatAreGonnaBeKicked.get(0).getName());
}
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Added IP '" + ChatColor.WHITE + p1[0] + ChatColor.GREEN + "' to the ban list");
}else {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "IP '" + ChatColor.WHITE + p1[0] + ChatColor.RED + "' is already on the ban list");
}
} catch (UnknownHostException e) {
e.printStackTrace();
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "ERROR: address '" + ChatColor.WHITE + p1[0] + ChatColor.RED + "' is suddenly invalid for some reason");
return;
}
}
}
}

View File

@ -0,0 +1,106 @@
package net.md_5.bungee.command;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Command;
import net.md_5.bungee.eaglercraft.BanList;
public class CommandGlobalBanRegex extends Command {
private final boolean replaceBukkit;
public CommandGlobalBanRegex(boolean replaceBukkit) {
super(replaceBukkit ? "ban-regex" : "eag-ban-regex", "bungeecord.command.eag.banregex", replaceBukkit ? new String[] { "eag-ban-regex", "e-ban-regex",
"gban-regex", "eag-banregex", "e-banregex", "gbanregex", "banregex" } : new String[] { "e-ban-regex", "gban-regex",
"eag-banregex", "e-banregex", "gbanregex" });
this.replaceBukkit = replaceBukkit;
}
@Override
public void execute(CommandSender p0, String[] p1) {
String w = (String) p0.getAttachment().get("banRegexWaitingToAdd");
if(w != null) {
List<ProxiedPlayer> lst = (List<ProxiedPlayer>)p0.getAttachment().get("banRegexWaitingToKick");
if(p1.length != 1 || (!p1[0].equalsIgnoreCase("confirm") && !p1[0].equalsIgnoreCase("cancel"))) {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Type " + ChatColor.WHITE + (replaceBukkit ? "/ban-regex" : "/eag-ban-regex") + " confirm" + ChatColor.RED + " to add regex " + ChatColor.WHITE + w +
ChatColor.RED + " and ban " + ChatColor.WHITE + lst.size() + ChatColor.RED + " players");
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Type " + ChatColor.WHITE + (replaceBukkit ? "/ban-regex" : "/eag-ban-regex") + " cancel" + ChatColor.RED + " to cancel this operation");
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.YELLOW + "Note: all usernames are converted to lowercase before being matched");
}else {
if(p1[0].equalsIgnoreCase("confirm")) {
if(BanList.banRegex(w)) {
for(ProxiedPlayer pp : lst) {
pp.disconnect("" + ChatColor.RED + "You are banned.\n" + ChatColor.DARK_GRAY + "Reason: banned by regex");
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Kicked: " + ChatColor.WHITE + pp.getName());
}
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Added regex '" + ChatColor.WHITE + w + ChatColor.GREEN + "' to the ban list");
}else {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Regex '" + ChatColor.WHITE + w + ChatColor.RED + "' is already banned");
}
}else {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Canceled ban");
}
p0.getAttachment().remove("banRegexWaitingToAdd");
p0.getAttachment().remove("banRegexWaitingToKick");
}
return;
}
if(p1.length != 1) {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "How to use: " + ChatColor.WHITE + (replaceBukkit ? "/ban-regex" : "/eag-ban-regex") + " <pattern>");
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.YELLOW + "Note: all usernames are converted to lowercase before being matched");
return;
}
Pattern p;
try {
p = Pattern.compile(p1[0]);
}catch(Throwable t) {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Regex syntax error: " + t.getMessage());
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.YELLOW + "Note: all usernames are converted to lowercase before being matched");
return;
}
boolean isSenderGonnaGetKicked = false;
List<ProxiedPlayer> usersThatAreGonnaBeKicked = new ArrayList();
for(ProxiedPlayer pp : BungeeCord.getInstance().getPlayers()) {
String n = pp.getName().toLowerCase();
if(p.matcher(n).matches()) {
usersThatAreGonnaBeKicked.add(pp);
if(n.equalsIgnoreCase(p0.getName())) {
isSenderGonnaGetKicked = true;
break;
}
}
}
if(isSenderGonnaGetKicked) {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "banning regex '" + ChatColor.WHITE + p1[0] + ChatColor.RED + "' is gonna ban your own username");
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.YELLOW + "Note: all usernames are converted to lowercase before being matched");
return;
}
if(usersThatAreGonnaBeKicked.size() > 1) {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "WARNING: banning regex '" + ChatColor.WHITE + p1[0] + ChatColor.RED + "' is gonna ban " +
ChatColor.WHITE + usersThatAreGonnaBeKicked.size() + ChatColor.RED + " players off of your server");
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Type " + ChatColor.WHITE + (replaceBukkit ? "/ban-regex" : "/eag-ban-regex") + " confirm" + ChatColor.RED + " to continue, or type " +
ChatColor.WHITE + "/eag-ban-regex cancel" + ChatColor.RED + " to cancel");
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.YELLOW + "Note: all usernames are converted to lowercase before being matched");
p0.getAttachment().put("banRegexWaitingToKick", usersThatAreGonnaBeKicked);
p0.getAttachment().put("banRegexWaitingToAdd", p1[0]);
}else {
if(BanList.banRegex(p1[0])) {
if(usersThatAreGonnaBeKicked.size() > 0) {
usersThatAreGonnaBeKicked.get(0).disconnect("" + ChatColor.RED + "You are banned.\n" + ChatColor.DARK_GRAY + "Reason: banned by regex");
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Kicked: " + ChatColor.WHITE + usersThatAreGonnaBeKicked.get(0).getName());
}
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Added regex '" + ChatColor.WHITE + p1[0] + ChatColor.GREEN + "' to the ban list");
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.YELLOW + "Note: all usernames are converted to lowercase before being matched");
}else {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Regex '" + ChatColor.WHITE + p1[0] + ChatColor.RED + "' is already banned");
}
}
}
}

View File

@ -0,0 +1,21 @@
package net.md_5.bungee.command;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.plugin.Command;
import net.md_5.bungee.eaglercraft.BanList;
public class CommandGlobalBanReload extends Command {
public CommandGlobalBanReload(boolean replaceBukkit) {
super(replaceBukkit ? "reloadban" : "eag-reloadban", "bungeecord.command.eag.reloadban", replaceBukkit ? new String[] { "eag-reloadban", "banreload", "eag-banreload", "e-reloadban",
"e-banreload", "gbanreload", "greloadban"} : new String[] { "eag-banreload", "e-reloadban", "e-banreload", "gbanreload", "greloadban"});
}
@Override
public void execute(CommandSender p0, String[] p1) {
BanList.maybeReloadBans(p0);
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.WHITE + "Ban list reloaded");
}
}

View File

@ -0,0 +1,125 @@
package net.md_5.bungee.command;
import java.util.ArrayList;
import java.util.List;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Command;
import net.md_5.bungee.eaglercraft.BanList;
public class CommandGlobalBanWildcard extends Command {
private final boolean replaceBukkit;
public CommandGlobalBanWildcard(boolean replaceBukkit) {
super(replaceBukkit ? "ban-wildcard" : "eag-ban-wildcard", "bungeecord.command.eag.banwildcard", replaceBukkit ? new String[] { "eag-ban-wildcard", "e-ban-wildcard", "gban-wildcard",
"banwildcard", "eag-banwildcard", "banwildcard"} : new String[] { "e-ban-wildcard", "gban-wildcard", "eag-banwildcard"});
this.replaceBukkit = replaceBukkit;
}
@Override
public void execute(CommandSender p0, String[] p1) {
String w = (String) p0.getAttachment().get("banWildcardWaitingToAdd");
if(w != null) {
List<ProxiedPlayer> lst = (List<ProxiedPlayer>)p0.getAttachment().get("banWildcardWaitingToKick");
if(p1.length != 1 || (!p1[0].equalsIgnoreCase("confirm") && !p1[0].equalsIgnoreCase("cancel"))) {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Type " + ChatColor.WHITE + (replaceBukkit ? "/ban-wildcard" : "/eag-ban-wildcard") + " confirm" + ChatColor.RED + " to add wildcard " + ChatColor.WHITE + w +
ChatColor.RED + " and ban " + ChatColor.WHITE + lst.size() + ChatColor.RED + " players");
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Type " + ChatColor.WHITE + (replaceBukkit ? "/ban-wildcard" : "/eag-ban-wildcard") + " cancel" + ChatColor.RED + " to cancel this operation");
}else {
if(p1[0].equalsIgnoreCase("confirm")) {
if(BanList.banWildcard(w)) {
for(ProxiedPlayer pp : lst) {
pp.disconnect("" + ChatColor.RED + "You are banned.\n" + ChatColor.DARK_GRAY + "Reason: banned by wildcard");
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Kicked: " + ChatColor.WHITE + pp.getName());
}
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Added wildcard '" + ChatColor.WHITE + w + ChatColor.GREEN + "' to the ban list");
}else {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Wildcard '" + ChatColor.WHITE + w + ChatColor.RED + "' is already banned");
}
}else {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Canceled ban");
}
p0.getAttachment().remove("banWildcardWaitingToAdd");
p0.getAttachment().remove("banWildcardWaitingToKick");
}
return;
}
if(p1.length != 1) {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "How to use: " + ChatColor.WHITE + (replaceBukkit ? "/ban-wildcard" : "/eag-ban-wildcard") + " <pattern>");
return;
}
p1[0] = p1[0].toLowerCase();
String s = p1[0];
boolean startStar = s.startsWith("*");
if(startStar) {
s = s.substring(1);
}
boolean endStar = s.endsWith("*");
if(endStar) {
s = s.substring(0, s.length() - 1);
}
if(!startStar && !endStar) {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "'" + ChatColor.WHITE + p1[0] + ChatColor.RED + "' is not a wildcard, try '"
+ ChatColor.WHITE + "*" + p1[0] + ChatColor.RED + "' or '" + ChatColor.WHITE + p1[0] + "*" + ChatColor.RED + "' or '" + ChatColor.WHITE
+ "*" + p1[0] + "*" + ChatColor.RED + "' instead");
return;
}
boolean isSenderGonnaGetKicked = false;
List<ProxiedPlayer> usersThatAreGonnaBeKicked = new ArrayList();
for(ProxiedPlayer pp : BungeeCord.getInstance().getPlayers()) {
String n = pp.getName().toLowerCase();
if(startStar && endStar) {
if(n.contains(s)) {
usersThatAreGonnaBeKicked.add(pp);
if(pp.getName().equalsIgnoreCase(p0.getName())) {
isSenderGonnaGetKicked = true;
break;
}
}
}else if(startStar) {
if(n.endsWith(s)) {
usersThatAreGonnaBeKicked.add(pp);
if(pp.getName().equalsIgnoreCase(p0.getName())) {
isSenderGonnaGetKicked = true;
break;
}
}
}else if(endStar) {
if(n.startsWith(s)) {
usersThatAreGonnaBeKicked.add(pp);
if(pp.getName().equalsIgnoreCase(p0.getName())) {
isSenderGonnaGetKicked = true;
break;
}
}
}
}
if(isSenderGonnaGetKicked) {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "banning wildcard '" + ChatColor.WHITE + p1[0] + ChatColor.RED + "' is gonna ban your own username");
return;
}
if(usersThatAreGonnaBeKicked.size() > 1) {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "WARNING: banning wildcard '" + ChatColor.WHITE + p1[0] + ChatColor.RED + "' is gonna ban " +
ChatColor.WHITE + usersThatAreGonnaBeKicked.size() + ChatColor.RED + " players off of your server");
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Type " + ChatColor.WHITE + (replaceBukkit ? "/ban-wildcard" : "/eag-ban-wildcard") + " confirm" + ChatColor.RED + " to continue, or type " +
ChatColor.WHITE + (replaceBukkit ? "/ban-wildcard" : "/eag-ban-wildcard") + " cancel" + ChatColor.RED + " to cancel");
p0.getAttachment().put("banWildcardWaitingToKick", usersThatAreGonnaBeKicked);
p0.getAttachment().put("banWildcardWaitingToAdd", p1[0]);
}else {
if(BanList.banWildcard(p1[0])) {
if(usersThatAreGonnaBeKicked.size() > 0) {
usersThatAreGonnaBeKicked.get(0).disconnect("" + ChatColor.RED + "You are banned.\n" + ChatColor.DARK_GRAY + "Reason: banned by wildcard");
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Kicked: " + ChatColor.WHITE + usersThatAreGonnaBeKicked.get(0).getName());
}
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Added wildcard '" + ChatColor.WHITE + p1[0] + ChatColor.GREEN + "' to the ban list");
}else {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Wildcard '" + ChatColor.WHITE + p1[0] + ChatColor.RED + "' is already banned");
}
}
}
}

View File

@ -0,0 +1,56 @@
package net.md_5.bungee.command;
import java.net.InetAddress;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.plugin.Command;
import net.md_5.bungee.eaglercraft.BanList;
import net.md_5.bungee.eaglercraft.BanList.BanCheck;
import net.md_5.bungee.eaglercraft.BanList.BanState;
public class CommandGlobalCheckBan extends Command {
private final boolean replaceBukkit;
public CommandGlobalCheckBan(boolean replaceBukkit) {
super(replaceBukkit ? "banned" : "eag-bannned", "bungeecord.command.eag.banned", replaceBukkit ? new String[] { "eag-banned", "isbanned", "e-banned", "gbanned", "eag-isbanned", "e-isbanned", "gisbanned" } :
new String[] { "e-banned", "gbanned", "eag-isbanned", "e-isbanned", "gisbanned" });
this.replaceBukkit = replaceBukkit;
}
@Override
public void execute(CommandSender p0, String[] p1) {
if(p1.length != 1) {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "To check if a player or IP is banned, use: " + ChatColor.WHITE + (replaceBukkit ? "/banned" : "/eag-banned") + " <username|ip>");
}else {
BanCheck bc = BanList.checkBanned(p1[0]);
if(!bc.isBanned()) {
try {
InetAddress addr = InetAddress.getByName(p1[0]);
bc = BanList.checkIpBanned(addr);
if(bc.isBanned()) {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "IP address '" + ChatColor.WHITE + p1[0] + ChatColor.RED + "' is banned by: "
+ "'" + ChatColor.WHITE + bc.match + ChatColor.RED + "' " + ChatColor.YELLOW + "(" + bc.string + ")");
return;
}
}catch(Throwable t) {
// no
}
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Player '" + ChatColor.WHITE + p1[0] + ChatColor.RED + "' has not been banned");
}else {
if(bc.reason == BanState.USER_BANNED) {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Player '" + ChatColor.WHITE + p1[0] + ChatColor.RED + "' is banned by username, reason: "
+ ChatColor.YELLOW + "\"" + bc.string + "\"");
}else if(bc.reason == BanState.WILDCARD_BANNED) {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Player '" + ChatColor.WHITE + p1[0] + ChatColor.RED + "' is banned by wildcard: "
+ ChatColor.WHITE + "\"" + bc.match + "\"");
}else if(bc.reason == BanState.REGEX_BANNED) {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Player '" + ChatColor.WHITE + p1[0] + ChatColor.RED + "' is banned by regex: "
+ ChatColor.WHITE + "\"" + bc.match + "\"");
}
}
}
}
}

View File

@ -0,0 +1,64 @@
package net.md_5.bungee.command;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.plugin.Command;
import net.md_5.bungee.eaglercraft.BanList;
public class CommandGlobalListBan extends Command {
private final boolean replaceBukkit;
public CommandGlobalListBan(boolean replaceBukkit) {
super(replaceBukkit ? "banlist" : "eag-banlist", "bungeecord.command.eag.banlist", replaceBukkit ? new String[] { "eag-banlist", "gbanlist", "e-banlist",
"gbanlist" } : new String[] { "gbanlist", "e-banlist" });
this.replaceBukkit = replaceBukkit;
}
@Override
public void execute(CommandSender p0, String[] p1) {
if(p1.length == 0 || (p1.length == 1 && (p1[0].equalsIgnoreCase("user") || p1[0].equalsIgnoreCase("username")
|| p1[0].equalsIgnoreCase("users") || p1[0].equalsIgnoreCase("usernames")))) {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Players banned by username: " + ChatColor.WHITE + BanList.listAllBans());
return;
}else if(p1.length == 1) {
if(p1[0].equalsIgnoreCase("regex") || p1[0].equalsIgnoreCase("regexes")) {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Regex ban list: " + ChatColor.WHITE + BanList.listAllRegexBans());
return;
}else if(p1[0].equalsIgnoreCase("wildcard") || p1[0].equalsIgnoreCase("wildcards")) {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Wildcard ban list: " + ChatColor.WHITE + BanList.listAllWildcardBans());
return;
}else if(p1[0].equalsIgnoreCase("ip") || p1[0].equalsIgnoreCase("ips")) {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "To list IP bans, use: " + ChatColor.WHITE + (replaceBukkit ? "/banlist" : "/eag-banlist") + " ip <addr|netmask> [v4|v6]");
return;
}
}else if(p1.length > 1 && p1.length <= 3 && (p1[0].equalsIgnoreCase("ip") || p1[0].equalsIgnoreCase("ips"))) {
int addrOrNetmask = 0;
if(p1[1].equalsIgnoreCase("addr") || p1[1].equalsIgnoreCase("addrs")) {
addrOrNetmask = 1;
}else if(p1[1].equalsIgnoreCase("netmask") || p1[1].equalsIgnoreCase("netmasks")) {
addrOrNetmask = 2;
}
if(addrOrNetmask > 0) {
boolean yes = false;
if(p1.length == 2 || (p1.length == 3 && (p1[2].equalsIgnoreCase("v4") || p1[2].equals("4")))) {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "IPv4 " + (addrOrNetmask == 2 ? "netmask" : "address") + " ban list: " + ChatColor.WHITE + BanList.listAllIPBans(false, addrOrNetmask == 2));
yes = true;
}
if(p1.length == 2 || (p1.length == 3 && (p1[2].equalsIgnoreCase("v6") || p1[2].equals("6")))) {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "IPv6 " + (addrOrNetmask == 2 ? "netmask" : "address") + " ban list: " + ChatColor.WHITE + BanList.listAllIPBans(true, addrOrNetmask == 2));
yes = true;
}
if(yes) {
return;
}
}
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "To list IP bans, use: " + ChatColor.WHITE + (replaceBukkit ? "/banlist" : "/eag-banlist") + " ip <addr|netmask> [v4|v6]");
return;
}
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "To list all user bans, use: " + ChatColor.WHITE + (replaceBukkit ? "/banlist" : "/eag-banlist"));
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "To list ips, regexes, and wildcards, use: " + ChatColor.WHITE + (replaceBukkit ? "/banlist" : "/eag-banlist") + " <ip|regex|wildcard>");
return;
}
}

View File

@ -0,0 +1,56 @@
package net.md_5.bungee.command;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.plugin.Command;
import net.md_5.bungee.eaglercraft.BanList;
public class CommandGlobalUnban extends Command {
private final boolean replaceBukkit;
public CommandGlobalUnban(boolean replaceBukkit) {
super(replaceBukkit ? "unban" : "eag-unban", "bungeecord.command.eag.unban", replaceBukkit ? new String[] {"eag-unban", "e-unban", "gunban"} :new String[] {"e-unban", "gunban"});
this.replaceBukkit = replaceBukkit;
}
@Override
public void execute(CommandSender p0, String[] p1) {
if(p1.length != 2 || (!p1[0].equalsIgnoreCase("user") && !p1[0].equalsIgnoreCase("username") && !p1[0].equalsIgnoreCase("player")
&& !p1[0].equalsIgnoreCase("wildcard") && !p1[0].equalsIgnoreCase("regex") && !p1[0].equalsIgnoreCase("ip"))) {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "To unban a player, use: " + ChatColor.WHITE + "/" + (replaceBukkit?"":"eag-") + "unban user <player>");
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "To unban an ip/wildcard/regex, use: " + ChatColor.WHITE + "/" + (replaceBukkit?"":"eag-") + "unban <ip|wildcard|regex> <value>");
return;
}
if(p1[0].equalsIgnoreCase("user") || p1[0].equalsIgnoreCase("username") || p1[0].equalsIgnoreCase("player")) {
if(BanList.unban(p1[1])) {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "User '" + ChatColor.WHITE + p1[1] + ChatColor.GREEN + "' was unbanned");
}else {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "User '" + ChatColor.WHITE + p1[1] + ChatColor.RED + "' is not banned");
}
}else if(p1[0].equalsIgnoreCase("ip")) {
try {
if(BanList.unbanIP(p1[1])) {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "IP '" + ChatColor.WHITE + p1[1] + ChatColor.GREEN + "' was unbanned");
}else {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "IP '" + ChatColor.WHITE + p1[1] + ChatColor.RED + "' is not banned");
}
}catch(Throwable t) {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "IP address '" + ChatColor.WHITE + p1[1] + ChatColor.RED + "' is invalid: " + t.getMessage());
}
}else if(p1[0].equalsIgnoreCase("wildcard")) {
if(BanList.unbanWildcard(p1[1])) {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Wildcard '" + ChatColor.WHITE + p1[1] + ChatColor.GREEN + "' was unbanned");
}else {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Wildcard '" + ChatColor.WHITE + p1[1] + ChatColor.RED + "' is not banned");
}
}else if(p1[0].equalsIgnoreCase("regex")) {
if(BanList.unbanRegex(p1[1])) {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.GREEN + "Regex '" + ChatColor.WHITE + p1[1] + ChatColor.GREEN + "' was unbanned");
}else {
p0.sendMessage(BanList.banChatMessagePrefix + ChatColor.RED + "Regex '" + ChatColor.WHITE + p1[1] + ChatColor.RED + "' is not banned");
}
}
}
}

View File

@ -4,10 +4,12 @@
package net.md_5.bungee.command;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.ProxyServer;
import java.net.InetAddress;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Command;
public class CommandIP extends Command {
@ -25,7 +27,12 @@ public class CommandIP extends Command {
if (user == null) {
sender.sendMessage(ChatColor.RED + "That user is not online");
} else {
sender.sendMessage(ChatColor.BLUE + "IP of " + args[0] + " is " + user.getAddress());
Object o = user.getAttachment().get("remoteAddr");
if(o != null) {
sender.sendMessage(ChatColor.BLUE + "IP of " + args[0] + " is " + (InetAddress)o);
}else {
sender.sendMessage(ChatColor.BLUE + "IP of " + args[0] + " is " + user.getAddress());
}
}
}
}

View File

@ -4,18 +4,17 @@
package net.md_5.bungee.command;
import java.util.List;
import java.util.Iterator;
import java.util.Collection;
import net.md_5.bungee.Util;
import java.util.Comparator;
import java.util.Collections;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import net.md_5.bungee.Util;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Command;
public class CommandList extends Command {

View File

@ -4,13 +4,12 @@
package net.md_5.bungee.command;
import java.util.Iterator;
import java.util.Set;
import net.md_5.bungee.api.ChatColor;
import java.util.Collection;
import net.md_5.bungee.api.ProxyServer;
import java.util.HashSet;
import java.util.Set;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.plugin.Command;
public class CommandPerms extends Command {

View File

@ -4,8 +4,8 @@
package net.md_5.bungee.command;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.plugin.Command;

View File

@ -4,12 +4,11 @@
package net.md_5.bungee.command;
import java.util.Iterator;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Command;
public class CommandSend extends Command {

View File

@ -4,12 +4,12 @@
package net.md_5.bungee.command;
import java.util.Iterator;
import java.util.Map;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Command;
public class CommandServer extends Command {

View File

@ -4,14 +4,17 @@
package net.md_5.bungee.command;
import java.util.Collections;
import java.util.HashSet;
import java.util.Collection;
import net.md_5.bungee.api.ProxyServer;
import java.util.HashSet;
import java.util.Map;
import java.util.WeakHashMap;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer;
public class ConsoleCommandSender implements CommandSender {
private static final ConsoleCommandSender instance;
private static final Map<String, Object> attachment = new WeakHashMap();
private ConsoleCommandSender() {
}
@ -65,4 +68,9 @@ public class ConsoleCommandSender implements CommandSender {
static {
instance = new ConsoleCommandSender();
}
@Override
public Map<String, Object> getAttachment() {
return attachment;
}
}

View File

@ -4,17 +4,20 @@
package net.md_5.bungee.config;
import java.util.Collection;
import java.util.Map;
import java.util.UUID;
import com.google.common.base.Preconditions;
import gnu.trove.map.TMap;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.config.AuthServiceInfo;
import net.md_5.bungee.api.config.ConfigurationAdapter;
import java.util.Map;
import net.md_5.bungee.util.CaseInsensitiveMap;
import com.google.common.base.Preconditions;
import net.md_5.bungee.api.ProxyServer;
import java.util.UUID;
import net.md_5.bungee.api.config.ServerInfo;
import gnu.trove.map.TMap;
import net.md_5.bungee.api.config.ListenerInfo;
import java.util.Collection;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.eaglercraft.EaglercraftBungee;
import net.md_5.bungee.util.CaseInsensitiveMap;
public class Configuration {
private int timeout;
@ -23,7 +26,18 @@ public class Configuration {
private TMap<String, ServerInfo> servers;
private AuthServiceInfo authInfo;
private boolean onlineMode;
private boolean voiceEnabled;
private boolean protocolSupport;
private int playerLimit;
private String name;
private boolean showBanType;
private boolean blacklistOfflineDownload;
private boolean blacklistReplits;
private boolean blacklistOriginless;
private boolean simpleWhitelistEnabled;
private boolean acceptBukkitConsoleCommandPacket;
private Collection<String> disabledCommands;
private Collection<String> iceServers;
public Configuration() {
this.timeout = 30000;
@ -38,9 +52,27 @@ public class Configuration {
this.listeners = adapter.getListeners();
this.timeout = adapter.getInt("timeout", this.timeout);
this.uuid = adapter.getString("stats", this.uuid);
if(this.uuid.equalsIgnoreCase("595698b3-9c36-4e86-b1ee-cb3027038f41")) {
this.uuid = UUID.randomUUID().toString();
System.err.println("Notice: this server has the stats UUID \"595698b3-9c36-4e86-b1ee-cb3027038f41\" which is a known duplicate");
System.err.println("It has been updated to \"" + this.uuid + "\". This is not an error");
adapter.getMap().put("stats", this.uuid);
adapter.forceSave();
}
this.authInfo = adapter.getAuthSettings();
this.onlineMode = false;
this.voiceEnabled = adapter.getBoolean("voice_enabled", true);
this.protocolSupport = adapter.getBoolean("protocol_support_fix", false);
this.playerLimit = adapter.getInt("player_limit", this.playerLimit);
this.name = adapter.getString("server_name", EaglercraftBungee.name + " Server");
this.showBanType = adapter.getBoolean("display_ban_type_on_kick", false);
this.blacklistOfflineDownload = adapter.getBoolean("origin_blacklist_block_offline_download", false);
this.blacklistReplits = adapter.getBoolean("origin_blacklist_block_replit_clients", false);
this.blacklistOriginless = adapter.getBoolean("origin_blacklist_block_missing_origin_header", false);
this.simpleWhitelistEnabled = adapter.getBoolean("origin_blacklist_use_simple_whitelist", false);
this.acceptBukkitConsoleCommandPacket = adapter.getBoolean("accept_bukkit_console_command_packets", false);
this.disabledCommands = adapter.getDisabledCommands();
this.iceServers = adapter.getICEServers();
Preconditions.checkArgument(this.listeners != null && !this.listeners.isEmpty(), (Object) "No listeners defined.");
final Map<String, ServerInfo> newServers = adapter.getServers();
Preconditions.checkArgument(newServers != null && !newServers.isEmpty(), (Object) "No servers defined");
@ -88,4 +120,49 @@ public class Configuration {
public AuthServiceInfo getAuthInfo() {
return authInfo;
}
public boolean getVoiceEnabled() {
return voiceEnabled;
}
public boolean getProtocolSupport() {
return protocolSupport;
}
public String getServerName() {
return name;
}
public boolean shouldShowBanType() {
return this.showBanType;
}
public boolean shouldBlacklistOfflineDownload() {
return blacklistOfflineDownload;
}
public boolean shouldBlacklistReplits() {
return blacklistReplits;
}
public boolean shouldBlacklistOriginless() {
return blacklistOriginless;
}
public boolean isSimpleWhitelistEnabled() {
return simpleWhitelistEnabled;
}
public boolean shouldAcceptBukkitConsoleCommandPacket() {
return acceptBukkitConsoleCommandPacket;
}
public Collection<String> getDisabledCommands() {
return disabledCommands;
}
public Collection<String> getICEServers() {
return iceServers;
}
}

View File

@ -4,38 +4,44 @@
package net.md_5.bungee.config;
import net.md_5.bungee.tab.ServerUnique;
import net.md_5.bungee.tab.GlobalPing;
import net.md_5.bungee.tab.Global;
import net.md_5.bungee.api.tab.TabListHandler;
import net.md_5.bungee.api.config.TexturePackInfo;
import net.md_5.bungee.api.ChatColor;
import java.util.HashSet;
import net.md_5.bungee.api.config.ListenerInfo;
import java.util.Collection;
import java.net.InetSocketAddress;
import java.util.Iterator;
import net.md_5.bungee.Util;
import net.md_5.bungee.api.config.ServerInfo;
import java.util.logging.Level;
import net.md_5.bungee.api.ProxyServer;
import java.io.Writer;
import java.io.FileWriter;
import java.util.LinkedHashMap;
import java.io.InputStream;
import java.util.Collections;
import java.util.Arrays;
import java.util.HashMap;
import java.io.IOException;
import net.md_5.bungee.util.CaseInsensitiveMap;
import java.io.FileInputStream;
import org.yaml.snakeyaml.DumperOptions;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.Util;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.config.AuthServiceInfo;
import net.md_5.bungee.api.config.ConfigurationAdapter;
import net.md_5.bungee.api.config.ListenerInfo;
import net.md_5.bungee.api.config.MOTDCacheConfiguration;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.config.TexturePackInfo;
import net.md_5.bungee.api.tab.TabListHandler;
import net.md_5.bungee.eaglercraft.WebSocketRateLimiter;
import net.md_5.bungee.tab.Global;
import net.md_5.bungee.tab.GlobalPing;
import net.md_5.bungee.tab.ServerUnique;
import net.md_5.bungee.util.CaseInsensitiveMap;
public class YamlConfig implements ConfigurationAdapter {
private Yaml yaml;
@ -64,21 +70,17 @@ public class YamlConfig implements ConfigurationAdapter {
} catch (IOException ex) {
throw new RuntimeException("Could not load configuration!", ex);
}
final Map<String, Object> permissions = this.get("permissions", new HashMap<String, Object>());
final Map<String, List<String>> permissions = this.get("permissions", new HashMap<String, List<String>>());
if (permissions.isEmpty()) {
permissions.put("default", Arrays.asList("bungeecord.command.server", "bungeecord.command.list"));
permissions.put("admin", Arrays.asList("bungeecord.command.alert", "bungeecord.command.end", "bungeecord.command.ip", "bungeecord.command.reload"));
permissions.put("default", Arrays.asList("bungeecord.command.server", "bungeecord.command.list", "bungeecord.command.eag.domain", "bungeecord.command.eag.changepassword"));
permissions.put("admin", Arrays.asList("bungeecord.command.alert", "bungeecord.command.end", "bungeecord.command.ip", "bungeecord.command.reload",
"bungeecord.command.eag.ban", "bungeecord.command.eag.banwildcard", "bungeecord.command.eag.banip", "bungeecord.command.eag.banregex",
"bungeecord.command.eag.reloadban", "bungeecord.command.eag.banned", "bungeecord.command.eag.banlist", "bungeecord.command.eag.unban", "bungeecord.command.eag.ratelimit",
"bungeecord.command.eag.blockdomain", "bungeecord.command.eag.blockdomainname", "bungeecord.command.eag.unblockdomain"));
} else if (this.get("authservice", new HashMap<String, Object>()).isEmpty() && permissions.containsKey("default") && !permissions.get("default").contains("bungeecord.command.eag.changepassword")) {
permissions.get("default").add("bungeecord.command.eag.changepassword");
}
this.get("groups", new HashMap<String, Object>());
/*
final Map<String, Object> auth = this.get("authservice", new HashMap<String, Object>());
if(auth.isEmpty()) {
auth.put("enabled", false);
auth.put("limbo", "lobby");
auth.put("authfile", "passwords.yml");
auth.put("timeout", 30);
}
*/
}
private <T> T get(final String path, final T def) {
@ -152,13 +154,25 @@ public class YamlConfig implements ConfigurationAdapter {
//forcedDef.put("pvp.md-5.net", "pvp");
final Collection<ListenerInfo> ret = new HashSet<ListenerInfo>();
for (final Map<String, Object> val : base) {
String motd = this.get("motd", "&6&lbungeecord eaglercraft server |>", val);
String motd = this.get("motd", null, val);
if(motd != null) {
val.remove("motd");
}
motd = this.get("motd1", motd, val);
if(motd == null) {
motd = this.get("motd1", "&6An Eaglercraft server", val);
}
motd = ChatColor.translateAlternateColorCodes('&', motd);
String motd2 = this.get("motd2", null, val);
if(motd2 != null && motd2.length() > 0) {
motd = motd + "\n" + ChatColor.translateAlternateColorCodes('&', motd2);
}
final int maxPlayers = this.get("max_players", 60, val);
final String defaultServer = this.get("default_server", "lobby", val);
final String fallbackServer = this.get("fallback_server", defaultServer, val);
final boolean forceDefault = this.get("force_default_server", true, val);
final boolean websocket = this.get("websocket", true, val);
final boolean forwardIp = this.get("forward_ip", false, val);
final String host = this.get("host", "0.0.0.0:25565", val);
final int tabListSize = this.get("tab_size", 60, val);
final InetSocketAddress address = Util.getAddr(host);
@ -167,15 +181,77 @@ public class YamlConfig implements ConfigurationAdapter {
final int textureSize = this.get("texture_size", 16, val);
final TexturePackInfo texture = (textureURL == null) ? null : new TexturePackInfo(textureURL, textureSize);
final String tabListName = this.get("tab_list", "GLOBAL_PING", val);
final String serverIcon = this.get("server_icon", "server-icon.png", val);
final boolean allowMOTD = this.get("allow_motd", true, val);
final boolean allowQuery = this.get("allow_query", true, val);
final MOTDCacheConfiguration cacheConfig = readCacheConfiguration(this.get("request_motd_cache", new HashMap<String, Object>(), val));
WebSocketRateLimiter ratelimitIP = null;
WebSocketRateLimiter ratelimitLogin = null;
WebSocketRateLimiter ratelimitMOTD = null;
WebSocketRateLimiter ratelimitQuery = null;
final Map<String, Object> rateLimits = this.get("ratelimit", new HashMap<String, Object>(), val);
final Map<String, Object> ratelimitIPConfig = this.get("ip", new HashMap<String, Object>(), rateLimits);
final Map<String, Object> ratelimitLoginConfig = this.get("login", new HashMap<String, Object>(), rateLimits);
final Map<String, Object> ratelimitMOTDConfig = this.get("motd", new HashMap<String, Object>(), rateLimits);
final Map<String, Object> ratelimitQueryConfig = this.get("query", new HashMap<String, Object>(), rateLimits);
if(this.get("enable", true, ratelimitIPConfig)) {
ratelimitIP = new WebSocketRateLimiter(
this.get("period", 90, ratelimitIPConfig),
this.get("limit", 60, ratelimitIPConfig),
this.get("limit_lockout", 80, ratelimitIPConfig),
this.get("lockout_duration", 1200, ratelimitIPConfig),
this.get("exceptions", new ArrayList<String>(), ratelimitIPConfig)
);
}
if(this.get("enable", true, ratelimitLoginConfig)) {
ratelimitLogin = new WebSocketRateLimiter(
this.get("period", 50, ratelimitLoginConfig),
this.get("limit", 5, ratelimitLoginConfig),
this.get("limit_lockout", 10, ratelimitLoginConfig),
this.get("lockout_duration", 300, ratelimitLoginConfig),
this.get("exceptions", new ArrayList<String>(), ratelimitLoginConfig)
);
}
if(this.get("enable", true, ratelimitMOTDConfig)) {
ratelimitMOTD = new WebSocketRateLimiter(
this.get("period", 30, ratelimitMOTDConfig),
this.get("limit", 5, ratelimitMOTDConfig),
this.get("limit_lockout", 15, ratelimitMOTDConfig),
this.get("lockout_duration", 1200, ratelimitMOTDConfig),
this.get("exceptions", new ArrayList<String>(), ratelimitMOTDConfig)
);
}
if(this.get("enable", true, ratelimitQueryConfig)) {
ratelimitQuery = new WebSocketRateLimiter(
this.get("period", 90, ratelimitQueryConfig),
this.get("limit", 60, ratelimitQueryConfig),
this.get("limit_lockout", 80, ratelimitQueryConfig),
this.get("lockout_duration", 1200, ratelimitQueryConfig),
this.get("exceptions", new ArrayList<String>(), ratelimitQueryConfig)
);
}
DefaultTabList value = DefaultTabList.valueOf(tabListName.toUpperCase());
if (value == null) {
value = DefaultTabList.GLOBAL_PING;
}
final ListenerInfo info = new ListenerInfo(address, motd, maxPlayers, tabListSize, defaultServer, fallbackServer, forceDefault, websocket, forced, texture, value.clazz);
ret.add(info);
ret.add(new ListenerInfo(host, address, motd, maxPlayers, tabListSize, defaultServer, fallbackServer, forceDefault, websocket, forwardIp,
forced, texture, value.clazz, serverIcon, cacheConfig, allowMOTD, allowQuery, ratelimitIP, ratelimitLogin, ratelimitMOTD, ratelimitQuery));
}
return ret;
}
private MOTDCacheConfiguration readCacheConfiguration(Map<String, Object> val) {
final int ttl = this.get("cache_ttl", 7200, val);
final boolean anim = this.get("online_server_list_animation", false, val);
final boolean results = this.get("online_server_list_results", true, val);
final boolean trending = this.get("online_server_list_trending", true, val);
final boolean portfolios = this.get("online_server_list_portfolios", false, val);
return new MOTDCacheConfiguration(ttl, anim, results, trending, portfolios);
}
@Override
public Collection<String> getGroups(final String player) {
@ -202,8 +278,108 @@ public class YamlConfig implements ConfigurationAdapter {
@Override
public AuthServiceInfo getAuthSettings() {
//final Map<String, Object> auth = this.get("authservice", new HashMap<String, Object>());
//return new AuthServiceInfo(this.get("enabled", true, auth), this.get("limbo", "lobby", auth), new File(this.get("authfile", "passwords.yml", auth)), this.get("timeout", 30, auth));
return null;
final Map<String, Object> auth = this.get("authservice", new HashMap<String, Object>());
final List<String> defaultJoinMessages = new ArrayList<String>();
defaultJoinMessages.add("&3Welcome to my &aEaglercraftBungee &3server!");
return new AuthServiceInfo(this.get("enabled", false, auth), this.get("register_enabled", true, auth), this.get("authfile", "auths.db", auth),
this.get("ip_limit", 0, auth), this.get("join_messages", defaultJoinMessages, auth), this.get("login_timeout", 30, auth));
}
@Override
public Map<String, Object> getMap() {
return config;
}
@Override
public void forceSave() {
this.save();
}
@Override
public Collection<String> getBlacklistURLs() {
boolean blacklistEnable = this.getBoolean("enable_web_origin_blacklist", true);
if(!blacklistEnable) {
return null;
}
Collection<String> c = this.get("origin_blacklist_subscriptions", null);
if(c == null) {
c = new ArrayList();
c.add("https://g.lax1dude.net/eaglercraft/origin_blacklist.txt");
c.add("https://raw.githubusercontent.com/LAX1DUDE/eaglercraft/main/stable-download/origin_blacklist.txt");
c = this.get("origin_blacklist_subscriptions", c);
}else {
if(c.remove("https://g.eags.us/eaglercraft/origin_blacklist.txt")) {
c.add("https://g.lax1dude.net/eaglercraft/origin_blacklist.txt");
this.save();
BungeeCord.getInstance().getLogger().warning("Your origin blacklist has been patched to use g.lax1dude.net instead");
}
}
return c;
}
@Override
public Collection<String> getBlacklistSimpleWhitelist() {
Collection<String> c = this.get("origin_blacklist_simple_whitelist", null);
if(c == null) {
c = new ArrayList();
c.add("type the name of your client's domain here");
c.add("(if 'origin_blacklist_use_simple_whitelist' is true)");
c.add("g.lax1dude.net");
c = this.get("origin_blacklist_simple_whitelist", c);
}
return c;
}
@Override
public Collection<String> getDisabledCommands() {
return this.get("disabled_commands", new ArrayList());
}
@Override
public Collection<String> getICEServers() {
Collection<String> ret = new ArrayList();
Collection<String> c = this.get("voice_stun_servers", null);
if(c == null) {
c = new ArrayList();
c.add("stun:openrelay.metered.ca:80");
c = this.get("voice_stun_servers", c);
}
ret.addAll(c);
Map<String, Object> turnServerList = this.get("voice_turn_servers", null);
if(turnServerList == null) {
turnServerList = new HashMap();
HashMap<String, Object> n = new HashMap();
n.put("url", "turn:openrelay.metered.ca:80");
n.put("username", "openrelayproject");
n.put("password", "openrelayproject");
turnServerList.put("openrelay1", n);
n = new HashMap();
n.put("url", "turn:openrelay.metered.ca:443");
n.put("username", "openrelayproject");
n.put("password", "openrelayproject");
turnServerList.put("openrelay2", n);
n = new HashMap();
n.put("url", "turn:openrelay.metered.ca:443?transport=tcp");
n.put("username", "openrelayproject");
n.put("password", "openrelayproject");
turnServerList.put("openrelay3", n);
turnServerList = this.get("voice_turn_servers", turnServerList);
}
for(Entry<String, Object> trn : turnServerList.entrySet()) {
Object o = trn.getValue();
if(o instanceof Map) {
Map<String, Object> o2 = (Map<String, Object>) o;
ret.add("" + o2.get("url") + ";" + o2.get("username") + ";" + o2.get("password"));
}
}
return ret;
}
}

View File

@ -5,38 +5,39 @@
package net.md_5.bungee.connection;
import java.beans.ConstructorProperties;
import net.md_5.bungee.api.event.ServerKickEvent;
import java.net.InetAddress;
import java.util.Objects;
import net.md_5.bungee.protocol.packet.PacketFFKick;
import java.util.Iterator;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteArrayDataInput;
import java.util.Collection;
import net.md_5.bungee.api.connection.Connection;
import net.md_5.bungee.api.event.PluginMessageEvent;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import net.md_5.bungee.protocol.packet.PacketFAPluginMessage;
import net.md_5.bungee.api.score.Team;
import net.md_5.bungee.protocol.packet.PacketD1Team;
import net.md_5.bungee.api.score.Position;
import net.md_5.bungee.protocol.packet.PacketD0DisplayScoreboard;
import net.md_5.bungee.api.score.Score;
import net.md_5.bungee.protocol.packet.PacketCFScoreboardScore;
import net.md_5.bungee.api.score.Scoreboard;
import net.md_5.bungee.api.score.Objective;
import net.md_5.bungee.protocol.packet.PacketCEScoreboardObjective;
import net.md_5.bungee.protocol.packet.PacketC9PlayerListItem;
import net.md_5.bungee.protocol.packet.Packet0KeepAlive;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.EntityMap;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.Util;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.ServerConnection;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.Util;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.PluginMessageEvent;
import net.md_5.bungee.api.event.ServerKickEvent;
import net.md_5.bungee.api.score.Objective;
import net.md_5.bungee.api.score.Position;
import net.md_5.bungee.api.score.Score;
import net.md_5.bungee.api.score.Scoreboard;
import net.md_5.bungee.api.score.Team;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.netty.PacketHandler;
import net.md_5.bungee.protocol.packet.Packet0KeepAlive;
import net.md_5.bungee.protocol.packet.PacketC9PlayerListItem;
import net.md_5.bungee.protocol.packet.PacketCEScoreboardObjective;
import net.md_5.bungee.protocol.packet.PacketCFScoreboardScore;
import net.md_5.bungee.protocol.packet.PacketD0DisplayScoreboard;
import net.md_5.bungee.protocol.packet.PacketD1Team;
import net.md_5.bungee.protocol.packet.PacketFAPluginMessage;
import net.md_5.bungee.protocol.packet.PacketFFKick;
public class DownstreamBridge extends PacketHandler {
private final ProxyServer bungee;
@ -201,8 +202,14 @@ public class DownstreamBridge extends PacketHandler {
}
if (subChannel.equals("IP")) {
out.writeUTF("IP");
out.writeUTF(this.con.getAddress().getHostString());
out.writeInt(this.con.getAddress().getPort());
Object ob = this.con.getAttachment().get("remoteAddr");
if(ob != null && (ob instanceof InetAddress)) {
out.writeUTF(((InetAddress)ob).getHostAddress());
out.writeInt(this.con.getAddress().getPort());
}else {
out.writeUTF(this.con.getAddress().getHostString());
out.writeInt(this.con.getAddress().getPort());
}
}
if (subChannel.equals("PlayerCount")) {
final String target = in.readUTF();
@ -246,6 +253,26 @@ public class DownstreamBridge extends PacketHandler {
out.writeUTF("GetServer");
out.writeUTF(this.server.getInfo().getName());
}
if (subChannel.equals("EAG|GetDomain")) {
out.writeUTF("EAG|GetDomain");
Object ob = this.con.getAttachment().get("origin");
if(ob != null && (ob instanceof String)) {
out.writeBoolean(true);
out.writeUTF((String)ob);
}else {
out.writeBoolean(false);
out.writeUTF("");
}
}
if (subChannel.equals("EAG|ConsoleCommand")) {
if(BungeeCord.getInstance().config.shouldAcceptBukkitConsoleCommandPacket()) {
String cmd = in.readUTF();
bungee.getLogger().info("Connection [" + this.con.getName() + "] <-> [" + this.server.getInfo().getName() + "] executed bungee console command: " + cmd);
bungee.getPluginManager().dispatchCommand(bungee.getConsole(), cmd);
}else {
bungee.getLogger().info("Connection [" + this.con.getName() + "] <-> [" + this.server.getInfo().getName() + "] tried executing a bungee console command but \"accept_bukkit_console_command_packets\" is set to false in config.yml");
}
}
if (out != null) {
final byte[] b = out.toByteArray();
if (b.length != 0) {

View File

@ -5,54 +5,50 @@
package net.md_5.bungee.connection;
import java.beans.ConstructorProperties;
import java.util.ArrayList;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import net.md_5.bungee.protocol.packet.PacketFFKick;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.netty.HandlerBoss;
import net.md_5.bungee.api.event.PostLoginEvent;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.protocol.packet.PacketCDClientStatus;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import java.security.GeneralSecurityException;
import net.md_5.bungee.netty.CipherEncoder;
import net.md_5.bungee.api.event.LoginEvent;
import net.md_5.bungee.api.Callback;
import javax.crypto.Cipher;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.net.URLEncoder;
import io.netty.channel.ChannelHandler;
import net.md_5.bungee.netty.CipherDecoder;
import net.md_5.bungee.netty.PipelineUtils;
import java.security.Key;
import net.md_5.bungee.protocol.packet.PacketFCEncryptionResponse;
import net.md_5.bungee.EncryptionUtil;
import net.md_5.bungee.protocol.packet.DefinedPacket;
import net.md_5.bungee.PacketConstants;
import net.md_5.bungee.BungeeCord;
import java.util.logging.Level;
import net.md_5.bungee.protocol.Protocol;
import net.md_5.bungee.protocol.Forge;
import net.md_5.bungee.netty.PacketDecoder;
import com.google.common.base.Preconditions;
import net.md_5.bungee.api.event.ProxyPingEvent;
import net.md_5.bungee.api.ServerPing;
import net.md_5.bungee.protocol.packet.PacketFEPing;
import net.md_5.bungee.Util;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.connection.Connection;
import javax.crypto.SecretKey;
import net.md_5.bungee.protocol.packet.PacketFAPluginMessage;
import java.util.ArrayList;
import java.util.List;
import net.md_5.bungee.protocol.packet.PacketFDEncryptionRequest;
import net.md_5.bungee.protocol.packet.Packet2Handshake;
import net.md_5.bungee.protocol.packet.Packet1Login;
import net.md_5.bungee.api.config.ListenerInfo;
import net.md_5.bungee.netty.ChannelWrapper;
import java.util.logging.Level;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import com.google.common.base.Preconditions;
import io.netty.channel.ChannelHandler;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.EncryptionUtil;
import net.md_5.bungee.PacketConstants;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.Util;
import net.md_5.bungee.api.Callback;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.ServerPing;
import net.md_5.bungee.api.config.ListenerInfo;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.Connection;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.LoginEvent;
import net.md_5.bungee.api.event.PostLoginEvent;
import net.md_5.bungee.api.event.ProxyPingEvent;
import net.md_5.bungee.eaglercraft.AuthHandler;
import net.md_5.bungee.eaglercraft.BanList;
import net.md_5.bungee.eaglercraft.BanList.BanCheck;
import net.md_5.bungee.eaglercraft.BanList.BanState;
import net.md_5.bungee.eaglercraft.WebSocketProxy;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.netty.CipherDecoder;
import net.md_5.bungee.netty.CipherEncoder;
import net.md_5.bungee.netty.HandlerBoss;
import net.md_5.bungee.netty.PacketDecoder;
import net.md_5.bungee.netty.PacketHandler;
import net.md_5.bungee.netty.PipelineUtils;
import net.md_5.bungee.protocol.Forge;
import net.md_5.bungee.protocol.packet.*;
public class InitialHandler extends PacketHandler implements PendingConnection {
private final ProxyServer bungee;
@ -112,23 +108,81 @@ public class InitialHandler extends PacketHandler implements PendingConnection {
if (handshake.getProcolVersion() == 69) {
skipEncryption = true;
this.handshake.swapProtocol((byte) 61);
}else if(handshake.getProcolVersion() == 71) {
this.disconnect("this server does not support microsoft accounts");
return;
}else if(handshake.getProcolVersion() != 61) {
this.disconnect("minecraft 1.5.2 required for eaglercraft backdoor access");
return;
}
if (handshake.getUsername().length() < 3) {
String un = handshake.getUsername();
if (un.length() < 3) {
this.disconnect("Username must be at least 3 characters");
return;
}
if (handshake.getUsername().length() > 16) {
if (un.length() > 16) {
this.disconnect("Cannot have username longer than 16 characters");
return;
}
if(!un.equals(un.replaceAll("[^A-Za-z0-9\\-_]", "_").trim())) {
this.disconnect("Go fuck yourself");
return;
}
InetAddress sc;
synchronized(WebSocketProxy.localToRemote) {
sc = WebSocketProxy.localToRemote.get(this.ch.getHandle().remoteAddress());
}
if(sc == null) {
this.bungee.getLogger().log(Level.WARNING, "player '" + un + "' doesn't have a websocket IP, remote address: " + this.ch.getHandle().remoteAddress().toString());
}else {
BanCheck bc = BanList.checkIpBanned(sc);
if(bc.isBanned()) {
this.bungee.getLogger().log(Level.SEVERE, "Player '" + un + "' [" + sc.toString() + "] is banned by IP: " + bc.match + " (" + bc.string + ")");
this.disconnect("" + ChatColor.RED + "You are banned.\n" + ChatColor.DARK_GRAY + "Reason: " + bc.string);
return;
}else {
this.bungee.getLogger().log(Level.INFO, "Player '" + un + "' [" + sc.toString() + "] has remote websocket IP: " + sc.getHostAddress());
}
}
String dnm;
synchronized(WebSocketProxy.localToRemote) {
dnm = WebSocketProxy.origins.get(this.ch.getHandle().remoteAddress());
}
if(dnm != null) {
if(dnm.equalsIgnoreCase("null")) {
this.bungee.getLogger().log(Level.INFO, "Player '" + un + "' [" + sc.toString() + "] is using an offline download");
}else {
this.bungee.getLogger().log(Level.INFO, "Player '" + un + "' [" + sc.toString() + "] is using a client at: " + dnm);
}
}
BanCheck bc = BanList.checkBanned(un);
if(bc.isBanned()) {
switch(bc.reason) {
case USER_BANNED:
this.bungee.getLogger().log(Level.SEVERE, "Player '" + un + "' is banned by username, because '" + bc.string + "'");
break;
case WILDCARD_BANNED:
this.bungee.getLogger().log(Level.SEVERE, "Player '" + un + "' is banned by wildcard: " + bc.match);
break;
case REGEX_BANNED:
this.bungee.getLogger().log(Level.SEVERE, "Player '" + un + "' is banned by regex: " + bc.match);
break;
default:
this.bungee.getLogger().log(Level.SEVERE, "Player '" + un + "' is banned: " + bc.string);
}
if(bc.reason == BanState.USER_BANNED || ((BungeeCord)bungee).config.shouldShowBanType()) {
this.disconnect("" + ChatColor.RED + "You are banned.\n" + ChatColor.DARK_GRAY + "Reason: " + bc.string);
}else {
this.disconnect("" + ChatColor.RED + "You are banned.");
}
return;
}
final int limit = BungeeCord.getInstance().config.getPlayerLimit();
if (limit > 0 && this.bungee.getOnlineCount() > limit) {
this.disconnect(this.bungee.getTranslation("proxy_full"));
return;
}
if (!BungeeCord.getInstance().config.isOnlineMode() && this.bungee.getPlayer(handshake.getUsername()) != null) {
if (!BungeeCord.getInstance().config.isOnlineMode() && this.bungee.getPlayer(un) != null) {
this.disconnect(this.bungee.getTranslation("already_connected"));
return;
}
@ -189,11 +243,24 @@ public class InitialHandler extends PacketHandler implements PendingConnection {
public void handle(final PacketCDClientStatus clientStatus) throws Exception {
Preconditions.checkState(this.thisState == State.LOGIN, (Object) "Not expecting LOGIN");
final UserConnection userCon = new UserConnection(this.bungee, this.ch, this.getName(), this);
InetAddress ins = WebSocketProxy.localToRemote.get(this.ch.getHandle().remoteAddress());
if(ins != null) {
userCon.getAttachment().put("remoteAddr", ins);
}
String origin = WebSocketProxy.origins.get(this.ch.getHandle().remoteAddress());
if(origin != null) {
userCon.getAttachment().put("origin", origin);
}
userCon.init();
this.bungee.getPluginManager().callEvent(new PostLoginEvent(userCon));
((HandlerBoss) this.ch.getHandle().pipeline().get((Class) HandlerBoss.class)).setHandler(new UpstreamBridge(this.bungee, userCon));
final ServerInfo server = this.bungee.getReconnectHandler().getServer(userCon);
userCon.connect(server, true);
HandlerBoss handlerBoss = ((HandlerBoss) this.ch.getHandle().pipeline().get((Class) HandlerBoss.class));
if (BungeeCord.getInstance().config.getAuthInfo().isEnabled()) {
handlerBoss.setHandler(new AuthHandler(this.bungee, userCon, handlerBoss));
} else {
this.bungee.getPluginManager().callEvent(new PostLoginEvent(userCon));
handlerBoss.setHandler(new UpstreamBridge(this.bungee, userCon));
final ServerInfo server = this.bungee.getReconnectHandler().getServer(userCon);
userCon.connect(server, true);
}
this.thisState = State.FINISHED;
throw new CancelSendSignal();
}
@ -218,7 +285,7 @@ public class InitialHandler extends PacketHandler implements PendingConnection {
@Override
public InetSocketAddress getVirtualHost() {
return (this.handshake == null) ? null : new InetSocketAddress(this.handshake.getHost(), this.handshake.getPort());
return (this.handshake == null) ? null : new InetSocketAddress(this.handshake.getHost(), this.handshake.getPort() & 0xFFFF);
}
@Override

View File

@ -5,12 +5,13 @@
package net.md_5.bungee.connection;
import java.beans.ConstructorProperties;
import net.md_5.bungee.protocol.packet.PacketFFKick;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.api.ServerPing;
import net.md_5.bungee.api.Callback;
import net.md_5.bungee.api.ServerPing;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.netty.PacketHandler;
import net.md_5.bungee.protocol.packet.PacketFFKick;
public class PingHandler extends PacketHandler {
private final ServerInfo target;

View File

@ -4,25 +4,21 @@
package net.md_5.bungee.connection;
import net.md_5.bungee.api.event.PluginMessageEvent;
import net.md_5.bungee.protocol.packet.PacketFAPluginMessage;
import net.md_5.bungee.protocol.packet.PacketCCSettings;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.connection.Connection;
import net.md_5.bungee.api.event.ChatEvent;
import net.md_5.bungee.protocol.packet.Packet3Chat;
import net.md_5.bungee.protocol.packet.Packet0KeepAlive;
import net.md_5.bungee.EntityMap;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.Util;
import net.md_5.bungee.api.config.TexturePackInfo;
import net.md_5.bungee.protocol.packet.DefinedPacket;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.EntityMap;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.Util;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.config.TexturePackInfo;
import net.md_5.bungee.api.event.ChatEvent;
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
import net.md_5.bungee.api.event.PluginMessageEvent;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.netty.PacketHandler;
import net.md_5.bungee.protocol.packet.Packet0KeepAlive;
import net.md_5.bungee.protocol.packet.Packet3Chat;
import net.md_5.bungee.protocol.packet.PacketCCSettings;
import net.md_5.bungee.protocol.packet.PacketFAPluginMessage;
public class UpstreamBridge extends PacketHandler {
private final ProxyServer bungee;
@ -59,7 +55,7 @@ public class UpstreamBridge extends PacketHandler {
@Override
public void handle(final byte[] buf) throws Exception {
EntityMap.rewrite(buf, this.con.getClientEntityId(), this.con.getServerEntityId());
if (this.con.getServer() != null) {
if (this.con.getServer() != null && this.con.getServer().getCh() != null) {
this.con.getServer().getCh().write(buf);
}
}

View File

@ -0,0 +1,178 @@
package net.md_5.bungee.eaglercraft;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.ServerConnection;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.Util;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.event.PostLoginEvent;
import net.md_5.bungee.connection.UpstreamBridge;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.netty.HandlerBoss;
import net.md_5.bungee.netty.PacketHandler;
import net.md_5.bungee.protocol.packet.Packet1Login;
import net.md_5.bungee.protocol.packet.Packet9Respawn;
import net.md_5.bungee.protocol.packet.Packet0DPositionAndLook;
import net.md_5.bungee.protocol.packet.Packet3Chat;
import net.md_5.bungee.protocol.packet.Packet0KeepAlive;
import net.md_5.bungee.protocol.packet.PacketCCSettings;
public class AuthHandler extends PacketHandler {
private static final AuthSystem authSystem = BungeeCord.getInstance().authSystem;
private final ProxyServer bungee;
private final UserConnection con;
private final HandlerBoss handlerBoss;
private final String username;
private static final Collection<AuthHandler> openHandlers = new LinkedList();
private boolean loggedIn = false;
private long startTime;
public AuthHandler(final ProxyServer bungee, final UserConnection con, final HandlerBoss handlerBoss) {
this.bungee = bungee;
this.con = con;
this.handlerBoss = handlerBoss;
this.username = this.con.getName();
this.startTime = System.currentTimeMillis();
synchronized(openHandlers) {
openHandlers.add(this);
}
this.con.unsafe().sendPacket(new Packet1Login(0, "END", (byte) 2, 1, (byte) 0, (byte) 0,
(byte) this.con.getPendingConnection().getListener().getTabListSize()));
this.con.unsafe().sendPacket(new Packet9Respawn(1, (byte) 0, (byte) 2, (short) 255, "END"));
this.con.unsafe().sendPacket(new Packet0DPositionAndLook(0, 0, 0, 0, 0f, 0f, true));
this.con.sendMessages(authSystem.joinMessages);
if (authSystem.isRegistered(this.username)) {
this.con.sendMessage("\u00A7cPlease login to continue! /login <password>");
} else {
this.con.sendMessage("\u00A7cPlease register to continue! /register <password> <confirmPassword>");
}
}
@Override
public void exception(final Throwable t) throws Exception {
this.con.disconnect(Util.exception(t));
}
@Override
public void disconnected(final ChannelWrapper channel) {
this.loggedIn = true;
}
@Override
public void handle(final Packet0KeepAlive alive) throws Exception {
if (alive.getRandomId() == this.con.getSentPingId()) {
final int newPing = (int) (System.currentTimeMillis() - this.con.getSentPingTime());
this.con.setPing(newPing);
}
}
@Override
public void handle(final Packet3Chat chat) throws Exception {
String message = chat.getMessage();
if (message.startsWith("/")) {
String[] args = message.substring(1).trim().split(" ");
switch (args[0]) {
case "login":
case "l":
if (args.length == 1) {
this.con.sendMessage("\u00A7cYou must specify a password to login! /login <password>");
} else if (!authSystem.isRegistered(this.username)) {
this.con.sendMessage("\u00A7cThis username is not registered on this server!");
} else if (authSystem.login(this.username, args[1])) {
this.con.sendMessage("\u00A7cLogging in...");
this.onLogin();
} else {
this.con.sendMessage("\u00A7cThat password is invalid!");
}
break;
case "register":
case "reg":
if(BungeeCord.getInstance().config.getAuthInfo().isRegisterEnabled()) {
if (args.length == 1 || args.length == 2) {
this.con.sendMessage("\u00A7cUsage: /" + args[0].toLowerCase() + " <password> <confirmPassword>");
} else if (!args[1].equals(args[2])) {
this.con.sendMessage("\u00A7cThose passwords do not match!");
} else if (authSystem.isRegistered(this.username)) {
this.con.sendMessage("\u00A7cThis username is already registered!");
} else if (authSystem.register(this.username, args[1],
this.con.getAddress().getAddress().getHostAddress())) {
this.con.sendMessage("\u00A7cSuccessfully registered and logging in...");
this.onLogin();
} else {
this.con.sendMessage("\u00A7cUnable to register...");
}
}else {
this.con.disconnect("Registration is not enabled!");
}
break;
case "changepassword":
case "changepasswd":
case "changepwd":
case "changepass":
if (args.length == 1 || args.length == 2) {
this.con.sendMessage("\u00A7cUsage: /" + args[0].toLowerCase() + " <oldPassword> <newPassword>");
} else if (authSystem.login(this.username, args[1])) {
if (authSystem.changePass(this.username, args[2])) {
this.con.sendMessage("\u00A7cPassword changed successfully!");
} else {
this.con.sendMessage("\u00A7cUnable to change your password...");
}
} else {
this.con.sendMessage("\u00A7cThe old password specified is incorrect!");
}
break;
default:
}
}
}
private void onLogin() {
this.loggedIn = true;
this.bungee.getPluginManager().callEvent(new PostLoginEvent(this.con));
handlerBoss.setHandler(new UpstreamBridge(this.bungee, this.con));
final ServerInfo server = this.bungee.getReconnectHandler().getServer(this.con);
this.con.setServer(new ServerConnection(null, null));
this.con.connect(server, true);
}
@Override
public void handle(final PacketCCSettings settings) throws Exception {
this.con.setSettings(settings);
}
@Override
public String toString() {
return "[" + this.con.getName() + "] -> AuthHandler";
}
public static void closeInactive(int timeout) {
synchronized(openHandlers) {
long millis = System.currentTimeMillis();
timeout *= 1000;
Iterator<AuthHandler> handlers = openHandlers.iterator();
while(handlers.hasNext()) {
AuthHandler h = handlers.next();
if(!h.loggedIn) {
if(millis - h.startTime > timeout) {
h.con.disconnect("You did not login in time you eagler!");
handlers.remove();
}
}else {
handlers.remove();
}
}
}
}
}

View File

@ -0,0 +1,202 @@
package net.md_5.bungee.eaglercraft;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.config.AuthServiceInfo;
import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class AuthSystem {
private final String authFileName;
private final int ipLimit;
public final String[] joinMessages;
public AuthSystem(AuthServiceInfo authInfo) {
this.authFileName = authInfo.getAuthfile();
this.ipLimit = authInfo.getIpLimit();
List<String> listJoinMessages = authInfo.getJoinMessages();
String[] arrayJoinMessages = new String[listJoinMessages.size()];
for (int i = 0; i < listJoinMessages.size(); i++) {
arrayJoinMessages[i] = ChatColor.translateAlternateColorCodes('&', listJoinMessages.get(i));
}
this.joinMessages = arrayJoinMessages;
this.readDatabase();
}
private static class AuthData {
public String salt;
public String hash;
public String ip;
public long timestamp;
public AuthData(String salt, String hash, String ip, long timestamp) {
this.salt = salt;
this.hash = hash;
this.ip = ip;
this.timestamp = timestamp;
}
}
private final Map<String, AuthData> database = new HashMap<>();
public boolean register(String username, String password, String ip) {
synchronized (database) {
AuthData authData = database.get(username);
if (authData != null)
return false;
if (isIpAtTheLimit(ip))
return false;
String salt = createSalt(16);
String hash = getSaltedHash(password, salt);
database.put(username, new AuthData(salt, hash, ip, System.currentTimeMillis()));
writeDatabase();
return true;
}
}
public boolean isRegistered(String username) {
synchronized (database) {
return database.containsKey(username);
}
}
public boolean changePass(String username, String password) {
synchronized (database) {
AuthData authData = database.get(username);
authData.salt = createSalt(16);
authData.hash = getSaltedHash(password, authData.salt);
writeDatabase();
return true;
}
}
public boolean login(String username, String password) {
synchronized (database) {
AuthData authData = database.get(username);
if (authData == null)
return false;
return authData.hash.equals(getSaltedHash(password, authData.salt));
}
}
private boolean isIpAtTheLimit(String ip) {
synchronized (database) {
if (this.ipLimit <= 0)
return false;
int num = 0;
for (AuthData authData : database.values()) {
if (authData.ip.equals(ip))
num++;
if (num >= this.ipLimit) {
return true;
}
}
return false;
}
}
// only use once, on load
public void readDatabase() {
synchronized (database) {
try {
File authFile = new File(this.authFileName);
if (!authFile.exists())
authFile.createNewFile();
database.clear();
String[] lines = new String(Files.readAllBytes(authFile.toPath())).trim().split("\n");
if (lines.length == 1 && lines[0].isEmpty())
return;
boolean alreadyLogged = false;
for (String line : lines) {
String[] pieces = line.split(":");
if (!pieces[1].startsWith("$SHA$")) {
if (!alreadyLogged) {
alreadyLogged = true;
BungeeCord.getInstance().getLogger().warning(
"One or more entries in the auth file are hashed in an unsupported format! (not SHA-256!)");
}
// continue;
}
String[] saltHash = pieces[1].substring(pieces[1].substring(1).indexOf('$') + 2).split("\\$");
database.put(pieces[0],
new AuthData(saltHash[0], saltHash[1], pieces[2], Long.parseLong(pieces[3])));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void writeDatabase() {
synchronized (database) {
StringBuilder out = new StringBuilder();
for (String username : database.keySet()) {
AuthData entry = database.get(username);
out.append(username);
out.append(":$SHA$");
out.append(entry.salt);
out.append("$");
out.append(entry.hash);
out.append(":");
out.append(entry.ip);
out.append(":");
out.append(entry.timestamp);
out.append("\n");
}
try {
Files.write(Paths.get(this.authFileName), out.toString().getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
}
// hashing used is based on hashing from AuthMe
private static final SecureRandom rnd = new SecureRandom();
private static String getSHA256(String message) {
try {
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
sha256.reset();
sha256.update(message.getBytes());
byte[] digest = sha256.digest();
return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest));
} catch (NoSuchAlgorithmException e) {
return "";
}
}
private static String getSaltedHash(String message, String salt) {
return getSHA256(getSHA256(message) + salt);
}
private static String createSalt(int length) {
try {
byte[] msg = new byte[40];
rnd.nextBytes(msg);
MessageDigest sha1 = MessageDigest.getInstance("SHA1");
sha1.reset();
byte[] digest = sha1.digest(msg);
return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)).substring(0, length);
} catch (NoSuchAlgorithmException e) {
return "";
}
}
}

View File

@ -0,0 +1,872 @@
package net.md_5.bungee.eaglercraft;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.math.BigInteger;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.eaglercraft.sun.net.util.IPAddressUtil;
public class BanList {
private static final Object banListMutex = new Object();
public static enum BanState {
NOT_BANNED, USER_BANNED, IP_BANNED, WILDCARD_BANNED, REGEX_BANNED;
}
public static class BanCheck {
public final BanState reason;
public final String match;
public final String string;
private BanCheck(BanState reason, String match, String string) {
this.reason = reason;
this.match = match;
this.string = string;
}
public boolean isBanned() {
return reason != BanState.NOT_BANNED;
}
}
private static class RegexBan {
public final String string;
public final Pattern compiled;
private RegexBan(String string, Pattern compiled) {
this.string = string;
this.compiled = compiled;
}
public String toString() {
return string;
}
public int hashCode() {
return string.hashCode();
}
}
public static interface IPBan {
boolean checkBan(InetAddress addr);
InetAddress getBaseAddress();
boolean hasNetMask();
}
private static class IPBan4 implements IPBan {
private final int addr;
private final InetAddress addrI;
private final int mask;
private final String string;
protected IPBan4(Inet4Address addr, String s, int mask) {
if(mask >= 32) {
this.mask = 0xFFFFFFFF;
}else {
this.mask = ~((1 << (32 - mask)) - 1);
}
this.string = s;
byte[] bits = addr.getAddress();
this.addr = this.mask & ((bits[0] << 24) | (bits[1] << 16) | (bits[2] << 8) | (bits[3] & 0xFF));
this.addrI = addr;
}
@Override
public boolean checkBan(InetAddress addr4) {
if(addr4 instanceof Inet4Address) {
Inet4Address a = (Inet4Address)addr4;
byte[] bits = a.getAddress();
int addrBits = ((bits[0] << 24) | (bits[1] << 16) | (bits[2] << 8) | (bits[3] & 0xFF));
return (mask & addrBits) == addr;
}else {
return false;
}
}
@Override
public InetAddress getBaseAddress() {
return addrI;
}
@Override
public String toString() {
return string;
}
@Override
public int hashCode() {
return string.hashCode();
}
@Override
public boolean equals(Object o) {
return o != null && o instanceof IPBan4 && ((IPBan4)o).addr == addr && ((IPBan4)o).mask == mask;
}
@Override
public boolean hasNetMask() {
return mask != 0xFFFFFFFF;
}
}
private static class IPBan6 implements IPBan {
private static final BigInteger mask128 = new BigInteger(1, new byte[] {
(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,
(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,
(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,
(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF
});
private final BigInteger addr;
private final InetAddress addrI;
private final BigInteger mask;
private final String string;
protected IPBan6(Inet6Address addr, String s, int mask) {
this.mask = BigInteger.valueOf(1l).shiftLeft(128 - mask).subtract(BigInteger.valueOf(1l)).xor(mask128);
this.string = s.toLowerCase();
this.addr = new BigInteger(1, addr.getAddress()).and(this.mask);
this.addrI = addr;
}
@Override
public boolean checkBan(InetAddress addr6) {
if(addr6 instanceof Inet6Address) {
Inet6Address a = (Inet6Address)addr6;
BigInteger addrBits = new BigInteger(1, a.getAddress()).and(this.mask);
return addr.equals(addrBits);
}else {
return false;
}
}
@Override
public InetAddress getBaseAddress() {
return addrI;
}
@Override
public String toString() {
return string;
}
@Override
public int hashCode() {
return string.hashCode();
}
@Override
public boolean equals(Object o) {
return o != null && o instanceof IPBan6 && ((IPBan6)o).addr.equals(addr) && ((IPBan6)o).mask.equals(mask);
}
@Override
public boolean hasNetMask() {
return !mask.equals(mask128);
}
}
public static final File bansFile = new File("bans.txt");
public static final String banChatMessagePrefix = ChatColor.GOLD + "[BanList] ";
public static final Map<String,String> userBans = new HashMap();
public static final Set<IPBan> ipBans = new HashSet();
public static final Set<String> wildcardBans = new HashSet();
public static final Set<RegexBan> regexBans = new HashSet();
private static List<String> currentBanList = null;
public static final List<IPBan> blockedBans = new ArrayList();
static {
try {
blockedBans.add(constructIpBan("127.0.0.0/8"));
}catch(UnknownHostException e) {
BungeeCord.getInstance().getLogger().severe("Error: could not whitelist '127.0.0.0/8'");
e.printStackTrace();
}
try {
blockedBans.add(constructIpBan("10.0.0.0/8"));
}catch(UnknownHostException e) {
BungeeCord.getInstance().getLogger().severe("Error: could not whitelist '10.0.0.0/8'");
e.printStackTrace();
}
try {
blockedBans.add(constructIpBan("172.24.0.0/14"));
}catch(UnknownHostException e) {
BungeeCord.getInstance().getLogger().severe("Error: could not whitelist '172.24.0.0/14'");
e.printStackTrace();
}
try {
blockedBans.add(constructIpBan("192.168.0.0/16"));
}catch(UnknownHostException e) {
BungeeCord.getInstance().getLogger().severe("Error: could not whitelist '192.168.0.0/16'");
e.printStackTrace();
}
try {
blockedBans.add(constructIpBan("::1/128"));
}catch(UnknownHostException e) {
BungeeCord.getInstance().getLogger().severe("Error: could not whitelist '::1/128'");
e.printStackTrace();
}
}
public static boolean isBlockedBan(InetAddress addr) {
for(IPBan b : BanList.blockedBans) {
if(b.checkBan(addr)) {
return true;
}
}
return false;
}
private static long lastListTest = 0l;
private static long lastListLoad = 0l;
private static boolean fileIsBroken = false;
public static BanCheck checkIpBanned(InetAddress addr) {
synchronized(banListMutex) {
for(IPBan b : ipBans) {
if(b.checkBan(addr)) {
return new BanCheck(BanState.IP_BANNED, b.toString(), b.hasNetMask() ? "Banned by Netmask" : "Banned by IP");
}
}
}
return new BanCheck(BanState.NOT_BANNED, "none", "not banned");
}
public static BanCheck checkBanned(String player) {
synchronized(banListMutex) {
player = player.trim().toLowerCase();
String r = userBans.get(player);
if(r != null) {
if(r.length() <= 0) {
r = "The ban hammer has spoken";
}
return new BanCheck(BanState.USER_BANNED, player, r);
}
for(String ss : wildcardBans) {
String s = ss;
boolean startStar = s.startsWith("*");
if(startStar) {
s = s.substring(1);
}
boolean endStar = s.endsWith("*");
if(endStar) {
s = s.substring(0, s.length() - 1);
}
if(startStar && endStar) {
if(player.contains(s)) {
return new BanCheck(BanState.WILDCARD_BANNED, ss, "You've been banned via wildcard");
}
}else if(endStar) {
if(player.startsWith(s)) {
return new BanCheck(BanState.WILDCARD_BANNED, ss, "You've been banned via wildcard");
}
}else if(startStar) {
if(player.endsWith(s)) {
return new BanCheck(BanState.WILDCARD_BANNED, ss, "You've been banned via wildcard");
}
}else {
if(player.equals(s)) {
return new BanCheck(BanState.WILDCARD_BANNED, ss, "You've been banned via wildcard");
}
}
}
for(RegexBan p : regexBans) {
if(p.compiled.matcher(player).matches()) {
return new BanCheck(BanState.REGEX_BANNED, p.string, "You've been banned via regex");
}
}
}
return new BanCheck(BanState.NOT_BANNED, "none", "not banned");
}
private static void saveCurrentBanListLines() {
try {
PrintWriter pf = new PrintWriter(new FileWriter(bansFile));
for(String s : currentBanList) {
pf.println(s);
}
pf.close();
lastListLoad = lastListTest = System.currentTimeMillis();
}catch(Throwable t) {
BungeeCord.getInstance().getLogger().severe("ERROR: the ban list could not be saved to file '" + bansFile.getName() + "', please fix this or you will lose all your bans next time this server restarts");
t.printStackTrace();
}
}
private static boolean addEntryToFile(BanState b, String s) {
if(b == null || b == BanState.NOT_BANNED) {
return false;
}
String wantedHeader = b == BanState.USER_BANNED ? "[Usernames]" : (b == BanState.WILDCARD_BANNED ? "[Wildcards]" : (b == BanState.REGEX_BANNED ? "[Regex]" : (b == BanState.IP_BANNED ? "[IPs]" : "shit")));
int lastFullPart = -1;
boolean isFilePart = false;
boolean isPartStart = false;
for(int i = 0, l = currentBanList.size(); i < l; ++i) {
String ss = currentBanList.get(i).trim();
if(ss.length() <= 0) {
continue;
}
if(ss.startsWith("#")) {
continue;
}
if(ss.equalsIgnoreCase(wantedHeader)) {
isFilePart = true;
isPartStart = true;
lastFullPart = i;
}else if(ss.indexOf('[') != -1) {
if(isFilePart) {
break;
}
}else {
if(isFilePart) {
lastFullPart = i;
isPartStart = false;
}
}
}
if(lastFullPart != -1) {
if(isPartStart) {
lastFullPart += 1;
currentBanList.add(lastFullPart, "");
}
lastFullPart += 1;
currentBanList.add(lastFullPart, s);
lastFullPart += 1;
if(currentBanList.size() > lastFullPart && currentBanList.get(lastFullPart).trim().length() > 0) {
currentBanList.add(lastFullPart, "");
}
}else {
if(currentBanList.size() > 0 && currentBanList.get(currentBanList.size() - 1).trim().length() > 0) {
currentBanList.add("");
}
currentBanList.add(wantedHeader);
currentBanList.add("");
currentBanList.add(s);
currentBanList.add("");
}
saveCurrentBanListLines();
return true;
}
private static boolean removeEntryFromFile(BanState b, String s, boolean ignoreCase) {
if(b == null || b == BanState.NOT_BANNED) {
return false;
}
String wantedHeader = b == BanState.USER_BANNED ? "[Usernames]" : (b == BanState.WILDCARD_BANNED ? "[Wildcards]" : (b == BanState.REGEX_BANNED ? "[Regex]" : (b == BanState.IP_BANNED ? "[IPs]" : "shit")));
Iterator<String> lns = currentBanList.iterator();
boolean isFilePart = false;
boolean wasRemoved = false;
while(lns.hasNext()) {
String ss = lns.next().trim();
if(ss.length() <= 0) {
continue;
}
if(ss.startsWith("#")) {
continue;
}
if(ss.equalsIgnoreCase(wantedHeader)) {
isFilePart = true;
}else if(ss.indexOf('[') != -1) {
isFilePart = false;
}else {
if(b == BanState.USER_BANNED && ss.contains(":")) {
ss = ss.substring(0, ss.indexOf(':')).trim();
}
if(isFilePart && (ignoreCase ? ss.equalsIgnoreCase(s) : ss.equals(s))) {
lns.remove();
wasRemoved = true;
}
}
}
if(wasRemoved) {
saveCurrentBanListLines();
}
return wasRemoved;
}
public static boolean unban(String player) {
synchronized(banListMutex) {
String s = player.trim().toLowerCase();
if(userBans.remove(s) != null) {
removeEntryFromFile(BanState.USER_BANNED, player, true);
return true;
}else {
return false;
}
}
}
public static boolean ban(String player, String reason) {
synchronized(banListMutex) {
player = player.trim().toLowerCase();
if(userBans.put(player, reason) == null) {
addEntryToFile(BanState.USER_BANNED, player + (reason == null || reason.length() <= 0 ? "" : ": " + reason));
return true;
}else {
return false;
}
}
}
public static boolean banWildcard(String wc) throws PatternSyntaxException {
synchronized(banListMutex) {
wc = wc.trim().toLowerCase();
boolean b = wc.contains("*");
if(!b || (b && !wc.startsWith("*") && !wc.endsWith("*"))) {
throw new PatternSyntaxException("Wildcard can only begin and/or end with *", wc, 0);
}
if(wildcardBans.add(wc)) {
addEntryToFile(BanState.WILDCARD_BANNED, wc);
return true;
}else {
return false;
}
}
}
public static boolean unbanWildcard(String wc) {
synchronized(banListMutex) {
wc = wc.trim().toLowerCase();
if(wildcardBans.remove(wc)) {
removeEntryFromFile(BanState.WILDCARD_BANNED, wc, true);
return true;
}else {
return false;
}
}
}
public static boolean banRegex(String regex) throws PatternSyntaxException {
synchronized(banListMutex) {
regex = regex.trim();
Pattern p = Pattern.compile(regex);
if(regexBans.add(new RegexBan(regex, p))) {
addEntryToFile(BanState.REGEX_BANNED, regex);
return true;
}else {
return false;
}
}
}
public static boolean unbanRegex(String regex) {
synchronized(banListMutex) {
regex = regex.trim();
Iterator<RegexBan> banz = regexBans.iterator();
while(banz.hasNext()) {
if(banz.next().string.equals(regex)) {
banz.remove();
removeEntryFromFile(BanState.REGEX_BANNED, regex, false);
return true;
}
}
return false;
}
}
public static IPBan constructIpBan(String ip) throws UnknownHostException {
synchronized(banListMutex) {
ip = ip.trim();
String s = ip;
int subnet = -1;
int i = s.indexOf('/');
if(i != -1) {
String s2 = s.substring(i + 1);
s = s.substring(0, i);
try {
subnet = Integer.parseInt(s2);
}catch(Throwable t) {
throw new UnknownHostException("Invalid netmask: '" + s + "'");
}
}
if(!IPAddressUtil.isIPv4LiteralAddress(s) && !IPAddressUtil.isIPv6LiteralAddress(s)) {
throw new UnknownHostException("Invalid address: '" + s + "'");
}
InetAddress aa = InetAddress.getByName(s);
if(aa instanceof Inet4Address) {
if(subnet > 32 || subnet < -1) {
throw new UnknownHostException("IPv4 netmask '" + subnet + "' is invalid");
}
if(subnet == -1) {
subnet = 32;
}
return new IPBan4((Inet4Address)aa, ip, subnet);
}else if(aa instanceof Inet6Address) {
if(subnet > 128 || subnet < -1) {
throw new UnknownHostException("IPv6 netmask '" + subnet + "' is invalid");
}
if(subnet == -1) {
subnet = 128;
}
return new IPBan6((Inet6Address)aa, ip, subnet);
}else {
throw new UnknownHostException("Only ipv4 and ipv6 addresses allowed in Eaglercraft");
}
}
}
public static boolean banIP(String ip) throws UnknownHostException {
synchronized(banListMutex) {
ip = ip.trim();
IPBan b = constructIpBan(ip);
if(b != null) {
if(ipBans.add(b)) {
addEntryToFile(BanState.IP_BANNED, ip);
return true;
}
}
return false;
}
}
public static boolean unbanIP(String ip) throws UnknownHostException {
synchronized(banListMutex) {
ip = ip.trim();
IPBan b = constructIpBan(ip);
if(b != null) {
Iterator<IPBan> banz = ipBans.iterator();
while(banz.hasNext()) {
IPBan bb = banz.next();
if(bb.equals(b)) {
banz.remove();
removeEntryFromFile(BanState.IP_BANNED, bb.toString(), true);
return true;
}
}
}
return false;
}
}
private static final int MAX_CHAT_LENGTH = 118;
public static String listAllBans() {
synchronized(banListMutex) {
String ret = "";
for(String s : userBans.keySet()) {
if(ret.length() > 0) {
ret += ", ";
}
ret += s;
}
return ret.length() > 0 ? ret : "(none)";
}
}
public static String listAllWildcardBans() {
synchronized(banListMutex) {
String ret = "";
for(String s : wildcardBans) {
if(ret.length() > 0) {
ret += ", ";
}
ret += s;
}
return ret.length() > 0 ? ret : "(none)";
}
}
public static String listAllRegexBans() {
synchronized(banListMutex) {
String ret = "";
for(RegexBan s : regexBans) {
if(ret.length() > 0) {
ret += " | ";
}
ret += s.string;
}
return ret.length() > 0 ? ret : "(none)";
}
}
public static String listAllIPBans(boolean v6, boolean netmask) {
synchronized(banListMutex) {
String ret = "";
for(IPBan b : ipBans) {
if(v6) {
if(b instanceof IPBan6) {
IPBan6 b2 = (IPBan6)b;
if(netmask == b2.hasNetMask()) {
if(ret.length() > 0) {
ret += ", ";
}
ret += b2.string;
}
}
}else {
if(b instanceof IPBan4) {
IPBan4 b2 = (IPBan4)b;
if(netmask == b2.hasNetMask()) {
if(ret.length() > 0) {
ret += ", ";
}
ret += b2.string;
}
}
}
}
if(ret.length() <= 0) {
ret = "(none)";
}
return ret;
}
}
public static void maybeReloadBans(CommandSender cs) {
synchronized(banListMutex) {
long st = System.currentTimeMillis();
if(cs == null && st - lastListTest < 1000l) {
return;
}
lastListTest = st;
boolean ex = bansFile.exists();
if(!fileIsBroken && !ex) {
try {
PrintWriter p = new PrintWriter(new FileWriter(bansFile));
p.println();
p.println("#");
p.println("# This file allows you to configure bans for eaglercraftbungee");
p.println("# When it is saved, eaglercraft should reload it automatically");
p.println("# (check the console though to be safe)");
p.println("#");
p.println("# For a [Usernames] ban, just add the player's name. Use a colon ':' to put in a ban reason");
p.println("# For a [IPs] ban, just add the player's IP, or a subnet like 69.69.0.0/16 to ban all IPs beginning with 69.69.*");
p.println("# For a [Wildcards] ban, type a string and prefix and/or suffix it with * to define the wildcard");
p.println("# For a [Regex] ban, type a valid regular expression in the java.util.regex format");
p.println("#");
p.println("# All bans are case-insensitive, USERNAMES ARE CONVERTED TO LOWERCASE BEFORE BEING MATCHED VIA REGEX");
p.println("# Java regex syntax: https://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html");
p.println("#");
p.println();
p.println("# set this to to true to use \"/ban\" to ban on bungee instead of \"/eag-ban\"");
p.println("# (most likely needs a restart to take effect)");
p.println("replace-bukkit=false");
p.println();
p.println();
p.println("[Usernames]");
p.println();
p.println("# ban_test1: The ban hammer has spoken!");
p.println("# ban_test2: custom ban message here");
p.println("# ban_test3");
p.println();
p.println("# (remove the '#' before each line to enable)");
p.println();
p.println("[IPs]");
p.println();
p.println("# WARNING: if you're using nginx, banning any player's IP is gonna ban ALL PLAYERS on your server");
p.println("# For this reason, the ban IP command doesn't ban 127.0.0.1 or any other 'private' range IPs");
p.println();
p.println("# 101.202.69.11");
p.println("# 123.21.43.0/24");
p.println("# 2601:1062:69:418:BEEF::10");
p.println("# 2601:6090:420::/48");
p.println();
p.println();
p.println("[Wildcards]");
p.println();
p.println("# *fuck*");
p.println("# shi*");
p.println();
p.println();
p.println("[Regex]");
p.println();
p.println("# you.+are.(a|the).+bitch");
p.println();
p.println();
p.println("# end of file");
p.println();
p.close();
BungeeCord.getInstance().getLogger().info("Wrote a new bans.txt to: " + bansFile.getAbsolutePath());
lastListLoad = 0l;
}catch(Throwable t) {
fileIsBroken = true;
if(cs != null) {
cs.sendMessage(banChatMessagePrefix + ChatColor.RED + "Could not create blank 'bans.txt' list file");
cs.sendMessage(banChatMessagePrefix + ChatColor.RED + "(Reason: " + t.toString() + ")");
}
BungeeCord.getInstance().getLogger().severe("Could not create blank 'bans.txt' list file");
BungeeCord.getInstance().getLogger().severe("(Reason: " + t.toString() + ")");
t.printStackTrace();
}
return;
}
if(fileIsBroken && ex) {
fileIsBroken = false;
lastListLoad = 0l;
}
if(fileIsBroken) {
return;
}
long lastEdit = bansFile.lastModified();
if(cs != null || lastEdit - lastListLoad > 400l) {
try {
BufferedReader r = new BufferedReader(new FileReader(bansFile));
currentBanList = new LinkedList();
String s;
while((s = r.readLine()) != null) {
currentBanList.add(s);
}
r.close();
lastListLoad = lastEdit;
BungeeCord.getInstance().getLogger().info("Server bans.txt changed, it will be reloaded automatically");
if(cs == null) {
for(ProxiedPlayer pp : BungeeCord.getInstance().getPlayers()) {
if(pp.hasPermission("bungeecord.command.eag.reloadban")) {
pp.sendMessage(BanList.banChatMessagePrefix + ChatColor.WHITE + "Your Eaglercraftbungee bans.txt just got modified, it will be reloaded asap");
pp.sendMessage(BanList.banChatMessagePrefix + ChatColor.YELLOW + "Stop your server and check your config immediately if you don't know how this happened!!");
}
}
}
parseListFrom();
BungeeCord.getInstance().getLogger().info("Reload complete");
}catch(Throwable t) {
if(cs != null) {
cs.sendMessage(banChatMessagePrefix + ChatColor.RED + "Could not reload 'bans.txt' list file");
cs.sendMessage(banChatMessagePrefix + ChatColor.RED + "(Reason: " + t.toString() + ")");
}
BungeeCord.getInstance().getLogger().severe("Could not reload 'bans.txt' list file");
BungeeCord.getInstance().getLogger().severe("(Reason: " + t.toString() + ")");
}
}
}
}
private static void parseListFrom() {
userBans.clear();
ipBans.clear();
wildcardBans.clear();
regexBans.clear();
int filePart = 0;
boolean replaceBukkit = false;
for(String s : currentBanList) {
s = s.trim();
if(s.length() <= 0) {
continue;
}
if(s.startsWith("#")) {
continue;
}
if(s.equals("[Usernames]")) {
filePart = 1;
}else if(s.equals("[Wildcards]")) {
filePart = 2;
}else if(s.equals("[Regex]")) {
filePart = 3;
}else if(s.equals("[IPs]")) {
filePart = 4;
}else if(s.equals("replace-bukkit=true")) {
replaceBukkit = true;
}else if(s.equals("replace-bukkit=false")) {
continue;
}else {
if(filePart == 1) {
int i = s.indexOf(':');
if(i == -1) {
userBans.put(s.toLowerCase(), "");
}else {
userBans.put(s.substring(0, i).trim().toLowerCase(), s.substring(i + 1).trim());
}
}else if(filePart == 2) {
boolean ws = s.startsWith("*");
boolean we = s.endsWith("*");
if(!ws && !we) {
if(s.contains("*")) {
BungeeCord.getInstance().getLogger().severe("wildcard '" + s + "' contains a '*' not at the start/end of the string");
}else {
BungeeCord.getInstance().getLogger().severe("wildcard '" + s + "' does not contain a '*' wildcard character");
}
}else {
int total = (ws ? 1 : 0) + (we ? 1 : 0);
int t2 = 0;
for(char c : s.toCharArray()) {
if(c == '*') ++t2;
}
if(total != t2) {
BungeeCord.getInstance().getLogger().severe("wildcard '" + s + "' contains a '*' not at the start/end of the string");
}
}
wildcardBans.add(s.toLowerCase());
}else if(filePart == 3) {
Pattern p = null;
try {
p = Pattern.compile(s);
}catch(Throwable t) {
BungeeCord.getInstance().getLogger().severe("the regex " + s.toLowerCase() + " is invalid");
BungeeCord.getInstance().getLogger().severe("Reason: " + t.getClass().getSimpleName() + ": " + t.getLocalizedMessage());
}
if(p != null) {
regexBans.add(new RegexBan(s, p));
}
}else if(filePart == 4) {
String ss = s;
int subnet = -1;
int i = s.indexOf('/');
if(i != -1) {
String s2 = s.substring(i + 1);
s = s.substring(0, i);
try {
subnet = Integer.parseInt(s2);
}catch(Throwable t) {
BungeeCord.getInstance().getLogger().severe("the subnet '"+ s2 +"' for IP ban address " + s + " was invalid");
subnet = -2;
}
}
if(subnet >= -1) {
try {
InetAddress aa = InetAddress.getByName(s);
if(aa instanceof Inet4Address) {
if(subnet == -1) {
subnet = 32;
}
ipBans.add(new IPBan4((Inet4Address)aa, ss, subnet));
}else if(aa instanceof Inet6Address) {
if(subnet == -1) {
subnet = 128;
}
ipBans.add(new IPBan6((Inet6Address)aa, ss, subnet));
}else {
throw new UnknownHostException("Only ipv4 and ipv6 addresses allowed in Eaglercraft");
}
}catch(Throwable t) {
BungeeCord.getInstance().getLogger().severe("the IP ban address " + s + " could not be parsed");
t.printStackTrace();
}
}
}
}
}
BungeeCord.getInstance().reconfigureBanCommands(replaceBukkit);
}
}

View File

@ -0,0 +1,359 @@
package net.md_5.bungee.eaglercraft;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.api.config.ConfigurationAdapter;
import net.md_5.bungee.config.Configuration;
public class DomainBlacklist {
public static final Collection<Pattern> regexBlacklist = new ArrayList();
public static final Collection<Pattern> regexLocalBlacklist = new ArrayList();
public static final Collection<Pattern> regexBlacklistReplit = new ArrayList();
public static final Collection<String> simpleWhitelist = new ArrayList();
public static final File localBlacklist = new File("origin_blacklist.txt");
private static Collection<String> blacklistSubscriptions = null;
private static boolean blockOfflineDownload = false;
private static boolean blockAllReplits = false;
private static boolean localWhitelistMode = false;
private static boolean simpleWhitelistMode = false;
private static final HashSet<String> brokenURLs = new HashSet();
private static final HashSet<String> brokenRegex = new HashSet();
public static final HashSet<String> regexBlacklistReplitInternalStrings = new HashSet();
public static final Collection<Pattern> regexBlacklistReplitInternal = new ArrayList();
static {
regexBlacklistReplitInternalStrings.add(".*repl(it)?\\..{1,5}$");
for(String s : regexBlacklistReplitInternalStrings) {
regexBlacklistReplitInternal.add(Pattern.compile(s));
}
}
private static int updateRate = 15 * 60 * 1000;
private static long lastLocalUpdate = 0l;
private static long lastUpdate = 0;
public static boolean test(String origin) {
synchronized(regexBlacklist) {
if(blockOfflineDownload && origin.equalsIgnoreCase("null")) {
return true;
}
if(simpleWhitelistMode) {
for(String st : simpleWhitelist) {
if(origin.equalsIgnoreCase(st)) {
return false;
}
}
}
if(localWhitelistMode || simpleWhitelistMode) {
if(!blockOfflineDownload && origin.equalsIgnoreCase("null")) {
return false;
}
for(Pattern m : regexLocalBlacklist) {
if(m.matcher(origin).matches()) {
return false;
}
}
return true;
}else {
if(blockAllReplits) {
for(Pattern m : regexBlacklistReplitInternal) {
if(m.matcher(origin).matches()) {
return true;
}
}
for(Pattern m : regexBlacklistReplit) {
if(m.matcher(origin).matches()) {
return true;
}
}
}
for(Pattern m : regexBlacklist) {
if(m.matcher(origin).matches()) {
return true;
}
}
for(Pattern m : regexLocalBlacklist) {
if(m.matcher(origin).matches()) {
return true;
}
}
return false;
}
}
}
public static void init(BungeeCord bg) {
synchronized(regexBlacklist) {
brokenURLs.clear();
brokenRegex.clear();
regexBlacklist.clear();
regexLocalBlacklist.clear();
regexBlacklistReplit.clear();
simpleWhitelist.clear();
ConfigurationAdapter cfg2 = bg.getConfigurationAdapter();
Configuration cfg = bg.config;
blacklistSubscriptions = cfg2.getBlacklistURLs();
blockOfflineDownload = cfg.shouldBlacklistOfflineDownload();
blockAllReplits = cfg.shouldBlacklistReplits();
simpleWhitelistMode = cfg.isSimpleWhitelistEnabled();
simpleWhitelist.addAll(cfg2.getBlacklistSimpleWhitelist());
lastLocalUpdate = 0l;
lastUpdate = System.currentTimeMillis() - updateRate - 1000l;
update();
}
}
public static void update() {
long ct = System.currentTimeMillis();
if((int)(ct - lastUpdate) > updateRate) {
lastUpdate = ct;
synchronized(regexBlacklist) {
if(blacklistSubscriptions != null) {
ArrayList<Pattern> newBlacklist = new ArrayList();
ArrayList<Pattern> newReplitBlacklist = new ArrayList();
HashSet<String> newBlacklistSet = new HashSet();
newBlacklistSet.addAll(regexBlacklistReplitInternalStrings);
for(String str : blacklistSubscriptions) {
try {
URL u;
try {
u = new URL(str);
}catch(MalformedURLException e) {
if(brokenURLs.add(str)) {
BungeeCord.getInstance().getLogger().severe("The blacklist subscription URL '" + str + "' is invalid");
}
continue;
}
URLConnection cc = u.openConnection();
if(cc instanceof HttpURLConnection) {
HttpURLConnection ccc = (HttpURLConnection)cc;
ccc.setRequestProperty("Accept", "text/plain,text/html,application/xhtml+xml,application/xml");
ccc.setRequestProperty("User-Agent", "Mozilla/5.0 EaglercraftBungee/" + EaglercraftBungee.version);
}
cc.connect();
BufferedReader is = new BufferedReader(new InputStreamReader(cc.getInputStream()));
String firstLine = is.readLine();
if(firstLine == null) {
is.close();
throw new IOException("Could not read line");
}
firstLine = firstLine.trim();
if(!firstLine.startsWith("#") || !firstLine.substring(1).trim().toLowerCase().startsWith("eaglercraft domain blacklist")) {
throw new IOException("File does not contain a list of domains");
}
String ss;
while((ss = is.readLine()) != null) {
if((ss = ss.trim()).length() > 0) {
if(ss.startsWith("#")) {
ss = ss.substring(1).trim();
if(ss.startsWith("replit-wildcard:")) {
ss = ss.substring(16).trim();
if(newBlacklistSet.add(ss)) {
try {
newReplitBlacklist.add(Pattern.compile(ss));
}catch(PatternSyntaxException shit) {
if(brokenRegex.add(ss)) {
BungeeCord.getInstance().getLogger().severe("the blacklist replit wildcard regex '" + ss + "' is invalid");
continue;
}
}
brokenRegex.remove(ss);
}
}
continue;
}
if(newBlacklistSet.add(ss)) {
try {
newBlacklist.add(Pattern.compile(ss));
}catch(PatternSyntaxException shit) {
if(brokenRegex.add(ss)) {
BungeeCord.getInstance().getLogger().severe("the blacklist regex '" + ss + "' is invalid");
continue;
}
}
brokenRegex.remove(ss);
}
}
}
is.close();
brokenURLs.remove(str);
}catch(Throwable t) {
if(brokenURLs.add(str)) {
BungeeCord.getInstance().getLogger().severe("the blacklist subscription URL '" + str + "' is invalid");
}
t.printStackTrace();
}
}
if(!newBlacklist.isEmpty()) {
regexBlacklist.clear();
regexBlacklist.addAll(newBlacklist);
}
if(!newReplitBlacklist.isEmpty()) {
regexBlacklistReplit.clear();
regexBlacklistReplit.addAll(newReplitBlacklist);
}
}else {
brokenURLs.clear();
brokenRegex.clear();
regexBlacklist.clear();
lastLocalUpdate = 0l;
}
}
}
if(localBlacklist.exists()) {
long lastLocalEdit = localBlacklist.lastModified();
if(lastLocalEdit != lastLocalUpdate) {
lastLocalUpdate = lastLocalEdit;
synchronized(regexBlacklist) {
try {
BufferedReader is = new BufferedReader(new FileReader(localBlacklist));
regexLocalBlacklist.clear();
localWhitelistMode = false;
boolean foundWhitelistStatement = false;
String ss;
while((ss = is.readLine()) != null) {
try {
if((ss = ss.trim()).length() > 0) {
if(!ss.startsWith("#")) {
regexLocalBlacklist.add(Pattern.compile(ss));
}else {
String st = ss.substring(1).trim();
if(st.startsWith("whitelistMode:")) {
foundWhitelistStatement = true;
String str = st.substring(14).trim().toLowerCase();
localWhitelistMode = str.equals("true") || str.equals("on") || str.equals("1");
}
}
}
}catch(PatternSyntaxException shit) {
BungeeCord.getInstance().getLogger().severe("the local " + (localWhitelistMode ? "whitelist" : "blacklist") + " regex '" + ss + "' is invalid");
}
}
is.close();
if(!foundWhitelistStatement) {
List<String> newLines = new ArrayList();
newLines.add("#whitelistMode: false");
newLines.add("");
BufferedReader is2 = new BufferedReader(new FileReader(localBlacklist));
while((ss = is2.readLine()) != null) {
newLines.add(ss);
}
is2.close();
PrintWriter os = new PrintWriter(new FileWriter(localBlacklist));
for(String str : newLines) {
os.println(str);
}
os.close();
lastLocalUpdate = localBlacklist.lastModified();
}
BungeeCord.getInstance().getLogger().info("Reloaded '" + localBlacklist.getName() + "'.");
}catch(IOException ex) {
regexLocalBlacklist.clear();
BungeeCord.getInstance().getLogger().severe("failed to read local " + (localWhitelistMode ? "whitelist" : "blacklist") + " file '" + localBlacklist.getName() + "'");
ex.printStackTrace();
}
}
}
}else {
synchronized(regexBlacklist) {
if(!regexLocalBlacklist.isEmpty()) {
BungeeCord.getInstance().getLogger().warning("the blacklist file '" + localBlacklist.getName() + "' has been deleted");
}
regexLocalBlacklist.clear();
}
}
}
public static void addLocal(String o) {
String p = "^" + Pattern.quote(o.trim()) + "$";
ArrayList<String> lines = new ArrayList();
if(localBlacklist.exists()) {
try {
BufferedReader is = new BufferedReader(new FileReader(localBlacklist));
String ss;
while((ss = is.readLine()) != null) {
if((ss = ss.trim()).length() > 0) {
lines.add(ss);
}
}
is.close();
}catch(IOException ex) {
// ?
}
}
if(lines.isEmpty()) {
lines.add("#whitelist false");
lines.add("");
}
if(!lines.contains(p)) {
lines.add(p);
try {
PrintWriter os = new PrintWriter(new FileWriter(localBlacklist));
for(String s : lines) {
os.println(s);
}
os.close();
lastLocalUpdate = 0l;
update();
}catch(IOException ex) {
// ?
}
}
}
public static boolean removeLocal(String o) {
String p = "^" + Pattern.quote(o.trim()) + "$";
ArrayList<String> lines = new ArrayList();
if(localBlacklist.exists()) {
try {
BufferedReader is = new BufferedReader(new FileReader(localBlacklist));
String ss;
while((ss = is.readLine()) != null) {
if((ss = ss.trim()).length() > 0) {
lines.add(ss);
}
}
is.close();
}catch(IOException ex) {
// ?
}
}
if(lines.contains(p)) {
lines.remove(p);
try {
PrintWriter os = new PrintWriter(new FileWriter(localBlacklist));
for(String s : lines) {
os.println(s);
}
os.close();
lastLocalUpdate = 0l;
update();
return true;
}catch(IOException ex) {
BungeeCord.getInstance().getLogger().severe("Failed to save '" + localBlacklist.getName() + "'");
ex.printStackTrace();
}
}
return false;
}
}

View File

@ -0,0 +1,10 @@
package net.md_5.bungee.eaglercraft;
public class EaglercraftBungee {
public static final String brand = "Eagtek";
public static final String name = "EaglercraftBungee";
public static final String version = "0.4.0"; // wtf does this even mean at this point
public static final boolean cracked = true;
}

View File

@ -0,0 +1,71 @@
package net.md_5.bungee.eaglercraft;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
// note that there's a few things not implemented, but I don't care.
public class ExpiringSet<T> extends HashSet<T> {
private final long expiration;
private final ExpiringEvent<T> event;
private final Map<T, Long> timestamps = new HashMap<>();
public ExpiringSet(long expiration) {
this.expiration = expiration;
this.event = null;
}
public ExpiringSet(long expiration, ExpiringEvent<T> event) {
this.expiration = expiration;
this.event = event;
}
public interface ExpiringEvent<T> {
void onExpiration(T item);
}
public void checkForExpirations() {
Iterator<T> iterator = this.timestamps.keySet().iterator();
long now = System.currentTimeMillis();
while (iterator.hasNext()) {
T element = iterator.next();
if (super.contains(element)) {
if (this.timestamps.get(element) + this.expiration < now) {
if (this.event != null) this.event.onExpiration(element);
iterator.remove();
super.remove(element);
}
} else {
iterator.remove();
super.remove(element);
}
}
}
public boolean add(T o) {
checkForExpirations();
boolean success = super.add(o);
if (success) timestamps.put(o, System.currentTimeMillis());
return success;
}
public boolean remove(Object o) {
checkForExpirations();
boolean success = super.remove(o);
if (success) timestamps.remove(o);
return success;
}
public void clear() {
this.timestamps.clear();
super.clear();
}
public boolean contains(Object o) {
checkForExpirations();
return super.contains(o);
}
}

View File

@ -0,0 +1,124 @@
package net.md_5.bungee.eaglercraft;
/**
* base implementation of MD4 family style digest as outlined in
* "Handbook of Applied Cryptography", pages 344 - 347.
*/
public abstract class GeneralDigest {
private byte[] xBuf;
private int xBufOff;
private long byteCount;
/**
* Standard constructor
*/
protected GeneralDigest()
{
xBuf = new byte[4];
xBufOff = 0;
}
/**
* Copy constructor. We are using copy constructors in place
* of the Object.clone() interface as this interface is not
* supported by J2ME.
*/
protected GeneralDigest(GeneralDigest t)
{
xBuf = new byte[t.xBuf.length];
System.arraycopy(t.xBuf, 0, xBuf, 0, t.xBuf.length);
xBufOff = t.xBufOff;
byteCount = t.byteCount;
}
public void update(
byte in)
{
xBuf[xBufOff++] = in;
if (xBufOff == xBuf.length)
{
processWord(xBuf, 0);
xBufOff = 0;
}
byteCount++;
}
public void update(
byte[] in,
int inOff,
int len)
{
//
// fill the current word
//
while ((xBufOff != 0) && (len > 0))
{
update(in[inOff]);
inOff++;
len--;
}
//
// process whole words.
//
while (len > xBuf.length)
{
processWord(in, inOff);
inOff += xBuf.length;
len -= xBuf.length;
byteCount += xBuf.length;
}
//
// load in the remainder.
//
while (len > 0)
{
update(in[inOff]);
inOff++;
len--;
}
}
public void finish()
{
long bitLength = (byteCount << 3);
//
// add the pad bytes.
//
update((byte)128);
while (xBufOff != 0)
{
update((byte)0);
}
processLength(bitLength);
processBlock();
}
public void reset()
{
byteCount = 0;
xBufOff = 0;
for ( int i = 0; i < xBuf.length; i++ ) {
xBuf[i] = 0;
}
}
protected abstract void processWord(byte[] in, int inOff);
protected abstract void processLength(long bitLength);
protected abstract void processBlock();
}

View File

@ -0,0 +1,202 @@
package net.md_5.bungee.eaglercraft;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.java_websocket.WebSocket;
import org.json.JSONArray;
import org.json.JSONObject;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.MOTD;
import net.md_5.bungee.api.config.ListenerInfo;
import net.md_5.bungee.api.config.MOTDCacheConfiguration;
import net.md_5.bungee.api.connection.ProxiedPlayer;
public class MOTDConnectionImpl extends QueryConnectionImpl implements MOTD {
private String line1;
private String line2;
private List<String> players;
private int[] bitmap;
private int onlinePlayers;
private int maxPlayers;
private boolean hasIcon;
private boolean iconDirty;
private String subType;
public MOTDConnectionImpl(ListenerInfo listener, InetAddress addr, WebSocket socket, String arg1) {
super(listener, addr, socket, "motd");
String[] lns = listener.getMotd().split("\n");
if(lns.length >= 1) {
line1 = lns[0];
}
if(lns.length >= 2) {
line2 = lns[1];
}
maxPlayers = listener.getMaxPlayers();
onlinePlayers = BungeeCord.getInstance().getOnlineCount();
players = new ArrayList();
for(ProxiedPlayer pp : BungeeCord.getInstance().getPlayers()) {
players.add(pp.getDisplayName());
if(players.size() >= 9) {
players.add("" + ChatColor.GRAY + ChatColor.ITALIC + "(" + (onlinePlayers - players.size()) + " more)");
break;
}
}
bitmap = new int[4096];
setReturnType("motd");
int i = arg1.indexOf('.');
if(i > 0) {
subType = arg1.substring(i + 1);
if(subType.length() == 0) {
subType = "motd";
}
}else {
subType = "motd";
}
if(!subType.startsWith("noicon") && !subType.startsWith("cache.noicon")) {
iconDirty = hasIcon = listener.isIconSet();
if(hasIcon) {
System.arraycopy(listener.getServerIconCache(), 0, bitmap, 0, 4096);
}
}
}
@Override
public String getLine1() {
return line1;
}
@Override
public String getLine2() {
return line2;
}
@Override
public List<String> getPlayerList() {
return players;
}
@Override
public int[] getBitmap() {
return bitmap;
}
@Override
public int getOnlinePlayers() {
return onlinePlayers;
}
@Override
public int getMaxPlayers() {
return maxPlayers;
}
@Override
public String getSubType() {
return subType;
}
@Override
public void setLine1(String p) {
line1 = p;
}
@Override
public void setLine2(String p) {
line2 = p;
}
@Override
public void setPlayerList(List<String> p) {
players = p;
}
@Override
public void setPlayerList(String... p) {
players = Arrays.asList(p);
}
@Override
public void setBitmap(int[] p) {
iconDirty = hasIcon = true;
bitmap = p;
}
@Override
public void setOnlinePlayers(int i) {
onlinePlayers = i;
}
@Override
public void setMaxPlayers(int i) {
maxPlayers = i;
}
@Override
public void sendToUser() {
if(!isClosed()) {
JSONObject obj = new JSONObject();
if(subType.startsWith("cache.anim")) {
obj.put("unsupported", true);
writeResponse(obj);
close();
return;
}else if(subType.startsWith("cache")) {
JSONArray cacheControl = new JSONArray();
MOTDCacheConfiguration cc = listener.getCacheConfig();
if(cc.cacheServerListAnimation) {
cacheControl.put("animation");
}
if(cc.cacheServerListResults) {
cacheControl.put("results");
}
if(cc.cacheServerListTrending) {
cacheControl.put("trending");
}
if(cc.cacheServerListPortfolios) {
cacheControl.put("portfolio");
}
obj.put("cache", cacheControl);
obj.put("ttl", cc.cacheTTL);
}else {
MOTDCacheConfiguration cc = listener.getCacheConfig();
obj.put("cache", cc.cacheServerListAnimation || cc.cacheServerListResults ||
cc.cacheServerListTrending || cc.cacheServerListPortfolios);
}
boolean noIcon = subType.startsWith("noicon") || subType.startsWith("cache.noicon");
JSONArray motd = new JSONArray();
if(line1 != null && line1.length() > 0) motd.put(line1);
if(line2 != null && line2.length() > 0) motd.put(line2);
obj.put("motd", motd);
obj.put("icon", hasIcon && !noIcon);
obj.put("online", onlinePlayers);
obj.put("max", maxPlayers);
JSONArray playerz = new JSONArray();
for(String s : players) {
playerz.put(s);
}
obj.put("players", playerz);
writeResponse(obj);
if(hasIcon && !noIcon && iconDirty && bitmap != null) {
byte[] iconPixels = new byte[16384];
for(int i = 0; i < 4096; ++i) {
iconPixels[i * 4] = (byte)((bitmap[i] >> 16) & 0xFF);
iconPixels[i * 4 + 1] = (byte)((bitmap[i] >> 8) & 0xFF);
iconPixels[i * 4 + 2] = (byte)(bitmap[i] & 0xFF);
iconPixels[i * 4 + 3] = (byte)((bitmap[i] >> 24) & 0xFF);
}
writeResponseBinary(iconPixels);
iconDirty = false;
}
if(subType.startsWith("cache")) {
close();
}
}
}
}

View File

@ -1,55 +0,0 @@
package net.md_5.bungee.eaglercraft;
import java.util.Collections;
import java.util.HashSet;
import net.md_5.bungee.PacketConstants;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.PostLoginEvent;
import net.md_5.bungee.api.event.ServerConnectEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.api.plugin.PluginDescription;
import net.md_5.bungee.event.EventHandler;
import net.md_5.bungee.protocol.packet.Packet1Login;
import net.md_5.bungee.protocol.packet.Packet9Respawn;
public class PluginEaglerAuth extends Plugin implements Listener {
public PluginEaglerAuth() {
super(new PluginDescription("EaglerAuth", PluginEaglerAuth.class.getName(), "1.0.0", "LAX1DUDE", Collections.emptySet(), null));
}
@Override
public void onLoad() {
}
@Override
public void onEnable() {
getProxy().getPluginManager().registerListener(this, this);
}
@Override
public void onDisable() {
}
private final HashSet<ProxiedPlayer> playersInLimbo = new HashSet();
@EventHandler
public void onPostLogin(PostLoginEvent event) {
//playersInLimbo.add(event.getPlayer());
}
@EventHandler
public void onServerConnect(ServerConnectEvent event) {
ProxiedPlayer player = event.getPlayer();
//if(playersInLimbo.contains(player)) {
// event.setCancelled(true);
// player.unsafe().sendPacket(new Packet1Login(0, "END", (byte) 1, 1, (byte) 0, (byte) 0, (byte) 1));
// player.unsafe().sendPacket(new Packet9Respawn(1, (byte) 0, (byte) 1, (short) 255, "END"));
//}
}
}

View File

@ -1,10 +1,13 @@
package net.md_5.bungee.eaglercraft;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
import net.md_5.bungee.api.event.PluginMessageEvent;
import net.md_5.bungee.api.plugin.Listener;
@ -13,12 +16,13 @@ import net.md_5.bungee.api.plugin.PluginDescription;
import net.md_5.bungee.event.EventHandler;
public class PluginEaglerSkins extends Plugin implements Listener {
private final HashMap<String,byte[]> skinCollection = new HashMap();
private static final int[] SKIN_DATA_SIZE = new int[] { 64*32*4, 64*64*4, 128*64*4, 128*128*4, 1 };
private static final int VALID_DEFAULT_SKINS = 33;
private final HashMap<String,byte[]> capeCollection = new HashMap();
private final HashMap<String,Long> lastSkinLayerUpdate = new HashMap();
private static final int[] SKIN_DATA_SIZE = new int[] { 64*32*4, 64*64*4, -9, -9, 1, 64*64*4, -9 }; // 128 pixel skins crash clients
private static final int[] CAPE_DATA_SIZE = new int[] { 32*32*4, -9, 1 };
public PluginEaglerSkins() {
super(new PluginDescription("EaglerSkins", PluginEaglerSkins.class.getName(), "1.0.0", "LAX1DUDE", Collections.emptySet(), null));
@ -41,34 +45,78 @@ public class PluginEaglerSkins extends Plugin implements Listener {
if(event.getSender() instanceof UserConnection && event.getData().length > 0) {
String user = ((UserConnection)event.getSender()).getName();
byte[] msg = event.getData();
if("EAG|MySkin".equals(event.getTag())) {
int t = (int)msg[0] & 0xFF;
if(t >= 0 && t < SKIN_DATA_SIZE.length && msg.length == (SKIN_DATA_SIZE[t] + 1)) {
if(msg.length == 2) {
if(((int)msg[1] & 0xFF) >= VALID_DEFAULT_SKINS) {
msg[1] = 0;
try {
event.setCancelled(true);
if("EAG|MySkin".equals(event.getTag())) {
if(!skinCollection.containsKey(user)) {
int t = (int)msg[0] & 0xFF;
if(t < SKIN_DATA_SIZE.length && msg.length == (SKIN_DATA_SIZE[t] + 1)) {
skinCollection.put(user, msg);
}
}
skinCollection.put(user, msg);
}
}else if("EAG|FetchSkin".equals(event.getTag())) {
if(msg.length > 2) {
String fetch = new String(msg, 2, msg.length - 2, StandardCharsets.UTF_8);
byte[] data;
if((data = skinCollection.get(fetch)) != null) {
byte[] conc = new byte[data.length + 2];
conc[0] = msg[0]; conc[1] = msg[1]; //synchronization cookie
System.arraycopy(data, 0, conc, 2, data.length);
((UserConnection)event.getSender()).sendData("EAG|UserSkin", conc);
}else if("EAG|MyCape".equals(event.getTag())) {
if(!capeCollection.containsKey(user)) {
int t = (int)msg[0] & 0xFF;
if(t < CAPE_DATA_SIZE.length && msg.length == (CAPE_DATA_SIZE[t] + 2)) {
capeCollection.put(user, msg);
}
}
}else if("EAG|FetchSkin".equals(event.getTag())) {
if(msg.length > 2) {
String fetch = new String(msg, 2, msg.length - 2, StandardCharsets.UTF_8);
byte[] data;
if((data = skinCollection.get(fetch)) != null) {
byte[] conc = new byte[data.length + 2];
conc[0] = msg[0]; conc[1] = msg[1]; //synchronization cookie
System.arraycopy(data, 0, conc, 2, data.length);
if((data = capeCollection.get(fetch)) != null) {
byte[] conc2 = new byte[conc.length + data.length];
System.arraycopy(conc, 0, conc2, 0, conc.length);
System.arraycopy(data, 0, conc2, conc.length, data.length);
conc = conc2;
}
((UserConnection)event.getSender()).sendData("EAG|UserSkin", conc);
}
}
}else if("EAG|SkinLayers".equals(event.getTag())) {
long millis = System.currentTimeMillis();
Long lsu = lastSkinLayerUpdate.get(user);
if(lsu != null && millis - lsu < 700L) { // DoS protection
return;
}
lastSkinLayerUpdate.put(user, millis);
byte[] data;
if((data = capeCollection.get(user)) != null) {
data[1] = msg[0];
}else {
data = new byte[] { (byte)2, msg[0], (byte)0 };
capeCollection.put(user, data);
}
ByteArrayOutputStream bao = new ByteArrayOutputStream();
DataOutputStream dd = new DataOutputStream(bao);
dd.write(msg[0]);
dd.writeUTF(user);
byte[] bpacket = bao.toByteArray();
for(ProxiedPlayer pl : getProxy().getPlayers()) {
if(!pl.getName().equals(user)) {
pl.sendData("EAG|SkinLayers", bpacket);
}
}
}else {
event.setCancelled(false);
}
}catch(Throwable t) {
// hacker
}
}
}
@EventHandler
public void onPlayerDisconnect(PlayerDisconnectEvent event) {
skinCollection.remove(event.getPlayer().getName());
String nm = event.getPlayer().getName();
skinCollection.remove(nm);
capeCollection.remove(nm);
lastSkinLayerUpdate.remove(nm);
}
}

View File

@ -0,0 +1,248 @@
package net.md_5.bungee.eaglercraft;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
import net.md_5.bungee.api.event.PluginMessageEvent;
import net.md_5.bungee.api.event.PostLoginEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.api.plugin.PluginDescription;
import net.md_5.bungee.event.EventHandler;
public class PluginEaglerVoice extends Plugin implements Listener {
private final boolean voiceEnabled;
private final Map<String, UserConnection> voicePlayers = new HashMap<>();
private final Map<String, ExpiringSet<String>> voiceRequests = new HashMap<>();
private final Set<String[]> voicePairs = new HashSet<>();
private static final int VOICE_SIGNAL_ALLOWED = 0;
private static final int VOICE_SIGNAL_REQUEST = 0;
private static final int VOICE_SIGNAL_CONNECT = 1;
private static final int VOICE_SIGNAL_DISCONNECT = 2;
private static final int VOICE_SIGNAL_ICE = 3;
private static final int VOICE_SIGNAL_DESC = 4;
private static final int VOICE_SIGNAL_GLOBAL = 5;
public PluginEaglerVoice(boolean voiceEnabled) {
super(new PluginDescription("EaglerVoice", PluginEaglerVoice.class.getName(), "1.0.0", "ayunami2000", Collections.emptySet(), null));
this.voiceEnabled = voiceEnabled;
}
public void onLoad() {
}
public void onEnable() {
getProxy().getPluginManager().registerListener(this, this);
}
public void onDisable() {
}
@EventHandler
public void onPluginMessage(PluginMessageEvent event) {
synchronized (voicePlayers) {
if (!voiceEnabled) return;
if (event.getSender() instanceof UserConnection && event.getData().length > 0) {
UserConnection connection = (UserConnection) event.getSender();
String user = connection.getName();
byte[] msg = event.getData();
try {
if (!("EAG|Voice".equals(event.getTag()))) return;
event.setCancelled(true);
DataInputStream streamIn = new DataInputStream(new ByteArrayInputStream(msg));
int sig = streamIn.read();
switch (sig) {
case VOICE_SIGNAL_CONNECT:
if (voicePlayers.containsKey(user)) return; // user is already using voice chat
// send out packet for player joined voice
// notice: everyone on the server can see this packet!! however, it doesn't do anything but let clients know that the player has turned on voice chat
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
dos.write(VOICE_SIGNAL_CONNECT);
dos.writeUTF(user);
byte[] out = baos.toByteArray();
for (UserConnection conn : voicePlayers.values()) conn.sendData("EAG|Voice", out);
voicePlayers.put(user, connection);
for (String username : voicePlayers.keySet()) sendVoicePlayers(username);
break;
case VOICE_SIGNAL_DISCONNECT:
if (!voicePlayers.containsKey(user)) return; // user is not using voice chat
try {
String user2 = streamIn.readUTF();
if (!voicePlayers.containsKey(user2)) return;
if (voicePairs.removeIf(pair -> (pair[0].equals(user) && pair[1].equals(user2)) || (pair[0].equals(user2) && pair[1].equals(user)))) {
ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
DataOutputStream dos2 = new DataOutputStream(baos2);
dos2.write(VOICE_SIGNAL_DISCONNECT);
dos2.writeUTF(user);
voicePlayers.get(user2).sendData("EAG|Voice", baos2.toByteArray());
baos2 = new ByteArrayOutputStream();
dos2 = new DataOutputStream(baos2);
dos2.write(VOICE_SIGNAL_DISCONNECT);
dos2.writeUTF(user2);
connection.sendData("EAG|Voice", baos2.toByteArray());
}
} catch (EOFException e) {
removeUser(user);
}
break;
case VOICE_SIGNAL_REQUEST:
if (!voicePlayers.containsKey(user)) return; // user is not using voice chat
String targetUser = streamIn.readUTF();
if (user.equals(targetUser)) return; // prevent duplicates
if (checkVoicePair(user, targetUser)) return; // already paired
if (!voicePlayers.containsKey(targetUser)) return; // target user is not using voice chat
if (!voiceRequests.containsKey(user)) voiceRequests.put(user, new ExpiringSet<>(2000));
if (voiceRequests.get(user).contains(targetUser)) return;
voiceRequests.get(user).add(targetUser);
// check if other has requested earlier
if (voiceRequests.containsKey(targetUser) && voiceRequests.get(targetUser).contains(user)) {
if (voiceRequests.containsKey(targetUser)) {
voiceRequests.get(targetUser).remove(user);
if (voiceRequests.get(targetUser).isEmpty()) voiceRequests.remove(targetUser);
}
if (voiceRequests.containsKey(user)) {
voiceRequests.get(user).remove(targetUser);
if (voiceRequests.get(user).isEmpty()) voiceRequests.remove(user);
}
// send each other add data
voicePairs.add(new String[]{user, targetUser});
ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
DataOutputStream dos2 = new DataOutputStream(baos2);
dos2.write(VOICE_SIGNAL_CONNECT);
dos2.writeUTF(user);
dos2.writeBoolean(false);
voicePlayers.get(targetUser).sendData("EAG|Voice", baos2.toByteArray());
baos2 = new ByteArrayOutputStream();
dos2 = new DataOutputStream(baos2);
dos2.write(VOICE_SIGNAL_CONNECT);
dos2.writeUTF(targetUser);
dos2.writeBoolean(true);
connection.sendData("EAG|Voice", baos2.toByteArray());
}
break;
case VOICE_SIGNAL_ICE:
case VOICE_SIGNAL_DESC:
if (!voicePlayers.containsKey(user)) return; // user is not using voice chat
String targetUser2 = streamIn.readUTF();
if (checkVoicePair(user, targetUser2)) {
String data = streamIn.readUTF();
ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
DataOutputStream dos2 = new DataOutputStream(baos2);
dos2.write(sig);
dos2.writeUTF(user);
dos2.writeUTF(data);
voicePlayers.get(targetUser2).sendData("EAG|Voice", baos2.toByteArray());
}
break;
default:
break;
}
} catch (Throwable t) {
// hacker
// t.printStackTrace(); // todo: remove in production
removeUser(user);
}
}
}
}
@EventHandler
public void onPostLogin(PostLoginEvent event) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
dos.write(VOICE_SIGNAL_ALLOWED);
dos.writeBoolean(voiceEnabled);
Collection<String> servs = BungeeCord.getInstance().config.getICEServers();
dos.write(servs.size());
for(String str : servs) {
dos.writeUTF(str);
}
event.getPlayer().sendData("EAG|Voice", baos.toByteArray());
sendVoicePlayers(event.getPlayer().getName());
} catch (IOException ignored) { }
}
@EventHandler
public void onPlayerDisconnect(PlayerDisconnectEvent event) {
String nm = event.getPlayer().getName();
removeUser(nm);
}
public void sendVoicePlayers(String name) {
synchronized (voicePlayers) {
if (!voiceEnabled) return;
if (!voicePlayers.containsKey(name)) return;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
dos.write(VOICE_SIGNAL_GLOBAL);
Set<String> mostlyGlobalPlayers = new HashSet<>();
for (String username : voicePlayers.keySet()) {
if (username.equals(name)) continue;
if (voicePairs.stream().anyMatch(pair -> (pair[0].equals(name) && pair[1].equals(username)) || (pair[0].equals(username) && pair[1].equals(name))))
continue;
mostlyGlobalPlayers.add(username);
}
if (mostlyGlobalPlayers.size() > 0) {
dos.writeInt(mostlyGlobalPlayers.size());
for (String username : mostlyGlobalPlayers) dos.writeUTF(username);
voicePlayers.get(name).sendData("EAG|Voice", baos.toByteArray());
}
} catch (IOException ignored) {
}
}
}
public void removeUser(String name) {
synchronized (voicePlayers) {
voicePlayers.remove(name);
for (String username : voicePlayers.keySet()) {
if (!name.equals(username)) sendVoicePlayers(username);
}
for (String[] voicePair : voicePairs) {
String target = null;
if (voicePair[0].equals(name)) {
target = voicePair[1];
} else if (voicePair[1].equals(name)) {
target = voicePair[0];
}
if (target != null && voicePlayers.containsKey(target)) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
dos.write(VOICE_SIGNAL_DISCONNECT);
dos.writeUTF(name);
voicePlayers.get(target).sendData("EAG|Voice", baos.toByteArray());
} catch (IOException ignored) {
}
}
}
voicePairs.removeIf(pair -> pair[0].equals(name) || pair[1].equals(name));
}
}
private boolean checkVoicePair(String user1, String user2) {
return voicePairs.stream().anyMatch(pair -> (pair[0].equals(user1) && pair[1].equals(user2)) || (pair[0].equals(user2) && pair[1].equals(user1)));
}
}

View File

@ -0,0 +1,121 @@
package net.md_5.bungee.eaglercraft;
import java.net.InetAddress;
import java.util.LinkedList;
import java.util.List;
import org.java_websocket.WebSocket;
import net.md_5.bungee.api.QueryConnection;
import net.md_5.bungee.api.config.ListenerInfo;
public class QueryConnectionImpl implements QueryConnection {
protected ListenerInfo listener;
protected InetAddress addr;
protected WebSocket socket;
protected String accept;
protected String responseType;
protected List<String> packetBuffer = new LinkedList();
protected long creationTime;
protected boolean keepAlive = false;
public static String confirmHash = null;
public QueryConnectionImpl(ListenerInfo listener, InetAddress addr, WebSocket socket, String accept) {
this.listener = listener;
this.addr = addr;
this.socket = socket;
this.accept = accept.toLowerCase();
this.responseType = "unknown";
this.creationTime = System.currentTimeMillis();
}
public void postMessage(String m) {
synchronized(packetBuffer) {
packetBuffer.add(m);
}
}
@Override
public InetAddress getRemoteAddress() {
return addr;
}
@Override
public ListenerInfo getListener() {
return listener;
}
@Override
public String getAccept() {
return accept;
}
@Override
public int availableRequests() {
synchronized(packetBuffer) {
return packetBuffer.size();
}
}
@Override
public String readRequestString() {
synchronized(packetBuffer) {
if(packetBuffer.size() > 0) {
return packetBuffer.remove(0);
}else {
return null;
}
}
}
@Override
public long getConnectionTimestamp() {
return creationTime;
}
@Override
public void writeResponseRaw(String msg) {
socket.send(msg);
}
@Override
public void writeResponseBinary(byte[] blob) {
socket.send(blob);
}
@Override
public void keepAlive(boolean yes) {
keepAlive = yes;
}
@Override
public boolean shouldKeepAlive() {
return keepAlive;
}
@Override
public boolean isClosed() {
return socket.isClosing() || socket.isClosed();
}
@Override
public void close() {
socket.close();
}
@Override
public void setReturnType(String type) {
if(!"unknown".equals(responseType) && !type.equalsIgnoreCase(responseType)) {
throw new IllegalStateException("Tried to change query return type to '" + type + "' when it was already set to '" + responseType + "'");
}
responseType = type;
}
@Override
public String getReturnType() {
return responseType;
}
}

View File

@ -0,0 +1,270 @@
package net.md_5.bungee.eaglercraft;
/**
* implementation of SHA-1 as outlined in "Handbook of Applied Cryptography", pages 346 - 349.
*
* It is interesting to ponder why the, apart from the extra IV, the other difference here from MD5
* is the "endienness" of the word processing!
*/
public class SHA1Digest
extends GeneralDigest
{
private static final int DIGEST_LENGTH = 20;
private int H1, H2, H3, H4, H5;
private int[] X = new int[80];
private int xOff;
/**
* Standard constructor
*/
public SHA1Digest()
{
reset();
}
/**
* Copy constructor. This will copy the state of the provided
* message digest.
*/
public SHA1Digest(SHA1Digest t)
{
super(t);
H1 = t.H1;
H2 = t.H2;
H3 = t.H3;
H4 = t.H4;
H5 = t.H5;
System.arraycopy(t.X, 0, X, 0, t.X.length);
xOff = t.xOff;
}
public String getAlgorithmName()
{
return "SHA-1";
}
public int getDigestSize()
{
return DIGEST_LENGTH;
}
protected void processWord(
byte[] in,
int inOff)
{
X[xOff++] = ((in[inOff] & 0xff) << 24) | ((in[inOff + 1] & 0xff) << 16)
| ((in[inOff + 2] & 0xff) << 8) | ((in[inOff + 3] & 0xff));
if (xOff == 16)
{
processBlock();
}
}
private void unpackWord(
int word,
byte[] out,
int outOff)
{
out[outOff] = (byte)(word >>> 24);
out[outOff + 1] = (byte)(word >>> 16);
out[outOff + 2] = (byte)(word >>> 8);
out[outOff + 3] = (byte)word;
}
protected void processLength(
long bitLength)
{
if (xOff > 14)
{
processBlock();
}
X[14] = (int)(bitLength >>> 32);
X[15] = (int)(bitLength & 0xffffffff);
}
public int doFinal(
byte[] out,
int outOff)
{
finish();
unpackWord(H1, out, outOff);
unpackWord(H2, out, outOff + 4);
unpackWord(H3, out, outOff + 8);
unpackWord(H4, out, outOff + 12);
unpackWord(H5, out, outOff + 16);
reset();
return DIGEST_LENGTH;
}
/**
* reset the chaining variables
*/
public void reset()
{
super.reset();
H1 = 0x67452301;
H2 = 0xefcdab89;
H3 = 0x98badcfe;
H4 = 0x10325476;
H5 = 0xc3d2e1f0;
xOff = 0;
for (int i = 0; i != X.length; i++)
{
X[i] = 0;
}
}
//
// Additive constants
//
private static final int Y1 = 0x5a827999;
private static final int Y2 = 0x6ed9eba1;
private static final int Y3 = 0x8f1bbcdc;
private static final int Y4 = 0xca62c1d6;
private int f(
int u,
int v,
int w)
{
return ((u & v) | ((~u) & w));
}
private int h(
int u,
int v,
int w)
{
return (u ^ v ^ w);
}
private int g(
int u,
int v,
int w)
{
return ((u & v) | (u & w) | (v & w));
}
private int rotateLeft(
int x,
int n)
{
return (x << n) | (x >>> (32 - n));
}
protected void processBlock()
{
//
// expand 16 word block into 80 word block.
//
for (int i = 16; i <= 79; i++)
{
X[i] = rotateLeft((X[i - 3] ^ X[i - 8] ^ X[i - 14] ^ X[i - 16]), 1);
}
//
// set up working variables.
//
int A = H1;
int B = H2;
int C = H3;
int D = H4;
int E = H5;
//
// round 1
//
for (int j = 0; j <= 19; j++)
{
int t = rotateLeft(A, 5) + f(B, C, D) + E + X[j] + Y1;
E = D;
D = C;
C = rotateLeft(B, 30);
B = A;
A = t;
}
//
// round 2
//
for (int j = 20; j <= 39; j++)
{
int t = rotateLeft(A, 5) + h(B, C, D) + E + X[j] + Y2;
E = D;
D = C;
C = rotateLeft(B, 30);
B = A;
A = t;
}
//
// round 3
//
for (int j = 40; j <= 59; j++)
{
int t = rotateLeft(A, 5) + g(B, C, D) + E + X[j] + Y3;
E = D;
D = C;
C = rotateLeft(B, 30);
B = A;
A = t;
}
//
// round 4
//
for (int j = 60; j <= 79; j++)
{
int t = rotateLeft(A, 5) + h(B, C, D) + E + X[j] + Y4;
E = D;
D = C;
C = rotateLeft(B, 30);
B = A;
A = t;
}
H1 += A;
H2 += B;
H3 += C;
H4 += D;
H5 += E;
//
// reset the offset and clean out the word buffer.
//
xOff = 0;
for (int i = 0; i != X.length; i++)
{
X[i] = 0;
}
}
private static final String hex = "0123456789abcdef";
public static String hash2string(byte[] b) {
char[] ret = new char[b.length * 2];
for(int i = 0; i < b.length; ++i) {
int bb = (int)b[i] & 0xFF;
ret[i * 2] = hex.charAt((bb >> 4) & 0xF);
ret[i * 2 + 1] = hex.charAt(bb & 0xF);
}
return new String(ret);
}
}

View File

@ -1,35 +1,91 @@
package net.md_5.bungee.eaglercraft;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import org.java_websocket.WebSocket;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.server.WebSocketServer;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.api.MOTD;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.config.ListenerInfo;
import net.md_5.bungee.api.event.WebsocketMOTDEvent;
import net.md_5.bungee.api.event.WebsocketQueryEvent;
import net.md_5.bungee.eaglercraft.WebSocketRateLimiter.RateLimit;
public class WebSocketListener extends WebSocketServer {
public static final String queryResponseBlocked = "{\"type\":\"blocked\"}";
public static final String queryResponseLockout = "{\"type\":\"locked\"}";
public static final String ipBlockedString = "BLOCKED";
public static final String ipLockedString = "LOCKED";
public static class PendingSocket {
public long openTime;
public InetAddress realAddress;
public String origin;
public boolean bypassBan;
protected PendingSocket(long openTime, InetAddress realAddress, String origin, boolean bypassBan) {
this.openTime = openTime;
this.realAddress = realAddress;
this.origin = origin;
this.bypassBan = bypassBan;
}
}
private InetSocketAddress bungeeProxy;
private ProxyServer bungeeCord;
private boolean blockOriginless;
private ListenerInfo info;
private final WebSocketRateLimiter ratelimitIP;
private final WebSocketRateLimiter ratelimitLogin;
private final WebSocketRateLimiter ratelimitMOTD;
private final WebSocketRateLimiter ratelimitQuery;
public WebSocketListener(ListenerInfo info, InetSocketAddress sock, ProxyServer bungeeCord) {
super(info.getHost());
this.setReuseAddr(true);
this.setTcpNoDelay(true);
this.setConnectionLostTimeout(5);
this.setConnectionLostTimeout(20);
this.start();
this.info = info;
this.bungeeProxy = sock;
this.bungeeCord = bungeeCord;
this.blockOriginless = ((BungeeCord)bungeeCord).config.shouldBlacklistOriginless();
this.ratelimitIP = info.getRateLimitIP();
this.ratelimitLogin = info.getRateLimitLogin();
this.ratelimitMOTD = info.getRateLimitMOTD();
this.ratelimitQuery = info.getRateLimitQuery();
if(this.ratelimitIP != null) {
this.ratelimitIP.resetLimiters();
}
if(this.ratelimitLogin != null) {
this.ratelimitLogin.resetLimiters();
}
if(this.ratelimitMOTD != null) {
this.ratelimitMOTD.resetLimiters();
}
if(this.ratelimitQuery != null) {
this.ratelimitQuery.resetLimiters();
}
}
@Override
public void onClose(WebSocket arg0, int arg1, String arg2, boolean arg3) {
if(arg0.getAttachment() != null) {
((WebSocketProxy)arg0.getAttachment()).killConnection();
Object o = arg0.getAttachment();
if(o != null) {
if(o instanceof WebSocketProxy) {
((WebSocketProxy)arg0.getAttachment()).killConnection();
}
}
System.out.println("websocket closed - " + arg0.getRemoteSocketAddress());
}
@Override
@ -39,28 +95,229 @@ public class WebSocketListener extends WebSocketServer {
@Override
public void onMessage(WebSocket arg0, String arg1) {
}
@Override
public void onMessage(WebSocket arg0, ByteBuffer arg1) {
if(arg0.getAttachment() != null) {
((WebSocketProxy)arg0.getAttachment()).sendPacket(arg1);
}
}
@Override
public void onOpen(WebSocket arg0, ClientHandshake arg1) {
System.out.println("websocket opened - " + arg0.getRemoteSocketAddress());
WebSocketProxy proxyObj = new WebSocketProxy(arg0, bungeeProxy);
arg0.setAttachment(proxyObj);
if(!proxyObj.connect()) {
Object o = arg0.getAttachment();
if(o != null) {
if(o instanceof PendingSocket) {
InetAddress realAddr = ((PendingSocket)o).realAddress;
arg1 = arg1.trim().toLowerCase();
QueryConnectionImpl con;
if(arg1.startsWith("accept:")) {
arg1 = arg1.substring(7).trim();
WebsocketQueryEvent evt;
if(arg1.startsWith("motd")) {
if(info.isAllowMOTD()) {
if(ratelimitMOTD != null && !BanList.isBlockedBan(realAddr)) {
RateLimit l = ratelimitMOTD.rateLimit(realAddr);
if(l.blocked()) {
if(l == RateLimit.LIMIT) {
arg0.send(queryResponseBlocked);
}else if(l == RateLimit.NOW_LOCKED_OUT) {
arg0.send(queryResponseLockout);
}
arg0.close();
return;
}
}
con = new MOTDConnectionImpl(info, realAddr, arg0, arg1);
evt = new WebsocketMOTDEvent((MOTD)con);
}else {
arg0.send(queryResponseBlocked);
arg0.close();
return;
}
}else {
if(QueryConnectionImpl.confirmHash != null && arg1.equalsIgnoreCase(QueryConnectionImpl.confirmHash)) {
QueryConnectionImpl.confirmHash = null;
arg0.send("OK");
arg0.close();
return;
}else if(info.isAllowQuery()) {
if(ratelimitQuery != null && !BanList.isBlockedBan(realAddr)) {
RateLimit l = ratelimitQuery.rateLimit(realAddr);
if(l.blocked()) {
if(l == RateLimit.LIMIT) {
arg0.send(queryResponseBlocked);
}else if(l == RateLimit.NOW_LOCKED_OUT) {
arg0.send(queryResponseLockout);
}
arg0.close();
return;
}
}
con = new QueryConnectionImpl(info, realAddr, arg0, arg1);
evt = new WebsocketQueryEvent(con);
}else {
arg0.send(queryResponseBlocked);
arg0.close();
return;
}
}
BungeeCord.getInstance().getPluginManager().callEvent(evt);
if(!con.isClosed() && (con instanceof MOTDConnectionImpl)) {
((MOTDConnectionImpl)con).sendToUser();
}
if(!con.shouldKeepAlive() && !con.isClosed()) {
con.close();
}else {
if(!arg0.isClosed()) {
arg0.setAttachment(con);
}
}
}else {
arg0.close();
}
return;
}else if(o instanceof QueryConnectionImpl) {
((QueryConnectionImpl)o).postMessage(arg1);
}
}else {
arg0.close();
}
}
@Override
public void onMessage(WebSocket arg0, ByteBuffer arg1) {
Object o = arg0.getAttachment();
if(o == null || (o instanceof PendingSocket)) {
InetAddress realAddr;
if(o == null) {
realAddr = arg0.getRemoteSocketAddress().getAddress();
}else {
realAddr = ((PendingSocket)o).realAddress;
}
if(ratelimitLogin != null && !BanList.isBlockedBan(realAddr)) {
RateLimit l = ratelimitLogin.rateLimit(realAddr);
if(l.blocked()) {
if(l == RateLimit.LIMIT) {
arg0.send(createRawKickPacket("BLOCKED"));
}else if(l == RateLimit.NOW_LOCKED_OUT) {
arg0.send(createRawKickPacket("LOCKED"));
}
arg0.close();
return;
}
}
WebSocketProxy proxyObj = new WebSocketProxy(arg0, realAddr, ((PendingSocket)o).origin, bungeeProxy);
arg0.setAttachment(proxyObj);
if(!proxyObj.connect()) {
System.err.println("loopback to '" + bungeeProxy.toString() + "' failed - " + realAddr);
arg0.close();
return;
}
o = proxyObj;
}
if(o != null) {
if(o instanceof WebSocketProxy) {
((WebSocketProxy)o).sendPacket(arg1);
}else {
arg0.close();
}
}
}
@Override
public void onOpen(WebSocket arg0, ClientHandshake arg1) {
String origin = arg1.getFieldValue("Origin");
if(origin != null) {
int idx = origin.indexOf("://");
if(idx != -1) {
origin = origin.substring(idx + 3);
}
origin = origin.trim().toLowerCase();
if(DomainBlacklist.test(origin)) {
arg0.send(createRawKickPacket("End of Stream"));
arg0.close();
return;
}
}else {
if(blockOriginless) {
arg0.send(createRawKickPacket("End of Stream"));
arg0.close();
return;
}
}
String ua = arg1.getFieldValue("User-Agent");
if(blockOriginless && (ua == null || (ua = ua.toLowerCase()).contains("java-websocket") || ua.contains("tootallnate") || ua.contains("eaglercraft"))) {
arg0.send(createRawKickPacket("End of Stream"));
arg0.close();
return;
}
InetAddress addr;
if(info.hasForwardedHeaders()) {
String s = arg1.getFieldValue("X-Real-IP");
if(s != null) {
try {
addr = InetAddress.getByName(s);
}catch(UnknownHostException e) {
System.out.println("invalid 'X-Real-IP' header - " + e.toString());
arg0.close();
return;
}
}else {
addr = arg0.getRemoteSocketAddress().getAddress();
}
}else {
addr = arg0.getRemoteSocketAddress().getAddress();
}
boolean bypassBan = BanList.isBlockedBan(addr);
if(!bypassBan && ratelimitIP != null) {
RateLimit l = ratelimitIP.rateLimit(addr);
if(l.blocked()) {
if(l == RateLimit.LIMIT) {
arg0.send(ipBlockedString);
}else if(l == RateLimit.NOW_LOCKED_OUT) {
arg0.send(ipLockedString);
}
arg0.close();
return;
}
}
arg0.setAttachment(new PendingSocket(System.currentTimeMillis(), addr, origin, bypassBan));
}
@Override
public void onStart() {
}
public void closeInactiveSockets() {
for(WebSocket w : this.getConnections()) {
Object o = w.getAttachment();
if(o == null) {
w.close();
}else if(o instanceof PendingSocket) {
if(System.currentTimeMillis() - ((PendingSocket)o).openTime > 1500l) {
w.close();
}
}
}
}
@Override
public void stop() throws IOException, InterruptedException {
for(WebSocket w : this.getConnections()) {
Object o = w.getAttachment();
if(o != null && o instanceof WebSocketProxy) {
((WebSocketProxy)o).killConnection();
}
}
super.stop();
}
private byte[] createRawKickPacket(String str) {
ByteArrayOutputStream bao = new ByteArrayOutputStream();
DataOutputStream dout = new DataOutputStream(bao);
try {
dout.write(255);
dout.writeShort(str.length());
dout.writeChars(str);
return bao.toByteArray();
}catch(IOException e) {
return new byte[] { (byte)255, 0, 0 };
}
}
public ListenerInfo getInfo() {
return info;
}
}

View File

@ -1,7 +1,9 @@
package net.md_5.bungee.eaglercraft;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.HashMap;
import org.java_websocket.WebSocket;
@ -16,6 +18,8 @@ import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
/**
* Not the ideal solution but what are we supposed to do
@ -25,19 +29,27 @@ public class WebSocketProxy extends SimpleChannelInboundHandler<ByteBuf> {
private WebSocket client;
private InetSocketAddress tcpListener;
private InetSocketAddress localAddress;
private InetAddress realRemoteAddr;
private String origin;
private NioSocketChannel tcpChannel;
private static final EventLoopGroup group = new NioEventLoopGroup(4);
public static final HashMap<InetSocketAddress,InetAddress> localToRemote = new HashMap();
public static final HashMap<InetSocketAddress,String> origins = new HashMap();
public WebSocketProxy(WebSocket w, InetSocketAddress addr) {
public WebSocketProxy(WebSocket w, InetAddress remoteAddr, String originz, InetSocketAddress addr) {
client = w;
realRemoteAddr = remoteAddr;
origin = originz;
tcpListener = addr;
tcpChannel = null;
}
public void killConnection() {
if(client.isOpen()) {
client.close();
synchronized(localToRemote) {
localToRemote.remove(localAddress);
origins.remove(localAddress);
}
if(tcpChannel != null && tcpChannel.isOpen()) {
try {
@ -59,9 +71,24 @@ public class WebSocketProxy extends SimpleChannelInboundHandler<ByteBuf> {
clientBootstrap.handler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(WebSocketProxy.this);
socketChannel.closeFuture().addListener(new GenericFutureListener<Future<? super Void>>() {
@Override
public void operationComplete(Future<? super Void> paramF) throws Exception {
synchronized(localToRemote) {
localToRemote.remove(localAddress);
origins.remove(localAddress);
}
}
});
}
});
tcpChannel = (NioSocketChannel) clientBootstrap.connect().sync().channel();
synchronized(localToRemote) {
localToRemote.put(localAddress = tcpChannel.localAddress(), realRemoteAddr);
if(origin != null) {
origins.put(localAddress, origin);
}
}
return true;
}
}catch(Throwable t) {
@ -75,7 +102,11 @@ public class WebSocketProxy extends SimpleChannelInboundHandler<ByteBuf> {
ByteBuffer toSend = ByteBuffer.allocateDirect(buffer.capacity());
toSend.put(buffer.nioBuffer());
toSend.flip();
client.send(toSend);
if (client.isOpen()) {
client.send(toSend);
} else {
killConnection();
}
}
@Override
@ -89,4 +120,11 @@ public class WebSocketProxy extends SimpleChannelInboundHandler<ByteBuf> {
}
}
public void finalize() {
synchronized(localToRemote) {
localToRemote.remove(localAddress);
origins.remove(localAddress);
}
}
}

View File

@ -0,0 +1,171 @@
package net.md_5.bungee.eaglercraft;
import java.net.InetAddress;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class WebSocketRateLimiter {
public static enum RateLimit {
NONE, LIMIT, LOCKED_OUT, NOW_LOCKED_OUT;
public boolean blocked() {
return this != NONE;
}
}
public final int period;
public final int limit;
public final int lockoutLimit;
public final int lockoutTime;
public final Collection<String> exceptions;
protected final Map<String, RateLimiter> ratelimiters = new HashMap();
public WebSocketRateLimiter(int period, int limit, int lockoutLimit, int lockoutTime, Collection<String> exceptions) {
this.period = period;
this.limit = limit;
this.lockoutLimit = lockoutLimit;
this.lockoutTime = lockoutTime;
this.exceptions = exceptions;
}
protected static class RateLimiter {
protected final WebSocketRateLimiter limiterConfig;
protected RateLimiter(WebSocketRateLimiter limiterConfig) {
this.limiterConfig = limiterConfig;
this.cooldownTimestamp = System.currentTimeMillis();
}
protected int requestCounter = 0;
protected long lockoutTimestamp = 0l;
protected long cooldownTimestamp;
private boolean checkLockout(long currentTimeMillis) {
if(lockoutTimestamp > 0l) {
if(currentTimeMillis - lockoutTimestamp < (long)(limiterConfig.lockoutTime * 1000l)) {
return true;
}else {
lockoutTimestamp = 0l;
requestCounter = 0;
cooldownTimestamp = currentTimeMillis;
}
}
return false;
}
private boolean checkCooldown(long currentTimeMillis) {
long cooldownIncrement = limiterConfig.period * 1000 / limiterConfig.limit;
while(currentTimeMillis - cooldownTimestamp > cooldownIncrement && requestCounter > 0) {
--requestCounter;
cooldownTimestamp += cooldownIncrement;
}
if(requestCounter == 0) {
cooldownTimestamp = currentTimeMillis;
return false;
}else {
return requestCounter >= limiterConfig.limit;
}
}
protected RateLimit increment() {
long t = System.currentTimeMillis();
if(checkLockout(t)) {
return RateLimit.LOCKED_OUT;
}
++requestCounter;
boolean blockByCooldown = checkCooldown(t);
if(requestCounter >= limiterConfig.lockoutLimit) {
requestCounter = 0;
cooldownTimestamp = t;
lockoutTimestamp = t;
return RateLimit.NOW_LOCKED_OUT;
}
if(blockByCooldown) {
return RateLimit.LIMIT;
}else {
return RateLimit.NONE;
}
}
protected RateLimit checkLimited() {
long t = System.currentTimeMillis();
if(checkLockout(t)) {
return RateLimit.LOCKED_OUT;
}else if(checkCooldown(t)) {
return RateLimit.LIMIT;
}else {
return RateLimit.NONE;
}
}
protected boolean checkClear() {
long t = System.currentTimeMillis();
if(checkLockout(t) || checkCooldown(t)) {
return false;
}else if(requestCounter > 0) {
return false;
}else {
return true;
}
}
}
public void resetLimiters() {
synchronized(ratelimiters) {
ratelimiters.clear();
}
}
public void deleteClearLimiters() {
synchronized(ratelimiters) {
Iterator<RateLimiter> itr = ratelimiters.values().iterator();
while(itr.hasNext()) {
if(itr.next().checkClear()) {
itr.remove();
}
}
}
}
public RateLimit checkLimit(InetAddress identifier) {
return checkLimit(identifier.getHostAddress());
}
public RateLimit rateLimit(InetAddress identifier) {
return rateLimit(identifier.getHostAddress());
}
public RateLimit checkLimit(String identifier) {
if(exceptions.contains(identifier)) {
return RateLimit.NONE;
}
synchronized(ratelimiters) {
RateLimiter l = ratelimiters.get(identifier);
if(l == null) {
return RateLimit.NONE;
}else {
return l.checkLimited();
}
}
}
public RateLimit rateLimit(String identifier) {
if(exceptions.contains(identifier)) {
return RateLimit.NONE;
}
synchronized(ratelimiters) {
RateLimiter l = ratelimiters.get(identifier);
if(l == null) {
l = new RateLimiter(this);
ratelimiters.put(identifier, l);
}
return l.increment();
}
}
}

View File

@ -0,0 +1,321 @@
package net.md_5.bungee.eaglercraft.sun.net.util;
import java.net.URL;
import java.util.Arrays;
public class IPAddressUtil {
private static final int INADDR4SZ = 4;
private static final int INADDR16SZ = 16;
private static final int INT16SZ = 2;
private static final long L_IPV6_DELIMS = 0L;
private static final long H_IPV6_DELIMS = 671088640L;
private static final long L_GEN_DELIMS = -8935000888854970368L;
private static final long H_GEN_DELIMS = 671088641L;
private static final long L_AUTH_DELIMS = 288230376151711744L;
private static final long H_AUTH_DELIMS = 671088641L;
private static final long L_COLON = 288230376151711744L;
private static final long H_COLON = 0L;
private static final long L_SLASH = 140737488355328L;
private static final long H_SLASH = 0L;
private static final long L_BACKSLASH = 0L;
private static final long H_BACKSLASH = 268435456L;
private static final long L_NON_PRINTABLE = 4294967295L;
private static final long H_NON_PRINTABLE = -9223372036854775808L;
private static final long L_EXCLUDE = -8935000884560003073L;
private static final long H_EXCLUDE = -9223372035915251711L;
public static byte[] textToNumericFormatV4(String paramString) {
byte[] arrayOfByte = new byte[4];
long l = 0L;
byte b1 = 0;
boolean bool = true;
int i = paramString.length();
if (i == 0 || i > 15)
return null;
for (byte b2 = 0; b2 < i; b2++) {
char c = paramString.charAt(b2);
if (c == '.') {
if (bool || l < 0L || l > 255L || b1 == 3)
return null;
arrayOfByte[b1++] = (byte) (int) (l & 0xFFL);
l = 0L;
bool = true;
} else {
int j = Character.digit(c, 10);
if (j < 0)
return null;
l *= 10L;
l += j;
bool = false;
}
}
if (bool || l < 0L || l >= 1L << (4 - b1) * 8)
return null;
switch (b1) {
case 0 :
arrayOfByte[0] = (byte) (int) (l >> 24L & 0xFFL);
case 1 :
arrayOfByte[1] = (byte) (int) (l >> 16L & 0xFFL);
case 2 :
arrayOfByte[2] = (byte) (int) (l >> 8L & 0xFFL);
case 3 :
arrayOfByte[3] = (byte) (int) (l >> 0L & 0xFFL);
break;
}
return arrayOfByte;
}
public static byte[] textToNumericFormatV6(String paramString) {
if (paramString.length() < 2)
return null;
char[] arrayOfChar = paramString.toCharArray();
byte[] arrayOfByte1 = new byte[16];
int j = arrayOfChar.length;
int k = paramString.indexOf("%");
if (k == j - 1)
return null;
if (k != -1)
j = k;
byte b = -1;
byte b1 = 0, b2 = 0;
if (arrayOfChar[b1] == ':' && arrayOfChar[++b1] != ':')
return null;
byte b3 = b1;
boolean bool = false;
int i = 0;
while (b1 < j) {
char c = arrayOfChar[b1++];
int m = Character.digit(c, 16);
if (m != -1) {
i <<= 4;
i |= m;
if (i > 65535)
return null;
bool = true;
continue;
}
if (c == ':') {
b3 = b1;
if (!bool) {
if (b != -1)
return null;
b = b2;
continue;
}
if (b1 == j)
return null;
if (b2 + 2 > 16)
return null;
arrayOfByte1[b2++] = (byte) (i >> 8 & 0xFF);
arrayOfByte1[b2++] = (byte) (i & 0xFF);
bool = false;
i = 0;
continue;
}
if (c == '.' && b2 + 4 <= 16) {
String str = paramString.substring(b3, j);
byte b4 = 0;
int n = 0;
while ((n = str.indexOf('.', n)) != -1) {
b4++;
n++;
}
if (b4 != 3)
return null;
byte[] arrayOfByte = textToNumericFormatV4(str);
if (arrayOfByte == null)
return null;
for (byte b5 = 0; b5 < 4; b5++)
arrayOfByte1[b2++] = arrayOfByte[b5];
bool = false;
break;
}
return null;
}
if (bool) {
if (b2 + 2 > 16)
return null;
arrayOfByte1[b2++] = (byte) (i >> 8 & 0xFF);
arrayOfByte1[b2++] = (byte) (i & 0xFF);
}
if (b != -1) {
int m = b2 - b;
if (b2 == 16)
return null;
for (b1 = 1; b1 <= m; b1++) {
arrayOfByte1[16 - b1] = arrayOfByte1[b + m - b1];
arrayOfByte1[b + m - b1] = 0;
}
b2 = 16;
}
if (b2 != 16)
return null;
byte[] arrayOfByte2 = convertFromIPv4MappedAddress(arrayOfByte1);
if (arrayOfByte2 != null)
return arrayOfByte2;
return arrayOfByte1;
}
public static boolean isIPv4LiteralAddress(String paramString) {
return (textToNumericFormatV4(paramString) != null);
}
public static boolean isIPv6LiteralAddress(String paramString) {
return (textToNumericFormatV6(paramString) != null);
}
public static byte[] convertFromIPv4MappedAddress(byte[] paramArrayOfbyte) {
if (isIPv4MappedAddress(paramArrayOfbyte)) {
byte[] arrayOfByte = new byte[4];
System.arraycopy(paramArrayOfbyte, 12, arrayOfByte, 0, 4);
return arrayOfByte;
}
return null;
}
private static boolean isIPv4MappedAddress(byte[] paramArrayOfbyte) {
if (paramArrayOfbyte.length < 16)
return false;
if (paramArrayOfbyte[0] == 0 && paramArrayOfbyte[1] == 0 && paramArrayOfbyte[2] == 0 && paramArrayOfbyte[3] == 0
&& paramArrayOfbyte[4] == 0 && paramArrayOfbyte[5] == 0 && paramArrayOfbyte[6] == 0
&& paramArrayOfbyte[7] == 0 && paramArrayOfbyte[8] == 0 && paramArrayOfbyte[9] == 0
&& paramArrayOfbyte[10] == -1 && paramArrayOfbyte[11] == -1)
return true;
return false;
}
public static boolean match(char paramChar, long paramLong1, long paramLong2) {
if (paramChar < '@')
return ((1L << paramChar & paramLong1) != 0L);
return false;
}
public static int scan(String paramString, long paramLong1, long paramLong2) {
byte b = -1;
int i;
if (paramString == null || (i = paramString.length()) == 0)
return -1;
boolean bool = false;
while (++b < i && !(bool = match(paramString.charAt(b), paramLong1, paramLong2)));
if (bool)
return b;
return -1;
}
public static int scan(String paramString, long paramLong1, long paramLong2, char[] paramArrayOfchar) {
byte b = -1;
int i;
if (paramString == null || (i = paramString.length()) == 0)
return -1;
boolean bool = false;
char c2 = paramArrayOfchar[0];
char c1;
while (++b < i && !(bool = match(c1 = paramString.charAt(b), paramLong1, paramLong2))) {
if (c1 >= c2 && Arrays.binarySearch(paramArrayOfchar, c1) > -1) {
bool = true;
break;
}
}
if (bool)
return b;
return -1;
}
private static String describeChar(char paramChar) {
if (paramChar < ' ' || paramChar == '') {
if (paramChar == '\n')
return "LF";
if (paramChar == '\r')
return "CR";
return "control char (code=" + paramChar + ")";
}
if (paramChar == '\\')
return "'\\'";
return "'" + paramChar + "'";
}
private static String checkUserInfo(String paramString) {
int i = scan(paramString, -9223231260711714817L, -9223372035915251711L);
if (i >= 0)
return "Illegal character found in user-info: " + describeChar(paramString.charAt(i));
return null;
}
private static String checkHost(String paramString) {
if (paramString.startsWith("[") && paramString.endsWith("]")) {
paramString = paramString.substring(1, paramString.length() - 1);
if (isIPv6LiteralAddress(paramString)) {
int j = paramString.indexOf('%');
if (j >= 0) {
j = scan(paramString = paramString.substring(j), 4294967295L, -9223372036183687168L);
if (j >= 0)
return "Illegal character found in IPv6 scoped address: " + describeChar(paramString.charAt(j));
}
return null;
}
return "Unrecognized IPv6 address format";
}
int i = scan(paramString, -8935000884560003073L, -9223372035915251711L);
if (i >= 0)
return "Illegal character found in host: " + describeChar(paramString.charAt(i));
return null;
}
private static String checkAuth(String paramString) {
int i = scan(paramString, -9223231260711714817L, -9223372036586340352L);
if (i >= 0)
return "Illegal character found in authority: " + describeChar(paramString.charAt(i));
return null;
}
public static String checkAuthority(URL paramURL) {
if (paramURL == null)
return null;
String str1;
String str2;
if ((str1 = checkUserInfo(str2 = paramURL.getUserInfo())) != null)
return str1;
String str3;
if ((str1 = checkHost(str3 = paramURL.getHost())) != null)
return str1;
if (str3 == null && str2 == null)
return checkAuth(paramURL.getAuthority());
return null;
}
public static String checkExternalForm(URL paramURL) {
if (paramURL == null)
return null;
String str;
int i = scan(str = paramURL.getUserInfo(), 140741783322623L, Long.MIN_VALUE);
if (i >= 0)
return "Illegal character found in authority: " + describeChar(str.charAt(i));
if ((str = checkHostString(paramURL.getHost())) != null)
return str;
return null;
}
public static String checkHostString(String paramString) {
if (paramString == null)
return null;
return null;
}
}

View File

@ -4,19 +4,18 @@
package net.md_5.bungee.event;
import java.util.HashSet;
import java.util.Set;
import java.util.Iterator;
import java.lang.reflect.InvocationTargetException;
import java.text.MessageFormat;
import java.util.logging.Level;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.HashMap;
import java.lang.annotation.Annotation;
import java.util.logging.Logger;
import java.util.concurrent.locks.ReadWriteLock;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Level;
import java.util.logging.Logger;
public class EventBus {
private final Map<Class<?>, Map<Object, Method[]>> eventToHandler;

View File

@ -5,10 +5,9 @@
package net.md_5.bungee.event;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Retention;
import java.lang.annotation.Annotation;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })

View File

@ -4,14 +4,14 @@
package net.md_5.bungee.log;
import java.util.logging.LogRecord;
import java.io.IOException;
import java.util.logging.Handler;
import java.util.logging.FileHandler;
import java.util.logging.Formatter;
import net.md_5.bungee.BungeeCord;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import net.md_5.bungee.BungeeCord;
public class BungeeLogger extends Logger {
private final BungeeCord bungee;
private final ColouredWriter writer;

View File

@ -5,11 +5,13 @@
package net.md_5.bungee.log;
import java.io.IOException;
import org.fusesource.jansi.Ansi;
import java.util.EnumMap;
import java.util.Map;
import org.fusesource.jansi.Ansi;
import jline.console.ConsoleReader;
import net.md_5.bungee.api.ChatColor;
import java.util.Map;
public class ColouredWriter {
private final Map<ChatColor, String> replacements;

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