/*
* Copyright 2007-2015, Haiku, Inc. All rights reserved.
* Copyright 2001-2002 Dr. Zoidberg Enterprises. All rights reserved.
* Copyright 2011, Clemens Zeidler <haiku@clemens-zeidler.de>
*
* Distributed under the terms of the MIT License.
*/
//! Implementation of the SMTP protocol
#include "SMTP.h"
#include <map>
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <unistd.h>
#include <Alert.h>
#include <Catalog.h>
#include <DataIO.h>
#include <Entry.h>
#include <File.h>
#include <MenuField.h>
#include <Message.h>
#include <Path.h>
#include <Socket.h>
#include <SecureSocket.h>
#include <TextControl.h>
#include <crypt.h>
#include <mail_encoding.h>
#include <MailSettings.h>
#include <NodeMessage.h>
#include <ProtocolConfigView.h>
#include "md5.h"
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "smtp"
#define CRLF "\r\n"
#define SMTP_RESPONSE_SIZE 8192
//#define DEBUG
#ifdef DEBUG
# define D(x) x
# define bug printf
#else
# define D(x) ;
#endif
// Authentication types recognized. Not all methods are implemented.
enum AuthType {
LOGIN = 1,
PLAIN = 1 << 2,
CRAM_MD5 = 1 << 3,
DIGEST_MD5 = 1 << 4
};
using namespace std;
/*
** Function: md5_hmac
** taken from the file rfc2104.txt
** written by Martin Schaaf <mascha@ma-scha.de>
*/
void
MD5Hmac(unsigned char *digest, const unsigned char* text, int text_len,
const unsigned char* key, int key_len)
{
MD5_CTX context;
unsigned char k_ipad[64];
// inner padding - key XORd with ipad
unsigned char k_opad[64];
// outer padding - key XORd with opad
int i;
/* start out by storing key in pads */
memset(k_ipad, 0, sizeof k_ipad);
memset(k_opad, 0, sizeof k_opad);
if (key_len > 64) {
/* if key is longer than 64 bytes reset it to key=MD5(key) */
MD5_CTX tctx;
MD5_Init(&tctx);
MD5_Update(&tctx, (unsigned char*)key, key_len);
MD5_Final(k_ipad, &tctx);
MD5_Final(k_opad, &tctx);
} else {
memcpy(k_ipad, key, key_len);
memcpy(k_opad, key, key_len);
}
/*
* the HMAC_MD5 transform looks like:
*
* MD5(K XOR opad, MD5(K XOR ipad, text))
*
* where K is an n byte key
* ipad is the byte 0x36 repeated 64 times
* opad is the byte 0x5c repeated 64 times
* and text is the data being protected
*/
/* XOR key with ipad and opad values */
for (i = 0; i < 64; i++) {
k_ipad[i] ^= 0x36;
k_opad[i] ^= 0x5c;
}
/*
* perform inner MD5
*/
MD5_Init(&context); /* init context for 1st
* pass */
MD5_Update(&context, k_ipad, 64); /* start with inner pad */
MD5_Update(&context, (unsigned char*)text, text_len); /* then text of datagram */
MD5_Final(digest, &context); /* finish up 1st pass */
/*
* perform outer MD5
*/
MD5_Init(&context); /* init context for 2nd
* pass */
MD5_Update(&context, k_opad, 64); /* start with outer pad */
MD5_Update(&context, digest, 16); /* then results of 1st
* hash */
MD5_Final(digest, &context); /* finish up 2nd pass */
}
void
MD5HexHmac(char *hexdigest, const unsigned char* text, int text_len,
const unsigned char* key, int key_len)
{
unsigned char digest[16];
int i;
unsigned char c;
MD5Hmac(digest, text, text_len, key, key_len);
for (i = 0; i < 16; i++) {
c = digest[i];
*hexdigest++ = (c > 0x9F ? 'a'-10 : '0')+(c>>4);
*hexdigest++ = ((c&0x0F) > 9 ? 'a'-10 : '0')+(c&0x0F);
}
*hexdigest = '\0';
}
/*
** Function: MD5Sum
** generates an MD5-sum from the given string
*/
void
MD5Sum (char* sum, unsigned char *text, int text_len) {
MD5_CTX context;
MD5_Init(&context);
MD5_Update(&context, text, text_len);
MD5_Final((unsigned char*)sum, &context);
sum[16] = '\0';
}
/*
** Function: MD5Digest
** generates an MD5-digest from the given string
*/
void MD5Digest (char* hexdigest, unsigned char *text, int text_len) {
int i;
unsigned char digest[17];
unsigned char c;
MD5Sum((char*)digest, text, text_len);
for (i = 0; i < 16; i++) {
c = digest[i];
*hexdigest++ = (c > 0x9F ? 'a'-10 : '0')+(c>>4);
*hexdigest++ = ((c&0x0F) > 9 ? 'a'-10 : '0')+(c&0x0F);
}
*hexdigest = '\0';
}
/*
** Function: SplitChallengeIntoMap
** splits a challenge-string into the given map (see RFC-2831)
*/
// :
static bool
SplitChallengeIntoMap(BString str, map<BString,BString>& m)
{
m.clear();
const char* key;
const char* val;
char* s = (char*)str.String();
while(*s != 0) {
while(isspace(*s))
s++;
key = s;
while(isalpha(*s))
s++;
if (*s != '=')
return false;
*s++ = '\0';
while(isspace(*s))
s++;
if (*s=='"') {
val = ++s;
while(*s!='"') {
if (*s == 0)
return false;
s++;
}
*s++ = '\0';
} else {
val = s;
while(*s!=0 && *s!=',' && !isspace(*s))
s++;
if (*s != 0)
*s++ = '\0';
}
m[key] = val;
while(isspace(*s))
s++;
if (*s != ',')
return false;
s++;
}
return true;
}
// #pragma mark -
SMTPProtocol::SMTPProtocol(const BMailAccountSettings& settings)
:
BOutboundMailProtocol("SMTP", settings),
fAuthType(0)
{
fSettingsMessage = settings.OutboundSettings();
}
SMTPProtocol::~SMTPProtocol()
{
}
status_t
SMTPProtocol::Connect()
{
BString errorMessage;
int32 authMethod = fSettingsMessage.FindInt32("auth_method");
status_t status = B_ERROR;
if (authMethod == 2) {
// POP3 authentication is handled here instead of SMTPProtocol::Login()
// because some servers obviously don't like establishing the connection
// to the SMTP server first...
status_t status = _POP3Authentication();
if (status < B_OK) {
errorMessage << B_TRANSLATE("POP3 authentication failed. The "
"server said:\n") << fLog;
ShowError(errorMessage.String());
return status;
}
}
status = Open(fSettingsMessage.FindString("server"),
fSettingsMessage.FindInt32("port"), authMethod == 1);
if (status < B_OK) {
errorMessage << B_TRANSLATE("Error while opening connection to %serv");
errorMessage.ReplaceFirst("%serv",
fSettingsMessage.FindString("server"));
if (fSettingsMessage.FindInt32("port") > 0)
errorMessage << ":" << fSettingsMessage.FindInt32("port");
if (fLog.Length() > 0)
errorMessage << B_TRANSLATE(". The server says:\n") << fLog;
else {
errorMessage << ". " << strerror(status);
}
ShowError(errorMessage.String());
return status;
}
const char* password = get_passwd(&fSettingsMessage, "cpasswd");
status = Login(fSettingsMessage.FindString("username"), password);
delete[] password;
if (status != B_OK) {
errorMessage << B_TRANSLATE("Error while logging in to %serv")
<< B_TRANSLATE(". The server said:\n") << fLog;
errorMessage.ReplaceFirst("%serv",
fSettingsMessage.FindString("server"));
ShowError(errorMessage.String());
}
return B_OK;
}
void
SMTPProtocol::Disconnect()
{
Close();
}
//! Process EMail to be sent
status_t
SMTPProtocol::HandleSendMessages(const BMessage& message, off_t totalBytes)
{
type_code type;
int32 count;
status_t status = message.GetInfo("ref", &type, &count);
if (status != B_OK)
return status;
// TODO: sort out already sent messages -- the request could
// be issued while we're busy sending them already
SetTotalItems(count);
SetTotalItemsSize(totalBytes);
status = Connect();
if (status != B_OK)
return status;
entry_ref ref;
for (int32 i = 0; message.FindRef("ref", i++, &ref) == B_OK;) {
status = _SendMessage(ref);
if (status != B_OK) {
BString error;
error << "An error occurred while sending the message "
<< ref.name << " (" << strerror(status) << "):\n" << fLog;
ShowError(error.String());
ResetProgress();
break;
}
}
Disconnect();
return B_ERROR;
}
//! Opens connection to server
status_t
SMTPProtocol::Open(const char *address, int port, bool esmtp)
{
ReportProgress(0, 0, B_TRANSLATE("Connecting to server" B_UTF8_ELLIPSIS));
use_ssl = (fSettingsMessage.FindInt32("flavor") == 1);
if (port <= 0)
port = use_ssl ? 465 : 25;
BNetworkAddress addr(address);
if (addr.InitCheck() != B_OK) {
BString str;
str.SetToFormat("Invalid network address for SMTP server: %s",
strerror(addr.InitCheck()));
ShowError(str.String());
return addr.InitCheck();
}
if (addr.Port() == 0)
addr.SetPort(port);
if (use_ssl)
fSocket = new(std::nothrow) BSecureSocket;
else
fSocket = new(std::nothrow) BSocket;
if (!fSocket)
return B_NO_MEMORY;
if (fSocket->Connect(addr) != B_OK) {
BString error;
error << "Could not connect to SMTP server "
<< fSettingsMessage.FindString("server");
error << ":" << addr.Port();
ShowError(error.String());
delete fSocket;
return B_ERROR;
}
BString line;
ReceiveResponse(line);
char localhost[255];
gethostname(localhost, 255);
if (localhost[0] == 0)
strcpy(localhost, "namethisbebox");
char *cmd = new char[::strlen(localhost)+8];
if (!esmtp)
::sprintf(cmd,"HELO %s" CRLF, localhost);
else
::sprintf(cmd,"EHLO %s" CRLF, localhost);
if (SendCommand(cmd) != B_OK) {
delete[] cmd;
return B_ERROR;
}
delete[] cmd;
// Check auth type
if (esmtp) {
const char *res = fLog.String();
char *p;
if ((p = ::strstr(res, "250-AUTH")) != NULL
|| (p = ::strstr(res, "250 AUTH")) != NULL) {
if(::strstr(p, "LOGIN"))
fAuthType |= LOGIN;
if(::strstr(p, "PLAIN"))
fAuthType |= PLAIN;
if(::strstr(p, "CRAM-MD5"))
fAuthType |= CRAM_MD5;
if(::strstr(p, "DIGEST-MD5")) {
fAuthType |= DIGEST_MD5;
fServerName = address;
}
}
}
return B_OK;
}
status_t
SMTPProtocol::_SendMessage(const entry_ref& ref)
{
// open read write to be able to manipulate in MessageReadyToSend hook
BFile file(&ref, B_READ_WRITE);
status_t status = file.InitCheck();
if (status != B_OK)
return status;
BMessage header;
file >> header;
const char *from = header.FindString("MAIL:from");
const char *to = header.FindString("MAIL:recipients");
if (to == NULL)
to = header.FindString("MAIL:to");
if (to == NULL || from == NULL) {
fLog = "Invalid message headers";
return B_ERROR;
}
NotifyMessageReadyToSend(ref, file);
status = Send(to, from, &file);
if (status != B_OK)
return status;
NotifyMessageSent(ref, file);
off_t size = 0;
file.GetSize(&size);
ReportProgress(size, 1);
return B_OK;
}
status_t
SMTPProtocol::_POP3Authentication()
{
const entry_ref& entry = fAccountSettings.InboundAddOnRef();
if (strcmp(entry.name, "POP3") != 0)
return B_ERROR;
status_t (*pop3_smtp_auth)(const BMailAccountSettings&);
BPath path(&entry);
image_id image = load_add_on(path.Path());
if (image < 0)
return B_ERROR;
if (get_image_symbol(image, "pop3_smtp_auth",
B_SYMBOL_TYPE_TEXT, (void **)&pop3_smtp_auth) != B_OK) {
unload_add_on(image);
image = -1;
return B_ERROR;
}
status_t status = (*pop3_smtp_auth)(fAccountSettings);
unload_add_on(image);
return status;
}
status_t
SMTPProtocol::Login(const char *_login, const char *password)
{
if (fAuthType == 0)
return B_OK;
const char *login = _login;
char hex_digest[33];
BString out;
int32 loginlen = ::strlen(login);
int32 passlen = ::strlen(password);
if (fAuthType & DIGEST_MD5) {
//******* DIGEST-MD5 Authentication ( tested. works fine [with Cyrus SASL] )
// this implements only the subpart of DIGEST-MD5 which is
// required for authentication to SMTP-servers. Integrity-
// and confidentiality-protection are not implemented, as
// they are provided by the use of OpenSSL.
SendCommand("AUTH DIGEST-MD5" CRLF);
const char *res = fLog.String();
if (strncmp(res, "334", 3) != 0)
return B_ERROR;
int32 baselen = ::strlen(&res[4]);
char *base = new char[baselen+1];
baselen = ::decode_base64(base, &res[4], baselen);
base[baselen] = '\0';
D(bug("base: %s\n", base));
map<BString,BString> challengeMap;
SplitChallengeIntoMap(base, challengeMap);
delete[] base;
BString rawResponse = BString("username=") << '"' << login << '"';
rawResponse << ",realm=" << '"' << challengeMap["realm"] << '"';
rawResponse << ",nonce=" << '"' << challengeMap["nonce"] << '"';
rawResponse << ",nc=00000001";
char temp[33];
for( int i=0; i<32; ++i)
temp[i] = 1+(rand()%254);
temp[32] = '\0';
BString rawCnonce(temp);
BString cnonce;
char* cnoncePtr = cnonce.LockBuffer(rawCnonce.Length()*2);
baselen = ::encode_base64(cnoncePtr, rawCnonce.String(), rawCnonce.Length(), true /* headerMode */);
cnoncePtr[baselen] = '\0';
cnonce.UnlockBuffer(baselen);
rawResponse << ",cnonce=" << '"' << cnonce << '"';
rawResponse << ",qop=auth";
BString digestUriValue = BString("smtp/") << fServerName;
rawResponse << ",digest-uri=" << '"' << digestUriValue << '"';
char sum[17], hex_digest2[33];
BString a1,a2,kd;
BString t1 = BString(login) << ":"
<< challengeMap["realm"] << ":"
<< password;
MD5Sum(sum, (unsigned char*)t1.String(), t1.Length());
a1 << sum << ":" << challengeMap["nonce"] << ":" << cnonce;
MD5Digest(hex_digest, (unsigned char*)a1.String(), a1.Length());
a2 << "AUTHENTICATE:" << digestUriValue;
MD5Digest(hex_digest2, (unsigned char*)a2.String(), a2.Length());
kd << hex_digest << ':' << challengeMap["nonce"]
<< ":" << "00000001" << ':' << cnonce << ':' << "auth"
<< ':' << hex_digest2;
MD5Digest(hex_digest, (unsigned char*)kd.String(), kd.Length());
rawResponse << ",response=" << hex_digest;
BString postResponse;
char *resp = postResponse.LockBuffer(rawResponse.Length() * 2 + 10);
baselen = ::encode_base64(resp, rawResponse.String(), rawResponse.Length(), true /* headerMode */);
resp[baselen] = 0;
postResponse.UnlockBuffer();
postResponse.Append(CRLF);
SendCommand(postResponse.String());
res = fLog.String();
if (atol(res) >= 500)
return B_ERROR;
// actually, we are supposed to check the rspauth sent back
// by the SMTP-server, but that isn't strictly required,
// so we skip that for now.
SendCommand(CRLF); // finish off authentication
res = fLog.String();
if (atol(res) < 500)
return B_OK;
}
if (fAuthType & CRAM_MD5) {
//******* CRAM-MD5 Authentication ( tested. works fine [with Cyrus SASL] )
SendCommand("AUTH CRAM-MD5" CRLF);
const char *res = fLog.String();
if (strncmp(res, "334", 3) != 0)
return B_ERROR;
int32 baselen = ::strlen(&res[4]);
char *base = new char[baselen+1];
baselen = ::decode_base64(base, &res[4], baselen);
base[baselen] = '\0';
D(bug("base: %s\n", base));
::MD5HexHmac(hex_digest, (const unsigned char *)base, (int)baselen,
(const unsigned char *)password, (int)passlen);
D(bug("%s\n%s\n", base, hex_digest));
delete[] base;
BString preResponse, postResponse;
preResponse = login;
preResponse << " " << hex_digest << CRLF;
char *resp = postResponse.LockBuffer(preResponse.Length() * 2 + 10);
baselen = ::encode_base64(resp, preResponse.String(), preResponse.Length(), true /* headerMode */);
resp[baselen] = 0;
postResponse.UnlockBuffer();
postResponse.Append(CRLF);
SendCommand(postResponse.String());
res = fLog.String();
if (atol(res) < 500)
return B_OK;
}
if (fAuthType & LOGIN) {
//******* LOGIN Authentication ( tested. works fine)
ssize_t encodedsize; // required by our base64 implementation
SendCommand("AUTH LOGIN" CRLF);
const char *res = fLog.String();
if (strncmp(res, "334", 3) != 0)
return B_ERROR;
// Send login name as base64
char *login64 = new char[loginlen*3 + 6];
encodedsize = ::encode_base64(login64, (char *)login, loginlen, true /* headerMode */);
login64[encodedsize] = 0;
strcat (login64, CRLF);
SendCommand(login64);
delete[] login64;
res = fLog.String();
if (strncmp(res,"334",3) != 0)
return B_ERROR;
// Send password as base64
login64 = new char[passlen*3 + 6];
encodedsize = ::encode_base64(login64, (char *)password, passlen, true /* headerMode */);
login64[encodedsize] = 0;
strcat (login64, CRLF);
SendCommand(login64);
delete[] login64;
res = fLog.String();
if (atol(res) < 500)
return B_OK;
}
if (fAuthType & PLAIN) {
//******* PLAIN Authentication ( tested. works fine [with Cyrus SASL] )
// format is:
// authenticateID + \0 + username + \0 + password
// (where authenticateID is always empty !?!)
BString preResponse, postResponse;
char *stringPntr;
ssize_t encodedLength;
stringPntr = preResponse.LockBuffer(loginlen + passlen + 3);
// +3 to make room for the two \0-chars between the tokens and
// the final delimiter added by sprintf().
sprintf (stringPntr, "%c%s%c%s", 0, login, 0, password);
preResponse.UnlockBuffer(loginlen + passlen + 2);
// +2 in order to leave out the final delimiter (which is not part
// of the string).
stringPntr = postResponse.LockBuffer(preResponse.Length() * 3);
encodedLength = ::encode_base64(stringPntr, preResponse.String(),
preResponse.Length(), true /* headerMode */);
stringPntr[encodedLength] = 0;
postResponse.UnlockBuffer();
postResponse.Prepend("AUTH PLAIN ");
postResponse << CRLF;
SendCommand(postResponse.String());
const char *res = fLog.String();
if (atol(res) < 500)
return B_OK;
}
return B_ERROR;
}
void
SMTPProtocol::Close()
{
BString cmd = "QUIT";
cmd += CRLF;
if (SendCommand(cmd.String()) != B_OK) {
// Error
}
delete fSocket;
}
status_t
SMTPProtocol::Send(const char* to, const char* from, BPositionIO *message)
{
BString cmd = from;
cmd.Remove(0, cmd.FindFirst("\" <") + 2);
cmd.Prepend("MAIL FROM: ");
cmd += CRLF;
if (SendCommand(cmd.String()) != B_OK)
return B_ERROR;
int32 len = strlen(to);
BString addr("");
for (int32 i = 0;i < len;i++) {
char c = to[i];
if (c != ',')
addr += (char)c;
if (c == ','||i == len-1) {
if(addr.Length() == 0)
continue;
cmd = "RCPT TO: ";
cmd << addr.String() << CRLF;
if (SendCommand(cmd.String()) != B_OK)
return B_ERROR;
addr ="";
}
}
cmd = "DATA";
cmd += CRLF;
if (SendCommand(cmd.String()) != B_OK)
return B_ERROR;
// Send the message data. Convert lines starting with a period to start
// with two periods and so on. The actual sequence is CR LF Period. The
// SMTP server will remove the periods. Of course, the POP server may then
// add some of its own, but the POP client should take care of them.
ssize_t amountRead;
ssize_t amountToRead;
ssize_t amountUnread;
ssize_t bufferLen = 0;
const int bufferMax = 2000;
bool foundCRLFPeriod;
int i;
bool messageEndedWithCRLF = false;
message->Seek(0, SEEK_END);
amountUnread = message->Position();
message->Seek(0, SEEK_SET);
char *data = new char[bufferMax];
while (true) {
// Fill the buffer if it is getting low, but not every time, to avoid
// small reads.
if (bufferLen < bufferMax / 2) {
amountToRead = bufferMax - bufferLen;
if (amountToRead > amountUnread)
amountToRead = amountUnread;
if (amountToRead > 0) {
amountRead = message->Read (data + bufferLen, amountToRead);
if (amountRead <= 0 || amountRead > amountToRead)
amountUnread = 0; // Just stop reading when an error happens.
else {
amountUnread -= amountRead;
bufferLen += amountRead;
}
}
}
// Look for the next CRLFPeriod triple.
foundCRLFPeriod = false;
for (i = 0; i <= bufferLen - 3; i++) {
if (data[i] == '\r' && data[i+1] == '\n' && data[i+2] == '.') {
foundCRLFPeriod = true;
// Send data up to the CRLF, and include the period too.
if (fSocket->Write(data, i + 3) < 0) {
amountUnread = 0; // Stop when an error happens.
bufferLen = 0;
break;
}
ReportProgress (i + 2 /* Don't include the double period here */,0);
// Move the data over in the buffer, but leave the period there
// so it gets sent a second time.
memmove(data, data + (i + 2), bufferLen - (i + 2));
bufferLen -= i + 2;
break;
}
}
if (!foundCRLFPeriod) {
if (amountUnread <= 0) { // No more data, all we have is in the buffer.
if (bufferLen > 0) {
fSocket->Write(data, bufferLen);
ReportProgress(bufferLen, 0);
if (bufferLen >= 2)
messageEndedWithCRLF = (data[bufferLen-2] == '\r' &&
data[bufferLen-1] == '\n');
}
break; // Finished!
}
// Send most of the buffer, except a few characters to overlap with
// the next read, in case the CRLFPeriod is split between reads.
if (bufferLen > 3) {
if (fSocket->Write(data, bufferLen - 3) < 0)
break; // Stop when an error happens.
ReportProgress(bufferLen - 3, 0);
memmove (data, data + bufferLen - 3, 3);
bufferLen = 3;
}
}
}
delete [] data;
if (messageEndedWithCRLF)
cmd = "." CRLF; // The standard says don't add extra CRLF.
else
cmd = CRLF "." CRLF;
if (SendCommand(cmd.String()) != B_OK)
return B_ERROR;
return B_OK;
}
//! Receives response from server.
int32
SMTPProtocol::ReceiveResponse(BString &out)
{
out = "";
int32 len = 0,r;
char buf[SMTP_RESPONSE_SIZE];
bigtime_t timeout = 1000000*180; // timeout 180 secs
bool gotCode = false;
int32 errCode;
BString searchStr = "";
if (fSocket->WaitForReadable(timeout) == B_OK) {
while (1) {
r = fSocket->Read(buf, SMTP_RESPONSE_SIZE - 1);
if (r <= 0)
break;
if (!gotCode) {
if (buf[3] == ' ' || buf[3] == '-') {
errCode = atol(buf);
gotCode = true;
searchStr << errCode << ' ';
}
}
len += r;
out.Append(buf, r);
if (strstr(buf, CRLF) && (out.FindFirst(searchStr) != B_ERROR))
break;
}
} else
fLog = "SMTP socket timeout.";
D(bug("S:%s\n", out.String()));
return len;
}
// Sends SMTP command. Result kept in fLog
status_t
SMTPProtocol::SendCommand(const char *cmd)
{
D(bug("C:%s\n", cmd));
if (fSocket->Write(cmd, ::strlen(cmd)) < 0)
return B_ERROR;
fLog = "";
// Receive
while (1) {
int32 len = ReceiveResponse(fLog);
if (len <= 0) {
D(bug("SMTP: len == %" B_PRId32 "\n", len));
return B_ERROR;
}
if (fLog.Length() > 4 && (fLog[3] == ' ' || fLog[3] == '-'))
{
int32 num = atol(fLog.String());
D(bug("ReplyNumber: %" B_PRId32 "\n", num));
if (num >= 500)
return B_ERROR;
break;
}
}
return B_OK;
}
// #pragma mark -
extern "C" BOutboundMailProtocol*
instantiate_outbound_protocol(const BMailAccountSettings& settings)
{
return new SMTPProtocol(settings);
}
↑ V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fSocket, use_ssl, fStatus.