src/org/freenetproject/freemail/wot/WoTConnection.java
public interface WoTConnection {
	/**
	 * Returns all the OwnIdentities fetched from WoT, or {@code null} if an error occurs. If there
	 * are no OwnIdentities the returned list will be empty.
	 * @return all the OwnIdentities fetched from WoT
	 * @throws PluginNotFoundException If the WoT plugin isn't loaded
	 */
	List<OwnIdentity> getAllOwnIdentities() throws PluginNotFoundException;

	/**
	 * Returns all the identities or {@code null} if an error occurs.
	 * @return all the identities
	 * @throws PluginNotFoundException If the WoT plugin isn't loaded
	 */
	Set<Identity> getAllIdentities() throws PluginNotFoundException;

	/**
	 * Returns the Identity with the given identity string. The truster parameter is used to fetch
	 * the trust and score of the identity and must be a valid OwnIdentity.
	 * @param identity the id of the Identity that should be fetched
	 * @param truster the OwnIdentity to use for fetching the trust and score
	 * @return the Identity with the given identity string
	 * @throws NullPointerException if any of the parameters are {@code null}
	 * @throws PluginNotFoundException If the WoT plugin isn't loaded
	 */
	Identity getIdentity(String identity, String truster) throws PluginNotFoundException;

	/**
	 * Sets the property {@code key} to {@code value} for the given identity, returning {@code true}
	 * if the property was successfully set and {@code false} otherwise.
	 * @param identity the identity whose parameter should be set
	 * @param key the name of the parameter
	 * @param value the new value of the parameter
	 * @return {@code true} if the parameter was successfully set
	 * @throws NullPointerException if any of the parameters are {@code null}
	 * @throws PluginNotFoundException If the WoT plugin isn't loaded
	 */
	boolean setProperty(String identity, String key, String value) throws PluginNotFoundException;

	/**
	 * Returns the value of the named parameter from the given identity. {@code null} is returned if
	 * the parameter doesn't exist, or if an error occurs.
	 * @param identity the identity whose parameter should be fetched
	 * @param key the name of the parameter to be fetched
	 * @return the value of the parameter, or {@code null}
	 * @throws NullPointerException if any of the parameters are {@code null}
	 * @throws PluginNotFoundException If the WoT plugin isn't loaded
	 */
	String getProperty(String identity, String key) throws PluginNotFoundException;

	/**
	 * Sets the context {@code context} for the given identity, returning {@code true} if the
	 * context was successfully set and {@code false} otherwise.
	 * @param identity the identity whose context should be set
	 * @param context the context that should be set
	 */
	boolean setContext(String identity, String context) throws PluginNotFoundException;
}
src/org/freenetproject/freemail/fcp/FCPException.java
@SuppressWarnings("serial")
public class FCPException extends Exception {
	public final int errorcode;
	public final boolean isFatal;

	protected final String codeDescription;
	protected final String extraDescription;
	protected final String shortCodeDescription;
	protected final String fcpMessageType;

	FCPException(FCPMessage msg) {
		//Read the error code
		String code = msg.headers.get("Code");
		if(code != null)
			this.errorcode = Integer.parseInt(code);
		else
			this.errorcode = 0;

		//Read the fatal field
		String fatal = msg.headers.get("Fatal");
		if(fatal != null)
			this.isFatal = (fatal.equalsIgnoreCase("true"));
		else
			this.isFatal = false;

		//Read the description fields
		this.codeDescription = msg.headers.get("CodeDescription");
		this.extraDescription = msg.headers.get("ExtraDescription");
		this.shortCodeDescription = msg.headers.get("ShortCodeDescription");

		//Save the type
		this.fcpMessageType = msg.getType();
	}

	FCPException(int code, boolean fatal) {
		errorcode = code;
		isFatal = fatal;

		this.codeDescription = null;
		this.extraDescription = null;
		this.shortCodeDescription = null;
		this.fcpMessageType = null;
	}

	/**
	 * Creates a new FCPException based on the given FCPMessage. The class of the instance that is
	 * returned is decided based on the type of the FCPMessage.
	 * @param msg the FCPMessage the returned exception is based on
	 * @return a new FCPException based on the given FCPMessage
	 */
	static FCPException create(FCPMessage msg) {
		if(msg.getType().equalsIgnoreCase("GetFailed")) {
			return new FCPFetchException(msg);
		}
		if(msg.getType().equalsIgnoreCase("PutFailed")) {
			return new FCPPutFailedException(msg);
		}
		if(msg.getType().equalsIgnoreCase("ProtocolError")) {
			return new FCPProtocolException(msg);
		}
		return new FCPException(msg);
	}

	@Override
	public String toString() {
		return "Message type " + fcpMessageType + " with error code " + errorcode;
	}
}
src/org/freenetproject/freemail/FreenetURI.java
public class FreenetURI {
	//Regular expressions for matching SSKs and USKs
	private static final String BASE64_REGEXP = "[0-9a-zA-Z~-]";
	private static final String SSK_HASH_REGEX = BASE64_REGEXP + "{42,44}";
	private static final String SSK_KEY_REGEX = SSK_HASH_REGEX;
	private static final String SSK_EXTRA_REGEX = BASE64_REGEXP + "{7}";
	private static final String SSK_REGEX = "(freenet:)?SSK@" + SSK_HASH_REGEX + "," + SSK_KEY_REGEX + "," + SSK_EXTRA_REGEX;
	private static final String USK_REGEX = "(freenet:)?USK@" + SSK_HASH_REGEX + "," + SSK_KEY_REGEX + "," + SSK_EXTRA_REGEX;
	private static final String USK_SITENAME_REGEX = "\\w+";
	private static final String USK_EDITION_REGEX = "-?[0-9]+";

	private String keytype;
	private String keybody;
	private String suffix; // this includes USK versions etc for now

