1:
38:
39: package ;
40:
41: import ;
42: import ;
43: import ;
44: import ;
45: import ;
46: import ;
47: import ;
48: import ;
49: import ;
50: import ;
51: import ;
52: import ;
53: import ;
54: import ;
55: import ;
56: import ;
57: import ;
58: import ;
59:
60: import ;
61: import ;
62: import ;
63: import ;
64:
65: import ;
66: import ;
67: import ;
68: import ;
69:
70: import ;
71: import ;
72: import ;
73: import ;
74: import ;
75: import ;
76: import ;
77: import ;
78: import ;
79: import ;
80: import ;
81: import ;
82: import ;
83:
84:
95: public class POP3Connection
96: {
97:
98:
101: public static final int DEFAULT_PORT = 110;
102:
103:
104: private static final String _OK = "+OK";
105: private static final String _ERR = "-ERR";
106: private static final String _READY = "+ ";
107:
108: protected static final String STAT = "STAT";
109: protected static final String LIST = "LIST";
110: protected static final String RETR = "RETR";
111: protected static final String DELE = "DELE";
112: protected static final String NOOP = "NOOP";
113: protected static final String RSET = "RSET";
114: protected static final String QUIT = "QUIT";
115: protected static final String TOP = "TOP";
116: protected static final String UIDL = "UIDL";
117: protected static final String USER = "USER";
118: protected static final String PASS = "PASS";
119: protected static final String APOP = "APOP";
120: protected static final String CAPA = "CAPA";
121: protected static final String STLS = "STLS";
122: protected static final String AUTH = "AUTH";
123:
124: protected static final int OK = 0;
125: protected static final int ERR = 1;
126: protected static final int READY = 2;
127:
128:
131: protected Socket socket;
132:
133:
136: protected LineInputStream in;
137:
138:
141: protected CRLFOutputStream out;
142:
143:
147: protected String response;
148:
149:
152: protected boolean debug;
153:
154:
158: protected byte[] timestamp;
159:
160:
164: public POP3Connection(String hostname)
165: throws UnknownHostException, IOException
166: {
167: this(hostname, -1, 0, 0, false);
168: }
169:
170:
175: public POP3Connection(String hostname, int port)
176: throws UnknownHostException, IOException
177: {
178: this(hostname, port, 0, 0, false);
179: }
180:
181:
189: public POP3Connection(String hostname, int port,
190: int connectionTimeout, int timeout, boolean debug)
191: throws UnknownHostException, IOException
192: {
193: this.debug = debug;
194: if (port <= 0)
195: {
196: port = DEFAULT_PORT;
197: }
198:
199:
200: socket = new Socket();
201: InetSocketAddress address = new InetSocketAddress(hostname, port);
202: if (connectionTimeout > 0)
203: {
204: socket.connect(address, connectionTimeout);
205: }
206: else
207: {
208: socket.connect(address);
209: }
210: if (timeout > 0)
211: {
212: socket.setSoTimeout(timeout);
213: }
214:
215: InputStream in = socket.getInputStream();
216: in = new BufferedInputStream(in);
217: in = new CRLFInputStream(in);
218: this.in = new LineInputStream(in);
219: OutputStream out = socket.getOutputStream();
220: out = new BufferedOutputStream(out);
221: this.out = new CRLFOutputStream(out);
222:
223: if (getResponse() != OK)
224: {
225: throw new ProtocolException("Connect failed: " + response);
226: }
227:
228: timestamp = parseTimestamp(response);
229: }
230:
231:
240: public boolean auth(String mechanism, String username, String password)
241: throws IOException
242: {
243: try
244: {
245: String[] m = new String[] { mechanism };
246: CallbackHandler ch = new SaslCallbackHandler(username, password);
247:
248: Properties p = new Properties();
249: p.put("gnu.crypto.sasl.username", username);
250: p.put("gnu.crypto.sasl.password", password);
251: SaslClient sasl =
252: Sasl.createSaslClient(m, null, "pop3",
253: socket.getInetAddress().getHostName(),
254: p, ch);
255: if (sasl == null)
256: {
257:
258: if ("LOGIN".equalsIgnoreCase(mechanism))
259: {
260: sasl = new SaslLogin(username, password);
261: }
262: else if ("PLAIN".equalsIgnoreCase(mechanism))
263: {
264: sasl = new SaslPlain(username, password);
265: }
266: else if ("CRAM-MD5".equalsIgnoreCase(mechanism))
267: {
268: sasl = new SaslCramMD5(username, password);
269: }
270: else
271: {
272: return false;
273: }
274: }
275:
276: StringBuffer cmd = new StringBuffer(AUTH);
277: cmd.append(' ');
278: cmd.append(mechanism);
279: send(cmd.toString());
280: while (true)
281: {
282: switch (getResponse())
283: {
284: case OK:
285: String qop = (String) sasl.getNegotiatedProperty(Sasl.QOP);
286: if ("auth-int".equalsIgnoreCase(qop)
287: || "auth-conf".equalsIgnoreCase(qop))
288: {
289: InputStream in = socket.getInputStream();
290: in = new BufferedInputStream(in);
291: in = new SaslInputStream(sasl, in);
292: in = new CRLFInputStream(in);
293: this.in = new LineInputStream(in);
294: OutputStream out = socket.getOutputStream();
295: out = new BufferedOutputStream(out);
296: out = new SaslOutputStream(sasl, out);
297: this.out = new CRLFOutputStream(out);
298: }
299: return true;
300: case READY:
301: try
302: {
303: byte[] c0 = response.getBytes("US-ASCII");
304: byte[] c1 = BASE64.decode(c0);
305: byte[] r0 = sasl.evaluateChallenge(c1);
306: byte[] r1 = BASE64.encode(r0);
307: out.write(r1);
308: out.write(0x0d);
309: out.flush();
310: if (debug)
311: {
312: Logger logger = Logger.getInstance();
313: logger.log("pop3", "> " +
314: new String(r1, "US-ASCII"));
315: }
316: }
317: catch (SaslException e)
318: {
319:
320: out.write(0x2a);
321: out.write(0x0d);
322: out.flush();
323: if (debug)
324: {
325: Logger logger = Logger.getInstance();
326: logger.log("pop3", "> *");
327: }
328: }
329: default:
330: return false;
331: }
332: }
333: }
334: catch (SaslException e)
335: {
336: return false;
337: }
338: catch (RuntimeException e)
339: {
340: return false;
341: }
342: }
343:
344:
351: public boolean apop(String username, String password)
352: throws IOException
353: {
354: if (username == null || password == null || timestamp == null)
355: {
356: return false;
357: }
358:
359: try
360: {
361: byte[] secret = password.getBytes("US-ASCII");
362:
363: byte[] target = new byte[timestamp.length + secret.length];
364: System.arraycopy(timestamp, 0, target, 0, timestamp.length);
365: System.arraycopy(secret, 0, target, timestamp.length, secret.length);
366: MessageDigest md5 = MessageDigest.getInstance("MD5");
367: byte[] db = md5.digest(target);
368:
369: StringBuffer digest = new StringBuffer();
370: for (int i = 0; i < db.length; i++)
371: {
372: int c = (int) db[i];
373: if (c < 0)
374: {
375: c += 256;
376: }
377: digest.append(Integer.toHexString((c & 0xf0) >> 4));
378: digest.append(Integer.toHexString(c & 0x0f));
379: }
380:
381: String cmd = new StringBuffer(APOP)
382: .append(' ')
383: .append(username)
384: .append(' ')
385: .append(digest.toString())
386: .toString();
387: send(cmd);
388: return getResponse() == OK;
389: }
390: catch (NoSuchAlgorithmException e)
391: {
392: Logger logger = Logger.getInstance();
393: logger.log("pop3", "MD5 algorithm not found");
394: return false;
395: }
396: }
397:
398:
406: public boolean login(String username, String password)
407: throws IOException
408: {
409: if (username == null || password == null)
410: {
411: return false;
412: }
413:
414: String cmd = USER + ' ' + username;
415: send(cmd);
416: if (getResponse() != OK)
417: {
418: return false;
419: }
420:
421: cmd = PASS + ' ' + password;
422: send(cmd);
423: return getResponse() == OK;
424: }
425:
426:
431: public boolean stls()
432: throws IOException
433: {
434: return stls(new EmptyX509TrustManager());
435: }
436:
437:
443: public boolean stls(TrustManager tm)
444: throws IOException
445: {
446: try
447: {
448:
449:
450: SSLContext context = SSLContext.getInstance("TLS");
451:
452: TrustManager[] trust = new TrustManager[] { tm };
453: context.init(null, trust, null);
454: SSLSocketFactory factory = context.getSocketFactory();
455:
456: send(STLS);
457: if (getResponse() != OK)
458: {
459: return false;
460: }
461:
462: String hostname = socket.getInetAddress().getHostName();
463: int port = socket.getPort();
464: SSLSocket ss =
465: (SSLSocket) factory.createSocket(socket, hostname, port, true);
466: String[] protocols = { "TLSv1", "SSLv3" };
467: ss.setEnabledProtocols(protocols);
468: ss.setUseClientMode(true);
469: ss.startHandshake();
470:
471:
472: InputStream in = ss.getInputStream();
473: in = new BufferedInputStream(in);
474: in = new CRLFInputStream(in);
475: this.in = new LineInputStream(in);
476: OutputStream out = ss.getOutputStream();
477: out = new BufferedOutputStream(out);
478: this.out = new CRLFOutputStream(out);
479:
480: return true;
481: }
482: catch (GeneralSecurityException e)
483: {
484: return false;
485: }
486: }
487:
488:
491: public int stat()
492: throws IOException
493: {
494: send(STAT);
495: if (getResponse() != OK)
496: {
497: throw new ProtocolException("STAT failed: " + response);
498: }
499: try
500: {
501: return
502: Integer.parseInt(response.substring(0, response.indexOf(' ')));
503: }
504: catch (NumberFormatException e)
505: {
506: throw new ProtocolException("Not a number: " + response);
507: }
508: catch (ArrayIndexOutOfBoundsException e)
509: {
510: throw new ProtocolException("Not a STAT response: " + response);
511: }
512: }
513:
514:
518: public int list(int msgnum)
519: throws IOException
520: {
521: String cmd = LIST + ' ' + msgnum;
522: send(cmd);
523: if (getResponse() != OK)
524: {
525: throw new ProtocolException("LIST failed: " + response);
526: }
527: try
528: {
529: return
530: Integer.parseInt(response.substring(response.indexOf(' ') + 1));
531: }
532: catch (NumberFormatException e)
533: {
534: throw new ProtocolException("Not a number: " + response);
535: }
536: }
537:
538:
544: public InputStream retr(int msgnum)
545: throws IOException
546: {
547: String cmd = RETR + ' ' + msgnum;
548: send(cmd);
549: if (getResponse() != OK)
550: {
551: throw new ProtocolException("RETR failed: " + response);
552: }
553: return new MessageInputStream(in);
554: }
555:
556:
560: public void dele(int msgnum)
561: throws IOException
562: {
563: String cmd = DELE + ' ' + msgnum;
564: send(cmd);
565: if (getResponse() != OK)
566: {
567: throw new ProtocolException("DELE failed: " + response);
568: }
569: }
570:
571:
575: public void noop()
576: throws IOException
577: {
578: send(NOOP);
579: if (getResponse() != OK)
580: {
581: throw new ProtocolException("NOOP failed: " + response);
582: }
583: }
584:
585:
588: public void rset()
589: throws IOException
590: {
591: send(RSET);
592: if (getResponse() != OK)
593: {
594: throw new ProtocolException("RSET failed: " + response);
595: }
596: }
597:
598:
605: public boolean quit()
606: throws IOException
607: {
608: send(QUIT);
609: int ret = getResponse();
610: socket.close();
611: return ret == OK;
612: }
613:
614:
620: public InputStream top(int msgnum)
621: throws IOException
622: {
623: String cmd = TOP + ' ' + msgnum + ' ' + '0';
624: send(cmd);
625: if (getResponse() != OK)
626: {
627: throw new ProtocolException("TOP failed: " + response);
628: }
629: return new MessageInputStream(in);
630: }
631:
632:
636: public String uidl(int msgnum)
637: throws IOException
638: {
639: String cmd = UIDL + ' ' + msgnum;
640: send(cmd);
641: if (getResponse() != OK)
642: {
643: throw new ProtocolException("UIDL failed: " + response);
644: }
645: return response.substring(response.indexOf(' ') + 1);
646: }
647:
648:
652: public Map uidl()
653: throws IOException
654: {
655: send(UIDL);
656: if (getResponse() != OK)
657: {
658: throw new ProtocolException("UIDL failed: " + response);
659: }
660: Map uids = new LinkedHashMap();
661: String line = in.readLine();
662: while (line != null && !(".".equals(line)))
663: {
664: int si = line.indexOf(' ');
665: if (si < 1)
666: {
667: throw new ProtocolException("Invalid UIDL response: " + line);
668: }
669: try
670: {
671: uids.put(new Integer(line.substring(0, si)),
672: line.substring(si + 1));
673: }
674: catch (NumberFormatException e)
675: {
676: throw new ProtocolException("Invalid message number: " + line);
677: }
678: }
679: return Collections.unmodifiableMap(uids);
680: }
681:
682:
687: public List capa()
688: throws IOException
689: {
690: send(CAPA);
691: if (getResponse() == OK)
692: {
693: final String DOT = ".";
694: List list = new ArrayList();
695: for (String line = in.readLine();
696: !DOT.equals(line);
697: line = in.readLine())
698: {
699: list.add(line);
700: }
701: return Collections.unmodifiableList(list);
702: }
703: return null;
704: }
705:
706:
711: protected void send(String command)
712: throws IOException
713: {
714: if (debug)
715: {
716: Logger logger = Logger.getInstance();
717: logger.log("pop3", "> " + command);
718: }
719: out.write(command);
720: out.writeln();
721: out.flush();
722: }
723:
724:
729: protected int getResponse()
730: throws IOException
731: {
732: response = in.readLine();
733: if (debug)
734: {
735: Logger logger = Logger.getInstance();
736: logger.log("pop3", "< " + response);
737: }
738: if (response.indexOf(_OK) == 0)
739: {
740: response = response.substring(3).trim();
741: return OK;
742: }
743: else if (response.indexOf(_ERR) == 0)
744: {
745: response = response.substring(4).trim();
746: return ERR;
747: }
748: else if (response.indexOf(_READY) == 0)
749: {
750: response = response.substring(2).trim();
751: return READY;
752: }
753: else
754: {
755: throw new ProtocolException("Unexpected response: " + response);
756: }
757: }
758:
759:
762: byte[] parseTimestamp(String greeting)
763: throws IOException
764: {
765: int bra = greeting.indexOf('<');
766: if (bra != -1)
767: {
768: int ket = greeting.indexOf('>', bra);
769: if (ket != -1)
770: {
771: String mid = greeting.substring(bra, ket + 1);
772: int at = mid.indexOf('@');
773: if (at != -1)
774: {
775: return mid.getBytes("US-ASCII");
776: }
777: }
778: }
779: return null;
780: }
781:
782: }