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("SMTP", 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::HandleSendMessages(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 // TODO: sort out already sent messages -- the request could 341 // be issued while we're busy sending them already 342 343 SetTotalItems(count); 344 SetTotalItemsSize(totalBytes); 345 346 status = Connect(); 347 if (status != B_OK) 348 return status; 349 350 entry_ref ref; 351 for (int32 i = 0; message.FindRef("ref", i++, &ref) == B_OK;) { 352 status = _SendMessage(ref); 353 if (status != B_OK) { 354 BString error; 355 error << "An error occurred while sending the message " 356 << ref.name << ":\n" << fLog; 357 ShowError(error.String()); 358 359 ResetProgress(); 360 break; 361 } 362 } 363 364 Disconnect(); 365 return B_ERROR; 366 } 367 368 369 //! Opens connection to server 370 status_t 371 SMTPProtocol::Open(const char *address, int port, bool esmtp) 372 { 373 ReportProgress(0, 0, B_TRANSLATE("Connecting to server" B_UTF8_ELLIPSIS)); 374 375 #ifdef USE_SSL 376 use_ssl = (fSettingsMessage.FindInt32("flavor") == 1); 377 use_STARTTLS = (fSettingsMessage.FindInt32("flavor") == 2); 378 ssl = NULL; 379 ctx = NULL; 380 #endif 381 382 if (port <= 0) 383 #ifdef USE_SSL 384 port = use_ssl ? 465 : 25; 385 #else 386 port = 25; 387 #endif 388 389 uint32 hostIP = inet_addr(address); // first see if we can parse it as a numeric address 390 if ((hostIP == 0)||(hostIP == (uint32)-1)) { 391 struct hostent * he = gethostbyname(address); 392 hostIP = he ? *((uint32*)he->h_addr) : 0; 393 } 394 395 if (hostIP == 0) 396 return EHOSTUNREACH; 397 398 fSocket = socket(AF_INET, SOCK_STREAM, 0); 399 if (fSocket < 0) 400 return errno; 401 402 struct sockaddr_in saAddr; 403 memset(&saAddr, 0, sizeof(saAddr)); 404 saAddr.sin_family = AF_INET; 405 saAddr.sin_port = htons(port); 406 saAddr.sin_addr.s_addr = hostIP; 407 int result = connect(fSocket, (struct sockaddr *)&saAddr, 408 sizeof(saAddr)); 409 if (result < 0) { 410 close(fSocket); 411 fSocket = -1; 412 return errno; 413 } 414 415 #ifdef USE_SSL 416 if (use_ssl) { 417 SSL_library_init(); 418 SSL_load_error_strings(); 419 RAND_seed(this,sizeof(SMTPProtocol)); 420 /*--- Because we're an add-on loaded at an unpredictable time, all 421 the memory addresses and things contained in ourself are 422 esssentially random. */ 423 424 ctx = SSL_CTX_new(SSLv23_method()); 425 ssl = SSL_new(ctx); 426 sbio=BIO_new_socket(fSocket,BIO_NOCLOSE); 427 SSL_set_bio(ssl,sbio,sbio); 428 429 if (SSL_connect(ssl) <= 0) { 430 BString error; 431 error << "Could not connect to SMTP server " 432 << fSettingsMessage.FindString("server"); 433 if (port != 465) 434 error << ":" << port; 435 error << ". (SSL connection error)"; 436 ShowError(error.String()); 437 SSL_CTX_free(ctx); 438 close(fSocket); 439 fSocket = -1; 440 return B_ERROR; 441 } 442 } 443 #endif // USE_SSL 444 445 BString line; 446 ReceiveResponse(line); 447 448 char localhost[255]; 449 gethostname(localhost,255); 450 451 if (localhost[0] == 0) 452 strcpy(localhost,"namethisbebox"); 453 454 char *cmd = new char[::strlen(localhost)+8]; 455 if (!esmtp) 456 ::sprintf(cmd,"HELO %s" CRLF, localhost); 457 else 458 ::sprintf(cmd,"EHLO %s" CRLF, localhost); 459 460 if (SendCommand(cmd) != B_OK) { 461 delete[] cmd; 462 return B_ERROR; 463 } 464 465 #ifdef USE_SSL 466 // Check for STARTTLS 467 if (use_STARTTLS) { 468 const char *res = fLog.String(); 469 char *p; 470 471 SSL_library_init(); 472 RAND_seed(this,sizeof(SMTPProtocol)); 473 ::sprintf(cmd, "STARTTLS" CRLF); 474 475 if ((p = ::strstr(res, "STARTTLS")) != NULL) { 476 // Server advertises STARTTLS support 477 if (SendCommand(cmd) != B_OK) { 478 delete[] cmd; 479 return B_ERROR; 480 } 481 482 // We should start TLS negotiation 483 use_ssl = true; 484 ctx = SSL_CTX_new(TLSv1_method()); 485 ssl = SSL_new(ctx); 486 sbio = BIO_new_socket(fSocket,BIO_NOCLOSE); 487 BIO_set_nbio(sbio, 0); 488 SSL_set_bio(ssl, sbio, sbio); 489 SSL_set_connect_state(ssl); 490 if(SSL_do_handshake(ssl) != 1) 491 return B_ERROR; 492 493 // Should send EHLO command again 494 if(!esmtp) 495 ::sprintf(cmd, "HELO %s" CRLF, localhost); 496 else 497 ::sprintf(cmd, "EHLO %s" CRLF, localhost); 498 499 if (SendCommand(cmd) != B_OK) { 500 delete[] cmd; 501 return B_ERROR; 502 } 503 } 504 } 505 #endif // USE_SSL 506 507 delete[] cmd; 508 509 // Check auth type 510 if (esmtp) { 511 const char *res = fLog.String(); 512 char *p; 513 if ((p = ::strstr(res, "250-AUTH")) != NULL) { 514 if(::strstr(p, "LOGIN")) 515 fAuthType |= LOGIN; 516 if(::strstr(p, "PLAIN")) 517 fAuthType |= PLAIN; 518 if(::strstr(p, "CRAM-MD5")) 519 fAuthType |= CRAM_MD5; 520 if(::strstr(p, "DIGEST-MD5")) { 521 fAuthType |= DIGEST_MD5; 522 fServerName = address; 523 } 524 } 525 } 526 return B_OK; 527 } 528 529 530 status_t 531 SMTPProtocol::_SendMessage(const entry_ref& ref) 532 { 533 // open read write to be able to manipulate in MessageReadyToSend hook 534 BFile file(&ref, B_READ_WRITE); 535 status_t status = file.InitCheck(); 536 if (status != B_OK) 537 return status; 538 539 BMessage header; 540 file >> header; 541 542 const char *from = header.FindString("MAIL:from"); 543 const char *to = header.FindString("MAIL:recipients"); 544 if (to == NULL) 545 to = header.FindString("MAIL:to"); 546 547 if (to == NULL || from == NULL) { 548 fLog = "Invalid message headers"; 549 return B_ERROR; 550 } 551 552 NotifyMessageReadyToSend(ref, file); 553 status = Send(to, from, &file); 554 if (status != B_OK) 555 return status; 556 NotifyMessageSent(ref, file); 557 558 off_t size = 0; 559 file.GetSize(&size); 560 ReportProgress(size, 1); 561 562 return B_OK; 563 } 564 565 566 status_t 567 SMTPProtocol::_POP3Authentication() 568 { 569 const entry_ref& entry = fAccountSettings.InboundAddOnRef(); 570 if (strcmp(entry.name, "POP3") != 0) 571 return B_ERROR; 572 573 status_t (*pop3_smtp_auth)(const BMailAccountSettings&); 574 575 BPath path(&entry); 576 image_id image = load_add_on(path.Path()); 577 if (image < 0) 578 return B_ERROR; 579 if (get_image_symbol(image, "pop3_smtp_auth", 580 B_SYMBOL_TYPE_TEXT, (void **)&pop3_smtp_auth) != B_OK) { 581 unload_add_on(image); 582 image = -1; 583 return B_ERROR; 584 } 585 status_t status = (*pop3_smtp_auth)(fAccountSettings); 586 unload_add_on(image); 587 return status; 588 } 589 590 591 status_t 592 SMTPProtocol::Login(const char *_login, const char *password) 593 { 594 if (fAuthType == 0) 595 return B_OK; 596 597 const char *login = _login; 598 char hex_digest[33]; 599 BString out; 600 601 int32 loginlen = ::strlen(login); 602 int32 passlen = ::strlen(password); 603 604 if (fAuthType & DIGEST_MD5) { 605 //******* DIGEST-MD5 Authentication ( tested. works fine [with Cyrus SASL] ) 606 // this implements only the subpart of DIGEST-MD5 which is 607 // required for authentication to SMTP-servers. Integrity- 608 // and confidentiality-protection are not implemented, as 609 // they are provided by the use of OpenSSL. 610 SendCommand("AUTH DIGEST-MD5" CRLF); 611 const char *res = fLog.String(); 612 613 if (strncmp(res, "334", 3) != 0) 614 return B_ERROR; 615 int32 baselen = ::strlen(&res[4]); 616 char *base = new char[baselen+1]; 617 baselen = ::decode_base64(base, &res[4], baselen); 618 base[baselen] = '\0'; 619 620 D(bug("base: %s\n", base)); 621 622 map<BString,BString> challengeMap; 623 SplitChallengeIntoMap(base, challengeMap); 624 625 delete[] base; 626 627 BString rawResponse = BString("username=") << '"' << login << '"'; 628 rawResponse << ",realm=" << '"' << challengeMap["realm"] << '"'; 629 rawResponse << ",nonce=" << '"' << challengeMap["nonce"] << '"'; 630 rawResponse << ",nc=00000001"; 631 char temp[33]; 632 for( int i=0; i<32; ++i) 633 temp[i] = 1+(rand()%254); 634 temp[32] = '\0'; 635 BString rawCnonce(temp); 636 BString cnonce; 637 char* cnoncePtr = cnonce.LockBuffer(rawCnonce.Length()*2); 638 baselen = ::encode_base64(cnoncePtr, rawCnonce.String(), rawCnonce.Length(), true /* headerMode */); 639 cnoncePtr[baselen] = '\0'; 640 cnonce.UnlockBuffer(baselen); 641 rawResponse << ",cnonce=" << '"' << cnonce << '"'; 642 rawResponse << ",qop=auth"; 643 BString digestUriValue = BString("smtp/") << fServerName; 644 rawResponse << ",digest-uri=" << '"' << digestUriValue << '"'; 645 char sum[17], hex_digest2[33]; 646 BString a1,a2,kd; 647 BString t1 = BString(login) << ":" 648 << challengeMap["realm"] << ":" 649 << password; 650 MD5Sum(sum, (unsigned char*)t1.String(), t1.Length()); 651 a1 << sum << ":" << challengeMap["nonce"] << ":" << cnonce; 652 MD5Digest(hex_digest, (unsigned char*)a1.String(), a1.Length()); 653 a2 << "AUTHENTICATE:" << digestUriValue; 654 MD5Digest(hex_digest2, (unsigned char*)a2.String(), a2.Length()); 655 kd << hex_digest << ':' << challengeMap["nonce"] 656 << ":" << "00000001" << ':' << cnonce << ':' << "auth" 657 << ':' << hex_digest2; 658 MD5Digest(hex_digest, (unsigned char*)kd.String(), kd.Length()); 659 660 rawResponse << ",response=" << hex_digest; 661 BString postResponse; 662 char *resp = postResponse.LockBuffer(rawResponse.Length() * 2 + 10); 663 baselen = ::encode_base64(resp, rawResponse.String(), rawResponse.Length(), true /* headerMode */); 664 resp[baselen] = 0; 665 postResponse.UnlockBuffer(); 666 postResponse.Append(CRLF); 667 668 SendCommand(postResponse.String()); 669 670 res = fLog.String(); 671 if (atol(res) >= 500) 672 return B_ERROR; 673 // actually, we are supposed to check the rspauth sent back 674 // by the SMTP-server, but that isn't strictly required, 675 // so we skip that for now. 676 SendCommand(CRLF); // finish off authentication 677 res = fLog.String(); 678 if (atol(res) < 500) 679 return B_OK; 680 } 681 if (fAuthType & CRAM_MD5) { 682 //******* CRAM-MD5 Authentication ( tested. works fine [with Cyrus SASL] ) 683 SendCommand("AUTH CRAM-MD5" CRLF); 684 const char *res = fLog.String(); 685 686 if (strncmp(res, "334", 3) != 0) 687 return B_ERROR; 688 int32 baselen = ::strlen(&res[4]); 689 char *base = new char[baselen+1]; 690 baselen = ::decode_base64(base, &res[4], baselen); 691 base[baselen] = '\0'; 692 693 D(bug("base: %s\n", base)); 694 695 ::MD5HexHmac(hex_digest, (const unsigned char *)base, (int)baselen, 696 (const unsigned char *)password, (int)passlen); 697 698 D(bug("%s\n%s\n", base, hex_digest)); 699 700 delete[] base; 701 702 BString preResponse, postResponse; 703 preResponse = login; 704 preResponse << " " << hex_digest << CRLF; 705 char *resp = postResponse.LockBuffer(preResponse.Length() * 2 + 10); 706 baselen = ::encode_base64(resp, preResponse.String(), preResponse.Length(), true /* headerMode */); 707 resp[baselen] = 0; 708 postResponse.UnlockBuffer(); 709 postResponse.Append(CRLF); 710 711 SendCommand(postResponse.String()); 712 713 res = fLog.String(); 714 if (atol(res) < 500) 715 return B_OK; 716 } 717 if (fAuthType & LOGIN) { 718 //******* LOGIN Authentication ( tested. works fine) 719 ssize_t encodedsize; // required by our base64 implementation 720 721 SendCommand("AUTH LOGIN" CRLF); 722 const char *res = fLog.String(); 723 724 if (strncmp(res, "334", 3) != 0) 725 return B_ERROR; 726 727 // Send login name as base64 728 char *login64 = new char[loginlen*3 + 6]; 729 encodedsize = ::encode_base64(login64, (char *)login, loginlen, true /* headerMode */); 730 login64[encodedsize] = 0; 731 strcat (login64, CRLF); 732 SendCommand(login64); 733 delete[] login64; 734 735 res = fLog.String(); 736 if (strncmp(res,"334",3) != 0) 737 return B_ERROR; 738 739 // Send password as base64 740 login64 = new char[passlen*3 + 6]; 741 encodedsize = ::encode_base64(login64, (char *)password, passlen, true /* headerMode */); 742 login64[encodedsize] = 0; 743 strcat (login64, CRLF); 744 SendCommand(login64); 745 delete[] login64; 746 747 res = fLog.String(); 748 if (atol(res) < 500) 749 return B_OK; 750 } 751 if (fAuthType & PLAIN) { 752 //******* PLAIN Authentication ( tested. works fine [with Cyrus SASL] ) 753 // format is: 754 // authenticateID + \0 + username + \0 + password 755 // (where authenticateID is always empty !?!) 756 BString preResponse, postResponse; 757 char *stringPntr; 758 ssize_t encodedLength; 759 stringPntr = preResponse.LockBuffer(loginlen + passlen + 3); 760 // +3 to make room for the two \0-chars between the tokens and 761 // the final delimiter added by sprintf(). 762 sprintf (stringPntr, "%c%s%c%s", 0, login, 0, password); 763 preResponse.UnlockBuffer(loginlen + passlen + 2); 764 // +2 in order to leave out the final delimiter (which is not part 765 // of the string). 766 stringPntr = postResponse.LockBuffer(preResponse.Length() * 3); 767 encodedLength = ::encode_base64(stringPntr, preResponse.String(), 768 preResponse.Length(), true /* headerMode */); 769 stringPntr[encodedLength] = 0; 770 postResponse.UnlockBuffer(); 771 postResponse.Prepend("AUTH PLAIN "); 772 postResponse << CRLF; 773 774 SendCommand(postResponse.String()); 775 776 const char *res = fLog.String(); 777 if (atol(res) < 500) 778 return B_OK; 779 } 780 return B_ERROR; 781 } 782 783 784 void 785 SMTPProtocol::Close() 786 { 787 788 BString cmd = "QUIT"; 789 cmd += CRLF; 790 791 if (SendCommand(cmd.String()) != B_OK) { 792 // Error 793 } 794 795 #ifdef USE_SSL 796 if (use_ssl) { 797 if (ssl) 798 SSL_shutdown(ssl); 799 if (ctx) 800 SSL_CTX_free(ctx); 801 } 802 #endif 803 804 close(fSocket); 805 } 806 807 808 status_t 809 SMTPProtocol::Send(const char* to, const char* from, BPositionIO *message) 810 { 811 BString cmd = from; 812 cmd.Remove(0, cmd.FindFirst("\" <") + 2); 813 cmd.Prepend("MAIL FROM: "); 814 cmd += CRLF; 815 if (SendCommand(cmd.String()) != B_OK) 816 return B_ERROR; 817 818 int32 len = strlen(to); 819 BString addr(""); 820 for (int32 i = 0;i < len;i++) { 821 char c = to[i]; 822 if (c != ',') 823 addr += (char)c; 824 if (c == ','||i == len-1) { 825 if(addr.Length() == 0) 826 continue; 827 cmd = "RCPT TO: "; 828 cmd << addr.String() << CRLF; 829 if (SendCommand(cmd.String()) != B_OK) 830 return B_ERROR; 831 832 addr =""; 833 } 834 } 835 836 cmd = "DATA"; 837 cmd += CRLF; 838 if (SendCommand(cmd.String()) != B_OK) 839 return B_ERROR; 840 841 // Send the message data. Convert lines starting with a period to start 842 // with two periods and so on. The actual sequence is CR LF Period. The 843 // SMTP server will remove the periods. Of course, the POP server may then 844 // add some of its own, but the POP client should take care of them. 845 846 ssize_t amountRead; 847 ssize_t amountToRead; 848 ssize_t amountUnread; 849 ssize_t bufferLen = 0; 850 const int bufferMax = 2000; 851 bool foundCRLFPeriod; 852 int i; 853 bool messageEndedWithCRLF = false; 854 855 message->Seek(0, SEEK_END); 856 amountUnread = message->Position(); 857 message->Seek(0, SEEK_SET); 858 char *data = new char[bufferMax]; 859 860 while (true) { 861 // Fill the buffer if it is getting low, but not every time, to avoid 862 // small reads. 863 if (bufferLen < bufferMax / 2) { 864 amountToRead = bufferMax - bufferLen; 865 if (amountToRead > amountUnread) 866 amountToRead = amountUnread; 867 if (amountToRead > 0) { 868 amountRead = message->Read (data + bufferLen, amountToRead); 869 if (amountRead <= 0 || amountRead > amountToRead) 870 amountUnread = 0; // Just stop reading when an error happens. 871 else { 872 amountUnread -= amountRead; 873 bufferLen += amountRead; 874 } 875 } 876 } 877 878 // Look for the next CRLFPeriod triple. 879 foundCRLFPeriod = false; 880 for (i = 0; i <= bufferLen - 3; i++) { 881 if (data[i] == '\r' && data[i+1] == '\n' && data[i+2] == '.') { 882 foundCRLFPeriod = true; 883 // Send data up to the CRLF, and include the period too. 884 #ifdef USE_SSL 885 if (use_ssl) { 886 if (SSL_write(ssl,data,i + 3) < 0) { 887 amountUnread = 0; // Stop when an error happens. 888 bufferLen = 0; 889 break; 890 } 891 } else 892 #endif 893 if (send (fSocket,data, i + 3,0) < 0) { 894 amountUnread = 0; // Stop when an error happens. 895 bufferLen = 0; 896 break; 897 } 898 ReportProgress (i + 2 /* Don't include the double period here */,0); 899 // Move the data over in the buffer, but leave the period there 900 // so it gets sent a second time. 901 memmove(data, data + (i + 2), bufferLen - (i + 2)); 902 bufferLen -= i + 2; 903 break; 904 } 905 } 906 907 if (!foundCRLFPeriod) { 908 if (amountUnread <= 0) { // No more data, all we have is in the buffer. 909 if (bufferLen > 0) { 910 #ifdef USE_SSL 911 if (use_ssl) 912 SSL_write(ssl, data, bufferLen); 913 else 914 send(fSocket, data, bufferLen, 0); 915 #else 916 send(fSocket, data, bufferLen, 0); 917 #endif 918 ReportProgress(bufferLen, 0); 919 if (bufferLen >= 2) 920 messageEndedWithCRLF = (data[bufferLen-2] == '\r' && 921 data[bufferLen-1] == '\n'); 922 } 923 break; // Finished! 924 } 925 926 // Send most of the buffer, except a few characters to overlap with 927 // the next read, in case the CRLFPeriod is split between reads. 928 if (bufferLen > 3) { 929 #ifdef USE_SSL 930 if (use_ssl) { 931 if (SSL_write(ssl, data, bufferLen - 3) < 0) 932 break; 933 } else { 934 if (send(fSocket, data, bufferLen - 3, 0) < 0) 935 break; // Stop when an error happens. 936 } 937 #else 938 if (send(fSocket, data, bufferLen - 3, 0) < 0) 939 break; // Stop when an error happens. 940 #endif 941 ReportProgress(bufferLen - 3, 0); 942 memmove (data, data + bufferLen - 3, 3); 943 bufferLen = 3; 944 } 945 } 946 } 947 delete [] data; 948 949 if (messageEndedWithCRLF) 950 cmd = "." CRLF; // The standard says don't add extra CRLF. 951 else 952 cmd = CRLF "." CRLF; 953 954 if (SendCommand(cmd.String()) != B_OK) 955 return B_ERROR; 956 957 return B_OK; 958 } 959 960 961 //! Receives response from server. 962 int32 963 SMTPProtocol::ReceiveResponse(BString &out) 964 { 965 out = ""; 966 int32 len = 0,r; 967 char buf[SMTP_RESPONSE_SIZE]; 968 bigtime_t timeout = 1000000*180; // timeout 180 secs 969 bool gotCode = false; 970 int32 errCode; 971 BString searchStr = ""; 972 973 struct timeval tv; 974 struct fd_set fds; 975 976 tv.tv_sec = long(timeout / 1e6); 977 tv.tv_usec = long(timeout-(tv.tv_sec * 1e6)); 978 979 /* Initialize (clear) the socket mask. */ 980 FD_ZERO(&fds); 981 982 /* Set the socket in the mask. */ 983 FD_SET(fSocket, &fds); 984 int result = -1; 985 #ifdef USE_SSL 986 if ((use_ssl) && (SSL_pending(ssl))) 987 result = 1; 988 else 989 #endif 990 result = select(1 + fSocket, &fds, NULL, NULL, &tv); 991 if (result < 0) 992 return errno; 993 994 if (result > 0) { 995 while (1) { 996 #ifdef USE_SSL 997 if (use_ssl) 998 r = SSL_read(ssl,buf,SMTP_RESPONSE_SIZE - 1); 999 else 1000 #endif 1001 r = recv(fSocket,buf, SMTP_RESPONSE_SIZE - 1,0); 1002 if (r <= 0) 1003 break; 1004 1005 if (!gotCode) 1006 { 1007 if (buf[3] == ' ' || buf[3] == '-') 1008 { 1009 errCode = atol(buf); 1010 gotCode = true; 1011 searchStr << errCode << ' '; 1012 } 1013 } 1014 1015 len += r; 1016 out.Append(buf, r); 1017 1018 if (strstr(buf, CRLF) && (out.FindFirst(searchStr) != B_ERROR)) 1019 break; 1020 } 1021 } else 1022 fLog = "SMTP socket timeout."; 1023 1024 D(bug("S:%s\n", out.String())); 1025 return len; 1026 } 1027 1028 1029 // Sends SMTP command. Result kept in fLog 1030 1031 status_t 1032 SMTPProtocol::SendCommand(const char *cmd) 1033 { 1034 D(bug("C:%s\n", cmd)); 1035 1036 #ifdef USE_SSL 1037 if (use_ssl && ssl) { 1038 if (SSL_write(ssl,cmd,::strlen(cmd)) < 0) 1039 return B_ERROR; 1040 } else 1041 #endif 1042 if (send(fSocket,cmd, ::strlen(cmd),0) < 0) 1043 return B_ERROR; 1044 fLog = ""; 1045 1046 // Receive 1047 while (1) { 1048 int32 len = ReceiveResponse(fLog); 1049 1050 if (len <= 0) { 1051 D(bug("SMTP: len == %" B_PRId32 "\n", len)); 1052 return B_ERROR; 1053 } 1054 1055 if (fLog.Length() > 4 && (fLog[3] == ' ' || fLog[3] == '-')) 1056 { 1057 int32 num = atol(fLog.String()); 1058 D(bug("ReplyNumber: %" B_PRId32 "\n", num)); 1059 if (num >= 500) 1060 return B_ERROR; 1061 1062 break; 1063 } 1064 } 1065 1066 return B_OK; 1067 } 1068 1069 1070 // #pragma mark - 1071 1072 1073 extern "C" BOutboundMailProtocol* 1074 instantiate_outbound_protocol(const BMailAccountSettings& settings) 1075 { 1076 return new SMTPProtocol(settings); 1077 } 1078