	public FreenetURI(String uri) throws MalformedURLException {
		String[] parts = uri.split(":", 2);

		if(parts.length == 2 && !parts[0].equals("freenet")) {
			throw new MalformedURLException("Invalid scheme - not a Freenet address");
		} else if(parts.length == 2) {
			uri = parts[1];
		}

		// now split on the '@'
		parts = uri.split("@", 2);

		if(parts.length < 2) {
			this.keytype  = "KSK";
		} else {
			this.keytype = parts[0];
			uri = parts[1];
		}

		// finally, separate the body from the metastrings
		parts = uri.split("/", 2);

		if(parts.length < 2) {
			this.keybody = uri;
			this.suffix = null;
		} else {
			this.keybody = parts[0];
			this.suffix = parts[1];
		}
	}

	public String getKeyType() {
		return this.keytype;
	}

	public String getKeyBody() {
		return this.keybody;
	}

	public String getSuffix() {
		return this.suffix;
	}

	/**
	 * Returns {@code true} if {@code key} is a valid SSK key and {@code false} otherwise.
	 * @param key the key that should be checked
	 * @return {@code true} if {@code key} is a valid SSK key
	 */
	public static boolean checkSSK(String key) {
		//Valid SSK, possibly with sitename and filename
		if(key.matches("^" + SSK_REGEX + "/.*$")) return true;

		//Valid SSK without trailing /
		if(key.matches("^" + SSK_REGEX + "$")) return true;

		return false;
	}

	/**
	 * Returns {@code true} if {@code key} is a valid USK key and {@code false} otherwise.
	 * @param key the key that should be checked
	 * @return {@code true} if {@code key} is a valid USK key
	 */
	public static boolean checkUSK(String key) {
		//Valid USK with sitename, edition and filename
		if(key.matches("^" + USK_REGEX + "/" + USK_SITENAME_REGEX + "/" + USK_EDITION_REGEX + "/.*$")) return true;

		//Valid USK with sitename, edition (optional trailing /)
		if(key.matches("^" + USK_REGEX + "/" + USK_SITENAME_REGEX + "/" + USK_EDITION_REGEX + "/{0,1}$")) return true;

		//Valid USK with sitename but no edition (optional trailing /)
		if(key.matches("^" + USK_REGEX + "/" + USK_SITENAME_REGEX + "/{0,1}$")) return true;

		//Valid USK without sitename (optional trailing /)
		if(key.matches("^" + USK_REGEX + "/{0,1}$")) return true;

		return false;
	}

	/**
	 * Returns {@code true} if {@code hash} is a valid hash from an SSK key and {@code false}
	 * otherwise. Since the hash is also used as the identity ID by WoT this can be used to verify
	 * identity ids as well.
	 * @param hash the hash that should be checked
	 * @return {@code true} if {@code key} is a valid SSK hash
	 */
	public static boolean checkSSKHash(String hash) {
		return hash.matches("^" + SSK_HASH_REGEX + "$");
	}

	/*
	 * Read a Freenet URI from args and print out in parts to test
	 */
	public static void main(String args[]) {
		FreenetURI uri;
		try {
			uri = new FreenetURI(args[0]);
		} catch (MalformedURLException mue) {
			mue.printStackTrace();
			return;
		}

		System.out.println("Key type: "+uri.getKeyType());
		System.out.println("Key body: "+uri.getKeyBody());
		System.out.println("Suffix: "+uri.getSuffix());
	}
}
src/org/freenetproject/freemail/fcp/FCPBadFileException.java
public class FCPBadFileException extends Exception {
	static final long serialVersionUID = -1;
}
src/org/freenetproject/freemail/fcp/HighLevelFCPClient.java
public class HighLevelFCPClient implements FCPClient {
	private static final int FCP_TOO_MANY_PATH_COMPONENTS = 11;
	private static final int FCP_PERMANANT_REDIRECT = 27;

	private FCPConnection conn;

	private final List<FCPMessage> doneMsgs = new LinkedList<FCPMessage>();

	public HighLevelFCPClient() {
		this.conn = Freemail.getFCPConnection();
	}

	// It's up to the client to delete this File once they're
	// done with it
	public File fetch(String key) throws ConnectionTerminatedException, FCPFetchException, FCPException,
	                                     InterruptedException {
		FCPMessage msg = this.conn.getMessage("ClientGet");
		msg.headers.put("URI", key);
		msg.headers.put("ReturnType", "direct");
		msg.headers.put("Persistence", "connection");

		while(true) {
			try {
				this.conn.doRequest(this, msg);
				break;
			} catch (NoNodeConnectionException nnce) {
				Logger.error(this, "Warning - no connection to node. Waiting...");
				Thread.sleep(10000);
			} catch (FCPBadFileException bfe) {
				// won't be thrown since this is a get,
				// but keep the compiler happy
			}
		}

		FCPMessage reply;
		try {
			reply = getReply(msg.getId());
		} catch(InterruptedException e) {
			Logger.debug(this, "HighLevelFCPClient interrupted in fetch, stopping");
			conn.cancelRequest(msg);
			throw e;
		}

		if(reply.getType().equalsIgnoreCase("AllData")) {
			return reply.getData();
		} else if(reply.getType().equalsIgnoreCase("GetFailed")) {
			String s_code = reply.headers.get("Code");
			if(s_code == null) return null;
			int code = Integer.parseInt(s_code);
			if(code == FCP_PERMANANT_REDIRECT || code == FCP_TOO_MANY_PATH_COMPONENTS) {
				String newuri = reply.headers.get("RedirectURI");
				if(newuri == null) return null;
				return this.fetch(newuri);
			}
			throw new FCPFetchException(reply);
		} else {
			throw FCPException.create(reply);
		}
	}

	private FCPMessage getReply(String identifier) throws InterruptedException {
		synchronized (doneMsgs) {
			while(true) {
				Iterator<FCPMessage> it = doneMsgs.iterator();
				while(it.hasNext()) {
					FCPMessage msg = it.next();
					if(identifier.equals(msg.getId())) {
						it.remove();
						return msg;
					}
				}

				doneMsgs.wait();
			}
		}
	}

