From: adam Date: Wed, 23 Feb 2005 04:38:20 +0000 (+0000) Subject: implemented bounce messages X-Git-Url: http://git.megacz.com/?a=commitdiff_plain;h=fc0c2c9d0d6fe19eabefa8b0c95e73040aa7e8f0;p=org.ibex.mail.git implemented bounce messages darcs-hash:20050223043820-5007d-09fd8cd503a883f7b84d70b945b64bae711a22ce.gz --- diff --git a/src/org/ibex/mail/ContentType.java b/src/org/ibex/mail/ContentType.java new file mode 100644 index 0000000..7fc7ca6 --- /dev/null +++ b/src/org/ibex/mail/ContentType.java @@ -0,0 +1,62 @@ +// Copyright 2000-2005 the Contributors, as shown in the revision logs. +// Licensed under the Apache Public Source License 2.0 ("the License"). +// You may not use this file except in compliance with the License. + +package org.ibex.mail; +import static org.ibex.mail.MailException.*; +import org.ibex.crypto.*; +import org.ibex.util.*; +import org.ibex.mail.protocol.*; +import org.ibex.io.*; +import org.ibex.js.*; +import java.util.*; +import java.net.*; +import java.io.*; + +// multipart/mixed -- default +// multipart/parallel -- order of components does not matter +// multipart/alternative -- same data, different versions +// multipart/digest -- default content-type of components is message/rfc822 +// message/rfc822 -- FIXME +// message/partial -- not supported; see RFC 2046, section 5.2.2 +// message/external-body -- not supported; see RFC 2046, section 5.2.3 +// FIXME charsets US-ASCII, ISO-8559-X, +public class ContentType extends org.ibex.js.JSReflection { + public final String type; + public final String subtype; + public final String description; + public final String id; + public final String transferEncoding; + public final String charset; + public final boolean composite; + public final boolean alternative; + public final Hash parameters = new Hash(); + public ContentType(String header, String description, String id, String transferEncoding) { + this.id = id; + this.description = description; + this.transferEncoding = transferEncoding; + if (header == null) { type="text"; subtype="plain"; charset="us-ascii"; alternative=false; composite=false; return; } + header = header.trim(); + if (header.indexOf('/') == -1) { + Log.warn(this, "content-type lacks a forward slash: \""+header+"\""); + header = "text/plain"; + } + type = header.substring(0, header.indexOf('/')).toLowerCase(); + header = header.substring(header.indexOf('/') + 1); + subtype = (header.indexOf(';') == -1) ? header.toLowerCase() : header.substring(0, header.indexOf(';')).toLowerCase(); + composite = type != null && (type.equals("message") || type.equals("multipart")); + alternative = composite && subtype.equals("alternative"); + charset = parameters.get("charset") == null ? "us-ascii" : parameters.get("charset").toString(); + if (header.indexOf(';') == -1) return; + StringTokenizer st = new StringTokenizer(header.substring(header.indexOf(';') + 1), ";"); + while(st.hasMoreTokens()) { + String key = st.nextToken().trim(); + if (key.indexOf('=') == -1) + throw new Malformed("content-type parameter lacks an equals sign: \""+key+"\""); + String val = key.substring(key.indexOf('=')+1).trim(); + if (val.startsWith("\"") && val.endsWith("\"")) val = val.substring(1, val.length() - 2); + key = key.substring(0, key.indexOf('=')+1).toLowerCase(); + parameters.put(key, val); + } + } +} diff --git a/src/org/ibex/mail/Headers.java b/src/org/ibex/mail/Headers.java new file mode 100644 index 0000000..70ad93c --- /dev/null +++ b/src/org/ibex/mail/Headers.java @@ -0,0 +1,124 @@ +// Copyright 2000-2005 the Contributors, as shown in the revision logs. +// Licensed under the Apache Public Source License 2.0 ("the License"). +// You may not use this file except in compliance with the License. + +package org.ibex.mail; +import static org.ibex.mail.MailException.*; +import org.ibex.crypto.*; +import org.ibex.util.*; +import org.ibex.mail.protocol.*; +import org.ibex.js.*; +import org.ibex.io.*; +import org.ibex.io.Fountain; +import java.util.*; +import java.net.*; +import java.io.*; + +public class Headers extends JS.Immutable implements Fountain { + private final Hash head = new Hash(); + private final Hash headModified = new Hash(); + public int lines; + public final boolean mime; + + private String raw; + private StringFountain fountain; + + public String get(String s) { + String ret = (String)headModified.get(s.toLowerCase()); + if (ret==null) ret = (String)head.get(s.toLowerCase()); + return ret; + } + public void put(String k, String v) { + Stream stream = getStream(); + StringBuffer all = new StringBuffer(); + int lines = 0; + boolean good = false; + String key = null; + for(String s = stream.readln(); s != null && !s.equals(""); s = stream.readln()) { + if (Character.isSpace(s.charAt(0))) { all.append(s); all.append("\r\n"); lines++; continue; } + if (s.indexOf(':') == -1) throw new Malformed("Header line does not contain colon: " + s); + key = s.substring(0, s.indexOf(':')).toLowerCase(); + lines++; + if (key.toLowerCase().equals(k.toLowerCase())) { + good = true; + all.append(k + ": " + v + "\r\n"); + continue; + } + all.append(s); + all.append("\r\n"); + } + if (!good) { + lines++; + all.append(k + ": " + v + "\r\n"); + } + this.raw = all.toString(); + this.lines = lines; + this.fountain = new Fountain.StringFountain(this.raw); + } + public JS get(JS s) throws JSExn { return JSU.S(get(JSU.toString(s).toLowerCase())); } + + public Stream getStream() { return fountain.getStream(); } + public int getLength() { return fountain.getLength(); } + public int getNumLines() { return fountain.getNumLines(); } + public Stream getStreamWithCRLF() { return new Stream(raw+"\r\n"); } + + // FIXME + public String getString() { return raw; } + + public Headers(Stream stream) throws Malformed { this(stream, false); } + public Headers(Stream stream, boolean assumeMime) throws Malformed { + StringBuffer all = new StringBuffer(); + String key = null; + int lines = 0; + for(String s = stream.readln(); s != null && !s.equals(""); s = stream.readln()) { + all.append(s); + all.append("\r\n"); + lines++; + if (Character.isSpace(s.charAt(0))) { + if (key == null) throw new Malformed("Message began with a blank line; no headers"); + head.put(key, head.get(key) + " " + s.trim()); + continue; + } + if (s.indexOf(':') == -1) throw new Malformed("Header line does not contain colon: " + s); + key = s.substring(0, s.indexOf(':')).toLowerCase(); + for(int i=0; i 126) + throw new Malformed("Header key \""+key+"\" contains invalid character \"" + key.charAt(i) + "\""); + String val = s.substring(s.indexOf(':') + 1).trim(); + if (get(key) != null) val = get(key) + " " + val; // just append it to the previous one; + head.put(key, val); + } + this.raw = all.toString(); + this.fountain = new Fountain.StringFountain(this.raw); + this.lines = lines; + this.mime = assumeMime | (get("mime-version") != null && get("mime-version").trim().equals("1.0")); + /* + java.util.Enumeration e = head.keys(); + while(e.hasNext()) { + String k = (String)e.next(); + String v = (String)head.get(k); + if (mime) k = Encode.RFC2047.decode(k); + v = uncomment(v); + if (mime) v = Encode.RFC2047.decode(v); + head.put(k, v); + } + */ + } + + // Helpers ////////////////////////////////////////////////////////////////////////////// + + public static Stream skip(Stream stream) { + for(String s = stream.readln(); s!=null && s.length() > 0;) s = stream.readln(); + return stream; + } + + public static String uncomment(String val) { + boolean inquotes = false; + for(int i=0; i"); + h.put("From", "MAILER-DAEMON"); + h.put("To", envelopeFrom.toString()); + h.put("Subject", "failure notice"); + + String error = + "Hi. This is the Ibex Mail Server. I'm afraid I wasn't able to deliver\r\n"+ + "your message to the following addresses. This is a permanent error;\r\n"+ + "I've given up. Sorry it didn't work out\r\n."+ + "\r\n"+ + "<"+envelopeTo.toString()+">:\r\n"+ + reason+"\r\n"+ + "\r\n"+ + "--- Below this line is a copy of the message.\r\n"+ + "\r\n"; + + try { + return newMessage(new Fountain.Concatenate(new Fountain.StringFountain(h.getString()+"\r\n"+error), getBody())); + } catch (Message.Malformed e) { + Log.error(this, "caught Message.Malformed in Message.bounce(); this should never happen"); + Log.error(this, e); + return null; + } + } public String toString() { throw new RuntimeException("Message.toString() called"); } public String summary() { return "[" + envelopeFrom + " -> " + envelopeTo + "] " + subject; } diff --git a/src/org/ibex/mail/protocol/IMAP.java b/src/org/ibex/mail/protocol/IMAP.java index 3a02998..d8ada8d 100644 --- a/src/org/ibex/mail/protocol/IMAP.java +++ b/src/org/ibex/mail/protocol/IMAP.java @@ -42,6 +42,13 @@ public class IMAP { public IMAP() { } public static final float version = (float)0.2; + // FIXME this is evil + public static String getBodyString(Message m) { + StringBuffer sb = new StringBuffer(); + m.getStream().transcribe(sb); + return sb.toString(); + } + // API Class ////////////////////////////////////////////////////////////////////////////// public static interface Client { @@ -227,7 +234,7 @@ public class IMAP { for(Mailbox.Iterator it = selected().iterator(q); it.next(); ) { Message message = ((spec & (BODYSTRUCTURE | ENVELOPE | INTERNALDATE | FIELDS | FIELDSNOT | RFC822 | RFC822TEXT | RFC822SIZE | HEADERNOT | HEADER)) != 0) ? it.cur() : null; - int size = message == null ? 0 : message.size(); + int size = message == null ? 0 : message.getLength(); client.fetch(it.num(), it.flags(), size, message, it.uid()); it.recent(false); } @@ -437,9 +444,9 @@ public class IMAP { } else if (s.equals("FLAGS")) { spec|=FLAGS; if(e){r.append(" ");r.append(Printer.flags(flags));} } else if (s.equals("INTERNALDATE")) { spec|=INTERNALDATE; if(e){r.append(" ");r.append(Printer.date(m.arrival));} } else if (s.equals("RFC822")) { spec|=RFC822; if(e){r.append(" ");r.append(Printer.message(m));} - } else if (s.equals("RFC822.TEXT")) { spec|=RFC822TEXT; if(e){r.append(" ");r.append(Printer.qq(m.getBodyString()));} + } else if (s.equals("RFC822.TEXT")) { spec|=RFC822TEXT; if(e){r.append(" ");r.append(Printer.qq(getBodyString(m)));} } else if (s.equals("RFC822.HEADER")){ spec|=HEADER;if(e){r.append(" ");r.append(Printer.qq(m.headers.getString()+"\r\n"));} - } else if (s.equals("RFC822.SIZE")) { spec|=RFC822SIZE; if(e){r.append(" ");r.append(m.size());} + } else if (s.equals("RFC822.SIZE")) { spec|=RFC822SIZE; if(e){r.append(" ");r.append(m.getLength());} } else if (s.equals("UID")) { spec|=UID; if(e){r.append(" ");r.append(muid); } } else if (!(s.equals("BODY.PEEK") || s.equals("BODY"))) { throw new Server.No("unknown fetch argument: " + s); } else { @@ -455,9 +462,9 @@ public class IMAP { Parser.Token[] list = t[++i].l(); s = list.length == 0 ? "" : list[0].s.toUpperCase(); r.append(s); - if (list.length == 0) { spec |= RFC822TEXT; if(e) payload = m.headers.getString()+"\r\n"+m.getBodyString(); } - else if (s.equals("") || s.equals("1")) { spec |= RFC822TEXT; if(e) payload = m.headers.getString()+"\r\n"+m.getBodyString(); } - else if (s.equals("TEXT")) { spec |= RFC822TEXT; if(e) payload = m.getBodyString(); } + if (list.length == 0) { spec |= RFC822TEXT; if(e) payload = m.headers.getString()+"\r\n"+getBodyString(m); } + else if (s.equals("") || s.equals("1")) { spec |= RFC822TEXT; if(e) payload = m.headers.getString()+"\r\n"+getBodyString(m); } + else if (s.equals("TEXT")) { spec |= RFC822TEXT; if(e) payload = getBodyString(m); } else if (s.equals("HEADER")) { spec |= HEADER; if(e) payload = m.headers.getString()+"\r\n"; } else if (s.equals("HEADER.FIELDS")) { spec |= FIELDS; payload=headers(r,t[i].l()[1].sl(),false,m,e); } else if (s.equals("HEADER.FIELDS.NOT")) { spec |= FIELDSNOT; payload=headers(r,t[i].l()[1].sl(),true,m,e); } @@ -787,7 +794,7 @@ public class IMAP { } static String bodystructure(Message m) { // FIXME - return "(\"TEXT\" \"PLAIN\" (\"CHARSET\" \"ISO-8859-1\") NIL NIL \"7BIT\" "+m.size()+" "+m.lines()+")"; + return "(\"TEXT\" \"PLAIN\" (\"CHARSET\" \"ISO-8859-1\") NIL NIL \"7BIT\" "+m.getLength()+" "+m.getNumLines()+")"; } static String message(Message m) { return m.toString(); } static String date(Date d) { return "\""+d.toString()+"\""; } diff --git a/src/org/ibex/mail/protocol/NNTP.java b/src/org/ibex/mail/protocol/NNTP.java index f1291b6..ddaeeba 100644 --- a/src/org/ibex/mail/protocol/NNTP.java +++ b/src/org/ibex/mail/protocol/NNTP.java @@ -133,7 +133,7 @@ public class NNTP { if (head) println(a.message.headers.getString()); if (head && body) println(); if (body) { - Stream stream = a.message.getBody(); + Stream stream = a.message.getBody().getStream(); while(true) { s = stream.readln(); if (s == null) break; @@ -201,7 +201,7 @@ public class NNTP { try { Message m = it.cur(); println(it.num()+"\t"+m.subject+"\t"+m.from+"\t"+m.date+"\t"+m.messageid+"\t"+ - m.headers.get("references") + "\t" + m.size() + "\t" + m.lines()); + m.headers.get("references") + "\t" + m.getLength() + "\t" + m.getNumLines()); } catch (Exception e) { Log.error(this, e); } } println("."); diff --git a/src/org/ibex/mail/protocol/SMTP.java b/src/org/ibex/mail/protocol/SMTP.java index a26b2b9..f7463e8 100644 --- a/src/org/ibex/mail/protocol/SMTP.java +++ b/src/org/ibex/mail/protocol/SMTP.java @@ -192,7 +192,11 @@ public class SMTP { conn.println("HELO " + conn.vhost); check(conn.readln(), conn); } - conn.println("MAIL FROM:<" + m.envelopeFrom.user + "@" + m.envelopeFrom.host+">"); check(conn.readln(), conn); + if (m.envelopeFrom==null) { + conn.println("MAIL FROM:<>"); check(conn.readln(), conn); + } else { + conn.println("MAIL FROM:<" + m.envelopeFrom.user + "@" + m.envelopeFrom.host+">"); check(conn.readln(), conn); + } conn.println("RCPT TO:<" + m.envelopeTo.user + "@" + m.envelopeTo.host+">"); check(conn.readln(), conn); conn.println("DATA"); check(conn.readln(), conn); Stream stream = m.getStream(); @@ -214,6 +218,7 @@ public class SMTP { } catch (Exception e) { if (accepted) return true; Log.warn(SMTP.Outgoing.class, " unable to send; error=" + e); + Log.warn(SMTP.Outgoing.class, " message: " + m.summary()); Log.warn(SMTP.Outgoing.class, e); return false; } finally { diff --git a/src/org/ibex/mail/target/Drop.java b/src/org/ibex/mail/target/Drop.java new file mode 100644 index 0000000..c17c64a --- /dev/null +++ b/src/org/ibex/mail/target/Drop.java @@ -0,0 +1,17 @@ +// Copyright 2000-2005 the Contributors, as shown in the revision logs. +// Licensed under the Apache Public Source License 2.0 ("the License"). +// You may not use this file except in compliance with the License. + +package org.ibex.mail.target; +import java.io.*; +import org.ibex.js.*; +import org.ibex.util.*; +import org.ibex.mail.*; +import org.ibex.mail.target.*; + +public class Drop extends Target { + public static final Drop instance = new Drop(); + public void accept(Message m) throws IOException, MailException { + Log.warn(this, "dropping message " + m.summary()); + } +} diff --git a/src/org/ibex/mail/target/Script.java b/src/org/ibex/mail/target/Script.java index 912c732..d68d91e 100644 --- a/src/org/ibex/mail/target/Script.java +++ b/src/org/ibex/mail/target/Script.java @@ -115,6 +115,8 @@ public class Script extends Target { case "mail.forward": return METHOD; case "mail.forward2": return METHOD; case "mail.send": return METHOD; + case "mail.drop": return Drop.instance; + case "mail.bounce": return METHOD; case "mail.my": return getSub("mail.my"); case "mail.my.prefs": try { return new org.ibex.js.Directory(new File("/etc/org.ibex.mail.prefs")); @@ -163,6 +165,18 @@ public class Script extends Target { if (!ok) throw new JSExn("SMTP server rejected message"); return JSU.T; } + if (name.equals("mail.bounce")) { + return new Target() { + public void accept(Message m) throws MailException { + try { + Message m2 = m.bounce(JSU.toString(a)); + org.ibex.mail.protocol.SMTP.Outgoing.accept(m2); + Log.error(this, "BOUNCING! " + m2.summary()); + } catch (Exception e) { + Log.warn(this, e); + } + } }; + } if (name.equals("mail.forward2") || name.equals("forward2")) { try { Message m2 = Message.newMessage(new org.ibex.io.Fountain.StringFountain(m.toString()), @@ -175,16 +189,12 @@ public class Script extends Target { } return null; } - if (name.equals("mail.forward") || name.equals("forward")) { return new Target() { - public void accept(Message m) throws MailException { - try { - Message m2 = Message.newMessage(m, m.envelopeFrom, new Address(JSU.toString(a))); - org.ibex.mail.protocol.SMTP.Outgoing.accept(m2); - } catch (Exception e) { - throw new MailException(e.toString()); - } - } - }; } + if (name.equals("mail.forward") || name.equals("forward")) { + Message m = (Message)a; + Message m2 = Message.newMessage(m, m.envelopeFrom, new Address(JSU.toString(a))); + org.ibex.mail.protocol.SMTP.Outgoing.attempt(m2); + return Drop.instance; + } if (name.equals("log.debug") || name.equals("debug")) { JSU.debug(a== null ? "**null**" : JSU.toString(a)); return null; } if (name.equals("log.info") || name.equals("info")) { JSU.info(a== null ? "**null**" : JSU.toString(a)); return null; } if (name.equals("log.warn") || name.equals("warn")) { JSU.warn(a== null ? "**null**" : JSU.toString(a)); return null; }