diff --git a/pom.xml b/pom.xml
index 630821e..34c8b5b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,19 +7,6 @@
de.gurkengewuerz
postfix-rest-send
1.0-SNAPSHOT
-
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
-
-
- 1.8
-
-
-
-
-
org.simplejavamail
@@ -46,5 +33,50 @@
json
20170516
+
+ mysql
+ mysql-connector-java
+ 5.1.39
+
+
+
+
+
+ maven-compiler-plugin
+ 3.5.1
+
+
+ 1.8
+
+
+
+ maven-assembly-plugin
+ 2.6
+
+ false
+ ${project.artifactId}
+
+
+ jar-with-dependencies
+
+
+
+
+ ${project.groupId}.postfix_rest_send.Main
+
+
+
+
+
+ assamble
+
+ single
+
+ package
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/de/gurkengewuerz/postfix_rest_send/Config.java b/src/main/java/de/gurkengewuerz/postfix_rest_send/Config.java
new file mode 100644
index 0000000..c30bd5e
--- /dev/null
+++ b/src/main/java/de/gurkengewuerz/postfix_rest_send/Config.java
@@ -0,0 +1,136 @@
+package de.gurkengewuerz.postfix_rest_send;
+
+import org.json.JSONObject;
+import org.json.JSONTokener;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.file.AccessDeniedException;
+import java.nio.file.Files;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Created by gurkengewuerz.de on 02.07.2017.
+ */
+public class Config extends JSONObject {
+ private File file;
+ private JSONObject database;
+ private boolean firstRun = false;
+
+ public Config(File file) throws IOException {
+ this.file = file;
+
+ if (!file.exists()) {
+ file.createNewFile();
+ firstRun = true;
+ }
+
+ if (!file.isFile()) {
+ throw new FileNotFoundException(file.getAbsolutePath() + " not found");
+ }
+
+ if (!file.canRead() || !file.canWrite()) {
+ throw new AccessDeniedException(file.getAbsolutePath() + " is not accessable");
+ }
+
+ this.put("debug", true);
+ this.put("disable_bruteforcefilter", false);
+ this.put("token_expire", 730); // houres/1M
+ this.put("http_port", 8081);
+ this.put("postfixadmin_encryption", "md5crypt");
+ this.put("postfixadmin_path", "/var/www/html/postfixadmin/");
+
+ JSONObject database = new JSONObject();
+ database.put("port", 3306);
+ database.put("host", "127.0.0.1");
+ database.put("db", "mail");
+ database.put("username", "postfixadmin");
+ database.put("password", "abc123");
+ this.put("database", database);
+ }
+
+ public void save() {
+ try {
+ FileWriter fw = new FileWriter(file.getAbsolutePath());
+ fw.write(this.toString(4));
+ fw.close();
+ loadRestData();
+ } catch (IOException e) {
+ Logger.getLogger(getClass().getName()).log(Level.SEVERE, null, e);
+ }
+ }
+
+ public void load() {
+ try {
+ String content = new String(Files.readAllBytes(file.toPath()), "UTF-8");
+ if (content.isEmpty()) {
+ save();
+ return;
+ }
+ JSONTokener jt = new JSONTokener(content);
+ if (jt.nextClean() != 123) {
+ throw jt.syntaxError("A JSONObject text must begin with '{'");
+ } else {
+ while (jt.more()) {
+ char c = jt.nextClean();
+ switch (c) {
+ case '\u0000':
+ throw jt.syntaxError("A JSONObject text must end with '}'");
+ case '}':
+ return;
+ default:
+ jt.back();
+ String key = jt.nextValue().toString();
+ c = jt.nextClean();
+ if (c != 58) {
+ throw jt.syntaxError("Expected a ':' after a key");
+ }
+
+ this.remove(key);
+ this.putOnce(key, jt.nextValue());
+ switch (jt.nextClean()) {
+ case ',':
+ case ';':
+ if (jt.nextClean() == 125) {
+ return;
+ }
+
+ jt.back();
+ break;
+ case '}':
+ save();
+ return;
+ default:
+ throw jt.syntaxError("Expected a ',' or '}'");
+ }
+ }
+ }
+ }
+ } catch (IOException e) {
+ Logger.getLogger(getClass().getName()).log(Level.SEVERE, null, e);
+ }
+ }
+
+ public boolean debug() {
+ return getBoolean("debug");
+ }
+
+ private void loadRestData() {
+ database = getJSONObject("database");
+ }
+
+ public JSONObject getDatabaseConfig() {
+ return database;
+ }
+
+ public String getDBURL() {
+ return "jdbc:mysql://" + getDatabaseConfig().getString("host") + ":" + getDatabaseConfig().getInt("port") + "/" + getDatabaseConfig().getString("db");
+ }
+
+ public boolean isFirstRun() {
+ return firstRun;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/de/gurkengewuerz/postfix_rest_send/Main.java b/src/main/java/de/gurkengewuerz/postfix_rest_send/Main.java
index a2c5c71..e15816d 100644
--- a/src/main/java/de/gurkengewuerz/postfix_rest_send/Main.java
+++ b/src/main/java/de/gurkengewuerz/postfix_rest_send/Main.java
@@ -1,104 +1,254 @@
package de.gurkengewuerz.postfix_rest_send;
+import de.gurkengewuerz.postfix_rest_send.objects.BruteforceFilter;
+import de.gurkengewuerz.postfix_rest_send.objects.ReturnHolder;
+import de.gurkengewuerz.postfix_rest_send.utils.BashUtils;
+import de.gurkengewuerz.postfix_rest_send.utils.HtmlToPlainText;
+import de.gurkengewuerz.postfix_rest_send.utils.RandomUtils;
import fi.iki.elonen.NanoHTTPD;
+import org.hazlewood.connor.bottema.emailaddress.EmailAddressCriteria;
+import org.hazlewood.connor.bottema.emailaddress.EmailAddressValidator;
import org.json.JSONObject;
+import org.simplejavamail.email.Email;
+import org.simplejavamail.mailer.Mailer;
+import org.simplejavamail.mailer.config.ServerConfig;
-import java.io.BufferedReader;
+import javax.mail.Message;
+import java.io.File;
import java.io.IOException;
-import java.io.InputStreamReader;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.EnumSet;
import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
/**
* Created by gurkengewuerz.de on 12.07.2017.
*/
public class Main extends NanoHTTPD {
+ private static MySQL db;
+ private static Config conf;
+ private static Mailer mailer;
+ private static BruteforceFilter filter;
+
+ /*
+ TODO:
+ Main umbennen
+ Configurierbare Domains/IPs
+ Post JSON Support
+ */
+
public Main() throws IOException {
- super(8081);
+ super(conf.getInt("http_port"));
+ Logger.getLogger(Main.class.getName()).log(Level.SEVERE, "Started Server :" + getListeningPort());
start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
}
-// http://www.simplejavamail.org/#/debugging
-
- public static void main(String[] args) {
-// Mailer m = new Mailer(new ServerConfig("localhost", 25));
-// m.setDebug(true);
-//
-// Email email = new Email();
-// email.setFromAddress("admin@gurkengewuerz.de", "admin@gurkengewuerz.de");
-// email.setReplyToAddress("support@gurkengewuerz.de", "support@gurkengewuerz.de");
-// email.addRecipient("developer@the-town.net", "developer@the-town.net", Message.RecipientType.TO);
-// email.setSubject("Email Test");
-// email.setText("Dies ist ein Email Test von Java.\nHoffentlich bald mit Rest API");
-// email.setTextHTML("We should meet up!");
-//
-// m.sendMail(email);
-
-// HtmlToPlainText formatter = new HtmlToPlainText();
-// formatter.getPlainText()
- try {
- System.out.println("Started");
- new Main();
- } catch (IOException e) {
- e.printStackTrace();
+ public static void main(String[] args) throws IOException, SQLException {
+ String settingsFile = "." + File.separator + "settings.json";
+ if (args.length >= 1) {
+ settingsFile = args[0];
}
+ File f = new File(settingsFile);
+
+ conf = new Config(f);
+ conf.load();
+
+ if (conf.debug())
+ Logger.getLogger(Main.class.getName()).log(Level.INFO, "Using Settingsfile " + f.getAbsolutePath());
+
+ if (conf.isFirstRun()) System.exit(0);
+
+ db = new MySQL(MySQL.Type.MYSQL, conf.getDBURL(), conf.getDatabaseConfig().getString("username"), conf.getDatabaseConfig().getString("password"));
+ db.executeUpdate("CREATE TABLE IF NOT EXISTS token (" +
+ " tokenid int(11) NOT NULL AUTO_INCREMENT," +
+ " username VARCHAR(255) NOT NULL," +
+ " token VARCHAR(255) NOT NULL," +
+ " created INT NOT NULL," +
+ " expire INT NOT NULL," +
+ " PRIMARY KEY (tokenid), " +
+ " UNIQUE (token)" +
+ " ); ");
+ db.executeUpdate("CREATE TABLE IF NOT EXISTS token_bruteforce (" +
+ " id int(11) NOT NULL AUTO_INCREMENT," +
+ " occurred INT NOT NULL," +
+ " ip VARCHAR(255) NOT NULL," +
+ " PRIMARY KEY (id)" +
+ " ); ");
+ mailer = new Mailer(new ServerConfig("localhost", 25));
+ mailer.setDebug(conf.debug());
+
+ filter = new BruteforceFilter(6);
+
+ new Main();
}
@Override
public Response serve(IHTTPSession session) {
+ Map headers = session.getHeaders();
+ String ip = headers.get("remote-addr");
+ Logger.getLogger(Main.class.getName()).log(Level.INFO, session.getMethod().name() + ": " + session.getUri() + " " + ip);
Map parms = session.getParms();
-
JSONObject json = new JSONObject("{'error':'not found'}");
- Response.Status status = Response.Status.NOT_FOUND;
+ Response.Status status = Response.Status.NOT_IMPLEMENTED;
- if (session.getUri().startsWith("/authorize")) {
- if (session.getUri().startsWith("/authorize/add")) {
- String username = parms.get("username");
- String password = parms.get("password");
+ if (filter.banned(ip))
+ return getError(Response.Status.FORBIDDEN, "you are temporary banned");
- if (username != null && password != null) {
- System.out.println(checkPassword("/var/www/html/tools/functions.inc.php", "md5", "EMailFam.Schuetrumpf@123", "460b7d128333a4be972d4c7bdb0ee142"));
+ try {
+ if (session.getMethod().equals(Method.POST))
+ session.parseBody(parms);
+ } catch (IOException | ResponseException e) {
+ return getError(Response.Status.INTERNAL_ERROR);
+ }
+ if (db == null) return getError(Response.Status.INTERNAL_ERROR, "Database Error");
+ try {
+ if (session.getUri().startsWith("/authorize")) {
+ if (session.getUri().startsWith("/authorize/add")) {
+ String username = parms.get("username");
+ String password = parms.get("password");
+
+ if (username == null || password == null) return getError(Response.Status.UNAUTHORIZED);
+
+ boolean usernameValid = EmailAddressValidator.isValid(username, EnumSet.of(EmailAddressCriteria.ALLOW_DOMAIN_LITERALS));
+ if (!usernameValid) {
+ filter.failed(ip);
+ return getError(Response.Status.UNAUTHORIZED, "invalid user");
+ }
+
+ ResultSet rs = db.executeQuery("SELECT * FROM mailbox WHERE username = ? AND active = 1;", username);
+ int rowcount = 0;
+ while (rs.next()) {
+ rowcount++;
+ }
+ rs.beforeFirst();
+ rs.next();
+ if (rowcount <= 0) {
+ filter.failed(ip);
+ return getError(Response.Status.UNAUTHORIZED);
+ }
+
+ boolean correct = checkPassword(conf.getString("postfixadmin_encryption"), password, rs.getString("password"));
+ if (!correct) {
+ filter.failed(ip);
+ return getError(Response.Status.UNAUTHORIZED);
+ }
+ String token = RandomUtils.rndToken(username);
+ long expires = (System.currentTimeMillis() / 1000) + (conf.getInt("token_expire") * 60 * 60);
+ db.executeUpdate("INSERT INTO token (username, token, created, expire) VALUES (?,?,?,?);", username, token, (System.currentTimeMillis() / 1000), expires);
status = Response.Status.OK;
- json = new JSONObject("{'token':''}");
- } else {
- json = new JSONObject("{'error':'username/password is null'}");
+ json = new JSONObject("{'token':'" + token + "', 'expires': " + expires + "}");
+ } else if (session.getUri().startsWith("/authorize/revoke")) {
+ String token = parms.get("token");
+ if (token == null) return getError(Response.Status.BAD_REQUEST);
+ db.executeUpdate("DELETE FROM token WHERE token = ?;", token);
+ status = Response.Status.OK;
+ json = new JSONObject("{'status':'ok'}");
}
- } else if (session.getUri().startsWith("/authorize/revoke")) {
+ } else if (session.getUri().startsWith("/validate/address")) {
+ String mail = parms.get("mail");
+ if (mail == null) return getError(Response.Status.BAD_REQUEST);
+ boolean valid = EmailAddressValidator.isValid(mail, EnumSet.of(EmailAddressCriteria.ALLOW_DOMAIN_LITERALS));
+ status = Response.Status.OK;
+ json = new JSONObject("{'valid':" + valid + "}");
+ } else if (session.getUri().startsWith("/send")) {
+ String token = parms.get("token");
+ String to = parms.get("to");
+ String subject = parms.get("to");
+ String text = parms.get("text");
+ String html = parms.get("html");
+ if (token == null) return getError(Response.Status.UNAUTHORIZED);
+
+ ResultSet rs = db.executeQuery("SELECT * FROM token WHERE token = ?;", token);
+ int rowcount = 0;
+ while (rs.next()) {
+ rowcount++;
+ }
+ rs.beforeFirst();
+ rs.next();
+ if (rowcount <= 0){
+ filter.banned(ip);
+ return getError(Response.Status.UNAUTHORIZED);
+ }
+
+ if (rs.getLong("expire") <= System.currentTimeMillis() / 1000)
+ return getError(Response.Status.UNAUTHORIZED, "token expired");
+
+ String username = rs.getString("username");
+
+ if (to == null || subject == null || (text == null && html == null))
+ return getError(Response.Status.BAD_REQUEST);
+
+ ArrayList toList = new ArrayList<>();
+
+ if (to.contains(";")) {
+ for (String mailTo : to.split(";")) {
+ if (!EmailAddressValidator.isValid(mailTo, EnumSet.of(EmailAddressCriteria.ALLOW_DOMAIN_LITERALS)))
+ return getError(Response.Status.BAD_REQUEST, "invalid recipient");
+ toList.add(mailTo);
+ }
+ } else {
+ if (!EmailAddressValidator.isValid(to, EnumSet.of(EmailAddressCriteria.ALLOW_DOMAIN_LITERALS)))
+ return getError(Response.Status.BAD_REQUEST, "invalid recipient");
+ toList.add(to);
+ }
+
+ Email email = new Email();
+ email.setFromAddress(username, username);
+ toList.forEach(s -> email.addRecipient(s, s, Message.RecipientType.TO));
+ email.setSubject(subject);
+ if (text != null) email.setText(text);
+ if (html != null) email.setTextHTML(html);
+ if (text == null && html != null) {
+ HtmlToPlainText formatter = new HtmlToPlainText();
+ email.setText(formatter.getPlainText(html));
+ }
+
+ mailer.sendMail(email);
+ status = Response.Status.OK;
+ json = new JSONObject("{'status':'send'}");
}
- } else if (session.getUri().startsWith("/validate/address")) {
-
- } else if (session.getUri().startsWith("/send")) {
-
+ } catch (SQLException e) {
+ Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, e);
+ return getError(Response.Status.INTERNAL_ERROR);
}
return newFixedLengthResponse(status, "application/json", json.toString());
}
- public boolean checkPassword(String path, String encryption, String plain, String dbPW) {
- String crypted = execPHP("-r '$CONF = array(); $CONF[\"encrypt\"] = \"" + encryption + "\"; include \"" + path + "\"; echo(pacrypt(\"" + plain + "\", \"" + dbPW + "\")).\"\\n\";'");
- System.out.println(crypted);
- return crypted.equals(dbPW);
+ public boolean checkPassword(String encryption, String plain, String dbPW) {
+ ReturnHolder crypted = BashUtils.run(conf.debug(), "php", "-r", "$CONF = array(); $CONF[\"encrypt\"] = \"" + encryption + "\"; include \"" + conf.getString("postfixadmin_path") + "/functions.inc.php" + "\"; echo(pacrypt(\"" + plain + "\", \"" + dbPW + "\")).\"\\n\";");
+ return String.join("", crypted.output).equals(dbPW);
}
- public String execPHP(String args) {
- try {
- System.out.println(args);
- String line;
- StringBuilder output = new StringBuilder();
- Process p = Runtime.getRuntime().exec("php " + args);
- BufferedReader input =
- new BufferedReader
- (new InputStreamReader(p.getInputStream()));
- while ((line = input.readLine()) != null) {
- output.append(line);
- System.out.println(line);
- }
- input.close();
- return output.toString();
- } catch (Exception err) {
- err.printStackTrace();
+
+ public Response getError(Response.Status status, String error) {
+ JSONObject json = new JSONObject("{'error':'unknown error'}");
+ json.put("error", error);
+ return newFixedLengthResponse(status, "application/json", json.toString());
+ }
+
+ public Response getError(Response.Status status) {
+ switch (status) {
+ case INTERNAL_ERROR:
+ return getError(status, "internal server rror");
+ case BAD_REQUEST:
+ return getError(status, "bad request");
+ case UNAUTHORIZED:
+ return getError(status, "unauthorized");
}
- return "";
+ return getError(status, "unknown error");
+ }
+
+ public static MySQL getDatabase() {
+ return db;
+ }
+
+ public static Config getConfig() {
+ return conf;
}
}
diff --git a/src/main/java/de/gurkengewuerz/postfix_rest_send/MySQL.java b/src/main/java/de/gurkengewuerz/postfix_rest_send/MySQL.java
new file mode 100644
index 0000000..fd2b896
--- /dev/null
+++ b/src/main/java/de/gurkengewuerz/postfix_rest_send/MySQL.java
@@ -0,0 +1,252 @@
+package de.gurkengewuerz.postfix_rest_send;
+
+import java.sql.*;
+import java.util.logging.Logger;
+
+/**
+ * Simple wrapper for database abstraction.
+ *
+ * @author Nijiko
+ * @url https://raw.githubusercontent.com/iConomy/SimpleShop/master/com/nijiko/simpleshop/database/Wrapper.java
+ */
+public class MySQL {
+ private static final Logger log = Logger.getLogger("MySQL");
+
+ /**
+ * Database Types
+ */
+ public enum Type {
+ SQLITE,
+ MYSQL
+ }
+
+ ;
+
+ /**
+ * Fetch type from string.
+ *
+ * @param type
+ * @return
+ */
+ public static Type getType(String type) {
+ for (Type dbType : Type.values()) {
+ if (dbType.toString().equalsIgnoreCase(type)) {
+ return dbType;
+ }
+ }
+
+ return Type.SQLITE;
+ }
+
+ /*
+ * Database Settings
+ */
+ public Type database = null;
+
+ /*
+ * Database Connection Settings
+ */
+ private String db;
+ private String user;
+ private String pass;
+
+ /*
+ * Database Memory
+ */
+ private Connection connection;
+ private Statement Stmt;
+ private PreparedStatement Statement;
+ private ResultSet ResultSet;
+
+ /**
+ * Create a new instance of the wrapper for usage.
+ *
+ * @param database
+ * @param db
+ * @param user
+ * @param pass
+ */
+ public MySQL(Type database, String db, String user, String pass) {
+ this.database = database;
+ this.db = db;
+ this.user = user;
+ this.pass = pass;
+ }
+
+ /**
+ * Initialize the wrapper
+ *
+ * @return Connection
+ * @throws ClassNotFoundException
+ * @throws SQLException
+ */
+ public void initialize() {
+ try {
+ this.connection();
+ } catch (SQLException ex) {
+ log.severe("[" + this.database.toString() + " Database] Failed to connect: " + ex);
+ } catch (ClassNotFoundException e) {
+ log.severe("[" + this.database.toString() + " Database] Connector not found: " + e);
+ }
+ }
+
+ /**
+ * Connect to the database, return connection.
+ *
+ * @return Connection
+ * @throws ClassNotFoundException
+ * @throws SQLException
+ */
+ private Connection connection() throws ClassNotFoundException, SQLException {
+ if (this.database.equals(database.SQLITE)) {
+ Class.forName("org.sqlite.JDBC");
+ this.connection = DriverManager.getConnection(this.db);
+ } else {
+ Class.forName("com.mysql.jdbc.Driver");
+ this.connection = DriverManager.getConnection(this.db, this.user, this.pass);
+ }
+
+ return this.connection;
+ }
+
+ /**
+ * Check to see if table exists.
+ *
+ * @param table
+ * @return boolean
+ */
+ public boolean checkTable(String table) {
+ initialize();
+
+ try {
+ DatabaseMetaData dbm = this.connection.getMetaData();
+ this.ResultSet = dbm.getTables(null, null, table, null);
+ return this.ResultSet.next();
+ } catch (SQLException ex) {
+ log.severe("[" + this.database.toString() + " Database] Table check failed: " + ex);
+ } finally {
+ this.close();
+ }
+
+ return false;
+ }
+
+ /**
+ * Execute pure SQL String.
+ *
+ * @param query
+ * @return
+ */
+ public ResultSet executeQuery(String query) {
+ initialize();
+
+ try {
+ this.Statement = this.connection.prepareStatement(query);
+
+ return this.Statement.executeQuery();
+ } catch (SQLException ex) {
+ log.severe("[" + this.database.toString() + " Database] Could not execute query: " + ex);
+ }
+
+ return null;
+ }
+
+ /**
+ * Execute Query with variables.
+ *
+ * @param query
+ * @param variables
+ * @return
+ */
+ public ResultSet executeQuery(String query, Object... variables) {
+ initialize();
+
+ try {
+ this.Statement = this.connection.prepareStatement(query);
+ int i = 1;
+ for (Object obj : variables) {
+ this.Statement.setObject(i, obj);
+ i++;
+ }
+ return this.Statement.executeQuery();
+ } catch (SQLException ex) {
+ log.severe("[" + this.database.toString() + " Database] Could not execute query: " + ex);
+ }
+
+ return null;
+ }
+
+ /**
+ * Execute pure SQL String.
+ *
+ * @param query
+ * @return
+ */
+ public int executeUpdate(String query) {
+ initialize();
+
+ try {
+ this.Stmt = this.connection.createStatement();
+ return this.Stmt.executeUpdate(query);
+ } catch (SQLException ex) {
+ log.severe("[" + this.database.toString() + " Database] Could not execute query: " + ex);
+ }
+
+ return 0;
+ }
+
+ /**
+ * Execute Query with variables.
+ *
+ * @param query
+ * @param variables
+ * @return
+ */
+ public int executeUpdate(String query, Object... variables) {
+ initialize();
+
+ try {
+ this.Statement = this.connection.prepareStatement(query);
+ int i = 1;
+
+ for (Object obj : variables) {
+ this.Statement.setObject(i, obj);
+ i++;
+ }
+
+ return this.Statement.executeUpdate();
+ } catch (SQLException ex) {
+ log.severe("[" + this.database.toString() + " Database] Could not execute query: " + ex);
+ }
+
+ return 0;
+ }
+
+ public void close() {
+ try {
+ if (this.Statement != null) {
+ this.Statement.close();
+ }
+
+ if (this.ResultSet != null) {
+ this.ResultSet.close();
+ }
+
+ if (this.connection != null) {
+ this.connection.close();
+ }
+
+ } catch (SQLException ex) {
+ log.severe("[" + this.database.toString() + " Database] Failed to close connection: " + ex);
+
+ // Close anyway.
+ this.connection = null;
+ this.Statement = null;
+ this.ResultSet = null;
+ }
+ }
+
+ protected void finalize() {
+ close();
+ }
+}
diff --git a/src/main/java/de/gurkengewuerz/postfix_rest_send/objects/BruteforceFilter.java b/src/main/java/de/gurkengewuerz/postfix_rest_send/objects/BruteforceFilter.java
new file mode 100644
index 0000000..d7a28a2
--- /dev/null
+++ b/src/main/java/de/gurkengewuerz/postfix_rest_send/objects/BruteforceFilter.java
@@ -0,0 +1,71 @@
+package de.gurkengewuerz.postfix_rest_send.objects;
+
+import de.gurkengewuerz.postfix_rest_send.Main;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Created by gurkengewuerz.de on 13.07.2017.
+ */
+public class BruteforceFilter {
+ private final HashMap attempts = new HashMap<>();
+ private int maxAttemps = 3;
+
+ public BruteforceFilter(int maxAttemps) {
+ this.maxAttemps = maxAttemps;
+ Timer t = new Timer();
+ t.scheduleAtFixedRate(new TimerTask() {
+ @Override
+ public void run() {
+ try {
+ refresh();
+ } catch (SQLException e) {
+ Logger.getLogger(BruteforceFilter.class.getName()).log(Level.SEVERE, null, e);
+ }
+ }
+ }, 0, 2 * 60 * 1000);
+ }
+
+ public BruteforceFilter() {
+ this(3);
+ }
+
+ private void refresh() throws SQLException {
+ ResultSet rs = Main.getDatabase().executeQuery("SELECT ip, COUNT(*) count FROM token_bruteforce WHERE occurred > ? GROUP BY ip ORDER BY COUNT(*) DESC;", (System.currentTimeMillis() / 1000) - (24 * 60 * 60));
+ synchronized (attempts) {
+ attempts.clear();
+ while (rs.next()) {
+ attempts.put(rs.getString("ip"), rs.getInt("count"));
+ }
+ }
+ if (Main.getConfig().debug())
+ Logger.getLogger(getClass().getName()).log(Level.INFO, "refreshed bans (" + attempts.size() + ")");
+ }
+
+ public void failed(String ip) {
+ if (Main.getConfig().getBoolean("disable_bruteforcefilter")) return;
+ Main.getDatabase().executeUpdate("INSERT INTO token_bruteforce (ip, occurred) VALUES (?, ?);", ip, System.currentTimeMillis() / 1000);
+ synchronized (attempts) {
+ if (attempts.containsKey(ip)) {
+ attempts.replace(ip, attempts.get(ip) + 1);
+ } else {
+ attempts.put(ip, 1);
+ }
+ if (Main.getConfig().debug())
+ Logger.getLogger(getClass().getName()).log(Level.INFO, "banned " + ip + " attemp " + attempts.get(ip));
+ }
+ }
+
+ public boolean banned(String ip) {
+ if (Main.getConfig().getBoolean("disable_bruteforcefilter")) return false;
+ synchronized (attempts) {
+ return attempts.containsKey(ip) && attempts.get(ip) >= maxAttemps;
+ }
+ }
+}
diff --git a/src/main/java/de/gurkengewuerz/postfix_rest_send/objects/ReturnHolder.java b/src/main/java/de/gurkengewuerz/postfix_rest_send/objects/ReturnHolder.java
new file mode 100644
index 0000000..63c59dc
--- /dev/null
+++ b/src/main/java/de/gurkengewuerz/postfix_rest_send/objects/ReturnHolder.java
@@ -0,0 +1,11 @@
+package de.gurkengewuerz.postfix_rest_send.objects;
+
+public class ReturnHolder {
+ public final String[] output;
+ public final String[] error;
+
+ public ReturnHolder(String[] output, String[] error) {
+ this.output = output;
+ this.error = error;
+ }
+}
diff --git a/src/main/java/de/gurkengewuerz/postfix_rest_send/utils/BashUtils.java b/src/main/java/de/gurkengewuerz/postfix_rest_send/utils/BashUtils.java
new file mode 100644
index 0000000..aaf2626
--- /dev/null
+++ b/src/main/java/de/gurkengewuerz/postfix_rest_send/utils/BashUtils.java
@@ -0,0 +1,71 @@
+package de.gurkengewuerz.postfix_rest_send.utils;
+
+import de.gurkengewuerz.postfix_rest_send.objects.ReturnHolder;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Created by gurkengewuerz.de on 13.07.2017.
+ */
+public class BashUtils {
+ public static ReturnHolder run(boolean directPrint, String... command) {
+ try {
+ ProcessBuilder pb = new ProcessBuilder(command);
+ Process p = pb.start();
+ return getOutput(directPrint, p);
+ } catch (IOException ex) {
+ Logger.getLogger(BashUtils.class.getName()).log(Level.SEVERE, "error executing command", ex);
+ }
+ return new ReturnHolder(new String[0], new String[]{"exception"});
+ }
+
+ private static ReturnHolder getOutput(boolean print, Process P) {
+ try {
+ BufferedReader stdInput = new BufferedReader(new InputStreamReader(P.getInputStream()));
+ BufferedReader stdError = new BufferedReader(new InputStreamReader(P.getErrorStream()));
+ ArrayList output = new ArrayList<>();
+ ArrayList error = new ArrayList<>();
+ Thread t = null;
+ if (print) {
+ t = new Thread(() -> {
+ String s;
+ try {
+ while ((s = stdError.readLine()) != null) {
+ error.add(s);
+ System.out.println("ERR: " + s);
+ }
+ } catch (IOException ex) {
+ Logger.getLogger(BashUtils.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ });
+ }
+ String s;
+ while ((s = stdInput.readLine()) != null) {
+ output.add(s);
+ if (print) {
+ System.out.println("OUT: " + s);
+ }
+ }
+ if (t != null) {
+ if (t.isAlive()) {
+ t.stop();
+ }
+ }
+ while ((s = stdError.readLine()) != null) {
+ error.add(s);
+ if (print) {
+ System.out.println("ERR: " + s);
+ }
+ }
+ return new ReturnHolder(output.toArray(new String[output.size()]), error.toArray(new String[error.size()]));
+ } catch (IOException ex) {
+ Logger.getLogger(BashUtils.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ return new ReturnHolder(new String[0], new String[]{"exception"});
+ }
+}
diff --git a/src/main/java/de/gurkengewuerz/postfix_rest_send/utils/RandomUtils.java b/src/main/java/de/gurkengewuerz/postfix_rest_send/utils/RandomUtils.java
new file mode 100644
index 0000000..51c6db5
--- /dev/null
+++ b/src/main/java/de/gurkengewuerz/postfix_rest_send/utils/RandomUtils.java
@@ -0,0 +1,21 @@
+package de.gurkengewuerz.postfix_rest_send.utils;
+
+import org.apache.commons.codec.digest.DigestUtils;
+
+import java.util.UUID;
+
+/**
+ * Created by gurkengewuerz.de on 13.07.2017.
+ */
+public class RandomUtils {
+ public static String rndToken() {
+ return rndToken("");
+ }
+
+ public static String rndToken(String extraSalt) {
+ String uuidFront = UUID.randomUUID().toString();
+ String uuidBack = UUID.randomUUID().toString();
+ String salted = uuidFront + System.currentTimeMillis() + uuidBack + extraSalt;
+ return DigestUtils.sha256Hex(salted);
+ }
+}