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