summaryrefslogtreecommitdiff
path: root/src/org/noreply/fancydress/directory/Directory.java
blob: ff0cfdfc52a740c6e9aaef722ff5ec4d4e6d34bf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
/* $Id$ */
package org.noreply.fancydress.directory;

import org.bouncycastle.util.encoders.Base64;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.noreply.fancydress.directory.parser.*;
import org.noreply.fancydress.misc.Util;
import org.noreply.fancydress.crypto.*;
import org.noreply.fancydress.status.*;
import java.util.*;

/**
 * This class represents a type III Directory.
 *
 * A directory holds information about the state of a type III network, like
 * recommended servers, which servers exist, their keys, etc.
 *
 * FIXME:
 * This whole flex and cup thing was just a nice way to try these tools.
 * Eventually this should be rewritten to a simple parser in Java itself.
 * Writing it should be pretty straight forward and cut down the
 * dependencies.
 *
 * @see Server
 */
public class Directory {
	/**
	 * directory version we understand.
	 */

	private static final String DIRECTORY_VERSION = "0.2";

	/**
	 * Hash holding all Servers.
	 *
	 * This hash holds all Servers.  Since nickname are to be treaded case
	 * insensitive the hash's keys are the lowercased nicknames of the Servers.
	 *
	 * All values in this hash are instances of Server.
	 *
	 * @see Server
	 */
	private Hashtable byName;

	/**
	 * Useable servers.
	 *
	 * A server is useable if it understands PacketVersions that we understand
	 * and has a serverdescriptor that is useable right now.
	 */
	private Server[] useableServers;

	/**
	 * Recommended servers.
	 */
	private Server[] recommendedServers;
	
	/*
	 * matrix of friends.
	 *
	 * (a, b) are friends, if a can send messages to b.
	 */
	/*
	private boolean[][] friends;
	*/


	/**
	 * Create a directory from an entire DirectoryMessage.
	 *
	 * This constructor builds up the directory from the message
	 * <code>m</code>.  It optionally checks the directory's signature and
	 * splits the message into single server descriptors to load them.
	 *
	 * @param m the directory message
	 * @param checkDirectorySignature whether or not to check the directory's signature
	 * @throws Mix3BadDirectorySignatureException if the directory's signature is invalid.
	 * @throws Mix3BadServerSignatureException if a server descriptor's signature is invalid.
	 * @throws Mix3BadServerFormatException if a section is syntactially invalid and the error cannot be ignored.
	 */
	public Directory(DirectoryMessage m, boolean checkDirectorySignature) throws
		Mix3BadDirectorySignatureException,
		Mix3BadDirectoryFormatException,
		Mix3BadServerFormatException,
		Mix3BadServerSignatureException
	{
		byName = new Hashtable();


		parseDirectory(m, checkDirectorySignature);


		Collection all = byName.values();
		ArrayList useable = new ArrayList();
		for (Iterator i = all.iterator(); i.hasNext(); ) {
			Server s = (Server) i.next();
			if (s.isUseable())
				useable.add(s);
		}
		useableServers = (Server[]) useable.toArray( new Server[useable.size()] );


		ArrayList recommended = new ArrayList();
		for (int i=0; i<useableServers.length; i++)
			if (useableServers[i].isRecommended())
				recommended.add(useableServers[i]);
		recommendedServers = (Server[]) recommended.toArray( new Server[recommended.size()] );
	}



	/**
	 * Possibly add a new server descriptor to the directory.
	 *
	 * This method adds a server desccriptor to the directory if it
	 *  - conforms to the syntax specified.
	 *  - the server is not yet known or it's identity key matches the key of the already known ServerDescriptor.
	 *
	 * @param server a DirectoryMessage consisting of one of more sections.  At 'Server' section needs to be present.
	 * @throws Mix3BadServerFormatException if a section is syntactially invalid and the error cannot be ignored.
	 * @throws Mix3BadServerSignatureException if the server descriptor's signature is invalid.
	 */
	private void addServerDescriptor(DirectoryMessage server) throws Mix3BadServerFormatException, Mix3BadServerSignatureException {
		ServerDescriptor sd;
		try {
			sd = new ServerDescriptor(server);
		} catch (Mix3BadServerUnrecognizedVersionException e) {
			System.err.println("Ignoring unregonized version");
			return;
		}
		String key = sd.getNickname().toLowerCase();
		if (byName.containsKey(key)) {
			Server s = (Server) byName.get(key);
			try {
				s.addDescriptor(sd);
			} catch (Mix3BadServerFormatException e) {
				System.err.println("Ignoring Descriptor with different identity for "+key);
			}
		} else {
			Server s = new Server(sd);
			byName.put(key, s);
		}
	}


