1 package SnowMailClient.MailEngine; 2 3 4 import SnowMailClient.crypto.*; 5 import SnowMailClient.gnupg.GnuPGLink; 6 import SnowMailClient.gnupg.Main.GnuPGCommands; 7 import SnowMailClient.gnupg.model.*; 8 9 import SnowMailClient.utils.*; 10 import SnowMailClient.model.*; 11 import snow.utils.storage.*; 12 import snow.crypto.*; 13 import SnowMailClient.model.accounts.*; 14 import SnowMailClient.SnowMailClientApp; 15 import SnowMailClient.Language.Language; 16 import snow.utils.gui.*; 17 import snow.concurrent.*; 18 19 import java.io.*; 20 import java.net.*; 21 import javax.net.ssl.*; 22 23 import java.math.*; 24 import javax.swing.*; 25 import java.util.*; 26 import java.awt.EventQueue ; 27 28 31 public final class SecureSmtpConnection 32 { 33 final private MailAccount mailAccount; 34 final private AccountLog accountLog; 35 final private String username; 36 final private String password; 37 38 private Socket connection; 39 private String domainName; 40 41 private boolean snowAccount; private boolean sslMode; 43 44 private OutputStreamWriter out = null; 46 47 private BufferedReader in = null; 48 private byte[] key = null; 50 private boolean encrypt = false; 51 52 private final AppProperties ehloContent = new AppProperties(); 54 55 56 private final static boolean debug = false; 57 58 private boolean shouldUseTunellingOnPOP = true; 60 private boolean tunellingOnPOPActive = false; 61 62 boolean forceAUTH = false; 63 final private Vector<String > ehloPreamble = new Vector<String >(); 64 65 69 public SecureSmtpConnection(MailAccount mailAccount, 70 String domainName, 71 boolean forceAUTH 72 ) throws Exception 73 { 74 this.mailAccount = mailAccount; 75 this.accountLog = mailAccount.getAccountLog(); 76 this.username = mailAccount.getAccountUserName(); 77 this.password = mailAccount.getAccountPassword(); 78 this.snowAccount = mailAccount.isSnowraverAccount(); 79 this.domainName = domainName; 80 this.sslMode = mailAccount.useSSLSMTP(); 81 this.forceAUTH = forceAUTH; 82 83 if(!snowAccount || !shouldUseTunellingOnPOP) 84 { 85 if(sslMode) 87 { 88 accountLog.appendComment("\nOpening new connection to SSL SMTP Server "+mailAccount.getSMTP()+" on port "+mailAccount.getSSLSMTPPort()); 89 try 90 { 91 connection = SSLConnection.createSSLConnection(mailAccount.getSMTP(), mailAccount.getSSLSMTPPort()); 92 accountLog.appendComment("=========== "+(new Date())+" =========="); 93 } 94 catch(Exception e) 95 { 96 accountLog.appendError("SMTP SSL connection Error:"+e.getMessage()); 97 throw e; 98 } 99 } 100 else 101 { 102 accountLog.appendComment("\nOpening new connection to SMTP Server "+mailAccount.getSMTP()+" on port "+mailAccount.getSMTPPort()); 103 try 104 { 105 connection = new Socket(mailAccount.getSMTP(), mailAccount.getSMTPPort()); 106 connection.setSoTimeout(1000*60); 107 accountLog.appendComment("=========== "+(new Date())+" =========="); 108 } 109 catch(Exception e) 110 { 111 accountLog.appendError("SMTP connection Error:"+e.getMessage()); 112 throw new Exception ("Cannot connect to "+mailAccount.getSMTP()); 113 } 114 } 115 116 out = new OutputStreamWriter(connection.getOutputStream()); in = new BufferedReader( new InputStreamReader(connection.getInputStream())); 120 } 121 else 122 { 123 if(mailAccount.useSSLPOP()) 126 { 127 try 128 { 129 accountLog.appendComment("\nOpening new connection to SSL SMTP Server "+mailAccount.getSMTP()+" on the POP port "+mailAccount.getSSLPopPort()); 130 connection = SSLConnection.createSSLConnection(mailAccount.getSMTP(), mailAccount.getSSLPopPort()); 131 } 132 catch(Exception e) 133 { 134 accountLog.appendError("Error:"+e.getMessage()); 135 throw e; 136 } 137 sslMode = true; 138 } 139 else 140 { 141 try 142 { 143 accountLog.appendComment("\nOpening new connection to SMTP Server "+mailAccount.getSMTP()+" on the POP port "+mailAccount.getPopPort()); 144 connection = new Socket(mailAccount.getSMTP(), mailAccount.getPopPort()); 145 } 146 catch(Exception e) 147 { 148 accountLog.appendError("Error:"+e.getMessage()); 149 throw e; 150 } 151 } 152 try 153 { 154 out = new OutputStreamWriter(connection.getOutputStream()); in = new BufferedReader( new InputStreamReader(connection.getInputStream())); String line = in.readLine(); 158 if(!line.toLowerCase().startsWith("+ok")) 159 { 160 throw new Exception ("Cannot connect to "+mailAccount.getSMTP() 161 +" for pop tunnelling, waiting +ok, received="+line); 162 } 163 accountLog.appendComment("=========== "+(new Date())+" =========="); 164 } 165 catch(Exception e) 166 { 167 accountLog.appendError("Error:"+e.getMessage()); 168 throw new Exception ("Cannot connect to "+mailAccount.getSMTP()); 169 } 170 171 out.write("SMTP\r\n"); 173 out.flush(); 175 String line = readOneLineFromServer(); 177 if(!line.toLowerCase().startsWith("+ok")) 178 { 179 String err = "Cannot initiate SMTP on POP, waiting +ok, received "+line; 180 accountLog.appendError(err); 181 throw new Exception (err); 182 } 183 184 tunellingOnPOPActive = true; 185 186 } 187 188 connect(); 189 } 190 191 192 194 public final String getConnectionStatusExplanation() 195 { 196 StringBuffer sb = new StringBuffer (); 197 sb.append("SMTP Connection status:"); 198 if(tunellingOnPOPActive) 199 { 200 sb.append("\n +++ SMTP is tunnelled through the POP server."); 201 } 202 sb.append("\n Connected to " +connection.getInetAddress()+", port "+connection.getPort()+", local port="+connection.getLocalPort()); 203 if(this.sslMode) 204 { 205 sb.append("\n +++ SSL mode is active."); 206 } 207 else 208 { 209 sb.append("\n --- SSL mode is NOT active."); 210 } 211 212 if(encrypt) 213 { 214 sb.append("\n +++ Snow encryption is active."); 215 } 216 else 217 { 218 sb.append("\n --- Snow encryption not active."); 219 } 220 221 return sb.toString(); 222 } 223 224 225 227 private void connect() throws Exception 228 { 229 String line = readOneLineFromServer(); 231 if(line==null || !line.toLowerCase().startsWith("220")) 232 { 233 throw new Exception ("Bad Greetings, awaiting 220 xxx, reveived "+line); 234 } 235 236 String gline = line; 238 while(gline!=null && gline.startsWith("220-")) 239 { 240 gline = readOneLineFromServer(); 241 } 242 243 if(snowAccount) 244 { 245 if(line.toLowerCase().indexOf("snowraver.org")==-1) 247 { 248 int rep = JOptionPane.showConfirmDialog(null, 250 "The server is not Snowraver encryption compatible\n greetings="+line 251 +"\nDo you want to send your message through this server, WITHOUT encryption ?"); 252 if(rep != JOptionPane.YES_OPTION ) 253 { 254 throw new Exception ("Send aborted"); 255 } 256 simpleEHLOConnect(); 258 } 259 else 260 { 261 String timestamp = null; 263 int pos = line.indexOf("<"); 264 if(pos !=- 1) 265 { 266 timestamp = line.substring(pos).trim(); 267 if(timestamp.length()<3) 268 { 269 throw new Exception ("Too short timestamp, security leak because replay attack possible ? "+timestamp); 270 } 271 } 272 else 273 { 274 throw new Exception ("No timestamp in the greeting, secure mode not possible..., \nwaiting +OK* <xxxx.xxxx>, received "+line); 275 } 276 277 String secPass = Utilities.hashPassword( timestamp+Utilities.hashPassword(password)); 278 String secUser = Utilities.hashPassword( username); 279 280 sendOneLineToServer("SPAS "+secUser+" "+secPass); 281 line = readOneLineFromServer(); 282 if(!line.toLowerCase().startsWith("250")) 283 { 284 throw new BadPasswordException("Secure SPAS failled "+line); 285 } 286 byte[] hash = Utilities.hashPassword(password).getBytes(); 288 key = new byte[16]; 289 System.arraycopy(hash, 0, key, 0, 16); 290 291 encrypt = true; 292 } 293 } 294 else { 296 simpleEHLOConnect(); 297 } 298 } 299 300 301 302 303 304 308 private void simpleEHLOConnect() throws Exception 309 { 310 sendOneLineToServer("EHLO "+domainName); 312 while(true) 313 { 314 String line = readOneLineFromServer(); 315 if(!line.toLowerCase().startsWith("250")) 316 { 317 oldEasyHELOConnect(); 318 return; 319 } 320 String cont = line.substring(3).trim(); 321 322 if(!cont.startsWith("-")) 323 { 324 ehloPreamble.addElement(cont); 326 break; 327 } 328 else 329 { 330 ehloPreamble.addElement(cont.substring(1)); 331 } 332 } 333 334 343 344 if(forceAUTH || canAuthenticateCRAMMD5() || canAuthenticateLogin()) authenticate(); 346 } 347 348 public final boolean canAuthenticateCRAMMD5() 349 { 350 for(String eli : ehloPreamble) 351 { 352 if(eli.toLowerCase().indexOf("cram-md5")!=-1) return true; 353 } 354 return false; 355 } 356 357 public final boolean canAuthenticateLogin() 360 { 361 for(String eli : ehloPreamble) 362 { 363 if(eli.toLowerCase().indexOf("login")!=-1) return true; 364 } 365 return false; 366 } 367 368 private void authenticate() throws Exception 369 { 370 if(this.canAuthenticateCRAMMD5()) 371 { 372 sendOneLineToServer("AUTH CRAM-MD5"); 373 String rep1 = readOneLineFromServer(); 374 if(!rep1.startsWith("334")) throw new Exception ("I send AUTH CRAM-MD5 and wait 334 <hash>, but I received: "+rep1); 375 String stamp = new String (Utilities.decodeBase64(rep1.substring(4))); 376 String hmac = Utilities.HMAC_KEYED_CRAM_MD5(password.getBytes(), stamp.getBytes(), username); 378 sendOneLineToServer(hmac); 379 String rep2 = readOneLineFromServer(); 380 if(!rep2.startsWith("235")) throw new Exception ("Bad authentication: "+rep2); 381 382 return; 384 } 385 386 if(this.canAuthenticateLogin()) 388 { 389 sendOneLineToServer("AUTH LOGIN"); 390 String rep1 = readOneLineFromServer(); 391 if(!rep1.startsWith("334")) throw new Exception ("I send AUTH CRAM-MD5 and wait 334 <hash>, but I received: "+rep1); 392 sendOneLineToServer(""+Utilities.encodeBase64(this.username.getBytes())); 393 String rep2 = readOneLineFromServer(); 394 if(!rep2.startsWith("334")) throw new Exception ("Bad authentication username: "+rep2); 395 396 if(!this.mailAccount.getAllowUnsecurePasswordProtocols()) 397 { 398 throw new Exception (Language.translate("Stopped authentication because the unsecure protocol USER / PASS was requested.")); 399 } 400 401 sendOneLineToServer(""+Utilities.encodeBase64(this.password.getBytes())); 402 String rep3 = readOneLineFromServer(); 403 if(!rep3.startsWith("235")) throw new Exception ("Bad authentication password: "+rep3); 404 405 return; 407 } 408 } 409 410 public static void main(String [] a) 411 { 412 System.out.println(Utilities.encodeBase64("hans".getBytes())); 413 } 414 415 private void oldEasyHELOConnect() throws Exception 416 { 417 sendOneLineToServer("HELO "+domainName); 418 String line = readOneLineFromServer(); 419 if(!line.toLowerCase().startsWith("250")) throw new Exception ("Bad HELO "+line); 420 } 421 422 423 427 428 431 public void sendMessage(final MailMessage message, Interrupter interrupter, Counter counter) throws Exception 432 { 433 int bytesSended = 0; 434 435 String from = message.getFromAddress().getMailAddress(); 436 if(from.equals("?")) throw new Exception ("\"From:\" field is not correct"); 437 438 sendOneLineToServer("MAIL FROM:<"+MailMessageUtils.grabAddress(from)+">"); 439 441 442 String line = readOneLineFromServer(); 443 if(!line.toLowerCase().startsWith("250")) 444 { 445 throw new Exception ("MAIL FROM error :"+line); 446 } 447 448 Vector<Address> tos = message.getToAddresses(); 449 if(tos.size()==0) throw new Exception ("\"To:\" field is empty ??"); 450 451 for(Address a: tos) 452 { 453 String to = a.getMailAddress(); 454 sendOneLineToServer("RCPT TO:<"+to+">"); 455 line = readOneLineFromServer(); 456 if(!line.toLowerCase().startsWith("250")) throw new Exception ("RCPT TO error : "+line); 457 } 458 459 sendOneLineToServer("DATA"); 461 line = readOneLineFromServer(); 462 if(!line.toLowerCase().startsWith("354")) throw new Exception ("DATA error : "+line); 463 465 message.setActualDate(); 469 470 471 byte[] completeMessageContentBytes = null; 472 if(message.getMustBeSigned()) 473 { 474 GnuPGLink gpg = SnowMailClientApp.getInstance().getGnuPGLink(); 475 GnuPGKeyID[] kids = gpg.getSecretKeyIDForAddress(from); 476 if(kids.length==0) 477 { 478 throw new Exception (Language.translate("No key to sign with %", from)); 479 } 480 481 byte[] pass = gpg.getPasswordForKeyAskIfNotFoundOrNotValid(kids[0], true, SnowMailClientApp.getInstance()); 482 if(pass==null) 483 { 484 throw new Exception (Language.translate("No password given to sign with %", from)); 485 } 486 487 completeMessageContentBytes = message.getCompleteContentToSend( 488 SnowMailClientApp.getInstance().getGnuPGLink(), 489 kids[0], pass, 490 interrupter); 491 } 492 else 493 { 494 completeMessageContentBytes = message.getCompleteContentToSend( 496 SnowMailClientApp.getInstance().getGnuPGLink(), 497 null, null, 498 interrupter); 499 } 500 501 String completeMailContent = new String (completeMessageContentBytes); 502 503 if(message.getMustBeEncrypted()) 504 { 505 507 GnuPGLink gpg = SnowMailClientApp.getInstance().getGnuPGLink(); 508 String toa = tos.firstElement().getMailAddress(); 509 GnuPGKeyID kid = gpg.getPublicKeyIDForAddress(toa)[0]; 510 511 512 completeMailContent = message.getHeader().to_ASCII_String() + "\r\n" 514 + GnuPGCommands.encryptBufferForRecipient(gpg.getPathToGPG(), 515 completeMailContent, kid, true, interrupter); 516 517 message.setHasBeenEncrypted(true); 518 } 519 520 BufferedReader reader = new BufferedReader(new StringReader(completeMailContent)); line = reader.readLine(); 523 long lastProgressUpdate = 0; 524 int incr =0; 525 526 while(line!=null) 527 { 528 long now = System.currentTimeMillis(); 529 if(counter!=null && (now-lastProgressUpdate)>200) 530 { 531 lastProgressUpdate = now; 532 counter.increment(incr); 533 incr=0; 534 } 535 536 if(line.startsWith(".")) line = "."+line; 540 541 bytesSended += line.length(); 542 incr += line.length(); 543 544 sendOneLineToServer(line); 545 546 line = reader.readLine(); 547 } 548 549 sendOneLineToServer("."); line = readOneLineFromServer(); 551 if(!line.toLowerCase().startsWith("250")) throw new Exception ("DATA error :"+line); 552 553 sendOneLineToServer("RSET"); 554 line = readOneLineFromServer(); 555 if(!line.toLowerCase().startsWith("250")) throw new Exception ("RSET error :"+line); 556 557 message.setHasBeenSent(true); 559 560 if(counter!=null) 561 { 562 counter.increment(incr); 563 } 564 } 565 566 567 569 public BigInteger[] getPublicKey(String address) throws Exception 570 { 571 sendOneLineToServer("GEPK "+address); 572 String line = readOneLineFromServer(); 573 if(!line.toLowerCase().startsWith("250")) throw new Exception ("Get Public Key error :"+line); 574 line = line.substring(6).trim(); 575 int pos = line.indexOf(' '); 576 if(pos==-1) throw new Exception ("Bad public key format"); 577 String n = line.substring(0, pos); 578 String e = line.substring(pos+1); 579 return new BigInteger[]{ 581 new BigInteger(n), 582 new BigInteger(e)}; 583 } 584 585 586 588 public void terminateSession() throws Exception 589 { 590 sendOneLineToServer("QUIT"); 591 connection.close(); 592 } 593 594 595 598 public boolean isAlive() 599 { 600 try 601 { 602 sendOneLineToServer("NOOP"); 603 String line = readOneLineFromServer(); 604 if(!line.toLowerCase().startsWith("250")) return false; 605 } 606 catch(Exception e) 607 { 608 return false; 609 } 610 return true; 611 } 612 613 614 617 private void sendOneLineToServer(String _line) throws Exception 618 { 619 String line = _line; 620 accountLog.appendSendLine(line, encrypt); 621 622 if(encrypt) 623 { 624 line = Utilities.encryptSingleLineBlowfishFormat64(_line, key); 625 } 626 out.write(line); 627 out.write("\r\n"); 628 out.flush(); 629 } 630 631 634 private String readOneLineFromServer() throws Exception 635 { 636 String line = in.readLine(); 637 if (line==null) return null; 639 accountLog.appendReadLine(line, encrypt); 640 641 if(encrypt) 642 { 643 return Utilities.decryptSingleLineBlowfishFormat64(line, key); 644 } 645 return line; 646 } 647 648 649 } | Popular Tags |