/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.apache.logging.dumbster.smtp; import org.apache.logging.log4j.util.Strings; /** * Contains an SMTP client request. Handles state transitions using the following state transition table. * <PRE> * -----------+------------------------------------------------------------------------------------------------- * | State * Action +-------------+-----------+-----------+--------------+---------------+---------------+------------ * | CONNECT | GREET | MAIL | RCPT | DATA_HDR | DATA_BODY | QUIT * -----------+-------------+-----------+-----------+--------------+---------------+---------------+------------ * connect | 220/GREET | 503/GREET | 503/MAIL | 503/RCPT | 503/DATA_HDR | 503/DATA_BODY | 503/QUIT * ehlo | 503/CONNECT | 250/MAIL | 503/MAIL | 503/RCPT | 503/DATA_HDR | 503/DATA_BODY | 503/QUIT * mail | 503/CONNECT | 503/GREET | 250/RCPT | 503/RCPT | 503/DATA_HDR | 503/DATA_BODY | 250/RCPT * rcpt | 503/CONNECT | 503/GREET | 503/MAIL | 250/RCPT | 503/DATA_HDR | 503/DATA_BODY | 503/QUIT * data | 503/CONNECT | 503/GREET | 503/MAIL | 354/DATA_HDR | 503/DATA_HDR | 503/DATA_BODY | 503/QUIT * data_end | 503/CONNECT | 503/GREET | 503/MAIL | 503/RCPT | 250/QUIT | 250/QUIT | 503/QUIT * unrecog | 500/CONNECT | 500/GREET | 500/MAIL | 500/RCPT | ---/DATA_HDR | ---/DATA_BODY | 500/QUIT * quit | 503/CONNECT | 503/GREET | 503/MAIL | 503/RCPT | 503/DATA_HDR | 503/DATA_BODY | 250/CONNECT * blank_line | 503/CONNECT | 503/GREET | 503/MAIL | 503/RCPT | ---/DATA_BODY | ---/DATA_BODY | 503/QUIT * rset | 250/GREET | 250/GREET | 250/GREET | 250/GREET | 250/GREET | 250/GREET | 250/GREET * vrfy | 252/CONNECT | 252/GREET | 252/MAIL | 252/RCPT | 252/DATA_HDR | 252/DATA_BODY | 252/QUIT * expn | 252/CONNECT | 252/GREET | 252/MAIL | 252/RCPT | 252/DATA_HDR | 252/DATA_BODY | 252/QUIT * help | 211/CONNECT | 211/GREET | 211/MAIL | 211/RCPT | 211/DATA_HDR | 211/DATA_BODY | 211/QUIT * noop | 250/CONNECT | 250/GREET | 250/MAIL | 250/RCPT | 250|DATA_HDR | 250/DATA_BODY | 250/QUIT * </PRE> */ public class SmtpRequest { /** * SMTP action received from client. */ private final SmtpActionType action; /** * Current state of the SMTP state table. */ private final SmtpState state; /** * Additional information passed from the client with the SMTP action. */ private final String params; /** * Create a new SMTP client request. * * @param actionType type of action/command * @param params remainder of command line once command is removed * @param state current SMTP server state */ public SmtpRequest(final SmtpActionType actionType, final String params, final SmtpState state) { this.action = actionType; this.state = state; this.params = params; } /** * Execute the SMTP request returning a response. This method models the state transition table for the SMTP server. * * @return reponse to the request */ public SmtpResponse execute() { SmtpResponse response = null; if (action.isStateless()) { if (SmtpActionType.EXPN == action || SmtpActionType.VRFY == action) { response = new SmtpResponse(252, "Not supported", this.state); } else if (SmtpActionType.HELP == action) { response = new SmtpResponse(211, "No help available", this.state); } else if (SmtpActionType.NOOP == action) { response = new SmtpResponse(250, "OK", this.state); } else if (SmtpActionType.VRFY == action) { response = new SmtpResponse(252, "Not supported", this.state); } else if (SmtpActionType.RSET == action) { response = new SmtpResponse(250, "OK", SmtpState.GREET); } else { response = new SmtpResponse(500, "Command not recognized", this.state); } } else { // Stateful commands if (SmtpActionType.CONNECT == action) { if (SmtpState.CONNECT == state) { response = new SmtpResponse(220, "localhost Dumbster SMTP service ready", SmtpState.GREET); } else { response = new SmtpResponse(503, "Bad sequence of commands: " + action, this.state); } } else if (SmtpActionType.EHLO == action) { if (SmtpState.GREET == state) { response = new SmtpResponse(250, "OK", SmtpState.MAIL); } else { response = new SmtpResponse(503, "Bad sequence of commands: " + action, this.state); } } else if (SmtpActionType.MAIL == action) { if (SmtpState.MAIL == state || SmtpState.QUIT == state) { response = new SmtpResponse(250, "OK", SmtpState.RCPT); } else { response = new SmtpResponse(503, "Bad sequence of commands: " + action, this.state); } } else if (SmtpActionType.RCPT == action) { if (SmtpState.RCPT == state) { response = new SmtpResponse(250, "OK", this.state); } else { response = new SmtpResponse(503, "Bad sequence of commands: " + action, this.state); } } else if (SmtpActionType.DATA == action) { if (SmtpState.RCPT == state) { response = new SmtpResponse(354, "Start mail input; end with <CRLF>.<CRLF>", SmtpState.DATA_HDR); } else { response = new SmtpResponse(503, "Bad sequence of commands: " + action, this.state); } } else if (SmtpActionType.UNRECOG == action) { if (SmtpState.DATA_HDR == state || SmtpState.DATA_BODY == state) { response = new SmtpResponse(-1, Strings.EMPTY, this.state); } else { response = new SmtpResponse(500, "Command not recognized", this.state); } } else if (SmtpActionType.DATA_END == action) { if (SmtpState.DATA_HDR == state || SmtpState.DATA_BODY == state) { response = new SmtpResponse(250, "OK", SmtpState.QUIT); } else { response = new SmtpResponse(503, "Bad sequence of commands: " + action, this.state); } } else if (SmtpActionType.BLANK_LINE == action) { if (SmtpState.DATA_HDR == state) { response = new SmtpResponse(-1, Strings.EMPTY, SmtpState.DATA_BODY); } else if (SmtpState.DATA_BODY == state) { response = new SmtpResponse(-1, Strings.EMPTY, this.state); } else { response = new SmtpResponse(503, "Bad sequence of commands: " + action, this.state); } } else if (SmtpActionType.QUIT == action) { if (SmtpState.QUIT == state) { response = new SmtpResponse(221, "localhost Dumbster service closing transmission channel", SmtpState.CONNECT); } else { response = new SmtpResponse(503, "Bad sequence of commands: " + action, this.state); } } else { response = new SmtpResponse(500, "Command not recognized", this.state); } } return response; } /** * Create an SMTP request object given a line of the input stream from the client and the current internal state. * * @param s line of input * @param state current state * @return a populated SmtpRequest object */ public static SmtpRequest createRequest(final String s, final SmtpState state) { SmtpActionType action = null; String params = null; if (state == SmtpState.DATA_HDR) { if (s.equals(".")) { action = SmtpActionType.DATA_END; } else if (s.length() < 1) { action = SmtpActionType.BLANK_LINE; } else { action = SmtpActionType.UNRECOG; params = s; } } else if (state == SmtpState.DATA_BODY) { if (s.equals(".")) { action = SmtpActionType.DATA_END; } else { action = SmtpActionType.UNRECOG; if (s.length() < 1) { params = "\n"; } else { params = s; } } } else { final String su = s.toUpperCase(); if (su.startsWith("EHLO ") || su.startsWith("HELO")) { action = SmtpActionType.EHLO; params = s.substring(5); } else if (su.startsWith("MAIL FROM:")) { action = SmtpActionType.MAIL; params = s.substring(10); } else if (su.startsWith("RCPT TO:")) { action = SmtpActionType.RCPT; params = s.substring(8); } else if (su.startsWith("DATA")) { action = SmtpActionType.DATA; } else if (su.startsWith("QUIT")) { action = SmtpActionType.QUIT; } else if (su.startsWith("RSET")) { action = SmtpActionType.RSET; } else if (su.startsWith("NOOP")) { action = SmtpActionType.NOOP; } else if (su.startsWith("EXPN")) { action = SmtpActionType.EXPN; } else if (su.startsWith("VRFY")) { action = SmtpActionType.VRFY; } else if (su.startsWith("HELP")) { action = SmtpActionType.HELP; } else { action = SmtpActionType.UNRECOG; } } final SmtpRequest req = new SmtpRequest(action, params, state); return req; } /** * Get the parameters of this request (remainder of command line once the command is removed. * * @return parameters */ public String getParams() { return params; } }