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 //! POP3Protocol - implementation of the POP3 protocol 11 12 13 #include "POP3.h" 14 15 #include <errno.h> 16 #include <netdb.h> 17 #include <stdio.h> 18 #include <stdlib.h> 19 #include <sys/socket.h> 20 #include <sys/time.h> 21 #include <unistd.h> 22 23 #include <arpa/inet.h> 24 25 #if USE_SSL 26 #include <openssl/md5.h> 27 #else 28 #include "md5.h" 29 #endif 30 31 #include <Alert.h> 32 #include <Catalog.h> 33 #include <Debug.h> 34 #include <Directory.h> 35 #include <fs_attr.h> 36 #include <Path.h> 37 #include <SecureSocket.h> 38 #include <String.h> 39 #include <VolumeRoster.h> 40 #include <Query.h> 41 42 #include <mail_util.h> 43 44 #include "crypt.h" 45 #include "MailSettings.h" 46 #include "MessageIO.h" 47 48 49 #undef B_TRANSLATION_CONTEXT 50 #define B_TRANSLATION_CONTEXT "pop3" 51 52 53 #define POP3_RETRIEVAL_TIMEOUT 60000000 54 #define CRLF "\r\n" 55 56 57 static void 58 NotHere(BStringList& that, BStringList& otherList, BStringList* results) 59 { 60 for (int32 i = 0; i < otherList.CountStrings(); i++) { 61 if (!that.HasString(otherList.StringAt(i))) 62 results->Add(otherList.StringAt(i)); 63 } 64 } 65 66 67 // #pragma mark - 68 69 70 POP3Protocol::POP3Protocol(const BMailAccountSettings& settings) 71 : 72 BInboundMailProtocol("POP3", settings), 73 fNumMessages(-1), 74 fMailDropSize(0), 75 fServerConnection(NULL) 76 { 77 printf("POP3Protocol::POP3Protocol(BMailAccountSettings* settings)\n"); 78 fSettings = fAccountSettings.InboundSettings(); 79 80 fUseSSL = fSettings.FindInt32("flavor") == 1 ? true : false; 81 82 if (fSettings.FindString("destination", &fDestinationDir) != B_OK) 83 fDestinationDir = "/boot/home/mail/in"; 84 85 create_directory(fDestinationDir, 0777); 86 87 fFetchBodyLimit = -1; 88 if (fSettings.HasInt32("partial_download_limit")) 89 fFetchBodyLimit = fSettings.FindInt32("partial_download_limit"); 90 } 91 92 93 POP3Protocol::~POP3Protocol() 94 { 95 Disconnect(); 96 } 97 98 99 status_t 100 POP3Protocol::Connect() 101 { 102 status_t error = Open(fSettings.FindString("server"), 103 fSettings.FindInt32("port"), fSettings.FindInt32("flavor")); 104 if (error != B_OK) 105 return error; 106 107 char* password = get_passwd(&fSettings, "cpasswd"); 108 109 error = Login(fSettings.FindString("username"), password, 110 fSettings.FindInt32("auth_method")); 111 delete[] password; 112 113 if (error != B_OK) 114 fServerConnection->Disconnect(); 115 return error; 116 } 117 118 119 status_t 120 POP3Protocol::Disconnect() 121 { 122 if (fServerConnection == NULL) 123 return B_OK; 124 125 SendCommand("QUIT" CRLF); 126 127 fServerConnection->Disconnect(); 128 delete fServerConnection; 129 fServerConnection = NULL; 130 131 return B_OK; 132 } 133 134 135 status_t 136 POP3Protocol::SyncMessages() 137 { 138 bool leaveOnServer; 139 if (fSettings.FindBool("leave_mail_on_server", &leaveOnServer) != B_OK) 140 leaveOnServer = true; 141 142 // create directory if not exist 143 create_directory(fDestinationDir, 0777); 144 145 printf("POP3Protocol::SyncMessages()\n"); 146 _ReadManifest(); 147 148 SetTotalItems(2); 149 ReportProgress(1, 0, B_TRANSLATE("Connect to server" B_UTF8_ELLIPSIS)); 150 151 status_t error = Connect(); 152 if (error != B_OK) { 153 printf("POP3 could not connect: %s\n", strerror(error)); 154 ResetProgress(); 155 return error; 156 } 157 158 ReportProgress(1, 0, B_TRANSLATE("Getting UniqueIDs" B_UTF8_ELLIPSIS)); 159 160 error = _RetrieveUniqueIDs(); 161 if (error < B_OK) { 162 ResetProgress(); 163 Disconnect(); 164 return error; 165 } 166 167 BStringList toDownload; 168 NotHere(fManifest, fUniqueIDs, &toDownload); 169 170 int32 numMessages = toDownload.CountStrings(); 171 if (numMessages == 0) { 172 CheckForDeletedMessages(); 173 ResetProgress(); 174 Disconnect(); 175 return B_OK; 176 } 177 178 ResetProgress(); 179 SetTotalItems(toDownload.CountStrings()); 180 SetTotalItemsSize(fTotalSize); 181 182 printf("POP3: Messages to download: %i\n", (int)toDownload.CountStrings()); 183 for (int32 i = 0; i < toDownload.CountStrings(); i++) { 184 const char* uid = toDownload.StringAt(i); 185 int32 toRetrieve = fUniqueIDs.IndexOf(uid); 186 187 if (toRetrieve < 0) { 188 // should not happen! 189 error = B_NAME_NOT_FOUND; 190 printf("POP3: uid %s index %i not found in fUniqueIDs!\n", uid, 191 (int)toRetrieve); 192 continue; 193 } 194 195 BPath path(fDestinationDir); 196 BString fileName = "Downloading file... uid: "; 197 fileName += uid; 198 fileName.ReplaceAll("/", "_SLASH_"); 199 path.Append(fileName); 200 BEntry entry(path.Path()); 201 BFile file(&entry, B_READ_WRITE | B_CREATE_FILE | B_ERASE_FILE); 202 error = file.InitCheck(); 203 if (error != B_OK) { 204 printf("POP3: Can't create file %s\n ", path.Path()); 205 break; 206 } 207 BMailMessageIO mailIO(this, &file, toRetrieve); 208 BMessage attributes; 209 210 entry_ref ref; 211 entry.GetRef(&ref); 212 213 int32 size = MessageSize(toRetrieve); 214 if (fFetchBodyLimit < 0 || size <= fFetchBodyLimit) { 215 error = mailIO.Seek(0, SEEK_END); 216 if (error < 0) { 217 printf("POP3: Failed to download body %s\n ", uid); 218 break; 219 } 220 ProcessMessageFetched(ref, file, attributes); 221 222 if (!leaveOnServer) 223 Delete(toRetrieve); 224 } else { 225 int32 dummy; 226 error = mailIO.ReadAt(0, &dummy, 1); 227 if (error < 0) { 228 printf("POP3: Failed to download header %s\n ", uid); 229 break; 230 } 231 ProcessHeaderFetched(ref, file, attributes); 232 } 233 ReportProgress(1, 0); 234 235 if (file.WriteAttr("MAIL:unique_id", B_STRING_TYPE, 0, uid, 236 strlen(uid)) < 0) 237 error = B_ERROR; 238 239 file.WriteAttr("MAIL:size", B_INT32_TYPE, 0, &size, sizeof(int32)); 240 write_read_attr(file, B_UNREAD); 241 242 // save manifest in case we get disturbed 243 fManifest.Add(uid); 244 _WriteManifest(); 245 } 246 247 ResetProgress(); 248 249 CheckForDeletedMessages(); 250 Disconnect(); 251 return error; 252 } 253 254 255 status_t 256 POP3Protocol::FetchBody(const entry_ref& ref) 257 { 258 ResetProgress("Fetch body"); 259 SetTotalItems(1); 260 261 status_t error = Connect(); 262 if (error != B_OK) 263 return error; 264 265 error = _RetrieveUniqueIDs(); 266 if (error != B_OK) { 267 Disconnect(); 268 return error; 269 } 270 271 BFile file(&ref, B_READ_WRITE); 272 status_t status = file.InitCheck(); 273 if (status != B_OK) { 274 Disconnect(); 275 return status; 276 } 277 278 char uidString[256]; 279 BNode node(&ref); 280 if (node.ReadAttr("MAIL:unique_id", B_STRING_TYPE, 0, uidString, 256) < 0) { 281 Disconnect(); 282 return B_ERROR; 283 } 284 285 int32 toRetrieve = fUniqueIDs.IndexOf(uidString); 286 if (toRetrieve < 0) { 287 Disconnect(); 288 return B_NAME_NOT_FOUND; 289 } 290 291 bool leaveOnServer; 292 if (fSettings.FindBool("leave_mail_on_server", &leaveOnServer) != B_OK) 293 leaveOnServer = true; 294 295 // TODO: get rid of this BMailMessageIO! 296 BMailMessageIO io(this, &file, toRetrieve); 297 // read body 298 status = io.Seek(0, SEEK_END); 299 if (status < 0) { 300 Disconnect(); 301 return status; 302 } 303 304 BMessage attributes; 305 NotifyBodyFetched(ref, file, attributes); 306 307 if (!leaveOnServer) 308 Delete(toRetrieve); 309 310 ReportProgress(1, 0); 311 ResetProgress(); 312 313 Disconnect(); 314 return B_OK; 315 } 316 317 318 status_t 319 POP3Protocol::DeleteMessage(const entry_ref& ref) 320 { 321 status_t error = Connect(); 322 if (error < B_OK) 323 return error; 324 325 error = _RetrieveUniqueIDs(); 326 if (error < B_OK) { 327 Disconnect(); 328 return error; 329 } 330 331 char uidString[256]; 332 BNode node(&ref); 333 if (node.ReadAttr("MAIL:unique_id", B_STRING_TYPE, 0, uidString, 256) < 0) { 334 Disconnect(); 335 return B_ERROR; 336 } 337 338 #if DEBUG 339 printf("DeleteMessage: ID is %d\n", (int)fUniqueIDs.IndexOf(uidString)); 340 // What should we use for int32 instead of %d? 341 #endif 342 Delete(fUniqueIDs.IndexOf(uidString)); 343 344 Disconnect(); 345 return B_OK; 346 } 347 348 349 status_t 350 POP3Protocol::Open(const char* server, int port, int) 351 { 352 ReportProgress(0, 0, B_TRANSLATE("Connecting to POP3 server" 353 B_UTF8_ELLIPSIS)); 354 355 if (port <= 0) 356 port = fUseSSL ? 995 : 110; 357 358 fLog = ""; 359 360 // Prime the error message 361 BString errorMessage(B_TRANSLATE("Error while connecting to server %serv")); 362 errorMessage.ReplaceFirst("%serv", server); 363 if (port != 110) 364 errorMessage << ":" << port; 365 366 delete fServerConnection; 367 fServerConnection = NULL; 368 369 BNetworkAddress address(server, port); 370 if (fUseSSL) 371 fServerConnection = new(std::nothrow) BSecureSocket(address); 372 else 373 fServerConnection = new(std::nothrow) BSocket(address); 374 375 status_t status = B_NO_MEMORY; 376 if (fServerConnection != NULL) 377 status = fServerConnection->InitCheck(); 378 379 BString line; 380 if (status == B_OK) { 381 ssize_t length = ReceiveLine(line); 382 if (length < 0) 383 status = length; 384 } 385 386 if (status != B_OK) { 387 fServerConnection->Disconnect(); 388 errorMessage << ": " << strerror(status); 389 ShowError(errorMessage.String()); 390 return status; 391 } 392 393 if (strncmp(line.String(), "+OK", 3) != 0) { 394 if (line.Length() > 0) { 395 errorMessage << B_TRANSLATE(". The server said:\n") 396 << line.String(); 397 } else 398 errorMessage << B_TRANSLATE(": No reply.\n"); 399 400 ShowError(errorMessage.String()); 401 fServerConnection->Disconnect(); 402 return B_ERROR; 403 } 404 405 fLog = line; 406 return B_OK; 407 } 408 409 410 status_t 411 POP3Protocol::Login(const char* uid, const char* password, int method) 412 { 413 status_t err; 414 415 BString errorMessage(B_TRANSLATE("Error while authenticating user %user")); 416 errorMessage.ReplaceFirst("%user", uid); 417 418 if (method == 1) { //APOP 419 int32 index = fLog.FindFirst("<"); 420 if(index != B_ERROR) { 421 ReportProgress(0, 0, B_TRANSLATE("Sending APOP authentication" 422 B_UTF8_ELLIPSIS)); 423 int32 end = fLog.FindFirst(">", index); 424 BString timestamp(""); 425 fLog.CopyInto(timestamp, index, end - index + 1); 426 timestamp += password; 427 char md5sum[33]; 428 MD5Digest((unsigned char*)timestamp.String(), md5sum); 429 BString cmd = "APOP "; 430 cmd += uid; 431 cmd += " "; 432 cmd += md5sum; 433 cmd += CRLF; 434 435 err = SendCommand(cmd.String()); 436 if (err != B_OK) { 437 errorMessage << B_TRANSLATE(". The server said:\n") << fLog; 438 ShowError(errorMessage.String()); 439 return err; 440 } 441 442 return B_OK; 443 } else { 444 errorMessage << B_TRANSLATE(": The server does not support APOP."); 445 ShowError(errorMessage.String()); 446 return B_NOT_ALLOWED; 447 } 448 } 449 ReportProgress(0, 0, B_TRANSLATE("Sending username" B_UTF8_ELLIPSIS)); 450 451 BString cmd = "USER "; 452 cmd += uid; 453 cmd += CRLF; 454 455 err = SendCommand(cmd.String()); 456 if (err != B_OK) { 457 errorMessage << B_TRANSLATE(". The server said:\n") << fLog; 458 ShowError(errorMessage.String()); 459 return err; 460 } 461 462 ReportProgress(0, 0, B_TRANSLATE("Sending password" B_UTF8_ELLIPSIS)); 463 cmd = "PASS "; 464 cmd += password; 465 cmd += CRLF; 466 467 err = SendCommand(cmd.String()); 468 if (err != B_OK) { 469 errorMessage << B_TRANSLATE(". The server said:\n") << fLog; 470 ShowError(errorMessage.String()); 471 return err; 472 } 473 474 return B_OK; 475 } 476 477 478 status_t 479 POP3Protocol::Stat() 480 { 481 ReportProgress(0, 0, B_TRANSLATE("Getting mailbox size" B_UTF8_ELLIPSIS)); 482 483 if (SendCommand("STAT" CRLF) < B_OK) 484 return B_ERROR; 485 486 int32 messages; 487 int32 dropSize; 488 if (sscanf(fLog.String(), "+OK %" B_SCNd32" %" B_SCNd32, &messages, 489 &dropSize) < 2) 490 return B_ERROR; 491 492 fNumMessages = messages; 493 fMailDropSize = dropSize; 494 495 return B_OK; 496 } 497 498 499 int32 500 POP3Protocol::Messages() 501 { 502 if (fNumMessages < 0) 503 Stat(); 504 505 return fNumMessages; 506 } 507 508 509 size_t 510 POP3Protocol::MailDropSize() 511 { 512 if (fNumMessages < 0) 513 Stat(); 514 515 return fMailDropSize; 516 } 517 518 519 void 520 POP3Protocol::CheckForDeletedMessages() 521 { 522 { 523 // Delete things from the manifest no longer on the server 524 BStringList list; 525 NotHere(fUniqueIDs, fManifest, &list); 526 fManifest.Remove(list); 527 } 528 529 if (!fSettings.FindBool("delete_remote_when_local") 530 || fManifest.CountStrings() == 0) 531 return; 532 533 BStringList toDelete; 534 535 BStringList queryContents; 536 BVolumeRoster volumes; 537 BVolume volume; 538 539 while (volumes.GetNextVolume(&volume) == B_OK) { 540 BQuery fido; 541 entry_ref entry; 542 543 fido.SetVolume(&volume); 544 fido.PushAttr(B_MAIL_ATTR_ACCOUNT_ID); 545 fido.PushInt32(fAccountSettings.AccountID()); 546 fido.PushOp(B_EQ); 547 548 fido.Fetch(); 549 550 BString uid; 551 while (fido.GetNextRef(&entry) == B_OK) { 552 BNode(&entry).ReadAttrString("MAIL:unique_id", &uid); 553 queryContents.Add(uid); 554 } 555 } 556 NotHere(queryContents, fManifest, &toDelete); 557 558 for (int32 i = 0; i < toDelete.CountStrings(); i++) { 559 printf("delete mail on server uid %s\n", toDelete.StringAt(i).String()); 560 Delete(fUniqueIDs.IndexOf(toDelete.StringAt(i))); 561 } 562 563 // Don't remove ids from fUniqueIDs, the indices have to stay the same when 564 // retrieving new messages. 565 fManifest.Remove(toDelete); 566 567 // TODO: at some point the purged manifest should be written to disk 568 // otherwise it will grow forever 569 } 570 571 572 status_t 573 POP3Protocol::Retrieve(int32 message, BPositionIO* to) 574 { 575 BString cmd; 576 cmd << "RETR " << message + 1 << CRLF; 577 status_t status = RetrieveInternal(cmd.String(), message, to, true); 578 ReportProgress(1, 0); 579 580 if (status == B_OK) { 581 // Check if the actual message size matches the expected one 582 int32 size = MessageSize(message); 583 to->Seek(0, SEEK_END); 584 if (to->Position() != size) { 585 printf("POP3Protocol::Retrieve Note: message size is %" B_PRIdOFF 586 ", was expecting %" B_PRId32 ", for message #%" B_PRId32 587 ". Could be a transmission error or a bad POP server " 588 "implementation (does it remove escape codes when it counts " 589 "size?).\n", to->Position(), size, message); 590 } 591 } 592 593 return status; 594 } 595 596 597 status_t 598 POP3Protocol::GetHeader(int32 message, BPositionIO* to) 599 { 600 BString cmd; 601 cmd << "TOP " << message + 1 << " 0" << CRLF; 602 return RetrieveInternal(cmd.String(), message, to, false); 603 } 604 605 606 status_t 607 POP3Protocol::RetrieveInternal(const char* command, int32 message, 608 BPositionIO* to, bool postProgress) 609 { 610 const int bufSize = 1024 * 30; 611 612 // To avoid waiting for the non-arrival of the next data packet, try to 613 // receive only the message size, plus the 3 extra bytes for the ".\r\n" 614 // after the message. Of course, if we get it wrong (or it is a huge 615 // message or has lines starting with escaped periods), it will then switch 616 // back to receiving full buffers until the message is done. 617 int amountToReceive = MessageSize(message) + 3; 618 if (amountToReceive >= bufSize || amountToReceive <= 0) 619 amountToReceive = bufSize - 1; 620 621 BString bufBString; // Used for auto-dealloc on return feature. 622 char* buf = bufBString.LockBuffer(bufSize); 623 int amountInBuffer = 0; 624 int amountReceived; 625 int testIndex; 626 char* testStr; 627 bool cont = true; 628 bool flushWholeBuffer = false; 629 to->Seek(0, SEEK_SET); 630 631 if (SendCommand(command) != B_OK) 632 return B_ERROR; 633 634 while (cont) { 635 status_t result = fServerConnection->WaitForReadable( 636 POP3_RETRIEVAL_TIMEOUT); 637 if (result == B_TIMED_OUT) { 638 // No data available, even after waiting a minute. 639 fLog = "POP3 timeout - no data received after a long wait."; 640 return B_ERROR; 641 } 642 if (amountToReceive > bufSize - 1 - amountInBuffer) 643 amountToReceive = bufSize - 1 - amountInBuffer; 644 645 amountReceived = fServerConnection->Read(buf + amountInBuffer, 646 amountToReceive); 647 648 if (amountReceived < 0) { 649 fLog = strerror(amountReceived); 650 return amountReceived; 651 } 652 if (amountReceived == 0) { 653 fLog = "POP3 data supposedly ready to receive but not received!"; 654 return B_ERROR; 655 } 656 657 amountToReceive = bufSize - 1; // For next time, read a full buffer. 658 amountInBuffer += amountReceived; 659 buf[amountInBuffer] = 0; // NUL stops tests past the end of buffer. 660 661 // Look for lines starting with a period. A single period by itself on 662 // a line "\r\n.\r\n" marks the end of the message (thus the need for 663 // at least five characters in the buffer for testing). A period 664 // "\r\n.Stuff" at the start of a line get deleted "\r\nStuff", since 665 // POP adds one as an escape code to let you have message text with 666 // lines starting with a period. For convenience, assume that no 667 // messages start with a period on the very first line, so we can 668 // search for the previous line's "\r\n". 669 670 for (testIndex = 0; testIndex <= amountInBuffer - 5; testIndex++) { 671 testStr = buf + testIndex; 672 if (testStr[0] == '\r' && testStr[1] == '\n' && testStr[2] == '.') { 673 if (testStr[3] == '\r' && testStr[4] == '\n') { 674 // Found the end of the message marker. 675 // Ignore remaining data. 676 if (amountInBuffer > testIndex + 5) { 677 printf("POP3Protocol::RetrieveInternal Ignoring %d " 678 "bytes of extra data past message end.\n", 679 amountInBuffer - (testIndex + 5)); 680 } 681 amountInBuffer = testIndex + 2; // Don't include ".\r\n". 682 buf[amountInBuffer] = 0; 683 cont = false; 684 } else { 685 // Remove an extra period at the start of a line. 686 // Inefficient, but it doesn't happen often that you have a 687 // dot starting a line of text. Of course, a file with a 688 // lot of double period lines will get processed very 689 // slowly. 690 memmove(buf + testIndex + 2, buf + testIndex + 3, 691 amountInBuffer - (testIndex + 3) + 1); 692 amountInBuffer--; 693 // Watch out for the end of buffer case, when the POP text 694 // is "\r\n..X". Don't want to leave the resulting 695 // "\r\n.X" in the buffer (flush out the whole buffer), 696 // since that will get mistakenly evaluated again in the 697 // next loop and delete a character by mistake. 698 if (testIndex >= amountInBuffer - 4 && testStr[2] == '.') { 699 printf("POP3Protocol::RetrieveInternal: Jackpot! " 700 "You have hit the rare situation with an escaped " 701 "period at the end of the buffer. Aren't you happy" 702 "it decodes it correctly?\n"); 703 flushWholeBuffer = true; 704 } 705 } 706 } 707 } 708 709 if (cont && !flushWholeBuffer) { 710 // Dump out most of the buffer, but leave the last 4 characters for 711 // comparison continuity, in case the line starting with a period 712 // crosses a buffer boundary. 713 if (amountInBuffer > 4) { 714 to->Write(buf, amountInBuffer - 4); 715 if (postProgress) 716 ReportProgress(0, amountInBuffer - 4); 717 memmove(buf, buf + amountInBuffer - 4, 4); 718 amountInBuffer = 4; 719 } 720 } else { 721 // Dump everything - end of message or flushing the whole buffer. 722 to->Write(buf, amountInBuffer); 723 if (postProgress) 724 ReportProgress(0, amountInBuffer); 725 amountInBuffer = 0; 726 } 727 } 728 return B_OK; 729 } 730 731 732 void 733 POP3Protocol::Delete(int32 index) 734 { 735 BString cmd = "DELE "; 736 cmd << (index + 1) << CRLF; 737 if (SendCommand(cmd.String()) != B_OK) { 738 // Error 739 } 740 #if DEBUG 741 puts(fLog.String()); 742 #endif 743 // The mail is just marked as deleted and removed from the server when 744 // sending the QUIT command. Because of that the message number stays 745 // the same and we keep the uid in the uid list. 746 } 747 748 749 size_t 750 POP3Protocol::MessageSize(int32 index) 751 { 752 return fSizes[index]; 753 } 754 755 756 ssize_t 757 POP3Protocol::ReceiveLine(BString& line) 758 { 759 int32 length = 0; 760 bool flag = false; 761 762 line = ""; 763 764 status_t result = fServerConnection->WaitForReadable( 765 POP3_RETRIEVAL_TIMEOUT); 766 if (result == B_TIMED_OUT) 767 return errno; 768 769 while (true) { 770 // Hope there's an end of line out there else this gets stuck. 771 int32 bytesReceived; 772 uint8 c = 0; 773 774 bytesReceived = fServerConnection->Read((char*)&c, 1); 775 if (bytesReceived < 0) 776 return bytesReceived; 777 778 if (c == '\n' || bytesReceived == 0) 779 break; 780 781 if (c == '\r') { 782 flag = true; 783 } else { 784 if (flag) { 785 length++; 786 line += '\r'; 787 flag = false; 788 } 789 length++; 790 line += (char)c; 791 } 792 } 793 794 return length; 795 } 796 797 798 status_t 799 POP3Protocol::SendCommand(const char* cmd) 800 { 801 // Flush any accumulated garbage data before we send our command, so we 802 // don't misinterrpret responses from previous commands (that got left over 803 // due to bugs) as being from this command. 804 while (fServerConnection->WaitForReadable(1000) == B_OK) { 805 char buffer[4096]; 806 ssize_t amountReceived = fServerConnection->Read(buffer, 807 sizeof(buffer) - 1); 808 if (amountReceived < 0) 809 return amountReceived; 810 811 buffer[amountReceived] = 0; 812 printf("POP3Protocol::SendCommand Bug! Had to flush %" B_PRIdSSIZE 813 " bytes: %s\n", amountReceived, buffer); 814 } 815 816 if (fServerConnection->Write(cmd, ::strlen(cmd)) < 0) { 817 fLog = strerror(errno); 818 printf("POP3Protocol::SendCommand Send \"%s\" failed, code %d: %s\n", 819 cmd, errno, fLog.String()); 820 return errno; 821 } 822 823 fLog = ""; 824 int32 length = ReceiveLine(fLog); 825 if (length <= 0 || fLog.ICompare("+OK", 3) == 0) 826 return B_OK; 827 828 if (fLog.ICompare("-ERR", 4) == 0) { 829 printf("POP3Protocol::SendCommand \"%s\" got error message " 830 "from server: %s\n", cmd, fLog.String()); 831 return B_ERROR; 832 } 833 834 printf("POP3Protocol::SendCommand \"%s\" got nonsense message " 835 "from server: %s\n", cmd, fLog.String()); 836 return B_BAD_DATA; 837 // If it's not +OK, and it's not -ERR, then what the heck 838 // is it? Presume an error 839 } 840 841 842 void 843 POP3Protocol::MD5Digest(unsigned char* in, char* asciiDigest) 844 { 845 unsigned char digest[16]; 846 847 #ifdef USE_SSL 848 MD5(in, ::strlen((char*)in), digest); 849 #else 850 MD5_CTX context; 851 852 MD5Init(&context); 853 MD5Update(&context, in, ::strlen((char*)in)); 854 MD5Final(digest, &context); 855 #endif 856 857 for (int i = 0; i < 16; i++) { 858 sprintf(asciiDigest + 2 * i, "%02x", digest[i]); 859 } 860 861 return; 862 } 863 864 865 status_t 866 POP3Protocol::_RetrieveUniqueIDs() 867 { 868 fUniqueIDs.MakeEmpty(); 869 fSizes.clear(); 870 fTotalSize = 0; 871 872 status_t status = SendCommand("UIDL" CRLF); 873 if (status != B_OK) 874 return status; 875 876 BString result; 877 int32 uidOffset; 878 while (ReceiveLine(result) > 0) { 879 if (result.ByteAt(0) == '.') 880 break; 881 882 uidOffset = result.FindFirst(' ') + 1; 883 result.Remove(0, uidOffset); 884 fUniqueIDs.Add(result); 885 } 886 887 if (SendCommand("LIST" CRLF) != B_OK) 888 return B_ERROR; 889 890 while (ReceiveLine(result) > 0) { 891 if (result.ByteAt(0) == '.') 892 break; 893 894 int32 index = result.FindLast(" "); 895 int32 size; 896 if (index >= 0) 897 size = atol(&result.String()[index]); 898 else 899 size = 0; 900 901 fTotalSize += size; 902 fSizes.push_back(size); 903 } 904 905 return B_OK; 906 } 907 908 909 void 910 POP3Protocol::_ReadManifest() 911 { 912 fManifest.MakeEmpty(); 913 BString attribute = "MAIL:"; 914 attribute << fAccountSettings.AccountID() << ":manifest"; 915 // In case someone puts multiple accounts in the same directory 916 917 BNode node(fDestinationDir); 918 if (node.InitCheck() != B_OK) 919 return; 920 921 // We already have a directory so we can try to read metadata 922 // from it. Note that it is normal for this directory not to 923 // be found on the first run as it will be later created by 924 // the INBOX system filter. 925 attr_info info; 926 if (node.GetAttrInfo(attribute.String(), &info) != B_OK || info.size == 0) 927 return; 928 929 void* flatmanifest = malloc(info.size); 930 node.ReadAttr(attribute.String(), fManifest.TypeCode(), 0, 931 flatmanifest, info.size); 932 fManifest.Unflatten(fManifest.TypeCode(), flatmanifest, info.size); 933 free(flatmanifest); 934 } 935 936 937 void 938 POP3Protocol::_WriteManifest() 939 { 940 BString attribute = "MAIL:"; 941 attribute << fAccountSettings.AccountID() << ":manifest"; 942 // In case someone puts multiple accounts in the same directory 943 BNode node(fDestinationDir); 944 if (node.InitCheck() != B_OK) { 945 ShowError("Error while saving account manifest: cannot use " 946 "destination directory."); 947 return; 948 } 949 950 node.RemoveAttr(attribute.String()); 951 ssize_t manifestsize = fManifest.FlattenedSize(); 952 void* flatmanifest = malloc(manifestsize); 953 fManifest.Flatten(flatmanifest, manifestsize); 954 status_t err = node.WriteAttr(attribute.String(), 955 fManifest.TypeCode(), 0, flatmanifest, manifestsize); 956 if (err < 0) { 957 BString error = "Error while saving account manifest: "; 958 error << strerror(err); 959 printf("moep error\n"); 960 ShowError(error.String()); 961 } 962 963 free(flatmanifest); 964 } 965 966 967 // #pragma mark - 968 969 970 BInboundMailProtocol* 971 instantiate_inbound_protocol(const BMailAccountSettings& settings) 972 { 973 return new POP3Protocol(settings); 974 } 975 976 977 status_t 978 pop3_smtp_auth(const BMailAccountSettings& settings) 979 { 980 POP3Protocol protocol(settings); 981 protocol.Connect(); 982 protocol.Disconnect(); 983 return B_OK; 984 } 985