/*
* Copyright 2007-2016, 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.
*/
//! POP3Protocol - implementation of the POP3 protocol
#include "POP3.h"
#include <errno.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>
#include <arpa/inet.h>
#include "md5.h"
#include <Alert.h>
#include <Catalog.h>
#include <Debug.h>
#include <Directory.h>
#include <fs_attr.h>
#include <Path.h>
#include <SecureSocket.h>
#include <String.h>
#include <VolumeRoster.h>
#include <Query.h>
#include <mail_util.h>
#include "crypt.h"
#include "MailSettings.h"
#include "MessageIO.h"
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "pop3"
#define POP3_RETRIEVAL_TIMEOUT 60000000
#define CRLF "\r\n"
static void
NotHere(BStringList& that, BStringList& otherList, BStringList* results)
{
for (int32 i = 0; i < otherList.CountStrings(); i++) {
if (!that.HasString(otherList.StringAt(i)))
results->Add(otherList.StringAt(i));
}
}
// #pragma mark -
POP3Protocol::POP3Protocol(const BMailAccountSettings& settings)
:
BInboundMailProtocol("POP3", settings),
fNumMessages(-1),
fMailDropSize(0),
fServerConnection(NULL)
{
printf("POP3Protocol::POP3Protocol(BMailAccountSettings* settings)\n");
fSettings = fAccountSettings.InboundSettings();
fUseSSL = fSettings.FindInt32("flavor") == 1 ? true : false;
if (fSettings.FindString("destination", &fDestinationDir) != B_OK)
fDestinationDir = "/boot/home/mail/in";
create_directory(fDestinationDir, 0777);
fFetchBodyLimit = -1;
if (fSettings.HasInt32("partial_download_limit"))
fFetchBodyLimit = fSettings.FindInt32("partial_download_limit");
}
POP3Protocol::~POP3Protocol()
{
Disconnect();
}
status_t
POP3Protocol::Connect()
{
status_t error = Open(fSettings.FindString("server"),
fSettings.FindInt32("port"), fSettings.FindInt32("flavor"));
if (error != B_OK)
return error;
char* password = get_passwd(&fSettings, "cpasswd");
error = Login(fSettings.FindString("username"), password,
fSettings.FindInt32("auth_method"));
delete[] password;
if (error != B_OK)
fServerConnection->Disconnect();
return error;
}
status_t
POP3Protocol::Disconnect()
{
if (fServerConnection == NULL)
return B_OK;
SendCommand("QUIT" CRLF);
fServerConnection->Disconnect();
delete fServerConnection;
fServerConnection = NULL;
return B_OK;
}
status_t
POP3Protocol::SyncMessages()
{
bool leaveOnServer;
if (fSettings.FindBool("leave_mail_on_server", &leaveOnServer) != B_OK)
leaveOnServer = true;
// create directory if not exist
create_directory(fDestinationDir, 0777);
printf("POP3Protocol::SyncMessages()\n");
_ReadManifest();
SetTotalItems(2);
ReportProgress(1, 0, B_TRANSLATE("Connect to server" B_UTF8_ELLIPSIS));
status_t error = Connect();
if (error != B_OK) {
printf("POP3 could not connect: %s\n", strerror(error));
ResetProgress();
return error;
}
ReportProgress(1, 0, B_TRANSLATE("Getting UniqueIDs" B_UTF8_ELLIPSIS));
error = _RetrieveUniqueIDs();
if (error < B_OK) {
ResetProgress();
Disconnect();
return error;
}
BStringList toDownload;
NotHere(fManifest, fUniqueIDs, &toDownload);
int32 numMessages = toDownload.CountStrings();
if (numMessages == 0) {
CheckForDeletedMessages();
ResetProgress();
Disconnect();
return B_OK;
}
ResetProgress();
SetTotalItems(toDownload.CountStrings());
SetTotalItemsSize(fTotalSize);
printf("POP3: Messages to download: %i\n", (int)toDownload.CountStrings());
for (int32 i = 0; i < toDownload.CountStrings(); i++) {
const char* uid = toDownload.StringAt(i);
int32 toRetrieve = fUniqueIDs.IndexOf(uid);
if (toRetrieve < 0) {
// should not happen!
error = B_NAME_NOT_FOUND;
printf("POP3: uid %s index %i not found in fUniqueIDs!\n", uid,
(int)toRetrieve);
continue;
}
BPath path(fDestinationDir);
BString fileName = "Downloading file... uid: ";
fileName += uid;
fileName.ReplaceAll("/", "_SLASH_");
path.Append(fileName);
BEntry entry(path.Path());
BFile file(&entry, B_READ_WRITE | B_CREATE_FILE | B_ERASE_FILE);
error = file.InitCheck();
if (error != B_OK) {
printf("POP3: Can't create file %s\n ", path.Path());
break;
}
BMailMessageIO mailIO(this, &file, toRetrieve);
BMessage attributes;
entry_ref ref;
entry.GetRef(&ref);
int32 size = MessageSize(toRetrieve);
if (fFetchBodyLimit < 0 || size <= fFetchBodyLimit) {
error = mailIO.Seek(0, SEEK_END);
if (error < 0) {
printf("POP3: Failed to download body %s\n ", uid);
break;
}
ProcessMessageFetched(ref, file, attributes);
if (!leaveOnServer)
Delete(toRetrieve);
} else {
int32 dummy;
error = mailIO.ReadAt(0, &dummy, 1);
if (error < 0) {
printf("POP3: Failed to download header %s\n ", uid);
break;
}
ProcessHeaderFetched(ref, file, attributes);
}
ReportProgress(1, 0);
const BString uidStr(uid);
if (file.WriteAttrString("MAIL:unique_id", &uidStr) < 0)
error = B_ERROR;
file.WriteAttr("MAIL:size", B_INT32_TYPE, 0, &size, sizeof(int32));
write_read_attr(file, B_UNREAD);
// save manifest in case we get disturbed
fManifest.Add(uid);
_WriteManifest();
}
ResetProgress();
CheckForDeletedMessages();
Disconnect();
return error;
}
status_t
POP3Protocol::HandleFetchBody(const entry_ref& ref, const BMessenger& replyTo)
{
ResetProgress("Fetch body");
SetTotalItems(1);
status_t error = Connect();
if (error != B_OK)
return error;
error = _RetrieveUniqueIDs();
if (error != B_OK) {
Disconnect();
return error;
}
BFile file(&ref, B_READ_WRITE);
status_t status = file.InitCheck();
if (status != B_OK) {
Disconnect();
return status;
}
char uidString[256];
BNode node(&ref);
if (node.ReadAttr("MAIL:unique_id", B_STRING_TYPE, 0, uidString, 256) < 0) {
Disconnect();
return B_ERROR;
}
int32 toRetrieve = fUniqueIDs.IndexOf(uidString);
if (toRetrieve < 0) {
Disconnect();
return B_NAME_NOT_FOUND;
}
bool leaveOnServer;
if (fSettings.FindBool("leave_mail_on_server", &leaveOnServer) != B_OK)
leaveOnServer = true;
// TODO: get rid of this BMailMessageIO!
BMailMessageIO io(this, &file, toRetrieve);
// read body
status = io.Seek(0, SEEK_END);
if (status < 0) {
Disconnect();
return status;
}
BMessage attributes;
NotifyBodyFetched(ref, file, attributes);
ReplyBodyFetched(replyTo, ref, B_OK);
if (!leaveOnServer)
Delete(toRetrieve);
ReportProgress(1, 0);
ResetProgress();
Disconnect();
return B_OK;
}
status_t
POP3Protocol::Open(const char* server, int port, int)
{
ReportProgress(0, 0, B_TRANSLATE("Connecting to POP3 server"
B_UTF8_ELLIPSIS));
if (port <= 0)
port = fUseSSL ? 995 : 110;
fLog = "";
// Prime the error message
BString errorMessage(B_TRANSLATE("Error while connecting to server %serv"));
errorMessage.ReplaceFirst("%serv", server);
if (port != 110)
errorMessage << ":" << port;
delete fServerConnection;
fServerConnection = NULL;
BNetworkAddress address(server, port);
if (fUseSSL)
fServerConnection = new(std::nothrow) BSecureSocket(address);
else
fServerConnection = new(std::nothrow) BSocket(address);
status_t status = B_NO_MEMORY;
if (fServerConnection != NULL)
status = fServerConnection->InitCheck();
BString line;
if (status == B_OK) {
ssize_t length = ReceiveLine(line);
if (length < 0)
status = length;
}
if (status != B_OK) {
fServerConnection->Disconnect();
errorMessage << ": " << strerror(status);
ShowError(errorMessage.String());
return status;
}
if (strncmp(line.String(), "+OK", 3) != 0) {
if (line.Length() > 0) {
errorMessage << B_TRANSLATE(". The server said:\n")
<< line.String();
} else
errorMessage << B_TRANSLATE(": No reply.\n");
ShowError(errorMessage.String());
fServerConnection->Disconnect();
return B_ERROR;
}
fLog = line;
return B_OK;
}
status_t
POP3Protocol::Login(const char* uid, const char* password, int method)
{
status_t err;
BString errorMessage(B_TRANSLATE("Error while authenticating user %user"));
errorMessage.ReplaceFirst("%user", uid);
if (method == 1) { //APOP
int32 index = fLog.FindFirst("<");
if(index != B_ERROR) {
ReportProgress(0, 0, B_TRANSLATE("Sending APOP authentication"
B_UTF8_ELLIPSIS));
int32 end = fLog.FindFirst(">", index);
BString timestamp("");
fLog.CopyInto(timestamp, index, end - index + 1);
timestamp += password;
char md5sum[33];
MD5Digest((unsigned char*)timestamp.String(), md5sum);
BString cmd = "APOP ";
cmd += uid;
cmd += " ";
cmd += md5sum;
cmd += CRLF;
err = SendCommand(cmd.String());
if (err != B_OK) {
errorMessage << B_TRANSLATE(". The server said:\n") << fLog;
ShowError(errorMessage.String());
return err;
}
return B_OK;
} else {
errorMessage << B_TRANSLATE(": The server does not support APOP.");
ShowError(errorMessage.String());
return B_NOT_ALLOWED;
}
}
ReportProgress(0, 0, B_TRANSLATE("Sending username" B_UTF8_ELLIPSIS));
BString cmd = "USER ";
cmd += uid;
cmd += CRLF;
err = SendCommand(cmd.String());
if (err != B_OK) {
errorMessage << B_TRANSLATE(". The server said:\n") << fLog;
ShowError(errorMessage.String());
return err;
}
ReportProgress(0, 0, B_TRANSLATE("Sending password" B_UTF8_ELLIPSIS));
cmd = "PASS ";
cmd += password;
cmd += CRLF;
err = SendCommand(cmd.String());
if (err != B_OK) {
errorMessage << B_TRANSLATE(". The server said:\n") << fLog;
ShowError(errorMessage.String());
return err;
}
return B_OK;
}
status_t
POP3Protocol::Stat()
{
ReportProgress(0, 0, B_TRANSLATE("Getting mailbox size" B_UTF8_ELLIPSIS));
if (SendCommand("STAT" CRLF) < B_OK)
return B_ERROR;
int32 messages;
int32 dropSize;
if (sscanf(fLog.String(), "+OK %" B_SCNd32" %" B_SCNd32, &messages,
&dropSize) < 2)
return B_ERROR;
fNumMessages = messages;
fMailDropSize = dropSize;
return B_OK;
}
int32
POP3Protocol::Messages()
{
if (fNumMessages < 0)
Stat();
return fNumMessages;
}
size_t
POP3Protocol::MailDropSize()
{
if (fNumMessages < 0)
Stat();
return fMailDropSize;
}
void
POP3Protocol::CheckForDeletedMessages()
{
{
// Delete things from the manifest no longer on the server
BStringList list;
NotHere(fUniqueIDs, fManifest, &list);
fManifest.Remove(list);
}
if (!fSettings.FindBool("delete_remote_when_local")
|| fManifest.CountStrings() == 0)
return;
BStringList toDelete;
BStringList queryContents;
BVolumeRoster volumes;
BVolume volume;
while (volumes.GetNextVolume(&volume) == B_OK) {
BQuery fido;
entry_ref entry;
fido.SetVolume(&volume);
fido.PushAttr(B_MAIL_ATTR_ACCOUNT_ID);
fido.PushInt32(fAccountSettings.AccountID());
fido.PushOp(B_EQ);
fido.Fetch();
BString uid;
while (fido.GetNextRef(&entry) == B_OK) {
BNode(&entry).ReadAttrString("MAIL:unique_id", &uid);
queryContents.Add(uid);
}
}
NotHere(queryContents, fManifest, &toDelete);
for (int32 i = 0; i < toDelete.CountStrings(); i++) {
printf("delete mail on server uid %s\n", toDelete.StringAt(i).String());
Delete(fUniqueIDs.IndexOf(toDelete.StringAt(i)));
}
// Don't remove ids from fUniqueIDs, the indices have to stay the same when
// retrieving new messages.
fManifest.Remove(toDelete);
// TODO: at some point the purged manifest should be written to disk
// otherwise it will grow forever
}
status_t
POP3Protocol::Retrieve(int32 message, BPositionIO* to)
{
BString cmd;
cmd << "RETR " << message + 1 << CRLF;
status_t status = RetrieveInternal(cmd.String(), message, to, true);
ReportProgress(1, 0);
if (status == B_OK) {
// Check if the actual message size matches the expected one
int32 size = MessageSize(message);
to->Seek(0, SEEK_END);
if (to->Position() != size) {
printf("POP3Protocol::Retrieve Note: message size is %" B_PRIdOFF
", was expecting %" B_PRId32 ", for message #%" B_PRId32
". Could be a transmission error or a bad POP server "
"implementation (does it remove escape codes when it counts "
"size?).\n", to->Position(), size, message);
}
}
return status;
}
status_t
POP3Protocol::GetHeader(int32 message, BPositionIO* to)
{
BString cmd;
cmd << "TOP " << message + 1 << " 0" << CRLF;
return RetrieveInternal(cmd.String(), message, to, false);
}
status_t
POP3Protocol::RetrieveInternal(const char* command, int32 message,
BPositionIO* to, bool postProgress)
{
const int bufSize = 1024 * 30;
// To avoid waiting for the non-arrival of the next data packet, try to
// receive only the message size, plus the 3 extra bytes for the ".\r\n"
// after the message. Of course, if we get it wrong (or it is a huge
// message or has lines starting with escaped periods), it will then switch
// back to receiving full buffers until the message is done.
int amountToReceive = MessageSize(message) + 3;
if (amountToReceive >= bufSize || amountToReceive <= 0)
amountToReceive = bufSize - 1;
BString bufBString; // Used for auto-dealloc on return feature.
char* buf = bufBString.LockBuffer(bufSize);
int amountInBuffer = 0;
int amountReceived;
int testIndex;
char* testStr;
bool cont = true;
bool flushWholeBuffer = false;
to->Seek(0, SEEK_SET);
if (SendCommand(command) != B_OK)
return B_ERROR;
while (cont) {
status_t result = fServerConnection->WaitForReadable(
POP3_RETRIEVAL_TIMEOUT);
if (result == B_TIMED_OUT) {
// No data available, even after waiting a minute.
fLog = "POP3 timeout - no data received after a long wait.";
return B_TIMED_OUT;
}
if (amountToReceive > bufSize - 1 - amountInBuffer)
amountToReceive = bufSize - 1 - amountInBuffer;
amountReceived = fServerConnection->Read(buf + amountInBuffer,
amountToReceive);
if (amountReceived < 0) {
fLog = strerror(amountReceived);
return amountReceived;
}
if (amountReceived == 0) {
fLog = "POP3 data supposedly ready to receive but not received!";
return B_ERROR;
}
amountToReceive = bufSize - 1; // For next time, read a full buffer.
amountInBuffer += amountReceived;
buf[amountInBuffer] = 0; // NUL stops tests past the end of buffer.
// Look for lines starting with a period. A single period by itself on
// a line "\r\n.\r\n" marks the end of the message (thus the need for
// at least five characters in the buffer for testing). A period
// "\r\n.Stuff" at the start of a line get deleted "\r\nStuff", since
// POP adds one as an escape code to let you have message text with
// lines starting with a period. For convenience, assume that no
// messages start with a period on the very first line, so we can
// search for the previous line's "\r\n".
for (testIndex = 0; testIndex <= amountInBuffer - 5; testIndex++) {
testStr = buf + testIndex;
if (testStr[0] == '\r' && testStr[1] == '\n' && testStr[2] == '.') {
if (testStr[3] == '\r' && testStr[4] == '\n') {
// Found the end of the message marker.
// Ignore remaining data.
if (amountInBuffer > testIndex + 5) {
printf("POP3Protocol::RetrieveInternal Ignoring %d "
"bytes of extra data past message end.\n",
amountInBuffer - (testIndex + 5));
}
amountInBuffer = testIndex + 2; // Don't include ".\r\n".
buf[amountInBuffer] = 0;
cont = false;
} else {
// Remove an extra period at the start of a line.
// Inefficient, but it doesn't happen often that you have a
// dot starting a line of text. Of course, a file with a
// lot of double period lines will get processed very
// slowly.
memmove(buf + testIndex + 2, buf + testIndex + 3,
amountInBuffer - (testIndex + 3) + 1);
amountInBuffer--;
// Watch out for the end of buffer case, when the POP text
// is "\r\n..X". Don't want to leave the resulting
// "\r\n.X" in the buffer (flush out the whole buffer),
// since that will get mistakenly evaluated again in the
// next loop and delete a character by mistake.
if (testIndex >= amountInBuffer - 4 && testStr[2] == '.') {
printf("POP3Protocol::RetrieveInternal: Jackpot! "
"You have hit the rare situation with an escaped "
"period at the end of the buffer. Aren't you happy"
"it decodes it correctly?\n");
flushWholeBuffer = true;
}
}
}
}
if (cont && !flushWholeBuffer) {
// Dump out most of the buffer, but leave the last 4 characters for
// comparison continuity, in case the line starting with a period
// crosses a buffer boundary.
if (amountInBuffer > 4) {
to->Write(buf, amountInBuffer - 4);
if (postProgress)
ReportProgress(0, amountInBuffer - 4);
memmove(buf, buf + amountInBuffer - 4, 4);
amountInBuffer = 4;
}
} else {
// Dump everything - end of message or flushing the whole buffer.
to->Write(buf, amountInBuffer);
if (postProgress)
ReportProgress(0, amountInBuffer);
amountInBuffer = 0;
}
}
return B_OK;
}
void
POP3Protocol::Delete(int32 index)
{
BString cmd = "DELE ";
cmd << (index + 1) << CRLF;
if (SendCommand(cmd.String()) != B_OK) {
// Error
}
#if DEBUG
puts(fLog.String());
#endif
// The mail is just marked as deleted and removed from the server when
// sending the QUIT command. Because of that the message number stays
// the same and we keep the uid in the uid list.
}
size_t
POP3Protocol::MessageSize(int32 index)
{
return fSizes[index];
}
ssize_t
POP3Protocol::ReceiveLine(BString& line)
{
int32 length = 0;
bool flag = false;
line = "";
status_t result = fServerConnection->WaitForReadable(
POP3_RETRIEVAL_TIMEOUT);
if (result == B_TIMED_OUT)
return errno;
while (true) {
// Hope there's an end of line out there else this gets stuck.
int32 bytesReceived;
uint8 c = 0;
bytesReceived = fServerConnection->Read((char*)&c, 1);
if (bytesReceived < 0)
return bytesReceived;
if (c == '\n' || bytesReceived == 0)
break;
if (c == '\r') {
flag = true;
} else {
if (flag) {
length++;
line += '\r';
flag = false;
}
length++;
line += (char)c;
}
}
return length;
}
status_t
POP3Protocol::SendCommand(const char* cmd)
{
// Flush any accumulated garbage data before we send our command, so we
// don't misinterrpret responses from previous commands (that got left over
// due to bugs) as being from this command.
while (fServerConnection->WaitForReadable(1000) == B_OK) {
char buffer[4096];
ssize_t amountReceived = fServerConnection->Read(buffer,
sizeof(buffer) - 1);
if (amountReceived < 0)
return amountReceived;
buffer[amountReceived] = 0;
printf("POP3Protocol::SendCommand Bug! Had to flush %" B_PRIdSSIZE
" bytes: %s\n", amountReceived, buffer);
}
if (fServerConnection->Write(cmd, ::strlen(cmd)) < 0) {
fLog = strerror(errno);
printf("POP3Protocol::SendCommand Send \"%s\" failed, code %d: %s\n",
cmd, errno, fLog.String());
return errno;
}
fLog = "";
int32 length = ReceiveLine(fLog);
if (length <= 0 || fLog.ICompare("+OK", 3) == 0)
return B_OK;
if (fLog.ICompare("-ERR", 4) == 0) {
printf("POP3Protocol::SendCommand \"%s\" got error message "
"from server: %s\n", cmd, fLog.String());
return B_ERROR;
}
printf("POP3Protocol::SendCommand \"%s\" got nonsense message "
"from server: %s\n", cmd, fLog.String());
return B_BAD_DATA;
// If it's not +OK, and it's not -ERR, then what the heck
// is it? Presume an error
}
void
POP3Protocol::MD5Digest(unsigned char* in, char* asciiDigest)
{
unsigned char digest[16];
MD5_CTX context;
MD5Init(&context);
MD5Update(&context, in, ::strlen((char*)in));
MD5Final(digest, &context);
for (int i = 0; i < 16; i++) {
sprintf(asciiDigest + 2 * i, "%02x", digest[i]);
}
return;
}
status_t
POP3Protocol::_RetrieveUniqueIDs()
{
fUniqueIDs.MakeEmpty();
fSizes.clear();
fTotalSize = 0;
status_t status = SendCommand("UIDL" CRLF);
if (status != B_OK)
return status;
BString result;
int32 uidOffset;
while (ReceiveLine(result) > 0) {
if (result.ByteAt(0) == '.')
break;
uidOffset = result.FindFirst(' ') + 1;
result.Remove(0, uidOffset);
fUniqueIDs.Add(result);
}
if (SendCommand("LIST" CRLF) != B_OK)
return B_ERROR;
while (ReceiveLine(result) > 0) {
if (result.ByteAt(0) == '.')
break;
int32 index = result.FindLast(" ");
int32 size;
if (index >= 0)
size = atol(&result.String()[index]);
else
size = 0;
fTotalSize += size;
fSizes.push_back(size);
}
return B_OK;
}
void
POP3Protocol::_ReadManifest()
{
fManifest.MakeEmpty();
BString attribute = "MAIL:";
attribute << fAccountSettings.AccountID() << ":manifest";
// In case someone puts multiple accounts in the same directory
BNode node(fDestinationDir);
if (node.InitCheck() != B_OK)
return;
// We already have a directory so we can try to read metadata
// from it. Note that it is normal for this directory not to
// be found on the first run as it will be later created by
// the INBOX system filter.
attr_info info;
if (node.GetAttrInfo(attribute.String(), &info) != B_OK || info.size == 0)
return;
void* flatmanifest = malloc(info.size);
node.ReadAttr(attribute.String(), fManifest.TypeCode(), 0,
flatmanifest, info.size);
fManifest.Unflatten(fManifest.TypeCode(), flatmanifest, info.size);
free(flatmanifest);
}
void
POP3Protocol::_WriteManifest()
{
BString attribute = "MAIL:";
attribute << fAccountSettings.AccountID() << ":manifest";
// In case someone puts multiple accounts in the same directory
BNode node(fDestinationDir);
if (node.InitCheck() != B_OK) {
ShowError("Error while saving account manifest: cannot use "
"destination directory.");
return;
}
node.RemoveAttr(attribute.String());
ssize_t manifestsize = fManifest.FlattenedSize();
void* flatmanifest = malloc(manifestsize);
fManifest.Flatten(flatmanifest, manifestsize);
status_t err = node.WriteAttr(attribute.String(),
fManifest.TypeCode(), 0, flatmanifest, manifestsize);
if (err < 0) {
BString error = "Error while saving account manifest: ";
error << strerror(err);
printf("moep error\n");
ShowError(error.String());
}
free(flatmanifest);
}
// #pragma mark -
BInboundMailProtocol*
instantiate_inbound_protocol(const BMailAccountSettings& settings)
{
return new POP3Protocol(settings);
}
status_t
pop3_smtp_auth(const BMailAccountSettings& settings)
{
POP3Protocol protocol(settings);
protocol.Connect();
protocol.Disconnect();
return B_OK;
}
↑ V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fTotalSize.