View Javadoc
1   /*
2    * MIT License
3    *
4    * Copyright (c) 2010-2022 The Waffle Project Contributors: https://github.com/Waffle/waffle/graphs/contributors
5    *
6    * Permission is hereby granted, free of charge, to any person obtaining a copy
7    * of this software and associated documentation files (the "Software"), to deal
8    * in the Software without restriction, including without limitation the rights
9    * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10   * copies of the Software, and to permit persons to whom the Software is
11   * furnished to do so, subject to the following conditions:
12   *
13   * The above copyright notice and this permission notice shall be included in all
14   * copies or substantial portions of the Software.
15   *
16   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22   * SOFTWARE.
23   */
24  package waffle.util;
25  
26  import com.sun.jna.Platform;
27  import com.sun.jna.platform.WindowUtils;
28  import com.sun.jna.platform.win32.LMJoin;
29  import com.sun.jna.platform.win32.Netapi32Util;
30  import com.sun.jna.platform.win32.Win32Exception;
31  
32  import java.awt.Desktop;
33  import java.io.File;
34  import java.io.IOException;
35  import java.io.StringWriter;
36  import java.nio.charset.StandardCharsets;
37  import java.nio.file.Files;
38  import java.nio.file.StandardOpenOption;
39  import java.util.ArrayList;
40  import java.util.Arrays;
41  import java.util.List;
42  
43  import javax.xml.XMLConstants;
44  import javax.xml.parsers.DocumentBuilderFactory;
45  import javax.xml.parsers.ParserConfigurationException;
46  import javax.xml.transform.OutputKeys;
47  import javax.xml.transform.Transformer;
48  import javax.xml.transform.TransformerException;
49  import javax.xml.transform.TransformerFactory;
50  import javax.xml.transform.dom.DOMSource;
51  import javax.xml.transform.stream.StreamResult;
52  
53  import org.slf4j.Logger;
54  import org.slf4j.LoggerFactory;
55  import org.w3c.dom.Document;
56  import org.w3c.dom.Element;
57  
58  import waffle.windows.auth.IWindowsAccount;
59  import waffle.windows.auth.IWindowsAuthProvider;
60  import waffle.windows.auth.IWindowsComputer;
61  import waffle.windows.auth.IWindowsDomain;
62  import waffle.windows.auth.impl.WindowsAccountImpl;
63  import waffle.windows.auth.impl.WindowsAuthProviderImpl;
64  
65  /**
66   * A Utility class to read system info to help troubleshoot WAFFLE system configuration.
67   *
68   * <pre>
69   * This utility class collects system information and returns it as an XML document.
70   * </pre>
71   *
72   * From the command line, you can write the info to stdout using:
73   *
74   * <pre>
75   * <code>
76   *   java -cp "jna.jar;waffle-core.jar;waffle-api.jar;jna-platform.jar;guava-21.0.jar" waffle.util.WaffleInfo
77   * </code>
78   * </pre>
79   *
80   * To show this information in a browser, run:
81   *
82   * <pre>
83   * <code>
84   *   java -cp "..." waffle.util.WaffleInfo -show
85   * </code>
86   * </pre>
87   *
88   * To lookup account names and return any listed info, run:
89   *
90   * <pre>
91   * <code>
92   *   java -cp "..." waffle.util.WaffleInfo -lookup AccountName
93   * </code>
94   * </pre>
95   */
96  public class WaffleInfo {
97  
98      /** The Constant LOGGER. */
99      private static final Logger LOGGER = LoggerFactory.getLogger(WaffleInfo.class);
100 
101     /**
102      * Get a Document with basic system information.
103      * <p>
104      * This uses the builtin javax.xml package even though the API is quite verbose
105      *
106      * @return Document with waffle info.
107      *
108      * @throws ParserConfigurationException
109      *             when getting new document builder.
110      */
111     public Document getWaffleInfo() throws ParserConfigurationException {
112         final DocumentBuilderFactory df = DocumentBuilderFactory.newInstance();
113         df.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
114         df.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
115         df.setExpandEntityReferences(false);
116 
117         final Document doc = df.newDocumentBuilder().newDocument();
118 
119         // create the root element and add it to the document
120         final Element root = doc.createElement("waffle");
121 
122         // Add Version Information as attributes
123         String version = WaffleInfo.class.getPackage().getImplementationVersion();
124         if (version != null) {
125             root.setAttribute("version", version);
126         }
127         version = Platform.class.getPackage().getImplementationVersion();
128         if (version != null) {
129             root.setAttribute("jna", version);
130         }
131         version = WindowUtils.class.getPackage().getImplementationVersion();
132         if (version != null) {
133             root.setAttribute("jna-platform", version);
134         }
135 
136         doc.appendChild(root);
137         root.appendChild(this.getAuthProviderInfo(doc));
138 
139         return doc;
140     }
141 
142     /**
143      * Gets the auth provider info.
144      *
145      * @param doc
146      *            the doc
147      *
148      * @return the auth provider info
149      */
150     protected Element getAuthProviderInfo(final Document doc) {
151         final IWindowsAuthProvider auth = new WindowsAuthProviderImpl();
152 
153         final Element node = doc.createElement("auth");
154         node.setAttribute("class", auth.getClass().getName());
155 
156         // Current User
157         Element child = doc.createElement("currentUser");
158         node.appendChild(child);
159 
160         final String currentUsername = WindowsAccountImpl.getCurrentUsername();
161         this.addAccountInfo(doc, child, new WindowsAccountImpl(currentUsername));
162 
163         // Computer
164         child = doc.createElement("computer");
165         node.appendChild(child);
166 
167         final IWindowsComputer c = auth.getCurrentComputer();
168         Element value = doc.createElement("computerName");
169         value.setTextContent(c.getComputerName());
170         child.appendChild(value);
171 
172         value = doc.createElement("memberOf");
173         value.setTextContent(c.getMemberOf());
174         child.appendChild(value);
175 
176         value = doc.createElement("joinStatus");
177         value.setTextContent(c.getJoinStatus());
178         child.appendChild(value);
179 
180         value = doc.createElement("groups");
181         Element g;
182         for (final String s : c.getGroups()) {
183             g = doc.createElement("group");
184             g.setTextContent(s);
185             value.appendChild(g);
186         }
187         child.appendChild(value);
188 
189         // Only Show Domains if we are in a Domain
190         if (Netapi32Util.getJoinStatus() == LMJoin.NETSETUP_JOIN_STATUS.NetSetupDomainName) {
191             child = doc.createElement("domains");
192             node.appendChild(child);
193 
194             Element d;
195             for (final IWindowsDomain domain : auth.getDomains()) {
196                 d = doc.createElement("domain");
197                 node.appendChild(d);
198 
199                 value = doc.createElement("FQN");
200                 value.setTextContent(domain.getFqn());
201                 child.appendChild(value);
202 
203                 value = doc.createElement("TrustTypeString");
204                 value.setTextContent(domain.getTrustTypeString());
205                 child.appendChild(value);
206 
207                 value = doc.createElement("TrustDirectionString");
208                 value.setTextContent(domain.getTrustDirectionString());
209                 child.appendChild(value);
210             }
211         }
212         return node;
213     }
214 
215     /**
216      * Adds the account info.
217      *
218      * @param doc
219      *            the doc
220      * @param node
221      *            the node
222      * @param account
223      *            the account
224      */
225     protected void addAccountInfo(final Document doc, final Element node, final IWindowsAccount account) {
226         Element value = doc.createElement("Name");
227         value.setTextContent(account.getName());
228         node.appendChild(value);
229 
230         value = doc.createElement("FQN");
231         value.setTextContent(account.getFqn());
232         node.appendChild(value);
233 
234         value = doc.createElement("Domain");
235         value.setTextContent(account.getDomain());
236         node.appendChild(value);
237 
238         value = doc.createElement("SID");
239         value.setTextContent(account.getSidString());
240         node.appendChild(value);
241     }
242 
243     /**
244      * Gets the lookup info.
245      *
246      * @param doc
247      *            the doc
248      * @param lookup
249      *            the lookup
250      *
251      * @return the lookup info
252      */
253     public Element getLookupInfo(final Document doc, final String lookup) {
254         final IWindowsAuthProvider auth = new WindowsAuthProviderImpl();
255         final Element node = doc.createElement("lookup");
256         node.setAttribute("name", lookup);
257         try {
258             this.addAccountInfo(doc, node, auth.lookupAccount(lookup));
259         } catch (final Win32Exception e) {
260             node.appendChild(WaffleInfo.getException(doc, e));
261         }
262         return node;
263     }
264 
265     /**
266      * Gets the exception.
267      *
268      * @param doc
269      *            the doc
270      * @param t
271      *            the t
272      *
273      * @return the exception
274      */
275     public static Element getException(final Document doc, final Exception t) {
276         final Element node = doc.createElement("exception");
277         node.setAttribute("class", t.getClass().getName());
278 
279         Element value = doc.createElement("message");
280         if (t.getMessage() != null) {
281             value.setTextContent(t.getMessage());
282             node.appendChild(value);
283         }
284 
285         value = doc.createElement("trace");
286         value.setTextContent(Arrays.toString(t.getStackTrace()));
287         node.appendChild(value);
288         return node;
289     }
290 
291     /**
292      * To pretty xml.
293      *
294      * @param doc
295      *            the doc
296      *
297      * @return the string
298      *
299      * @throws TransformerException
300      *             the transformer exception
301      */
302     public static String toPrettyXML(final Document doc) throws TransformerException {
303         // set up a transformer
304         final TransformerFactory transfac = TransformerFactory.newInstance();
305         transfac.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
306         transfac.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
307 
308         final Transformer trans = transfac.newTransformer();
309         trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
310         trans.setOutputProperty(OutputKeys.INDENT, "yes");
311 
312         // create string from xml tree
313         final StringWriter sw = new StringWriter();
314         final StreamResult result = new StreamResult(sw);
315         final DOMSource source = new DOMSource(doc);
316         trans.transform(source, result);
317         return sw.toString();
318     }
319 
320     /**
321      * Print system information.
322      *
323      * @param args
324      *            variable arguments to pass to main. Valid values are "-show" and "-lookup".
325      */
326     public static void main(final String[] args) {
327         boolean show = false;
328         final List<String> lookup = new ArrayList<>();
329         if (args != null) {
330             String arg;
331             for (int i = 0; i < args.length; i++) {
332                 arg = args[i];
333                 if (null != arg) {
334                     switch (arg) {
335                         case "-show":
336                             show = true;
337                             break;
338                         case "-lookup":
339                             lookup.add(args[++i]);
340                             break;
341                         default:
342                             WaffleInfo.LOGGER.error("Unknown Argument: {}", arg);
343                             throw new RuntimeException("Unknown Argument: " + arg);
344                     }
345                 }
346             }
347         }
348 
349         final WaffleInfo helper = new WaffleInfo();
350         try {
351             final Document info = helper.getWaffleInfo();
352             for (final String name : lookup) {
353                 info.getDocumentElement().appendChild(helper.getLookupInfo(info, name));
354             }
355 
356             final String xml = WaffleInfo.toPrettyXML(info);
357             final File f;
358             if (show) {
359                 f = Files.createTempFile("waffle-info-", ".xml").toFile();
360                 Files.write(f.toPath(), xml.getBytes(StandardCharsets.UTF_8), StandardOpenOption.APPEND);
361                 Desktop.getDesktop().open(f);
362             } else {
363                 WaffleInfo.LOGGER.info(xml);
364             }
365         } catch (final IOException | TransformerException | ParserConfigurationException e) {
366             WaffleInfo.LOGGER.error(e.getMessage());
367             WaffleInfo.LOGGER.trace("", e);
368         }
369     }
370 }