1/* 2 * Copyright 2007-2011, Haiku, Inc. All rights reserved. 3 * Copyright 2001-2002 Dr. Zoidberg Enterprises. All rights reserved. 4 * Copyright 2011, Clemens Zeidler <haiku@clemens-zeidler.de> 5 * 6 * Distributed under the terms of the MIT License. 7 */ 8 9 10//! Implementation of the SMTP protocol 11 12 13#include "smtp.h" 14 15#include <map> 16 17#include <arpa/inet.h> 18#include <ctype.h> 19#include <errno.h> 20#include <netdb.h> 21#include <stdio.h> 22#include <stdlib.h> 23#include <sys/select.h> 24#include <sys/socket.h> 25#include <sys/time.h> 26#include <unistd.h> 27 28#include <Alert.h> 29#include <Catalog.h> 30#include <DataIO.h> 31#include <Entry.h> 32#include <File.h> 33#include <MenuField.h> 34#include <Message.h> 35#include <Path.h> 36#include <TextControl.h> 37 38#include <crypt.h> 39#include <mail_encoding.h> 40#include <MailSettings.h> 41#include <NodeMessage.h> 42#include <ProtocolConfigView.h> 43 44#ifdef USE_SSL 45# include <openssl/md5.h> 46#else 47# include "md5.h" 48#endif 49 50 51#undef B_TRANSLATION_CONTEXT 52#define B_TRANSLATION_CONTEXT "smtp" 53 54 55#define CRLF "\r\n" 56#define SMTP_RESPONSE_SIZE 8192 57 58//#define DEBUG 59#ifdef DEBUG 60# define D(x) x 61# define bug printf 62#else 63# define D(x) ; 64#endif 65 66using namespace std; 67 68/* 69** Function: md5_hmac 70** taken from the file rfc2104.txt 71** written by Martin Schaaf <mascha@ma-scha.de> 72*/ 73void 74MD5Hmac(unsigned char *digest, const unsigned char* text, int text_len, 75 const unsigned char* key, int key_len) 76{ 77 MD5_CTX context; 78 unsigned char k_ipad[64]; 79 // inner padding - key XORd with ipad 80 unsigned char k_opad[64]; 81 // outer padding - key XORd with opad 82 int i; 83 84 /* start out by storing key in pads */ 85 memset(k_ipad, 0, sizeof k_ipad); 86 memset(k_opad, 0, sizeof k_opad); 87 if (key_len > 64) { 88 /* if key is longer than 64 bytes reset it to key=MD5(key) */ 89 MD5_CTX tctx; 90 91 MD5_Init(&tctx); 92 MD5_Update(&tctx, (unsigned char*)key, key_len); 93 MD5_Final(k_ipad, &tctx); 94 MD5_Final(k_opad, &tctx); 95 } else { 96 memcpy(k_ipad, key, key_len); 97 memcpy(k_opad, key, key_len); 98 } 99 100 /* 101 * the HMAC_MD5 transform looks like: 102 * 103 * MD5(K XOR opad, MD5(K XOR ipad, text)) 104 * 105 * where K is an n byte key 106 * ipad is the byte 0x36 repeated 64 times 107 * opad is the byte 0x5c repeated 64 times 108 * and text is the data being protected 109 */ 110 111 /* XOR key with ipad and opad values */ 112 for (i = 0; i < 64; i++) { 113 k_ipad[i] ^= 0x36; 114 k_opad[i] ^= 0x5c; 115 } 116 117 /* 118 * perform inner MD5 119 */ 120 MD5_Init(&context); /* init context for 1st 121 * pass */ 122 MD5_Update(&context, k_ipad, 64); /* start with inner pad */ 123 MD5_Update(&context, (unsigned char*)text, text_len); /* then text of datagram */ 124 MD5_Final(digest, &context); /* finish up 1st pass */ 125 /* 126 * perform outer MD5 127 */ 128 MD5_Init(&context); /* init context for 2nd 129 * pass */ 130 MD5_Update(&context, k_opad, 64); /* start with outer pad */ 131 MD5_Update(&context, digest, 16); /* then results of 1st 132 * hash */ 133 MD5_Final(digest, &context); /* finish up 2nd pass */ 134} 135 136 137void 138MD5HexHmac(char *hexdigest, const unsigned char* text, int text_len, 139 const unsigned char* key, int key_len) 140{ 141 unsigned char digest[16]; 142 int i; 143 unsigned char c; 144 145 MD5Hmac(digest, text, text_len, key, key_len); 146 for (i = 0; i < 16; i++) { 147 c = digest[i]; 148 *hexdigest++ = (c > 0x9F ? 'a'-10 : '0')+(c>>4); 149 *hexdigest++ = ((c&0x0F) > 9 ? 'a'-10 : '0')+(c&0x0F); 150 } 151 *hexdigest = '\0'; 152} 153 154 155/* 156** Function: MD5Sum 157** generates an MD5-sum from the given string 158*/ 159void 160MD5Sum (char* sum, unsigned char *text, int text_len) { 161 MD5_CTX context; 162 MD5_Init(&context); 163 MD5_Update(&context, text, text_len); 164 MD5_Final((unsigned char*)sum, &context); 165 sum[16] = '\0'; 166} 167 168/* 169** Function: MD5Digest 170** generates an MD5-digest from the given string 171*/ 172void MD5Digest (char* hexdigest, unsigned char *text, int text_len) { 173 int i; 174 unsigned char digest[17]; 175 unsigned char c; 176 177 MD5Sum((char*)digest, text, text_len); 178 179 for (i = 0; i < 16; i++) { 180 c = digest[i]; 181 *hexdigest++ = (c > 0x9F ? 'a'-10 : '0')+(c>>4); 182 *hexdigest++ = ((c&0x0F) > 9 ? 'a'-10 : '0')+(c&0x0F); 183 } 184 *hexdigest = '\0'; 185} 186 187/* 188** Function: SplitChallengeIntoMap 189** splits a challenge-string into the given map (see RFC-2831) 190*/ 191// : 192static bool 193SplitChallengeIntoMap(BString str, map<BString,BString>& m) 194{ 195 m.clear(); 196 const char* key; 197 const char* val; 198 char* s = (char*)str.String(); 199 while(*s != 0) { 200 while(isspace(*s)) 201 s++; 202 key = s; 203 while(isalpha(*s)) 204 s++; 205 if (*s != '=') 206 return false; 207 *s++ = '\0'; 208 while(isspace(*s)) 209 s++; 210 if (*s=='"') { 211 val = ++s; 212 while(*s!='"') { 213 if (*s == 0) 214 return false; 215 s++; 216 } 217 *s++ = '\0'; 218 } else { 219 val = s; 220 while(*s!=0 && *s!=',' && !isspace(*s)) 221 s++; 222 if (*s != 0) 223 *s++ = '\0'; 224 } 225 m[key] = val; 226 while(isspace(*s)) 227 s++; 228 if (*s != ',') 229 return false; 230 s++; 231 } 232 return true; 233} 234 235 236// Authentication types recognized. Not all methods are implemented. 237enum AuthType { 238 LOGIN = 1, 239 PLAIN = 1 << 2, 240 CRAM_MD5 = 1 << 3, 241 DIGEST_MD5 = 1 << 4 242}; 243 244 245SMTPProtocol::SMTPProtocol(BMailAccountSettings* settings) 246 : 247 OutboundProtocol(settings), 248 fAuthType(0) 249{ 250 fSettingsMessage = settings->OutboundSettings().Settings(); 251} 252 253 254SMTPProtocol::~SMTPProtocol() 255{ 256 257} 258 259 260status_t 261SMTPProtocol::Connect() 262{ 263 BString error_msg; 264 int32 authMethod = fSettingsMessage.FindInt32("auth_method"); 265 266 status_t status = B_ERROR; 267 268 if (authMethod == 2) { 269 // POP3 authentication is handled here instead of SMTPProtocol::Login() 270 // because some servers obviously don't like establishing the connection 271 // to the SMTP server first... 272 status_t status = _POP3Authentication(); 273 if (status < B_OK) { 274 error_msg << B_TRANSLATE("POP3 authentication failed. The server " 275 "said:\n") << fLog; 276 ShowError(error_msg.String()); 277 return status; 278 } 279 } 280 281 status = Open(fSettingsMessage.FindString("server"), 282 fSettingsMessage.FindInt32("port"), authMethod == 1); 283 if (status < B_OK) { 284 error_msg << B_TRANSLATE("Error while opening connection to %serv"); 285 error_msg.ReplaceFirst("%serv", fSettingsMessage.FindString("server")); 286 287 if (fSettingsMessage.FindInt32("port") > 0) 288 error_msg << ":" << fSettingsMessage.FindInt32("port"); 289 290 // << strerror(err) - BNetEndpoint sucks, we can't use this; 291 if (fLog.Length() > 0) 292 error_msg << B_TRANSLATE(". The server says:\n") << fLog; 293 else 294 error_msg << B_TRANSLATE(": Connection refused or host not found."); 295 296 ShowError(error_msg.String()); 297 298 return status; 299 } 300 301 const char* password = get_passwd(&fSettingsMessage, "cpasswd"); 302 status = Login(fSettingsMessage.FindString("username"), password); 303 delete[] password; 304 305 if (status != B_OK) { 306 //-----This is a really cool kind of error message. How can we make it work for POP3? 307 error_msg << B_TRANSLATE("Error while logging in to %serv") 308 << B_TRANSLATE(". The server said:\n") << fLog; 309 310 error_msg.ReplaceFirst("%serv", fSettingsMessage.FindString("server")); 311 312 ShowError(error_msg.String()); 313 } 314 return B_OK; 315} 316 317 318void 319SMTPProtocol::Disconnect() 320{ 321 Close(); 322} 323 324 325//! Process EMail to be sent 326status_t 327SMTPProtocol::SendMessages(const std::vector<entry_ref>& mails, 328 size_t totalBytes) 329{ 330 status_t status = Connect(); 331 if (status != B_OK) 332 return status; 333 334 for (unsigned int i = 0; i < mails.size(); i++) { 335 status = _SendMessage(mails[i]); 336 337 if (status != B_OK) { 338 BString error; 339 error << "An error occurred while sending the message " << 340 mails[i].name << ":\n" << fLog; 341 ShowError(error.String()); 342 343 ResetProgress(); 344 break; 345 } 346 off_t size = 0; 347 const entry_ref& ref = mails[i]; 348 BNode(&ref).GetSize(&size); 349 ReportProgress(size, 1); 350 } 351 352 Disconnect(); 353 return B_ERROR; 354} 355 356 357//! Opens connection to server 358status_t 359SMTPProtocol::Open(const char *address, int port, bool esmtp) 360{ 361 ReportProgress(0, 0, B_TRANSLATE("Connecting to server" B_UTF8_ELLIPSIS)); 362 363 #ifdef USE_SSL 364 use_ssl = (fSettingsMessage.FindInt32("flavor") == 1); 365 use_STARTTLS = (fSettingsMessage.FindInt32("flavor") == 2); 366 ssl = NULL; 367 ctx = NULL; 368 #endif 369 370 if (port <= 0) 371 #ifdef USE_SSL 372 port = use_ssl ? 465 : 25; 373 #else 374 port = 25; 375 #endif 376 377 uint32 hostIP = inet_addr(address); // first see if we can parse it as a numeric address 378 if ((hostIP == 0)||(hostIP == (uint32)-1)) { 379 struct hostent * he = gethostbyname(address); 380 hostIP = he ? *((uint32*)he->h_addr) : 0; 381 } 382 383 if (hostIP == 0) 384 return EHOSTUNREACH; 385 386 fSocket = socket(AF_INET, SOCK_STREAM, 0); 387 if (fSocket < 0) 388 return errno; 389 390 struct sockaddr_in saAddr; 391 memset(&saAddr, 0, sizeof(saAddr)); 392 saAddr.sin_family = AF_INET; 393 saAddr.sin_port = htons(port); 394 saAddr.sin_addr.s_addr = hostIP; 395 int result = connect(fSocket, (struct sockaddr *)&saAddr, 396 sizeof(saAddr)); 397 if (result < 0) { 398 close(fSocket); 399 fSocket = -1; 400 return errno; 401 } 402 403#ifdef USE_SSL 404 if (use_ssl) { 405 SSL_library_init(); 406 SSL_load_error_strings(); 407 RAND_seed(this,sizeof(SMTPProtocol)); 408 /*--- Because we're an add-on loaded at an unpredictable time, all 409 the memory addresses and things contained in ourself are 410 esssentially random. */ 411 412 ctx = SSL_CTX_new(SSLv23_method()); 413 ssl = SSL_new(ctx); 414 sbio=BIO_new_socket(fSocket,BIO_NOCLOSE); 415 SSL_set_bio(ssl,sbio,sbio); 416 417 if (SSL_connect(ssl) <= 0) { 418 BString error; 419 error << "Could not connect to SMTP server " 420 << fSettingsMessage.FindString("server"); 421 if (port != 465) 422 error << ":" << port; 423 error << ". (SSL connection error)"; 424 ShowError(error.String()); 425 SSL_CTX_free(ctx); 426 close(fSocket); 427 fSocket = -1; 428 return B_ERROR; 429 } 430 } 431#endif // USE_SSL 432 433 BString line; 434 ReceiveResponse(line); 435 436 char localhost[255]; 437 gethostname(localhost,255); 438 439 if (localhost[0] == 0) 440 strcpy(localhost,"namethisbebox"); 441 442 char *cmd = new char[::strlen(localhost)+8]; 443 if (!esmtp) 444 ::sprintf(cmd,"HELO %s"CRLF, localhost); 445 else 446 ::sprintf(cmd,"EHLO %s"CRLF, localhost); 447 448 if (SendCommand(cmd) != B_OK) { 449 delete[] cmd; 450 return B_ERROR; 451 } 452 453#ifdef USE_SSL 454 // Check for STARTTLS 455 if (use_STARTTLS) { 456 const char *res = fLog.String(); 457 char *p; 458 459 SSL_library_init(); 460 RAND_seed(this,sizeof(SMTPProtocol)); 461 ::sprintf(cmd, "STARTTLS"CRLF); 462 463 if ((p = ::strstr(res, "STARTTLS")) != NULL) { 464 // Server advertises STARTTLS support 465 if (SendCommand(cmd) != B_OK) { 466 delete[] cmd; 467 return B_ERROR; 468 } 469 470 // We should start TLS negotiation 471 use_ssl = true; 472 ctx = SSL_CTX_new(TLSv1_method()); 473 ssl = SSL_new(ctx); 474 sbio = BIO_new_socket(fSocket,BIO_NOCLOSE); 475 BIO_set_nbio(sbio, 0); 476 SSL_set_bio(ssl, sbio, sbio); 477 SSL_set_connect_state(ssl); 478 if(SSL_do_handshake(ssl) != 1) 479 return B_ERROR; 480 481 // Should send EHLO command again 482 if(!esmtp) 483 ::sprintf(cmd, "HELO %s"CRLF, localhost); 484 else 485 ::sprintf(cmd, "EHLO %s"CRLF, localhost); 486 487 if (SendCommand(cmd) != B_OK) { 488 delete[] cmd; 489 return B_ERROR; 490 } 491 } 492 } 493#endif // USE_SSL 494 495 delete[] cmd; 496 497 // Check auth type 498 if (esmtp) { 499 const char *res = fLog.String(); 500 char *p; 501 if ((p = ::strstr(res, "250-AUTH")) != NULL) { 502 if(::strstr(p, "LOGIN")) 503 fAuthType |= LOGIN; 504 if(::strstr(p, "PLAIN")) 505 fAuthType |= PLAIN; 506 if(::strstr(p, "CRAM-MD5")) 507 fAuthType |= CRAM_MD5; 508 if(::strstr(p, "DIGEST-MD5")) { 509 fAuthType |= DIGEST_MD5; 510 fServerName = address; 511 } 512 } 513 } 514 return B_OK; 515} 516 517 518status_t 519SMTPProtocol::_SendMessage(const entry_ref& mail) 520{ 521 // open read write to be able to manipulate in MessageReadyToSend hook 522 BFile file(&mail, B_READ_WRITE); 523 status_t status = file.InitCheck(); 524 if (status != B_OK) 525 return status; 526 527 BMessage header; 528 file >> header; 529 530 const char *from = header.FindString("MAIL:from"); 531 const char *to = header.FindString("MAIL:recipients"); 532 if (!to) 533 to = header.FindString("MAIL:to"); 534 535 if (to == NULL || from == NULL) { 536 fLog = "Invalid message headers"; 537 return B_ERROR; 538 } 539 540 NotifyMessageReadyToSend(mail, &file); 541 status = Send(to, from, &file); 542 if (status != B_OK) 543 return status; 544 NotifyMessageSent(mail, &file); 545 return B_OK; 546} 547 548 549status_t 550SMTPProtocol::_POP3Authentication() 551{ 552 const entry_ref& entry = fAccountSettings.InboundPath(); 553 if (strcmp(entry.name, "POP3") != 0) 554 return B_ERROR; 555 556 status_t (*pop3_smtp_auth)(BMailAccountSettings*); 557 558 BPath path(&entry); 559 image_id image = load_add_on(path.Path()); 560 if (image < 0) 561 return B_ERROR; 562 if (get_image_symbol(image, "pop3_smtp_auth", 563 B_SYMBOL_TYPE_TEXT, (void **)&pop3_smtp_auth) != B_OK) { 564 unload_add_on(image); 565 image = -1; 566 return B_ERROR; 567 } 568 status_t status = (*pop3_smtp_auth)(&fAccountSettings); 569 unload_add_on(image); 570 return status; 571} 572 573 574status_t 575SMTPProtocol::Login(const char *_login, const char *password) 576{ 577 if (fAuthType == 0) 578 return B_OK; 579 580 const char *login = _login; 581 char hex_digest[33]; 582 BString out; 583 584 int32 loginlen = ::strlen(login); 585 int32 passlen = ::strlen(password); 586 587 if (fAuthType & DIGEST_MD5) { 588 //******* DIGEST-MD5 Authentication ( tested. works fine [with Cyrus SASL] ) 589 // this implements only the subpart of DIGEST-MD5 which is 590 // required for authentication to SMTP-servers. Integrity- 591 // and confidentiality-protection are not implemented, as 592 // they are provided by the use of OpenSSL. 593 SendCommand("AUTH DIGEST-MD5"CRLF); 594 const char *res = fLog.String(); 595 596 if (strncmp(res, "334", 3) != 0) 597 return B_ERROR; 598 int32 baselen = ::strlen(&res[4]); 599 char *base = new char[baselen+1]; 600 baselen = ::decode_base64(base, &res[4], baselen); 601 base[baselen] = '\0'; 602 603 D(bug("base: %s\n", base)); 604 605 map<BString,BString> challengeMap; 606 SplitChallengeIntoMap(base, challengeMap); 607 608 delete[] base; 609 610 BString rawResponse = BString("username=") << '"' << login << '"'; 611 rawResponse << ",realm=" << '"' << challengeMap["realm"] << '"'; 612 rawResponse << ",nonce=" << '"' << challengeMap["nonce"] << '"'; 613 rawResponse << ",nc=00000001"; 614 char temp[33]; 615 for( int i=0; i<32; ++i) 616 temp[i] = 1+(rand()%254); 617 temp[32] = '\0'; 618 BString rawCnonce(temp); 619 BString cnonce; 620 char* cnoncePtr = cnonce.LockBuffer(rawCnonce.Length()*2); 621 baselen = ::encode_base64(cnoncePtr, rawCnonce.String(), rawCnonce.Length(), true /* headerMode */); 622 cnoncePtr[baselen] = '\0'; 623 cnonce.UnlockBuffer(baselen); 624 rawResponse << ",cnonce=" << '"' << cnonce << '"'; 625 rawResponse << ",qop=auth"; 626 BString digestUriValue = BString("smtp/") << fServerName; 627 rawResponse << ",digest-uri=" << '"' << digestUriValue << '"'; 628 char sum[17], hex_digest2[33]; 629 BString a1,a2,kd; 630 BString t1 = BString(login) << ":" 631 << challengeMap["realm"] << ":" 632 << password; 633 MD5Sum(sum, (unsigned char*)t1.String(), t1.Length()); 634 a1 << sum << ":" << challengeMap["nonce"] << ":" << cnonce; 635 MD5Digest(hex_digest, (unsigned char*)a1.String(), a1.Length()); 636 a2 << "AUTHENTICATE:" << digestUriValue; 637 MD5Digest(hex_digest2, (unsigned char*)a2.String(), a2.Length()); 638 kd << hex_digest << ':' << challengeMap["nonce"] 639 << ":" << "00000001" << ':' << cnonce << ':' << "auth" 640 << ':' << hex_digest2; 641 MD5Digest(hex_digest, (unsigned char*)kd.String(), kd.Length()); 642 643 rawResponse << ",response=" << hex_digest; 644 BString postResponse; 645 char *resp = postResponse.LockBuffer(rawResponse.Length() * 2 + 10); 646 baselen = ::encode_base64(resp, rawResponse.String(), rawResponse.Length(), true /* headerMode */); 647 resp[baselen] = 0; 648 postResponse.UnlockBuffer(); 649 postResponse.Append(CRLF); 650 651 SendCommand(postResponse.String()); 652 653 res = fLog.String(); 654 if (atol(res) >= 500) 655 return B_ERROR; 656 // actually, we are supposed to check the rspauth sent back 657 // by the SMTP-server, but that isn't strictly required, 658 // so we skip that for now. 659 SendCommand(CRLF); // finish off authentication 660 res = fLog.String(); 661 if (atol(res) < 500) 662 return B_OK; 663 } 664 if (fAuthType & CRAM_MD5) { 665 //******* CRAM-MD5 Authentication ( tested. works fine [with Cyrus SASL] ) 666 SendCommand("AUTH CRAM-MD5"CRLF); 667 const char *res = fLog.String(); 668 669 if (strncmp(res, "334", 3) != 0) 670 return B_ERROR; 671 int32 baselen = ::strlen(&res[4]); 672 char *base = new char[baselen+1]; 673 baselen = ::decode_base64(base, &res[4], baselen); 674 base[baselen] = '\0'; 675 676 D(bug("base: %s\n", base)); 677 678 ::MD5HexHmac(hex_digest, (const unsigned char *)base, (int)baselen, 679 (const unsigned char *)password, (int)passlen); 680 681 D(bug("%s\n%s\n", base, hex_digest)); 682 683 delete[] base; 684 685 BString preResponse, postResponse; 686 preResponse = login; 687 preResponse << " " << hex_digest << CRLF; 688 char *resp = postResponse.LockBuffer(preResponse.Length() * 2 + 10); 689 baselen = ::encode_base64(resp, preResponse.String(), preResponse.Length(), true /* headerMode */); 690 resp[baselen] = 0; 691 postResponse.UnlockBuffer(); 692 postResponse.Append(CRLF); 693 694 SendCommand(postResponse.String()); 695 696 res = fLog.String(); 697 if (atol(res) < 500) 698 return B_OK; 699 } 700 if (fAuthType & LOGIN) { 701 //******* LOGIN Authentication ( tested. works fine) 702 ssize_t encodedsize; // required by our base64 implementation 703 704 SendCommand("AUTH LOGIN"CRLF); 705 const char *res = fLog.String(); 706 707 if (strncmp(res, "334", 3) != 0) 708 return B_ERROR; 709 710 // Send login name as base64 711 char *login64 = new char[loginlen*3 + 6]; 712 encodedsize = ::encode_base64(login64, (char *)login, loginlen, true /* headerMode */); 713 login64[encodedsize] = 0; 714 strcat (login64, CRLF); 715 SendCommand(login64); 716 delete[] login64; 717 718 res = fLog.String(); 719 if (strncmp(res,"334",3) != 0) 720 return B_ERROR; 721 722 // Send password as base64 723 login64 = new char[passlen*3 + 6]; 724 encodedsize = ::encode_base64(login64, (char *)password, passlen, true /* headerMode */); 725 login64[encodedsize] = 0; 726 strcat (login64, CRLF); 727 SendCommand(login64); 728 delete[] login64; 729 730 res = fLog.String(); 731 if (atol(res) < 500) 732 return B_OK; 733 } 734 if (fAuthType & PLAIN) { 735 //******* PLAIN Authentication ( tested. works fine [with Cyrus SASL] ) 736 // format is: 737 // authenticateID + \0 + username + \0 + password 738 // (where authenticateID is always empty !?!) 739 BString preResponse, postResponse; 740 char *stringPntr; 741 ssize_t encodedLength; 742 stringPntr = preResponse.LockBuffer(loginlen + passlen + 3); 743 // +3 to make room for the two \0-chars between the tokens and 744 // the final delimiter added by sprintf(). 745 sprintf (stringPntr, "%c%s%c%s", 0, login, 0, password); 746 preResponse.UnlockBuffer(loginlen + passlen + 2); 747 // +2 in order to leave out the final delimiter (which is not part 748 // of the string). 749 stringPntr = postResponse.LockBuffer(preResponse.Length() * 3); 750 encodedLength = ::encode_base64(stringPntr, preResponse.String(), 751 preResponse.Length(), true /* headerMode */); 752 stringPntr[encodedLength] = 0; 753 postResponse.UnlockBuffer(); 754 postResponse.Prepend("AUTH PLAIN "); 755 postResponse << CRLF; 756 757 SendCommand(postResponse.String()); 758 759 const char *res = fLog.String(); 760 if (atol(res) < 500) 761 return B_OK; 762 } 763 return B_ERROR; 764} 765 766 767void 768SMTPProtocol::Close() 769{ 770 771 BString cmd = "QUIT"; 772 cmd += CRLF; 773 774 if (SendCommand(cmd.String()) != B_OK) { 775 // Error 776 } 777 778#ifdef USE_SSL 779 if (use_ssl) { 780 if (ssl) 781 SSL_shutdown(ssl); 782 if (ctx) 783 SSL_CTX_free(ctx); 784 } 785#endif 786 787 close(fSocket); 788} 789 790 791status_t 792SMTPProtocol::Send(const char* to, const char* from, BPositionIO *message) 793{ 794 BString cmd = from; 795 cmd.Remove(0, cmd.FindFirst("\" <") + 2); 796 cmd.Prepend("MAIL FROM: "); 797 cmd += CRLF; 798 if (SendCommand(cmd.String()) != B_OK) 799 return B_ERROR; 800 801 int32 len = strlen(to); 802 BString addr(""); 803 for (int32 i = 0;i < len;i++) { 804 char c = to[i]; 805 if (c != ',') 806 addr += (char)c; 807 if (c == ','||i == len-1) { 808 if(addr.Length() == 0) 809 continue; 810 cmd = "RCPT TO: "; 811 cmd << addr.String() << CRLF; 812 if (SendCommand(cmd.String()) != B_OK) 813 return B_ERROR; 814 815 addr =""; 816 } 817 } 818 819 cmd = "DATA"; 820 cmd += CRLF; 821 if (SendCommand(cmd.String()) != B_OK) 822 return B_ERROR; 823 824 // Send the message data. Convert lines starting with a period to start 825 // with two periods and so on. The actual sequence is CR LF Period. The 826 // SMTP server will remove the periods. Of course, the POP server may then 827 // add some of its own, but the POP client should take care of them. 828 829 ssize_t amountRead; 830 ssize_t amountToRead; 831 ssize_t amountUnread; 832 ssize_t bufferLen = 0; 833 const int bufferMax = 2000; 834 bool foundCRLFPeriod; 835 int i; 836 bool messageEndedWithCRLF = false; 837 838 message->Seek(0, SEEK_END); 839 amountUnread = message->Position(); 840 message->Seek(0, SEEK_SET); 841 char *data = new char[bufferMax]; 842 843 while (true) { 844 // Fill the buffer if it is getting low, but not every time, to avoid 845 // small reads. 846 if (bufferLen < bufferMax / 2) { 847 amountToRead = bufferMax - bufferLen; 848 if (amountToRead > amountUnread) 849 amountToRead = amountUnread; 850 if (amountToRead > 0) { 851 amountRead = message->Read (data + bufferLen, amountToRead); 852 if (amountRead <= 0 || amountRead > amountToRead) 853 amountUnread = 0; // Just stop reading when an error happens. 854 else { 855 amountUnread -= amountRead; 856 bufferLen += amountRead; 857 } 858 } 859 } 860 861 // Look for the next CRLFPeriod triple. 862 foundCRLFPeriod = false; 863 for (i = 0; i <= bufferLen - 3; i++) { 864 if (data[i] == '\r' && data[i+1] == '\n' && data[i+2] == '.') { 865 foundCRLFPeriod = true; 866 // Send data up to the CRLF, and include the period too. 867#ifdef USE_SSL 868 if (use_ssl) { 869 if (SSL_write(ssl,data,i + 3) < 0) { 870 amountUnread = 0; // Stop when an error happens. 871 bufferLen = 0; 872 break; 873 } 874 } else 875#endif 876 if (send (fSocket,data, i + 3,0) < 0) { 877 amountUnread = 0; // Stop when an error happens. 878 bufferLen = 0; 879 break; 880 } 881 ReportProgress (i + 2 /* Don't include the double period here */,0); 882 // Move the data over in the buffer, but leave the period there 883 // so it gets sent a second time. 884 memmove(data, data + (i + 2), bufferLen - (i + 2)); 885 bufferLen -= i + 2; 886 break; 887 } 888 } 889 890 if (!foundCRLFPeriod) { 891 if (amountUnread <= 0) { // No more data, all we have is in the buffer. 892 if (bufferLen > 0) { 893 #ifdef USE_SSL 894 if (use_ssl) 895 SSL_write(ssl,data,bufferLen); 896 else 897 #endif 898 send (fSocket,data, bufferLen,0); 899 ReportProgress (bufferLen,0); 900 if (bufferLen >= 2) 901 messageEndedWithCRLF = (data[bufferLen-2] == '\r' && 902 data[bufferLen-1] == '\n'); 903 } 904 break; // Finished! 905 } 906 907 // Send most of the buffer, except a few characters to overlap with 908 // the next read, in case the CRLFPeriod is split between reads. 909 if (bufferLen > 3) { 910 #ifdef USE_SSL 911 if (use_ssl) { 912 if (SSL_write(ssl,data,bufferLen - 3) < 0) 913 break; 914 } else 915 #endif 916 if (send (fSocket,data, bufferLen - 3,0) < 0) 917 break; // Stop when an error happens. 918 ReportProgress (bufferLen - 3,0); 919 memmove (data, data + bufferLen - 3, 3); 920 bufferLen = 3; 921 } 922 } 923 } 924 delete [] data; 925 926 if (messageEndedWithCRLF) 927 cmd = "."CRLF; // The standard says don't add extra CRLF. 928 else 929 cmd = CRLF"."CRLF; 930 931 if (SendCommand(cmd.String()) != B_OK) 932 return B_ERROR; 933 934 return B_OK; 935} 936 937 938//! Receives response from server. 939int32 940SMTPProtocol::ReceiveResponse(BString &out) 941{ 942 out = ""; 943 int32 len = 0,r; 944 char buf[SMTP_RESPONSE_SIZE]; 945 bigtime_t timeout = 1000000*180; // timeout 180 secs 946 bool gotCode = false; 947 int32 errCode; 948 BString searchStr = ""; 949 950 struct timeval tv; 951 struct fd_set fds; 952 953 tv.tv_sec = long(timeout / 1e6); 954 tv.tv_usec = long(timeout-(tv.tv_sec * 1e6)); 955 956 /* Initialize (clear) the socket mask. */ 957 FD_ZERO(&fds); 958 959 /* Set the socket in the mask. */ 960 FD_SET(fSocket, &fds); 961 int result = -1; 962#ifdef USE_SSL 963 if ((use_ssl) && (SSL_pending(ssl))) 964 result = 1; 965 else 966#endif 967 result = select(1 + fSocket, &fds, NULL, NULL, &tv); 968 if (result < 0) 969 return errno; 970 971 if (result > 0) { 972 while (1) { 973 #ifdef USE_SSL 974 if (use_ssl) 975 r = SSL_read(ssl,buf,SMTP_RESPONSE_SIZE - 1); 976 else 977 #endif 978 r = recv(fSocket,buf, SMTP_RESPONSE_SIZE - 1,0); 979 if (r <= 0) 980 break; 981 982 if (!gotCode) 983 { 984 if (buf[3] == ' ' || buf[3] == '-') 985 { 986 errCode = atol(buf); 987 gotCode = true; 988 searchStr << errCode << ' '; 989 } 990 } 991 992 len += r; 993 out.Append(buf, r); 994 995 if (strstr(buf, CRLF) && (out.FindFirst(searchStr) != B_ERROR)) 996 break; 997 } 998 } else 999 fLog = "SMTP socket timeout."; 1000 1001 D(bug("S:%s\n", out.String())); 1002 return len; 1003} 1004 1005 1006// Sends SMTP command. Result kept in fLog 1007 1008status_t 1009SMTPProtocol::SendCommand(const char *cmd) 1010{ 1011 D(bug("C:%s\n", cmd)); 1012 1013#ifdef USE_SSL 1014 if (use_ssl && ssl) { 1015 if (SSL_write(ssl,cmd,::strlen(cmd)) < 0) 1016 return B_ERROR; 1017 } else 1018#endif 1019 if (send(fSocket,cmd, ::strlen(cmd),0) < 0) 1020 return B_ERROR; 1021 fLog = ""; 1022 1023 // Receive 1024 while (1) { 1025 int32 len = ReceiveResponse(fLog); 1026 1027 if (len <= 0) { 1028 D(bug("SMTP: len == %ld\n", len)); 1029 return B_ERROR; 1030 } 1031 1032 if (fLog.Length() > 4 && (fLog[3] == ' ' || fLog[3] == '-')) 1033 { 1034 int32 num = atol(fLog.String()); 1035 D(bug("ReplyNumber: %ld\n", num)); 1036 if (num >= 500) 1037 return B_ERROR; 1038 1039 break; 1040 } 1041 } 1042 1043 return B_OK; 1044} 1045 1046 1047// #pragma mark - 1048 1049 1050OutboundProtocol* 1051instantiate_outbound_protocol(BMailAccountSettings* settings) 1052{ 1053 return new SMTPProtocol(settings); 1054} 1055