1 36 package org.columba.ristretto.pop3; 37 38 import java.io.IOException ; 39 import java.io.InputStream ; 40 import java.io.OutputStream ; 41 import java.net.Socket ; 42 import java.nio.ByteBuffer ; 43 import java.security.MessageDigest ; 44 import java.security.NoSuchAlgorithmException ; 45 import java.util.LinkedList ; 46 import java.util.logging.Logger ; 47 import java.util.regex.Matcher ; 48 import java.util.regex.Pattern ; 49 50 import javax.net.ssl.SSLException; 51 import javax.net.ssl.SSLSocket; 52 53 import org.columba.ristretto.auth.AuthenticationException; 54 import org.columba.ristretto.auth.AuthenticationFactory; 55 import org.columba.ristretto.auth.AuthenticationMechanism; 56 import org.columba.ristretto.auth.AuthenticationServer; 57 import org.columba.ristretto.auth.NoSuchAuthenticationException; 58 import org.columba.ristretto.coder.Base64; 59 import org.columba.ristretto.concurrency.Mutex; 60 import org.columba.ristretto.config.RistrettoConfig; 61 import org.columba.ristretto.io.Source; 62 import org.columba.ristretto.log.LogInputStream; 63 import org.columba.ristretto.log.LogOutputStream; 64 import org.columba.ristretto.log.RistrettoLogger; 65 import org.columba.ristretto.parser.ParserException; 66 import org.columba.ristretto.pop3.parser.ScanListParser; 67 import org.columba.ristretto.pop3.parser.UIDListParser; 68 import org.columba.ristretto.ssl.RistrettoSSLSocketFactory; 69 70 75 public class POP3Protocol implements AuthenticationServer { 76 77 78 private static final Logger LOG = Logger.getLogger("org.columba.ristretto.pop3.protocol"); 79 80 81 84 public static final int DEFAULT_PORT = 110; 85 88 public static final int DEFAULT_SSL_PORT = 995; 89 90 93 public static final int CONNECTION_CLOSED = 0; 94 95 98 public static final int NOT_CONNECTED = 0; 99 102 public static final int AUTHORIZATION = 1; 103 106 public static final int TRANSACTION = 2; 107 108 private static final Pattern timestampPattern = 109 Pattern.compile("(<[^>]*>)"); 110 111 private static final Pattern linePattern = Pattern.compile("(([^\r\n]+)\r?\n?)"); 112 113 private static final Pattern statPattern = Pattern.compile("(\\d+) (\\d+)"); 114 115 116 private String servername; 118 private int port; 119 private Socket socket; 120 private POP3InputStream in; 121 private OutputStream out; 122 private int state; 123 124 private String timestamp; 125 126 private Mutex mutex; 127 128 134 public POP3Protocol(String servername, int port) { 135 this.port = port; 136 this.servername = servername; 137 138 mutex = new Mutex(); 139 140 state = NOT_CONNECTED; 141 } 142 143 148 public POP3Protocol(String servername) { 149 this(servername, DEFAULT_PORT); 150 } 151 152 158 public void openPort() throws IOException , POP3Exception { 159 socket = new Socket (servername, port); 160 161 socket.setSoTimeout(RistrettoConfig.getInstance().getTimeout()); 162 163 createStreams(); 164 165 POP3Response response = in.readSingleLineResponse(); 166 167 Matcher matcher = timestampPattern.matcher(response.getMessage()); 169 if (matcher.find()) { 170 timestamp = matcher.group(1); 171 } 172 173 if( response.isOK() ) { 174 state = AUTHORIZATION; 175 } 176 } 177 178 186 public void openSSLPort() throws IOException , SSLException, POP3Exception { 187 socket = RistrettoSSLSocketFactory.getInstance().createSocket(servername, 188 port); 189 socket.setSoTimeout(RistrettoConfig.getInstance().getTimeout()); 190 191 ((SSLSocket) socket).startHandshake(); 193 194 createStreams(); 195 196 POP3Response response = in.readSingleLineResponse(); 197 198 Matcher matcher = timestampPattern.matcher(response.getMessage()); 200 if (matcher.find()) { 201 timestamp = matcher.group(1); 202 } 203 204 if( response.isOK() ) { 205 state = AUTHORIZATION; 206 } 207 } 208 209 216 public void startTLS() throws IOException , SSLException, POP3Exception { 217 sendCommand("STLS", null); 218 219 POP3Response response = readSingleLineResponse(); 220 if( response.isERR() ) throw new CommandNotSupportedException("STLS"); 221 222 socket = RistrettoSSLSocketFactory.getInstance().createSocket(socket, servername, port, true); 223 224 ((SSLSocket) socket).startHandshake(); 226 227 createStreams(); 228 } 229 230 protected void sendCommand(String command, String [] parameters) 231 throws IOException { 232 try { 233 out.write(command.getBytes()); 235 236 if (parameters != null) { 238 for (int i = 0; i < parameters.length; i++) { 239 out.write(' '); 240 out.write(parameters[i].getBytes()); 241 } 242 } 243 244 out.write('\r'); 246 out.write('\n'); 247 248 out.flush(); 250 } catch (IOException e) { 251 state = NOT_CONNECTED; 252 throw e; 253 } 254 } 255 256 protected POP3Response readSingleLineResponse() throws IOException , POP3Exception { 257 try { 258 return in.readSingleLineResponse(); 259 } catch (IOException e) { 260 state = NOT_CONNECTED; 262 throw e; 263 } 264 } 265 266 protected POP3Response readMultiLineResponse() throws IOException , POP3Exception { 267 try { 268 return in.readMultiLineResponse(); 269 } catch (IOException e) { 270 state = NOT_CONNECTED; 272 throw e; 273 } 274 } 275 276 277 private void checkState(int state) throws POP3Exception { 278 if( this.state != state ) { 279 throw new POP3Exception("Wrong state: Should be "+ state + "but is in " + this.state); 280 } 281 } 282 283 284 292 public boolean isApopSupported() throws POP3Exception { 293 checkState(AUTHORIZATION); 294 295 return( timestamp != null); 296 } 297 298 306 public void apop(String user, char[] secret) 307 throws IOException , POP3Exception { 308 if (timestamp == null) { 309 throw new CommandNotSupportedException("No timestamp from server - APOP not possible"); 310 } 311 try { 312 MessageDigest md = MessageDigest.getInstance("MD5"); 313 md.update(timestamp.getBytes()); 314 if (secret == null) 315 secret = new char[0]; 316 byte[] digest = md.digest(new String (secret).getBytes("ISO-8859-1")); 317 318 mutex.lock(); 319 sendCommand("APOP", new String [] { user, digestToString(digest)}); 320 321 POP3Response response = readSingleLineResponse(); 322 if (!response.isOK()) { 323 throw new POP3Exception(response); 324 } 325 state = TRANSACTION; 326 } catch (NoSuchAlgorithmException e) { 327 throw new POP3Exception("Installed JRE doesn't support MD5 - APOP not possible"); 328 } finally { 329 mutex.release(); 330 } 331 } 332 333 345 public void auth(String algorithm, String user, char[] password) throws IOException , POP3Exception, AuthenticationException { 346 347 mutex.lock(); 348 try { 349 AuthenticationMechanism auth = AuthenticationFactory.getInstance().getAuthentication(algorithm); 350 sendCommand("AUTH", new String [] { algorithm } ); 351 352 auth.authenticate(this, user, password); 353 } catch (NoSuchAuthenticationException e) { 354 throw new POP3Exception( e ); 355 } catch (AuthenticationException e) { 356 throw e; 357 } finally { 358 mutex.release(); 359 } 360 361 POP3Response response = readSingleLineResponse(); 362 mutex.release(); 363 364 if (!response.isOK()) { 365 throw new POP3Exception(response); 366 } 367 state = TRANSACTION; 368 } 369 376 private String digestToString(byte[] digest) { 377 StringBuffer sb = new StringBuffer (); 378 for (int i = 0; i < 16; ++i) { 379 if ((digest[i] & 0xFF) < 0x10) { 380 sb.append("0"); 381 } 382 sb.append(Integer.toHexString(digest[i] & 0xFF)); 383 } 384 return sb.toString(); 385 } 386 387 388 396 public void userPass(String usr, char[] pass) throws IOException , POP3Exception { 397 POP3Response response; 398 mutex.lock(); 399 sendCommand("USER", new String [] { usr }); 400 try { 401 response = readSingleLineResponse(); 402 403 if (response.isOK()) { 404 sendCommand("PASS", new String [] { new String (pass) }); 405 406 response = readSingleLineResponse(); 407 } 408 } 409 410 411 finally { 412 mutex.release(); 413 } 414 415 if (!response.isOK()) { 416 throw new POP3Exception(response); 417 } 418 419 state = TRANSACTION; 420 } 421 422 429 public int[] stat() throws IOException , POP3Exception { 430 checkState(TRANSACTION); 431 POP3Response response; 432 433 mutex.lock(); 434 sendCommand("STAT", null); 435 try { 436 response = readSingleLineResponse(); 437 } finally { 438 mutex.release(); 439 } 440 441 if (response.isOK()) { 442 Matcher matcher = statPattern.matcher(response.getMessage()); 443 if( matcher.find() ) { 444 return new int[] { Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(2)) }; 445 } 446 } 447 448 throw new POP3Exception(response); 449 } 450 451 458 public ScanListEntry[] list() throws IOException , POP3Exception { 459 checkState(TRANSACTION); 460 POP3Response response; 461 LinkedList list = new LinkedList (); 462 463 mutex.lock(); 464 sendCommand("LIST", null); 465 try { 466 response = readMultiLineResponse(); 467 } finally { 468 mutex.release(); 469 } 470 471 if( response.isOK() ) { 472 Source source = response.getData(); 473 Matcher matcher = linePattern.matcher(source); 474 475 while( matcher.find()) { 476 try { 477 list.add(ScanListParser.parse(matcher.group(1))); 478 } catch (ParserException e) { 479 LOG.severe(e.getMessage()); 482 } 483 } 484 485 } 486 487 return (ScanListEntry[]) list.toArray(new ScanListEntry[] {}); 488 } 489 490 491 499 public ScanListEntry list(int messageindex) throws IOException , POP3Exception { 500 checkState(TRANSACTION); 501 POP3Response response; 502 503 sendCommand("LIST", new String [] {Integer.toString(messageindex)}); 504 response = readSingleLineResponse(); 505 if( response.isOK() ) { 506 try { 507 return ScanListParser.parse( response.getMessage() ); 508 } catch (ParserException e) { 509 throw new POP3Exception( e.getMessage() ); 510 } 511 } 512 513 throw new POP3Exception(response); 514 } 515 516 527 public InputStream retr(int messageindex) throws IOException , POP3Exception { 528 checkState(TRANSACTION); 529 530 ScanListEntry info = list( messageindex ); 531 532 return retr( messageindex, info.getSize()); 533 } 534 535 536 537 549 public InputStream retr(int messageindex, int size) throws IOException , POP3Exception { 550 checkState(TRANSACTION); 551 POP3Response response; 552 553 mutex.lock(); 554 sendCommand("RETR", new String [] {Integer.toString(messageindex)}); 555 response = readSingleLineResponse(); 556 if( response.isERR() ) { 557 throw new POP3Exception( response ); 558 } 559 560 return in.asyncDownload(size, mutex); 561 } 562 563 564 576 public InputStream sretr(int messageindex, int size) throws IOException , POP3Exception { 577 checkState(TRANSACTION); 578 POP3Response response; 579 580 581 sendCommand("RETR", new String [] {Integer.toString(messageindex)}); 582 response = readSingleLineResponse(); 583 if( response.isERR() ) { 584 throw new POP3Exception( response ); 585 } 586 587 return in.syncDownload(size); } 589 590 591 592 600 public boolean dele(int messageindex) throws IOException , POP3Exception { 601 checkState(TRANSACTION); 602 POP3Response response; 603 604 try { 605 mutex.lock(); 606 607 sendCommand("DELE", new String [] {Integer.toString(messageindex)}); 608 response = readSingleLineResponse(); 609 } finally { 610 mutex.release(); 611 } 612 613 return response.isOK(); 614 } 615 616 622 public void noop() throws IOException , POP3Exception { 623 checkState(TRANSACTION); 624 POP3Response response; 625 626 try { 627 mutex.lock(); 628 629 sendCommand("NOOP", null ); 630 response = readSingleLineResponse(); 631 } finally { 632 mutex.release(); 633 } 634 635 636 if( !response.isOK() ) throw new POP3Exception( response); 637 } 638 639 645 public void rset() throws IOException , POP3Exception { 646 checkState(TRANSACTION); 647 POP3Response response; 648 649 try { 650 mutex.lock(); 651 652 sendCommand("RSET", null ); 653 response = readSingleLineResponse(); 654 } finally { 655 mutex.release(); 656 } 657 658 659 if( !response.isOK() ) throw new POP3Exception( response); 660 } 661 662 668 public void quit() throws IOException , POP3Exception { 669 try { 670 mutex.lock(); 671 sendCommand("QUIT", null ); 672 } finally { 673 mutex.release(); 674 socket.close(); 675 676 in = null; 677 out = null; 678 socket = null; 679 680 state = NOT_CONNECTED; 681 682 } 683 } 684 685 696 public Source top(int messageindex, int numberOfLines) throws IOException , POP3Exception { 697 checkState(TRANSACTION); 698 POP3Response response; 699 try { 700 mutex.lock(); 701 sendCommand("TOP", new String [] {Integer.toString(messageindex), Integer.toString(numberOfLines)}); 702 response = readMultiLineResponse(); 703 704 } finally { 705 mutex.release(); 706 } 707 708 return response.getData(); 709 } 710 711 719 public UidListEntry uidl(int messageindex) throws IOException , POP3Exception { 720 checkState(TRANSACTION); 721 POP3Response response; 722 723 try { 724 mutex.lock(); 725 sendCommand("UIDL", new String [] {Integer.toString(messageindex) } ); 726 response = readSingleLineResponse(); 727 } finally { 728 mutex.release(); 729 } 730 731 732 if( response.isOK() ) { 733 try { 734 return UIDListParser.parse( response.getMessage() ); 735 } catch (ParserException e) { 736 throw new POP3Exception( e.getMessage() ); 737 } 738 } 739 740 throw new POP3Exception(response); 741 } 742 743 750 public UidListEntry[] uidl() throws IOException , POP3Exception { 751 checkState(TRANSACTION); 752 POP3Response response; 753 LinkedList list = new LinkedList (); 754 755 try { 756 mutex.lock(); 757 sendCommand("UIDL", null); 758 response = readMultiLineResponse(); 759 } finally { 760 mutex.release(); 761 } 762 763 if( response.isOK() ) { 764 Source source = response.getData(); 765 Matcher matcher = linePattern.matcher(source); 766 767 while( matcher.find()) { 768 try { 769 list.add(UIDListParser.parse(matcher.group(1))); 770 } catch (ParserException e) { 771 LOG.severe(e.getMessage()); 773 } 774 } 775 776 } else throw new CommandNotSupportedException("UIDL"); 777 778 return (UidListEntry[]) list.toArray(new UidListEntry[] {}); 779 } 780 781 788 public String [] capa() throws IOException , POP3Exception { 789 POP3Response response; 790 LinkedList list = new LinkedList (); 791 792 try { 793 mutex.lock(); 794 sendCommand("CAPA", null); 795 response = readMultiLineResponse(); 796 } finally { 797 mutex.release(); 798 } 799 800 if( response.isOK() ) { 801 Source source = response.getData(); 802 Matcher matcher = linePattern.matcher(source); 803 804 while( matcher.find()) { 805 list.add(matcher.group(2)); 806 } 807 808 } else { 809 throw new CommandNotSupportedException("CAPA"); 810 } 811 812 return (String []) list.toArray(new String [] {}); 813 } 814 815 816 817 820 public void authSend(byte[] call) throws IOException { 821 out.write(Base64.encode(ByteBuffer.wrap(call), false).toString() 822 .getBytes("Us-ASCII")); 823 out.write('\r'); 824 out.write('\n'); 825 out.flush(); 826 } 827 828 831 public byte[] authReceive() throws AuthenticationException, IOException { 832 try { 833 POP3Response response = in.readSingleLineResponse(); 834 if(response.isOK()) { 835 if( response.getMessage() != null) { 836 return Base64.decodeToArray(response.getMessage()); 837 } else { 838 return new byte[0]; 839 } 840 } 841 throw new AuthenticationException(new POP3Exception(response)); 842 } catch (POP3Exception e) { 843 throw new AuthenticationException( e ); 844 } 845 } 846 847 850 public int getState() { 851 return state; 852 } 853 854 private void createStreams() throws IOException { 855 if( RistrettoLogger.logStream != null ) { 856 in = 857 new POP3InputStream( new LogInputStream( 858 socket.getInputStream(),RistrettoLogger.logStream )); 859 out = new LogOutputStream( socket.getOutputStream(),RistrettoLogger.logStream) ; 860 } else { 861 in = 862 new POP3InputStream( 863 socket.getInputStream()); 864 out = socket.getOutputStream(); 865 866 } 867 } 868 871 public String getHostName() { 872 return servername; 873 } 874 875 878 public String getService() { 879 return "pop3"; 880 } 881 882 888 public void dropConnection() throws IOException { 889 if (state != NOT_CONNECTED) { 890 state = NOT_CONNECTED; 891 892 socket.close(); 893 in = null; 894 out = null; 895 socket = null; 896 897 mutex.release(); 898 } 899 } 900 901 } 902 | Popular Tags |