/*
* 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;
}
}