Source for gnu.inet.pop3.POP3Connection

   1: /*
   2:  * POP3Connection.java
   3:  * Copyright (C) 2003 The Free Software Foundation
   4:  * 
   5:  * This file is part of GNU inetlib, a library.
   6:  * 
   7:  * GNU inetlib is free software; you can redistribute it and/or modify
   8:  * it under the terms of the GNU General Public License as published by
   9:  * the Free Software Foundation; either version 2 of the License, or
  10:  * (at your option) any later version.
  11:  * 
  12:  * GNU inetlib is distributed in the hope that it will be useful,
  13:  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14:  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15:  * GNU General Public License for more details.
  16:  * 
  17:  * You should have received a copy of the GNU General Public License
  18:  * along with this library; if not, write to the Free Software
  19:  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  20:  * 
  21:  * Linking this library statically or dynamically with other modules is
  22:  * making a combined work based on this library.  Thus, the terms and
  23:  * conditions of the GNU General Public License cover the whole
  24:  * combination.
  25:  *
  26:  * As a special exception, the copyright holders of this library give you
  27:  * permission to link this library with independent modules to produce an
  28:  * executable, regardless of the license terms of these independent
  29:  * modules, and to copy and distribute the resulting executable under
  30:  * terms of your choice, provided that you also meet, for each linked
  31:  * independent module, the terms and conditions of the license of that
  32:  * module.  An independent module is a module which is not derived from
  33:  * or based on this library.  If you modify this library, you may extend
  34:  * this exception to your version of the library, but you are not
  35:  * obliged to do so.  If you do not wish to do so, delete this
  36:  * exception statement from your version.
  37:  */
  38: 
  39: package gnu.inet.pop3;
  40: 
  41: import java.io.BufferedInputStream;
  42: import java.io.BufferedOutputStream;
  43: import java.io.InputStream;
  44: import java.io.IOException;
  45: import java.io.OutputStream;
  46: import java.net.InetSocketAddress;
  47: import java.net.ProtocolException;
  48: import java.net.Socket;
  49: import java.net.UnknownHostException;
  50: import java.security.GeneralSecurityException;
  51: import java.security.MessageDigest;
  52: import java.security.NoSuchAlgorithmException;
  53: import java.util.ArrayList;
  54: import java.util.Collections;
  55: import java.util.LinkedHashMap;
  56: import java.util.List;
  57: import java.util.Map;
  58: import java.util.Properties;
  59: 
  60: import javax.net.ssl.SSLContext;
  61: import javax.net.ssl.SSLSocket;
  62: import javax.net.ssl.SSLSocketFactory;
  63: import javax.net.ssl.TrustManager;
  64: 
  65: import javax.security.auth.callback.CallbackHandler;
  66: import javax.security.sasl.Sasl;
  67: import javax.security.sasl.SaslClient;
  68: import javax.security.sasl.SaslException;
  69: 
  70: import gnu.inet.util.BASE64;
  71: import gnu.inet.util.CRLFInputStream;
  72: import gnu.inet.util.CRLFOutputStream;
  73: import gnu.inet.util.EmptyX509TrustManager;
  74: import gnu.inet.util.LineInputStream;
  75: import gnu.inet.util.Logger;
  76: import gnu.inet.util.MessageInputStream;
  77: import gnu.inet.util.SaslCallbackHandler;
  78: import gnu.inet.util.SaslCramMD5;
  79: import gnu.inet.util.SaslInputStream;
  80: import gnu.inet.util.SaslLogin;
  81: import gnu.inet.util.SaslOutputStream;
  82: import gnu.inet.util.SaslPlain;
  83: 
  84: /**
  85:  * A POP3 client connection.
  86:  * This implements the entire POP3 specification as detailed in RFC 1939,
  87:  * with the exception of the no-arg LIST and UIDL commands (use STAT
  88:  * followed by multiple LIST and/or UIDL instead) and TOP with a specified
  89:  * number of content lines. It also implements the POP3 extension mechanism
  90:  * CAPA, documented in RFC 2449, as well as the STLS command to initiate TLS
  91:  * over POP3 documented in RFC 2595 and the AUTH command in RFC 1734.
  92:  *
  93:  * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a>
  94:  */
  95: public class POP3Connection
  96: {
  97: 
  98:   /**
  99:    * The default POP3 port.
 100:    */
 101:   public static final int DEFAULT_PORT = 110;
 102: 
 103:   // -- POP3 vocabulary --
 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:   /**
 129:    * The socket used to communicate with the server.
 130:    */
 131:   protected Socket socket;
 132: 
 133:   /**
 134:    * The socket input stream.
 135:    */
 136:   protected LineInputStream in;
 137: 
 138:   /**
 139:    * The socket output stream.
 140:    */
 141:   protected CRLFOutputStream out;
 142: 
 143:   /**
 144:    * The last response received from the server.
 145:    * The status code (+OK or -ERR) is stripped from the line.
 146:    */
 147:   protected String response;
 148: 
 149:   /**
 150:    * If true, print debugging information.
 151:    */
 152:   protected boolean debug;
 153: 
 154:   /**
 155:    * The APOP timestamp, if sent by the server on connection.
 156:    * Otherwise null.
 157:    */
 158:   protected byte[] timestamp;
 159: 
 160:   /**
 161:    * Creates a new connection to the server.
 162:    * @param hostname the hostname of the server to connect to
 163:    */
 164:   public POP3Connection(String hostname)
 165:     throws UnknownHostException, IOException
 166:   {
 167:     this(hostname, -1, 0, 0, false);
 168:   }
 169: 
 170:   /**
 171:    * Creates a new connection to the server.
 172:    * @param hostname the hostname of the server to connect to
 173:    * @param port the port to connect to(if &lt;=0, use default POP3 port)
 174:    */
 175:   public POP3Connection(String hostname, int port)
 176:     throws UnknownHostException, IOException
 177:   {
 178:     this(hostname, port, 0, 0, false);
 179:   }
 180: 
 181:   /**
 182:    * Creates a new connection to the server.
 183:    * @param hostname the hostname of the server to connect to
 184:    * @param port the port to connect to(if &lt;=0, use default POP3 port)
 185:    * @param connectionTimeout the connection timeout, in milliseconds
 186:    * @param timeout the I/O timeout, in milliseconds
 187:    * @param debug print debugging information
 188:    */
 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:     // Set up socket
 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:     // APOP timestamp
 228:     timestamp = parseTimestamp(response);
 229:   }
 230: 
 231:   /**
 232:    * Authenticates the connection using the specified SASL mechanism,
 233:    * username, and password.
 234:    * @param mechanism a SASL authentication mechanism, e.g. LOGIN, PLAIN,
 235:    * CRAM-MD5, GSSAPI
 236:    * @param username the authentication principal
 237:    * @param password the authentication credentials
 238:    * @return true if authentication was successful, false otherwise
 239:    */
 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:         // Avoid lengthy callback procedure for GNU Crypto
 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:             // Fall back to home-grown SASL clients
 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);       // challenge
 305:                     byte[] r0 = sasl.evaluateChallenge(c1);
 306:                     byte[] r1 = BASE64.encode(r0);       // response
 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:                     // Error in SASL challenge evaluation - cancel exchange
 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;             // No provider for mechanism
 337:       }
 338:     catch (RuntimeException e)
 339:       {
 340:         return false;             // No javax.security.sasl classes
 341:       }
 342:   }
 343:   
 344:   /**
 345:    * Authenticate the specified user using the APOP MD5-based method.
 346:    * This does not transmit the password in the clear, but doesn't provide
 347:    * any transport-level privacy features either.
 348:    * @param username the user to authenticate
 349:    * @param password the user's password
 350:    */
 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:     // APOP <username> <digest>
 359:     try
 360:       {
 361:         byte[] secret = password.getBytes("US-ASCII");
 362:         // compute digest
 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:         // create hexadecimal representation
 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:         // send command
 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:   /**
 399:    * Authenticate the user using the basic USER and PASS handshake.
 400:    * It is recommended to use a more secure authentication method such as
 401:    * the <code>auth</code> or <code>apop</code> method if the server
 402:    * understands them.
 403:    * @param username the user to authenticate
 404:    * @param password the user's password
 405:    */
 406:   public boolean login(String username, String password)
 407:     throws IOException
 408:   {
 409:     if (username == null || password == null)
 410:       {
 411:         return false;
 412:       }
 413:     // USER <username>
 414:     String cmd = USER + ' ' + username;
 415:     send(cmd);
 416:     if (getResponse() != OK)
 417:       {
 418:         return false;
 419:       }
 420:     // PASS <password>
 421:     cmd = PASS + ' ' + password;
 422:     send(cmd);
 423:     return getResponse() == OK;
 424:   }
 425:   
 426:   /**
 427:    * Attempts to start TLS on the specified connection.
 428:    * See RFC 2595 for details
 429:    * @return true if successful, false otherwise
 430:    */
 431:   public boolean stls()
 432:     throws IOException
 433:   {
 434:     return stls(new EmptyX509TrustManager());
 435:   }
 436:   
 437:   /**
 438:    * Attempts to start TLS on the specified connection.
 439:    * See RFC 2595 for details
 440:    * @param tm the custom trust manager to use
 441:    * @return true if successful, false otherwise
 442:    */
 443:   public boolean stls(TrustManager tm)
 444:     throws IOException
 445:   {
 446:     try
 447:       {
 448:         // Use SSLSocketFactory to negotiate a TLS session and wrap the
 449:         // current socket.
 450:         SSLContext context = SSLContext.getInstance("TLS");
 451:         // We don't require strong validation of the server certificate
 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:         // set up streams
 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:   /**
 489:    * Returns the number of messages in the maildrop.
 490:    */
 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:   /**
 515:    * Returns the size of the specified message.
 516:    * @param msgnum the message number
 517:    */
 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:   /**
 539:    * Returns an input stream containing the entire message.
 540:    * This input stream must be read in its entirety before further commands
 541:    * can be issued on this connection.
 542:    * @param msgnum the message number
 543:    */
 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:   /**
 557:    * Marks the specified message as deleted.
 558:    * @param msgnum the message number
 559:    */
 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:   /**
 572:    * Does nothing.
 573:    * This can be used to keep the connection alive.
 574:    */
 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:   /**
 586:    * If any messages have been marked as deleted, they are unmarked.
 587:    */
 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:   /**
 599:    * Closes the connection.
 600:    * No further commands may be issued on this connection after this method
 601:    * has been called.
 602:    * @return true if all deleted messages were successfully removed, false
 603:    * otherwise
 604:    */
 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:   /**
 615:    * Returns just the headers of the specified message as an input stream.
 616:    * The stream must be read in its entirety before further commands can be
 617:    * issued.
 618:    * @param msgnum the message number
 619:    */
 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:   /**
 633:    * Returns a unique identifier for the specified message.
 634:    * @param msgnum the message number
 635:    */
 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:   /**
 649:    * Returns a map of message number to UID pairs.
 650:    * Message numbers are Integers, UIDs are Strings.
 651:    */
 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:   /**
 683:    * Returns a list of capabilities supported by the POP3 server.
 684:    * If the server does not support POP3 extensions, returns
 685:    * <code>null</code>.
 686:    */
 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:   /** 
 707:    * Send the command to the server.
 708:    * If <code>debug</code> is <code>true</code>,
 709:    * the command is logged.
 710:    */
 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:   /**
 725:    * Parse the response from the server.
 726:    * If <code>debug</code> is <code>true</code>,
 727:    * the response is logged.
 728:    */
 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:   /*
 760:    * Parse the APOP timestamp from the server's banner greeting.
 761:    */
 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)           // This is a valid RFC822 msg-id
 774:               {
 775:                 return mid.getBytes("US-ASCII");
 776:               }
 777:           }
 778:       }
 779:     return null;
 780:   }
 781:   
 782: }