	public SSKKeyPair makeSSK() throws ConnectionTerminatedException, InterruptedException {
		FCPMessage msg = this.conn.getMessage("GenerateSSK");

		while(true) {
			try {
				this.conn.doRequest(this, msg);
				break;
			} catch (NoNodeConnectionException nnce) {
				Logger.error(this, "Warning - no connection to node. Waiting...");
				Thread.sleep(5000);
			} catch (FCPBadFileException bfe) {
				// won't be thrown since no data
			}
		}

		FCPMessage reply;
		try {
			reply = getReply(msg.getId());
		} catch(InterruptedException e) {
			Logger.debug(this, "HighLevelFCPClient interrupted in makeSSK, stopping");
			conn.cancelRequest(msg);
			throw e;
		}

		if(reply.getType().equalsIgnoreCase("SSKKeypair")) {
			SSKKeyPair retval = new SSKKeyPair();

			retval.privkey = reply.headers.get("InsertURI");
			retval.pubkey = reply.headers.get("RequestURI");
			return retval;
		} else {
			return null;
		}
	}

	public FCPPutFailedException put(InputStream data, String key) throws FCPBadFileException,
	                                                                      ConnectionTerminatedException,
	                                                                      FCPException, InterruptedException {
		FCPMessage msg = this.conn.getMessage("ClientPut");
		msg.headers.put("URI", key);
		msg.headers.put("Persistence", "connection");
		msg.setData(data);

		while(true) {
			try {
				this.conn.doRequest(this, msg);
				break;
			} catch (NoNodeConnectionException nnce) {
				Logger.error(this, "Warning - no connection to node. Waiting...");
				Thread.sleep(5000);
			}
		}

		FCPMessage reply;
		try {
			reply = getReply(msg.getId());
		} catch(InterruptedException e) {
			Logger.debug(this, "HighLevelFCPClient interrupted in put, stopping");
			conn.cancelRequest(msg);
			throw e;
		}

		if(reply.getType().equalsIgnoreCase("PutSuccessful")) {
			return null;
		} else if(reply.getType().equalsIgnoreCase("PutFailed")) {
			return new FCPPutFailedException(reply);
		} else {
			throw FCPException.create(reply);
		}
	}

	public int SlotInsert(File data, String basekey, int minslot, String suffix) throws ConnectionTerminatedException,
	                                                                                    InterruptedException {
		int slot = minslot;
		boolean carryon = true;
		FileInputStream fis;
		if(basekey.startsWith("USK@")) {
			basekey = basekey.replace("USK@", "SSK@");

			if(basekey.charAt(basekey.length() - 1) == '/') {
				basekey = basekey.substring(0, basekey.length() - 1);
			}
		}
		while(carryon) {
			Logger.debug(this, "trying slotinsert to "+basekey+"-"+slot+suffix);

			try {
				fis = new FileInputStream(data);
			} catch (FileNotFoundException fnfe) {
				return -1;
			}

			FCPPutFailedException emsg;
			try {
				emsg = this.put(fis, basekey+"-"+slot+suffix);
			} catch (FCPBadFileException bfe) {
				return -1;
			} catch (FCPException e) {
				Logger.error(this, "Unknown error while doing slotinsert: " + e.getMessage());
				return -1;
			}
			if(emsg == null) {
				Logger.debug(this, "insert of "+basekey+"-"+slot+suffix+" successful");
				return slot;
			} else if(emsg.errorcode == FCPPutFailedException.COLLISION) {
				slot++;
				Logger.debug(this, "collision");
			} else {
				Logger.error(this, "Slot insert failed, error code is "+emsg.errorcode);
				// try again later
				return -1;
			}
		}
		return -1;
	}

	public int slotInsert(byte[] data, String basekey, int minslot, String suffix) throws ConnectionTerminatedException,
	                                                                                      InterruptedException {
		int slot = minslot;
		boolean carryon = true;
		ByteArrayInputStream bis;
		if(basekey.startsWith("USK@")) {
			basekey = basekey.replace("USK@", "SSK@");

			if(basekey.charAt(basekey.length() - 1) == '/') {
				basekey = basekey.substring(0, basekey.length() - 1);
			}
		}
		while(carryon) {
			Logger.debug(this, "trying slotinsert to "+basekey+"-"+slot+suffix);

			bis = new ByteArrayInputStream(data);

			FCPPutFailedException emsg;
			try {
				emsg = this.put(bis, basekey+"-"+slot+suffix);
			} catch (FCPBadFileException bfe) {
				return -1;
			} catch (FCPException e) {
				Logger.error(this, "Unknown error while doing slotinsert: " + e.getMessage());
				return -1;
			}
			if(emsg == null) {
				Logger.debug(this, "insert of "+basekey+"-"+slot+suffix+" successful");
				return slot;
			} else if(emsg.errorcode == FCPPutFailedException.COLLISION) {
				slot++;
				Logger.debug(this, "collision");
			} else {
				Logger.error(this, "Slot insert failed, error code is "+emsg.errorcode);
				// try again later
				return -1;
			}
		}
		return -1;
	}

	@Override
	public void requestStatus(FCPMessage msg) {

	}

	@Override
	public void requestFinished(FCPMessage msg) {
		synchronized(doneMsgs) {
			doneMsgs.add(msg);
			doneMsgs.notifyAll();
		}
	}
}
src/org/freenetproject/freemail/utils/PropsFile.java
public class PropsFile {
	// substitute static methods for constructor

	private static final Hashtable<String, PropsFile> propsList=new Hashtable<String, PropsFile>();

	private static int reapCounter = 0;
	/// We go through the list and remove stale entries once in this many times a PropsFile is created
	private static final int reapEvery = 20;

	public static synchronized PropsFile createPropsFile(File f, boolean stopAtBlank) {
		if(reapCounter == reapEvery) {
			reapOld();
			reapCounter = 0;
		} else {
			++reapCounter;
		}

		String fn=f.getPath();

		PropsFile pf=propsList.get(fn);

		if(pf!=null) {
			return pf;
		} else {
			pf=new PropsFile(f, stopAtBlank);
			propsList.put(fn, pf);
			return pf;
		}
	}

	public static PropsFile createPropsFile(File f) {
		return createPropsFile(f, false);
	}

	public static void reapOld() {
		Logger.debug(PropsFile.class, "Cleaning up stale PropsFiles");

		Iterator<Map.Entry<String, PropsFile>> i = propsList.entrySet().iterator();

		while(i.hasNext()) {
			Map.Entry<String, PropsFile> entry = i.next();
			File f = new File(entry.getKey());
			if(!f.exists()) {
				Logger.debug(PropsFile.class, "Removing "+f.getPath());
				i.remove();
			}
		}
	}

