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