From 1c1cab006d37a1858283b6be32564b4ec27db63c Mon Sep 17 00:00:00 2001
From: Endi Sukma Dewata <edewata@redhat.com>
Date: Sat, 19 May 2012 11:16:53 -0500
Subject: [PATCH] Added user CLI.

The user CLI provides a tool to manage users and user certificates.

Ticket #160
---
 base/common/src/CMakeLists.txt                     |   23 ++-
 .../src/com/netscape/cms/client/cli/CLI.java       |   88 ++++++
 .../src/com/netscape/cms/client/cli/MainCLI.java   |  299 ++++++++++++++++++++
 .../com/netscape/cms/client/user/UserAddCLI.java   |  106 +++++++
 .../netscape/cms/client/user/UserAddCertCLI.java   |   97 +++++++
 .../src/com/netscape/cms/client/user/UserCLI.java  |  152 ++++++++++
 .../com/netscape/cms/client/user/UserFindCLI.java  |   98 +++++++
 .../netscape/cms/client/user/UserFindCertCLI.java  |  104 +++++++
 .../netscape/cms/client/user/UserModifyCLI.java    |  107 +++++++
 .../netscape/cms/client/user/UserRemoveCLI.java    |   53 ++++
 .../cms/client/user/UserRemoveCertCLI.java         |   61 ++++
 .../netscape/cms/client/user/UserRestClient.java   |   92 ++++++
 .../com/netscape/cms/client/user/UserShowCLI.java  |   53 ++++
 .../netscape/cms/client/user/UserShowCertCLI.java  |   87 ++++++
 .../netscape/cms/servlet/base/CMSException.java    |    2 +-
 .../cms/servlet/csadmin/CMSErrorInterceptor.java   |    2 +-
 .../cms/servlet/csadmin/CMSRestClient.java         |   32 +--
 base/setup/CMakeLists.txt                          |    1 +
 base/setup/pki                                     |  106 +++++++
 specs/pki-core.spec                                |    6 +-
 20 files changed, 1547 insertions(+), 22 deletions(-)
 create mode 100644 base/common/src/com/netscape/cms/client/cli/CLI.java
 create mode 100644 base/common/src/com/netscape/cms/client/cli/MainCLI.java
 create mode 100644 base/common/src/com/netscape/cms/client/user/UserAddCLI.java
 create mode 100644 base/common/src/com/netscape/cms/client/user/UserAddCertCLI.java
 create mode 100644 base/common/src/com/netscape/cms/client/user/UserCLI.java
 create mode 100644 base/common/src/com/netscape/cms/client/user/UserFindCLI.java
 create mode 100644 base/common/src/com/netscape/cms/client/user/UserFindCertCLI.java
 create mode 100644 base/common/src/com/netscape/cms/client/user/UserModifyCLI.java
 create mode 100644 base/common/src/com/netscape/cms/client/user/UserRemoveCLI.java
 create mode 100644 base/common/src/com/netscape/cms/client/user/UserRemoveCertCLI.java
 create mode 100644 base/common/src/com/netscape/cms/client/user/UserRestClient.java
 create mode 100644 base/common/src/com/netscape/cms/client/user/UserShowCLI.java
 create mode 100644 base/common/src/com/netscape/cms/client/user/UserShowCertCLI.java
 create mode 100755 base/setup/pki

diff --git a/base/common/src/CMakeLists.txt b/base/common/src/CMakeLists.txt
index 2b9f76e4c929eccaa9cc813c2e96d4769d3222c8..26a8397c9a9c53f30cbcc9261b4e43db4a3e227a 100644
--- a/base/common/src/CMakeLists.txt
+++ b/base/common/src/CMakeLists.txt
@@ -30,6 +30,13 @@ find_file(COMMONS_HTTPCLIENT_JAR
         /usr/share/java
 )
 
