mitm.application.djigzo.tools.CLITool.java Source code

Java tutorial

Introduction

Here is the source code for mitm.application.djigzo.tools.CLITool.java

Source

/*
 * Copyright (c) 2012, Martijn Brinkers, Djigzo.
 * 
 * This file is part of Djigzo email encryption.
 *
 * Djigzo is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License 
 * version 3, 19 November 2007 as published by the Free Software 
 * Foundation.
 *
 * Djigzo 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public 
 * License along with Djigzo. If not, see <http://www.gnu.org/licenses/>
 *
 * Additional permission under GNU AGPL version 3 section 7
 * 
 * If you modify this Program, or any covered work, by linking or 
 * combining it with aspectjrt.jar, aspectjweaver.jar, tyrex-1.0.3.jar, 
 * freemarker.jar, dom4j.jar, mx4j-jmx.jar, mx4j-tools.jar, 
 * spice-classman-1.0.jar, spice-loggerstore-0.5.jar, spice-salt-0.8.jar, 
 * spice-xmlpolicy-1.0.jar, saaj-api-1.3.jar, saaj-impl-1.3.jar, 
 * wsdl4j-1.6.1.jar (or modified versions of these libraries), 
 * containing parts covered by the terms of Eclipse Public License, 
 * tyrex license, freemarker license, dom4j license, mx4j license,
 * Spice Software License, Common Development and Distribution License
 * (CDDL), Common Public License (CPL) the licensors of this Program grant 
 * you additional permission to convey the resulting work.
 */
package mitm.application.djigzo.tools;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.net.ConnectException;
import java.net.MalformedURLException;
import java.net.URL;

import javax.xml.ws.WebServiceException;

import mitm.application.djigzo.ws.DjigzoWSDefaults;
import mitm.application.djigzo.ws.DomainWS;
import mitm.application.djigzo.ws.DomainsWS;
import mitm.application.djigzo.ws.GlobalPreferencesManagerWS;
import mitm.application.djigzo.ws.HierarchicalPropertiesWS;
import mitm.application.djigzo.ws.UserPreferencesDTO;
import mitm.application.djigzo.ws.UserWS;
import mitm.application.djigzo.ws.UsersWS;
import mitm.application.djigzo.ws.impl.factory.DomainWSProxyFactory;
import mitm.application.djigzo.ws.impl.factory.DomainsWSProxyFactory;
import mitm.application.djigzo.ws.impl.factory.GlobalPreferencesManagerWSProxyFactory;
import mitm.application.djigzo.ws.impl.factory.HierarchicalPropertiesWSProxyFactory;
import mitm.application.djigzo.ws.impl.factory.UserWSProxyFactory;
import mitm.application.djigzo.ws.impl.factory.UsersWSProxyFactory;
import mitm.application.djigzo.xml.XMLDomain;
import mitm.application.djigzo.xml.XMLProperty;
import mitm.application.djigzo.xml.XMLStore;
import mitm.application.djigzo.xml.XMLStoreFactory;
import mitm.application.djigzo.xml.XMLUser;
import mitm.common.mail.EmailAddressUtils;
import mitm.common.security.digest.Digest;
import mitm.common.security.digest.Digests;
import mitm.common.util.LogUtils;
import mitm.common.util.MiscStringUtils;
import mitm.common.ws.AbstractWSProxyFactory;
import mitm.common.ws.Credential;
import mitm.common.ws.SOAPPasswordMode;
import mitm.common.ws.WSProxyFactoryException;
import mitm.common.ws.WebServiceCheckedException;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.MissingArgumentException;
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.apache.commons.cli.PosixParser;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.log4j.BasicConfigurator;

/**
 * CLI Tool can be used to manage certain aspects of the DJIGZO gateway from the command line
 * 
 * @author Martijn Brinkers
 *
 */
public class CLITool {
    private static final String COMMAND_NAME = CLITool.class.getName();

    /*
     * The user which is used to authenicate to the DJIGZO soap service
     */
    private String soapUser;

    /*
     * The user password which is used to authenicate to the DJIGZO soap service
     */
    private String soapPassword;

    /*
     * The user's email address
     */
    private String email;

    /*
     * The domain
     */
    private String domain;

    /*
     * The value to set
     */
    private String value;

