+ * @return null as no such data is needed. + */ + public String getPersistentData() + { + return null; + } + + /** + * Determines whether or not this contact has been resolved against the + * server. Unresolved contacts are used when initially loading a contact + * list that has been stored in a local file until the presence operation + * set has managed to retrieve all the contact list from the server and has + * properly mapped contacts to their on-line buddies. + * @return true if the contact has been resolved (mapped against a buddy) + * and false otherwise. + */ + public boolean isResolved() + { + return isResolved; + } + + /** + * Makes the group resolved or unresolved. + * + * @param resolved true to make the group resolved; false to + * make it unresolved + */ + public void setResolved(boolean resolved) + { + this.isResolved = resolved; + } + + /** + * Returns a String that uniquely represnets the group inside + * the current protocol. The string MUST be persistent (it must not change + * across connections or runs of the application). In many cases (Jabber, + * ICQ) the string may match the name of the group as these protocols + * only allow a single level of contact groups and there is no danger of + * having the same name twice in the same contact list. Other protocols + * (no examples come to mind but that doesn't bother me ;) ) may be + * supporting mutilple levels of grooups so it might be possible for group + * A and group B to both contain groups named C. In such cases the + * implementation must find a way to return a unique identifier in this + * method and this UID should never change for a given group. + * + * @return a String representing this group in a unique and persistent + * way. + */ + public String getUID() + { + return uid; + } + + /** + * Ugly but tricky conversion method. + * @param uid the uid we'd like to get a name from + * @return the name of the group with the specified uid. + */ + static String createNameFromUID(String uid) + { + return uid.substring(0, uid.length() - (UID_SUFFIX.length())); + } + + /** + * Indicates whether some other object is "equal to" this one which in terms + * of contact groups translates to having the equal names and matching + * subgroups and child contacts. The resolved status of contactgroups and + * contacts is deliberately ignored so that groups and/or contacts would + * be assumed equal even if it differs. + *
+ * @param obj the reference object with which to compare.
+ * @return
+ * @param obj the reference object with which to compare.
+ * @return
+ * @param contactIdentifier the contact whose status updates we are
+ * subscribing for.
+ * @throws IllegalArgumentException if contact or
+ * parent are not a contact known to the underlying protocol
+ * provider.
+ * @throws IllegalStateException if the underlying protocol provider is
+ * not registered/signed on a public service.
+ * @throws OperationFailedException with code NETWORK_FAILURE if
+ * subscribing fails due to errors experienced during network
+ * communication
+ */
+ public void subscribe(
+ ContactGroup parent,
+ String contactIdentifier)
+ throws IllegalArgumentException,
+ IllegalStateException,
+ OperationFailedException
+ {
+ BundleContext context = SSHActivator.getBundleContext();
+
+ ContactSSH sshContact = new ContactSSHImpl(contactIdentifier,
+ parentProvider);
+
+/* ProtocolProviderServiceSSHImpl.getUIService().getConfigurationWindow()
+ .setVisible(true);
+*/
+ sshContact.getSSHConfigurationForm().setVisible(true);
+
+
+
+/* Gets the domain name or IP address of the sshContact machine via the
+ * UI Service Interface
+ sshContact.setPersistentData(ProtocolProviderServiceSSHImpl
+ .getUIService().getPopupDialog()
+ .showInputPopupDialog("Enter Domain Name or IP Address of "
+ + sshContact.getDisplayName()));
+
+ // contact is added to list later after the user has provided
+ // details in SSHConfigurationForm
+
+ // addContactToList method is called
+*/
+ //add contact to contact list
+ addContactToList(parent, sshContact);
+ }
+
+ /**
+ * Add a contact to the specified group
+ *
+ * @param parent the group
+ * @param sshContact the contact
+ */
+ public void addContactToList(
+ ContactGroup parent,
+ ContactSSH sshContact)
+ {
+ // Adds the sshContact to the sshContact list
+
+ ((ContactGroupSSHImpl)parent).addContact(sshContact);
+
+ fireSubscriptionEvent(sshContact,
+ parent,
+ SubscriptionEvent.SUBSCRIPTION_CREATED);
+
+ //notify presence listeners for the status change.
+ fireContactPresenceStatusChangeEvent(sshContact
+ , parent
+ , SSHStatusEnum.NOT_AVAILABLE);
+
+ sshContact.startTimerTask();
+ }
+
+ /**
+ * Adds a subscription for the presence status of the contact
+ * corresponding to the specified contactIdentifier.
+ *
+ * @param contactIdentifier the identifier of the contact whose status
+ * updates we are subscribing for.
+ * @throws IllegalArgumentException if contact is not a contact
+ * known to the underlying protocol provider
+ * @throws IllegalStateException if the underlying protocol provider is
+ * not registered/signed on a public service.
+ * @throws OperationFailedException with code NETWORK_FAILURE if
+ * subscribing fails due to errors experienced during network
+ * communication
+ */
+ public void subscribe(String contactIdentifier) throws
+ IllegalArgumentException,
+ IllegalStateException,
+ OperationFailedException
+ {
+ subscribe(contactListRoot, contactIdentifier);
+
+ }
+
+ /**
+ * Removes a subscription for the presence status of the specified
+ * contact.
+ *
+ * @param contact the contact whose status updates we are unsubscribing
+ * from.
+ * @throws IllegalArgumentException if contact is not a contact
+ * known to the underlying protocol provider
+ * @throws IllegalStateException if the underlying protocol provider is
+ * not registered/signed on a public service.
+ * @throws OperationFailedException with code NETWORK_FAILURE if
+ * unsubscribing fails due to errors experienced during network
+ * communication
+ */
+ public void unsubscribe(Contact contact) throws
+ IllegalArgumentException,
+ IllegalStateException,
+ OperationFailedException
+ {
+ ContactGroupSSHImpl parentGroup
+ = (ContactGroupSSHImpl)((ContactSSHImpl)contact)
+ .getParentContactGroup();
+
+ parentGroup.removeContact((ContactSSHImpl)contact);
+
+ fireSubscriptionEvent((ContactSSHImpl)contact,
+ ((ContactSSHImpl)contact).getParentContactGroup()
+ , SubscriptionEvent.SUBSCRIPTION_REMOVED);
+ }
+
+ /**
+ * Creates and returns a unresolved contact from the specified
+ * address and persistentData. The method will not try
+ * to establish a network connection and resolve the newly created Contact
+ * against the server. The protocol provider may will later try and resolve
+ * the contact. When this happens the corresponding event would notify
+ * interested subscription listeners.
+ *
+ * @param address an identifier of the contact that we'll be creating.
+ * @param persistentData a String returned Contact's getPersistentData()
+ * method during a previous run and that has been persistently stored
+ * locally.
+ * @return the unresolved Contact created from the specified
+ * address and persistentData
+ */
+ public Contact createUnresolvedContact(
+ String address,
+ String persistentData)
+ {
+ return createUnresolvedContact(address
+ , persistentData
+ , getServerStoredContactListRoot());
+ }
+
+ /**
+ * Creates and returns a unresolved contact from the specified
+ * address and persistentData. The method will not try
+ * to establish a network connection and resolve the newly created Contact
+ * against the server. The protocol provider may will later try and resolve
+ * the contact. When this happens the corresponding event would notify
+ * interested subscription listeners.
+ *
+ * @param address an identifier of the contact that we'll be creating.
+ * @param persistentData a String returned Contact's getPersistentData()
+ * method during a previous run and that has been persistently stored
+ * locally.
+ * @param parent the group where the unresolved contact is
+ * supposed to belong to.
+ *
+ * @return the unresolved Contact created from the specified
+ * address and persistentData
+ */
+ public Contact createUnresolvedContact(
+ String address,
+ String persistentData,
+ ContactGroup parent)
+ {
+ ContactSSH contact = new ContactSSHImpl(
+ address,
+ parentProvider);
+
+ contact.setPersistentData(persistentData);
+ contact.startTimerTask();
+
+ // SSH Contacts are resolved by default
+ contact.setResolved(true);
+
+ ( (ContactGroupSSHImpl) parent).addContact(contact);
+
+ fireSubscriptionEvent(contact,
+ parent,
+ SubscriptionEvent.SUBSCRIPTION_CREATED);
+
+ //since we don't have any server, we'll simply resolve the contact
+ //ourselves as if we've just received an event from the server telling
+ //us that it has been resolved.
+ fireSubscriptionEvent(
+ contact, parent, SubscriptionEvent.SUBSCRIPTION_RESOLVED);
+
+ return contact;
+ }
+
+ /**
+ * Looks for a ssh protocol provider registered for a user id matching
+ * sshUserID.
+ *
+ * @param sshUserID the ID of the SSH user whose corresponding
+ * protocol provider we'd like to find.
+ * @return ProtocolProviderServiceSSHImpl a ssh protocol
+ * provider registered for a user with id sshUserID or null
+ * if there is no such protocol provider.
+ */
+ public ProtocolProviderServiceSSHImpl
+ findProviderForSSHUserID(String sshUserID)
+ {
+ BundleContext bc = SSHActivator.getBundleContext();
+
+ String osgiQuery = "(&"
+ + "(" + ProtocolProviderFactory.PROTOCOL
+ + "=SSH)"
+ + "(" + ProtocolProviderFactory.USER_ID
+ + "=" + sshUserID + ")"
+ + ")";
+
+ ServiceReference[] refs = null;
+ try
+ {
+ refs = bc.getServiceReferences(
+ ProtocolProviderService.class.getName()
+ ,osgiQuery);
+ }
+ catch (InvalidSyntaxException ex)
+ {
+ logger.error("Failed to execute the following osgi query: "
+ + osgiQuery
+ , ex);
+ }
+
+ if(refs != null && refs.length > 0)
+ {
+ return (ProtocolProviderServiceSSHImpl)bc.getService(refs[0]);
+ }
+
+ return null;
+ }
+
+ /**
+ * Looks for ssh protocol providers that have added us to their
+ * contact list and returns list of all contacts representing us in these
+ * providers.
+ *
+ * @return a list of all contacts in other providers' contact lists that
+ * point to us.
+ */
+ public List findContactsPointingToUs()
+ {
+ List contacts = new LinkedList();
+ BundleContext bc = SSHActivator.getBundleContext();
+
+ String osgiQuery =
+ "(" + ProtocolProviderFactory.PROTOCOL
+ + "=SSH)";
+
+ ServiceReference[] refs = null;
+ try
+ {
+ refs = bc.getServiceReferences(
+ ProtocolProviderService.class.getName()
+ ,osgiQuery);
+ }
+ catch (InvalidSyntaxException ex)
+ {
+ logger.error("Failed to execute the following osgi query: "
+ + osgiQuery
+ , ex);
+ }
+
+ for (int i =0; refs != null && i < refs.length; i++)
+ {
+ ProtocolProviderServiceSSHImpl gibProvider
+ = (ProtocolProviderServiceSSHImpl)bc.getService(refs[i]);
+
+ OperationSetPersistentPresenceSSHImpl opSetPersPresence
+ = (OperationSetPersistentPresenceSSHImpl)gibProvider
+ .getOperationSet(OperationSetPersistentPresence.class);
+
+ Contact contact = opSetPersPresence.findContactByID(
+ parentProvider.getAccountID().getUserID());
+
+ if (contact != null)
+ contacts.add(contact);
+ }
+
+ return contacts;
+ }
+
+
+ /**
+ * Creates and returns a unresolved contact group from the specified
+ * address and persistentData. The method will not try
+ * to establish a network connection and resolve the newly created
+ * ContactGroup against the server or the contact itself. The
+ * protocol provider will later resolve the contact group. When this happens
+ * the corresponding event would notify interested subscription listeners.
+ *
+ * @param groupUID an identifier, returned by ContactGroup's getGroupUID,
+ * that the protocol provider may use in order to create the group.
+ * @param persistentData a String returned ContactGroups's
+ * getPersistentData() method during a previous run and that has been
+ * persistently stored locally.
+ * @param parentGroup the group under which the new group is to be created
+ * or null if this is group directly underneath the root.
+ * @return the unresolved ContactGroup created from the specified
+ * uid and persistentData
+ */
+ public ContactGroup createUnresolvedContactGroup(
+ String groupUID,
+ String persistentData,
+ ContactGroup parentGroup)
+ {
+ ContactGroupSSHImpl newGroup
+ = new ContactGroupSSHImpl(
+ ContactGroupSSHImpl.createNameFromUID(groupUID)
+ , parentProvider);
+ newGroup.setResolved(false);
+
+ //if parent is null then we're adding under root.
+ if(parentGroup == null)
+ parentGroup = getServerStoredContactListRoot();
+
+ ((ContactGroupSSHImpl)parentGroup).addSubgroup(newGroup);
+
+ this.fireServerStoredGroupEvent(
+ newGroup, ServerStoredGroupEvent.GROUP_CREATED_EVENT);
+
+ return newGroup;
+ }
+
+ private class UnregistrationListener
+ implements RegistrationStateChangeListener
+ {
+ /**
+ * The method is called by a ProtocolProvider implementation whenver
+ * a change in the registration state of the corresponding provider had
+ * occurred. The method is particularly interested in events stating
+ * that the ssh provider has unregistered so that it would fire
+ * status change events for all contacts in our buddy list.
+ *
+ * @param evt ProviderStatusChangeEvent the event describing the status
+ * change.
+ */
+ public void registrationStateChanged(RegistrationStateChangeEvent evt)
+ {
+ if (! evt.getNewState().equals(RegistrationState.UNREGISTERED)
+ && !evt.getNewState().equals(RegistrationState
+ .AUTHENTICATION_FAILED)
+ && !evt.getNewState().equals(RegistrationState.CONNECTION_FAILED))
+ {
+ return;
+ }
+
+ //send event notifications saying that all our buddies are
+ //offline. The icq protocol does not implement top level buddies
+ //nor subgroups for top level groups so a simple nested loop
+ //would be enough.
+ Iterator groupsIter = getServerStoredContactListRoot()
+ .subgroups();
+ while (groupsIter.hasNext())
+ {
+ ContactGroupSSHImpl group
+ = (ContactGroupSSHImpl) groupsIter.next();
+
+ Iterator contactsIter = group.contacts();
+
+ while (contactsIter.hasNext())
+ {
+ ContactSSHImpl contact
+ = (ContactSSHImpl) contactsIter.next();
+
+ PresenceStatus oldContactStatus
+ = contact.getPresenceStatus();
+
+ if (!oldContactStatus.isOnline())
+ continue;
+
+ contact.setPresenceStatus(SSHStatusEnum.OFFLINE);
+
+ fireContactPresenceStatusChangeEvent(
+ contact
+ , contact.getParentContactGroup()
+ , oldContactStatus);
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the volatile group or null if this group has not yet been
+ * created.
+ *
+ * @return a volatile group existing in our contact list or null
+ * if such a group has not yet been created.
+ */
+ private ContactGroupSSHImpl getNonPersistentGroup()
+ {
+ for (int i = 0
+ ; i < getServerStoredContactListRoot().countSubgroups()
+ ; i++)
+ {
+ ContactGroupSSHImpl gr =
+ (ContactGroupSSHImpl)getServerStoredContactListRoot()
+ .getGroup(i);
+
+ if(!gr.isPersistent())
+ return gr;
+ }
+
+ return null;
+ }
+
+
+ /**
+ * Creates a non persistent contact for the specified address. This would
+ * also create (if necessary) a group for volatile contacts that would not
+ * be added to the server stored contact list. This method would have no
+ * effect on the server stored contact list.
+ *
+ * @param contactAddress the address of the volatile contact we'd like to
+ * create.
+ * @return the newly created volatile contact.
+ */
+ public ContactSSHImpl createVolatileContact(String contactAddress)
+ {
+ //First create the new volatile contact;
+ ContactSSHImpl newVolatileContact = new ContactSSHImpl(
+ contactAddress,
+ this.parentProvider);
+
+ newVolatileContact.setPersistent(false);
+
+
+ //Check whether a volatile group already exists and if not create
+ //one
+ ContactGroupSSHImpl theVolatileGroup = getNonPersistentGroup();
+
+
+ //if the parent volatile group is null then we create it
+ if (theVolatileGroup == null)
+ {
+ List emptyBuddies = new LinkedList();
+ theVolatileGroup = new ContactGroupSSHImpl(
+ "NotInContactList"
+ , parentProvider);
+ theVolatileGroup.setResolved(false);
+ theVolatileGroup.setPersistent(false);
+ theVolatileGroup.addContact(newVolatileContact);
+
+ this.contactListRoot.addSubgroup(theVolatileGroup);
+
+ fireServerStoredGroupEvent(theVolatileGroup
+ , ServerStoredGroupEvent.GROUP_CREATED_EVENT);
+ }
+
+ //now add the volatile contact instide it
+ theVolatileGroup.addContact(newVolatileContact);
+ fireSubscriptionEvent(newVolatileContact
+ , theVolatileGroup
+ , SubscriptionEvent.SUBSCRIPTION_CREATED);
+
+ return newVolatileContact;
+ }
+
+ /**
+ * DUMMY METHOD
+ * Handler for incoming authorization requests.
+ *
+ * @param handler an instance of an AuthorizationHandler for
+ * authorization requests coming from other users requesting
+ * permission add us to their contact list.
+ */
+ public void setAuthorizationHandler(AuthorizationHandler handler)
+ {
+ }
+
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/ProtocolIconSSHImpl.java b/src/net/java/sip/communicator/impl/protocol/ssh/ProtocolIconSSHImpl.java
new file mode 100644
index 000000000..0dd209b94
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/ssh/ProtocolIconSSHImpl.java
@@ -0,0 +1,102 @@
+/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ *
+ * ProtocolIconSSHImpl.java
+ *
+ * SSH Suport in SIP Communicator - GSoC' 07 Project
+ *
+ */
+
+package net.java.sip.communicator.impl.protocol.ssh;
+
+import java.io.*;
+import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.util.*;
+import java.util.*;
+
+/**
+ * Reperesents the SSH protocol icon. Implements the ProtocolIcon
+ * interface in order to provide a ssh logo image in two different sizes.
+ *
+ * @author Shobhit Jindal
+ */
+public class ProtocolIconSSHImpl
+ implements ProtocolIcon
+{
+ private static Logger logger
+ = Logger.getLogger(ProtocolIconSSHImpl.class);
+
+ /**
+ * A hash table containing the protocol icon in different sizes.
+ */
+ private static Hashtable iconsTable = new Hashtable();
+ static {
+ iconsTable.put(ProtocolIcon.ICON_SIZE_16x16,
+ loadIcon("resources/images/ssh/ssh-online.png"));
+
+ iconsTable.put(ProtocolIcon.ICON_SIZE_64x64,
+ loadIcon("resources/images/ssh/ssh64x64.png"));
+ }
+
+ /**
+ * Implements the ProtocolIcon.getSupportedSizes() method. Returns
+ * an iterator to a set containing the supported icon sizes.
+ * @return an iterator to a set containing the supported icon sizes
+ */
+ public Iterator getSupportedSizes()
+ {
+ return iconsTable.keySet().iterator();
+ }
+
+ /**
+ * Returne TRUE if a icon with the given size is supported, FALSE-otherwise.
+ *
+ * @return TRUE if a icon with the given size is supported, FALSE otherwise
+ */
+ public boolean isSizeSupported(String iconSize)
+ {
+ return iconsTable.containsKey(iconSize);
+ }
+
+ /**
+ * Returns the icon image in the given size.
+ * @param iconSize the icon size; one of ICON_SIZE_XXX constants
+ * @return the icon
+ */
+ public byte[] getIcon(String iconSize)
+ {
+ return (byte[])iconsTable.get(iconSize);
+ }
+
+ /**
+ * Returns the icon image used to represent the protocol connecting state.
+ * @return the icon image used to represent the protocol connecting state
+ */
+ public byte[] getConnectingIcon()
+ {
+ return loadIcon("resources/images/ssh/ssh-online.png");
+ }
+
+ /**
+ * Loads an image from a given image path.
+ * @param imagePath The identifier of the image.
+ * @return The image for the given identifier.
+ */
+ public static byte[] loadIcon(String imagePath)
+ {
+ InputStream is = ProtocolIconSSHImpl.class
+ .getClassLoader().getResourceAsStream(imagePath);
+
+ byte[] icon = null;
+ try {
+ icon = new byte[is.available()];
+ is.read(icon);
+ } catch (IOException e) {
+ logger.error("Failed to load icon: " + imagePath, e);
+ }
+ return icon;
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/ProtocolProviderFactorySSH.java b/src/net/java/sip/communicator/impl/protocol/ssh/ProtocolProviderFactorySSH.java
new file mode 100644
index 000000000..f59b2cfc3
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/ssh/ProtocolProviderFactorySSH.java
@@ -0,0 +1,36 @@
+/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ *
+ * ProtocolProviderFactorySSH.java
+ *
+ * SSH Suport in SIP Communicator - GSoC' 07 Project
+ *
+ */
+
+package net.java.sip.communicator.impl.protocol.ssh;
+
+import net.java.sip.communicator.service.protocol.*;
+
+/**
+ *
+ * @author Shobhit Jindal
+ */
+public abstract class ProtocolProviderFactorySSH
+ extends ProtocolProviderFactory
+{
+ /**
+ * The name of a property representing the IDENTITY_FILE of the protocol for
+ * a ProtocolProviderFactory.
+ */
+ public static final String IDENTITY_FILE = "IDENTITY_FILE";
+
+ /**
+ * The name of a property representing the KNOWN_HOSTS_FILE of the protocol
+ * for a ProtocolProviderFactory.
+ */
+ public static final String KNOWN_HOSTS_FILE = "KNOWN_HOSTS_FILE";
+
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/ProtocolProviderFactorySSHImpl.java b/src/net/java/sip/communicator/impl/protocol/ssh/ProtocolProviderFactorySSHImpl.java
new file mode 100644
index 000000000..92e92269d
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/ssh/ProtocolProviderFactorySSHImpl.java
@@ -0,0 +1,306 @@
+/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ *
+ * ProtocolProviderFactorySSHImpl.java
+ *
+ * SSH Suport in SIP Communicator - GSoC' 07 Project
+ *
+ */
+
+package net.java.sip.communicator.impl.protocol.ssh;
+
+import java.util.*;
+
+import org.osgi.framework.*;
+import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.util.*;
+
+/**
+ * The SSH protocol provider factory creates instances of the SSH
+ * protocol provider service. One Service instance corresponds to one account.
+ *
+ * @author Shobhit Jindal
+ */
+public class ProtocolProviderFactorySSHImpl
+ extends ProtocolProviderFactorySSH
+{
+ private static final Logger logger
+ = Logger.getLogger(ProtocolProviderFactorySSHImpl.class);
+
+ /**
+ * The table that we store our accounts in.
+ */
+ private Hashtable registeredAccounts = new Hashtable();
+
+
+ /**
+ * Creates an instance of the ProtocolProviderFactorySSHImpl.
+ */
+ public ProtocolProviderFactorySSHImpl()
+ {
+ super();
+ }
+
+ /**
+ * Returns the ServiceReference for the protocol provider corresponding
+ * to the specified accountID or null if the accountID is unknown.
+ *
+ * @param accountID the accountID of the protocol provider we'd like to
+ * get
+ * @return a ServiceReference object to the protocol provider with the
+ * specified account id and null if the account id is unknwon to the
+ * provider factory.
+ */
+ public ServiceReference getProviderForAccount(AccountID accountID)
+ {
+ ServiceRegistration registration
+ = (ServiceRegistration)registeredAccounts.get(accountID);
+
+ return (registration == null )
+ ? null
+ : registration.getReference();
+ }
+
+ /**
+ * Returns a copy of the list containing the AccoudIDs of all
+ * accounts currently registered in this protocol provider.
+ *
+ * @return a copy of the list containing the AccoudIDs of all
+ * accounts currently registered in this protocol provider.
+ */
+ public ArrayList getRegisteredAccounts()
+ {
+ return new ArrayList(registeredAccounts.keySet());
+ }
+
+ /**
+ * Loads (and hence installs) all accounts previously stored in the
+ * configuration service.
+ */
+ public void loadStoredAccounts()
+ {
+ super.loadStoredAccounts( SSHActivator.getBundleContext());
+ }
+
+
+ /**
+ * Initializaed and creates an account corresponding to the specified
+ * accountProperties and registers the resulting ProtocolProvider in the
+ * context BundleContext parameter.
+ *
+ * @param userIDStr tha/a user identifier uniquely representing the newly
+ * created account within the protocol namespace.
+ * @param accountProperties a set of protocol (or implementation)
+ * specific properties defining the new account.
+ * @return the AccountID of the newly created account.
+ */
+ public AccountID installAccount(
+ String userIDStr,
+ Map accountProperties)
+ {
+ BundleContext context = SSHActivator.getBundleContext();
+ if (context == null)
+ throw new NullPointerException("The specified BundleContext was " +
+ "null");
+
+ if (userIDStr == null)
+ throw new NullPointerException("The specified AccountID was null");
+
+ if (accountProperties == null)
+ throw new NullPointerException("The specified property map was" +
+ " null");
+
+ accountProperties.put(USER_ID, userIDStr);
+
+ AccountID accountID = new SSHAccountID(userIDStr, accountProperties);
+
+ //make sure we haven't seen this account id before.
+ if (registeredAccounts.containsKey(accountID))
+ throw new IllegalStateException(
+ "An account for id " + userIDStr + " was already" +
+ " installed!");
+
+ //first store the account and only then load it as the load generates
+ //an osgi event, the osgi event triggers (through the UI) a call to the
+ //ProtocolProviderService.register() method and it needs to acces
+ //the configuration service and check for a stored password.
+ this.storeAccount(
+ SSHActivator.getBundleContext()
+ , accountID);
+
+ accountID = loadAccount(accountProperties);
+
+/* ServiceReference ppServiceRef = context
+ .getServiceReference(ProtocolProviderService.class.getName());
+
+ ProtocolProviderService ppService = (ProtocolProviderService)
+ context.getService(ppServiceRef);
+
+ OperationSetPersistentPresence operationSetPersistentPresence =
+ (OperationSetPersistentPresence) ppService.getOperationSet(
+ OperationSetPersistentPresence.class);
+
+ try
+ {
+ // The below should never fail for SSH accounts
+ operationSetPersistentPresence.subscribe(userIDStr);
+
+ }
+ catch(OperationFailedException ex)
+ {
+ ex.printStackTrace();
+ }
+*/
+ return accountID;
+ }
+
+ /**
+ * Initializes and creates an account corresponding to the specified
+ * accountProperties and registers the resulting ProtocolProvider in the
+ * context BundleContext parameter.
+ *
+ * @param accountProperties a set of protocol (or implementation)
+ * specific properties defining the new account.
+ * @return the AccountID of the newly loaded account
+ */
+ public AccountID loadAccount(Map accountProperties)
+ {
+ BundleContext context = SSHActivator.getBundleContext();
+ if(context == null)
+ throw new NullPointerException("The specified BundleContext was" +
+ " null");
+
+ String userIDStr = (String) accountProperties.get(USER_ID);
+ AccountID accountID = new SSHAccountID(userIDStr, accountProperties);
+
+ //get a reference to the configuration service and register whatever
+ //properties we have in it.
+
+ Hashtable properties = new Hashtable();
+ properties.put(PROTOCOL, "SSH");
+ properties.put(USER_ID, userIDStr);
+
+ ProtocolProviderServiceSSHImpl sshProtocolProvider
+ = new ProtocolProviderServiceSSHImpl();
+
+ sshProtocolProvider.initialize(userIDStr, accountID);
+
+ ServiceRegistration registration = context.registerService(
+ ProtocolProviderService.class.getName(),
+ sshProtocolProvider,
+ properties);
+
+ registeredAccounts.put(accountID, registration);
+ return accountID;
+ }
+
+
+ /**
+ * Removes the specified account from the list of accounts that this
+ * provider factory is handling.
+ *
+ * @param accountID the ID of the account to remove.
+ * @return true if an account with the specified ID existed and was
+ * removed and false otherwise.
+ */
+ public boolean uninstallAccount(AccountID accountID)
+ {
+ //unregister the protocol provider
+ ServiceReference serRef = getProviderForAccount(accountID);
+
+ ProtocolProviderService protocolProvider
+ = (ProtocolProviderService) SSHActivator.getBundleContext()
+ .getService(serRef);
+
+ try
+ {
+ protocolProvider.unregister();
+ }
+ catch (OperationFailedException exc)
+ {
+ logger.error("Failed to unregister protocol provider for account : "
+ + accountID + " caused by : " + exc);
+ }
+
+ ServiceRegistration registration
+ = (ServiceRegistration)registeredAccounts.remove(accountID);
+
+ if(registration == null)
+ return false;
+
+ //kill the service
+ registration.unregister();
+
+ registeredAccounts.remove(accountID);
+
+ return removeStoredAccount(
+ SSHActivator.getBundleContext()
+ , accountID);
+ }
+//
+// /**
+// * Saves the password for the specified account after scrambling it a bit
+// * so that it is not visible from first sight (Method remains highly
+// * insecure).
+// *
+// * @param accountID the AccountID for the account whose password we're
+// * storing.
+// * @param passwd the password itself.
+// *
+// * @throws java.lang.IllegalArgumentException if no account corresponding
+// * to accountID has been previously stored.
+// */
+// public void storePassword(AccountID accountID, String passwd)
+// throws IllegalArgumentException
+// {
+// super.storePassword(SSHActivator.getBundleContext(),
+// accountID,
+// String.valueOf(Base64.encode(passwd.getBytes())));
+// }
+//
+// /**
+// * Returns the password last saved for the specified account.
+// *
+// * @param accountID the AccountID for the account whose password we're
+// * looking for..
+// *
+// * @return a String containing the password for the specified accountID.
+// *
+// * @throws java.lang.IllegalArgumentException if no account corresponding
+// * to accountID has been previously stored.
+// */
+// public String loadPassword(AccountID accountID)
+// throws IllegalArgumentException
+// {
+// String password = super.loadPassword(SSHActivator.getBundleContext()
+// , accountID );
+// return(String.valueOf(Base64.decode(password)));
+// }
+
+ /**
+ * Prepares the factory for bundle shutdown.
+ */
+ public void stop()
+ {
+ Enumeration registrations = this.registeredAccounts.elements();
+
+ while(registrations.hasMoreElements())
+ {
+ ServiceRegistration reg
+ = ((ServiceRegistration)registrations.nextElement());
+
+ reg.unregister();
+ }
+
+ Enumeration idEnum = registeredAccounts.keys();
+
+ while(idEnum.hasMoreElements())
+ {
+ registeredAccounts.remove(idEnum.nextElement());
+ }
+ }
+
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/ProtocolProviderServiceSSHImpl.java b/src/net/java/sip/communicator/impl/protocol/ssh/ProtocolProviderServiceSSHImpl.java
new file mode 100644
index 000000000..99055a79f
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/ssh/ProtocolProviderServiceSSHImpl.java
@@ -0,0 +1,760 @@
+/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ *
+ * ProtocolProviderServiceSSHImpl.java
+ *
+ * SSH Suport in SIP Communicator - GSoC' 07 Project
+ *
+ */
+
+package net.java.sip.communicator.impl.protocol.ssh;
+
+import java.io.*;
+import java.util.*;
+import com.jcraft.jsch.*;
+import javax.swing.*;
+
+import org.osgi.framework.*;
+import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.service.protocol.event.*;
+import net.java.sip.communicator.util.*;
+import net.java.sip.communicator.util.Logger;
+import net.java.sip.communicator.service.gui.*;
+
+/**
+ * A SSH implementation of the ProtocolProviderService.
+ *
+ * @author Shobhit Jindal
+ */
+public class ProtocolProviderServiceSSHImpl
+ implements ProtocolProviderService
+{
+ private static final Logger logger
+ = Logger.getLogger(ProtocolProviderServiceSSHImpl.class);
+
+ /**
+ * The name of this protocol.
+ */
+ public static final String SSH_PROTOCOL_NAME = "SSH";
+
+// /**
+// * The identifier for SSH Stack
+// * Java Secure Channel JSch
+// */
+// JSch jsch = new JSch();
+
+ /**
+ * The test command given after each command to determine the reply length
+ * of the command
+ */
+ private final String testCommand = Resources.getString("testCommand");
+
+ /**
+ * A reference to the protocol provider of UIService
+ */
+ private static ServiceReference ppUIServiceRef;
+
+ /**
+ * Connection timeout to a remote server in milliseconds
+ */
+ private static int connectionTimeout = 30000;
+
+ /**
+ * A reference to UI Service
+ */
+ private static UIService uiService;
+
+ /**
+ * The id of the account that this protocol provider represents.
+ */
+ private AccountID accountID = null;
+
+ /**
+ * We use this to lock access to initialization.
+ */
+ private Object initializationLock = new Object();
+
+ /**
+ * The hashtable with the operation sets that we support locally.
+ */
+ private final Hashtable supportedOperationSets = new Hashtable();
+
+ private OperationSetBasicInstantMessagingSSHImpl basicInstantMessaging;
+
+ private OperationSetFileTransferSSHImpl fileTranfer;
+
+ /**
+ * A list of listeners interested in changes in our registration state.
+ */
+ private Vector registrationStateListeners = new Vector();
+
+ /**
+ * Indicates whether or not the provider is initialized and ready for use.
+ */
+ private boolean isInitialized = false;
+
+ /**
+ * The logo corresponding to the ssh protocol.
+ */
+ private ProtocolIconSSHImpl sshIcon
+ = new ProtocolIconSSHImpl();
+
+ /**
+ * The registration state of SSH Provider is taken to be registered by
+ * default as it doesn't correspond to the state on remote server
+ */
+ private RegistrationState currentRegistrationState
+ = RegistrationState.REGISTERED;
+
+ /**
+ * The default constructor for the SSH protocol provider.
+ */
+ public ProtocolProviderServiceSSHImpl()
+ {
+ logger.trace("Creating a ssh provider.");
+
+ try
+ {
+ // converting to milliseconds
+ connectionTimeout = Integer.parseInt(Resources.getString(
+ "connectionTimeout")) * 1000;
+ }
+ catch(NumberFormatException ex)
+ {
+ logger.error("Connection Timeout set to 30 seconds");
+ }
+ }
+
+ /**
+ * Initializes the service implementation, and puts it in a sate where it
+ * could interoperate with other services. It is strongly recomended that
+ * properties in this Map be mapped to property names as specified by
+ * AccountProperties.
+ *
+ * @param userID the user id of the ssh account we're currently
+ * initializing
+ * @param accountID the identifier of the account that this protocol
+ * provider represents.
+ *
+ * @see net.java.sip.communicator.service.protocol.AccountID
+ */
+ protected void initialize(
+ String userID,
+ AccountID accountID)
+ {
+ synchronized(initializationLock)
+ {
+ this.accountID = accountID;
+
+ //initialize the presence operationset
+ OperationSetPersistentPresenceSSHImpl persistentPresence =
+ new OperationSetPersistentPresenceSSHImpl(this);
+
+ supportedOperationSets.put(
+ OperationSetPersistentPresence.class.getName(),
+ persistentPresence);
+
+ //register it once again for those that simply need presence and
+ //won't be smart enough to check for a persistent presence
+ //alternative
+ supportedOperationSets.put(
+ OperationSetPresence.class.getName(),
+ persistentPresence);
+
+ //initialize the IM operation set
+ basicInstantMessaging = new
+ OperationSetBasicInstantMessagingSSHImpl(
+ this,
+ persistentPresence);
+
+ supportedOperationSets.put(
+ OperationSetBasicInstantMessaging.class.getName(),
+ basicInstantMessaging);
+
+ //initialze the file transfer operation set
+ fileTranfer = new OperationSetFileTransferSSHImpl(
+ this,
+ persistentPresence,
+ basicInstantMessaging);
+
+ supportedOperationSets.put(
+ OperationSetFileTransfer.class.getName(),
+ fileTranfer);
+
+ isInitialized = true;
+ }
+ }
+
+ /**
+ * Determines whether a vaild session exists for the contact of remote
+ * machine.
+ *
+ * @param sshContact ID of SSH Contact
+ *
+ * @return true if the session is connected
+ * false otherwise
+ */
+ public boolean isSessionValid(ContactSSH sshContact)
+ {
+ Session sshSession = sshContact.getSSHSession();
+ if( sshSession != null)
+ if(sshSession.isConnected())
+ return true;
+
+ // remove reference to an unconnected SSH Session, if any
+ sshContact.setSSHSession(null);
+ return false;
+ }
+
+ /**
+ * Determines whether the contact is connected to shell of remote machine
+ * as a precheck for any further operation
+ *
+ * @param sshContact ID of SSH Contact
+ *
+ * @return true if the contact is connected
+ * false if the contact is not connected
+ */
+ public boolean isShellConnected(ContactSSH sshContact)
+ {
+ // a test command may also be run here
+
+ if(isSessionValid(sshContact))
+ {
+ return(sshContact.getShellChannel() != null);
+ }
+
+ /*
+ * Above should be return(sshContact.getShellChannel() != null
+ * && sshContact.getShellChannel().isConnected());
+ *
+ * but incorrect reply from stack for isConnected()
+ */
+
+ return false;
+ }
+
+ /**
+ * Creates a shell channel to the remote machine
+ * a new jsch session is also created if the current one is invalid
+ *
+ * @param sshContact the contact of the remote machine
+ * @param Message the first message
+ */
+ public void connectShell(
+ final ContactSSH sshContact,
+ final Message firstMessage)
+ {
+ sshContact.setConnectionInProgress(true);
+
+ final UIService uiService = this.uiService;
+
+ final Thread newConnection = new Thread((new Runnable()
+ {
+ public void run()
+ {
+ OperationSetPersistentPresenceSSHImpl persistentPresence
+ = (OperationSetPersistentPresenceSSHImpl)sshContact
+ .getParentPresenceOperationSet();
+
+ persistentPresence.changeContactPresenceStatus(
+ sshContact,
+ SSHStatusEnum.CONNECTING);
+
+ try
+ {
+ if(!isSessionValid(sshContact))
+ createSSHSessionAndLogin(sshContact);
+
+ createShellChannel(sshContact);
+
+ //initalizing the reader and writers of ssh contact
+
+ persistentPresence.changeContactPresenceStatus(
+ sshContact,
+ SSHStatusEnum.CONNECTED);
+
+ showWelcomeMessage(sshContact);
+
+ sshContact.setMessageType(sshContact
+ .CONVERSATION_MESSAGE_RECEIVED);
+
+ sshContact.setConnectionInProgress(false);
+
+ Thread.sleep(1500);
+
+ sshContact.setCommandSent(true);
+
+ basicInstantMessaging.sendInstantMessage(
+ sshContact,
+ firstMessage);
+ }
+ // rigoruos Exception Checking in future
+ catch (Exception ex)
+ {
+ persistentPresence.changeContactPresenceStatus(
+ sshContact,
+ SSHStatusEnum.NOT_AVAILABLE);
+
+ ex.printStackTrace();
+ }
+ finally
+ {
+ sshContact.setConnectionInProgress(false);
+ }
+ }
+ }));
+
+ newConnection.start();
+ }
+
+ /**
+ * Creates a channel for shell type in the current session
+ * channel types = shell, sftp, exec(X forwarding),
+ * direct-tcpip(stream forwarding) etc
+ *
+ * @param sshContact ID of SSH Contact
+ *
+ */
+ public void createShellChannel(ContactSSH sshContact)
+ throws IOException
+ {
+ try
+ {
+ Channel shellChannel = sshContact.getSSHSession()
+ .openChannel("shell");
+
+ //initalizing the reader and writers of ssh contact
+ sshContact.initializeShellIO(shellChannel.getInputStream(),
+ shellChannel.getOutputStream());
+
+ ((ChannelShell)shellChannel).setPtyType(
+ sshContact.getSSHConfigurationForm().getTerminalType());
+
+ //initializing the shell
+ shellChannel.connect(1000);
+
+ sshContact.setShellChannel(shellChannel);
+
+ sshContact.sendLine("export PS1=");
+ }
+ catch (JSchException ex)
+ {
+ sshContact.setSSHSession(null);
+ throw new IOException("Unable to create shell channel to remote" +
+ " server");
+ }
+ }
+
+ /**
+ * Closes the Shell channel are associated IO Streams
+ *
+ * @param sshContact ID of SSH Contact
+ */
+ public void closeShellChannel(ContactSSH sshContact) throws
+ JSchException,
+ IOException
+ {
+ sshContact.closeShellIO();
+ sshContact.getShellChannel().disconnect();
+ sshContact.setShellChannel(null);
+ }
+
+ /**
+ * Creates a SSH Session with a remote machine and tries to login
+ * according to the details specified by Contact
+ * An appropriate message is shown to the end user in case the login fails
+ *
+ * @param sshContact ID of SSH Contact
+ *
+ * @throws JSchException if a JSch is unable to create a SSH Session with the remote machine
+ * @throws InterruptedException if the thread is interrupted before session
+ * connected or is timed out
+ * @throws OperationFailedException if not of above reasons :-)
+ */
+ public void createSSHSessionAndLogin(ContactSSH sshContact) throws
+ JSchException,
+ OperationFailedException,
+ InterruptedException
+ {
+ logger.info("Creating a new SSH Session to "
+ + sshContact.getHostName());
+
+ // creating a new JSch Stack identifier for contact
+ JSch jsch = new JSch();
+
+ String knownHosts = (String)accountID
+ .getAccountProperties().get("KNOWN_HOSTS_FILE");
+
+ if(!knownHosts.equals("Optional"))
+ jsch.setKnownHosts(knownHosts);
+
+ String identitiyKey = (String)accountID
+ .getAccountProperties().get("IDENTITY_FILE");
+
+ String userName = sshContact.getUserName();
+
+ // use the name of system user if the contact has not supplied SSH
+ // details
+ if(userName.equals(""))
+ userName = System.getProperty("user.name");
+
+ if(!identitiyKey.equals("Optional"))
+ jsch.addIdentity(identitiyKey);
+
+ // creating a new session for the contact
+ Session session = jsch.getSession(
+ userName,
+ sshContact.getHostName(),
+ sshContact.getSSHConfigurationForm().getPort());
+
+ /**
+ * Creating and associating User Info with the session
+ * User Info passes authentication from sshContact to SSH Stack
+ */
+ SSHUserInfo sshUserInfo = new SSHUserInfo(sshContact);
+
+ session.setUserInfo(sshUserInfo);
+
+ /**
+ * initializing the session
+ */
+ session.connect(connectionTimeout);
+
+ int count = 0;
+
+ // wait for session to get connected
+ while(!session.isConnected() && count<=30000)
+ {
+ Thread.sleep(1000);
+ count += 1000;
+ logger.trace("SSH:" + sshContact.getHostName()
+ + ": Sleep zzz .. " );
+ }
+
+ // if timeout have exceeded
+ if(count>30000)
+ {
+ sshContact.setSSHSession(null);
+ JOptionPane.showMessageDialog(
+ null,
+ "SSH Connection attempt to "
+ + sshContact.getHostName() + " timed out");
+
+ // error codes are not defined yet
+ throw new OperationFailedException("SSH Connection attempt to " +
+ sshContact.getHostName() + " timed out", 2);
+ }
+
+ sshContact.setJSch(jsch);
+ sshContact.setSSHSession(session);
+
+ logger.info("A new SSH Session to " + sshContact.getHostName()
+ + " Created");
+ }
+
+ /**
+ * Closes the SSH Session associated with the contact
+ *
+ * @param sshContact ID of SSH Contact
+ */
+ void closeSSHSession(ContactSSH sshContact)
+ {
+ sshContact.getSSHSession().disconnect();
+ sshContact.setSSHSession(null);
+ }
+
+ /**
+ * Presents the login welcome message to user
+ *
+ * @param sshContact ID of SSH Contact
+ */
+ public void showWelcomeMessage(ContactSSH sshContact)
+ throws IOException
+ {
+/* //sending the command
+ sshContact.sendLine(testCommand);
+
+ String reply = "", line = "";
+
+ // message is extracted until the test Command ie echoed back
+ while(line.indexOf(testCommand) == -1)
+ {
+ reply += line + "\n";
+ line = sshContact.getLine();
+ }
+
+ uiService.getPopupDialog().showMessagePopupDialog
+ (reply,"Message from " + sshContact.getDisplayName(),
+ uiService.getPopupDialog().INFORMATION_MESSAGE);
+
+ if(line.startsWith(testCommand))
+ while(!sshContact.getLine().contains(testCommand));
+
+ //one line output of testCommand
+ sshContact.getLine();
+*/
+ logger.debug("SSH: Welcome message shown");
+ }
+
+ /**
+ * Returns a reference to UIServce for accessing UI related services
+ *
+ * @return uiService a reference to UIService
+ */
+ public static UIService getUIService()
+ {
+ return uiService;
+ }
+
+ /**
+ * Registers the specified listener with this provider so that it would
+ * receive notifications on changes of its state or other properties such
+ * as its local address and display name.
+ *
+ * @param listener the listener to register.
+ */
+ public void addRegistrationStateChangeListener(
+ RegistrationStateChangeListener listener)
+ {
+ synchronized(registrationStateListeners)
+ {
+ if (!registrationStateListeners.contains(listener))
+ registrationStateListeners.add(listener);
+ }
+
+ }
+
+ /**
+ * Removes the specified registration listener so that it won't receive
+ * further notifications when our registration state changes.
+ *
+ * @param listener the listener to remove.
+ */
+ public void removeRegistrationStateChangeListener(
+ RegistrationStateChangeListener listener)
+ {
+ synchronized(registrationStateListeners)
+ {
+ registrationStateListeners.remove(listener);
+ }
+ }
+
+ /**
+ * Creates a RegistrationStateChangeEvent corresponding to the
+ * specified old and new states and notifies all currently registered
+ * listeners.
+ *
+ * @param oldState the state that the provider had before the change
+ * occurred
+ * @param newState the state that the provider is currently in.
+ * @param reasonCode a value corresponding to one of the REASON_XXX fields
+ * of the RegistrationStateChangeEvent class, indicating the reason for
+ * this state transition.
+ * @param reason a String further explaining the reason code or null if
+ * no such explanation is necessary.
+ */
+ private void fireRegistrationStateChanged(
+ RegistrationState oldState,
+ RegistrationState newState,
+ int reasonCode,
+ String reason)
+ {
+ RegistrationStateChangeEvent event =
+ new RegistrationStateChangeEvent(
+ this, oldState, newState, reasonCode, reason);
+
+ logger.debug("Dispatching " + event + " to "
+ + registrationStateListeners.size()+ " listeners.");
+
+ Iterator listeners = null;
+ synchronized (registrationStateListeners)
+ {
+ listeners = new ArrayList(registrationStateListeners).iterator();
+ }
+
+ while (listeners.hasNext())
+ {
+ RegistrationStateChangeListener listener
+ = (RegistrationStateChangeListener) listeners.next();
+
+ listener.registrationStateChanged(event);
+ }
+
+ logger.trace("Done.");
+ }
+
+
+ /**
+ * Returns the AccountID that uniquely identifies the account represented
+ * by this instance of the ProtocolProviderService.
+ *
+ * @return the id of the account represented by this provider.
+ */
+ public AccountID getAccountID()
+ {
+ return accountID;
+ }
+
+ /**
+ * Returns the operation set corresponding to the specified class or null
+ * if this operation set is not supported by the provider implementation.
+ *
+ * @param opsetClass the Class of the operation set that we're
+ * looking for.
+ * @return returns an OperationSet of the specified Class if
+ * the undelying implementation supports it or null otherwise.
+ */
+ public OperationSet getOperationSet(Class opsetClass)
+ {
+ return (OperationSet) getSupportedOperationSets()
+ .get(opsetClass.getName());
+ }
+
+ /**
+ * Returns the short name of the protocol that the implementation of this
+ * provider is based upon (like SIP, Jabber, ICQ/AIM, or others for
+ * example).
+ *
+ * @return a String containing the short name of the protocol this
+ * service is implementing (most often that would be a name in
+ * ProtocolNames).
+ */
+ public String getProtocolName()
+ {
+ return SSH_PROTOCOL_NAME;
+ }
+
+ /**
+ * Returns the state of the registration of this protocol provider with
+ * the corresponding registration service.
+ *
+ * @return ProviderRegistrationState
+ */
+ public RegistrationState getRegistrationState()
+ {
+ return currentRegistrationState;
+ }
+
+ /**
+ * Returns an array containing all operation sets supported by the
+ * current implementation.
+ *
+ * @return a java.util.Map containing instance of all supported
+ * operation sets mapped against their class names (e.g.
+ * OperationSetPresence.class.getName()) .
+ */
+ public Map getSupportedOperationSets()
+ {
+ //Copy the map so that the caller is not able to modify it.
+ return (Map)supportedOperationSets.clone();
+ }
+
+ /**
+ * Indicates whether or not this provider is registered
+ *
+ * @return true if the provider is currently registered and false
+ * otherwise.
+ */
+ public boolean isRegistered()
+ {
+ return currentRegistrationState.equals(RegistrationState.REGISTERED);
+ }
+
+ /**
+ * Starts the registration process.
+ *
+ * @param authority the security authority that will be used for
+ * resolving any security challenges that may be returned during the
+ * registration or at any moment while wer're registered.
+ * @throws OperationFailedException with the corresponding code it the
+ * registration fails for some reason (e.g. a networking error or an
+ * implementation problem).
+ */
+ public void register(SecurityAuthority authority)
+ throws OperationFailedException
+ {
+ RegistrationState oldState = currentRegistrationState;
+ currentRegistrationState = RegistrationState.REGISTERED;
+
+ //get a reference to UI Service via its Service Reference
+ ppUIServiceRef = SSHActivator.getBundleContext()
+ .getServiceReference(UIService.class.getName());
+
+ uiService = (UIService)SSHActivator.getBundleContext()
+ .getService(ppUIServiceRef);
+
+ fireRegistrationStateChanged(
+ oldState
+ , currentRegistrationState
+ , RegistrationStateChangeEvent.REASON_USER_REQUEST
+ , null);
+
+ }
+
+ /**
+ * Makes the service implementation close all open sockets and release
+ * any resources that it might have taken and prepare for
+ * shutdown/garbage collection.
+ */
+ public void shutdown()
+ {
+ if(!isInitialized)
+ {
+ return;
+ }
+ logger.trace("Killing the SSH Protocol Provider.");
+
+ if(isRegistered())
+ {
+ try
+ {
+ //do the unregistration
+ unregister();
+ }
+ catch (OperationFailedException ex)
+ {
+ //we're shutting down so we need to silence the exception here
+ logger.error(
+ "Failed to properly unregister before shutting down. "
+ + getAccountID()
+ , ex);
+ }
+ }
+
+ isInitialized = false;
+ }
+
+ /**
+ * Ends the registration of this protocol provider with the current
+ * registration service.
+ *
+ * @throws OperationFailedException with the corresponding code it the
+ * registration fails for some reason (e.g. a networking error or an
+ * implementation problem).
+ */
+ public void unregister()
+ throws OperationFailedException
+ {
+ RegistrationState oldState = currentRegistrationState;
+ currentRegistrationState = RegistrationState.UNREGISTERED;
+
+ fireRegistrationStateChanged(
+ oldState
+ , currentRegistrationState
+ , RegistrationStateChangeEvent.REASON_USER_REQUEST
+ , null);
+ }
+
+ /**
+ * Returns the ssh protocol icon.
+ * @return the ssh protocol icon
+ */
+ public ProtocolIcon getProtocolIcon()
+ {
+ return sshIcon;
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/Resources.java b/src/net/java/sip/communicator/impl/protocol/ssh/Resources.java
new file mode 100644
index 000000000..cc8649ed7
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/ssh/Resources.java
@@ -0,0 +1,98 @@
+/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ *
+ * Resources.java
+ *
+ * SSH Suport in SIP Communicator - GSoC' 07 Project
+ *
+ */
+
+package net.java.sip.communicator.impl.protocol.ssh;
+
+import java.io.*;
+import java.util.*;
+
+import net.java.sip.communicator.util.*;
+
+/**
+ *
+ * @author Shobhit Jindal
+ */
+public class Resources
+{
+ private static Logger log = Logger.getLogger(Resources.class);
+
+ private static final String BUNDLE_NAME
+ = "net.java.sip.communicator.impl.protocol.ssh.resources";
+
+ private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle
+ .getBundle(BUNDLE_NAME);
+
+ public static ImageID SSH_LOGO = new ImageID("protocolIcon");
+
+ /**
+ * Returns an string corresponding to the given key.
+ *
+ * @param key The key of the string.
+ *
+ * @return a string corresponding to the given key.
+ */
+ public static String getString(String key)
+ {
+ try
+ {
+ return RESOURCE_BUNDLE.getString(key);
+
+ }
+ catch (MissingResourceException exc)
+ {
+ return '!' + key + '!';
+ }
+ }
+
+ /**
+ * Loads an image from a given image identifier.
+ * @param imageID The identifier of the image.
+ * @return The image for the given identifier.
+ */
+ public static byte[] getImage(ImageID imageID)
+ {
+ byte[] image = new byte[100000];
+
+ String path = Resources.getString(imageID.getId());
+ try
+ {
+ Resources.class.getClassLoader()
+ .getResourceAsStream(path).read(image);
+
+ }
+ catch (IOException exc)
+ {
+ log.error("Failed to load image:" + path, exc);
+ }
+
+ return image;
+ }
+
+ /**
+ * Represents the Image Identifier.
+ */
+ public static class ImageID
+ {
+ private String id;
+
+ private ImageID(String id)
+ {
+ this.id = id;
+ }
+
+ public String getId()
+ {
+ return id;
+ }
+ }
+
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/SSHAccountID.java b/src/net/java/sip/communicator/impl/protocol/ssh/SSHAccountID.java
new file mode 100644
index 000000000..982fafc75
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/ssh/SSHAccountID.java
@@ -0,0 +1,38 @@
+/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ *
+ * SSHAccountID.java
+ *
+ * SSH Suport in SIP Communicator - GSoC' 07 Project
+ *
+ */
+
+package net.java.sip.communicator.impl.protocol.ssh;
+
+import net.java.sip.communicator.service.protocol.*;
+import java.util.Map;
+
+/**
+ * The SSH implementation of a sip-communicator account id.
+ * @author Shobhit Jindal
+ */
+public class SSHAccountID
+ extends AccountID
+{
+ /**
+ * Creates an account id from the specified id and account properties.
+ *
+ * @param userID the user identifier correspnding to thi account
+ * @param accountProperties any other properties necessary for the account.
+ */
+ SSHAccountID(String userID, Map accountProperties)
+ {
+ super(userID
+ , accountProperties
+ , "SSH"
+ , "sip-communicator.org");
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/SSHActivator.java b/src/net/java/sip/communicator/impl/protocol/ssh/SSHActivator.java
new file mode 100644
index 000000000..31c89f020
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/ssh/SSHActivator.java
@@ -0,0 +1,124 @@
+/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ *
+ * SSHActivator.java
+ *
+ * SSH Suport in SIP Communicator - GSoC' 07 Project
+ *
+ */
+
+package net.java.sip.communicator.impl.protocol.ssh;
+
+import java.util.*;
+
+import org.osgi.framework.*;
+import net.java.sip.communicator.util.*;
+import net.java.sip.communicator.service.protocol.*;
+
+/**
+ * Loads the SSH provider factory and registers its services in the OSGI
+ * bundle context.
+ *
+ * @author Shobhit Jindal
+ */
+public class SSHActivator
+ implements BundleActivator
+{
+ private static final Logger logger
+ = Logger.getLogger(SSHActivator.class);
+
+ /**
+ * A reference to the registration of our SSH protocol provider
+ * factory.
+ */
+ private ServiceRegistration sshPpFactoryServReg = null;
+
+ /**
+ * A reference to the SSH protocol provider factory.
+ */
+ private static ProtocolProviderFactorySSHImpl
+ sshProviderFactory = null;
+
+ /**
+ * The currently valid bundle context.
+ */
+ private static BundleContext bundleContext = null;
+
+
+ /**
+ * Called when this bundle is started. In here we'll export the
+ * ssh ProtocolProviderFactory implementation so that it could be
+ * possible to register accounts with it in SIP Communicator.
+ *
+ * @param context The execution context of the bundle being started.
+ * @throws Exception If this method throws an exception, this bundle is
+ * marked as stopped and the Framework will remove this bundle's
+ * listeners, unregister all services registered by this bundle, and
+ * release all services used by this bundle.
+ */
+ public void start(BundleContext context)
+ throws Exception
+ {
+ this.bundleContext = context;
+
+ Hashtable hashtable = new Hashtable();
+ hashtable.put(ProtocolProviderFactory.PROTOCOL, "SSH");
+
+ sshProviderFactory = new ProtocolProviderFactorySSHImpl();
+
+ //load all stored SSH accounts.
+ sshProviderFactory.loadStoredAccounts();
+
+ //reg the ssh provider factory.
+ sshPpFactoryServReg = context.registerService(
+ ProtocolProviderFactory.class.getName(),
+ sshProviderFactory,
+ hashtable);
+
+ logger.info("SSH protocol implementation [STARTED].");
+ }
+
+ /**
+ * Returns a reference to the bundle context that we were started with.
+ * @return bundleContext a reference to the BundleContext instance
+ * that we were started with.
+ */
+ public static BundleContext getBundleContext()
+ {
+ return bundleContext;
+ }
+
+ /**
+ * Retrurns a reference to the protocol provider factory that we have
+ * registered.
+ * @return a reference to the ProtocolProviderFactoryJabberImpl
+ * instance that we have registered from this package.
+ */
+ public static ProtocolProviderFactorySSHImpl
+ getProtocolProviderFactory()
+ {
+ return sshProviderFactory;
+ }
+
+
+ /**
+ * Called when this bundle is stopped so the Framework can perform the
+ * bundle-specific activities necessary to stop the bundle.
+ *
+ * @param context The execution context of the bundle being stopped.
+ * @throws Exception If this method throws an exception, the bundle is
+ * still marked as stopped, and the Framework will remove the bundle's
+ * listeners, unregister all services registered by the bundle, and
+ * release all services used by the bundle.
+ */
+ public void stop(BundleContext context)
+ throws Exception
+ {
+ this.sshProviderFactory.stop();
+ sshPpFactoryServReg.unregister();
+ logger.info("SSH protocol implementation [STOPPED].");
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/SSHStatusEnum.java b/src/net/java/sip/communicator/impl/protocol/ssh/SSHStatusEnum.java
new file mode 100644
index 000000000..e7689f1bb
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/ssh/SSHStatusEnum.java
@@ -0,0 +1,154 @@
+/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ *
+ * SSHStatusEnum.java
+ *
+ * SSH Suport in SIP Communicator - GSoC' 07 Project
+ */
+
+package net.java.sip.communicator.impl.protocol.ssh;
+
+import java.util.*;
+
+import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.util.*;
+import java.io.*;
+
+/**
+ * An implementation of PresenceStatus that enumerates all states that
+ * a SSH contact can fall into.
+ *
+ * @author Shobhit Jindal
+ */
+public class SSHStatusEnum
+ extends PresenceStatus
+{
+ private static final Logger logger
+ = Logger.getLogger(SSHStatusEnum.class);
+
+ /**
+ * Indicates an Offline status or status with 0 connectivity.
+ */
+ public static final SSHStatusEnum OFFLINE
+ = new SSHStatusEnum(
+ 0
+ , "Offline"
+ , loadIcon("resources/images/ssh/ssh-offline.png"));
+
+ /**
+ * The Not Available status. Indicates that the user has connectivity
+ * but might not be able to immediately act (i.e. even less immediately than
+ * when in an Away status ;-P ) upon initiation of communication.
+ *
+ */
+ public static final SSHStatusEnum NOT_AVAILABLE
+ = new SSHStatusEnum(
+ 35
+ , "Not Available"
+ , loadIcon("resources/images/ssh/ssh-na.png"));
+
+ /**
+ * The Connecting status. Indicate that the user is connecting to remote
+ * server
+ */
+ public static final SSHStatusEnum CONNECTING
+ = new SSHStatusEnum(
+ 55
+ , "Connecting"
+ , loadIcon("resources/images/ssh/ssh-connecting.png"));
+
+ /**
+ * The Online status. Indicate that the user is able and willing to
+ * communicate.
+ */
+ public static final SSHStatusEnum ONLINE
+ = new SSHStatusEnum(
+ 65
+ , "Online"
+ , loadIcon("resources/images/ssh/ssh-online.png"));
+
+
+ /**
+ * The Connecting status. Indicate that the user is connecting to remote
+ * server
+ */
+ public static final SSHStatusEnum CONNECTED
+ = new SSHStatusEnum(
+ 70
+ , "Connecting"
+ , loadIcon("resources/images/ssh/ssh-connected.png"));
+
+ /**
+ * The File Transfer status. Indicate that the user is transfering a file
+ * to/from a remote server
+ */
+ public static final SSHStatusEnum FILE_TRANSFER
+ = new SSHStatusEnum(
+ 75
+ , "Transfering File"
+ , loadIcon("resources/images/ssh/ssh-filetransfer.png"));
+
+ /**
+ * Initialize the list of supported status states.
+ */
+ private static List supportedStatusSet = new LinkedList();
+ static
+ {
+ supportedStatusSet.add(OFFLINE);
+// supportedStatusSet.add(NOT_AVAILABLE);
+ supportedStatusSet.add(ONLINE);
+// supportedStatusSet.add(CONNECTING);
+ }
+
+ /**
+ * Creates an instance of SSHPresneceStatus with the
+ * specified parameters.
+ * @param status the connectivity level of the new presence status instance
+ * @param statusName the name of the presence status.
+ * @param statusIcon the icon associated with this status
+ */
+ private SSHStatusEnum(int status,
+ String statusName,
+ byte[] statusIcon)
+ {
+ super(status, statusName, statusIcon);
+ }
+
+ /**
+ * Returns an iterator over all status instances supproted by the ssh
+ * provider.
+ * @return an Iterator over all status instances supported by the
+ * ssh provider.
+ */
+ static Iterator supportedStatusSet()
+ {
+ return supportedStatusSet.iterator();
+ }
+
+ /**
+ * Loads an image from a given image path.
+ * @param imagePath The path to the image resource.
+ * @return The image extracted from the resource at the specified path.
+ */
+ public static byte[] loadIcon(String imagePath)
+ {
+ InputStream is = SSHStatusEnum.class.getClassLoader()
+ .getResourceAsStream(imagePath);
+
+ byte[] icon = null;
+ try
+ {
+ icon = new byte[is.available()];
+ is.read(icon);
+ }
+ catch (IOException exc)
+ {
+ logger.error("Failed to load icon: " + imagePath, exc);
+ }
+ return icon;
+ }
+
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/SSHUserInfo.java b/src/net/java/sip/communicator/impl/protocol/ssh/SSHUserInfo.java
new file mode 100644
index 000000000..79999b549
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/ssh/SSHUserInfo.java
@@ -0,0 +1,157 @@
+/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ *
+ * SSHUserInfo.java
+ *
+ * SSH Suport in SIP Communicator - GSoC' 07 Project
+ *
+ */
+
+package net.java.sip.communicator.impl.protocol.ssh;
+
+import com.jcraft.jsch.*;
+import javax.swing.*;
+
+/**
+ * SSHUserInfo passes authentication details to JSch SSH Stack
+ *
+ * @author Shobhit Jindal
+ */
+class SSHUserInfo
+ implements UserInfo,
+ UIKeyboardInteractive
+{
+ /**
+ * The Contact of the remote machine
+ */
+ private ContactSSH sshContact;
+
+ /**
+ * Identifier for failure of authentication
+ * more explanation below in promptPassword function
+ */
+ private boolean failedOnce = false;
+
+ /**
+ * Password field for requesting auth details from user
+ */
+ JTextField passwordField=(JTextField)new JPasswordField(20);
+
+ /**
+ * Creates a UserInfo instance
+ *
+ * @param sshContact the contact concerned
+ */
+ SSHUserInfo(ContactSSH sshContact)
+ {
+ this.sshContact = sshContact;
+ }
+
+ /**
+ * Returns the password of account associated with this contact
+ *
+ * @return the password of account associated with this contact
+ */
+ public String getPassword()
+ {
+ return sshContact.getPassword();
+ }
+
+ /**
+ * Prompt for accepting the cipher information of the remote server
+ *
+ * @param str the string to display
+ *
+ * @return the user's answer
+ */
+ public boolean promptYesNo(String str)
+ {
+ Object[] options={ "yes", "no" };
+ int foo=JOptionPane.showOptionDialog(null,
+ str,
+ "Warning",
+ JOptionPane.DEFAULT_OPTION,
+ JOptionPane.WARNING_MESSAGE,
+ null, options, options[0]);
+ return foo==0;
+ }
+
+ /**
+ * Passphrase authentication presently not implemented
+ *
+ * @return null
+ */
+ public String getPassphrase()
+ { return null; }
+
+ /**
+ * Passphrase authentication presently not implemented
+ *
+ * @return true
+ */
+ public boolean promptPassphrase(String message)
+ { return true; }
+
+ /**
+ * Asks user to re-enter password information in case of an auth failure
+ *
+ * @param message the message to display
+ *
+ * @return the user's answer
+ */
+ public boolean promptPassword(String message)
+ {
+ /**
+ * Auth always fails for the first time for Redhat based machines.
+ * Trying again with the same password
+ */
+ if(!failedOnce)
+ {
+ failedOnce = true;
+ return true;
+ }
+
+ Object[] ob={passwordField};
+ int result=JOptionPane.showConfirmDialog(null, ob, "Auth Failed: "
+ + message,
+ JOptionPane.OK_CANCEL_OPTION);
+
+ if(result==JOptionPane.OK_OPTION)
+ {
+ sshContact.setPassword(passwordField.getText());
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Shows a message from server
+ *
+ * @param message The message to display
+ */
+ public void showMessage(String message)
+ {
+ JOptionPane.showMessageDialog(null, message);
+ }
+
+ /**
+ * Keyboard Interactive Auth - not implemented
+ */
+ public String[] promptKeyboardInteractive(
+ String destination,
+ String name,
+ String instruction,
+ String[] prompt,
+ boolean[] echo)
+ {
+ String response[] = new String[prompt.length];
+ response[0] = sshContact.getPassword();
+ return response;
+ }
+
+
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/resources.properties b/src/net/java/sip/communicator/impl/protocol/ssh/resources.properties
new file mode 100644
index 000000000..c066c884b
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/ssh/resources.properties
@@ -0,0 +1,13 @@
+#The test command should be choosen uniquely i.e. it should not appear in output
+# of any other command(including its own), else the reply of the offending
+# command will be cut short
+testCommand=uname
+#First line of reply of test command (must be independent of state of remote
+# machine)
+testCommandResponse=Linux
+#Contact Details Seperator(must not be part of contact data stored as persistent
+# data)
+detailsSeperator=~
+#Connection timeout to a remote server in seconds
+connectionTimeout=30
+protocolIcon=resources/images/ssh/ssh-online.png
diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/ssh.provider.manifest.mf b/src/net/java/sip/communicator/impl/protocol/ssh/ssh.provider.manifest.mf
new file mode 100644
index 000000000..851c08907
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/ssh/ssh.provider.manifest.mf
@@ -0,0 +1,17 @@
+Bundle-Activator: net.java.sip.communicator.impl.protocol.ssh.SSHActivator
+Bundle-Name: SSH Protocol Provider
+Bundle-Description: A bundle providing support for the SSH protocol.
+Bundle-Vendor: sip-communicator.org
+Bundle-Version: 0.0.1
+Import-Package: org.osgi.framework,
+ javax.swing,
+ javax.swing.border,
+ javax.crypto,
+ javax.crypto.spec,
+ javax.crypto.interfaces,
+ net.java.sip.communicator.service.configuration,
+ net.java.sip.communicator.service.configuration.event,
+ net.java.sip.communicator.util,
+ net.java.sip.communicator.service.protocol,
+ net.java.sip.communicator.service.protocol.event,
+ net.java.sip.communicator.service.gui
diff --git a/src/net/java/sip/communicator/plugin/sshaccregwizz/FirstWizardPage.java b/src/net/java/sip/communicator/plugin/sshaccregwizz/FirstWizardPage.java
new file mode 100644
index 000000000..ff1044dfc
--- /dev/null
+++ b/src/net/java/sip/communicator/plugin/sshaccregwizz/FirstWizardPage.java
@@ -0,0 +1,419 @@
+/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ *
+ * FirstWizardPage.java
+ *
+ * Created on 22 May, 2007, 8:44 AM
+ *
+ * SSH Suport in SIP Communicator - GSoC' 07 Project
+ */
+
+package net.java.sip.communicator.plugin.sshaccregwizz;
+
+import java.util.*;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.event.*;
+
+import net.java.sip.communicator.service.gui.*;
+import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.impl.protocol.ssh.*;
+
+/**
+ * The FirstWizardPage is the page, where user could enter the user ID
+ * and the password of the account.
+ *
+ * @author Shobhit Jindal
+ */
+public class FirstWizardPage
+ extends JPanel implements WizardPage, DocumentListener
+{
+
+ public static final String FIRST_PAGE_IDENTIFIER = "FirstPageIdentifier";
+
+ private JPanel accountPanel = new JPanel(new BorderLayout(10, 10));
+
+ private JPanel labelsPanel = new JPanel();
+
+ private JPanel valuesPanel = new JPanel();
+
+ private JLabel accountID = new JLabel(Resources.getString("accountID"));
+
+ private JLabel identityFile = new JLabel(Resources.getString(
+ "identityFile"));
+
+ private JLabel knownHostsFile = new JLabel(Resources.getString(
+ "knownHosts"));
+
+ private JLabel existingAccountLabel
+ = new JLabel(Resources.getString("existingAccount"));
+
+ private JPanel emptyPanel1 = new JPanel();
+
+ private JPanel emptyPanel2 = new JPanel();
+
+ private JPanel emptyPanel3 = new JPanel();
+
+ private JPanel emptyPanel4 = new JPanel();
+
+ private JPanel emptyPanel5 = new JPanel();
+
+ private JPanel emptyPanel6 = new JPanel();
+
+ private JPanel emptyPanel7 = new JPanel();
+
+ private JPanel emptyPanel8 = new JPanel();
+
+ private JPanel emptyPanel9 = new JPanel();
+
+ private JPanel emptyPanel10 = new JPanel();
+
+ private JPanel emptyPanel11 = new JPanel();
+
+ private JTextField accountIDField = new JTextField();
+
+ private JTextField identityFileField = new JTextField("Optional");
+
+ private JButton identityFileButton = new JButton("Browse");
+
+ private JFileChooser identityFileChooser = new JFileChooser();
+
+ private JPanel identityFilePanel = new JPanel();
+
+ private JTextField knownHostsFileField = new JTextField("Optional");
+
+ private JButton knownHostsFileButton = new JButton("Browse");
+
+ private JFileChooser knownHostsFileChooser = new JFileChooser();
+
+ private JPanel knownHostsFilePanel = new JPanel();
+
+ private JPanel mainPanel = new JPanel();
+
+ private Object nextPageIdentifier = WizardPage.SUMMARY_PAGE_IDENTIFIER;
+
+ private SSHAccountRegistration registration = null;
+
+ private WizardContainer wizardContainer;
+
+ /**
+ * Creates an instance of FirstWizardPage.
+ * @param registration the SSHAccountRegistration, where
+ * all data through the wizard are stored
+ * @param wizardContainer the wizardContainer, where this page will
+ * be added
+ */
+ public FirstWizardPage(SSHAccountRegistration registration,
+ WizardContainer wizardContainer)
+ {
+
+ super(new BorderLayout());
+
+ this.wizardContainer = wizardContainer;
+
+ this.registration = registration;
+
+ this.setPreferredSize(new Dimension(300, 150));
+
+ mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
+
+ identityFileField.setEditable(false);
+
+ knownHostsFileField.setEditable(false);
+
+ identityFileChooser.setFileHidingEnabled(false);
+
+ knownHostsFileChooser.setFileHidingEnabled(false);
+
+ this.init();
+
+ this.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
+
+ this.labelsPanel.setLayout(new BoxLayout(labelsPanel,
+ BoxLayout.Y_AXIS));
+
+ this.valuesPanel.setLayout(new BoxLayout(valuesPanel,
+ BoxLayout.Y_AXIS));
+
+ this.identityFilePanel.setLayout(new BoxLayout(identityFilePanel,
+ BoxLayout.X_AXIS));
+
+ this.knownHostsFilePanel.setLayout(new BoxLayout(knownHostsFilePanel,
+ BoxLayout.X_AXIS));
+ }
+
+ /**
+ * Initializes all panels, buttons, etc.
+ */
+ private void init()
+ {
+
+ this.accountIDField.getDocument().addDocumentListener(this);
+ this.existingAccountLabel.setForeground(Color.RED);
+
+ /*
+ * Following empty panels cover the space needed between key labels
+ * WRT Height 2 key lables = 1 text field
+ */
+ this.emptyPanel1.setMaximumSize(new Dimension(40, 35));
+ this.emptyPanel2.setMaximumSize(new Dimension(40, 35));
+ this.emptyPanel3.setMaximumSize(new Dimension(40, 35));
+ this.emptyPanel4.setMaximumSize(new Dimension(40, 35));
+ this.emptyPanel5.setMaximumSize(new Dimension(40, 35));
+ this.emptyPanel6.setMaximumSize(new Dimension(40, 35));
+ this.emptyPanel7.setMaximumSize(new Dimension(40, 35));
+
+ identityFilePanel.add(identityFileField);
+ identityFilePanel.add(identityFileButton);
+
+ knownHostsFilePanel.add(knownHostsFileField);
+ knownHostsFilePanel.add(knownHostsFileButton);
+
+ labelsPanel.add(emptyPanel1);
+ labelsPanel.add(accountID);
+ labelsPanel.add(emptyPanel2);
+ labelsPanel.add(emptyPanel3);
+ labelsPanel.add(identityFile);
+ labelsPanel.add(emptyPanel4);
+ labelsPanel.add(emptyPanel5);
+ labelsPanel.add(knownHostsFile);
+ labelsPanel.add(emptyPanel6);
+
+ valuesPanel.add(accountIDField);
+ valuesPanel.add(emptyPanel7);
+ valuesPanel.add(identityFilePanel);
+ valuesPanel.add(emptyPanel8);
+ valuesPanel.add(knownHostsFilePanel);
+ labelsPanel.add(emptyPanel9);
+
+ accountPanel.add(labelsPanel, BorderLayout.WEST);
+ accountPanel.add(valuesPanel, BorderLayout.CENTER);
+
+ identityFileButton.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ int returnVal = identityFileChooser.showDialog
+ (FirstWizardPage.this, "Select Identify File");
+
+ if(returnVal == JFileChooser.APPROVE_OPTION)
+ identityFileField.setText(identityFileChooser
+ .getSelectedFile().getAbsolutePath());
+ }
+ }
+ );
+
+ knownHostsFileButton.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ int returnVal = knownHostsFileChooser.showDialog
+ (FirstWizardPage.this, "Select Identify File");
+
+ if(returnVal == JFileChooser.APPROVE_OPTION)
+ knownHostsFileField.setText(knownHostsFileChooser
+ .getSelectedFile().getAbsolutePath());
+ }
+ }
+ );
+
+ accountPanel.setBorder(BorderFactory
+ .createTitledBorder(Resources.getString(
+ "accountDetails")));
+
+ this.add(accountPanel, BorderLayout.NORTH);
+ }
+
+ /**
+ * Fills the Account ID, Identity File and Known Hosts File fields in this
+ * panel with the data comming from the given protocolProvider.
+ *
+ * @param protocolProvider The ProtocolProviderService to load the
+ * data from.
+ */
+ public void loadAccount(ProtocolProviderService protocolProvider)
+ {
+ ProtocolProviderFactorySSH protocolProviderSSH =
+ (ProtocolProviderFactorySSH)protocolProvider;
+
+ AccountID accountID = protocolProvider.getAccountID();
+
+ String identityFile = (String) accountID.getAccountProperties()
+ .get(ProtocolProviderFactorySSH.IDENTITY_FILE);
+
+ String knownHostsFile = (String) accountID.getAccountProperties()
+ .get(ProtocolProviderFactorySSH.KNOWN_HOSTS_FILE);
+
+ this.accountIDField.setText(accountID.getUserID());
+
+ this.identityFileField.setText(identityFile);
+
+ this.knownHostsFileField.setText(knownHostsFile);
+ }
+
+ /**
+ * Implements the true if this contact group has the equal child
+ * contacts and subgroups to those of the obj argument.
+ */
+ public boolean equals(Object obj)
+ {
+ if(obj == null
+ || !(obj instanceof ContactGroupSSHImpl))
+ return false;
+
+ ContactGroupSSHImpl sshGroup
+ = (ContactGroupSSHImpl)obj;
+
+ if( ! sshGroup.getGroupName().equals(getGroupName())
+ || ! sshGroup.getUID().equals(getUID())
+ || sshGroup.countContacts() != countContacts()
+ || sshGroup.countSubgroups() != countSubgroups())
+ return false;
+
+ //traverse child contacts
+ Iterator theirContacts = sshGroup.contacts();
+
+ while(theirContacts.hasNext())
+ {
+ ContactSSHImpl theirContact
+ = (ContactSSHImpl)theirContacts.next();
+
+ ContactSSHImpl ourContact
+ = (ContactSSHImpl)getContact(theirContact.getAddress());
+
+ if(ourContact == null
+ || !ourContact.equals(theirContact))
+ return false;
+ }
+
+ //traverse subgroups
+ Iterator theirSubgroups = sshGroup.subgroups();
+
+ while(theirSubgroups.hasNext())
+ {
+ ContactGroupSSHImpl theirSubgroup
+ = (ContactGroupSSHImpl)theirSubgroups.next();
+
+ ContactGroupSSHImpl ourSubgroup
+ = (ContactGroupSSHImpl)getGroup(
+ theirSubgroup.getGroupName());
+
+ if(ourSubgroup == null
+ || !ourSubgroup.equals(theirSubgroup))
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/ContactSSH.java b/src/net/java/sip/communicator/impl/protocol/ssh/ContactSSH.java
new file mode 100644
index 000000000..432ac1194
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/ssh/ContactSSH.java
@@ -0,0 +1,364 @@
+/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ *
+ * ContactSSH.java
+ *
+ * SSH Suport in SIP Communicator - GSoC' 07 Project
+ *
+ */
+
+package net.java.sip.communicator.impl.protocol.ssh;
+
+import java.io.*;
+import com.jcraft.jsch.*;
+
+import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.util.*;
+
+/**
+ * This interface represents a Contact of SSH Type
+ * As a SSH Session is specific to a contact, additional information needed
+ * to maintain its state with the remote server is present here
+ *
+ * @author Shobhit Jindal
+ */
+interface ContactSSH
+ extends Contact
+{
+ /**
+ * An event type indicating that the message being received is a standard
+ * conversation message sent by another contact.
+ */
+ public static final int CONVERSATION_MESSAGE_RECEIVED = 1;
+
+ /**
+ * An event type indicting that the message being received is a system
+ * message being sent by the server or a system administrator.
+ */
+ public static final int SYSTEM_MESSAGE_RECEIVED = 2;
+
+ //Following eight function declations to be moved to Contact
+
+ /**
+ * This method is only called when the contact is added to a new
+ * ContactGroupSSHImpl by the
+ * ContactGroupSSHImpl itself.
+ *
+ * @param newParentGroup the ContactGroupSSHImpl that is now
+ * parent of this ContactSSHImpl
+ */
+ void setParentGroup (ContactGroupSSHImpl newParentGroup);
+
+ /**
+ * Sets sshPresenceStatus as the PresenceStatus that this
+ * contact is currently in.
+ * @param sshPresenceStatus the SSHPresenceStatus
+ * currently valid for this contact.
+ */
+ public void setPresenceStatus (PresenceStatus sshPresenceStatus);
+
+ /**
+ * Returns the persistent presence operation set that this contact belongs
+ * to.
+ *
+ * @return the OperationSetPersistentPresenceSSHImpl that
+ * this contact belongs to.
+ */
+ public OperationSetPersistentPresence
+ getParentPresenceOperationSet ();
+
+ /**
+ * Returns the BasicInstant Messaging operation set that this contact
+ * belongs to.
+ *
+ * @return the OperationSetBasicInstantMessagingSSHImpl that
+ * this contact belongs to.
+ */
+ public OperationSetBasicInstantMessaging
+ getParentBasicInstantMessagingOperationSet ();
+
+ /**
+ * Returns the File Transfer operation set that this contact belongs
+ * to.
+ *
+ * @return the OperationSetFileTransferSSHImpl that
+ * this contact belongs to.
+ */
+ public OperationSetFileTransfer
+ getFileTransferOperationSet ();
+
+ /**
+ * Return the type of message received from remote server
+ *
+ * @return messageType
+ */
+ public int getMessageType ();
+
+ /**
+ * Sets the type of message received from remote server
+ *
+ * @param messageType
+ */
+ public void setMessageType (int messageType);
+
+ /**
+ * Stores persistent data of the contact.
+ *
+ * @param persistentData of the contact
+ */
+ public void setPersistentData (String persistentData);
+
+ /**
+ * Makes the contact resolved or unresolved.
+ *
+ * @param resolved true to make the contact resolved; false to
+ * make it unresolved
+ */
+ public void setResolved (boolean resolved);
+
+ /**
+ * Specifies whether or not this contact is being stored by the server.
+ * Non persistent contacts are common in the case of simple, non-persistent
+ * presence operation sets. They could however also be seen in persistent
+ * presence operation sets when for example we have received an event
+ * from someone not on our contact list. Non persistent contacts are
+ * volatile even when coming from a persistent presence op. set. They would
+ * only exist until the application is closed and will not be there next
+ * time it is loaded.
+ *
+ * @param isPersistent true if the contact is persistent and false
+ * otherwise.
+ */
+ public void setPersistent (boolean isPersistent);
+
+ /**
+ * Returns true if a command has been sent whos reply was not received yet
+ * false otherwise
+ *
+ * @return commandSent
+ */
+ public boolean isCommandSent ();
+
+ /**
+ * Set the state of commandSent variable which determines whether a reply
+ * to a command sent is awaited
+ *
+ * @param commandSent
+ */
+ public void setCommandSent (boolean commandSent);
+
+ /**
+ * Initializes the reader and writers associated with shell of this contact
+ *
+ * @param shellInputStream The InputStream of stack
+ * @param shellOutputStream The OutputStream of stack
+ */
+ void initializeShellIO (InputStream shellInputStream,
+ OutputStream shellOutputStream);
+
+ /**
+ * Closes the readers and writer associated with shell of this contact
+ */
+ void closeShellIO ();
+
+ /**
+ * Determines whether a connection to a remote server is already underway
+ *
+ * @return connectionInProgress
+ */
+ public boolean isConnectionInProgress ();
+
+ /**
+ * Sets the status of connection attempt to remote server
+ *
+ * @param connectionInProgress
+ */
+ public void setConnectionInProgress (boolean connectionInProgress);
+
+// /**
+// * Sets the PS1 prompt of the current shell of Contact
+// * This method is synchronized
+// *
+// * @param sshPrompt to be associated
+// */
+// public void setShellPrompt(String sshPrompt);
+//
+// /**
+// * Returns the PS1 prompt of the current shell of Contact
+// *
+// * @return sshPrompt
+// */
+// public String getShellPrompt();
+
+
+ /**
+ * Saves the details of contact in persistentData
+ */
+ public void savePersistentDetails ();
+
+ /*
+ * Returns the OperationSetContactInfo associated with this contact
+ *
+ * @return sshConfigurationForm
+ */
+ public OperationSetContactInfo getSSHConfigurationForm ();
+
+ /**
+ * Returns the JSch Stack identified associated with this contact
+ *
+ * @return jsch
+ */
+ JSch getJSch ();
+
+ /**
+ * Starts the timer and its task to periodically update the status of
+ * remote machine
+ */
+ void startTimerTask ();
+
+ /**
+ * Stops the timer and its task to stop updating the status of
+ * remote machine
+ */
+ void stopTimerTask ();
+
+ /**
+ * Sets the JSch Stack identified associated with this contact
+ *
+ * @param jsch to be associated
+ */
+ void setJSch (JSch jsch);
+
+ /**
+ * Returns the Username associated with this contact
+ *
+ * @return userName
+ */
+ String getUserName ();
+
+ /**
+ * Returns the Hostname associated with this contact
+ *
+ * @return hostName
+ */
+ String getHostName ();
+
+ /**
+ * Returns the Password associated with this contact
+ *
+ * @return password
+ */
+ String getPassword ();
+
+ /**
+ * Sets the Password associated with this contact
+ *
+ * @param password
+ */
+ void setPassword (String password);
+
+ /**
+ * Returns the SSH Session associated with this contact
+ *
+ * @return sshSession
+ */
+ Session getSSHSession ();
+
+ /**
+ * Sets the SSH Session associated with this contact
+ *
+ * @param sshSession the newly created SSH Session to be associated
+ */
+ void setSSHSession (Session sshSession);
+
+ /**
+ * Returns the SSH Shell Channel associated with this contact
+ *
+ * @return shellChannel
+ */
+ Channel getShellChannel ();
+
+ /**
+ * Sets the SSH Shell channel associated with this contact
+ *
+ * @param shellChannel to be associated with SSH Session of this contact
+ */
+ void setShellChannel (Channel shellChannel);
+
+ /**
+ * Sends a message a line to remote machine via the Shell Writer
+ *
+ * @param message to be sent
+ */
+ public void sendLine (String message)
+ throws IOException;
+
+// /**
+// * Reads a line from the remote machine via the Shell Reader
+// *
+// * @return message read
+// */
+// public String getLine()
+// throws IOException;
+
+ /**
+ * Returns the Input Stream associated with SSH Channel of this contact
+ *
+ * @return shellInputStream associated with SSH Channel of this contact
+ */
+ public InputStream getShellInputStream ();
+
+// /**
+// * Sets the Input Stream associated with SSH Channel of this contact
+// *
+// * @param shellInputStream to be associated with SSH Channel of this
+// * contact
+// */
+// public void setShellInputStream(InputStream shellInputStream);
+
+ /**
+ * Returns the Output Stream associated with SSH Channel of this contact
+ *
+ * @return shellOutputStream associated with SSH Channel of this contact
+ */
+ public OutputStream getShellOutputStream ();
+
+// /**
+// * Sets the Output Stream associated with SSH Channel of this contact
+// *
+// * @param shellOutputStream to be associated with SSH Channel of this
+// * contact
+// */
+// public void setShellOutputStream(OutputStream shellOutputStream);
+//
+ /**
+ * Returns the BufferedReader associated with SSH Channel of this contact
+ *
+ * @return shellReader associated with SSH Channel of this contact
+ */
+ public InputStreamReader getShellReader ();
+//
+// /**
+// * Sets the BufferedReader associated with SSH Channel of this contact
+// *
+// * @param shellReader to be associated with SSH Channel of this contact
+// */
+// public void setShellReader(BufferedReader shellReader);
+
+ /**
+ * Returns the PrintWriter associated with SSH Channel of this contact
+ *
+ * @return shellWriter associated with SSH Channel of this contact
+ */
+ public PrintWriter getShellWriter ();
+
+// /**
+// * Sets the PrintWriter associated with SSH Channel of this contact
+// *
+// * @param shellWriter to be associated with SSH Channel of this contact
+// */
+// public void setShellWriter(PrintWriter shellWriter);
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/ContactSSHFileTransferDaemon.java b/src/net/java/sip/communicator/impl/protocol/ssh/ContactSSHFileTransferDaemon.java
new file mode 100644
index 000000000..122db4676
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/ssh/ContactSSHFileTransferDaemon.java
@@ -0,0 +1,468 @@
+/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ *
+ * ContactSSHFileTransferDaemon.java
+ *
+ * SSH Suport in SIP Communicator - GSoC' 07 Project
+ *
+ */
+
+package net.java.sip.communicator.impl.protocol.ssh;
+
+import java.io.*;
+import javax.swing.*;
+import com.jcraft.jsch.*;
+
+import net.java.sip.communicator.util.Logger;
+import net.java.sip.communicator.service.protocol.*;
+
+/**
+ *
+ * @author Shobhit Jindal
+ */
+public class ContactSSHFileTransferDaemon
+ extends Thread
+{
+ private static final Logger logger =
+ Logger.getLogger(ContactSSHFileTransferDaemon .class);
+
+ /**
+ * The contact of the remote machine
+ */
+ private ContactSSH sshContact;
+
+ /**
+ * The currently valid ssh protocol provider
+ */
+ private ProtocolProviderServiceSSHImpl ppService;
+
+ /**
+ * JSch Channel to be used for file transfer
+ */
+ private Channel fileTransferChannel;
+
+ /**
+ * The identifier for the Input Stream associated with SCP Channel
+ */
+ private InputStream scpInputStream = null;
+
+ /**
+ * The identifier for the Output Stream associated with SCP Channel
+ */
+ private OutputStream scpOutputStream = null;
+
+ /**
+ * Identifier of local file
+ */
+ private String localPath;
+
+ /**
+ * Identifier of remote file
+ */
+ private String remotePath;
+
+ /**
+ * File to be uploaded or saved
+ */
+ private File file;
+
+ /**
+ * The file input stream associated with the file to be uploaded
+ */
+ private FileInputStream fileInputStream;
+
+ /**
+ * The file output stream associated with the file to be uploaded
+ */
+ private FileOutputStream fileOutputStream;
+
+ /**
+ * The boolean which determines whether we are uploading or downloading
+ * files
+ */
+ private boolean uploadFile;
+
+ /**
+ * The currently valid ssh persistent presence operation set
+ */
+ private OperationSetPersistentPresenceSSHImpl opSetPersPresence = null;
+
+ /**
+ * The currently valid ssh instant messaging operation set
+ */
+ private OperationSetBasicInstantMessagingSSHImpl instantMessaging = null;
+
+ /**
+ * Creates a new instance of ContactSSHFileTransferDaemon
+ *
+ * @param sshContact The contact of the remote machine
+ * @param opSetPersPresence The current ssh presence operation set
+ * @param instantMessaging The current ssh instant messaging operation set
+ * @param ppService The current ssh protocol provider
+ */
+ public ContactSSHFileTransferDaemon(
+ ContactSSH sshContact,
+ OperationSetPersistentPresenceSSHImpl opSetPersPresence,
+ OperationSetBasicInstantMessagingSSHImpl instantMessaging,
+ ProtocolProviderServiceSSHImpl ppService)
+ {
+ super();
+ this.sshContact = sshContact;
+ this.opSetPersPresence = opSetPersPresence;
+ this.instantMessaging = instantMessaging;
+ this.ppService = ppService;
+ }
+
+ /**
+ * This method is called when file is to be transfered from local machine
+ * to remote machine
+ *
+ * @param remotePath - the identifier for the remote file
+ * @param localPath - the identifier for the local file
+ */
+ public void uploadFile(
+ String remotePath,
+ String localPath)
+ {
+ this.uploadFile = true;
+ this.remotePath = remotePath;
+ this.localPath = localPath;
+
+ file = new File(localPath);
+
+ start();
+ }
+
+ /**
+ * This method is called when a file is to be downloaded from remote machine
+ * to local machine
+ *
+ * @param remotePath - the identifier for the remote file
+ * @param localPath - the identifier for the local file
+ */
+ public void downloadFile(
+ String remotePath,
+ String localPath)
+ {
+ this.uploadFile = false;
+ this.remotePath = remotePath;
+ this.localPath = localPath;
+
+ file = new File(localPath);
+
+ start();
+ }
+
+ /**
+ * Background thread for the file transfer
+ */
+ public void run()
+ {
+ //oldStatus to be resumed earlier
+ PresenceStatus oldStatus = sshContact.getPresenceStatus();
+
+ opSetPersPresence.changeContactPresenceStatus(
+ sshContact,
+ SSHStatusEnum.CONNECTING);
+
+ try
+ {
+ //create a new JSch session if current is invalid
+ if( !ppService.isSessionValid(sshContact))
+ ppService.createSSHSessionAndLogin(sshContact);
+
+ fileTransferChannel = sshContact.getSSHSession()
+ .openChannel("exec");
+ String command;
+
+ // -p = Preserves modification times, access times, and modes from
+ // the original file
+ if(uploadFile)
+ command = "scp -p -t " + remotePath;
+ else
+ command = "scp -f " + remotePath;
+
+ //the command to be executed on the remote terminal
+ ((ChannelExec)fileTransferChannel).setCommand(command);
+
+ scpInputStream = fileTransferChannel.getInputStream();
+ scpOutputStream = fileTransferChannel.getOutputStream();
+
+ fileTransferChannel.connect();
+
+ //file transfer is setup
+ opSetPersPresence.changeContactPresenceStatus(
+ sshContact,
+ SSHStatusEnum.FILE_TRANSFER);
+
+ if(uploadFile)
+ {
+ instantMessaging.deliverMessage(
+ instantMessaging.createMessage(
+ "Uploading " + file.getName() + " to server"),
+ sshContact);
+
+ upload();
+ }
+ else
+ {
+ instantMessaging.deliverMessage(
+ instantMessaging.createMessage(
+ "Downloading " + file.getName() + " from server"),
+ sshContact);
+
+ download();
+ }
+
+ }
+ catch(Exception ex)
+ {
+ //presently errors(any type) are directly logged directly in chat
+ instantMessaging.deliverMessage(
+ instantMessaging.createMessage(ex.getMessage()),
+ sshContact);
+
+ ex.printStackTrace();
+ logger.error(ex.getMessage());
+
+ try
+ {
+ if(fileInputStream!=null)
+ {
+ fileInputStream.close();
+ }
+
+ if(fileOutputStream!=null)
+ {
+ fileOutputStream.close();
+ }
+ }
+ catch(Exception e)
+ {}
+ }
+
+ // restore old status
+ opSetPersPresence.changeContactPresenceStatus(
+ sshContact,
+ oldStatus);
+
+ }
+
+ /**
+ * Check for error in reading stream of remote machine
+ *
+ * @return 0 for success
+ * @return 1 for error
+ * @return 2 for fatal error
+ * @return -1 otherwise
+ *
+ * @throws IOException when the network goes down
+ */
+ private int checkAck(InputStream inputStream)
+ throws IOException
+ {
+ int result = inputStream.read();
+
+ // read error message
+ if(result==1 || result==2)
+ {
+ StringBuffer buffer = new StringBuffer();
+
+ int ch;
+
+ do
+ {
+ //read a line of message
+ ch = inputStream.read();
+ buffer.append((char)ch);
+
+ }while(ch != '\n');
+
+ ppService.getUIService().getPopupDialog().showMessagePopupDialog(
+ buffer.toString(),
+ "File Transfer Error: " + sshContact.getDisplayName(),
+ ppService.getUIService().getPopupDialog().ERROR_MESSAGE);
+
+ logger.error(buffer.toString());
+ }
+
+ return result;
+ }
+
+ /**
+ * Uploads the file to the remote server
+ *
+ * @throws IOException when the network goes down
+ * @throws OperationFailedException when server behaves unexpectedly
+ */
+ private void upload()
+ throws IOException,
+ OperationFailedException
+ {
+ fileInputStream = new FileInputStream(file);
+
+ byte[] buffer = new byte[1024];
+ int result, bytesRead;
+
+ if( (result = checkAck(scpInputStream)) !=0)
+ throw new OperationFailedException("Error in Ack", result);
+
+ // send "C0644 filesize filename", where filename should not include '/'
+ long filesize= file.length();
+ String command = "C0644 " + filesize + " ";
+
+// if(lfile.lastIndexOf('/')>0)
+// {
+// command+=lfile.substring(lfile.lastIndexOf('/')+1);
+// }
+// else
+// {
+// command+=lfile;
+// }
+
+ command += file.getName() + "\n";
+ logger.trace(command);
+ scpOutputStream.write(command.getBytes());
+ scpOutputStream.flush();
+
+ if( (result = checkAck(scpInputStream)) !=0)
+ throw new OperationFailedException("Error in Ack", result);
+
+ while(true)
+ {
+ bytesRead = fileInputStream.read(buffer, 0, buffer.length);
+ if(bytesRead <= 0)
+ break;
+
+ scpOutputStream.write(buffer, 0, bytesRead); //out.flush();
+ }
+ fileInputStream.close();
+ fileInputStream = null;
+
+ // send '\0'
+ buffer[0]=0; scpOutputStream.write(buffer, 0, 1);
+ scpOutputStream.flush();
+
+ if( (result = checkAck(scpInputStream)) !=0)
+ throw new OperationFailedException("Error in Ack", result);
+
+ scpInputStream.close();
+ scpOutputStream.close();
+
+ fileTransferChannel.disconnect();
+
+ instantMessaging.deliverMessage(
+ instantMessaging.createMessage(file.getName()
+ + " uploaded to Server"),
+ sshContact);
+ }
+
+ /**
+ * Downloads a file from the remote machine
+ *
+ * @throws IOException when the network goes down
+ * @throws OperationFailedException when server behaves unexpectedly
+ */
+ private void download()
+ throws IOException,
+ OperationFailedException
+ {
+ fileOutputStream = new FileOutputStream(file);
+
+ int result;
+
+ byte[] buffer = new byte[1024];
+
+ // send '\0'
+ buffer[0]=0;
+
+ scpOutputStream.write(buffer, 0, 1);
+ scpOutputStream.flush();
+
+ int ch = checkAck(scpInputStream);
+
+ if(ch!='C')
+ {
+ throw new OperationFailedException("Invalid reply from server", 12);
+ }
+
+ // read '0644 '
+ scpInputStream.read(buffer, 0, 5);
+
+ long filesize=0L;
+ while(true)
+ {
+ if(scpInputStream.read(buffer, 0, 1) < 0)
+ {
+ // error
+ break;
+ }
+ if(buffer[0]==' ')break;
+ filesize=filesize*10L+(long)(buffer[0]-'0');
+ }
+
+ String file=null;
+ for(int i=0;;i++)
+ {
+ scpInputStream.read(buffer, i, 1);
+ if(buffer[i]==(byte)0x0a)
+ {
+ file=new String(buffer, 0, i);
+ break;
+ }
+ }
+
+ //System.out.println("filesize="+filesize+", file="+file);
+
+ // send '\0'
+ buffer[0]=0;
+ scpOutputStream.write(buffer, 0, 1);
+ scpOutputStream.flush();
+
+ // read a content of lfile
+ int foo;
+ while(true)
+ {
+ if(buffer.lengthtrue if this contact has the same id as that of the
+ * obj argument.
+ */
+ public boolean equals(Object obj)
+ {
+ if (obj == null
+ || ! (obj instanceof ContactSSHImpl))
+ return false;
+
+ ContactSSHImpl sshContact = (ContactSSHImpl) obj;
+
+ return this.getAddress().equals(sshContact.getAddress());
+ }
+
+ /**
+ * Returns the persistent presence operation set that this contact belongs
+ * to.
+ *
+ * @return the OperationSetPersistentPresenceSSHImpl that
+ * this contact belongs to.
+ */
+ public OperationSetPersistentPresence
+ getParentPresenceOperationSet()
+ {
+ return (OperationSetPersistentPresence)parentProvider
+ .getOperationSet(OperationSetPersistentPresence.class);
+ }
+
+ /**
+ * Returns the BasicInstant Messaging operation set that this contact belongs
+ * to.
+ *
+ * @return the OperationSetBasicInstantMessagingSSHImpl that
+ * this contact belongs to.
+ */
+ public OperationSetBasicInstantMessaging
+ getParentBasicInstantMessagingOperationSet()
+ {
+ return (OperationSetBasicInstantMessaging)parentProvider
+ .getOperationSet(OperationSetBasicInstantMessaging.class);
+ }
+
+ /**
+ * Returns the File Transfer operation set that this contact belongs
+ * to.
+ *
+ * @return the OperationSetFileTransferSSHImpl that
+ * this contact belongs to.
+ */
+ public OperationSetFileTransfer
+ getFileTransferOperationSet()
+ {
+ return (OperationSetFileTransfer)parentProvider
+ .getOperationSet(OperationSetFileTransfer.class);
+ }
+
+
+ /**
+ * Returns the SSH Session associated with this contact
+ *
+ * @return sshSession
+ */
+ public Session getSSHSession()
+ {
+ return this.sshSession;
+ }
+
+ /**
+ * Sets the SSH Session associated with this contact
+ *
+ * @param sshSession the newly created SSH Session to be associated
+ */
+ public void setSSHSession(Session sshSession)
+ {
+ this.sshSession = sshSession;
+ }
+
+ /**
+ * Returns the SSH Shell Channel associated with this contact
+ *
+ * @return sshShellChannel
+ */
+ public Channel getShellChannel()
+ {
+ return this.sshShellChannel;
+ }
+
+ /**
+ * Sets the SSH Shell channel associated with this contact
+ *
+ * @param sshShellChannel to be associated with SSH Session of this contact
+ */
+ public void setShellChannel(Channel sshShellChannel)
+ {
+ this.sshShellChannel = sshShellChannel;
+ }
+
+ /**
+ * Returns the Input Stream associated with SSH Channel of this contact
+ *
+ * @return shellInputStream associated with SSH Channel of this contact
+ */
+ public InputStream getShellInputStream()
+ {
+ return this.shellInputStream;
+ }
+
+// /**
+// * Sets the Input Stream associated with SSH Channel of this contact
+// *
+// * @param shellInputStream to be associated with SSH Channel of this contact
+// */
+// public void setShellInputStream(InputStream shellInputStream)
+// {
+// this.shellInputStream = shellInputStream;
+// }
+
+ /**
+ * Returns the Output Stream associated with SSH Channel of this contact
+ *
+ * @return shellOutputStream associated with SSH Channel of this contact
+ */
+ public OutputStream getShellOutputStream()
+ {
+ return this.shellOutputStream;
+ }
+
+// /**
+// * Sets the Output Stream associated with SSH Channel of this contact
+// *
+// * @param shellOutputStream to be associated with SSH Channel of this contact
+// */
+// public void setShellOutputStream(OutputStream shellOutputStream)
+// {
+// this.shellOutputStream = shellOutputStream;
+// }
+
+ /**
+ * Returns the BufferedReader associated with SSH Channel of this contact
+ *
+ * @return shellReader associated with SSH Channel of this contact
+ */
+ public InputStreamReader getShellReader()
+ {
+ return this.shellReader;
+ }
+
+// /**
+// * Sets the BufferedReader associated with SSH Channel of this contact
+// *
+// * @param shellReader to be associated with SSH Channel of this contact
+// */
+// public void setShellReader(BufferedReader shellReader)
+// {
+// this.shellReader = shellReader;
+// }
+
+ /**
+ * Returns the PrintWriter associated with SSH Channel of this contact
+ *
+ * @return shellWriter associated with SSH Channel of this contact
+ */
+ public PrintWriter getShellWriter()
+ {
+ return this.shellWriter;
+ }
+
+// /**
+// * Sets the PrintWriter associated with SSH Channel of this contact
+// *
+// * @param shellWriter to be associated with SSH Channel of this contact
+// */
+// public void setShellWriter(PrintWriter shellWriter)
+// {
+// this.shellWriter = shellWriter;
+// }
+
+ /**
+ * Returns the userName associated with SSH Channel of this contact
+ *
+ * @return userName associated with SSH Channel of this contact
+ */
+ public String getUserName()
+ {
+ return sshConfigurationForm.getUserName();
+ }
+
+ /**
+ * Returns the password associated with SSH Channel of this contact
+ *
+ * @return password associated with SSH Channel of this contact
+ */
+ public String getPassword()
+ {
+ return sshConfigurationForm.getPassword();
+ }
+
+ /**
+ * Sets the Password associated with this contact
+ *
+ * @param password
+ */
+ public void setPassword(String password)
+ {
+ this.sshConfigurationForm.setPasswordField(password);
+ savePersistentDetails();
+ }
+
+// /**
+// * Sets the PS1 prompt of the current shell of Contact
+// *
+// * @param sshPrompt to be associated
+// */
+// public void setShellPrompt(String sshPrompt)
+// {
+// this.sshPrompt = sshPrompt;
+// }
+//
+// /**
+// * Returns the PS1 prompt of the current shell of Contact
+// *
+// * @return sshPrompt
+// */
+// public String getShellPrompt()
+// {
+// return this.sshPrompt;
+// }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/ContactSSHReaderDaemon.java b/src/net/java/sip/communicator/impl/protocol/ssh/ContactSSHReaderDaemon.java
new file mode 100644
index 000000000..50e047b3b
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/ssh/ContactSSHReaderDaemon.java
@@ -0,0 +1,198 @@
+/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ *
+ * ContactSSHReaderDaemon.java
+ *
+ * SSH Suport in SIP Communicator - GSoC' 07 Project
+ *
+ */
+
+package net.java.sip.communicator.impl.protocol.ssh;
+
+import java.io.*;
+import javax.swing.*;
+
+import net.java.sip.communicator.util.Logger;
+import net.java.sip.communicator.service.protocol.*;
+
+/**
+ *
+ * @author Shobhit Jindal
+ */
+public class ContactSSHReaderDaemon
+ extends Thread
+{
+ private static final Logger logger =
+ Logger.getLogger(ContactSSHReaderDaemon.class);
+
+ /**
+ * A Buffer to aggregate replies to be sent as one message
+ */
+ private StringBuffer replyBuffer;
+
+ /**
+ * The identifier of Contact representing the remote machine
+ */
+ private ContactSSHImpl sshContact;
+
+ /**
+ * The identifier of the message received from server
+ */
+ private String message;
+
+ /**
+ * An identifier representing the state of Reader Daemon
+ */
+ private boolean isActive = false;
+
+ /**
+ * This OperationSet delivers incoming message
+ */
+ private OperationSetBasicInstantMessagingSSHImpl instantMessaging;
+
+ /**
+ * Input Stream of remote user to be read
+ */
+ private InputStream shellInputStream;
+
+ /**
+ * Buffered Reader associated with above input stream
+ */
+ private InputStreamReader shellReader;
+
+// /**
+// * This OperationSet delivers incoming message
+// */
+// private OperationSetPersistentPresenceSSHImpl persistentPresence;
+
+ /**
+ * Bytes available in Input Stream before reading
+ */
+ private int bytesAvailable;
+
+ private int bytesRead;
+
+ char buffer[] = new char[1024], buf;
+
+ /**
+ * Creates a new instance of ContactSSHReaderDaemon
+ */
+ public ContactSSHReaderDaemon(ContactSSH sshContact)
+ {
+ this.sshContact = (ContactSSHImpl)sshContact;
+ instantMessaging = (OperationSetBasicInstantMessagingSSHImpl) sshContact
+ .getProtocolProvider().getOperationSet(
+ OperationSetBasicInstantMessaging.class);
+ }
+
+ /**
+ * Reads the remote machine, updating the chat window as necessary
+ * in a background thread
+ */
+ public void run()
+ {
+ shellInputStream = sshContact.getShellInputStream();
+ shellReader = sshContact.getShellReader();
+ replyBuffer = new StringBuffer();
+
+ try
+ {
+ do
+ {
+ bytesAvailable = shellInputStream.available();
+
+ if(bytesAvailable == 0 )
+ {
+ // wait if more data is available
+ // for a slower connection this value need to be raised
+ // to avoid splitting of messages
+ Thread.sleep(50);
+ continue;
+ }
+
+// if(replyBuffer > 0)
+ do
+ {
+ // store the responses in a buffer
+ storeMessage(replyBuffer);
+
+ Thread.sleep(250);
+
+ bytesAvailable = shellInputStream.available();
+
+ }while(bytesAvailable > 0 );
+
+ message = replyBuffer.toString();
+
+ if(sshContact.isCommandSent())
+ {
+ // if the response is as a result of a command sent
+ sshContact.setMessageType(sshContact
+ .CONVERSATION_MESSAGE_RECEIVED);
+
+ message = message.substring(message.indexOf('\n') + 1);
+
+ sshContact.setCommandSent(false);
+ }
+ else
+ {
+ // server sent an asynchronous message to the terminal
+ // display it as a system message
+ sshContact.setMessageType(sshContact
+ .SYSTEM_MESSAGE_RECEIVED);
+
+ //popup disabled
+// JOptionPane.showMessageDialog(
+// null,
+// message,
+// "Message from " + sshContact.getDisplayName(),
+// JOptionPane.INFORMATION_MESSAGE);
+ }
+
+ instantMessaging.deliverMessage(
+ instantMessaging.createMessage(message),
+ sshContact);
+
+ replyBuffer.delete(0, replyBuffer.length());
+
+ }while(isActive);
+ }
+ catch(Exception ex)
+ {
+ ex.printStackTrace();
+ }
+ }
+
+ /**
+ * Stores the response from server in a temporary buffer
+ * the bytes available are determined before the function is called
+ *
+ * @param replyBuffer to store the response from server
+ *
+ * @throws IOException if the network goes down
+ */
+ private void storeMessage(StringBuffer replyBuffer) throws IOException
+ {
+ do
+ {
+ buf = (char)shellInputStream.read();
+
+// System.out.println(String.valueOf(buf)+ " " + (int)buf);
+
+ replyBuffer.append(String.valueOf(buf));
+
+// logger.debug(shellReader.readLine());
+
+ bytesAvailable--;
+
+ }while(bytesAvailable>0);
+ }
+
+ public void isActive(boolean isActive)
+ {
+ this.isActive = isActive;
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/MessageSSHImpl.java b/src/net/java/sip/communicator/impl/protocol/ssh/MessageSSHImpl.java
new file mode 100644
index 000000000..454c206eb
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/ssh/MessageSSHImpl.java
@@ -0,0 +1,143 @@
+/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ *
+ * MessageSSHImpl.java
+ *
+ * SSH Suport in SIP Communicator - GSoC' 07 Project
+ *
+ */
+
+package net.java.sip.communicator.impl.protocol.ssh;
+
+import net.java.sip.communicator.service.protocol.*;
+
+/**
+ * Very simple message implementation for the SSH protocol.
+ *
+ * @author Shobhit Jindal
+ */
+public class MessageSSHImpl
+ implements Message
+{
+ /**
+ * The actual message content.
+ */
+ private String textContent = null;
+
+ /**
+ * The content type of the message. (text/plain if null)
+ */
+ private String contentType = null;
+
+ /**
+ * The message encoding. (UTF8 if null).
+ */
+ private String contentEncoding = null;
+
+ /**
+ * A String uniquely identifying the message
+ */
+ private String messageUID = null;
+
+ /**
+ * The subject of the message. (most often is null)
+ */
+ private String subject = null;
+
+ /**
+ * Creates a message instance according to the specified parameters.
+ *
+ * @param content the message body
+ * @param contentType message content type or null for text/plain
+ * @param contentEncoding message encoding or null for UTF8
+ * @param subject the subject of the message or null for no subject.
+ */
+ public MessageSSHImpl(String content,
+ String contentType,
+ String contentEncoding,
+ String subject)
+ {
+ this.textContent = content;
+ this.contentType = contentType;
+ this.contentEncoding = contentEncoding;
+ this.subject = subject;
+
+ //generate the uid
+ this.messageUID = String.valueOf(System.currentTimeMillis())
+ + String.valueOf(hashCode());
+
+ }
+
+ /**
+ * Returns the message body.
+ *
+ * @return the message content.
+ */
+ public String getContent()
+ {
+ return textContent;
+ }
+
+ /**
+ * Returns the type of the content of this message.
+ *
+ * @return the type of the content of this message.
+ */
+ public String getContentType()
+ {
+ return contentType;
+ }
+
+ /**
+ * Returns the encoding used for the message content.
+ *
+ * @return the encoding of the message body.
+ */
+ public String getEncoding()
+ {
+ return contentEncoding;
+ }
+
+ /**
+ * A string uniquely identifying the message.
+ *
+ * @return a String uniquely identifying the message.
+ */
+ public String getMessageUID()
+ {
+ return messageUID;
+ }
+
+ /**
+ * Returns the message body in a binary form.
+ *
+ * @return a byte[] representation of the message body.
+ */
+ public byte[] getRawData()
+ {
+ return getContent().getBytes();
+ }
+
+ /**
+ * Return the length of this message.
+ *
+ * @return the length of this message.
+ */
+ public int getSize()
+ {
+ return getContent().length();
+ }
+
+ /**
+ * Returns the message subject.
+ *
+ * @return the message subject.
+ */
+ public String getSubject()
+ {
+ return subject;
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/OperationSetBasicInstantMessagingSSHImpl.java b/src/net/java/sip/communicator/impl/protocol/ssh/OperationSetBasicInstantMessagingSSHImpl.java
new file mode 100644
index 000000000..fc3727f6b
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/ssh/OperationSetBasicInstantMessagingSSHImpl.java
@@ -0,0 +1,404 @@
+/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ *
+ * OperationSetBasicInstantMessagingSSHImpl.java
+ *
+ * SSH Suport in SIP Communicator - GSoC' 07 Project
+ *
+ */
+
+package net.java.sip.communicator.impl.protocol.ssh;
+
+import java.io.*;
+import java.util.*;
+import net.java.sip.communicator.util.Logger;
+
+import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.service.protocol.event.*;
+
+/**
+ * Instant messaging functionalites for the SSH protocol.
+ *
+ * @author Shobhit Jindal
+ */
+public class OperationSetBasicInstantMessagingSSHImpl
+ implements OperationSetBasicInstantMessaging
+{
+ private static final Logger logger
+ = Logger.getLogger(OperationSetBasicInstantMessagingSSHImpl.class);
+
+ /**
+ * Currently registered message listeners.
+ */
+ private Vector messageListeners = new Vector();
+
+ /**
+ * The currently valid persistent presence operation set..
+ */
+ private OperationSetPersistentPresenceSSHImpl opSetPersPresence = null;
+
+ /**
+ * The currently valid file transfer operation set
+ */
+ private OperationSetFileTransferSSHImpl fileTransfer;
+
+ /**
+ * The protocol provider that created us.
+ */
+ private ProtocolProviderServiceSSHImpl parentProvider = null;
+
+ /**
+ * The test command given after each command to determine the reply length
+ * of the command
+ */
+ private final String testCommand = Resources.getString("testCommand");
+
+
+ private String testCommandResponse = Resources
+ .getString("testCommandResponse");
+
+ /**
+ * Creates an instance of this operation set keeping a reference to the
+ * parent protocol provider and presence operation set.
+ *
+ * @param provider The provider instance that creates us.
+ * @param opSetPersPresence the currently valid
+ * OperationSetPersistentPresenceSSHImpl instance.
+ */
+ public OperationSetBasicInstantMessagingSSHImpl(
+ ProtocolProviderServiceSSHImpl provider,
+ OperationSetPersistentPresenceSSHImpl opSetPersPresence)
+// OperationSetFileTransferSSHImpl fileTransfer)
+ {
+ this.opSetPersPresence = opSetPersPresence;
+ this.parentProvider = provider;
+// this.fileTransfer = fileTransfer;
+ }
+
+ /**
+ * Registers a MessageListener with this operation set so that it gets
+ * notifications of successful message delivery, failure or reception of
+ * incoming messages..
+ *
+ * @param listener the MessageListener to register.
+ */
+ public void addMessageListener(MessageListener listener)
+ {
+ if(!messageListeners.contains(listener))
+ messageListeners.add(listener);
+ }
+
+ /**
+ * Create a Message instance for sending arbitrary MIME-encoding content.
+ *
+ * @param content content value
+ * @param contentType the MIME-type for content
+ * @param contentEncoding encoding used for content
+ * @param subject a String subject or null for now
+ * subject.
+ * @return the newly created message.
+ */
+ public Message createMessage(
+ byte[] content,
+ String contentType,
+ String contentEncoding,
+ String subject)
+ {
+ return new MessageSSHImpl(new String(content), contentType
+ , contentEncoding, subject);
+ }
+
+ /**
+ * Create a Message instance for sending a simple text messages with
+ * default (text/plain) content type and encoding.
+ *
+ * @param messageText the string content of the message.
+ * @return Message the newly created message
+ */
+ public Message createMessage(String messageText)
+ {
+ return new MessageSSHImpl(messageText, DEFAULT_MIME_TYPE
+ , DEFAULT_MIME_ENCODING, null);
+ }
+
+ /**
+ * Unregisteres listener so that it won't receive any further
+ * notifications upon successful message delivery, failure or reception
+ * of incoming messages..
+ *
+ * @param listener the MessageListener to unregister.
+ */
+ public void removeMessageListener(MessageListener listener)
+ {
+ messageListeners.remove(listener);
+ }
+
+ /**
+ * Sends the message to the destination indicated by the
+ * to contact. An attempt is made to re-establish the shell
+ * connection if the current one is invalid.
+ * The reply from server is sent by a seperate reader thread
+ *
+ * @param to the Contact to send message to
+ * @param message the Message to send.
+ * @throws IllegalStateException if the underlying ICQ stack is not
+ * registered and initialized.
+ * @throws IllegalArgumentException if to is not an instance
+ * belonging to the underlying implementation.
+ */
+ public void sendInstantMessage(
+ Contact to,
+ Message message)
+ throws IllegalStateException,
+ IllegalArgumentException
+ {
+ if( !(to instanceof ContactSSHImpl) )
+ throw new IllegalArgumentException(
+ "The specified contact is not a SSH contact."
+ + to);
+
+ ContactSSH sshContact = (ContactSSH)to;
+
+ // making sure no messages are sent and no new threads are triggered,
+ // until a thread trying to connect to remote server returns
+ if(sshContact.isConnectionInProgress())
+ {
+ deliverMessage(
+ createMessage("A connection attempt is in progress"),
+ (ContactSSHImpl)to);
+ return;
+ }
+
+ if( !parentProvider.isShellConnected(sshContact) )
+ {
+
+ try
+ {
+ /**
+ * creating a new SSH session / shell channel
+ * - first message
+ * - session is timed out
+ * - network problems
+ */
+ parentProvider.connectShell(sshContact, message);
+
+ //the first message is ignored
+ return;
+ }
+ catch (Exception ex)
+ {
+ throw new IllegalStateException(ex.getMessage());
+ }
+ }
+
+ if(wrappedMessage(message.getContent(), sshContact))
+ {
+ fireMessageDelivered(message, to);
+ return;
+ }
+
+ try
+ {
+ sshContact.sendLine(message.getContent());
+ sshContact.setCommandSent(true);
+ }
+ catch (IOException ex)
+ {
+ // Closing IO Streams
+ sshContact.closeShellIO();
+
+ throw new IllegalStateException(ex.getMessage());
+ }
+
+ fireMessageDelivered(message, to);
+ }
+
+ /**
+ * Check the message for wrapped Commands
+ * All commands begin with /
+ *
+ * @param message from user
+ * @param sshContact of the remote machine
+ *
+ * @return true if the message had commands, false otherwise
+ */
+ private boolean wrappedMessage(
+ String message,
+ ContactSSH sshContact)
+ {
+
+ if(message.startsWith("/upload"))
+ {
+ int firstSpace = message.indexOf(' ');
+ sshContact.getFileTransferOperationSet().sendFile(
+ sshContact,
+ null,
+ message.substring(message.indexOf(' ', firstSpace+1) + 1),
+ message.substring(firstSpace+1, message.indexOf(' ', firstSpace+1))
+ );
+
+ return true;
+ }
+ else if(message.startsWith("/download"))
+ {
+ int firstSpace = message.indexOf(' ');
+ sshContact.getFileTransferOperationSet().sendFile(
+ null,
+ sshContact,
+ message.substring(firstSpace+1, message.indexOf(' ', firstSpace+1)),
+ message.substring(message.indexOf(' ', firstSpace+1) + 1));
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * In case the to Contact corresponds to another ssh
+ * protocol provider registered with SIP Communicator, we deliver
+ * the message to them, in case the to Contact represents us, we
+ * fire a MessageReceivedEvent, and if to is simply
+ * a contact in our contact list, then we simply echo the message.
+ *
+ * @param message the Message the message to deliver.
+ * @param to the Contact that we should deliver the message to.
+ */
+ void deliverMessage(
+ Message message,
+ ContactSSH to)
+ {
+ String userID = to.getAddress();
+
+ //if the user id is owr own id, then this message is being routed to us
+ //from another instance of the ssh provider.
+ if (userID.equals(this.parentProvider.getAccountID().getUserID()))
+ {
+ //check who is the provider sending the message
+ String sourceUserID
+ = to.getProtocolProvider().getAccountID().getUserID();
+
+ //check whether they are in our contact list
+ Contact from = opSetPersPresence.findContactByID(sourceUserID);
+
+ //and if not - add them there as volatile.
+ if(from == null)
+ {
+ from = opSetPersPresence.createVolatileContact(sourceUserID);
+ }
+
+ //and now fire the message received event.
+ fireMessageReceived(message, from);
+ }
+ else
+ {
+ //if userID is not our own, try an check whether another provider
+ //has that id and if yes - deliver the message to them.
+ ProtocolProviderServiceSSHImpl sshProvider
+ = this.opSetPersPresence.findProviderForSSHUserID(userID);
+ if(sshProvider != null)
+ {
+ OperationSetBasicInstantMessagingSSHImpl opSetIM
+ = (OperationSetBasicInstantMessagingSSHImpl)
+ sshProvider.getOperationSet(
+ OperationSetBasicInstantMessaging.class);
+ opSetIM.deliverMessage(message, to);
+ }
+ else
+ {
+ //if we got here then "to" is simply someone in our contact
+ //list so let's just echo the message.
+ fireMessageReceived(message, to);
+ }
+ }
+ }
+
+ /**
+ * Notifies all registered message listeners that a message has been
+ * delivered successfully to its addressee..
+ *
+ * @param message the Message that has been delivered.
+ * @param to the Contact that message was delivered to.
+ */
+ private void fireMessageDelivered(
+ Message message,
+ Contact to)
+ {
+ MessageDeliveredEvent evt
+ = new MessageDeliveredEvent(message, to, new Date());
+
+ Iterator listeners = null;
+ synchronized (messageListeners)
+ {
+ listeners = new ArrayList(messageListeners).iterator();
+ }
+
+ while (listeners.hasNext())
+ {
+ MessageListener listener
+ = (MessageListener) listeners.next();
+
+ listener.messageDelivered(evt);
+ }
+ }
+
+ /**
+ * Notifies all registered message listeners that a message has been
+ * received.
+ *
+ * @param message the Message that has been received.
+ * @param from the Contact that message was received from.
+ */
+ private void fireMessageReceived(
+ Message message,
+ Contact from)
+ {
+ MessageReceivedEvent evt
+ = new MessageReceivedEvent(
+ message,
+ from,
+ new Date(),
+ ((ContactSSH)from).getMessageType());
+
+ Iterator listeners = null;
+ synchronized (messageListeners)
+ {
+ listeners = new ArrayList(messageListeners).iterator();
+ }
+
+ while (listeners.hasNext())
+ {
+ MessageListener listener
+ = (MessageListener) listeners.next();
+
+ listener.messageReceived(evt);
+ }
+ }
+
+ /**
+ * Determines wheter the SSH protocol provider supports
+ * sending and receiving offline messages.
+ *
+ * @return false
+ */
+ public boolean isOfflineMessagingSupported()
+ {
+ return false;
+ }
+
+ /**
+ * Determines wheter the protocol supports the supplied content type
+ *
+ * @param contentType the type we want to check
+ * @return true if the protocol supports it and
+ * false otherwise.
+ */
+ public boolean isContentTypeSupported(String contentType)
+ {
+ return false;
+ }
+
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/OperationSetContactInfo.java b/src/net/java/sip/communicator/impl/protocol/ssh/OperationSetContactInfo.java
new file mode 100644
index 000000000..cebfee1f4
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/ssh/OperationSetContactInfo.java
@@ -0,0 +1,348 @@
+/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ *
+ * OperationSetContactInfo.java
+ *
+ * SSH Suport in SIP Communicator - GSoC' 07 Project
+ *
+ */
+
+package net.java.sip.communicator.impl.protocol.ssh;
+
+import net.java.sip.communicator.service.gui.*;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+
+/**
+ *
+ * @author Shobhit Jindal
+ */
+class OperationSetContactInfo extends JDialog
+ implements ConfigurationForm
+{
+ private ContactSSH sshContact;
+
+ private JPanel mainPanel = new JPanel();
+ private JPanel machinePanel = new JPanel();
+ private JPanel detailNamesPanel = new JPanel();
+ private JPanel detailFieldsPanel = new JPanel();
+ private JPanel detailsPanel = new JPanel();
+
+ private JCheckBox addDetailsCheckBox = new JCheckBox("Add Details");
+
+ private JButton doneButton = new JButton("Done");
+ private JLabel machineID = new JLabel("Hostname / IP: ");
+ private JTextField machineIDField = new JTextField();
+ private JLabel userName = new JLabel("User Name: ");
+ private JTextField userNameField = new JTextField();
+ private JLabel password = new JLabel("Password: ");
+ private JTextField passwordField = new JPasswordField();
+ private JLabel port = new JLabel("Port: ");
+ private JTextField portField = new JTextField("22");
+ private JLabel secs = new JLabel("secs");
+ private JLabel statusUpdate = new JLabel("Update Interval: ");
+ private JLabel terminalType = new JLabel("Terminal Type: ");
+ private JTextField terminalTypeField = new JTextField("SIP Communicator");
+ private JSpinner updateTimer = new JSpinner();
+
+ private JPanel emptyPanel1 = new JPanel();
+
+ private JPanel emptyPanel2 = new JPanel();
+
+ private JPanel emptyPanel3 = new JPanel();
+
+ private JPanel emptyPanel4 = new JPanel();
+
+ private JPanel emptyPanel5 = new JPanel();
+
+ private JPanel emptyPanel6 = new JPanel();
+
+ private JPanel emptyPanel7 = new JPanel();
+
+ private JPanel emptyPanel8 = new JPanel();
+
+ private JPanel emptyPanel9 = new JPanel();
+
+ private JPanel emptyPanel10 = new JPanel();
+
+ private JPanel emptyPanel11 = new JPanel();
+
+// private ContactGroup contactGroup = null;
+
+ /**
+ * Creates a new instance of OperationSetContactInfo
+ *
+ * @param sshContact the concerned contact
+ */
+ public OperationSetContactInfo(ContactSSH sshContact)
+ {
+ super(new JFrame(), true);
+ this.sshContact = sshContact;
+ initForm();
+
+ this.getContentPane().add(mainPanel);
+
+ this.setSize(370, 325);
+
+ this.setResizable(false);
+
+ this.setTitle("SSH: Account Details of " + sshContact.getDisplayName());
+
+ Toolkit toolkit = Toolkit.getDefaultToolkit();
+ Dimension screenSize = toolkit.getScreenSize();
+
+ int x = (screenSize.width - this.getWidth()) / 2;
+ int y = (screenSize.height - this.getHeight()) / 2;
+
+ this.setLocation(x,y);
+
+// ProtocolProviderServiceSSHImpl.getUIService().getConfigurationWindow().
+// addConfigurationForm(this);
+ }
+
+ /**
+ * initialize the form.
+ */
+ public void initForm()
+ {
+ updateTimer.setValue(new Integer(30));
+
+ userNameField.setEnabled(false);
+ passwordField.setEditable(false);
+ portField.setEnabled(false);
+ terminalTypeField.setEnabled(false);
+ updateTimer.setEnabled(false);
+
+ mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
+ machinePanel.setLayout(new BoxLayout(machinePanel, BoxLayout.X_AXIS));
+ detailNamesPanel.setLayout(new BoxLayout(detailNamesPanel,
+ BoxLayout.Y_AXIS));
+ detailFieldsPanel.setLayout(new BoxLayout(detailFieldsPanel,
+ BoxLayout.Y_AXIS));
+ detailsPanel.setLayout(new BoxLayout(detailsPanel, BoxLayout.X_AXIS));
+
+ machinePanel.add(machineID);
+ machinePanel.add(machineIDField);
+
+ detailNamesPanel.add(userName);
+ detailNamesPanel.add(emptyPanel1);
+ detailNamesPanel.add(password);
+ detailNamesPanel.add(emptyPanel2);
+ detailNamesPanel.add(port);
+ detailNamesPanel.add(emptyPanel3);
+ detailNamesPanel.add(statusUpdate);
+ detailNamesPanel.add(emptyPanel4);
+ detailNamesPanel.add(terminalType);
+
+ detailFieldsPanel.add(userNameField);
+ detailFieldsPanel.add(emptyPanel5);
+ detailFieldsPanel.add(passwordField);
+ detailFieldsPanel.add(emptyPanel6);
+ detailFieldsPanel.add(portField);
+ detailFieldsPanel.add(emptyPanel7);
+ detailFieldsPanel.add(updateTimer);
+ detailFieldsPanel.add(emptyPanel8);
+ detailFieldsPanel.add(terminalTypeField);
+
+ detailsPanel.add(detailNamesPanel);
+ detailsPanel.add(detailFieldsPanel);
+
+ detailsPanel.setBorder(BorderFactory.createTitledBorder("Details"));
+
+ mainPanel.add(emptyPanel9);
+ mainPanel.add(machinePanel);
+ mainPanel.add(addDetailsCheckBox);
+ mainPanel.add(detailsPanel);
+ mainPanel.add(emptyPanel10);
+ mainPanel.add(doneButton);
+ mainPanel.add(emptyPanel11);
+
+ addDetailsCheckBox.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ addDetailsCheckBox.setEnabled(false);
+ userNameField.setEnabled(true);
+ passwordField.setEditable(true);
+ portField.setEnabled(true);
+ terminalTypeField.setEnabled(true);
+ updateTimer.setEnabled(true);
+
+ userNameField.grabFocus();
+ }
+ });
+
+ doneButton.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent event)
+ {
+ if(machineIDField.getText().equals(""))
+ {
+ machineIDField.setText("Field needed");
+ return;
+ }
+
+ sshContact.savePersistentDetails();
+// ((OperationSetPersistentPresenceSSHImpl)sshContact
+// .getParentPresenceOperationSet())
+// .addContactToList(contactGroup, sshContact);
+ setVisible(false);
+ }
+ });
+ }
+
+ /**
+ * Return the ssh icon
+ *
+ * @return the ssh icon
+ */
+ public byte[] getIcon()
+ {
+ return Resources.getImage(Resources.SSH_LOGO);
+ }
+
+ /**
+ * Return the main panel
+ *
+ * @return the main panel
+ */
+ public Object getForm()
+ {
+ return mainPanel;
+ }
+//
+// public void setContactGroup(ContactGroup contactGroup)
+// {
+// this.contactGroup = contactGroup;
+// }
+//
+// public ContactGroup getContactGroup()
+// {
+// return this.contactGroup;
+// }
+
+ /**
+ * Sets the UserName of the dialog
+ *
+ * @param userName to be associated
+ */
+ public void setUserNameField(String userName)
+ {
+ this.userNameField.setText(userName);
+ }
+
+ /**
+ * Sets the Password of the dialog
+ *
+ * @param password to be associated
+ */
+ public void setPasswordField(String password)
+ {
+ this.passwordField.setText(password);
+ }
+
+ /**
+ * Return the hostname
+ *
+ * @return the hostname
+ */
+ public String getHostName()
+ {
+ return this.machineIDField.getText();
+ }
+
+ /**
+ * Return the username
+ *
+ * @return the username
+ */
+ public String getUserName()
+ {
+ return this.userNameField.getText();
+ }
+
+ /**
+ * Return the password
+ *
+ * @return the password in a clear form
+ */
+ public String getPassword()
+ {
+ return this.passwordField.getText();
+ }
+
+ /**
+ * Return the terminal type
+ *
+ * @return the terminal type
+ */
+ public String getTerminalType()
+ {
+ return this.terminalTypeField.getText();
+ }
+
+ /**
+ * Return the port
+ *
+ * @return the port value
+ */
+ public int getPort()
+ {
+ return Integer.parseInt(this.portField.getText());
+ }
+
+ /**
+ * Return the update interval
+ *
+ * @return the update interval
+ */
+ public int getUpdateInterval()
+ {
+ return Integer.parseInt(String.valueOf(this.updateTimer.getValue()));
+ }
+
+ /**
+ * Sets the HostName of the dialog
+ *
+ * @param hostName to be associated
+ */
+ public void setHostNameField(String hostName)
+ {
+ this.machineIDField.setText(hostName);
+ }
+
+ /**
+ * Sets the Terminal Type of the dialog
+ *
+ * @param termType to be associated
+ */
+ public void setTerminalType(String termType)
+ {
+ this.terminalTypeField.setText(termType);
+ }
+
+ /**
+ * Sets the Update Interval of the dialog
+ *
+ * @param interval to be associated
+ */
+ public void setUpdateInterval(Integer interval)
+ {
+ this.updateTimer.setValue(interval);
+ }
+
+ /**
+ * Sets the Port of the dialog
+ *
+ * @param port to be associated
+ */
+ public void setPort(String port)
+ {
+ this.portField.setText(port);
+ }
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/OperationSetContactTimerSSHImpl.java b/src/net/java/sip/communicator/impl/protocol/ssh/OperationSetContactTimerSSHImpl.java
new file mode 100644
index 000000000..bbc8d4841
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/ssh/OperationSetContactTimerSSHImpl.java
@@ -0,0 +1,116 @@
+/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging clitent.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ *
+ * OperationSetContactTimerSSHImpl.java
+ *
+ * SSH Suport in SIP Communicator - GSoC' 07 Project
+ */
+
+package net.java.sip.communicator.impl.protocol.ssh;
+
+import java.net.*;
+import java.util.*;
+import net.java.sip.communicator.util.*;
+
+/**
+ * Timer Task to update the reachability status of SSH Contact in contact list
+ * The timer is started at either of the two places
+ * - A new contact - OperationSetPersistentPresenceSSHImpl
+ * .createUnresolvedContact
+ * - Existing Contact - OperationSetPersistentPresenceSSHImpl.subscribe
+ *
+ * @author Shobhit Jindal
+ */
+public class OperationSetContactTimerSSHImpl
+ extends TimerTask
+{
+ private static final Logger logger
+ = Logger.getLogger(OperationSetFileTransferSSHImpl.class);
+
+ /**
+ * The contact ID of the remote machine
+ */
+ private ContactSSH sshContact;
+
+ /**
+ * PersistentPresence Identifer assoiciated with SSH Contact
+ */
+ private OperationSetPersistentPresenceSSHImpl persistentPresence;
+
+ /**
+ * The method which is called at regular intervals to update the status
+ * of remote machines
+ *
+ * Presently only ONLINE and OFFILINE status are checked
+ */
+ public void run()
+ {
+ try
+ {
+/* InetAddress remoteMachine = InetAddress.getByName(
+ sshContact.getSSHConfigurationForm().getHostName());
+
+ //check if machine is reachable
+ if(remoteMachine.isReachable(
+ sshContact.getSSHConfigurationForm().getUpdateInterval()))
+ {
+ if(
+ ! sshContact.getPresenceStatus().equals(SSHStatusEnum
+ .ONLINE)
+ &&
+ ! sshContact.getPresenceStatus().equals(SSHStatusEnum
+ .CONNECTING)
+ &&
+ ! sshContact.getPresenceStatus().equals(SSHStatusEnum
+ .CONNECTED)
+ )
+*/
+
+ Socket socket = new Socket(
+ sshContact.getSSHConfigurationForm().getHostName(),
+ sshContact.getSSHConfigurationForm().getPort());
+
+
+ socket.close();
+
+ if (sshContact.getPresenceStatus().equals(SSHStatusEnum.OFFLINE)
+ || sshContact.getPresenceStatus().equals(SSHStatusEnum
+ .NOT_AVAILABLE))
+ {
+ // change status to online
+ persistentPresence.changeContactPresenceStatus(
+ sshContact, SSHStatusEnum.ONLINE);
+
+ logger.debug("SSH Host " + sshContact.getSSHConfigurationForm()
+ .getHostName() + ": Online");
+ }
+ }
+ catch (Exception ex)
+ {
+ if (sshContact.getPresenceStatus().equals(SSHStatusEnum.ONLINE)
+ || sshContact.getPresenceStatus().equals(SSHStatusEnum
+ .NOT_AVAILABLE))
+ {
+ persistentPresence.changeContactPresenceStatus(
+ sshContact, SSHStatusEnum.OFFLINE);
+
+ logger.debug("SSH Host " + sshContact.getSSHConfigurationForm()
+ .getHostName() + ": Offline");
+ }
+ }
+ }
+ /**
+ * Creates a new instance of OperationSetContactTimerSSHImpl
+ */
+ public OperationSetContactTimerSSHImpl(ContactSSH sshContact)
+ {
+ super();
+ this.sshContact = sshContact;
+ this.persistentPresence = (OperationSetPersistentPresenceSSHImpl)
+ sshContact.getParentPresenceOperationSet();
+ }
+
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/OperationSetFileTransferSSHImpl.java b/src/net/java/sip/communicator/impl/protocol/ssh/OperationSetFileTransferSSHImpl.java
new file mode 100644
index 000000000..b09efd0af
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/ssh/OperationSetFileTransferSSHImpl.java
@@ -0,0 +1,132 @@
+/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ *
+ * OperationSetFileTransferSSHImpl.java
+ *
+ * SSH Suport in SIP Communicator - GSoC' 07 Project
+ *
+ */
+
+package net.java.sip.communicator.impl.protocol.ssh;
+
+import java.util.*;
+
+import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.service.protocol.event.*;
+import net.java.sip.communicator.util.*;
+
+/**
+ * This class provides operations to upload/download files to remote machines
+ *
+ * @author Shobhit Jindal
+ */
+public class OperationSetFileTransferSSHImpl
+ implements OperationSetFileTransfer
+{
+ private static final Logger logger
+ = Logger.getLogger(OperationSetFileTransferSSHImpl.class);
+
+ /**
+ * Currently registered message listeners.
+ */
+ private Vector fileTransferListeners = new Vector();
+
+ /**
+ * The currently valid persistent presence operation set..
+ */
+ private OperationSetPersistentPresenceSSHImpl opSetPersPresence = null;
+
+ /**
+ * The currently valid ssh instant messaging operation set
+ */
+ private OperationSetBasicInstantMessagingSSHImpl instantMessaging = null;
+
+ /**
+ * The protocol provider that created us.
+ */
+ private ProtocolProviderServiceSSHImpl parentProvider = null;
+
+
+ /** Creates a new instance of OperationSetFileTransferSSHImpl */
+ public OperationSetFileTransferSSHImpl(
+ ProtocolProviderServiceSSHImpl parentProvider,
+ OperationSetPersistentPresenceSSHImpl opSetPersPresence,
+ OperationSetBasicInstantMessagingSSHImpl instantMessaging)
+ {
+ this.parentProvider = parentProvider;
+ this.opSetPersPresence = opSetPersPresence;
+ this.instantMessaging = instantMessaging;
+ }
+
+ /**
+ * Registers a FileTransferListener with this operation set so that it gets
+ * notifications of start, complete, failure of file transfers
+ *
+ * @param listener the FileListener to register.
+ */
+ public void addFileListener(FileListener listener)
+ {
+ if(!fileTransferListeners.contains(listener))
+ fileTransferListeners.add(listener);
+ }
+
+ /**
+ * The file transfer method to/from the remote machine
+ * either toContact is null(we are downloading file from remote machine
+ * or fromContact is null(we are uploading file to remote machine
+ *
+ * @param toContact - the file recipient
+ * @param fromContact - the file sender
+ * @param remotePath - the identifier for the remote file
+ * @param localPath - the identifier for the local file
+ */
+ public void sendFile(
+ Contact toContact,
+ Contact fromContact,
+ String remotePath,
+ String localPath)
+ {
+ if(toContact == null)
+ {
+ ContactSSHFileTransferDaemon fileTransferDaemon
+ = new ContactSSHFileTransferDaemon(
+ (ContactSSH)fromContact,
+ opSetPersPresence,
+ instantMessaging,
+ parentProvider);
+
+ if(localPath.endsWith(System.getProperty("file.separator")))
+ localPath += remotePath.substring(remotePath.lastIndexOf('/')
+ + 1);
+
+ fileTransferDaemon.downloadFile(
+ remotePath,
+ localPath);
+
+ return;
+ }
+ else if(fromContact == null)
+ {
+ ContactSSHFileTransferDaemon fileTransferDaemon
+ = new ContactSSHFileTransferDaemon(
+ (ContactSSH) toContact,
+ opSetPersPresence,
+ instantMessaging,
+ parentProvider);
+
+ fileTransferDaemon.uploadFile(
+ remotePath,
+ localPath);
+
+ return;
+ }
+
+ // code should not reach here
+ //assert false;
+ logger.error("we should not be here !");
+ }
+
+}
diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/OperationSetPersistentPresenceSSHImpl.java b/src/net/java/sip/communicator/impl/protocol/ssh/OperationSetPersistentPresenceSSHImpl.java
new file mode 100644
index 000000000..ab5342d8f
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/protocol/ssh/OperationSetPersistentPresenceSSHImpl.java
@@ -0,0 +1,1312 @@
+/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ *
+ * OperationSetPersistentPresenceSSHImpl.java
+ *
+ * SSH Suport in SIP Communicator - GSoC' 07 Project
+ *
+ */
+
+package net.java.sip.communicator.impl.protocol.ssh;
+
+import java.util.*;
+
+import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.service.protocol.event.*;
+import net.java.sip.communicator.util.*;
+import org.osgi.framework.*;
+
+/**
+ * A SSH implementation of a persistent presence operation set. In order
+ * to simulate server persistence, this operation set would simply accept all
+ * unresolved contacts and resolve them immediately. A real world protocol
+ * implementation would save it on a server using methods provided by the
+ * protocol stack.
+ *
+ * @author Shobhit Jindal
+ */
+public class OperationSetPersistentPresenceSSHImpl
+ implements OperationSetPersistentPresence
+{
+ private static final Logger logger =
+ Logger.getLogger(OperationSetPersistentPresenceSSHImpl.class);
+ /**
+ * A list of listeners registered for SubscriptionEvents.
+ */
+ private Vector subscriptionListeners = new Vector();
+
+ /**
+ * A list of listeners registered for ServerStoredGroupChangeEvents.
+ */
+ private Vector serverStoredGroupListeners = new Vector();
+
+ /**
+ * A list of listeners registered for
+ * ProviderPresenceStatusChangeEvents.
+ */
+ private Vector providerPresenceStatusListeners = new Vector();
+
+ /**
+ * A list of listeneres registered for
+ * ContactPresenceStatusChangeEvents.
+ */
+ private Vector contactPresenceStatusListeners = new Vector();
+
+ /**
+ * The root of the ssh contact list.
+ */
+ private ContactGroupSSHImpl contactListRoot = null;
+
+ /**
+ * The provider that created us.
+ */
+ private ProtocolProviderServiceSSHImpl parentProvider = null;
+
+ /**
+ * The currently active status message.
+ */
+ private String statusMessage = "Default Status Message";
+
+ /**
+ * Our default presence status.
+ */
+ private PresenceStatus presenceStatus = SSHStatusEnum.ONLINE;
+
+ /**
+ * Creates an instance of this operation set keeping a reference to the
+ * specified parent provider.
+ * @param provider the ProtocolProviderServiceSSHImpl instance that
+ * created us.
+ */
+ public OperationSetPersistentPresenceSSHImpl(
+ ProtocolProviderServiceSSHImpl provider)
+ {
+ this.parentProvider = provider;
+ contactListRoot = new ContactGroupSSHImpl("RootGroup", provider);
+
+ //add our unregistration listener
+ parentProvider.addRegistrationStateChangeListener(
+ new UnregistrationListener());
+ }
+
+ /**
+ * This function changes the status of contact as well as that of the
+ * provider
+ *
+ * @param sshContact the contact of the remote machine
+ * @param newStatus new status of the contact
+ */
+ public void changeContactPresenceStatus(
+ ContactSSH sshContact,
+ PresenceStatus newStatus)
+ {
+ PresenceStatus oldStatus = sshContact.getPresenceStatus();
+ sshContact.setPresenceStatus(newStatus);
+ fireContactPresenceStatusChangeEvent(
+ sshContact
+ , sshContact.getParentContactGroup()
+ , oldStatus);
+ fireProviderStatusChangeEvent(oldStatus);
+ }
+
+ /**
+ * SSH implementation of the corresponding ProtocolProviderService
+ * method.
+ *
+ * @param listener a dummy param.
+ */
+ public void addContactPresenceStatusListener(
+ ContactPresenceStatusListener listener)
+ {
+ synchronized(contactPresenceStatusListeners)
+ {
+ if (!contactPresenceStatusListeners.contains(listener))
+ contactPresenceStatusListeners.add(listener);
+ }
+ }
+
+ /**
+ * Notifies all registered listeners of the new event.
+ *
+ * @param source the contact that has caused the event.
+ * @param parentGroup the group that contains the source contact.
+ * @param oldValue the status that the source contact detained before
+ * changing it.
+ */
+ public void fireContactPresenceStatusChangeEvent(
+ Contact source,
+ ContactGroup parentGroup,
+ PresenceStatus oldValue)
+ {
+ ContactPresenceStatusChangeEvent evt
+ = new ContactPresenceStatusChangeEvent(source, parentProvider
+ , parentGroup, oldValue, source.getPresenceStatus());
+
+ Iterator listeners = null;
+ synchronized(contactPresenceStatusListeners)
+ {
+ listeners = new ArrayList(contactPresenceStatusListeners)
+ .iterator();
+ }
+
+
+ while(listeners.hasNext())
+ {
+ ContactPresenceStatusListener listener
+ = (ContactPresenceStatusListener)listeners.next();
+
+ listener.contactPresenceStatusChanged(evt);
+ }
+ }
+
+
+ /**
+ * Notifies all registered listeners of the new event.
+ *
+ * @param source the contact that has caused the event.
+ * @param parentGroup the group that contains the source contact.
+ * @param eventID an identifier of the event to dispatch.
+ */
+ public void fireSubscriptionEvent(
+ ContactSSH source,
+ ContactGroup parentGroup,
+ int eventID)
+ {
+ SubscriptionEvent evt = new SubscriptionEvent(source
+ , this.parentProvider
+ , parentGroup
+ , eventID);
+
+ Iterator listeners = null;
+ synchronized (subscriptionListeners)
+ {
+ listeners = new ArrayList(subscriptionListeners).iterator();
+ }
+
+ while (listeners.hasNext())
+ {
+ SubscriptionListener listener
+ = (SubscriptionListener) listeners.next();
+
+ if(eventID == SubscriptionEvent.SUBSCRIPTION_CREATED)
+ {
+ listener.subscriptionCreated(evt);
+ }
+ else if (eventID == SubscriptionEvent.SUBSCRIPTION_FAILED)
+ {
+ listener.subscriptionFailed(evt);
+ }
+ else if (eventID == SubscriptionEvent.SUBSCRIPTION_REMOVED)
+ {
+ listener.subscriptionRemoved(evt);
+ }
+ }
+ }
+
+ /**
+ * Notifies all registered listeners of the new event.
+ *
+ * @param source the contact that has been moved..
+ * @param oldParent the group where the contact was located before being
+ * moved.
+ * @param newParent the group where the contact has been moved.
+ */
+ public void fireSubscriptionMovedEvent(
+ Contact source,
+ ContactGroup oldParent,
+ ContactGroup newParent)
+ {
+ SubscriptionMovedEvent evt = new SubscriptionMovedEvent(source
+ , this.parentProvider
+ , oldParent
+ , newParent);
+
+ Iterator listeners = null;
+ synchronized (subscriptionListeners)
+ {
+ listeners = new ArrayList(subscriptionListeners).iterator();
+ }
+
+ while (listeners.hasNext())
+ {
+ SubscriptionListener listener
+ = (SubscriptionListener) listeners.next();
+
+ listener.subscriptionMoved(evt);
+ }
+ }
+
+
+ /**
+ * Notifies all registered listeners of the new event.
+ *
+ * @param source the contact that has caused the event.
+ * @param eventID an identifier of the event to dispatch.
+ */
+ public void fireServerStoredGroupEvent(
+ ContactGroupSSHImpl source,
+ int eventID)
+ {
+ ServerStoredGroupEvent evt = new ServerStoredGroupEvent(
+ source,
+ eventID,
+ (ContactGroupSSHImpl)source.getParentContactGroup(),
+ this.parentProvider,
+ this);
+
+ Iterator listeners = null;
+ synchronized (serverStoredGroupListeners)
+ {
+ listeners = new ArrayList(serverStoredGroupListeners).iterator();
+ }
+
+ while (listeners.hasNext())
+ {
+ ServerStoredGroupListener listener
+ = (ServerStoredGroupListener) listeners.next();
+
+ if(eventID == ServerStoredGroupEvent.GROUP_CREATED_EVENT)
+ {
+ listener.groupCreated(evt);
+ }
+ else if(eventID == ServerStoredGroupEvent.GROUP_RENAMED_EVENT)
+ {
+ listener.groupNameChanged(evt);
+ }
+ else if(eventID == ServerStoredGroupEvent.GROUP_REMOVED_EVENT)
+ {
+ listener.groupRemoved(evt);
+ }
+ }
+ }
+
+ /**
+ * Notifies all registered listeners of the new event.
+ *
+ * @param oldValue the presence status we were in before the change.
+ */
+ public void fireProviderStatusChangeEvent(PresenceStatus oldValue)
+ {
+ ProviderPresenceStatusChangeEvent evt
+ = new ProviderPresenceStatusChangeEvent(this.parentProvider,
+ oldValue, this.getPresenceStatus());
+
+ Iterator listeners = null;
+ synchronized (providerPresenceStatusListeners)
+ {
+ listeners = new ArrayList(providerPresenceStatusListeners)
+ .iterator();
+ }
+
+ while (listeners.hasNext())
+ {
+ ProviderPresenceStatusListener listener
+ = (ProviderPresenceStatusListener) listeners.next();
+
+ listener.providerStatusChanged(evt);
+ }
+ }
+
+ /**
+ * SSH implementation of the corresponding ProtocolProviderService
+ * method.
+ *
+ * @param listener a dummy param.
+ */
+ public void addProviderPresenceStatusListener(
+ ProviderPresenceStatusListener listener)
+ {
+ synchronized(providerPresenceStatusListeners)
+ {
+ if (!providerPresenceStatusListeners.contains(listener))
+ this.providerPresenceStatusListeners.add(listener);
+ }
+ }
+
+ /**
+ * Registers a listener that would receive events upon changes in server
+ * stored groups.
+ *
+ * @param listener a ServerStoredGroupChangeListener impl that would
+ * receive events upong group changes.
+ */
+ public void addServerStoredGroupChangeListener(
+ ServerStoredGroupListener listener)
+ {
+ synchronized(serverStoredGroupListeners)
+ {
+ if (!serverStoredGroupListeners.contains(listener))
+ serverStoredGroupListeners.add(listener);
+ }
+ }
+
+ /**
+ * SSH implementation of the corresponding ProtocolProviderService
+ * method.
+ *
+ * @param listener the SubscriptionListener to register
+ */
+ public void addSubsciptionListener(
+ SubscriptionListener listener)
+ {
+ synchronized(subscriptionListeners)
+ {
+ if (!subscriptionListeners.contains(listener))
+ this.subscriptionListeners.add(listener);
+ }
+ }
+
+ /**
+ * Creates a group with the specified name and parent in the server
+ * stored contact list.
+ *
+ * @param parent the group where the new group should be created
+ * @param groupName the name of the new group to create.
+ */
+ public void createServerStoredContactGroup(
+ ContactGroup parent,
+ String groupName)
+ {
+ ContactGroupSSHImpl newGroup
+ = new ContactGroupSSHImpl(groupName, parentProvider);
+
+ ((ContactGroupSSHImpl)parent).addSubgroup(newGroup);
+
+ this.fireServerStoredGroupEvent(
+ newGroup, ServerStoredGroupEvent.GROUP_CREATED_EVENT);
+ }
+
+ /**
+ * A SSH Provider method to use for fast filling of a contact list.
+ *
+ * @param contactGroup the group to add
+ */
+ public void addSSHGroup(ContactGroupSSHImpl contactGroup)
+ {
+ contactListRoot.addSubgroup(contactGroup);
+ }
+
+ /**
+ * A SSH Provider method to use for fast filling of a contact list.
+ * This method would add both the group and fire an event.
+ *
+ * @param parent the group where contactGroup should be added.
+ * @param contactGroup the group to add
+ */
+ public void addSSHGroupAndFireEvent(
+ ContactGroupSSHImpl parent,
+ ContactGroupSSHImpl contactGroup)
+ {
+ parent.addSubgroup(contactGroup);
+
+ this.fireServerStoredGroupEvent(
+ contactGroup, ServerStoredGroupEvent.GROUP_CREATED_EVENT);
+ }
+
+
+ /**
+ * Returns a reference to the contact with the specified ID in case we
+ * have a subscription for it and null otherwise/
+ *
+ * @param contactID a String identifier of the contact which we're
+ * seeking a reference of.
+ * @return a reference to the Contact with the specified
+ * contactID or null if we don't have a subscription for the
+ * that identifier.
+ */
+ public Contact findContactByID(String contactID)
+ {
+ return contactListRoot.findContactByID(contactID);
+ }
+
+ /**
+ * Sets the specified status message.
+ * @param statusMessage a String containing the new status message.
+ */
+ public void setStatusMessage(String statusMessage)
+ {
+ this.statusMessage = statusMessage;
+ }
+
+ /**
+ * Returns the status message that was last set through
+ * setCurrentStatusMessage.
+ *
+ * @return the last status message that we have requested and the aim
+ * server has confirmed.
+ */
+ public String getCurrentStatusMessage()
+ {
+ return statusMessage;
+ }
+
+ /**
+ * Returns the protocol specific contact instance representing the local
+ * user.
+ *
+ * @return the Contact (address, phone number, or uin) that the Provider
+ * implementation is communicating on behalf of.
+ */
+ public Contact getLocalContact()
+ {
+ return null;
+ }
+
+ /**
+ * Returns a PresenceStatus instance representing the state this provider
+ * is currently in.
+ *
+ * @return the PresenceStatus last published by this provider.
+ */
+ public PresenceStatus getPresenceStatus()
+ {
+ return presenceStatus;
+ }
+
+ /**
+ * Returns the root group of the server stored contact list.
+ *
+ * @return the root ContactGroup for the ContactList stored by this
+ * service.
+ */
+ public ContactGroup getServerStoredContactListRoot()
+ {
+ return contactListRoot;
+ }
+
+ /**
+ * Returns the set of PresenceStatus objects that a user of this service
+ * may request the provider to enter.
+ *
+ * @return Iterator a PresenceStatus array containing "enterable" status
+ * instances.
+ */
+ public Iterator getSupportedStatusSet()
+ {
+ return SSHStatusEnum.supportedStatusSet();
+ }
+
+ /**
+ * Removes the specified contact from its current parent and places it
+ * under newParent.
+ *
+ * @param contactToMove the Contact to move
+ * @param newParent the ContactGroup where Contact
+ * would be placed.
+ */
+ public void moveContactToGroup(
+ Contact contactToMove,
+ ContactGroup newParent)
+ {
+ ContactSSHImpl sshContact
+ = (ContactSSHImpl)contactToMove;
+
+ ContactGroupSSHImpl parentSSHGroup
+ = findContactParent(sshContact);
+
+ parentSSHGroup.removeContact(sshContact);
+
+ //if this is a volatile contact then we haven't really subscribed to
+ //them so we'd need to do so here
+ if(!sshContact.isPersistent())
+ {
+ //first tell everyone that the volatile contact was removed
+ fireSubscriptionEvent(sshContact
+ , parentSSHGroup
+ , SubscriptionEvent.SUBSCRIPTION_REMOVED);
+
+ try
+ {
+ //now subscribe
+ this.subscribe(newParent, contactToMove.getAddress());
+
+ //now tell everyone that we've added the contact
+ fireSubscriptionEvent(sshContact
+ , newParent
+ , SubscriptionEvent.SUBSCRIPTION_CREATED);
+ }
+ catch (Exception ex)
+ {
+ logger.error("Failed to move contact "
+ + sshContact.getAddress()
+ , ex);
+ }
+ }
+ else
+ {
+ ( (ContactGroupSSHImpl) newParent)
+ .addContact(sshContact);
+
+ fireSubscriptionMovedEvent(contactToMove
+ , parentSSHGroup
+ , newParent);
+ }
+ }
+
+ /**
+ * Requests the provider to enter into a status corresponding to the
+ * specified paramters.
+ *
+ * @param status the PresenceStatus as returned by
+ * getRequestableStatusSet
+ * @param statusMessage the message that should be set as the reason to
+ * enter that status
+ * @throws IllegalArgumentException if the status requested is not a
+ * valid PresenceStatus supported by this provider.
+ * @throws IllegalStateException if the provider is not currently
+ * registered.
+ */
+ public void publishPresenceStatus(
+ PresenceStatus status,
+ String statusMessage)
+ throws IllegalArgumentException,
+ IllegalStateException
+ {
+ PresenceStatus oldPresenceStatus = this.presenceStatus;
+ this.presenceStatus = status;
+ this.statusMessage = statusMessage;
+
+ this.fireProviderStatusChangeEvent(oldPresenceStatus);
+
+
+// //since we are not a real protocol, we set the contact presence status
+// //ourselves and make them have the same status as ours.
+// changePresenceStatusForAllContacts( getServerStoredContactListRoot()
+// , getPresenceStatus());
+//
+// //now check whether we are in someone else's contact list and modify
+// //our status there
+// List contacts = findContactsPointingToUs();
+//
+// Iterator contactsIter = contacts.iterator();
+// while (contactsIter.hasNext())
+// {
+// ContactSSHImpl contact
+// = (ContactSSHImpl) contactsIter.next();
+//
+// PresenceStatus oldStatus = contact.getPresenceStatus();
+// contact.setPresenceStatus(status);
+// contact.getParentPresenceOperationSet()
+// .fireContactPresenceStatusChangeEvent(
+// contact
+// , contact.getParentContactGroup()
+// , oldStatus);
+//
+// }
+ }
+
+
+
+ /**
+ * Get the PresenceStatus for a particular contact.
+ *
+ * @param contactIdentifier the identifier of the contact whose status
+ * we're interested in.
+ * @return PresenceStatus the PresenceStatus of the specified
+ * contact
+ * @throws IllegalArgumentException if contact is not a contact
+ * known to the underlying protocol provider
+ * @throws IllegalStateException if the underlying protocol provider is
+ * not registered/signed on a public service.
+ * @throws OperationFailedException with code NETWORK_FAILURE if
+ * retrieving the status fails due to errors experienced during
+ * network communication
+ */
+ public PresenceStatus queryContactStatus(String contactIdentifier)
+ throws IllegalArgumentException,
+ IllegalStateException,
+ OperationFailedException
+ {
+ return findContactByID(contactIdentifier).getPresenceStatus();
+ }
+
+ /**
+ * Sets the presence status of contact to newStatus.
+ *
+ * @param contact the ContactSSHImpl whose status we'd like
+ * to set.
+ * @param newStatus the new status we'd like to set to contact.
+ */
+ private void changePresenceStatusForContact(
+ ContactSSH contact,
+ PresenceStatus newStatus)
+ {
+ PresenceStatus oldStatus = contact.getPresenceStatus();
+ contact.setPresenceStatus(newStatus);
+
+ fireContactPresenceStatusChangeEvent(
+ contact, findContactParent(contact), oldStatus);
+ }
+
+ /**
+ * Sets the presence status of all contacts in our contact list
+ * (except those that correspond to another provider registered with SC)
+ * to newStatus.
+ *
+ * @param newStatus the new status we'd like to set to contact.
+ * @param parent the group in which we'd have to update the status of all
+ * direct and indirect child contacts.
+ */
+ private void changePresenceStatusForAllContacts(
+ ContactGroup parent,
+ PresenceStatus newStatus)
+ {
+ //first set the status for contacts in this group
+ Iterator childContacts = parent.contacts();
+
+ while(childContacts.hasNext())
+ {
+ ContactSSHImpl contact
+ = (ContactSSHImpl)childContacts.next();
+
+ if(findProviderForSSHUserID(contact.getAddress()) != null)
+ {
+ //this is a contact corresponding to another SIP Communicator
+ //provider so we won't change it's status here.
+ continue;
+ }
+ PresenceStatus oldStatus = contact.getPresenceStatus();
+ contact.setPresenceStatus(newStatus);
+
+ fireContactPresenceStatusChangeEvent(
+ contact, parent, oldStatus);
+ }
+
+ //now call this method recursively for all subgroups
+ Iterator subgroups = parent.subgroups();
+
+ while(subgroups.hasNext())
+ {
+ ContactGroup subgroup = (ContactGroup)subgroups.next();
+ changePresenceStatusForAllContacts(subgroup, newStatus);
+ }
+ }
+
+
+ /**
+ * Removes the specified listener so that it won't receive any further
+ * updates on contact presence status changes
+ *
+ * @param listener the listener to remove.
+ */
+ public void removeContactPresenceStatusListener(
+ ContactPresenceStatusListener listener)
+ {
+ synchronized(contactPresenceStatusListeners)
+ {
+ contactPresenceStatusListeners.remove(listener);
+ }
+ }
+
+ /**
+ * Unregisters the specified listener so that it does not receive further
+ * events upon changes in local presence status.
+ *
+ * @param listener ProviderPresenceStatusListener
+ */
+ public void removeProviderPresenceStatusListener(
+ ProviderPresenceStatusListener listener)
+ {
+ synchronized(providerPresenceStatusListeners)
+ {
+ this.providerPresenceStatusListeners.remove(listener);
+ }
+ }
+
+ /**
+ * Returns the group that is parent of the specified sshGroup or null
+ * if no parent was found.
+ * @param sshGroup the group whose parent we're looking for.
+ * @return the ContactGroupSSHImpl instance that sshGroup
+ * belongs to or null if no parent was found.
+ */
+ public ContactGroupSSHImpl findGroupParent(
+ ContactGroupSSHImpl sshGroup)
+ {
+ return contactListRoot.findGroupParent(sshGroup);
+ }
+
+ /**
+ * Returns the group that is parent of the specified sshContact or
+ * null if no parent was found.
+ * @param sshContact the contact whose parent we're looking for.
+ * @return the ContactGroupSSHImpl instance that sshContact
+ * belongs to or null if no parent was found.
+ */
+ public ContactGroupSSHImpl findContactParent(
+ ContactSSH sshContact)
+ {
+ return (ContactGroupSSHImpl)sshContact
+ .getParentContactGroup();
+ }
+
+
+ /**
+ * Removes the specified group from the server stored contact list.
+ *
+ * @param group the group to remove.
+ *
+ * @throws IllegalArgumentException if group was not found in this
+ * protocol's contact list.
+ */
+ public void removeServerStoredContactGroup(ContactGroup group)
+ throws IllegalArgumentException
+ {
+ ContactGroupSSHImpl sshGroup
+ = (ContactGroupSSHImpl)group;
+
+ ContactGroupSSHImpl parent = findGroupParent(sshGroup);
+
+ if(parent == null)
+ {
+ throw new IllegalArgumentException(
+ "group " + group
+ + " does not seem to belong to this protocol's contact "
+ + "list.");
+ }
+
+ parent.removeSubGroup(sshGroup);
+
+ this.fireServerStoredGroupEvent(
+ sshGroup, ServerStoredGroupEvent.GROUP_REMOVED_EVENT);
+ }
+
+
+ /**
+ * Removes the specified group change listener so that it won't receive
+ * any further events.
+ *
+ * @param listener the ServerStoredGroupChangeListener to remove
+ */
+ public void removeServerStoredGroupChangeListener(
+ ServerStoredGroupListener listener)
+ {
+ synchronized(serverStoredGroupListeners)
+ {
+ serverStoredGroupListeners.remove(listener);
+ }
+ }
+
+ /**
+ * Removes the specified subscription listener.
+ *
+ * @param listener the listener to remove.
+ */
+ public void removeSubscriptionListener(
+ SubscriptionListener listener)
+ {
+ synchronized(subscriptionListeners)
+ {
+ this.subscriptionListeners.remove(listener);
+ }
+ }
+
+ /**
+ * Renames the specified group from the server stored contact list.
+ *
+ * @param group the group to rename.
+ * @param newName the new name of the group.
+ */
+ public void renameServerStoredContactGroup(
+ ContactGroup group,
+ String newName)
+ {
+ ((ContactGroupSSHImpl)group).setGroupName(newName);
+
+ this.fireServerStoredGroupEvent(
+ (ContactGroupSSHImpl)group, ServerStoredGroupEvent
+ .GROUP_RENAMED_EVENT);
+ }
+
+
+ /**
+ * Persistently adds a subscription for the presence status of the
+ * contact corresponding to the specified contactIdentifier and indicates
+ * that it should be added to the specified group of the server stored
+ * contact list.
+ *
+ * @param parent the parent group of the server stored contact list
+ * where the contact should be added. WizardPage.getIdentifier to return
+ * this page identifier.
+ *
+ * @return the Identifier of the first page in this wizard.
+ */
+ public Object getIdentifier()
+ {
+ return FIRST_PAGE_IDENTIFIER;
+ }
+
+ /**
+ * Implements the WizardPage.getNextPageIdentifier to return
+ * the next page identifier - the summary page.
+ *
+ * @return the identifier of the page following this one.
+ */
+ public Object getNextPageIdentifier()
+ {
+ return nextPageIdentifier;
+ }
+
+ /**
+ * Implements the WizardPage.getBackPageIdentifier to return
+ * the next back identifier - the default page.
+ *
+ * @return the identifier of the default wizard page.
+ */
+ public Object getBackPageIdentifier()
+ {
+ return WizardPage.DEFAULT_PAGE_IDENTIFIER;
+ }
+
+ /**
+ * Implements the WizardPage.getWizardForm to return
+ * this panel.
+ *
+ * @return the component to be displayed in this wizard page.
+ */
+ public Object getWizardForm()
+ {
+ return this;
+ }
+
+ /**
+ * Before this page is displayed enables or disables the "Next" wizard
+ * button according to whether the UserID field is empty.
+ */
+ public void pageShowing()
+ {
+ this.setNextButtonAccordingToUserID();
+ }
+
+ /**
+ * Saves the user input when the "Next" wizard buttons is clicked.
+ */
+ public void pageNext()
+ {
+ String userID = accountIDField.getText();
+
+ if (isExistingAccount(userID))
+ {
+ nextPageIdentifier = FIRST_PAGE_IDENTIFIER;
+ accountPanel.add(existingAccountLabel, BorderLayout.NORTH);
+ this.revalidate();
+ }
+ else
+ {
+ nextPageIdentifier = SUMMARY_PAGE_IDENTIFIER;
+ accountPanel.remove(existingAccountLabel);
+
+ registration.setAccountID(accountIDField.getText());
+ registration.setIdentityFile(identityFileField.getText());
+ registration.setKnownHostsFile(knownHostsFileField.getText());
+ }
+ }
+
+ /**
+ * Enables or disables the "Next" wizard button according to whether the
+ * User ID field is empty.
+ */
+ private void setNextButtonAccordingToUserID()
+ {
+ if (accountIDField.getText() == null || accountIDField.getText()
+ .equals(""))
+ {
+ wizardContainer.setNextFinishButtonEnabled(false);
+ }
+ else
+ {
+ wizardContainer.setNextFinishButtonEnabled(true);
+ }
+ }
+
+ /**
+ * Handles the DocumentEvent triggered when user types in the
+ * User ID field. Enables or disables the "Next" wizard button according to
+ * whether the User ID field is empty.
+ *
+ * @param event the event containing the update.
+ */
+ public void insertUpdate(DocumentEvent event)
+ {
+ this.setNextButtonAccordingToUserID();
+ }
+
+ /**
+ * Handles the DocumentEvent triggered when user deletes letters
+ * from the UserID field. Enables or disables the "Next" wizard button
+ * according to whether the UserID field is empty.
+ *
+ * @param event the event containing the update.
+ */
+ public void removeUpdate(DocumentEvent event)
+ {
+ this.setNextButtonAccordingToUserID();
+ }
+
+ public void changedUpdate(DocumentEvent event)
+ {
+ }
+
+ public void pageHiding()
+ {
+ }
+
+ public void pageShown()
+ {
+ }
+
+ public void pageBack()
+ {
+ }
+
+ /**
+ * Verifies whether there is already an account installed with the same
+ * details as the one that the user has just entered.
+ *
+ * @param accountID the name of the user that the account is registered for
+ * @return true if there is already an account for this accountID and false
+ * otherwise.
+ */
+ private boolean isExistingAccount(String userID)
+ {
+ ProtocolProviderFactory factory
+ = SSHAccRegWizzActivator.getSSHProtocolProviderFactory();
+
+ ArrayList registeredAccounts = factory.getRegisteredAccounts();
+
+ for (int i = 0; i < registeredAccounts.size(); i++)
+ {
+ AccountID accountID = (AccountID) registeredAccounts.get(i);
+
+ if (userID.equalsIgnoreCase(accountID.getUserID()))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/net/java/sip/communicator/plugin/sshaccregwizz/Resources.java b/src/net/java/sip/communicator/plugin/sshaccregwizz/Resources.java
new file mode 100644
index 000000000..7948cb30e
--- /dev/null
+++ b/src/net/java/sip/communicator/plugin/sshaccregwizz/Resources.java
@@ -0,0 +1,103 @@
+/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ *
+ * Resources.java
+ *
+ * Created on 22 May, 2007, 8:53 AM
+ *
+ * SSH Suport in SIP Communicator - GSoC' 07 Project
+ *
+ */
+
+package net.java.sip.communicator.plugin.sshaccregwizz;
+
+import java.io.*;
+import java.util.*;
+
+import net.java.sip.communicator.util.*;
+
+/**
+ * The Messages class manages the access to the internationalization
+ * properties files.
+ *
+ * @author Shobhit Jindal
+ */
+public class Resources
+{
+
+ private static Logger log = Logger.getLogger(Resources.class);
+
+ private static final String BUNDLE_NAME
+ = "net.java.sip.communicator.plugin.sshaccregwizz.resources";
+
+ private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle
+ .getBundle(BUNDLE_NAME);
+
+ public static ImageID SSH_LOGO = new ImageID("protocolIcon");
+
+ public static ImageID PAGE_IMAGE = new ImageID("pageImage");
+
+ /**
+ * Returns an internationalized string corresponding to the given key.
+ * @param key The key of the string.
+ * @return An internationalized string corresponding to the given key.
+ */
+ public static String getString(String key)
+ {
+ try
+ {
+ return RESOURCE_BUNDLE.getString(key);
+
+ }
+ catch (MissingResourceException exc)
+ {
+ return '!' + key + '!';
+ }
+ }
+
+ /**
+ * Loads an image from a given image identifier.
+ * @param imageID The identifier of the image.
+ * @return The image for the given identifier.
+ */
+ public static byte[] getImage(ImageID imageID)
+ {
+ byte[] image = new byte[100000];
+
+ String path = Resources.getString(imageID.getId());
+ try
+ {
+ Resources.class.getClassLoader()
+ .getResourceAsStream(path).read(image);
+
+ }
+ catch (IOException exc)
+ {
+ log.error("Failed to load image:" + path, exc);
+ }
+
+ return image;
+ }
+
+ /**
+ * Represents the Image Identifier.
+ */
+ public static class ImageID
+ {
+ private String id;
+
+ private ImageID(String id)
+ {
+ this.id = id;
+ }
+
+ public String getId()
+ {
+ return id;
+ }
+ }
+
+}
diff --git a/src/net/java/sip/communicator/plugin/sshaccregwizz/SSHAccRegWizzActivator.java b/src/net/java/sip/communicator/plugin/sshaccregwizz/SSHAccRegWizzActivator.java
new file mode 100644
index 000000000..4061fd662
--- /dev/null
+++ b/src/net/java/sip/communicator/plugin/sshaccregwizz/SSHAccRegWizzActivator.java
@@ -0,0 +1,120 @@
+/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ *
+ * SSHAccRegWizzActivator.java
+ *
+ * Created on 22 May, 2007, 8:48 AM
+ *
+ * SSH Suport in SIP Communicator - GSoC' 07 Project
+ */
+
+package net.java.sip.communicator.plugin.sshaccregwizz;
+
+import org.osgi.framework.*;
+
+import net.java.sip.communicator.service.configuration.*;
+import net.java.sip.communicator.service.gui.*;
+import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.util.*;
+
+/**
+ * Registers the SSHAccountRegistrationWizard in the UI Service.
+ *
+ * @author Shobhit Jindal
+ */
+public class SSHAccRegWizzActivator
+ implements BundleActivator
+{
+ private static Logger logger = Logger.getLogger(
+ SSHAccRegWizzActivator.class.getName());
+
+ /**
+ * A currently valid bundle context.
+ */
+ public static BundleContext bundleContext;
+
+ /**
+ * A currently valid reference to the configuration service.
+ */
+ private static ConfigurationService configService;
+
+ private static AccountRegistrationWizardContainer wizardContainer;
+
+ private static SSHAccountRegistrationWizard sshWizard;
+
+ /**
+ * Starts this bundle.
+ * @param bc the currently valid BundleContext.
+ */
+ public void start(BundleContext bc)
+ {
+ logger.info("Loading ssh account wizard.");
+
+ bundleContext = bc;
+
+ ServiceReference uiServiceRef = bundleContext
+ .getServiceReference(UIService.class.getName());
+
+ UIService uiService = (UIService) bundleContext
+ .getService(uiServiceRef);
+
+ wizardContainer = uiService.getAccountRegWizardContainer();
+
+ sshWizard
+ = new SSHAccountRegistrationWizard(wizardContainer);
+
+ wizardContainer.addAccountRegistrationWizard(sshWizard);
+
+ logger.info("SSH account registration wizard [STARTED].");
+ }
+
+ /**
+ * Called when this bundle is stopped so the Framework can perform the
+ * bundle-specific activities necessary to stop the bundle.
+ *
+ * @param bundleContext The execution context of the bundle being stopped.
+ */
+ public void stop(BundleContext bundleContext) throws Exception
+ {
+ wizardContainer.removeAccountRegistrationWizard(sshWizard);
+ }
+
+ /**
+ * Returns the ProtocolProviderFactory for the SSH protocol.
+ * @return the ProtocolProviderFactory for the SSH protocol
+ */
+ public static ProtocolProviderFactory getSSHProtocolProviderFactory()
+ {
+
+ ServiceReference[] serRefs = null;
+
+ String osgiFilter = "("
+ + ProtocolProviderFactory.PROTOCOL
+ + "=" + "SSH" + ")";
+
+ try
+ {
+ serRefs = bundleContext.getServiceReferences(
+ ProtocolProviderFactory.class.getName(), osgiFilter);
+ }
+ catch (InvalidSyntaxException ex)
+ {
+ logger.error(ex);
+ }
+
+ return (ProtocolProviderFactory) bundleContext.getService(serRefs[0]);
+ }
+
+ /**
+ * Returns the bundleContext that we received when we were started.
+ *
+ * @return a currently valid instance of a bundleContext.
+ */
+ public BundleContext getBundleContext()
+ {
+ return bundleContext;
+ }
+}
diff --git a/src/net/java/sip/communicator/plugin/sshaccregwizz/SSHAccountRegistration.java b/src/net/java/sip/communicator/plugin/sshaccregwizz/SSHAccountRegistration.java
new file mode 100644
index 000000000..00fbee54d
--- /dev/null
+++ b/src/net/java/sip/communicator/plugin/sshaccregwizz/SSHAccountRegistration.java
@@ -0,0 +1,88 @@
+/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ *
+ * SSHAccountRegistration.java
+ *
+ * Created on 22 May, 2007, 8:49 AM
+ *
+ * SSH Suport in SIP Communicator - GSoC' 07 Project
+ *
+ */
+
+package net.java.sip.communicator.plugin.sshaccregwizz;
+
+/**
+ * The SSHAccountRegistration is used to store all user input data
+ * through the SSHAccountRegistrationWizard.
+ *
+ * @author Shobhit Jindal
+ */
+public class SSHAccountRegistration
+{
+ private String accountID;
+ private String knownHostsFile;
+ private String identityFile;
+
+ /**
+ * Returns the Account ID of the ssh registration account.
+ * @return accountID
+ */
+ public String getAccountID()
+ {
+ return accountID;
+ }
+
+ /**
+ * Sets the Account ID of the ssh registration account.
+ *
+ * @param accountID the accountID of the ssh registration account.
+ */
+ public void setAccountID(String accountID)
+ {
+ this.accountID = accountID;
+ }
+
+ /**
+ * Returns the Known Hosts of the ssh registration account.
+ *
+ * @return knownHostsFile
+ */
+ public String getKnownHostsFile()
+ {
+ return knownHostsFile;
+ }
+
+ /**
+ * Sets the Known Hosts of the ssh registration account.
+ *
+ * @param knownHostsFile
+ */
+ public void setKnownHostsFile(String knownHostsFile)
+ {
+ this.knownHostsFile = knownHostsFile;
+ }
+
+ /**
+ * Returns the Identity File of the ssh registration account.
+ *
+ * @return identityFile
+ */
+ public String getIdentityFile()
+ {
+ return identityFile;
+ }
+
+ /**
+ * Sets the Machine Port of the ssh registration account.
+ *
+ * @param machinePort
+ */
+ public void setIdentityFile(String machinePort)
+ {
+ this.identityFile = machinePort;
+ }
+}
+
diff --git a/src/net/java/sip/communicator/plugin/sshaccregwizz/SSHAccountRegistrationWizard.java b/src/net/java/sip/communicator/plugin/sshaccregwizz/SSHAccountRegistrationWizard.java
new file mode 100644
index 000000000..721ca81e2
--- /dev/null
+++ b/src/net/java/sip/communicator/plugin/sshaccregwizz/SSHAccountRegistrationWizard.java
@@ -0,0 +1,220 @@
+/*
+ * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
+ *
+ * Distributable under LGPL license.
+ * See terms of license at gnu.org.
+ *
+ * SSHAccountRegistrationWizard.java
+ *
+ * Created on 22 May, 2007, 8:51 AM
+ *
+ * SSH Suport in SIP Communicator - GSoC' 07 Project
+ *
+ */
+
+package net.java.sip.communicator.plugin.sshaccregwizz;
+
+import java.util.*;
+
+import org.osgi.framework.*;
+import net.java.sip.communicator.impl.gui.customcontrols.*;
+import net.java.sip.communicator.service.gui.*;
+import net.java.sip.communicator.service.protocol.*;
+import net.java.sip.communicator.impl.protocol.ssh.*;
+
+/**
+ * The SSHAccountRegistrationWizard is an implementation of the
+ * AccountRegistrationWizard for the SSH protocol. It allows
+ * the user to create and configure a new SSH account.
+ *
+ * @author Shobhit Jindal
+ */
+public class SSHAccountRegistrationWizard
+ implements AccountRegistrationWizard
+{
+
+ /**
+ * The first page of the ssh account registration wizard.
+ */
+ private FirstWizardPage firstWizardPage;
+
+ /**
+ * The object that we use to store details on an account that we will be
+ * creating.
+ */
+ private SSHAccountRegistration registration
+ = new SSHAccountRegistration();
+
+ private WizardContainer wizardContainer;
+
+ private ProtocolProviderService protocolProvider;
+
+ private String propertiesPackage
+ = "net.java.sip.communicator.plugin.sshaccregwizz";
+
+ private boolean isModification;
+
+ /**
+ * Creates an instance of SSHAccountRegistrationWizard.
+ * @param wizardContainer the wizard container, where this wizard
+ * is added
+ */
+ public SSHAccountRegistrationWizard(WizardContainer wizardContainer)
+ {
+ this.wizardContainer = wizardContainer;
+ }
+
+ /**
+ * Implements the AccountRegistrationWizard.getIcon method.
+ * Returns the icon to be used for this wizard.
+ * @return byte[]
+ */
+ public byte[] getIcon()
+ {
+ return Resources.getImage(Resources.SSH_LOGO);
+ }
+
+ /**
+ * Implements the AccountRegistrationWizard.getPageImage
+ * method.
+ * Returns the image used to decorate the wizard page
+ *
+ * @return byte[] the image used to decorate the wizard page
+ */
+ public byte[] getPageImage()
+ {
+ return Resources.getImage(Resources.PAGE_IMAGE);
+ }
+
+ /**
+ * Implements the AccountRegistrationWizard.getProtocolName
+ * method. Returns the protocol name for this wizard.
+ * @return String
+ */
+ public String getProtocolName()
+ {
+ return Resources.getString("protocolName");
+ }
+
+ /**
+ * Implements the AccountRegistrationWizard.getProtocolDescription
+ * method. Returns the description of the protocol for this wizard.
+ * @return String
+ */
+ public String getProtocolDescription()
+ {
+ return Resources.getString("protocolDescription");
+ }
+
+ /**
+ * Returns the set of pages contained in this wizard.
+ * @return Iterator
+ */
+ public Iterator getPages()
+ {
+ ArrayList pages = new ArrayList();
+ firstWizardPage = new FirstWizardPage(registration, wizardContainer);
+
+ pages.add(firstWizardPage);
+
+ return pages.iterator();
+ }
+
+ /**
+ * Returns the set of data that user has entered through this wizard.
+ * @return Iterator
+ */
+ public Iterator getSummary()
+ {
+ Hashtable summaryTable = new Hashtable();
+
+ /*
+ * Hashtable arranges the entries alphabetically so the order of appearance is
+ * - Computer Name / IP
+ * - Port
+ * - User ID
+ */
+
+ summaryTable.put("Account ID", registration.getAccountID());
+ summaryTable.put("Known Hosts", registration.getKnownHostsFile());
+ summaryTable.put("Identity", registration.getIdentityFile());
+
+ return summaryTable.entrySet().iterator();
+ }
+
+ /**
+ * Installs the account created through this wizard.
+ * @return ProtocolProviderService
+ */
+ public ProtocolProviderService finish()
+ {
+ firstWizardPage = null;
+ ProtocolProviderFactory factory
+ = SSHAccRegWizzActivator.getSSHProtocolProviderFactory();
+
+ return this.installAccount(factory,
+ registration.getAccountID());
+ }
+
+ /**
+ * Creates an account for the given Account ID, Identity File and Known
+ * Hosts File
+ *
+ * @param providerFactory the ProtocolProviderFactory which will create
+ * the account
+ * @param user the user identifier
+ * @return the ProtocolProviderService for the new account.
+ */
+ public ProtocolProviderService installAccount(
+ ProtocolProviderFactory providerFactory,
+ String user)
+ {
+
+ Hashtable accountProperties = new Hashtable();
+
+ accountProperties.put(ProtocolProviderFactorySSHImpl.IDENTITY_FILE,
+ registration.getIdentityFile());
+
+ accountProperties.put(ProtocolProviderFactorySSH.KNOWN_HOSTS_FILE,
+ String.valueOf(registration.getKnownHostsFile()));
+
+ try
+ {
+ AccountID accountID = providerFactory.installAccount(
+ user, accountProperties);
+
+ ServiceReference serRef = providerFactory
+ .getProviderForAccount(accountID);
+
+ protocolProvider = (ProtocolProviderService)
+ SSHAccRegWizzActivator.bundleContext
+ .getService(serRef);
+ }
+ catch (IllegalArgumentException exc)
+ {
+ new ErrorDialog(null, exc.getMessage(), exc).showDialog();
+ }
+ catch (IllegalStateException exc)
+ {
+ new ErrorDialog(null, exc.getMessage(), exc).showDialog();
+ }
+
+ return protocolProvider;
+ }
+
+ /**
+ * Fills the UserID and Password fields in this panel with the data comming
+ * from the given protocolProvider.
+ * @param protocolProvider The ProtocolProviderService to load the
+ * data from.
+ */
+ public void loadAccount(ProtocolProviderService protocolProvider)
+ {
+
+ this.protocolProvider = protocolProvider;
+
+ this.firstWizardPage.loadAccount(protocolProvider);
+
+ isModification = true;
+ }
+}
diff --git a/src/net/java/sip/communicator/plugin/sshaccregwizz/resources.properties b/src/net/java/sip/communicator/plugin/sshaccregwizz/resources.properties
new file mode 100644
index 000000000..d7eef240a
--- /dev/null
+++ b/src/net/java/sip/communicator/plugin/sshaccregwizz/resources.properties
@@ -0,0 +1,10 @@
+protocolName=SSH
+protocolDescription=A Protocol to connect to remote machines over SSH.
+accountID=Account ID:
+identityFile=Identitity File:
+knownHosts=Known Hosts:
+accountDetails=SSH Account Details
+existingAccount=* The account you entered is already installed.
+
+protocolIcon=resources/images/ssh/ssh-online.png
+pageImage=resources/images/ssh/ssh64x64.png
diff --git a/src/net/java/sip/communicator/plugin/sshaccregwizz/sshaccregwizz.manifest.mf b/src/net/java/sip/communicator/plugin/sshaccregwizz/sshaccregwizz.manifest.mf
new file mode 100644
index 000000000..b75dc1e92
--- /dev/null
+++ b/src/net/java/sip/communicator/plugin/sshaccregwizz/sshaccregwizz.manifest.mf
@@ -0,0 +1,30 @@
+Bundle-Activator: net.java.sip.communicator.plugin.sshaccregwizz.SSHAccRegWizzActivator
+Bundle-Name: SSH account registration wizard
+Bundle-Description: SSH account registration wizard.
+Bundle-Vendor: sip-communicator.org
+Bundle-Version: 0.0.1
+Import-Package: org.osgi.framework,
+ net.java.sip.communicator.util,
+ net.java.sip.communicator.service.configuration,
+ net.java.sip.communicator.service.configuration.event,
+ net.java.sip.communicator.service.protocol,
+ net.java.sip.communicator.service.protocol.event,
+ net.java.sip.communicator.service.contactlist,
+ net.java.sip.communicator.service.contactlist.event,
+ net.java.sip.communicator.service.gui,
+ net.java.sip.communicator.service.gui.event,
+ net.java.sip.communicator.service.browserlauncher,
+ javax.swing,
+ javax.swing.event,
+ javax.swing.table,
+ javax.swing.text,
+ javax.swing.text.html,
+ javax.accessibility,
+ javax.swing.plaf,
+ javax.swing.plaf.metal,
+ javax.swing.plaf.basic,
+ javax.imageio,
+ javax.swing.filechooser,
+ javax.swing.tree,
+ javax.swing.undo,
+ javax.swing.border
diff --git a/src/net/java/sip/communicator/service/protocol/OperationSetFileTransfer.java b/src/net/java/sip/communicator/service/protocol/OperationSetFileTransfer.java
index f13a2ee7c..12517eca7 100644
--- a/src/net/java/sip/communicator/service/protocol/OperationSetFileTransfer.java
+++ b/src/net/java/sip/communicator/service/protocol/OperationSetFileTransfer.java
@@ -19,7 +19,11 @@
public interface OperationSetFileTransfer
extends OperationSet
{
- public void sendFile();
+ public void sendFile(
+ Contact toContact,
+ Contact fromContact,
+ String remotePath,
+ String localPath);
public void addFileListener(FileListener listener);
}