001/*
002 * Copyright (c) 2004-2010 The Regents of the University of California.
003 * All rights reserved.
004 *
005 * '$Author: welker $'
006 * '$Date: 2010-05-06 05:21:26 +0000 (Thu, 06 May 2010) $'
007 * '$Revision: 24234 $'
008 *
009 * Permission is hereby granted, without written agreement and without
010 * license or royalty fees, to use, copy, modify, and distribute this
011 * software and its documentation for any purpose, provided that the above
012 * copyright notice and the following two paragraphs appear in all copies
013 * of this software.
014 *
015 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
016 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
017 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
018 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
019 * SUCH DAMAGE.
020 *
021 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
022 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
023 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
024 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
025 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
026 * ENHANCEMENTS, OR MODIFICATIONS.
027 *
028 */
029
030package org.kepler.ssh;
031
032import java.util.ArrayList;
033import java.util.Hashtable;
034import java.util.List;
035
036import org.apache.commons.logging.Log;
037import org.apache.commons.logging.LogFactory;
038
039/**
040 * This class provides a factory to give you an object implementing the
041 * ExecInterface. It provides either an LocalExec object or an RemoteExec object
042 * based on the target you provide. RemoteExec object can either be an instance of
043 * SshExec or GsiSshExec based on whether the remote host supports grid authentication
044 * or not. The reference to a target is with "user@host:port". If port is not given,
045 * first an attempt is made to connect using gssapi at port 2222. If that fails,
046 * default to ssh at port 22.
047 *
048 * <p>
049 *
050 * @author Norbert Podhorszki
051 */
052
053public class ExecFactory {
054
055    private static final String GSSAPI = "gssapi";
056    private static final String SSH ="SSH";
057    private ExecFactory() {
058    }
059
060    private static final Log log = LogFactory.getLog(ExecFactory.class.getName());
061    private static final boolean isDebugging = log.isDebugEnabled();
062
063    private static Hashtable<String,String> authenticationMethod = new Hashtable<String,String>();
064
065    /**
066     * Helper method to provide the target as a complete string. This method
067     * calls the other one after processing the input string.
068     * @throws ExecException
069     */
070    public static ExecInterface getExecObject(String target) throws ExecException 
071    {
072        // get USER
073        String user, host;
074        int port = -1;
075
076        int atPos = target.indexOf('@');
077        if (atPos >= 0)
078            user = target.substring(0, target.indexOf('@'));
079        else
080            user = System.getProperty("user.name");
081
082        // get the HOST and PORT
083        int colonPos = target.indexOf(':');
084        if (colonPos >= 0 && colonPos > atPos) {
085            host = target.substring(atPos + 1, colonPos);
086            String portStr = target.substring(colonPos + 1);
087            try {
088                port = Integer.parseInt(portStr);
089            } catch (java.lang.NumberFormatException ex) {
090                log.error("The port should be a number or omitted in "+ target);
091            }
092        } else
093            host = target.substring(atPos + 1);
094        return ExecFactory.getExecObject(user, host, port);
095    }
096
097    @SuppressWarnings("unchecked")
098    public static ExecInterface getExecObject(String user, String host)
099        throws ExecException 
100    {
101        return getExecObject(user, host, -1);
102    }
103
104    /**
105     * Return an object with ExecInterface, based on the specified target. It
106     * will be a LocalExec if host is null, empty or equals 'local', otherwise
107     * it will be an SshExec or GsiSshExec object based on the supported
108     * authentication mechanism. Grid authentication is given priority over
109     * ssh authentication. If no valid port is provided, first tries
110     * grid authentication at port 2222, if that fails defaults to ssh
111     * at port 22. The check for available authentications method is done only
112     * once per remote host and cached for later use.
113     * @throws ExecException
114     */
115    @SuppressWarnings("unchecked")
116    public static ExecInterface getExecObject(String user, String host, int port)
117        throws ExecException 
118    {
119
120        if (host == null || host.trim().equals("")
121                || host.equals("localhost") || host.equals("local") ) {
122            // local execution
123            if (isDebugging) {
124                log.debug("Provide LocalExec");
125            }
126            return  new LocalExec();
127        } else {
128            return getRemoteExec(user, host, port);
129        }
130    }
131
132    @SuppressWarnings("unchecked")
133    private static ExecInterface getExecByEarlierMethod(String user, String host, int port) 
134        throws ExecException 
135    {
136
137        String am = authenticationMethod.get(user + "@" + host + ":" + port);
138        if (GSSAPI.equals(am)) {
139            log.info("Providing new GsiSshExec for " + user + "@" + host + ":" + port);
140            return new GsiSshExec(user,host,port);
141        } else if (SSH.equals(am)) {
142            log.info("Providing new SshExec for " + user + "@" + host + ":" + port);
143            return new SshExec(user,host,port);
144        }
145        return null;
146    }
147
148    @SuppressWarnings("unchecked")
149    private static ExecInterface getRemoteExec(String user, String host, int port) 
150        throws ExecException 
151    {
152
153        //REMOTE HOST
154        //Check if the server support grid authentication
155        ExecInterface execObj = null;
156        String target = user + "@" + host + ":" + port;
157        String cert = System.getProperty("X509_USER_CERT");
158        String proxy = System.getProperty("X509_USER_PROXY");
159        boolean trygsi = (cert != null || proxy != null); 
160                
161        // check if we had a factory call for this target before
162        if (port>0) {
163            execObj = getExecByEarlierMethod (user, host, port);
164        } else {
165            if (trygsi)
166                execObj = getExecByEarlierMethod (user, host, 2222);
167            if (execObj == null)
168                execObj = getExecByEarlierMethod (user, host, 22);
169        }
170        if (execObj != null) 
171            return execObj;
172
173        // try the GSI options first
174        if (trygsi) {
175            int testport = port;
176            if (port > 0) {
177                execObj = getGsisshExec(user, host, port);
178            } else {
179                // Try port 2222 
180                testport = 2222;
181                execObj = getGsisshExec(user, host, testport);
182                if (execObj == null) {
183                    // Try port 22
184                    testport = 22;
185                    execObj = getGsisshExec(user, host, testport);
186                }
187            }
188            
189            if (execObj != null) {
190                // we have a GSI-SSH server
191                authenticationMethod.put(user + "@" + host + ":" + testport, GSSAPI);
192                log.info("Providing new GsiSshExec for " + user + "@" + host + ":" + testport);
193                return execObj;
194            }
195        } // end if (trygsi)
196
197        // last chance is a traditional SSH connection
198        if (port>0) {
199            log.info("Providing new SshExec for " + user + "@" + host + ":" + port);
200            execObj = new SshExec(user, host, port);
201            authenticationMethod.put(user + "@" + host + ":" + port, SSH);
202        } else {
203            log.info("Providing new SshExec for " + user + "@" + host + ":22");
204            execObj = new SshExec(user, host, 22);
205            authenticationMethod.put(user + "@" + host + ":22", SSH);
206        }
207        return execObj;
208    }
209
210    private static GsiSshExec getGsisshExec(String user, String host, int port) {
211        log.debug("Try GSI server at " + user + "@" + host + ":"+ port);
212        GsiSshExec result = null;
213        GsiSshExec temp = new GsiSshExec(user,host,port);
214        List availableMethods =  new ArrayList();
215        try {
216            availableMethods = temp.getAvailableAuthMethods();
217            log.info("Available authentication mechanism for "+ user + "@" + host + ":"+ port +" -- " + availableMethods );
218        } catch(SshException e) {
219            temp.closeConnection();
220            log.warn("Unable to retrieve available authentication mechanisms.");
221        }
222        if(availableMethods==null){
223                log.warn("Unable to retrieve available authentication mechanisms.");
224                temp.closeConnection();
225        }
226        else if (availableMethods.contains(GSSAPI)) {
227            result = temp;
228        } else {
229            temp.closeConnection();
230        }
231        return result;
232    }
233
234
235
236}