    /*
     * True if the property is/should be encrypted
     */
    private boolean encrypt;

    /*
     * True if the global options should be set/get
     */
    private boolean global;

    /*
     * The optional salt value for encoding the password
     */
    private String salt;

    /*
     * The CLI options 
     */
    private Option soapUserOption;

    private Option soapPasswordOption;

    private Option helpOption;

    private Option loggingOption;

    private Option hostOption;

    private Option portOption;

    private Option setPropertyOption;

    private Option getPropertyOption;

    private Option valueOption;

    private Option encryptOption;

    private Option emailOption;

    private Option domainOption;

    private Option addUserOption;

    private Option deleteUserOption;

    private Option addDomainOption;

    private Option deleteDomainOption;

    private Option globalOption;

    private Option importXMLOption;

    private Option encodePasswordOption;

    private Option saltOption;

    @SuppressWarnings("static-access")
    private Options createCommandLineOptions() {
        Options options = new Options();

        soapUserOption = OptionBuilder.withLongOpt("soap-user").withDescription("the user for SOAP interface")
                .hasArg().withArgName("SOAP USER").create();
        options.addOption(soapUserOption);

        soapPasswordOption = OptionBuilder.withLongOpt("soap-password")
                .withDescription("the user password for SOAP interface").hasArg().withArgName("SOAP PASSWORD")
                .create();
        options.addOption(soapPasswordOption);

        loggingOption = OptionBuilder.withLongOpt("logging")
                .withDescription("If set, debug logging will be enabled").create();
        options.addOption(loggingOption);

        helpOption = OptionBuilder.withLongOpt("help").withDescription("Show help").create();
        options.addOption(helpOption);

        hostOption = OptionBuilder.withLongOpt("host").withArgName("HOSTNAME | IP").hasArg()
                .withDescription("The host to connect to (127.0.0.1)").create();
        options.addOption(hostOption);

        portOption = OptionBuilder.withLongOpt("port").withArgName("PORT").hasArg()
                .withDescription("The port to use (" + DjigzoWSDefaults.PORT + ")").create();
        options.addOption(portOption);

        setPropertyOption = OptionBuilder.withLongOpt("set-property").withDescription("Sets a property").hasArg()
                .withArgName("PROPERTY NAME").create();
        options.addOption(setPropertyOption);

        getPropertyOption = OptionBuilder.withLongOpt("get-property").withDescription("Returns a property value")
                .hasArg().withArgName("PROPERTY NAME").create();
        options.addOption(getPropertyOption);

        valueOption = OptionBuilder.withLongOpt("value").withDescription("The value to set").hasArg()
                .withArgName("VALUE").create();
        options.addOption(valueOption);

        encryptOption = OptionBuilder.withLongOpt("encrypt")
                .withDescription("If set, the property is an encrypted property").create();
        options.addOption(encryptOption);

        emailOption = OptionBuilder.withLongOpt("email").withDescription("The email address of the user").hasArg()
                .withArgName("EMAIL").create();
        options.addOption(emailOption);

        domainOption = OptionBuilder.withLongOpt("domain").withDescription("The domain").hasArg()
                .withArgName("DOMAIN").create();
        options.addOption(domainOption);

        addUserOption = OptionBuilder.withLongOpt("add-user").withDescription("Add a user").hasArg()
                .withArgName("EMAIL").create();
        options.addOption(addUserOption);

        deleteUserOption = OptionBuilder.withLongOpt("delete-user").withDescription("Delete a user").hasArg()
                .withArgName("EMAIL").create();
        options.addOption(deleteUserOption);

        addDomainOption = OptionBuilder.withLongOpt("add-domain").withDescription("Add a domain").hasArg()
                .withArgName("DOMAIN").create();
        options.addOption(addDomainOption);

        deleteDomainOption = OptionBuilder.withLongOpt("delete-domain").withDescription("Delete a domain").hasArg()
                .withArgName("DOMAIN").create();
        options.addOption(deleteDomainOption);

        globalOption = OptionBuilder.withLongOpt("global").withDescription("Set/Get the global options").create();
        options.addOption(globalOption);

        importXMLOption = OptionBuilder.withLongOpt("import-xml")
                .withDescription("Import users and domains from XML").hasArg().withArgName("XML FILE").create();
        options.addOption(importXMLOption);

        encodePasswordOption = OptionBuilder.withLongOpt("encode-password")
                .withDescription("Encodes a portal password").hasArg().withArgName("PASSWORD").create();
        options.addOption(encodePasswordOption);

        saltOption = OptionBuilder.withLongOpt("salt")
                .withDescription("Optional salt used for encoding a portal password").hasArg().withArgName("SALT")
                .create();
        options.addOption(saltOption);

        return options;
    }

