postfix-api/src/main/java/de/gurkengewuerz/postfix_rest_send/Main.java

255 lines
11 KiB
Java

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<String, String> headers = session.getHeaders();
String ip = headers.get("remote-addr");
Logger.getLogger(Main.class.getName()).log(Level.INFO, session.getMethod().name() + ": " + session.getUri() + " " + ip);
Map<String, String> 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<String> 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;
}
}