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
53static void
54NotHere(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
66POP3Protocol::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
89POP3Protocol::~POP3Protocol()
90{
91	Disconnect();
92}
93
94
95status_t
96POP3Protocol::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
115status_t
116POP3Protocol::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
131status_t
132POP3Protocol::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
251status_t
252POP3Protocol::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
315status_t
316POP3Protocol::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
376status_t
377POP3Protocol::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
444status_t
445POP3Protocol::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
465int32
466POP3Protocol::Messages()
467{
468	if (fNumMessages < 0)
469		Stat();
470
471	return fNumMessages;
472}
473
474
475size_t
476POP3Protocol::MailDropSize()
477{
478	if (fNumMessages < 0)
479		Stat();
480
481	return fMailDropSize;
482}
483
484
485void
486POP3Protocol::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
538status_t
539POP3Protocol::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
563status_t
564POP3Protocol::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
572status_t
573POP3Protocol::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
698void
699POP3Protocol::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
715size_t
716POP3Protocol::MessageSize(int32 index)
717{
718	return fSizes[index];
719}
720
721
722ssize_t
723POP3Protocol::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
764status_t
765POP3Protocol::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
808void
809POP3Protocol::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
827status_t
828POP3Protocol::_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
871void
872POP3Protocol::_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
899void
900POP3Protocol::_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
932BInboundMailProtocol*
933instantiate_inbound_protocol(const BMailAccountSettings& settings)
934{
935	return new POP3Protocol(settings);
936}
937
938
939status_t
940pop3_smtp_auth(const BMailAccountSettings& settings)
941{
942	POP3Protocol protocol(settings);
943	protocol.Connect();
944	protocol.Disconnect();
945	return B_OK;
946}
947