/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.sun.jini.tool.envcheck.plugins; import com.sun.jini.start.NonActivatableServiceDescriptor; import com.sun.jini.start.ServiceDescriptor; import com.sun.jini.start.SharedActivationGroupDescriptor; import com.sun.jini.tool.envcheck.AbstractPlugin; import com.sun.jini.tool.envcheck.Plugin; import com.sun.jini.tool.envcheck.EnvCheck; import com.sun.jini.tool.envcheck.Reporter; import com.sun.jini.tool.envcheck.Reporter.Message; import com.sun.jini.tool.envcheck.SubVMTask; import java.io.InputStream; import java.io.IOException; import java.net.InetAddress; import java.net.MalformedURLException; import java.net.UnknownHostException; import java.net.URL; import java.net.URLConnection; import java.util.StringTokenizer; /** * Plugin which performs a variety of checks on codebase components. If not * configured to perform service starter checks, the codebase is expected to be * defined by the <code>java.rmi.server.codebase</code> system * property. Otherwise, all of the codebases contained in the service * descriptors of the service starter <code>Configuration</code> are examined * (excepting <code>SharedActivationGroupDescriptors</code>, which do not have a * codebase). First, an existence check is performed; the codebase string must * be non-null and have length > 0 after white space is trimmed. Non-existence * is reported as an error. Then the codebase is decomposed into tokens (URL * strings). Each component in a codebase is checked for the following: * <ul> * <li>check for a valid URL. As a special case, an httpmd URL which * is invalid because the necessary protocol handler was not loaded * will result in the generation of an appropriate error message * and explanation. Further checks are not done for invalid URLs. * <li>check that the host name is expressed using a fully qualified domain name * <li>check for the use of md5 hashes in httpmd URLs * <li>check the ability to resolve the host name to an address * <li>check for a host name of 'localhost' * <li>check for the ability to access (connect to) the URL * </ul> * Failure of the first or last checks are displayed as errors. Failure of * the other checks are displayed as warnings. */ public class CheckCodebase extends AbstractPlugin { /** reference to the plugin container */ EnvCheck envCheck; /** * Depending on whether service start checks are configured, * either check the codebase system property or all of the * <code>ServiceDescriptors</code> that are <code>instanceof</code> * <code>NonActivatableServiceDescriptor</code>. */ public void run(EnvCheck envCheck) { this.envCheck = envCheck; String source; String codebase; if (envCheck.getDescriptors().length == 0) { source = getString("propsource"); codebase = envCheck.getProperty("java.rmi.server.codebase"); doChecks(null, null, source, codebase); } else { ServiceDescriptor[] sd = envCheck.getDescriptors(); SharedActivationGroupDescriptor g = envCheck.getGroupDescriptor(); for (int i = 0; i < sd.length; i++) { if (sd[i] instanceof NonActivatableServiceDescriptor) { NonActivatableServiceDescriptor d = (NonActivatableServiceDescriptor) sd[i]; source = getString("desc") + " " + d.getImplClassName(); codebase = d.getExportCodebase(); doChecks(d, g, source, codebase); } } } } /** * Perform all of the checks on <code>codebase</code>. * * @param source a string describing the source of the codebase for * use in report messages * @param codebase the codebase to check */ private void doChecks(NonActivatableServiceDescriptor d, SharedActivationGroupDescriptor g, String source, String codebase) { if (checkExistance(source, codebase)) { StringTokenizer tok = new StringTokenizer(codebase); while (tok.hasMoreTokens()) { String urlToken = tok.nextToken(); URL url = checkURL(d, g, source, urlToken); if (url != null) { checkForFQDomain(url, source); checkForMD5(url, source); checkForKnownHost(url, source); checkForLocalHost(url, source); checkAccessibility(url, source); } } } } /** * Check for existence. <code>codebase</code> must be non-null * and have length > 0 after trimming whitespace. * * @param source identifies the source of the codebase * @param codebase the codebase to check * @return true if existence check is successful */ private boolean checkExistance(String source, final String codebase) { Message message; boolean gotCodebase; if (codebase != null && codebase.trim().length() > 0) { message = new Message(Reporter.INFO, getString("codebaseIs") + " " + codebase, getString("existenceExp")); gotCodebase = true; } else { message = new Message(Reporter.INFO, getString("nocodebase"), getString("existenceExp")); gotCodebase = false; } Reporter.print(message, source); return gotCodebase; } /** * Check whether <code>urlToken</code> can be used to construct * a <code>URL</code> object. If a <code>MalformedURLException</code> * is thrown, check whether the protocol portion of the URL is * <code>httpmd:</code>. If so, check whether the protocol handler is * installed. If not, output an appropriate message. Otherwise, just * complain generally that the URL is malformed. * * @param source the source of the codebase * @param urlToken the codebase component to check * @return the corresponding URL object if successful, <code>null</code> * otherwise */ private URL checkURL(NonActivatableServiceDescriptor d, SharedActivationGroupDescriptor g, String source, final String urlToken) { Message message; URL url = null; String[] args = new String[]{urlToken}; Object lobj = envCheck.launch(d, g, taskName("GetURLTask"), args); if (lobj instanceof URL) { url = (URL) lobj; } else if (lobj instanceof String) { String cause = (String) lobj; if (cause.equals("nohandler")) { message = new Message(Reporter.ERROR, getString("nohandler", urlToken), getString("httpmdExp")); Reporter.print(message, source); try { url = new URL(urlToken); } catch (MalformedURLException e) { // should never happen message = new Message(Reporter.ERROR, getString("badURL", urlToken), e, null); Reporter.print(message, source); } } else { message = new Message(Reporter.ERROR, getString("badURL", urlToken) + ": " + cause, null); Reporter.print(message, source); } } else { handleUnexpectedSubtaskReturn(lobj, source); } return url; } /** * Check the ability to resolve the host component of <code>url</code> * to an <code>InetAddress</code>. If successful, this method is silent. * * @param url the <code>URL</code> to check * @param source the source of the <code>URL</code> */ private void checkForKnownHost(final URL url, String source) { try { InetAddress.getByName(url.getHost()).getCanonicalHostName(); } catch (UnknownHostException e) { Message message = new Message(Reporter.ERROR, getString("noHost", url.getHost(), url), null); Reporter.print(message, source); } } /** * Check whether the host component of <code>url</code> resolves * to a loopback address. * * @param url the <code>URL</code> to check * @param source the source of the <code>URL</code> */ private void checkForLocalHost(final URL url, String source) { try { if (InetAddress.getByName(url.getHost()).isLoopbackAddress()) { Message message = new Message(Reporter.WARNING, getString("usedLocalhost", url), getString("localhostExp")); Reporter.print(message, source); } } catch (Exception ignore) { // accessibility check handles this failure } } /** * Check the accessibility of the codebase <code>URL</code> by opening a * connection to it. This check fails if the <code>openConnection</code> * call or subsequent <code>getInputStream </code> call throws an * <code>IOException</code>, or if these calls do not complete within 5 * seconds. These two failure modes result in different error messages * being output. * * @param url the <code>URL</code> to check * @param source the source of the <code>URL</code> */ private void checkAccessibility(final URL url, String source) { Message message; URLAccessor accessor = new URLAccessor(url); Thread t = new Thread(accessor); t.setDaemon(true); t.start(); try { t.join(5000, 0); } catch (Exception e) { e.printStackTrace(); } if (t.isAlive()) { message = new Message(Reporter.ERROR, getString("noresponse", url), null); } else if (accessor.getException() == null) { message = new Message(Reporter.INFO, getString("available", url), null); } else { message = new Message(Reporter.ERROR, getString("unavailable", url), accessor.getException(), null); } Reporter.print(message, source); } /** * Check for a fully qualified host name. To be valid, the host * name must consist of at least two '.' separated tokens, and the * last token must be one of the well-known top level domain names, * or the last token must be a two character string which is assumed * to be a country code. * * @param url the <code>URL</code> to check * @param source the source of the <code>URL</code> */ private void checkForFQDomain(final URL url, String source) { String[] topLevelDomains = {"aero", "biz", "com", "coop","edu", "gov", "info", "int", "mil","museum", "name", "net", "org", "pro"}; String hostName = url.getHost(); int lastDot = hostName.lastIndexOf('.'); if (lastDot >= 0 && lastDot < hostName.length() - 1) { String tld = hostName.substring(lastDot + 1); // top level domain if (tld.length() == 2) { return; } for (int i = 0; i < topLevelDomains.length; i++) { if (tld.equals(topLevelDomains[i])) { return; } } } Message message = new Message(Reporter.WARNING, getString("unqualified", url.getHost()), getString("unqualifiedExp")); Reporter.print(message, source); } /** * Check for use of an MD5 httpmd URL. If the protocol of <code>url</code> * is httpmd, then the hash function identifier is parsed from the * file component of <code>url</code> and a string comparison is done. * * @param url the <code>URL</code> to check * @param source the source of the <code>URL</code> */ private void checkForMD5(final URL url, String source) { if(! url.getProtocol().equalsIgnoreCase("httpmd")) { return; } // can assume hashcode is present, or url construction would have failed String target = url.getFile(); int lastSemi = target.lastIndexOf(';'); String hash = target.substring(lastSemi + 1); if (hash.startsWith("md5")) { Message message = new Message(Reporter.WARNING, getString("usesmd5", url), getString("usesmd5Exp")); Reporter.print(message, source); } } /** * A <code>Runnable</code> which attempts to open a <code>URL</code> * connection. If the attempt results in an exception being throw, * the exception is stored for later access by the invoker of the thread. */ private class URLAccessor implements Runnable { URL url; Throwable exception = null; URLAccessor(URL url) { this.url = url; } public void run() { try { URLConnection con = url.openConnection(); InputStream stream = con.getInputStream(); } catch (IOException e) { exception = e; } } Throwable getException() { return exception; } } public static class GetURLTask implements SubVMTask { public Object run(String[] args) { String urlToken = args[0]; URL url; try { url = new URL(urlToken); } catch (MalformedURLException e) { int firstColon = urlToken.indexOf(':'); if (firstColon > 0) { String protocol = urlToken.substring(0, firstColon); if (protocol.equalsIgnoreCase("httpmd")) { if (!httpmdHandlerInstalled()) { return "nohandler"; } } } return e.getMessage(); } return url; } private boolean httpmdHandlerInstalled() { try { new URL("httpmd://localhost/foo;sha=0"); } catch (MalformedURLException e) { return false; } return true; } } }