WaffleInfo.java

/**
 * Waffle (https://github.com/dblock/waffle)
 *
 * Copyright (c) 2010 - 2015 Application Security, Inc.
 *
 * 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:
 *     Application Security, Inc.
 */
package waffle.util;

import java.awt.Desktop;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import com.google.common.base.Charsets;
import com.google.common.io.Files;
import com.sun.jna.Platform;
import com.sun.jna.platform.WindowUtils;
import com.sun.jna.platform.win32.LMJoin;
import com.sun.jna.platform.win32.Netapi32Util;
import com.sun.jna.platform.win32.Win32Exception;

import waffle.windows.auth.IWindowsAccount;
import waffle.windows.auth.IWindowsAuthProvider;
import waffle.windows.auth.IWindowsComputer;
import waffle.windows.auth.IWindowsDomain;
import waffle.windows.auth.impl.WindowsAccountImpl;
import waffle.windows.auth.impl.WindowsAuthProviderImpl;

/**
 * A Utility class to read system info to help troubleshoot WAFFLE system configuration.
 * 
 * <pre>
 * This utility class collects system information and returns it as an XML document.
 * </pre>
 * 
 * From the command line, you can write the info to stdout using:
 * 
 * <pre>
 * <code>
 *   java -cp "jna.jar;waffle-core.jar;waffle-api.jar;jna-platform.jar;guava-17.0.jar" waffle.util.WaffleInfo
 * </code>
 * </pre>
 * 
 * To show this information in a browser, run:
 * 
 * <pre>
 * <code>
 *   java -cp "..." waffle.util.WaffleInfo -show
 * </code>
 * </pre>
 * 
 * To lookup account names and return any listed info, run:
 * 
 * <pre>
 * <code>
 *   java -cp "..." waffle.util.WaffleInfo -lookup AccountName
 * </code>
 * </pre>
 * 
 */
public class WaffleInfo {

    /** The Constant LOGGER. */
    private static final Logger LOGGER = LoggerFactory.getLogger(WaffleInfo.class);

    /**
     * Get a Document with basic system information
     * 
     * This uses the builtin javax.xml package even though the API is quite verbose
     * 
     * @return Document with waffle info.
     * 
     * @throws ParserConfigurationException
     *             when getting new document builder.
     */
    public Document getWaffleInfo() throws ParserConfigurationException {
        final Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();

        // create the root element and add it to the document
        final Element root = doc.createElement("waffle");

        // Add Version Information as attributes
        String version = WaffleInfo.class.getPackage().getImplementationVersion();
        if (version != null) {
            root.setAttribute("version", version);
        }
        version = Platform.class.getPackage().getImplementationVersion();
        if (version != null) {
            root.setAttribute("jna", version);
        }
        version = WindowUtils.class.getPackage().getImplementationVersion();
        if (version != null) {
            root.setAttribute("jna-platform", version);
        }

        doc.appendChild(root);
        root.appendChild(this.getAuthProviderInfo(doc));

        return doc;
    }

    /**
     * Gets the auth provider info.
     *
     * @param doc
     *            the doc
     * @return the auth provider info
     */
    protected Element getAuthProviderInfo(final Document doc) {
        final IWindowsAuthProvider auth = new WindowsAuthProviderImpl();

        final Element node = doc.createElement("auth");
        node.setAttribute("class", auth.getClass().getName());

        // Current User
        Element child = doc.createElement("currentUser");
        node.appendChild(child);

        final String currentUsername = WindowsAccountImpl.getCurrentUsername();
        this.addAccountInfo(doc, child, new WindowsAccountImpl(currentUsername));

        // Computer
        child = doc.createElement("computer");
        node.appendChild(child);

        final IWindowsComputer c = auth.getCurrentComputer();
        Element value = doc.createElement("computerName");
        value.setTextContent(c.getComputerName());
        child.appendChild(value);

        value = doc.createElement("memberOf");
        value.setTextContent(c.getMemberOf());
        child.appendChild(value);

        value = doc.createElement("joinStatus");
        value.setTextContent(c.getJoinStatus());
        child.appendChild(value);

        value = doc.createElement("groups");
        Element g;
        for (final String s : c.getGroups()) {
            g = doc.createElement("group");
            g.setTextContent(s);
            value.appendChild(g);
        }
        child.appendChild(value);

        // Only Show Domains if we are in a Domain
        if (Netapi32Util.getJoinStatus() == LMJoin.NETSETUP_JOIN_STATUS.NetSetupDomainName) {
            child = doc.createElement("domains");
            node.appendChild(child);

            Element d;
            for (final IWindowsDomain domain : auth.getDomains()) {
                d = doc.createElement("domain");
                node.appendChild(d);

                value = doc.createElement("FQN");
                value.setTextContent(domain.getFqn());
                child.appendChild(value);

                value = doc.createElement("TrustTypeString");
                value.setTextContent(domain.getTrustTypeString());
                child.appendChild(value);

                value = doc.createElement("TrustDirectionString");
                value.setTextContent(domain.getTrustDirectionString());
                child.appendChild(value);
            }
        }
        return node;
    }

