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