1 // Copyright 2000-2005 the Contributors, as shown in the revision logs.
2 // Licensed under the Apache Public Source License 2.0 ("the License").
3 // You may not use this file except in compliance with the License.
8 import org.ibex.io.Fountain;
9 import org.ibex.util.*;
10 import org.ibex.mail.filter.*;
11 import org.ibex.mail.target.*;
17 // - better matching syntax:
21 // - ==> {discard, refuse, bounce}
24 public class Script extends JS.Obj implements Target {
26 private static final JS.Method METHOD = new JS.Method();
28 private static Script root = null;
29 private static final String DEFAULT_CONF = Mailbox.STORAGE_ROOT + "conf" + File.separatorChar + "inbound.js";
30 public static Script root() {
32 if (root == null) root = new Script(System.getProperty("ibex.mail.conf", DEFAULT_CONF));
34 } catch (Exception e) {
35 Log.error(Script.class, e);
41 private Message m = null;
42 private String filePath = null;
43 public Script(String filePath) throws JSExn, IOException {
44 this.filePath = filePath;
45 js = JSU.cloneWithNewGlobalScope(JSU.fromReader(filePath, 1, new InputStreamReader(new FileInputStream(filePath))),
48 private class ScriptScope extends JS.Immutable {
49 ScriptEnv env = new ScriptEnv();
50 public JS get(JS name) throws JSExn {
53 case "ibex": return env;
60 public void accept(Message m) throws IOException, MailException {
62 new Script(filePath).reallyAccept(m);
65 throw new MailException(e.toString());
69 private synchronized void reallyAccept(Message m) throws IOException, MailException, JSExn {
72 Object ret = js.call(null, new JS[] { m });
73 Log.warn(this, "configuration script returned " + ret);
74 if (ret == null) throw new IOException("configuration script returned null");
75 while (ret instanceof JSReflection.Wrapper) ret = ((JSReflection.Wrapper)ret).unwrap();
76 if (ret instanceof Target) ((Target)ret).accept(m);
77 //else if (ret instanceof Filter) ((Filter)ret).process(m);
78 else throw new IOException("configuration script returned a " + ret.getClass().getName());
81 throw new IOException("configuration script threw an exception");
85 // FIXME: this should extend org.ibex.core.Ibex
86 public class ScriptEnv extends JS.Obj {
88 private PropertyFile prefs = null;
93 prefs = new PropertyFile(new File("/etc/org.ibex.mail.properties"));
94 } catch (IOException e) {
95 Log.error(ScriptEnv.class, e);
100 /** lets us put multi-level get/put/call keys all in the same method */
101 private class Sub extends JS.Immutable {
103 Sub(String key) { this.key = key; }
104 public void put(JS key, JS val) throws JSExn {
105 ScriptEnv.this.put(JSU.S(this.key + "." + JSU.toString(key)), val); }
106 public JS get(JS key) throws JSExn { return ScriptEnv.this.get(JSU.S(this.key + "." + JSU.toString(key))); }
107 public JS call(JS method, JS[] args) throws JSExn {
108 return ScriptEnv.this.call(JSU.S(this.key + "." + JSU.toString(method)), args);
111 private Sub getSub(String s) { return new Sub(s); }
113 public JS get(JS name) throws JSExn {
115 case "math": return ibexMath;
116 case "string": return ibexString;
117 case "date": return METHOD;
118 case "regexp": return METHOD;
119 case "log": return getSub("log");
120 case "log.debug": return METHOD;
121 case "log.info": return METHOD;
122 case "log.warn": return METHOD;
123 case "log.error": return METHOD;
124 case "list": return getSub("list");
125 case "url": return getSub("url");
126 case "url.encode": return METHOD;
127 case "mail": return getSub("mail");
128 case "mail.forward": return METHOD;
129 case "mail.forward2": return METHOD;
130 case "mail.send": return METHOD;
131 case "mail.attempt": return METHOD;
132 case "mail.later": return Later.instance;
133 case "mail.drop": return METHOD;
134 case "mail.razor": return getSub("mail.razor");
135 case "mail.razor.check": return METHOD;
136 case "mail.procmail": /* FEATURE */ return null;
137 case "mail.vacation": /* FEATURE */ return null;
138 case "mail.dcc": return getSub("mail.dcc");
139 case "mail.dcc.check": return METHOD;
140 case "mail.bounce": return METHOD;
141 case "mail.reject": return METHOD;
142 case "mail.my": return getSub("mail.my");
143 case "mail.dir": return METHOD;
144 case "mail.shell": return METHOD;
145 case "mail.my.prefs": try {
146 return new org.ibex.js.Directory(new File("/etc/org.ibex.mail.prefs"));
147 } catch (IOException e) { throw new JSExn(e.toString()); }
148 case "mail.whitelist": return JSReflection.wrap(org.ibex.mail.SMTP.whitelist);
149 case "mail.my.mailbox":
150 MailTree root = FileBasedMailbox.getFileBasedMailbox(Mailbox.STORAGE_ROOT, true);
151 return (JS)root.slash("user", true).slash("megacz", true);
152 case "mail.list": return METHOD;
154 return super.get(name);
157 public JS call(JS name0, JS[] args) throws JSExn {
158 final JS a = args.length >= 1 ? args[0] : null;
159 final JS b = args.length >= 2 ? args[1] : null;
160 final JS c = args.length >= 3 ? args[2] : null;
161 final int nargs = args.length;
162 String name = JSU.toString(name0);
164 if (name.equals("url.encode")) return JSU.S(java.net.URLEncoder.encode(JSU.toString(args[0])));
165 if (name.equals("mail.list")) return JSReflection.wrap(FileBasedMailbox.getFileBasedMailbox(JSU.toString(args[0]), false));
166 if (name.equals("mail.dir")) {
167 return new org.ibex.js.Directory(new File(JSU.toString(args[0])));
169 if (name.equals("mail.shell")) {
171 Log.warn("dbug", a.getClass().getName());
172 Log.warn("dbug", b.getClass().getName());
173 Message m = (Message)b;
174 final Process p = Runtime.getRuntime().exec(JSU.toString(a));
175 Main.threadPool.start(new Runnable() {
178 BufferedReader br = new BufferedReader(new InputStreamReader(p.getErrorStream()));
180 while((s = br.readLine())!=null)
181 Log.warn("shell", s);
182 } catch (Exception e) { e.printStackTrace(); }
185 OutputStream os = p.getOutputStream();
186 Stream stream = new Stream(os);
188 // why do I need to go via an sb here?
189 StringBuffer sb = new StringBuffer();
190 m.getBody().getStream().transcribe(sb);
191 stream.println(sb.toString());
198 if (name.equals("date")) { return new JSDate(args); }
199 if (name.equals("mail.send") || name.equals("send") || name.equals("mail.attempt") || name.equals("attempt")) {
200 boolean attempt = name.equals("mail.attempt") || name.equals("attempt");
203 Address from = null, to = null, envelopeFrom = null, envelopeTo = null;
204 JS.Enumeration e = m.keys();
205 Headers headers = new Headers();
206 for(; e.hasNext();) {
207 JS key = (JS)e.next();
208 JS val = m.get(key) == null ? null : m.get(key);
209 if ("body".equalsIgnoreCase(JSU.toString(key))) body = JSU.toString(val);
210 else headers = new Headers(headers, new String[] { JSU.toString(key), JSU.toString(val) });
211 if ("from".equalsIgnoreCase(JSU.toString(key))) from = Address.parse(JSU.toString(val));
212 if ("to".equalsIgnoreCase(JSU.toString(key))) to = Address.parse(JSU.toString(val));
213 if ("envelopeFrom".equalsIgnoreCase(JSU.toString(key))) envelopeFrom = Address.parse(JSU.toString(val));
214 if ("envelopeTo".equalsIgnoreCase(JSU.toString(key))) envelopeTo = Address.parse(JSU.toString(val));
216 if (envelopeTo == null) envelopeTo = to;
217 if (envelopeFrom == null) envelopeFrom = from;
219 Message.newMessageFromHeadersAndBody(headers, Fountain.Util.create(body), envelopeFrom, envelopeTo);
224 org.ibex.mail.SMTP.Outgoing.attempt(message);
226 org.ibex.mail.SMTP.Outgoing.enqueue(message);
229 } catch (Exception ex) {
230 if (!attempt) Log.warn(this, ex);
232 if (!ok && !attempt) throw new JSExn("SMTP server rejected message");
235 if (name.equals("mail.razor.check")) {
236 Process p = Runtime.getRuntime().exec("razor-check");
237 ((Message)args[0]).getStream().transcribe(new Stream(p.getOutputStream()), true);
238 return JSU.N(p.waitFor());
240 if (name.equals("mail.dcc.check")) {
241 Process p = Runtime.getRuntime().exec(new String[] { "dccproc", "-H" });
242 ((Message)args[0]).getStream().transcribe(new Stream(p.getOutputStream()), true);
243 StringBuffer ret = new StringBuffer();
244 new Stream(p.getInputStream()).transcribe(ret);
246 String result = ret.toString();
247 Log.warn("dcc", ((Message)args[0]).summary() + ":\n " + result);
251 int i_body = result.indexOf("Body=");
252 int i_fuz1 = result.indexOf("Fuz1=");
253 int i_fuz2 = result.indexOf("Fuz2=");
254 if (i_body != -1) try {
255 String s = result.substring(i_body+5);
256 if (s.indexOf(' ') != -1) s = s.substring(0, s.indexOf(' '));
257 if (s.indexOf('\n') != -1) s = s.substring(0, s.indexOf('\n'));
258 body = s.equals("many") ? 999 : Integer.parseInt(s.trim());
259 } catch (Exception e) { Log.error("", e); }
260 if (i_fuz1 != -1) try {
261 String s = result.substring(i_fuz1+5);
262 if (s.indexOf(' ') != -1) s = s.substring(0, s.indexOf(' '));
263 if (s.indexOf('\n') != -1) s = s.substring(0, s.indexOf('\n'));
264 fuz1 = s.equals("many") ? 999 : Integer.parseInt(s.trim());
265 } catch (Exception e) { Log.error("", e); }
266 if (i_fuz2 != -1) try {
267 String s = result.substring(i_fuz2+5);
268 if (s.indexOf(' ') != -1) s = s.substring(0, s.indexOf(' '));
269 if (s.indexOf('\n') != -1) s = s.substring(0, s.indexOf('\n'));
270 fuz2 = s.equals("many") ? 999 : Integer.parseInt(s.trim());
271 } catch (Exception e) { Log.error("", e); }
272 JSArray jsa = new JSArray();
273 jsa.put(JSU.N(0), JSU.N(body));
274 jsa.put(JSU.N(1), JSU.N(fuz1));
275 jsa.put(JSU.N(2), JSU.N(fuz2));
278 if (name.equals("mail.drop")) {
279 return args.length==0 ? new Drop() : new Drop(JSU.toString(args[0]));
281 if (name.equals("mail.bounce")) {
282 return new JSTarget() {
283 public void accept(Message m) throws MailException {
285 Message m2 = m.bounce(JSU.toString(a));
286 org.ibex.mail.SMTP.Outgoing.enqueue(m2);
287 Log.error(this, "BOUNCING! " + m2.summary());
288 } catch (Exception e) {
293 if (name.equals("mail.forward2") || name.equals("forward2")) {
295 Message m2 = m.withEnvelope(m.envelopeFrom, new Address(JSU.toString(a)));
296 org.ibex.mail.SMTP.Outgoing.enqueue(m2);
297 } catch (Exception e) {
299 throw new JSExn(e.toString());
303 if (name.equals("mail.forward") || name.equals("forward")) {
304 Message m2 = Script.this.m.withEnvelope(Script.this.m.envelopeFrom, new Address(JSU.toString(a)));
305 org.ibex.mail.SMTP.Outgoing.attempt(m2, false);
306 return Drop.instance;
308 if (name.equals("mail.reject"))
309 return new Reject(JSU.toString(a));
310 if (name.equals("log.debug") || name.equals("debug")) { JSU.debug(a== null ? "**null**" : JSU.toString(a)); return null; }
311 if (name.equals("log.info") || name.equals("info")) { JSU.info(a== null ? "**null**" : JSU.toString(a)); return null; }
312 if (name.equals("log.warn") || name.equals("warn")) { JSU.warn(a== null ? "**null**" : JSU.toString(a)); return null; }
313 if (name.equals("log.error") || name.equals("error")) { JSU.error(a== null ? "**null**" : JSU.toString(a)); return null; }
316 if (name.equals("regexp")) {return new JSRegexp(a, null); }
319 if (name.equals("regexp")) {return new JSRegexp(a, b); }
321 } catch (MailException e) { throw e;
322 } catch (Exception e) {
323 Log.warn(this, "ibex."+name+"() threw: " + e);
325 if (e instanceof JSExn) throw ((JSExn)e);
326 throw new JSExn("invalid argument for ibex object method "+JSU.toString(name0)+"()");
328 throw new JSExn("invalid number of arguments ("+nargs+") for ibex object method "+JSU.toString(name0)+"()");
331 public final JSMath ibexMath = new JSMath() {
332 public JS get(JS name) throws JSExn {
335 case "isNaN": return gs.get(name);
336 case "isFinite": return gs.get(name);
337 case "NaN": return gs.get(name);
338 case "Infinity": return gs.get(name);
344 public final JS ibexString = new JS.Immutable() {
345 public JS get(JS name) throws JSExn {
348 case "parseInt": return gs.get(JSU.S("parseInt"));
349 case "parseFloat": return gs.get(JSU.S("parseFloat"));
350 case "decodeURI": return gs.get(JSU.S("decodeURI"));
351 case "decodeURIComponent": return gs.get(JSU.S("decodeURIComponent"));
352 case "encodeURI": return gs.get(JSU.S("encodeURI"));
353 case "encodeURIComponent": return gs.get(JSU.S("encodeURIComponent"));
354 case "escape": return gs.get(JSU.S("escape"));
355 case "unescape": return gs.get(JSU.S("unescape"));
356 case "fromCharCode": return gs.get(JSU.S("stringFromCharCode"));
363 private static abstract class JSTarget extends JS.Obj implements Target { }
365 public static class Drop extends JS.Obj implements Target {
366 public static final Drop instance = new Drop();
367 public final String reason;
368 public Drop() { this(null); }
369 public Drop(String reason) { this.reason = reason; }
370 public void accept(Message m) throws IOException, MailException {
371 Log.warn(this, "dropping" +(reason==null?"":(" because "+reason))+ ": " + m.summary());
375 public static class Later extends JS.Obj implements Target {
376 public static final Later instance = new Later();
377 public static class LaterException extends RuntimeException { }
378 public void accept(Message m) throws IOException, MailException {
379 Log.warn(this, "delaying message " + m.summary());
380 throw new LaterException();
384 /** a fast-write, slow-read place to stash all messages we touch -- in case of a major f*ckup */
385 public static class Transcript implements Target {
387 public static final Transcript transcript = new Transcript(Mailbox.STORAGE_ROOT + File.separatorChar + "transcript");
390 public Transcript(String path) { new File(this.path = path).mkdirs(); }
391 private static String lastTime = null;
392 private static int lastCounter = 0;
394 public synchronized void accept(Message message) {
396 File today = new File(path + File.separatorChar + (new SimpleDateFormat("yy-MMM-dd").format(new Date())));
399 String time = new SimpleDateFormat("HH:mm:ss").format(new Date());
400 synchronized (Transcript.class) {
401 if (lastTime != null && lastTime.equals(time)) {
402 time += "." + (++lastCounter);
409 File target = new File(today.getPath() + File.separatorChar + time + ".txt");
410 OutputStream os = new FileOutputStream(target);
412 message.getStream().transcribe(new Stream(os));
414 } finally { os.close(); }
415 } catch (IOException e) { throw new MailException.IOException(e); }
419 public static class Reject extends JS.Obj implements Target {
420 public final String reason;
421 public Reject(String reason) { this.reason = reason; }
422 public void accept(Message m) throws IOException, MailException {
423 throw new RejectException(m, reason);
425 public static class RejectException extends RuntimeException {
426 public final Message m;
427 public final String reason;
428 public RejectException(Message m, String reason) { this.m = m; this.reason = reason; }