/**
* Copyright (c) Members of the EGEE Collaboration. 2006-2009.
* See http://www.eu-egee.org/partners/ for details on the copyright holders.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.glite.authz.pap.ui.cli;
import java.io.File;
import java.io.FileInputStream;
import java.io.PrintWriter;
import java.rmi.RemoteException;
import java.util.Collection;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.glite.authz.pap.client.ServiceClient;
import org.glite.authz.pap.client.ServiceClientFactory;
import org.glite.authz.pap.common.Pap;
import org.glite.authz.pap.common.exceptions.PAPException;
import org.glite.authz.pap.common.utils.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import eu.emi.security.authn.x509.impl.PEMCredential;
public abstract class ServiceCLI {
public static enum ExitStatus {
// Attention keep that order because elements ordinal number is used.
SUCCESS, // value = 0
PARTIAL_SUCCESS, // value = 1
FAILURE, // value = 2
INITIALIZATION_ERROR, // value = 3
PARSE_ERROR, // value = 4
REMOTE_EXCEPTION
// value = 5
}
private static final Logger log = LoggerFactory.getLogger(ServiceCLI.class);
private static final HelpFormatter helpFormatter = new HelpFormatter();
private static final String OPT_PROXY_DESCRIPTION = "Specifies a user proxy to be used for authentication.";
private static final String OPT_PROXY_LONG = "proxy";
private static final String OPT_CERT_DESCRIPTION = "Specifies non-standard user certificate.";
private static final String OPT_CERT_LONG = "cert";
private static final String OPT_HOST_DESCRIPTION = "Specifies the target PAP hostname (default is localhost). "
+ "This option defines the PAP endpoint to be contacted as follows: https://arg:port"
+ Pap.DEFAULT_SERVICES_ROOT_PATH;
private static final String OPT_HOST_LONG = "host";
private static final String OPT_KEY_DESCRIPTION = "Specifies non-standard user private key.";
private static final String OPT_KEY_LONG = "key";
private static final String OPT_PORT = "p";
private static final String OPT_PORT_DESCRIPTION = "Specifies the port on which the target PAP is listening "
+ "(default is " + Pap.DEFAULT_PORT + ")";
private static final String OPT_PORT_LONG = "port";
private static final String OPT_URL_LONG = "url";
private static final String OPT_VERBOSE = "v";
private static final String OPT_VERBOSE_DESCRIPTION = "Verbose mode.";
private static final String OPT_VERBOSE_LONG = "verbose";
protected static final String DEFAULT_SERVICE_URL = "https://%s:%s%s";
protected static final String OPT_HELP = "h";
protected static final String OPT_HELP_DESCRIPTION = "Print this message.";
protected static final String OPT_HELP_LONG = "help";
protected static final String OPT_PRIVATE_LONG = "private";
protected static final String OPT_PUBLIC_LONG = "public";
protected static final String PAP_HOST_PROPERTY = "host";
protected static final String PAP_PORT_PROPERTY = "port";
protected static final CommandLineParser parser = new GnuParser();
private String[] commandNameValues;
private Options commandOptions;
private String descriptionText;
private static Options globalOptions;
private String longDescriptionText;
private Options options;
private final ServiceClient serviceClient;
private String usageText;
protected boolean verboseMode = false;
static {
globalOptions = defineGlobalOptions();
}
@SuppressWarnings("unchecked")
public ServiceCLI(String[] commandNameValues, String usage, String description, String longDescription) {
ServiceClientFactory serviceClientFactory = ServiceClientFactory.getServiceClientFactory();
serviceClient = serviceClientFactory.createServiceClient();
helpFormatter.setWidth(80);
helpFormatter.setLeftPadding(4);
this.commandNameValues = commandNameValues;
this.usageText = usage;
this.descriptionText = description;
this.longDescriptionText = longDescription;
commandOptions = defineCommandOptions();
if (commandOptions == null)
commandOptions = new Options();
commandOptions.addOption(OPT_HELP, OPT_HELP_LONG, false, OPT_HELP_DESCRIPTION);
options = new Options();
Collection<Option> optionsList = commandOptions.getOptions();
for (Option opt : optionsList) {
options.addOption(opt);
}
optionsList = globalOptions.getOptions();
for (Option opt : optionsList) {
options.addOption(opt);
}
}
public static Options getGlobalOptions() {
return globalOptions;
}
@SuppressWarnings("static-access")
private static Options defineGlobalOptions() {
Options options = new Options();
// TODO: OPT_URL and (OPT_HOST, OPT_PORT) are mutually exclusive
// options. Use OptionGroup.
options.addOption(OptionBuilder.hasArg(true)
.withLongOpt(OPT_URL_LONG)
.withDescription("Specifies the target PAP endpoint (default: "
+ String.format(DEFAULT_SERVICE_URL,
Pap.DEFAULT_HOST,
Pap.DEFAULT_PORT,
Pap.DEFAULT_SERVICES_ROOT_PATH) + ").")
.withArgName("url")
.create());
options.addOption(OptionBuilder.hasArg(true)
.withLongOpt(OPT_HOST_LONG)
.withDescription(OPT_HOST_DESCRIPTION)
.withArgName("hostname")
.create());
options.addOption(OptionBuilder.hasArg(true)
.withLongOpt(OPT_PORT_LONG)
.withDescription(OPT_PORT_DESCRIPTION)
.create(OPT_PORT));
options.addOption(OptionBuilder.hasArg(true)
.withLongOpt(OPT_PROXY_LONG)
.withDescription(OPT_PROXY_DESCRIPTION)
.withArgName("file")
.create());
options.addOption(OptionBuilder.hasArg(true)
.withLongOpt(OPT_CERT_LONG)
.withDescription(OPT_CERT_DESCRIPTION)
.withArgName("file")
.create());
options.addOption(OptionBuilder.hasArg(true)
.withLongOpt(OPT_KEY_LONG)
.withDescription(OPT_KEY_DESCRIPTION)
.withArgName("file")
.create());
options.addOption(OptionBuilder.hasArg(false)
.withLongOpt(OPT_VERBOSE_LONG)
.withDescription(OPT_VERBOSE_DESCRIPTION)
.create(OPT_VERBOSE));
return options;
}
public boolean commandMatch(String command) {
for (String value : commandNameValues) {
if (value.equals(command))
return true;
}
return false;
}
public int execute(String[] args) throws ParseException, HelpMessageException, RemoteException {
CommandLine commandLine = parser.parse(options, args);
if (commandLine.hasOption(OPT_HELP)) {
throw new HelpMessageException();
}
if (commandLine.hasOption(OPT_VERBOSE)) {
verboseMode = true;
}
if (commandLine.hasOption(OPT_URL_LONG)) {
serviceClient.setTargetEndpoint(commandLine.getOptionValue(OPT_URL_LONG));
} else {
String host = Pap.DEFAULT_HOST;
String papHostProperty = System.getProperty(PAP_HOST_PROPERTY);
if (papHostProperty != null && !"".equals(papHostProperty.trim())){
host = papHostProperty;
}
String port = Pap.DEFAULT_PORT;
String papPortProperty = System.getProperty(PAP_PORT_PROPERTY);
if (papPortProperty != null && !"".equals(papPortProperty.trim())){
port = papPortProperty;
}
if (commandLine.hasOption(OPT_HOST_LONG)) {
host = commandLine.getOptionValue(OPT_HOST_LONG);
}
if (commandLine.hasOption(OPT_PORT)) {
port = commandLine.getOptionValue(OPT_PORT);
}
try {
Integer.valueOf(port);
} catch (NumberFormatException e) {
throw new ParseException(String.format("Invalid port number \"%s\" (option -%s, --%s)",
port,
OPT_PORT,
OPT_PORT_LONG));
}
serviceClient.setTargetEndpoint(String.format(DEFAULT_SERVICE_URL,
host,
port,
Pap.DEFAULT_SERVICES_ROOT_PATH));
}
boolean credentialsNotRetrieved = true;
if (commandLine.hasOption(OPT_PROXY_LONG)) {
serviceClient.setClientProxy(commandLine.getOptionValue(OPT_PROXY_LONG));
credentialsNotRetrieved = false;
}
if (commandLine.hasOption(OPT_CERT_LONG)) {
if (commandLine.hasOption(OPT_PROXY_LONG)) {
throw new ParseException(String.format("Conflicting options --%s and --%s.",
OPT_PROXY_LONG,
OPT_CERT_LONG));
} else {
if (!commandLine.hasOption(OPT_KEY_LONG)) {
throw new ParseException(String.format("Option --%s requires also option --%s.",
OPT_CERT_LONG,
OPT_KEY_LONG));
}
serviceClient.setClientCertificate(commandLine.getOptionValue(OPT_CERT_LONG));
credentialsNotRetrieved = false;
}
}
if (commandLine.hasOption(OPT_KEY_LONG)) {
if (commandLine.hasOption(OPT_PROXY_LONG)) {
throw new ParseException(String.format("Conflicting options --%s and --%s.",
OPT_PROXY_LONG,
OPT_KEY_LONG));
} else {
if (!commandLine.hasOption(OPT_CERT_LONG)) {
throw new ParseException(String.format("Option --%s requires also option --%s.",
OPT_KEY_LONG,
OPT_CERT_LONG));
}
serviceClient.setClientPrivateKey(commandLine.getOptionValue(OPT_KEY_LONG));
credentialsNotRetrieved = false;
}
}
if (credentialsNotRetrieved) {
// 1. if running as root take the cert /etc/grid-security/hostcert.pem
// 2. check the env variable X509_USER_PROXY
// 3. check the env variable X509_USER_CERT (and X509_USER_KEY)
// 4. check the proxy /tmp/x509up_u<id_utente>
// 5. check the cert $HOME/.globus/usercert.pem and key $HOME/.globus/userkey.pem
String euid = getEUID();
if (euid == null) {
log.error("Cannot enstabilish user's effective user id.");
throw new PAPException(String.format("Cannot enstabilish user's effective user id, please use the --%s or --%s, --%s options.",
OPT_PROXY_LONG,
OPT_CERT_LONG,
OPT_KEY_LONG));
}
String messageString = null;
if ("0".equals(euid)) {
// user: root
if (setCertFromEnvironment()) {
messageString = String.format("Connecting to %s using %s and %s (from environment X509_USER_CERT and X509_USER_KEY)",
serviceClient.getTargetEndpoint(),
serviceClient.getClientCertificate(),
serviceClient.getClientPrivateKey());
} else {
serviceClient.setClientCertificate("/etc/grid-security/hostcert.pem");
serviceClient.setClientPrivateKey("/etc/grid-security/hostkey.pem");
messageString = String.format("Connecting to %s using %s and %s",
serviceClient.getTargetEndpoint(),
serviceClient.getClientCertificate(),
serviceClient.getClientPrivateKey());
}
} else {
if (setProxyFromEnvironment()) {
messageString = String.format("Connecting to %s using proxy (from environment X509_USER_PROXY) %s",
serviceClient.getTargetEndpoint(),
serviceClient.getClientProxy());
} else if (setCertFromEnvironment()) {
messageString = String.format("Connecting to %s using %s and %s (from environment X509_USER_CERT and X509_USER_KEY)",
serviceClient.getTargetEndpoint(),
serviceClient.getClientCertificate(),
serviceClient.getClientPrivateKey());
} else if (setProxyFromStandardLocation(euid)) {
messageString = String.format("Connecting to %s using proxy %s",
serviceClient.getTargetEndpoint(),
serviceClient.getClientProxy());
} else if (setCertFromHomeDir()) {
messageString = String.format("Connecting to %s using %s and %s",
serviceClient.getTargetEndpoint(),
serviceClient.getClientCertificate(),
serviceClient.getClientPrivateKey());
} else {
throw new ParseException(String.format("Unable to find a certificate or a proxy, please specify a proxy file with option --%s or certificate and key with options --%s and --%s",
OPT_PROXY_LONG,
OPT_CERT_LONG,
OPT_KEY_LONG));
}
}
log.info(messageString);
if (verboseMode) {
System.out.println(messageString);
}
}
if (serviceClient.getClientPrivateKey() != null) {
String keyPassword = getPrivateKeyPasswordFromConsole(serviceClient.getClientPrivateKey(),
serviceClient.getClientCertificate());
if (keyPassword != null)
serviceClient.setClientPrivateKeyPassword(keyPassword);
}
return executeCommandService(commandLine, serviceClient);
}
private String getPrivateKeyPasswordFromConsole(String keyFile, String certFile){
String prompt = "Please enter the passphrase for the private key file " + keyFile
+ ": ";
ConsolePasswordFinder pf = new ConsolePasswordFinder(prompt);
try {
new PEMCredential(new FileInputStream(keyFile),
new FileInputStream(certFile), pf);
} catch (Exception e) {
log.error(e.getMessage(),e);
throw new PAPException(e.getMessage(),e);
}
return pf.getLastReadPassword();
}
public String[] getCommandNameValues() {
return commandNameValues;
}
public ServiceClient getServiceClient() {
return serviceClient;
}
public void printHelpMessage(PrintWriter pw) {
String syntax = String.format("pap-admin [global-options] %s %s", commandNameValues[0], usageText);
pw.println();
helpFormatter.printUsage(pw, helpFormatter.getWidth(), syntax);
if (descriptionText != null) {
pw.println();
helpFormatter.printWrapped(pw, helpFormatter.getWidth(), descriptionText);
}
if (longDescriptionText != null) {
pw.println();
helpFormatter.printWrapped(pw, helpFormatter.getWidth(), longDescriptionText);
}
// command specific options
pw.println();
helpFormatter.printWrapped(pw, helpFormatter.getWidth(), "Valid options:");
helpFormatter.printOptions(pw,
helpFormatter.getWidth(),
commandOptions,
helpFormatter.getLeftPadding(),
helpFormatter.getDescPadding());
// global options
pw.println();
helpFormatter.printWrapped(pw, helpFormatter.getWidth(), "Global options:");
helpFormatter.printOptions(pw,
helpFormatter.getWidth(),
globalOptions,
helpFormatter.getLeftPadding(),
helpFormatter.getDescPadding());
}
protected abstract Options defineCommandOptions();
protected abstract int executeCommandService(CommandLine commandLine, ServiceClient serviceClient)
throws CLIException, ParseException, RemoteException;
protected void printErrorMessage(String msg) {
System.out.println(msg);
}
protected void printOutputMessage(String msg) {
System.out.println(msg);
}
protected void printVerboseMessage(String msg) {
if (verboseMode)
System.out.println(msg);
}
/**
* Return the effective user ID.
*
* @return the effective user ID or <code>null</code> if the effective user ID couldn't be established.
*/
private String getEUID() {
String euid = System.getenv("EUID");
if (!Utils.isDefined(euid)) {
String euidProperty = System.getProperty("effectiveUserId");
if (Utils.isDefined(euidProperty)) {
euid = euidProperty;
} else {
euid = null;
}
}
return euid;
}
/**
* Set the certificate and the key in {@link ServiceCLI#serviceClient} using ~/.globus/user{cert,key}.pem.
*
* @return <code>true</code> if cert and key were found, <code>false</code> otherwise.
*
*/
private boolean setCertFromHomeDir() throws ParseException {
String baseDir = System.getProperty("user.home") + File.separator + ".globus" + File.separator;
String cert = baseDir + "usercert.pem";
String key = baseDir + "userkey.pem";
File file = new File(cert);
if (!file.exists()) {
return false;
}
file = new File(key);
if (!file.exists()) {
return false;
}
serviceClient.setClientCertificate(cert);
serviceClient.setClientPrivateKey(key);
return true;
}
/**
* Set the certificate the the key in {@link ServiceCLI#serviceClient} using the envirinment variables
* X509_USER_CERT and X509_USER_KEY.
*
* @return <code>true</code> if the env variables are set, <code>false</code> otherwise.
* @throws ParseException if only one of X509_USER_CERT or X509_USER_KEY is set.
*
*/
private boolean setCertFromEnvironment() throws ParseException {
String x509UserCert = System.getenv("X509_USER_CERT");
String x509UserKey = System.getenv("X509_USER_KEY");
if (!Utils.isDefined(x509UserCert) && (!Utils.isDefined(x509UserKey))) {
return false;
}
if (!(Utils.isDefined(x509UserCert) && Utils.isDefined(x509UserKey))) {
if (Utils.isDefined(x509UserCert)) {
throw new ParseException("Trying to use environment variable X509_USER_CERT for certificate but environment variable X509_USER_KEY is not set.");
}
throw new ParseException("Trying to use environment variable X509_USER_KEY for private key but environment variable X509_USER_CERT is not set.");
}
serviceClient.setClientCertificate(x509UserCert);
serviceClient.setClientPrivateKey(x509UserKey);
return true;
}
/**
* Set the proxy in {@link ServiceCLI#serviceClient} using the location defined in the environment
* variable X509_USER_PROXY.
*
* @return <code>true</code> if the env variable is set, <code>false</code> otherwise.
*/
private boolean setProxyFromEnvironment() {
String x509UserProxy = System.getenv("X509_USER_PROXY");
if (Utils.isDefined(x509UserProxy)) {
serviceClient.setClientProxy(x509UserProxy);
return true;
} else {
return false;
}
}
/**
* Set the proxy in {@link ServiceCLI#serviceClient} using the standard location (i.e. /tmp).
*
* @param euid the effective user ID to build the proxy file name.
* @return <code>true</code> the proxy was found, <code>false</code> otherwise.
*/
private boolean setProxyFromStandardLocation(String euid) {
String proxy = "/tmp/x509up_u" + euid;
File file = new File(proxy);
if (!file.exists()) {
return false;
}
serviceClient.setClientProxy(proxy);
return true;
}
}