xref: /haiku/src/add-ons/mail_daemon/inbound_protocols/pop3/POP3.cpp (revision f0650dc98fed895fc134a359aab99c27de6a0c6a)
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