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 || (p = ::strstr(res, "250 AUTH")) != NULL) { 430 if(::strstr(p, "LOGIN")) 431 fAuthType |= LOGIN; 432 if(::strstr(p, "PLAIN")) 433 fAuthType |= PLAIN; 434 if(::strstr(p, "CRAM-MD5")) 435 fAuthType |= CRAM_MD5; 436 if(::strstr(p, "DIGEST-MD5")) { 437 fAuthType |= DIGEST_MD5; 438 fServerName = address; 439 } 440 } 441 } 442 return B_OK; 443 } 444 445 446 status_t 447 SMTPProtocol::_SendMessage(const entry_ref& ref) 448 { 449 // open read write to be able to manipulate in MessageReadyToSend hook 450 BFile file(&ref, B_READ_WRITE); 451 status_t status = file.InitCheck(); 452 if (status != B_OK) 453 return status; 454 455 BMessage header; 456 file >> header; 457 458 const char *from = header.FindString("MAIL:from"); 459 const char *to = header.FindString("MAIL:recipients"); 460 if (to == NULL) 461 to = header.FindString("MAIL:to"); 462 463 if (to == NULL || from == NULL) { 464 fLog = "Invalid message headers"; 465 return B_ERROR; 466 } 467 468 NotifyMessageReadyToSend(ref, file); 469 status = Send(to, from, &file); 470 if (status != B_OK) 471 return status; 472 NotifyMessageSent(ref, file); 473 474 off_t size = 0; 475 file.GetSize(&size); 476 ReportProgress(size, 1); 477 478 return B_OK; 479 } 480 481 482 status_t 483 SMTPProtocol::_POP3Authentication() 484 { 485 const entry_ref& entry = fAccountSettings.InboundAddOnRef(); 486 if (strcmp(entry.name, "POP3") != 0) 487 return B_ERROR; 488 489 status_t (*pop3_smtp_auth)(const BMailAccountSettings&); 490 491 BPath path(&entry); 492 image_id image = load_add_on(path.Path()); 493 if (image < 0) 494 return B_ERROR; 495 if (get_image_symbol(image, "pop3_smtp_auth", 496 B_SYMBOL_TYPE_TEXT, (void **)&pop3_smtp_auth) != B_OK) { 497 unload_add_on(image); 498 image = -1; 499 return B_ERROR; 500 } 501 status_t status = (*pop3_smtp_auth)(fAccountSettings); 502 unload_add_on(image); 503 return status; 504 } 505 506 507 status_t 508 SMTPProtocol::Login(const char *_login, const char *password) 509 { 510 if (fAuthType == 0) 511 return B_OK; 512 513 const char *login = _login; 514 char hex_digest[33]; 515 BString out; 516 517 int32 loginlen = ::strlen(login); 518 int32 passlen = ::strlen(password); 519 520 if (fAuthType & DIGEST_MD5) { 521 //******* DIGEST-MD5 Authentication ( tested. works fine [with Cyrus SASL] ) 522 // this implements only the subpart of DIGEST-MD5 which is 523 // required for authentication to SMTP-servers. Integrity- 524 // and confidentiality-protection are not implemented, as 525 // they are provided by the use of OpenSSL. 526 SendCommand("AUTH DIGEST-MD5" CRLF); 527 const char *res = fLog.String(); 528 529 if (strncmp(res, "334", 3) != 0) 530 return B_ERROR; 531 int32 baselen = ::strlen(&res[4]); 532 char *base = new char[baselen+1]; 533 baselen = ::decode_base64(base, &res[4], baselen); 534 base[baselen] = '\0'; 535 536 D(bug("base: %s\n", base)); 537 538 map<BString,BString> challengeMap; 539 SplitChallengeIntoMap(base, challengeMap); 540 541 delete[] base; 542 543 BString rawResponse = BString("username=") << '"' << login << '"'; 544 rawResponse << ",realm=" << '"' << challengeMap["realm"] << '"'; 545 rawResponse << ",nonce=" << '"' << challengeMap["nonce"] << '"'; 546 rawResponse << ",nc=00000001"; 547 char temp[33]; 548 for( int i=0; i<32; ++i) 549 temp[i] = 1+(rand()%254); 550 temp[32] = '\0'; 551 BString rawCnonce(temp); 552 BString cnonce; 553 char* cnoncePtr = cnonce.LockBuffer(rawCnonce.Length()*2); 554 baselen = ::encode_base64(cnoncePtr, rawCnonce.String(), rawCnonce.Length(), true /* headerMode */); 555 cnoncePtr[baselen] = '\0'; 556 cnonce.UnlockBuffer(baselen); 557 rawResponse << ",cnonce=" << '"' << cnonce << '"'; 558 rawResponse << ",qop=auth"; 559 BString digestUriValue = BString("smtp/") << fServerName; 560 rawResponse << ",digest-uri=" << '"' << digestUriValue << '"'; 561 char sum[17], hex_digest2[33]; 562 BString a1,a2,kd; 563 BString t1 = BString(login) << ":" 564 << challengeMap["realm"] << ":" 565 << password; 566 MD5Sum(sum, (unsigned char*)t1.String(), t1.Length()); 567 a1 << sum << ":" << challengeMap["nonce"] << ":" << cnonce; 568 MD5Digest(hex_digest, (unsigned char*)a1.String(), a1.Length()); 569 a2 << "AUTHENTICATE:" << digestUriValue; 570 MD5Digest(hex_digest2, (unsigned char*)a2.String(), a2.Length()); 571 kd << hex_digest << ':' << challengeMap["nonce"] 572 << ":" << "00000001" << ':' << cnonce << ':' << "auth" 573 << ':' << hex_digest2; 574 MD5Digest(hex_digest, (unsigned char*)kd.String(), kd.Length()); 575 576 rawResponse << ",response=" << hex_digest; 577 BString postResponse; 578 char *resp = postResponse.LockBuffer(rawResponse.Length() * 2 + 10); 579 baselen = ::encode_base64(resp, rawResponse.String(), rawResponse.Length(), true /* headerMode */); 580 resp[baselen] = 0; 581 postResponse.UnlockBuffer(); 582 postResponse.Append(CRLF); 583 584 SendCommand(postResponse.String()); 585 586 res = fLog.String(); 587 if (atol(res) >= 500) 588 return B_ERROR; 589 // actually, we are supposed to check the rspauth sent back 590 // by the SMTP-server, but that isn't strictly required, 591 // so we skip that for now. 592 SendCommand(CRLF); // finish off authentication 593 res = fLog.String(); 594 if (atol(res) < 500) 595 return B_OK; 596 } 597 if (fAuthType & CRAM_MD5) { 598 //******* CRAM-MD5 Authentication ( tested. works fine [with Cyrus SASL] ) 599 SendCommand("AUTH CRAM-MD5" CRLF); 600 const char *res = fLog.String(); 601 602 if (strncmp(res, "334", 3) != 0) 603 return B_ERROR; 604 int32 baselen = ::strlen(&res[4]); 605 char *base = new char[baselen+1]; 606 baselen = ::decode_base64(base, &res[4], baselen); 607 base[baselen] = '\0'; 608 609 D(bug("base: %s\n", base)); 610 611 ::MD5HexHmac(hex_digest, (const unsigned char *)base, (int)baselen, 612 (const unsigned char *)password, (int)passlen); 613 614 D(bug("%s\n%s\n", base, hex_digest)); 615 616 delete[] base; 617 618 BString preResponse, postResponse; 619 preResponse = login; 620 preResponse << " " << hex_digest << CRLF; 621 char *resp = postResponse.LockBuffer(preResponse.Length() * 2 + 10); 622 baselen = ::encode_base64(resp, preResponse.String(), preResponse.Length(), true /* headerMode */); 623 resp[baselen] = 0; 624 postResponse.UnlockBuffer(); 625 postResponse.Append(CRLF); 626 627 SendCommand(postResponse.String()); 628 629 res = fLog.String(); 630 if (atol(res) < 500) 631 return B_OK; 632 } 633 if (fAuthType & LOGIN) { 634 //******* LOGIN Authentication ( tested. works fine) 635 ssize_t encodedsize; // required by our base64 implementation 636 637 SendCommand("AUTH LOGIN" CRLF); 638 const char *res = fLog.String(); 639 640 if (strncmp(res, "334", 3) != 0) 641 return B_ERROR; 642 643 // Send login name as base64 644 char *login64 = new char[loginlen*3 + 6]; 645 encodedsize = ::encode_base64(login64, (char *)login, loginlen, true /* headerMode */); 646 login64[encodedsize] = 0; 647 strcat (login64, CRLF); 648 SendCommand(login64); 649 delete[] login64; 650 651 res = fLog.String(); 652 if (strncmp(res,"334",3) != 0) 653 return B_ERROR; 654 655 // Send password as base64 656 login64 = new char[passlen*3 + 6]; 657 encodedsize = ::encode_base64(login64, (char *)password, passlen, true /* headerMode */); 658 login64[encodedsize] = 0; 659 strcat (login64, CRLF); 660 SendCommand(login64); 661 delete[] login64; 662 663 res = fLog.String(); 664 if (atol(res) < 500) 665 return B_OK; 666 } 667 if (fAuthType & PLAIN) { 668 //******* PLAIN Authentication ( tested. works fine [with Cyrus SASL] ) 669 // format is: 670 // authenticateID + \0 + username + \0 + password 671 // (where authenticateID is always empty !?!) 672 BString preResponse, postResponse; 673 char *stringPntr; 674 ssize_t encodedLength; 675 stringPntr = preResponse.LockBuffer(loginlen + passlen + 3); 676 // +3 to make room for the two \0-chars between the tokens and 677 // the final delimiter added by sprintf(). 678 sprintf (stringPntr, "%c%s%c%s", 0, login, 0, password); 679 preResponse.UnlockBuffer(loginlen + passlen + 2); 680 // +2 in order to leave out the final delimiter (which is not part 681 // of the string). 682 stringPntr = postResponse.LockBuffer(preResponse.Length() * 3); 683 encodedLength = ::encode_base64(stringPntr, preResponse.String(), 684 preResponse.Length(), true /* headerMode */); 685 stringPntr[encodedLength] = 0; 686 postResponse.UnlockBuffer(); 687 postResponse.Prepend("AUTH PLAIN "); 688 postResponse << CRLF; 689 690 SendCommand(postResponse.String()); 691 692 const char *res = fLog.String(); 693 if (atol(res) < 500) 694 return B_OK; 695 } 696 return B_ERROR; 697 } 698 699 700 void 701 SMTPProtocol::Close() 702 { 703 704 BString cmd = "QUIT"; 705 cmd += CRLF; 706 707 if (SendCommand(cmd.String()) != B_OK) { 708 // Error 709 } 710 711 delete fSocket; 712 } 713 714 715 status_t 716 SMTPProtocol::Send(const char* to, const char* from, BPositionIO *message) 717 { 718 BString cmd = from; 719 cmd.Remove(0, cmd.FindFirst("\" <") + 2); 720 cmd.Prepend("MAIL FROM: "); 721 cmd += CRLF; 722 if (SendCommand(cmd.String()) != B_OK) 723 return B_ERROR; 724 725 int32 len = strlen(to); 726 BString addr(""); 727 for (int32 i = 0;i < len;i++) { 728 char c = to[i]; 729 if (c != ',') 730 addr += (char)c; 731 if (c == ','||i == len-1) { 732 if(addr.Length() == 0) 733 continue; 734 cmd = "RCPT TO: "; 735 cmd << addr.String() << CRLF; 736 if (SendCommand(cmd.String()) != B_OK) 737 return B_ERROR; 738 739 addr =""; 740 } 741 } 742 743 cmd = "DATA"; 744 cmd += CRLF; 745 if (SendCommand(cmd.String()) != B_OK) 746 return B_ERROR; 747 748 // Send the message data. Convert lines starting with a period to start 749 // with two periods and so on. The actual sequence is CR LF Period. The 750 // SMTP server will remove the periods. Of course, the POP server may then 751 // add some of its own, but the POP client should take care of them. 752 753 ssize_t amountRead; 754 ssize_t amountToRead; 755 ssize_t amountUnread; 756 ssize_t bufferLen = 0; 757 const int bufferMax = 2000; 758 bool foundCRLFPeriod; 759 int i; 760 bool messageEndedWithCRLF = false; 761 762 message->Seek(0, SEEK_END); 763 amountUnread = message->Position(); 764 message->Seek(0, SEEK_SET); 765 char *data = new char[bufferMax]; 766 767 while (true) { 768 // Fill the buffer if it is getting low, but not every time, to avoid 769 // small reads. 770 if (bufferLen < bufferMax / 2) { 771 amountToRead = bufferMax - bufferLen; 772 if (amountToRead > amountUnread) 773 amountToRead = amountUnread; 774 if (amountToRead > 0) { 775 amountRead = message->Read (data + bufferLen, amountToRead); 776 if (amountRead <= 0 || amountRead > amountToRead) 777 amountUnread = 0; // Just stop reading when an error happens. 778 else { 779 amountUnread -= amountRead; 780 bufferLen += amountRead; 781 } 782 } 783 } 784 785 // Look for the next CRLFPeriod triple. 786 foundCRLFPeriod = false; 787 for (i = 0; i <= bufferLen - 3; i++) { 788 if (data[i] == '\r' && data[i+1] == '\n' && data[i+2] == '.') { 789 foundCRLFPeriod = true; 790 // Send data up to the CRLF, and include the period too. 791 if (fSocket->Write(data, i + 3) < 0) { 792 amountUnread = 0; // Stop when an error happens. 793 bufferLen = 0; 794 break; 795 } 796 ReportProgress (i + 2 /* Don't include the double period here */,0); 797 // Move the data over in the buffer, but leave the period there 798 // so it gets sent a second time. 799 memmove(data, data + (i + 2), bufferLen - (i + 2)); 800 bufferLen -= i + 2; 801 break; 802 } 803 } 804 805 if (!foundCRLFPeriod) { 806 if (amountUnread <= 0) { // No more data, all we have is in the buffer. 807 if (bufferLen > 0) { 808 fSocket->Write(data, bufferLen); 809 ReportProgress(bufferLen, 0); 810 if (bufferLen >= 2) 811 messageEndedWithCRLF = (data[bufferLen-2] == '\r' && 812 data[bufferLen-1] == '\n'); 813 } 814 break; // Finished! 815 } 816 817 // Send most of the buffer, except a few characters to overlap with 818 // the next read, in case the CRLFPeriod is split between reads. 819 if (bufferLen > 3) { 820 if (fSocket->Write(data, bufferLen - 3) < 0) 821 break; // Stop when an error happens. 822 823 ReportProgress(bufferLen - 3, 0); 824 memmove (data, data + bufferLen - 3, 3); 825 bufferLen = 3; 826 } 827 } 828 } 829 delete [] data; 830 831 if (messageEndedWithCRLF) 832 cmd = "." CRLF; // The standard says don't add extra CRLF. 833 else 834 cmd = CRLF "." CRLF; 835 836 if (SendCommand(cmd.String()) != B_OK) 837 return B_ERROR; 838 839 return B_OK; 840 } 841 842 843 //! Receives response from server. 844 int32 845 SMTPProtocol::ReceiveResponse(BString &out) 846 { 847 out = ""; 848 int32 len = 0,r; 849 char buf[SMTP_RESPONSE_SIZE]; 850 bigtime_t timeout = 1000000*180; // timeout 180 secs 851 bool gotCode = false; 852 int32 errCode; 853 BString searchStr = ""; 854 855 if (fSocket->WaitForReadable(timeout) == B_OK) { 856 while (1) { 857 r = fSocket->Read(buf, SMTP_RESPONSE_SIZE - 1); 858 if (r <= 0) 859 break; 860 861 if (!gotCode) { 862 if (buf[3] == ' ' || buf[3] == '-') { 863 errCode = atol(buf); 864 gotCode = true; 865 searchStr << errCode << ' '; 866 } 867 } 868 869 len += r; 870 out.Append(buf, r); 871 872 if (strstr(buf, CRLF) && (out.FindFirst(searchStr) != B_ERROR)) 873 break; 874 } 875 } else 876 fLog = "SMTP socket timeout."; 877 878 D(bug("S:%s\n", out.String())); 879 return len; 880 } 881 882 883 // Sends SMTP command. Result kept in fLog 884 885 status_t 886 SMTPProtocol::SendCommand(const char *cmd) 887 { 888 D(bug("C:%s\n", cmd)); 889 890 if (fSocket->Write(cmd, ::strlen(cmd)) < 0) 891 return B_ERROR; 892 fLog = ""; 893 894 // Receive 895 while (1) { 896 int32 len = ReceiveResponse(fLog); 897 898 if (len <= 0) { 899 D(bug("SMTP: len == %" B_PRId32 "\n", len)); 900 return B_ERROR; 901 } 902 903 if (fLog.Length() > 4 && (fLog[3] == ' ' || fLog[3] == '-')) 904 { 905 int32 num = atol(fLog.String()); 906 D(bug("ReplyNumber: %" B_PRId32 "\n", num)); 907 if (num >= 500) 908 return B_ERROR; 909 910 break; 911 } 912 } 913 914 return B_OK; 915 } 916 917 918 // #pragma mark - 919 920 921 extern "C" BOutboundMailProtocol* 922 instantiate_outbound_protocol(const BMailAccountSettings& settings) 923 { 924 return new SMTPProtocol(settings); 925 } 926