1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.geronimo.javamail.store.pop3.connection;
19
20 import java.io.*;
21 import java.net.InetAddress;
22 import java.net.Socket;
23 import java.net.SocketException;
24 import java.net.UnknownHostException;
25 import java.security.MessageDigest;
26 import java.security.NoSuchAlgorithmException;
27 import java.util.ArrayList;
28 import java.util.Date;
29 import java.util.HashMap;
30 import java.util.Iterator;
31 import java.util.LinkedList;
32 import java.util.List;
33 import java.util.StringTokenizer;
34
35 import javax.mail.MessagingException;
36 import javax.mail.internet.InternetHeaders;
37
38 import org.apache.geronimo.javamail.authentication.AuthenticatorFactory;
39 import org.apache.geronimo.javamail.authentication.ClientAuthenticator;
40 import org.apache.geronimo.javamail.store.pop3.POP3Constants;
41 import org.apache.geronimo.javamail.util.CommandFailedException;
42 import org.apache.geronimo.javamail.util.InvalidCommandException;
43 import org.apache.geronimo.javamail.util.MIMEInputReader;
44 import org.apache.geronimo.javamail.util.MailConnection;
45 import org.apache.geronimo.javamail.util.ProtocolProperties;
46 import org.apache.geronimo.mail.util.Base64;
47 import org.apache.geronimo.mail.util.Hex;
48
49
50
51
52
53
54 public class POP3Connection extends MailConnection implements POP3Constants {
55
56 static final protected String MAIL_APOP_ENABLED = "apop.enable";
57 static final protected String MAIL_AUTH_ENABLED = "auth.enable";
58 static final protected String MAIL_RESET_QUIT = "rsetbeforequit";
59 static final protected String MAIL_DISABLE_TOP = "disabletop";
60 static final protected String MAIL_FORGET_TOP = "forgettopheaders";
61
62
63 protected String greeting;
64
65 protected boolean authEnabled;
66
67 protected boolean apopEnabled;
68
69 protected BufferedReader reader;
70
71 protected PrintWriter writer;
72
73 protected boolean closed;
74
75
76 protected boolean loggedIn;
77
78
79 protected boolean topDisabled = false;
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96 public POP3Connection(ProtocolProperties props) {
97 super(props);
98
99
100 authEnabled = props.getBooleanProperty(MAIL_AUTH_ENABLED, false);
101 apopEnabled = props.getBooleanProperty(MAIL_APOP_ENABLED, false);
102 topDisabled = props.getBooleanProperty(MAIL_DISABLE_TOP, false);
103 }
104
105
106
107
108
109
110
111 public boolean protocolConnect(String host, int port, String authid, String realm, String username, String password) throws MessagingException {
112 this.serverHost = host;
113 this.serverPort = port;
114 this.realm = realm;
115 this.authid = authid;
116 this.username = username;
117 this.password = password;
118
119 try {
120
121 getConnection();
122
123 getWelcome();
124
125
126 if (login())
127 {
128 loggedIn = true;
129 return true;
130 }
131 return false;
132 } catch (IOException e) {
133 if (debug) {
134 debugOut("I/O exception establishing connection", e);
135 }
136 throw new MessagingException("Connection error", e);
137 }
138 }
139
140
141
142
143
144
145
146
147 protected void getConnection() throws MessagingException
148 {
149 try {
150
151
152 super.getConnection();
153 } catch (IOException e) {
154 throw new MessagingException("Unable to obtain a connection to the POP3 server", e);
155 }
156
157
158
159 reader = new BufferedReader(new InputStreamReader(inputStream));
160 writer = new PrintWriter(new BufferedOutputStream(outputStream));
161 }
162
163 protected void getWelcome() throws IOException {
164
165
166 greeting = reader.readLine();
167 }
168
169 public String toString() {
170 return "POP3Connection host: " + serverHost + " port: " + serverPort;
171 }
172
173
174
175
176
177
178
179
180 public void close() throws MessagingException {
181
182 if (socket == null) {
183 return;
184 }
185 try {
186
187 logout();
188 } finally {
189
190
191 closeServerConnection();
192
193 reader = null;
194 writer = null;
195 }
196 }
197
198
199
200
201
202
203
204 public void setClosed() {
205 closed = true;
206 }
207
208
209
210
211
212
213 public boolean isClosed() {
214 return closed;
215 }
216
217 protected POP3Response sendCommand(String cmd) throws MessagingException {
218 return sendCommand(cmd, false);
219 }
220
221 protected POP3Response sendMultiLineCommand(String cmd) throws MessagingException {
222 return sendCommand(cmd, true);
223 }
224
225 protected synchronized POP3Response sendCommand(String cmd, boolean multiLine) throws MessagingException {
226 if (socket.isConnected()) {
227 {
228
229
230 writer.write(cmd);
231 writer.write("\r\n");
232 writer.flush();
233
234 POP3Response response = buildResponse(multiLine);
235 if (response.isError()) {
236 throw new CommandFailedException("Error issuing POP3 command: " + cmd);
237 }
238 return response;
239 }
240 }
241 throw new MessagingException("Connection to Mail Server is lost, connection " + this.toString());
242 }
243
244
245
246
247
248
249
250
251
252
253 protected POP3Response buildResponse(boolean isMultiLineResponse) throws MessagingException {
254 int status = ERR;
255 byte[] data = null;
256
257 String line;
258 MIMEInputReader source = new MIMEInputReader(reader);
259
260 try {
261 line = reader.readLine();
262 } catch (IOException e) {
263 throw new MessagingException("Error in receving response");
264 }
265
266 if (line == null || line.trim().equals("")) {
267 throw new MessagingException("Empty Response");
268 }
269
270 if (line.startsWith("+OK")) {
271 status = OK;
272 line = removeStatusField(line);
273 if (isMultiLineResponse) {
274 data = getMultiLineResponse();
275 }
276 } else if (line.startsWith("-ERR")) {
277 status = ERR;
278 line = removeStatusField(line);
279 }else if (line.startsWith("+")) {
280 status = CHALLENGE;
281 line = removeStatusField(line);
282 if (isMultiLineResponse) {
283 data = getMultiLineResponse();
284 }
285 } else {
286 throw new MessagingException("Unexpected response: " + line);
287 }
288 return new POP3Response(status, line, data);
289 }
290
291 private static String removeStatusField(String line) {
292 return line.substring(line.indexOf(SPACE) + 1);
293 }
294
295
296
297
298 private byte[] getMultiLineResponse() throws MessagingException {
299
300 MIMEInputReader source = new MIMEInputReader(reader);
301
302 ByteArrayOutputStream out = new ByteArrayOutputStream();
303
304
305
306
307 OutputStreamWriter outWriter = new OutputStreamWriter(out);
308 char buffer[] = new char[500];
309 try {
310 int charsRead = -1;
311 while ((charsRead = source.read(buffer)) >= 0) {
312 outWriter.write(buffer, 0, charsRead);
313 }
314 outWriter.flush();
315 } catch (IOException e) {
316 throw new MessagingException("Error processing a multi-line response", e);
317 }
318
319 return out.toByteArray();
320 }
321
322
323
324
325
326
327
328
329
330
331
332
333
334 public byte[] retrieveMessageData(int sequenceNumber) throws MessagingException {
335 POP3Response msgResponse = sendMultiLineCommand("RETR " + sequenceNumber);
336
337 return msgResponse.getData();
338 }
339
340
341
342
343
344
345
346
347
348
349
350
351
352 public ByteArrayInputStream retrieveMessageHeaders(int sequenceNumber) throws MessagingException {
353 POP3Response msgResponse;
354
355
356
357
358
359 if (topDisabled) {
360 msgResponse = sendMultiLineCommand("RETR " + sequenceNumber);
361 }
362 else {
363 msgResponse = sendMultiLineCommand("TOP " + sequenceNumber + " 0");
364 }
365
366
367 return msgResponse.getContentStream();
368 }
369
370
371
372
373
374
375
376
377
378
379
380
381 public int retrieveMessageSize(int sequenceNumber) throws MessagingException {
382 POP3Response msgResponse = sendCommand("LIST " + sequenceNumber);
383
384 POP3ListResponse list = new POP3ListResponse(msgResponse);
385
386 return list.getSize();
387 }
388
389
390
391
392
393
394
395 public POP3StatusResponse retrieveMailboxStatus() throws MessagingException {
396
397 return new POP3StatusResponse(sendCommand("STAT"));
398 }
399
400
401
402
403
404
405
406
407
408
409
410 public String retrieveMessageUid(int sequenceNumber) throws MessagingException {
411 POP3Response msgResponse = sendCommand("UIDL " + sequenceNumber);
412
413 String message = msgResponse.getFirstLine();
414
415
416
417 return message.substring(message.indexOf(' ') + 1).trim();
418 }
419
420
421
422
423
424
425
426
427
428
429 public void deleteMessage(int sequenceNumber) throws MessagingException {
430
431 sendCommand("DELE " + sequenceNumber);
432 }
433
434
435
436
437
438
439
440 public void logout() throws MessagingException {
441
442 if (!loggedIn) {
443 return;
444 }
445
446 sendCommand("QUIT");
447 loggedIn = false;
448 }
449
450
451
452
453
454
455 public void reset() throws MessagingException {
456
457
458
459 if (props.getBooleanProperty(MAIL_RESET_QUIT, false)) {
460
461 sendCommand("RSET");
462 }
463 }
464
465
466
467
468
469
470 public void pingServer() throws MessagingException {
471
472 sendCommand("NOOP");
473 }
474
475
476
477
478
479
480
481
482
483 public synchronized boolean login() throws MessagingException {
484
485 if (authEnabled) {
486 try {
487
488 return processSaslAuthentication();
489 } catch (MessagingException e) {
490
491 }
492 }
493
494 if (apopEnabled) {
495 try {
496
497 return processAPOPAuthentication();
498 } catch (MessagingException e) {
499
500 }
501 }
502
503 try {
504
505 return processLogin();
506 } catch (MessagingException e) {
507 }
508
509 return false;
510 }
511
512
513
514
515
516
517
518
519
520 public boolean processLogin() throws MessagingException {
521
522
523 sendCommand("USER " + username);
524 sendCommand("PASS " + password);
525 return true;
526 }
527
528
529
530
531
532
533
534
535
536 public boolean processAPOPAuthentication() throws MessagingException {
537 int timeStart = greeting.indexOf('<');
538
539
540
541 if (timeStart == -1) {
542 throw new MessagingException("POP3 Server does not support APOP");
543 }
544 int timeEnd = greeting.indexOf('>');
545 String timeStamp = greeting.substring(timeStart, timeEnd + 1);
546
547
548
549 String digestPassword = timeStamp + password;
550
551 byte[] digest;
552
553 try {
554
555 MessageDigest md = MessageDigest.getInstance("MD5");
556 digest = md.digest(digestPassword.getBytes("iso-8859-1"));
557 } catch (NoSuchAlgorithmException e) {
558
559
560 throw new MessagingException("Unable to create MD5 digest", e);
561 } catch (UnsupportedEncodingException e) {
562
563
564 throw new MessagingException("Unable to create MD5 digest", e);
565 }
566
567 sendCommand("APOP " + username + " " + Hex.encode(digest));
568
569 return true;
570 }
571
572
573
574
575
576
577
578
579
580 protected boolean processSaslAuthentication() throws MessagingException {
581
582 ClientAuthenticator authenticator = getSaslAuthenticator();
583 if (authenticator == null) {
584 throw new MessagingException("Unable to obtain SASL authenticator");
585 }
586
587
588 return processLogin(authenticator);
589 }
590
591
592
593
594
595
596
597
598 protected ClientAuthenticator getSaslAuthenticator() {
599 return AuthenticatorFactory.getAuthenticator(props, selectSaslMechanisms(), serverHost, username, password, authid, realm);
600 }
601
602
603
604
605
606
607
608
609
610
611
612
613 protected synchronized boolean processLogin(ClientAuthenticator authenticator) throws MessagingException {
614 if (debug) {
615 debugOut("Authenticating for user: " + username + " using " + authenticator.getMechanismName());
616 }
617
618 POP3Response response = sendCommand("AUTH " + authenticator.getMechanismName());
619
620
621
622 while (true) {
623
624 if (response.isChallenge()) {
625
626 byte[] challenge = response.decodeChallengeResponse();
627
628 String responseString = new String(Base64.encode(authenticator.evaluateChallenge(challenge)));
629
630
631 response = sendCommand(responseString);
632 }
633 else {
634
635
636 return true;
637 }
638 }
639 }
640
641
642
643
644
645
646
647
648
649
650 protected List selectSaslMechanisms() {
651
652 return getSaslMechanisms();
653 }
654 }
655