Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7cda39ad12 | ||
|
|
b377a53de4 | ||
|
|
9a306365b8 | ||
|
|
f74cdbe648 | ||
|
|
6a51c1c106 | ||
|
|
36195be292 | ||
|
|
893fe93ab6 | ||
|
|
90e844b082 | ||
|
|
886b60b2de | ||
|
|
30d8e9884c | ||
|
|
7e05b0752b | ||
|
|
e124c371b3 | ||
|
|
4af4ae1006 | ||
|
|
b63c4a7c68 | ||
|
|
3cf4741cbb | ||
|
|
bb6c311e2f |
No files matched your search
+1
-3
@@ -1,13 +1,11 @@
|
||||
sudo: false
|
||||
language: java
|
||||
jdk:
|
||||
- openjdk7
|
||||
- oraclejdk7
|
||||
- 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.jar
|
||||
file: universal/target/nuvotifier-universal-2.3.2.2.jar
|
||||
on:
|
||||
tags: true
|
||||
+3
-1
@@ -2,6 +2,8 @@
|
||||
|
||||
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.
|
||||
@@ -27,7 +29,7 @@ NuVotifier configures itself the first time it is run. If you have an existing V
|
||||
|
||||
If you want to customize NuVotifier, simply the edit `./plugins/(Nu)Votifier/config.yml` file.
|
||||
|
||||
An in-depth configuration guide is located in `usage.md` of this repository.
|
||||
An in-depth configuration guide is located in the [usage](https://github.com/NuVotifier/NuVotifier/blob/master/usage.md) document.
|
||||
|
||||
### Tokens
|
||||
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>nuvotifier</artifactId>
|
||||
<groupId>com.vexsoftware</groupId>
|
||||
<version>2.3.2</version>
|
||||
<version>2.3.2.2</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -258,7 +258,7 @@ public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, Votifie
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
if (future.isSuccess()) {
|
||||
serverChannel = future.channel();
|
||||
getLogger().info("Votifier enabled.");
|
||||
getLogger().info("Votifier enabled on socket "+serverChannel.localAddress()+".");
|
||||
} else {
|
||||
SocketAddress socketAddress = future.channel().localAddress();
|
||||
if (socketAddress == null) {
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>nuvotifier</artifactId>
|
||||
<groupId>com.vexsoftware</groupId>
|
||||
<version>2.3.2</version>
|
||||
<version>2.3.2.2</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -221,7 +221,7 @@ public class NuVotifier extends Plugin implements VoteHandler, VotifierPlugin {
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
if (future.isSuccess()) {
|
||||
serverChannel = future.channel();
|
||||
getLogger().info("Votifier enabled.");
|
||||
getLogger().info("Votifier enabled on socket "+serverChannel.localAddress()+".");
|
||||
} else {
|
||||
SocketAddress socketAddress = future.channel().localAddress();
|
||||
if (socketAddress == null) {
|
||||
@@ -293,7 +293,8 @@ public class NuVotifier extends Plugin implements VoteHandler, VotifierPlugin {
|
||||
continue;
|
||||
}
|
||||
ProxyForwardingVoteSource.BackendServer server = new ProxyForwardingVoteSource.BackendServer(s,
|
||||
new InetSocketAddress(address, section.getShort("port")), KeyCreator.createKeyFrom(section.getString("key")));
|
||||
new InetSocketAddress(address, section.getShort("port")),
|
||||
KeyCreator.createKeyFrom(section.getString("token",section.getString("key"))));
|
||||
serverList.add(server);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -76,7 +76,7 @@ public class ProxyForwardingVoteSource implements ForwardingVoteSource {
|
||||
public void onFailure(Throwable error) {
|
||||
handleFailure(server, v, error, tries);
|
||||
}
|
||||
}));
|
||||
}, plugin));
|
||||
}
|
||||
})
|
||||
.connect(server.address)
|
||||
|
||||
+7
-2
@@ -1,5 +1,6 @@
|
||||
package com.vexsoftware.votifier.bungee.forwarding.proxy.client;
|
||||
|
||||
import com.vexsoftware.votifier.bungee.NuVotifier;
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
@@ -8,10 +9,12 @@ import io.netty.handler.codec.CorruptedFrameException;
|
||||
public class VotifierProtocol2HandshakeHandler extends SimpleChannelInboundHandler<String> {
|
||||
private final Vote toSend;
|
||||
private final VotifierResponseHandler responseHandler;
|
||||
private final NuVotifier nuVotifier;
|
||||
|
||||
public VotifierProtocol2HandshakeHandler(Vote toSend, VotifierResponseHandler responseHandler) {
|
||||
public VotifierProtocol2HandshakeHandler(Vote toSend, VotifierResponseHandler responseHandler, NuVotifier nuVotifier) {
|
||||
this.toSend = toSend;
|
||||
this.responseHandler = responseHandler;
|
||||
this.nuVotifier = nuVotifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -22,7 +25,9 @@ public class VotifierProtocol2HandshakeHandler extends SimpleChannelInboundHandl
|
||||
}
|
||||
|
||||
VoteRequest request = new VoteRequest(handshakeContents[2], toSend);
|
||||
System.out.println(request);
|
||||
if (nuVotifier.isDebug()) {
|
||||
nuVotifier.getLogger().info("Sent request: " + request.toString());
|
||||
}
|
||||
ctx.writeAndFlush(request);
|
||||
ctx.pipeline().addLast(new VotifierProtocol2ResponseHandler(responseHandler));
|
||||
ctx.pipeline().remove(this);
|
||||
|
||||
@@ -58,8 +58,8 @@ forwarding:
|
||||
Hub:
|
||||
address: 127.0.0.1
|
||||
port: 8193
|
||||
key: MyKeyHere
|
||||
token: MyKeyHere
|
||||
Hub2:
|
||||
address: 127.0.0.1
|
||||
port: 8194
|
||||
key: AnotherKeyHere
|
||||
token: AnotherKeyHere
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>nuvotifier</artifactId>
|
||||
<groupId>com.vexsoftware</groupId>
|
||||
<version>2.3.2</version>
|
||||
<version>2.3.2.2</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -52,9 +52,6 @@ public class Vote {
|
||||
}
|
||||
|
||||
public Vote(String serviceName, String username, String address, String timeStamp) {
|
||||
if (username.length() > 16) {
|
||||
throw new IllegalArgumentException("Username is too long.");
|
||||
}
|
||||
this.serviceName = serviceName;
|
||||
this.username = username;
|
||||
this.address = address;
|
||||
@@ -105,9 +102,6 @@ public class Vote {
|
||||
*/
|
||||
@Deprecated
|
||||
public void setUsername(String username) {
|
||||
if (username.length() > 16) {
|
||||
throw new IllegalArgumentException("Username is too long.");
|
||||
}
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
|
||||
+6
-2
@@ -17,10 +17,14 @@ import java.util.List;
|
||||
public class VotifierProtocol1Decoder extends ByteToMessageDecoder {
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> list) throws Exception {
|
||||
byte[] block = new byte[256];
|
||||
if (buf.readableBytes() < 256) {
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] block = new byte[buf.readableBytes()];
|
||||
buf.getBytes(0, block);
|
||||
// "Drain" the whole buffer
|
||||
buf.readerIndex(buf.capacity());
|
||||
buf.readerIndex(buf.readableBytes());
|
||||
|
||||
VotifierPlugin plugin = ctx.channel().attr(VotifierPlugin.KEY).get();
|
||||
|
||||
|
||||
+10
-3
@@ -63,6 +63,10 @@ public class VotifierProtocol2Decoder extends MessageToMessageDecoder<String> {
|
||||
UUID.fromString(votePayload.getString("uuid"));
|
||||
}
|
||||
|
||||
if (votePayload.getString("username").length() > 16) {
|
||||
throw new CorruptedFrameException("Username too long");
|
||||
}
|
||||
|
||||
// Create the vote.
|
||||
Vote vote = new Vote(votePayload);
|
||||
list.add(vote);
|
||||
@@ -75,15 +79,18 @@ public class VotifierProtocol2Decoder extends MessageToMessageDecoder<String> {
|
||||
// This randomizes the byte order to make timing attacks more difficult.
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(key);
|
||||
byte[] messageCalc = mac.doFinal(message);
|
||||
byte[] calculatedSig = mac.doFinal(message);
|
||||
|
||||
Mac mac2 = Mac.getInstance("HmacSHA256");
|
||||
// Generate a random key for use in comparison
|
||||
byte[] randomKey = new byte[32];
|
||||
RANDOM.nextBytes(randomKey);
|
||||
|
||||
// Then generate two HMACs for the different signatures found
|
||||
Mac mac2 = Mac.getInstance("HmacSHA256");
|
||||
mac2.init(new SecretKeySpec(randomKey, "HmacSHA256"));
|
||||
byte[] clientSig = mac2.doFinal(sig);
|
||||
mac2.reset();
|
||||
byte[] realSig = mac2.doFinal(messageCalc);
|
||||
byte[] realSig = mac2.doFinal(calculatedSig);
|
||||
|
||||
return Arrays.equals(clientSig, realSig);
|
||||
}
|
||||
|
||||
+4
-4
@@ -33,8 +33,7 @@ public class VotifierProtocolDifferentiator extends ByteToMessageDecoder {
|
||||
|
||||
if (readable < 2) {
|
||||
// Some retarded voting sites (PMC?) seem to send empty buffers for no good reason.
|
||||
// TODO: How can we handle this?
|
||||
throw new CorruptedFrameException("Frame is too short");
|
||||
return;
|
||||
}
|
||||
|
||||
short readMagic = buf.readShort();
|
||||
@@ -46,6 +45,8 @@ public class VotifierProtocolDifferentiator extends ByteToMessageDecoder {
|
||||
|
||||
if (readMagic == PROTOCOL_2_MAGIC) {
|
||||
// Short 0x733A + Message = Protocol v2 Vote
|
||||
session.setVersion(VotifierSession.ProtocolVersion.TWO);
|
||||
|
||||
if (!testMode) {
|
||||
ctx.pipeline().addAfter("protocolDifferentiator", "protocol2LengthDecoder", new LengthFieldBasedFrameDecoder(1024, 2, 2, 0, 4));
|
||||
ctx.pipeline().addAfter("protocol2LengthDecoder", "protocol2StringDecoder", new StringDecoder(StandardCharsets.UTF_8));
|
||||
@@ -53,17 +54,16 @@ public class VotifierProtocolDifferentiator extends ByteToMessageDecoder {
|
||||
ctx.pipeline().addAfter("protocol2VoteDecoder", "protocol2StringEncoder", new StringEncoder(StandardCharsets.UTF_8));
|
||||
ctx.pipeline().remove(this);
|
||||
}
|
||||
session.setVersion(VotifierSession.ProtocolVersion.TWO);
|
||||
} else {
|
||||
if (!allowv1) {
|
||||
throw new CorruptedFrameException("This server only accepts well-formed Votifier v2 packets.");
|
||||
}
|
||||
// Probably Protocol v1 Vote Message
|
||||
session.setVersion(VotifierSession.ProtocolVersion.ONE);
|
||||
if (!testMode) {
|
||||
ctx.pipeline().addAfter("protocolDifferentiator", "protocol1Handler", new VotifierProtocol1Decoder());
|
||||
ctx.pipeline().remove(this);
|
||||
}
|
||||
session.setVersion(VotifierSession.ProtocolVersion.ONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.protocol.v1crypto.RSA;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.PublicKey;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class VoteUtil {
|
||||
public static byte[] encodePOJOv1(Vote vote) throws Exception {
|
||||
return encodePOJOv1(vote, TestVotifierPlugin.getI().getProtocolV1Key().getPublic());
|
||||
}
|
||||
|
||||
public static byte[] encodePOJOv1(Vote vote, PublicKey key) throws Exception {
|
||||
List<String> ordered = new ArrayList<>();
|
||||
ordered.add("VOTE");
|
||||
ordered.add(vote.getServiceName());
|
||||
ordered.add(vote.getUsername());
|
||||
ordered.add(vote.getAddress());
|
||||
ordered.add(vote.getTimeStamp());
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (String s : ordered) {
|
||||
builder.append(s).append('\n'); // naive join needed!
|
||||
}
|
||||
|
||||
return RSA.encrypt(builder.toString().getBytes(StandardCharsets.UTF_8), key);
|
||||
}
|
||||
}
|
||||
+2
-7
@@ -29,16 +29,12 @@ public class VotifierProtocol1DecoderTest {
|
||||
|
||||
@Test
|
||||
public void testSuccessfulDecode() throws Exception {
|
||||
// Encode a test vote.
|
||||
String voteString = "VOTE\nTest\ntest\ntest\ntest\n";
|
||||
|
||||
// For reference, this is the same vote as a POJO:
|
||||
Vote votePojo = new Vote("Test", "test", "test", "test");
|
||||
|
||||
// Send the vote
|
||||
EmbeddedChannel channel = createChannel();
|
||||
|
||||
byte[] encrypted = RSA.encrypt(voteString.getBytes(StandardCharsets.UTF_8), TestVotifierPlugin.getI().getProtocolV1Key().getPublic());
|
||||
byte[] encrypted = VoteUtil.encodePOJOv1(votePojo);
|
||||
ByteBuf encryptedByteBuf = Unpooled.wrappedBuffer(encrypted);
|
||||
|
||||
assertTrue(channel.writeInbound(encryptedByteBuf));
|
||||
@@ -81,8 +77,7 @@ public class VotifierProtocol1DecoderTest {
|
||||
// Send the bad vote
|
||||
EmbeddedChannel channel = createChannel();
|
||||
|
||||
String voteString = "VOTE\nTest\ntest\ntest\ntest\n";
|
||||
byte[] encrypted = RSA.encrypt(voteString.getBytes(StandardCharsets.UTF_8), badPublicKey);
|
||||
byte[] encrypted = VoteUtil.encodePOJOv1(new Vote("Test", "test", "test", "test"), badPublicKey);
|
||||
ByteBuf encryptedByteBuf = Unpooled.wrappedBuffer(encrypted);
|
||||
|
||||
try {
|
||||
|
||||
+19
-15
@@ -1,5 +1,6 @@
|
||||
package com.vexsoftware.votifier.net.protocol;
|
||||
|
||||
import com.vexsoftware.votifier.model.Vote;
|
||||
import com.vexsoftware.votifier.net.VotifierSession;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
@@ -62,21 +63,6 @@ public class VotifierProtocolDifferentiatorTest {
|
||||
channel.close();
|
||||
}
|
||||
|
||||
@Test(expected = DecoderException.class)
|
||||
public void failOnSmallBufferTest() {
|
||||
EmbeddedChannel channel = new EmbeddedChannel(new VotifierProtocolDifferentiator(true, false));
|
||||
|
||||
ByteBuf buf = Unpooled.buffer(1);
|
||||
buf.writeByte(0);
|
||||
|
||||
try {
|
||||
channel.writeInbound(buf);
|
||||
} finally {
|
||||
buf.release();
|
||||
channel.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = DecoderException.class)
|
||||
public void failOnBadPacketTest() {
|
||||
EmbeddedChannel channel = new EmbeddedChannel(new VotifierProtocolDifferentiator(true, false));
|
||||
@@ -93,4 +79,22 @@ public class VotifierProtocolDifferentiatorTest {
|
||||
channel.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tryIdentifyRealVotev1() throws Exception {
|
||||
EmbeddedChannel channel = new EmbeddedChannel(new VotifierProtocolDifferentiator(true, true));
|
||||
|
||||
VotifierSession session = new VotifierSession();
|
||||
channel.attr(VotifierSession.KEY).set(session);
|
||||
|
||||
Vote votePojo = new Vote("Test", "test", "test", "test");
|
||||
byte[] encrypted = VoteUtil.encodePOJOv1(votePojo);
|
||||
ByteBuf encryptedByteBuf = Unpooled.wrappedBuffer(encrypted);
|
||||
|
||||
channel.writeInbound(encryptedByteBuf);
|
||||
|
||||
assertEquals(VotifierSession.ProtocolVersion.ONE, session.getVersion());
|
||||
encryptedByteBuf.release();
|
||||
channel.close();
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
<groupId>com.vexsoftware</groupId>
|
||||
<artifactId>nuvotifier</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<version>2.3.2</version>
|
||||
<version>2.3.2.2</version>
|
||||
<modules>
|
||||
<module>common</module>
|
||||
<module>bukkit</module>
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>nuvotifier</artifactId>
|
||||
<groupId>com.vexsoftware</groupId>
|
||||
<version>2.3.2</version>
|
||||
<version>2.3.2.2</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
@Plugin(id = "nuvotifier", name = "NuVotifier", version = "2.3.2")
|
||||
@Plugin(id = "nuvotifier", name = "NuVotifier", version = "2.3.2.2")
|
||||
public class VotifierPlugin implements VoteHandler, com.vexsoftware.votifier.VotifierPlugin, ForwardedVoteListener {
|
||||
private Logger logger;
|
||||
@Inject
|
||||
@@ -229,7 +229,7 @@ public class VotifierPlugin implements VoteHandler, com.vexsoftware.votifier.Vot
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
if (future.isSuccess()) {
|
||||
serverChannel = future.channel();
|
||||
logger.info("Votifier enabled.");
|
||||
logger.info("Votifier enabled on socket "+serverChannel.localAddress()+".");
|
||||
} else {
|
||||
SocketAddress socketAddress = future.channel().localAddress();
|
||||
if (socketAddress == null) {
|
||||
|
||||
@@ -21,4 +21,8 @@ public class VotifierEvent extends AbstractEvent {
|
||||
public Cause getCause() {
|
||||
return cause;
|
||||
}
|
||||
|
||||
public Vote getVote() {
|
||||
return vote;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>nuvotifier</artifactId>
|
||||
<groupId>com.vexsoftware</groupId>
|
||||
<version>2.3.2</version>
|
||||
<version>2.3.2.2</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
Reference in new issue
Block a user