made newMessage a static method and Message() private
[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, Date now) throws IOException, Message.Malformed {
87         SMTP.Outgoing.accept(Message.newMessage(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                                                 FROM, who)
94                              );
95     }
96
97     public String sign(long secret) throws IOException {
98         ByteArrayOutputStream os = new ByteArrayOutputStream();
99         ObjectOutputStream oos = new ObjectOutputStream(new DeflaterOutputStream(os));
100         oos.writeObject(this);
101         oos.flush();
102         oos.close();
103         byte[] b = os.toByteArray();
104         StringBuffer sb = new StringBuffer(new String(Base64.encode(b)));
105         sb.append('.');
106         SHA1 sha1 = new SHA1();
107         sha1.update(b, 0, b.length);
108         b = new byte[sha1.getDigestSize()];
109         sha1.doFinal(b, 0);
110         sb.append(new String(Base64.encode(b)));
111         return sb.toString();
112     }
113
114     public static Confirmation decode(String encoded, long secret, Date now) {
115         try {
116             String payload = encoded.substring(0, encoded.indexOf('.'));
117             ObjectInputStream ois = new ObjectInputStream(new InflaterInputStream(new Base64.InputStream(payload)));
118             Confirmation cve = (Confirmation)ois.readObject();
119             if (!cve.sign(secret).equals(encoded)) throw new InvalidSignature();
120             if (now.getTime() > cve.expiration) throw new Expired();
121             return cve;
122         } catch (ClassNotFoundException e) {
123            Log.error(Confirmation.class, e);
124            throw new InvalidSignature();
125         } catch (IOException e) { 
126            Log.error(Confirmation.class, e);
127            throw new InvalidSignature();
128         }
129     }
130
131     public static class Exn extends RuntimeException { }
132     public static class Expired extends Exn { }
133     public static class InvalidSignature extends Exn { }
134 }