	private final File file;
	private HashMap<String, String> data;
	private BufferedReader bufrdr;
	private String commentPrefix;
	private String header;

	/** Pass true into stopAtBlank to cause the reader to stop upon encountering
	 * a blank line. It's the the caller's responsibility to get
	 * (using the getReader() method) the stream and close it properly.
	 */
	private PropsFile(File f, boolean stopAtBlank) {
		this.file = f;
		this.data = null;

		if(f.exists()) {
			try {
				this.bufrdr = this.read(stopAtBlank);
			} catch (IOException ioe) {
			}
		}
		this.commentPrefix = null;
		this.header = null;
	}

	public void setCommentPrefix(String cp) {
		this.commentPrefix = cp;
	}

	public void setHeader(String hdr) {
		this.header = hdr;
	}

	private synchronized BufferedReader read(boolean stopAtBlank) throws IOException {
		this.data = new HashMap<String, String>();

		BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(this.file), "UTF-8"));

		String line = null;
		while((line = br.readLine()) != null) {
			if(this.commentPrefix != null && line.startsWith(this.commentPrefix)) {
				continue;
			}
			if(stopAtBlank && line.length() == 0) {
				return br;
			}
			String[] parts = line.split("=", 2);
			if(parts.length < 2) continue;
			this.data.put(parts[0], parts[1]);
		}

		br.close();
		return null;
	}

	public BufferedReader getReader() {
		return this.bufrdr;
	}

	public void closeReader() {
		if(this.bufrdr == null) return;
		try {
			this.bufrdr.close();
		} catch (IOException ioe) {
		}
	}

	private synchronized void write() throws IOException {
		File parentDir = file.getParentFile();
		if(parentDir != null && !parentDir.exists()) {
			if(!parentDir.mkdirs()) {
				Logger.error(this, "Couldn't create parent directory of " + file);
				throw new IOException("Couldn't create parent directory of " + file);
			}
		}

		PrintWriter pw = new PrintWriter(new FileOutputStream(this.file));

		if(this.header != null) pw.println(this.header);

		Iterator<Map.Entry<String, String>> i = this.data.entrySet().iterator();
		while(i.hasNext()) {
			Map.Entry<String, String> e = i.next();
			String key = e.getKey();
			String val = e.getValue();

			pw.println(key+"="+val);
		}

		pw.close();
	}

	public String get(String key) {
		if(this.data == null) return null;

		return this.data.get(key);
	}

	public boolean put(String key, String val) {
		if(this.data == null) {
			this.data = new HashMap<String, String>();
		}

		Object o = this.data.put(key, val);
		if(o == null || !o.equals(val)) {
			try {
				this.write();
			} catch (IOException ioe) {
				ioe.printStackTrace();
				return false;
			}
		}
		return true;
	}

	public boolean put(String key, long val) {
		return this.put(key, Long.toString(val));
	}

	public boolean exists() {
		return this.file.exists();
	}

	public Set<String> listProps() {
		return this.data.keySet();
	}

	public boolean remove(String key) {
		if(this.data.containsKey(key)) {
			this.data.remove(key);
			try {
				this.write();
			} catch (IOException ioe) {
				ioe.printStackTrace();
				return false;
			}
		}
		return true;
	}

	@Override
	public String toString() {
		return file.getPath();
	}
}
src/org/freenetproject/freemail/fcp/FCPPutFailedException.java
@SuppressWarnings("serial")
public class FCPPutFailedException extends FCPException {
	/* Caller supplied a URI we cannot use */
	public static final int INVALID_URI = 1;
	/* Failed to read from or write to a bucket; a kind of internal error */
	public static final int BUCKET_ERROR = 2;
	/* Internal error of some sort */
	public static final int INTERNAL_ERROR = 3;
	/* Downstream node was overloaded */
	public static final int REJECTED_OVERLOAD = 4;
	/* Couldn't find enough nodes to send the data to */
	public static final int ROUTE_NOT_FOUND = 5;
	/* There were fatal errors in a splitfile insert. */
	public static final int FATAL_ERRORS_IN_BLOCKS = 6;
	/* Could not insert a splitfile because a block failed too many times */
	public static final int TOO_MANY_RETRIES_IN_BLOCKS = 7;
	/* Not able to leave the node at all */
	public static final int ROUTE_REALLY_NOT_FOUND = 8;
	/* Collided with pre-existing content */
	public static final int COLLISION = 9;
	/* Cancelled by user */
	public static final int CANCELLED = 10;
	public static final int META_STRING_IN_KEY = 11;
	public static final int INVALID_BINARY_BLOB_DATA = 12;

	// we generate this error, not Freenet
	public static final int TIMEOUT = 100;

	FCPPutFailedException(FCPMessage msg) {
		super(msg);

		assert (msg.getType().equalsIgnoreCase("PutFailed")) : "Message type was " + msg.getType();
	}

	FCPPutFailedException(int code, boolean fatal) {
		super(code, fatal);
	}

	@Override
	public String toString() {
		return "FCPPutFailedException (error code " + errorcode + ", " + (isFatal ? "fatal" : "not fatal") + ")";
	}
}
src/org/freenetproject/freemail/utils/DateStringFactory.java
public class DateStringFactory {
	private static final TimeZone utc = TimeZone.getTimeZone("UTC");
	private static final Calendar cal = Calendar.getInstance(utc);

	public static String getKeyString() {
		return getOffsetKeyString(0);
	}

	// get a date in a format we use for keys, offset from today
	public static synchronized String getOffsetKeyString(int offset) {
		cal.setTime(new Date());
		cal.add(Calendar.DAY_OF_MONTH, offset);

		SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd", Locale.ROOT);
		sdf.setTimeZone(utc);

		return sdf.format(cal.getTime());
	}

