Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 55 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.vexsoftware</groupId>
<artifactId>votifier</artifactId>
<version>1.9</version>
<version>2.0</version>
<name>Votifier</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
Expand All @@ -23,6 +23,30 @@
<artifactId>bukkit</artifactId>
<version>1.7.2-R0.1</version>
<type>jar</type>
<scope>provided</scope>
</dependency>
<!-- Netty Dependency -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-handler</artifactId>
<version>4.0.23.Final</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<!-- JSON Dependency -->
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20141113</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<!-- Apache Commons-IO -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
</dependencies>
Expand All @@ -40,6 +64,36 @@
<target>1.7</target>
</configuration>
</plugin>
<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>
<minimizeJar>true</minimizeJar>
<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>
<!-- Jar Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
Expand Down
16 changes: 16 additions & 0 deletions src/main/java/com/vexsoftware/votifier/TokenUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.vexsoftware.votifier;

import java.math.BigInteger;
import java.security.SecureRandom;

public class TokenUtil {
private TokenUtil() {

}

private static final SecureRandom RANDOM = new SecureRandom();

public static String newToken() {
return new BigInteger(130, RANDOM).toString(32);
}
}
108 changes: 80 additions & 28 deletions src/main/java/com/vexsoftware/votifier/Votifier.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,32 @@
package com.vexsoftware.votifier;

import java.io.*;
import java.security.Key;
import java.security.KeyPair;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
import java.util.logging.*;

import com.vexsoftware.votifier.crypto.KeyCreator;
import com.vexsoftware.votifier.net.VoteInboundHandler;
import com.vexsoftware.votifier.net.VotifierSession;
import com.vexsoftware.votifier.net.protocol.VotifierGreetingHandler;
import com.vexsoftware.votifier.net.protocol.VotifierProtocolDifferentiator;
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.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.java.JavaPlugin;
import com.vexsoftware.votifier.crypto.RSAIO;
import com.vexsoftware.votifier.crypto.RSAKeygen;
import com.vexsoftware.votifier.model.ListenerLoader;
import com.vexsoftware.votifier.model.VoteListener;
import com.vexsoftware.votifier.net.VoteReceiver;

/**
* The main Votifier plugin class.
Expand All @@ -55,15 +69,21 @@ public class Votifier extends JavaPlugin {
/** The vote listeners. */
private final List<VoteListener> listeners = new ArrayList<VoteListener>();

/** The vote receiver. */
private VoteReceiver voteReceiver;
/** The server channel. */
private Channel serverChannel;

/** The event group handling the channel. */
private NioEventLoopGroup serverGroup;

/** The RSA key pair. */
private KeyPair keyPair;

/** Debug mode flag */
private boolean debug;

/** Keys used for websites. */
private Map<String, Key> tokens = new HashMap<>();

/**
* Attach custom log filter to logger.
*/
Expand Down Expand Up @@ -125,6 +145,14 @@ public void onEnable() {
LOG.info("a different port, which you need to specify in config.yml");
LOG.info("------------------------------------------------------------------------------");

String token = TokenUtil.newToken();
ConfigurationSection tokenSection = cfg.createSection("tokens");
tokenSection.set("default", token);
LOG.info("Your default Votifier token is " + token + ".");
LOG.info("You will need to provide this token when you submit your server to a voting");
LOG.info("list.");
LOG.info("------------------------------------------------------------------------------");

cfg.set("listener_folder", listenerDirectory);
cfg.save(config);
} catch (Exception ex) {
Expand Down Expand Up @@ -152,7 +180,7 @@ public void onEnable() {
}
} catch (Exception ex) {
LOG.log(Level.SEVERE,
"Error reading configuration file or RSA keys", ex);
"Error reading configuration file or RSA tokens", ex);
gracefulExit();
return;
}
Expand All @@ -161,30 +189,60 @@ public void onEnable() {
listenerDirectory = cfg.getString("listener_folder");
listeners.addAll(ListenerLoader.load(listenerDirectory));

// 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()));
LOG.info("Loaded token for website: " + website.getKey());
}
} else {
LOG.warning("No websites are listed in your configuration.");
}

// Initialize the receiver.
String host = cfg.getString("host", hostAddr);
int port = cfg.getInt("port", 8192);
debug = cfg.getBoolean("debug", false);
if (debug)
LOG.info("DEBUG mode enabled!");

try {
voteReceiver = new VoteReceiver(this, host, port);
voteReceiver.start();

LOG.info("Votifier enabled.");
} catch (Exception ex) {
gracefulExit();
return;
}
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.pipeline().addLast("greetingHandler", new VotifierGreetingHandler());
channel.pipeline().addLast("protocolDifferentiator", new VotifierProtocolDifferentiator());
channel.pipeline().addLast("voteHandler", new VoteInboundHandler());
}
})
.bind(host, port)
.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
serverChannel = future.channel();
LOG.info("Votifier enabled.");
} else {
LOG.log(Level.SEVERE, "Votifier was not able to bind to " + future.channel().localAddress(), future.cause());
}
}
});
}

