xref: /haiku/src/add-ons/mail_daemon/outbound_protocols/smtp/SMTP.cpp (revision d0ac609964842f8cdb6d54b3c539c6c15293e172)
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(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::SendMessages(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 	SetTotalItems(count);
341 	SetTotalItemsSize(totalBytes);
342 
343 	status = Connect();
344 	if (status != B_OK)
345 		return status;
346 
347 	entry_ref ref;
348 	for (int32 i = 0; message.FindRef("ref", i++, &ref) == B_OK;) {
349 		status = _SendMessage(ref);
350 		if (status != B_OK) {
351 			BString error;
352 			error << "An error occurred while sending the message "
353 				<< ref.name << ":\n" << fLog;
354 			ShowError(error.String());
355 
356 			ResetProgress();
357 			break;
358 		}
359 	}
360 
361 	Disconnect();
362 	return B_ERROR;
363 }
364 
365 
366 //! Opens connection to server
367 status_t
368 SMTPProtocol::Open(const char *address, int port, bool esmtp)
369 {
370 	ReportProgress(0, 0, B_TRANSLATE("Connecting to server" B_UTF8_ELLIPSIS));
371 
372 	#ifdef USE_SSL
373 		use_ssl = (fSettingsMessage.FindInt32("flavor") == 1);
374 		use_STARTTLS = (fSettingsMessage.FindInt32("flavor") == 2);
375 		ssl = NULL;
376 		ctx = NULL;
377 	#endif
378 
379 	if (port <= 0)
380 	#ifdef USE_SSL
381 		port = use_ssl ? 465 : 25;
382 	#else
383 		port = 25;
384 	#endif
385 
386 	uint32 hostIP = inet_addr(address);  // first see if we can parse it as a numeric address
387 	if ((hostIP == 0)||(hostIP == (uint32)-1)) {
388 		struct hostent * he = gethostbyname(address);
389 		hostIP = he ? *((uint32*)he->h_addr) : 0;
390 	}
391 
392 	if (hostIP == 0)
393 		return EHOSTUNREACH;
394 
395 	fSocket = socket(AF_INET, SOCK_STREAM, 0);
396 	if (fSocket < 0)
397 		return errno;
398 
399 	struct sockaddr_in saAddr;
400 	memset(&saAddr, 0, sizeof(saAddr));
401 	saAddr.sin_family = AF_INET;
402 	saAddr.sin_port = htons(port);
403 	saAddr.sin_addr.s_addr = hostIP;
404 	int result = connect(fSocket, (struct sockaddr *)&saAddr,
405 		sizeof(saAddr));
406 	if (result < 0) {
407 		close(fSocket);
408 		fSocket = -1;
409 		return errno;
410 	}
411 
412 #ifdef USE_SSL
413 	if (use_ssl) {
414 		SSL_library_init();
415     	SSL_load_error_strings();
416     	RAND_seed(this,sizeof(SMTPProtocol));
417     	/*--- Because we're an add-on loaded at an unpredictable time, all
418     	      the memory addresses and things contained in ourself are
419     	      esssentially random. */
420 
421     	ctx = SSL_CTX_new(SSLv23_method());
422     	ssl = SSL_new(ctx);
423     	sbio=BIO_new_socket(fSocket,BIO_NOCLOSE);
424     	SSL_set_bio(ssl,sbio,sbio);
425 
426     	if (SSL_connect(ssl) <= 0) {
427     		BString error;
428 			error << "Could not connect to SMTP server "
429 				<< fSettingsMessage.FindString("server");
430 			if (port != 465)
431 				error << ":" << port;
432 			error << ". (SSL connection error)";
433 			ShowError(error.String());
434 			SSL_CTX_free(ctx);
435 			close(fSocket);
436 			fSocket = -1;
437 			return B_ERROR;
438 		}
439 	}
440 #endif	// USE_SSL
441 
442 	BString line;
443 	ReceiveResponse(line);
444 
445 	char localhost[255];
446 	gethostname(localhost,255);
447 
448 	if (localhost[0] == 0)
449 		strcpy(localhost,"namethisbebox");
450 
451 	char *cmd = new char[::strlen(localhost)+8];
452 	if (!esmtp)
453 		::sprintf(cmd,"HELO %s" CRLF, localhost);
454 	else
455 		::sprintf(cmd,"EHLO %s" CRLF, localhost);
456 
457 	if (SendCommand(cmd) != B_OK) {
458 		delete[] cmd;
459 		return B_ERROR;
460 	}
461 
462 #ifdef USE_SSL
463 	// Check for STARTTLS
464 	if (use_STARTTLS) {
465 		const char *res = fLog.String();
466 		char *p;
467 
468 		SSL_library_init();
469 		RAND_seed(this,sizeof(SMTPProtocol));
470 		::sprintf(cmd, "STARTTLS" CRLF);
471 
472 		if ((p = ::strstr(res, "STARTTLS")) != NULL) {
473 			// Server advertises STARTTLS support
474 			if (SendCommand(cmd) != B_OK) {
475 				delete[] cmd;
476 				return B_ERROR;
477 			}
478 
479 			// We should start TLS negotiation
480 			use_ssl = true;
481 			ctx = SSL_CTX_new(TLSv1_method());
482     		ssl = SSL_new(ctx);
483     		sbio = BIO_new_socket(fSocket,BIO_NOCLOSE);
484     		BIO_set_nbio(sbio, 0);
485     		SSL_set_bio(ssl, sbio, sbio);
486     		SSL_set_connect_state(ssl);
487     		if(SSL_do_handshake(ssl) != 1)
488     			return B_ERROR;
489 
490     		// Should send EHLO command again
491     		if(!esmtp)
492     			::sprintf(cmd, "HELO %s" CRLF, localhost);
493     		else
494     			::sprintf(cmd, "EHLO %s" CRLF, localhost);
495 
496     		if (SendCommand(cmd) != B_OK) {
497     			delete[] cmd;
498     			return B_ERROR;
499     		}
500 		}
501 	}
502 #endif	// USE_SSL
503 
504 	delete[] cmd;
505 
506 	// Check auth type
507 	if (esmtp) {
508 		const char *res = fLog.String();
509 		char *p;
510 		if ((p = ::strstr(res, "250-AUTH")) != NULL) {
511 			if(::strstr(p, "LOGIN"))
512 				fAuthType |= LOGIN;
513 			if(::strstr(p, "PLAIN"))
514 				fAuthType |= PLAIN;
515 			if(::strstr(p, "CRAM-MD5"))
516 				fAuthType |= CRAM_MD5;
517 			if(::strstr(p, "DIGEST-MD5")) {
518 				fAuthType |= DIGEST_MD5;
519 				fServerName = address;
520 			}
521 		}
522 	}
523 	return B_OK;
524 }
525 
526 
527 status_t
528 SMTPProtocol::_SendMessage(const entry_ref& ref)
529 {
530 	// open read write to be able to manipulate in MessageReadyToSend hook
531 	BFile file(&ref, B_READ_WRITE);
532 	status_t status = file.InitCheck();
533 	if (status != B_OK)
534 		return status;
535 
536 	BMessage header;
537 	file >> header;
538 
539 	const char *from = header.FindString("MAIL:from");
540 	const char *to = header.FindString("MAIL:recipients");
541 	if (to == NULL)
542 		to = header.FindString("MAIL:to");
543 
544 	if (to == NULL || from == NULL) {
545 		fLog = "Invalid message headers";
546 		return B_ERROR;
547 	}
548 
549 	NotifyMessageReadyToSend(ref, file);
550 	status = Send(to, from, &file);
551 	if (status != B_OK)
552 		return status;
553 	NotifyMessageSent(ref, file);
554 
555 	off_t size = 0;
556 	file.GetSize(&size);
557 	ReportProgress(size, 1);
558 
559 	return B_OK;
560 }
561 
562 
563 status_t
564 SMTPProtocol::_POP3Authentication()
565 {
566 	const entry_ref& entry = fAccountSettings.InboundAddOnRef();
567 	if (strcmp(entry.name, "POP3") != 0)
568 		return B_ERROR;
569 
570 	status_t (*pop3_smtp_auth)(const BMailAccountSettings&);
571 
572 	BPath path(&entry);
573 	image_id image = load_add_on(path.Path());
574 	if (image < 0)
575 		return B_ERROR;
576 	if (get_image_symbol(image, "pop3_smtp_auth",
577 			B_SYMBOL_TYPE_TEXT, (void **)&pop3_smtp_auth) != B_OK) {
578 		unload_add_on(image);
579 		image = -1;
580 		return B_ERROR;
581 	}
582 	status_t status = (*pop3_smtp_auth)(fAccountSettings);
583 	unload_add_on(image);
584 	return status;
585 }
586 
587 
588 status_t
589 SMTPProtocol::Login(const char *_login, const char *password)
590 {
591 	if (fAuthType == 0)
592 		return B_OK;
593 
594 	const char *login = _login;
595 	char hex_digest[33];
596 	BString out;
597 
598 	int32 loginlen = ::strlen(login);
599 	int32 passlen = ::strlen(password);
600 
601 	if (fAuthType & DIGEST_MD5) {
602 		//******* DIGEST-MD5 Authentication ( tested. works fine [with Cyrus SASL] )
603 		// this implements only the subpart of DIGEST-MD5 which is
604 		// required for authentication to SMTP-servers. Integrity-
605 		// and confidentiality-protection are not implemented, as
606 		// they are provided by the use of OpenSSL.
607 		SendCommand("AUTH DIGEST-MD5" CRLF);
608 		const char *res = fLog.String();
609 
610 		if (strncmp(res, "334", 3) != 0)
611 			return B_ERROR;
612 		int32 baselen = ::strlen(&res[4]);
613 		char *base = new char[baselen+1];
614 		baselen = ::decode_base64(base, &res[4], baselen);
615 		base[baselen] = '\0';
616 
617 		D(bug("base: %s\n", base));
618 
619 		map<BString,BString> challengeMap;
620 		SplitChallengeIntoMap(base, challengeMap);
621 
622 		delete[] base;
623 
624 		BString rawResponse = BString("username=") << '"' << login << '"';
625 		rawResponse << ",realm=" << '"' << challengeMap["realm"] << '"';
626 		rawResponse << ",nonce=" << '"' << challengeMap["nonce"] << '"';
627 		rawResponse << ",nc=00000001";
628 		char temp[33];
629 		for( int i=0; i<32; ++i)
630 			temp[i] = 1+(rand()%254);
631 		temp[32] = '\0';
632 		BString rawCnonce(temp);
633 		BString cnonce;
634 		char* cnoncePtr = cnonce.LockBuffer(rawCnonce.Length()*2);
635 		baselen = ::encode_base64(cnoncePtr, rawCnonce.String(), rawCnonce.Length(), true /* headerMode */);
636 		cnoncePtr[baselen] = '\0';
637 		cnonce.UnlockBuffer(baselen);
638 		rawResponse << ",cnonce=" << '"' << cnonce << '"';
639 		rawResponse << ",qop=auth";
640 		BString digestUriValue	= BString("smtp/") << fServerName;
641 		rawResponse << ",digest-uri=" << '"' << digestUriValue << '"';
642 		char sum[17], hex_digest2[33];
643 		BString a1,a2,kd;
644 		BString t1 = BString(login) << ":"
645 				<< challengeMap["realm"] << ":"
646 				<< password;
647 		MD5Sum(sum, (unsigned char*)t1.String(), t1.Length());
648 		a1 << sum << ":" << challengeMap["nonce"] << ":" << cnonce;
649 		MD5Digest(hex_digest, (unsigned char*)a1.String(), a1.Length());
650 		a2 << "AUTHENTICATE:" << digestUriValue;
651 		MD5Digest(hex_digest2, (unsigned char*)a2.String(), a2.Length());
652 		kd << hex_digest << ':' << challengeMap["nonce"]
653 		   << ":" << "00000001" << ':' << cnonce << ':' << "auth"
654 		   << ':' << hex_digest2;
655 		MD5Digest(hex_digest, (unsigned char*)kd.String(), kd.Length());
656 
657 		rawResponse << ",response=" << hex_digest;
658 		BString postResponse;
659 		char *resp = postResponse.LockBuffer(rawResponse.Length() * 2 + 10);
660 		baselen = ::encode_base64(resp, rawResponse.String(), rawResponse.Length(), true /* headerMode */);
661 		resp[baselen] = 0;
662 		postResponse.UnlockBuffer();
663 		postResponse.Append(CRLF);
664 
665 		SendCommand(postResponse.String());
666 
667 		res = fLog.String();
668 		if (atol(res) >= 500)
669 			return B_ERROR;
670 		// actually, we are supposed to check the rspauth sent back
671 		// by the SMTP-server, but that isn't strictly required,
672 		// so we skip that for now.
673 		SendCommand(CRLF);	// finish off authentication
674 		res = fLog.String();
675 		if (atol(res) < 500)
676 			return B_OK;
677 	}
678 	if (fAuthType & CRAM_MD5) {
679 		//******* CRAM-MD5 Authentication ( tested. works fine [with Cyrus SASL] )
680 		SendCommand("AUTH CRAM-MD5" CRLF);
681 		const char *res = fLog.String();
682 
683 		if (strncmp(res, "334", 3) != 0)
684 			return B_ERROR;
685 		int32 baselen = ::strlen(&res[4]);
686 		char *base = new char[baselen+1];
687 		baselen = ::decode_base64(base, &res[4], baselen);
688 		base[baselen] = '\0';
689 
690 		D(bug("base: %s\n", base));
691 
692 		::MD5HexHmac(hex_digest, (const unsigned char *)base, (int)baselen,
693 			(const unsigned char *)password, (int)passlen);
694 
695 		D(bug("%s\n%s\n", base, hex_digest));
696 
697 		delete[] base;
698 
699 		BString preResponse, postResponse;
700 		preResponse = login;
701 		preResponse << " " << hex_digest << CRLF;
702 		char *resp = postResponse.LockBuffer(preResponse.Length() * 2 + 10);
703 		baselen = ::encode_base64(resp, preResponse.String(), preResponse.Length(), true /* headerMode */);
704 		resp[baselen] = 0;
705 		postResponse.UnlockBuffer();
706 		postResponse.Append(CRLF);
707 
708 		SendCommand(postResponse.String());
709 
710 		res = fLog.String();
711 		if (atol(res) < 500)
712 			return B_OK;
713 	}
714 	if (fAuthType & LOGIN) {
715 		//******* LOGIN Authentication ( tested. works fine)
716 		ssize_t encodedsize; // required by our base64 implementation
717 
718 		SendCommand("AUTH LOGIN" CRLF);
719 		const char *res = fLog.String();
720 
721 		if (strncmp(res, "334", 3) != 0)
722 			return B_ERROR;
723 
724 		// Send login name as base64
725 		char *login64 = new char[loginlen*3 + 6];
726 		encodedsize = ::encode_base64(login64, (char *)login, loginlen, true /* headerMode */);
727 		login64[encodedsize] = 0;
728 		strcat (login64, CRLF);
729 		SendCommand(login64);
730 		delete[] login64;
731 
732 		res = fLog.String();
733 		if (strncmp(res,"334",3) != 0)
734 			return B_ERROR;
735 
736 		// Send password as base64
737 		login64 = new char[passlen*3 + 6];
738 		encodedsize = ::encode_base64(login64, (char *)password, passlen, true /* headerMode */);
739 		login64[encodedsize] = 0;
740 		strcat (login64, CRLF);
741 		SendCommand(login64);
742 		delete[] login64;
743 
744 		res = fLog.String();
745 		if (atol(res) < 500)
746 			return B_OK;
747 	}
748 	if (fAuthType & PLAIN) {
749 		//******* PLAIN Authentication ( tested. works fine [with Cyrus SASL] )
750 		// format is:
751 		// 	authenticateID + \0 + username + \0 + password
752 		// 	(where authenticateID is always empty !?!)
753 		BString preResponse, postResponse;
754 		char *stringPntr;
755 		ssize_t encodedLength;
756 		stringPntr = preResponse.LockBuffer(loginlen + passlen + 3);
757 			// +3 to make room for the two \0-chars between the tokens and
758 			// the final delimiter added by sprintf().
759 		sprintf (stringPntr, "%c%s%c%s", 0, login, 0, password);
760 		preResponse.UnlockBuffer(loginlen + passlen + 2);
761 			// +2 in order to leave out the final delimiter (which is not part
762 			// of the string).
763 		stringPntr = postResponse.LockBuffer(preResponse.Length() * 3);
764 		encodedLength = ::encode_base64(stringPntr, preResponse.String(),
765 			preResponse.Length(), true /* headerMode */);
766 		stringPntr[encodedLength] = 0;
767 		postResponse.UnlockBuffer();
768 		postResponse.Prepend("AUTH PLAIN ");
769 		postResponse << CRLF;
770 
771 		SendCommand(postResponse.String());
772 
773 		const char *res = fLog.String();
774 		if (atol(res) < 500)
775 			return B_OK;
776 	}
777 	return B_ERROR;
778 }
779 
780 
781 void
782 SMTPProtocol::Close()
783 {
784 
785 	BString cmd = "QUIT";
786 	cmd += CRLF;
787 
788 	if (SendCommand(cmd.String()) != B_OK) {
789 		// Error
790 	}
791 
792 #ifdef USE_SSL
793         if (use_ssl)  {
794                 if (ssl)
795                         SSL_shutdown(ssl);
796                 if (ctx)
797                         SSL_CTX_free(ctx);
798         }
799 #endif
800 
801 	close(fSocket);
802 }
803 
804 
805 status_t
806 SMTPProtocol::Send(const char* to, const char* from, BPositionIO *message)
807 {
808 	BString cmd = from;
809 	cmd.Remove(0, cmd.FindFirst("\" <") + 2);
810 	cmd.Prepend("MAIL FROM: ");
811 	cmd += CRLF;
812 	if (SendCommand(cmd.String()) != B_OK)
813 		return B_ERROR;
814 
815 	int32 len = strlen(to);
816 	BString addr("");
817 	for (int32 i = 0;i < len;i++) {
818 		char c = to[i];
819 		if (c != ',')
820 			addr += (char)c;
821 		if (c == ','||i == len-1) {
822 			if(addr.Length() == 0)
823 				continue;
824 			cmd = "RCPT TO: ";
825 			cmd << addr.String() << CRLF;
826 			if (SendCommand(cmd.String()) != B_OK)
827 				return B_ERROR;
828 
829 			addr ="";
830 		}
831 	}
832 
833 	cmd = "DATA";
834 	cmd += CRLF;
835 	if (SendCommand(cmd.String()) != B_OK)
836 		return B_ERROR;
837 
838 	// Send the message data.  Convert lines starting with a period to start
839 	// with two periods and so on.  The actual sequence is CR LF Period.  The
840 	// SMTP server will remove the periods.  Of course, the POP server may then
841 	// add some of its own, but the POP client should take care of them.
842 
843 	ssize_t amountRead;
844 	ssize_t amountToRead;
845 	ssize_t amountUnread;
846 	ssize_t bufferLen = 0;
847 	const int bufferMax = 2000;
848 	bool foundCRLFPeriod;
849 	int i;
850 	bool messageEndedWithCRLF = false;
851 
852 	message->Seek(0, SEEK_END);
853 	amountUnread = message->Position();
854 	message->Seek(0, SEEK_SET);
855 	char *data = new char[bufferMax];
856 
857 	while (true) {
858 		// Fill the buffer if it is getting low, but not every time, to avoid
859 		// small reads.
860 		if (bufferLen < bufferMax / 2) {
861 			amountToRead = bufferMax - bufferLen;
862 			if (amountToRead > amountUnread)
863 				amountToRead = amountUnread;
864 			if (amountToRead > 0) {
865 				amountRead = message->Read (data + bufferLen, amountToRead);
866 				if (amountRead <= 0 || amountRead > amountToRead)
867 					amountUnread = 0; // Just stop reading when an error happens.
868 				else {
869 					amountUnread -= amountRead;
870 					bufferLen += amountRead;
871 				}
872 			}
873 		}
874 
875 		// Look for the next CRLFPeriod triple.
876 		foundCRLFPeriod = false;
877 		for (i = 0; i <= bufferLen - 3; i++) {
878 			if (data[i] == '\r' && data[i+1] == '\n' && data[i+2] == '.') {
879 				foundCRLFPeriod = true;
880 				// Send data up to the CRLF, and include the period too.
881 #ifdef USE_SSL
882 				if (use_ssl) {
883 					if (SSL_write(ssl,data,i + 3) < 0) {
884 						amountUnread = 0; // Stop when an error happens.
885 						bufferLen = 0;
886 						break;
887 					}
888 				} else
889 #endif
890 				if (send (fSocket,data, i + 3,0) < 0) {
891 					amountUnread = 0; // Stop when an error happens.
892 					bufferLen = 0;
893 					break;
894 				}
895 				ReportProgress (i + 2 /* Don't include the double period here */,0);
896 				// Move the data over in the buffer, but leave the period there
897 				// so it gets sent a second time.
898 				memmove(data, data + (i + 2), bufferLen - (i + 2));
899 				bufferLen -= i + 2;
900 				break;
901 			}
902 		}
903 
904 		if (!foundCRLFPeriod) {
905 			if (amountUnread <= 0) { // No more data, all we have is in the buffer.
906 				if (bufferLen > 0) {
907                             #ifdef USE_SSL
908                                     if (use_ssl)
909                                             SSL_write(ssl,data,bufferLen);
910                                     else
911                             #endif
912 					send (fSocket,data, bufferLen,0);
913 					ReportProgress (bufferLen,0);
914 					if (bufferLen >= 2)
915 						messageEndedWithCRLF = (data[bufferLen-2] == '\r' &&
916 							data[bufferLen-1] == '\n');
917 				}
918 				break; // Finished!
919 			}
920 
921 			// Send most of the buffer, except a few characters to overlap with
922 			// the next read, in case the CRLFPeriod is split between reads.
923 			if (bufferLen > 3) {
924                         #ifdef USE_SSL
925                             if (use_ssl) {
926                                     if (SSL_write(ssl,data,bufferLen - 3) < 0)
927                                         break;
928                             } else
929                         #endif
930 				if (send (fSocket,data, bufferLen - 3,0) < 0)
931 					break; // Stop when an error happens.
932 				ReportProgress (bufferLen - 3,0);
933 				memmove (data, data + bufferLen - 3, 3);
934 				bufferLen = 3;
935 			}
936 		}
937 	}
938 	delete [] data;
939 
940 	if (messageEndedWithCRLF)
941 		cmd = "." CRLF; // The standard says don't add extra CRLF.
942 	else
943 		cmd = CRLF "." CRLF;
944 
945 	if (SendCommand(cmd.String()) != B_OK)
946 		return B_ERROR;
947 
948 	return B_OK;
949 }
950 
951 
952 //! Receives response from server.
953 int32
954 SMTPProtocol::ReceiveResponse(BString &out)
955 {
956 	out = "";
957 	int32 len = 0,r;
958 	char buf[SMTP_RESPONSE_SIZE];
959 	bigtime_t timeout = 1000000*180; // timeout 180 secs
960 	bool gotCode = false;
961 	int32 errCode;
962 	BString searchStr = "";
963 
964 	struct timeval tv;
965 	struct fd_set fds;
966 
967 	tv.tv_sec = long(timeout / 1e6);
968 	tv.tv_usec = long(timeout-(tv.tv_sec * 1e6));
969 
970 	/* Initialize (clear) the socket mask. */
971 	FD_ZERO(&fds);
972 
973 	/* Set the socket in the mask. */
974 	FD_SET(fSocket, &fds);
975         int result = -1;
976 #ifdef USE_SSL
977         if ((use_ssl) && (SSL_pending(ssl)))
978             result = 1;
979         else
980 #endif
981             result = select(1 + fSocket, &fds, NULL, NULL, &tv);
982 	if (result < 0)
983 		return errno;
984 
985 	if (result > 0) {
986 		while (1) {
987                  #ifdef USE_SSL
988 			if (use_ssl)
989 				r = SSL_read(ssl,buf,SMTP_RESPONSE_SIZE - 1);
990 			else
991 		  #endif
992 			r = recv(fSocket,buf, SMTP_RESPONSE_SIZE - 1,0);
993 			if (r <= 0)
994 				break;
995 
996 			if (!gotCode)
997 			{
998 				if (buf[3] == ' ' || buf[3] == '-')
999 				{
1000 					errCode = atol(buf);
1001 					gotCode = true;
1002 					searchStr << errCode << ' ';
1003 				}
1004 			}
1005 
1006 			len += r;
1007 			out.Append(buf, r);
1008 
1009 			if (strstr(buf, CRLF) && (out.FindFirst(searchStr) != B_ERROR))
1010 				break;
1011 		}
1012 	} else
1013 		fLog = "SMTP socket timeout.";
1014 
1015 	D(bug("S:%s\n", out.String()));
1016 	return len;
1017 }
1018 
1019 
1020 // Sends SMTP command. Result kept in fLog
1021 
1022 status_t
1023 SMTPProtocol::SendCommand(const char *cmd)
1024 {
1025 	D(bug("C:%s\n", cmd));
1026 
1027 #ifdef USE_SSL
1028 	if (use_ssl && ssl) {
1029 		if (SSL_write(ssl,cmd,::strlen(cmd)) < 0)
1030                     return B_ERROR;
1031 	} else
1032 #endif
1033 	if (send(fSocket,cmd, ::strlen(cmd),0) < 0)
1034 		return B_ERROR;
1035 	fLog = "";
1036 
1037 	// Receive
1038 	while (1) {
1039 		int32 len = ReceiveResponse(fLog);
1040 
1041 		if (len <= 0) {
1042 			D(bug("SMTP: len == %" B_PRId32 "\n", len));
1043 			return B_ERROR;
1044 		}
1045 
1046 		if (fLog.Length() > 4 && (fLog[3] == ' ' || fLog[3] == '-'))
1047 		{
1048 			int32 num = atol(fLog.String());
1049 			D(bug("ReplyNumber: %" B_PRId32 "\n", num));
1050 			if (num >= 500)
1051 				return B_ERROR;
1052 
1053 			break;
1054 		}
1055 	}
1056 
1057 	return B_OK;
1058 }
1059 
1060 
1061 // #pragma mark -
1062 
1063 
1064 extern "C" BOutboundMailProtocol*
1065 instantiate_outbound_protocol(const BMailAccountSettings& settings)
1066 {
1067 	return new SMTPProtocol(settings);
1068 }
1069