xref: /haiku/src/add-ons/mail_daemon/inbound_protocols/pop3/POP3.cpp (revision 44d19f4d32b8f7e9c01f00294c87ca5cc2e057f7)
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 #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::HandleFetchBody(const entry_ref& ref, const BMessenger& replyTo)
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 	ReplyBodyFetched(replyTo, ref, B_OK);
307 
308 	if (!leaveOnServer)
309 		Delete(toRetrieve);
310 
311 	ReportProgress(1, 0);
312 	ResetProgress();
313 
314 	Disconnect();
315 	return B_OK;
316 }
317 
318 
319 status_t
320 POP3Protocol::Open(const char* server, int port, int)
321 {
322 	ReportProgress(0, 0, B_TRANSLATE("Connecting to POP3 server"
323 		B_UTF8_ELLIPSIS));
324 
325 	if (port <= 0)
326 		port = fUseSSL ? 995 : 110;
327 
328 	fLog = "";
329 
330 	// Prime the error message
331 	BString errorMessage(B_TRANSLATE("Error while connecting to server %serv"));
332 	errorMessage.ReplaceFirst("%serv", server);
333 	if (port != 110)
334 		errorMessage << ":" << port;
335 
336 	delete fServerConnection;
337 	fServerConnection = NULL;
338 
339 	BNetworkAddress address(server, port);
340 	if (fUseSSL)
341 		fServerConnection = new(std::nothrow) BSecureSocket(address);
342 	else
343 		fServerConnection = new(std::nothrow) BSocket(address);
344 
345 	status_t status = B_NO_MEMORY;
346 	if (fServerConnection != NULL)
347 		status = fServerConnection->InitCheck();
348 
349 	BString line;
350 	if (status == B_OK) {
351 		ssize_t length = ReceiveLine(line);
352 		if (length < 0)
353 			status = length;
354 	}
355 
356 	if (status != B_OK) {
357 		fServerConnection->Disconnect();
358 		errorMessage << ": " << strerror(status);
359 		ShowError(errorMessage.String());
360 		return status;
361 	}
362 
363 	if (strncmp(line.String(), "+OK", 3) != 0) {
364 		if (line.Length() > 0) {
365 			errorMessage << B_TRANSLATE(". The server said:\n")
366 				<< line.String();
367 		} else
368 			errorMessage << B_TRANSLATE(": No reply.\n");
369 
370 		ShowError(errorMessage.String());
371 		fServerConnection->Disconnect();
372 		return B_ERROR;
373 	}
374 
375 	fLog = line;
376 	return B_OK;
377 }
378 
379 
380 status_t
381 POP3Protocol::Login(const char* uid, const char* password, int method)
382 {
383 	status_t err;
384 
385 	BString errorMessage(B_TRANSLATE("Error while authenticating user %user"));
386 	errorMessage.ReplaceFirst("%user", uid);
387 
388 	if (method == 1) {	//APOP
389 		int32 index = fLog.FindFirst("<");
390 		if(index != B_ERROR) {
391 			ReportProgress(0, 0, B_TRANSLATE("Sending APOP authentication"
392 				B_UTF8_ELLIPSIS));
393 			int32 end = fLog.FindFirst(">", index);
394 			BString timestamp("");
395 			fLog.CopyInto(timestamp, index, end - index + 1);
396 			timestamp += password;
397 			char md5sum[33];
398 			MD5Digest((unsigned char*)timestamp.String(), md5sum);
399 			BString cmd = "APOP ";
400 			cmd += uid;
401 			cmd += " ";
402 			cmd += md5sum;
403 			cmd += CRLF;
404 
405 			err = SendCommand(cmd.String());
406 			if (err != B_OK) {
407 				errorMessage << B_TRANSLATE(". The server said:\n") << fLog;
408 				ShowError(errorMessage.String());
409 				return err;
410 			}
411 
412 			return B_OK;
413 		} else {
414 			errorMessage << B_TRANSLATE(": The server does not support APOP.");
415 			ShowError(errorMessage.String());
416 			return B_NOT_ALLOWED;
417 		}
418 	}
419 	ReportProgress(0, 0, B_TRANSLATE("Sending username" B_UTF8_ELLIPSIS));
420 
421 	BString cmd = "USER ";
422 	cmd += uid;
423 	cmd += CRLF;
424 
425 	err = SendCommand(cmd.String());
426 	if (err != B_OK) {
427 		errorMessage << B_TRANSLATE(". The server said:\n") << fLog;
428 		ShowError(errorMessage.String());
429 		return err;
430 	}
431 
432 	ReportProgress(0, 0, B_TRANSLATE("Sending password" B_UTF8_ELLIPSIS));
433 	cmd = "PASS ";
434 	cmd += password;
435 	cmd += CRLF;
436 
437 	err = SendCommand(cmd.String());
438 	if (err != B_OK) {
439 		errorMessage << B_TRANSLATE(". The server said:\n") << fLog;
440 		ShowError(errorMessage.String());
441 		return err;
442 	}
443 
444 	return B_OK;
445 }
446 
447 
448 status_t
449 POP3Protocol::Stat()
450 {
451 	ReportProgress(0, 0, B_TRANSLATE("Getting mailbox size" B_UTF8_ELLIPSIS));
452 
453 	if (SendCommand("STAT" CRLF) < B_OK)
454 		return B_ERROR;
455 
456 	int32 messages;
457 	int32 dropSize;
458 	if (sscanf(fLog.String(), "+OK %" B_SCNd32" %" B_SCNd32, &messages,
459 			&dropSize) < 2)
460 		return B_ERROR;
461 
462 	fNumMessages = messages;
463 	fMailDropSize = dropSize;
464 
465 	return B_OK;
466 }
467 
468 
469 int32
470 POP3Protocol::Messages()
471 {
472 	if (fNumMessages < 0)
473 		Stat();
474 
475 	return fNumMessages;
476 }
477 
478 
479 size_t
480 POP3Protocol::MailDropSize()
481 {
482 	if (fNumMessages < 0)
483 		Stat();
484 
485 	return fMailDropSize;
486 }
487 
488 
489 void
490 POP3Protocol::CheckForDeletedMessages()
491 {
492 	{
493 		// Delete things from the manifest no longer on the server
494 		BStringList list;
495 		NotHere(fUniqueIDs, fManifest, &list);
496 		fManifest.Remove(list);
497 	}
498 
499 	if (!fSettings.FindBool("delete_remote_when_local")
500 		|| fManifest.CountStrings() == 0)
501 		return;
502 
503 	BStringList toDelete;
504 
505 	BStringList queryContents;
506 	BVolumeRoster volumes;
507 	BVolume volume;
508 
509 	while (volumes.GetNextVolume(&volume) == B_OK) {
510 		BQuery fido;
511 		entry_ref entry;
512 
513 		fido.SetVolume(&volume);
514 		fido.PushAttr(B_MAIL_ATTR_ACCOUNT_ID);
515 		fido.PushInt32(fAccountSettings.AccountID());
516 		fido.PushOp(B_EQ);
517 
518 		fido.Fetch();
519 
520 		BString uid;
521 		while (fido.GetNextRef(&entry) == B_OK) {
522 			BNode(&entry).ReadAttrString("MAIL:unique_id", &uid);
523 			queryContents.Add(uid);
524 		}
525 	}
526 	NotHere(queryContents, fManifest, &toDelete);
527 
528 	for (int32 i = 0; i < toDelete.CountStrings(); i++) {
529 		printf("delete mail on server uid %s\n", toDelete.StringAt(i).String());
530 		Delete(fUniqueIDs.IndexOf(toDelete.StringAt(i)));
531 	}
532 
533 	// Don't remove ids from fUniqueIDs, the indices have to stay the same when
534 	// retrieving new messages.
535 	fManifest.Remove(toDelete);
536 
537 	// TODO: at some point the purged manifest should be written to disk
538 	// otherwise it will grow forever
539 }
540 
541 
542 status_t
543 POP3Protocol::Retrieve(int32 message, BPositionIO* to)
544 {
545 	BString cmd;
546 	cmd << "RETR " << message + 1 << CRLF;
547 	status_t status = RetrieveInternal(cmd.String(), message, to, true);
548 	ReportProgress(1, 0);
549 
550 	if (status == B_OK) {
551 		// Check if the actual message size matches the expected one
552 		int32 size = MessageSize(message);
553  		to->Seek(0, SEEK_END);
554 		if (to->Position() != size) {
555 			printf("POP3Protocol::Retrieve Note: message size is %" B_PRIdOFF
556 				", was expecting %" B_PRId32 ", for message #%" B_PRId32
557 				".  Could be a transmission error or a bad POP server "
558 				"implementation (does it remove escape codes when it counts "
559 				"size?).\n", to->Position(), size, message);
560 		}
561 	}
562 
563 	return status;
564 }
565 
566 
567 status_t
568 POP3Protocol::GetHeader(int32 message, BPositionIO* to)
569 {
570 	BString cmd;
571 	cmd << "TOP " << message + 1 << " 0" << CRLF;
572 	return RetrieveInternal(cmd.String(), message, to, false);
573 }
574 
575 
576 status_t
577 POP3Protocol::RetrieveInternal(const char* command, int32 message,
578 	BPositionIO* to, bool postProgress)
579 {
580 	const int bufSize = 1024 * 30;
581 
582 	// To avoid waiting for the non-arrival of the next data packet, try to
583 	// receive only the message size, plus the 3 extra bytes for the ".\r\n"
584 	// after the message.  Of course, if we get it wrong (or it is a huge
585 	// message or has lines starting with escaped periods), it will then switch
586 	// back to receiving full buffers until the message is done.
587 	int amountToReceive = MessageSize(message) + 3;
588 	if (amountToReceive >= bufSize || amountToReceive <= 0)
589 		amountToReceive = bufSize - 1;
590 
591 	BString bufBString; // Used for auto-dealloc on return feature.
592 	char* buf = bufBString.LockBuffer(bufSize);
593 	int amountInBuffer = 0;
594 	int amountReceived;
595 	int testIndex;
596 	char* testStr;
597 	bool cont = true;
598 	bool flushWholeBuffer = false;
599 	to->Seek(0, SEEK_SET);
600 
601 	if (SendCommand(command) != B_OK)
602 		return B_ERROR;
603 
604 	while (cont) {
605 		status_t result = fServerConnection->WaitForReadable(
606 			POP3_RETRIEVAL_TIMEOUT);
607 		if (result == B_TIMED_OUT) {
608 			// No data available, even after waiting a minute.
609 			fLog = "POP3 timeout - no data received after a long wait.";
610 			return B_ERROR;
611 		}
612 		if (amountToReceive > bufSize - 1 - amountInBuffer)
613 			amountToReceive = bufSize - 1 - amountInBuffer;
614 
615 		amountReceived = fServerConnection->Read(buf + amountInBuffer,
616 			amountToReceive);
617 
618 		if (amountReceived < 0) {
619 			fLog = strerror(amountReceived);
620 			return amountReceived;
621 		}
622 		if (amountReceived == 0) {
623 			fLog = "POP3 data supposedly ready to receive but not received!";
624 			return B_ERROR;
625 		}
626 
627 		amountToReceive = bufSize - 1; // For next time, read a full buffer.
628 		amountInBuffer += amountReceived;
629 		buf[amountInBuffer] = 0; // NUL stops tests past the end of buffer.
630 
631 		// Look for lines starting with a period.  A single period by itself on
632 		// a line "\r\n.\r\n" marks the end of the message (thus the need for
633 		// at least five characters in the buffer for testing).  A period
634 		// "\r\n.Stuff" at the start of a line get deleted "\r\nStuff", since
635 		// POP adds one as an escape code to let you have message text with
636 		// lines starting with a period.  For convenience, assume that no
637 		// messages start with a period on the very first line, so we can
638 		// search for the previous line's "\r\n".
639 
640 		for (testIndex = 0; testIndex <= amountInBuffer - 5; testIndex++) {
641 			testStr = buf + testIndex;
642 			if (testStr[0] == '\r' && testStr[1] == '\n' && testStr[2] == '.') {
643 				if (testStr[3] == '\r' && testStr[4] == '\n') {
644 					// Found the end of the message marker.
645 					// Ignore remaining data.
646 					if (amountInBuffer > testIndex + 5) {
647 						printf("POP3Protocol::RetrieveInternal Ignoring %d "
648 							"bytes of extra data past message end.\n",
649 							amountInBuffer - (testIndex + 5));
650 					}
651 					amountInBuffer = testIndex + 2; // Don't include ".\r\n".
652 					buf[amountInBuffer] = 0;
653 					cont = false;
654 				} else {
655 					// Remove an extra period at the start of a line.
656 					// Inefficient, but it doesn't happen often that you have a
657 					// dot starting a line of text.  Of course, a file with a
658 					// lot of double period lines will get processed very
659 					// slowly.
660 					memmove(buf + testIndex + 2, buf + testIndex + 3,
661 						amountInBuffer - (testIndex + 3) + 1);
662 					amountInBuffer--;
663 					// Watch out for the end of buffer case, when the POP text
664 					// is "\r\n..X".  Don't want to leave the resulting
665 					// "\r\n.X" in the buffer (flush out the whole buffer),
666 					// since that will get mistakenly evaluated again in the
667 					// next loop and delete a character by mistake.
668 					if (testIndex >= amountInBuffer - 4 && testStr[2] == '.') {
669 						printf("POP3Protocol::RetrieveInternal: Jackpot!  "
670 							"You have hit the rare situation with an escaped "
671 							"period at the end of the buffer.  Aren't you happy"
672 							"it decodes it correctly?\n");
673 						flushWholeBuffer = true;
674 					}
675 				}
676 			}
677 		}
678 
679 		if (cont && !flushWholeBuffer) {
680 			// Dump out most of the buffer, but leave the last 4 characters for
681 			// comparison continuity, in case the line starting with a period
682 			// crosses a buffer boundary.
683 			if (amountInBuffer > 4) {
684 				to->Write(buf, amountInBuffer - 4);
685 				if (postProgress)
686 					ReportProgress(0, amountInBuffer - 4);
687 				memmove(buf, buf + amountInBuffer - 4, 4);
688 				amountInBuffer = 4;
689 			}
690 		} else {
691 			// Dump everything - end of message or flushing the whole buffer.
692 			to->Write(buf, amountInBuffer);
693 			if (postProgress)
694 				ReportProgress(0, amountInBuffer);
695 			amountInBuffer = 0;
696 		}
697 	}
698 	return B_OK;
699 }
700 
701 
702 void
703 POP3Protocol::Delete(int32 index)
704 {
705 	BString cmd = "DELE ";
706 	cmd << (index + 1) << CRLF;
707 	if (SendCommand(cmd.String()) != B_OK) {
708 		// Error
709 	}
710 #if DEBUG
711 	puts(fLog.String());
712 #endif
713 	// The mail is just marked as deleted and removed from the server when
714 	// sending the QUIT command. Because of that the message number stays
715 	// the same and we keep the uid in the uid list.
716 }
717 
718 
719 size_t
720 POP3Protocol::MessageSize(int32 index)
721 {
722 	return fSizes[index];
723 }
724 
725 
726 ssize_t
727 POP3Protocol::ReceiveLine(BString& line)
728 {
729 	int32 length = 0;
730 	bool flag = false;
731 
732 	line = "";
733 
734 	status_t result = fServerConnection->WaitForReadable(
735 		POP3_RETRIEVAL_TIMEOUT);
736 	if (result == B_TIMED_OUT)
737 		return errno;
738 
739 	while (true) {
740 		// Hope there's an end of line out there else this gets stuck.
741 		int32 bytesReceived;
742 		uint8 c = 0;
743 
744 		bytesReceived = fServerConnection->Read((char*)&c, 1);
745 		if (bytesReceived < 0)
746 			return bytesReceived;
747 
748 		if (c == '\n' || bytesReceived == 0)
749 			break;
750 
751 		if (c == '\r') {
752 			flag = true;
753 		} else {
754 			if (flag) {
755 				length++;
756 				line += '\r';
757 				flag = false;
758 			}
759 			length++;
760 			line += (char)c;
761 		}
762 	}
763 
764 	return length;
765 }
766 
767 
768 status_t
769 POP3Protocol::SendCommand(const char* cmd)
770 {
771 	// Flush any accumulated garbage data before we send our command, so we
772 	// don't misinterrpret responses from previous commands (that got left over
773 	// due to bugs) as being from this command.
774 	while (fServerConnection->WaitForReadable(1000) == B_OK) {
775 		char buffer[4096];
776 		ssize_t amountReceived = fServerConnection->Read(buffer,
777 			sizeof(buffer) - 1);
778 		if (amountReceived < 0)
779 			return amountReceived;
780 
781 		buffer[amountReceived] = 0;
782 		printf("POP3Protocol::SendCommand Bug! Had to flush %" B_PRIdSSIZE
783 			" bytes: %s\n", amountReceived, buffer);
784 	}
785 
786 	if (fServerConnection->Write(cmd, ::strlen(cmd)) < 0) {
787 		fLog = strerror(errno);
788 		printf("POP3Protocol::SendCommand Send \"%s\" failed, code %d: %s\n",
789 			cmd, errno, fLog.String());
790 		return errno;
791 	}
792 
793 	fLog = "";
794 	int32 length = ReceiveLine(fLog);
795 	if (length <= 0 || fLog.ICompare("+OK", 3) == 0)
796 		return B_OK;
797 
798 	if (fLog.ICompare("-ERR", 4) == 0) {
799 		printf("POP3Protocol::SendCommand \"%s\" got error message "
800 			"from server: %s\n", cmd, fLog.String());
801 		return B_ERROR;
802 	}
803 
804 	printf("POP3Protocol::SendCommand \"%s\" got nonsense message "
805 		"from server: %s\n", cmd, fLog.String());
806 	return B_BAD_DATA;
807 		// If it's not +OK, and it's not -ERR, then what the heck
808 		// is it? Presume an error
809 }
810 
811 
812 void
813 POP3Protocol::MD5Digest(unsigned char* in, char* asciiDigest)
814 {
815 	unsigned char digest[16];
816 
817 #ifdef USE_SSL
818 	MD5(in, ::strlen((char*)in), digest);
819 #else
820 	MD5_CTX context;
821 
822 	MD5Init(&context);
823 	MD5Update(&context, in, ::strlen((char*)in));
824 	MD5Final(digest, &context);
825 #endif
826 
827 	for (int i = 0;  i < 16;  i++) {
828 		sprintf(asciiDigest + 2 * i, "%02x", digest[i]);
829 	}
830 
831 	return;
832 }
833 
834 
835 status_t
836 POP3Protocol::_RetrieveUniqueIDs()
837 {
838 	fUniqueIDs.MakeEmpty();
839 	fSizes.clear();
840 	fTotalSize = 0;
841 
842 	status_t status = SendCommand("UIDL" CRLF);
843 	if (status != B_OK)
844 		return status;
845 
846 	BString result;
847 	int32 uidOffset;
848 	while (ReceiveLine(result) > 0) {
849 		if (result.ByteAt(0) == '.')
850 			break;
851 
852 		uidOffset = result.FindFirst(' ') + 1;
853 		result.Remove(0, uidOffset);
854 		fUniqueIDs.Add(result);
855 	}
856 
857 	if (SendCommand("LIST" CRLF) != B_OK)
858 		return B_ERROR;
859 
860 	while (ReceiveLine(result) > 0) {
861 		if (result.ByteAt(0) == '.')
862 			break;
863 
864 		int32 index = result.FindLast(" ");
865 		int32 size;
866 		if (index >= 0)
867 			size = atol(&result.String()[index]);
868 		else
869 			size = 0;
870 
871 		fTotalSize += size;
872 		fSizes.push_back(size);
873 	}
874 
875 	return B_OK;
876 }
877 
878 
879 void
880 POP3Protocol::_ReadManifest()
881 {
882 	fManifest.MakeEmpty();
883 	BString attribute = "MAIL:";
884 	attribute << fAccountSettings.AccountID() << ":manifest";
885 		// In case someone puts multiple accounts in the same directory
886 
887 	BNode node(fDestinationDir);
888 	if (node.InitCheck() != B_OK)
889 		return;
890 
891 	// We already have a directory so we can try to read metadata
892 	// from it. Note that it is normal for this directory not to
893 	// be found on the first run as it will be later created by
894 	// the INBOX system filter.
895 	attr_info info;
896 	if (node.GetAttrInfo(attribute.String(), &info) != B_OK || info.size == 0)
897 		return;
898 
899 	void* flatmanifest = malloc(info.size);
900 	node.ReadAttr(attribute.String(), fManifest.TypeCode(), 0,
901 		flatmanifest, info.size);
902 	fManifest.Unflatten(fManifest.TypeCode(), flatmanifest, info.size);
903 	free(flatmanifest);
904 }
905 
906 
907 void
908 POP3Protocol::_WriteManifest()
909 {
910 	BString attribute = "MAIL:";
911 	attribute << fAccountSettings.AccountID() << ":manifest";
912 		// In case someone puts multiple accounts in the same directory
913 	BNode node(fDestinationDir);
914 	if (node.InitCheck() != B_OK) {
915 		ShowError("Error while saving account manifest: cannot use "
916 			"destination directory.");
917 		return;
918 	}
919 
920 	node.RemoveAttr(attribute.String());
921 	ssize_t manifestsize = fManifest.FlattenedSize();
922 	void* flatmanifest = malloc(manifestsize);
923 	fManifest.Flatten(flatmanifest, manifestsize);
924 	status_t err = node.WriteAttr(attribute.String(),
925 		fManifest.TypeCode(), 0, flatmanifest, manifestsize);
926 	if (err < 0) {
927 		BString error = "Error while saving account manifest: ";
928 		error << strerror(err);
929 			printf("moep error\n");
930 		ShowError(error.String());
931 	}
932 
933 	free(flatmanifest);
934 }
935 
936 
937 //	#pragma mark -
938 
939 
940 BInboundMailProtocol*
941 instantiate_inbound_protocol(const BMailAccountSettings& settings)
942 {
943 	return new POP3Protocol(settings);
944 }
945 
946 
947 status_t
948 pop3_smtp_auth(const BMailAccountSettings& settings)
949 {
950 	POP3Protocol protocol(settings);
951 	protocol.Connect();
952 	protocol.Disconnect();
953 	return B_OK;
954 }
955