1 package SnowMailClient.MailEngine; 2 3 4 import SnowMailClient.crypto.*; 5 import SnowMailClient.utils.*; 6 import snow.utils.gui.*; 7 import snow.crypto.*; 8 import snow.concurrent.*; 9 import SnowMailClient.model.*; 10 import SnowMailClient.model.accounts.*; 11 import SnowMailClient.Language.Language; 12 13 14 import java.io.*; 15 import java.net.*; 16 import javax.net.ssl.*; 17 18 import java.util.Vector ; 19 import java.util.Date ; 20 import java.util.LinkedHashMap ; 21 import java.util.concurrent.*; 22 23 28 public final class SecurePopConnection 29 { 30 final private MailAccount mailAccount; 31 final private AccountLog accountLog; 32 final private String username; 33 final private String password; 34 35 private Socket connection; 36 private boolean secure; 37 38 private OutputStreamWriter out = null; 39 private BufferedReader in = null; 40 private byte[] pass = null; 41 42 44 private boolean encryptNow = false; 45 46 private boolean adminPrivileges = false; 48 49 private final static boolean debug = false; 50 51 private final LinkedHashMap <String , Integer > messageUIDLs = new LinkedHashMap <String , Integer >(); 54 55 private boolean sslMode = false; 56 57 58 59 65 public SecurePopConnection(MailAccount mailAccount) throws Exception 66 { 67 this.mailAccount = mailAccount; 68 69 this.accountLog = mailAccount.getAccountLog(); 70 this.username = mailAccount.getAccountUserName(); 71 this.password = mailAccount.getAccountPassword(); 72 this.secure = mailAccount.isSnowraverAccount(); this.sslMode = mailAccount.useSSLPOP(); 74 75 76 if(sslMode) 77 { 78 try 79 { 80 accountLog.appendComment("\n" 81 +Language.translate("Opening new connection to SSL POP Server %1 on port %2",mailAccount.getPop(),""+mailAccount.getSSLPopPort())); 82 accountLog.appendComment("=========== "+(new Date ())+" =========="); 83 connection = SSLConnection.createSSLConnection(mailAccount.getPop(), mailAccount.getSSLPopPort()); 84 } 85 catch(Exception e) 86 { 87 accountLog.appendError(Language.translate("Error")+":"+e.getMessage()); 89 throw e; 90 } 91 92 } 93 else 94 { 95 try 97 { 98 accountLog.appendComment("\n" 99 +Language.translate("Opening new connection to POP Server %1 on port %2",mailAccount.getPop(),""+mailAccount.getPopPort())); 100 connection = new Socket(mailAccount.getPop(), mailAccount.getPopPort()); 101 connection.setSoTimeout(1000*60); 102 accountLog.appendComment("=========== "+(new Date ())+" =========="); 103 } 104 catch(Exception e) 105 { 106 accountLog.appendError(Language.translate("Error")+": "+e.getMessage()); 107 throw new Exception (Language.translate("Cannot connect to %",mailAccount.getPop())); 108 } 109 } 110 111 out = new OutputStreamWriter(connection.getOutputStream()); in = new BufferedReader( new InputStreamReader(connection.getInputStream())); 114 String line = readOneLineFromServer(); 116 if(!line.toLowerCase().startsWith("+ok")) 117 throw new Exception (Language.translate("Bad POP3 Greetings, awaiting +OK*, reveived")+" "+line); 118 119 String timestamp = null; 121 int pos = line.indexOf("<"); 122 if(pos !=- 1) 123 { 124 timestamp = line.substring(pos).trim(); 125 if(secure) 127 { 128 if(timestamp.length()<3) 129 throw new Exception ("Too short timestamp, security leak because replay attack possible ? "+timestamp); 130 } 131 } 132 else 133 { 134 if(secure) 135 { 136 throw new Exception ("No timestamp in the greeting, secure mode not possible..., \nwaiting +OK* <xxxx.xxxx>, received "+line); 137 } 138 } 139 140 if(secure) 141 { 142 String secPass = Utilities.hashPassword(timestamp+Utilities.hashPassword(password)); 143 String secUser = Utilities.hashPassword(username); 144 145 sendOneLineToServer("SPAS "+secUser+" "+secPass); 146 line = readOneLineFromServer(); 147 if(!line.toLowerCase().startsWith("+ok")) 148 { 149 throw new BadPasswordException("Secure SPAS failled,\n"+line); 150 } 151 152 adminPrivileges = line.endsWith("admin"); 153 154 byte[] hash = Utilities.hashPassword(password).getBytes(); 157 pass = new byte[16]; 158 System.arraycopy(hash, 0, pass, 0,16); 159 encryptNow = true; 160 } 161 else 162 { 163 logUsingAuthentication(); 165 } 166 167 retrieveMessagesUIDL(); 168 } 169 170 171 173 public String getConnectionStatusExplanation() 174 { 175 StringBuffer sb = new StringBuffer (); 176 sb.append(Language.translate("POP Connection status")+":"); 177 sb.append("\n Connected to " +connection.getInetAddress() 178 +", port "+connection.getPort()+", local port="+connection.getLocalPort()); 179 if(this.sslMode) 180 { 181 sb.append("\n +++ SSL mode is active."); 182 if(this.connection instanceof SSLSocket) 183 { 184 SSLSocket sconnection = (SSLSocket) connection; 185 SSLSession sSLSession = sconnection.getSession(); 186 if(sSLSession!=null) 187 { 188 String cipherSuite = sSLSession.getCipherSuite(); 189 String proto = sSLSession.getProtocol(); 190 sb.append("\n Protocol="+proto+", Cipher suite="+cipherSuite); 191 } 192 } 193 else 194 { 195 sb.append("\nERROR: socket is not instance of SSLSocket"); 196 } 197 } 198 else 199 { 200 sb.append("\n --- SSL mode is NOT active."); 201 } 202 203 if(encryptNow) 204 { 205 sb.append("\n +++ Snow encryption is active."); 206 } 207 else 208 { 209 sb.append("\n --- Snow encryption not active."); 210 } 211 212 return sb.toString(); 213 } 214 215 216 final private Vector <String > authenticationTypes = new Vector <String >(); 217 218 private synchronized void logUsingAuthentication() throws Exception 219 { 220 sendOneLineToServer("AUTH"); 221 String line = readOneLineFromServer(); 222 if(!line.toLowerCase().startsWith("+ok")) 223 { 224 accountLog.appendError("Cannot authenticate using AUTH, try old unsecure method"); 225 logUsingUserAndPass(); 226 return; 229 } 230 line = readOneLineFromServer().trim(); 232 boolean cancrammd5 = false; 233 while(!line.equals(".")) 234 { 235 if( line.toLowerCase().indexOf("cram-md5")!=-1) cancrammd5 = true; 236 authenticationTypes.addElement(line); 237 line = readOneLineFromServer().trim(); 238 } 239 240 if(cancrammd5) 241 { 242 sendOneLineToServer("AUTH CRAM-MD5"); 243 line = readOneLineFromServer(); 244 if(!line.toLowerCase().startsWith("+ ")) 245 { 246 throw new Exception ("Sent >AUTH CRAM-MD5, awaiting '+ code'\nreceived: "+line); 247 } 248 String stamp = new String (Utilities.decodeBase64(line.substring(2).trim())); 249 String rep = Utilities.HMAC_KEYED_CRAM_MD5(password.getBytes(), stamp.getBytes(),username); 250 sendOneLineToServer(rep); 251 line = readOneLineFromServer(); 252 if(!line.toLowerCase().startsWith("+ok")) 253 { 254 throw new Exception ("AUTH failed, received: "+line); 255 } 256 } 258 else 259 { 260 logUsingUserAndPass(); 261 } 262 } 263 264 266 private synchronized void logUsingUserAndPass() throws Exception 267 { 268 269 sendOneLineToServer("USER "+username); 270 String line = readOneLineFromServer(); 271 if(!line.toLowerCase().startsWith("+ok")) throw new Exception ("USER error : "+line); 272 273 if(!this.mailAccount.getAllowUnsecurePasswordProtocols()) 274 { 275 throw new Exception ( 276 Language.translate("Stopped authentication process for %\r\nbecause the unsecure protocol USER / PASS was requested.", 277 this.mailAccount.getAddress())); 278 } 279 280 sendOneLineToServer("PASS "+password); 281 283 line = readOneLineFromServer(); 284 if(!line.toLowerCase().startsWith("+ok")) throw new BadPasswordException("PASS error : "+line); 285 } 286 287 288 289 292 public boolean hasAdminPrivileges() {return adminPrivileges;} 293 294 295 297 public synchronized void publishPublicKey(String address, String publickey) throws Exception 298 { 299 sendOneLineToServer("CHPK "+address+" "+ publickey); 300 String line = readOneLineFromServer(); 301 if(!line.toLowerCase().startsWith("+ok")) throw new Exception ("CHPK error : "+line); 302 } 303 304 305 307 public synchronized void changePassword(String newHash) throws Exception 308 { 309 sendOneLineToServer("CPAS "+ newHash); 310 String line = readOneLineFromServer(); 311 if(!line.toLowerCase().startsWith("+ok")) throw new Exception ("CPAS error : "+line); 312 } 313 314 315 317 public synchronized void addNewUser(String name, String hash) throws Exception 318 { 319 if(!adminPrivileges) throw new Exception ( 320 Language.translate("Requires administrator privileges")); 321 sendOneLineToServer("ADUR "+ name+" "+hash); 322 String line = readOneLineFromServer(); 323 if(!line.toLowerCase().startsWith("+ok")) throw new Exception ("ADUR error : "+line); 324 } 325 326 328 public synchronized void setUserPassword(String name, String hash) throws Exception 329 { 330 if(!adminPrivileges) throw new Exception (Language.translate("Requires administrator privileges")); 331 sendOneLineToServer("SUPA "+ name+" "+hash); 332 String line = readOneLineFromServer(); 333 if(!line.toLowerCase().startsWith("+ok")) throw new Exception ("SUPA error : "+line); 334 } 335 336 338 public synchronized void addNewForbiddenFrom(String forbidden) throws Exception 339 { 340 sendOneLineToServer("ADFO "+ forbidden); 341 String line = readOneLineFromServer(); 342 if(!line.toLowerCase().startsWith("+ok")) throw new Exception ("ADFO error : "+line); 343 } 344 345 347 public synchronized void removeForbiddenFrom(String forbidden) throws Exception 348 { 349 sendOneLineToServer("REFO "+ forbidden); 350 String line = readOneLineFromServer(); 351 if(!line.toLowerCase().startsWith("+ok")) throw new Exception ("REFO error : "+line); 352 } 353 354 355 public synchronized Vector getForbiddenList() throws Exception 356 { 357 sendOneLineToServer("GEFO"); 358 String line = readOneLineFromServer(); 359 if(!line.toLowerCase().startsWith("+ok")) throw new Exception ("GEFO error : "+line); 360 Vector <String > addresses = new Vector <String >(); 361 while(true) 362 { 363 line = readOneLineFromServer(); 364 if(line==null || line.equals(".")) break; 365 addresses.addElement(line); 366 } 367 return addresses; 368 } 369 370 371 373 public synchronized void getServerLogFile(AccountLog log) throws Exception 374 { 375 if(!adminPrivileges) throw new Exception (Language.translate("Requires administrator privileges")); 376 sendOneLineToServer("GELO") ; 377 String line = readOneLineFromServer(); 378 if(!line.toLowerCase().startsWith("+ok")) throw new Exception ("GELO error : "+line); 379 380 while(true) 381 { 382 line = readOneLineFromServer(); 383 if( line==null || line.equals(".")) break; 384 log.appendLine(line); 385 } 386 } 387 388 389 392 public synchronized String [] getMessagesLIST_() throws Exception 393 { 394 sendOneLineToServer("LIST"); 395 String line = readOneLineFromServer(); 396 if(!line.toLowerCase().startsWith("+ok")) throw new Exception ("STAT error : "+line); 397 398 Vector <String > ident = new Vector <String >(); 399 while(true) 400 { 401 line = readOneLineFromServer(); 402 if(line==null || line.equals(".")) break; 403 ident.addElement(line); 404 } 405 return ident.toArray(new String [ident.size()]); 406 } 407 408 409 411 public synchronized int[] getSizes_from_MessagesLIST(String [] list) throws Exception 412 { 413 int[] sizes = new int[list.length]; 414 for(int i=0; i<list.length; i++) 415 { 416 String nn = list[i].trim(); 417 int pos = nn.lastIndexOf(" "); 418 try 419 { 420 nn = nn.substring(pos+1); 421 sizes[i] = Integer.parseInt(nn); 422 } 423 catch(NumberFormatException e) 424 { 425 System.out.println("Cannot parse a number from "+nn); 426 } 427 } 428 return sizes; 429 } 430 431 433 public synchronized String [] getMessagesUIDLs() throws Exception 434 { 435 String [] uidls = new String [messageUIDLs.size()]; 436 return messageUIDLs.keySet().toArray(uidls); 437 } 438 439 440 446 private synchronized void retrieveMessagesUIDL() throws Exception 447 { 448 messageUIDLs.clear(); 449 450 sendOneLineToServer("UIDL"); 451 String line = readOneLineFromServer(); 452 if(!line.toLowerCase().startsWith("+ok")) throw new Exception ("UIDL error : "+line); 453 454 while(true) 455 { 456 line = readOneLineFromServer(); 457 if(line==null || line.equals(".")) break; 458 int pos = line.indexOf(" "); 459 if(pos==-1) throw new Exception ("Bad UIDL line : "+line); 460 String number = line.substring(0,pos); 461 String ident = line.substring(pos+1); 462 messageUIDLs.put(ident, new Integer (number)); 463 } 464 465 } 467 468 469 471 public synchronized int[] getNumberOfMessages() throws Exception 472 { 473 sendOneLineToServer("STAT"); 474 String line = readOneLineFromServer(); 475 if(!line.toLowerCase().startsWith("+ok")) throw new Exception ("STAT error : "+line); 476 477 String nStr = line.substring(4); int numberOfMessages = 0; 480 int pos = nStr.indexOf(" "); 481 int size = 0; 482 if(pos!=-1) 483 { 484 try 485 { 486 numberOfMessages = Integer.parseInt( nStr.substring(0,pos) ); 487 } 488 catch(NumberFormatException e) 489 { 490 throw new Exception ("ERROR Cannot read the number of messages in '"+nStr.substring(0,pos)+"'"); 491 } 492 493 try 494 { 495 size = Integer.parseInt(nStr.substring(pos+1) ); 496 } 497 catch(NumberFormatException e) 498 { 499 size = numberOfMessages*2000; 501 } 502 } 503 return new int[]{numberOfMessages, size}; 504 } 505 506 507 public synchronized void deleteMessage(String uidl) throws Exception 508 { 509 deleteMessage(getMessageNumberFromUIDL(uidl)); 510 this.messageUIDLs.remove(uidl); 512 } 513 514 516 private synchronized void deleteMessage(int number) throws Exception 517 { 518 sendOneLineToServer( "DELE "+number ); 519 String line = readOneLineFromServer(); 520 if(!line.toLowerCase().startsWith("+ok")) throw new Exception ("DELE error : "+line); 521 } 522 523 524 public synchronized void sendFile(byte[] bytes, String name) throws Exception 525 { 526 sendOneLineToServer( "SEFI "+name); 527 String line = readOneLineFromServer(); 528 if(!line.toLowerCase().startsWith("+ok")) throw new Exception ("SEFI error : "+line); 529 530 OutputStream os = connection.getOutputStream(); 531 DataOutputStream dos = new DataOutputStream(os); 532 dos.writeInt(bytes.length); 533 534 ByteArrayInputStream bis = new ByteArrayInputStream(bytes); 535 536 byte[] buf = new byte[256]; 537 int read = 0; 538 while((read=bis.read(buf))!=-1) 539 { 540 dos.write(buf,0,read); 541 } 542 dos.flush(); 543 } 544 545 546 public synchronized void deleteFile(String name) throws Exception 547 { 548 sendOneLineToServer( "DEFI "+name); 549 String line = readOneLineFromServer(); 550 if(!line.toLowerCase().startsWith("+ok")) throw new Exception ("DEFI error : "+line); 551 } 552 553 554 public synchronized boolean hasFile(String name) throws Exception 555 { 556 sendOneLineToServer( "HAFI "+name); 557 String line = readOneLineFromServer(); 558 if(!line.toLowerCase().startsWith("+ok")) throw new Exception ("HAFI error : "+line); 559 String rep = line.substring(3).trim().toLowerCase(); 560 if(rep.equals("yes")) return true; 561 return false; 562 } 563 564 565 public synchronized byte[] getFile(String name) throws Exception 566 { 567 sendOneLineToServer( "GEFI "+name); 568 String line = readOneLineFromServer(); 569 if(!line.toLowerCase().startsWith("+ok")) throw new Exception ("GEFI error : "+line); 570 571 InputStream is = connection.getInputStream(); 572 DataInputStream dis = new DataInputStream(is); 573 int size = dis.readInt(); 574 575 byte[] bytes = new byte[size]; 576 dis.readFully(bytes); 577 return bytes; 578 } 579 580 581 582 584 private synchronized int getMessageNumberFromUIDL(String uidl) throws Exception 585 { 586 if(!messageUIDLs.containsKey(uidl)) 587 { 588 throw new Exception ("Message uidl='"+uidl+"' not found on server"); 589 } 591 return messageUIDLs.get(uidl); 592 } 593 594 595 598 public synchronized String getMessageTop(String ident, int len) throws Exception 599 { 600 return getMessageTop( 601 getMessageNumberFromUIDL(ident), len); 602 } 603 604 607 private synchronized String getMessageTop(int number, int len) throws Exception 608 { 609 StringBuffer message = new StringBuffer (); 610 sendOneLineToServer( "TOP "+number+" "+len ); 611 String line = readOneLineFromServer(); 612 if(!line.toLowerCase().startsWith("+ok")) 613 { 614 throw new Exception ("TOP error : "+line); 615 } 616 617 while(true) 618 { 619 line = readOneLineFromServer(); 620 if(line==null || line.equals(".")) break; 621 message.append(line); 622 message.append("\r\n"); 623 } 624 return message.toString().trim(); 625 } 626 627 629 public synchronized String getMessage(String UIDL, Interrupter interrupter, Counter counter) throws Exception 630 { 631 return getMessage(getMessageNumberFromUIDL(UIDL), interrupter, counter); 632 } 633 634 636 private synchronized String getMessage(int number, final Interrupter interrupter, Counter counter) throws Exception 637 { 638 StringBuffer message = new StringBuffer (); 639 sendOneLineToServer( "RETR " + number ); 640 641 String line = readOneLineFromServer(); 642 if(!line.toLowerCase().startsWith("+ok")) throw new Exception ("RETR error : "+line); 643 644 int readTotal = 0; 645 long lastTime = 0; 646 int incr = 0; 647 648 while(true) 649 { 650 if(interrupter!=null && interrupter.shouldStopEvaluation()) 651 { 652 try 655 { 656 in.close(); 657 } catch(Exception e) {} 658 throw new ManualInterruptedException(Language.translate("The message reception was cancelled by the user")); 659 } 660 661 line = readOneLineFromServer(); 662 if(line==null || line.equals(".")) break; 663 message.append(line); 664 message.append("\r\n"); 665 readTotal += line.length(); 666 incr += line.length(); 667 668 long time = System.currentTimeMillis(); 669 if(counter!=null && (time-lastTime) > 200) 670 { 671 lastTime = time; 672 counter.increment(incr); 673 incr=0; 674 } 675 } 676 677 if(counter!=null) 678 { 679 counter.increment(incr); 680 } 681 682 return message.toString(); 683 } 684 685 686 690 public synchronized void terminateSession() throws Exception 691 { 692 695 sendOneLineToServer("QUIT"); 696 String dummyIgnoredLine = readOneLineFromServer(); 698 messageUIDLs.clear(); 700 701 try 702 { 703 this.connection.close(); 704 } 705 catch(Exception e) 706 { 707 e.printStackTrace(); 708 } 709 } 710 711 712 715 public synchronized boolean isAlive() 716 { 717 try 718 { 719 sendOneLineToServer( "NOOP" ); 720 String line = readOneLineFromServer(); 721 if(!line.toLowerCase().startsWith("+ok")) return false; 722 } 723 catch(Exception e) 724 { 725 return false; 726 } 727 return true; 728 } 729 730 731 734 private synchronized void sendOneLineToServer(String _line) throws Exception 735 { 736 String line = _line; 737 738 accountLog.appendSendLine(line, encryptNow); 739 740 if(encryptNow) 741 { 742 line = Utilities.encryptSingleLineBlowfishFormat64(_line, pass); 743 } 744 out.write(line+"\r\n"); 745 out.flush(); 746 } 747 748 749 750 753 private synchronized String readOneLineFromServer() throws Exception 754 { 755 String line = in.readLine(); 756 if (line==null) return null; 758 if(encryptNow) 759 { 760 if(line.length() ==0) throw new Exception ("Zero length line received"); 761 line = Utilities.decryptSingleLineBlowfishFormat64(line, pass); 762 } 763 764 accountLog.appendReadLine(line, encryptNow); 765 return line; 766 } 767 768 769 } | Popular Tags |