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