    private Credential getCredentials() {
        String localUser = soapUser;
        String localPassword = soapPassword;

        if (StringUtils.isBlank(localUser)) {
            localUser = "admin";
        }

        if (StringUtils.isBlank(localPassword)) {
            localPassword = "password";
        }

        return new Credential(localUser, localPassword);
    }

    private String getSOAPProtocol() {
        return "http";
    }

    private String getSOAPHost() {
        String host = StringUtils.trimToNull(hostOption.getValue());

        if (host == null) {
            host = "127.0.0.1";
        }

        return host;
    }

    private int getSOAPPort() {
        return NumberUtils.toInt(portOption.getValue(), DjigzoWSDefaults.PORT);
    }

    private void setPasswordMode(AbstractWSProxyFactory<?> factory) {
        /*
         * We use TEXT mode because DIGEST is way too slow for command line util. I think the slowness
         * is caused by initializing the secure random generator
         */
        factory.setPasswordMode(SOAPPasswordMode.TEXT);
    }

    private UsersWS getUsersWS() throws MalformedURLException, WSProxyFactoryException {
        URL wsdlURL = new URL(getSOAPProtocol(), getSOAPHost(), getSOAPPort(), DjigzoWSDefaults.USERS_WSDL);

        UsersWSProxyFactory factory = new UsersWSProxyFactory(wsdlURL, DjigzoWSDefaults.NAMESPACE,
                DjigzoWSDefaults.USERS_SERVICE_NAME);

        setPasswordMode(factory);

        return factory.createProxy(getCredentials());
    }

    private DomainsWS getDomainsWS() throws MalformedURLException, WSProxyFactoryException {
        URL wsdlURL = new URL(getSOAPProtocol(), getSOAPHost(), getSOAPPort(), DjigzoWSDefaults.DOMAINS_WSDL);

        DomainsWSProxyFactory factory = new DomainsWSProxyFactory(wsdlURL, DjigzoWSDefaults.NAMESPACE,
                DjigzoWSDefaults.DOMAINS_SERVICE_NAME);

        setPasswordMode(factory);

        return factory.createProxy(getCredentials());
    }

    private UserWS getUserWS() throws MalformedURLException, WSProxyFactoryException {
        URL wsdlURL = new URL(getSOAPProtocol(), getSOAPHost(), getSOAPPort(), DjigzoWSDefaults.USER_WSDL);

        UserWSProxyFactory factory = new UserWSProxyFactory(wsdlURL, DjigzoWSDefaults.NAMESPACE,
                DjigzoWSDefaults.USER_SERVICE_NAME);

        setPasswordMode(factory);

        return factory.createProxy(getCredentials());
    }

    private DomainWS getDomainWS() throws MalformedURLException, WSProxyFactoryException {
        URL wsdlURL = new URL(getSOAPProtocol(), getSOAPHost(), getSOAPPort(), DjigzoWSDefaults.DOMAIN_WSDL);

        DomainWSProxyFactory factory = new DomainWSProxyFactory(wsdlURL, DjigzoWSDefaults.NAMESPACE,
                DjigzoWSDefaults.DOMAIN_SERVICE_NAME);

        setPasswordMode(factory);

        return factory.createProxy(getCredentials());
    }

    private GlobalPreferencesManagerWS getGlobalPreferencesManagerWS()
            throws MalformedURLException, WSProxyFactoryException {
        URL wsdlURL = new URL(getSOAPProtocol(), getSOAPHost(), getSOAPPort(),
                DjigzoWSDefaults.GLOBAL_PREFERENCES_MANAGER_WSDL);

        GlobalPreferencesManagerWSProxyFactory factory = new GlobalPreferencesManagerWSProxyFactory(wsdlURL,
                DjigzoWSDefaults.NAMESPACE, DjigzoWSDefaults.GLOBAL_PREFERENCES_MANAGER_SERVICE_NAME);

        setPasswordMode(factory);

        return factory.createProxy(getCredentials());
    }

