blob: 6a7c22805756e569a4121c4c957afead8eaed6fd [file] [log] [blame]
/******************************************************************************
* Copyright (c) 2006 IBM Corporation.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - Initial Implementation
*
*****************************************************************************/
package org.eclipse.ptp.remotetools.internal.ssh;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.ptp.remotetools.core.IRemoteExecutionTools;
import org.eclipse.ptp.remotetools.core.IRemoteFileTools;
import org.eclipse.ptp.remotetools.core.IRemoteStatusTools;
import org.eclipse.ptp.remotetools.core.messages.Messages;
import org.eclipse.ptp.remotetools.exception.CancelException;
import org.eclipse.ptp.remotetools.exception.RemoteConnectionException;
import org.eclipse.ptp.remotetools.exception.RemoteExecutionException;
import org.eclipse.ptp.remotetools.exception.RemoteOperationException;
/**
* Concrete class responsible for returning status data from the remote host
*
* NOTE: This implementation assumes that the remote host is Linux based
*
* @author Richard Maciel
*
*/
public class StatusTools implements IRemoteStatusTools {
private ExecutionManager manager;
private UserInformation userInfoCache;
/**
* This class is responsible for caching information that is "stable" enough to be stored and retrieved
* instead of generated every time.
*
* @author Richard Maciel
*
*/
class UserInformation {
private Integer userID;
private Set<Integer> groupIDSet;
private String username;
public UserInformation(Integer userID, Set<Integer> groupIDSet, String username) {
this.userID = userID;
this.groupIDSet = groupIDSet;
this.username = username;
}
/**
* @return the userID
*/
public Integer getUserID() {
return userID;
}
/**
* @return the groupIDSet
*/
public Set<Integer> getGroupIDSet() {
return groupIDSet;
}
/**
* @return the username
*/
public String getUsername() {
return username;
}
}
protected StatusTools(ExecutionManager manager) {
this.manager = manager;
}
/* (non-Javadoc)
* @see org.eclipse.ptp.remotetools.core.IRemoteStatusTools#getRemotePortsInUse(int)
*/
public Set<Integer> getRemotePortsInUse(int protocol) throws RemoteConnectionException, RemoteOperationException, CancelException {
// Uses the following shell script to get a list of used ports in the
// remote host:
// cat /proc/net/<transport protocol>
// Then, the data coming from the command is filtered using java commands equivalent to the
// following shell command:
// cat /proc/net/tcp | tail -n +2 | sed -r 's/^\s+//g' | cut -d ' ' -f 2 | cut -d ':' -f 2
HashSet<Integer> portSet = new HashSet<Integer>();
String protoStr = null, protoStr6 = null;
if(protocol == IRemoteStatusTools.PROTO_UDP) {
protoStr = "cat /proc/net/udp"; //$NON-NLS-1$
protoStr6 = "cat /proc/net/udp6"; //$NON-NLS-1$
} else if (protocol == IRemoteStatusTools.PROTO_TCP){
protoStr = "cat /proc/net/tcp"; //$NON-NLS-1$
protoStr6 = "cat /proc/net/tcp6"; //$NON-NLS-1$
}
try {
IRemoteExecutionTools remExecTools = manager.getExecutionTools();
// Filter information from files.
/*
* "protoOutput" and "proto6Output" are the
* outputs of the above commands, WITHOUT the header
* that is being wiped out by the "replaceFirst"
* method. Thus, the resulting string
* "rawProcOutput" contains only valid connection
* entries for both ipv4 and ipv6 and must be
* totally processed on the following loop.
*
*/
String protoOutput = remExecTools.executeWithOutput(protoStr).
replaceFirst("^\\p{Space}*sl.*inode\\p{Space}*\\n", ""); //$NON-NLS-1$ //$NON-NLS-2$
String proto6Output = remExecTools.executeWithOutput(protoStr6).
replaceFirst("^\\p{Space}*sl.*inode\\p{Space}*\\n", ""); //$NON-NLS-1$ //$NON-NLS-2$
String rawProcOutput = protoOutput.concat(proto6Output);
String [] rawProcLines = rawProcOutput.split("\n"); //$NON-NLS-1$
for(int i=0; i < rawProcLines.length; i++) {
// Trim the beginning of each line, and split the string using
// whitespace as separator.
String [] procFields = rawProcLines[i].trim().split(" "); //$NON-NLS-1$
// Get the second field and split it using ':' as separator.
// Get the second field.
String [] addrFields = procFields[1].split(":"); //$NON-NLS-1$
String allocedPort = addrFields[1];
// Convert the value to an integer (it's on base 16)
// and put it into the set.
portSet.add(Integer.valueOf(allocedPort, 16));
}
} catch (RemoteExecutionException e) {
throw new RemoteOperationException(e);
}
return portSet;
}
private static int PASSWD_USERNAME_FIELD = 0;
private static int PASSWD_USERID_FIELD = 2;
private static int PASSWD_GROUPID_FIELD = 3;
private static int PASSWD_HOMEDIR_FIELD = 5;
private static int GROUP_GROUPID_FIELD = 2;
private static int GROUP_USERLIST_FIELD = 3;
/* (non-Javadoc)
* @see org.eclipse.ptp.remotetools.core.IRemoteStatusTools#getGroupIDSet()
*/
public Set<Integer> getGroupIDSet() throws RemoteConnectionException, RemoteOperationException, CancelException {
// If there's already information on cache, use it
if(userInfoCache == null) {
userInfoCache = fetchRemoteUserInfo();
}
return userInfoCache.getGroupIDSet();
}
/**
* An implementation of the {@link getGroupIDSet} method that only uses the cat command on the remote machine
* to extract desired data. Specifically, it retrieves information from the /etc/passwd looking and /etc/group files
* and extract the information about the groups of the user whose username comes from the {@link getUsername} method
*
* @return
* @throws RemoteConnectionException
* @throws RemoteOperationException
* @throws CancelException
*/
private Set<Integer> getGroupIDSet_CatBased() throws RemoteConnectionException, RemoteOperationException, CancelException {
String [] passwdFields = getPasswdFields();
String username = passwdFields[PASSWD_USERNAME_FIELD].trim();
String strPasswdGroupID = passwdFields[PASSWD_GROUPID_FIELD].trim();
Set<Integer> groupIDSet = new HashSet<Integer>();
// Put the the group from the passd file into the list (if not blank)
if(!strPasswdGroupID.trim().equals("")) { //$NON-NLS-1$
groupIDSet.add(new Integer(Integer.parseInt(strPasswdGroupID)));
}
/*
* Get the group file and search for the groups that the user belongs
* to.
*/
// Cat the /etc/group file and split it in lines
IRemoteExecutionTools remExecTools = manager.getExecutionTools();
try {
String rawGroup = remExecTools.executeWithOutput("cat /etc/group"); //$NON-NLS-1$
String [] rawGroupLines = rawGroup.split("\\n"); //$NON-NLS-1$
for(int i=0; i < rawGroupLines.length; i++) {
// Last field can be non-existant, so use a negative parameter in split to
// make sure it becomes a field
String [] groupFields = rawGroupLines[i].split(":", -1); //$NON-NLS-1$
// Search for the username in the list of users of the group.
// IF the username belongs to the group, insert it into the list.
if(isUsernameInList(username, groupFields[GROUP_USERLIST_FIELD].split(","))) { //$NON-NLS-1$
groupIDSet.add(new Integer(Integer.parseInt(groupFields[GROUP_GROUPID_FIELD].trim())));
}
}
} catch (RemoteExecutionException e) {
throw new RemoteOperationException(e);
}
// Returns the set
return groupIDSet;
}
/**
* Given a list of usernames, verifies if user belongs to it.
*
* @param username
* @param usernameList
* @return
*/
private boolean isUsernameInList(String username, String [] usernameList) {
for(int i=0; i < usernameList.length; i++) {
if(usernameList[i].trim().equals(username)) {
return true;
}
}
return false;
}
/* (non-Javadoc)
* @see org.eclipse.ptp.remotetools.core.IRemoteStatusTools#getUserID()
*/
public int getUserID() throws RemoteConnectionException, RemoteOperationException, CancelException {
// If there's already information on cache, use it
if(userInfoCache == null) {
userInfoCache = fetchRemoteUserInfo();
}
return userInfoCache.getUserID().intValue();
}
/**
* An implementation of the {@link getUserID} method that only uses the cat command on the remote machine
* to extract desired data. Specifically it reads information from the /etc/passwd file and parses it to
* retrieve the user id.
*
* @return
* @throws RemoteConnectionException
* @throws RemoteOperationException
* @throws CancelException
*/
public int getUserID_CatBased() throws RemoteConnectionException, RemoteOperationException, CancelException {
// Get the field userid from the result of the getPasswdFields method
// and return it.
String strUserID = getPasswdFields()[PASSWD_USERID_FIELD];
return Integer.parseInt(strUserID);
}
/**
* Get the line that has the user information from the passwd file
*
* @return String [] A vector containing the fields or null if no user matches
* @throws RemoteOperationException
* @throws CancelException
* @throws RemoteConnectionException
*/
private String [] getPasswdFields() throws RemoteOperationException, RemoteConnectionException, CancelException {
String currentUsername = getUsername_WhoamiBased();
IRemoteExecutionTools remExecTools = manager.getExecutionTools();
try {
// Get lines from the passwd
String rawPasswd = remExecTools.executeWithOutput("cat /etc/passwd"); //$NON-NLS-1$
String [] rawPasswdLines = rawPasswd.split("\\n"); //$NON-NLS-1$
// Look for the string that matches the value from the currentUsername
// That string is on the first line
for(int i=0; i < rawPasswdLines.length; i++) {
// Make sure it includes the last field even if it's blank.
String [] passwdFields = rawPasswdLines[i].split(":", -1); //$NON-NLS-1$
if(passwdFields[PASSWD_USERNAME_FIELD].equals(currentUsername))
return passwdFields;
}
} catch (RemoteExecutionException e) {
throw new RemoteOperationException(e);
}
throw new RuntimeException(Messages.RemoteStatusTools_GetPasswdFields_NoUsernameInPasswdFile);
}
/* (non-Javadoc)
* @see org.eclipse.ptp.remotetools.core.IRemoteStatusTools#getUsername()
*/
public String getUsername() throws RemoteConnectionException, RemoteOperationException,
CancelException {
if(userInfoCache == null) {
userInfoCache = fetchRemoteUserInfo();
}
return userInfoCache.getUsername();
}
/**
* * An implementation of the {@link getUsername} method that uses the whoami command on the remote machine
* to extract username information.
*
* @return
* @throws RemoteConnectionException
* @throws RemoteOperationException
* @throws CancelException
*/
private String getUsername_WhoamiBased() throws RemoteConnectionException, RemoteOperationException,
CancelException {
// Get information from the whoami command
try {
IRemoteExecutionTools remExecTools = manager.getExecutionTools();
String rawWhoAmI = remExecTools.executeWithOutput("whoami"); //$NON-NLS-1$
return rawWhoAmI.trim();
} catch (RemoteExecutionException e) {
throw new RemoteOperationException(e);
}
}
/**
* A method designed to parse the /etc/group file to find the list
* of groups that a user is enrolled in.
*
* @param username The name of the user that we are searching for enrollments.
* @return A list of groups that username belongs to.
* @throws RemoteConnectionException
* @throws RemoteOperationException
*/
private Set<Integer> readGroupList(String username) throws RemoteConnectionException, RemoteOperationException {
Set<Integer> groupList = new HashSet<Integer>();
BufferedReader etcGroupFile = null;
try {
IRemoteFileTools ft = manager.getRemoteFileTools();
InputStream is = ft.getInputStream("/etc/group",null); //$NON-NLS-1$
etcGroupFile = new BufferedReader(new InputStreamReader(is ));
String line = etcGroupFile.readLine();
while (line != null) {
String[] fields = line.split(":"); //$NON-NLS-1$
if (fields[0].trim().compareTo(username) == 0) {
groupList.add(new Integer(fields[2]));
} else if (fields.length == 4) {
String[] users = fields[3].split(","); //$NON-NLS-1$
for (int i = 0; i < users.length; i++) {
if (users[i].trim().compareTo(username) == 0) {
groupList.add(new Integer(fields[2]));
}
}
}
line = etcGroupFile.readLine();
}
} catch (IOException ioe) {
throw new RemoteOperationException(ioe);
} catch (CancelException ce) {
throw new RemoteOperationException(ce);
} catch (NumberFormatException nfe) {
//Maybe the format of this /etc/group file put the fields in a
//different order.
throw new RemoteOperationException(nfe);
} catch (ArrayIndexOutOfBoundsException aioobe) {
//This case is going to happen if the format is not the expected one.
throw new RemoteOperationException(aioobe);
}
finally {
try {
if (etcGroupFile != null) {
etcGroupFile.close();
}
} catch (IOException ioe) {throw new RemoteOperationException(ioe);}
}
return groupList;
}
/**
* Return all the user information from the remote machine.
* This operation is a bit slow and should be used only once per RemoteStatusTools object.
*
* @throws RemoteConnectionException
* @throws RemoteOperationException
* @throws CancelException
* @return
*/
private UserInformation fetchRemoteUserInfo() throws RemoteConnectionException, RemoteOperationException,
CancelException {
try {
//Set of groups that this user belongs to.
Set<Integer> groupList;
IRemoteExecutionTools remExecTools = manager.getExecutionTools();
// Execute command to return the username, uid and the group list
String rawUserInfo = remExecTools.executeWithOutput("echo `id -un`:`id -u`:`id -G`"); //$NON-NLS-1$
String [] userInfoFields = rawUserInfo.trim().split(":", -1); //$NON-NLS-1$
//With systems using BusyBox, id has no argument -G
//so we must parse /etc/group in this case
if (userInfoFields[2].trim().equalsIgnoreCase("")) { //$NON-NLS-1$
groupList = readGroupList(userInfoFields[0]);
} else {
String[] groupFields = userInfoFields[2].split(" ", -1); //$NON-NLS-1$
groupList = new HashSet<Integer>();
for (int i = 0; i < groupFields.length; i++) {
// Convert groups to Integer and put into the Set
groupList.add(new Integer(groupFields[i]));
}
}
return new UserInformation(new Integer(userInfoFields[1]), groupList, userInfoFields[0]);
} catch (RemoteExecutionException e) {
throw new RemoteOperationException(e);
}
}
public long getTime() throws RemoteConnectionException, RemoteOperationException, CancelException {
try {
IRemoteExecutionTools execTools = manager.getExecutionTools();
String rawData = execTools.executeWithOutput("date +'%s'"); //$NON-NLS-1$
rawData = rawData.trim();
return Long.parseLong(rawData)*1000;
} catch (RemoteExecutionException e) {
throw new RemoteOperationException(e);
}
}
}