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