1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package davmail.smtp;
20
21 import davmail.AbstractConnection;
22 import davmail.BundleMessage;
23 import davmail.DavGateway;
24 import davmail.exception.DavMailException;
25 import davmail.exchange.DoubleDotInputStream;
26 import davmail.exchange.ExchangeSessionFactory;
27 import davmail.ui.tray.DavGatewayTray;
28 import davmail.util.IOUtil;
29
30 import javax.mail.internet.AddressException;
31 import javax.mail.internet.InternetAddress;
32 import javax.mail.internet.MimeMessage;
33 import javax.mail.util.SharedByteArrayInputStream;
34 import java.io.ByteArrayOutputStream;
35 import java.io.IOException;
36 import java.net.Socket;
37 import java.net.SocketException;
38 import java.util.ArrayList;
39 import java.util.Date;
40 import java.util.List;
41 import java.util.StringTokenizer;
42
43
44
45
46 public class SmtpConnection extends AbstractConnection {
47
48
49
50
51
52
53 public SmtpConnection(Socket clientSocket) {
54 super(SmtpConnection.class.getSimpleName(), clientSocket, null);
55 }
56
57
58 @Override
59 public void run() {
60 String line;
61 StringTokenizer tokens;
62 List<String> recipients = new ArrayList<>();
63
64 try {
65 ExchangeSessionFactory.checkConfig();
66 sendClient("220 DavMail " + DavGateway.getCurrentVersion() + " SMTP ready at " + new Date());
67 for (; ;) {
68 line = readClient();
69
70 if (line == null) {
71 break;
72 }
73
74 tokens = new StringTokenizer(line);
75 if (tokens.hasMoreTokens()) {
76 String command = tokens.nextToken();
77
78 if (state == State.LOGIN) {
79
80 userName = IOUtil.decodeBase64AsString(line);
81 sendClient("334 " + IOUtil.encodeBase64AsString("Password:"));
82 state = State.PASSWORD;
83 } else if (state == State.PASSWORD) {
84
85 password = IOUtil.decodeBase64AsString(line);
86 authenticate();
87 } else if ("QUIT".equalsIgnoreCase(command)) {
88 sendClient("221 Closing connection");
89 break;
90 } else if ("NOOP".equalsIgnoreCase(command)) {
91 sendClient("250 OK");
92 } else if ("EHLO".equalsIgnoreCase(command)) {
93 sendClient("250-" + tokens.nextToken());
94
95
96 sendClient("250-AUTH LOGIN PLAIN");
97 sendClient("250-8BITMIME");
98 sendClient("250 Hello");
99 } else if ("HELO".equalsIgnoreCase(command)) {
100 sendClient("250 Hello");
101 } else if ("AUTH".equalsIgnoreCase(command)) {
102 if (tokens.hasMoreElements()) {
103 String authType = tokens.nextToken();
104 if ("PLAIN".equalsIgnoreCase(authType) && tokens.hasMoreElements()) {
105 decodeCredentials(tokens.nextToken());
106 authenticate();
107 } else if ("LOGIN".equalsIgnoreCase(authType)) {
108 if (tokens.hasMoreTokens()) {
109
110 userName = IOUtil.decodeBase64AsString(tokens.nextToken());
111 sendClient("334 " + IOUtil.encodeBase64AsString("Password:"));
112 state = State.PASSWORD;
113 } else {
114 sendClient("334 " + IOUtil.encodeBase64AsString("Username:"));
115 state = State.LOGIN;
116 }
117 } else {
118 sendClient("451 Error : unknown authentication type");
119 }
120 } else {
121 sendClient("451 Error : authentication type not specified");
122 }
123 } else if ("MAIL".equalsIgnoreCase(command)) {
124 if (state == State.AUTHENTICATED) {
125 state = State.STARTMAIL;
126 recipients.clear();
127 sendClient("250 Sender OK");
128 } else if (state == State.INITIAL) {
129 sendClient("530 Authentication required");
130 } else {
131 state = State.INITIAL;
132 sendClient("503 Bad sequence of commands");
133 }
134 } else if ("RCPT".equalsIgnoreCase(command)) {
135 if (state == State.STARTMAIL || state == State.RECIPIENT) {
136 if (line.toUpperCase().startsWith("RCPT TO:")) {
137 state = State.RECIPIENT;
138 try {
139 InternetAddress internetAddress = new InternetAddress(line.substring("RCPT TO:".length()));
140 recipients.add(internetAddress.getAddress());
141 } catch (AddressException e) {
142 throw new DavMailException("EXCEPTION_INVALID_RECIPIENT", line);
143 }
144 sendClient("250 Recipient OK");
145 } else {
146 sendClient("500 Unrecognized command");
147 }
148
149 } else {
150 state = State.AUTHENTICATED;
151 sendClient("503 Bad sequence of commands");
152 }
153 } else if ("DATA".equalsIgnoreCase(command)) {
154 if (state == State.RECIPIENT) {
155 state = State.MAILDATA;
156 sendClient("354 Start mail input; end with <CRLF>.<CRLF>");
157
158 try {
159
160 ByteArrayOutputStream baos = new ByteArrayOutputStream();
161 DoubleDotInputStream doubleDotInputStream = new DoubleDotInputStream(in);
162 int b;
163 while ((b = doubleDotInputStream.read()) >= 0) {
164 baos.write(b);
165 }
166 MimeMessage mimeMessage = new MimeMessage(null, new SharedByteArrayInputStream(baos.toByteArray()));
167 session.sendMessage(recipients, mimeMessage);
168 state = State.AUTHENTICATED;
169 sendClient("250 Queued mail for delivery");
170 } catch (Exception e) {
171 DavGatewayTray.error(e);
172 state = State.AUTHENTICATED;
173 String error = e.getMessage();
174 if (error == null) {
175 error = e.toString();
176 }
177 sendClient("451 Error : " + error.replaceAll("[\\r\\n]", ""));
178 }
179
180 } else {
181 state = State.AUTHENTICATED;
182 sendClient("503 Bad sequence of commands");
183 }
184 } else if ("RSET".equalsIgnoreCase(command)) {
185 recipients.clear();
186
187 if (state == State.STARTMAIL ||
188 state == State.RECIPIENT ||
189 state == State.MAILDATA ||
190 state == State.AUTHENTICATED) {
191 state = State.AUTHENTICATED;
192 } else {
193 state = State.INITIAL;
194 }
195 sendClient("250 OK Reset");
196 } else {
197 sendClient("500 Unrecognized command");
198 }
199 } else {
200 sendClient("500 Unrecognized command");
201 }
202
203 os.flush();
204 }
205
206 } catch (SocketException e) {
207 DavGatewayTray.debug(new BundleMessage("LOG_CONNECTION_CLOSED"));
208 } catch (Exception e) {
209 DavGatewayTray.log(e);
210 try {
211
212 sendClient("421 " + ((e.getMessage() == null) ? e : e.getMessage())+"\n");
213 } catch (IOException e2) {
214 DavGatewayTray.debug(new BundleMessage("LOG_EXCEPTION_SENDING_ERROR_TO_CLIENT"), e2);
215 }
216 } finally {
217 close();
218 }
219 DavGatewayTray.resetIcon();
220 }
221
222
223
224
225
226
227 protected void authenticate() throws IOException {
228 try {
229 session = ExchangeSessionFactory.getInstance(userName, password);
230 logConnection("LOGON", userName);
231 sendClient("235 OK Authenticated");
232 state = State.AUTHENTICATED;
233 } catch (Exception e) {
234 logConnection("FAILED", userName);
235 DavGatewayTray.error(e);
236 String message = e.getMessage();
237 if (message == null) {
238 message = e.toString();
239 }
240 message = message.replaceAll("\\n", " ");
241 sendClient("535 Authentication failed " + message);
242 state = State.INITIAL;
243 }
244
245 }
246
247
248
249
250
251
252
253 protected void decodeCredentials(String encodedCredentials) throws IOException {
254 String decodedCredentials = IOUtil.decodeBase64AsString(encodedCredentials);
255 int startIndex = decodedCredentials.indexOf((char) 0);
256 if (startIndex >=0) {
257 int endIndex = decodedCredentials.indexOf((char) 0, startIndex+1);
258 if (endIndex >=0) {
259 userName = decodedCredentials.substring(startIndex+1, endIndex);
260 password = decodedCredentials.substring(endIndex + 1);
261 } else {
262 throw new DavMailException("EXCEPTION_INVALID_CREDENTIALS");
263 }
264 } else {
265 throw new DavMailException("EXCEPTION_INVALID_CREDENTIALS");
266 }
267 }
268
269 }
270