diff --git a/build.xml b/build.xml index 12d979156..9e73af17e 100644 --- a/build.xml +++ b/build.xml @@ -875,7 +875,8 @@ bundle-rss, bundle-rss-slick,bundle-plugin-rssaccregwizz, bundle-zeroconf,bundle-plugin-zeroconfaccregwizz, bundle-irc,bundle-plugin-ircaccregwizz, - bundle-pluginmanager,bundle-notification"/> + bundle-pluginmanager,bundle-notification, + bundle-ssh,bundle-plugin-sshaccregwizz"/> @@ -1273,6 +1274,31 @@ javax.swing.event, javax.swing.border"/> + + + + + + + + + + + + + + + + + + + diff --git a/ide/eclipse/.classpath b/ide/eclipse/.classpath index 6c25bcd25..7d96d77ed 100644 --- a/ide/eclipse/.classpath +++ b/ide/eclipse/.classpath @@ -49,5 +49,6 @@ + - \ No newline at end of file + diff --git a/lib/installer-exclude/jsch-0.1.33.jar b/lib/installer-exclude/jsch-0.1.33.jar new file mode 100644 index 000000000..01fcb29b0 Binary files /dev/null and b/lib/installer-exclude/jsch-0.1.33.jar differ diff --git a/resources/images/ssh/ssh-connected.png b/resources/images/ssh/ssh-connected.png new file mode 100644 index 000000000..9135523ba Binary files /dev/null and b/resources/images/ssh/ssh-connected.png differ diff --git a/resources/images/ssh/ssh-connecting.png b/resources/images/ssh/ssh-connecting.png new file mode 100644 index 000000000..47f61aeb5 Binary files /dev/null and b/resources/images/ssh/ssh-connecting.png differ diff --git a/resources/images/ssh/ssh-filetransfer.png b/resources/images/ssh/ssh-filetransfer.png new file mode 100644 index 000000000..d92d6aa3b Binary files /dev/null and b/resources/images/ssh/ssh-filetransfer.png differ diff --git a/resources/images/ssh/ssh-na.png b/resources/images/ssh/ssh-na.png new file mode 100644 index 000000000..d0d6265c7 Binary files /dev/null and b/resources/images/ssh/ssh-na.png differ diff --git a/resources/images/ssh/ssh-offline.png b/resources/images/ssh/ssh-offline.png new file mode 100644 index 000000000..51c83b2cf Binary files /dev/null and b/resources/images/ssh/ssh-offline.png differ diff --git a/resources/images/ssh/ssh-online.png b/resources/images/ssh/ssh-online.png new file mode 100644 index 000000000..14683db95 Binary files /dev/null and b/resources/images/ssh/ssh-online.png differ diff --git a/resources/images/ssh/ssh64x64.png b/resources/images/ssh/ssh64x64.png new file mode 100644 index 000000000..2a62b3926 Binary files /dev/null and b/resources/images/ssh/ssh64x64.png differ diff --git a/src/net/java/sip/communicator/impl/protocol/ssh/ContactGroupSSHImpl.java b/src/net/java/sip/communicator/impl/protocol/ssh/ContactGroupSSHImpl.java new file mode 100644 index 000000000..545cf559f --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/ssh/ContactGroupSSHImpl.java @@ -0,0 +1,592 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + * + * ContactGroupSSHImpl.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.*; + +/** + * A simple, straightforward implementation of a ssh ContactGroup. Since + * the SSH protocol is not a real one, we simply store all group details + * in class fields. You should know that when implementing a real protocol, + * the contact group implementation would rather encapsulate group objects from + * the protocol stack and group property values should be returned by consulting + * the encapsulated object. + * + * @author Shobhit Jindal + */ +public class ContactGroupSSHImpl + implements ContactGroup +{ + private static final Logger logger + = Logger.getLogger(ContactGroupSSHImpl.class); + + /** + * The name of this SSH contact group. + */ + private String groupName = null; + + /** + * The list of this group's members. + */ + private Vector contacts = new Vector(); + + /** + * The list of sub groups belonging to this group. + */ + private Vector subGroups = new Vector(); + + /** + * The group that this group belongs to (or null if this is the root group). + */ + private ContactGroupSSHImpl parentGroup = null; + + /** + * Determines whether this group is really in the contact list or whether + * it is here only temporarily and will be gone next time we restart. + */ + private boolean isPersistent = true; + + /** + * The protocol provider that created us. + */ + private ProtocolProviderServiceSSHImpl parentProvider = null; + + /** + * Determines whether this group has been resolved on the server. + * Unresolved groups are groups that were available on previous runs and + * that the meta contact list has stored. During all next runs, when + * bootstrapping, the meta contact list would create these groups as + * unresolved. Once a protocol provider implementation confirms that the + * groups are still on the server, it would issue an event indicating that + * the groups are now resolved. + */ + private boolean isResolved = true; + + /** + * An id uniquely identifying the group. For many protocols this could be + * the group name itself. + */ + private String uid = null; + private static final String UID_SUFFIX = ".uid"; + + /** + * Creates a ContactGroupSSHImpl with the specified name. + * + * @param groupName the name of the group. + * @param parentProvider the protocol provider that created this group. + */ + public ContactGroupSSHImpl( + String groupName, + ProtocolProviderServiceSSHImpl parentProvider) + { + this.groupName = groupName; + this.uid = groupName + UID_SUFFIX; + this.parentProvider = parentProvider; + } + + /** + * Determines whether the group may contain subgroups or not. + * + * @return always true in this implementation. + */ + public boolean canContainSubgroups() + { + return true; + } + + /** + * Returns the protocol provider that this group belongs to. + * @return a regerence to the ProtocolProviderService instance that this + * ContactGroup belongs to. + */ + public ProtocolProviderService getProtocolProvider() + { + return parentProvider; + } + + /** + * Returns an Iterator over all contacts, member of this + * ContactGroup. + * + * @return a java.util.Iterator over all contacts inside this + * ContactGroup + */ + public Iterator contacts() + { + return contacts.iterator(); + } + + /** + * Adds the specified contact to this group. + * @param contactToAdd the ContactSSHImpl to add to this group. + */ + public void addContact(ContactSSH contactToAdd) + { + this.contacts.add(contactToAdd); + contactToAdd.setParentGroup(this); + } + + /** + * Returns the number of Contact members of this + * ContactGroup + * + * @return an int indicating the number of Contacts, members of + * this ContactGroup. + */ + public int countContacts() + { + return contacts.size(); + } + + /** + * Returns the number of subgroups contained by this + * ContactGroup. + * + * @return the number of subGroups currently added to this group. + */ + public int countSubgroups() + { + return subGroups.size(); + } + + /** + * Adds the specified contact group to the contained by this group. + * @param subgroup the ContactGroupSSHImpl to add as a subgroup to this group. + */ + public void addSubgroup(ContactGroupSSHImpl subgroup) + { + this.subGroups.add(subgroup); + subgroup.setParentGroup(this); + } + + /** + * Sets the group that is the new parent of this group + * @param parent ContactGroupSSHImpl + */ + void setParentGroup(ContactGroupSSHImpl parent) + { + this.parentGroup = parent; + } + + /** + * Returns the contact group that currently contains this group or null if + * this is the root contact group. + * @return the contact group that currently contains this group or null if + * this is the root contact group. + */ + public ContactGroup getParentContactGroup() + { + return this.parentGroup; + } + + /** + * Removes the specified contact group from the this group's subgroups. + * @param subgroup the ContactGroupSSHImpl subgroup to remove. + */ + public void removeSubGroup(ContactGroupSSHImpl subgroup) + { + this.subGroups.remove(subgroup); + subgroup.setParentGroup(null); + } + + + /** + * Returns the Contact with the specified index. + * + * @param index the index of the Contact to return. + * @return the Contact with the specified index. + */ + public Contact getContact(int index) + { + return (ContactSSHImpl) contacts.get(index); + } + + /** + * 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) + { + if ( subGroups.contains(sshGroup) ) + return this; + + Iterator subGroupsIter = subgroups(); + while (subGroupsIter.hasNext()) + { + ContactGroupSSHImpl subgroup + = (ContactGroupSSHImpl) subGroupsIter.next(); + + ContactGroupSSHImpl parent + = subgroup.findGroupParent(sshGroup); + + if(parent != null) + return parent; + } + return null; + } + + /** + * 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( + ContactSSHImpl sshContact) + { + if ( contacts.contains(sshContact) ) + return this; + + Iterator subGroupsIter = subgroups(); + while (subGroupsIter.hasNext()) + { + ContactGroupSSHImpl subgroup + = (ContactGroupSSHImpl) subGroupsIter.next(); + + ContactGroupSSHImpl parent + = subgroup.findContactParent(sshContact); + + if(parent != null) + return parent; + } + return null; + } + + + + /** + * Returns the Contact with the specified address or identifier. + * + * @param id the addres or identifier of the Contact we are + * looking for. + * @return the Contact with the specified id or address. + */ + public Contact getContact(String id) + { + Iterator contactsIter = contacts(); + while (contactsIter.hasNext()) + { + ContactSSHImpl contact = (ContactSSHImpl) contactsIter.next(); + if (contact.getAddress().equals(id)) + return contact; + + } + return null; + } + + /** + * Returns the subgroup with the specified index. + * + * @param index the index of the ContactGroup to retrieve. + * @return the ContactGroup with the specified index. + */ + public ContactGroup getGroup(int index) + { + return (ContactGroup)subGroups.get(index); + } + + /** + * Returns the subgroup with the specified name. + * + * @param groupName the name of the ContactGroup to retrieve. + * @return the ContactGroup with the specified index. + */ + public ContactGroup getGroup(String groupName) + { + Iterator groupsIter = subgroups(); + while (groupsIter.hasNext()) + { + ContactGroupSSHImpl contactGroup + = (ContactGroupSSHImpl) groupsIter.next(); + if (contactGroup.getGroupName().equals(groupName)) + return contactGroup; + + } + return null; + + } + + /** + * Returns the name of this group. + * + * @return a String containing the name of this group. + */ + public String getGroupName() + { + return this.groupName; + } + + /** + * Sets this group a new name. + * @param newGrpName a String containing the new name of this group. + */ + public void setGroupName(String newGrpName) + { + this.groupName = newGrpName; + } + + /** + * Returns an iterator over the sub groups that this + * ContactGroup contains. + * + * @return a java.util.Iterator over the ContactGroup children + * of this group (i.e. subgroups). + */ + public Iterator subgroups() + { + return subGroups.iterator(); + } + + /** + * Removes the specified contact from this group. + * @param contact the ContactSSHImpl to remove from this group + */ + public void removeContact(ContactSSHImpl contact) + { + this.contacts.remove(contact); + } + + /** + * Returns the contact with the specified id or null if no such contact + * exists. + * @param id the id of the contact we're looking for. + * @return ContactSSHImpl + */ + public ContactSSHImpl findContactByID(String id) + { + //first go through the contacts that are direct children. + Iterator contactsIter = contacts(); + + while(contactsIter.hasNext()) + { + ContactSSHImpl mContact = (ContactSSHImpl)contactsIter.next(); + + if( mContact.getAddress().equals(id) ) + return mContact; + } + + //if we didn't find it here, let's try in the subougroups + Iterator groupsIter = subgroups(); + + while( groupsIter.hasNext() ) + { + ContactGroupSSHImpl mGroup = (ContactGroupSSHImpl)groupsIter.next(); + + ContactSSHImpl mContact = mGroup.findContactByID(id); + + if (mContact != null) + return mContact; + } + + return null; + } + + /** + * Returns a String representation of this group and the contacts it + * contains (may turn out to be a relatively long string). + * @return a String representing this group and its child contacts. + */ + public String toString() + { + + StringBuffer buff = new StringBuffer(getGroupName()); + buff.append(".subGroups=" + countSubgroups() + ":\n"); + + Iterator subGroups = subgroups(); + while (subGroups.hasNext()) + { + ContactGroupSSHImpl group = (ContactGroupSSHImpl)subGroups.next(); + buff.append(group.toString()); + if (subGroups.hasNext()) + buff.append("\n"); + } + + buff.append("\nChildContacts="+countContacts()+":["); + + Iterator contacts = contacts(); + while (contacts.hasNext()) + { + ContactSSHImpl contact = (ContactSSHImpl) contacts.next(); + buff.append(contact.toString()); + if(contacts.hasNext()) + buff.append(", "); + } + return buff.append("]").toString(); + } + + /** + * Specifies whether or not this contact group is being stored by the server. + * Non persistent contact groups 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 and the contact that we + * associated with that user is placed in a non persistent group. Non + * persistent contact groups 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 group is to be persistent and + * false otherwise. + */ + public void setPersistent(boolean isPersistent) + { + this.isPersistent = isPersistent; + } + + /** + * Determines whether or not this contact group is being stored by the + * server. Non persistent contact groups exist for the sole purpose of + * containing non persistent contacts. + * @return true if the contact group is persistent and false otherwise. + */ + public boolean isPersistent() + { + return isPersistent; + } + + /** + * Returns null as no persistent data is required and the contact address is + * sufficient for restoring the contact. + *

