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 package com.sun.enterprise.admin.cli;
041
042 import com.sun.enterprise.util.OS;
043 import com.sun.enterprise.util.io.FileUtils;
044 import java.io.*;
045 import java.net.*;
046 import java.util.*;
047 import java.security.KeyStore;
048
049 import org.glassfish.api.admin.CommandException;
050 import com.sun.enterprise.admin.cli.remote.RemoteCommand;
051 import com.sun.enterprise.security.store.PasswordAdapter;
052 import com.sun.enterprise.universal.i18n.LocalStringsImpl;
053 import com.sun.enterprise.universal.io.SmartFile;
054 import com.sun.enterprise.universal.process.Jps;
055 import com.sun.enterprise.universal.process.ProcessUtils;
056 import com.sun.enterprise.universal.xml.MiniXmlParser;
057 import com.sun.enterprise.universal.xml.MiniXmlParserException;
058 import com.sun.enterprise.util.HostAndPort;
059 import com.sun.enterprise.util.io.ServerDirs;
060
061 /**
062 * A class that's supposed to capture all the behavior common to operation
063 * on a "local" server.
064 * It's getting fairly complicated thus the "section headers" comments.
065 * This class plays two roles, <UL><LI>a place for putting common code - which
066 * are final methods. A parent class that is communicating with its own unknown
067 * sub-classes. These are non-final methods
068 *
069 * @author Byron Nevins
070 */
071 public abstract class LocalServerCommand extends CLICommand {
072 ////////////////////////////////////////////////////////////////
073 /// Section: protected methods that are OK to override
074 ////////////////////////////////////////////////////////////////
075 /**
076 * Override this method and return false to turn-off the file validation.
077 * E.g. it demands that config/domain.xml be present. In special cases like
078 * Synchronization -- this is how you turn off the testing.
079 * @return true - do the checks, false - don't do the checks
080 */
081 protected boolean checkForSpecialFiles() {
082 return true;
083 }
084
085 ////////////////////////////////////////////////////////////////
086 /// Section: protected methods that are notOK to override.
087 ////////////////////////////////////////////////////////////////
088 /**
089 * Returns the admin address of the local domain. Note that this method
090 * should be called only when you own the domain that is available on
091 * an accessible file system.
092 *
093 * @return HostAndPort object with admin server address
094 * @throws CommandException in case of parsing errors
095 */
096 protected final HostAndPort getAdminAddress() throws CommandException {
097 // default: DAS which always has the name "server"
098 return getAdminAddress("server");
099 }
100
101 /**
102 * Returns the admin address of a particular server. Note that this method
103 * should be called only when you own the server that is available on
104 * an accessible file system.
105 *
106 * @return HostAndPort object with admin server address
107 * @throws CommandException in case of parsing errors
108 */
109 protected final HostAndPort getAdminAddress(String serverName)
110 throws CommandException {
111
112 try {
113 MiniXmlParser parser = new MiniXmlParser(getDomainXml(), serverName);
114 List<HostAndPort> addrSet = parser.getAdminAddresses();
115
116 if (addrSet.size() > 0)
117 return addrSet.get(0);
118 else
119 throw new CommandException(strings.get("NoAdminPort"));
120 }
121 catch (MiniXmlParserException ex) {
122 throw new CommandException(strings.get("NoAdminPortEx", ex), ex);
123 }
124 }
125
126 protected final void setServerDirs(ServerDirs sd) {
127 serverDirs = sd;
128 }
129
130 protected final void setLocalPassword() {
131 String pw = serverDirs == null ? null : serverDirs.getLocalPassword();
132
133 if (ok(pw)) {
134 programOpts.setPassword(pw,
135 ProgramOptions.PasswordLocation.LOCAL_PASSWORD);
136 logger.finer("Using local password");
137 }
138 else
139 logger.finer("Not using local password");
140 }
141
142 protected final void unsetLocalPassword() {
143 programOpts.setPassword(null,
144 ProgramOptions.PasswordLocation.LOCAL_PASSWORD);
145 }
146
147 protected final void resetServerDirs() throws IOException {
148 serverDirs = serverDirs.refresh();
149 }
150
151 protected final ServerDirs getServerDirs() {
152 return serverDirs;
153 }
154
155 protected final File getDomainXml() {
156 return serverDirs.getDomainXml();
157 }
158
159 /**
160 * Checks if the create-domain was created using --savemasterpassword flag
161 * which obtains security by obfuscation! Returns null in case of failure
162 * of any kind.
163 * @return String representing the password from the JCEKS store named
164 * master-password in domain folder
165 */
166 protected final String readFromMasterPasswordFile() {
167 File mpf = getMasterPasswordFile();
168 if (mpf == null)
169 return null; // no master password saved
170 try {
171 PasswordAdapter pw = new PasswordAdapter(mpf.getAbsolutePath(),
172 "master-password".toCharArray()); // fixed key
173 return pw.getPasswordForAlias("master-password");
174 }
175 catch (Exception e) {
176 logger.finer("master password file reading error: "
177 + e.getMessage());
178 return null;
179 }
180 }
181
182 protected final boolean verifyMasterPassword(String mpv) {
183 //issue : 14971, should ideally use javax.net.ssl.keyStore and
184 //javax.net.ssl.keyStoreType system props here but they are
185 //unavailable to asadmin start-domain hence falling back to
186 //cacerts.jks instead of keystore.jks. Since the truststore
187 //is less-likely to be Non-JKS
188
189 return loadAndVerifyKeystore(getJKS(),mpv);
190 }
191
192 protected boolean loadAndVerifyKeystore(File jks,String mpv) {
193 FileInputStream fis = null;
194 try {
195 fis = new FileInputStream(jks);
196 KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
197 ks.load(fis, mpv.toCharArray());
198 return true;
199 }
200 catch (Exception e) {
201 logger.finer(e.getMessage());
202 return false;
203 }
204 finally {
205 try {
206 if (fis != null)
207 fis.close();
208 }
209 catch (IOException ioe) {
210 // ignore, I know ...
211 }
212 }
213 }
214
215 /**
216 * Get the master password, either from a password file or
217 * by asking the user.
218 */
219 protected final String getMasterPassword() throws CommandException {
220 // Sets the password into the launcher info.
221 // Yes, returning master password as a string is not right ...
222 final int RETRIES = 3;
223 long t0 = System.currentTimeMillis();
224 String mpv = passwords.get(CLIConstants.MASTER_PASSWORD);
225 if (mpv == null) { //not specified in the password file
226 mpv = "changeit"; //optimization for the default case -- see 9592
227 if (!verifyMasterPassword(mpv)) {
228 mpv = readFromMasterPasswordFile();
229 if (!verifyMasterPassword(mpv)) {
230 mpv = retry(RETRIES);
231 }
232 }
233 }
234 else { // the passwordfile contains AS_ADMIN_MASTERPASSWORD, use it
235 if (!verifyMasterPassword(mpv))
236 mpv = retry(RETRIES);
237 }
238 long t1 = System.currentTimeMillis();
239 logger.finer("Time spent in master password extraction: "
240 + (t1 - t0) + " msec"); //TODO
241 return mpv;
242 }
243
244 /**
245 * See if the server is alive and is the one at the specified directory.
246 *
247 * @return true if it's the DAS at this domain directory
248 */
249 protected final boolean isThisServer(File ourDir, String directoryKey) {
250 if (!ok(directoryKey))
251 throw new NullPointerException();
252
253 ourDir = getUniquePath(ourDir);
254 logger.finer("Check if server is at location " + ourDir);
255
256 try {
257 RemoteCommand cmd =
258 new RemoteCommand("__locations", programOpts, env);
259 Map<String, String> attrs =
260 cmd.executeAndReturnAttributes(new String[]{"__locations"});
261 String theirDirPath = attrs.get(directoryKey);
262 logger.finer("Remote server has root directory " + theirDirPath);
263
264 if (ok(theirDirPath)) {
265 File theirDir = getUniquePath(new File(theirDirPath));
266 return theirDir.equals(ourDir);
267 }
268 return false;
269 }
270 catch (Exception ex) {
271 return false;
272 }
273 }
274
275 /**
276 * There is sometimes a need for subclasses to know if a
277 * <code> local domain </code> is running. An example of such a command is
278 * change-master-password command. The stop-domain command also needs to
279 * know if a domain is running <i> without </i> having to provide user
280 * name and password on command line (this is the case when I own a domain
281 * that has non-default admin user and password) and want to stop it
282 * without providing it.
283 * <p>
284 * In such cases, we need to know if the domain is running and this method
285 * provides a way to do that.
286 *
287 * @return boolean indicating whether the server is running
288 */
289 protected final boolean isRunning(String host, int port) {
290 Socket server = null;
291 try {
292 server = new Socket(host, port);
293 return true;
294 }
295 catch (Exception ex) {
296 logger.finer("\nisRunning got exception: " + ex);
297 return false;
298 }
299 finally {
300 if (server != null) {
301 try {
302 server.close();
303 }
304 catch (IOException ex) {
305 }
306 }
307 }
308 }
309
310 /**
311 * convenience method for the local machine
312 */
313 protected final boolean isRunning(int port) {
314 return isRunning(null, port);
315 }
316
317 /**
318 * Is the server still running?
319 * This is only called when we're hanging around waiting for the server to die.
320 * Byron Nevins, Nov 7, 2010 - Check to see if the process itself is still running
321 * We use OS tools to figure this out. See ProcessUtils for details.
322 * Failover to the JPS check if necessary
323 */
324 protected boolean isRunning() {
325 int pp = getPrevPid();
326
327 if (pp < 0)
328 return isRunningByCheckingForPidFile();
329
330 Boolean b = ProcessUtils.isProcessRunning(pp);
331
332 if (b == null) // this means it couldn't find out!
333 return isRunningUsingJps();
334 else
335 return b.booleanValue();
336 }
337
338 protected final void waitForRestart(File pwFile, long oldTimeStamp, long uptimeOldServer) throws CommandException {
339 if (oldTimeStamp <= 0 || !usingLocalPassword())
340 waitForRestartRemote(uptimeOldServer);
341 else
342 waitForRestartLocal(pwFile, oldTimeStamp, uptimeOldServer);
343 }
344
345 // todo move prevpid to ServerDirs ???
346 protected final int getPrevPid() {
347 try {
348 File prevPidFile = new File(getServerDirs().getPidFile().getPath() + ".prev");
349
350 if (!prevPidFile.canRead())
351 return -1;
352
353 String pids = FileUtils.readSmallFile(prevPidFile).trim();
354 return Integer.parseInt(pids);
355 }
356 catch (Exception ex) {
357 return -1;
358 }
359 }
360
361 /**
362 * Is the server still running?
363 * This is only called when we're hanging around waiting for the server to die.
364 * Byron Nevins, Nov 7, 2010 - Check to see if the process itself is still running
365 * We use jps to check
366 * If there are any problems fall back to the previous implementation of
367 * isRunning() which looks for the pidfile to get deleted
368 */
369 private final boolean isRunningUsingJps() {
370 int pp = getPrevPid();
371
372 if (pp < 0)
373 return isRunningByCheckingForPidFile();
374
375 return Jps.isPid(pp);
376 }
377
378 /**
379 * Is the server still running?
380 * This is only called when we're hanging around waiting for the server to die.
381 */
382 private boolean isRunningByCheckingForPidFile() {
383 File pf = getServerDirs().getPidFile();
384
385 if (pf != null) {
386 return pf.exists();
387 }
388 else
389 return isRunning(programOpts.getHost(), // remote case
390 programOpts.getPort());
391 }
392
393 /**
394 * Wait for the local server to restart.
395 */
396 private void waitForRestartLocal(File pwFile, long oldTimeStamp, long uptimeOldServer) throws CommandException {
397 // we are using local-password for authentication to the local server. We need
398 // to use the NEW password that will be soon generated. After that we can
399 // do Uptime calls to make sure V3 is ready to receive commands
400
401 if (!usingLocalPassword())
402 throw new CommandException("Internal Error - waitForRestartLocal should "
403 + "not be called unless using local password authentication.");
404
405 long end = CLIConstants.WAIT_FOR_DAS_TIME_MS
406 + System.currentTimeMillis();
407
408 while (System.currentTimeMillis() < end) {
409 // when the server has restarted the passwordfile will be different
410 // don't waste time reading the file again and again, just look
411 // for the time stamp to change.
412 // Careful -- there is a slice of time where the file does not exist!
413 try {
414 long newTimeStamp = pwFile.lastModified(); // could be 0L
415 logger.finer("Checking timestamp of local-password. "
416 + "old: " + oldTimeStamp + ", new: " + newTimeStamp);
417
418 if (newTimeStamp > oldTimeStamp) {
419 // Server has restarted but may not be quite ready to handle commands
420 // automated tests would have issues if we returned right here...
421 resetServerDirs();
422 programOpts.setPassword(getServerDirs().getLocalPassword(), ProgramOptions.PasswordLocation.LOCAL_PASSWORD);
423 waitForRestartRemote(uptimeOldServer);
424 return;
425 }
426 Thread.sleep(CLIConstants.RESTART_CHECK_INTERVAL_MSEC);
427 }
428 catch (Exception e) {
429 // continue
430 }
431 }
432 // if we get here -- we timed out
433 throw new CommandException(strings.get("restartDomain.noGFStart"));
434 }
435
436 /**
437 * Wait for the remote server to restart.
438 */
439 private void waitForRestartRemote(long uptimeOldServer) throws CommandException {
440 long end = CLIConstants.WAIT_FOR_DAS_TIME_MS
441 + System.currentTimeMillis();
442
443 while (System.currentTimeMillis() < end) {
444 try {
445 Thread.sleep(CLIConstants.RESTART_CHECK_INTERVAL_MSEC);
446 long up = getUptime();
447 logger.finer("oldserver-uptime, newserver-uptime = " + uptimeOldServer + " --- " + up);
448
449 if (up > 0 && up < uptimeOldServer) {
450 return;
451 }
452 }
453 catch (Exception e) {
454 // continue
455 }
456 }
457 // if we get here -- we timed out
458 throw new CommandException(strings.get("restartDomain.noGFStart"));
459 }
460
461 /**
462 * Get uptime from the server.
463 */
464 protected final long getUptime() throws CommandException {
465 RemoteCommand cmd = new RemoteCommand("uptime", programOpts, env);
466 String up = cmd.executeAndReturnOutput("uptime", "--milliseconds").trim();
467 long up_ms = parseUptime(up);
468
469 if (up_ms <= 0) {
470 throw new CommandException(strings.get("restart.dasNotRunning"));
471 }
472
473 logger.finer("server uptime: " + up_ms);
474 return up_ms;
475 }
476 /**
477 * See if the server is restartable
478 * As of March 2011 -- this only returns false if a passwordfile argument was given
479 * when the server started -- but it is no longer available - i.e. the user
480 * deleted it or made it unreadable.
481 */
482 protected final boolean isRestartable() throws CommandException {
483 // false negative is worse than false positive.
484 // there is one and only one case where we return false
485 RemoteCommand cmd = new RemoteCommand("_get-runtime-info", programOpts, env);
486 Map<String, String> atts = cmd.executeAndReturnAttributes("_get-runtime-info");
487
488 if (atts != null) {
489 String val = atts.get("restartable_value");
490
491 if (ok(val) && val.equals("false"))
492 return false;
493 }
494 return true;
495 }
496
497 ////////////////////////////////////////////////////////////////
498 /// Section: private methods
499 ////////////////////////////////////////////////////////////////
500 /**
501 * The remote uptime command returns a string like:
502 * Uptime: 10 minutes, 53 seconds, Total milliseconds: 653859\n
503 * We find that last number and extract it.
504 * XXX - this is pretty gross, and fragile
505 */
506 private long parseUptime(String up) {
507 try {
508 return Long.parseLong(up);
509 }
510 catch (Exception e) {
511 return 0;
512 }
513 }
514
515 private boolean usingLocalPassword() {
516 return programOpts.getPasswordLocation() == ProgramOptions.PasswordLocation.LOCAL_PASSWORD;
517 }
518
519 private final File getJKS() {
520 if (serverDirs == null)
521 return null;
522
523 File mp = new File(new File(serverDirs.getServerDir(), "config"), "cacerts.jks");
524 if (!mp.canRead())
525 return null;
526 return mp;
527 }
528
529 protected File getMasterPasswordFile() {
530
531 if (serverDirs == null)
532 return null;
533
534 File mp = new File(serverDirs.getServerDir(), "master-password");
535 if (!mp.canRead())
536 return null;
537
538 return mp;
539 }
540
541 private String retry(int times) throws CommandException {
542 String mpv;
543 // prompt times times
544 for (int i = 0; i < times; i++) {
545 // XXX - I18N
546 String prompt = strings.get("mp.prompt", (times - i));
547 mpv = super.readPassword(prompt);
548 if (mpv == null)
549 throw new CommandException(strings.get("no.console"));
550 // ignore retries :)
551 if (verifyMasterPassword(mpv))
552 return mpv;
553 if (i < (times - 1))
554 logger.info(strings.get("retry.mp"));
555 // make them pay for typos?
556 //Thread.currentThread().sleep((i+1)*10000);
557 }
558 throw new CommandException(strings.get("mp.giveup", times));
559 }
560
561 private File getUniquePath(File f) {
562 try {
563 f = f.getCanonicalFile();
564 }
565 catch (IOException ioex) {
566 f = SmartFile.sanitize(f);
567 }
568 return f;
569 }
570 ////////////////////////////////////////////////////////////////
571 /// Section: private variables
572 ////////////////////////////////////////////////////////////////
573 private ServerDirs serverDirs;
574 private static final LocalStringsImpl strings =
575 new LocalStringsImpl(LocalDomainCommand.class);
576 }