	public static Date dateFromKeyString(String str) {
		try {
			SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd", Locale.ROOT);
			sdf.setTimeZone(utc);

			sdf.setLenient(false);
			return sdf.parse(str);
		} catch (ParseException pe) {
			return null;
		}
	}
}
src/org/archive/util/Base32.java
public class Base32 {
    private static final String base32Chars =
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
    private static final int[] base32Lookup =
    { 0xFF,0xFF,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F, // '0', '1', '2', '3', '4', '5', '6', '7'
      0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, // '8', '9', ':', ';', '<', '=', '>', '?'
      0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x06, // '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G'
      0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E, // 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O'
      0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16, // 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W'
      0x17,0x18,0x19,0xFF,0xFF,0xFF,0xFF,0xFF, // 'X', 'Y', 'Z', '[', '\', ']', '^', '_'
      0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x06, // '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g'
      0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E, // 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o'
      0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16, // 'p', 'q', 'r', 's', 't', 'u', 'v', 'w'
      0x17,0x18,0x19,0xFF,0xFF,0xFF,0xFF,0xFF  // 'x', 'y', 'z', '{', '|', '}', '~', 'DEL'
    };

    /**
     * Encodes byte array to Base32 String.
     *
     * @param bytes Bytes to encode.
     * @return Encoded byte array <code>bytes</code> as a String.
     *
     */
    static public String encode(final byte[] bytes) {
        int i = 0, index = 0, digit = 0;
        int currByte, nextByte;
        StringBuffer base32 = new StringBuffer((bytes.length + 7) * 8 / 5);

        while (i < bytes.length) {
            currByte = (bytes[i] >= 0) ? bytes[i] : (bytes[i] + 256); // unsign

            /* Is the current digit going to span a byte boundary? */
            if (index > 3) {
                if ((i + 1) < bytes.length) {
                    nextByte =
                        (bytes[i + 1] >= 0) ? bytes[i + 1] : (bytes[i + 1] + 256);
                } else {
                    nextByte = 0;
                }

                digit = currByte & (0xFF >> index);
                index = (index + 5) % 8;
                digit <<= index;
                digit |= nextByte >> (8 - index);
                i++;
            } else {
                digit = (currByte >> (8 - (index + 5))) & 0x1F;
                index = (index + 5) % 8;
                if (index == 0)
                    i++;
            }
            base32.append(base32Chars.charAt(digit));
        }

        return base32.toString();
    }

    /**
     * Decodes the given Base32 String to a raw byte array.
     *
     * @param base32 the base32 string that should be decoded
     * @return Decoded <code>base32</code> String as a raw byte array.
     */
    static public byte[] decode(final String base32) {
        int i, index, lookup, offset, digit;
        byte[] bytes = new byte[base32.length() * 5 / 8];

        for (i = 0, index = 0, offset = 0; i < base32.length(); i++) {
            lookup = base32.charAt(i) - '0';

            /* Skip chars outside the lookup table */
            if (lookup < 0 || lookup >= base32Lookup.length) {
                continue;
            }

            digit = base32Lookup[lookup];

            /* If this digit is not in the table, ignore it */
            if (digit == 0xFF) {
                continue;
            }

            if (index <= 3) {
                index = (index + 5) % 8;
                if (index == 0) {
                    bytes[offset] |= digit;
                    offset++;
                    if (offset >= bytes.length)
                        break;
                } else {
                    bytes[offset] |= digit << (8 - index);
                }
            } else {
                index = (index + 5) % 8;
                bytes[offset] |= (digit >>> index);
                offset++;

                if (offset >= bytes.length) {
                    break;
                }
                bytes[offset] |= digit << (8 - index);
            }
        }
        return bytes;
    }

    /** For testing, take a command-line argument in Base32, decode, print in hex,
     * encode, print
     *
     * @param args contains the base32 encoded string that should be decoded as the only element
     */
    static public void main(String[] args) {
        if (args.length == 0) {
            System.out.println("Supply a Base32-encoded argument.");
            return;
        }
        System.out.println(" Original: " + args[0]);
        byte[] decoded = Base32.decode(args[0]);
        System.out.print("      Hex: ");
        for (int i = 0; i < decoded.length; i++) {
            int b = decoded[i];
            if (b < 0) {
                b += 256;
            }
            System.out.print((Integer.toHexString(b + 256)).substring(1));
        }
        System.out.println();
        System.out.println("Reencoded: " + Base32.encode(decoded));
    }
}
src/org/freenetproject/freemail/SlotSaveCallback.java
public interface SlotSaveCallback {
	public void saveSlots(String slots, Object userdata);
}
src/org/freenetproject/freemail/wot/Identity.java
public class Identity {
	private final String identityID;
	private final String requestURI;
	private final String nickname;

	public Identity(String identityID, String requestURI, String nickname) {
		this.identityID = identityID;
		this.requestURI = requestURI;
		this.nickname = nickname;
	}

	public String getIdentityID() {
		return identityID;
	}

	public String getBase32IdentityID() {
		try {
			return Base32.encode(Base64.decode(identityID)).toLowerCase(Locale.ROOT);
		} catch (IllegalBase64Exception e) {
			//Can't happen since we always get the id from WoT
			throw new AssertionError();
		}
	}

	public String getRequestURI() {
		return requestURI;
	}

	public String getNickname() {
		return nickname;
	}

	@Override
	public int hashCode() {
		return identityID.hashCode();
	}

	@Override
	public boolean equals(Object obj) {
		if(!(obj instanceof Identity)) {
			return false;
		}

		Identity other = (Identity) obj;
		return identityID.equals(other.identityID);
	}

	@Override
	public String toString() {
		return "Identity " + identityID;
	}
}
src/org/freenetproject/freemail/fcp/ConnectionTerminatedException.java
public class ConnectionTerminatedException extends Exception {
	static final long serialVersionUID = -1;

	ConnectionTerminatedException() {
		super();
	}

	ConnectionTerminatedException(String s) {
		super(s);
	}
}
src/org/freenetproject/freemail/AccountManager.java
public class AccountManager {
	static final String ACCOUNT_FILE = "accprops";
	private static final int RTS_KEY_LENGTH = 32;

	private static final int ASYM_KEY_MODULUS_LENGTH = 4096;
	private static final BigInteger ASYM_KEY_EXPONENT = new BigInteger("17", 10);
	private static final int ASYM_KEY_CERTAINTY = 80;

