e64c6587fb84ab077f5608dd10a3a378e1423144
[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         SMTP.Outgoing.enqueue(Message.newMessage(new Fountain.StringFountain("From: "    + sender + "\r\n" +
106                                                                              "To: "      + who.toString(true) + "\r\n" +
107                                                                              "Subject: confirm " + getDescription() + "\r\n" +
108                                                                              "Message-Id: "+Message.generateFreshMessageId()+"\r\n" +
109                                                                              "\r\n" +
110                                                                              "Please click the link below to " + getDescription() + "\r\n" +
111                                                                              getURL(sign(secret))),
112                                                  sender,
113                                                  who
114                                                  )
115                               );
116     }
117
118     public String sign(long secret) throws IOException {
119         ByteArrayOutputStream os = new ByteArrayOutputStream();
120         ObjectOutputStream oos = new ObjectOutputStream(new DeflaterOutputStream(os));
121         oos.writeObject(this);
122         oos.flush();
123         oos.close();
124         byte[] b = os.toByteArray();
125         StringBuffer sb = new StringBuffer();
126         sb.append(serial);
127         //sb.append(new String(Encode.toBase64(b)));
128         sb.append('.');
129         SHA1 sha1 = new SHA1();
130         sha1.update(b, 0, b.length);
131         b = new byte[sha1.getDigestSize()];
132         sha1.doFinal(b, 0);
133         sb.append(new String(Encode.toBase64(b)));
134         return sb.toString();
135     }
136
137     public static Confirmation decode(String encoded, long secret, Date now) {
138         try {
139             String serial = encoded.substring(0, encoded.indexOf('.'));
140             Confirmation cve = get(Integer.parseInt(serial));
141             if (!cve.sign(secret).equals(encoded)) throw new InvalidSignature();
142             if (now.getTime() > cve.expiration) throw new Expired();
143             return cve;
144         } catch (IOException e) { 
145            Log.error(Confirmation.class, e);
146            throw new InvalidSignature();
147         }
148     }
149
150     public static class Exn extends RuntimeException { }
151     public static class Expired extends Exn { }
152     public static class InvalidSignature extends Exn { }
153 }