diff --git a/pom.xml b/pom.xml
index c2a2917..396ef35 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
org.example
Widerhall
- 0.2.21
+ 0.2.22
diff --git a/src/main/java/de/srsoftware/widerhall/data/Database.java b/src/main/java/de/srsoftware/widerhall/data/Database.java
index c7af879..b6c3691 100644
--- a/src/main/java/de/srsoftware/widerhall/data/Database.java
+++ b/src/main/java/de/srsoftware/widerhall/data/Database.java
@@ -183,6 +183,11 @@ public class Database {
}
}
+
+ public void run() throws SQLException {
+ compile().run();
+ }
+
/**
* set single value for a certain key
* @param key
@@ -194,6 +199,11 @@ public class Database {
return this;
}
+ public Request sort(String field) {
+ sortFields.add(field);
+ return this;
+ }
+
/**
* get the current (i.e. non-final) sql
* @return
@@ -251,11 +261,6 @@ public class Database {
list.add(value);
return this;
}
-
- public Request sort(String field) {
- sortFields.add(field);
- return this;
- }
}
public Database(Connection connection) {
diff --git a/src/main/java/de/srsoftware/widerhall/data/ListMember.java b/src/main/java/de/srsoftware/widerhall/data/ListMember.java
index b0c5554..f17f8b7 100644
--- a/src/main/java/de/srsoftware/widerhall/data/ListMember.java
+++ b/src/main/java/de/srsoftware/widerhall/data/ListMember.java
@@ -406,6 +406,10 @@ public class ListMember {
}
}
+ public static void updateListEmail(String oldEmail, String newEmail) throws SQLException {
+ Database.open().update(TABLE_NAME).set(LIST_EMAIL,newEmail).where(LIST_EMAIL,oldEmail).compile().run();
+ }
+
public User user(){
return user;
}
diff --git a/src/main/java/de/srsoftware/widerhall/data/MailingList.java b/src/main/java/de/srsoftware/widerhall/data/MailingList.java
index 15a2eb4..ada0285 100644
--- a/src/main/java/de/srsoftware/widerhall/data/MailingList.java
+++ b/src/main/java/de/srsoftware/widerhall/data/MailingList.java
@@ -3,14 +3,12 @@ package de.srsoftware.widerhall.data;
import de.srsoftware.widerhall.Configuration;
import de.srsoftware.widerhall.mail.ImapClient;
import de.srsoftware.widerhall.mail.MessageHandler;
+import de.srsoftware.widerhall.mail.ProblemListener;
import de.srsoftware.widerhall.mail.SmtpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import javax.mail.Address;
-import javax.mail.Flags;
-import javax.mail.Message;
-import javax.mail.MessagingException;
+import javax.mail.*;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import java.io.*;
@@ -27,7 +25,7 @@ import static de.srsoftware.widerhall.data.User.PERMISSION_ADMIN;
/**
* this class encapsulates a MailingList db object
*/
-public class MailingList implements MessageHandler {
+public class MailingList implements MessageHandler, ProblemListener {
public static final String KEY_FORWARD_FROM = "forward_from";
public static final String KEY_FORWARD_ATTACHED = "forward_attached";
public static final String KEY_HIDE_RECEIVERS = "hide_receivers";
@@ -61,11 +59,11 @@ public class MailingList implements MessageHandler {
private static final int HIDDEN = 0;
private static final int DEFAULT_STATE = STATE_PENDING|STATE_HIDE_RECEIVERS|STATE_PUBLIC_ARCHIVE;
private static final String RETAINED_FOLDER = "retained";
- private final String name;
- private final String email;
+ private static final String LAST_ERROR = "last_error";
+ private String email, name, lastError;
private int state;
- private final SmtpClient smtp;
- private final ImapClient imap;
+ private SmtpClient smtp;
+ private ImapClient imap;
private static final HashMap cache = new HashMap<>();
@@ -88,7 +86,7 @@ public class MailingList implements MessageHandler {
this.name = name;
this.state = state;
this.smtp = new SmtpClient(smtpHost,smtpPort,smtpUser,smtpPass);
- this.imap = new ImapClient(imapHost,imapPort,imapUser,imapPass,inbox);
+ this.imap = new ImapClient(imapHost,imapPort,imapUser,imapPass,inbox,this);
}
public MailingList archive(boolean enabled) throws SQLException {
@@ -133,7 +131,8 @@ public class MailingList implements MessageHandler {
.append(SMTP_PORT).append(" ").append(INT).append(", ")
.append(SMTP_USER).append(" ").append(VARCHAR).append(", ")
.append(SMTP_PASS).append(" ").append(VARCHAR).append(", ")
- .append(STATE).append(" ").append(INT)
+ .append(STATE).append(" ").append(INT).append(", ")
+ .append(LAST_ERROR).append(" ").append(TEXT)
.append(");");
Database.open().query(sql).compile().run();
}
@@ -146,7 +145,10 @@ public class MailingList implements MessageHandler {
public MailingList enable(boolean enable) throws SQLException {
if (!enable) imap.stop();
setFlag(STATE_ENABLED,enable);
- if (enable) imap.start().addListener(this);
+ if (enable) {
+ setLastError(null);
+ imap.start().addListener(this);
+ }
return this;
}
@@ -273,12 +275,15 @@ public class MailingList implements MessageHandler {
public static MailingList load(String listEmail) {
if (listEmail == null) return null;
var ml = cache.get(listEmail);
- if (ml == null) try {
+ try {
var rs = Database.open()
.select(TABLE_NAME)
.where(EMAIL,listEmail)
.compile().exec();
- if (rs.next()) ml = MailingList.from(rs);
+ if (rs.next()) {
+ if (ml == null ) ml = MailingList.from(rs);
+ ml.lastError = rs.getString(LAST_ERROR);
+ }
} catch (SQLException e) {
LOG.debug("Failed to load MailingList: ",e);
}
@@ -352,6 +357,7 @@ public class MailingList implements MessageHandler {
map.put(EMAIL,Map.of(PREFIX,parts[0],DOMAIN,parts[1]));
map.put(NAME, name);
map.put(STATE, stateMap());
+ map.put(LAST_ERROR, lastError);
return map;
}
@@ -381,6 +387,16 @@ public class MailingList implements MessageHandler {
return name;
}
+ @Override
+ public void onImapException(MessagingException e) {
+ try {
+ if (e instanceof AuthenticationFailedException afe) this.enable(false);
+ setLastError(e.getMessage());
+ } catch (SQLException sqle) {
+ LOG.warn("Database error during onImapException ({})",e.getMessage(),sqle);
+ }
+ }
+
@Override
public void onMessageReceived(Message message) throws MessagingException {
LOG.info("Message received: {}",message.getFrom());
@@ -477,6 +493,7 @@ public class MailingList implements MessageHandler {
if (smtp.port() != 0) map.put(SMTP_PORT, smtp.port());
if (smtp.username() != null) map.put(SMTP_USER, smtp.username());
map.put(STATE,stateMap());
+ if (lastError != null) map.put(LAST_ERROR, lastError);
return map;
}
@@ -550,6 +567,10 @@ public class MailingList implements MessageHandler {
return this;
}
+ private void setLastError(String message) throws SQLException {
+ Database.open().update(TABLE_NAME).set(LAST_ERROR,message).where(EMAIL,email()).run();
+ }
+
public Map stateMap(){
var map = new HashMap();
if (hasState(STATE_ENABLED)) map.put("enabled",VISIBLE);
@@ -664,4 +685,32 @@ public class MailingList implements MessageHandler {
return new ArrayList().stream();
}
}
+
+ public void update(String name, String email, String imapHost, Integer imapPort, String imapUser, String imapPass, String inbox, String smtpHost, Integer smtpPort, String smtpUser, String smtpPass) throws SQLException {
+ imap.stop();
+ Database.open()
+ .update(TABLE_NAME)
+ .set(EMAIL, email)
+ .set(NAME, name)
+ .set(IMAP_HOST, imapHost)
+ .set(IMAP_PORT, imapPort)
+ .set(IMAP_USER, imapUser)
+ .set(IMAP_PASS, imapPass)
+ .set(INBOX,inbox)
+ .set(SMTP_HOST, smtpHost)
+ .set(SMTP_PORT, smtpPort)
+ .set(SMTP_USER, smtpUser)
+ .set(SMTP_PASS, smtpPass)
+ .where(EMAIL,email())
+ .compile()
+ .run();
+ if (!this.email.equals(email)){
+ ListMember.updateListEmail(this.email,email);
+ this.email = email;
+ }
+ this.name = name;
+ smtp = new SmtpClient(smtpHost,smtpPort,smtpUser,smtpPass);
+ imap = new ImapClient(imapHost,imapPort,imapUser,imapPass,inbox,this);
+ if (this.hasState(STATE_ENABLED)) imap.start().addListener(this);
+ }
}
diff --git a/src/main/java/de/srsoftware/widerhall/mail/ImapClient.java b/src/main/java/de/srsoftware/widerhall/mail/ImapClient.java
index bb364fd..4fe955f 100644
--- a/src/main/java/de/srsoftware/widerhall/mail/ImapClient.java
+++ b/src/main/java/de/srsoftware/widerhall/mail/ImapClient.java
@@ -14,6 +14,7 @@ public class ImapClient {
private static final Logger LOG = LoggerFactory.getLogger(ImapClient.class);
private final int port;
private final String host, username, password, folderName;
+ private final ProblemListener problemListener;
private IMAPFolder inbox;
private ListeningThread listeningThread;
private Heartbeat heartbeat;
@@ -47,6 +48,7 @@ public class ImapClient {
openInbox();
} catch (MessagingException e){
LOG.warn("Connection problem:",e);
+ problemListener.onImapException(e);
}
}
}
@@ -127,12 +129,13 @@ public class ImapClient {
}
}
- public ImapClient(String host, int port, String username, String password, String folderName) {
+ public ImapClient(String host, int port, String username, String password, String folderName,ProblemListener listener) {
this.host = host;
this.port = port;
this.username = username;
this.password = password;
this.folderName = folderName;
+ this.problemListener = listener;
}
public ImapClient addListener(MessageHandler messageHandler) {
diff --git a/src/main/java/de/srsoftware/widerhall/mail/ProblemListener.java b/src/main/java/de/srsoftware/widerhall/mail/ProblemListener.java
new file mode 100644
index 0000000..b22d747
--- /dev/null
+++ b/src/main/java/de/srsoftware/widerhall/mail/ProblemListener.java
@@ -0,0 +1,7 @@
+package de.srsoftware.widerhall.mail;
+
+import javax.mail.MessagingException;
+
+public interface ProblemListener {
+ public void onImapException(MessagingException e);
+}
diff --git a/src/main/java/de/srsoftware/widerhall/web/Web.java b/src/main/java/de/srsoftware/widerhall/web/Web.java
index ffac30f..b46cd29 100644
--- a/src/main/java/de/srsoftware/widerhall/web/Web.java
+++ b/src/main/java/de/srsoftware/widerhall/web/Web.java
@@ -21,6 +21,7 @@ import java.util.Map;
import static de.srsoftware.widerhall.Constants.*;
import static de.srsoftware.widerhall.Util.t;
import static de.srsoftware.widerhall.data.MailingList.*;
+import static de.srsoftware.widerhall.data.User.PERMISSION_ADMIN;
public class Web extends TemplateServlet {
public static final String WEB_ROOT = "/web";
@@ -29,6 +30,7 @@ public class Web extends TemplateServlet {
private static final String CSS = "css";
private static final Logger LOG = LoggerFactory.getLogger(Web.class);
private static final String ADMIN = "admin";
+ private static final String EDIT_LIST = "edit_list";
private static final String INSPECT = "inspect";
private static final String LOGIN = "login";
private static final String LOGOUT = "logout";
@@ -48,13 +50,10 @@ public class Web extends TemplateServlet {
private static final int PRIMARY_KEY_CONSTRAINT = 19;
private String addList(HttpServletRequest req, HttpServletResponse resp) {
-
- var o = req.getSession().getAttribute(USER);
- if (!(o instanceof User user)) {
- return redirectTo(LOGIN,resp);
- }
+ var user = Util.getUser(req);
+ if (user == null) return redirectTo(LOGIN,resp);
var data = new HashMap();
- data.put(USER, user);
+ data.put(USER, user.safeMap());
if (!user.hashPermission(User.PERMISSION_CREATE_LISTS)){
data.put(ERROR,t("You are not allowed to create new mailing lists!"));
@@ -128,7 +127,7 @@ public class Web extends TemplateServlet {
try {
var list = MailingList.create(email, name, imapHost, imapPort, imapUser, imapPass, inbox, smtpHost, smtpPort, smtpUser, smtpPass);
ListMember.create(list, user, ListMember.STATE_OWNER);
- return redirectTo(INDEX, resp);
+ return redirectTo(ADMIN, resp);
} catch (SQLException e) {
return t("Failed to create list '{}': {}", name, e.getMessage());
}
@@ -164,6 +163,91 @@ public class Web extends TemplateServlet {
if (error != null) resp.sendError(400,error);
}
+ public String editList(HttpServletRequest req, HttpServletResponse resp) {
+ var user = Util.getUser(req);
+ if (user == null) return redirectTo(LOGIN,resp);
+
+ var data = new HashMap();
+ data.put(USER,user.safeMap());
+
+ var list = Util.getMailingList(req);
+ data.put(LIST,list.safeMap());
+ try {
+ var allowed = user.hashPermission(PERMISSION_ADMIN) || ListMember.load(list,user).isOwner();
+ if (!allowed) return loadTemplate(ADMIN,data,resp);
+
+ var name = req.getParameter(NAME);
+ data.put(NAME, name);
+
+ var email = req.getParameter(EMAIL);
+ data.put(EMAIL, email);
+
+ var imapHost = req.getParameter(IMAP_HOST);
+ data.put(IMAP_HOST, imapHost);
+ var imapUser = req.getParameter(IMAP_USER);
+ data.put(IMAP_USER, imapUser);
+ var imapPass = req.getParameter(IMAP_PASS);
+ var inbox = req.getParameter(INBOX);
+ if (inbox == null || inbox.isBlank()) inbox = DEFAULT_INBOX;
+ data.put(INBOX, inbox);
+
+ var smtpHost = req.getParameter(SMTP_HOST);
+ data.put(SMTP_HOST, smtpHost);
+ var smtpUser = req.getParameter(SMTP_USER);
+ data.put(SMTP_USER, smtpUser);
+ var smtpPass = req.getParameter(SMTP_PASS);
+
+ Integer imapPort = 993;
+ data.put(IMAP_PORT, imapPort);
+
+ Integer smtpPort = 465;
+ data.put(SMTP_PORT, smtpPort);
+
+ if (name == null || name.isBlank() || email == null || email.isBlank()) {
+ data.put(ERROR, "List name and address are required!");
+ return loadTemplate(EDIT_LIST, data, resp);
+ }
+
+ if (!Util.isEmail(email)) {
+ data.put(ERROR, t("List email ({}) is not a valid email address!", email));
+ return loadTemplate(EDIT_LIST, data, resp);
+ }
+
+ if (imapHost == null || imapHost.isBlank() || imapUser == null || imapUser.isBlank() || imapPass == null || imapPass.isBlank()) {
+ data.put(ERROR, "IMAP credentials are required!");
+ return loadTemplate(EDIT_LIST, data, resp);
+ }
+
+
+ try {
+ imapPort = Integer.parseInt(req.getParameter(IMAP_PORT));
+ data.put(IMAP_PORT, imapPort);
+ } catch (NumberFormatException nfe) {
+ data.put(ERROR, t("'{}' is not a proper port number!", req.getParameter(IMAP_PORT)));
+ return loadTemplate(EDIT_LIST, data, resp);
+ }
+
+ if (smtpHost == null || smtpHost.isBlank() || smtpUser == null || smtpUser.isBlank() || smtpPass == null || smtpPass.isBlank()) {
+ data.put(ERROR, "SMTP credentials are required!");
+ return loadTemplate(EDIT_LIST, data, resp);
+ }
+
+ try {
+ smtpPort = Integer.parseInt(req.getParameter(SMTP_PORT));
+ data.put(SMTP_PORT, smtpPort);
+ } catch (NumberFormatException nfe) {
+ data.put(ERROR, t("'{}' is not a proper port number!", req.getParameter(SMTP_PORT)));
+ return loadTemplate(EDIT_LIST, data, resp);
+ }
+
+ list.update(name,email,imapHost,imapPort,imapUser,imapPass,inbox,smtpHost,smtpPort,smtpUser,smtpPass);
+ return loadTemplate(ADMIN,data,resp);
+ } catch (SQLException e) {
+ LOG.warn("Editing list {} by {} failed",list.email(),user.email(),e);
+ return t("Editing list {} by {} failed",list.email(),user.email());
+ }
+ }
+
private SQLException getCausingException(SQLException sqle) {
Throwable cause = sqle.getCause();
while (cause instanceof SQLException){
@@ -230,7 +314,10 @@ public class Web extends TemplateServlet {
if (user != null){
if (list != null) data.put(LIST,req.getParameter(LIST));
- //data.put(NOTES,notes);
+ switch (path){
+ case EDIT_LIST:
+ return editList(req,resp);
+ }
return loadTemplate(path,data,resp);
}
return redirectTo(LOGIN,resp);
@@ -263,6 +350,8 @@ public class Web extends TemplateServlet {
switch (path){
case ADD_LIST:
return addList(req,resp);
+ case EDIT_LIST:
+ return editList(req,resp);
case INSPECT:
return inspect(req,resp);
case LOGIN:
@@ -367,7 +456,7 @@ public class Web extends TemplateServlet {
try {
var user = User.create(email, name, pass);
- if (firstUser) user.addPermission(User.PERMISSION_ADMIN|User.PERMISSION_CREATE_LISTS);
+ if (firstUser) user.addPermission(PERMISSION_ADMIN|User.PERMISSION_CREATE_LISTS);
req.getSession().setAttribute("user",user);
return redirectTo(INDEX,resp);
} catch (SQLException e) {
diff --git a/static/templates/add_list.st b/static/templates/add_list.st
index 41a4a3e..b09b30e 100644
--- a/static/templates/add_list.st
+++ b/static/templates/add_list.st
@@ -9,66 +9,8 @@
«navigation()»
«userinfo()»
- Widerhall List Creation
«messages()»
-
+ Widerhall List Creation
+ «list_data_form()»