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