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