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