vfs: check userland buffers before reading them.
[haiku.git] / src / add-ons / mail_daemon / outbound_protocols / smtp / SMTP.cpp
blob81436313ccbabe45d06b7d1b50428e56605afa6d
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>
6 * Distributed under the terms of the MIT License.
7 */
10 //! Implementation of the SMTP protocol
13 #include "SMTP.h"
15 #include <map>
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>
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>
36 #include <crypt.h>
37 #include <mail_encoding.h>
38 #include <MailSettings.h>
39 #include <NodeMessage.h>
40 #include <ProtocolConfigView.h>
42 #include "md5.h"
45 #undef B_TRANSLATION_CONTEXT
46 #define B_TRANSLATION_CONTEXT "smtp"
49 #define CRLF "\r\n"
50 #define SMTP_RESPONSE_SIZE 8192
52 //#define DEBUG
53 #ifdef DEBUG
54 # define D(x) x
55 # define bug printf
56 #else
57 # define D(x) ;
58 #endif
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
70 using namespace std;
73 ** Function: md5_hmac
74 ** taken from the file rfc2104.txt
75 ** written by Martin Schaaf <mascha@ma-scha.de>
77 void
78 MD5Hmac(unsigned char *digest, const unsigned char* text, int text_len,
79 const unsigned char* key, int key_len)
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;
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;
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);
105 * the HMAC_MD5 transform looks like:
107 * MD5(K XOR opad, MD5(K XOR ipad, text))
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
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;
122 * perform inner MD5
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 */
130 * perform outer MD5
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 */
141 void
142 MD5HexHmac(char *hexdigest, const unsigned char* text, int text_len,
143 const unsigned char* key, int key_len)
145 unsigned char digest[16];
146 int i;
147 unsigned char c;
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);
155 *hexdigest = '\0';
160 ** Function: MD5Sum
161 ** generates an MD5-sum from the given string
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';
173 ** Function: MD5Digest
174 ** generates an MD5-digest from the given string
176 void MD5Digest (char* hexdigest, unsigned char *text, int text_len) {
177 int i;
178 unsigned char digest[17];
179 unsigned char c;
181 MD5Sum((char*)digest, text, text_len);
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);
188 *hexdigest = '\0';
192 ** Function: SplitChallengeIntoMap
193 ** splits a challenge-string into the given map (see RFC-2831)
195 // :
196 static bool
197 SplitChallengeIntoMap(BString str, map<BString,BString>& m)
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++;
221 *s++ = '\0';
222 } else {
223 val = s;
224 while(*s!=0 && *s!=',' && !isspace(*s))
225 s++;
226 if (*s != 0)
227 *s++ = '\0';
229 m[key] = val;
230 while(isspace(*s))
231 s++;
232 if (*s != ',')
233 return false;
234 s++;
236 return true;
240 // #pragma mark -
243 SMTPProtocol::SMTPProtocol(const BMailAccountSettings& settings)
245 BOutboundMailProtocol("SMTP", settings),
246 fAuthType(0)
248 fSettingsMessage = settings.OutboundSettings();
252 SMTPProtocol::~SMTPProtocol()
257 status_t
258 SMTPProtocol::Connect()
260 BString errorMessage;
261 int32 authMethod = fSettingsMessage.FindInt32("auth_method");
263 status_t status = B_ERROR;
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;
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"));
285 if (fSettingsMessage.FindInt32("port") > 0)
286 errorMessage << ":" << fSettingsMessage.FindInt32("port");
288 if (fLog.Length() > 0)
289 errorMessage << B_TRANSLATE(". The server says:\n") << fLog;
290 else {
291 errorMessage << ". " << strerror(status);
294 ShowError(errorMessage.String());
296 return status;
299 const char* password = get_passwd(&fSettingsMessage, "cpasswd");
300 status = Login(fSettingsMessage.FindString("username"), password);
301 delete[] password;
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"));
309 ShowError(errorMessage.String());
311 return B_OK;
315 void
316 SMTPProtocol::Disconnect()
318 Close();
322 //! Process EMail to be sent
323 status_t
324 SMTPProtocol::HandleSendMessages(const BMessage& message, off_t totalBytes)
326 type_code type;
327 int32 count;
328 status_t status = message.GetInfo("ref", &type, &count);
329 if (status != B_OK)
330 return status;
332 // TODO: sort out already sent messages -- the request could
333 // be issued while we're busy sending them already
335 SetTotalItems(count);
336 SetTotalItemsSize(totalBytes);
338 status = Connect();
339 if (status != B_OK)
340 return status;
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());
351 ResetProgress();
352 break;
356 Disconnect();
357 return B_ERROR;
361 //! Opens connection to server
362 status_t
363 SMTPProtocol::Open(const char *address, int port, bool esmtp)
365 ReportProgress(0, 0, B_TRANSLATE("Connecting to server" B_UTF8_ELLIPSIS));
367 use_ssl = (fSettingsMessage.FindInt32("flavor") == 1);
369 if (port <= 0)
370 port = use_ssl ? 465 : 25;
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();
381 if (addr.Port() == 0)
382 addr.SetPort(port);
384 if (use_ssl)
385 fSocket = new(std::nothrow) BSecureSocket;
386 else
387 fSocket = new(std::nothrow) BSocket;
389 if (!fSocket)
390 return B_NO_MEMORY;
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;
402 BString line;
403 ReceiveResponse(line);
405 char localhost[255];
406 gethostname(localhost, 255);
408 if (localhost[0] == 0)
409 strcpy(localhost, "namethisbebox");
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);
417 if (SendCommand(cmd) != B_OK) {
418 delete[] cmd;
419 return B_ERROR;
422 delete[] cmd;
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 if(::strstr(p, "LOGIN"))
430 fAuthType |= LOGIN;
431 if(::strstr(p, "PLAIN"))
432 fAuthType |= PLAIN;
433 if(::strstr(p, "CRAM-MD5"))
434 fAuthType |= CRAM_MD5;
435 if(::strstr(p, "DIGEST-MD5")) {
436 fAuthType |= DIGEST_MD5;
437 fServerName = address;
441 return B_OK;
445 status_t
446 SMTPProtocol::_SendMessage(const entry_ref& ref)
448 // open read write to be able to manipulate in MessageReadyToSend hook
449 BFile file(&ref, B_READ_WRITE);
450 status_t status = file.InitCheck();
451 if (status != B_OK)
452 return status;
454 BMessage header;
455 file >> header;
457 const char *from = header.FindString("MAIL:from");
458 const char *to = header.FindString("MAIL:recipients");
459 if (to == NULL)
460 to = header.FindString("MAIL:to");
462 if (to == NULL || from == NULL) {
463 fLog = "Invalid message headers";
464 return B_ERROR;
467 NotifyMessageReadyToSend(ref, file);
468 status = Send(to, from, &file);
469 if (status != B_OK)
470 return status;
471 NotifyMessageSent(ref, file);
473 off_t size = 0;
474 file.GetSize(&size);
475 ReportProgress(size, 1);
477 return B_OK;
481 status_t
482 SMTPProtocol::_POP3Authentication()
484 const entry_ref& entry = fAccountSettings.InboundAddOnRef();
485 if (strcmp(entry.name, "POP3") != 0)
486 return B_ERROR;
488 status_t (*pop3_smtp_auth)(const BMailAccountSettings&);
490 BPath path(&entry);
491 image_id image = load_add_on(path.Path());
492 if (image < 0)
493 return B_ERROR;
494 if (get_image_symbol(image, "pop3_smtp_auth",
495 B_SYMBOL_TYPE_TEXT, (void **)&pop3_smtp_auth) != B_OK) {
496 unload_add_on(image);
497 image = -1;
498 return B_ERROR;
500 status_t status = (*pop3_smtp_auth)(fAccountSettings);
501 unload_add_on(image);
502 return status;
506 status_t
507 SMTPProtocol::Login(const char *_login, const char *password)
509 if (fAuthType == 0)
510 return B_OK;
512 const char *login = _login;
513 char hex_digest[33];
514 BString out;
516 int32 loginlen = ::strlen(login);
517 int32 passlen = ::strlen(password);
519 if (fAuthType & DIGEST_MD5) {
520 //******* DIGEST-MD5 Authentication ( tested. works fine [with Cyrus SASL] )
521 // this implements only the subpart of DIGEST-MD5 which is
522 // required for authentication to SMTP-servers. Integrity-
523 // and confidentiality-protection are not implemented, as
524 // they are provided by the use of OpenSSL.
525 SendCommand("AUTH DIGEST-MD5" CRLF);
526 const char *res = fLog.String();
528 if (strncmp(res, "334", 3) != 0)
529 return B_ERROR;
530 int32 baselen = ::strlen(&res[4]);
531 char *base = new char[baselen+1];
532 baselen = ::decode_base64(base, &res[4], baselen);
533 base[baselen] = '\0';
535 D(bug("base: %s\n", base));
537 map<BString,BString> challengeMap;
538 SplitChallengeIntoMap(base, challengeMap);
540 delete[] base;
542 BString rawResponse = BString("username=") << '"' << login << '"';
543 rawResponse << ",realm=" << '"' << challengeMap["realm"] << '"';
544 rawResponse << ",nonce=" << '"' << challengeMap["nonce"] << '"';
545 rawResponse << ",nc=00000001";
546 char temp[33];
547 for( int i=0; i<32; ++i)
548 temp[i] = 1+(rand()%254);
549 temp[32] = '\0';
550 BString rawCnonce(temp);
551 BString cnonce;
552 char* cnoncePtr = cnonce.LockBuffer(rawCnonce.Length()*2);
553 baselen = ::encode_base64(cnoncePtr, rawCnonce.String(), rawCnonce.Length(), true /* headerMode */);
554 cnoncePtr[baselen] = '\0';
555 cnonce.UnlockBuffer(baselen);
556 rawResponse << ",cnonce=" << '"' << cnonce << '"';
557 rawResponse << ",qop=auth";
558 BString digestUriValue = BString("smtp/") << fServerName;
559 rawResponse << ",digest-uri=" << '"' << digestUriValue << '"';
560 char sum[17], hex_digest2[33];
561 BString a1,a2,kd;
562 BString t1 = BString(login) << ":"
563 << challengeMap["realm"] << ":"
564 << password;
565 MD5Sum(sum, (unsigned char*)t1.String(), t1.Length());
566 a1 << sum << ":" << challengeMap["nonce"] << ":" << cnonce;
567 MD5Digest(hex_digest, (unsigned char*)a1.String(), a1.Length());
568 a2 << "AUTHENTICATE:" << digestUriValue;
569 MD5Digest(hex_digest2, (unsigned char*)a2.String(), a2.Length());
570 kd << hex_digest << ':' << challengeMap["nonce"]
571 << ":" << "00000001" << ':' << cnonce << ':' << "auth"
572 << ':' << hex_digest2;
573 MD5Digest(hex_digest, (unsigned char*)kd.String(), kd.Length());
575 rawResponse << ",response=" << hex_digest;
576 BString postResponse;
577 char *resp = postResponse.LockBuffer(rawResponse.Length() * 2 + 10);
578 baselen = ::encode_base64(resp, rawResponse.String(), rawResponse.Length(), true /* headerMode */);
579 resp[baselen] = 0;
580 postResponse.UnlockBuffer();
581 postResponse.Append(CRLF);
583 SendCommand(postResponse.String());
585 res = fLog.String();
586 if (atol(res) >= 500)
587 return B_ERROR;
588 // actually, we are supposed to check the rspauth sent back
589 // by the SMTP-server, but that isn't strictly required,
590 // so we skip that for now.
591 SendCommand(CRLF); // finish off authentication
592 res = fLog.String();
593 if (atol(res) < 500)
594 return B_OK;
596 if (fAuthType & CRAM_MD5) {
597 //******* CRAM-MD5 Authentication ( tested. works fine [with Cyrus SASL] )
598 SendCommand("AUTH CRAM-MD5" CRLF);
599 const char *res = fLog.String();
601 if (strncmp(res, "334", 3) != 0)
602 return B_ERROR;
603 int32 baselen = ::strlen(&res[4]);
604 char *base = new char[baselen+1];
605 baselen = ::decode_base64(base, &res[4], baselen);
606 base[baselen] = '\0';
608 D(bug("base: %s\n", base));
610 ::MD5HexHmac(hex_digest, (const unsigned char *)base, (int)baselen,
611 (const unsigned char *)password, (int)passlen);
613 D(bug("%s\n%s\n", base, hex_digest));
615 delete[] base;
617 BString preResponse, postResponse;
618 preResponse = login;
619 preResponse << " " << hex_digest << CRLF;
620 char *resp = postResponse.LockBuffer(preResponse.Length() * 2 + 10);
621 baselen = ::encode_base64(resp, preResponse.String(), preResponse.Length(), true /* headerMode */);
622 resp[baselen] = 0;
623 postResponse.UnlockBuffer();
624 postResponse.Append(CRLF);
626 SendCommand(postResponse.String());
628 res = fLog.String();
629 if (atol(res) < 500)
630 return B_OK;
632 if (fAuthType & LOGIN) {
633 //******* LOGIN Authentication ( tested. works fine)
634 ssize_t encodedsize; // required by our base64 implementation
636 SendCommand("AUTH LOGIN" CRLF);
637 const char *res = fLog.String();
639 if (strncmp(res, "334", 3) != 0)
640 return B_ERROR;
642 // Send login name as base64
643 char *login64 = new char[loginlen*3 + 6];
644 encodedsize = ::encode_base64(login64, (char *)login, loginlen, true /* headerMode */);
645 login64[encodedsize] = 0;
646 strcat (login64, CRLF);
647 SendCommand(login64);
648 delete[] login64;
650 res = fLog.String();
651 if (strncmp(res,"334",3) != 0)
652 return B_ERROR;
654 // Send password as base64
655 login64 = new char[passlen*3 + 6];
656 encodedsize = ::encode_base64(login64, (char *)password, passlen, true /* headerMode */);
657 login64[encodedsize] = 0;
658 strcat (login64, CRLF);
659 SendCommand(login64);
660 delete[] login64;
662 res = fLog.String();
663 if (atol(res) < 500)
664 return B_OK;
666 if (fAuthType & PLAIN) {
667 //******* PLAIN Authentication ( tested. works fine [with Cyrus SASL] )
668 // format is:
669 // authenticateID + \0 + username + \0 + password
670 // (where authenticateID is always empty !?!)
671 BString preResponse, postResponse;
672 char *stringPntr;
673 ssize_t encodedLength;
674 stringPntr = preResponse.LockBuffer(loginlen + passlen + 3);
675 // +3 to make room for the two \0-chars between the tokens and
676 // the final delimiter added by sprintf().
677 sprintf (stringPntr, "%c%s%c%s", 0, login, 0, password);
678 preResponse.UnlockBuffer(loginlen + passlen + 2);
679 // +2 in order to leave out the final delimiter (which is not part
680 // of the string).
681 stringPntr = postResponse.LockBuffer(preResponse.Length() * 3);
682 encodedLength = ::encode_base64(stringPntr, preResponse.String(),
683 preResponse.Length(), true /* headerMode */);
684 stringPntr[encodedLength] = 0;
685 postResponse.UnlockBuffer();
686 postResponse.Prepend("AUTH PLAIN ");
687 postResponse << CRLF;
689 SendCommand(postResponse.String());
691 const char *res = fLog.String();
692 if (atol(res) < 500)
693 return B_OK;
695 return B_ERROR;
699 void
700 SMTPProtocol::Close()
703 BString cmd = "QUIT";
704 cmd += CRLF;
706 if (SendCommand(cmd.String()) != B_OK) {
707 // Error
710 delete fSocket;
714 status_t
715 SMTPProtocol::Send(const char* to, const char* from, BPositionIO *message)
717 BString cmd = from;
718 cmd.Remove(0, cmd.FindFirst("\" <") + 2);
719 cmd.Prepend("MAIL FROM: ");
720 cmd += CRLF;
721 if (SendCommand(cmd.String()) != B_OK)
722 return B_ERROR;
724 int32 len = strlen(to);
725 BString addr("");
726 for (int32 i = 0;i < len;i++) {
727 char c = to[i];
728 if (c != ',')
729 addr += (char)c;
730 if (c == ','||i == len-1) {
731 if(addr.Length() == 0)
732 continue;
733 cmd = "RCPT TO: ";
734 cmd << addr.String() << CRLF;
735 if (SendCommand(cmd.String()) != B_OK)
736 return B_ERROR;
738 addr ="";
742 cmd = "DATA";
743 cmd += CRLF;
744 if (SendCommand(cmd.String()) != B_OK)
745 return B_ERROR;
747 // Send the message data. Convert lines starting with a period to start
748 // with two periods and so on. The actual sequence is CR LF Period. The
749 // SMTP server will remove the periods. Of course, the POP server may then
750 // add some of its own, but the POP client should take care of them.
752 ssize_t amountRead;
753 ssize_t amountToRead;
754 ssize_t amountUnread;
755 ssize_t bufferLen = 0;
756 const int bufferMax = 2000;
757 bool foundCRLFPeriod;
758 int i;
759 bool messageEndedWithCRLF = false;
761 message->Seek(0, SEEK_END);
762 amountUnread = message->Position();
763 message->Seek(0, SEEK_SET);
764 char *data = new char[bufferMax];
766 while (true) {
767 // Fill the buffer if it is getting low, but not every time, to avoid
768 // small reads.
769 if (bufferLen < bufferMax / 2) {
770 amountToRead = bufferMax - bufferLen;
771 if (amountToRead > amountUnread)
772 amountToRead = amountUnread;
773 if (amountToRead > 0) {
774 amountRead = message->Read (data + bufferLen, amountToRead);
775 if (amountRead <= 0 || amountRead > amountToRead)
776 amountUnread = 0; // Just stop reading when an error happens.
777 else {
778 amountUnread -= amountRead;
779 bufferLen += amountRead;
784 // Look for the next CRLFPeriod triple.
785 foundCRLFPeriod = false;
786 for (i = 0; i <= bufferLen - 3; i++) {
787 if (data[i] == '\r' && data[i+1] == '\n' && data[i+2] == '.') {
788 foundCRLFPeriod = true;
789 // Send data up to the CRLF, and include the period too.
790 if (fSocket->Write(data, i + 3) < 0) {
791 amountUnread = 0; // Stop when an error happens.
792 bufferLen = 0;
793 break;
795 ReportProgress (i + 2 /* Don't include the double period here */,0);
796 // Move the data over in the buffer, but leave the period there
797 // so it gets sent a second time.
798 memmove(data, data + (i + 2), bufferLen - (i + 2));
799 bufferLen -= i + 2;
800 break;
804 if (!foundCRLFPeriod) {
805 if (amountUnread <= 0) { // No more data, all we have is in the buffer.
806 if (bufferLen > 0) {
807 fSocket->Write(data, bufferLen);
808 ReportProgress(bufferLen, 0);
809 if (bufferLen >= 2)
810 messageEndedWithCRLF = (data[bufferLen-2] == '\r' &&
811 data[bufferLen-1] == '\n');
813 break; // Finished!
816 // Send most of the buffer, except a few characters to overlap with
817 // the next read, in case the CRLFPeriod is split between reads.
818 if (bufferLen > 3) {
819 if (fSocket->Write(data, bufferLen - 3) < 0)
820 break; // Stop when an error happens.
822 ReportProgress(bufferLen - 3, 0);
823 memmove (data, data + bufferLen - 3, 3);
824 bufferLen = 3;
828 delete [] data;
830 if (messageEndedWithCRLF)
831 cmd = "." CRLF; // The standard says don't add extra CRLF.
832 else
833 cmd = CRLF "." CRLF;
835 if (SendCommand(cmd.String()) != B_OK)
836 return B_ERROR;
838 return B_OK;
842 //! Receives response from server.
843 int32
844 SMTPProtocol::ReceiveResponse(BString &out)
846 out = "";
847 int32 len = 0,r;
848 char buf[SMTP_RESPONSE_SIZE];
849 bigtime_t timeout = 1000000*180; // timeout 180 secs
850 bool gotCode = false;
851 int32 errCode;
852 BString searchStr = "";
854 if (fSocket->WaitForReadable(timeout) == B_OK) {
855 while (1) {
856 r = fSocket->Read(buf, SMTP_RESPONSE_SIZE - 1);
857 if (r <= 0)
858 break;
860 if (!gotCode) {
861 if (buf[3] == ' ' || buf[3] == '-') {
862 errCode = atol(buf);
863 gotCode = true;
864 searchStr << errCode << ' ';
868 len += r;
869 out.Append(buf, r);
871 if (strstr(buf, CRLF) && (out.FindFirst(searchStr) != B_ERROR))
872 break;
874 } else
875 fLog = "SMTP socket timeout.";
877 D(bug("S:%s\n", out.String()));
878 return len;
882 // Sends SMTP command. Result kept in fLog
884 status_t
885 SMTPProtocol::SendCommand(const char *cmd)
887 D(bug("C:%s\n", cmd));
889 if (fSocket->Write(cmd, ::strlen(cmd)) < 0)
890 return B_ERROR;
891 fLog = "";
893 // Receive
894 while (1) {
895 int32 len = ReceiveResponse(fLog);
897 if (len <= 0) {
898 D(bug("SMTP: len == %" B_PRId32 "\n", len));
899 return B_ERROR;
902 if (fLog.Length() > 4 && (fLog[3] == ' ' || fLog[3] == '-'))
904 int32 num = atol(fLog.String());
905 D(bug("ReplyNumber: %" B_PRId32 "\n", num));
906 if (num >= 500)
907 return B_ERROR;
909 break;
913 return B_OK;
917 // #pragma mark -
920 extern "C" BOutboundMailProtocol*
921 instantiate_outbound_protocol(const BMailAccountSettings& settings)
923 return new SMTPProtocol(settings);