View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership. The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License. You may obtain a copy of the License at
9    * 
10   *      http://www.apache.org/licenses/LICENSE-2.0
11   * 
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.river.tool.envcheck.plugins;
19  
20  import org.apache.river.start.NonActivatableServiceDescriptor;
21  import org.apache.river.start.ServiceDescriptor;
22  import org.apache.river.start.SharedActivationGroupDescriptor;
23  
24  import org.apache.river.tool.envcheck.AbstractPlugin;
25  import org.apache.river.tool.envcheck.Plugin;
26  import org.apache.river.tool.envcheck.EnvCheck;
27  import org.apache.river.tool.envcheck.Reporter;
28  import org.apache.river.tool.envcheck.Reporter.Message;
29  import org.apache.river.tool.envcheck.SubVMTask;
30  
31  import java.io.InputStream;
32  import java.io.IOException;
33  import java.net.InetAddress;
34  import java.net.MalformedURLException;
35  import java.net.UnknownHostException;
36  import java.net.URL;
37  import java.net.URLConnection;
38  import java.util.StringTokenizer;
39  
40  /**
41   * Plugin which performs a variety of checks on codebase components.  If not
42   * configured to perform service starter checks, the codebase is expected to be
43   * defined by the <code>java.rmi.server.codebase</code> system
44   * property. Otherwise, all of the codebases contained in the service
45   * descriptors of the service starter <code>Configuration</code> are examined
46   * (excepting <code>SharedActivationGroupDescriptors</code>, which do not have a
47   * codebase). First, an existence check is performed; the codebase string must
48   * be non-null and have length &gt; 0 after white space is trimmed. Non-existence
49   * is reported as an error. Then the codebase is decomposed into tokens (URL
50   * strings). Each component in a codebase is checked for the following:
51   * <ul>
52   * <li>check for a valid URL. As a special case, an httpmd URL which
53   *     is invalid because the necessary protocol handler was not loaded
54   *     will result in the generation of an appropriate error message
55   *     and explanation. Further checks are not done for invalid URLs.
56   * <li>check that the host name is expressed using a fully qualified domain name
57   * <li>check for the use of md5 hashes in httpmd URLs
58   * <li>check the ability to resolve the host name to an address
59   * <li>check for a host name of 'localhost'
60   * <li>check for the ability to access (connect to) the URL
61   * </ul>
62   * Failure of the first or last checks are displayed as errors. Failure of
63   * the other checks are displayed as warnings.
64   */
65  public class CheckCodebase extends AbstractPlugin {
66  
67      /** reference to the plugin container */
68      EnvCheck envCheck;
69  
70      /**
71       * Depending on whether service start checks are configured,
72       * either check the codebase system property or all of the
73       * <code>ServiceDescriptors</code> that are <code>instanceof</code>
74       * <code>NonActivatableServiceDescriptor</code>.
75       */
76      public void run(EnvCheck envCheck) {
77  	this.envCheck = envCheck;
78  	String source;
79  	String codebase;
80  	if (envCheck.getDescriptors().length == 0) {
81  	    source = getString("propsource");
82  	    codebase = envCheck.getProperty("java.rmi.server.codebase");
83  	    doChecks(null, null, source, codebase);
84  	} else {
85  	    ServiceDescriptor[] sd = envCheck.getDescriptors();
86  	    SharedActivationGroupDescriptor g = envCheck.getGroupDescriptor();
87  	    for (int i = 0; i < sd.length; i++) {
88  		if (sd[i] instanceof NonActivatableServiceDescriptor) {
89  		    NonActivatableServiceDescriptor d = 
90  			(NonActivatableServiceDescriptor) sd[i];
91  		    source = getString("desc") + " " + d.getImplClassName();
92  		    codebase = d.getExportCodebase();
93  		    doChecks(d, g, source, codebase);
94  		}
95  	    }
96  	}
97      }
98  
99      /** 
100      * Perform all of the checks on <code>codebase</code>. 
101      * 
102      * @param source a string describing the source of the codebase for
103      *               use in report messages
104      * @param codebase the codebase to check
105      */
106     private void doChecks(NonActivatableServiceDescriptor d,
107 			  SharedActivationGroupDescriptor g,
108 			  String source, 
109 			  String codebase) 
110     {
111 	if (checkExistance(source, codebase)) {
112 	    StringTokenizer tok = new StringTokenizer(codebase);
113 	    while (tok.hasMoreTokens()) {
114 		String urlToken = tok.nextToken();
115 		URL url = checkURL(d, g, source, urlToken);
116 		if (url != null) {
117 		    checkForFQDomain(url, source);
118 		    checkForMD5(url, source);
119 		    checkForKnownHost(url, source);
120 		    checkForLocalHost(url, source);
121 		    checkAccessibility(url, source);
122 		}
123 	    }
124 	}
125     }
126 
127     /**
128      * Check for existence. <code>codebase</code> must be non-null
129      * and have length > 0 after trimming whitespace.
130      *
131      * @param source identifies the source of the codebase
132      * @param codebase the codebase to check
133      * @return true if existence check is successful
134      */
135     private boolean checkExistance(String source, final String codebase) {
136 	Message message;
137 	boolean gotCodebase;
138 	if (codebase != null && codebase.trim().length() > 0) {
139 	    message = new Message(Reporter.INFO,
140 				  getString("codebaseIs") + " " + codebase,
141 				  getString("existenceExp"));
142 	    gotCodebase = true;
143 	} else {
144 	    message = new Message(Reporter.INFO,
145 				  getString("nocodebase"),
146 				  getString("existenceExp"));
147 	    gotCodebase = false;
148 	}
149 	Reporter.print(message, source);
150 	return gotCodebase;
151     }
152 
153     /**
154      * Check whether <code>urlToken</code> can be used to construct
155      * a <code>URL</code> object. If a <code>MalformedURLException</code>
156      * is thrown, check whether the protocol portion of the URL is
157      * <code>httpmd:</code>. If so, check whether the protocol handler is
158      * installed. If not, output an appropriate message. Otherwise, just
159      * complain generally that the URL is malformed.
160      *
161      * @param source the source of the codebase 
162      * @param urlToken the codebase component to check
163      * @return the corresponding URL object if successful, <code>null</code>
164      *         otherwise
165      */
166     private URL checkURL(NonActivatableServiceDescriptor d,
167 			 SharedActivationGroupDescriptor g,
168 			 String source, 
169 			 final String urlToken) 
170     {
171 	Message message;
172 	URL url = null;
173 	String[] args = new String[]{urlToken};
174 	Object lobj = envCheck.launch(d, g, taskName("GetURLTask"), args);
175 	if (lobj instanceof URL) {
176 	    url = (URL) lobj;
177 	} else if (lobj instanceof String) {
178 	    String cause = (String) lobj;
179 	    if (cause.equals("nohandler")) {
180 		message = new Message(Reporter.ERROR,
181 				      getString("nohandler", urlToken),
182 				      getString("httpmdExp"));
183 		Reporter.print(message, source);
184 		try {
185 		    url = new URL(urlToken);
186 		} catch (MalformedURLException e) { // should never happen
187 		    message = new Message(Reporter.ERROR,
188 					  getString("badURL", urlToken),
189 					  e, 
190 					  null);
191 		    Reporter.print(message, source);
192 		}
193 	    } else {
194 		message = 
195 		    new Message(Reporter.ERROR,
196 				getString("badURL", urlToken) + ": " + cause,
197 				null);
198 		
199 		Reporter.print(message, source);
200 	    }
201 	} else {
202 	    handleUnexpectedSubtaskReturn(lobj, source);
203 	}
204 	return url;
205     }
206 
207     /**
208      * Check the ability to resolve the host component of <code>url</code>
209      * to an <code>InetAddress</code>. If successful, this method is silent.
210      *
211      * @param url the <code>URL</code> to check
212      * @param source the source of the <code>URL</code>
213      */
214     private void checkForKnownHost(final URL url, String source) {
215 	try {
216 	    InetAddress.getByName(url.getHost()).getCanonicalHostName();
217 	} catch (UnknownHostException e) {
218 	    Message message = new Message(Reporter.ERROR,
219 					  getString("noHost", 
220 						    url.getHost(), 
221 						    url),
222 					  null);
223 	    Reporter.print(message, source);
224 	}
225     }
226 
227     /**
228      * Check whether the host component of <code>url</code> resolves
229      * to a loopback address.
230      *
231      * @param url the <code>URL</code> to check
232      * @param source the source of the <code>URL</code>
233      */
234     private void checkForLocalHost(final URL url, String source) {
235 	try {
236 	    if (InetAddress.getByName(url.getHost()).isLoopbackAddress()) {
237 		Message message = new Message(Reporter.WARNING,
238 					      getString("usedLocalhost", url),
239 					      getString("localhostExp"));
240 		Reporter.print(message, source);
241 	    }
242 	} catch (Exception ignore) { // accessibility check handles this failure
243 	}
244     }
245 
246     /**
247      * Check the accessibility of the codebase <code>URL</code> by opening a
248      * connection to it. This check fails if the <code>openConnection</code>
249      * call or subsequent <code>getInputStream </code> call throws an
250      * <code>IOException</code>, or if these calls do not complete within 5
251      * seconds. These two failure modes result in different error messages
252      * being output.
253      *
254      * @param url the <code>URL</code> to check
255      * @param source the source of the <code>URL</code>
256      */
257     private void checkAccessibility(final URL url, String source) {
258 	Message message;
259 	URLAccessor accessor = new URLAccessor(url);
260 	Thread t = new Thread(accessor);
261 	t.setDaemon(true);
262 	t.start();
263 	try {
264 	    t.join(5000, 0);
265 	} catch (Exception e) {
266 	    e.printStackTrace();
267 	}
268 	if (t.isAlive()) {
269 	    message = new Message(Reporter.ERROR,
270 				  getString("noresponse", url),
271 				  null);
272 	} else if (accessor.getException() == null) {
273 	    message = new Message(Reporter.INFO,
274 				  getString("available", url),
275 				  null);
276 	} else {
277 	    message = new Message(Reporter.ERROR,
278 				  getString("unavailable", url),
279 				  accessor.getException(),
280 				  null);
281 	}
282 	Reporter.print(message, source);
283     }
284 
285     /**
286      * Check for a fully qualified host name. To be valid, the host
287      * name must consist of at least two '.' separated tokens, and the
288      * last token must be one of the well-known top level domain names,
289      * or the last token must be a two character string which is assumed
290      * to be a country code.
291      *
292      * @param url the <code>URL</code> to check
293      * @param source the source of the <code>URL</code>
294      */
295     private void checkForFQDomain(final URL url, String source) {
296 
297 	String[] topLevelDomains = {"aero", "biz", "com", "coop","edu", 
298 				    "gov", "info", "int", "mil","museum", 
299 				    "name", "net", "org", "pro"};
300 	String hostName = url.getHost();
301 	int lastDot = hostName.lastIndexOf('.');
302 	if (lastDot >= 0 && lastDot < hostName.length() - 1) {
303 	    String tld = hostName.substring(lastDot + 1); // top level domain
304 	    if (tld.length() == 2) {
305 		return;
306 	    }
307 	    for (int i = 0; i < topLevelDomains.length; i++) {
308 		if (tld.equals(topLevelDomains[i])) {
309 		    return;
310 		}
311 	    }
312 	}
313 	Message message = new Message(Reporter.WARNING,
314 				      getString("unqualified", url.getHost()),
315 				      getString("unqualifiedExp"));
316 	Reporter.print(message, source);
317     }
318 
319     /**
320      * Check for use of an MD5 httpmd URL. If the protocol of <code>url</code>
321      * is httpmd, then the hash function identifier is parsed from the
322      * file component of <code>url</code> and a string comparison is done.
323      *
324      * @param url the <code>URL</code> to check
325      * @param source the source of the <code>URL</code>
326      */
327     private void checkForMD5(final URL url, String source) {
328 	if(! url.getProtocol().equalsIgnoreCase("httpmd")) {
329 	    return;
330 	}
331 	// can assume hashcode is present, or url construction would have failed
332 	String target = url.getFile();
333 	int lastSemi = target.lastIndexOf(';');
334 	String hash = target.substring(lastSemi + 1);
335 	if (hash.startsWith("md5")) {
336 	    Message message = new Message(Reporter.WARNING,
337 					  getString("usesmd5", url),
338 					  getString("usesmd5Exp"));
339 	    Reporter.print(message, source);
340 	}
341     }
342 
343     /**
344      * A <code>Runnable</code> which attempts to open a <code>URL</code>
345      * connection. If the attempt results in an exception being throw,
346      * the exception is stored for later access by the invoker of the thread.
347      */
348     private class URLAccessor implements Runnable {
349 
350 	URL url;
351 	Throwable exception = null;
352 	
353 	URLAccessor(URL url) {
354 	    this.url = url;
355 	}
356 
357 	public void run() {
358 	    try {
359 		URLConnection con = url.openConnection();
360 		InputStream stream = con.getInputStream();
361 	    } catch (IOException e) {
362 		exception = e;
363 	    }
364 	}
365 	
366 	Throwable getException() {
367 	    return exception;
368 	}
369     }  
370 
371     public static class GetURLTask implements SubVMTask {
372 
373 	public Object run(String[] args) {
374 	    String urlToken = args[0];
375 	    URL url;
376 	    try {
377 		url = new URL(urlToken);
378 	    } catch (MalformedURLException e) {
379 		int firstColon = urlToken.indexOf(':');
380 		if (firstColon > 0) {
381 		    String protocol = urlToken.substring(0, firstColon);
382 		    if (protocol.equalsIgnoreCase("httpmd")) {
383 			if (!httpmdHandlerInstalled()) {
384 			    return "nohandler";
385 			}
386 		    }
387 		}
388 		return e.getMessage(); 
389 	    }
390 	    return url;
391 	}
392 
393 	private boolean httpmdHandlerInstalled() {
394 	    try {
395 		new URL("httpmd://localhost/foo;sha=0");
396 	    } catch (MalformedURLException e) {
397 		return false;
398 	    }
399 	    return true;
400 	}
401     }
402 }
403 	
404