/* * NOTE: This copyright does *not* cover user programs that use HQ * program services by normal system calls through the application * program interfaces provided as part of the Hyperic Plug-in Development * Kit or the Hyperic Client Development Kit - this is merely considered * normal use of the program, and does *not* fall under the heading of * "derived work". * * Copyright (C) [2004-2013], VMware, Inc. * This file is part of HQ. * * HQ is free software; you can redistribute it and/or modify * it under the terms version 2 of the GNU General Public License as * published by the Free Software Foundation. This program is distributed * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA. */ package org.hyperic.hq.plugin.netservices; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.Header; import org.apache.http.HttpMessage; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.ParseException; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpHead; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.params.ClientPNames; import org.apache.http.impl.auth.BasicScheme; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.message.BasicNameValuePair; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.CoreProtocolPNames; import org.apache.http.params.HttpParams; import org.apache.http.util.EntityUtils; import org.hyperic.hq.agent.AgentKeystoreConfig; import org.hyperic.hq.common.shared.ProductProperties; import org.hyperic.hq.product.LogTrackPlugin; import org.hyperic.hq.product.Metric; import org.hyperic.hq.product.PluginException; import org.hyperic.util.http.HQHttpClient; import org.hyperic.util.http.HttpConfig; import org.springframework.util.StringUtils; public class HTTPCollector extends SocketChecker { private static final Log log = LogFactory.getLog(HTTPCollector.class); private AtomicBoolean isPingCompat = new AtomicBoolean(); private AtomicReference<String> url = new AtomicReference<String>(); private AtomicReference<String> method = new AtomicReference<String>(); private AtomicReference<String> hosthdr = new AtomicReference<String>(); private AtomicReference<String> useragent = new AtomicReference<String>(); private AtomicReference<Pattern> pattern = new AtomicReference<Pattern>(); private List<String> matches = new ArrayList<String>(); private AtomicReference<String> proxyHost = new AtomicReference<String>(); private AtomicInteger proxyPort = new AtomicInteger(8080); private AtomicReference<Map<String, String>> params = new AtomicReference<Map<String,String>>(); protected void init() throws PluginException { super.init(); Properties props = getProperties(); boolean isSSL = isSSL(); String protocol = props.getProperty(PROP_PROTOCOL, isSSL ? PROTOCOL_HTTPS : PROTOCOL_HTTP); // back compat w/ old url.availability templates isPingCompat.set(protocol.equals("ping")); if (isPingCompat.get()) { return; } method.set(props.getProperty(PROP_METHOD, METHOD_HEAD)); hosthdr.set(props.getProperty("hostheader")); setParams(props); try { URL url = new URL(protocol, super.getHostname(), super.getPort(), getPath()); this.url.set(url.toString()); } catch (MalformedURLException e) { throw new PluginException(e); } useragent.set(getPlugin().getManagerProperty("http.useragent")); if (useragent.get() == null || useragent.get().trim().length() == 0) { useragent.set("Hyperic-HQ-Agent/" + ProductProperties.getVersion()); } // for log_track setSource(url.get()); // to allow self-signed server certs if (isSSL) { // Try to get grab and accept the certificate try { getSocketWrapper(true); } catch (IOException e) { log.warn(e); // ...log it but probably going to be a problem later... } } String pattern = props.getProperty("pattern"); if (pattern != null) { this.pattern.set(Pattern.compile(pattern)); } String proxy = props.getProperty("proxy"); if (proxy != null) { setSource(getSource() + " [via " + proxy + "]"); int ix = proxy.indexOf(':'); if (ix != -1) { this.proxyPort.set(Integer.parseInt(proxy.substring(ix + 1))); proxy = proxy.substring(0, ix); } this.proxyHost.set(proxy); } collect(); if (getLogLevel() == LogTrackPlugin.LOGLEVEL_ERROR) { throw new PluginException(getMessage()); } } protected boolean isProxied() { return (StringUtils.hasText(proxyHost.get()) && proxyPort.get() != -1); } // Used by NetServicesCollector to determine the Socket address @Override public String getHostname() { if (isProxied()) { return proxyHost.get(); } else { return super.getHostname(); } } // Used by NetServicesCollector to determine the Socket port @Override public int getPort() { if (isProxied()) { return proxyPort.get(); } else { return super.getPort(); } } protected String getURL() { return url.get(); } protected void setURL(String url) { this.url.set(url); } protected String getMethod() { return method.get(); } protected void setMethod(String method) { this.method.set(method); } private double getAvail(int code) { // There are too many options to list everything that is // successful. So, instead we are going to call out the // things that should be considered failure, everything else // is OK. switch (code) { case HttpURLConnection.HTTP_BAD_REQUEST: case HttpURLConnection.HTTP_FORBIDDEN: case HttpURLConnection.HTTP_NOT_FOUND: case HttpURLConnection.HTTP_BAD_METHOD: case HttpURLConnection.HTTP_CLIENT_TIMEOUT: case HttpURLConnection.HTTP_CONFLICT: case HttpURLConnection.HTTP_PRECON_FAILED: case HttpURLConnection.HTTP_ENTITY_TOO_LARGE: case HttpURLConnection.HTTP_REQ_TOO_LONG: case HttpURLConnection.HTTP_INTERNAL_ERROR: case HttpURLConnection.HTTP_NOT_IMPLEMENTED: case HttpURLConnection.HTTP_UNAVAILABLE: case HttpURLConnection.HTTP_VERSION: case HttpURLConnection.HTTP_BAD_GATEWAY: case HttpURLConnection.HTTP_GATEWAY_TIMEOUT: return Metric.AVAIL_DOWN; default: } if (hasCredentials()) { if (code == HttpURLConnection.HTTP_UNAUTHORIZED) { return Metric.AVAIL_DOWN; } } return Metric.AVAIL_UP; } // allow response to have metrics, must be: // Content-Type: text/plain // Content-Length: <= 8192 DRC: Why the limitation? // XXX flag to always disable and/or change these checks protected void parseResults(HttpResponse response) { Header length = response.getFirstHeader("Content-Length"); Header type = response.getFirstHeader("Content-Type"); if (type == null || !type.getValue().equals("text/plain")) { return; } if (length != null) { try { if (Integer.parseInt(length.getValue()) > 8192) { return; } } catch (NumberFormatException e) { return; } } try { parseResults(EntityUtils.toString(response.getEntity(), "UTF-8")); } catch (ParseException e) { setErrorMessage("Exception parsing response: " + e.getMessage(), e); } catch (IOException e) { setErrorMessage("Exception reading response stream: " + e.getMessage(), e); } } private boolean matchResponse(HttpResponse response) { String body; try { body = EntityUtils.toString(response.getEntity(), "UTF-8"); body = body == null ? "" : body; if (log.isDebugEnabled()) { log.debug("attempting to match pattern=" + pattern.get() + " against response body=" + body); } } catch (ParseException e) { setErrorMessage("Exception parsing response: " + e, e); return false; } catch (IOException e) { setErrorMessage("Exception reading response stream: " + e, e); return false; } Matcher matcher = this.pattern.get().matcher(body); boolean matches = false; while (matcher.find()) { matches = true; int count = matcher.groupCount(); // skip group(0): // "Group zero denotes the entire pattern by convention" for (int i = 1; i <= count; i++) { this.matches.add(matcher.group(i)); } } if (matches) { if (log.isDebugEnabled()) { log.debug("pattern='" + pattern.get() + "' matches"); } return true; } else { if (log.isDebugEnabled()) { log.debug("pattern='" + pattern.get() + "' does not match"); } setWarningMessage("Response (length=" + body.length() + ") does not match " + pattern.get()); return false; } } public void collect() { if (isPingCompat.get()) { // back compat w/ old url.availability templates super.collect(); return; } this.matches.clear(); HttpConfig config = new HttpConfig(getTimeoutMillis(), getTimeoutMillis(), proxyHost.get(), proxyPort.get()); AgentKeystoreConfig keystoreConfig = new AgentKeystoreConfig(); log.debug("isAcceptUnverifiedCert:"+keystoreConfig.isAcceptUnverifiedCert()); HQHttpClient client = new HQHttpClient (keystoreConfig, config, keystoreConfig.isAcceptUnverifiedCert()); HttpParams params = client.getParams(); params.setParameter(CoreProtocolPNames.USER_AGENT, useragent.get()); if (this.hosthdr != null) { params.setParameter(ClientPNames.VIRTUAL_HOST, this.hosthdr); } HttpRequestBase request; double avail = 0; try { if (getMethod().equals(HttpHead.METHOD_NAME)) { request = new HttpHead(getURL()); addParams(request, this.params.get()); } else if (getMethod().equals(HttpPost.METHOD_NAME)) { HttpPost httpPost = new HttpPost(getURL()); request = httpPost; addParams(httpPost, this.params.get()); } else { request = new HttpGet(getURL()); addParams(request, this.params.get()); } request.getParams().setParameter(ClientPNames.HANDLE_REDIRECTS, isFollow()); addCredentials(request, client); startTime(); HttpResponse response = client.execute(request); int statusCode = response.getStatusLine().getStatusCode(); int tries = 0; while (statusCode == HttpURLConnection.HTTP_MOVED_TEMP && tries < 3) { tries++; Header header = response.getFirstHeader("Location"); String url = header.getValue(); String[] toks = url.split(";"); String[] t = toks.length > 1 ? toks[1].split("\\?") : new String[0]; response = getRedirect(toks[0], t.length > 1 ? t[1] : ""); statusCode = response.getStatusLine().getStatusCode(); } endTime(); setResponseCode(statusCode); avail = getAvail(statusCode); StringBuilder msg = new StringBuilder(String.valueOf(statusCode)); Header header = response.getFirstHeader("Server"); msg.append(header != null ? " (" + header.getValue() + ")" : ""); setLastModified(response, statusCode); avail = checkPattern(response, avail, msg); setAvailMsg(avail, msg); } catch (UnsupportedEncodingException e) { log.error("unsupported encoding: " + e, e); } catch (IOException e) { avail = Metric.AVAIL_DOWN; setErrorMessage(e.toString()); } finally { setAvailability(avail); } netstat(); } private void setAvailMsg(double avail, StringBuilder msg) { if (avail == Metric.AVAIL_UP) { if (this.matches.size() != 0) { setInfoMessage(msg.toString()); } else { setDebugMessage(msg.toString()); } } else if (avail == Metric.AVAIL_WARN) { setWarningMessage(msg.toString()); } else { setErrorMessage(msg.toString()); } } private void setLastModified(HttpMessage response, int statusCode) { long lastModified = 0; Header header = response.getFirstHeader("Last-Modified"); if (header != null) { try { DateFormat format = new SimpleDateFormat(); // TODO lock down the expected format (wasn't specified in orig code... lastModified = format.parse(header.getValue()).getTime(); } catch (java.text.ParseException e) { log.error(e, e); } } else if (statusCode == 200) { lastModified = System.currentTimeMillis(); } if (lastModified != 0) { setValue("LastModified", lastModified); } } private double checkPattern(HttpResponse response, double avail, StringBuilder msg) { if (!getMethod().equals(HttpHead.METHOD_NAME) && (avail == Metric.AVAIL_UP)) { if (pattern.get() != null) { if (!matchResponse(response)) { avail = Metric.AVAIL_WARN; } else if (matches.size() != 0) { msg.append(" match results=").append(matches); } } else { parseResults(response); } } return avail; } private Map<String, String> getArgs(String args) { Map<String, String> params = new HashMap<String, String>(); String[] toks = args.split("&"); for (String t : toks) { String[] pair = t.split("="); if (pair.length < 2) { continue; } params.put(pair[0], pair[1]); } return params; } private HttpResponse getRedirect(String url, String args) throws ClientProtocolException, IOException { HttpConfig config = new HttpConfig(getTimeoutMillis(), getTimeoutMillis(), proxyHost.get(), proxyPort.get()); AgentKeystoreConfig keystoreConfig = new AgentKeystoreConfig(); HQHttpClient client = new HQHttpClient (keystoreConfig, config, keystoreConfig.isAcceptUnverifiedCert()); HttpParams params = client.getParams(); params.setParameter(CoreProtocolPNames.USER_AGENT, useragent.get()); if (this.hosthdr != null) { params.setParameter(ClientPNames.VIRTUAL_HOST, this.hosthdr); } HttpRequestBase request; request = new HttpGet(url); addParams(request, getArgs(args)); addCredentials(request, client); return client.execute(request); } private void addCredentials(HttpMessage request, DefaultHttpClient client) { if (hasCredentials()) { UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(getUsername(), getPassword()); String realm = getProperties().getProperty("realm", ""); if (realm.length() == 0) { // send header w/o challenge Header authenticationHeader = BasicScheme.authenticate(credentials, "UTF-8", isProxied()); request.addHeader(authenticationHeader); } else { String authenticationHost = (hosthdr.get() == null) ? super.getHostname() : hosthdr.get(); AuthScope authScope = new AuthScope(authenticationHost, -1, realm); ((DefaultHttpClient) client).getCredentialsProvider().setCredentials(authScope, credentials); request.getParams().setParameter(ClientPNames.HANDLE_AUTHENTICATION, true); } } } private void setParams(Properties props) { String secretparams = props.getProperty("secretrequestparams", ""); String params = props.getProperty("requestparams", "") + "," + secretparams; params = params.trim(); if (params.length() == 0 || params.equals(",")) { this.params.set(null); return; } this.params.set(new HashMap<String, String>()); String[] toks = params.split(","); for (String tok : toks) { String[] pair = tok.split("="); if (pair.length != 2) { log.warn("specified params do not match the proper pattern: " + tok); continue; } this.params.get().put(pair[0], pair[1]); } } private void addParams(HttpRequestBase request, Map<String, String> params) throws UnsupportedEncodingException { if (params != null && !params.isEmpty()) { BasicHttpParams prms = new BasicHttpParams(); for (Map.Entry<String, String> entry : params.entrySet()) { prms.setParameter(entry.getKey(), entry.getValue()); } request.setParams(prms); } } private void addParams(HttpEntityEnclosingRequestBase request, Map<String, String> params) throws UnsupportedEncodingException { if (params != null && !params.isEmpty()) { List<NameValuePair> postParams = new ArrayList<NameValuePair>(); BasicHttpParams prms = new BasicHttpParams(); for (Map.Entry<String, String> entry : params.entrySet()) { postParams.add(new BasicNameValuePair(entry.getKey(), entry.getValue())); prms.setParameter(entry.getKey(), entry.getValue()); } request.setEntity(new UrlEncodedFormEntity(postParams, "UTF-8")); request.setParams(prms); } } }