	public static final String MAILSITE_SUFFIX = "mailsite";
	public static final String MAILSITE_VERSION = "-1";

	// We keep FreemailAccount objects for all the accounts in this instance of Freemail - they need to be in memory
	// anyway since there's SingleAccountWatcher thread running for each of them anyway - and we return the same object
	// each time a request is made for a given account.
	private Map<String, FreemailAccount> accounts = new HashMap<String, FreemailAccount>();

	//singleAccountWatcherList locks both these lists
	private final ArrayList<SingleAccountWatcher> singleAccountWatcherList = new ArrayList<SingleAccountWatcher>();
	private final ArrayList<Thread> singleAccountWatcherThreadList = new ArrayList<Thread>();

	private final File datadir;
	private final Freemail freemail;

	public AccountManager(File _datadir, Freemail freemail) {
		datadir = _datadir;
		if(!datadir.exists()) {
			datadir.mkdir();
		}

		this.freemail = freemail;

		for(File accountDir : datadir.listFiles()) {
			if(!accountDir.isDirectory()) {
				continue;
			}

			PropsFile accFile = getAccountFile(accountDir);
			if(accFile == null) {
				Logger.error(this, "Couldn't initialise account from directory '"+accountDir.getName()+"' - ignoring.");
				continue;
			}

			String identityId = Base64.encode(Base32.decode(accountDir.getName()));
			FreemailAccount account = new FreemailAccount(identityId, accountDir, accFile, freemail);
			account.setNickname(accFile.get("nickname"));
			synchronized(accounts) {
				accounts.put(account.getIdentity(), account);
			}
		}
	}

	public void startTasks() {
		synchronized(accounts) {
			for(FreemailAccount account : accounts.values()) {
				//Start the tasks needed for this account
				account.startTasks();

				//Now start a SingleAccountWatcher for this account
				SingleAccountWatcher saw = new SingleAccountWatcher(account, freemail);
				Thread t = new Thread(saw, "Freemail Account Watcher for "+account.getIdentity());
				t.setDaemon(true);
				t.start();

				synchronized(singleAccountWatcherList) {
					singleAccountWatcherList.add(saw);
					singleAccountWatcherThreadList.add(t);
				}
			}
		}
	}

	public FreemailAccount getAccount(String username) {
		synchronized(accounts) {
			return accounts.get(username);
		}
	}

	public List<FreemailAccount> getAllAccounts() {
		synchronized(accounts) {
			return new LinkedList<FreemailAccount>(accounts.values());
		}
	}

	public static void changePassword(FreemailAccount account, String newpassword) {
		MD5Digest md5 = new MD5Digest();

		try {
			byte[] passwordBytes = newpassword.getBytes("UTF-8");
			md5.update(passwordBytes, 0, passwordBytes.length);
		} catch (UnsupportedEncodingException e) {
			//JVMs are required to support UTF-8, so we can assume it is always available
			throw new AssertionError("JVM doesn't support UTF-8 charset");
		}
		byte[] md5passwd = new byte[md5.getDigestSize()];
		md5.doFinal(md5passwd, 0);
		String strmd5 = new String(Hex.encode(md5passwd));

		account.getProps().put("md5passwd", strmd5);
	}

	private static PropsFile getAccountFile(File accdir) {
		PropsFile accfile = PropsFile.createPropsFile(new File(accdir, ACCOUNT_FILE));

		if(!accdir.exists() || !accfile.exists()) {
			return null;
		}

		return accfile;
	}

	public static RSAKeyParameters getPrivateKey(PropsFile props) {
		String mod_str = props.get("asymkey.modulus");
		String privexp_str = props.get("asymkey.privexponent");

		if(mod_str == null || privexp_str == null) {
			Logger.error(AccountManager.class, "Couldn't get private key - account file corrupt?");
			return null;
		}

		return new RSAKeyParameters(true, new BigInteger(mod_str, 32), new BigInteger(privexp_str, 32));
	}

	private static boolean initAccFile(PropsFile accfile, OwnIdentity oid) {
		//Initialise RTS KSK
		// Use a secure RNG for this too.
		SecureRandom rnd = Freemail.getRNG();
		String rtskey = new String();

		int i;
		for(i = 0; i < RTS_KEY_LENGTH; i++) {
			rtskey += (char)(rnd.nextInt(26) + 'a');
		}

		if(!accfile.put("rtskey", rtskey)) {
			Logger.error(AccountManager.class, "Couldn't put rts key");
			return false;
		}

		// generate an RSA keypair
		Logger.normal(AccountManager.class, "Generating cryptographic keypair (this could take a few minutes)...");

		RSAKeyGenerationParameters kparams = new RSAKeyGenerationParameters(ASYM_KEY_EXPONENT, Freemail.getRNG(), ASYM_KEY_MODULUS_LENGTH, ASYM_KEY_CERTAINTY);

		RSAKeyPairGenerator kpg = new RSAKeyPairGenerator();
		kpg.init(kparams);

		AsymmetricCipherKeyPair keypair = kpg.generateKeyPair();
		RSAKeyParameters pub = (RSAKeyParameters) keypair.getPublic();
		RSAKeyParameters priv = (RSAKeyParameters) keypair.getPrivate();

		accfile.put("asymkey.modulus", pub.getModulus().toString(32));
		accfile.put("asymkey.pubexponent", pub.getExponent().toString(32));
		accfile.put("asymkey.privexponent", priv.getExponent().toString(32));

		String privateKey = oid.getInsertURI();
		privateKey = privateKey.substring(0, privateKey.indexOf("/"));
		privateKey = privateKey + "/mailsite/";
		accfile.put("mailsite.privkey", privateKey);

		Logger.normal(AccountManager.class, "Account creation completed.");
		return true;
	}

