1 |
/* |
/* |
2 |
* $Id$ |
* SMTPConnection.java |
3 |
* Copyright (C) 2003 Chris Burdess <dog@gnu.org> |
* Copyright (C) 2003 Chris Burdess <dog@gnu.org> |
4 |
* |
* |
5 |
* This file is part of GNU inetlib, a library. |
* This file is part of GNU inetlib, a library. |
81 |
* This implements RFC 2821. |
* This implements RFC 2821. |
82 |
* |
* |
83 |
* @author <a href="mailto:dog@gnu.org">Chris Burdess</a> |
* @author <a href="mailto:dog@gnu.org">Chris Burdess</a> |
|
* @version $Revision$ $Date$ |
|
84 |
*/ |
*/ |
85 |
public class SMTPConnection |
public class SMTPConnection |
86 |
{ |
{ |
154 |
* port. |
* port. |
155 |
* @param host the server hostname |
* @param host the server hostname |
156 |
*/ |
*/ |
157 |
public SMTPConnection (String host) throws IOException |
public SMTPConnection(String host) |
158 |
|
throws IOException |
159 |
{ |
{ |
160 |
this (host, DEFAULT_PORT); |
this(host, DEFAULT_PORT); |
161 |
} |
} |
162 |
|
|
163 |
/** |
/** |
166 |
* @param host the server hostname |
* @param host the server hostname |
167 |
* @param port the port to connect to |
* @param port the port to connect to |
168 |
*/ |
*/ |
169 |
public SMTPConnection (String host, int port) throws IOException |
public SMTPConnection(String host, int port) |
170 |
|
throws IOException |
171 |
{ |
{ |
172 |
this (host, port, 0, 0, false); |
this(host, port, 0, 0, false); |
173 |
} |
} |
174 |
|
|
175 |
/** |
/** |
181 |
* @param timeout the I/O timeout in milliseconds |
* @param timeout the I/O timeout in milliseconds |
182 |
* @param debug whether to log progress |
* @param debug whether to log progress |
183 |
*/ |
*/ |
184 |
public SMTPConnection (String host, int port, |
public SMTPConnection(String host, int port, |
185 |
int connectionTimeout, int timeout, boolean debug) |
int connectionTimeout, int timeout, boolean debug) |
186 |
throws IOException |
throws IOException |
187 |
{ |
{ |
188 |
if (port <= 0) |
if (port <= 0) |
194 |
this.debug = debug; |
this.debug = debug; |
195 |
|
|
196 |
// Initialise socket |
// Initialise socket |
197 |
socket = new Socket (); |
socket = new Socket(); |
198 |
InetSocketAddress address = new InetSocketAddress (host, port); |
InetSocketAddress address = new InetSocketAddress(host, port); |
199 |
if (connectionTimeout > 0) |
if (connectionTimeout > 0) |
200 |
{ |
{ |
201 |
socket.connect (address, connectionTimeout); |
socket.connect(address, connectionTimeout); |
202 |
} |
} |
203 |
else |
else |
204 |
{ |
{ |
205 |
socket.connect (address); |
socket.connect(address); |
206 |
} |
} |
207 |
if (timeout > 0) |
if (timeout > 0) |
208 |
{ |
{ |
209 |
socket.setSoTimeout (timeout); |
socket.setSoTimeout(timeout); |
210 |
} |
} |
211 |
|
|
212 |
// Initialise streams |
// Initialise streams |
213 |
InputStream in = socket.getInputStream (); |
InputStream in = socket.getInputStream(); |
214 |
in = new BufferedInputStream (in); |
in = new BufferedInputStream(in); |
215 |
in = new CRLFInputStream (in); |
in = new CRLFInputStream(in); |
216 |
this.in = new LineInputStream (in); |
this.in = new LineInputStream(in); |
217 |
OutputStream out = socket.getOutputStream (); |
OutputStream out = socket.getOutputStream(); |
218 |
out = new BufferedOutputStream (out); |
out = new BufferedOutputStream(out); |
219 |
this.out = new CRLFOutputStream (out); |
this.out = new CRLFOutputStream(out); |
220 |
|
|
221 |
// Greeting |
// Greeting |
222 |
StringBuffer greetingBuffer = new StringBuffer (); |
StringBuffer greetingBuffer = new StringBuffer(); |
223 |
boolean notFirst = false; |
boolean notFirst = false; |
224 |
do |
do |
225 |
{ |
{ |
226 |
if (getResponse () != READY) |
if (getResponse() != READY) |
227 |
{ |
{ |
228 |
throw new ProtocolException (response); |
throw new ProtocolException(response); |
229 |
} |
} |
230 |
if (notFirst) |
if (notFirst) |
231 |
{ |
{ |
232 |
greetingBuffer.append ('\n'); |
greetingBuffer.append('\n'); |
233 |
} |
} |
234 |
else |
else |
235 |
{ |
{ |
236 |
notFirst = true; |
notFirst = true; |
237 |
} |
} |
238 |
greetingBuffer.append (response); |
greetingBuffer.append(response); |
239 |
|
|
240 |
} |
} |
241 |
while (continuation); |
while (continuation); |
242 |
greeting = greetingBuffer.toString (); |
greeting = greetingBuffer.toString(); |
243 |
} |
} |
244 |
|
|
245 |
/** |
/** |
246 |
* Returns the server greeting message. |
* Returns the server greeting message. |
247 |
*/ |
*/ |
248 |
public String getGreeting () |
public String getGreeting() |
249 |
{ |
{ |
250 |
return greeting; |
return greeting; |
251 |
} |
} |
253 |
/** |
/** |
254 |
* Returns the text of the last response received from the server. |
* Returns the text of the last response received from the server. |
255 |
*/ |
*/ |
256 |
public String getLastResponse () |
public String getLastResponse() |
257 |
{ |
{ |
258 |
return response; |
return response; |
259 |
} |
} |
262 |
|
|
263 |
/** |
/** |
264 |
* Execute a MAIL command. |
* Execute a MAIL command. |
265 |
* @param reversePath the source mailbox (from address) |
* @param reversePath the source mailbox(from address) |
266 |
* @param parameters optional ESMTP parameters |
* @param parameters optional ESMTP parameters |
267 |
* @return true if accepted, false otherwise |
* @return true if accepted, false otherwise |
268 |
*/ |
*/ |
269 |
public boolean mailFrom (String reversePath, ParameterList parameters) |
public boolean mailFrom(String reversePath, ParameterList parameters) |
270 |
throws IOException |
throws IOException |
271 |
{ |
{ |
272 |
StringBuffer command = new StringBuffer (MAIL_FROM); |
StringBuffer command = new StringBuffer(MAIL_FROM); |
273 |
command.append ('<'); |
command.append('<'); |
274 |
command.append (reversePath); |
command.append(reversePath); |
275 |
command.append ('>'); |
command.append('>'); |
276 |
if (parameters != null) |
if (parameters != null) |
277 |
{ |
{ |
278 |
command.append (SP); |
command.append(SP); |
279 |
command.append (parameters); |
command.append(parameters); |
280 |
} |
} |
281 |
send (command.toString ()); |
send(command.toString()); |
282 |
switch (getAllResponses ()) |
switch (getAllResponses()) |
283 |
{ |
{ |
284 |
case OK: |
case OK: |
285 |
case OK_NOT_LOCAL: |
case OK_NOT_LOCAL: |
292 |
|
|
293 |
/** |
/** |
294 |
* Execute a RCPT command. |
* Execute a RCPT command. |
295 |
* @param forwardPath the forward-path (recipient address) |
* @param forwardPath the forward-path(recipient address) |
296 |
* @param parameters optional ESMTP parameters |
* @param parameters optional ESMTP parameters |
297 |
* @return true if successful, false otherwise |
* @return true if successful, false otherwise |
298 |
*/ |
*/ |
299 |
public boolean rcptTo (String forwardPath, ParameterList parameters) |
public boolean rcptTo(String forwardPath, ParameterList parameters) |
300 |
throws IOException |
throws IOException |
301 |
{ |
{ |
302 |
StringBuffer command = new StringBuffer (RCPT_TO); |
StringBuffer command = new StringBuffer(RCPT_TO); |
303 |
command.append ('<'); |
command.append('<'); |
304 |
command.append (forwardPath); |
command.append(forwardPath); |
305 |
command.append ('>'); |
command.append('>'); |
306 |
if (parameters != null) |
if (parameters != null) |
307 |
{ |
{ |
308 |
command.append (SP); |
command.append(SP); |
309 |
command.append (parameters); |
command.append(parameters); |
310 |
} |
} |
311 |
send (command.toString ()); |
send(command.toString()); |
312 |
switch (getAllResponses ()) |
switch (getAllResponses()) |
313 |
{ |
{ |
314 |
case OK: |
case OK: |
315 |
case OK_NOT_LOCAL: |
case OK_NOT_LOCAL: |
329 |
* must be called to complete the transfer and determine its success. |
* must be called to complete the transfer and determine its success. |
330 |
* @return a stream for writing messages to |
* @return a stream for writing messages to |
331 |
*/ |
*/ |
332 |
public OutputStream data () throws IOException |
public OutputStream data() |
333 |
|
throws IOException |
334 |
{ |
{ |
335 |
send (DATA); |
send(DATA); |
336 |
switch (getAllResponses ()) |
switch (getAllResponses()) |
337 |
{ |
{ |
338 |
case SEND_DATA: |
case SEND_DATA: |
339 |
return new MessageOutputStream (out); |
return new MessageOutputStream(out); |
340 |
default: |
default: |
341 |
throw new ProtocolException (response); |
throw new ProtocolException(response); |
342 |
} |
} |
343 |
} |
} |
344 |
|
|
347 |
* @see #data |
* @see #data |
348 |
* @return true id transfer was successful, false otherwise |
* @return true id transfer was successful, false otherwise |
349 |
*/ |
*/ |
350 |
public boolean finishData () throws IOException |
public boolean finishData() |
351 |
|
throws IOException |
352 |
{ |
{ |
353 |
send (FINISH_DATA); |
send(FINISH_DATA); |
354 |
switch (getAllResponses ()) |
switch (getAllResponses()) |
355 |
{ |
{ |
356 |
case OK: |
case OK: |
357 |
return true; |
return true; |
363 |
/** |
/** |
364 |
* Aborts the current mail transaction. |
* Aborts the current mail transaction. |
365 |
*/ |
*/ |
366 |
public void rset () throws IOException |
public void rset() |
367 |
|
throws IOException |
368 |
{ |
{ |
369 |
send (RSET); |
send(RSET); |
370 |
if (getAllResponses () != OK) |
if (getAllResponses() != OK) |
371 |
{ |
{ |
372 |
throw new ProtocolException (response); |
throw new ProtocolException(response); |
373 |
} |
} |
374 |
} |
} |
375 |
|
|
380 |
* null on failure. |
* null on failure. |
381 |
* @param address a mailbox, or real name and mailbox |
* @param address a mailbox, or real name and mailbox |
382 |
*/ |
*/ |
383 |
public List vrfy (String address) throws IOException |
public List vrfy(String address) |
384 |
|
throws IOException |
385 |
{ |
{ |
386 |
String command = VRFY + ' ' + address; |
String command = VRFY + ' ' + address; |
387 |
send (command); |
send(command); |
388 |
List list = new ArrayList (); |
List list = new ArrayList(); |
389 |
do |
do |
390 |
{ |
{ |
391 |
switch (getResponse ()) |
switch (getResponse()) |
392 |
{ |
{ |
393 |
case OK: |
case OK: |
394 |
case AMBIGUOUS: |
case AMBIGUOUS: |
395 |
response = response.trim (); |
response = response.trim(); |
396 |
if (response.indexOf ('@') != -1) |
if (response.indexOf('@') != -1) |
397 |
{ |
{ |
398 |
list.add (response); |
list.add(response); |
399 |
} |
} |
400 |
else if (response.indexOf ('<') != -1) |
else if (response.indexOf('<') != -1) |
401 |
{ |
{ |
402 |
list.add (response); |
list.add(response); |
403 |
} |
} |
404 |
else if (response.indexOf (' ') == -1) |
else if (response.indexOf(' ') == -1) |
405 |
{ |
{ |
406 |
list.add (response); |
list.add(response); |
407 |
} |
} |
408 |
break; |
break; |
409 |
default: |
default: |
411 |
} |
} |
412 |
} |
} |
413 |
while (continuation); |
while (continuation); |
414 |
return Collections.unmodifiableList (list); |
return Collections.unmodifiableList(list); |
415 |
} |
} |
416 |
|
|
417 |
/** |
/** |
419 |
* or null on failure. |
* or null on failure. |
420 |
* @param address a mailing list name |
* @param address a mailing list name |
421 |
*/ |
*/ |
422 |
public List expn (String address) throws IOException |
public List expn(String address) |
423 |
|
throws IOException |
424 |
{ |
{ |
425 |
String command = EXPN + ' ' + address; |
String command = EXPN + ' ' + address; |
426 |
send (command); |
send(command); |
427 |
List list = new ArrayList (); |
List list = new ArrayList(); |
428 |
do |
do |
429 |
{ |
{ |
430 |
switch (getResponse ()) |
switch (getResponse()) |
431 |
{ |
{ |
432 |
case OK: |
case OK: |
433 |
response = response.trim (); |
response = response.trim(); |
434 |
list.add (response); |
list.add(response); |
435 |
break; |
break; |
436 |
default: |
default: |
437 |
return null; |
return null; |
438 |
} |
} |
439 |
} |
} |
440 |
while (continuation); |
while (continuation); |
441 |
return Collections.unmodifiableList (list); |
return Collections.unmodifiableList(list); |
442 |
} |
} |
443 |
|
|
444 |
/** |
/** |
448 |
* @return a list of possibly useful information, or null if the command |
* @return a list of possibly useful information, or null if the command |
449 |
* failed. |
* failed. |
450 |
*/ |
*/ |
451 |
public List help (String arg) throws IOException |
public List help(String arg) |
452 |
|
throws IOException |
453 |
{ |
{ |
454 |
String command = (arg == null) ? HELP : |
String command = (arg == null) ? HELP : |
455 |
HELP + ' ' + arg; |
HELP + ' ' + arg; |
456 |
send (command); |
send(command); |
457 |
List list = new ArrayList (); |
List list = new ArrayList(); |
458 |
do |
do |
459 |
{ |
{ |
460 |
switch (getResponse ()) |
switch (getResponse()) |
461 |
{ |
{ |
462 |
case INFO: |
case INFO: |
463 |
list.add (response); |
list.add(response); |
464 |
break; |
break; |
465 |
default: |
default: |
466 |
return null; |
return null; |
467 |
} |
} |
468 |
} |
} |
469 |
while (continuation); |
while (continuation); |
470 |
return Collections.unmodifiableList (list); |
return Collections.unmodifiableList(list); |
471 |
} |
} |
472 |
|
|
473 |
/** |
/** |
474 |
* Issues a NOOP command. |
* Issues a NOOP command. |
475 |
* This does nothing, but can be used to keep the connection alive. |
* This does nothing, but can be used to keep the connection alive. |
476 |
*/ |
*/ |
477 |
public void noop () throws IOException |
public void noop() |
478 |
|
throws IOException |
479 |
{ |
{ |
480 |
send (NOOP); |
send(NOOP); |
481 |
getAllResponses (); |
getAllResponses(); |
482 |
} |
} |
483 |
|
|
484 |
/** |
/** |
485 |
* Close the connection to the server. |
* Close the connection to the server. |
486 |
*/ |
*/ |
487 |
public void quit () throws IOException |
public void quit() |
488 |
|
throws IOException |
489 |
{ |
{ |
490 |
try |
try |
491 |
{ |
{ |
492 |
send (QUIT); |
send(QUIT); |
493 |
getAllResponses (); |
getAllResponses(); |
494 |
/* RFC 2821 states that the server MUST send an OK reply here, but |
/* RFC 2821 states that the server MUST send an OK reply here, but |
495 |
* many don't: postfix, for instance, sends 221. |
* many don't: postfix, for instance, sends 221. |
496 |
* In any case we have done our best. */ |
* In any case we have done our best. */ |
501 |
finally |
finally |
502 |
{ |
{ |
503 |
// Close the socket anyway. |
// Close the socket anyway. |
504 |
socket.close (); |
socket.close(); |
505 |
} |
} |
506 |
} |
} |
507 |
|
|
509 |
* Issues a HELO command. |
* Issues a HELO command. |
510 |
* @param hostname the local host name |
* @param hostname the local host name |
511 |
*/ |
*/ |
512 |
public boolean helo (String hostname) throws IOException |
public boolean helo(String hostname) |
513 |
|
throws IOException |
514 |
{ |
{ |
515 |
String command = HELO + ' ' + hostname; |
String command = HELO + ' ' + hostname; |
516 |
send (command); |
send(command); |
517 |
return (getAllResponses () == OK); |
return (getAllResponses() == OK); |
518 |
} |
} |
519 |
|
|
520 |
/** |
/** |
524 |
* Otherwise returns null, and HELO should be called. |
* Otherwise returns null, and HELO should be called. |
525 |
* @param hostname the local host name |
* @param hostname the local host name |
526 |
*/ |
*/ |
527 |
public List ehlo (String hostname) throws IOException |
public List ehlo(String hostname) |
528 |
|
throws IOException |
529 |
{ |
{ |
530 |
String command = EHLO + ' ' + hostname; |
String command = EHLO + ' ' + hostname; |
531 |
send (command); |
send(command); |
532 |
List extensions = new ArrayList (); |
List extensions = new ArrayList(); |
533 |
do |
do |
534 |
{ |
{ |
535 |
switch (getResponse ()) |
switch (getResponse()) |
536 |
{ |
{ |
537 |
case OK: |
case OK: |
538 |
extensions.add (response); |
extensions.add(response); |
539 |
break; |
break; |
540 |
default: |
default: |
541 |
return null; |
return null; |
542 |
} |
} |
543 |
} |
} |
544 |
while (continuation); |
while (continuation); |
545 |
return Collections.unmodifiableList (extensions); |
return Collections.unmodifiableList(extensions); |
546 |
} |
} |
547 |
|
|
548 |
/** |
/** |
550 |
* This depends on many features, such as the JSSE classes being in the |
* This depends on many features, such as the JSSE classes being in the |
551 |
* classpath. Returns true if successful, false otherwise. |
* classpath. Returns true if successful, false otherwise. |
552 |
*/ |
*/ |
553 |
public boolean starttls () throws IOException |
public boolean starttls() |
554 |
|
throws IOException |
555 |
{ |
{ |
556 |
return starttls (new EmptyX509TrustManager ()); |
return starttls(new EmptyX509TrustManager()); |
557 |
} |
} |
558 |
|
|
559 |
/** |
/** |
562 |
* classpath. Returns true if successful, false otherwise. |
* classpath. Returns true if successful, false otherwise. |
563 |
* @param tm the custom trust manager to use |
* @param tm the custom trust manager to use |
564 |
*/ |
*/ |
565 |
public boolean starttls (TrustManager tm) throws IOException |
public boolean starttls(TrustManager tm) |
566 |
|
throws IOException |
567 |
{ |
{ |
568 |
try |
try |
569 |
{ |
{ |
570 |
// Use SSLSocketFactory to negotiate a TLS session and wrap the |
// Use SSLSocketFactory to negotiate a TLS session and wrap the |
571 |
// current socket. |
// current socket. |
572 |
SSLContext context = SSLContext.getInstance ("TLS"); |
SSLContext context = SSLContext.getInstance("TLS"); |
573 |
// We don't require strong validation of the server certificate |
// We don't require strong validation of the server certificate |
574 |
TrustManager[] trust = new TrustManager[] { tm }; |
TrustManager[] trust = new TrustManager[] { tm }; |
575 |
context.init (null, trust, null); |
context.init(null, trust, null); |
576 |
SSLSocketFactory factory = context.getSocketFactory (); |
SSLSocketFactory factory = context.getSocketFactory(); |
577 |
|
|
578 |
send (STARTTLS); |
send(STARTTLS); |
579 |
if (getAllResponses () != READY) |
if (getAllResponses() != READY) |
580 |
{ |
{ |
581 |
return false; |
return false; |
582 |
} |
} |
583 |
|
|
584 |
String hostname = socket.getInetAddress ().getHostName (); |
String hostname = socket.getInetAddress().getHostName(); |
585 |
int port = socket.getPort (); |
int port = socket.getPort(); |
586 |
SSLSocket ss = |
SSLSocket ss = |
587 |
(SSLSocket) factory.createSocket (socket, hostname, port, true); |
(SSLSocket) factory.createSocket(socket, hostname, port, true); |
588 |
String[] protocols = { "TLSv1", "SSLv3" }; |
String[] protocols = { "TLSv1", "SSLv3" }; |
589 |
ss.setEnabledProtocols (protocols); |
ss.setEnabledProtocols(protocols); |
590 |
ss.setUseClientMode (true); |
ss.setUseClientMode(true); |
591 |
ss.startHandshake (); |
ss.startHandshake(); |
592 |
|
|
593 |
// Set up streams |
// Set up streams |
594 |
InputStream in = ss.getInputStream (); |
InputStream in = ss.getInputStream(); |
595 |
in = new BufferedInputStream (in); |
in = new BufferedInputStream(in); |
596 |
in = new CRLFInputStream (in); |
in = new CRLFInputStream(in); |
597 |
this.in = new LineInputStream (in); |
this.in = new LineInputStream(in); |
598 |
OutputStream out = ss.getOutputStream (); |
OutputStream out = ss.getOutputStream(); |
599 |
out = new BufferedOutputStream (out); |
out = new BufferedOutputStream(out); |
600 |
this.out = new CRLFOutputStream (out); |
this.out = new CRLFOutputStream(out); |
601 |
return true; |
return true; |
602 |
} |
} |
603 |
catch (GeneralSecurityException e) |
catch (GeneralSecurityException e) |
617 |
* @param password the authentication credentials |
* @param password the authentication credentials |
618 |
* @return true if authentication was successful, false otherwise |
* @return true if authentication was successful, false otherwise |
619 |
*/ |
*/ |
620 |
public boolean authenticate (String mechanism, String username, |
public boolean authenticate(String mechanism, String username, |
621 |
String password) throws IOException |
String password) throws IOException |
622 |
{ |
{ |
623 |
try |
try |
624 |
{ |
{ |
625 |
String[] m = new String[] { mechanism }; |
String[] m = new String[] { mechanism }; |
626 |
CallbackHandler ch = new SaslCallbackHandler (username, password); |
CallbackHandler ch = new SaslCallbackHandler(username, password); |
627 |
// Avoid lengthy callback procedure for GNU Crypto |
// Avoid lengthy callback procedure for GNU Crypto |
628 |
Properties p = new Properties (); |
Properties p = new Properties(); |
629 |
p.put ("gnu.crypto.sasl.username", username); |
p.put("gnu.crypto.sasl.username", username); |
630 |
p.put ("gnu.crypto.sasl.password", password); |
p.put("gnu.crypto.sasl.password", password); |
631 |
SaslClient sasl = |
SaslClient sasl = |
632 |
Sasl.createSaslClient (m, null, "smtp", |
Sasl.createSaslClient(m, null, "smtp", |
633 |
socket.getInetAddress ().getHostName (), |
socket.getInetAddress().getHostName(), |
634 |
p, ch); |
p, ch); |
635 |
if (sasl == null) |
if (sasl == null) |
636 |
{ |
{ |
637 |
// Fall back to home-grown SASL clients |
// Fall back to home-grown SASL clients |
638 |
if ("LOGIN".equalsIgnoreCase (mechanism)) |
if ("LOGIN".equalsIgnoreCase(mechanism)) |
639 |
{ |
{ |
640 |
sasl = new SaslLogin (username, password); |
sasl = new SaslLogin(username, password); |
641 |
} |
} |
642 |
else if ("PLAIN".equalsIgnoreCase (mechanism)) |
else if ("PLAIN".equalsIgnoreCase(mechanism)) |
643 |
{ |
{ |
644 |
sasl = new SaslPlain (username, password); |
sasl = new SaslPlain(username, password); |
645 |
} |
} |
646 |
else if ("CRAM-MD5".equalsIgnoreCase (mechanism)) |
else if ("CRAM-MD5".equalsIgnoreCase(mechanism)) |
647 |
{ |
{ |
648 |
sasl = new SaslCramMD5 (username, password); |
sasl = new SaslCramMD5(username, password); |
649 |
} |
} |
650 |
else |
else |
651 |
{ |
{ |
653 |
} |
} |
654 |
} |
} |
655 |
|
|
656 |
StringBuffer cmd = new StringBuffer (AUTH); |
StringBuffer cmd = new StringBuffer(AUTH); |
657 |
cmd.append (' '); |
cmd.append(' '); |
658 |
cmd.append (mechanism); |
cmd.append(mechanism); |
659 |
if (sasl.hasInitialResponse ()) |
if (sasl.hasInitialResponse()) |
660 |
{ |
{ |
661 |
cmd.append (' '); |
cmd.append(' '); |
662 |
byte[] init = sasl.evaluateChallenge (new byte[0]); |
byte[] init = sasl.evaluateChallenge(new byte[0]); |
663 |
if (init.length == 0) |
if (init.length == 0) |
664 |
{ |
{ |
665 |
cmd.append ('='); |
cmd.append('='); |
666 |
} |
} |
667 |
else |
else |
668 |
{ |
{ |
669 |
cmd.append (new String (BASE64.encode (init), "US-ASCII")); |
cmd.append(new String(BASE64.encode(init), "US-ASCII")); |
670 |
} |
} |
671 |
} |
} |
672 |
send (cmd.toString ()); |
send(cmd.toString()); |
673 |
while (true) |
while (true) |
674 |
{ |
{ |
675 |
switch (getAllResponses ()) |
switch (getAllResponses()) |
676 |
{ |
{ |
677 |
case 334: |
case 334: |
678 |
try |
try |
679 |
{ |
{ |
680 |
byte[] c0 = response.getBytes ("US-ASCII"); |
byte[] c0 = response.getBytes("US-ASCII"); |
681 |
byte[] c1 = BASE64.decode (c0); // challenge |
byte[] c1 = BASE64.decode(c0); // challenge |
682 |
byte[] r0 = sasl.evaluateChallenge (c1); |
byte[] r0 = sasl.evaluateChallenge(c1); |
683 |
byte[] r1 = BASE64.encode (r0); // response |
byte[] r1 = BASE64.encode(r0); // response |
684 |
out.write (r1); |
out.write(r1); |
685 |
out.write (0x0d); |
out.write(0x0d); |
686 |
out.flush (); |
out.flush(); |
687 |
if (debug) |
if (debug) |
688 |
{ |
{ |
689 |
Logger logger = Logger.getInstance (); |
Logger logger = Logger.getInstance(); |
690 |
logger.log ("smtp", "> " + |
logger.log("smtp", "> " + |
691 |
new String (r1, "US-ASCII")); |
new String(r1, "US-ASCII")); |
692 |
} |
} |
693 |
} |
} |
694 |
catch (SaslException e) |
catch (SaslException e) |
695 |
{ |
{ |
696 |
// Error in SASL challenge evaluation - cancel exchange |
// Error in SASL challenge evaluation - cancel exchange |
697 |
out.write (0x2a); |
out.write(0x2a); |
698 |
out.write (0x0d); |
out.write(0x0d); |
699 |
out.flush (); |
out.flush(); |
700 |
if (debug) |
if (debug) |
701 |
{ |
{ |
702 |
Logger logger = Logger.getInstance (); |
Logger logger = Logger.getInstance(); |
703 |
logger.log ("smtp", "> *"); |
logger.log("smtp", "> *"); |
704 |
} |
} |
705 |
} |
} |
706 |
break; |
break; |
707 |
case 235: |
case 235: |
708 |
String qop = (String) sasl.getNegotiatedProperty (Sasl.QOP); |
String qop = (String) sasl.getNegotiatedProperty(Sasl.QOP); |
709 |
if ("auth-int".equalsIgnoreCase (qop) |
if ("auth-int".equalsIgnoreCase(qop) |
710 |
|| "auth-conf".equalsIgnoreCase (qop)) |
|| "auth-conf".equalsIgnoreCase(qop)) |
711 |
{ |
{ |
712 |
InputStream in = socket.getInputStream (); |
InputStream in = socket.getInputStream(); |
713 |
in = new BufferedInputStream (in); |
in = new BufferedInputStream(in); |
714 |
in = new SaslInputStream (sasl, in); |
in = new SaslInputStream(sasl, in); |
715 |
in = new CRLFInputStream (in); |
in = new CRLFInputStream(in); |
716 |
this.in = new LineInputStream (in); |
this.in = new LineInputStream(in); |
717 |
OutputStream out = socket.getOutputStream (); |
OutputStream out = socket.getOutputStream(); |
718 |
out = new BufferedOutputStream (out); |
out = new BufferedOutputStream(out); |
719 |
out = new SaslOutputStream (sasl, out); |
out = new SaslOutputStream(sasl, out); |
720 |
this.out = new CRLFOutputStream (out); |
this.out = new CRLFOutputStream(out); |
721 |
} |
} |
722 |
return true; |
return true; |
723 |
default: |
default: |
743 |
* Send the specified command string to the server. |
* Send the specified command string to the server. |
744 |
* @param command the command to send |
* @param command the command to send |
745 |
*/ |
*/ |
746 |
protected void send (String command) throws IOException |
protected void send(String command) |
747 |
|
throws IOException |
748 |
{ |
{ |
749 |
if (debug) |
if (debug) |
750 |
{ |
{ |
751 |
Logger logger = Logger.getInstance (); |
Logger logger = Logger.getInstance(); |
752 |
logger.log ("smtp", "> " + command); |
logger.log("smtp", "> " + command); |
753 |
} |
} |
754 |
out.write (command.getBytes ("US-ASCII")); |
out.write(command.getBytes("US-ASCII")); |
755 |
out.write (0x0d); |
out.write(0x0d); |
756 |
out.flush (); |
out.flush(); |
757 |
} |
} |
758 |
|
|
759 |
/** |
/** |
760 |
* Returns the next response from the server. |
* Returns the next response from the server. |
761 |
*/ |
*/ |
762 |
protected int getResponse () throws IOException |
protected int getResponse() |
763 |
|
throws IOException |
764 |
{ |
{ |
765 |
String line = null; |
String line = null; |
766 |
try |
try |
767 |
{ |
{ |
768 |
line = in.readLine (); |
line = in.readLine(); |
769 |
// Handle special case eg 334 where CRLF occurs after code. |
// Handle special case eg 334 where CRLF occurs after code. |
770 |
if (line.length () < 4) |
if (line.length() < 4) |
771 |
{ |
{ |
772 |
line = line + '\n' + in.readLine(); |
line = line + '\n' + in.readLine(); |
773 |
} |
} |
774 |
if (debug) |
if (debug) |
775 |
{ |
{ |
776 |
Logger logger = Logger.getInstance (); |
Logger logger = Logger.getInstance(); |
777 |
logger.log ("smtp", "< " + line); |
logger.log("smtp", "< " + line); |
778 |
} |
} |
779 |
int code = Integer.parseInt (line.substring (0, 3)); |
int code = Integer.parseInt(line.substring(0, 3)); |
780 |
continuation = (line.charAt (3) == '-'); |
continuation = (line.charAt(3) == '-'); |
781 |
response = line.substring (4); |
response = line.substring(4); |
782 |
return code; |
return code; |
783 |
} |
} |
784 |
catch (NumberFormatException e) |
catch (NumberFormatException e) |
785 |
{ |
{ |
786 |
throw new ProtocolException ("Unexpected response: " + line); |
throw new ProtocolException("Unexpected response: " + line); |
787 |
} |
} |
788 |
} |
} |
789 |
|
|
793 |
* continuation ceases. If a different response code from the first is |
* continuation ceases. If a different response code from the first is |
794 |
* encountered, this causes a protocol exception. |
* encountered, this causes a protocol exception. |
795 |
*/ |
*/ |
796 |
protected int getAllResponses () throws IOException |
protected int getAllResponses() |
797 |
|
throws IOException |
798 |
{ |
{ |
799 |
int code1, code; |
int code1, code; |
800 |
boolean err = false; |
boolean err = false; |
801 |
code1 = code = getResponse (); |
code1 = code = getResponse(); |
802 |
while (continuation) |
while (continuation) |
803 |
{ |
{ |
804 |
code = getResponse(); |
code = getResponse(); |
809 |
} |
} |
810 |
if (err) |
if (err) |
811 |
{ |
{ |
812 |
throw new ProtocolException ("Conflicting response codes"); |
throw new ProtocolException("Conflicting response codes"); |
813 |
} |
} |
814 |
return code; |
return code; |
815 |
} |
} |
816 |
|
|
817 |
} |
} |
818 |
|
|