Mercurial Hosting > nabble
view src/nabble/model/PostByEmail.java @ 0:7ecd1a4ef557
add content
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Thu, 21 Mar 2019 19:15:52 -0600 |
parents | |
children |
line wrap: on
line source
package nabble.model; import fschmidt.db.DbDatabase; import fschmidt.util.mail.Mail; import fschmidt.util.mail.MailAddress; import fschmidt.util.mail.MailException; import fschmidt.util.mail.MailHome; import fschmidt.util.mail.MailIterator; import fschmidt.util.mail.PlainTextContent; import fschmidt.util.mail.Pop3Server; import nabble.naml.compiler.Command; import nabble.naml.compiler.CommandSpec; import nabble.naml.compiler.IPrintWriter; import nabble.naml.compiler.Interpreter; import nabble.naml.compiler.Namespace; import nabble.naml.compiler.ScopedInterpreter; import nabble.naml.compiler.Template; import nabble.naml.compiler.TemplatePrintWriter; import nabble.naml.namespaces.BasicNamespace; import nabble.naml.namespaces.TemplateException; import nabble.view.lib.Permissions; import nabble.view.web.template.NabbleNamespace; import nabble.view.web.template.NodeNamespace; import nabble.view.web.template.UserNamespace; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Collections; import java.util.Date; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; @Namespace ( name = "post_by_email", global = true ) public final class PostByEmail { private static final Logger logger = LoggerFactory.getLogger(PostByEmail.class); // I would like to get rid of this. static final MailMessageFormat msgFmt = new MailMessageFormat('s', "subscription"); private static final Pop3Server pop3Server = (Pop3Server)Init.get("subscriptionsPop3Server"); static { if (Init.hasDaemons) { runSubscriptions(); } } private static class Lazy { static final String emailPrefix; static final String emailSuffix; static final Pattern pattern; static { String addrSpec = pop3Server.getUsername(); int ind = addrSpec.indexOf('@'); emailPrefix = addrSpec.substring(0, ind) + "+"; emailSuffix = addrSpec.substring(ind); pattern = Pattern.compile( "\\+s(\\d+)n(\\d+)h(\\d+)" + Pattern.quote(emailSuffix), Pattern.CASE_INSENSITIVE); } } private static void runSubscriptions() { if (pop3Server == null) { logger.error("Subscriptions: no pop3 specified for subscriptions"); return; } Executors.scheduleWithFixedDelay(new Runnable() { public void run() { try { processSubscriptions(); processBounces(); } catch(MailException e) { logger.error("mail processing",e); } } }, 10, 10, TimeUnit.SECONDS ); logger.info("Subscriptions: pop3 reading thread started"); } private static void processSubscriptions() { MailIterator mails = pop3Server.getMail(); try { while (mails.hasNext()) { Mail mail = mails.next(); try { new PostByEmail(mail).processMessage(); } catch (Exception e) { logger.error("mail:\n"+mail.getRawInput(),e); } } } finally { mails.close(); } } // begin non-static part private final Mail mail; private String email; private String messageId; private String address; private NodeImpl repliedToNode; private NodeImpl postedNode; private UserImpl mailAuthor; private PostByEmail(Mail mail) { this.mail = mail; } private void processMessage() { if( MailSubsystem.getReturnPath(mail).equals("") ) { logger.info("ignoring bounce"); return; } email = mail.getFrom().getAddrSpec(); String[] messageIds = mail.getHeader("Message-Id"); // returns both Id and ID messageId = messageIds!=null && messageIds.length==1 ? MailSubsystem.stripBrackets(messageIds[0]) : null; String[] a = mail.getHeader("Envelope-To"); if (a == null) a = mail.getHeader("X-Original-To"); // postfix if (a == null) a = mail.getHeader("X-Delivered-to"); // fastmail if (a.length > 1) a = new String[] { a[0] }; for( String s : a[0].split(",") ) { address = s.trim(); Matcher matcher = Lazy.pattern.matcher(address); if( matcher.find() ) { long siteId = Long.valueOf(matcher.group(1)); SiteImpl site = SiteKey.getInstance(siteId).site(); if( site != null ) { long nodeId = Long.valueOf(matcher.group(2)); repliedToNode = site.getNodeImpl(nodeId); if( repliedToNode != null ) { if( repliedToNode.getAssociatedMailingList() != null) { sendFailureMail( "Nabble", failureMessagePrefix() + "You can't post by email to a mailing list archive." ); continue; } mailAuthor = site.getUserImplFromEmail(email); if( mailAuthor != null && !generateHash(mailAuthor,repliedToNode).equals(matcher.group(3)) ) mailAuthor = null; callNaml(); continue; } } } //System.out.println("qqqqqqqqqqqqqqqqqqqqqqqqqqqqqq "+address); sendFailureMail( "Nabble", failureMessagePrefix() + "No forum exists for this address." ); } } private void callNaml() { Site site = repliedToNode.getSite(); Template template = site.getTemplate( "post by email", BasicNamespace.class, NabbleNamespace.class, PostByEmail.class ); template.run( TemplatePrintWriter.NULL, Collections.<String,Object>emptyMap(), new BasicNamespace(template), new NabbleNamespace(site), this ); } private String failureMessagePrefix() { return "Delivery to the following recipient failed permanently:\n\n" + " " + address + "\n\n" ; } private void sendFailureMail(String fromName,String failureMessage) { /* why? MailSubsystem.bounce(mail, "Delivery to the following recipient failed permanently:\n\n " + address + "\n\n" + failureMessage + "\n" ); */ Mail bounce = MailHome.newMail(); bounce.setFrom( new MailAddress(ModelHome.noReply,fromName) ); bounce.setTo( new MailAddress(email) ); bounce.setSubject( "Delivery Status Notification (Failure)" ); bounce.setHeader( "X-Failed-Recipients", mail.getHeader("Envelope-To") ); StringBuilder content = new StringBuilder(); content .append( failureMessage ).append( "\n\n" ) .append( "----- Original message -----\n\n" ) .append( mail.getRawInput() ) ; bounce.setContent(new PlainTextContent(content.toString())); ModelHome.send(bounce); logger.warn("bouncing subscription mail for "+email); } private UserImpl mailAuthor() throws TemplateException { if( mailAuthor==null ) throw TemplateException.newInstance("subscription_processing_bad_user"); return mailAuthor; } private void saveToPost() throws TemplateException { logger.info("Processing email from: " + address); Date date = mail.getSentDate(); Date now = new Date(); if (date == null || date.compareTo(now) > 0 || date.getTime() < 0) { date = now; } String subject = mail.getSubject(); if (subject == null || subject.trim().length() == 0) subject = "(no subject)"; String message = mail.getRawInput(); message = message.replace("\000",""); // postgres can't handle 0 if( !msgFmt.isOk(message) ) throw TemplateException.newInstance("bad_mail"); UserImpl mailAuthor = mailAuthor(); logger.info("Making a post from a message from " + address); DbDatabase db = repliedToNode.siteKey.getDb(); db.beginTransaction(); try { postedNode = NodeImpl.newChildNode(Node.Kind.POST, mailAuthor, subject, message, msgFmt, repliedToNode); postedNode.setWhenCreated(date); if( messageId != null ) postedNode.setMessageID(messageId); postedNode.checkNewPostLimit(); postedNode.insert(true); db.commitTransaction(); } catch(ModelException e) { logger.error("Subscription processing failed: " + mail.getRawInput(), e); } finally { db.endTransaction(); } } // naml public static final CommandSpec thread_by_subject = new CommandSpec.Builder() .parameters("prefixes") .build() ; @Command public void thread_by_subject(IPrintWriter out,Interpreter interp) { if( repliedToNode.getKind() == Node.Kind.APP ) return; String subject = mail.getSubject(); if (subject == null) return; String prefixes = interp.getArgString("prefixes"); Pattern prefixRegex = MailingLists.prefixRegex(prefixes); if( MailingLists.normalizeSubject(subject,prefixRegex).equals(MailingLists.normalizeSubject(repliedToNode.getSubject(),prefixRegex)) ) return; NodeImpl app = repliedToNode.getAppImpl(); if( app != null ) repliedToNode = app; } @Command public void new_post_subject(IPrintWriter out,Interpreter interp) { out.print(mail.getSubject()); } public static final CommandSpec set_new_post_subject = new CommandSpec.Builder() .dotParameter("subject") .build() ; @Command public void set_new_post_subject(IPrintWriter out,Interpreter interp) { String newSubject = interp.getArgString("subject"); mail.setSubject(newSubject); } public static final CommandSpec save_to_post = CommandSpec.NO_OUTPUT; @Command public void save_to_post(IPrintWriter out,Interpreter interp) throws TemplateException { saveToPost(); } public static final CommandSpec send_failure_mail = CommandSpec.NO_OUTPUT() .dotParameter("text") .optionalParameters("from") .build() ; @Command public void send_failure_mail(IPrintWriter out,Interpreter interp) { String from = interp.getArgString("from"); if( from == null ) from = "Nabble"; String msg = interp.getArgString("text"); sendFailureMail(from,msg); } @Command public void email_from(IPrintWriter out,Interpreter interp) throws TemplateException { out.print( email ); } @Command public void email_to(IPrintWriter out,Interpreter interp) throws TemplateException { out.print( address ); } public static final CommandSpec replied_to_node = CommandSpec.DO; @Command public void replied_to_node(IPrintWriter out,ScopedInterpreter<NodeNamespace> interp) { out.print( interp.getArg( new NodeNamespace(repliedToNode), "do" ) ); } public static final CommandSpec posted_node = CommandSpec.DO; @Command public void posted_node(IPrintWriter out,ScopedInterpreter<NodeNamespace> interp) { out.print( interp.getArg( new NodeNamespace(postedNode), "do" ) ); } public static final CommandSpec mail_author = CommandSpec.DO; @Command public void mail_author(IPrintWriter out,ScopedInterpreter<UserNamespace> interp) throws TemplateException { out.print( interp.getArg( new UserNamespace(mailAuthor()), "do" ) ); } // end non-static part static String getMailAddress(User user, Node node) { Site site = user.getSite(); if( !node.getSite().equals(site) ) throw new RuntimeException(); return Lazy.emailPrefix + 's' + site.getId() + 'n' + node.getId() + 'h' + generateHash(user,node) + Lazy.emailSuffix ; } private static String generateHash(User user,Node node) { int h = user.hashCode(); h = 31*h + node.hashCode(); h = 31*h + 23; return Integer.toString(Math.abs(h)%100); } private static final Pop3Server bouncesPop3Server = (Pop3Server)Init.get("subscriptionBouncesPop3Server"); private static class LazyBounces { static final String emailPrefix; static final String emailSuffix; static final Pattern pattern; static { String addrSpec = bouncesPop3Server.getUsername(); int ind = addrSpec.indexOf('@'); emailPrefix = addrSpec.substring(0, ind) + "+"; emailSuffix = addrSpec.substring(ind); pattern = Pattern.compile( "\\+s(\\d+)u(\\d+)" + Pattern.quote(emailSuffix) , Pattern.CASE_INSENSITIVE ); } } private static synchronized void processBounces() { if( bouncesPop3Server == null ) { logger.error("subscriptionBouncesPop3Server not defined"); System.exit(-1); } MailIterator mails = bouncesPop3Server.getMail(); try { while( mails.hasNext() ) { Mail mail = mails.next(); try { processBounce(mail); } catch (Exception e) { logger.error("mail:\n"+mail.getRawInput(),e); } } } finally { mails.close(); } } private static void processBounce(Mail mail) { String[] envTo = mail.getHeader("Envelope-To"); if (envTo == null) envTo = mail.getHeader("X-Original-To"); // postfix if (envTo == null) envTo = mail.getHeader("X-Delivered-to"); // fastmail String originalTo = envTo[0]; Matcher matcher = LazyBounces.pattern.matcher(originalTo); if( !matcher.find() ) throw new RuntimeException("invalid email: "+originalTo); long siteId = Long.parseLong( matcher.group(1) ); long userId = Long.parseLong( matcher.group(2) ); SiteImpl site = SiteKey.getInstance(siteId).site(); UserImpl user = site.getUserImpl(userId); user.bounced(); logger.info(""+user+" has "+user.getBounces()+" bounces"); } static String getBouncesAddress(User user) { return LazyBounces.emailPrefix + 's' + user.getSite().getId() + 'u' + user.getId() + LazyBounces.emailSuffix ; } }