Compare commits
129
Commits
standalone
...
stable
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
904ed87619 | ||
|
|
45b2a7a8a9 | ||
|
|
0ef4793df3 | ||
|
|
4aa276a2d0 | ||
|
|
1e2b22f06c | ||
|
|
dbd9f86be9 | ||
|
|
e71ff635e3 | ||
|
|
4d52ef42e4 | ||
|
|
c21d18594d | ||
|
|
9944bb7077 | ||
|
|
7d43b7399f | ||
|
|
5d797065cb | ||
|
|
43c8489574 | ||
|
|
348dbca900 | ||
|
|
395c63a80b | ||
|
|
99afd0ca71 | ||
|
|
2243be4954 | ||
|
|
8a73b0bf9c | ||
|
|
595b7ed75f | ||
|
|
759b7a886b | ||
|
|
eb68085746 | ||
|
|
16b093e4d6 | ||
|
|
a2848e43d0 | ||
|
|
1bfa28af48 | ||
|
|
b745236131 | ||
|
|
4330d41286 | ||
|
|
75228868f8 | ||
|
|
671c6e17a9 | ||
|
|
c3030d56e8 | ||
|
|
9fe4d6b9c3 | ||
|
|
99dbaa703a | ||
|
|
0fa42b44cc | ||
|
|
7d75bce02c | ||
|
|
60923598b9 | ||
|
|
0c6dc99bcc | ||
|
|
f5776dd1cb | ||
|
|
7134c12826 | ||
|
|
8cc93daf70 | ||
|
|
98dfc345e9 | ||
|
|
590b7618f3 | ||
|
|
4cd2ce55fc | ||
|
|
9fe98a92a9 | ||
|
|
c72972c286 | ||
|
|
92a122eaf8 | ||
|
|
67117b38e3 | ||
|
|
9255062aff | ||
|
|
fe45e48447 | ||
|
|
0391e9e4bf | ||
|
|
59fa4857e3 | ||
|
|
4d663737c1 | ||
|
|
8380ffce9a | ||
|
|
b9e648bba8 | ||
|
|
c34551e630 | ||
|
|
d931cc2464 | ||
|
|
07f81eb781 | ||
|
|
c0ef61c819 | ||
|
|
09bbfa8629 | ||
|
|
aef038ef3a | ||
|
|
ac3827625d | ||
|
|
acc092f955 | ||
|
|
b494e1dc9d | ||
|
|
27a368bd08 | ||
|
|
3c5b533a44 | ||
|
|
f569c725d0 | ||
|
|
a09bc5a131 | ||
|
|
c4e4f6e416 | ||
|
|
6ed181e835 | ||
|
|
3ac45554ac | ||
|
|
d27f6c5cea | ||
|
|
4d1f7016f6 | ||
|
|
2673fc2d4d | ||
|
|
60c787c7f2 | ||
|
|
713cf90c56 | ||
|
|
eba7ab9c86 | ||
|
|
d72977325c | ||
|
|
c3e9a5bac9 | ||
|
|
161617d636 | ||
|
|
cf95fd1f28 | ||
|
|
8f1bf5b4b9 | ||
|
|
ee9d3cae42 | ||
|
|
e4f910e576 | ||
|
|
77cf6bf457 | ||
|
|
8a72c4d9c7 | ||
|
|
39ae4ef860 | ||
|
|
d2d12a4a86 | ||
|
|
5644078997 | ||
|
|
bf5822282e | ||
|
|
55e883759f | ||
|
|
ba6a118058 | ||
|
|
93c0d53f2d | ||
|
|
8d4bdf69b3 | ||
|
|
700be0a04e | ||
|
|
bf7c78de46 | ||
|
|
c0ea57176c | ||
|
|
65bf833fa7 | ||
|
|
03beac9ae0 | ||
|
|
4277dfd74d | ||
|
|
28b4d8dcd0 | ||
|
|
733204b6b9 | ||
|
|
52956ce210 | ||
|
|
2de64da511 | ||
|
|
0ea6867d91 | ||
|
|
190bfaa4a9 | ||
|
|
b43dda37b2 | ||
|
|
4dfb157166 | ||
|
|
d8c2e072b1 | ||
|
|
359b616a41 | ||
|
|
9342f75317 | ||
|
|
f17bb4e414 | ||
|
|
5f347e538b | ||
|
|
b4fb8c69a9 | ||
|
|
95ebcf22a8 | ||
|
|
302da3c907 | ||
|
|
f8019debd0 | ||
|
|
ab317e76b3 | ||
|
|
15556be265 | ||
|
|
26958e35fe | ||
|
|
e905881238 | ||
|
|
fde7a941b6 | ||
|
|
9d24deabe9 | ||
|
|
c2f331e7ec | ||
|
|
705bddd062 | ||
|
|
826609db30 | ||
|
|
0eca96bd67 | ||
|
|
f767387840 | ||
|
|
8ae3c21a23 | ||
|
|
be1c056502 | ||
|
|
500142eb5b | ||
|
|
1e5876bc08 |
No files matched your search
+7
-2
@@ -11,13 +11,14 @@
|
||||
/build.xml
|
||||
|
||||
# maven
|
||||
/target
|
||||
/*/target/
|
||||
dependency-reduced-pom.xml
|
||||
|
||||
# vim
|
||||
.*.sw[a-p]
|
||||
|
||||
# various other potential build files
|
||||
/build
|
||||
build/
|
||||
/bin
|
||||
/dist
|
||||
/manifest.mf
|
||||
@@ -42,3 +43,7 @@ debug.log
|
||||
# Doxygen
|
||||
/docs/html
|
||||
debug.txt
|
||||
|
||||
# gradle stuff
|
||||
.gradle/
|
||||
.gradletasknamecache
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
sudo: false
|
||||
language: java
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
deploy:
|
||||
provider: releases
|
||||
api_key:
|
||||
secure: j8y4qeDOXAYjmf6WU6EbpUA6AKTz26yJ4zNb6oAuB4h9Y3PpBRjFjCIBL6jJQnMxZMLjdgk72/3uqS7+//jJ8/iiY/OJhO2L+2us8TJDcQhH5EI1USe/lcZKf60iJjmd9Q9pjh1eHNYhxr6kyNLnpa6fw5b/hIBT6CvNZAJV1qbhRpHwtzbgdlMFaHGnpX192vJNw7E3uuJuSwue5XNK2ybDp81oTrM0Q16JCq/UsQhGq4Ba3JffOhdBhpfg4EI2jJfe3vN8CFqojCYKNSp7eL6DPFPaV4x8Ly8cRIOZ5Zb60m5SyWAcagPKJuUq3ZOTHfeVTjkI9ymZMHe1pla2Mq6eitIBwZIGnonjyMxcTdkR9EJgq9jo+1PzdStR/NlGLakBiwPzk8Ov0DZ+iOwpYUkISUkMU0UgKWwoFoRu18CHtp+ALEsoYZhBQGlcShq+4EGv9VMNBYk9vN3KXNRMVGKqXd6O3T6shvSQgoUn/3mvksOTAuhS5J4U3woHecDrXwl/RfP2aqiHhX/S7p7yY4hVcHu8kiSYpNQMbay+rNd6HxFcRe/U9HOUt6kW8/yo1ROyCxs3ze+N6LtGDHspgzkFaay6I6wMHYNDnzzRP4MC3Zc73zxViXol6dGBk4Tawsm16VaI6b1uyWLBGKvuU23azkmMTHUmGFXl2/BTaYw=
|
||||
file: universal/target/nuvotifier-universal-2.3.2.2.jar
|
||||
on:
|
||||
tags: true
|
||||
-112
@@ -1,112 +0,0 @@
|
||||
# NuVotifier
|
||||
|
||||
NuVotifier is a Bukkit plugin whose purpose is to be notified (aka *votified*) when a vote is made on a Minecraft server top list for the server. NuVotifier creates a *lightweight* server that waits for connections by Minecraft server lists and uses a simple protocol to get the required information. NuVotifier is *secure*, and makes sure that all vote notifications are delivered by authentic top lists.
|
||||
|
||||
[](https://travis-ci.org/NuVotifier/NuVotifier)
|
||||
|
||||
## Why fork Votifier?
|
||||
|
||||
Votifier has lagged in activity, has severe security flaws in its protocol, and is vulnerable to denial of service attacks.
|
||||
|
||||
### Is NuVotifier a drop-in replacement for Votifier?
|
||||
|
||||
Yes. NuVotifier is compatible with Votifier server lists and Votifier listeners, with some differences:
|
||||
|
||||
* The `Vote` object's setters are deprecated.
|
||||
* Support for standalone vote listeners (`.class` files) has been removed, as handling `VotifierEvent` is more flexible.
|
||||
|
||||
### Why a new protocol?
|
||||
|
||||
The old Votifier protocol has three major flaws, which we feel are corrected in the new protocol.
|
||||
|
||||
* The data is encrypted (with RSA) but not hashed to verify its integrity. The new protocol utilizes HMAC with SHA256 (but no encryption, given that the data sent is eventually revealed in some form or another).
|
||||
* The canonical Votifier protocol is vulnerable to replay attacks.
|
||||
* The canonical Votifier is one key for all clients, which is weak. While NuVotifier still supports a default key (for the less paranoid), this ability can be disabled and force per-server keys.
|
||||
|
||||
## Configuring NuVotifier
|
||||
|
||||
NuVotifier configures itself the first time it is run. If you have an existing Votifier configuration, NuVotifier will copy it over and use it.
|
||||
|
||||
If you want to customize NuVotifier, simply the edit `./plugins/(Nu)Votifier/config.yml` file.
|
||||
|
||||
An in-depth configuration guide is located in the [usage](https://github.com/NuVotifier/NuVotifier/blob/master/usage.md) document.
|
||||
|
||||
### Tokens
|
||||
|
||||
In `config.yml`, you will notice a `tokens` section. NuVotifier has a new protocol version that requires tokens. The `tokens` section can be used to easily grant or revoke access to a server list based on the keys it uses.
|
||||
|
||||
### RSA Keys
|
||||
|
||||
RSA keys for use with the old Votifier protocol are available under the `plugins/(Nu)Votifier/rsa` directory.
|
||||
|
||||
### Vote Forwarding
|
||||
|
||||
Starting from NuVotifier 2.2, there is a feature to forward votes from NuVotifier running on BungeeCord to your Bukkit servers without complicated setup.
|
||||
|
||||
You must enable the feature on both your Bukkit and BungeeCord servers. This feature is still in beta but is stable.
|
||||
|
||||
## Building NuVotifier
|
||||
|
||||
NuVotifier can be built by running `mvn clean package` from within the root directory of NuVotifier. A universal Bukkit/Spigot & BungeeCord plugin will be built to `universal/target/nuvotifier-universal-{version}.jar`
|
||||
|
||||
## Protocol Documentation
|
||||
|
||||
This documentation is for server lists that wish to add NuVotifier support.
|
||||
|
||||
There are two versions of the NuVotifier protocol.
|
||||
|
||||
### Handshake
|
||||
|
||||
A connection is made to the NuVotifier server by the server list, and immediately NuVotifier will send its version in the following packet:
|
||||
|
||||
"VOTIFIER <version> <challenge>"
|
||||
|
||||
The challenge will not be present in version 1.x servers.
|
||||
|
||||
### Protocol v2
|
||||
|
||||
NuVotifier expects a message composed of `0x733A` (16-bit big-endian integer) and the length of the following JSON message (as a 16-bit big-endian integer) sent plus the message.
|
||||
|
||||
The message is JSON-encoded containing the following data:
|
||||
|
||||
* `payload` contains the fully encoded vote. It is a JSON message containing the `serviceName` (string), `username` (string), `address` (string, but must be a valid IPv4 or IPv6 address), `timestamp` (a long with the time this vote was taken in _milliseconds_), and `challenge` (from the handshake). It is optional, but recommended to supply a `uuid` field with the player's UUID (with dashes, e.g. `652a2bc4-e8cd-405d-b7b6-98156ee2dc09`).
|
||||
* `signature` is a HMAC-SHA256 digest of the payload with a owner-provided key.
|
||||
|
||||
A response is given after the vote has been received and parsed, but not yet processed.
|
||||
|
||||
For your convenience, the NuVotifier team has provided client libraries in [JavaScript](https://github.com/NuVotifier/votifier2-js) and [PHP](https://github.com/NuVotifier/votifier2-php). If you are stuck, these libraries may help you.
|
||||
|
||||
### Protocol v1 (deprecated)
|
||||
|
||||
For better compatibility, NuVotifier supports the old Votifier protocol as well.
|
||||
|
||||
NuVotifier expects a 256 byte RSA encrypted block (the public key should be obtained by the NuVotifier user), with the following format:
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Type</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>string</td>
|
||||
<td>VOTE</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>string</td>
|
||||
<td>serviceName</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>string</td>
|
||||
<td>username</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>string</td>
|
||||
<td>address</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>string</td>
|
||||
<td>timeStamp</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
The first string of value "VOTE" is an opcode check to ensure that RSA was encoded and decoded properly, if this value is wrong then NuVotifier assumes that there was a problem with encryption and drops the connection. `serviceName` is the name of the top list service, `username` is the username (entered by the voter) of the person who voted, `address` is the IP address of the voter, and `timeStamp` is the time stamp of the vote. Each string is delimited by the newline character `\n` (byte value 10). The `space` block is the empty space that is left over, **the block must be exactly 256 bytes** regardless of how much information it holds.
|
||||
@@ -0,0 +1,35 @@
|
||||
# NuVotifier
|
||||
|
||||
NuVotifier is a Bukkit plugin whose purpose is to be notified (aka *votified*)
|
||||
when a vote is made on a Minecraft server top list for the server. NuVotifier
|
||||
creates a *lightweight* server that waits for connections by Minecraft server
|
||||
lists and uses a simple protocol to get the required information. NuVotifier is
|
||||
*secure*, and makes sure that all vote notifications are delivered by authentic
|
||||
top lists.
|
||||
|
||||
## Why fork Votifier?
|
||||
|
||||
Votifier has lagged in activity, has severe security flaws in its protocol, and
|
||||
is vulnerable to denial of service attacks.
|
||||
|
||||
## Is NuVotifier a drop-in replacement for Votifier?
|
||||
|
||||
Yes! Except for some minor non-breaking changes (look into the [technical qa
|
||||
docs](docs/technical_qa.md) for more info), NuVotifier is 100% compatible with
|
||||
the old Votifier with many more features.
|
||||
|
||||
# Documentation / Configuration Guide
|
||||
|
||||
All documentation is located under the [docs](docs/) directory. A server admin
|
||||
configuration guide is available [here](docs/usage.md).
|
||||
|
||||
# Support
|
||||
|
||||
Feel free to join the MOSS Discord server using the below link:
|
||||
|
||||
[](https://discord.gg/anUtuAC)
|
||||
|
||||
# License
|
||||
|
||||
NuVotifier is GNU GPLv3 licensed. This project's license can be viewed
|
||||
[here](LICENSE).
|
||||
@@ -0,0 +1,46 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.+'
|
||||
}
|
||||
}
|
||||
|
||||
import org.apache.tools.ant.filters.ReplaceTokens
|
||||
|
||||
allprojects {
|
||||
group 'com.vexsoftware'
|
||||
version '2.6.0'
|
||||
}
|
||||
|
||||
subprojects {
|
||||
apply plugin: 'java'
|
||||
|
||||
sourceCompatibility = 1.8
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
maven {
|
||||
url "https://hub.spigotmc.org/nexus/content/repositories/snapshots"
|
||||
}
|
||||
maven {
|
||||
url "https://oss.sonatype.org/content/repositories/snapshots"
|
||||
}
|
||||
maven {
|
||||
url "https://repo.spongepowered.org/maven"
|
||||
}
|
||||
maven {
|
||||
url "https://repo.velocitypowered.com/snapshots/"
|
||||
}
|
||||
}
|
||||
|
||||
processResources {
|
||||
filter(ReplaceTokens, tokens: ["app.version": this.project.version])
|
||||
}
|
||||
}
|
||||
|
||||
if (System.env.PUBLISH) {
|
||||
apply from: 'publish.gradle'
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
dependencies {
|
||||
compileOnly group: "org.spigotmc", name: "spigot-api", version: "1.8.8-R0.1-SNAPSHOT"
|
||||
compile project(":nuvotifier-common")
|
||||
}
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>nuvotifier</artifactId>
|
||||
<groupId>com.vexsoftware</groupId>
|
||||
<version>2.3.2.2</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>nuvotifier-bukkit</artifactId>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.google.code.maven-replacer-plugin</groupId>
|
||||
<artifactId>maven-replacer-plugin</artifactId>
|
||||
<version>1.3.8</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>replace</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<file>target/classes/plugin.yml</file>
|
||||
<replacements>
|
||||
<replacement>
|
||||
<token>maven-version-number</token>
|
||||
<value>${project.version}</value>
|
||||
</replacement>
|
||||
</replacements>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>2.3.1</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>2.9</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.vexsoftware</groupId>
|
||||
<artifactId>nuvotifier-common</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>bukkit</artifactId>
|
||||
<version>1.7.2-R0.1</version>
|
||||
<type>jar</type>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.vexsoftware.votifier;
|
||||
|
||||
import com.vexsoftware.votifier.platform.scheduler.ScheduledVotifierTask;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
class BukkitScheduler implements VotifierScheduler {
|
||||
private final NuVotifierBukkit plugin;
|
||||
|
||||
public BukkitScheduler(NuVotifierBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
private int toTicks(int time, TimeUnit unit) {
|
||||
return (int) (unit.toMillis(time) / 50);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask sync(Runnable runnable) {
|
||||
return new BukkitTaskWrapper(plugin.getServer().getScheduler().runTask(plugin, runnable));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask onPool(Runnable runnable) {
|
||||
return new BukkitTaskWrapper(plugin.getServer().getScheduler().runTaskAsynchronously(plugin, runnable));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask delayedSync(Runnable runnable, int delay, TimeUnit unit) {
|
||||
return new BukkitTaskWrapper(plugin.getServer().getScheduler().runTaskLater(plugin, runnable, toTicks(delay, unit)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask delayedOnPool(Runnable runnable, int delay, TimeUnit unit) {
|
||||
return new BukkitTaskWrapper(plugin.getServer().getScheduler().runTaskLaterAsynchronously(plugin, runnable, toTicks(delay, unit)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask repeatOnPool(Runnable runnable, int delay, int repeat, TimeUnit unit) {
|
||||
return new BukkitTaskWrapper(plugin.getServer().getScheduler().runTaskTimerAsynchronously(plugin, runnable, toTicks(delay, unit), toTicks(repeat, unit)));
|
||||
}
|
||||
|
||||
private static class BukkitTaskWrapper implements ScheduledVotifierTask {
|
||||
private final BukkitTask task;
|
||||
|
||||
private BukkitTaskWrapper(BukkitTask task) {
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
task.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,35 +21,31 @@ package com.vexsoftware.votifier;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.common.io.Files;
|
||||
import com.vexsoftware.votifier.forwarding.BukkitPluginMessagingForwardingSink;
|
||||
import com.vexsoftware.votifier.forwarding.ForwardedVoteListener;
|
||||
import com.vexsoftware.votifier.forwarding.ForwardingVoteSink;
|
||||
import com.vexsoftware.votifier.support.forwarding.ForwardedVoteListener;
|
||||
import com.vexsoftware.votifier.support.forwarding.ForwardingVoteSink;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.model.VotifierEvent;
|
||||
import com.vexsoftware.votifier.net.VotifierServerBootstrap;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.net.protocol.VoteInboundHandler;
|
||||
import com.vexsoftware.votifier.net.protocol.VotifierGreetingHandler;
|
||||
import com.vexsoftware.votifier.net.protocol.VotifierProtocolDifferentiator;
|
||||
import com.vexsoftware.votifier.net.protocol.v1crypto.RSAIO;
|
||||
import com.vexsoftware.votifier.net.protocol.v1crypto.RSAKeygen;
|
||||
import com.vexsoftware.votifier.platform.JavaUtilLogger;
|
||||
import com.vexsoftware.votifier.platform.LoggingAdapter;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
import com.vexsoftware.votifier.util.KeyCreator;
|
||||
import com.vexsoftware.votifier.util.TokenUtil;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.ConsoleCommandSender;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
@@ -76,14 +72,9 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
private String version;
|
||||
|
||||
/**
|
||||
* The server channel.
|
||||
* The server bootstrap.
|
||||
*/
|
||||
private Channel serverChannel;
|
||||
|
||||
/**
|
||||
* The event group handling the channel.
|
||||
*/
|
||||
private NioEventLoopGroup serverGroup;
|
||||
private VotifierServerBootstrap bootstrap;
|
||||
|
||||
/**
|
||||
* The RSA key pair.
|
||||
@@ -101,22 +92,18 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
private Map<String, Key> tokens = new HashMap<>();
|
||||
|
||||
private ForwardingVoteSink forwardingMethod;
|
||||
private VotifierScheduler scheduler;
|
||||
private LoggingAdapter pluginLogger;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
NuVotifierBukkit.instance = this;
|
||||
|
||||
// Set the plugin version.
|
||||
version = getDescription().getVersion();
|
||||
|
||||
private boolean loadAndBind() {
|
||||
scheduler = new BukkitScheduler(this);
|
||||
pluginLogger = new JavaUtilLogger(getLogger());
|
||||
if (!getDataFolder().exists()) {
|
||||
getDataFolder().mkdir();
|
||||
}
|
||||
|
||||
// Handle configuration.
|
||||
File config = new File(getDataFolder() + "/config.yml");
|
||||
YamlConfiguration cfg;
|
||||
File rsaDirectory = new File(getDataFolder() + "/rsa");
|
||||
File config = new File(getDataFolder(), "config.yml");
|
||||
|
||||
/*
|
||||
* Use IP address from server.properties as a default for
|
||||
@@ -143,7 +130,7 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
String cfgStr = new String(ByteStreams.toByteArray(getResource("bukkitConfig.yml")), StandardCharsets.UTF_8);
|
||||
String token = TokenUtil.newToken();
|
||||
cfgStr = cfgStr.replace("%default_token%", token).replace("%ip%", hostAddr);
|
||||
Files.write(cfgStr, config, StandardCharsets.UTF_8);
|
||||
Files.asCharSink(config, StandardCharsets.UTF_8).write(cfgStr);
|
||||
|
||||
/*
|
||||
* Remind hosted server admins to be sure they have the right
|
||||
@@ -161,11 +148,13 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
} catch (Exception ex) {
|
||||
getLogger().log(Level.SEVERE, "Error creating configuration file", ex);
|
||||
gracefulExit();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
YamlConfiguration cfg;
|
||||
File rsaDirectory = new File(getDataFolder(), "rsa");
|
||||
|
||||
// Load configuration.
|
||||
cfg = YamlConfiguration.loadConfiguration(config);
|
||||
|
||||
@@ -184,51 +173,53 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
} catch (Exception ex) {
|
||||
getLogger().log(Level.SEVERE,
|
||||
"Error reading configuration file or RSA tokens", ex);
|
||||
gracefulExit();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
debug = cfg.getBoolean("debug", false);
|
||||
// the quiet flag always runs priority to the debug flag
|
||||
if (cfg.isBoolean("quiet")) {
|
||||
debug = !cfg.getBoolean("quiet");
|
||||
} else {
|
||||
// otherwise, default to being noisy
|
||||
debug = cfg.getBoolean("debug", true);
|
||||
}
|
||||
|
||||
boolean setUpPort = cfg.getBoolean("enableExternal", true); //Always default to running the external port
|
||||
// Load Votifier tokens.
|
||||
ConfigurationSection tokenSection = cfg.getConfigurationSection("tokens");
|
||||
|
||||
if (setUpPort) {
|
||||
// Load Votifier tokens.
|
||||
ConfigurationSection tokenSection = cfg.getConfigurationSection("tokens");
|
||||
|
||||
if (tokenSection != null) {
|
||||
Map<String, Object> websites = tokenSection.getValues(false);
|
||||
for (Map.Entry<String, Object> website : websites.entrySet()) {
|
||||
tokens.put(website.getKey(), KeyCreator.createKeyFrom(website.getValue().toString()));
|
||||
getLogger().info("Loaded token for website: " + website.getKey());
|
||||
}
|
||||
} else {
|
||||
String token = TokenUtil.newToken();
|
||||
tokenSection = cfg.createSection("tokens");
|
||||
tokenSection.set("default", token);
|
||||
tokens.put("default", KeyCreator.createKeyFrom(token));
|
||||
try {
|
||||
cfg.save(config);
|
||||
} catch (IOException e) {
|
||||
getLogger().log(Level.SEVERE,
|
||||
"Error generating Votifier token", e);
|
||||
gracefulExit();
|
||||
return;
|
||||
}
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
getLogger().info("No tokens were found in your configuration, so we've generated one for you.");
|
||||
getLogger().info("Your default Votifier token is " + token + ".");
|
||||
getLogger().info("You will need to provide this token when you submit your server to a voting");
|
||||
getLogger().info("list.");
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
if (tokenSection != null) {
|
||||
Map<String, Object> websites = tokenSection.getValues(false);
|
||||
for (Map.Entry<String, Object> website : websites.entrySet()) {
|
||||
tokens.put(website.getKey(), KeyCreator.createKeyFrom(website.getValue().toString()));
|
||||
getLogger().info("Loaded token for website: " + website.getKey());
|
||||
}
|
||||
} else {
|
||||
String token = TokenUtil.newToken();
|
||||
tokenSection = cfg.createSection("tokens");
|
||||
tokenSection.set("default", token);
|
||||
tokens.put("default", KeyCreator.createKeyFrom(token));
|
||||
try {
|
||||
cfg.save(config);
|
||||
} catch (IOException e) {
|
||||
getLogger().log(Level.SEVERE,
|
||||
"Error generating Votifier token", e);
|
||||
return false;
|
||||
}
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
getLogger().info("No tokens were found in your configuration, so we've generated one for you.");
|
||||
getLogger().info("Your default Votifier token is " + token + ".");
|
||||
getLogger().info("You will need to provide this token when you submit your server to a voting");
|
||||
getLogger().info("list.");
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
}
|
||||
|
||||
// Initialize the receiver.
|
||||
final String host = cfg.getString("host", hostAddr);
|
||||
final int port = cfg.getInt("port", 8192);
|
||||
if (debug)
|
||||
getLogger().info("DEBUG mode enabled!");
|
||||
// Initialize the receiver.
|
||||
final String host = cfg.getString("host", hostAddr);
|
||||
final int port = cfg.getInt("port", 8192);
|
||||
if (!debug)
|
||||
getLogger().info("QUIET mode enabled!");
|
||||
|
||||
if (port >= 0) {
|
||||
final boolean disablev1 = cfg.getBoolean("disable-v1-protocol");
|
||||
if (disablev1) {
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
@@ -237,39 +228,14 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
}
|
||||
|
||||
serverGroup = new NioEventLoopGroup(1);
|
||||
|
||||
new ServerBootstrap()
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.group(serverGroup)
|
||||
.childHandler(new ChannelInitializer<NioSocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(NioSocketChannel channel) throws Exception {
|
||||
channel.attr(VotifierSession.KEY).set(new VotifierSession());
|
||||
channel.attr(VotifierPlugin.KEY).set(NuVotifierBukkit.this);
|
||||
channel.pipeline().addLast("greetingHandler", new VotifierGreetingHandler());
|
||||
channel.pipeline().addLast("protocolDifferentiator", new VotifierProtocolDifferentiator(false, !disablev1));
|
||||
channel.pipeline().addLast("voteHandler", new VoteInboundHandler(NuVotifierBukkit.this));
|
||||
}
|
||||
})
|
||||
.bind(host, port)
|
||||
.addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
if (future.isSuccess()) {
|
||||
serverChannel = future.channel();
|
||||
getLogger().info("Votifier enabled on socket "+serverChannel.localAddress()+".");
|
||||
} else {
|
||||
SocketAddress socketAddress = future.channel().localAddress();
|
||||
if (socketAddress == null) {
|
||||
socketAddress = new InetSocketAddress(host, port);
|
||||
}
|
||||
getLogger().log(Level.SEVERE, "Votifier was not able to bind to " + socketAddress, future.cause());
|
||||
}
|
||||
}
|
||||
});
|
||||
this.bootstrap = new VotifierServerBootstrap(host, port, this, disablev1);
|
||||
this.bootstrap.start(error -> {});
|
||||
} else {
|
||||
getLogger().info("You have enableExternal set to false in your config.yml. NuVotifier will NOT listen to votes coming in from an external voting list.");
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
getLogger().info("Your Votifier port is less than 0, so we assume you do NOT want to start the");
|
||||
getLogger().info("votifier port server! Votifier will not listen for votes over any port, and");
|
||||
getLogger().info("will only listen for pluginMessaging forwarded votes!");
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
}
|
||||
|
||||
ConfigurationSection forwardingConfig = cfg.getConfigurationSection("forwarding");
|
||||
@@ -289,22 +255,70 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
getLogger().severe("No vote forwarding method '" + method + "' known. Defaulting to noop implementation.");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void halt() {
|
||||
// Shut down the network handlers.
|
||||
if (bootstrap != null) {
|
||||
bootstrap.shutdown();
|
||||
bootstrap = null;
|
||||
}
|
||||
|
||||
if (forwardingMethod != null) {
|
||||
forwardingMethod.halt();
|
||||
forwardingMethod = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
NuVotifierBukkit.instance = this;
|
||||
|
||||
// Set the plugin version.
|
||||
version = getDescription().getVersion();
|
||||
|
||||
if (!loadAndBind()) {
|
||||
gracefulExit();
|
||||
setEnabled(false); // safer to just bomb out
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
// Shut down the network handlers.
|
||||
if (serverGroup != null) {
|
||||
if (serverChannel != null)
|
||||
serverChannel.close();
|
||||
serverGroup.shutdownGracefully();
|
||||
}
|
||||
if (forwardingMethod != null) {
|
||||
forwardingMethod.halt();
|
||||
}
|
||||
halt();
|
||||
getLogger().info("Votifier disabled.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (sender instanceof ConsoleCommandSender) {
|
||||
reload();
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.RED + "For security and stability, only console may run this command!");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void reload() {
|
||||
try {
|
||||
halt();
|
||||
} catch (Exception ex) {
|
||||
getLogger().log(Level.SEVERE, "On halt, an exception was thrown. This may be fine!", ex);
|
||||
}
|
||||
|
||||
if (loadAndBind()) {
|
||||
getLogger().info("Reload was successful.");
|
||||
} else {
|
||||
try {
|
||||
halt();
|
||||
getLogger().log(Level.SEVERE, "On reload, there was a problem with the configuration. Votifier currently does nothing!");
|
||||
} catch (Exception ex) {
|
||||
getLogger().log(Level.SEVERE, "On reload, there was a problem loading, and we could not re-halt the server. Votifier is in an unstable state!", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void gracefulExit() {
|
||||
getLogger().log(Level.SEVERE, "Votifier did not initialize properly!");
|
||||
}
|
||||
@@ -318,13 +332,14 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the version.
|
||||
*
|
||||
* @return The version
|
||||
*/
|
||||
public String getVersion() {
|
||||
return version;
|
||||
@Override
|
||||
public LoggingAdapter getPluginLogger() {
|
||||
return pluginLogger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VotifierScheduler getScheduler() {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
public boolean isDebug() {
|
||||
@@ -342,27 +357,27 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVoteReceived(final Vote vote, VotifierSession.ProtocolVersion protocolVersion) throws Exception {
|
||||
public void onVoteReceived(Channel channel, final Vote vote, VotifierSession.ProtocolVersion protocolVersion) {
|
||||
if (debug) {
|
||||
if (protocolVersion == VotifierSession.ProtocolVersion.ONE) {
|
||||
getLogger().info("Got a protocol v1 vote record -> " + vote);
|
||||
getLogger().info("Got a protocol v1 vote record from " + channel.remoteAddress() + " -> " + vote);
|
||||
} else {
|
||||
getLogger().info("Got a protocol v2 vote record -> " + vote);
|
||||
getLogger().info("Got a protocol v2 vote record from " + channel.remoteAddress() + " -> " + vote);
|
||||
}
|
||||
}
|
||||
Bukkit.getScheduler().runTask(this, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Bukkit.getPluginManager().callEvent(new VotifierEvent(vote));
|
||||
}
|
||||
});
|
||||
Bukkit.getScheduler().runTask(this, () -> Bukkit.getPluginManager().callEvent(new VotifierEvent(vote)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Channel channel, Throwable throwable) {
|
||||
public void onError(Channel channel, boolean alreadyHandledVote, Throwable throwable) {
|
||||
if (debug) {
|
||||
getLogger().log(Level.SEVERE, "Unable to process vote from " + channel.remoteAddress(), throwable);
|
||||
} else {
|
||||
if (alreadyHandledVote) {
|
||||
getLogger().log(Level.SEVERE, "Vote processed, however an exception " +
|
||||
"occurred with a vote from " + channel.remoteAddress(), throwable);
|
||||
} else {
|
||||
getLogger().log(Level.SEVERE, "Unable to process vote from " + channel.remoteAddress(), throwable);
|
||||
}
|
||||
} else if (!alreadyHandledVote) {
|
||||
getLogger().log(Level.SEVERE, "Unable to process vote from " + channel.remoteAddress());
|
||||
}
|
||||
}
|
||||
@@ -372,11 +387,6 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
if (debug) {
|
||||
getLogger().info("Got a forwarded vote -> " + v);
|
||||
}
|
||||
Bukkit.getScheduler().runTask(this, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Bukkit.getPluginManager().callEvent(new VotifierEvent(v));
|
||||
}
|
||||
});
|
||||
Bukkit.getScheduler().runTask(this, () -> Bukkit.getPluginManager().callEvent(new VotifierEvent(v)));
|
||||
}
|
||||
}
|
||||
+5
-10
@@ -1,33 +1,31 @@
|
||||
package com.vexsoftware.votifier.forwarding;
|
||||
|
||||
import com.vexsoftware.votifier.NuVotifierBukkit;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.support.forwarding.AbstractPluginMessagingForwardingSink;
|
||||
import com.vexsoftware.votifier.support.forwarding.ForwardedVoteListener;
|
||||
import org.apache.commons.lang.Validate;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.messaging.PluginMessageListener;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* Created by Joe Hirschfeld on 10/20/2015.
|
||||
*/
|
||||
public class BukkitPluginMessagingForwardingSink implements ForwardingVoteSink, PluginMessageListener {
|
||||
public class BukkitPluginMessagingForwardingSink extends AbstractPluginMessagingForwardingSink implements PluginMessageListener {
|
||||
|
||||
public BukkitPluginMessagingForwardingSink(Plugin p, String channel, ForwardedVoteListener listener) {
|
||||
super(listener);
|
||||
Validate.notNull(channel, "Channel cannot be null.");
|
||||
this.channel = channel;
|
||||
Bukkit.getMessenger().registerIncomingPluginChannel(p, channel, this);
|
||||
this.listener = listener;
|
||||
this.p = p;
|
||||
}
|
||||
|
||||
private final Plugin p;
|
||||
private final String channel;
|
||||
private final ForwardedVoteListener listener;
|
||||
|
||||
@Override
|
||||
public void halt() {
|
||||
@@ -37,10 +35,7 @@ public class BukkitPluginMessagingForwardingSink implements ForwardingVoteSink,
|
||||
@Override
|
||||
public void onPluginMessageReceived(String s, Player player, byte[] bytes) {
|
||||
try {
|
||||
String message = new String(bytes, StandardCharsets.UTF_8);
|
||||
JSONObject jsonObject = new JSONObject(message);
|
||||
Vote v = new Vote(jsonObject);
|
||||
listener.onForward(v);
|
||||
this.handlePluginMessage(bytes);
|
||||
} catch (Exception e) {
|
||||
NuVotifierBukkit.getInstance().getLogger().log(Level.SEVERE, "There was an unknown error when processing a forwarded vote.", e);
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.vexsoftware.votifier.forwarding;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
|
||||
/**
|
||||
* Represents a method to call when a vote is forwarded through the {@link ForwardingVoteSink}.
|
||||
*/
|
||||
public interface ForwardedVoteListener {
|
||||
|
||||
/**
|
||||
* Called whenever a vote is forwarded from a {@link ForwardingVoteSink}
|
||||
*
|
||||
* @param v Vote that was forwarded
|
||||
*/
|
||||
void onForward(Vote v);
|
||||
|
||||
}
|
||||
@@ -5,14 +5,6 @@ host: %ip%
|
||||
# Port to listen for new votes on
|
||||
port: 8192
|
||||
|
||||
# Whether or not to print debug messages. In a production system, this should be set to false.
|
||||
# This is useful when initially setting up NuVotifier to ensure votes are being delivered.
|
||||
debug: false
|
||||
|
||||
# Setting this value to false will turn off the votifier port listening for external votes. This is beneficial if the server
|
||||
# is only listening for votes coming from NuVotifier running on your BungeeCord.
|
||||
enableExternal: true
|
||||
|
||||
# Setting this option to true will disable handling of Protocol v1 packets. While the old protocol is not secure, this
|
||||
# option is currently not recommended as most voting sites only support the old protocol at present. However, if you are
|
||||
# using NuVotifier's proxy forwarding mechanism, enabling this option will increase your server's security.
|
||||
@@ -30,4 +22,4 @@ forwarding:
|
||||
# - pluginMessaging - Sets up plugin messaging
|
||||
method: none
|
||||
pluginMessaging:
|
||||
channel: NuVotifier
|
||||
channel: nuvotifier:votes
|
||||
@@ -1,5 +1,13 @@
|
||||
name: Votifier
|
||||
main: com.vexsoftware.votifier.NuVotifierBukkit
|
||||
version: maven-version-number
|
||||
version: @app.version@
|
||||
description: A plugin that gets notified when votes are made for the server on toplists.
|
||||
authors: [blakeman8192, Kramer, tuxed]
|
||||
authors: [Ichbinjoe, blakeman8192, Kramer, tuxed]
|
||||
api-version: 1.13
|
||||
|
||||
commands:
|
||||
nvreload:
|
||||
description: Reloads the NuVotifier configuration
|
||||
permission: nuvotifier.console
|
||||
permission-message: "You do not have permission to run this command."
|
||||
usage: "/nvreload"
|
||||
@@ -0,0 +1,4 @@
|
||||
dependencies {
|
||||
compileOnly group: "net.md-5", name: "bungeecord-api", version: "1.8-SNAPSHOT"
|
||||
compile project(":nuvotifier-common")
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>nuvotifier</artifactId>
|
||||
<groupId>com.vexsoftware</groupId>
|
||||
<version>2.3.2.2</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>nuvotifier-bungeecord</artifactId>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.google.code.maven-replacer-plugin</groupId>
|
||||
<artifactId>maven-replacer-plugin</artifactId>
|
||||
<version>1.3.8</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>prepare-package</phase>
|
||||
<goals>
|
||||
<goal>replace</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<file>target/classes/bungee.yml</file>
|
||||
<replacements>
|
||||
<replacement>
|
||||
<token>maven-version-number</token>
|
||||
<value>${project.version}</value>
|
||||
</replacement>
|
||||
</replacements>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>2.3.1</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>2.9</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-api</artifactId>
|
||||
<version>1.8-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.vexsoftware</groupId>
|
||||
<artifactId>nuvotifier-common</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.vexsoftware.votifier.bungee;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.vexsoftware.votifier.platform.BackendServer;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
|
||||
public class BungeeBackendServer implements BackendServer {
|
||||
private final ServerInfo info;
|
||||
|
||||
public BungeeBackendServer(ServerInfo info) {
|
||||
this.info = Preconditions.checkNotNull(info, "info");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return info.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendPluginMessage(String channel, byte[] data) {
|
||||
return info.sendData(channel, data, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return info.getName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.vexsoftware.votifier.bungee;
|
||||
|
||||
import com.vexsoftware.votifier.platform.scheduler.ScheduledVotifierTask;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
import net.md_5.bungee.api.scheduler.ScheduledTask;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
class BungeeScheduler implements VotifierScheduler {
|
||||
private final NuVotifier plugin;
|
||||
|
||||
public BungeeScheduler(NuVotifier plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask sync(Runnable runnable) {
|
||||
return onPool(runnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask onPool(Runnable runnable) {
|
||||
return new BungeeTaskWrapper(plugin.getProxy().getScheduler().runAsync(plugin, runnable));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask delayedSync(Runnable runnable, int delay, TimeUnit unit) {
|
||||
return delayedOnPool(runnable, delay, unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask delayedOnPool(Runnable runnable, int delay, TimeUnit unit) {
|
||||
return new BungeeTaskWrapper(plugin.getProxy().getScheduler().schedule(plugin, runnable, delay, unit));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask repeatOnPool(Runnable runnable, int delay, int repeat, TimeUnit unit) {
|
||||
return new BungeeTaskWrapper(plugin.getProxy().getScheduler().schedule(plugin, runnable, delay, repeat, unit));
|
||||
}
|
||||
|
||||
private static class BungeeTaskWrapper implements ScheduledVotifierTask {
|
||||
private final ScheduledTask task;
|
||||
|
||||
private BungeeTaskWrapper(ScheduledTask task) {
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
this.task.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,33 +4,32 @@ import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.common.io.Files;
|
||||
import com.vexsoftware.votifier.VoteHandler;
|
||||
import com.vexsoftware.votifier.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.net.VotifierServerBootstrap;
|
||||
import com.vexsoftware.votifier.platform.BackendServer;
|
||||
import com.vexsoftware.votifier.platform.JavaUtilLogger;
|
||||
import com.vexsoftware.votifier.platform.LoggingAdapter;
|
||||
import com.vexsoftware.votifier.platform.ProxyVotifierPlugin;
|
||||
import com.vexsoftware.votifier.bungee.events.VotifierEvent;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.ForwardingVoteSource;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.OnlineForwardPluginMessagingForwardingSource;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.PluginMessagingForwardingSource;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.cache.FileVoteCache;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.cache.MemoryVoteCache;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.cache.VoteCache;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.proxy.ProxyForwardingVoteSource;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
import com.vexsoftware.votifier.support.forwarding.ForwardingVoteSource;
|
||||
import com.vexsoftware.votifier.support.forwarding.ServerFilter;
|
||||
import com.vexsoftware.votifier.support.forwarding.cache.FileVoteCache;
|
||||
import com.vexsoftware.votifier.support.forwarding.cache.MemoryVoteCache;
|
||||
import com.vexsoftware.votifier.support.forwarding.cache.VoteCache;
|
||||
import com.vexsoftware.votifier.support.forwarding.proxy.ProxyForwardingVoteSource;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.net.protocol.VoteInboundHandler;
|
||||
import com.vexsoftware.votifier.net.protocol.VotifierGreetingHandler;
|
||||
import com.vexsoftware.votifier.net.protocol.VotifierProtocolDifferentiator;
|
||||
import com.vexsoftware.votifier.net.protocol.v1crypto.RSAIO;
|
||||
import com.vexsoftware.votifier.net.protocol.v1crypto.RSAKeygen;
|
||||
import com.vexsoftware.votifier.util.KeyCreator;
|
||||
import com.vexsoftware.votifier.util.TokenUtil;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
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;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import net.md_5.bungee.config.Configuration;
|
||||
import net.md_5.bungee.config.ConfigurationProvider;
|
||||
@@ -40,31 +39,23 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.FutureTask;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class NuVotifier extends Plugin implements VoteHandler, VotifierPlugin {
|
||||
public class NuVotifier extends Plugin implements VoteHandler, ProxyVotifierPlugin {
|
||||
|
||||
/**
|
||||
* The server channel.
|
||||
*/
|
||||
private Channel serverChannel;
|
||||
|
||||
/**
|
||||
* The event group handling the channel.
|
||||
*/
|
||||
private NioEventLoopGroup serverGroup;
|
||||
private VotifierServerBootstrap bootstrap;
|
||||
|
||||
/**
|
||||
* The RSA key pair.
|
||||
@@ -86,15 +77,19 @@ public class NuVotifier extends Plugin implements VoteHandler, VotifierPlugin {
|
||||
*/
|
||||
private ForwardingVoteSource forwardingMethod;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
private VotifierScheduler scheduler;
|
||||
private LoggingAdapter pluginLogger;
|
||||
|
||||
private void loadAndBind() {
|
||||
scheduler = new BungeeScheduler(this);
|
||||
pluginLogger = new JavaUtilLogger(getLogger());
|
||||
if (!getDataFolder().exists()) {
|
||||
getDataFolder().mkdir();
|
||||
}
|
||||
|
||||
// Handle configuration.
|
||||
File config = new File(getDataFolder() + "/config.yml");
|
||||
File rsaDirectory = new File(getDataFolder() + "/rsa");
|
||||
File config = new File(getDataFolder() , "config.yml");
|
||||
File rsaDirectory = new File(getDataFolder() , "rsa");
|
||||
Configuration configuration;
|
||||
|
||||
if (!config.exists()) {
|
||||
@@ -108,7 +103,7 @@ public class NuVotifier extends Plugin implements VoteHandler, VotifierPlugin {
|
||||
String cfgStr = new String(ByteStreams.toByteArray(getResourceAsStream("bungeeConfig.yml")), StandardCharsets.UTF_8);
|
||||
String token = TokenUtil.newToken();
|
||||
cfgStr = cfgStr.replace("%default_token%", token);
|
||||
Files.write(cfgStr, config, StandardCharsets.UTF_8);
|
||||
Files.asCharSink(config, StandardCharsets.UTF_8).write(cfgStr);
|
||||
|
||||
/*
|
||||
* Remind hosted server admins to be sure they have the right
|
||||
@@ -184,9 +179,14 @@ public class NuVotifier extends Plugin implements VoteHandler, VotifierPlugin {
|
||||
// Initialize the receiver.
|
||||
final String host = configuration.getString("host", "0.0.0.0");
|
||||
final int port = configuration.getInt("port", 8192);
|
||||
debug = configuration.getBoolean("debug", false);
|
||||
if (debug)
|
||||
getLogger().info("DEBUG mode enabled!");
|
||||
|
||||
if (configuration.get("quiet") != null)
|
||||
debug = !configuration.getBoolean("quiet");
|
||||
else
|
||||
debug = configuration.getBoolean("debug", true);
|
||||
|
||||
if (!debug)
|
||||
getLogger().info("QUIET mode enabled!");
|
||||
|
||||
final boolean disablev1 = configuration.getBoolean("disable-v1-protocol");
|
||||
if (disablev1) {
|
||||
@@ -197,41 +197,9 @@ public class NuVotifier extends Plugin implements VoteHandler, VotifierPlugin {
|
||||
}
|
||||
|
||||
// Must set up server asynchronously due to BungeeCord goofiness.
|
||||
FutureTask<?> initTask = new FutureTask<>(Executors.callable(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
serverGroup = new NioEventLoopGroup(2);
|
||||
|
||||
new ServerBootstrap()
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.group(serverGroup)
|
||||
.childHandler(new ChannelInitializer<NioSocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(NioSocketChannel channel) throws Exception {
|
||||
channel.attr(VotifierSession.KEY).set(new VotifierSession());
|
||||
channel.attr(VotifierPlugin.KEY).set(NuVotifier.this);
|
||||
channel.pipeline().addLast("greetingHandler", new VotifierGreetingHandler());
|
||||
channel.pipeline().addLast("protocolDifferentiator", new VotifierProtocolDifferentiator(false, !disablev1));
|
||||
channel.pipeline().addLast("voteHandler", new VoteInboundHandler(NuVotifier.this));
|
||||
}
|
||||
})
|
||||
.bind(host, port)
|
||||
.addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
if (future.isSuccess()) {
|
||||
serverChannel = future.channel();
|
||||
getLogger().info("Votifier enabled on socket "+serverChannel.localAddress()+".");
|
||||
} else {
|
||||
SocketAddress socketAddress = future.channel().localAddress();
|
||||
if (socketAddress == null) {
|
||||
socketAddress = new InetSocketAddress(host, port);
|
||||
}
|
||||
getLogger().log(Level.SEVERE, "Votifier was not able to bind to " + socketAddress, future.cause());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
FutureTask<?> initTask = new FutureTask<>(Executors.callable(() -> {
|
||||
this.bootstrap = new VotifierServerBootstrap(host, port, NuVotifier.this, disablev1);
|
||||
this.bootstrap.start(err -> {});
|
||||
}));
|
||||
getProxy().getScheduler().runAsync(this, initTask);
|
||||
try {
|
||||
@@ -251,22 +219,31 @@ public class NuVotifier extends Plugin implements VoteHandler, VotifierPlugin {
|
||||
if ("none".equals(cacheMethod)) {
|
||||
getLogger().info("Vote cache none selected for caching: votes that cannot be immediately delivered will be lost.");
|
||||
} else if ("memory".equals(cacheMethod)) {
|
||||
voteCache = new MemoryVoteCache(ProxyServer.getInstance().getServers().size());
|
||||
voteCache = new MemoryVoteCache(
|
||||
ProxyServer.getInstance().getServers().size(), this,
|
||||
fwdCfg.getInt("pluginMessaging.memory.cacheTime", -1));
|
||||
getLogger().info("Using in-memory cache for votes that are not able to be delivered.");
|
||||
} else if ("file".equals(cacheMethod)) {
|
||||
try {
|
||||
voteCache = new FileVoteCache(ProxyServer.getInstance().getServers().size(), this, new File(getDataFolder(),
|
||||
fwdCfg.getString("pluginMessaging.file.name")));
|
||||
voteCache = new FileVoteCache(
|
||||
ProxyServer.getInstance().getServers().size(), this,
|
||||
new File(getDataFolder(), fwdCfg.getString("pluginMessaging.file.name")),
|
||||
fwdCfg.getInt("pluginMessaging.file.cacheTime", -1));
|
||||
} catch (IOException e) {
|
||||
getLogger().log(Level.SEVERE, "Unload to load file cache. Votes will be lost!", e);
|
||||
}
|
||||
}
|
||||
|
||||
int dumpRate = fwdCfg.getInt("pluginMessaging.dumpRate", 5);
|
||||
|
||||
ServerFilter filter = new ServerFilter(
|
||||
fwdCfg.getStringList("pluginMessaging.excludedServers"),
|
||||
fwdCfg.getBoolean("pluginMessaging.whitelist", false)
|
||||
);
|
||||
|
||||
if (!fwdCfg.getBoolean("pluginMessaging.onlySendToJoinedServer")) {
|
||||
|
||||
List<String> ignoredServers = fwdCfg.getStringList("pluginMessaging.excludedServers");
|
||||
|
||||
try {
|
||||
forwardingMethod = new PluginMessagingForwardingSource(channel, ignoredServers, this, voteCache);
|
||||
forwardingMethod = new PluginMessagingForwardingSource(channel, filter, this, voteCache, dumpRate);
|
||||
getLogger().info("Forwarding votes over PluginMessaging channel '" + channel + "' for vote forwarding!");
|
||||
} catch (RuntimeException e) {
|
||||
getLogger().log(Level.SEVERE, "NuVotifier could not set up PluginMessaging for vote forwarding!", e);
|
||||
@@ -275,7 +252,7 @@ public class NuVotifier extends Plugin implements VoteHandler, VotifierPlugin {
|
||||
try {
|
||||
String fallbackServer = fwdCfg.getString("pluginMessaging.joinedServerFallback", null);
|
||||
if (fallbackServer != null && fallbackServer.isEmpty()) fallbackServer = null;
|
||||
forwardingMethod = new OnlineForwardPluginMessagingForwardingSource(channel, this, voteCache, fallbackServer);
|
||||
forwardingMethod = new OnlineForwardPluginMessagingForwardingSource(channel, this, filter, voteCache, fallbackServer, dumpRate);
|
||||
} catch (RuntimeException e) {
|
||||
getLogger().log(Level.SEVERE, "NuVotifier could not set up PluginMessaging for vote forwarding!", e);
|
||||
}
|
||||
@@ -292,66 +269,113 @@ public class NuVotifier extends Plugin implements VoteHandler, VotifierPlugin {
|
||||
getLogger().info("Address " + section.getString("address") + " couldn't be looked up. Ignoring!");
|
||||
continue;
|
||||
}
|
||||
ProxyForwardingVoteSource.BackendServer server = new ProxyForwardingVoteSource.BackendServer(s,
|
||||
new InetSocketAddress(address, section.getShort("port")),
|
||||
KeyCreator.createKeyFrom(section.getString("token",section.getString("key"))));
|
||||
serverList.add(server);
|
||||
|
||||
Key token = null;
|
||||
try {
|
||||
token = KeyCreator.createKeyFrom(section.getString("token", section.getString("key")));
|
||||
} catch (IllegalArgumentException e) {
|
||||
getLogger().log(Level.SEVERE,
|
||||
"An exception occurred while attempting to add proxy target '" + s + "' - maybe your token is wrong? " +
|
||||
"Votes will not be forwarded to this server!", e);
|
||||
}
|
||||
|
||||
if (token != null) {
|
||||
ProxyForwardingVoteSource.BackendServer server = new ProxyForwardingVoteSource.BackendServer(s,
|
||||
new InetSocketAddress(address, section.getInt("port")),
|
||||
token);
|
||||
serverList.add(server);
|
||||
}
|
||||
}
|
||||
|
||||
forwardingMethod = new ProxyForwardingVoteSource(this, serverGroup, serverList, null);
|
||||
forwardingMethod = new ProxyForwardingVoteSource(this, bootstrap::client, serverList, null);
|
||||
getLogger().info("Forwarding votes from this NuVotifier instance to another NuVotifier server.");
|
||||
} else {
|
||||
getLogger().severe("No vote forwarding method '" + fwdMethod + "' known. Defaulting to noop implementation.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
public void onEnable() {
|
||||
loadAndBind();
|
||||
|
||||
getProxy().getPluginManager().registerCommand(this, new Command("nvreload") {
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
if (sender instanceof ProxiedPlayer)
|
||||
sender.sendMessage(ChatColor.RED + "For security and stability, only console may run this command!");
|
||||
else
|
||||
reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void halt() {
|
||||
// Shut down the network handlers.
|
||||
if (serverChannel != null)
|
||||
serverChannel.close();
|
||||
serverGroup.shutdownGracefully();
|
||||
if (bootstrap != null) {
|
||||
bootstrap.shutdown();
|
||||
bootstrap = null;
|
||||
}
|
||||
|
||||
if (forwardingMethod != null) {
|
||||
forwardingMethod.halt();
|
||||
forwardingMethod = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
try {
|
||||
halt();
|
||||
} catch (Exception ex) {
|
||||
getLogger().log(Level.SEVERE, "On halt, an exception was thrown. This may be fine!", ex);
|
||||
}
|
||||
|
||||
try {
|
||||
loadAndBind();
|
||||
getLogger().info("Reload was successful.");
|
||||
} catch (Exception ex) {
|
||||
try {
|
||||
halt();
|
||||
getLogger().log(Level.SEVERE, "On reload, there was a problem with the configuration. Votifier currently does nothing!", ex);
|
||||
} catch (Exception ex2) {
|
||||
getLogger().log(Level.SEVERE, "On reload, there was a problem loading, and we could not re-halt the server. Votifier is in an unstable state!", ex);
|
||||
getLogger().log(Level.SEVERE, "(halt exception)", ex2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
halt();
|
||||
getLogger().info("Votifier disabled.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVoteReceived(final Vote vote, VotifierSession.ProtocolVersion protocolVersion) throws Exception {
|
||||
public void onVoteReceived(Channel channel, final Vote vote, VotifierSession.ProtocolVersion protocolVersion) {
|
||||
if (debug) {
|
||||
if (protocolVersion == VotifierSession.ProtocolVersion.ONE) {
|
||||
getLogger().info("Got a protocol v1 vote record -> " + vote);
|
||||
getLogger().info("Got a protocol v1 vote record from " + channel.remoteAddress() + " -> " + vote);
|
||||
} else {
|
||||
getLogger().info("Got a protocol v2 vote record -> " + vote);
|
||||
getLogger().info("Got a protocol v2 vote record from " + channel.remoteAddress() + " -> " + vote);
|
||||
}
|
||||
}
|
||||
|
||||
getProxy().getScheduler().runAsync(this, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
getProxy().getPluginManager().callEvent(new VotifierEvent(vote));
|
||||
}
|
||||
});
|
||||
getProxy().getScheduler().runAsync(this, () -> getProxy().getPluginManager().callEvent(new VotifierEvent(vote)));
|
||||
|
||||
if (forwardingMethod != null) {
|
||||
getProxy().getScheduler().runAsync(this, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
forwardingMethod.forward(vote);
|
||||
}
|
||||
});
|
||||
getProxy().getScheduler().runAsync(this, () -> forwardingMethod.forward(vote));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Channel channel, Throwable throwable) {
|
||||
public void onError(Channel channel, boolean alreadyHandledVote, Throwable throwable) {
|
||||
if (debug) {
|
||||
getLogger().log(Level.SEVERE, "Unable to process vote from " + channel.remoteAddress(), throwable);
|
||||
} else {
|
||||
if (alreadyHandledVote) {
|
||||
getLogger().log(Level.SEVERE, "Vote processed, however an exception " +
|
||||
"occurred with a vote from " + channel.remoteAddress(), throwable);
|
||||
} else {
|
||||
getLogger().log(Level.SEVERE, "Unable to process vote from " + channel.remoteAddress(), throwable);
|
||||
}
|
||||
} else if (!alreadyHandledVote) {
|
||||
getLogger().log(Level.SEVERE, "Unable to process vote from " + channel.remoteAddress());
|
||||
}
|
||||
}
|
||||
@@ -367,11 +391,29 @@ public class NuVotifier extends Plugin implements VoteHandler, VotifierPlugin {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return getDescription().getVersion();
|
||||
public LoggingAdapter getPluginLogger() {
|
||||
return pluginLogger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VotifierScheduler getScheduler() {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
public boolean isDebug() {
|
||||
return debug;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<BackendServer> getAllBackendServers() {
|
||||
return getProxy().getServers().values().stream()
|
||||
.map(BungeeBackendServer::new)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<BackendServer> getServer(String name) {
|
||||
ServerInfo info = getProxy().getServerInfo(name);
|
||||
return Optional.ofNullable(info).map(BungeeBackendServer::new);
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package com.vexsoftware.votifier.bungee;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.platform.BackendServer;
|
||||
import com.vexsoftware.votifier.support.forwarding.AbstractPluginMessagingForwardingSource;
|
||||
import com.vexsoftware.votifier.support.forwarding.ServerFilter;
|
||||
import com.vexsoftware.votifier.support.forwarding.cache.VoteCache;
|
||||
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.ServerConnectedEvent;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
|
||||
/**
|
||||
* @author Joseph Hirschfeld
|
||||
* @date 12/31/2015
|
||||
*/
|
||||
public final class OnlineForwardPluginMessagingForwardingSource extends AbstractPluginMessagingForwardingSource implements Listener {
|
||||
public OnlineForwardPluginMessagingForwardingSource(String channel, NuVotifier nuVotifier, ServerFilter serverFilter,
|
||||
VoteCache cache, String fallbackServer, int dumpRate) {
|
||||
super(channel, serverFilter, nuVotifier, cache, dumpRate);
|
||||
this.fallbackServer = fallbackServer;
|
||||
ProxyServer.getInstance().getPluginManager().registerListener(nuVotifier, this);
|
||||
}
|
||||
|
||||
private final String fallbackServer;
|
||||
|
||||
@Override
|
||||
public void forward(Vote v) {
|
||||
ProxiedPlayer p = ProxyServer.getInstance().getPlayer(v.getUsername());
|
||||
if (p != null && p.getServer() != null &&
|
||||
serverFilter.isAllowed(p.getServer().getInfo().getName())) {
|
||||
if (forwardSpecific(new BungeeBackendServer(p.getServer().getInfo()), v)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ServerInfo serverInfo = ProxyServer.getInstance().getServers().get(fallbackServer);
|
||||
|
||||
// nowhere to fall back to, yet still not online. lets save this vote yet!
|
||||
if (serverInfo == null)
|
||||
attemptToAddToPlayerCache(v, v.getUsername());
|
||||
else if (!forwardSpecific(new BungeeBackendServer(serverInfo), v))
|
||||
attemptToAddToCache(v, fallbackServer);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPluginMessage(PluginMessageEvent e) {
|
||||
if (e.getTag().equals(channel)) e.setCancelled(true);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onServerConnected(final ServerConnectedEvent e) { //Attempt to resend any votes that were previously cached.
|
||||
BackendServer server = new BungeeBackendServer(e.getServer().getInfo());
|
||||
handlePlayerSwitch(server, e.getPlayer().getName());
|
||||
onServerConnect(server);
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package com.vexsoftware.votifier.bungee;
|
||||
|
||||
import com.vexsoftware.votifier.support.forwarding.AbstractPluginMessagingForwardingSource;
|
||||
import com.vexsoftware.votifier.support.forwarding.ServerFilter;
|
||||
import com.vexsoftware.votifier.support.forwarding.cache.VoteCache;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.event.PluginMessageEvent;
|
||||
import net.md_5.bungee.api.event.ServerConnectedEvent;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
|
||||
public class PluginMessagingForwardingSource extends AbstractPluginMessagingForwardingSource implements Listener {
|
||||
|
||||
public PluginMessagingForwardingSource(String channel, ServerFilter serverFilter, NuVotifier nuVotifier, VoteCache cache, int dumpRate) {
|
||||
super(channel, serverFilter, nuVotifier, cache, dumpRate);
|
||||
ProxyServer.getInstance().getPluginManager().registerListener(nuVotifier, this);
|
||||
}
|
||||
|
||||
protected PluginMessagingForwardingSource(String channel, NuVotifier nuVotifier, VoteCache voteCache, int dumpRate) {
|
||||
super(channel, nuVotifier, voteCache, dumpRate);
|
||||
ProxyServer.getInstance().getPluginManager().registerListener(nuVotifier, this);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPluginMessage(PluginMessageEvent e) {
|
||||
if (e.getTag().equals(channel)) e.setCancelled(true);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onServerConnected(final ServerConnectedEvent e) { //Attempt to resend any votes that were previously cached.
|
||||
onServerConnect(new BungeeBackendServer(e.getServer().getInfo()));
|
||||
}
|
||||
}
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding;
|
||||
|
||||
import com.vexsoftware.votifier.bungee.NuVotifier;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.cache.VoteCache;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
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.ServerConnectedEvent;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
|
||||
/**
|
||||
* @author Joseph Hirschfeld
|
||||
* @date 12/31/2015
|
||||
*/
|
||||
public final class OnlineForwardPluginMessagingForwardingSource extends PluginMessagingForwardingSource {
|
||||
public OnlineForwardPluginMessagingForwardingSource(String channel, NuVotifier nuVotifier, VoteCache cache, String fallbackServer) {
|
||||
super(channel, nuVotifier, cache);
|
||||
this.fallbackServer = fallbackServer;
|
||||
}
|
||||
|
||||
private final String fallbackServer;
|
||||
|
||||
@Override
|
||||
public void forward(Vote v) {
|
||||
ProxiedPlayer p = ProxyServer.getInstance().getPlayer(v.getUsername());
|
||||
if (p == null || !forwardSpecific(p.getServer().getInfo(), v)) {
|
||||
ServerInfo serverInfo = ProxyServer.getInstance().getServers().get(fallbackServer);
|
||||
if (serverInfo == null || !forwardSpecific(serverInfo, v)) {
|
||||
attemptToAddToCache(v, fallbackServer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The below is to allow us to share code since the event handlers from the parent class don't get
|
||||
// picked up.
|
||||
@EventHandler
|
||||
public void onPluginMessage(PluginMessageEvent e) {
|
||||
if (e.getTag().equals(channel)) e.setCancelled(true);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onServerConnected(final ServerConnectedEvent e) { //Attempt to resend any votes that were previously cached.
|
||||
handleServerConnected(e);
|
||||
}
|
||||
}
|
||||
-123
@@ -1,123 +0,0 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding;
|
||||
|
||||
import com.vexsoftware.votifier.bungee.NuVotifier;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.cache.FileVoteCache;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.cache.VoteCache;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
import net.md_5.bungee.api.event.PluginMessageEvent;
|
||||
import net.md_5.bungee.api.event.ServerConnectedEvent;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* Created by Joe Hirschfeld on 10/20/2015.
|
||||
*/
|
||||
public class PluginMessagingForwardingSource implements ForwardingVoteSource, Listener {
|
||||
|
||||
public PluginMessagingForwardingSource(String channel, List<String> ignoredServers, NuVotifier nuVotifier, VoteCache cache) {
|
||||
ProxyServer.getInstance().registerChannel(channel);
|
||||
ProxyServer.getInstance().getPluginManager().registerListener(nuVotifier, this);
|
||||
this.channel = channel;
|
||||
this.nuVotifier = nuVotifier;
|
||||
this.cache = cache;
|
||||
this.ignoredServers = ignoredServers;
|
||||
}
|
||||
|
||||
protected PluginMessagingForwardingSource(String channel, NuVotifier nuVotifier, VoteCache voteCache) {
|
||||
this(channel, null, nuVotifier, voteCache);
|
||||
}
|
||||
|
||||
protected final NuVotifier nuVotifier;
|
||||
protected final String channel;
|
||||
protected final VoteCache cache;
|
||||
protected final List<String> ignoredServers;
|
||||
|
||||
@Override
|
||||
public void forward(Vote v) {
|
||||
byte[] rawData = v.serialize().toString().getBytes(StandardCharsets.UTF_8);
|
||||
for (ServerInfo s : ProxyServer.getInstance().getServers().values()) {
|
||||
if (ignoredServers != null && ignoredServers.contains(s.getName())) continue;
|
||||
if (!forwardSpecific(s, rawData)) attemptToAddToCache(v, s.getName());
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean forwardSpecific(ServerInfo connection, Vote vote) {
|
||||
byte[] rawData = vote.serialize().toString().getBytes(StandardCharsets.UTF_8);
|
||||
return connection.sendData(channel, rawData, false);
|
||||
}
|
||||
|
||||
private boolean forwardSpecific(ServerInfo connection, byte[] data) {
|
||||
return connection.sendData(channel, data, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void halt() {
|
||||
ProxyServer.getInstance().unregisterChannel(channel);
|
||||
|
||||
if (cache != null && cache instanceof FileVoteCache) {
|
||||
try {
|
||||
((FileVoteCache) cache).halt();
|
||||
} catch (IOException e) {
|
||||
nuVotifier.getLogger().log(Level.SEVERE, "Unable to save cached votes, votes will be lost.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPluginMessage(PluginMessageEvent e) {
|
||||
if (e.getTag().equals(channel)) e.setCancelled(true);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onServerConnected(final ServerConnectedEvent e) { //Attempt to resend any votes that were previously cached.
|
||||
handleServerConnected(e);
|
||||
}
|
||||
|
||||
protected void handleServerConnected(final ServerConnectedEvent e) {
|
||||
if (cache == null) return;
|
||||
final String serverName = e.getServer().getInfo().getName();
|
||||
final Collection<Vote> cachedVotes = cache.evict(serverName);
|
||||
if (!cachedVotes.isEmpty()) {
|
||||
nuVotifier.getProxy().getScheduler().schedule(nuVotifier, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
int evicted = 0;
|
||||
int unsuccessfulEvictions = 0;
|
||||
for (Vote v : cachedVotes) {
|
||||
if (forwardSpecific(e.getServer().getInfo(), v)) {
|
||||
evicted++;
|
||||
} else {
|
||||
// Re-add to cache to send later.
|
||||
cache.addToCache(v, serverName);
|
||||
unsuccessfulEvictions++;
|
||||
}
|
||||
}
|
||||
if (nuVotifier.isDebug()) {
|
||||
nuVotifier.getLogger().info("Successfully evicted " + evicted + " votes to server '" + serverName + "'.");
|
||||
if (unsuccessfulEvictions > 0) {
|
||||
nuVotifier.getLogger().info("Held " + unsuccessfulEvictions + " votes for server '" + serverName + "'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 1, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
protected void attemptToAddToCache(Vote v, String server) {
|
||||
if (cache != null) {
|
||||
cache.addToCache(v, server);
|
||||
if (nuVotifier.isDebug())
|
||||
nuVotifier.getLogger().info("Added to forwarding cache: " + v + " -> " + server);
|
||||
} else if (nuVotifier.isDebug())
|
||||
nuVotifier.getLogger().severe("Could not immediately send vote to backend, vote lost! " + v + " -> " + server);
|
||||
}
|
||||
}
|
||||
Vendored
-87
@@ -1,87 +0,0 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.cache;
|
||||
|
||||
import com.google.common.io.Files;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import net.md_5.bungee.api.scheduler.ScheduledTask;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class FileVoteCache extends MemoryVoteCache {
|
||||
|
||||
private final File cacheFile;
|
||||
private final ScheduledTask saveTask;
|
||||
|
||||
public FileVoteCache(int initialMemorySize, final Plugin plugin, File cacheFile) throws IOException {
|
||||
super(initialMemorySize);
|
||||
this.cacheFile = cacheFile;
|
||||
load();
|
||||
|
||||
saveTask = ProxyServer.getInstance().getScheduler().schedule(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
save();
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Unable to save cached votes, votes will be lost if you restart.", e);
|
||||
}
|
||||
}
|
||||
}, 3, 3, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
private void load() throws IOException {
|
||||
if (cacheFile.exists()) {
|
||||
// Load the cache from disk
|
||||
JSONObject object = new JSONObject(Files.toString(cacheFile, StandardCharsets.UTF_8));
|
||||
|
||||
// Deserialize all votes contained
|
||||
for (Object server : object.keySet()) {
|
||||
JSONArray voteArray = object.optJSONArray(((String) server));
|
||||
if (voteArray == null) continue;
|
||||
Set<Vote> votes = new LinkedHashSet<>(voteArray.length());
|
||||
for (int i = 0; i < voteArray.length(); i++) {
|
||||
votes.add(new Vote(voteArray.getJSONObject(i)));
|
||||
}
|
||||
voteCache.put(((String) server), votes);
|
||||
}
|
||||
} else {
|
||||
cacheFile.createNewFile();
|
||||
}
|
||||
}
|
||||
|
||||
public void save() throws IOException {
|
||||
cacheLock.lock();
|
||||
// Create a copy of the votes.
|
||||
JSONObject votesObject = new JSONObject();
|
||||
for (Map.Entry<String, Collection<Vote>> entry : voteCache.entrySet()) {
|
||||
JSONArray array = new JSONArray();
|
||||
for (Vote vote : entry.getValue()) {
|
||||
array.put(vote.serialize());
|
||||
}
|
||||
votesObject.put(entry.getKey(), array);
|
||||
}
|
||||
cacheLock.unlock();
|
||||
|
||||
try (BufferedWriter writer = Files.newWriter(cacheFile, StandardCharsets.UTF_8)) {
|
||||
votesObject.write(writer);
|
||||
}
|
||||
}
|
||||
|
||||
public void halt() throws IOException {
|
||||
saveTask.cancel();
|
||||
save();
|
||||
}
|
||||
|
||||
}
|
||||
Vendored
-49
@@ -1,49 +0,0 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.cache;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* Created by Joe Hirschfeld on 10/20/2015.
|
||||
*/
|
||||
public class MemoryVoteCache implements VoteCache {
|
||||
|
||||
public MemoryVoteCache(int initialSize) {
|
||||
voteCache = new HashMap<>(initialSize);
|
||||
}
|
||||
|
||||
protected final Map<String, Collection<Vote>> voteCache;
|
||||
|
||||
protected final ReentrantLock cacheLock = new ReentrantLock();
|
||||
|
||||
@Override
|
||||
public Collection<String> getCachedServers() {
|
||||
cacheLock.lock();
|
||||
Set<String> servers = ImmutableSet.copyOf(voteCache.keySet());
|
||||
cacheLock.unlock();
|
||||
return servers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addToCache(Vote v, String server) {
|
||||
cacheLock.lock();
|
||||
Collection<Vote> voteCollection = voteCache.get(server);
|
||||
if (voteCollection == null) {
|
||||
voteCollection = new LinkedHashSet<>();
|
||||
voteCache.put(server, voteCollection);
|
||||
}
|
||||
voteCollection.add(v);
|
||||
cacheLock.unlock();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Vote> evict(String server) {
|
||||
cacheLock.lock();
|
||||
Collection<Vote> fromCollection = voteCache.remove(server);
|
||||
cacheLock.unlock();
|
||||
return fromCollection != null ? ImmutableSet.copyOf(fromCollection) : ImmutableSet.<Vote>of();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
name: NuVotifier
|
||||
main: com.vexsoftware.votifier.bungee.NuVotifier
|
||||
version: maven-version-number
|
||||
author: blakeman8192, Kramer, tuxed
|
||||
version: @app.version@
|
||||
author: Ichbinjoe, blakeman8192, Kramer, tuxed
|
||||
@@ -5,10 +5,6 @@ host: 0.0.0.0
|
||||
# Port to listen for new votes on
|
||||
port: 8192
|
||||
|
||||
# Whether or not to print debug messages. In a production system, this should be set to false.
|
||||
# This is useful when initially setting up NuVotifier to ensure votes are being delivered.
|
||||
debug: false
|
||||
|
||||
# All tokens, labeled by the serviceName of each server list.
|
||||
tokens:
|
||||
# Default token for all server lists, if another isn't supplied.
|
||||
@@ -26,21 +22,22 @@ forwarding:
|
||||
# - proxy - Proxies votes to other NuVotifier servers from this server.
|
||||
method: none
|
||||
pluginMessaging:
|
||||
channel: NuVotifier
|
||||
channel: nuvotifier:votes
|
||||
|
||||
#Only send votes to certain servers. If a server is present on excludedServers, then votes will NOT be forwarded
|
||||
#to them. If excludedServers is not present, votes will be sent to all servers.
|
||||
#If onlySendToJoinedServer is true, this option is ignored.
|
||||
|
||||
# If onlySendToJoinedServer is true and a player is on one of these servers, NuVotifier will process the vote as if the
|
||||
# player was not on the network at all.
|
||||
#excludedServers:
|
||||
#- server1
|
||||
#- server2
|
||||
|
||||
# If uncommented, turns excludedServers into an includedServers, where only servers on the list will be used.
|
||||
#whitelist: true
|
||||
|
||||
# Sets which cache to use if there are no players on the server that the vote is being sent to. Supported caches:
|
||||
# - none - Votes will simply be lost when no players are present on the server.
|
||||
# - memory - Votes will be cached in memory when no players are present. When Bungee is restarted, the votes will be lost.
|
||||
# This method is NOT recommended for production systems!
|
||||
# - file - Votes will be saved to a local file every 3 minutes and on shutdown and reloaded when Bungee is restarted. This is suitable for
|
||||
# most set ups.
|
||||
cache: file
|
||||
@@ -50,9 +47,17 @@ forwarding:
|
||||
#If you do not want the vote forwarded to a fallback, set this value to empty ('')
|
||||
#ONLY USED IF onlySendToJoinedServer is true!!
|
||||
joinedServerFallback: 'Hub'
|
||||
# Defines how quickly to dump votes over a player's connection when offloading a cache in votes per second
|
||||
dumpRate: 5
|
||||
# Options for file caching.
|
||||
file:
|
||||
name: cached-votes.json
|
||||
# days before a vote is considered 'dead' - removed from cache with a console warning
|
||||
cacheTime: 10
|
||||
# Options for memory caching.
|
||||
memory:
|
||||
# days before a vote is considered 'dead' and removed from memory. All votes are removed when the server restarts. -1 signifies no TTL
|
||||
cacheTime: -1
|
||||
# Specify servers to proxy votes for.
|
||||
proxy:
|
||||
Hub:
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
dependencies {
|
||||
compile group: "io.netty", name: "netty-handler", version: "4.1.32.Final"
|
||||
compile 'com.google.code.gson:gson:2.8.0'
|
||||
compile 'com.google.guava:guava:24.1-jre'
|
||||
testCompile group: "junit", name: "junit", version: "4.12"
|
||||
testCompile group: "org.json", name: "json", version: "20180130" // retain this for testing reasons
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>nuvotifier</artifactId>
|
||||
<groupId>com.vexsoftware</groupId>
|
||||
<version>2.3.2.2</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>nuvotifier-common</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<!-- Netty Dependency -->
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-handler</artifactId>
|
||||
<version>4.0.31.Final</version>
|
||||
<type>jar</type>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<!-- JSON Dependency -->
|
||||
<dependency>
|
||||
<groupId>org.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
<version>20140107</version>
|
||||
<type>jar</type>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<!-- JUnit, for tests -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.12</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -5,7 +5,16 @@ import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import io.netty.channel.Channel;
|
||||
|
||||
public interface VoteHandler {
|
||||
void onVoteReceived(Vote vote, VotifierSession.ProtocolVersion protocolVersion) throws Exception;
|
||||
void onVoteReceived(Channel channel, Vote vote, VotifierSession.ProtocolVersion protocolVersion) throws Exception;
|
||||
|
||||
void onError(Channel channel, Throwable throwable);
|
||||
// these default members are kept for compatibility sake - there probably isn't anyone out there implementing these
|
||||
// on their own, but we still deprecate them before removing them because we are nice!
|
||||
default void onError(Channel channel, boolean voteAlreadyCompleted, Throwable throwable) {
|
||||
onError(channel, throwable);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
default void onError(Channel channel, Throwable throwable) {
|
||||
throw new RuntimeException("Unimplemented onError handler");
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.vexsoftware.votifier;
|
||||
|
||||
import io.netty.util.AttributeKey;
|
||||
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
import java.util.Map;
|
||||
|
||||
public interface VotifierPlugin {
|
||||
AttributeKey<VotifierPlugin> KEY = AttributeKey.valueOf("votifier_plugin");
|
||||
|
||||
Map<String, Key> getTokens();
|
||||
|
||||
KeyPair getProtocolV1Key();
|
||||
|
||||
String getVersion();
|
||||
}
|
||||
@@ -18,7 +18,8 @@
|
||||
|
||||
package com.vexsoftware.votifier.model;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* A model for a vote.
|
||||
@@ -47,33 +48,50 @@ public class Vote {
|
||||
*/
|
||||
private String timeStamp;
|
||||
|
||||
/**
|
||||
* Timestamp (unix-millis) normalized and taken from a known source
|
||||
*/
|
||||
private final long localTimestamp;
|
||||
|
||||
@Deprecated
|
||||
public Vote() {
|
||||
localTimestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public Vote(String serviceName, String username, String address, String timeStamp) {
|
||||
this(serviceName, username, address, timeStamp, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public Vote(String serviceName, String username, String address, String timeStamp, long localTimestamp) {
|
||||
this.serviceName = serviceName;
|
||||
this.username = username;
|
||||
this.address = address;
|
||||
this.timeStamp = timeStamp;
|
||||
this.localTimestamp = localTimestamp;
|
||||
}
|
||||
|
||||
private static String getTimestamp(JSONObject object) {
|
||||
private static String getTimestamp(JsonElement object) {
|
||||
try {
|
||||
return Long.toString(object.getLong("timestamp"));
|
||||
return Long.toString(object.getAsLong());
|
||||
} catch (Exception e) {
|
||||
return object.getString("timestamp");
|
||||
return object.getAsString();
|
||||
}
|
||||
}
|
||||
|
||||
public Vote(JSONObject jsonObject) {
|
||||
this(jsonObject.getString("serviceName"), jsonObject.getString("username"), jsonObject.getString("address"), getTimestamp(jsonObject));
|
||||
public Vote(JsonObject jsonObject) {
|
||||
this(jsonObject.get("serviceName").getAsString(),
|
||||
jsonObject.get("username").getAsString(),
|
||||
jsonObject.get("address").getAsString(),
|
||||
getTimestamp(jsonObject.get("timestamp")),
|
||||
// maintained for backwards compatibility with <2.3.6 peers
|
||||
(jsonObject.has("localTimestamp") ? jsonObject.get("localTimestamp").getAsLong() : System.currentTimeMillis()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Vote (from:" + serviceName + " username:" + username
|
||||
+ " address:" + address + " timeStamp:" + timeStamp + ")";
|
||||
+ " address:" + address + " timeStamp:" + timeStamp
|
||||
+ " localTimestamp:" + localTimestamp + ")";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -152,12 +170,22 @@ public class Vote {
|
||||
return timeStamp;
|
||||
}
|
||||
|
||||
public JSONObject serialize() {
|
||||
JSONObject ret = new JSONObject();
|
||||
ret.put("serviceName", serviceName);
|
||||
ret.put("username", username);
|
||||
ret.put("address", address);
|
||||
ret.put("timestamp", timeStamp);
|
||||
/**
|
||||
* Gets the local timestamp, in unix-millis. Calculated locally by a NuVotifier instance
|
||||
*
|
||||
* @return The local timestamp
|
||||
*/
|
||||
public long getLocalTimestamp() {
|
||||
return localTimestamp;
|
||||
}
|
||||
|
||||
public JsonObject serialize() {
|
||||
JsonObject ret = new JsonObject();
|
||||
ret.addProperty("serviceName", serviceName);
|
||||
ret.addProperty("username", username);
|
||||
ret.addProperty("address", address);
|
||||
ret.addProperty("timestamp", timeStamp);
|
||||
ret.addProperty("localTimestamp", localTimestamp);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -172,7 +200,6 @@ public class Vote {
|
||||
if (!username.equals(vote.username)) return false;
|
||||
if (!address.equals(vote.address)) return false;
|
||||
return timeStamp.equals(vote.timeStamp);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.vexsoftware.votifier.net;
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import com.vexsoftware.votifier.net.protocol.VoteInboundHandler;
|
||||
import com.vexsoftware.votifier.net.protocol.VotifierGreetingHandler;
|
||||
import com.vexsoftware.votifier.net.protocol.VotifierProtocolDifferentiator;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class VotifierServerBootstrap {
|
||||
private final String host;
|
||||
private final int port;
|
||||
private final NioEventLoopGroup bossLoopGroup;
|
||||
private final NioEventLoopGroup eventLoopGroup;
|
||||
private final VotifierPlugin plugin;
|
||||
private final boolean v1Disable;
|
||||
|
||||
private Channel serverChannel;
|
||||
|
||||
public VotifierServerBootstrap(String host, int port, VotifierPlugin plugin, boolean v1Disable) {
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.plugin = plugin;
|
||||
this.v1Disable = v1Disable;
|
||||
this.bossLoopGroup = new NioEventLoopGroup(1, new ThreadFactoryBuilder().setDaemon(true).setNameFormat("Votifier NIO boss").build());
|
||||
this.eventLoopGroup = new NioEventLoopGroup(1, new ThreadFactoryBuilder().setDaemon(true).setNameFormat("Votifier NIO worker").build());
|
||||
}
|
||||
|
||||
public void start(Consumer<Throwable> error) {
|
||||
Objects.requireNonNull(error, "error");
|
||||
new ServerBootstrap()
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.group(bossLoopGroup, eventLoopGroup)
|
||||
.childHandler(new ChannelInitializer<NioSocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(NioSocketChannel channel) {
|
||||
channel.attr(VotifierSession.KEY).set(new VotifierSession());
|
||||
channel.attr(VotifierPlugin.KEY).set(plugin);
|
||||
channel.pipeline().addLast("greetingHandler", new VotifierGreetingHandler());
|
||||
channel.pipeline().addLast("protocolDifferentiator", new VotifierProtocolDifferentiator(false, !v1Disable));
|
||||
channel.pipeline().addLast("voteHandler", new VoteInboundHandler(plugin));
|
||||
}
|
||||
})
|
||||
.bind(host, port)
|
||||
.addListener((ChannelFutureListener) future -> {
|
||||
if (future.isSuccess()) {
|
||||
serverChannel = future.channel();
|
||||
plugin.getPluginLogger().info("Votifier enabled on socket " + serverChannel.localAddress() + ".");
|
||||
error.accept(null);
|
||||
} else {
|
||||
SocketAddress socketAddress = future.channel().localAddress();
|
||||
if (socketAddress == null) {
|
||||
socketAddress = new InetSocketAddress(host, port);
|
||||
}
|
||||
plugin.getPluginLogger().error("Votifier was not able to bind to " + socketAddress.toString(), future.cause());
|
||||
error.accept(future.cause());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public Bootstrap client() {
|
||||
return new Bootstrap()
|
||||
.channel(NioSocketChannel.class)
|
||||
.group(eventLoopGroup);
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
if (serverChannel != null) {
|
||||
try {
|
||||
serverChannel.close().syncUninterruptibly();
|
||||
} catch (Exception e) {
|
||||
plugin.getPluginLogger().error("Unable to shutdown server channel", e);
|
||||
}
|
||||
}
|
||||
eventLoopGroup.shutdownGracefully();
|
||||
bossLoopGroup.shutdownGracefully();
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ public class VotifierSession {
|
||||
public static final AttributeKey<VotifierSession> KEY = AttributeKey.valueOf("votifier_session");
|
||||
private ProtocolVersion version = ProtocolVersion.UNKNOWN;
|
||||
private final String challenge;
|
||||
private boolean hasCompletedVote = false;
|
||||
|
||||
public VotifierSession() {
|
||||
challenge = TokenUtil.newToken();
|
||||
@@ -27,6 +28,17 @@ public class VotifierSession {
|
||||
return challenge;
|
||||
}
|
||||
|
||||
public void completeVote() {
|
||||
if (hasCompletedVote)
|
||||
throw new IllegalStateException("Protocol completed vote twice!");
|
||||
|
||||
hasCompletedVote = true;
|
||||
}
|
||||
|
||||
public boolean hasCompletedVote() {
|
||||
return hasCompletedVote;
|
||||
}
|
||||
|
||||
public enum ProtocolVersion {
|
||||
UNKNOWN,
|
||||
ONE,
|
||||
|
||||
+14
-12
@@ -1,12 +1,13 @@
|
||||
package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.vexsoftware.votifier.VoteHandler;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.util.GsonInst;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class VoteInboundHandler extends SimpleChannelInboundHandler<Vote> {
|
||||
private final VoteHandler handler;
|
||||
@@ -19,29 +20,30 @@ public class VoteInboundHandler extends SimpleChannelInboundHandler<Vote> {
|
||||
protected void channelRead0(ChannelHandlerContext ctx, final Vote vote) throws Exception {
|
||||
VotifierSession session = ctx.channel().attr(VotifierSession.KEY).get();
|
||||
|
||||
handler.onVoteReceived(vote, session.getVersion());
|
||||
handler.onVoteReceived(ctx.channel(), vote, session.getVersion());
|
||||
session.completeVote();
|
||||
|
||||
if (session.getVersion() == VotifierSession.ProtocolVersion.ONE) {
|
||||
ctx.close();
|
||||
} else {
|
||||
JSONObject object = new JSONObject();
|
||||
object.put("status", "ok");
|
||||
ctx.writeAndFlush(object.toString() + "\r\n").addListener(ChannelFutureListener.CLOSE);
|
||||
JsonObject object = new JsonObject();
|
||||
object.addProperty("status", "ok");
|
||||
ctx.writeAndFlush(GsonInst.gson.toJson(object) + "\r\n").addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
VotifierSession session = ctx.channel().attr(VotifierSession.KEY).get();
|
||||
|
||||
handler.onError(ctx.channel(), cause);
|
||||
handler.onError(ctx.channel(), session.hasCompletedVote(), cause);
|
||||
|
||||
if (session.getVersion() == VotifierSession.ProtocolVersion.TWO) {
|
||||
JSONObject object = new JSONObject();
|
||||
object.put("status", "error");
|
||||
object.put("cause", cause.getClass().getSimpleName());
|
||||
object.put("error", cause.getMessage());
|
||||
ctx.writeAndFlush(object.toString() + "\r\n").addListener(ChannelFutureListener.CLOSE);
|
||||
JsonObject object = new JsonObject();
|
||||
object.addProperty("status", "error");
|
||||
object.addProperty("cause", cause.getClass().getSimpleName());
|
||||
object.addProperty("error", cause.getMessage());
|
||||
ctx.writeAndFlush(GsonInst.gson.toJson(object) + "\r\n").addListener(ChannelFutureListener.CLOSE);
|
||||
} else {
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
+2
-5
@@ -1,6 +1,5 @@
|
||||
package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.vexsoftware.votifier.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
@@ -14,11 +13,9 @@ import java.nio.charset.StandardCharsets;
|
||||
*/
|
||||
public class VotifierGreetingHandler extends ChannelInboundHandlerAdapter {
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
VotifierSession session = ctx.channel().attr(VotifierSession.KEY).get();
|
||||
VotifierPlugin plugin = ctx.channel().attr(VotifierPlugin.KEY).get();
|
||||
|
||||
String version = "VOTIFIER " + plugin.getVersion() + " " + session.getChallenge() + "\n";
|
||||
String version = "VOTIFIER 2 " + session.getChallenge() + "\n";
|
||||
ByteBuf versionBuf = Unpooled.copiedBuffer(version, StandardCharsets.UTF_8);
|
||||
ctx.writeAndFlush(versionBuf);
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.vexsoftware.votifier.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.protocol.v1crypto.RSA;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
@@ -16,7 +16,7 @@ import java.util.List;
|
||||
*/
|
||||
public class VotifierProtocol1Decoder extends ByteToMessageDecoder {
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> list) throws Exception {
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> list) {
|
||||
if (buf.readableBytes() < 256) {
|
||||
return;
|
||||
}
|
||||
|
||||
+17
-19
@@ -1,22 +1,19 @@
|
||||
package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.vexsoftware.votifier.VotifierPlugin;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.util.GsonInst;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
import io.netty.handler.codec.MessageToMessageDecoder;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.Key;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.security.*;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -28,42 +25,43 @@ public class VotifierProtocol2Decoder extends MessageToMessageDecoder<String> {
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, String s, List<Object> list) throws Exception {
|
||||
JSONObject voteMessage = new JSONObject(s);
|
||||
JsonObject voteMessage = GsonInst.gson.fromJson(s, JsonObject.class);
|
||||
VotifierSession session = ctx.channel().attr(VotifierSession.KEY).get();
|
||||
|
||||
// Deserialize the payload.
|
||||
JSONObject votePayload = new JSONObject(voteMessage.getString("payload"));
|
||||
String payload = voteMessage.get("payload").getAsString();
|
||||
JsonObject votePayload = GsonInst.gson.fromJson(payload, JsonObject.class);
|
||||
|
||||
// Verify challenge.
|
||||
if (!votePayload.getString("challenge").equals(session.getChallenge())) {
|
||||
if (!votePayload.get("challenge").getAsString().equals(session.getChallenge())) {
|
||||
throw new CorruptedFrameException("Challenge is not valid");
|
||||
}
|
||||
|
||||
// Verify that we have keys available.
|
||||
VotifierPlugin plugin = ctx.channel().attr(VotifierPlugin.KEY).get();
|
||||
Key key = plugin.getTokens().get(votePayload.getString("serviceName"));
|
||||
Key key = plugin.getTokens().get(votePayload.get("serviceName").getAsString());
|
||||
|
||||
if (key == null) {
|
||||
key = plugin.getTokens().get("default");
|
||||
if (key == null) {
|
||||
throw new RuntimeException("Unknown service '" + votePayload.getString("serviceName") + "'");
|
||||
throw new RuntimeException("Unknown service '" + votePayload.get("serviceName").getAsString() + "'");
|
||||
}
|
||||
}
|
||||
|
||||
// Verify signature.
|
||||
String sigHash = voteMessage.getString("signature");
|
||||
byte[] sigBytes = DatatypeConverter.parseBase64Binary(sigHash);
|
||||
String sigHash = voteMessage.get("signature").getAsString();
|
||||
byte[] sigBytes = Base64.getDecoder().decode(sigHash);
|
||||
|
||||
if (!hmacEqual(sigBytes, voteMessage.getString("payload").getBytes(StandardCharsets.UTF_8), key)) {
|
||||
if (!hmacEqual(sigBytes, payload.getBytes(StandardCharsets.UTF_8), key)) {
|
||||
throw new CorruptedFrameException("Signature is not valid (invalid token?)");
|
||||
}
|
||||
|
||||
// Stopgap: verify the "uuid" field is valid, if provided.
|
||||
if (votePayload.has("uuid")) {
|
||||
UUID.fromString(votePayload.getString("uuid"));
|
||||
UUID.fromString(votePayload.get("uuid").getAsString());
|
||||
}
|
||||
|
||||
if (votePayload.getString("username").length() > 16) {
|
||||
if (votePayload.get("username").getAsString().length() > 16) {
|
||||
throw new CorruptedFrameException("Username too long");
|
||||
}
|
||||
|
||||
@@ -92,6 +90,6 @@ public class VotifierProtocol2Decoder extends MessageToMessageDecoder<String> {
|
||||
mac2.reset();
|
||||
byte[] realSig = mac2.doFinal(calculatedSig);
|
||||
|
||||
return Arrays.equals(clientSig, realSig);
|
||||
return MessageDigest.isEqual(clientSig, realSig);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -26,7 +26,7 @@ public class VotifierProtocolDifferentiator extends ByteToMessageDecoder {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> list) throws Exception {
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> list) {
|
||||
// Determine the number of bytes that are available.
|
||||
int readable = buf.readableBytes();
|
||||
|
||||
|
||||
@@ -18,9 +18,11 @@
|
||||
|
||||
package com.vexsoftware.votifier.net.protocol.v1crypto;
|
||||
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
import java.io.IOException;
|
||||
import java.util.Base64;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPair;
|
||||
@@ -45,21 +47,30 @@ public class RSAIO {
|
||||
PrivateKey privateKey = keyPair.getPrivate();
|
||||
PublicKey publicKey = keyPair.getPublic();
|
||||
|
||||
// Store the public key.
|
||||
// Store the public and private keys.
|
||||
X509EncodedKeySpec publicSpec = new X509EncodedKeySpec(
|
||||
publicKey.getEncoded());
|
||||
FileOutputStream out = new FileOutputStream(directory + "/public.key");
|
||||
out.write(DatatypeConverter.printBase64Binary(publicSpec.getEncoded())
|
||||
.getBytes());
|
||||
out.close();
|
||||
|
||||
// Store the private key.
|
||||
PKCS8EncodedKeySpec privateSpec = new PKCS8EncodedKeySpec(
|
||||
privateKey.getEncoded());
|
||||
out = new FileOutputStream(directory + "/private.key");
|
||||
out.write(DatatypeConverter.printBase64Binary(privateSpec.getEncoded())
|
||||
.getBytes());
|
||||
out.close();
|
||||
try (FileOutputStream publicOut = new FileOutputStream(directory + "/public.key");
|
||||
FileOutputStream privateOut = new FileOutputStream(directory + "/private.key")) {
|
||||
|
||||
publicOut.write(Base64.getEncoder().encode(publicSpec.getEncoded()));
|
||||
privateOut.write(Base64.getEncoder().encode(privateSpec.getEncoded()));
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] readB64File(File directory, String name) throws IOException {
|
||||
File f = new File(directory, name);
|
||||
byte[] contents = Files.readAllBytes(f.toPath());
|
||||
String strContents = new String(contents);
|
||||
strContents = strContents.trim();
|
||||
try {
|
||||
return Base64.getDecoder().decode(strContents);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException("Base64 decoding exception: This is probably due to a corrupted file, " +
|
||||
"but in case it isn't, here is a b64 representation of what we read: " + new String(Base64.getEncoder().encode(contents), StandardCharsets.UTF_8), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,16 +83,10 @@ public class RSAIO {
|
||||
*/
|
||||
public static KeyPair load(File directory) throws Exception {
|
||||
// Read the public key file.
|
||||
File publicKeyFile = new File(directory + "/public.key");
|
||||
byte[] encodedPublicKey = Files.readAllBytes(publicKeyFile.toPath());
|
||||
encodedPublicKey = DatatypeConverter.parseBase64Binary(new String(
|
||||
encodedPublicKey));
|
||||
byte[] encodedPublicKey = readB64File(directory, "public.key");
|
||||
|
||||
// Read the private key file.
|
||||
File privateKeyFile = new File(directory + "/private.key");
|
||||
byte[] encodedPrivateKey = Files.readAllBytes(privateKeyFile.toPath());
|
||||
encodedPrivateKey = DatatypeConverter.parseBase64Binary(new String(
|
||||
encodedPrivateKey));
|
||||
byte[] encodedPrivateKey = readB64File(directory, "private.key");
|
||||
|
||||
// Instantiate and return the key pair.
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.vexsoftware.votifier.platform;
|
||||
|
||||
public interface BackendServer {
|
||||
String getName();
|
||||
|
||||
boolean sendPluginMessage(String channel, byte[] data);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.vexsoftware.votifier.platform;
|
||||
|
||||
import com.vexsoftware.votifier.platform.LoggingAdapter;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class JavaUtilLogger implements LoggingAdapter {
|
||||
|
||||
private final Logger logger;
|
||||
|
||||
public JavaUtilLogger(Logger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String s) {
|
||||
logger.severe(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String s, Object... o) {
|
||||
logger.log(Level.SEVERE, s, o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String s) {
|
||||
logger.warning(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String s, Object... o) {
|
||||
logger.log(Level.WARNING, s, o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String s) {
|
||||
logger.info(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String s, Object... o) {
|
||||
logger.log(Level.INFO, s, o);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.vexsoftware.votifier.platform;
|
||||
|
||||
public interface LoggingAdapter {
|
||||
void error(String s);
|
||||
void error(String s, Object... o);
|
||||
|
||||
void warn(String s);
|
||||
void warn(String s, Object... o);
|
||||
|
||||
void info(String s);
|
||||
void info(String s, Object... o);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.vexsoftware.votifier.platform;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface ProxyVotifierPlugin extends VotifierPlugin {
|
||||
Collection<BackendServer> getAllBackendServers();
|
||||
|
||||
Optional<BackendServer> getServer(String name);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.vexsoftware.votifier.platform;
|
||||
|
||||
import com.vexsoftware.votifier.VoteHandler;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
import io.netty.util.AttributeKey;
|
||||
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
import java.util.Map;
|
||||
|
||||
public interface VotifierPlugin extends VoteHandler {
|
||||
AttributeKey<VotifierPlugin> KEY = AttributeKey.valueOf("votifier_plugin");
|
||||
|
||||
Map<String, Key> getTokens();
|
||||
|
||||
KeyPair getProtocolV1Key();
|
||||
|
||||
LoggingAdapter getPluginLogger();
|
||||
|
||||
VotifierScheduler getScheduler();
|
||||
|
||||
default boolean isDebug() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package com.vexsoftware.votifier.platform.scheduler;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ScheduledExecutorServiceVotifierScheduler implements VotifierScheduler {
|
||||
private final ScheduledExecutorService service;
|
||||
|
||||
public ScheduledExecutorServiceVotifierScheduler(ScheduledExecutorService service) {
|
||||
this.service = Preconditions.checkNotNull(service, "service");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask sync(Runnable runnable) {
|
||||
return new ScheduledVotifierTaskWrapper(service.submit(runnable));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask onPool(Runnable runnable) {
|
||||
return new ScheduledVotifierTaskWrapper(service.submit(runnable));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask delayedSync(Runnable runnable, int delay, TimeUnit unit) {
|
||||
return new ScheduledVotifierTaskWrapper(service.schedule(runnable, delay, unit));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask delayedOnPool(Runnable runnable, int delay, TimeUnit unit) {
|
||||
return new ScheduledVotifierTaskWrapper(service.schedule(runnable, delay, unit));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask repeatOnPool(Runnable runnable, int delay, int repeat, TimeUnit unit) {
|
||||
return new ScheduledVotifierTaskWrapper(service.scheduleAtFixedRate(runnable, delay, repeat, unit));
|
||||
}
|
||||
|
||||
private static class ScheduledVotifierTaskWrapper implements ScheduledVotifierTask {
|
||||
private final Future<?> future;
|
||||
|
||||
private ScheduledVotifierTaskWrapper(Future<?> future) {
|
||||
this.future = future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
future.cancel(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package com.vexsoftware.votifier.platform.scheduler;
|
||||
|
||||
public interface ScheduledVotifierTask {
|
||||
void cancel();
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package com.vexsoftware.votifier.platform.scheduler;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public interface VotifierScheduler {
|
||||
ScheduledVotifierTask sync(Runnable runnable);
|
||||
|
||||
ScheduledVotifierTask onPool(Runnable runnable);
|
||||
|
||||
ScheduledVotifierTask delayedSync(Runnable runnable, int delay, TimeUnit unit);
|
||||
|
||||
ScheduledVotifierTask delayedOnPool(Runnable runnable, int delay, TimeUnit unit);
|
||||
|
||||
ScheduledVotifierTask repeatOnPool(Runnable runnable, int delay, int repeat, TimeUnit unit);
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package com.vexsoftware.votifier.support.forwarding;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
|
||||
import java.io.CharArrayReader;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public abstract class AbstractPluginMessagingForwardingSink implements ForwardingVoteSink {
|
||||
|
||||
public AbstractPluginMessagingForwardingSink(ForwardedVoteListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
private final ForwardedVoteListener listener;
|
||||
|
||||
public void handlePluginMessage(byte[] message) {
|
||||
String strMessage = new String(message, StandardCharsets.UTF_8);
|
||||
try (CharArrayReader reader = new CharArrayReader(strMessage.toCharArray())) {
|
||||
JsonReader r = new JsonReader(reader);
|
||||
r.setLenient(true);
|
||||
while (r.peek() != JsonToken.END_DOCUMENT) {
|
||||
r.beginObject();
|
||||
JsonObject o = new JsonObject();
|
||||
|
||||
while (r.hasNext()) {
|
||||
String name = r.nextName();
|
||||
if (r.peek() == JsonToken.NUMBER)
|
||||
o.add(name, new JsonPrimitive(r.nextLong()));
|
||||
else
|
||||
o.add(name, new JsonPrimitive(r.nextString()));
|
||||
}
|
||||
r.endObject();
|
||||
|
||||
Vote v = new Vote(o);
|
||||
listener.onForward(v);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace(); // Should never happen.
|
||||
}
|
||||
}
|
||||
}
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
package com.vexsoftware.votifier.support.forwarding;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.platform.BackendServer;
|
||||
import com.vexsoftware.votifier.platform.ProxyVotifierPlugin;
|
||||
import com.vexsoftware.votifier.support.forwarding.cache.FileVoteCache;
|
||||
import com.vexsoftware.votifier.support.forwarding.cache.VoteCache;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public abstract class AbstractPluginMessagingForwardingSource implements ForwardingVoteSource {
|
||||
|
||||
public AbstractPluginMessagingForwardingSource(String channel, ServerFilter serverFilter, ProxyVotifierPlugin plugin, VoteCache cache, int dumpRate) {
|
||||
this.channel = channel;
|
||||
this.plugin = plugin;
|
||||
this.cache = cache;
|
||||
this.serverFilter = serverFilter;
|
||||
this.dumpRate = dumpRate;
|
||||
}
|
||||
|
||||
protected AbstractPluginMessagingForwardingSource(String channel, ProxyVotifierPlugin plugin, VoteCache voteCache, int dumpRate) {
|
||||
this(channel, null, plugin, voteCache, dumpRate);
|
||||
}
|
||||
|
||||
protected final ProxyVotifierPlugin plugin;
|
||||
protected final String channel;
|
||||
protected final VoteCache cache;
|
||||
protected final ServerFilter serverFilter;
|
||||
private final int dumpRate;
|
||||
|
||||
@Override
|
||||
public void forward(Vote v) {
|
||||
byte[] rawData = v.serialize().toString().getBytes(StandardCharsets.UTF_8);
|
||||
for (BackendServer server : plugin.getAllBackendServers()) {
|
||||
if (!serverFilter.isAllowed(server.getName())) continue;
|
||||
if (!forwardSpecific(server, rawData)) attemptToAddToCache(v, server.getName());
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean forwardSpecific(BackendServer connection, Vote vote) {
|
||||
byte[] rawData = vote.serialize().toString().getBytes(StandardCharsets.UTF_8);
|
||||
return forwardSpecific(connection, rawData);
|
||||
}
|
||||
|
||||
protected boolean forwardSpecific(BackendServer connection, Collection<Vote> votes) {
|
||||
StringBuilder data = new StringBuilder();
|
||||
for (Vote v : votes) {
|
||||
data.append(v.serialize().toString());
|
||||
}
|
||||
|
||||
return forwardSpecific(connection, data.toString().getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
private boolean forwardSpecific(BackendServer connection, byte[] data) {
|
||||
return connection.sendPluginMessage(channel, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void halt() {
|
||||
if (cache instanceof FileVoteCache) {
|
||||
try {
|
||||
((FileVoteCache) cache).halt();
|
||||
} catch (IOException e) {
|
||||
plugin.getPluginLogger().error("Unable to save cached votes, votes will be lost.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void onServerConnect(BackendServer server) {
|
||||
if (cache == null) return;
|
||||
final Collection<Vote> cachedVotes = cache.evict(server.getName());
|
||||
dumpVotesToServer(cachedVotes, server, "server '" + server + "'", failedVotes -> {
|
||||
for (Vote v : failedVotes)
|
||||
cache.addToCache(v, server.getName());
|
||||
});
|
||||
}
|
||||
|
||||
protected void attemptToAddToCache(Vote v, String server) {
|
||||
if (cache != null) {
|
||||
cache.addToCache(v, server);
|
||||
if (plugin.isDebug())
|
||||
plugin.getPluginLogger().info("Added to forwarding cache: " + v + " -> " + server);
|
||||
} else if (plugin.isDebug())
|
||||
plugin.getPluginLogger().error("Could not immediately send vote to backend, vote lost! " + v + " -> " + server);
|
||||
}
|
||||
|
||||
protected void attemptToAddToPlayerCache(Vote v, String player) {
|
||||
if (cache != null) {
|
||||
cache.addToCachePlayer(v, player);
|
||||
if (plugin.isDebug())
|
||||
plugin.getPluginLogger().info("Added to forwarding cache: " + v + " -> (player) " + player);
|
||||
} else if (plugin.isDebug())
|
||||
plugin.getPluginLogger().error("Could not immediately send vote to backend, vote lost! " + v + " -> (player) " + player);
|
||||
|
||||
}
|
||||
|
||||
// returns a collection of failed votes
|
||||
private void dumpVotesToServer(Collection<Vote> cachedVotes, BackendServer target, String identifier, Consumer<Collection<Vote>> cb) {
|
||||
dumpVotesToServer(cachedVotes, target, identifier, 0, cb);
|
||||
}
|
||||
private void dumpVotesToServer(Collection<Vote> cachedVotes, BackendServer target, String identifier, int evictedAlready, Consumer<Collection<Vote>> cb) {
|
||||
if (!cachedVotes.isEmpty()) {
|
||||
plugin.getScheduler().delayedOnPool(() -> {
|
||||
int evicted = 0;
|
||||
Iterator<Vote> vi = cachedVotes.iterator();
|
||||
Collection<Vote> chunk = new ArrayList<>(dumpRate);
|
||||
while (vi.hasNext() && evicted < dumpRate) {
|
||||
chunk.add(vi.next());
|
||||
vi.remove();
|
||||
}
|
||||
|
||||
if (forwardSpecific(target, chunk)) {
|
||||
evicted += chunk.size();
|
||||
|
||||
if (evicted >= dumpRate && !cachedVotes.isEmpty()) {
|
||||
// if we evicted everything we could but still need to evict more
|
||||
dumpVotesToServer(cachedVotes, target, identifier, evictedAlready + evicted, cb);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// so since our forwarding failed, break like we are done
|
||||
cachedVotes.addAll(chunk);
|
||||
}
|
||||
|
||||
if (plugin.isDebug()) {
|
||||
plugin.getPluginLogger().info("Successfully evicted " + (evictedAlready + evicted) + " votes to " + identifier + ".");
|
||||
if (!cachedVotes.isEmpty()) {
|
||||
plugin.getPluginLogger().info("Held " + cachedVotes.size() + " votes for " + identifier + ".");
|
||||
}
|
||||
}
|
||||
|
||||
// cachedVotes contains any votes which have yet to be evicted
|
||||
cb.accept(cachedVotes);
|
||||
}, 1, TimeUnit.SECONDS);
|
||||
} else {
|
||||
cb.accept(cachedVotes);
|
||||
}
|
||||
}
|
||||
|
||||
protected void handlePlayerSwitch(BackendServer server, String playerName) {
|
||||
if (cache == null) return;
|
||||
if (!serverFilter.isAllowed(server.getName())) return;
|
||||
|
||||
final Collection<Vote> cachedVotes = cache.evictPlayer(playerName);
|
||||
dumpVotesToServer(cachedVotes, server, "player '" + playerName + "'", failedVotes -> {
|
||||
for (Vote v : failedVotes)
|
||||
cache.addToCachePlayer(v, playerName);
|
||||
});
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.vexsoftware.votifier.sponge.forwarding;
|
||||
package com.vexsoftware.votifier.support.forwarding;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.vexsoftware.votifier.forwarding;
|
||||
package com.vexsoftware.votifier.support.forwarding;
|
||||
|
||||
/**
|
||||
* Represents a method at which to receive forwarded votes.
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding;
|
||||
package com.vexsoftware.votifier.support.forwarding;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.vexsoftware.votifier.support.forwarding;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
public class ServerFilter {
|
||||
|
||||
public ServerFilter(Collection<String> names, boolean whitelist) {
|
||||
if (names == null)
|
||||
names = Collections.EMPTY_SET;
|
||||
|
||||
this.names = names;
|
||||
this.whitelist = whitelist;
|
||||
}
|
||||
|
||||
public ServerFilter() {
|
||||
this(Collections.EMPTY_SET, true);
|
||||
}
|
||||
|
||||
private final Collection<String> names;
|
||||
private final boolean whitelist;
|
||||
|
||||
public boolean isAllowed(String name) {
|
||||
return names.contains(name) == whitelist;
|
||||
}
|
||||
}
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
package com.vexsoftware.votifier.support.forwarding.cache;
|
||||
|
||||
import com.google.common.io.Files;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.platform.LoggingAdapter;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.platform.scheduler.ScheduledVotifierTask;
|
||||
import com.vexsoftware.votifier.util.GsonInst;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class FileVoteCache extends MemoryVoteCache {
|
||||
|
||||
private final LoggingAdapter l;
|
||||
private final File cacheFile;
|
||||
private final ScheduledVotifierTask saveTask;
|
||||
|
||||
public FileVoteCache(int initialMemorySize, final VotifierPlugin plugin, File cacheFile, long voteTTL) throws IOException {
|
||||
super(initialMemorySize, plugin, voteTTL);
|
||||
this.cacheFile = cacheFile;
|
||||
this.l = plugin.getPluginLogger();
|
||||
|
||||
load();
|
||||
|
||||
saveTask = plugin.getScheduler().repeatOnPool(() -> {
|
||||
try {
|
||||
save();
|
||||
} catch (IOException e) {
|
||||
l.error("Unable to save cached votes, votes will be lost if you restart.", e);
|
||||
}
|
||||
}, 3, 3, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
private static Set<String> keySet(JsonObject object) {
|
||||
Set<String> set = new HashSet<>();
|
||||
for (Map.Entry<String, JsonElement> entry : object.entrySet()) {
|
||||
set.add(entry.getKey());
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
private void load() throws IOException {
|
||||
// Load the cache from disk
|
||||
JsonObject object;
|
||||
try (BufferedReader reader = Files.newReader(cacheFile, StandardCharsets.UTF_8)) {
|
||||
object = GsonInst.gson.fromJson(reader, JsonObject.class);
|
||||
} catch (FileNotFoundException e) {
|
||||
object = new JsonObject();
|
||||
}
|
||||
|
||||
boolean resave = false;
|
||||
|
||||
// First, lets figure out if we are converting from a pre2.3.6 cache
|
||||
if (!object.has("players") || !object.has("servers") || !object.has("version") || object.size() != 3) {
|
||||
JsonObject oldObject = object;
|
||||
object = new JsonObject();
|
||||
object.add("servers", oldObject);
|
||||
object.add("players", new JsonObject());
|
||||
object.addProperty("version", 2);
|
||||
resave = true;
|
||||
}
|
||||
|
||||
if (object.get("version").getAsInt() != 2)
|
||||
throw new IllegalStateException("Could not read cache file! Unknown version '" + object.get("version").getAsInt() + "' read.");
|
||||
|
||||
JsonObject players = object.getAsJsonObject("players");
|
||||
JsonObject servers = object.getAsJsonObject("servers");
|
||||
|
||||
for (String player : keySet(players)) {
|
||||
playerVoteCache.putAll(player, readVotes(players.getAsJsonArray(player)));
|
||||
}
|
||||
|
||||
for (String server : keySet(servers)) {
|
||||
voteCache.putAll(server, readVotes(servers.getAsJsonArray(server)));
|
||||
}
|
||||
|
||||
if (resave) {
|
||||
File replacementFile;
|
||||
for (int i = 0; ; i++) {
|
||||
replacementFile = new File(cacheFile.getParentFile(), cacheFile.getName() + ".bak." + i);
|
||||
if (!replacementFile.exists())
|
||||
break;
|
||||
}
|
||||
|
||||
if (!cacheFile.renameTo(replacementFile)) {
|
||||
l.error("Backup movement failed! Will not save.");
|
||||
return;
|
||||
}
|
||||
|
||||
l.warn("Saving new vote cache format to file - backup moved to " + replacementFile.getAbsolutePath());
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
||||
private Collection<Vote> readVotes(JsonArray voteArray) {
|
||||
List<Vote> votes = new ArrayList<>(voteArray.size());
|
||||
for (int i = 0; i < voteArray.size(); i++) {
|
||||
JsonObject voteObject = voteArray.get(i).getAsJsonObject();
|
||||
Vote v = new Vote(voteObject);
|
||||
if (hasTimedOut(v))
|
||||
l.warn("Purging out of date vote.", v);
|
||||
else
|
||||
votes.add(v);
|
||||
}
|
||||
return votes;
|
||||
}
|
||||
|
||||
public void save() throws IOException {
|
||||
cacheLock.lock();
|
||||
JsonObject votesObject = new JsonObject();
|
||||
votesObject.addProperty("version", 2);
|
||||
try {
|
||||
votesObject.add("players", serializeMap(playerVoteCache.asMap()));
|
||||
votesObject.add("servers", serializeMap(voteCache.asMap()));
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
|
||||
try (BufferedWriter writer = Files.newWriter(cacheFile, StandardCharsets.UTF_8)) {
|
||||
GsonInst.gson.toJson(votesObject, writer);
|
||||
}
|
||||
}
|
||||
|
||||
public JsonObject serializeMap(Map<String, Collection<Vote>> map) {
|
||||
JsonObject o = new JsonObject();
|
||||
|
||||
Iterator<Map.Entry<String, Collection<Vote>>> entryItr = map.entrySet().iterator();
|
||||
while (entryItr.hasNext()) {
|
||||
Map.Entry<String, Collection<Vote>> entry = entryItr.next();
|
||||
JsonArray array = new JsonArray();
|
||||
Iterator<Vote> voteItr = entry.getValue().iterator();
|
||||
while (voteItr.hasNext()) {
|
||||
Vote vote = voteItr.next();
|
||||
|
||||
// if the vote is no longer valid, notify and remove
|
||||
if (hasTimedOut(vote)) {
|
||||
l.warn("Purging out of date vote.", vote);
|
||||
voteItr.remove();
|
||||
} else {
|
||||
array.add(vote.serialize());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// if, during our iteration, we TTL invalidated all of the votes
|
||||
if (entry.getValue().isEmpty())
|
||||
entryItr.remove();
|
||||
o.add(entry.getKey(), array);
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
|
||||
public void halt() throws IOException {
|
||||
saveTask.cancel();
|
||||
save();
|
||||
}
|
||||
}
|
||||
Vendored
+120
@@ -0,0 +1,120 @@
|
||||
package com.vexsoftware.votifier.support.forwarding.cache;
|
||||
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.platform.LoggingAdapter;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.platform.scheduler.ScheduledVotifierTask;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* Created by Joe Hirschfeld on 10/20/2015.
|
||||
*/
|
||||
public class MemoryVoteCache implements VoteCache {
|
||||
|
||||
private final LoggingAdapter l;
|
||||
private final long voteTTL;
|
||||
|
||||
protected final Multimap<String, Vote> voteCache;
|
||||
protected final Multimap<String, Vote> playerVoteCache;
|
||||
|
||||
protected final ReentrantLock cacheLock = new ReentrantLock();
|
||||
|
||||
private final ScheduledVotifierTask sweepTask;
|
||||
|
||||
public MemoryVoteCache(int initialSize, VotifierPlugin p, long voteTTL) {
|
||||
voteCache = HashMultimap.create(initialSize, 10); // 10 is a complete guess. I have no idea .-.
|
||||
playerVoteCache = HashMultimap.create();
|
||||
|
||||
this.voteTTL = voteTTL;
|
||||
|
||||
this.l = p.getPluginLogger();
|
||||
this.sweepTask = p.getScheduler().repeatOnPool(this::sweep, 12, 12, TimeUnit.HOURS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getCachedServers() {
|
||||
cacheLock.lock();
|
||||
try {
|
||||
return ImmutableList.copyOf(voteCache.keySet());
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addToCache(Vote v, String server) {
|
||||
if (server == null) throw new NullPointerException();
|
||||
cacheLock.lock();
|
||||
try {
|
||||
voteCache.put(server, v);
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addToCachePlayer(Vote v, String player) {
|
||||
if (player == null) throw new NullPointerException();
|
||||
cacheLock.lock();
|
||||
try {
|
||||
playerVoteCache.put(player, v);
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Vote> evictPlayer(String player) {
|
||||
if (player == null) throw new NullPointerException();
|
||||
cacheLock.lock();
|
||||
try {
|
||||
return new HashSet<>(playerVoteCache.removeAll(player));
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Vote> evict(String server) {
|
||||
if (server == null) throw new NullPointerException();
|
||||
cacheLock.lock();
|
||||
try {
|
||||
return new HashSet<>(voteCache.removeAll(server));
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void sweep() {
|
||||
cacheLock.lock();
|
||||
try {
|
||||
sweep(voteCache);
|
||||
sweep(playerVoteCache);
|
||||
} finally {
|
||||
cacheLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private void sweep(Multimap<?, Vote> m) {
|
||||
Iterator<Vote> vi = m.values().iterator();
|
||||
while (vi.hasNext()) {
|
||||
Vote v = vi.next();
|
||||
if (hasTimedOut(v)) {
|
||||
l.warn("Purging out of date vote.", v);
|
||||
vi.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean hasTimedOut(Vote v) {
|
||||
if (voteTTL == -1) return false;
|
||||
// scale voteTTL to milliseconds
|
||||
return v.getLocalTimestamp() + voteTTL * 24 * 60 * 60 * 1000 < System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
+23
-1
@@ -1,8 +1,9 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.cache;
|
||||
package com.vexsoftware.votifier.support.forwarding.cache;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Represents a method of caching votes for forwarding to a server that it was not previously capable of sending.
|
||||
@@ -24,6 +25,16 @@ public interface VoteCache {
|
||||
*/
|
||||
void addToCache(Vote v, String server);
|
||||
|
||||
/**
|
||||
* Adds a vote to the vote cache for later sending, specifically keyed to a player. This is for the instance when the
|
||||
* player is rewarded, however is not online any servers able to recieve said vote.
|
||||
*
|
||||
* @param v Vote to add to the cache
|
||||
* @param player Server to add vote under
|
||||
*/
|
||||
default void addToCachePlayer(Vote v, String player) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Evicts all votes from the vote cache and returns a collection of vote objects
|
||||
*
|
||||
@@ -31,4 +42,15 @@ public interface VoteCache {
|
||||
* @return A collection of all votes which were previously in the cache under the server
|
||||
*/
|
||||
Collection<Vote> evict(String server);
|
||||
|
||||
/**
|
||||
* Evicts all votes from the vote cache for a specific player not assigned to a server and returns a collection of
|
||||
* vote objects
|
||||
*
|
||||
* @param player Player name of the votes to evict from the cache
|
||||
* @return A collection of all votes which were previously in the cache under the player
|
||||
*/
|
||||
default Collection<Vote> evictPlayer(String player) {
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
}
|
||||
+22
-34
@@ -1,17 +1,15 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.proxy;
|
||||
package com.vexsoftware.votifier.support.forwarding.proxy;
|
||||
|
||||
import com.vexsoftware.votifier.bungee.NuVotifier;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.ForwardingVoteSource;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.cache.VoteCache;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.proxy.client.VotifierProtocol2Encoder;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.proxy.client.VotifierProtocol2HandshakeHandler;
|
||||
import com.vexsoftware.votifier.bungee.forwarding.proxy.client.VotifierResponseHandler;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.support.forwarding.ForwardingVoteSource;
|
||||
import com.vexsoftware.votifier.support.forwarding.cache.VoteCache;
|
||||
import com.vexsoftware.votifier.support.forwarding.proxy.client.VotifierProtocol2Encoder;
|
||||
import com.vexsoftware.votifier.support.forwarding.proxy.client.VotifierProtocol2HandshakeHandler;
|
||||
import com.vexsoftware.votifier.support.forwarding.proxy.client.VotifierResponseHandler;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
|
||||
import io.netty.handler.codec.Delimiters;
|
||||
@@ -23,20 +21,20 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.security.Key;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class ProxyForwardingVoteSource implements ForwardingVoteSource {
|
||||
private static final int MAX_RETRIES = 5;
|
||||
private final NuVotifier plugin;
|
||||
private final NioEventLoopGroup eventLoopGroup;
|
||||
private final VotifierPlugin plugin;
|
||||
private final Supplier<Bootstrap> nettyBootstrap;
|
||||
private final List<BackendServer> backendServers;
|
||||
private final VoteCache voteCache;
|
||||
|
||||
private static final StringDecoder STRING_DECODER = new StringDecoder(StandardCharsets.US_ASCII);
|
||||
|
||||
public ProxyForwardingVoteSource(NuVotifier plugin, NioEventLoopGroup eventLoopGroup, List<BackendServer> backendServers, VoteCache voteCache) {
|
||||
public ProxyForwardingVoteSource(VotifierPlugin plugin, Supplier<Bootstrap> nettyBootstrap, List<BackendServer> backendServers, VoteCache voteCache) {
|
||||
this.plugin = plugin;
|
||||
this.eventLoopGroup = eventLoopGroup;
|
||||
this.nettyBootstrap = nettyBootstrap;
|
||||
this.backendServers = backendServers;
|
||||
this.voteCache = voteCache;
|
||||
}
|
||||
@@ -54,21 +52,19 @@ public class ProxyForwardingVoteSource implements ForwardingVoteSource {
|
||||
}
|
||||
|
||||
private void forwardVote(final BackendServer server, final Vote v, final int tries) {
|
||||
new Bootstrap()
|
||||
.channel(NioSocketChannel.class)
|
||||
.group(eventLoopGroup)
|
||||
nettyBootstrap.get()
|
||||
.handler(new ChannelInitializer<NioSocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(NioSocketChannel channel) throws Exception {
|
||||
protected void initChannel(NioSocketChannel channel) {
|
||||
channel.pipeline().addLast(new DelimiterBasedFrameDecoder(256, true, Delimiters.lineDelimiter()));
|
||||
channel.pipeline().addLast(new ReadTimeoutHandler(8, TimeUnit.SECONDS));
|
||||
channel.pipeline().addLast(new ReadTimeoutHandler(5, TimeUnit.SECONDS));
|
||||
channel.pipeline().addLast(STRING_DECODER);
|
||||
channel.pipeline().addLast(new VotifierProtocol2Encoder(server.key));
|
||||
channel.pipeline().addLast(new VotifierProtocol2HandshakeHandler(v, new VotifierResponseHandler() {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
if (plugin.isDebug()) {
|
||||
plugin.getLogger().info("Successfully forwarded vote " + v + " to " + server.address + ".");
|
||||
plugin.getPluginLogger().info("Successfully forwarded vote " + v + " to " + server.address + ".");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,12 +76,9 @@ public class ProxyForwardingVoteSource implements ForwardingVoteSource {
|
||||
}
|
||||
})
|
||||
.connect(server.address)
|
||||
.addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
handleFailure(server, v, future.cause(), tries);
|
||||
}
|
||||
.addListener((ChannelFutureListener) future -> {
|
||||
if (!future.isSuccess()) {
|
||||
handleFailure(server, v, future.cause(), tries);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -106,18 +99,13 @@ public class ProxyForwardingVoteSource implements ForwardingVoteSource {
|
||||
}
|
||||
|
||||
if (plugin.isDebug()) {
|
||||
plugin.getLogger().log(Level.SEVERE, msg, cause);
|
||||
plugin.getPluginLogger().error(msg, cause);
|
||||
} else {
|
||||
plugin.getLogger().log(Level.SEVERE, msg);
|
||||
plugin.getPluginLogger().error(msg);
|
||||
}
|
||||
|
||||
if (willRetry) {
|
||||
plugin.getProxy().getScheduler().schedule(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
forwardVote(server, v, tries + 1);
|
||||
}
|
||||
}, nextDelay, TimeUnit.SECONDS);
|
||||
plugin.getScheduler().delayedOnPool(() -> forwardVote(server, v, tries + 1), nextDelay, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.proxy.client;
|
||||
package com.vexsoftware.votifier.support.forwarding.proxy.client;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
|
||||
+9
-9
@@ -1,15 +1,15 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.proxy.client;
|
||||
package com.vexsoftware.votifier.support.forwarding.proxy.client;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Key;
|
||||
import java.util.Base64;
|
||||
|
||||
public class VotifierProtocol2Encoder extends MessageToByteEncoder<VoteRequest> {
|
||||
private static final short MAGIC = 0x733A;
|
||||
@@ -22,18 +22,18 @@ public class VotifierProtocol2Encoder extends MessageToByteEncoder<VoteRequest>
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, VoteRequest req, ByteBuf buf) throws Exception {
|
||||
JSONObject object = new JSONObject();
|
||||
JSONObject payloadObject = req.getVote().serialize();
|
||||
payloadObject.put("challenge", req.getChallenge());
|
||||
JsonObject object = new JsonObject();
|
||||
JsonObject payloadObject = req.getVote().serialize();
|
||||
payloadObject.addProperty("challenge", req.getChallenge());
|
||||
String payload = payloadObject.toString();
|
||||
object.put("payload", payload);
|
||||
object.addProperty("payload", payload);
|
||||
|
||||
// Generate the MAC
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(key);
|
||||
mac.update(payload.getBytes(StandardCharsets.UTF_8));
|
||||
String computed = DatatypeConverter.printBase64Binary(mac.doFinal());
|
||||
object.put("signature", computed);
|
||||
String computed = Base64.getEncoder().encodeToString(mac.doFinal());
|
||||
object.addProperty("signature", computed);
|
||||
|
||||
// JSON message is ready for encoding.
|
||||
String finalMessage = object.toString();
|
||||
+7
-7
@@ -1,6 +1,6 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.proxy.client;
|
||||
package com.vexsoftware.votifier.support.forwarding.proxy.client;
|
||||
|
||||
import com.vexsoftware.votifier.bungee.NuVotifier;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
@@ -9,16 +9,16 @@ import io.netty.handler.codec.CorruptedFrameException;
|
||||
public class VotifierProtocol2HandshakeHandler extends SimpleChannelInboundHandler<String> {
|
||||
private final Vote toSend;
|
||||
private final VotifierResponseHandler responseHandler;
|
||||
private final NuVotifier nuVotifier;
|
||||
private final VotifierPlugin nuVotifier;
|
||||
|
||||
public VotifierProtocol2HandshakeHandler(Vote toSend, VotifierResponseHandler responseHandler, NuVotifier nuVotifier) {
|
||||
public VotifierProtocol2HandshakeHandler(Vote toSend, VotifierResponseHandler responseHandler, VotifierPlugin nuVotifier) {
|
||||
this.toSend = toSend;
|
||||
this.responseHandler = responseHandler;
|
||||
this.nuVotifier = nuVotifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, String s) throws Exception {
|
||||
protected void channelRead0(ChannelHandlerContext ctx, String s) {
|
||||
String[] handshakeContents = s.split(" ");
|
||||
if (handshakeContents.length != 3) {
|
||||
throw new CorruptedFrameException("Handshake is not valid.");
|
||||
@@ -26,7 +26,7 @@ public class VotifierProtocol2HandshakeHandler extends SimpleChannelInboundHandl
|
||||
|
||||
VoteRequest request = new VoteRequest(handshakeContents[2], toSend);
|
||||
if (nuVotifier.isDebug()) {
|
||||
nuVotifier.getLogger().info("Sent request: " + request.toString());
|
||||
nuVotifier.getPluginLogger().info("Sent request: " + request.toString());
|
||||
}
|
||||
ctx.writeAndFlush(request);
|
||||
ctx.pipeline().addLast(new VotifierProtocol2ResponseHandler(responseHandler));
|
||||
@@ -35,7 +35,7 @@ public class VotifierProtocol2HandshakeHandler extends SimpleChannelInboundHandl
|
||||
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
responseHandler.onFailure(cause);
|
||||
ctx.close();
|
||||
}
|
||||
+9
-7
@@ -1,8 +1,9 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.proxy.client;
|
||||
package com.vexsoftware.votifier.support.forwarding.proxy.client;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.vexsoftware.votifier.util.GsonInst;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class VotifierProtocol2ResponseHandler extends SimpleChannelInboundHandler<String> {
|
||||
private final VotifierResponseHandler responseHandler;
|
||||
@@ -12,19 +13,20 @@ public class VotifierProtocol2ResponseHandler extends SimpleChannelInboundHandle
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
|
||||
JSONObject object = new JSONObject(msg);
|
||||
String status = object.getString("status");
|
||||
protected void channelRead0(ChannelHandlerContext ctx, String msg) {
|
||||
JsonObject object = GsonInst.gson.fromJson(msg, JsonObject.class);
|
||||
String status = object.get("status").getAsString();
|
||||
if (status.equals("ok")) {
|
||||
responseHandler.onSuccess();
|
||||
} else {
|
||||
responseHandler.onFailure(new Exception("Remote server error: " + object.getString("cause") + ": " + object.getString("error")));
|
||||
responseHandler.onFailure(new Exception("Remote server error: " + object.get("cause").getAsString() +
|
||||
": " + object.get("error").getAsString()));
|
||||
}
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
responseHandler.onFailure(cause);
|
||||
ctx.close();
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.proxy.client;
|
||||
package com.vexsoftware.votifier.support.forwarding.proxy.client;
|
||||
|
||||
public interface VotifierResponseHandler {
|
||||
void onSuccess();
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.vexsoftware.votifier.util;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
public class GsonInst {
|
||||
|
||||
public static final Gson gson = new GsonBuilder().create();
|
||||
|
||||
}
|
||||
+30
-39
@@ -1,19 +1,14 @@
|
||||
package com.vexsoftware.votifier.util.standalone;
|
||||
|
||||
import com.vexsoftware.votifier.VoteHandler;
|
||||
import com.vexsoftware.votifier.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.net.VotifierServerBootstrap;
|
||||
import com.vexsoftware.votifier.platform.JavaUtilLogger;
|
||||
import com.vexsoftware.votifier.platform.LoggingAdapter;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.net.protocol.VoteInboundHandler;
|
||||
import com.vexsoftware.votifier.net.protocol.VotifierGreetingHandler;
|
||||
import com.vexsoftware.votifier.net.protocol.VotifierProtocolDifferentiator;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import com.vexsoftware.votifier.platform.scheduler.ScheduledExecutorServiceVotifierScheduler;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.security.Key;
|
||||
@@ -21,45 +16,36 @@ import java.security.KeyPair;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class StandaloneVotifierPlugin implements VotifierPlugin, VoteHandler {
|
||||
public class StandaloneVotifierPlugin implements VotifierPlugin {
|
||||
private final Map<String, Key> tokens;
|
||||
private final VoteReceiver receiver;
|
||||
private final KeyPair v1Key;
|
||||
private final InetSocketAddress bind;
|
||||
private NioEventLoopGroup group;
|
||||
private final VotifierScheduler scheduler;
|
||||
private VotifierServerBootstrap bootstrap;
|
||||
|
||||
public StandaloneVotifierPlugin(Map<String, Key> tokens, VoteReceiver receiver, KeyPair v1Key, InetSocketAddress bind) {
|
||||
this.receiver = receiver;
|
||||
this.bind = bind;
|
||||
this.tokens = Collections.unmodifiableMap(new HashMap<>(tokens));
|
||||
this.v1Key = v1Key;
|
||||
this.scheduler = new ScheduledExecutorServiceVotifierScheduler(Executors.newScheduledThreadPool(1));
|
||||
}
|
||||
|
||||
public Throwable start() throws Throwable {
|
||||
group = new NioEventLoopGroup(2);
|
||||
public void start() {
|
||||
start(o -> {});
|
||||
}
|
||||
|
||||
ChannelFuture future = new ServerBootstrap()
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.group(group)
|
||||
.childHandler(new ChannelInitializer<NioSocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(NioSocketChannel channel) throws Exception {
|
||||
channel.attr(VotifierSession.KEY).set(new VotifierSession());
|
||||
channel.attr(VotifierPlugin.KEY).set(StandaloneVotifierPlugin.this);
|
||||
channel.pipeline().addLast("greetingHandler", new VotifierGreetingHandler());
|
||||
channel.pipeline().addLast("protocolDifferentiator", new VotifierProtocolDifferentiator(false, v1Key != null));
|
||||
channel.pipeline().addLast("voteHandler", new VoteInboundHandler(StandaloneVotifierPlugin.this));
|
||||
}
|
||||
})
|
||||
.bind(bind)
|
||||
.syncUninterruptibly();
|
||||
|
||||
if (!future.isSuccess()) {
|
||||
return future.cause();
|
||||
} else {
|
||||
return null;
|
||||
public void start(Consumer<Throwable> error) {
|
||||
if (bootstrap != null) {
|
||||
bootstrap.shutdown();
|
||||
}
|
||||
this.bootstrap = new VotifierServerBootstrap(bind.getHostString(), bind.getPort(), this, v1Key == null);
|
||||
this.bootstrap.start(error);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -73,17 +59,22 @@ public class StandaloneVotifierPlugin implements VotifierPlugin, VoteHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return "2.3.2.2";
|
||||
public LoggingAdapter getPluginLogger() {
|
||||
return new JavaUtilLogger(Logger.getAnonymousLogger());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVoteReceived(Vote vote, VotifierSession.ProtocolVersion protocolVersion) throws Exception {
|
||||
public VotifierScheduler getScheduler() {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVoteReceived(Channel channel, Vote vote, VotifierSession.ProtocolVersion protocolVersion) throws Exception {
|
||||
receiver.onVote(vote);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Channel channel, Throwable throwable) {
|
||||
public void onError(Channel channel, boolean alreadyHandledVote, Throwable throwable) {
|
||||
receiver.onException(throwable);
|
||||
}
|
||||
}
|
||||
+4
@@ -7,6 +7,7 @@ import java.io.File;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
import java.security.interfaces.RSAKey;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@@ -27,6 +28,9 @@ public class VotifierServerBuilder {
|
||||
|
||||
public VotifierServerBuilder v1Key(KeyPair v1Key) {
|
||||
this.v1Key = Objects.requireNonNull(v1Key, "v1Key");
|
||||
if (!(v1Key.getPrivate() instanceof RSAKey)) {
|
||||
throw new IllegalArgumentException("Provided key is not an RSA key.");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.vexsoftware.votifier.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.platform.LoggingAdapter;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
import com.vexsoftware.votifier.util.KeyCreator;
|
||||
import io.netty.channel.Channel;
|
||||
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
@@ -12,6 +16,7 @@ import java.nio.file.Paths;
|
||||
import java.security.*;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -23,7 +28,7 @@ public class TestVotifierPlugin implements VotifierPlugin {
|
||||
URL resourceUrl = TestVotifierPlugin.class.getResource(u);
|
||||
Path resourcePath = Paths.get(resourceUrl.toURI());
|
||||
|
||||
return DatatypeConverter.parseBase64Binary(new String(Files.readAllBytes(resourcePath), StandardCharsets.UTF_8));
|
||||
return Base64.getDecoder().decode(new String(Files.readAllBytes(resourcePath), StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
static {
|
||||
@@ -71,8 +76,13 @@ public class TestVotifierPlugin implements VotifierPlugin {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return "2.3";
|
||||
public LoggingAdapter getPluginLogger() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VotifierScheduler getScheduler() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void specificKeysOnly() {
|
||||
@@ -84,4 +94,9 @@ public class TestVotifierPlugin implements VotifierPlugin {
|
||||
keyMap.clear();
|
||||
keyMap.put("default", KeyCreator.createKeyFrom("test"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVoteReceived(Channel channel, Vote vote, VotifierSession.ProtocolVersion protocolVersion) throws Exception {
|
||||
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.vexsoftware.votifier.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.net.protocol.v1crypto.RSA;
|
||||
|
||||
+21
-16
@@ -1,8 +1,10 @@
|
||||
package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.vexsoftware.votifier.VotifierPlugin;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.vexsoftware.votifier.platform.VotifierPlugin;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.util.GsonInst;
|
||||
import com.vexsoftware.votifier.util.KeyCreator;
|
||||
import io.netty.channel.embedded.EmbeddedChannel;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
@@ -11,9 +13,9 @@ import org.json.JSONObject;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Key;
|
||||
import java.util.Base64;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
@@ -32,13 +34,14 @@ public class VotifierProtocol2DecoderTest {
|
||||
EmbeddedChannel channel = createChannel();
|
||||
|
||||
JSONObject object = new JSONObject();
|
||||
JSONObject payload = vote.serialize();
|
||||
payload.put("challenge", SESSION.getChallenge());
|
||||
object.put("payload", payload.toString());
|
||||
JsonObject payload = vote.serialize();
|
||||
payload.addProperty("challenge", SESSION.getChallenge());
|
||||
String payloadEncoded = GsonInst.gson.toJson(payload);
|
||||
object.put("payload", payloadEncoded);
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(key);
|
||||
object.put("signature",
|
||||
DatatypeConverter.printBase64Binary(mac.doFinal(payload.toString().getBytes(StandardCharsets.UTF_8))));
|
||||
Base64.getEncoder().encodeToString(mac.doFinal(payloadEncoded.getBytes(StandardCharsets.UTF_8))));
|
||||
|
||||
if (expectSuccess) {
|
||||
assertTrue(channel.writeInbound(object.toString()));
|
||||
@@ -59,15 +62,15 @@ public class VotifierProtocol2DecoderTest {
|
||||
}
|
||||
|
||||
@Test(expected = DecoderException.class)
|
||||
public void testFailureDecodeBadPacket() throws Exception {
|
||||
public void testFailureDecodeBadPacket() {
|
||||
// Create a well-formed request
|
||||
EmbeddedChannel channel = createChannel();
|
||||
|
||||
Vote vote = new Vote("Test", "test", "test", "0");
|
||||
JSONObject object = new JSONObject();
|
||||
JSONObject payload = vote.serialize();
|
||||
payload.put("challenge", SESSION.getChallenge());
|
||||
object.put("payload", payload.toString());
|
||||
JsonObject payload = vote.serialize();
|
||||
payload.addProperty("challenge", SESSION.getChallenge());
|
||||
object.put("payload", GsonInst.gson.toJson(payload));
|
||||
// We "forget" the signature.
|
||||
|
||||
try {
|
||||
@@ -84,13 +87,14 @@ public class VotifierProtocol2DecoderTest {
|
||||
|
||||
Vote vote = new Vote("Test", "test", "test", "0");
|
||||
JSONObject object = new JSONObject();
|
||||
JSONObject payload = vote.serialize();
|
||||
JsonObject payload = vote.serialize();
|
||||
String payloadEncoded = GsonInst.gson.toJson(payload);
|
||||
// We "forget" the challenge.
|
||||
object.put("payload", payload.toString());
|
||||
object.put("payload", payloadEncoded);
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(TestVotifierPlugin.getI().getTokens().get("default"));
|
||||
object.put("signature",
|
||||
DatatypeConverter.printBase64Binary(mac.doFinal(payload.toString().getBytes(StandardCharsets.UTF_8))));
|
||||
Base64.getEncoder().encodeToString(mac.doFinal(payloadEncoded.getBytes(StandardCharsets.UTF_8))));
|
||||
|
||||
try {
|
||||
channel.writeInbound(object.toString());
|
||||
@@ -106,14 +110,15 @@ public class VotifierProtocol2DecoderTest {
|
||||
|
||||
Vote vote = new Vote("Test", "test", "test", "0");
|
||||
JSONObject object = new JSONObject();
|
||||
JSONObject payload = vote.serialize();
|
||||
JsonObject payload = vote.serialize();
|
||||
// We provide the wrong challenge.
|
||||
payload.put("challenge", "not a challenge for me");
|
||||
payload.addProperty("challenge", "not a challenge for me");
|
||||
object.put("payload", payload.toString());
|
||||
String payloadEncoded = GsonInst.gson.toJson(payload);
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(TestVotifierPlugin.getI().getTokens().get("default"));
|
||||
object.put("signature",
|
||||
DatatypeConverter.printBase64Binary(mac.doFinal(payload.toString().getBytes(StandardCharsets.UTF_8))));
|
||||
Base64.getEncoder().encode(mac.doFinal(payloadEncoded.getBytes(StandardCharsets.UTF_8))));
|
||||
|
||||
try {
|
||||
channel.writeInbound(object.toString());
|
||||
|
||||
-5
@@ -25,7 +25,6 @@ public class VotifierProtocolDifferentiatorTest {
|
||||
channel.writeInbound(test);
|
||||
|
||||
assertEquals(VotifierSession.ProtocolVersion.ONE, session.getVersion());
|
||||
test.release();
|
||||
channel.close();
|
||||
}
|
||||
|
||||
@@ -41,7 +40,6 @@ public class VotifierProtocolDifferentiatorTest {
|
||||
channel.writeInbound(test);
|
||||
|
||||
assertEquals(VotifierSession.ProtocolVersion.TWO, session.getVersion());
|
||||
test.release();
|
||||
channel.close();
|
||||
}
|
||||
|
||||
@@ -59,7 +57,6 @@ public class VotifierProtocolDifferentiatorTest {
|
||||
channel.writeInbound(test);
|
||||
|
||||
assertEquals(VotifierSession.ProtocolVersion.ONE, session.getVersion());
|
||||
test.release();
|
||||
channel.close();
|
||||
}
|
||||
|
||||
@@ -75,7 +72,6 @@ public class VotifierProtocolDifferentiatorTest {
|
||||
try {
|
||||
channel.writeInbound(buf);
|
||||
} finally {
|
||||
buf.release();
|
||||
channel.close();
|
||||
}
|
||||
}
|
||||
@@ -94,7 +90,6 @@ public class VotifierProtocolDifferentiatorTest {
|
||||
channel.writeInbound(encryptedByteBuf);
|
||||
|
||||
assertEquals(VotifierSession.ProtocolVersion.ONE, session.getVersion());
|
||||
encryptedByteBuf.release();
|
||||
channel.close();
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package com.vexsoftware.votifier.support.forwarding;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.support.forwarding.ForwardedVoteListener;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class PMForwardingSinkTest {
|
||||
|
||||
@Test
|
||||
public void testSuccessfulMultiDecode() throws Exception {
|
||||
List<Vote> receivedVotes = Lists.newArrayList();
|
||||
ForwardedVoteListener vl = receivedVotes::add;
|
||||
|
||||
List<Vote> sentVotes = Lists.newArrayList(
|
||||
new Vote("serviceA", "usernameA", "1.1.1.1", "1546300800"),
|
||||
new Vote("serviceB", "usernameBBBBBBB", "1.2.23.4", "1514764800", System.currentTimeMillis())
|
||||
);
|
||||
|
||||
StringBuilder message = new StringBuilder();
|
||||
|
||||
for (Vote v : sentVotes) {
|
||||
message.append(v.serialize());
|
||||
}
|
||||
|
||||
byte[] messageBytes = message.toString().getBytes();
|
||||
System.out.println(message.toString());
|
||||
|
||||
AbstractPluginMessagingForwardingSink sink = new AbstractPluginMessagingForwardingSink(vl) {
|
||||
@Override
|
||||
public void halt() {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
sink.handlePluginMessage(messageBytes);
|
||||
|
||||
assertEquals(sentVotes.size(), receivedVotes.size());
|
||||
|
||||
for (int i = 0; i < receivedVotes.size(); i++) {
|
||||
assertEquals(sentVotes.get(i), receivedVotes.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
BIN
Binary file not shown.
+6
@@ -0,0 +1,6 @@
|
||||
#Thu May 31 03:56:27 EDT 2018
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-all.zip
|
||||
@@ -0,0 +1,172 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
Vendored
+84
@@ -0,0 +1,84 @@
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@@ -1,57 +0,0 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.vexsoftware</groupId>
|
||||
<artifactId>nuvotifier</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<version>2.3.2.2</version>
|
||||
<modules>
|
||||
<module>common</module>
|
||||
<module>bukkit</module>
|
||||
<module>bungeecord</module>
|
||||
<module>universal</module>
|
||||
<module>sponge</module>
|
||||
</modules>
|
||||
<name>NuVotifier</name>
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>sonatype</id>
|
||||
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>bukkit</id>
|
||||
<url>http://repo.bukkit.org/content/groups/public/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<distributionManagement>
|
||||
<repository>
|
||||
<id>minimumio-repo</id>
|
||||
<url>http://repo.minimum.io/content/repositories/releases/</url>
|
||||
</repository>
|
||||
<snapshotRepository>
|
||||
<id>minimumio-repo-snapshots</id>
|
||||
<url>http://repo.minimum.io/content/repositories/snapshots/</url>
|
||||
</snapshotRepository>
|
||||
</distributionManagement>
|
||||
|
||||
<build>
|
||||
<defaultGoal>clean package</defaultGoal>
|
||||
<plugins>
|
||||
<!-- Compiler -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>2.3.2</version>
|
||||
<configuration>
|
||||
<source>1.7</source>
|
||||
<target>1.7</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,27 @@
|
||||
subprojects {
|
||||
buildscript {
|
||||
}
|
||||
apply plugin: 'maven-publish'
|
||||
apply plugin: 'com.jfrog.bintray'
|
||||
|
||||
bintray {
|
||||
user = System.getenv("BINTRAY_USER")
|
||||
key = System.getenv("BINTRAY_KEY")
|
||||
pkg {
|
||||
repo = 'public'
|
||||
name = 'nuvotifier'
|
||||
version {
|
||||
name = project.version
|
||||
}
|
||||
publications = ['DefaultPublication']
|
||||
}
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
DefaultPublication(MavenPublication) {
|
||||
from components.java
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
rootProject.name = 'nuvotifier'
|
||||
|
||||
include 'common'
|
||||
findProject(':common')?.name = 'nuvotifier-common'
|
||||
|
||||
include 'bukkit'
|
||||
findProject(':bukkit')?.name = 'nuvotifier-bukkit'
|
||||
include 'bungeecord'
|
||||
findProject(':bungeecord')?.name = 'nuvotifier-bungeecord'
|
||||
include 'sponge'
|
||||
findProject(':sponge')?.name = 'nuvotifier-sponge'
|
||||
|
||||
include 'universal'
|
||||
findProject(':universal')?.name = 'nuvotifier-universal'
|
||||
include 'velocity'
|
||||
findProject(':velocity')?.name = 'nuvotifier-velocity'
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
plugins {
|
||||
id 'org.spongepowered.plugin' version '0.9.0'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly group: "org.spongepowered", name: "spongeapi", version: "7.1.0"
|
||||
annotationProcessor group: "org.spongepowered", name: "spongeapi", version: "7.1.0"
|
||||
compile project(":nuvotifier-common")
|
||||
}
|
||||
|
||||
sponge {
|
||||
plugin {
|
||||
id = 'nuvotifier'
|
||||
meta {
|
||||
version = project.version
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>nuvotifier</artifactId>
|
||||
<groupId>com.vexsoftware</groupId>
|
||||
<version>2.3.2.2</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>nuvotifier-sponge</artifactId>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>sponge-maven-repo</id>
|
||||
<name>Sponge maven repo</name>
|
||||
<url>http://repo.spongepowered.org/maven</url>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>2.3.1</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>2.9</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.vexsoftware</groupId>
|
||||
<artifactId>nuvotifier-common</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.spongepowered</groupId>
|
||||
<artifactId>spongeapi</artifactId>
|
||||
<version>4.0.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.vexsoftware.votifier.sponge;
|
||||
|
||||
import com.vexsoftware.votifier.platform.LoggingAdapter;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
public class SLF4JLogger implements LoggingAdapter {
|
||||
|
||||
private final Logger l;
|
||||
public SLF4JLogger(Logger l) {
|
||||
this.l = l;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String s) {
|
||||
l.error(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String s, Object... o) {
|
||||
l.error(s, o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String s) {
|
||||
l.warn(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String s, Object... o) {
|
||||
l.warn(s, o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String s) {
|
||||
l.info(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String s, Object... o) {
|
||||
l.info(s, o);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.vexsoftware.votifier.sponge;
|
||||
|
||||
import com.vexsoftware.votifier.platform.scheduler.ScheduledVotifierTask;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
import org.spongepowered.api.Sponge;
|
||||
import org.spongepowered.api.scheduler.Task;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
class SpongeScheduler implements VotifierScheduler {
|
||||
private final VotifierPlugin plugin;
|
||||
|
||||
SpongeScheduler(VotifierPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
private Task.Builder taskBuilder(Runnable runnable) {
|
||||
return Sponge.getScheduler().createTaskBuilder().execute(runnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask sync(Runnable runnable) {
|
||||
return new TaskWrapper(taskBuilder(runnable).submit(plugin));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask onPool(Runnable runnable) {
|
||||
return new TaskWrapper(taskBuilder(runnable).async().submit(plugin));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask delayedSync(Runnable runnable, int delay, TimeUnit unit) {
|
||||
return new TaskWrapper(taskBuilder(runnable).delay(delay, unit).submit(plugin));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask delayedOnPool(Runnable runnable, int delay, TimeUnit unit) {
|
||||
return new TaskWrapper(taskBuilder(runnable).delay(delay, unit).async().submit(plugin));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask repeatOnPool(Runnable runnable, int delay, int repeat, TimeUnit unit) {
|
||||
return new TaskWrapper(taskBuilder(runnable).delay(delay, unit).interval(repeat, unit).submit(plugin));
|
||||
}
|
||||
|
||||
private static class TaskWrapper implements ScheduledVotifierTask {
|
||||
private final Task task;
|
||||
|
||||
private TaskWrapper(Task task) {
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
task.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,152 +1,63 @@
|
||||
package com.vexsoftware.votifier.sponge;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.inject.Inject;
|
||||
import com.vexsoftware.votifier.VoteHandler;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.VotifierServerBootstrap;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.net.protocol.VoteInboundHandler;
|
||||
import com.vexsoftware.votifier.net.protocol.VotifierGreetingHandler;
|
||||
import com.vexsoftware.votifier.net.protocol.VotifierProtocolDifferentiator;
|
||||
import com.vexsoftware.votifier.net.protocol.v1crypto.RSAIO;
|
||||
import com.vexsoftware.votifier.net.protocol.v1crypto.RSAKeygen;
|
||||
import com.vexsoftware.votifier.platform.LoggingAdapter;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
import com.vexsoftware.votifier.sponge.config.ConfigLoader;
|
||||
import com.vexsoftware.votifier.sponge.event.VotifierEvent;
|
||||
import com.vexsoftware.votifier.sponge.forwarding.ForwardedVoteListener;
|
||||
import com.vexsoftware.votifier.sponge.forwarding.ForwardingVoteSink;
|
||||
import com.vexsoftware.votifier.sponge.forwarding.SpongePluginMessagingForwardingSink;
|
||||
import com.vexsoftware.votifier.support.forwarding.ForwardedVoteListener;
|
||||
import com.vexsoftware.votifier.support.forwarding.ForwardingVoteSink;
|
||||
import com.vexsoftware.votifier.util.KeyCreator;
|
||||
import com.vexsoftware.votifier.util.TokenUtil;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import ninja.leaping.configurate.ConfigurationNode;
|
||||
import ninja.leaping.configurate.loader.ConfigurationLoader;
|
||||
import ninja.leaping.configurate.yaml.YAMLConfigurationLoader;
|
||||
import org.slf4j.Logger;
|
||||
import org.spongepowered.api.Sponge;
|
||||
import org.spongepowered.api.config.ConfigDir;
|
||||
import org.spongepowered.api.event.Listener;
|
||||
import org.spongepowered.api.event.cause.Cause;
|
||||
import org.spongepowered.api.event.cause.NamedCause;
|
||||
import org.spongepowered.api.event.game.state.GameStartedServerEvent;
|
||||
import org.spongepowered.api.event.game.state.GameStoppingServerEvent;
|
||||
import org.spongepowered.api.plugin.Plugin;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
@Plugin(id = "nuvotifier", name = "NuVotifier", version = "2.3.2.2")
|
||||
public class VotifierPlugin implements VoteHandler, com.vexsoftware.votifier.VotifierPlugin, ForwardedVoteListener {
|
||||
private Logger logger;
|
||||
@Plugin(id = "nuvotifier", name = "NuVotifier", version="SOMEONE_FUCKED_UP_THE_BUILD", authors = "Ichbinjoe",
|
||||
description = "Safe, smart, and secure Votifier server plugin")
|
||||
public class VotifierPlugin implements VoteHandler, com.vexsoftware.votifier.platform.VotifierPlugin, ForwardedVoteListener {
|
||||
|
||||
@Inject
|
||||
public Logger logger;
|
||||
|
||||
private SLF4JLogger loggerAdapter;
|
||||
|
||||
@Inject
|
||||
@ConfigDir(sharedRoot = false)
|
||||
private Path baseDirectory;
|
||||
public File configDir;
|
||||
|
||||
@Inject
|
||||
public VotifierPlugin(Logger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
private VotifierScheduler scheduler;
|
||||
|
||||
@Listener
|
||||
public void onServerStart(GameStartedServerEvent event) {
|
||||
// Set the plugin version.
|
||||
version = this.getClass().getAnnotation(Plugin.class).version();
|
||||
|
||||
// Handle configuration.
|
||||
Path config = baseDirectory.resolve("config.yml");
|
||||
File rsaDirectory = baseDirectory.resolve("rsa").toFile();
|
||||
|
||||
/*
|
||||
* Use IP address from server.properties as a default for
|
||||
* configurations. Do not use InetAddress.getLocalHost() as it most
|
||||
* likely will return the main server address instead of the address
|
||||
* assigned to the server.
|
||||
*/
|
||||
String hostAddr;
|
||||
Optional<InetSocketAddress> address = Sponge.getServer().getBoundAddress();
|
||||
if (address.isPresent())
|
||||
hostAddr = address.get().getAddress().getHostAddress();
|
||||
else
|
||||
hostAddr = "0.0.0.0";
|
||||
|
||||
/*
|
||||
* Create configuration file if it does not exists; otherwise, load it
|
||||
*/
|
||||
try {
|
||||
Files.createDirectories(baseDirectory);
|
||||
} catch (FileAlreadyExistsException ignored) {
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Can't create base directory", e);
|
||||
}
|
||||
|
||||
if (!Files.exists(config)) {
|
||||
try {
|
||||
// First time run - do some initialization.
|
||||
logger.info("Configuring Votifier for the first time...");
|
||||
|
||||
// Load and manually replace variables in the configuration.
|
||||
String cfgStr = new String(ByteStreams.toByteArray(this.getClass().getResourceAsStream("spongeConfig.yml")),
|
||||
StandardCharsets.UTF_8);
|
||||
String token = TokenUtil.newToken();
|
||||
cfgStr = cfgStr.replace("%default_token%", token).replace("%ip%", hostAddr);
|
||||
Files.write(config, ImmutableList.<CharSequence>of(cfgStr), StandardCharsets.UTF_8);
|
||||
|
||||
/*
|
||||
* Remind hosted server admins to be sure they have the right
|
||||
* port number.
|
||||
*/
|
||||
logger.info("------------------------------------------------------------------------------");
|
||||
logger.info("Assigning NuVotifier to listen on port 8192. If you are hosting Craftbukkit on a");
|
||||
logger.info("shared server please check with your hosting provider to verify that this port");
|
||||
logger.info("is available for your use. Chances are that your hosting provider will assign");
|
||||
logger.info("a different port, which you need to specify in config.yml");
|
||||
logger.info("------------------------------------------------------------------------------");
|
||||
logger.info("Your default NuVotifier token is " + token + ".");
|
||||
logger.info("You will need to provide this token when you submit your server to a voting");
|
||||
logger.info("list.");
|
||||
logger.info("------------------------------------------------------------------------------");
|
||||
} catch (Exception ex) {
|
||||
logger.error("Error creating configuration file", ex);
|
||||
gracefulExit();
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.scheduler = new SpongeScheduler(this);
|
||||
this.loggerAdapter = new SLF4JLogger(logger);
|
||||
|
||||
// Load configuration.
|
||||
ConfigurationLoader loader = YAMLConfigurationLoader.builder()
|
||||
.setPath(config)
|
||||
.build();
|
||||
ConfigurationNode node;
|
||||
try {
|
||||
node = loader.load();
|
||||
} catch (IOException e) {
|
||||
logger.error("Error loading configuration file", e);
|
||||
gracefulExit();
|
||||
return;
|
||||
}
|
||||
ConfigLoader.loadConfig(this);
|
||||
|
||||
/*
|
||||
* Create RSA directory and keys if it does not exist; otherwise, read
|
||||
* keys.
|
||||
*/
|
||||
File rsaDirectory = new File(configDir, "rsa");
|
||||
try {
|
||||
if (!rsaDirectory.exists()) {
|
||||
rsaDirectory.mkdir();
|
||||
@@ -161,47 +72,23 @@ public class VotifierPlugin implements VoteHandler, com.vexsoftware.votifier.Vot
|
||||
return;
|
||||
}
|
||||
|
||||
debug = node.getNode("debug").getBoolean(false);
|
||||
debug = ConfigLoader.getSpongeConfig().debug;
|
||||
|
||||
boolean setUpPort = node.getNode("enableExternal").getBoolean(true); //Always default to running the external port
|
||||
// Load Votifier tokens.
|
||||
ConfigLoader.getSpongeConfig().tokens.forEach((s, s2) -> {
|
||||
tokens.put(s, KeyCreator.createKeyFrom(s2));
|
||||
logger.info("Loaded token for website: " + s);
|
||||
});
|
||||
|
||||
if (setUpPort) {
|
||||
// Load Votifier tokens.
|
||||
ConfigurationNode tokenSection = node.getNode("tokens");
|
||||
// Initialize the receiver.
|
||||
final String host = ConfigLoader.getSpongeConfig().host;
|
||||
final int port = ConfigLoader.getSpongeConfig().port;
|
||||
|
||||
if (tokenSection.hasMapChildren()) {
|
||||
for (Map.Entry<Object, ? extends ConfigurationNode> entry : tokenSection.getChildrenMap().entrySet()) {
|
||||
if (entry.getKey() instanceof String) {
|
||||
tokens.put((String) entry.getKey(), KeyCreator.createKeyFrom(entry.getValue().getString()));
|
||||
logger.info("Loaded token for website: " + entry.getKey());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
String token = TokenUtil.newToken();
|
||||
tokenSection.setValue(ImmutableMap.of("default", token));
|
||||
tokens.put("default", KeyCreator.createKeyFrom(token));
|
||||
try {
|
||||
loader.save(node);
|
||||
} catch (IOException e) {
|
||||
logger.error("Error generating Votifier token", e);
|
||||
gracefulExit();
|
||||
return;
|
||||
}
|
||||
logger.info("------------------------------------------------------------------------------");
|
||||
logger.info("No tokens were found in your configuration, so we've generated one for you.");
|
||||
logger.info("Your default Votifier token is " + token + ".");
|
||||
logger.info("You will need to provide this token when you submit your server to a voting");
|
||||
logger.info("list.");
|
||||
logger.info("------------------------------------------------------------------------------");
|
||||
}
|
||||
if (!debug)
|
||||
logger.info("QUIET mode enabled!");
|
||||
|
||||
// Initialize the receiver.
|
||||
final String host = node.getNode("host").getString(hostAddr);
|
||||
final int port = node.getNode("port").getInt(8192);
|
||||
if (debug)
|
||||
logger.info("DEBUG mode enabled!");
|
||||
|
||||
final boolean disablev1 = node.getNode("disable-v1-protocol").getBoolean(false);
|
||||
if (port >= 0) {
|
||||
final boolean disablev1 = ConfigLoader.getSpongeConfig().disableV1Protocol;
|
||||
if (disablev1) {
|
||||
logger.info("------------------------------------------------------------------------------");
|
||||
logger.info("Votifier protocol v1 parsing has been disabled. Most voting websites do not");
|
||||
@@ -209,48 +96,22 @@ public class VotifierPlugin implements VoteHandler, com.vexsoftware.votifier.Vot
|
||||
logger.info("------------------------------------------------------------------------------");
|
||||
}
|
||||
|
||||
serverGroup = new NioEventLoopGroup(1);
|
||||
|
||||
new ServerBootstrap()
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.group(serverGroup)
|
||||
.childHandler(new ChannelInitializer<NioSocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(NioSocketChannel channel) throws Exception {
|
||||
channel.attr(VotifierSession.KEY).set(new VotifierSession());
|
||||
channel.attr(com.vexsoftware.votifier.VotifierPlugin.KEY).set(VotifierPlugin.this);
|
||||
channel.pipeline().addLast("greetingHandler", new VotifierGreetingHandler());
|
||||
channel.pipeline().addLast("protocolDifferentiator", new VotifierProtocolDifferentiator(false, !disablev1));
|
||||
channel.pipeline().addLast("voteHandler", new VoteInboundHandler(VotifierPlugin.this));
|
||||
}
|
||||
})
|
||||
.bind(host, port)
|
||||
.addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
if (future.isSuccess()) {
|
||||
serverChannel = future.channel();
|
||||
logger.info("Votifier enabled on socket "+serverChannel.localAddress()+".");
|
||||
} else {
|
||||
SocketAddress socketAddress = future.channel().localAddress();
|
||||
if (socketAddress == null) {
|
||||
socketAddress = new InetSocketAddress(host, port);
|
||||
}
|
||||
logger.error("Votifier was not able to bind to " + socketAddress, future.cause());
|
||||
}
|
||||
}
|
||||
});
|
||||
this.bootstrap = new VotifierServerBootstrap(host, port, this, disablev1);
|
||||
this.bootstrap.start(err -> {});
|
||||
} else {
|
||||
logger.info("You have enableExternal set to false in your config.yml. NuVotifier will NOT listen to votes coming in from an external voting list.");
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
getLogger().info("Your Votifier port is less than 0, so we assume you do NOT want to start the");
|
||||
getLogger().info("votifier port server! Votifier will not listen for votes over any port, and");
|
||||
getLogger().info("will only listen for pluginMessaging forwarded votes!");
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
}
|
||||
|
||||
ConfigurationNode forwardingConfig = node.getNode("forwarding");
|
||||
if (forwardingConfig != null) {
|
||||
String method = forwardingConfig.getNode("method").getString("none").toLowerCase(); //Default to lower case for case-insensitive searches
|
||||
if (ConfigLoader.getSpongeConfig().forwarding != null) {
|
||||
String method = ConfigLoader.getSpongeConfig().forwarding.method.toLowerCase(); //Default to lower case for case-insensitive searches
|
||||
if ("none".equals(method)) {
|
||||
getLogger().info("Method none selected for vote forwarding: Votes will not be received from a forwarder.");
|
||||
} else if ("pluginmessaging".equals(method)) {
|
||||
String channel = forwardingConfig.getNode("pluginMessaging").getNode("channel").getString("NuVotifier");
|
||||
String channel = ConfigLoader.getSpongeConfig().forwarding.pluginMessaging.channel;
|
||||
try {
|
||||
forwardingMethod = new SpongePluginMessagingForwardingSink(this, channel, this);
|
||||
getLogger().info("Receiving votes over PluginMessaging channel '" + channel + "'.");
|
||||
@@ -265,11 +126,11 @@ public class VotifierPlugin implements VoteHandler, com.vexsoftware.votifier.Vot
|
||||
|
||||
@Listener
|
||||
public void onServerStop(GameStoppingServerEvent event) {
|
||||
if (serverGroup != null) {
|
||||
if (serverChannel != null)
|
||||
serverChannel.close();
|
||||
serverGroup.shutdownGracefully();
|
||||
if (bootstrap != null) {
|
||||
bootstrap.shutdown();
|
||||
bootstrap = null;
|
||||
}
|
||||
|
||||
if (forwardingMethod != null)
|
||||
forwardingMethod.halt();
|
||||
|
||||
@@ -286,14 +147,9 @@ public class VotifierPlugin implements VoteHandler, com.vexsoftware.votifier.Vot
|
||||
private String version;
|
||||
|
||||
/**
|
||||
* The server channel.
|
||||
* The server bootstrap.
|
||||
*/
|
||||
private Channel serverChannel;
|
||||
|
||||
/**
|
||||
* The event group handling the channel.
|
||||
*/
|
||||
private NioEventLoopGroup serverGroup;
|
||||
private VotifierServerBootstrap bootstrap;
|
||||
|
||||
/**
|
||||
* The RSA key pair.
|
||||
@@ -316,13 +172,14 @@ public class VotifierPlugin implements VoteHandler, com.vexsoftware.votifier.Vot
|
||||
logger.error("Votifier did not initialize properly!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the version.
|
||||
*
|
||||
* @return The version
|
||||
*/
|
||||
public String getVersion() {
|
||||
return version;
|
||||
@Override
|
||||
public LoggingAdapter getPluginLogger() {
|
||||
return loggerAdapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VotifierScheduler getScheduler() {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
public boolean isDebug() {
|
||||
@@ -339,32 +196,37 @@ public class VotifierPlugin implements VoteHandler, com.vexsoftware.votifier.Vot
|
||||
return keyPair;
|
||||
}
|
||||
|
||||
public File getConfigDir() {
|
||||
return configDir;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVoteReceived(final Vote vote, VotifierSession.ProtocolVersion protocolVersion) throws Exception {
|
||||
public void onVoteReceived(Channel channel, final Vote vote, VotifierSession.ProtocolVersion protocolVersion) {
|
||||
if (debug) {
|
||||
if (protocolVersion == VotifierSession.ProtocolVersion.ONE) {
|
||||
logger.info("Got a protocol v1 vote record -> " + vote);
|
||||
logger.info("Got a protocol v1 vote record from " + channel.remoteAddress() + " -> " + vote);
|
||||
} else {
|
||||
logger.info("Got a protocol v2 vote record -> " + vote);
|
||||
logger.info("Got a protocol v2 vote record from " + channel.remoteAddress() + " -> " + vote);
|
||||
}
|
||||
}
|
||||
Sponge.getScheduler().createTaskBuilder()
|
||||
.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
VotifierEvent event = new VotifierEvent(vote, Cause.of(NamedCause.of("Vote", vote)));
|
||||
Sponge.getEventManager().post(event);
|
||||
}
|
||||
.execute(() -> {
|
||||
VotifierEvent event = new VotifierEvent(vote, Sponge.getCauseStackManager().getCurrentCause());
|
||||
Sponge.getEventManager().post(event);
|
||||
})
|
||||
.async()
|
||||
.submit(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Channel channel, Throwable throwable) {
|
||||
public void onError(Channel channel, boolean alreadyHandledVote, Throwable throwable) {
|
||||
if (debug) {
|
||||
logger.error("Unable to process vote from " + channel.remoteAddress(), throwable);
|
||||
} else {
|
||||
if (alreadyHandledVote) {
|
||||
logger.error("Vote processed, however an exception " +
|
||||
"occurred with a vote from " + channel.remoteAddress(), throwable);
|
||||
} else {
|
||||
logger.error("Unable to process vote from " + channel.remoteAddress(), throwable);
|
||||
}
|
||||
} else if (!alreadyHandledVote) {
|
||||
logger.error("Unable to process vote from " + channel.remoteAddress());
|
||||
}
|
||||
}
|
||||
@@ -375,14 +237,10 @@ public class VotifierPlugin implements VoteHandler, com.vexsoftware.votifier.Vot
|
||||
logger.info("Got a forwarded vote -> " + v);
|
||||
}
|
||||
Sponge.getScheduler().createTaskBuilder()
|
||||
.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
VotifierEvent event = new VotifierEvent(v, Cause.of(NamedCause.of("ForwardedVote", v)));
|
||||
Sponge.getEventManager().post(event);
|
||||
}
|
||||
.execute(() -> {
|
||||
VotifierEvent event = new VotifierEvent(v, Sponge.getCauseStackManager().getCurrentCause());
|
||||
Sponge.getEventManager().post(event);
|
||||
})
|
||||
.async()
|
||||
.submit(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.vexsoftware.votifier.sponge.config;
|
||||
|
||||
import com.google.common.reflect.TypeToken;
|
||||
import com.vexsoftware.votifier.sponge.VotifierPlugin;
|
||||
import ninja.leaping.configurate.ConfigurationNode;
|
||||
import ninja.leaping.configurate.ConfigurationOptions;
|
||||
import ninja.leaping.configurate.loader.ConfigurationLoader;
|
||||
import ninja.leaping.configurate.yaml.YAMLConfigurationLoader;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class ConfigLoader {
|
||||
|
||||
private static VotifierPlugin plugin;
|
||||
private static SpongeConfig spongeConfig;
|
||||
|
||||
public static void loadConfig(VotifierPlugin pl) {
|
||||
plugin = pl;
|
||||
if (!plugin.getConfigDir().exists()) {
|
||||
plugin.getConfigDir().mkdirs();
|
||||
}
|
||||
try {
|
||||
File file = new File(plugin.getConfigDir(), "config.yml");
|
||||
if (!file.exists()) {
|
||||
file.createNewFile();
|
||||
}
|
||||
ConfigurationLoader loader = YAMLConfigurationLoader.builder().setFile(file).build();
|
||||
ConfigurationNode config = loader.load(ConfigurationOptions.defaults().setShouldCopyDefaults(true));
|
||||
spongeConfig = config.getValue(TypeToken.of(SpongeConfig.class), new SpongeConfig());
|
||||
loader.save(config);
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().error("Could not load config.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static SpongeConfig getSpongeConfig() {
|
||||
return spongeConfig;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.vexsoftware.votifier.sponge.config;
|
||||
|
||||
import com.vexsoftware.votifier.util.TokenUtil;
|
||||
import ninja.leaping.configurate.objectmapping.Setting;
|
||||
import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable;
|
||||
import org.spongepowered.api.Sponge;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
@ConfigSerializable
|
||||
public class SpongeConfig {
|
||||
|
||||
@Setting(comment = "The IP to listen to. Use 0.0.0.0 if you wish to listen to all interfaces on your server. (All IP addresses)\n" +
|
||||
"This defaults to the IP you have configured your server to listen on, or 0.0.0.0 if you have not configured this.")
|
||||
public String host = Sponge.getServer().getBoundAddress().isPresent() ? Sponge.getServer().getBoundAddress().get().getAddress().getHostAddress() : "0.0.0.0";
|
||||
|
||||
@Setting(comment = "Port to listen for new votes on")
|
||||
public int port = 8192;
|
||||
|
||||
@Setting(comment = "Whether or not to print debug messages. In a production system, this should be set to false.\n" +
|
||||
"This is useful when initially setting up NuVotifier to ensure votes are being delivered.")
|
||||
public boolean debug = true;
|
||||
|
||||
@Setting( value = "disable-v1-protocol", comment = "Setting this option to true will disable handling of Protocol v1 packets. While the old protocol is not secure, this\n" +
|
||||
"option is currently not recommended as most voting sites only support the old protocol at present. However, if you are\n" +
|
||||
"using NuVotifier's proxy forwarding mechanism, enabling this option will increase your server's security.")
|
||||
public boolean disableV1Protocol = false;
|
||||
|
||||
@Setting(comment = "All tokens, labeled by the serviceName of each server list.\n" +
|
||||
"Default token for all server lists, if another isn't supplied.")
|
||||
public Map<String, String> tokens = Collections.singletonMap("default", TokenUtil.newToken());
|
||||
|
||||
@Setting(comment = "Configuration section for all vote forwarding to NuVotifier")
|
||||
public Forwarding forwarding = new Forwarding();
|
||||
|
||||
@ConfigSerializable
|
||||
public static class Forwarding {
|
||||
|
||||
@Setting(comment = "Sets whether to set up a remote method for fowarding. Supported methods:\n" +
|
||||
"- none - Does not set up a forwarding method.\n" +
|
||||
"- pluginMessaging - Sets up plugin messaging")
|
||||
public String method = "none";
|
||||
|
||||
@Setting
|
||||
public PluginMessaging pluginMessaging = new PluginMessaging();
|
||||
|
||||
@ConfigSerializable
|
||||
public static class PluginMessaging {
|
||||
|
||||
@Setting
|
||||
public String channel = "nuvotifier:votes";
|
||||
}
|
||||
}
|
||||
}
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
package com.vexsoftware.votifier.sponge.forwarding;
|
||||
|
||||
/**
|
||||
* Represents a method at which to receive forwarded votes.
|
||||
*/
|
||||
public interface ForwardingVoteSink {
|
||||
|
||||
/**
|
||||
* Stop or close any oustanding network interfaces. Occurs on the onDisable method of a plugin.
|
||||
*/
|
||||
void halt();
|
||||
|
||||
}
|
||||
+6
-11
@@ -1,8 +1,8 @@
|
||||
package com.vexsoftware.votifier.sponge.forwarding;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.sponge.VotifierPlugin;
|
||||
import org.json.JSONObject;
|
||||
import com.vexsoftware.votifier.support.forwarding.AbstractPluginMessagingForwardingSink;
|
||||
import com.vexsoftware.votifier.support.forwarding.ForwardedVoteListener;
|
||||
import org.spongepowered.api.Platform;
|
||||
import org.spongepowered.api.Sponge;
|
||||
import org.spongepowered.api.network.ChannelBinding;
|
||||
@@ -10,19 +10,16 @@ import org.spongepowered.api.network.ChannelBuf;
|
||||
import org.spongepowered.api.network.RawDataListener;
|
||||
import org.spongepowered.api.network.RemoteConnection;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class SpongePluginMessagingForwardingSink implements ForwardingVoteSink, RawDataListener {
|
||||
public class SpongePluginMessagingForwardingSink extends AbstractPluginMessagingForwardingSink implements RawDataListener {
|
||||
|
||||
public SpongePluginMessagingForwardingSink(VotifierPlugin p, String channel, ForwardedVoteListener listener) {
|
||||
super(listener);
|
||||
this.channelBinding = Sponge.getChannelRegistrar().createRawChannel(p, channel);
|
||||
this.channelBinding.addListener(Platform.Type.SERVER, this);
|
||||
this.listener = listener;
|
||||
this.p = p;
|
||||
}
|
||||
|
||||
private final VotifierPlugin p;
|
||||
private final ForwardedVoteListener listener;
|
||||
private final ChannelBinding.RawDataChannel channelBinding;
|
||||
|
||||
@Override
|
||||
@@ -32,11 +29,9 @@ public class SpongePluginMessagingForwardingSink implements ForwardingVoteSink,
|
||||
|
||||
@Override
|
||||
public void handlePayload(ChannelBuf channelBuf, RemoteConnection remoteConnection, Platform.Type type) {
|
||||
byte[] msgDirBuf = channelBuf.readBytes(channelBuf.available());
|
||||
try {
|
||||
String message = new String(channelBuf.array(), StandardCharsets.UTF_8);
|
||||
JSONObject jsonObject = new JSONObject(message);
|
||||
Vote v = new Vote(jsonObject);
|
||||
listener.onForward(v);
|
||||
this.handlePluginMessage(msgDirBuf);
|
||||
} catch (Exception e) {
|
||||
p.getLogger().error("There was an unknown error when processing a forwarded vote.", e);
|
||||
}
|
||||
|
||||
@@ -5,14 +5,6 @@ host: %ip%
|
||||
# Port to listen for new votes on
|
||||
port: 8192
|
||||
|
||||
# Whether or not to print debug messages. In a production system, this should be set to false.
|
||||
# This is useful when initially setting up NuVotifier to ensure votes are being delivered.
|
||||
debug: false
|
||||
|
||||
# Setting this value to false will turn off the votifier port listening for external votes. This is beneficial if the server
|
||||
# is only listening for votes coming from NuVotifier running on your BungeeCord.
|
||||
enableExternal: true
|
||||
|
||||
# Setting this option to true will disable handling of Protocol v1 packets. While the old protocol is not secure, this
|
||||
# option is currently not recommended as most voting sites only support the old protocol at present. However, if you are
|
||||
# using NuVotifier's proxy forwarding mechanism, enabling this option will increase your server's security.
|
||||
@@ -30,4 +22,4 @@ forwarding:
|
||||
# - pluginMessaging - Sets up plugin messaging
|
||||
method: none
|
||||
pluginMessaging:
|
||||
channel: NuVotifier
|
||||
channel: nuvotifier:votes
|
||||
@@ -0,0 +1,70 @@
|
||||
import com.github.jengelman.gradle.plugins.shadow.relocation.SimpleRelocator
|
||||
|
||||
import java.nio.file.FileAlreadyExistsException
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Paths
|
||||
import java.util.regex.Pattern
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.4'
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'com.github.johnrengelman.shadow'
|
||||
|
||||
dependencies {
|
||||
compile project(":nuvotifier-bukkit")
|
||||
compile project(":nuvotifier-bungeecord")
|
||||
compile project(":nuvotifier-sponge")
|
||||
compile project(":nuvotifier-velocity")
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
dependencies {
|
||||
// All current implementations include Google Guava already, and explicitly shading in Guava causes the plugin
|
||||
// to fail to initialize on Sponge.
|
||||
exclude(dependency('com.google.guava:guava:24.1-jre'))
|
||||
}
|
||||
|
||||
baseName = 'nuvotifier'
|
||||
classifier = null
|
||||
relocate "io.netty", "com.vexsoftware.votifier.netty"
|
||||
relocate "org.json", "com.vexsoftware.votifier.json"
|
||||
relocate new SimpleRelocator("com.google.code", "com.vexsoftware.votifier.google.code", new ArrayList<String>(), Collections.singletonList("com.vexsoftware.votifier.sponge"))
|
||||
relocate "com.google.guava", "com.vexsoftware.votifier.google.guava"
|
||||
relocate "org.apache.commons.io", "com.vexsoftware.votifier.commons.io"
|
||||
}
|
||||
|
||||
artifacts {
|
||||
tasks.shadowJar.outputs.files.each {
|
||||
archives(it) {
|
||||
builtBy shadowJar
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def artifactPattern = Pattern.compile(/(.*)-([0-9]+\.)*[0-9]+(-SNAPSHOT)?\.jar/)
|
||||
|
||||
task('linkLatest') {
|
||||
dependsOn tasks.shadowJar
|
||||
doLast {
|
||||
tasks.shadowJar.outputs.files.each {
|
||||
System.println(it.absolutePath)
|
||||
def noVerMatcher = artifactPattern.matcher(it.absolutePath)
|
||||
if (!noVerMatcher.matches())
|
||||
throw new IllegalStateException("wtf")
|
||||
|
||||
def noVer = Paths.get(noVerMatcher.group(1) + ".jar")
|
||||
def versnd = it.toPath()
|
||||
try {
|
||||
Files.createSymbolicLink(noVer, versnd)
|
||||
} catch (FileAlreadyExistsException ignored) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
build.dependsOn linkLatest
|
||||
@@ -1,66 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>nuvotifier</artifactId>
|
||||
<groupId>com.vexsoftware</groupId>
|
||||
<version>2.3.2.2</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>nuvotifier-universal</artifactId>
|
||||
<name>nuvotifier</name>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>2.3</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<relocations>
|
||||
<relocation>
|
||||
<pattern>io.netty</pattern>
|
||||
<shadedPattern>com.vexsoftware.votifier.netty</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>org.json</pattern>
|
||||
<shadedPattern>com.vexsoftware.votifier.json</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>org.apache.commons.io</pattern>
|
||||
<shadedPattern>com.vexsoftware.votifier.commons.io</shadedPattern>
|
||||
</relocation>
|
||||
</relocations>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.vexsoftware</groupId>
|
||||
<artifactId>nuvotifier-bukkit</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.vexsoftware</groupId>
|
||||
<artifactId>nuvotifier-bungeecord</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.vexsoftware</groupId>
|
||||
<artifactId>nuvotifier-sponge</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -1,151 +0,0 @@
|
||||
This document outlines how to use NuVotifier as a plugin on both Bukkit based servers, as well as BungeeCord/Bukkit based
|
||||
networks.
|
||||
|
||||
# Installation
|
||||
|
||||
The NuVotifier Universal jar (the default jar that is distributed on spigotmc.org) can be installed on Bukkit, BungeeCord,
|
||||
and Sponge servers as-is without modification.
|
||||
|
||||
The Universal JAR can be downloaded off our [SpigotMC resource page](https://www.spigotmc.org/resources/nuvotifier.13449/).
|
||||
|
||||
# Votifier v1 vs NuVotifier v2 Protocols
|
||||
|
||||
NuVotifier introduces a more secure and failure tolerant protocol for sending votes. In this document, this new protocol
|
||||
will be referred to as v2. NuVotifier also supports the legacy Votifier protocol (using public key encryption). In this document,
|
||||
the legacy protocol will be referred to v1. As of writing, most if not all vote websites still use v1.
|
||||
|
||||
# Configuration
|
||||
|
||||
## Enabling the NuVotifier Server port
|
||||
|
||||
NuVotifier allows for the server port to be turned off when NuVotifier is only being used to forward votes. This is only
|
||||
an option on Bukkit based servers, since it would not make logical sense for the port to be turned off on Bungee servers.
|
||||
|
||||
If you are receiving votes from a server list directly on the server in question (votes are not being forwarded through plugin
|
||||
messaging), or if votes are being forwarded through the `proxy` method, then `enableExternal` should be set to true. Only
|
||||
if you are receiving forwarded votes from your bungee through the `pluginMessaging` forwarding method should `enableExternal`
|
||||
be set to false.
|
||||
|
||||
## Port Configuration
|
||||
|
||||
One of NuVotifier's functions is acting as a vote server (receiver). The IP for NuVotifier to bind to is configured
|
||||
by the `host` and `ip` configuration values within the config.yml files within the NuVotifier plugin folder.
|
||||
Most times, you will want this `host` value to be set to `0.0.0.0` or your server's ip. `0.0.0.0` will bind to all
|
||||
ip ports on your machine, while specifying a certain IP will only bind to that ip. If you don't know what to set this
|
||||
to, `0.0.0.0` is a safe bet. By default, NuVotifier listens on port 8192. This is the same as the default Votifier port.
|
||||
If you change this, you will have to remember this when setting up forwarding or server lists.
|
||||
|
||||
## Debug mode
|
||||
|
||||
When first setting up NuVotifier, you will probably want debug mode to be enabled. This prints extra information to the
|
||||
console, ensuring that votes are being processed as they should. If you are experiencing an error, or having issues with
|
||||
votes being processed, you should first look to enable this. Debug mode can be turned on by setting `debug` in the config.yml
|
||||
to true.
|
||||
|
||||
## Tokens vs Public Key
|
||||
|
||||
As explained earlier, NuVotifier supports 2 different protocols for receiving votes, v1 and v2. v1 uses the private and public
|
||||
key pair located in `rsa/` in NuVotifier's plugin folder. When setting up voting websites, the public key, `rsa/public.key`
|
||||
should be used. When setting up the `proxy` forwarding method, tokens should be used.
|
||||
|
||||
Making a new token is as easy as adding another entry into `tokens` with the serviceName as the key and whatever string you want
|
||||
as the value. You can usually find the serviceName of a vote provider on their setup page if they use v2.
|
||||
|
||||
Unless a website specifies it, you should assume that the website is using v1, and you should enter your `rsa/public.key`.
|
||||
|
||||
## Forwarding
|
||||
|
||||
NuVotifier allows for various methods for forwarding the vote from an upstream Bungeecord instance. Currently, 2 methods
|
||||
are supported: `proxy` and `pluginMessaging`. These are configured through the `forwarding` configuration section in your
|
||||
config.yml files.
|
||||
|
||||
### Plugin Messaging Based Forwarding
|
||||
|
||||
Plugin Messaging based forwarding works by sending a forwarded vote over Minecraft's plugin messaging system. NuVotifier
|
||||
is only capable of forwarding the vote if a player is on each backend server through the BungeeCord running NuVotifier.
|
||||
Plugin Messaging can be enabled by setting `method` in `forwarding` to `pluginMessaging`. You may also set the plugin messaging
|
||||
channel NuVotifier is to use by setting `channel` in the `pluginMessaging` section. This may be whatever you want, but it
|
||||
must be the same on your BungeeCord as well as your backend Bukkit servers. If they are not the same, the backend will not
|
||||
be able to receive the vote.
|
||||
|
||||
#### Filtering servers
|
||||
|
||||
If you only wish to send votes to some servers, but not all, there is a section within `pluginMessaging` called `excludedServers`.
|
||||
By default, this section is commented out, and will never filter out any servers. If you wish to filter out servers, uncomment
|
||||
this list and fill it with the names of servers you do not want to send votes to.
|
||||
|
||||
#### Plugin Messaging Vote Caching
|
||||
|
||||
In the event that the backend server does not have anyone on it from the BungeeCord instance, the vote will need to be cached
|
||||
until a player joins and the vote may be sent. There are 3 options for caching, however one is only suitable for actual
|
||||
production.
|
||||
|
||||
##### Memory Vote Caching
|
||||
|
||||
For votes to simply be cached in memory, you can set the `cache` setting of the `pluginMessaging` section of your BungeeCord's
|
||||
NuVotifier's config.yml to `memory`. This stores votes in memory. The votes are lost when the BungeeCord server shuts down.
|
||||
This is suitable for testing, but not for production system.
|
||||
|
||||
##### File Vote Caching
|
||||
|
||||
Votes can also be cached in a file located in the NuVotifier plugin folder. To enable this, you can set `cache` to file.
|
||||
If you want to change the file location from the default, you can set this under the `name` config under the `file` section.
|
||||
This caching method is suitable for most people.
|
||||
|
||||
##### No Caching
|
||||
|
||||
Caching can also be turned off. This is the default option. You can do this by setting `cache` to `none`. This is also not
|
||||
recommended, and is only suitable for a certain number of setups.
|
||||
|
||||
### Proxy Based Forwarding
|
||||
|
||||
You can also forward votes using the proxy method. This will cause the BungeeCord to act as a voting websites, sending votes
|
||||
to each server's NuVotifier port. To enable this method, you must set the `method` under `forwarding` to `proxy` in your
|
||||
BungeeCord config.yml. Within the server's config.yml, `forwarding` should be set to `none`.
|
||||
|
||||
For each server you wish to forward to, you must add each to your BungeeCord config.yml under the `proxy` section of `forwarding`.
|
||||
For each, you must specify the ip address (`address`), port (`port`) and token (`key`). Each backend Bukkit server should have
|
||||
1 default token under the `tokens` section. This is the token that should be used as the token for the `key` in that server's
|
||||
section within the `proxy` section. If the server is on the same server (same ip) as the BungeeCord server, you may
|
||||
use the `127.0.0.1` or `localhost` address, setting both the `proxy` forwarding address and Bukkit config.yml address to it.
|
||||
|
||||
In order for vote proxying to work, the backend server __must__ have `enableExternal` set to true. If it is set to
|
||||
false, the Bungee server will not be able to forward the vote.
|
||||
|
||||
|
||||
## Default Configurations
|
||||
|
||||
The default configuration for Bukkit can be found [here](https://github.com/NuVotifier/NuVotifier/blob/master/bukkit/src/main/resources/bukkitConfig.yml),
|
||||
for BungeeCord [here](https://github.com/NuVotifier/NuVotifier/blob/master/bungeecord/src/main/resources/bungeeConfig.yml),
|
||||
and for Sponge [here](https://github.com/NuVotifier/NuVotifier/blob/master/sponge/src/main/resources/com/vexsoftware/votifier/sponge/spongeConfig.yml).
|
||||
|
||||
|
||||
## Testing
|
||||
|
||||
There is a free resource for testing your NuVotifier setups at [ibj.io/v](https://ibj.io/v). If you are setting up your
|
||||
network or server and want a server to get test votes on, you are more than welcome to use it.
|
||||
|
||||
|
||||
## Basic Setups
|
||||
|
||||
### Single Server Quickstart
|
||||
|
||||
A single server setup should have `enableExternal` set to `true`, and `forwarding.method` set to `none`. The IP and port
|
||||
should also be set to appropriate values. For server lists, you should use the public key found in `rsa/public.key`.
|
||||
|
||||
## Advanced Setups
|
||||
|
||||
### Multi BungeeCord Setups
|
||||
|
||||
Using NuVotifier with multiple BungeeCords is a more tricky setup. You can use one of your player-facing proxies for the
|
||||
NuVotifier server, or use a separate one. You are not guaranteed to have players on all of your servers to allow votes to
|
||||
be dispatched, so proxy forwarding (requiring `enableExternal` set to `true` on your Bukkit servers) is highly recommended.
|
||||
|
||||
## Contributing
|
||||
|
||||
If you wish to contribute code wise, feel free to submit a pull request!
|
||||
|
||||
## Client Libraries
|
||||
|
||||
NuVotifier has libraries available for [NodeJS](https://github.com/NuVotifier/votifier2-js) and [PHP](https://github.com/NuVotifier/votifier2-php).
|
||||
Both are free to use by serverlists.
|
||||
@@ -0,0 +1,5 @@
|
||||
dependencies {
|
||||
compileOnly group: "com.velocitypowered", name: "velocity-api", version: "1.0-SNAPSHOT"
|
||||
annotationProcessor group: "com.velocitypowered", name: "velocity-api", version: "1.0-SNAPSHOT"
|
||||
compile project(":nuvotifier-common")
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package com.vexsoftware.votifier.velocity;
|
||||
|
||||
import com.velocitypowered.api.event.Subscribe;
|
||||
import com.velocitypowered.api.event.connection.PluginMessageEvent;
|
||||
import com.velocitypowered.api.event.player.ServerConnectedEvent;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.ServerConnection;
|
||||
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.platform.BackendServer;
|
||||
import com.vexsoftware.votifier.support.forwarding.AbstractPluginMessagingForwardingSource;
|
||||
import com.vexsoftware.votifier.support.forwarding.ServerFilter;
|
||||
import com.vexsoftware.votifier.support.forwarding.cache.VoteCache;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public final class OnlineForwardPluginMessagingForwardingSource extends AbstractPluginMessagingForwardingSource {
|
||||
|
||||
private final String fallbackServer;
|
||||
private final VotifierPlugin plugin;
|
||||
private final ChannelIdentifier velocityChannelId;
|
||||
|
||||
public OnlineForwardPluginMessagingForwardingSource(String channel, ServerFilter serverFilter, VotifierPlugin plugin, VoteCache cache, String fallbackServer, int dumpRate) {
|
||||
super(channel, serverFilter, plugin, cache, dumpRate);
|
||||
this.fallbackServer = fallbackServer;
|
||||
this.plugin = plugin;
|
||||
this.velocityChannelId = VelocityUtil.getId(channel);
|
||||
plugin.getServer().getChannelRegistrar().register(velocityChannelId);
|
||||
plugin.getServer().getEventManager().register(plugin, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forward(Vote v) {
|
||||
Optional<Player> p = plugin.getServer().getPlayer(v.getUsername());
|
||||
Optional<ServerConnection> sc = p.flatMap(Player::getCurrentServer);
|
||||
if (sc.isPresent() &&
|
||||
serverFilter.isAllowed(sc.get().getServerInfo().getName())
|
||||
) {
|
||||
if (forwardSpecific(new VelocityBackendServer(plugin.getServer(), sc.get().getServer()), v)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Optional<RegisteredServer> fs = plugin.getServer().getServer(fallbackServer);
|
||||
// nowhere to fall back to, yet still not online. lets save this vote yet!
|
||||
if (!fs.isPresent())
|
||||
attemptToAddToPlayerCache(v, v.getUsername());
|
||||
else if (!forwardSpecific(new VelocityBackendServer(plugin.getServer(), fs.get()), v))
|
||||
attemptToAddToCache(v, fallbackServer);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onServerConnected(final ServerConnectedEvent e) { //Attempt to resend any votes that were previously cached.
|
||||
BackendServer server = new VelocityBackendServer(plugin.getServer(), e.getServer());
|
||||
onServerConnect(server);
|
||||
handlePlayerSwitch(server, e.getPlayer().getUsername());
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onPluginMessage(PluginMessageEvent e) {
|
||||
if (e.getIdentifier().equals(velocityChannelId)) {
|
||||
e.setResult(PluginMessageEvent.ForwardResult.handled());
|
||||
}
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package com.vexsoftware.votifier.velocity;
|
||||
|
||||
import com.velocitypowered.api.event.Subscribe;
|
||||
import com.velocitypowered.api.event.connection.PluginMessageEvent;
|
||||
import com.velocitypowered.api.event.player.ServerConnectedEvent;
|
||||
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
||||
import com.vexsoftware.votifier.support.forwarding.AbstractPluginMessagingForwardingSource;
|
||||
import com.vexsoftware.votifier.support.forwarding.ServerFilter;
|
||||
import com.vexsoftware.votifier.support.forwarding.cache.VoteCache;
|
||||
|
||||
public class PluginMessagingForwardingSource extends AbstractPluginMessagingForwardingSource {
|
||||
|
||||
private final VotifierPlugin plugin;
|
||||
private final ChannelIdentifier velocityChannelId;
|
||||
|
||||
public PluginMessagingForwardingSource(String channel, ServerFilter serverFilter, VotifierPlugin plugin, VoteCache cache, int dumpRate) {
|
||||
super(channel, serverFilter, plugin, cache, dumpRate);
|
||||
this.plugin = plugin;
|
||||
this.velocityChannelId = VelocityUtil.getId(channel);
|
||||
plugin.getServer().getChannelRegistrar().register(velocityChannelId);
|
||||
plugin.getServer().getEventManager().register(plugin, this);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onServerConnected(final ServerConnectedEvent e) { //Attempt to resend any votes that were previously cached.
|
||||
onServerConnect(new VelocityBackendServer(plugin.getServer(), e.getServer()));
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onPluginMessage(PluginMessageEvent e) {
|
||||
if (e.getIdentifier().equals(velocityChannelId)) {
|
||||
e.setResult(PluginMessageEvent.ForwardResult.handled());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.vexsoftware.votifier.velocity;
|
||||
|
||||
import com.vexsoftware.votifier.platform.LoggingAdapter;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
public class SLF4JLogger implements LoggingAdapter {
|
||||
|
||||
private final Logger l;
|
||||
public SLF4JLogger(Logger l) {
|
||||
this.l = l;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String s) {
|
||||
l.error(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String s, Object... o) {
|
||||
l.error(s, o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String s) {
|
||||
l.warn(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String s, Object... o) {
|
||||
l.warn(s, o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String s) {
|
||||
l.info(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String s, Object... o) {
|
||||
l.info(s, o);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.vexsoftware.votifier.velocity;
|
||||
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.vexsoftware.votifier.platform.BackendServer;
|
||||
|
||||
class VelocityBackendServer implements BackendServer {
|
||||
private final ProxyServer server;
|
||||
private final RegisteredServer rs;
|
||||
|
||||
VelocityBackendServer(ProxyServer server, RegisteredServer rs) {
|
||||
this.server = server;
|
||||
this.rs = rs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return rs.getServerInfo().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendPluginMessage(String channel, byte[] data) {
|
||||
return rs.sendPluginMessage(VelocityUtil.getId(channel), data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return rs.getServerInfo().getName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.vexsoftware.votifier.velocity;
|
||||
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import com.velocitypowered.api.scheduler.ScheduledTask;
|
||||
import com.velocitypowered.api.scheduler.Scheduler;
|
||||
import com.vexsoftware.votifier.platform.scheduler.ScheduledVotifierTask;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
class VelocityScheduler implements VotifierScheduler {
|
||||
private final ProxyServer server;
|
||||
private final VotifierPlugin plugin;
|
||||
|
||||
public VelocityScheduler(ProxyServer server, VotifierPlugin plugin) {
|
||||
this.server = server;
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
private Scheduler.TaskBuilder builder(Runnable runnable) {
|
||||
return server.getScheduler().buildTask(plugin, runnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask sync(Runnable runnable) {
|
||||
return onPool(runnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask onPool(Runnable runnable) {
|
||||
return new TaskWrapper(builder(runnable).schedule());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask delayedSync(Runnable runnable, int delay, TimeUnit unit) {
|
||||
return delayedOnPool(runnable, delay, unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask delayedOnPool(Runnable runnable, int delay, TimeUnit unit) {
|
||||
return new TaskWrapper(builder(runnable).delay(delay, unit).schedule());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledVotifierTask repeatOnPool(Runnable runnable, int delay, int repeat, TimeUnit unit) {
|
||||
return new TaskWrapper(builder(runnable).delay(delay, unit).repeat(repeat, unit).schedule());
|
||||
}
|
||||
|
||||
private static class TaskWrapper implements ScheduledVotifierTask {
|
||||
private final ScheduledTask task;
|
||||
|
||||
private TaskWrapper(ScheduledTask task) {
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
task.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.vexsoftware.votifier.velocity;
|
||||
|
||||
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
|
||||
|
||||
public class VelocityUtil {
|
||||
private VelocityUtil() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
public static ChannelIdentifier getId(String channel) {
|
||||
if (channel.contains(":")) {
|
||||
String[] split = channel.split(":");
|
||||
return MinecraftChannelIdentifier.create(split[0], split[1]);
|
||||
}
|
||||
return new LegacyChannelIdentifier(channel);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
package com.vexsoftware.votifier.velocity;
|
||||
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.inject.Inject;
|
||||
import com.moandjiezana.toml.Toml;
|
||||
import com.velocitypowered.api.event.Subscribe;
|
||||
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
|
||||
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
|
||||
import com.velocitypowered.api.plugin.annotation.DataDirectory;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import com.vexsoftware.votifier.VoteHandler;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.VotifierServerBootstrap;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import com.vexsoftware.votifier.net.protocol.v1crypto.RSAIO;
|
||||
import com.vexsoftware.votifier.net.protocol.v1crypto.RSAKeygen;
|
||||
import com.vexsoftware.votifier.platform.BackendServer;
|
||||
import com.vexsoftware.votifier.platform.LoggingAdapter;
|
||||
import com.vexsoftware.votifier.platform.ProxyVotifierPlugin;
|
||||
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
|
||||
import com.vexsoftware.votifier.support.forwarding.ForwardingVoteSource;
|
||||
import com.vexsoftware.votifier.support.forwarding.ServerFilter;
|
||||
import com.vexsoftware.votifier.support.forwarding.cache.FileVoteCache;
|
||||
import com.vexsoftware.votifier.support.forwarding.cache.MemoryVoteCache;
|
||||
import com.vexsoftware.votifier.support.forwarding.cache.VoteCache;
|
||||
import com.vexsoftware.votifier.support.forwarding.proxy.ProxyForwardingVoteSource;
|
||||
import com.vexsoftware.votifier.util.KeyCreator;
|
||||
import com.vexsoftware.votifier.util.TokenUtil;
|
||||
import com.vexsoftware.votifier.velocity.event.VotifierEvent;
|
||||
import io.netty.channel.Channel;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class VotifierPlugin implements VoteHandler, ProxyVotifierPlugin {
|
||||
|
||||
@Inject
|
||||
public Logger logger;
|
||||
private LoggingAdapter loggingAdapter;
|
||||
|
||||
@Inject
|
||||
@DataDirectory
|
||||
public Path configDir;
|
||||
|
||||
@Inject
|
||||
public ProxyServer server;
|
||||
|
||||
private VotifierScheduler scheduler;
|
||||
|
||||
@Subscribe
|
||||
public void onServerStart(ProxyInitializeEvent event) {
|
||||
this.scheduler = new VelocityScheduler(server, this);
|
||||
this.loggingAdapter = new SLF4JLogger(logger);
|
||||
|
||||
// Load configuration.
|
||||
Toml config;
|
||||
try {
|
||||
config = loadConfig();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Unable to load configuration.", e);
|
||||
}
|
||||
|
||||
/*
|
||||
* Create RSA directory and keys if it does not exist; otherwise, read
|
||||
* keys.
|
||||
*/
|
||||
File rsaDirectory = new File(configDir.toFile(), "rsa");
|
||||
try {
|
||||
if (!rsaDirectory.exists()) {
|
||||
rsaDirectory.mkdir();
|
||||
keyPair = RSAKeygen.generate(2048);
|
||||
RSAIO.save(rsaDirectory, keyPair);
|
||||
} else {
|
||||
keyPair = RSAIO.load(rsaDirectory);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
logger.error("Error creating or reading RSA tokens", ex);
|
||||
gracefulExit();
|
||||
return;
|
||||
}
|
||||
|
||||
if (config.contains("quiet"))
|
||||
debug = !config.getBoolean("quiet");
|
||||
else
|
||||
debug = config.getBoolean("debug", true);
|
||||
|
||||
// Load Votifier tokens.
|
||||
config.getTable("tokens").toMap().forEach((service, key) -> {
|
||||
if (key instanceof String) {
|
||||
tokens.put(service, KeyCreator.createKeyFrom((String) key));
|
||||
logger.info("Loaded token for website: " + service);
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize the receiver.
|
||||
final String host = config.getString("host");
|
||||
final int port = Math.toIntExact(config.getLong("port"));
|
||||
if (!debug)
|
||||
logger.info("QUIET mode enabled!");
|
||||
|
||||
final boolean disablev1 = config.getBoolean("disable-v1-protocol", false);
|
||||
if (disablev1) {
|
||||
logger.info("------------------------------------------------------------------------------");
|
||||
logger.info("Votifier protocol v1 parsing has been disabled. Most voting websites do not");
|
||||
logger.info("currently support the modern Votifier protocol in NuVotifier.");
|
||||
logger.info("------------------------------------------------------------------------------");
|
||||
}
|
||||
|
||||
this.bootstrap = new VotifierServerBootstrap(host, port, this, disablev1);
|
||||
this.bootstrap.start(err -> {});
|
||||
|
||||
Toml fwdCfg = config.getTable("forwarding");
|
||||
String fwdMethod = fwdCfg.getString("method", "none").toLowerCase();
|
||||
if ("none".equals(fwdMethod)) {
|
||||
getLogger().info("Method none selected for vote forwarding: Votes will not be forwarded to backend servers.");
|
||||
} else if ("pluginmessaging".equals(fwdMethod)) {
|
||||
Toml pmCfg = fwdCfg.getTable("pluginMessaging");
|
||||
String channel = pmCfg.getString("channel", "NuVotifier");
|
||||
String cacheMethod = pmCfg.getString("cache", "file").toLowerCase();
|
||||
VoteCache voteCache = null;
|
||||
if ("none".equals(cacheMethod)) {
|
||||
getLogger().info("Vote cache none selected for caching: votes that cannot be immediately delivered will be lost.");
|
||||
} else if ("memory".equals(cacheMethod)) {
|
||||
voteCache = new MemoryVoteCache(
|
||||
server.getAllServers().size(), this,
|
||||
fwdCfg.getTable("memory-cache").getLong("cacheTime", -1L));
|
||||
getLogger().info("Using in-memory cache for votes that are not able to be delivered.");
|
||||
} else if ("file".equals(cacheMethod)) {
|
||||
try {
|
||||
voteCache = new FileVoteCache(
|
||||
server.getAllServers().size(), this,
|
||||
configDir.resolve(fwdCfg.getTable("file-cache").getString("name")).toFile(),
|
||||
fwdCfg.getTable("file-cache").getLong("cacheTime", -1L));
|
||||
} catch (IOException e) {
|
||||
getLogger().error("Unload to load file cache. Votes will be lost!", e);
|
||||
}
|
||||
}
|
||||
int dumpRate = pmCfg.getLong("dumpRate", 5L).intValue();
|
||||
|
||||
ServerFilter filter = new ServerFilter(
|
||||
pmCfg.getList("excludedServers"),
|
||||
pmCfg.getBoolean("whitelist", false)
|
||||
);
|
||||
|
||||
if (!pmCfg.getBoolean("onlySendToJoinedServer")) {
|
||||
try {
|
||||
forwardingMethod = new PluginMessagingForwardingSource(channel, filter, this, voteCache, dumpRate);
|
||||
getLogger().info("Forwarding votes over PluginMessaging channel '" + channel + "' for vote forwarding!");
|
||||
} catch (RuntimeException e) {
|
||||
getLogger().error("NuVotifier could not set up PluginMessaging for vote forwarding!", e);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
String fallbackServer = pmCfg.getString("joinedServerFallback", null);
|
||||
if (fallbackServer != null && fallbackServer.isEmpty()) fallbackServer = null;
|
||||
forwardingMethod = new OnlineForwardPluginMessagingForwardingSource(channel, filter, this, voteCache, fallbackServer, dumpRate);
|
||||
} catch (RuntimeException e) {
|
||||
getLogger().error("NuVotifier could not set up PluginMessaging for vote forwarding!", e);
|
||||
}
|
||||
}
|
||||
} else if ("proxy".equals(fwdMethod)) {
|
||||
Toml serverSection = fwdCfg.getTable("proxy");
|
||||
List<ProxyForwardingVoteSource.BackendServer> serverList = new ArrayList<>();
|
||||
for (String s : serverSection.toMap().keySet()) {
|
||||
Toml section = serverSection.getTable(s);
|
||||
InetAddress address;
|
||||
try {
|
||||
address = InetAddress.getByName(section.getString("address"));
|
||||
} catch (UnknownHostException e) {
|
||||
getLogger().info("Address " + section.getString("address") + " couldn't be looked up. Ignoring!");
|
||||
continue;
|
||||
}
|
||||
|
||||
Key token = null;
|
||||
try {
|
||||
token = KeyCreator.createKeyFrom(section.getString("token", section.getString("key")));
|
||||
} catch (IllegalArgumentException e) {
|
||||
getLogger().error(
|
||||
"An exception occurred while attempting to add proxy target '" + s + "' - maybe your token is wrong? " +
|
||||
"Votes will not be forwarded to this server!", e);
|
||||
}
|
||||
|
||||
if (token != null) {
|
||||
ProxyForwardingVoteSource.BackendServer server = new ProxyForwardingVoteSource.BackendServer(s,
|
||||
new InetSocketAddress(address, Math.toIntExact(section.getLong("port"))),
|
||||
token);
|
||||
serverList.add(server);
|
||||
}
|
||||
}
|
||||
|
||||
forwardingMethod = new ProxyForwardingVoteSource(this, bootstrap::client, serverList, null);
|
||||
getLogger().info("Forwarding votes from this NuVotifier instance to another NuVotifier server.");
|
||||
} else {
|
||||
getLogger().error("No vote forwarding method '" + fwdMethod + "' known. Defaulting to noop implementation.");
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onServerStop(ProxyShutdownEvent event) {
|
||||
if (bootstrap != null) {
|
||||
bootstrap.shutdown();
|
||||
bootstrap = null;
|
||||
}
|
||||
|
||||
if (forwardingMethod != null) {
|
||||
forwardingMethod.halt();
|
||||
forwardingMethod = null;
|
||||
}
|
||||
|
||||
logger.info("Votifier disabled.");
|
||||
}
|
||||
|
||||
public ProxyServer getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
private Toml loadConfig() throws IOException {
|
||||
if (!Files.exists(configDir)) {
|
||||
Files.createDirectory(configDir);
|
||||
}
|
||||
Path configPath = configDir.resolve("config.toml");
|
||||
try (Reader reader = Files.newBufferedReader(configPath, StandardCharsets.UTF_8)) {
|
||||
return new Toml().read(reader);
|
||||
} catch (NoSuchFileException e) {
|
||||
// This is ok. Just copy the default and load that.
|
||||
// First time run - do some initialization.
|
||||
getLogger().info("Configuring Votifier for the first time...");
|
||||
|
||||
// Initialize the configuration file.
|
||||
String cfgStr = new String(ByteStreams.toByteArray(VotifierPlugin.class.getResourceAsStream("/config.toml")), StandardCharsets.UTF_8);
|
||||
String token = TokenUtil.newToken();
|
||||
cfgStr = cfgStr.replace("%ip%", server.getBoundAddress().getAddress().getHostAddress());
|
||||
cfgStr = cfgStr.replace("%default_token%", token);
|
||||
|
||||
/*
|
||||
* Remind hosted server admins to be sure they have the right
|
||||
* port number.
|
||||
*/
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
getLogger().info("Assigning NuVotifier to listen on port 8192. If you are hosting BungeeCord on a");
|
||||
getLogger().info("shared server please check with your hosting provider to verify that this port");
|
||||
getLogger().info("is available for your use. Chances are that your hosting provider will assign");
|
||||
getLogger().info("a different port, which you need to specify in config.yml");
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
getLogger().info("Assigning NuVotifier to listen to interface 0.0.0.0. This is usually alright,");
|
||||
getLogger().info("however, if you want NuVotifier to only listen to one interface for security ");
|
||||
getLogger().info("reasons (or you use a shared host), you may change this in the config.yml.");
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
getLogger().info("Your default Votifier token is " + token + ".");
|
||||
getLogger().info("You will need to provide this token when you submit your server to a voting");
|
||||
getLogger().info("list.");
|
||||
getLogger().info("------------------------------------------------------------------------------");
|
||||
|
||||
Files.copy(new ByteArrayInputStream(cfgStr.getBytes(StandardCharsets.UTF_8)), configPath);
|
||||
return new Toml().read(cfgStr);
|
||||
}
|
||||
}
|
||||
|
||||
public Logger getLogger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* The current Votifier version.
|
||||
*/
|
||||
private String version;
|
||||
|
||||
/**
|
||||
* The server bootstrap.
|
||||
*/
|
||||
private VotifierServerBootstrap bootstrap;
|
||||
|
||||
/**
|
||||
* The RSA key pair.
|
||||
*/
|
||||
private KeyPair keyPair;
|
||||
|
||||
/**
|
||||
* Debug mode flag
|
||||
*/
|
||||
private boolean debug;
|
||||
|
||||
/**
|
||||
* Keys used for websites.
|
||||
*/
|
||||
private Map<String, Key> tokens = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Method used to forward votes to downstream servers
|
||||
*/
|
||||
private ForwardingVoteSource forwardingMethod;
|
||||
|
||||
private void gracefulExit() {
|
||||
logger.error("Votifier did not initialize properly!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoggingAdapter getPluginLogger() {
|
||||
return loggingAdapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VotifierScheduler getScheduler() {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
public boolean isDebug() {
|
||||
return debug;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Key> getTokens() {
|
||||
return tokens;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyPair getProtocolV1Key() {
|
||||
return keyPair;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVoteReceived(Channel channel, final Vote vote, VotifierSession.ProtocolVersion protocolVersion) {
|
||||
if (debug) {
|
||||
if (protocolVersion == VotifierSession.ProtocolVersion.ONE) {
|
||||
logger.info("Got a protocol v1 vote record from " + channel.remoteAddress() + " -> " + vote);
|
||||
} else {
|
||||
logger.info("Got a protocol v2 vote record from " + channel.remoteAddress() + " -> " + vote);
|
||||
}
|
||||
}
|
||||
|
||||
server.getEventManager().fireAndForget(new VotifierEvent(vote));
|
||||
if (forwardingMethod != null) {
|
||||
forwardingMethod.forward(vote);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Channel channel, boolean alreadyHandledVote, Throwable throwable) {
|
||||
if (debug) {
|
||||
if (alreadyHandledVote) {
|
||||
logger.error("Vote processed, however an exception " +
|
||||
"occurred with a vote from " + channel.remoteAddress(), throwable);
|
||||
} else {
|
||||
logger.error("Unable to process vote from " + channel.remoteAddress(), throwable);
|
||||
}
|
||||
} else if (!alreadyHandledVote) {
|
||||
logger.error("Unable to process vote from " + channel.remoteAddress());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<BackendServer> getAllBackendServers() {
|
||||
return server.getAllServers().stream().map(s -> new VelocityBackendServer(server, s)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<BackendServer> getServer(String name) {
|
||||
return server.getServer(name).map(s -> new VelocityBackendServer(server, s));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.vexsoftware.votifier.velocity.event;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
|
||||
public class VotifierEvent {
|
||||
private final Vote vote;
|
||||
|
||||
public VotifierEvent(Vote vote) {
|
||||
this.vote = vote;
|
||||
}
|
||||
|
||||
public Vote getVote() {
|
||||
return vote;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VotifierEvent{" +
|
||||
"vote=" + vote +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
# The IP to listen to. Use 0.0.0.0 if you wish to listen to all interfaces on your server. (All IP addresses)
|
||||
# This defaults to the IP you have configured your server to listen on, or 0.0.0.0 if you have not configured this.
|
||||
host = "%ip%"
|
||||
|
||||
# Port to listen for new votes on
|
||||
port = 8192
|
||||
|
||||
# Setting this option to true will disable handling of Protocol v1 packets. While the old protocol is not secure, this
|
||||
# option is currently not recommended as most voting sites only support the old protocol at present. However, if you are
|
||||
# using NuVotifier's proxy forwarding mechanism, enabling this option will increase your server's security.
|
||||
disable-v1-protocol = false
|
||||
|
||||
# All tokens, labeled by the serviceName of each server list.
|
||||
[tokens]
|
||||
# Default token for all server lists, if another isn't supplied.
|
||||
default = "%default_token%"
|
||||
|
||||
# Configuration section for all vote forwarding to NuVotifier
|
||||
[forwarding]
|
||||
# Sets whether to set up a remote method for fowarding. Supported methods:
|
||||
# - none - Does not set up a forwarding method.
|
||||
# - pluginMessaging - Sets up plugin messaging.
|
||||
# - proxy - Proxies votes to other NuVotifier servers from this server.
|
||||
method = "none"
|
||||
|
||||
[forwarding.pluginMessaging]
|
||||
channel = "nuvotifier:votes"
|
||||
|
||||
# Only send votes to certain servers. If a server is present on excludedServers, then votes will NOT be forwarded
|
||||
# to them. If excludedServers is not present, votes will be sent to all servers.
|
||||
# If onlySendToJoinedServer is true and a player is on one of these servers, NuVotifier will process the vote as if the
|
||||
# player was not on the network at all.
|
||||
# excludedServers = ["server1", "server2"]
|
||||
|
||||
# If uncommented, turns excludedServers into an includedServers, where only servers on the list will be used.
|
||||
# whitelist = true
|
||||
|
||||
# Sets which cache to use if there are no players on the server that the vote is being sent to. Supported caches:
|
||||
# - none - Votes will simply be lost when no players are present on the server.
|
||||
# - memory - Votes will be cached in memory when no players are present. When Bungee is restarted, the votes will be lost.
|
||||
# - file - Votes will be saved to a local file every 3 minutes and on shutdown and reloaded when Bungee is restarted. This is suitable for
|
||||
# most set ups.
|
||||
cache = "file"
|
||||
# Only sends the vote to the server in which the player is currently joined.
|
||||
onlySendToJoinedServer = false
|
||||
|
||||
# Sets which server to send the vote in the event the player is not online when the vote is cast
|
||||
# If you do not want the vote forwarded to a fallback, set this value to empty ('')
|
||||
# ONLY USED IF onlySendToJoinedServer is true!!
|
||||
joinedServerFallback = "Hub"
|
||||
|
||||
# Defines how quickly to dump votes over a player's connection while offloading a cache in votes per second
|
||||
dumpRate = 5
|
||||
|
||||
[forwarding.file-cache]
|
||||
# Options for file caching.
|
||||
name = "cached-votes.json"
|
||||
# days before a vote is considered 'dead' - removed from cache with a console warning. -1 signifies no cache.
|
||||
cacheTime = 10
|
||||
|
||||
[forwarding.memory-cache]
|
||||
# Options for file caching.
|
||||
name = "cached-votes.json"
|
||||
# days before a vote is considered 'dead' - removed from cache with a console warning. -1 signifies no cache. Votes are
|
||||
# also removed on restart
|
||||
cacheTime = 10
|
||||
|
||||
[forwarding.proxy]
|
||||
# Specify servers to proxy votes for.
|
||||
|
||||
[forwarding.proxy.Hub]
|
||||
address = "127.0.0.1"
|
||||
port = 8193
|
||||
token = "MyKeyHere"
|
||||
|
||||
[forwarding.proxy.Hub2]
|
||||
address = "127.0.0.1"
|
||||
port = 8194
|
||||
token = "MyKeyHere"
|
||||
Loaded 100 of 101 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user