+ * @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 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.length 1) + { + if(this.persistentData.substring(secondCommaIndex+1).length()>0) + this.sshConfigurationForm.setPasswordField( + new String(Base64.decode(this.persistentData + .substring(secondCommaIndex+1, thirdCommaIndex)))); + } + + + this.sshConfigurationForm.setPort( + this.persistentData.substring(thirdCommaIndex + 1, + fourthCommaIndex)); + + this.sshConfigurationForm.setTerminalType( + this.persistentData.substring(fourthCommaIndex + 1, + fifthCommaIndex)); + + this.sshConfigurationForm.setUpdateInterval(new Integer(Integer + .parseInt(this.persistentData.substring(fifthCommaIndex+1)) )); + } + + /** + * Determines whether a connection to a remote server is already underway + * + * @return isConnectionInProgress + */ + public boolean isConnectionInProgress() + { + return this.isConnectionInProgress; + } + + /** + * Sets the status of connection attempt to remote server + * This method is synchronized + * + * @param isConnectionInProgress + */ + public synchronized void setConnectionInProgress(boolean isConnectionInProgress) + { + this.isConnectionInProgress = isConnectionInProgress; + } + + /** + * Returns the OperationSetContactInfo associated with this contact + * + * @return sshConfigurationForm + */ + public OperationSetContactInfo getSSHConfigurationForm() + { + return this.sshConfigurationForm; + } + + /** + * Returns the JSch Stack identified associated with this contact + * + * @return jsch + */ + public JSch getJSch() + { + return this.jsch; + } + + /** + * Sets the JSch Stack identified associated with this contact + * + * @param jsch to be associated + */ + public void setJSch(JSch jsch) + { + this.jsch = jsch; + } + + /** + * 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 + */ + public void setParentGroup(ContactGroupSSHImpl newParentGroup) + { + this.parentGroup = newParentGroup; + } + + /** + * Returns the Hostname associated with this contact + * + * @return hostName + */ + public String getHostName() + { + return sshConfigurationForm.getHostName(); + } + + /** + * Returns a String that can be used for identifying the contact. + * + * @return a String id representing and uniquely identifying the contact. + */ + public String getAddress() + { + return contactID; + } + + /** + * Returns a String that could be used by any user interacting modules + * for referring to this contact. + * + * @return a String that can be used for referring to this contact when + * interacting with the user. + */ + public String getDisplayName() + { + return contactID; + } + + /** + * Returns a byte array containing an image (most often a photo or an + * avatar) that the contact uses as a representation. + * + * @return byte[] an image representing the contact. + */ + public byte[] getImage() + { + return null; + } + + /** + * Returns true if a command has been sent whos reply was not received yet + * false otherwise + * + * @return commandSent + */ + public boolean isCommandSent() + { + return this.commandSent; + } + + /** + * Set the state of commandSent variable which determines whether a reply + * to a command sent is awaited + */ + public void setCommandSent(boolean commandSent) + { + synchronized(lock) + { + this.commandSent = commandSent; + } + } + + /** + * Return the type of message received from remote server + * + * @return messageType + */ + public int getMessageType() + { + return this.messageType; + } + + /** + * Sets the type of message received from remote server + * + * @param messageType + */ + public void setMessageType(int messageType) + { + this.messageType = messageType; + } + + /** + * Returns the status of the contact. + * + * @return presenceStatus + */ + public PresenceStatus getPresenceStatus() + { + return this.presenceStatus; + } + + /** + * 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) + { + this.presenceStatus = sshPresenceStatus; + } + + /** + * Returns a reference to the protocol provider that created the contact. + * + * @return a refererence to an instance of the ProtocolProviderService + */ + public ProtocolProviderService getProtocolProvider() + { + return parentProvider; + } + + /** + * Determines whether or not this contact represents our own identity. + * + * @return true + */ + public boolean isLocal() + { + return true; + } + + /** + * Returns the group that contains this contact. + * @return a reference to the ContactGroupSSHImpl that + * contains this contact. + */ + public ContactGroup getParentContactGroup() + { + return this.parentGroup; + } + + /** + * Returns a string representation of this contact, containing most of its + * representative details. + * + * @return a string representation of this contact. + */ + public String toString() + { + StringBuffer buff + = new StringBuffer("ContactSSHImpl[ DisplayName=") + .append(getDisplayName()).append("]"); + + return buff.toString(); + } + + /** + * Determines 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. + * + * @return true if the contact is persistent and false otherwise. + */ + public boolean isPersistent() + { + return isPersistent; + } + + /** + * 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) + { + this.isPersistent = isPersistent; + } + + + /** + * Returns persistent data of the contact. + * + * @return persistentData of the contact + */ + public String getPersistentData() + { + return persistentData; + } + + /** + * 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 contact resolved or unresolved. + * + * @param resolved true to make the contact resolved; false to + * make it unresolved + */ + public void setResolved(boolean resolved) + { + this.isResolved = resolved; + } + + /** + * Indicates whether some other object is "equal to" this one which in terms + * of contacts translates to having equal ids. The resolved status of the + * contacts deliberately ignored so that contacts would be declared equal + * even if it differs. + *

+ * @param obj the reference object with which to compare. + * @return true 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.

+ * @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 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); }