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