/*
* JBoss, Home of Professional Open Source.
* Copyright 2012, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.gatein.portal.installer;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Properties;
/**
* Implementation of portal-setup.sh script. Either asks for the initial root password in the console or accepts the provided -p
* <password> command line parameter. The password is encoded and stored in configuration.properties file provided in -f
* command line parameter.
*
* @author <a href="mailto:lponce@redhat.com">Lucas Ponce</a>
*
*/
public class PortalSetupCommand {
/**
* Exception thrown by methods of {@link PortalSetupCommand}. Handlers are expected to call
* {@link PortalSetupCommand#usage(Throwable)}.
*
* @author <a href="mailto:ppalaga@redhat.com">Peter Palaga</a>
*
*/
public static class SetupCommandException extends Exception {
private static final long serialVersionUID = 2766753482448227104L;
/**
* @param message
*/
public SetupCommandException(String message) {
super(message);
}
/**
* @param message
* @param cause
*/
public SetupCommandException(String message, Throwable cause) {
super(message, cause);
}
/**
* @param cause
*/
public SetupCommandException(Throwable cause) {
super(cause);
}
}
/**
* The command line option -h (Help).
*/
public static final String HELP_OPTION = "-h";
/**
* The command line option -p (Password).
*/
public static final String PASSWORD_OPTION = "-p";
/**
* The command line option -f (Properties File Path).
*/
public static final String PROPERTIES_FILE_OPTION = "-f";
/**
* See {@link Properties#load(java.io.InputStream)}.
*/
public static final String PROPERTIES_STANDARD_ENCODING = "iso-8859-1";
/**
* The name of the wrapper script.
*/
public static final String SCRIPT_NAME = "portal-setup";
/**
* We do not consider logical lines spread over more than one physical line here - see
* {@link Properties#load(java.io.Reader)}.
*
* @param line
* @return
*/
private static boolean containsRootPasswordProperty(String line) {
int offset = 0;
/* ignorable whitespace at the beginning */
WHILE: while (offset < line.length()) {
switch (line.charAt(offset)) {
case ' ':
case '\t':
case '\f':
offset++;
break;
default:
break WHILE;
}
}
/* Try to match propName */
String propName = PortalSetupService.ROOT_PASSWORD_PROPERTY;
for (int i = 0; i < propName.length(); i++) {
if (offset >= line.length() || propName.charAt(i) != line.charAt(offset++)) {
return false;
}
}
/*
* propName matched what is next?
*/
if (offset == line.length()) {
/* no property value */
return true;
} else {
switch (line.charAt(offset)) {
case ' ':
case '\t':
case '\f':
case '=':
case ':':
/* end of property name */
return true;
default:
/* no end of property name */
return false;
}
}
}
/**
* Main method. See {@link PortalSetupCommand}.
*
* @param args
*/
public static void main(String[] args) {
PortalSetupCommand cmd = new PortalSetupCommand();
try {
cmd.parseArgs(args);
cmd.validate();
cmd.process();
} catch (SetupCommandException e) {
cmd.usage(e);
}
}
/**
* Holds root's plain text password.
*/
private String password;
/**
* Portal property file path.
*/
private File propertiesFile;
/**
* A public constructor.
*/
public PortalSetupCommand() {
}
/**
* Parses command line arguments and complains if necessary.
*
* @param args
*/
public void parseArgs(String[] args) throws SetupCommandException {
if (args != null) {
if (args.length < 2) {
throw new SetupCommandException("Missing parameter " + PROPERTIES_FILE_OPTION + ".");
} else {
for (int i = 0; i < args.length;) {
String arg = args[i++];
if (PASSWORD_OPTION.equals(arg)) {
if (i >= args.length) {
throw new SetupCommandException("Password expected after " + PASSWORD_OPTION + ".");
} else {
this.password = args[i++];
}
} else if (PROPERTIES_FILE_OPTION.equals(arg)) {
if (i >= args.length) {
throw new SetupCommandException("Properties file path expected after " + PROPERTIES_FILE_OPTION
+ ".");
} else {
this.propertiesFile = new File(args[i++]);
}
} else if (HELP_OPTION.equals(arg)) {
throw new SetupCommandException("");
} else {
throw new SetupCommandException("Unrecognized parameter '" + arg + "'.");
}
}
}
}
}
/**
* Writes the changes to {@link #propertiesFile}.
*
* @throws SetupCommandException
*
*/
public void process() throws SetupCommandException {
Properties props = new Properties();
FileInputStream in = null;
try {
in = new FileInputStream(propertiesFile);
props.load(in);
} catch (FileNotFoundException e) {
throw new SetupCommandException("File does not exist: '" + propertiesFile.getAbsolutePath() + "'", e);
} catch (IOException e) {
throw new SetupCommandException(e.getMessage(), e);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
if (props.containsKey(PortalSetupService.ROOT_PASSWORD_PROPERTY)) {
/* edit the file */
BufferedReader r = null;
OutputStream out = null;
BufferedWriter w = null;
File propertiesFileBackup = new File(propertiesFile + ".backup");
try {
/* rename the original file */
propertiesFile.renameTo(propertiesFileBackup);
in = new FileInputStream(propertiesFileBackup);
r = new BufferedReader(new InputStreamReader(in, PROPERTIES_STANDARD_ENCODING));
out = new FileOutputStream(propertiesFile);
w = new BufferedWriter(new OutputStreamWriter(out, PROPERTIES_STANDARD_ENCODING));
String line = null;
while ((line = r.readLine()) != null) {
if (containsRootPasswordProperty(line)) {
writeRootPasswordLine(w);
} else {
w.write(line);
w.write('\n');
}
}
} catch (FileNotFoundException e) {
throw new SetupCommandException("File does not exist: '" + propertiesFile.getAbsolutePath() + "'", e);
} catch (UnsupportedEncodingException e) {
/* Shoud never happen with PROPERTIES_STANDARD_ENCODING = "iso-8859-1" */
throw new SetupCommandException(e.getMessage(), e);
} catch (IOException e) {
throw new SetupCommandException(e.getMessage(), e);
} finally {
if (w != null) {
try {
w.close();
} catch (IOException e) {
throw new SetupCommandException(e.getMessage(), e);
}
}
if (r != null) {
try {
r.close();
propertiesFileBackup.delete();
} catch (IOException e) {
throw new SetupCommandException(e.getMessage(), e);
}
}
}
} else {
/* append */
OutputStream out = null;
Writer w = null;
try {
out = new FileOutputStream(propertiesFile, true);
w = new OutputStreamWriter(out, PROPERTIES_STANDARD_ENCODING);
writeRootPasswordLine(w);
} catch (FileNotFoundException e) {
throw new SetupCommandException("File does not exist: '" + propertiesFile.getAbsolutePath() + "'", e);
} catch (UnsupportedEncodingException e) {
/* Shoud never happen with PROPERTIES_STANDARD_ENCODING = "iso-8859-1" */
throw new SetupCommandException(e.getMessage(), e);
} catch (IOException e) {
throw new SetupCommandException(e.getMessage(), e);
} finally {
if (w != null) {
try {
w.close();
} catch (IOException e) {
throw new SetupCommandException(e.getMessage(), e);
}
}
}
}
}
/**
* Writes the root password line to {@link #propertiesFile}.
*
* @param w
* @throws SetupCommandException
* @throws IOException
*/
private void writeRootPasswordLine(Writer w) throws SetupCommandException, IOException {
try {
String encodedPassword = PortalSetupService.encodePassword(password);
w.write('\n');
w.write(PortalSetupService.ROOT_PASSWORD_PROPERTY);
w.write('=');
w.write(encodedPassword);
w.write(" # Modified by ");
w.write(SCRIPT_NAME);
w.write(".sh\n");
} catch (NoSuchAlgorithmException e) {
throw new PortalSetupCommand.SetupCommandException(e.getMessage(), e);
} catch (InvalidKeySpecException e) {
throw new PortalSetupCommand.SetupCommandException(e.getMessage(), e);
} catch (UnsupportedEncodingException e) {
throw new PortalSetupCommand.SetupCommandException(e.getMessage(), e);
} catch (Exception e) {
throw new PortalSetupCommand.SetupCommandException(e.getMessage(), e);
}
}
/**
* Prints an error message if there is any and command usage to stderr.
*
* @param string
*/
public void usage(Throwable error) {
if (error != null) {
String msg = error.getMessage();
if (msg != null && msg.length() > 0) {
System.err.println("Error: " + msg);
}
}
System.err.println("================================================================================");
System.err.println(SCRIPT_NAME + "[ .sh | .bat ] - a utility for setting an initial password for");
System.err.println(" portal user 'root'.");
System.err.println();
System.err.println("Usage: " + SCRIPT_NAME + "[ .sh | .bat ] [ " + PROPERTIES_FILE_OPTION + " <path> ] [ "
+ PASSWORD_OPTION + " <password> ] [ " + HELP_OPTION + " ]");
System.err.println();
System.err
.println(" " + PROPERTIES_FILE_OPTION + " <path> - path to portal properties file. If not set explicitly,");
System.err.println(" the default path for the given platform will be used.");
System.err.println();
System.err.println(" " + PASSWORD_OPTION
+ " <password> - the password that will be appended to properties file given");
System.err.println(" in " + PROPERTIES_FILE_OPTION + ".");
System.err.println();
System.err.println(" " + HELP_OPTION + " - Print these usage instructions and exit.");
System.err.println();
}
/**
* Validates the parameters and asks the user for missing information on the console if necessary.
*
* @throws IOException
* @throws FileNotFoundException
*
*/
public void validate() throws SetupCommandException {
if (propertiesFile == null) {
throw new SetupCommandException("Missing parameter " + PROPERTIES_FILE_OPTION + ".");
} else if (!propertiesFile.exists()) {
throw new SetupCommandException("File does not exist: '" + propertiesFile.getAbsolutePath() + "'");
}
if (password == null) {
System.out.print("Set root password: ");
password = new String(System.console().readPassword());
System.out.print("Repeat root password: ");
String password2 = new String(System.console().readPassword());
if (!password.equals(password2)) {
throw new SetupCommandException("Passwords are not equal.");
}
}
}
}