	public FreemailAccount authenticate(String username, String password) {
		FreemailAccount account = null;
		synchronized(accounts) {
			account = accounts.get(username);
		}
		if(account == null) return null;

		String realmd5str = account.getProps().get("md5passwd");
		if(realmd5str == null) return null;

		MD5Digest md5 = new MD5Digest();
		try {
			md5.update(password.getBytes("UTF-8"), 0, password.getBytes("UTF-8").length);
		} catch (UnsupportedEncodingException e) {
			//JVMs are required to support UTF-8, so we can assume it is always available
			throw new AssertionError("JVM doesn't support UTF-8 charset");
		}
		byte[] givenmd5 = new byte[md5.getDigestSize()];
		md5.doFinal(givenmd5, 0);

		String givenmd5str = new String(Hex.encode(givenmd5));

		if(realmd5str.equals(givenmd5str)) {
			return account;
		}
		return null;
	}

	private static void putWelcomeMessage(FreemailAccount account, EmailAddress to) throws IOException {
		SimpleDateFormat sdf = new SimpleDateFormat("dd MMM yyyy HH:mm:ss Z", Locale.ROOT);
		sdf.setTimeZone(TimeZone.getTimeZone("UTC"));

		MailMessage m = account.getMessageBank().createMessage();
		Date currentDate = new Date();

		m.addHeader("From", "Martin Nyhus <zidel@b5zswai7ybkmvcrfddlz5euw3ifzn5z5m3bzdgpucb26mzqvsflq.freemail>");
		m.addHeader("To", to.toString());
		m.addHeader("Subject", "Welcome to Freemail!");
		m.addHeader("Date", sdf.format(currentDate));
		m.addHeader("Content-Type", "text/plain;charset=\"us-ascii\"");
		m.addHeader("Content-Transfer-Encoding", "7bit");
		m.addHeader("Content-Disposition", "inline");
		m.addHeader("Message-id", "<freemail-welcome-msg@b5zswai7ybkmvcrfddlz5euw3ifzn5z5m3bzdgpucb26mzqvsflq.freemail>");

		PrintStream ps = m.writeHeadersAndGetStream();

		ps.println("Welcome to Freemail!");
		ps.println("");
		ps.println("Thanks for downloading and testing Freemail. Your new Freemail address is:");
		ps.println("");
		ps.println(to);
		ps.println("");
		ps.println("There are several places to discuss Freemail, report problems, or ask for help:");
		ps.println(" * The 'freemail' board on FMS");
		ps.println(" * The 'eng.freenet.freemail' board on Freetalk");
		ps.println(" * The bug tracker: https://bugs.freenetproject.org/ (select 'Freemail' in the top right).");
		ps.println(" * #freenet on irc.freenode.net");
		ps.println(" * The mailing list at http://emu.freenetproject.org/cgi-bin/mailman/listinfo/freemail.");
		ps.println(" * Send an email to zidel@b5zswai7ybkmvcrfddlz5euw3ifzn5z5m3bzdgpucb26mzqvsflq.freemail");
		ps.println("");
		ps.println("Don't forget to stay up to date with the Freemail news and latest version at the freesite, which can be found at:");
		ps.println("");
		ps.println("USK@M0d8y6YoLpXOeQGxu0-IDg8sE5Yt~Ky6t~GPyyZe~zo,KlqIjAj3~dA1Zf57VDljkmp3vHUozndpxnH-P2RRugI,AQACAAE/freemail/-8/");
		ps.println("");
		ps.println("Happy Freemailing!");
		ps.println("");
		ps.println("");
		ps.println("");
		ps.println("");
		ps.println("The Freemail developers");

		m.commit();
	}

	public void addIdentities(List<OwnIdentity> oids) {
		for(OwnIdentity oid : oids) {
			addIdentity(oid);
		}
	}

	private void addIdentity(OwnIdentity oid) {
		File accountDir = new File(datadir, oid.getBase32IdentityID());
		FreemailAccount account = null;
		PropsFile accProps = null;

		if(!accountDir.exists()) {
			//Need to create a new account
			if(!accountDir.mkdir()) {
				//Account creation failed, so don't start it and try again on next startup
				//FIXME: Need better error handling
				return;
			}

			accProps = PropsFile.createPropsFile(new File(accountDir, ACCOUNT_FILE));
			initAccFile(accProps, oid);

			account = new FreemailAccount(oid.getIdentityID(), accountDir, accProps, freemail);
			account.setNickname(oid.getNickname());

			String local = EmailAddress.cleanLocalPart(oid.getNickname());
			if(local.length() == 0) {
				local = "mail";
			}

			try {
				putWelcomeMessage(account, new EmailAddress(local + "@" + account.getDomain()));
			} catch (IOException e) {
				//FIXME: Handle this properly
				Logger.error(this, "Failed while sending welcome message to " + oid);
			}
		} else {
			accProps = PropsFile.createPropsFile(new File(accountDir, ACCOUNT_FILE));
			account = new FreemailAccount(oid.getIdentityID(), accountDir, accProps, freemail);
			account.setNickname(oid.getNickname());
		}

		accounts.put(account.getIdentity(), account);

		//Now start a SingleAccountWatcher for this account
		SingleAccountWatcher saw = new SingleAccountWatcher(account, freemail);
		Thread t = new Thread(saw, "Freemail Account Watcher for "+account.getIdentity());
		t.setDaemon(true);
		t.start();

		synchronized(singleAccountWatcherList) {
			singleAccountWatcherList.add(saw);
			singleAccountWatcherThreadList.add(t);
		}
	}

	void terminate() {
		synchronized(singleAccountWatcherList) {
			Iterator<SingleAccountWatcher> sawIt = singleAccountWatcherList.iterator();
			while(sawIt.hasNext()) {
				sawIt.next().kill();
				sawIt.remove();
			}

			Iterator<Thread> threadIt = singleAccountWatcherThreadList.iterator();
			while(threadIt.hasNext()) {
				threadIt.next().interrupt();
			}

			threadIt = singleAccountWatcherThreadList.iterator();
			while(threadIt.hasNext()) {
				try {
					threadIt.next().join();
				} catch (InterruptedException e) {
					Logger.error(this, "Got InterruptedException while joining SingleAccountWatcher thread");
				}
				threadIt.remove();
			}
		}
	}
}


