2a1090b1e33607dbdacf90de3bdc8708afc57369
[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
27     public static int master_serial = 0;
28
29     public    transient Address who         = null;
30     public              long    expiration;
31     public     abstract String  getDescription();
32     public     abstract String  getURL(String tail);
33     public              int serial;
34
35     private static HashMap<Integer,Confirmation> all = new HashMap<Integer,Confirmation>();
36
37     public static Confirmation get(int serial) {
38         return all.get(serial);
39     }
40
41     protected Confirmation(Address who, long expiration) {
42         this.who = who;
43         this.expiration = expiration;
44         this.serial = master_serial++;
45         all.put(serial, this);
46     }
47
48     public void readExternal(ObjectInput s) throws IOException {
49         try {
50             int numfields = s.readInt();
51             Class c = this.getClass();
52             for(int i=0; i<numfields; i++) {
53                 String name = s.readUTF();
54                 Field f = c.getField(name);
55                 Class c2 = f.getType();
56                 if (c2 == Boolean.TYPE) f.setBoolean(this, s.readBoolean());
57                 else if (c2 == Byte.TYPE) f.setByte(this, s.readByte());
58                 else if (c2 == Long.TYPE) f.setLong(this, s.readLong());
59                 else if (c2 == Integer.TYPE) f.setInt(this, s.readInt());
60                 else if (c2 == Character.TYPE) f.setChar(this, s.readChar());
61                 else if (c2 == Short.TYPE) f.setShort(this, s.readShort());
62                 else if (c2 == Float.TYPE) f.setFloat(this, s.readFloat());
63                 else if (c2 == Double.TYPE) f.setDouble(this, s.readDouble());
64                 else if (c2 == String.class) f.set(this, s.readObject());
65                 else f.set(this, s.readObject());
66             }
67         } catch (Exception e) {
68             Log.error(this, e);
69         }
70     }
71
72     public void writeExternal(ObjectOutput s) throws IOException {
73         try {
74             Class c = this.getClass();
75             Field[] fields = c.getFields();
76             int numfields = 0;
77             for(int i=0; i<fields.length; i++) {
78                 Field f = fields[i];
79                 if ((f.getModifiers() & (Modifier.STATIC | Modifier.TRANSIENT)) != 0) continue;
80                 numfields++;
81             }
82             s.writeInt(numfields);
83             for(int i=0; i<fields.length; i++) {
84                 Field f = fields[i];
85                 if ((f.getModifiers() & (Modifier.STATIC | Modifier.TRANSIENT)) != 0) continue;
86                 s.writeUTF(fields[i].getName());
87                 Class c2 = f.getType();
88                 if (c2 == Boolean.TYPE) s.writeBoolean(f.getBoolean(this));
89                 else if (c2 == Byte.TYPE) s.writeByte(f.getByte(this));
90                 else if (c2 == Long.TYPE) s.writeLong(f.getLong(this));
91                 else if (c2 == Integer.TYPE) s.writeInt(f.getInt(this));
92                 else if (c2 == Character.TYPE) s.writeChar(f.getChar(this));
93                 else if (c2 == Short.TYPE) s.writeShort(f.getShort(this));
94                 else if (c2 == Float.TYPE) s.writeFloat(f.getFloat(this));
95                 else if (c2 == Double.TYPE) s.writeDouble(f.getDouble(this));
96                 else if (c2 == String.class) s.writeUTF((String)f.get(this));
97                 else s.writeObject(f.get(this));
98             }
99         } catch (Exception e) {
100             Log.error(this, e);
101         }
102     }
103
104     public void signAndSend(Address sender, long secret, Date now) throws IOException, Message.Malformed {
105
106         Headers h = new Headers(new String[] {
107             "From",        sender.toString(true),
108             "To",          who.toString(true),
109             "Message-Id",  Message.generateFreshMessageId(),
110             "Date",        new Date()+"" /*FIXME!!!*/,
111             "Subject",     "confirm " + getDescription()
112         });
113
114         Fountain fountain = Fountain.Util.create("Please click the link below to " +
115                                                  getDescription() + "\r\n" +
116                                                  getURL(sign(secret)));
117         Message m = Message.newMessageFromHeadersAndBody(h, fountain, sender, who);
118         SMTP.Outgoing.enqueue(m);
119     }
120
121     public String sign(long secret) throws IOException {
122         ByteArrayOutputStream os = new ByteArrayOutputStream();
123         ObjectOutputStream oos = new ObjectOutputStream(new DeflaterOutputStream(os));
124         oos.writeObject(this);
125         oos.flush();
126         oos.close();
127         byte[] b = os.toByteArray();
128         StringBuffer sb = new StringBuffer();
129         sb.append(serial);
130         //sb.append(new String(Encode.toBase64(b)));
131         sb.append('.');
132         SHA1 sha1 = new SHA1();
133         sha1.update(b, 0, b.length);
134         b = new byte[sha1.getDigestSize()];
135         sha1.doFinal(b, 0);
136         sb.append(new String(Encode.toBase64(b)));
137         return sb.toString();
138     }
139
140     public static Confirmation decode(String encoded, long secret, Date now) {
141         try {
142             String serial = encoded.substring(0, encoded.indexOf('.'));
143             Confirmation cve = get(Integer.parseInt(serial));
144             if (!cve.sign(secret).equals(encoded)) throw new InvalidSignature();
145             if (now.getTime() > cve.expiration) throw new Expired();
146             return cve;
147         } catch (IOException e) { 
148            Log.error(Confirmation.class, e);
149            throw new InvalidSignature();
150         }
151     }
152
153     public static class Exn extends RuntimeException { }
154     public static class Expired extends Exn { }
155     public static class InvalidSignature extends Exn { }
156 }