xref: /haiku/src/add-ons/mail_daemon/outbound_protocols/smtp/SMTP.cpp (revision efafab643ce980e3f3c916795ed302599f6b4f66)
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 //!	Implementation of the SMTP protocol
11 
12 
13 #include "SMTP.h"
14 
15 #include <map>
16 
17 #include <ctype.h>
18 #include <errno.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <sys/time.h>
22 #include <unistd.h>
23 
24 #include <Alert.h>
25 #include <Catalog.h>
26 #include <DataIO.h>
27 #include <Entry.h>
28 #include <File.h>
29 #include <MenuField.h>
30 #include <Message.h>
31 #include <Path.h>
32 #include <Socket.h>
33 #include <SecureSocket.h>
34 #include <TextControl.h>
35 
36 #include <crypt.h>
37 #include <mail_encoding.h>
38 #include <MailSettings.h>
39 #include <NodeMessage.h>
40 #include <ProtocolConfigView.h>
41 
42 #include "md5.h"
43 
44 
45 #undef B_TRANSLATION_CONTEXT
46 #define B_TRANSLATION_CONTEXT "smtp"
47 
48 
49 #define CRLF "\r\n"
50 #define SMTP_RESPONSE_SIZE 8192
51 
52 //#define DEBUG
53 #ifdef DEBUG
54 #	define D(x) x
55 #	define bug printf
56 #else
57 #	define D(x) ;
58 #endif
59 
60 
61 // Authentication types recognized. Not all methods are implemented.
62 enum AuthType {
63 	LOGIN		= 1,
64 	PLAIN		= 1 << 2,
65 	CRAM_MD5	= 1 << 3,
66 	DIGEST_MD5	= 1 << 4
67 };
68 
69 
70 using namespace std;
71 
72 /*
73 ** Function: md5_hmac
74 ** taken from the file rfc2104.txt
75 ** written by Martin Schaaf <mascha@ma-scha.de>
76 */
77 void
78 MD5Hmac(unsigned char *digest, const unsigned char* text, int text_len,
79 	const unsigned char* key, int key_len)
80 {
81 	MD5_CTX context;
82 	unsigned char k_ipad[64];
83 		// inner padding - key XORd with ipad
84 	unsigned char k_opad[64];
85 		// outer padding - key XORd with opad
86 	int i;
87 
88 	/* start out by storing key in pads */
89 	memset(k_ipad, 0, sizeof k_ipad);
90 	memset(k_opad, 0, sizeof k_opad);
91 	if (key_len > 64) {
92 		/* if key is longer than 64 bytes reset it to key=MD5(key) */
93 		MD5_CTX tctx;
94 
95 		MD5_Init(&tctx);
96 		MD5_Update(&tctx, (unsigned char*)key, key_len);
97 		MD5_Final(k_ipad, &tctx);
98 		MD5_Final(k_opad, &tctx);
99 	} else {
100 		memcpy(k_ipad, key, key_len);
101 		memcpy(k_opad, key, key_len);
102 	}
103 
104 	/*
105 	 * the HMAC_MD5 transform looks like:
106 	 *
107 	 * MD5(K XOR opad, MD5(K XOR ipad, text))
108 	 *
109 	 * where K is an n byte key
110 	 * ipad is the byte 0x36 repeated 64 times
111 	 * opad is the byte 0x5c repeated 64 times
112 	 * and text is the data being protected
113 	 */
114 
115 	/* XOR key with ipad and opad values */
116 	for (i = 0; i < 64; i++) {
117 		k_ipad[i] ^= 0x36;
118 		k_opad[i] ^= 0x5c;
119 	}
120 
121 	/*
122 	 * perform inner MD5
123 	 */
124 	MD5_Init(&context);		      /* init context for 1st
125 					       * pass */
126 	MD5_Update(&context, k_ipad, 64);     /* start with inner pad */
127 	MD5_Update(&context, (unsigned char*)text, text_len); /* then text of datagram */
128 	MD5_Final(digest, &context);	      /* finish up 1st pass */
129 	/*
130 	 * perform outer MD5
131 	 */
132 	MD5_Init(&context);		      /* init context for 2nd
133 					       * pass */
134 	MD5_Update(&context, k_opad, 64);     /* start with outer pad */
135 	MD5_Update(&context, digest, 16);     /* then results of 1st
136 					       * hash */
137 	MD5_Final(digest, &context);	      /* finish up 2nd pass */
138 }
139 
140 
141 void
142 MD5HexHmac(char *hexdigest, const unsigned char* text, int text_len,
143 	const unsigned char* key, int key_len)
144 {
145 	unsigned char digest[16];
146 	int i;
147 	unsigned char c;
148 
149 	MD5Hmac(digest, text, text_len, key, key_len);
150   	for (i = 0;  i < 16;  i++) {
151   		c = digest[i];
152 		*hexdigest++ = (c > 0x9F ? 'a'-10 : '0')+(c>>4);
153 		*hexdigest++ = ((c&0x0F) > 9 ? 'a'-10 : '0')+(c&0x0F);
154   	}
155   	*hexdigest = '\0';
156 }
157 
158 
159 /*
160 ** Function: MD5Sum
161 ** generates an MD5-sum from the given string
162 */
163 void
164 MD5Sum (char* sum, unsigned char *text, int text_len) {
165 	MD5_CTX context;
166 	MD5_Init(&context);
167 	MD5_Update(&context, text, text_len);
168 	MD5_Final((unsigned char*)sum, &context);
169 	sum[16] = '\0';
170 }
171 
172 /*
173 ** Function: MD5Digest
174 ** generates an MD5-digest from the given string
175 */
176 void MD5Digest (char* hexdigest, unsigned char *text, int text_len) {
177 	int i;
178 	unsigned char digest[17];
179 	unsigned char c;
180 
181 	MD5Sum((char*)digest, text, text_len);
182 
183   	for (i = 0;  i < 16;  i++) {
184   		c = digest[i];
185 		*hexdigest++ = (c > 0x9F ? 'a'-10 : '0')+(c>>4);
186 		*hexdigest++ = ((c&0x0F) > 9 ? 'a'-10 : '0')+(c&0x0F);
187   	}
188   	*hexdigest = '\0';
189 }
190 
191 /*
192 ** Function: SplitChallengeIntoMap
193 ** splits a challenge-string into the given map (see RFC-2831)
194 */
195 // :
196 static bool
197 SplitChallengeIntoMap(BString str, map<BString,BString>& m)
198 {
199 	m.clear();
200 	const char* key;
201 	const char* val;
202 	char* s = (char*)str.String();
203 	while(*s != 0) {
204 		while(isspace(*s))
205 			s++;
206 		key = s;
207 		while(isalpha(*s))
208 			s++;
209 		if (*s != '=')
210 			return false;
211 		*s++ = '\0';
212 		while(isspace(*s))
213 			s++;
214 		if (*s=='"') {
215 			val = ++s;
216 			while(*s!='"') {
217 				if (*s == 0)
218 					return false;
219 				s++;
220 			}
221 			*s++ = '\0';
222 		} else {
223 			val = s;
224 			while(*s!=0 && *s!=',' && !isspace(*s))
225 				s++;
226 			if (*s != 0)
227 				*s++ = '\0';
228 		}
229 		m[key] = val;
230 		while(isspace(*s))
231 			s++;
232 		if (*s != ',')
233 			return false;
234 		s++;
235 	}
236 	return true;
237 }
238 
239 
240 // #pragma mark -
241 
242 
243 SMTPProtocol::SMTPProtocol(const BMailAccountSettings& settings)
244 	:
245 	BOutboundMailProtocol("SMTP", settings),
246 	fAuthType(0)
247 {
248 	fSettingsMessage = settings.OutboundSettings();
249 }
250 
251 
252 SMTPProtocol::~SMTPProtocol()
253 {
254 }
255 
256 
257 status_t
258 SMTPProtocol::Connect()
259 {
260 	BString errorMessage;
261 	int32 authMethod = fSettingsMessage.FindInt32("auth_method");
262 
263 	status_t status = B_ERROR;
264 
265 	if (authMethod == 2) {
266 		// POP3 authentication is handled here instead of SMTPProtocol::Login()
267 		// because some servers obviously don't like establishing the connection
268 		// to the SMTP server first...
269 		status_t status = _POP3Authentication();
270 		if (status < B_OK) {
271 			errorMessage << B_TRANSLATE("POP3 authentication failed. The "
272 				"server said:\n") << fLog;
273 			ShowError(errorMessage.String());
274 			return status;
275 		}
276 	}
277 
278 	status = Open(fSettingsMessage.FindString("server"),
279 		fSettingsMessage.FindInt32("port"), authMethod == 1);
280 	if (status < B_OK) {
281 		errorMessage << B_TRANSLATE("Error while opening connection to %serv");
282 		errorMessage.ReplaceFirst("%serv",
283 			fSettingsMessage.FindString("server"));
284 
285 		if (fSettingsMessage.FindInt32("port") > 0)
286 			errorMessage << ":" << fSettingsMessage.FindInt32("port");
287 
288 		if (fLog.Length() > 0)
289 			errorMessage << B_TRANSLATE(". The server says:\n") << fLog;
290 		else {
291 			errorMessage << ". " << strerror(status);
292 		}
293 
294 		ShowError(errorMessage.String());
295 
296 		return status;
297 	}
298 
299 	const char* password = get_passwd(&fSettingsMessage, "cpasswd");
300 	status = Login(fSettingsMessage.FindString("username"), password);
301 	delete[] password;
302 
303 	if (status != B_OK) {
304 		errorMessage << B_TRANSLATE("Error while logging in to %serv")
305 			<< B_TRANSLATE(". The server said:\n") << fLog;
306 		errorMessage.ReplaceFirst("%serv",
307 			fSettingsMessage.FindString("server"));
308 
309 		ShowError(errorMessage.String());
310 	}
311 	return B_OK;
312 }
313 
314 
315 void
316 SMTPProtocol::Disconnect()
317 {
318 	Close();
319 }
320 
321 
322 //! Process EMail to be sent
323 status_t
324 SMTPProtocol::HandleSendMessages(const BMessage& message, off_t totalBytes)
325 {
326 	type_code type;
327 	int32 count;
328 	status_t status = message.GetInfo("ref", &type, &count);
329 	if (status != B_OK)
330 		return status;
331 
332 	// TODO: sort out already sent messages -- the request could
333 	// be issued while we're busy sending them already
334 
335 	SetTotalItems(count);
336 	SetTotalItemsSize(totalBytes);
337 
338 	status = Connect();
339 	if (status != B_OK)
340 		return status;
341 
342 	entry_ref ref;
343 	for (int32 i = 0; message.FindRef("ref", i++, &ref) == B_OK;) {
344 		status = _SendMessage(ref);
345 		if (status != B_OK) {
346 			BString error;
347 			error << "An error occurred while sending the message "
348 				<< ref.name << " (" << strerror(status) << "):\n" << fLog;
349 			ShowError(error.String());
350 
351 			ResetProgress();
352 			break;
353 		}
354 	}
355 
356 	Disconnect();
357 	return B_ERROR;
358 }
359 
360 
361 //! Opens connection to server
362 status_t
363 SMTPProtocol::Open(const char *address, int port, bool esmtp)
364 {
365 	ReportProgress(0, 0, B_TRANSLATE("Connecting to server" B_UTF8_ELLIPSIS));
366 
367 	use_ssl = (fSettingsMessage.FindInt32("flavor") == 1);
368 
369 	if (port <= 0)
370 		port = use_ssl ? 465 : 25;
371 
372 	BNetworkAddress addr(address);
373 	if (addr.InitCheck() != B_OK) {
374 		BString str;
375 		str.SetToFormat("Invalid network address for SMTP server: %s",
376 			strerror(addr.InitCheck()));
377 		ShowError(str.String());
378 		return addr.InitCheck();
379 	}
380 
381 	if (addr.Port() == 0)
382 		addr.SetPort(port);
383 
384 	if (use_ssl)
385 		fSocket = new(std::nothrow) BSecureSocket;
386 	else
387 		fSocket = new(std::nothrow) BSocket;
388 
389 	if (!fSocket)
390 		return B_NO_MEMORY;
391 
392 	if (fSocket->Connect(addr) != B_OK) {
393 		BString error;
394 		error << "Could not connect to SMTP server "
395 			<< fSettingsMessage.FindString("server");
396 		error << ":" << addr.Port();
397 		ShowError(error.String());
398 		delete fSocket;
399 		return B_ERROR;
400 	}
401 
402 	BString line;
403 	ReceiveResponse(line);
404 
405 	char localhost[255];
406 	gethostname(localhost, 255);
407 
408 	if (localhost[0] == 0)
409 		strcpy(localhost, "namethisbebox");
410 
411 	char *cmd = new char[::strlen(localhost)+8];
412 	if (!esmtp)
413 		::sprintf(cmd,"HELO %s" CRLF, localhost);
414 	else
415 		::sprintf(cmd,"EHLO %s" CRLF, localhost);
416 
417 	if (SendCommand(cmd) != B_OK) {
418 		delete[] cmd;
419 		return B_ERROR;
420 	}
421 
422 	delete[] cmd;
423 
424 	// Check auth type
425 	if (esmtp) {
426 		const char *res = fLog.String();
427 		char *p;
428 		if ((p = ::strstr(res, "250-AUTH")) != NULL
429 				|| (p = ::strstr(res, "250 AUTH")) != NULL) {
430 			if(::strstr(p, "LOGIN"))
431 				fAuthType |= LOGIN;
432 			if(::strstr(p, "PLAIN"))
433 				fAuthType |= PLAIN;
434 			if(::strstr(p, "CRAM-MD5"))
435 				fAuthType |= CRAM_MD5;
436 			if(::strstr(p, "DIGEST-MD5")) {
437 				fAuthType |= DIGEST_MD5;
438 				fServerName = address;
439 			}
440 		}
441 	}
442 	return B_OK;
443 }
444 
445 
446 status_t
447 SMTPProtocol::_SendMessage(const entry_ref& ref)
448 {
449 	// open read write to be able to manipulate in MessageReadyToSend hook
450 	BFile file(&ref, B_READ_WRITE);
451 	status_t status = file.InitCheck();
452 	if (status != B_OK)
453 		return status;
454 
455 	BMessage header;
456 	file >> header;
457 
458 	const char *from = header.FindString("MAIL:from");
459 	const char *to = header.FindString("MAIL:recipients");
460 	if (to == NULL)
461 		to = header.FindString("MAIL:to");
462 
463 	if (to == NULL || from == NULL) {
464 		fLog = "Invalid message headers";
465 		return B_ERROR;
466 	}
467 
468 	NotifyMessageReadyToSend(ref, file);
469 	status = Send(to, from, &file);
470 	if (status != B_OK)
471 		return status;
472 	NotifyMessageSent(ref, file);
473 
474 	off_t size = 0;
475 	file.GetSize(&size);
476 	ReportProgress(size, 1);
477 
478 	return B_OK;
479 }
480 
481 
482 status_t
483 SMTPProtocol::_POP3Authentication()
484 {
485 	const entry_ref& entry = fAccountSettings.InboundAddOnRef();
486 	if (strcmp(entry.name, "POP3") != 0)
487 		return B_ERROR;
488 
489 	status_t (*pop3_smtp_auth)(const BMailAccountSettings&);
490 
491 	BPath path(&entry);
492 	image_id image = load_add_on(path.Path());
493 	if (image < 0)
494 		return B_ERROR;
495 	if (get_image_symbol(image, "pop3_smtp_auth",
496 			B_SYMBOL_TYPE_TEXT, (void **)&pop3_smtp_auth) != B_OK) {
497 		unload_add_on(image);
498 		image = -1;
499 		return B_ERROR;
500 	}
501 	status_t status = (*pop3_smtp_auth)(fAccountSettings);
502 	unload_add_on(image);
503 	return status;
504 }
505 
506 
507 status_t
508 SMTPProtocol::Login(const char *_login, const char *password)
509 {
510 	if (fAuthType == 0)
511 		return B_OK;
512 
513 	const char *login = _login;
514 	char hex_digest[33];
515 	BString out;
516 
517 	int32 loginlen = ::strlen(login);
518 	int32 passlen = ::strlen(password);
519 
520 	if (fAuthType & DIGEST_MD5) {
521 		//******* DIGEST-MD5 Authentication ( tested. works fine [with Cyrus SASL] )
522 		// this implements only the subpart of DIGEST-MD5 which is
523 		// required for authentication to SMTP-servers. Integrity-
524 		// and confidentiality-protection are not implemented, as
525 		// they are provided by the use of OpenSSL.
526 		SendCommand("AUTH DIGEST-MD5" CRLF);
527 		const char *res = fLog.String();
528 
529 		if (strncmp(res, "334", 3) != 0)
530 			return B_ERROR;
531 		int32 baselen = ::strlen(&res[4]);
532 		char *base = new char[baselen+1];
533 		baselen = ::decode_base64(base, &res[4], baselen);
534 		base[baselen] = '\0';
535 
536 		D(bug("base: %s\n", base));
537 
538 		map<BString,BString> challengeMap;
539 		SplitChallengeIntoMap(base, challengeMap);
540 
541 		delete[] base;
542 
543 		BString rawResponse = BString("username=") << '"' << login << '"';
544 		rawResponse << ",realm=" << '"' << challengeMap["realm"] << '"';
545 		rawResponse << ",nonce=" << '"' << challengeMap["nonce"] << '"';
546 		rawResponse << ",nc=00000001";
547 		char temp[33];
548 		for( int i=0; i<32; ++i)
549 			temp[i] = 1+(rand()%254);
550 		temp[32] = '\0';
551 		BString rawCnonce(temp);
552 		BString cnonce;
553 		char* cnoncePtr = cnonce.LockBuffer(rawCnonce.Length()*2);
554 		baselen = ::encode_base64(cnoncePtr, rawCnonce.String(), rawCnonce.Length(), true /* headerMode */);
555 		cnoncePtr[baselen] = '\0';
556 		cnonce.UnlockBuffer(baselen);
557 		rawResponse << ",cnonce=" << '"' << cnonce << '"';
558 		rawResponse << ",qop=auth";
559 		BString digestUriValue	= BString("smtp/") << fServerName;
560 		rawResponse << ",digest-uri=" << '"' << digestUriValue << '"';
561 		char sum[17], hex_digest2[33];
562 		BString a1,a2,kd;
563 		BString t1 = BString(login) << ":"
564 				<< challengeMap["realm"] << ":"
565 				<< password;
566 		MD5Sum(sum, (unsigned char*)t1.String(), t1.Length());
567 		a1 << sum << ":" << challengeMap["nonce"] << ":" << cnonce;
568 		MD5Digest(hex_digest, (unsigned char*)a1.String(), a1.Length());
569 		a2 << "AUTHENTICATE:" << digestUriValue;
570 		MD5Digest(hex_digest2, (unsigned char*)a2.String(), a2.Length());
571 		kd << hex_digest << ':' << challengeMap["nonce"]
572 		   << ":" << "00000001" << ':' << cnonce << ':' << "auth"
573 		   << ':' << hex_digest2;
574 		MD5Digest(hex_digest, (unsigned char*)kd.String(), kd.Length());
575 
576 		rawResponse << ",response=" << hex_digest;
577 		BString postResponse;
578 		char *resp = postResponse.LockBuffer(rawResponse.Length() * 2 + 10);
579 		baselen = ::encode_base64(resp, rawResponse.String(), rawResponse.Length(), true /* headerMode */);
580 		resp[baselen] = 0;
581 		postResponse.UnlockBuffer();
582 		postResponse.Append(CRLF);
583 
584 		SendCommand(postResponse.String());
585 
586 		res = fLog.String();
587 		if (atol(res) >= 500)
588 			return B_ERROR;
589 		// actually, we are supposed to check the rspauth sent back
590 		// by the SMTP-server, but that isn't strictly required,
591 		// so we skip that for now.
592 		SendCommand(CRLF);	// finish off authentication
593 		res = fLog.String();
594 		if (atol(res) < 500)
595 			return B_OK;
596 	}
597 	if (fAuthType & CRAM_MD5) {
598 		//******* CRAM-MD5 Authentication ( tested. works fine [with Cyrus SASL] )
599 		SendCommand("AUTH CRAM-MD5" CRLF);
600 		const char *res = fLog.String();
601 
602 		if (strncmp(res, "334", 3) != 0)
603 			return B_ERROR;
604 		int32 baselen = ::strlen(&res[4]);
605 		char *base = new char[baselen+1];
606 		baselen = ::decode_base64(base, &res[4], baselen);
607 		base[baselen] = '\0';
608 
609 		D(bug("base: %s\n", base));
610 
611 		::MD5HexHmac(hex_digest, (const unsigned char *)base, (int)baselen,
612 			(const unsigned char *)password, (int)passlen);
613 
614 		D(bug("%s\n%s\n", base, hex_digest));
615 
616 		delete[] base;
617 
618 		BString preResponse, postResponse;
619 		preResponse = login;
620 		preResponse << " " << hex_digest << CRLF;
621 		char *resp = postResponse.LockBuffer(preResponse.Length() * 2 + 10);
622 		baselen = ::encode_base64(resp, preResponse.String(), preResponse.Length(), true /* headerMode */);
623 		resp[baselen] = 0;
624 		postResponse.UnlockBuffer();
625 		postResponse.Append(CRLF);
626 
627 		SendCommand(postResponse.String());
628 
629 		res = fLog.String();
630 		if (atol(res) < 500)
631 			return B_OK;
632 	}
633 	if (fAuthType & LOGIN) {
634 		//******* LOGIN Authentication ( tested. works fine)
635 		ssize_t encodedsize; // required by our base64 implementation
636 
637 		SendCommand("AUTH LOGIN" CRLF);
638 		const char *res = fLog.String();
639 
640 		if (strncmp(res, "334", 3) != 0)
641 			return B_ERROR;
642 
643 		// Send login name as base64
644 		char *login64 = new char[loginlen*3 + 6];
645 		encodedsize = ::encode_base64(login64, (char *)login, loginlen, true /* headerMode */);
646 		login64[encodedsize] = 0;
647 		strcat (login64, CRLF);
648 		SendCommand(login64);
649 		delete[] login64;
650 
651 		res = fLog.String();
652 		if (strncmp(res,"334",3) != 0)
653 			return B_ERROR;
654 
655 		// Send password as base64
656 		login64 = new char[passlen*3 + 6];
657 		encodedsize = ::encode_base64(login64, (char *)password, passlen, true /* headerMode */);
658 		login64[encodedsize] = 0;
659 		strcat (login64, CRLF);
660 		SendCommand(login64);
661 		delete[] login64;
662 
663 		res = fLog.String();
664 		if (atol(res) < 500)
665 			return B_OK;
666 	}
667 	if (fAuthType & PLAIN) {
668 		//******* PLAIN Authentication ( tested. works fine [with Cyrus SASL] )
669 		// format is:
670 		// 	authenticateID + \0 + username + \0 + password
671 		// 	(where authenticateID is always empty !?!)
672 		BString preResponse, postResponse;
673 		char *stringPntr;
674 		ssize_t encodedLength;
675 		stringPntr = preResponse.LockBuffer(loginlen + passlen + 3);
676 			// +3 to make room for the two \0-chars between the tokens and
677 			// the final delimiter added by sprintf().
678 		sprintf (stringPntr, "%c%s%c%s", 0, login, 0, password);
679 		preResponse.UnlockBuffer(loginlen + passlen + 2);
680 			// +2 in order to leave out the final delimiter (which is not part
681 			// of the string).
682 		stringPntr = postResponse.LockBuffer(preResponse.Length() * 3);
683 		encodedLength = ::encode_base64(stringPntr, preResponse.String(),
684 			preResponse.Length(), true /* headerMode */);
685 		stringPntr[encodedLength] = 0;
686 		postResponse.UnlockBuffer();
687 		postResponse.Prepend("AUTH PLAIN ");
688 		postResponse << CRLF;
689 
690 		SendCommand(postResponse.String());
691 
692 		const char *res = fLog.String();
693 		if (atol(res) < 500)
694 			return B_OK;
695 	}
696 	return B_ERROR;
697 }
698 
699 
700 void
701 SMTPProtocol::Close()
702 {
703 
704 	BString cmd = "QUIT";
705 	cmd += CRLF;
706 
707 	if (SendCommand(cmd.String()) != B_OK) {
708 		// Error
709 	}
710 
711 	delete fSocket;
712 }
713 
714 
715 status_t
716 SMTPProtocol::Send(const char* to, const char* from, BPositionIO *message)
717 {
718 	BString cmd = from;
719 	cmd.Remove(0, cmd.FindFirst("\" <") + 2);
720 	cmd.Prepend("MAIL FROM: ");
721 	cmd += CRLF;
722 	if (SendCommand(cmd.String()) != B_OK)
723 		return B_ERROR;
724 
725 	int32 len = strlen(to);
726 	BString addr("");
727 	for (int32 i = 0;i < len;i++) {
728 		char c = to[i];
729 		if (c != ',')
730 			addr += (char)c;
731 		if (c == ','||i == len-1) {
732 			if(addr.Length() == 0)
733 				continue;
734 			cmd = "RCPT TO: ";
735 			cmd << addr.String() << CRLF;
736 			if (SendCommand(cmd.String()) != B_OK)
737 				return B_ERROR;
738 
739 			addr ="";
740 		}
741 	}
742 
743 	cmd = "DATA";
744 	cmd += CRLF;
745 	if (SendCommand(cmd.String()) != B_OK)
746 		return B_ERROR;
747 
748 	// Send the message data.  Convert lines starting with a period to start
749 	// with two periods and so on.  The actual sequence is CR LF Period.  The
750 	// SMTP server will remove the periods.  Of course, the POP server may then
751 	// add some of its own, but the POP client should take care of them.
752 
753 	ssize_t amountRead;
754 	ssize_t amountToRead;
755 	ssize_t amountUnread;
756 	ssize_t bufferLen = 0;
757 	const int bufferMax = 2000;
758 	bool foundCRLFPeriod;
759 	int i;
760 	bool messageEndedWithCRLF = false;
761 
762 	message->Seek(0, SEEK_END);
763 	amountUnread = message->Position();
764 	message->Seek(0, SEEK_SET);
765 	char *data = new char[bufferMax];
766 
767 	while (true) {
768 		// Fill the buffer if it is getting low, but not every time, to avoid
769 		// small reads.
770 		if (bufferLen < bufferMax / 2) {
771 			amountToRead = bufferMax - bufferLen;
772 			if (amountToRead > amountUnread)
773 				amountToRead = amountUnread;
774 			if (amountToRead > 0) {
775 				amountRead = message->Read (data + bufferLen, amountToRead);
776 				if (amountRead <= 0 || amountRead > amountToRead)
777 					amountUnread = 0; // Just stop reading when an error happens.
778 				else {
779 					amountUnread -= amountRead;
780 					bufferLen += amountRead;
781 				}
782 			}
783 		}
784 
785 		// Look for the next CRLFPeriod triple.
786 		foundCRLFPeriod = false;
787 		for (i = 0; i <= bufferLen - 3; i++) {
788 			if (data[i] == '\r' && data[i+1] == '\n' && data[i+2] == '.') {
789 				foundCRLFPeriod = true;
790 				// Send data up to the CRLF, and include the period too.
791 				if (fSocket->Write(data, i + 3) < 0) {
792 					amountUnread = 0; // Stop when an error happens.
793 					bufferLen = 0;
794 					break;
795 				}
796 				ReportProgress (i + 2 /* Don't include the double period here */,0);
797 				// Move the data over in the buffer, but leave the period there
798 				// so it gets sent a second time.
799 				memmove(data, data + (i + 2), bufferLen - (i + 2));
800 				bufferLen -= i + 2;
801 				break;
802 			}
803 		}
804 
805 		if (!foundCRLFPeriod) {
806 			if (amountUnread <= 0) { // No more data, all we have is in the buffer.
807 				if (bufferLen > 0) {
808 					fSocket->Write(data, bufferLen);
809 					ReportProgress(bufferLen, 0);
810 					if (bufferLen >= 2)
811 						messageEndedWithCRLF = (data[bufferLen-2] == '\r' &&
812 							data[bufferLen-1] == '\n');
813 				}
814 				break; // Finished!
815 			}
816 
817 			// Send most of the buffer, except a few characters to overlap with
818 			// the next read, in case the CRLFPeriod is split between reads.
819 			if (bufferLen > 3) {
820 				if (fSocket->Write(data, bufferLen - 3) < 0)
821 					break; // Stop when an error happens.
822 
823 				ReportProgress(bufferLen - 3, 0);
824 				memmove (data, data + bufferLen - 3, 3);
825 				bufferLen = 3;
826 			}
827 		}
828 	}
829 	delete [] data;
830 
831 	if (messageEndedWithCRLF)
832 		cmd = "." CRLF; // The standard says don't add extra CRLF.
833 	else
834 		cmd = CRLF "." CRLF;
835 
836 	if (SendCommand(cmd.String()) != B_OK)
837 		return B_ERROR;
838 
839 	return B_OK;
840 }
841 
842 
843 //! Receives response from server.
844 int32
845 SMTPProtocol::ReceiveResponse(BString &out)
846 {
847 	out = "";
848 	int32 len = 0,r;
849 	char buf[SMTP_RESPONSE_SIZE];
850 	bigtime_t timeout = 1000000*180; // timeout 180 secs
851 	bool gotCode = false;
852 	int32 errCode;
853 	BString searchStr = "";
854 
855 	if (fSocket->WaitForReadable(timeout) == B_OK) {
856 		while (1) {
857 			r = fSocket->Read(buf, SMTP_RESPONSE_SIZE - 1);
858 			if (r <= 0)
859 				break;
860 
861 			if (!gotCode) {
862 				if (buf[3] == ' ' || buf[3] == '-') {
863 					errCode = atol(buf);
864 					gotCode = true;
865 					searchStr << errCode << ' ';
866 				}
867 			}
868 
869 			len += r;
870 			out.Append(buf, r);
871 
872 			if (strstr(buf, CRLF) && (out.FindFirst(searchStr) != B_ERROR))
873 				break;
874 		}
875 	} else
876 		fLog = "SMTP socket timeout.";
877 
878 	D(bug("S:%s\n", out.String()));
879 	return len;
880 }
881 
882 
883 // Sends SMTP command. Result kept in fLog
884 
885 status_t
886 SMTPProtocol::SendCommand(const char *cmd)
887 {
888 	D(bug("C:%s\n", cmd));
889 
890 	if (fSocket->Write(cmd, ::strlen(cmd)) < 0)
891 		return B_ERROR;
892 	fLog = "";
893 
894 	// Receive
895 	while (1) {
896 		int32 len = ReceiveResponse(fLog);
897 
898 		if (len <= 0) {
899 			D(bug("SMTP: len == %" B_PRId32 "\n", len));
900 			return B_ERROR;
901 		}
902 
903 		if (fLog.Length() > 4 && (fLog[3] == ' ' || fLog[3] == '-'))
904 		{
905 			int32 num = atol(fLog.String());
906 			D(bug("ReplyNumber: %" B_PRId32 "\n", num));
907 			if (num >= 500)
908 				return B_ERROR;
909 
910 			break;
911 		}
912 	}
913 
914 	return B_OK;
915 }
916 
917 
918 // #pragma mark -
919 
920 
921 extern "C" BOutboundMailProtocol*
922 instantiate_outbound_protocol(const BMailAccountSettings& settings)
923 {
924 	return new SMTPProtocol(settings);
925 }
926