    private HierarchicalPropertiesWS getHierarchicalPropertiesWS()
            throws MalformedURLException, WSProxyFactoryException {
        URL wsdlURL = new URL(getSOAPProtocol(), getSOAPHost(), getSOAPPort(),
                DjigzoWSDefaults.HIERARCHICAL_PROPERTIES_WSDL);

        HierarchicalPropertiesWSProxyFactory factory = new HierarchicalPropertiesWSProxyFactory(wsdlURL,
                DjigzoWSDefaults.NAMESPACE, DjigzoWSDefaults.HIERARCHICAL_PROPERTIES_SERVICE_NAME);

        setPasswordMode(factory);

        return factory.createProxy(getCredentials());
    }

    private void setProperty() throws Exception {
        setProperty(email, domain, global, setPropertyOption.getValue(), value, encrypt);
    }

    private void setProperty(String email, String domain, boolean global, String property, String value,
            boolean encrypt) throws Exception {
        if (email == null && domain == null && !global) {
            throw new MissingArgumentException("Email, domain or global must be specified");
        }

        if (value == null) {
            throw new MissingArgumentException("Value is missing");
        }

        UserPreferencesDTO userPreferences;

        if (email != null) {
            UsersWS usersWS = getUsersWS();

            if (!usersWS.isUser(email)) {
                usersWS.addUser(email);
            }

            userPreferences = getUserWS().getUserPreferences(email);
        } else if (domain != null) {
            DomainsWS domainsWS = getDomainsWS();

            if (!domainsWS.isDomain(domain)) {
                domainsWS.addDomain(domain);
            }

            userPreferences = getDomainWS().getDomainPreferences(domain);
        } else {
            userPreferences = getGlobalPreferencesManagerWS().getGlobalUserPreferences();
        }

        if (StringUtils.isBlank(value)) {
            value = null;
        }

        getHierarchicalPropertiesWS().setProperty(userPreferences, property, value, encrypt);
    }

    private void getProperty() throws Exception {
        if (email == null && domain == null && !global) {
            throw new MissingArgumentException("Email, domain or global must be specified");
        }

        UserPreferencesDTO userPreferences;

        if (email != null) {
            if (!EmailAddressUtils.isValid(email)) {
                throw new CLIRuntimeException(email + " is not a valid email address");
            }

            userPreferences = getUserWS().getUserPreferences(email);
        } else if (domain != null) {
            userPreferences = getDomainWS().getDomainPreferences(domain);
        } else {
            userPreferences = getGlobalPreferencesManagerWS().getGlobalUserPreferences();
        }

        if (userPreferences == null) {
            throw new CLIRuntimeException("User or domain does not exist");
        }

        System.out.println(
                getHierarchicalPropertiesWS().getProperty(userPreferences, getPropertyOption.getValue(), encrypt));
    }

    private void addUser() throws Exception {
        String email = addUserOption.getValue();

        UsersWS usersWS = getUsersWS();

        if (!usersWS.isUser(email)) {
            usersWS.addUser(email);
        }
    }

    private void deleteUser() throws Exception {
        getUsersWS().deleteUser(deleteUserOption.getValue());
    }

    private void addDomain() throws Exception {
        String domain = addDomainOption.getValue();

        DomainsWS domainsWS = getDomainsWS();

        if (!domainsWS.isDomain(domain)) {
            domainsWS.addDomain(domain);
        }
    }

    private void deleteDomain() throws Exception {
        getDomainsWS().deleteDomain(deleteDomainOption.getValue());
    }

    private void importXML() throws Exception {
        File file = new File(importXMLOption.getValue());

        if (!file.exists() || !file.canRead()) {
            throw new FileNotFoundException("XML input file does not exist or cannot be read.");
        }

        XMLStore xmlStore = XMLStoreFactory.unmarshall(new FileInputStream(file));

        for (XMLUser user : xmlStore.getUsers()) {
            for (XMLProperty property : user.getProperties()) {
                setProperty(user.getEmail(), null, false, property.getName(), property.getValue(),
                        property.isEncrypted());
            }
        }

        for (XMLDomain domain : xmlStore.getDomains()) {
            for (XMLProperty property : domain.getProperties()) {
                setProperty(null, domain.getName(), false, property.getName(), property.getValue(),
                        property.isEncrypted());
            }
        }
    }

