disabled List until we figure out how to deal with the prevayler dependency
[org.ibex.mail.git] / src / org / ibex / mail / Confirmation.java
1 package org.ibex.mail;
2 import java.lang.reflect.*;
3 //import org.prevayler.*;
4 import org.ibex.crypto.*;
5 import org.ibex.util.*;
6 import org.ibex.mail.protocol.*;
7 import org.ibex.io.*;
8 import java.util.*;
9 import java.util.zip.*;
10 import java.net.*;
11 import java.io.*;
12
13 /**
14  *  A convenient way to verify that the agent requesting an action
15  *  owns a particular email address.  Extend this class; the
16  *  Transaction.executeOn() method will be invoked when the user
17  *  clicks through.
18  */
19 public abstract class Confirmation implements Externalizable {
20
21     public static final long serialVersionUID = 0x981879f18a11ffeeL;
22     public static final Address FROM = Address.parse("adam@megacz.com"); // FIXME
23
24     public    transient Address who         = null;
25     public              long    expiration;
26     public     abstract String  getDescription();
27
28     protected Confirmation(Address who, long expiration) { this.who = who; this.expiration = expiration; }
29
30     public void readExternal(ObjectInput s) throws IOException {
31         try {
32             int numfields = s.readInt();
33             Class c = this.getClass();
34             for(int i=0; i<numfields; i++) {
35                 String name = s.readUTF();
36                 Field f = c.getField(name);
37                 Class c2 = f.getType();
38                 if (c2 == Boolean.TYPE) f.setBoolean(this, s.readBoolean());
39                 else if (c2 == Byte.TYPE) f.setByte(this, s.readByte());
40                 else if (c2 == Long.TYPE) f.setLong(this, s.readLong());
41                 else if (c2 == Integer.TYPE) f.setInt(this, s.readInt());
42                 else if (c2 == Character.TYPE) f.setChar(this, s.readChar());
43                 else if (c2 == Short.TYPE) f.setShort(this, s.readShort());
44                 else if (c2 == Float.TYPE) f.setFloat(this, s.readFloat());
45                 else if (c2 == Double.TYPE) f.setDouble(this, s.readDouble());
46                 else if (c2 == String.class) f.set(this, s.readObject());
47                 else f.set(this, s.readObject());
48             }
49         } catch (Exception e) {
50             Log.error(this, e);
51         }
52     }
53
54     public void writeExternal(ObjectOutput s) throws IOException {
55         try {
56             Class c = this.getClass();
57             Field[] fields = c.getFields();
58             int numfields = 0;
59             for(int i=0; i<fields.length; i++) {
60                 Field f = fields[i];
61                 if ((f.getModifiers() & (Modifier.STATIC | Modifier.TRANSIENT)) != 0) continue;
62                 numfields++;
63             }
64             s.writeInt(numfields);
65             for(int i=0; i<fields.length; i++) {
66                 Field f = fields[i];
67                 if ((f.getModifiers() & (Modifier.STATIC | Modifier.TRANSIENT)) != 0) continue;
68                 s.writeUTF(fields[i].getName());
69                 Class c2 = f.getType();
70                 if (c2 == Boolean.TYPE) s.writeBoolean(f.getBoolean(this));
71                 else if (c2 == Byte.TYPE) s.writeByte(f.getByte(this));
72                 else if (c2 == Long.TYPE) s.writeLong(f.getLong(this));
73                 else if (c2 == Integer.TYPE) s.writeInt(f.getInt(this));
74                 else if (c2 == Character.TYPE) s.writeChar(f.getChar(this));
75                 else if (c2 == Short.TYPE) s.writeShort(f.getShort(this));
76                 else if (c2 == Float.TYPE) s.writeFloat(f.getFloat(this));
77                 else if (c2 == Double.TYPE) s.writeDouble(f.getDouble(this));
78                 else if (c2 == String.class) s.writeUTF((String)f.get(this));
79                 else s.writeObject(f.get(this));
80             }
81         } catch (Exception e) {
82             Log.error(this, e);
83         }
84     }
85
86     public void signAndSend(long secret) throws IOException, Message.Malformed {
87         SMTP.Outgoing.accept(new Message(new Stream("From: "    + FROM + "\r\n" +
88                                                     "To: "      + who.toString(true) + "\r\n" +
89                                                     "Subject: confirm " + getDescription() + "\r\n" +
90                                                     "\r\n" +
91                                                     "Please click the link below to " + getDescription() + "\r\n" +
92                                                     sign(secret)),
93                                          new Message.Envelope(FROM, who, new Date())
94                                          )
95                              );
96     }
97
98     public String sign(long secret) throws IOException {
99         ByteArrayOutputStream os = new ByteArrayOutputStream();
100         ObjectOutputStream oos = new ObjectOutputStream(new DeflaterOutputStream(os));
101         oos.writeObject(this);
102         oos.flush();
103         oos.close();
104         byte[] b = os.toByteArray();
105         StringBuffer sb = new StringBuffer(new String(Base64.encode(b)));
106         sb.append('.');
107         SHA1 sha1 = new SHA1();
108         sha1.update(b, 0, b.length);
109         b = new byte[sha1.getDigestSize()];
110         sha1.doFinal(b, 0);
111         sb.append(new String(Base64.encode(b)));
112         return sb.toString();
113     }
114
115     public static Confirmation decode(String encoded, long secret) {
116         try {
117             // FIXME: not prevayler-safe!
118             String payload = encoded.substring(0, encoded.indexOf('.'));
119             ObjectInputStream ois = new ObjectInputStream(new InflaterInputStream(new Base64.InputStream(payload)));
120             Confirmation cve = (Confirmation)ois.readObject();
121             if (!cve.sign(secret).equals(encoded)) throw new InvalidSignature();
122             if (System.currentTimeMillis() > cve.expiration) throw new Expired();
123             return cve;
124         } catch (ClassNotFoundException e) {
125            Log.error(Confirmation.class, e);
126            throw new InvalidSignature();
127         } catch (IOException e) { 
128            Log.error(Confirmation.class, e);
129            throw new InvalidSignature();
130         }
131     }
132
133     public static class Exn extends RuntimeException { }
134     public static class Expired extends Exn { }
135     public static class InvalidSignature extends Exn { }
136 }