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