@Override
public void onDisable() {
// Interrupt the vote receiver.
if (voteReceiver != null) {
voteReceiver.shutdown();
}
// Shut down the network handlers.
if (serverChannel != null)
serverChannel.close();
serverGroup.shutdownGracefully();
LOG.info("Votifier disabled.");
}

Expand Down Expand Up @@ -219,15 +277,6 @@ public List<VoteListener> getListeners() {
return listeners;
}

/**
* Gets the vote receiver.
*
* @return The vote receiver
*/
public VoteReceiver getVoteReceiver() {
return voteReceiver;
}

/**
* Gets the keyPair.
*
Expand All @@ -241,4 +290,7 @@ public boolean isDebug() {
return debug;
}

public Map<String, Key> getTokens() {
return tokens;
}
}
11 changes: 11 additions & 0 deletions src/main/java/com/vexsoftware/votifier/crypto/KeyCreator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.vexsoftware.votifier.crypto;

import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.Key;

public class KeyCreator {
public static Key createKeyFrom(String token) {
return new SecretKeySpec(token.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
}
}
16 changes: 6 additions & 10 deletions src/main/java/com/vexsoftware/votifier/crypto/RSAIO.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@

package com.vexsoftware.votifier.crypto;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.net.URL;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.PrivateKey;
Expand All @@ -32,8 +36,6 @@

/**
* Static utility methods for saving and loading RSA key pairs.
*
* @author Blake Beaupain
*/
public class RSAIO {

Expand Down Expand Up @@ -81,21 +83,15 @@ public static void save(File directory, KeyPair keyPair) throws Exception {
public static KeyPair load(File directory) throws Exception {
// Read the public key file.
File publicKeyFile = new File(directory + "/public.key");
FileInputStream in = new FileInputStream(directory + "/public.key");
byte[] encodedPublicKey = new byte[(int) publicKeyFile.length()];
in.read(encodedPublicKey);
byte[] encodedPublicKey = FileUtils.readFileToByteArray(publicKeyFile);
encodedPublicKey = DatatypeConverter.parseBase64Binary(new String(
encodedPublicKey));
in.close();

// Read the private key file.
File privateKeyFile = new File(directory + "/private.key");
in = new FileInputStream(directory + "/private.key");
byte[] encodedPrivateKey = new byte[(int) privateKeyFile.length()];
in.read(encodedPrivateKey);
byte[] encodedPrivateKey = FileUtils.readFileToByteArray(privateKeyFile);
encodedPrivateKey = DatatypeConverter.parseBase64Binary(new String(
encodedPrivateKey));
in.close();

// Instantiate and return the key pair.
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
Expand Down
63 changes: 63 additions & 0 deletions src/main/java/com/vexsoftware/votifier/net/VoteInboundHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.vexsoftware.votifier.net;

import com.vexsoftware.votifier.Votifier;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.model.VoteListener;
import com.vexsoftware.votifier.model.VotifierEvent;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import org.bukkit.Bukkit;
import org.json.JSONObject;

import java.util.logging.Level;

public class VoteInboundHandler extends SimpleChannelInboundHandler<Vote> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, final Vote vote) throws Exception {
// Fire a synchronous task and close the connection.
Votifier.getInstance().getServer().getScheduler().scheduleSyncDelayedTask(Votifier.getInstance(), new Runnable() {
@Override
public void run() {
for (VoteListener listener : Votifier.getInstance().getListeners()) {
try {
listener.voteMade(vote);
} catch (Exception ex) {
String vlName = listener.getClass().getSimpleName();
Votifier.getInstance().getLogger().log(Level.WARNING,
"Exception caught while sending the vote notification to the '"
+ vlName + "' listener", ex);
}
}
Bukkit.getServer().getPluginManager().callEvent(new VotifierEvent(vote));
}
});

VotifierSession session = ctx.channel().attr(VotifierSession.KEY).get();

if (session.getVersion() == VotifierSession.ProtocolVersion.ONE) {
ctx.close();
} else {
JSONObject object = new JSONObject();
object.put("status", "ok");
ctx.writeAndFlush(object.toString()).addListener(ChannelFutureListener.CLOSE);
}
}

@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
VotifierSession session = ctx.channel().attr(VotifierSession.KEY).get();

Votifier.getInstance().getLogger().log(Level.SEVERE, "Exception while processing vote from " + ctx.channel().remoteAddress(), 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()).addListener(ChannelFutureListener.CLOSE);
} else {
ctx.close();
}
}
}
Loading