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 javax.mail.Message; import java.io.File; import java.io.IOException; 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(conf.getInt("http_port")); Logger.getLogger(Main.class.getName()).log(Level.SEVERE, "Started Server :" + getListeningPort()); start(NanoHTTPD.SOCKET_READ_TIMEOUT, false); } 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_IMPLEMENTED; if (filter.banned(ip)) return getError(Response.Status.FORBIDDEN, "you are temporary banned"); 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':'" + 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("/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'}"); } } 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 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 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 getError(status, "unknown error"); } public static MySQL getDatabase() { return db; } public static Config getConfig() { return conf; } }