package edu.washington.cs.oneswarm.f2f.invitations; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.security.InvalidKeyException; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.Semaphore; import java.util.logging.Logger; import org.bouncycastle.util.encoders.Base64; import org.gudy.azureus2.core3.util.Base32; import org.gudy.azureus2.core3.util.HashWrapper; import org.gudy.azureus2.core3.util.SHA1Simple; import com.aelitis.azureus.core.AzureusCore; import com.aelitis.azureus.core.AzureusCoreComponent; import com.aelitis.azureus.core.AzureusCoreException; import com.aelitis.azureus.core.AzureusCoreLifecycleListener; import com.aelitis.azureus.core.impl.AzureusCoreImpl; import com.aelitis.azureus.core.networkmanager.ConnectionEndpoint; import com.aelitis.azureus.core.networkmanager.NetworkConnection; import edu.washington.cs.oneswarm.f2f.Friend; import edu.washington.cs.oneswarm.f2f.FriendInvitation; import edu.washington.cs.oneswarm.f2f.OSF2FMain; import edu.washington.cs.oneswarm.f2f.FriendInvitation.Status; import edu.washington.cs.oneswarm.f2f.friends.FriendManager; import edu.washington.cs.oneswarm.f2f.messaging.invitation.OSF2FAuthMessageFatory; import edu.washington.cs.oneswarm.f2f.xml.OSF2FXMLBeanReader; import edu.washington.cs.oneswarm.f2f.xml.OSF2FXMLBeanWriter; import edu.washington.cs.oneswarm.f2f.xml.OSF2FXMLBeanReader.OSF2FXMLBeanReaderCallback; public class InvitationManager { private static final long BANNED_PERIOD = 60 * 60 * 1000; private static final String OSF2F_INVITE_FILE = "osf2f.invites"; private static Logger logger = Logger.getLogger(InvitationManager.class.getName()); private Map<String, InvitationConnection> authConnections = new HashMap<String, InvitationConnection>(); private Map<String, Long> bannedIps = new HashMap<String, Long>(); private AuthCallback callback = new AuthCallback() { public void authenticated(FriendInvitation invitation) throws InvalidKeyException { synchronized (InvitationManager.this) { Friend f = new Friend("Invited", invitation.getName(), new String( Base64.encode(invitation.getRemotePublicKey()))); f.setCanSeeFileList(invitation.isCanSeeFileList()); f.setRequestFileList(invitation.isCanSeeFileList()); f.setBlocked(false); f.setAllowChat(true); f.setDateAdded(new Date()); try { f.setLastConnectIP(InetAddress.getByName(invitation.getLastConnectIp())); } catch (UnknownHostException e) { // TODO Auto-generated catch block e.printStackTrace(); } f.setLastConnectPort(invitation.getLastConnectPort()); f.setAllowChat(true); friendManager.addFriend(f); invitation.setStatus(Status.STATUS_SUCCESS); logger.fine("adding friend: " + f); } } public void banIp(String remoteIP) { synchronized (InvitationManager.this) { bannedIps.put(remoteIP, System.currentTimeMillis()); } } public void closed(InvitationConnection conn) { synchronized (InvitationManager.this) { authConnections.remove(conn.getRemoteIp().getHostAddress()); } } public FriendInvitation getInvitationFromInviteKey(HashWrapper key) { synchronized (InvitationManager.this) { FriendInvitation friendInvitation = invitations.get(key); if (friendInvitation != null) { if (friendInvitation.isCreatedLocally() && friendInvitation.isStillValid()) { return friendInvitation; } } return null; } } /** * this is for getting the invitation if we get an incoming connection * for an invite that we redeem */ public FriendInvitation getInvitationFromPublicKey(byte[] remotePublicKey) { FriendInvitation invitation = InvitationManager.this .getInvitationFromPublicKey(remotePublicKey); if (invitation != null && invitation.isStillValid()) { return invitation; } return null; } }; public FriendInvitation getInvitationFromPublicKey(byte[] remotePublicKey) { synchronized (InvitationManager.this) { for (FriendInvitation i : invitations.values()) { if (i.isRedeemed()) { if (i.pubKeyMatch(remotePublicKey)) { return i; } } } return null; } } private final ClassLoader cl; private FriendManager friendManager; private HashMap<HashWrapper, FriendInvitation> invitations = new HashMap<HashWrapper, FriendInvitation>(); private final byte[] localPublicKey; private final SecureRandom random; private final Semaphore diskSemaphore = new Semaphore(1); public InvitationManager(ClassLoader classLoader, FriendManager friendManager, byte[] localPublicKey) { this.friendManager = friendManager; this.localPublicKey = localPublicKey; this.random = new SecureRandom(); this.cl = classLoader; OSF2FAuthMessageFatory.init(); readFromDisk(); Timer timer = new Timer("invitation write timer", true); timer.schedule(new TimerTask() { @Override public void run() { flushToDisk(false, true, false); } }, 5 * 60 * 1000, 2 * 60 * 1000); registerShutdownHook(); } public void connectToInvitations() { this.waitForRead(); for (FriendInvitation invitation : invitations.values()) { if (invitation.getStatus().getCode() >= 0 && invitation.getStatus() != Status.STATUS_SUCCESS) { OSF2FMain.getSingelton().getDHTConnector().connectToInvitation(invitation); } } } private void registerShutdownHook() { AzureusCoreImpl.getSingleton().addLifecycleListener(new AzureusCoreLifecycleListener() { public void componentCreated(AzureusCore core, AzureusCoreComponent component) { // TODO Auto-generated method stub } public boolean restartRequested(AzureusCore core) throws AzureusCoreException { // TODO Auto-generated method stub return false; } public void started(AzureusCore core) { // TODO Auto-generated method stub } public void stopped(AzureusCore core) { // TODO Auto-generated method stub } public void stopping(AzureusCore core) { logger.fine("stopping, "); flushToDisk(false, true, false); } public boolean stopRequested(AzureusCore core) throws AzureusCoreException { // System.out // .println("stop requested, flushing friends to disk"); // flushToDisk(false); return true; } public boolean syncInvokeRequired() { // TODO Auto-generated method stub return false; } }); } public FriendInvitation createInvitation(String name, boolean canSeeFileList, long maxAge, byte securityLevel) { FriendInvitation invitation = new FriendInvitation(generateNewInvitationKey(securityLevel)); invitation.setName(name); invitation.setCanSeeFileList(canSeeFileList); invitation.setMaxAge(maxAge); invitation.setSecurityLevel(securityLevel); invitation.setCreatedLocally(true); invitation.setStatus(Status.STATUS_NEW); invitation.setCreatedDate(System.currentTimeMillis()); this.waitForRead(); synchronized (InvitationManager.this) { this.invitations.put(new HashWrapper(invitation.getKey()), invitation); OSF2FMain.getSingelton().getDHTConnector().publishInvitation(invitation); logger.fine("creating invitation: num=" + invitations.size()); logger.finer(invitation.toString()); } flushToDisk(true, false, false); return invitation; } public void deleteInvitation(FriendInvitation invitation) { this.waitForRead(); synchronized (InvitationManager.this) { this.invitations.remove(new HashWrapper(invitation.getKey())); } flushToDisk(true, false, true); } public List<FriendInvitation> getInvitations() { this.waitForRead(); synchronized (InvitationManager.this) { ArrayList<FriendInvitation> i = new ArrayList<FriendInvitation>(); i.addAll(invitations.values()); return i; } } private byte[] generateNewInvitationKey(byte securityLevel) { // the invitation code is INV_DHT_KEY_LENGTH bytes long byte[] key = new byte[FriendInvitation.INV_KEY_LENGTH]; // fill it with random bytes random.nextBytes(key); // the first INV_PUB_KEY_VERIFICATION_LENGTH bytes in the key is the // lower INV_PUB_KEY_VERIFICATION_LENGTH bytes of the sha1 of // the local public key byte[] localPubKeySha1 = new SHA1Simple().calculateHash(localPublicKey); logger.finest("local public key: " + new String(Base64.encode(localPublicKey))); logger.finest("local pubkeysha1: " + Base32.encode(localPubKeySha1)); System.arraycopy(localPubKeySha1, 0, key, 0, FriendInvitation.INV_PUB_KEY_VERIFICATION_LENGTH); /* * set the security level in the last field so the receiver knows */ key[FriendInvitation.INV_SECURITY_TYPE_POS] = securityLevel; logger.finer("invitekey=" + Base32.encode(key)); return key; } public FriendInvitation getInvitation(HashWrapper key) { this.waitForRead(); synchronized (InvitationManager.this) { return invitations.get(key); } } public List<FriendInvitation> getLocallyCreatedInvitations() { this.waitForRead(); synchronized (InvitationManager.this) { List<FriendInvitation> inv = new LinkedList<FriendInvitation>(); for (FriendInvitation friendInvitation : invitations.values()) { if (friendInvitation.isCreatedLocally()) { inv.add(friendInvitation); } } return inv; } } public List<FriendInvitation> getRedeemedInvitations() { this.waitForRead(); List<FriendInvitation> inv = new ArrayList<FriendInvitation>(); for (FriendInvitation friendInvitation : invitations.values()) { if (friendInvitation.isRedeemed()) { inv.add(friendInvitation); } } return inv; } public boolean newIncomingConnection(byte[] publicKey, NetworkConnection netConn) { synchronized (InvitationManager.this) { String remoteIp = netConn.getEndpoint().getNotionalAddress().getAddress() .getHostAddress(); logger.fine("new incoming auth connetion: " + remoteIp); /* * first, check if banned */ Long lastBanned = bannedIps.get(remoteIp); if (lastBanned != null && System.currentTimeMillis() < lastBanned + BANNED_PERIOD) { logger.fine("banned ip (" + remoteIp + "), closing"); return false; } if (authConnections.containsKey(remoteIp)) { logger.fine("already connected to ip (" + remoteIp + "), closing"); return false; } authConnections.put(remoteIp, new InvitationConnection(publicKey, netConn, callback)); return true; } } public boolean newOutgoingConnection(ConnectionEndpoint remoteFriendAddr, FriendInvitation invitation) { synchronized (InvitationManager.this) { InetSocketAddress notionalAddress = remoteFriendAddr.getNotionalAddress(); InetAddress address = notionalAddress.getAddress(); String remoteIp = address.getHostAddress(); logger.fine("making outgoing auth connection to: " + remoteIp); if (authConnections.containsKey(remoteIp)) { return false; } authConnections.put(remoteIp, new InvitationConnection(remoteFriendAddr, invitation, callback)); return true; } } public void redeemInvitation(FriendInvitation invitation, boolean testOnly) throws Exception { this.waitForRead(); synchronized (InvitationManager.this) { HashWrapper key = new HashWrapper(invitation.getKey()); if (invitation.pubKeyMatch(localPublicKey)) { throw new Exception("Invitation created by this computer"); } if (invitations.get(key) != null) { throw new Exception("Invitation already redeemed"); } if (!testOnly) { invitations.put(key, invitation); OSF2FMain.getSingelton().getDHTConnector().publishInvitation(invitation); OSF2FMain.getSingelton().getDHTConnector().connectToInvitation(invitation); flushToDisk(true, false, false); } } } public void updateInvitation(FriendInvitation invitation) { this.waitForRead(); synchronized (InvitationManager.this) { HashWrapper key = new HashWrapper(invitation.getKey()); if (this.invitations.containsKey(key)) { this.invitations.put(key, invitation); } else { throw new RuntimeException("Unable to update invitation, not found"); } } } private void flushToDisk(boolean makeBackup, boolean block, boolean allowDecreasedSize) { this.waitForRead(); synchronized (InvitationManager.this) { InvitationBean[] b = new InvitationBean[invitations.size()]; FriendInvitation[] f = invitations.values().toArray( new FriendInvitation[invitations.size()]); for (int i = 0; i < b.length; i++) { b[i] = InvitationBean.createBean(f[i]); } Thread t = new Thread(new OSF2FXMLBeanWriter<InvitationBean>(b, diskSemaphore, OSF2F_INVITE_FILE, makeBackup, allowDecreasedSize)); t.setName("OSF2FXMLBeanWriter - flushToDisk (inviationbean)"); t.setContextClassLoader(cl); t.start(); if (block) { try { t.join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } private boolean readCompleted = false; private void waitForRead() { if (!readCompleted) { try { diskSemaphore.acquire(); diskSemaphore.release(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } readCompleted = true; } } private void readFromDisk() { try { diskSemaphore.acquire(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } OSF2FXMLBeanReader<InvitationBean> reader = new OSF2FXMLBeanReader<InvitationBean>(cl, InvitationBean.class, OSF2F_INVITE_FILE, diskSemaphore, new OSF2FXMLBeanReaderCallback<InvitationBean>() { public void readObject(InvitationBean object) { FriendInvitation invitation = InvitationBean.getInvitation(object); invitations.put(new HashWrapper(invitation.getKey()), invitation); } public void completed() { // TODO Auto-generated method stub } }); Thread t = new Thread(reader); t.setDaemon(true); t.start(); // reader.run(); } public interface AuthCallback { public void authenticated(FriendInvitation invitation) throws InvalidKeyException; public void banIp(String remoteIP); public void closed(InvitationConnection conn); public FriendInvitation getInvitationFromInviteKey(HashWrapper key); public FriendInvitation getInvitationFromPublicKey(byte[] remotePublicKey); } }