001 /*
002 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003 *
004 * Copyright (c) 1997-2011 Oracle and/or its affiliates. All rights reserved.
005 *
006 * The contents of this file are subject to the terms of either the GNU
007 * General Public License Version 2 only ("GPL") or the Common Development
008 * and Distribution License("CDDL") (collectively, the "License"). You
009 * may not use this file except in compliance with the License. You can
010 * obtain a copy of the License at
011 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
012 * or packager/legal/LICENSE.txt. See the License for the specific
013 * language governing permissions and limitations under the License.
014 *
015 * When distributing the software, include this License Header Notice in each
016 * file and include the License file at packager/legal/LICENSE.txt.
017 *
018 * GPL Classpath Exception:
019 * Oracle designates this particular file as subject to the "Classpath"
020 * exception as provided by Oracle in the GPL Version 2 section of the License
021 * file that accompanied this code.
022 *
023 * Modifications:
024 * If applicable, add the following below the License Header, with the fields
025 * enclosed by brackets [] replaced by your own identifying information:
026 * "Portions Copyright [year] [name of copyright owner]"
027 *
028 * Contributor(s):
029 * If you wish your version of this file to be governed by only the CDDL or
030 * only the GPL Version 2, indicate your decision by adding "[Contributor]
031 * elects to include this software in this distribution under the [CDDL or GPL
032 * Version 2] license." If you don't indicate a single choice of license, a
033 * recipient has the option to distribute your version of this file under
034 * either the CDDL, the GPL Version 2 or to extend the choice of license to
035 * its licensees as provided above. However, if you add GPL Version 2 code
036 * and therefore, elected the GPL Version 2 license, then the option applies
037 * only if the new code is made subject to such option by the copyright
038 * holder.
039 */
040
041 package com.sun.enterprise.admin.cli.remote;
042
043 import java.io.*;
044 import java.net.*;
045 import java.util.*;
046 import java.util.logging.Level;
047 import java.util.logging.Logger;
048
049 import org.jvnet.hk2.component.*;
050 import com.sun.enterprise.module.*;
051 import com.sun.enterprise.module.single.StaticModulesRegistry;
052
053 import org.glassfish.api.admin.*;
054 import org.glassfish.api.admin.CommandModel.ParamModel;
055 import org.glassfish.common.util.admin.ManPageFinder;
056
057 import com.sun.appserv.management.client.prefs.LoginInfo;
058 import com.sun.appserv.management.client.prefs.LoginInfoStore;
059 import com.sun.appserv.management.client.prefs.LoginInfoStoreFactory;
060 import com.sun.appserv.management.client.prefs.StoreException;
061 import com.sun.enterprise.universal.i18n.LocalStringsImpl;
062 import com.sun.enterprise.admin.remote.RemoteAdminCommand;
063 import com.sun.enterprise.admin.cli.*;
064 import com.sun.enterprise.admin.cli.ProgramOptions.PasswordLocation;
065 import com.sun.enterprise.admin.util.*;
066 import com.sun.enterprise.admin.util.CommandModelData.ParamModelData;
067 import com.sun.enterprise.util.SystemPropertyConstants;
068
069 /**
070 * A remote command handled by the asadmin CLI.
071 */
072 public class RemoteCommand extends CLICommand {
073
074 private static final LocalStringsImpl strings =
075 new LocalStringsImpl(RemoteCommand.class);
076
077 // return output string rather than printing it
078 private boolean returnOutput = false;
079 private String output;
080 private boolean returnAttributes = false;
081 private Map<String, String> attrs;
082 private String usage;
083
084 private String responseFormatType;
085 private OutputStream userOut;
086 private File outputDir;
087
088 private CLIRemoteAdminCommand rac;
089
090 /**
091 * A special RemoteAdminCommand that overrides methods so that
092 * we can handle the interactive requirements of a CLI command.
093 */
094 private class CLIRemoteAdminCommand extends RemoteAdminCommand {
095 /**
096 * Construct a new remote command object. The command and arguments
097 * are supplied later using the execute method in the superclass.
098 */
099 public CLIRemoteAdminCommand(String name, String host, int port,
100 boolean secure, String user, String password, Logger logger,
101 String authToken)
102 throws CommandException {
103 super(name, host, port, secure, user, password, logger, authToken, true /* prohibitDirectoryUploads */);
104 }
105
106 /**
107 * If we're interactive, prompt for a new username and password.
108 * Return true if we're successful in collecting new information
109 * (and thus the caller should try the request again).
110 */
111 @Override
112 protected boolean updateAuthentication() {
113 Console cons;
114 if (programOpts.isInteractive() &&
115 (cons = System.console()) != null) {
116 // if appropriate, tell the user why authentication failed
117 PasswordLocation pwloc = programOpts.getPasswordLocation();
118 if (pwloc == PasswordLocation.PASSWORD_FILE) {
119 logger.fine(strings.get("BadPasswordFromFile",
120 programOpts.getPasswordFile()));
121 } else if (pwloc == PasswordLocation.LOGIN_FILE) {
122 try {
123 LoginInfoStore store =
124 LoginInfoStoreFactory.getDefaultStore();
125 logger.fine(strings.get("BadPasswordFromLogin",
126 store.getName()));
127 } catch (StoreException ex) {
128 // ignore it
129 }
130 }
131
132 String user = null;
133 // only prompt for a user name if the user name is set to
134 // the default. otherwise, assume the user specified the
135 // correct username to begin with and all we need is the
136 // password.
137 if (programOpts.getUser() == null) {
138 cons.printf("%s ", strings.get("AdminUserPrompt"));
139 user = cons.readLine();
140 if (user == null)
141 return false;
142 }
143 String password;
144 String puser = ok(user) ? user : programOpts.getUser();
145 if (ok(puser))
146 password = readPassword(
147 strings.get("AdminUserPasswordPrompt", puser));
148 else
149 password = readPassword(strings.get("AdminPasswordPrompt"));
150 if (password == null)
151 return false;
152 if (ok(user)) { // if none entered, don't change
153 programOpts.setUser(user);
154 this.user = user;
155 }
156 programOpts.setPassword(password, PasswordLocation.USER);
157 this.password = password;
158 return true;
159 }
160 return false;
161 }
162
163 /**
164 * Get from environment.
165 */
166 @Override
167 protected String getFromEnvironment(String name) {
168 return env.getStringOption(name);
169 }
170
171 /**
172 * Called when a non-secure connection attempt fails and it appears
173 * that the server requires a secure connection.
174 * Tell the user that we're retrying.
175 */
176 @Override
177 protected boolean retryUsingSecureConnection(String host, int port) {
178 String msg = strings.get("ServerMaybeSecure", host, port + "");
179 logger.info(msg);
180 return true;
181 }
182
183 /**
184 * Return the error message to be used in the AuthenticationException.
185 * Subclasses can override to provide a more detailed message, for
186 * example, indicating the source of the password that failed.
187 */
188 @Override
189 protected String reportAuthenticationException() {
190 String msg = null;
191 PasswordLocation pwloc =
192 programOpts.getPasswordLocation();
193 if (pwloc == PasswordLocation.PASSWORD_FILE) {
194 msg = strings.get("InvalidCredentialsFromFile",
195 programOpts.getUser(),
196 programOpts.getPasswordFile());
197 } else if (pwloc == PasswordLocation.LOGIN_FILE) {
198 try {
199 LoginInfoStore store =
200 LoginInfoStoreFactory.getDefaultStore();
201 msg = strings.get("InvalidCredentialsFromLogin",
202 programOpts.getUser(),
203 store.getName());
204 } catch (StoreException ex) {
205 // ignore it
206 }
207 }
208
209 if (msg == null)
210 msg = strings.get("InvalidCredentials", programOpts.getUser());
211 return msg;
212 }
213 }
214
215 /**
216 * A class loader for the "modules" directory.
217 */
218 private static ClassLoader moduleClassLoader;
219
220 /**
221 * A habitat just for finding man pages.
222 */
223 private static Habitat manHabitat;
224
225 /**
226 * Construct a new remote command object. The command and arguments
227 * are supplied later using the execute method in the superclass.
228 */
229 public RemoteCommand() throws CommandException {
230 super();
231 }
232
233 /**
234 * Construct a new remote command object. The command and arguments
235 * are supplied later using the execute method in the superclass.
236 */
237 public RemoteCommand(String name, ProgramOptions po, Environment env)
238 throws CommandException {
239 super(name, po, env);
240 }
241
242 /**
243 * Construct a new remote command object. The command and arguments
244 * are supplied later using the execute method in the superclass.
245 * This variant is used by the RemoteDeploymentFacility class to
246 * control and capture the output.
247 */
248 public RemoteCommand(String name, ProgramOptions po, Environment env,
249 String responseFormatType, OutputStream userOut)
250 throws CommandException {
251 this(name, po, env);
252 this.responseFormatType = responseFormatType;
253 this.userOut = userOut;
254 }
255
256 /**
257 * Set the directory in which any returned files will be stored.
258 * The default is the user's home directory.
259 */
260 public void setFileOutputDirectory(File dir) {
261 outputDir = dir;
262 }
263
264 @Override
265 protected void prepare()
266 throws CommandException, CommandValidationException {
267 try {
268 processProgramOptions();
269
270 initializeAuth();
271
272 /*
273 * Now we have all the information we need to create
274 * the remote admin command object.
275 */
276 initializeRemoteAdminCommand();
277
278 if (responseFormatType != null)
279 rac.setResponseFormatType(responseFormatType);
280 if (userOut != null)
281 rac.setUserOut(userOut);
282
283 /*
284 * If this is a help request, we don't need the command
285 * metadata and we throw away all the other options and
286 * fake everything else.
287 */
288 if (programOpts.isHelp()) {
289 commandModel = helpModel();
290 rac.setCommandModel(commandModel);
291 return;
292 }
293
294 /*
295 * Find the metadata for the command.
296 */
297 commandModel = rac.getCommandModel();
298 } catch (CommandException cex) {
299 logger.finer("RemoteCommand.prepare throws " + cex);
300 throw cex;
301 } catch (Exception e) {
302 logger.finer("RemoteCommand.prepare throws " + e);
303 throw new CommandException(e.getMessage());
304 }
305 }
306
307 /**
308 * If it's a help request, don't prompt for any missing options.
309 */
310 @Override
311 protected void validate()
312 throws CommandException, CommandValidationException {
313 if (programOpts.isHelp())
314 return;
315 super.validate();
316 }
317
318 /**
319 * We do all our help processing in executeCommand.
320 */
321 @Override
322 protected boolean checkHelp()
323 throws CommandException, CommandValidationException {
324 return false;
325 }
326
327 /**
328 * Runs the command using the specified arguments.
329 */
330 @Override
331 protected int executeCommand()
332 throws CommandException, CommandValidationException {
333 try {
334 options.set("DEFAULT", operands);
335 output = rac.executeCommand(options);
336 if (returnAttributes)
337 attrs = rac.getAttributes();
338 else if (!returnOutput) {
339 if (output.length() > 0) {
340 logger.info(output);
341 }
342 }
343 } catch (CommandException ex) {
344 // if a --help request failed, try to emulate it locally
345 if (programOpts.isHelp()) {
346 Reader r = getLocalManPage();
347 if (r != null) {
348 try {
349 BufferedReader br = new BufferedReader(r);
350 PrintWriter pw = new PrintWriter(System.out);
351 char[] buf = new char[8192];
352 int cnt;
353 while ((cnt = br.read(buf)) > 0)
354 pw.write(buf, 0, cnt);
355 pw.flush();
356 return SUCCESS;
357 } catch (IOException ioex2) {
358 // ignore it and throw original exception
359 } finally {
360 try {
361 r.close();
362 } catch (IOException ioex3) {
363 // ignore it
364 }
365 }
366 }
367 }
368 throw ex;
369 }
370 final Map<String,String> racAttrs = rac.getAttributes();
371 String returnVal = racAttrs != null ? racAttrs.get("exit-code") : null;
372 if(returnVal != null && "WARNING".equals(returnVal))
373 return WARNING;
374 return SUCCESS;
375 }
376
377
378 /**
379 * Execute the command and return the output as a string
380 * instead of writing it out.
381 */
382 public String executeAndReturnOutput(String... args)
383 throws CommandException, CommandValidationException {
384 /*
385 * Tell the low level output processing to just save the output
386 * string instead of writing it out. Yes, this is pretty gross.
387 */
388 returnOutput = true;
389 execute(args);
390 returnOutput = false;
391 return output;
392 }
393
394 /**
395 * Execute the command and return the main attributes from the manifest
396 * instead of writing out the output.
397 */
398 public Map<String, String> executeAndReturnAttributes(String... args)
399 throws CommandException, CommandValidationException {
400 /*
401 * Tell the low level output processing to just save the attributes
402 * instead of writing out the output. Yes, this is pretty gross.
403 */
404 returnAttributes = true;
405 execute(args);
406 returnAttributes = false;
407 return attrs;
408 }
409
410 /**
411 * Get the usage text.
412 * If we got usage information from the server, use it.
413 *
414 * @return usage text
415 */
416 public String getUsage() {
417 if (usage == null) {
418 if (rac == null) {
419 /*
420 * We weren't able to initialize the RemoteAdminCommand
421 * object, probably because we failed to parse the program
422 * options. With no ability to contact the remote server,
423 * we can't provide any command-specific usage information.
424 * Sigh.
425 */
426 return strings.get("Usage.asadmin.full", getName());
427 }
428 usage = rac.getUsage();
429 }
430 if (usage == null)
431 return super.getUsage();
432
433 StringBuilder usageText = new StringBuilder();
434 usageText.append(strings.get("Usage", strings.get("Usage.asadmin")));
435 usageText.append(" ");
436 usageText.append(usage);
437 return usageText.toString();
438 }
439
440 /**
441 * Get the man page from the server. If the man page isn't
442 * available, e.g., because the server is down, try to find
443 * it locally by looking in the modules directory.
444 */
445 public BufferedReader getManPage() {
446 try {
447 initializeRemoteAdminCommand();
448 rac.setCommandModel(helpModel());
449 ParameterMap params = new ParameterMap();
450 params.set("help", "true");
451 String manpage = rac.executeCommand(params);
452 return new BufferedReader(new StringReader(manpage));
453 } catch (CommandException cex) {
454 // ignore
455 }
456
457 /*
458 * Can't find the man page remotely, try to find it locally.
459 * XXX - maybe should only do this on connection failure
460 */
461 BufferedReader r = getLocalManPage();
462 return r != null ? r : super.getManPage();
463 }
464
465 /**
466 * Return a CommandModel that only includes the --help option.
467 */
468 private CommandModel helpModel() {
469 CommandModelData cm = new CommandModelData(name);
470 cm.add(new ParamModelData("help", boolean.class, true, "false", "?"));
471 return cm;
472 }
473
474 /**
475 * Try to find a local version of the man page for this command.
476 */
477 private BufferedReader getLocalManPage() {
478 logger.fine(strings.get("NoRemoteManPage"));
479 String cmdClass = getCommandClass(getName());
480 ClassLoader mcl = getModuleClassLoader();
481 if (cmdClass != null && mcl != null) {
482 return ManPageFinder.getCommandManPage(getName(), cmdClass,
483 Locale.getDefault(), mcl, logger);
484 }
485 return null;
486 }
487
488 private void initializeRemoteAdminCommand() throws CommandException {
489 if (rac == null) {
490 rac = new CLIRemoteAdminCommand(name,
491 programOpts.getHost(), programOpts.getPort(),
492 programOpts.isSecure(), programOpts.getUser(),
493 programOpts.getPassword(), logger, programOpts.getAuthToken());
494 rac.setFileOutputDirectory(outputDir);
495 rac.setInteractive(programOpts.isInteractive());;
496 }
497 }
498
499 private void initializeAuth() throws CommandException {
500 LoginInfo li = null;
501
502 try {
503 LoginInfoStore store = LoginInfoStoreFactory.getDefaultStore();
504 li = store.read(programOpts.getHost(), programOpts.getPort());
505 if (li == null)
506 return;
507 } catch (StoreException se) {
508 logger.finer(
509 "Login info could not be read from ~/.asadminpass file");
510 return;
511 }
512
513 /*
514 * If we don't have a user name, initialize it from .asadminpass.
515 * In that case, also initialize the password unless it was
516 * already specified (overriding what's in .asadminpass).
517 *
518 * If we already have a user name, and it's the same as what's
519 * in .asadminpass, and we don't have a password, use the password
520 * from .asadminpass.
521 */
522 if (programOpts.getUser() == null) {
523 // not on command line and in .asadminpass
524 logger.finer("Getting user name from ~/.asadminpass: " +
525 li.getUser());
526 programOpts.setUser(li.getUser());
527 if (programOpts.getPassword() == null) {
528 // not in passwordfile and in .asadminpass
529 logger.finer(
530 "Getting password from ~/.asadminpass");
531 programOpts.setPassword(li.getPassword(),
532 ProgramOptions.PasswordLocation.LOGIN_FILE);
533 }
534 } else if (programOpts.getUser().equals(li.getUser())) {
535 if (programOpts.getPassword() == null) {
536 // not in passwordfile and in .asadminpass
537 logger.finer(
538 "Getting password from ~/.asadminpass");
539 programOpts.setPassword(li.getPassword(),
540 ProgramOptions.PasswordLocation.LOGIN_FILE);
541 }
542 }
543 }
544
545 /**
546 * Given a command name, return the name of the class that implements
547 * that command in the server.
548 */
549 private static String getCommandClass(String cmdName) {
550 Habitat h = getManHabitat();
551 String cname = "org.glassfish.api.admin.AdminCommand";
552 for (Inhabitant<?> command : h.getInhabitantsByContract(cname)) {
553 for (String name : Inhabitants.getNamesFor(command, cname)) {
554 if (name.equals(cmdName))
555 return command.typeName();
556 }
557 }
558 return null;
559 }
560
561 /**
562 * Return a Habitat used just for reading man pages from the
563 * modules in the modules directory.
564 */
565 private static Habitat getManHabitat() {
566 if (manHabitat != null)
567 return manHabitat;
568 ModulesRegistry registry =
569 new StaticModulesRegistry(getModuleClassLoader());
570 manHabitat = registry.createHabitat("default");
571 return manHabitat;
572 }
573
574 /**
575 * Return a ClassLoader that loads classes from all the modules
576 * (jar files) in the <INSTALL_ROOT>/modules directory.
577 */
578 private static ClassLoader getModuleClassLoader() {
579 if (moduleClassLoader != null)
580 return moduleClassLoader;
581 try {
582 File installDir = new File(System.getProperty(
583 SystemPropertyConstants.INSTALL_ROOT_PROPERTY));
584 File modulesDir = new File(installDir, "modules");
585 moduleClassLoader = new DirectoryClassLoader(modulesDir,
586 CLICommand.class.getClassLoader());
587 return moduleClassLoader;
588 } catch (IOException ioex) {
589 return null;
590 }
591 }
592 }