1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package davmail.pop;
20
21 import davmail.AbstractConnection;
22 import davmail.BundleMessage;
23 import davmail.DavGateway;
24 import davmail.Settings;
25 import davmail.exchange.DoubleDotOutputStream;
26 import davmail.exchange.ExchangeSession;
27 import davmail.exchange.ExchangeSessionFactory;
28 import davmail.exchange.MessageLoadThread;
29 import davmail.ui.tray.DavGatewayTray;
30 import davmail.util.IOUtil;
31 import org.apache.log4j.Logger;
32
33 import java.io.FilterOutputStream;
34 import java.io.IOException;
35 import java.io.OutputStream;
36 import java.net.Socket;
37 import java.net.SocketException;
38 import java.nio.charset.StandardCharsets;
39 import java.util.Date;
40 import java.util.List;
41 import java.util.StringTokenizer;
42
43
44
45
46 public class PopConnection extends AbstractConnection {
47 private static final Logger LOGGER = Logger.getLogger(PopConnection.class);
48
49 private List<ExchangeSession.Message> messages;
50
51
52
53
54
55
56 public PopConnection(Socket clientSocket) {
57 super(PopConnection.class.getSimpleName(), clientSocket, null);
58 }
59
60 protected long getTotalMessagesLength() {
61 int result = 0;
62 for (ExchangeSession.Message message : messages) {
63 result += message.size;
64 }
65 return result;
66 }
67
68 protected void printCapabilities() throws IOException {
69 sendClient("TOP");
70 sendClient("USER");
71 sendClient("UIDL");
72 sendClient(".");
73 }
74
75 protected void printList() throws IOException {
76 int i = 1;
77 for (ExchangeSession.Message message : messages) {
78 sendClient(i++ + " " + message.size);
79 }
80 sendClient(".");
81 }
82
83 protected void printUidList() throws IOException {
84 int i = 1;
85 for (ExchangeSession.Message message : messages) {
86 sendClient(i++ + " " + message.getUid());
87 }
88 sendClient(".");
89 }
90
91
92 @Override
93 public void run() {
94 String line;
95 StringTokenizer tokens;
96
97 try {
98 ExchangeSessionFactory.checkConfig();
99 sendOK("DavMail " + DavGateway.getCurrentVersion() + " POP ready at " + new Date());
100
101 for (; ;) {
102 line = readClient();
103
104 if (line == null) {
105 break;
106 }
107
108 tokens = new StringTokenizer(line);
109 if (tokens.hasMoreTokens()) {
110 String command = tokens.nextToken();
111
112 if ("QUIT".equalsIgnoreCase(command)) {
113
114 if (session != null) {
115 session.purgeOldestTrashAndSentMessages();
116 }
117 sendOK("Bye");
118 break;
119 } else if ("USER".equalsIgnoreCase(command)) {
120 userName = null;
121 password = null;
122 session = null;
123 if (tokens.hasMoreTokens()) {
124 userName = line.substring("USER ".length());
125 sendOK("USER : " + userName);
126 state = State.USER;
127 } else {
128 sendERR("invalid syntax");
129 state = State.INITIAL;
130 }
131 } else if ("PASS".equalsIgnoreCase(command)) {
132 if (state != State.USER) {
133 sendERR("invalid state");
134 state = State.INITIAL;
135 } else if (!tokens.hasMoreTokens()) {
136 sendERR("invalid syntax");
137 } else {
138
139 password = line.substring("PASS".length() + 1);
140 try {
141 session = ExchangeSessionFactory.getInstance(userName, password);
142 logConnection("LOGON", userName);
143 sendOK("PASS");
144 state = State.AUTHENTICATED;
145 } catch (SocketException e) {
146 logConnection("FAILED", userName);
147
148 LOGGER.warn(BundleMessage.formatLog("LOG_CLIENT_CLOSED_CONNECTION"));
149 } catch (Exception e) {
150 DavGatewayTray.error(e);
151 sendERR(e);
152 }
153 }
154 } else if ("CAPA".equalsIgnoreCase(command)) {
155 sendOK("Capability list follows");
156 printCapabilities();
157 } else if (state != State.AUTHENTICATED) {
158 sendERR("Invalid state not authenticated");
159 } else {
160
161 if (messages == null) {
162 messages = session.getAllMessageUidAndSize("INBOX");
163 }
164 if ("STAT".equalsIgnoreCase(command)) {
165 sendOK(messages.size() + " " +
166 getTotalMessagesLength());
167 } else if ("NOOP".equalsIgnoreCase(command)) {
168 sendOK("");
169 } else if ("LIST".equalsIgnoreCase(command)) {
170 if (tokens.hasMoreTokens()) {
171 String token = tokens.nextToken();
172 try {
173 int messageNumber = Integer.parseInt(token);
174 ExchangeSession.Message message = messages.get(messageNumber - 1);
175 sendOK("" + messageNumber + ' ' + message.size);
176 } catch (NumberFormatException | IndexOutOfBoundsException e) {
177 sendERR("Invalid message index: " + token);
178 }
179 } else {
180 sendOK(messages.size() +
181 " messages (" + getTotalMessagesLength() +
182 " octets)");
183 printList();
184 }
185 } else if ("UIDL".equalsIgnoreCase(command)) {
186 if (tokens.hasMoreTokens()) {
187 String token = tokens.nextToken();
188 try {
189 int messageNumber = Integer.parseInt(token);
190 sendOK(messageNumber + " " + messages.get(messageNumber - 1).getUid());
191 } catch (NumberFormatException | IndexOutOfBoundsException e) {
192 sendERR("Invalid message index: " + token);
193 }
194 } else {
195 sendOK(messages.size() +
196 " messages (" + getTotalMessagesLength() +
197 " octets)");
198 printUidList();
199 }
200 } else if ("RETR".equalsIgnoreCase(command)) {
201 if (tokens.hasMoreTokens()) {
202 try {
203 int messageNumber = Integer.parseInt(tokens.nextToken()) - 1;
204 ExchangeSession.Message message = messages.get(messageNumber);
205
206
207 os.write("+OK ".getBytes(StandardCharsets.US_ASCII));
208 os.flush();
209 MessageLoadThread.loadMimeMessage(message, os);
210 sendClient("");
211
212 DoubleDotOutputStream doubleDotOutputStream = new DoubleDotOutputStream(os);
213 IOUtil.write(message.getRawInputStream(), doubleDotOutputStream);
214 doubleDotOutputStream.close();
215 if (Settings.getBooleanProperty("davmail.popMarkReadOnRetr")) {
216 message.markRead();
217 }
218 } catch (SocketException e) {
219
220 LOGGER.warn(BundleMessage.formatLog("LOG_CLIENT_CLOSED_CONNECTION"));
221 } catch (Exception e) {
222 DavGatewayTray.error(new BundleMessage("LOG_ERROR_RETRIEVING_MESSAGE"), e);
223 sendERR("error retrieving message " + e + ' ' + e.getMessage());
224 }
225 } else {
226 sendERR("invalid message index");
227 }
228 } else if ("DELE".equalsIgnoreCase(command)) {
229 if (tokens.hasMoreTokens()) {
230 ExchangeSession.Message message;
231 try {
232 int messageNumber = Integer.parseInt(tokens.nextToken()) - 1;
233 message = messages.get(messageNumber);
234 message.moveToTrash();
235 sendOK("DELETE");
236 } catch (NumberFormatException | IndexOutOfBoundsException e) {
237 sendERR("invalid message index");
238 }
239 } else {
240 sendERR("invalid message index");
241 }
242 } else if ("TOP".equalsIgnoreCase(command)) {
243 int message = 0;
244 try {
245 message = Integer.parseInt(tokens.nextToken());
246 int lines = Integer.parseInt(tokens.nextToken());
247 ExchangeSession.Message m = messages.get(message - 1);
248 sendOK("");
249 DoubleDotOutputStream doubleDotOutputStream = new DoubleDotOutputStream(os);
250 IOUtil.write(m.getRawInputStream(), new TopOutputStream(doubleDotOutputStream, lines));
251 doubleDotOutputStream.close();
252 } catch (NumberFormatException e) {
253 sendERR("invalid command");
254 } catch (IndexOutOfBoundsException e) {
255 sendERR("invalid message index: " + message);
256 } catch (Exception e) {
257 sendERR("error retreiving top of messages");
258 DavGatewayTray.error(e);
259 }
260 } else if ("RSET".equalsIgnoreCase(command)) {
261 sendOK("RSET");
262 } else {
263 sendERR("unknown command");
264 }
265 }
266
267 } else {
268 sendERR("unknown command");
269 }
270
271 os.flush();
272 }
273 } catch (SocketException e) {
274 DavGatewayTray.debug(new BundleMessage("LOG_CONNECTION_CLOSED"));
275 } catch (Exception e) {
276 DavGatewayTray.log(e);
277 try {
278 sendERR(e.getMessage());
279 } catch (IOException e2) {
280 DavGatewayTray.debug(new BundleMessage("LOG_EXCEPTION_SENDING_ERROR_TO_CLIENT"), e2);
281 }
282 } finally {
283 close();
284 }
285 DavGatewayTray.resetIcon();
286 }
287
288 protected void sendOK(String message) throws IOException {
289 sendClient("+OK ", message);
290 }
291
292 protected void sendERR(Exception e) throws IOException {
293 String message = e.getMessage();
294 if (message == null) {
295 message = e.toString();
296 }
297 sendERR(message);
298 }
299
300 protected void sendERR(String message) throws IOException {
301 sendClient("-ERR ", message.replaceAll("\\n", " "));
302 }
303
304
305
306
307 private static final class TopOutputStream extends FilterOutputStream {
308 protected enum State {
309 START, CR, CRLF, CRLFCR, BODY
310 }
311
312 private int maxLines;
313 private State state = State.START;
314
315 private TopOutputStream(OutputStream os, int maxLines) {
316 super(os);
317 this.maxLines = maxLines;
318 }
319
320 @Override
321 public void write(int b) throws IOException {
322 if (state != State.BODY || maxLines > 0) {
323 super.write(b);
324 }
325 if (state == State.BODY) {
326 if (b == '\n') {
327 maxLines--;
328 }
329 } else if (state == State.START) {
330 if (b == '\r') {
331 state = State.CR;
332 }
333 } else if (state == State.CR) {
334 if (b == '\n') {
335 state = State.CRLF;
336 } else {
337 state = State.START;
338 }
339 } else if (state == State.CRLF) {
340 if (b == '\r') {
341 state = State.CRLFCR;
342 } else {
343 state = State.START;
344 }
345 } else if (state == State.CRLFCR) {
346 if (b == '\n') {
347 state = State.BODY;
348 } else {
349 state = State.START;
350 }
351 }
352 }
353 }
354
355 }