+find_file(APACHE_COMMONS_CLI_JAR
+    NAMES
+        apache-commons-cli.jar
+    PATHS
+        /usr/share/java
+)
+
 find_file(APACHE_COMMONS_LANG_JAR
     NAMES
         apache-commons-lang.jar
@@ -444,6 +451,19 @@ set(pki-certsrv_java_SRCS
 )
 
 set(pki-cms_java_SRCS
+    com/netscape/cms/client/cli/CLI.java
+    com/netscape/cms/client/cli/MainCLI.java
+    com/netscape/cms/client/user/UserAddCertCLI.java
+    com/netscape/cms/client/user/UserAddCLI.java
+    com/netscape/cms/client/user/UserCLI.java
+    com/netscape/cms/client/user/UserFindCertCLI.java
+    com/netscape/cms/client/user/UserFindCLI.java
+    com/netscape/cms/client/user/UserModifyCLI.java
+    com/netscape/cms/client/user/UserRemoveCertCLI.java
+    com/netscape/cms/client/user/UserRemoveCLI.java
+    com/netscape/cms/client/user/UserRestClient.java
+    com/netscape/cms/client/user/UserShowCertCLI.java
+    com/netscape/cms/client/user/UserShowCLI.java
     com/netscape/cms/listeners/PinRemovalListener.java
     com/netscape/cms/listeners/RequestInQListener.java
     com/netscape/cms/listeners/CertificateIssuedListener.java
@@ -1119,7 +1139,8 @@ set(pki-cmsbundle_RCS
 set(CMAKE_JAVA_INCLUDE_PATH
     ${PKI_NSUTIL_JAR} ${PKI_CMSUTIL_JAR}
     ${LDAPJDK_JAR} ${SERVLET_JAR} ${VELOCITY_JAR} ${XALAN_JAR} ${XERCES_JAR}
-    ${JSS_JAR} ${COMMONS_CODEC_JAR} ${COMMONS_HTTPCLIENT_JAR} ${APACHE_COMMONS_LANG_JAR}
+    ${JSS_JAR} ${COMMONS_CODEC_JAR} ${COMMONS_HTTPCLIENT_JAR}
+    ${APACHE_COMMONS_CLI_JAR} ${APACHE_COMMONS_LANG_JAR}
     ${TOMCAT_CATALINA_JAR} ${SYMKEY_JAR}
     ${JAXRS_API_JAR} ${RESTEASY_JAXRS_JAR} ${RESTEASY_ATOM_PROVIDER_JAR}
     ${HTTPCLIENT_JAR} ${HTTPCORE_JAR})
diff --git a/base/common/src/com/netscape/cms/client/cli/CLI.java b/base/common/src/com/netscape/cms/client/cli/CLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..1fd0c0dc42f19cfdafc8bc2fa53d24efd4ec1de0
--- /dev/null
+++ b/base/common/src/com/netscape/cms/client/cli/CLI.java
@@ -0,0 +1,88 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2012 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cms.client.cli;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.PosixParser;
+
+
+/**
+ * @author Endi S. Dewata
+ */
+public class CLI {
+
+    public static boolean verbose;
+
+    public static CommandLineParser parser = new PosixParser();
+    public static HelpFormatter formatter = new HelpFormatter();
+
+    public String name;
+    public String description;
+
+    public Options options = new Options();
+    public Map<String, CLI> modules = new LinkedHashMap<String, CLI>();
+
+    public CLI(String name, String description) {
+        this.name = name;
+        this.description = description;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public void addModule(CLI module) {
+        modules.put(module.getName(), module);
+    }
+
+    public CLI getModule(String name) {
+        return modules.get(name);
+    }
+
+    public void execute(String[] args) throws Exception {
+    }
+
+    public void printHelp() {
+    }
+
+    public static boolean isVerbose() {
+        return verbose;
+    }
+
+    public static void setVerbose(boolean verbose) {
+        CLI.verbose = verbose;
+    }
+}
diff --git a/base/common/src/com/netscape/cms/client/cli/MainCLI.java b/base/common/src/com/netscape/cms/client/cli/MainCLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..96e1ea1ee093d0c22a6ab02d5fd5431b435bff27
--- /dev/null
+++ b/base/common/src/com/netscape/cms/client/cli/MainCLI.java
@@ -0,0 +1,299 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2012 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cms.client.cli;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+import org.apache.commons.lang.StringUtils;
+import org.mozilla.jss.CryptoManager;
+import org.mozilla.jss.crypto.AlreadyInitializedException;
+import org.mozilla.jss.crypto.CryptoToken;
+import org.mozilla.jss.util.Password;
+
+import com.netscape.cms.client.user.UserCLI;
+
+/**
+ * @author Endi S. Dewata
+ */
+public class MainCLI extends CLI {
+
+    public String protocol;
+    public String hostname;
+    public String port;
+    public String type;
+
+    public String certDBDirectory;
+    public String certDBPassword;
+    public String certNickname;
+
+    public String url;
+
+    public MainCLI() throws Exception {
+        super("pki", "PKI command-line interface");
+
+        addModule(new UserCLI(this));
+    }
+
+    public String getProtocol() {
+        return protocol;
+    }
+
+    public void setProtocol(String protocol) {
+        this.protocol = protocol;
+    }
+
+    public String getHostname() {
+        return hostname;
+    }
+
+    public void setHostname(String hostname) {
+        this.hostname = hostname;
+    }
+
+    public String getPort() {
+        return port;
+    }
+
+    public void setPort(String port) {
+        this.port = port;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String getCertDBDirectory() {
+        return certDBDirectory;
+    }
+
+    public void setCertDBDirectory(String certDBDirectory) {
+        this.certDBDirectory = certDBDirectory;
+    }
+
+    public String getCertDBPassword() {
+        return certDBPassword;
+    }
+
+    public void setCertDBPassword(String certDBPassword) {
+        this.certDBPassword = certDBPassword;
+    }
+
+    public String getCertNickname() {
+        return certNickname;
+    }
+
+    public void setCertNickname(String certNickname) {
+        this.certNickname = certNickname;
+    }
+
+    public void printHelp() {
+
+        formatter.printHelp(getName()+" [OPTIONS..] <command> [ARGS..]", options);
+
+        System.out.println();
+        System.out.println("Commands:");
+
+        int leftPadding = 1;
+        int rightPadding = 18;
+
+        for (CLI plugin : modules.values()) {
+            String label = plugin.getName();
+
+            int padding = rightPadding - leftPadding - label.length();
+            if (padding < 1) padding = 1;
+
+            System.out.print(StringUtils.repeat(" ", leftPadding));
+            System.out.print(label);
+            System.out.print(StringUtils.repeat(" ", padding));
+            System.out.println(plugin.getDescription());
+        }
+    }
+
+    public void printHelpCommand(String pluginName) {
+        CLI plugin = getModule(pluginName);
+        plugin.printHelp();
+    }
+
+    public void execute(String[] args) throws Exception {
+
+        Option option = new Option("U", true, "URL");
+        option.setArgName("url");
+        options.addOption(option);
+
+        option = new Option("P", true, "Protocol (default: http)");
+        option.setArgName("protocol");
+        options.addOption(option);
+
+        option = new Option("h", true, "Hostname (default: localhost)");
+        option.setArgName("hostname");
+        options.addOption(option);
+
+        option = new Option("p", true, "Port (default: 9180)");
+        option.setArgName("port");
+        options.addOption(option);
+
+        option = new Option("t", true, "Subsystem type (default: ca)");
+        option.setArgName("type");
+        options.addOption(option);
+
+        option = new Option("d", true, "Certificate database directory");
+        option.setArgName("directory");
+        options.addOption(option);
+
+        option = new Option("w", true, "Certificate database password");
+        option.setArgName("password");
+        options.addOption(option);
+
+        option = new Option("n", true, "Certificate nickname");
+        option.setArgName("cert");
+        options.addOption(option);
+
+        options.addOption("v", false, "Verbose");
+        options.addOption(null, "help", false, "Help");
+
+        CommandLine cmd = null;
+
+        try {
+            cmd = parser.parse(options, args, true);
+
+        } catch (Exception e) {
+            System.err.println("Error: " + e.getMessage());
+            printHelp();
+            System.exit(1);
+        }
+
+        String[] cmdArgs = cmd.getArgs();
+
+        if (cmd.hasOption("help") || cmdArgs.length == 0) {
+            printHelp();
+            System.exit(1);
+        }
+
+        verbose = cmd.hasOption("v");
+
+        url = cmd.getOptionValue("U");
+        protocol = cmd.getOptionValue("P", "http");
+        hostname = cmd.getOptionValue("h", "localhost");
+        port = cmd.getOptionValue("p", "9180");
+        type = cmd.getOptionValue("t", "ca");
+
+        if (url == null) {
+            url = protocol + "://" + hostname + ":" + port + "/" + type;
+        }
+
+        if (verbose) System.out.println("Server URL: "+url);
+
+        certDBDirectory = cmd.getOptionValue("d");
+        certDBPassword = cmd.getOptionValue("w");
+        certNickname = cmd.getOptionValue("n");
+
+        if (certDBDirectory != null && certDBPassword != null) {
+
+            if (verbose) System.out.println("Certificate DB: "+certDBDirectory);
+
+            try {
+                CryptoManager.initialize(certDBDirectory);
+            } catch (AlreadyInitializedException e) {
+                // ignore
+            }
+
+            CryptoManager manager = CryptoManager.getInstance();
+            CryptoToken token = manager.getInternalKeyStorageToken();
+            Password password = new Password(certDBPassword.toCharArray());
+
+            try {
+                token.login(password);
+            } catch (Exception e) {
+                System.err.println("Error: " + e.getMessage());
+                if (!token.isLoggedIn()) {
+                    token.initPassword(password, password);
+                }
+            }
+        }
+
+        if (verbose) {
+            System.out.print("Command:");
+            for (String arg : cmdArgs) {
+                System.out.print(" "+arg);
+            }
+            System.out.println();
+        }
+
+        // command-line args: <command> [command args...]
+        if (cmdArgs.length == 0) {
+            printHelp();
+            System.exit(1);
+        }
+
+        String command = cmdArgs[0];
+
+        String moduleName;
+        String moduleCommand;
+
+        // parse command: <module name>-<module command>
+        int i = command.indexOf('-');
+        if (i >= 0) {
+            moduleName = command.substring(0, i);
+            moduleCommand = command.substring(i+1);
+        } else {
+            moduleName = command;
+            moduleCommand = null;
+        }
+
+        // get command module
+        CLI module = getModule(moduleName);
+        if (module == null) {
+            System.err.println("Error: Invalid command \"" + command + "\"");
+            printHelp();
+            System.exit(1);
+        }
+
+        // prepare module arguments
+        String[] moduleArgs = new String[cmdArgs.length];
+        moduleArgs[0] = moduleCommand;
+        System.arraycopy(cmdArgs, 1, moduleArgs, 1, cmdArgs.length-1);
+
+        // execute module command
+        try {
+            module.execute(moduleArgs);
+
+        } catch (Throwable t) {
+            if (verbose) {
+                t.printStackTrace();
+            } else {
+                System.err.println(t.getClass().getSimpleName()+": "+t.getMessage());
+            }
+        }
+    }
+
+    public static void printMessage(String message) {
+        System.out.println(StringUtils.repeat("-", message.length()));
+        System.out.println(message);
+        System.out.println(StringUtils.repeat("-", message.length()));
+    }
+
+    public static void main(String args[]) throws Exception {
+        MainCLI cli = new MainCLI();
+        cli.execute(args);
+    }
+}
diff --git a/base/common/src/com/netscape/cms/client/user/UserAddCLI.java b/base/common/src/com/netscape/cms/client/user/UserAddCLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..f65d75cd986d8d960d920d5210af83ca35baf03f
--- /dev/null
+++ b/base/common/src/com/netscape/cms/client/user/UserAddCLI.java
@@ -0,0 +1,106 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2012 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cms.client.user;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+
+import com.netscape.certsrv.user.UserData;
+import com.netscape.cms.client.cli.CLI;
+import com.netscape.cms.client.cli.MainCLI;
+
+/**
+ * @author Endi S. Dewata
+ */
+public class UserAddCLI extends CLI {
+
+    public UserCLI parent;
+
+    public UserAddCLI(UserCLI parent) {
+        super("add", "Add user");
+        this.parent = parent;
+    }
+
+    public void printHelp() {
+        formatter.printHelp(parent.name + "-" + name + " <User ID> [OPTIONS...]", options);
+    }
+
+    public void execute(String[] args) throws Exception {
+
+        Option option = new Option(null, "fullName", true, "Full name");
+        option.setArgName("fullName");
+        option.setRequired(true);
+        options.addOption(option);
+
+        option = new Option(null, "email", true, "Email");
+        option.setArgName("email");
+        options.addOption(option);
+
+        option = new Option(null, "password", true, "Password");
+        option.setArgName("password");
+        options.addOption(option);
+
+        option = new Option(null, "phone", true, "Phone");
+        option.setArgName("phone");
+        options.addOption(option);
+
+        option = new Option(null, "type", true, "Type");
+        option.setArgName("type");
+        options.addOption(option);
+
+        option = new Option(null, "state", true, "State");
+        option.setArgName("state");
+        options.addOption(option);
+
+        CommandLine cmd = null;
+
+        try {
+            cmd = parser.parse(options, args);
+
+        } catch (Exception e) {
+            System.err.println("Error: " + e.getMessage());
+            printHelp();
+            System.exit(1);
+        }
+
+        String[] cmdArgs = cmd.getArgs();
+
+        if (cmdArgs.length != 1) {
+            printHelp();
+            System.exit(1);
+        }
+
+        String userId = cmdArgs[0];
+
+        UserData userData = new UserData();
+        userData.setID(userId);
+        userData.setFullName(cmd.getOptionValue("fullName"));
+        userData.setEmail(cmd.getOptionValue("email"));
+        userData.setPassword(cmd.getOptionValue("password"));
+        userData.setPhone(cmd.getOptionValue("phone"));
+        userData.setType(cmd.getOptionValue("type"));
+        userData.setState(cmd.getOptionValue("state"));
+
+        userData = parent.client.addUser(userData);
+
+        MainCLI.printMessage("Added user \"" + userId + "\"");
+
+        UserCLI.printUser(userData);
+    }
+}
diff --git a/base/common/src/com/netscape/cms/client/user/UserAddCertCLI.java b/base/common/src/com/netscape/cms/client/user/UserAddCertCLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..2370865ad9694b6791b90b67c056414dd32cc1d2
--- /dev/null
+++ b/base/common/src/com/netscape/cms/client/user/UserAddCertCLI.java
@@ -0,0 +1,97 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2012 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cms.client.user;
+
+import java.io.File;
+import java.util.Scanner;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+
+import com.netscape.certsrv.user.UserCertData;
+import com.netscape.cms.client.cli.CLI;
+import com.netscape.cms.client.cli.MainCLI;
+
+/**
+ * @author Endi S. Dewata
+ */
+public class UserAddCertCLI extends CLI {
+
+    public UserCLI parent;
+
+    public UserAddCertCLI(UserCLI parent) {
+        super("add-cert", "Add user cert");
+        this.parent = parent;
+    }
+
+    public void printHelp() {
+        formatter.printHelp(parent.name + "-" + name + " <User ID> [OPTIONS...]", options);
+    }
+
+    public void execute(String[] args) throws Exception {
+
+        Option option = new Option(null, "input", true, "Input file");
+        option.setArgName("file");
+        option.setRequired(true);
+        options.addOption(option);
+
+        CommandLine cmd = null;
+
+        try {
+            cmd = parser.parse(options, args);
+
+        } catch (Exception e) {
+            System.err.println("Error: " + e.getMessage());
+            printHelp();
+            System.exit(1);
+        }
+
+        String[] cmdArgs = cmd.getArgs();
+
+        if (cmdArgs.length != 1) {
+            printHelp();
+            System.exit(1);
+        }
+
+        String userId = cmdArgs[0];
+        String file = cmd.getOptionValue("input");
+
+        // get cert from file
+        if (verbose) {
+            System.out.println("Reading cert from "+file+".");
+        }
+        String encoded = new Scanner(new File(file)).useDelimiter("\\A").next();
+        if (verbose) {
+            System.out.println(encoded);
+        }
+
+        UserCertData userCertData = new UserCertData();
+        userCertData.setEncoded(encoded);
+
+        if (verbose) {
+            System.out.println(userCertData);
+        }
+
+        userCertData = parent.client.addUserCert(userId, userCertData);
+
+        MainCLI.printMessage("Added certificate \"" + userCertData.getID() + "\"");
+
+        UserCLI.printCert(userCertData);
+    }
+}
diff --git a/base/common/src/com/netscape/cms/client/user/UserCLI.java b/base/common/src/com/netscape/cms/client/user/UserCLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..a7bc752e14278a9b4d588968c76dc6737822de25
--- /dev/null
+++ b/base/common/src/com/netscape/cms/client/user/UserCLI.java
@@ -0,0 +1,152 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2012 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cms.client.user;
+
+import java.util.Arrays;
+
+import org.apache.commons.lang.StringUtils;
+import org.jboss.resteasy.plugins.providers.atom.Link;
+
+import com.netscape.certsrv.user.UserCertData;
+import com.netscape.certsrv.user.UserData;
+import com.netscape.cms.client.cli.CLI;
+import com.netscape.cms.client.cli.MainCLI;
+
+/**
+ * @author Endi S. Dewata
+ */
+public class UserCLI extends CLI {
+
+    public MainCLI parent;
+    public UserRestClient client;
+
+    public UserCLI(MainCLI parent) {
+        super("user", "User management commands");
+        this.parent = parent;
+
+        addModule(new UserFindCLI(this));
+        addModule(new UserShowCLI(this));
+        addModule(new UserAddCLI(this));
+        addModule(new UserModifyCLI(this));
+        addModule(new UserRemoveCLI(this));
+
+        addModule(new UserFindCertCLI(this));
+        addModule(new UserShowCertCLI(this));
+        addModule(new UserAddCertCLI(this));
+        addModule(new UserRemoveCertCLI(this));
+    }
+
+    public void printHelp() {
+
+        System.out.println("Commands:");
+
+        int leftPadding = 1;
+        int rightPadding = 25;
+
+        for (CLI module : modules.values()) {
+            String label = name + "-" + module.getName();
+
+            int padding = rightPadding - leftPadding - label.length();
+            if (padding < 1)
+                padding = 1;
+
+            System.out.print(StringUtils.repeat(" ", leftPadding));
+            System.out.print(label);
+            System.out.print(StringUtils.repeat(" ", padding));
+            System.out.println(module.getDescription());
+        }
+    }
+
+    public void execute(String[] args) throws Exception {
+
+        client = new UserRestClient(parent.url + "/pki", parent.certNickname);
+
+        String command = args[0];
+        String[] commandArgs = Arrays.copyOfRange(args, 1, args.length);
+
+        if (command == null) {
+            printHelp();
+            System.exit(1);
+        }
+
+        CLI module = getModule(command);
+        if (module != null) {
+            module.execute(commandArgs);
+
+        } else {
+            System.err.println("Error: Invalid command \"" + command + "\"");
+            printHelp();
+            System.exit(1);
+        }
+    }
+
+    public static void printUser(UserData userData) {
+        System.out.println("  User ID: " + userData.getID());
+
+        String fullName = userData.getFullName();
+        if (!StringUtils.isEmpty(fullName))
+            System.out.println("  Full name: " + fullName);
+
+        String email = userData.getEmail();
+        if (!StringUtils.isEmpty(email))
+            System.out.println("  Email: " + email);
+
+        String phone = userData.getPhone();
+        if (!StringUtils.isEmpty(phone))
+            System.out.println("  Phone: " + phone);
+
+        String type = userData.getType();
+        if (!StringUtils.isEmpty(type))
+            System.out.println("  Type: " + type);
+
+        String state = userData.getState();
+        if (!StringUtils.isEmpty(state))
+            System.out.println("  State: " + state);
+
+        Link link = userData.getLink();
+        if (verbose && link != null) {
+            System.out.println("  Link: " + link.getHref());
+        }
+    }
+
+    public static void printCert(UserCertData userCertData) throws Exception {
+        System.out.println("  Cert ID: " + userCertData.getID());
+        System.out.println("  Serial Number: " + userCertData.getSerialNumber());
+        System.out.println("  Version: " + userCertData.getVersion());
+        System.out.println("  Issuer DN: " + userCertData.getIssuerDN());
+        System.out.println("  Subject DN: " + userCertData.getSubjectDN());
+
+        String prettyPrint = userCertData.getPrettyPrint();
+        if (prettyPrint != null) {
+            System.out.println();
+            System.out.println(prettyPrint);
+        }
+
+        String encoded = userCertData.getEncoded();
+        if (encoded != null) {
+            System.out.println();
+            System.out.println(encoded);
+        }
+
+        Link link = userCertData.getLink();
+        if (verbose && link != null) {
+            System.out.println("  Link: " + link.getHref());
+        }
+    }
+}
diff --git a/base/common/src/com/netscape/cms/client/user/UserFindCLI.java b/base/common/src/com/netscape/cms/client/user/UserFindCLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..c4014987211beea7911945156013ad0e352bbc92
--- /dev/null
+++ b/base/common/src/com/netscape/cms/client/user/UserFindCLI.java
@@ -0,0 +1,98 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2012 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cms.client.user;
+
+import java.util.Collection;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+
+import com.netscape.certsrv.user.UserCollection;
+import com.netscape.certsrv.user.UserData;
+import com.netscape.cms.client.cli.CLI;
+import com.netscape.cms.client.cli.MainCLI;
+
+/**
+ * @author Endi S. Dewata
+ */
+public class UserFindCLI extends CLI {
+
+    public UserCLI parent;
+
+    public UserFindCLI(UserCLI parent) {
+        super("find", "Find users");
+        this.parent = parent;
+    }
+
+    public void printHelp() {
+        formatter.printHelp(parent.name + "-" + name + " [FILTER] [OPTIONS...]", options);
+    }
+
+    public void execute(String[] args) throws Exception {
+
+        Option option = new Option(null, "start", true, "Page start");
+        option.setArgName("start");
+        options.addOption(option);
+
+        option = new Option(null, "size", true, "Page size");
+        option.setArgName("size");
+        options.addOption(option);
+
+        CommandLine cmd = null;
+
+        try {
+            cmd = parser.parse(options, args);
+
+        } catch (Exception e) {
+            System.err.println("Error: " + e.getMessage());
+            printHelp();
+            System.exit(1);
+        }
+
+        String[] cmdArgs = cmd.getArgs();;
+        String filter = cmdArgs.length > 0 ? cmdArgs[0] : null;
+
+        String s = cmd.getOptionValue("start");
+        Integer start = s == null ? null : Integer.valueOf(s);
+
+        s = cmd.getOptionValue("size");
+        Integer size = s == null ? null : Integer.valueOf(s);
+
+        UserCollection response = parent.client.findUsers(filter, start, size);
+
+        Collection<UserData> entries = response.getUsers();
+
+        MainCLI.printMessage(entries.size() + " user(s) matched");
+
+        boolean first = true;
+
+        for (UserData userData : entries) {
+
+            if (first) {
+                first = false;
+            } else {
+                System.out.println();
+            }
+
+            UserCLI.printUser(userData);
+        }
+
+        MainCLI.printMessage("Number of entries returned " + entries.size());
+    }
+}
diff --git a/base/common/src/com/netscape/cms/client/user/UserFindCertCLI.java b/base/common/src/com/netscape/cms/client/user/UserFindCertCLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..41fe3984a3c9cec5c7c08cee89db6426aae3c5b2
--- /dev/null
+++ b/base/common/src/com/netscape/cms/client/user/UserFindCertCLI.java
@@ -0,0 +1,104 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2012 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cms.client.user;
+
+import java.util.Collection;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+
+import com.netscape.certsrv.user.UserCertCollection;
+import com.netscape.certsrv.user.UserCertData;
+import com.netscape.cms.client.cli.CLI;
+import com.netscape.cms.client.cli.MainCLI;
+
+/**
+ * @author Endi S. Dewata
+ */
+public class UserFindCertCLI extends CLI {
+
+    public UserCLI parent;
+
+    public UserFindCertCLI(UserCLI parent) {
+        super("find-cert", "Find user certs");
+        this.parent = parent;
+    }
+
+    public void printHelp() {
+        formatter.printHelp(parent.name + "-" + name + " <User ID> [OPTIONS...]", options);
+    }
+
+    public void execute(String[] args) throws Exception {
+
+        Option option = new Option(null, "start", true, "Page start");
+        option.setArgName("start");
+        options.addOption(option);
+
+        option = new Option(null, "size", true, "Page size");
+        option.setArgName("size");
+        options.addOption(option);
+
+        CommandLine cmd = null;
+
+        try {
+            cmd = parser.parse(options, args);
+
+        } catch (Exception e) {
+            System.err.println("Error: " + e.getMessage());
+            printHelp();
+            System.exit(1);
+        }
+
+        String[] cmdArgs = cmd.getArgs();
+
+        if (cmdArgs.length != 1) {
+            printHelp();
+            System.exit(1);
+        }
+
+        String userID = cmdArgs[0];
+
+        String s = cmd.getOptionValue("start");
+        Integer start = s == null ? null : Integer.valueOf(s);
+
+        s = cmd.getOptionValue("size");
+        Integer size = s == null ? null : Integer.valueOf(s);
+
+        UserCertCollection response = parent.client.findUserCerts(userID, start, size);
+
+        Collection<UserCertData> entries = response.getCerts();
+
+        MainCLI.printMessage(entries.size() + " user cert(s) matched");
+
+        boolean first = true;
+
+        for (UserCertData userCertData : entries) {
+
+            if (first) {
+                first = false;
+            } else {
+                System.out.println();
+            }
+
+            UserCLI.printCert(userCertData);
+        }
+
+        MainCLI.printMessage("Number of entries returned " + entries.size());
+    }
+}
diff --git a/base/common/src/com/netscape/cms/client/user/UserModifyCLI.java b/base/common/src/com/netscape/cms/client/user/UserModifyCLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..2eca31f01258ec52d3178a3897786149806b2df5
--- /dev/null
+++ b/base/common/src/com/netscape/cms/client/user/UserModifyCLI.java
@@ -0,0 +1,107 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2012 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cms.client.user;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+
+import com.netscape.certsrv.user.UserData;
+import com.netscape.cms.client.cli.CLI;
+import com.netscape.cms.client.cli.MainCLI;
+
+/**
+ * @author Endi S. Dewata
+ */
+public class UserModifyCLI extends CLI {
+
+    public UserCLI parent;
+
+    public UserModifyCLI(UserCLI parent) {
+        super("mod", "Modify user");
+        this.parent = parent;
+    }
+
+    public void printHelp() {
+        formatter.printHelp(parent.name + "-" + name + " <User ID> [OPTIONS...]", options);
+    }
+
+    public void execute(String[] args) throws Exception {
+
+        Option option = new Option(null, "fullName", true, "Full name");
+        option.setArgName("fullName");
+        options.addOption(option);
+
+        option = new Option(null, "email", true, "Email");
+        option.setArgName("email");
+        options.addOption(option);
+
+        option = new Option(null, "password", true, "Password");
+        option.setArgName("password");
+        options.addOption(option);
+
+        option = new Option(null, "phone", true, "Phone");
+        option.setArgName("phone");
+        options.addOption(option);
+
+        // type cannot be modified
+        // option = new Option(null, "type", true, "Type");
+        // option.setArgName("type");
+        // options.addOption(option);
+
+        option = new Option(null, "state", true, "State");
+        option.setArgName("state");
+        options.addOption(option);
+
+        CommandLine cmd = null;
+
+        try {
+            cmd = parser.parse(options, args);
+
+        } catch (Exception e) {
+            System.err.println("Error: " + e.getMessage());
+            printHelp();
+            System.exit(1);
+        }
+
+        String[] cmdArgs = cmd.getArgs();
+
+        if (cmdArgs.length != 1) {
+            printHelp();
+            System.exit(1);
+        }
+
+        String userId = cmdArgs[0];
+
+        UserData userData = new UserData();
+        userData.setID(userId);
+        userData.setFullName(cmd.getOptionValue("fullName"));
+        userData.setEmail(cmd.getOptionValue("email"));
+        userData.setPassword(cmd.getOptionValue("password"));
+        userData.setPhone(cmd.getOptionValue("phone"));
+        // type cannot be modified
+        // userData.setType(cmd.getOptionValue("type"));
+        userData.setState(cmd.getOptionValue("state"));
+
+        userData = parent.client.modifyUser(userId, userData);
+
+        MainCLI.printMessage("Modified user \"" + userId + "\"");
+
+        UserCLI.printUser(userData);
+    }
+}
diff --git a/base/common/src/com/netscape/cms/client/user/UserRemoveCLI.java b/base/common/src/com/netscape/cms/client/user/UserRemoveCLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..6c367ec48049aba05e28ef351d12712c4d4b79f3
--- /dev/null
+++ b/base/common/src/com/netscape/cms/client/user/UserRemoveCLI.java
@@ -0,0 +1,53 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2012 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cms.client.user;
+
+import com.netscape.cms.client.cli.CLI;
+import com.netscape.cms.client.cli.MainCLI;
+
+/**
+ * @author Endi S. Dewata
+ */
+public class UserRemoveCLI extends CLI {
+
+    public UserCLI parent;
+
+    public UserRemoveCLI(UserCLI parent) {
+        super("del", "Remove user");
+        this.parent = parent;
+    }
+
+    public void printHelp() {
+        formatter.printHelp(parent.name + "-" + name + " <User ID> [OPTIONS...]", options);
+    }
+
+    public void execute(String[] args) throws Exception {
+
+        if (args.length != 1) {
+            printHelp();
+            System.exit(1);
+        }
+
+        String userID = args[0];
+
+        parent.client.removeUser(userID);
+
+        MainCLI.printMessage("Deleted user \"" + userID + "\"");
+    }
+}
diff --git a/base/common/src/com/netscape/cms/client/user/UserRemoveCertCLI.java b/base/common/src/com/netscape/cms/client/user/UserRemoveCertCLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..096e46d642ba419578f75b514e718fe533d7b983
--- /dev/null
+++ b/base/common/src/com/netscape/cms/client/user/UserRemoveCertCLI.java
@@ -0,0 +1,61 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2012 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cms.client.user;
+
+import java.net.URLEncoder;
+
+import com.netscape.cms.client.cli.CLI;
+import com.netscape.cms.client.cli.MainCLI;
+
+
+/**
+ * @author Endi S. Dewata
+ */
+public class UserRemoveCertCLI extends CLI {
+
+    public UserCLI parent;
+
+    public UserRemoveCertCLI(UserCLI parent) {
+        super("remove-cert", "Remove user cert");
+        this.parent = parent;
+    }
+
+    public void printHelp() {
+        formatter.printHelp(parent.name + "-" + name + " <User ID> <Cert ID> [OPTIONS...]", options);
+    }
+
+    public void execute(String[] args) throws Exception {
+
+        if (args.length != 2) {
+            printHelp();
+            System.exit(1);
+        }
+
+        String userID = args[0];
+        String certID = args[1];
+
+        if (verbose) {
+            System.out.println("Removing cert "+certID+" from user "+userID+".");
+        }
+
+        parent.client.removeUserCert(userID, URLEncoder.encode(certID, "UTF-8"));
+
+        MainCLI.printMessage("Deleted certificate \"" + certID + "\"");
+    }
+}
diff --git a/base/common/src/com/netscape/cms/client/user/UserRestClient.java b/base/common/src/com/netscape/cms/client/user/UserRestClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..a214b261eb294080bc10d10cf44ddf4c068c642b
--- /dev/null
+++ b/base/common/src/com/netscape/cms/client/user/UserRestClient.java
@@ -0,0 +1,92 @@
+//--- BEGIN COPYRIGHT BLOCK ---
+//This program is free software; you can redistribute it and/or modify
+//it under the terms of the GNU General Public License as published by
+//the Free Software Foundation; version 2 of the License.
+//
+//This program 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 General Public License for more details.
+//
+//You should have received a copy of the GNU General Public License along
+//with this program; if not, write to the Free Software Foundation, Inc.,
+//51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+//(C) 2012 Red Hat, Inc.
+//All rights reserved.
+//--- END COPYRIGHT BLOCK ---
+package com.netscape.cms.client.user;
+
+import java.net.URISyntaxException;
+
+import org.jboss.resteasy.client.ClientResponse;
+
+import com.netscape.certsrv.user.UserCertCollection;
+import com.netscape.certsrv.user.UserCertData;
+import com.netscape.certsrv.user.UserCertResource;
+import com.netscape.certsrv.user.UserCollection;
+import com.netscape.certsrv.user.UserData;
+import com.netscape.certsrv.user.UserResource;
+import com.netscape.cms.servlet.csadmin.CMSRestClient;
+
+/**
+ * @author Endi S. Dewata
+ */
+public class UserRestClient extends CMSRestClient {
+
+    public UserResource userClient;
+    public UserCertResource userCertClient;
+
+    public UserRestClient(String baseUri) throws URISyntaxException {
+        this(baseUri, null);
+    }
+
+    public UserRestClient(String baseUri, String nickname) throws URISyntaxException {
+        super(baseUri, nickname);
+
+        userClient = createProxy(UserResource.class);
+        userCertClient = createProxy(UserCertResource.class);
+    }
+
+    public UserCollection findUsers(String filter, Integer start, Integer size) {
+        return userClient.findUsers(filter, start, size);
+    }
+
+    public UserData getUser(String userID) {
+        return userClient.getUser(userID);
+    }
+
+    public UserData addUser(UserData userData) {
+        @SuppressWarnings("unchecked")
+        ClientResponse<UserData> response = (ClientResponse<UserData>)userClient.addUser(userData);
+        return response.getEntity();
+    }
+
+    public UserData modifyUser(String userID, UserData userData) {
+        @SuppressWarnings("unchecked")
+        ClientResponse<UserData> response = (ClientResponse<UserData>)userClient.modifyUser(userID, userData);
+        return response.getEntity();
+    }
+
+    public void removeUser(String userID) {
+        userClient.removeUser(userID);
+    }
+
+    public UserCertCollection findUserCerts(String userID, Integer start, Integer size) {
+        return userCertClient.findUserCerts(userID, start, size);
+    }
+
+    public UserCertData getUserCert(String userID, String certID) {
+        return userCertClient.getUserCert(userID, certID);
+    }
+
+    public UserCertData addUserCert(String userID, UserCertData userCertData) {
+        @SuppressWarnings("unchecked")
+        ClientResponse<UserCertData> response = (ClientResponse<UserCertData>)userCertClient.addUserCert(userID, userCertData);
+        return response.getEntity();
+    }
+
+    public void removeUserCert(String userID, String certID) {
+        userCertClient.removeUserCert(userID, certID);
+    }
+}
diff --git a/base/common/src/com/netscape/cms/client/user/UserShowCLI.java b/base/common/src/com/netscape/cms/client/user/UserShowCLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..9549c05051f1d978d93735149b508a056d213b88
--- /dev/null
+++ b/base/common/src/com/netscape/cms/client/user/UserShowCLI.java
@@ -0,0 +1,53 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2012 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cms.client.user;
+
+import com.netscape.certsrv.user.UserData;
+import com.netscape.cms.client.cli.CLI;
+
+/**
+ * @author Endi S. Dewata
+ */
+public class UserShowCLI extends CLI {
+
+    public UserCLI parent;
+
+    public UserShowCLI(UserCLI parent) {
+        super("show", "Show user");
+        this.parent = parent;
+    }
+
+    public void printHelp() {
+        formatter.printHelp(parent.name + "-" + name + " <User ID> [OPTIONS...]", options);
+    }
+
+    public void execute(String[] args) throws Exception {
+
+        if (args.length != 1) {
+            printHelp();
+            System.exit(1);
+        }
+
+        String userId = args[0];
+
+        UserData userData = parent.client.getUser(userId);
+
+        UserCLI.printUser(userData);
+    }
+}
diff --git a/base/common/src/com/netscape/cms/client/user/UserShowCertCLI.java b/base/common/src/com/netscape/cms/client/user/UserShowCertCLI.java
new file mode 100644
index 0000000000000000000000000000000000000000..7e58fb1b00638aaa3383ac18451cac634caefe7e
--- /dev/null
+++ b/base/common/src/com/netscape/cms/client/user/UserShowCertCLI.java
@@ -0,0 +1,87 @@
+// --- BEGIN COPYRIGHT BLOCK ---
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; version 2 of the License.
+//
+// This program 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// (C) 2012 Red Hat, Inc.
+// All rights reserved.
+// --- END COPYRIGHT BLOCK ---
+
+package com.netscape.cms.client.user;
+
+import java.io.FileWriter;
+import java.io.PrintWriter;
+import java.net.URLEncoder;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Option;
+
+import com.netscape.certsrv.user.UserCertData;
+import com.netscape.cms.client.cli.CLI;
+
+/**
+ * @author Endi S. Dewata
+ */
+public class UserShowCertCLI extends CLI {
+
+    public UserCLI parent;
+
+    public UserShowCertCLI(UserCLI parent) {
+        super("show-cert", "Show user cert");
+        this.parent = parent;
+    }
+
+    public void printHelp() {
+        formatter.printHelp(parent.name + "-" + name + " <User ID> <Cert ID> [OPTIONS...]", options);
+    }
+
+    public void execute(String[] args) throws Exception {
+
+        Option option = new Option(null, "output", true, "Output file");
+        option.setArgName("file");
+        options.addOption(option);
+
+        CommandLine cmd = null;
+
+        try {
+            cmd = parser.parse(options, args);
+
+        } catch (Exception e) {
+            System.err.println("Error: " + e.getMessage());
+            printHelp();
+            System.exit(1);
+        }
+
+        String[] cmdArgs = cmd.getArgs();
+
+        if (cmdArgs.length != 2) {
+            printHelp();
+            System.exit(1);
+        }
+
+        String userID = cmdArgs[0];
+        String certID = cmdArgs[1];
+        String file = cmd.getOptionValue("output");
+
+        UserCertData userCertData = parent.client.getUserCert(userID, URLEncoder.encode(certID, "UTF-8"));
+
+        String encoded = userCertData.getEncoded();
+        if (encoded != null && file != null) {
+            // store cert to file
+            PrintWriter out = new PrintWriter(new FileWriter(file));
+            out.print(encoded);
+            out.close();
+        }
+
+        UserCLI.printCert(userCertData);
+    }
+}
diff --git a/base/common/src/com/netscape/cms/servlet/base/CMSException.java b/base/common/src/com/netscape/cms/servlet/base/CMSException.java
index 44a9a23dc7edc8135341c827586b049d418b81a4..5ceb30ee124c2b45f1eb608092999fdbd9fd2251 100644
--- a/base/common/src/com/netscape/cms/servlet/base/CMSException.java
+++ b/base/common/src/com/netscape/cms/servlet/base/CMSException.java
@@ -161,7 +161,7 @@ public class CMSException extends RuntimeException {
             return Response
                     .status(exception.getCode())
                     .entity(exception.getData())
-                    .type(MediaType.TEXT_XML)
+                    .type(MediaType.APPLICATION_XML)
                     .build();
         }
     }
diff --git a/base/common/src/com/netscape/cms/servlet/csadmin/CMSErrorInterceptor.java b/base/common/src/com/netscape/cms/servlet/csadmin/CMSErrorInterceptor.java
index b751fb091217f8fec7b0aabebc68dcef1aa68d9d..8704223910797ec6055f02292872994a41bcfa25 100644
--- a/base/common/src/com/netscape/cms/servlet/csadmin/CMSErrorInterceptor.java
+++ b/base/common/src/com/netscape/cms/servlet/csadmin/CMSErrorInterceptor.java
@@ -38,7 +38,7 @@ public class CMSErrorInterceptor implements ClientErrorInterceptor {
         String contentType = headers.getFirst("Content-Type");
 
         // handle XML content only
-        if (!contentType.startsWith(MediaType.TEXT_XML))
+        if (contentType == null || !contentType.startsWith(MediaType.APPLICATION_XML))
             return;
 
         CMSException exception;
diff --git a/base/common/src/com/netscape/cms/servlet/csadmin/CMSRestClient.java b/base/common/src/com/netscape/cms/servlet/csadmin/CMSRestClient.java
index 37db06bd5b8b71f1477f10c30c9cd4e82aa2eabd..ff028660a382e410fc11915b9584bf8a9d7b8f30 100644
--- a/base/common/src/com/netscape/cms/servlet/csadmin/CMSRestClient.java
+++ b/base/common/src/com/netscape/cms/servlet/csadmin/CMSRestClient.java
@@ -17,12 +17,23 @@ import org.apache.http.conn.scheme.SchemeSocketFactory;
 import org.apache.http.impl.client.DefaultHttpClient;
 import org.apache.http.params.HttpParams;
 import org.jboss.resteasy.client.ClientExecutor;
+import org.jboss.resteasy.client.ProxyFactory;
 import org.jboss.resteasy.client.core.executors.ApacheHttpClient4Executor;
 import org.jboss.resteasy.spi.ResteasyProviderFactory;
 import org.mozilla.jss.ssl.SSLCertificateApprovalCallback;
 import org.mozilla.jss.ssl.SSLSocket;
 
 public abstract class CMSRestClient {
+
+    protected String clientCertNickname;
+    protected ResteasyProviderFactory providerFactory;
+    protected ClientExecutor executor;
+    protected URI uri;
+
+    public CMSRestClient(String baseUri) throws URISyntaxException {
+        this(baseUri, null);
+    }
+
     // Callback to approve or deny returned SSL server certs
     // Right now, simply approve the cert.
     // ToDO: Look into taking this JSS http client code and move it into
@@ -58,25 +69,11 @@ public abstract class CMSRestClient {
             //For now lets just accept the server cert. This is a test tool, being
             // pointed at a well know kra instance.
 
-            if (servercert != null) {
-                System.out.println("Peer SSL Servercert details: " +
-                        "\n     subject: " + servercert.getSubjectDN().toString() +
-                        "\n     issuer:  " + servercert.getIssuerDN().toString() +
-                        "\n     serial:  " + servercert.getSerialNumber().toString()
-                        );
-            }
-
             SSLCertificateApprovalCallback.ValidityItem item;
 
             Enumeration<?> errors = status.getReasons();
-            int i = 0;
             while (errors.hasMoreElements()) {
-                i++;
                 item = (SSLCertificateApprovalCallback.ValidityItem) errors.nextElement();
-                System.out.println("item " + i +
-                        " reason=" + item.getReason() +
-                        " depth=" + item.getDepth());
-
                 int reason = item.getReason();
 
                 if (reason ==
@@ -168,8 +165,7 @@ public abstract class CMSRestClient {
 
     }
 
-    protected static String clientCertNickname;
-    protected ResteasyProviderFactory providerFactory;
-    protected ClientExecutor executor;
-    protected URI uri;
+    public <T> T createProxy(Class<T> clazz) {
+        return ProxyFactory.create(clazz, uri, executor, providerFactory);
+    }
 }
diff --git a/base/setup/CMakeLists.txt b/base/setup/CMakeLists.txt
index 05f18332ddddd8170709d97d969b109b41b0d0d0..54fe3b7f6515d31bef209e36021e61beb6bfd827 100644
--- a/base/setup/CMakeLists.txt
+++ b/base/setup/CMakeLists.txt
@@ -2,6 +2,7 @@ project(setup)
 
 install(
     FILES
+        pki
         pkicreate
         pkiremove
         pki-setup-proxy
diff --git a/base/setup/pki b/base/setup/pki
new file mode 100755
index 0000000000000000000000000000000000000000..f4ab1a4fc22af66d0d6e149d54739a12974f9d29
--- /dev/null
+++ b/base/setup/pki
@@ -0,0 +1,106 @@
+#!/usr/bin/perl
+# --- BEGIN COPYRIGHT BLOCK ---
+# This library 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
+#
+# This library 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 library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor,
+# Boston, MA  02110-1301  USA
+#
+# Copyright (C) 2007 Red Hat, Inc.
+# All rights reserved.
+# --- END COPYRIGHT BLOCK ---
+
+###############################################################################
+##  (1) Specify variables used by this script.                               ##
+###############################################################################
+
+my $PRODUCT="pki";
+my $libpath="";
+
+
+###############################################################################
+##  (2) Define helper functions.                                             ##
+###############################################################################
+
+sub invalid_architecture()
+{
+    print "\n";
+    print "ERROR:  pkisilent does not execute on this architecture\n";
+    print "ERROR:  check to make sure pki-native-tools package is installed!\n";
+    print "\n";
+}
+
+
+###############################################################################
+##  (3) Set the LD_LIBRARY_PATH environment variable                         ##
+##      (as well as the ${libpath} java property) to determine the           ##
+##      search order this command uses to find shared libraries.             ##
+###############################################################################
+
+my $ARCHITECTURE=`uname -i`;
+chop( $ARCHITECTURE );
+
+if( $ARCHITECTURE eq "i386" ) {
+    $libpath="/usr/lib";
+
+    $ENV{LD_LIBRARY_PATH} = "/usr/lib/jss:"
+                          . "/usr/lib:/lib";
+} elsif($ARCHITECTURE eq "x86_64") {
+    $libpath="/usr/lib64";
+
+    $ENV{LD_LIBRARY_PATH} = "/usr/lib64/jss:"
+                          . "/usr/lib64:/lib64:"
+                          . "/usr/lib/jss:"
+                          . "/usr/lib:/lib";
+} else {
+    invalid_architecture();
+    exit(255);
+}
+
+
+###############################################################################
+##  (4) Set the CP environment variable to determine the search              ##
+##      order this command wrapper uses to find jar files.                   ##
+###############################################################################
+
+$ENV{CLASSPATH} = "/usr/share/java/${PRODUCT}/pki-certsrv.jar:"
+                . "/usr/share/java/${PRODUCT}/pki-cms.jar:"
+                . "/usr/share/java/apache-commons-cli.jar:"
+                . "/usr/share/java/apache-commons-lang.jar:"
+                . "/usr/share/java/commons-httpclient.jar:"
+                . "/usr/share/java/httpcomponents/httpclient.jar:"
+                . "/usr/share/java/resteasy/jaxrs-api.jar:"
+                . "/usr/share/java/resteasy/resteasy-atom-provider.jar:"
+                . "/usr/share/java/resteasy/resteasy-jaxb-provider.jar:"
+                . "/usr/share/java/resteasy/resteasy-jaxrs.jar:"
+                . "/usr/share/java/servlet.jar:";
+
+if( $ARCHITECTURE eq "x86_64" ) {
+    $ENV{CLASSPATH} = $ENV{CLASSPATH}
+                    . "/usr/lib64/java/jss4.jar:"
+                    . "/usr/lib/java/jss4.jar:";
+} else {
+    $ENV{CLASSPATH} = $ENV{CLASSPATH}
+                    . "/usr/lib/java/jss4.jar:";
+}
+
+
+###############################################################################
+##  (5) Execute the java command specified by this java command wrapper      ##
+##      based upon the preset LD_LIBRARY_PATH and CP environment variables.  ##
+###############################################################################
+
+my @args = ();
+foreach (@ARGV) {
+    push(@args, quotemeta($_));
+}
+
+system("java -cp $ENV{CLASSPATH} com.netscape.cms.client.cli.MainCLI @args");
diff --git a/specs/pki-core.spec b/specs/pki-core.spec
index cd172a88769459c8ae48a5163a2195fb020b94c6..b742e52cfdc2a70769e2430bf3fa21f39b55868d 100644
--- a/specs/pki-core.spec
+++ b/specs/pki-core.spec
@@ -14,7 +14,7 @@ distutils.sysconfig import get_python_lib; print(get_python_lib(1))")}
 
 Name:             pki-core
 Version:          10.0.0
-Release:          %{?relprefix}16%{?prerel}%{?dist}
+Release:          %{?relprefix}17%{?prerel}%{?dist}
 Summary:          Certificate System - PKI Core Components
 URL:              http://pki.fedoraproject.org/
 License:          GPLv2
@@ -1147,6 +1147,7 @@ fi
 %files -n pki-setup
 %defattr(-,root,root,-)
 %doc base/setup/LICENSE
+%{_bindir}/pki
 %{_bindir}/pkicreate
 %{_bindir}/pkiremove
 %{_bindir}/pki-setup-proxy
@@ -1412,6 +1413,9 @@ fi
 
 
 %changelog
+* Mon May 29 2012 Endi S. Dewata <edewata@redhat.com> 10.0.0-0.17.a1
+- Added CLI for REST services
+
 * Fri May 18 2012 Matthew Harmsen <mharmsen@redhat.com> 10.0.0-0.16.a1
 - Integration of Tomcat 7
 - Addition of centralized 'pki-tomcatd' systemd functionality to the
-- 
1.7.7.6

