AuthCommand.java

/*
 * Copyright (c) 2014 Wael Chatila / Icegreen Technologies. All Rights Reserved.
 * This software is released under the Apache license 2.0
 * This file has been used and modified.
 * Original file can be found on http://foedus.sourceforge.net
 */
package com.icegreen.greenmail.pop3.commands;

import java.io.IOException;
import java.util.Arrays;

import com.icegreen.greenmail.pop3.Pop3Connection;
import com.icegreen.greenmail.pop3.Pop3State;
import com.icegreen.greenmail.store.FolderException;
import com.icegreen.greenmail.user.GreenMailUser;
import com.icegreen.greenmail.user.UserException;
import com.icegreen.greenmail.util.EncodingUtil;
import com.icegreen.greenmail.util.SaslMessage;

/**
 * SASL : PLAIN
 * <p>
 * <a href="https://tools.ietf.org/html/rfc5034">...</a>
 * <p>
 * AUTH mechanism [initial-response]
 * mechanism PLAIN : See <a href="https://tools.ietf.org/html/rfc4616">...</a>
 */
public class AuthCommand
        extends Pop3Command {

    public static final String CONTINUATION = "+ ";

    @Override
    public boolean isValidForState(Pop3State state) {

        return !state.isAuthenticated();
    }

    public enum Pop3SaslAuthMechanism {
        PLAIN;

        /**
         * WS separated list of supported auth mechanism.
         *
         * @return a list of supported auth mechanism.
         */
        static String list() {
            StringBuilder buf = new StringBuilder();
            for (Pop3SaslAuthMechanism mechanism : values()) {
                if (buf.length() > 0) {
                    buf.append(' ');
                }
                buf.append(mechanism.name());
            }
            return buf.toString();
        }
    }

    @Override
    public void execute(Pop3Connection conn, Pop3State state, String cmd) {
        if (state.isAuthenticated()) {
            conn.println("-ERR Already authenticated");
            return;
        }

        String[] args = cmd.split(" ");
        if (args.length < 2) {
            conn.println("-ERR Required syntax: AUTH mechanism [initial-response]");
            return;
        }

        String mechanism = args[1];
        if (Pop3SaslAuthMechanism.PLAIN.name().equalsIgnoreCase(mechanism)) {
            authPlain(conn, state, args);
        } else {
            conn.println("-ERR Required syntax: AUTH mechanism <" + mechanism +
                    "> not supported, expected one of " + Arrays.toString(Pop3SaslAuthMechanism.values()));
        }
    }

    private void authPlain(Pop3Connection conn, Pop3State state, String[] args) {
        // https://tools.ietf.org/html/rfc4616
        String initialResponse;
        if (args.length == 2 || args.length == 3 && "=".equals(args[2])) { // Continuation?
            conn.println(CONTINUATION);
            try {
                initialResponse = conn.readLine();
            } catch (IOException e) {
                conn.println("-ERR Invalid syntax, expected continuation with iniital-response");
                return;
            }
        } else if (args.length == 3) {
            initialResponse = args[2];
        } else {
            conn.println("-ERR Invalid syntax, expected initial-response : AUTH PLAIN [initial-response]");
            return;
        }

        // authorization-id\0authentication-id\0passwd
        final SaslMessage saslMessage;
        try {
            saslMessage = SaslMessage.parse(EncodingUtil.decodeBase64(initialResponse));
        } catch(IllegalArgumentException ex) { // Invalid Base64
            log.error("Expected base64 encoding but got <"+initialResponse+">", ex); /* GreenMail is just a test server */
            conn.println("-ERR Authentication failed, expected base64 encoding : " + ex.getMessage() );
            return;
        }

        GreenMailUser user;
        try {
            user = state.getUser(saslMessage.getAuthcid());
            state.setUser(user);
        } catch (UserException e) {
            log.error("Can not get user <" + saslMessage.getAuthcid() + ">", e);
            conn.println("-ERR Authentication failed: " + e.getMessage() /* GreenMail is just a test server */);
            return;
        }

        try {
            state.authenticate(saslMessage.getPasswd());
            conn.println("+OK");
        } catch (UserException e) {
            log.error("Can not authenticate using user <" + user.getLogin() + ">", e);
            conn.println("-ERR Authentication failed: " + e.getMessage());
        } catch (FolderException e) {
            log.error("Can not authenticate using user " + user + ", internal error", e);
            conn.println("-ERR Authentication failed, internal error: " + e.getMessage());
        }
    }
}