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:
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: import ;
70: import ;
71: import ;
72: import ;
73: import ;
74: import ;
75: import ;
76: import ;
77: import ;
78:
79:
85: public class SMTPConnection
86: {
87:
88:
91: public static final int DEFAULT_PORT = 25;
92:
93: protected static final String MAIL_FROM = "MAIL FROM:";
94: protected static final String RCPT_TO = "RCPT TO:";
95: protected static final String SP = " ";
96: protected static final String DATA = "DATA";
97: protected static final String FINISH_DATA = "\n.";
98: protected static final String RSET = "RSET";
99: protected static final String VRFY = "VRFY";
100: protected static final String EXPN = "EXPN";
101: protected static final String HELP = "HELP";
102: protected static final String NOOP = "NOOP";
103: protected static final String QUIT = "QUIT";
104: protected static final String HELO = "HELO";
105: protected static final String EHLO = "EHLO";
106: protected static final String AUTH = "AUTH";
107: protected static final String STARTTLS = "STARTTLS";
108:
109: protected static final int INFO = 214;
110: protected static final int READY = 220;
111: protected static final int OK = 250;
112: protected static final int OK_NOT_LOCAL = 251;
113: protected static final int OK_UNVERIFIED = 252;
114: protected static final int SEND_DATA = 354;
115: protected static final int AMBIGUOUS = 553;
116:
117:
120: protected Socket socket;
121:
122:
125: protected LineInputStream in;
126:
127:
130: protected CRLFOutputStream out;
131:
132:
135: protected boolean debug;
136:
137:
140: protected String response;
141:
142:
145: protected boolean continuation;
146:
147:
150: protected final String greeting;
151:
152:
157: public SMTPConnection(String host)
158: throws IOException
159: {
160: this(host, DEFAULT_PORT);
161: }
162:
163:
169: public SMTPConnection(String host, int port)
170: throws IOException
171: {
172: this(host, port, 0, 0, false);
173: }
174:
175:
184: public SMTPConnection(String host, int port,
185: int connectionTimeout, int timeout, boolean debug)
186: throws IOException
187: {
188: if (port <= 0)
189: {
190: port = DEFAULT_PORT;
191: }
192: response = null;
193: continuation = false;
194: this.debug = debug;
195:
196:
197: socket = new Socket();
198: InetSocketAddress address = new InetSocketAddress(host, port);
199: if (connectionTimeout > 0)
200: {
201: socket.connect(address, connectionTimeout);
202: }
203: else
204: {
205: socket.connect(address);
206: }
207: if (timeout > 0)
208: {
209: socket.setSoTimeout(timeout);
210: }
211:
212:
213: InputStream in = socket.getInputStream();
214: in = new BufferedInputStream(in);
215: in = new CRLFInputStream(in);
216: this.in = new LineInputStream(in);
217: OutputStream out = socket.getOutputStream();
218: out = new BufferedOutputStream(out);
219: this.out = new CRLFOutputStream(out);
220:
221:
222: StringBuffer greetingBuffer = new StringBuffer();
223: boolean notFirst = false;
224: do
225: {
226: if (getResponse() != READY)
227: {
228: throw new ProtocolException(response);
229: }
230: if (notFirst)
231: {
232: greetingBuffer.append('\n');
233: }
234: else
235: {
236: notFirst = true;
237: }
238: greetingBuffer.append(response);
239:
240: }
241: while (continuation);
242: greeting = greetingBuffer.toString();
243: }
244:
245:
248: public String getGreeting()
249: {
250: return greeting;
251: }
252:
253:
256: public String getLastResponse()
257: {
258: return response;
259: }
260:
261:
262:
263:
269: public boolean mailFrom(String reversePath, ParameterList parameters)
270: throws IOException
271: {
272: StringBuffer command = new StringBuffer(MAIL_FROM);
273: command.append('<');
274: command.append(reversePath);
275: command.append('>');
276: if (parameters != null)
277: {
278: command.append(SP);
279: command.append(parameters);
280: }
281: send(command.toString());
282: switch (getAllResponses())
283: {
284: case OK:
285: case OK_NOT_LOCAL:
286: case OK_UNVERIFIED:
287: return true;
288: default:
289: return false;
290: }
291: }
292:
293:
299: public boolean rcptTo(String forwardPath, ParameterList parameters)
300: throws IOException
301: {
302: StringBuffer command = new StringBuffer(RCPT_TO);
303: command.append('<');
304: command.append(forwardPath);
305: command.append('>');
306: if (parameters != null)
307: {
308: command.append(SP);
309: command.append(parameters);
310: }
311: send(command.toString());
312: switch (getAllResponses())
313: {
314: case OK:
315: case OK_NOT_LOCAL:
316: case OK_UNVERIFIED:
317: return true;
318: default:
319: return false;
320: }
321: }
322:
323:
332: public OutputStream data()
333: throws IOException
334: {
335: send(DATA);
336: switch (getAllResponses())
337: {
338: case SEND_DATA:
339: return new MessageOutputStream(out);
340: default:
341: throw new ProtocolException(response);
342: }
343: }
344:
345:
350: public boolean finishData()
351: throws IOException
352: {
353: send(FINISH_DATA);
354: switch (getAllResponses())
355: {
356: case OK:
357: return true;
358: default:
359: return false;
360: }
361: }
362:
363:
366: public void rset()
367: throws IOException
368: {
369: send(RSET);
370: if (getAllResponses() != OK)
371: {
372: throw new ProtocolException(response);
373: }
374: }
375:
376:
377:
378:
383: public List vrfy(String address)
384: throws IOException
385: {
386: String command = VRFY + ' ' + address;
387: send(command);
388: List list = new ArrayList();
389: do
390: {
391: switch (getResponse())
392: {
393: case OK:
394: case AMBIGUOUS:
395: response = response.trim();
396: if (response.indexOf('@') != -1)
397: {
398: list.add(response);
399: }
400: else if (response.indexOf('<') != -1)
401: {
402: list.add(response);
403: }
404: else if (response.indexOf(' ') == -1)
405: {
406: list.add(response);
407: }
408: break;
409: default:
410: return null;
411: }
412: }
413: while (continuation);
414: return Collections.unmodifiableList(list);
415: }
416:
417:
422: public List expn(String address)
423: throws IOException
424: {
425: String command = EXPN + ' ' + address;
426: send(command);
427: List list = new ArrayList();
428: do
429: {
430: switch (getResponse())
431: {
432: case OK:
433: response = response.trim();
434: list.add(response);
435: break;
436: default:
437: return null;
438: }
439: }
440: while (continuation);
441: return Collections.unmodifiableList(list);
442: }
443:
444:
451: public List help(String arg)
452: throws IOException
453: {
454: String command = (arg == null) ? HELP :
455: HELP + ' ' + arg;
456: send(command);
457: List list = new ArrayList();
458: do
459: {
460: switch (getResponse())
461: {
462: case INFO:
463: list.add(response);
464: break;
465: default:
466: return null;
467: }
468: }
469: while (continuation);
470: return Collections.unmodifiableList(list);
471: }
472:
473:
477: public void noop()
478: throws IOException
479: {
480: send(NOOP);
481: getAllResponses();
482: }
483:
484:
487: public void quit()
488: throws IOException
489: {
490: try
491: {
492: send(QUIT);
493: getAllResponses();
494:
497: }
498: catch (IOException e)
499: {
500: }
501: finally
502: {
503:
504: socket.close();
505: }
506: }
507:
508:
512: public boolean helo(String hostname)
513: throws IOException
514: {
515: String command = HELO + ' ' + hostname;
516: send(command);
517: return (getAllResponses() == OK);
518: }
519:
520:
527: public List ehlo(String hostname)
528: throws IOException
529: {
530: String command = EHLO + ' ' + hostname;
531: send(command);
532: List extensions = new ArrayList();
533: do
534: {
535: switch (getResponse())
536: {
537: case OK:
538: extensions.add(response);
539: break;
540: default:
541: return null;
542: }
543: }
544: while (continuation);
545: return Collections.unmodifiableList(extensions);
546: }
547:
548:
553: public boolean starttls()
554: throws IOException
555: {
556: return starttls(new EmptyX509TrustManager());
557: }
558:
559:
565: public boolean starttls(TrustManager tm)
566: throws IOException
567: {
568: try
569: {
570:
571:
572: SSLContext context = SSLContext.getInstance("TLS");
573:
574: TrustManager[] trust = new TrustManager[] { tm };
575: context.init(null, trust, null);
576: SSLSocketFactory factory = context.getSocketFactory();
577:
578: send(STARTTLS);
579: if (getAllResponses() != READY)
580: {
581: return false;
582: }
583:
584: String hostname = socket.getInetAddress().getHostName();
585: int port = socket.getPort();
586: SSLSocket ss =
587: (SSLSocket) factory.createSocket(socket, hostname, port, true);
588: String[] protocols = { "TLSv1", "SSLv3" };
589: ss.setEnabledProtocols(protocols);
590: ss.setUseClientMode(true);
591: ss.startHandshake();
592:
593:
594: InputStream in = ss.getInputStream();
595: in = new BufferedInputStream(in);
596: in = new CRLFInputStream(in);
597: this.in = new LineInputStream(in);
598: OutputStream out = ss.getOutputStream();
599: out = new BufferedOutputStream(out);
600: this.out = new CRLFOutputStream(out);
601: return true;
602: }
603: catch (GeneralSecurityException e)
604: {
605: return false;
606: }
607: }
608:
609:
610:
611:
620: public boolean authenticate(String mechanism, String username,
621: String password) throws IOException
622: {
623: try
624: {
625: String[] m = new String[] { mechanism };
626: CallbackHandler ch = new SaslCallbackHandler(username, password);
627:
628: Properties p = new Properties();
629: p.put("gnu.crypto.sasl.username", username);
630: p.put("gnu.crypto.sasl.password", password);
631: SaslClient sasl =
632: Sasl.createSaslClient(m, null, "smtp",
633: socket.getInetAddress().getHostName(),
634: p, ch);
635: if (sasl == null)
636: {
637:
638: if ("LOGIN".equalsIgnoreCase(mechanism))
639: {
640: sasl = new SaslLogin(username, password);
641: }
642: else if ("PLAIN".equalsIgnoreCase(mechanism))
643: {
644: sasl = new SaslPlain(username, password);
645: }
646: else if ("CRAM-MD5".equalsIgnoreCase(mechanism))
647: {
648: sasl = new SaslCramMD5(username, password);
649: }
650: else
651: {
652: return false;
653: }
654: }
655:
656: StringBuffer cmd = new StringBuffer(AUTH);
657: cmd.append(' ');
658: cmd.append(mechanism);
659: if (sasl.hasInitialResponse())
660: {
661: cmd.append(' ');
662: byte[] init = sasl.evaluateChallenge(new byte[0]);
663: if (init.length == 0)
664: {
665: cmd.append('=');
666: }
667: else
668: {
669: cmd.append(new String(BASE64.encode(init), "US-ASCII"));
670: }
671: }
672: send(cmd.toString());
673: while (true)
674: {
675: switch (getAllResponses())
676: {
677: case 334:
678: try
679: {
680: byte[] c0 = response.getBytes("US-ASCII");
681: byte[] c1 = BASE64.decode(c0);
682: byte[] r0 = sasl.evaluateChallenge(c1);
683: byte[] r1 = BASE64.encode(r0);
684: out.write(r1);
685: out.write(0x0d);
686: out.flush();
687: if (debug)
688: {
689: Logger logger = Logger.getInstance();
690: logger.log("smtp", "> " +
691: new String(r1, "US-ASCII"));
692: }
693: }
694: catch (SaslException e)
695: {
696:
697: out.write(0x2a);
698: out.write(0x0d);
699: out.flush();
700: if (debug)
701: {
702: Logger logger = Logger.getInstance();
703: logger.log("smtp", "> *");
704: }
705: }
706: break;
707: case 235:
708: String qop = (String) sasl.getNegotiatedProperty(Sasl.QOP);
709: if ("auth-int".equalsIgnoreCase(qop)
710: || "auth-conf".equalsIgnoreCase(qop))
711: {
712: InputStream in = socket.getInputStream();
713: in = new BufferedInputStream(in);
714: in = new SaslInputStream(sasl, in);
715: in = new CRLFInputStream(in);
716: this.in = new LineInputStream(in);
717: OutputStream out = socket.getOutputStream();
718: out = new BufferedOutputStream(out);
719: out = new SaslOutputStream(sasl, out);
720: this.out = new CRLFOutputStream(out);
721: }
722: return true;
723: default:
724: return false;
725: }
726: }
727: }
728: catch (SaslException e)
729: {
730: e.printStackTrace(System.err);
731: return false;
732: }
733: catch (RuntimeException e)
734: {
735: e.printStackTrace(System.err);
736: return false;
737: }
738: }
739:
740:
741:
742:
746: protected void send(String command)
747: throws IOException
748: {
749: if (debug)
750: {
751: Logger logger = Logger.getInstance();
752: logger.log("smtp", "> " + command);
753: }
754: out.write(command.getBytes("US-ASCII"));
755: out.write(0x0d);
756: out.flush();
757: }
758:
759:
762: protected int getResponse()
763: throws IOException
764: {
765: String line = null;
766: try
767: {
768: line = in.readLine();
769:
770: if (line.length() < 4)
771: {
772: line = line + '\n' + in.readLine();
773: }
774: if (debug)
775: {
776: Logger logger = Logger.getInstance();
777: logger.log("smtp", "< " + line);
778: }
779: int code = Integer.parseInt(line.substring(0, 3));
780: continuation = (line.charAt(3) == '-');
781: response = line.substring(4);
782: return code;
783: }
784: catch (NumberFormatException e)
785: {
786: throw new ProtocolException("Unexpected response: " + line);
787: }
788: }
789:
790:
796: protected int getAllResponses()
797: throws IOException
798: {
799: int code1, code;
800: boolean err = false;
801: code1 = code = getResponse();
802: while (continuation)
803: {
804: code = getResponse();
805: if (code != code1)
806: {
807: err = true;
808: }
809: }
810: if (err)
811: {
812: throw new ProtocolException("Conflicting response codes");
813: }
814: return code;
815: }
816:
817: }