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