001: /*
002: * Dumbster - a dummy SMTP server
003: * Copyright 2004 Jason Paul Kitchen
004: *
005: * Licensed under the Apache License, Version 2.0 (the "License");
006: * you may not use this file except in compliance with the License.
007: * You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package com.dumbster.smtp;
019:
020: /**
021: * Contains an SMTP client request. Handles state transitions using the following state transition table.
022: * <PRE>
023: * -----------+-------------------------------------------------------------------------------------------------
024: * | State
025: * Action +-------------+-----------+-----------+--------------+---------------+---------------+------------
026: * | CONNECT | GREET | MAIL | RCPT | DATA_HDR | DATA_BODY | QUIT
027: * -----------+-------------+-----------+-----------+--------------+---------------+---------------+------------
028: * connect | 220/GREET | 503/GREET | 503/MAIL | 503/RCPT | 503/DATA_HDR | 503/DATA_BODY | 503/QUIT
029: * ehlo | 503/CONNECT | 250/MAIL | 503/MAIL | 503/RCPT | 503/DATA_HDR | 503/DATA_BODY | 503/QUIT
030: * mail | 503/CONNECT | 503/GREET | 250/RCPT | 503/RCPT | 503/DATA_HDR | 503/DATA_BODY | 250/RCPT
031: * rcpt | 503/CONNECT | 503/GREET | 503/MAIL | 250/RCPT | 503/DATA_HDR | 503/DATA_BODY | 503/QUIT
032: * data | 503/CONNECT | 503/GREET | 503/MAIL | 354/DATA_HDR | 503/DATA_HDR | 503/DATA_BODY | 503/QUIT
033: * data_end | 503/CONNECT | 503/GREET | 503/MAIL | 503/RCPT | 250/QUIT | 250/QUIT | 503/QUIT
034: * unrecog | 500/CONNECT | 500/GREET | 500/MAIL | 500/RCPT | ---/DATA_HDR | ---/DATA_BODY | 500/QUIT
035: * quit | 503/CONNECT | 503/GREET | 503/MAIL | 503/RCPT | 503/DATA_HDR | 503/DATA_BODY | 250/CONNECT
036: * blank_line | 503/CONNECT | 503/GREET | 503/MAIL | 503/RCPT | ---/DATA_BODY | ---/DATA_BODY | 503/QUIT
037: * rset | 250/GREET | 250/GREET | 250/GREET | 250/GREET | 250/GREET | 250/GREET | 250/GREET
038: * vrfy | 252/CONNECT | 252/GREET | 252/MAIL | 252/RCPT | 252/DATA_HDR | 252/DATA_BODY | 252/QUIT
039: * expn | 252/CONNECT | 252/GREET | 252/MAIL | 252/RCPT | 252/DATA_HDR | 252/DATA_BODY | 252/QUIT
040: * help | 211/CONNECT | 211/GREET | 211/MAIL | 211/RCPT | 211/DATA_HDR | 211/DATA_BODY | 211/QUIT
041: * noop | 250/CONNECT | 250/GREET | 250/MAIL | 250/RCPT | 250|DATA_HDR | 250/DATA_BODY | 250/QUIT
042: * </PRE>
043: */
044: public class SmtpRequest {
045: /** SMTP action received from client. */
046: private SmtpActionType action;
047: /** Current state of the SMTP state table. */
048: private SmtpState state;
049: /** Additional information passed from the client with the SMTP action. */
050: private String params;
051:
052: /**
053: * Create a new SMTP client request.
054: * @param actionType type of action/command
055: * @param params remainder of command line once command is removed
056: * @param state current SMTP server state
057: */
058: public SmtpRequest(SmtpActionType actionType, String params,
059: SmtpState state) {
060: this .action = actionType;
061: this .state = state;
062: this .params = params;
063: }
064:
065: /**
066: * Execute the SMTP request returning a response. This method models the state transition table for the SMTP server.
067: * @return reponse to the request
068: */
069: public SmtpResponse execute() {
070: SmtpResponse response = null;
071: if (action.isStateless()) {
072: if (SmtpActionType.EXPN == action
073: || SmtpActionType.VRFY == action) {
074: response = new SmtpResponse(252, "Not supported",
075: this .state);
076: } else if (SmtpActionType.HELP == action) {
077: response = new SmtpResponse(211, "No help available",
078: this .state);
079: } else if (SmtpActionType.NOOP == action) {
080: response = new SmtpResponse(250, "OK", this .state);
081: } else if (SmtpActionType.VRFY == action) {
082: response = new SmtpResponse(252, "Not supported",
083: this .state);
084: } else if (SmtpActionType.RSET == action) {
085: response = new SmtpResponse(250, "OK", SmtpState.GREET);
086: } else {
087: response = new SmtpResponse(500,
088: "Command not recognized", this .state);
089: }
090: } else { // Stateful commands
091: if (SmtpActionType.CONNECT == action) {
092: if (SmtpState.CONNECT == state) {
093: response = new SmtpResponse(220,
094: "localhost Dumbster SMTP service ready",
095: SmtpState.GREET);
096: } else {
097: response = new SmtpResponse(503,
098: "Bad sequence of commands: " + action,
099: this .state);
100: }
101: } else if (SmtpActionType.EHLO == action) {
102: if (SmtpState.GREET == state) {
103: response = new SmtpResponse(250, "OK",
104: SmtpState.MAIL);
105: } else {
106: response = new SmtpResponse(503,
107: "Bad sequence of commands: " + action,
108: this .state);
109: }
110: } else if (SmtpActionType.MAIL == action) {
111: if (SmtpState.MAIL == state || SmtpState.QUIT == state) {
112: response = new SmtpResponse(250, "OK",
113: SmtpState.RCPT);
114: } else {
115: response = new SmtpResponse(503,
116: "Bad sequence of commands: " + action,
117: this .state);
118: }
119: } else if (SmtpActionType.RCPT == action) {
120: if (SmtpState.RCPT == state) {
121: response = new SmtpResponse(250, "OK", this .state);
122: } else {
123: response = new SmtpResponse(503,
124: "Bad sequence of commands: " + action,
125: this .state);
126: }
127: } else if (SmtpActionType.DATA == action) {
128: if (SmtpState.RCPT == state) {
129: response = new SmtpResponse(354,
130: "Start mail input; end with <CRLF>.<CRLF>",
131: SmtpState.DATA_HDR);
132: } else {
133: response = new SmtpResponse(503,
134: "Bad sequence of commands: " + action,
135: this .state);
136: }
137: } else if (SmtpActionType.UNRECOG == action) {
138: if (SmtpState.DATA_HDR == state
139: || SmtpState.DATA_BODY == state) {
140: response = new SmtpResponse(-1, "", this .state);
141: } else {
142: response = new SmtpResponse(500,
143: "Command not recognized", this .state);
144: }
145: } else if (SmtpActionType.DATA_END == action) {
146: if (SmtpState.DATA_HDR == state
147: || SmtpState.DATA_BODY == state) {
148: response = new SmtpResponse(250, "OK",
149: SmtpState.QUIT);
150: } else {
151: response = new SmtpResponse(503,
152: "Bad sequence of commands: " + action,
153: this .state);
154: }
155: } else if (SmtpActionType.BLANK_LINE == action) {
156: if (SmtpState.DATA_HDR == state) {
157: response = new SmtpResponse(-1, "",
158: SmtpState.DATA_BODY);
159: } else if (SmtpState.DATA_BODY == state) {
160: response = new SmtpResponse(-1, "", this .state);
161: } else {
162: response = new SmtpResponse(503,
163: "Bad sequence of commands: " + action,
164: this .state);
165: }
166: } else if (SmtpActionType.QUIT == action) {
167: if (SmtpState.QUIT == state) {
168: response = new SmtpResponse(
169: 221,
170: "localhost Dumbster service closing transmission channel",
171: SmtpState.CONNECT);
172: } else {
173: response = new SmtpResponse(503,
174: "Bad sequence of commands: " + action,
175: this .state);
176: }
177: } else {
178: response = new SmtpResponse(500,
179: "Command not recognized", this .state);
180: }
181: }
182: return response;
183: }
184:
185: /**
186: * Create an SMTP request object given a line of the input stream from the client and the current internal state.
187: * @param s line of input
188: * @param state current state
189: * @return a populated SmtpRequest object
190: */
191: public static SmtpRequest createRequest(String s, SmtpState state) {
192: SmtpActionType action = null;
193: String params = null;
194:
195: if (state == SmtpState.DATA_HDR) {
196: if (s.equals(".")) {
197: action = SmtpActionType.DATA_END;
198: } else if (s.length() < 1) {
199: action = SmtpActionType.BLANK_LINE;
200: } else {
201: action = SmtpActionType.UNRECOG;
202: params = s;
203: }
204: } else if (state == SmtpState.DATA_BODY) {
205: if (s.equals(".")) {
206: action = SmtpActionType.DATA_END;
207: } else {
208: action = SmtpActionType.UNRECOG;
209: if (s.length() < 1) {
210: params = "\n";
211: } else {
212: params = s;
213: }
214: }
215: } else {
216: String su = s.toUpperCase();
217: if (su.startsWith("EHLO ") || su.startsWith("HELO")) {
218: action = SmtpActionType.EHLO;
219: params = s.substring(5);
220: } else if (su.startsWith("MAIL FROM:")) {
221: action = SmtpActionType.MAIL;
222: params = s.substring(10);
223: } else if (su.startsWith("RCPT TO:")) {
224: action = SmtpActionType.RCPT;
225: params = s.substring(8);
226: } else if (su.startsWith("DATA")) {
227: action = SmtpActionType.DATA;
228: } else if (su.startsWith("QUIT")) {
229: action = SmtpActionType.QUIT;
230: } else if (su.startsWith("RSET")) {
231: action = SmtpActionType.RSET;
232: } else if (su.startsWith("NOOP")) {
233: action = SmtpActionType.NOOP;
234: } else if (su.startsWith("EXPN")) {
235: action = SmtpActionType.EXPN;
236: } else if (su.startsWith("VRFY")) {
237: action = SmtpActionType.VRFY;
238: } else if (su.startsWith("HELP")) {
239: action = SmtpActionType.HELP;
240: } else {
241: action = SmtpActionType.UNRECOG;
242: }
243: }
244:
245: SmtpRequest req = new SmtpRequest(action, params, state);
246: return req;
247: }
248:
249: /**
250: * Get the parameters of this request (remainder of command line once the command is removed.
251: * @return parameters
252: */
253: public String getParams() {
254: return params;
255: }
256: }
|