    private void encodePassword() throws Exception {
        /*
         * We will "hardcode" the password encoding procedure because we do not want to load any 
         * spring configuration or depend on Spring security (for now).
         * 
         * The password is encoded by Spring by appending the salt (witin {}) and then calculate the SHA1 hash:
         * 
         * echo -n "test{1337417534991}" | sha1sum
         * 
         * Note: the password and salt is UTF-8 encoded
         */
        String salt = this.salt;

        if (salt == null) {
            salt = Long.toString(System.currentTimeMillis());
        }

        System.out
                .println(salt + ":"
                        + Digests
                                .digestHex(MiscStringUtils.toUTF8Bytes(
                                        encodePasswordOption.getValue() + "{" + salt + "}"), Digest.SHA1)
                                .toLowerCase());
    }

    private void handleCommandline(String[] args) throws Exception {
        CommandLineParser parser = new PosixParser();

        Options options = createCommandLineOptions();

        HelpFormatter formatter = new HelpFormatter();

        CommandLine commandLine;

        try {
            commandLine = parser.parse(options, args);
        } catch (ParseException e) {
            formatter.printHelp(COMMAND_NAME, options, true);

            throw e;
        }

        initLogging(commandLine.hasOption(loggingOption.getLongOpt()));

        soapUser = soapUserOption.getValue();

        soapPassword = soapPasswordOption.getValue();

        encrypt = commandLine.hasOption(encryptOption.getLongOpt());

        value = valueOption.getValue();

        email = StringUtils.trimToNull(emailOption.getValue());

        domain = StringUtils.trimToNull(domainOption.getValue());

        global = commandLine.hasOption(globalOption.getLongOpt());

        salt = StringUtils.trimToNull(saltOption.getValue());

        if (commandLine.getOptions().length == 0 || commandLine.hasOption(helpOption.getLongOpt())) {
            formatter.printHelp(COMMAND_NAME, options, true);

            System.exit(1);

            return;
        }

        if (commandLine.hasOption(setPropertyOption.getLongOpt())) {
            setProperty();
        } else if (commandLine.hasOption(getPropertyOption.getLongOpt())) {
            getProperty();
        } else if (commandLine.hasOption(addUserOption.getLongOpt())) {
            addUser();
        } else if (commandLine.hasOption(deleteUserOption.getLongOpt())) {
            deleteUser();
        } else if (commandLine.hasOption(addDomainOption.getLongOpt())) {
            addDomain();
        } else if (commandLine.hasOption(deleteDomainOption.getLongOpt())) {
            deleteDomain();
        } else if (commandLine.hasOption(importXMLOption.getLongOpt())) {
            importXML();
        } else if (commandLine.hasOption(encodePasswordOption.getLongOpt())) {
            encodePassword();
        }
    }

    private static void initLogging(boolean enable) {
        if (!enable) {
            /*
             * We will disable all logging because we do not want to clutter the output
             * and std-err with logging info
             */
            LogUtils.disableLogging();
        } else {
            BasicConfigurator.configure();
        }
    }

    public static void main(String[] args) throws Exception {
        CLITool tool = new CLITool();

        try {
            tool.handleCommandline(args);
        } catch (CLIRuntimeException e) {
            System.err.println(e.getMessage());

            System.exit(2);
        } catch (MissingArgumentException e) {
            System.err.println("Not all required parameters are specified. " + e.getMessage());

            System.exit(3);
        } catch (ParseException e) {
            System.err.println("Command line parsing error. " + e);

            System.exit(4);
        } catch (WebServiceException e) {
            Throwable cause = ExceptionUtils.getRootCause(e);

            if (cause instanceof ConnectException) {
                System.err.println("Unable to connect to backend. Cause: " + cause.getMessage());
            } else {
                e.printStackTrace();
            }
            System.exit(5);
        } catch (WSProxyFactoryException e) {
            e.printStackTrace();

            System.exit(6);
        } catch (WebServiceCheckedException e) {
            e.printStackTrace();

            System.exit(7);
        } catch (Exception e) {
            e.printStackTrace();

            System.exit(8);
        }
    }
}