	private void parseDirectory(DirectoryMessage m, boolean checkDirectorySignature) throws
		Mix3BadDirectorySignatureException,
		Mix3BadDirectoryFormatException,
		Mix3BadServerFormatException,
		Mix3BadServerSignatureException
	{
		DirectorySection directorySection = m.getSection("Directory");
		if (directorySection == null)
			throw new Mix3BadDirectoryFormatException("No Directory section found.");

		DirectoryEntry versionEntry = directorySection.getEntry("Version");
		if (versionEntry == null)
			throw new Mix3BadDirectoryFormatException("No Version in Directory section found.");
		if (! versionEntry.getValue().equals(DIRECTORY_VERSION))
			throw new Mix3BadDirectoryFormatException("Directory Version "+versionEntry+" not recognized.");

		/* FIXME: check validity (date range) */

		if (checkDirectorySignature)
			checkSignature(m);
		DirectoryMessage server = null;
		for (Iterator i = m.getSectionsIterator(); i.hasNext(); ) {
			DirectorySection section = (DirectorySection) i.next();
			if (section.getName().equals("Server")) {
				if (server != null)
					addServerDescriptor(server);
				server = new DirectoryMessage();
			}
			if (server != null)
				server.addSection(section);
		}
		if (server != null)
			addServerDescriptor(server);

		DirectoryEntry recommendedServersEntry = directorySection.getEntry("Recommended-Servers");
		if (recommendedServersEntry == null)
			throw new Mix3BadDirectorySignatureException("No Recommended-Servers in Directory section found.");
		String[] recommendedServers = Util.tokenize( recommendedServersEntry.getValue(), ',');
		for (int i=0; i<recommendedServers.length; i++) {
			Server s = getServer(recommendedServers[i]);
			if (s == null)
				throw new Mix3BadDirectoryFormatException("Unkown nickname '"+recommendedServers[i]+"'in Recommended-Servers");
			s.setRecommended();
		}
	}

	/* FIXME: known directory servers should be passed as arguments */
	/**
	 * Check the signature of a type III directory.
	 */
	private void checkSignature(DirectoryMessage m) throws Mix3BadDirectorySignatureException, Mix3BadDirectoryFormatException {
		/* FIXME: handle more than one signature block,
		 */

		DirectorySection signatureSection = m.getSection("Signature");
		if (signatureSection == null)
			throw new Mix3BadDirectoryFormatException("No Signature section found.");

		DirectoryEntry signatureEntry = signatureSection.getEntry("DirectorySignature");
		if (signatureEntry == null)
			throw new Mix3BadDirectoryFormatException("No DirectorySignature in Signature section found.");

		DirectoryEntry identityEntry = signatureSection.getEntry("DirectoryIdentity");
		if (identityEntry == null)
			throw new Mix3BadDirectoryFormatException("No DirectoryIdentity in Signature section found.");

		byte[] signature = Base64.decode(signatureEntry.getValue());
		byte[] identity = Base64.decode(identityEntry.getValue());

		DirectoryMessage cleanedDirectory = new DirectoryMessage(m);
		cleanedDirectory.blindValue("Signature","DirectoryDigest");
		cleanedDirectory.blindValue("Signature","DirectorySignature");

		RSAPublicKey identityKey = new RSAPublicKey(identity);
		boolean verifies;
		try {
			verifies = identityKey.verify(signature, Util.toOctets(cleanedDirectory.toString()));
		} catch (InvalidCipherTextException e) {
			throw new Mix3BadDirectorySignatureException("Directory signature has invalid format.", e);
		};
		if (!verifies)
			throw new Mix3BadDirectorySignatureException("Directory signature does not verify.");
	}

	/**
	 * Get a server by nickname.
	 *
	 * @param name nickname of the requested server
	 * @return server specified by the nickname or null if no such server exists
	 */
	public Server getServer(String name) {
			String key = name.toLowerCase();
			if (byName.containsKey(key))
				return (Server) byName.get(key);
			return null;
	}

	/*
	 * Get current server descriptor of useable servers.
	 *
	 * A server is useable if it understands PacketVersions that we understand
	 * and has a serverdescriptor that is useable right now.
	 *
	 * @return server descriptor of useable servers
	 */
	/*
	private Server[] getUseableServers() {
		return useableServers;
	}
	*/

	/**
	 * Get server descriptor of all recommended servers that are useable.
	 *
	 * @return server descriptor recommended servers
	 * @see #getUseableServers
	 */
	public Server[] getRecommendedServers() {
		return recommendedServers;
	}

	/**
	 * Check if server a can talk to server b.
	 *
	 * A server can talk to another one if they have MMTP/Outgoing and
	 * MMTP/Incoming sections respectively and speak a common MMTP version.
	 *
	 * FIXME: check Allow/Deny ACLs
	 *
	 * @param a server a
	 * @param b server b
	 * @return whether a can send messages to b
	 */
	public boolean areFriends(Server a, Server b) {
		ServerDescriptor aa,bb;
		
		try {
			aa = a.getDescriptor();
		} catch (Mix3NoServerDescriptorException e) {
			throw new Error(a.getNickname()+" should have a valid ServerDescriptor.", e);
		}
		try {
			bb = b.getDescriptor();
		} catch (Mix3NoServerDescriptorException e) {
			throw new Error(b.getNickname()+" should have a valid ServerDescriptor.", e);
		}

		OutgoingMMTPSection out = aa.getOutgoingMMTPSection();
		IncomingMMTPSection in = bb.getIncomingMMTPSection();

		if (out == null || in == null)
			return false;
		String[] inP = in.getProtocols();
		String[] outP = out.getProtocols();
		for (int i=0; i<inP.length; i++)
			for (int o=0; o<outP.length; o++)
				if (inP[i].equals(outP[o]))
					return true;
		return false;
	}

	/*
	 * Build a matrix of friends.
	 *
	 * (a, b) are friends, if a can send messages to b.
	 */
	/*
	private void buildFriendsMatrix() {
		friends = new boolean[useableServers.length][useableServers.length];
		for (int i=0; i<useableServers.length; i++)
			for (int j=0; j<useableServers.length; j++)
				if (i==j)
					friends[i][j] = true;
				else
					friends[i][j] = areFriends(useableServers[i], useableServers[j]);
	}
	 */
}