Question: import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.net.MalformedURLException;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map.Entry;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import org.archive.util.Base32;
import org.bouncycastle.crypto.AsymmetricBlockCipher;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.engines.RSAEngine;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.paddings.PKCS7Padding;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.bouncycastle.crypto.params.RSAKeyParameters;
import org.freenetproject.freemail.AccountManager;
import org.freenetproject.freemail.Freemail;
import org.freenetproject.freemail.Freemail.TaskType;
import org.freenetproject.freemail.FreemailAccount;
import org.freenetproject.freemail.FreenetURI;
import org.freenetproject.freemail.SlotManager;
import org.freenetproject.freemail.SlotSaveCallback;
import org.freenetproject.freemail.fcp.ConnectionTerminatedException;
import org.freenetproject.freemail.fcp.FCPBadFileException;
import org.freenetproject.freemail.fcp.FCPException;
import org.freenetproject.freemail.fcp.FCPFetchException;
import org.freenetproject.freemail.fcp.FCPPutFailedException;
import org.freenetproject.freemail.fcp.HighLevelFCPClient;
import org.freenetproject.freemail.fcp.SSKKeyPair;
import org.freenetproject.freemail.utils.DateStringFactory;
import org.freenetproject.freemail.utils.Logger;
import org.freenetproject.freemail.utils.PropsFile;
import org.freenetproject.freemail.utils.Timer;
import org.freenetproject.freemail.wot.Identity;
import org.freenetproject.freemail.wot.WoTConnection;
import org.freenetproject.freemail.wot.WoTProperties;
import freenet.keys.InsertableClientSSK;
import freenet.pluginmanager.PluginNotFoundException;
import freenet.support.api.Bucket;
import freenet.support.io.ArrayBucket;
import freenet.support.io.BucketTools;
import freenet.support.io.Closer;
		if((state != null) && state.equals("cts-received")) {
			return;
		}

		rtsSender.execute();
	}

	/**
	 * Sends the message that is read from {@code message}, returning {@code true} if the message
	 * was inserted. The caller is responsible for freeing {@code message}.
	 * @param message the data to be sent
	 * @param messageId the message id that has been assigned to this message
	 * @return {@code true} if the message was sent, {@code false} otherwise
	 * @throws ChannelTimedOutException if the channel has timed out and can't be used for sending
	 *             messages
	 * @throws IOException if any operations on {@code message} throws IOException
	 * @throws InterruptedException if the current thread was interrupted while sending the message
	 * @throws NullPointerException if {@code message} is {@code null}
	 */
	boolean sendMessage(Bucket message, long messageId) throws ChannelTimedOutException, IOException, InterruptedException {
		if(message == null) throw new NullPointerException("Parameter message was null");

		synchronized(channelProps) {
			String rawTimeout = channelProps.get(PropsKeys.TIMEOUT);
			if(rawTimeout != null) {
				long timeout;
				try {
					timeout = Long.parseLong(rawTimeout);
				} catch(NumberFormatException e) {
					timeout = 0;
				}

				if(timeout < System.currentTimeMillis()) {
					throw new ChannelTimedOutException();
				}
			}
		}

		//Build the header of the inserted message
		String header =
			"messagetype=message\r\n"
			+ "id=" + messageId + "\r\n"
			+ "\r\n";
		Bucket messageHeader = new ArrayBucket(header.getBytes("UTF-8"));

		//Now combine them in a single bucket
		ArrayBucket fullMessage = new ArrayBucket();
		OutputStream messageOutputStream = null;
		try {
			messageOutputStream = fullMessage.getOutputStream();
			BucketTools.copyTo(messageHeader, messageOutputStream, -1);
			BucketTools.copyTo(message, messageOutputStream, -1);
		} finally {
			Closer.close(messageOutputStream);
		}

		return insertMessage(fullMessage, "msg" + messageId);
	}

	/**
	 * Inserts the given message to the next available slot, returning {@code true} if the message
	 * was inserted, {@code false} otherwise.
	 * @param message the message that should be inserted
	 * @return {@code true} if the message was inserted, {@code false} otherwise
	 * @throws IOException if the getInputStream() method of message throws IOException
	 * @throws InterruptedException if the current thread was interrupted while inserting the message
	 */
	private boolean insertMessage(Bucket message, String prefix) throws IOException, InterruptedException {
		String privateKey;
		String sendCode;
		synchronized (channelProps) {
			privateKey = channelProps.get(PropsKeys.PRIVATE_KEY);
			sendCode = channelProps.get(PropsKeys.SEND_CODE);

			if(privateKey == null) {
				/* Most likely because we tried sending the message before sending the RTS */
				Logger.minor(this, "Can't insert, missing private key");
				return false;
			}
			if(sendCode == null) {
				/* If we have the private key, but not the send code something
				 * is wrong since sending/receiving an RTS stores these atomically */
				/* FIXME: Perhaps set the timeout to 0 so the channel will be deleted? */
				Logger.error(this, "Can't insert, missing send code but have private key");
				return false;
			}
		}

		while(true) {
			/* First we get the slot for this message if one has been assigned */
			String sendSlot;
			synchronized(channelProps) {
				/* If a slot has been assigned, use it */
				sendSlot = channelProps.get(prefix + PropsKeys.MSG_SLOT);
				if(sendSlot == null) {
					/* If not, assign the next free slot */
					sendSlot = channelProps.get(PropsKeys.SEND_SLOT);
					String nextSlot = calculateNextSlot(sendSlot);
					channelProps.put(PropsKeys.SEND_SLOT, nextSlot);
					channelProps.put(prefix + PropsKeys.MSG_SLOT, sendSlot);

					Logger.debug(this, "Assigned slot " + sendSlot + " to message " + prefix);
				}
			}

			String insertKey = privateKey + sendCode + "-" + sendSlot;

			InputStream messageStream = null;
			try {
				messageStream = message.getInputStream();
				Logger.minor(this, "Inserting data");
				Logger.debug(this, "Insert key is " + insertKey);
				FCPPutFailedException fcpMessage;
				try {
					Timer messageInsert = Timer.start();
					fcpMessage = fcpClient.put(messageStream, insertKey);
					messageInsert.log(this, 1, TimeUnit.HOURS, "Time spent inserting message");
				} catch(FCPBadFileException e) {
					Logger.error(this, "Caugth FCPBadFileException while inserting message", e);
					return false;
Answer: 				} catch(ConnectionTerminatedException e) {