    /**
     * Adds the account info.
     *
     * @param doc
     *            the doc
     * @param node
     *            the node
     * @param account
     *            the account
     */
    protected void addAccountInfo(final Document doc, final Element node, final IWindowsAccount account) {
        Element value = doc.createElement("Name");
        value.setTextContent(account.getName());
        node.appendChild(value);

        value = doc.createElement("FQN");
        value.setTextContent(account.getFqn());
        node.appendChild(value);

        value = doc.createElement("Domain");
        value.setTextContent(account.getDomain());
        node.appendChild(value);

        value = doc.createElement("SID");
        value.setTextContent(account.getSidString());
        node.appendChild(value);
    }

    /**
     * Gets the lookup info.
     *
     * @param doc
     *            the doc
     * @param lookup
     *            the lookup
     * @return the lookup info
     */
    public Element getLookupInfo(final Document doc, final String lookup) {
        final IWindowsAuthProvider auth = new WindowsAuthProviderImpl();
        final Element node = doc.createElement("lookup");
        node.setAttribute("name", lookup);
        try {
            this.addAccountInfo(doc, node, auth.lookupAccount(lookup));
        } catch (final Win32Exception ex) {
            node.appendChild(WaffleInfo.getException(doc, ex));
        }
        return node;
    }

    /**
     * Gets the exception.
     *
     * @param doc
     *            the doc
     * @param t
     *            the t
     * @return the exception
     */
    public static Element getException(final Document doc, final Exception t) {
        final Element node = doc.createElement("exception");
        node.setAttribute("class", t.getClass().getName());

        Element value = doc.createElement("message");
        if (t.getMessage() != null) {
            value.setTextContent(t.getMessage());
            node.appendChild(value);
        }

        value = doc.createElement("trace");
        value.setTextContent(Arrays.toString(t.getStackTrace()));
        node.appendChild(value);
        return node;
    }

    /**
     * To pretty xml.
     *
     * @param doc
     *            the doc
     * @return the string
     * @throws TransformerException
     *             the transformer exception
     */
    public static String toPrettyXML(final Document doc) throws TransformerException {
        // set up a transformer
        final TransformerFactory transfac = TransformerFactory.newInstance();
        final Transformer trans = transfac.newTransformer();
        trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
        trans.setOutputProperty(OutputKeys.INDENT, "yes");

        // create string from xml tree
        final StringWriter sw = new StringWriter();
        final StreamResult result = new StreamResult(sw);
        final DOMSource source = new DOMSource(doc);
        trans.transform(source, result);
        return sw.toString();
    }

    /**
     * Print system information.
     * 
     * @param args
     *            variable arguments to pass to main. Valid values are "-show" and "-lookup".
     */
    public static void main(final String[] args) {
        boolean show = false;
        final List<String> lookup = new ArrayList<String>();
        if (args != null) {
            String arg;
            for (int i = 0; i < args.length; i++) {
                arg = args[i];
                if ("-show".equals(arg)) {
                    show = true;
                } else if ("-lookup".equals(arg)) {
                    lookup.add(args[++i]);
                } else {
                    WaffleInfo.LOGGER.error("Unknown Argument: {}", arg);
                    throw new RuntimeException("Unknown Argument: " + arg);
                }
            }
        }

        final WaffleInfo helper = new WaffleInfo();
        try {
            final Document info = helper.getWaffleInfo();
            for (final String name : lookup) {
                info.getDocumentElement().appendChild(helper.getLookupInfo(info, name));
            }

            final String xml = WaffleInfo.toPrettyXML(info);
            final File f;
            if (show) {
                f = File.createTempFile("waffle-info-", ".xml");
                Files.write(xml, f, Charsets.UTF_8);
                Desktop.getDesktop().open(f);
            } else {
                WaffleInfo.LOGGER.info(xml);
            }
        } catch (final IOException e) {
            WaffleInfo.LOGGER.error(e.getMessage());
            WaffleInfo.LOGGER.trace("{}", e);
        } catch (final TransformerException e) {
            WaffleInfo.LOGGER.error(e.getMessage());
            WaffleInfo.LOGGER.trace("{}", e);
        } catch (final ParserConfigurationException e) {
            WaffleInfo.LOGGER.error(e.getMessage());
            WaffleInfo.LOGGER.trace("{}", e);
        }
    }
}