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