src/tcp/pop3.c
/***************************************************************************
YAM - Yet Another Mailer
Copyright (C) 1995-2000 Marcel Beck
Copyright (C) 2000-2022 YAM Open Source Team
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
YAM Official Support Site : http://www.yam.ch
YAM OpenSource project : http://sourceforge.net/projects/yamos/
$Id$
***************************************************************************/
#include <string.h>
#include <mui/NList_mcc.h>
#include <mui/NListtree_mcc.h>
#include <clib/alib_protos.h>
#include <proto/dos.h>
#include <proto/exec.h>
#include <proto/timer.h>
#include <proto/utility.h>
#include "extrasrc.h"
#include "YAM.h"
#include "YAM_error.h"
#include "YAM_find.h"
#include "YAM_folderconfig.h"
#include "YAM_mainFolder.h"
#include "YAM_stringsizes.h"
#include "mime/md5.h"
#include "mui/ClassesExtra.h"
#include "mui/PreselectionWindow.h"
#include "mui/StringRequestWindow.h"
#include "mui/TransferControlGroup.h"
#include "mui/YAMApplication.h"
#include "tcp/Connection.h"
#include "tcp/ssl.h"
#include "AppIcon.h"
#include "Busy.h"
#include "Config.h"
#include "DynamicString.h"
#include "FolderList.h"
#include "Locale.h"
#include "Logfile.h"
#include "MailList.h"
#include "MailServers.h"
#include "MethodStack.h"
#include "MailTransferList.h"
#include "MUIObjects.h"
#include "Requesters.h"
#include "Threads.h"
#include "UIDL.h"
#include "Debug.h"
/**************************************************************************/
// POP3 commands (RFC 1939)
// the order of the following enum & pointer array is important and have to
// match each other or weird things will happen.
enum POPCommand
{
// POP3 standard commands
POPCMD_CONNECT, POPCMD_USER, POPCMD_PASS, POPCMD_QUIT, POPCMD_STAT, POPCMD_LIST,
POPCMD_RETR, POPCMD_DELE, POPCMD_NOOP, POPCMD_RSET, POPCMD_APOP, POPCMD_TOP,
POPCMD_UIDL,
// POP3 extended commands
POPCMD_STLS
};
static const char *const POPcmd[] =
{
// POP3 standard commands
"", "USER", "PASS", "QUIT", "STAT", "LIST",
"RETR", "DELE", "NOOP", "RSET", "APOP", "TOP",
"UIDL",
// POP3 extended commands
"STLS"
};
// POP responses
#define POP_RESP_ERROR "-ERR"
#define POP_RESP_OKAY "+OK"
/**************************************************************************/
// local macros & defines
struct TransferContext
{
struct Connection *connection;
struct MailServerNode *msn;
ULONG abortMask;
ULONG wakeupMask;
ULONG timerMask;
Object *transferGroup;
Object *preselectWindow;
char pop3Buffer[SIZE_LINE]; // RFC 2821 says 1000 should be enough
char lineBuffer[SIZE_LINE];
char windowTitle[SIZE_DEFAULT]; // the preselection window's title
char transferGroupTitle[SIZE_DEFAULT]; // the TransferControlGroup's title
char password[SIZE_PASSWORD];
ULONG flags;
struct UIDLhash *UIDLhashTable; // for maintaining all UIDLs
struct MinList *remoteFilters;
struct MailTransferList *transferList;
struct MailTransferNode *firstPreselect;
struct Folder *incomingFolder; // the folder to place the downloaded mails into
BOOL useTLS;
struct DownloadResult downloadResult;
struct FilterResult filterResult;
int numberOfMailsTotal;
int numberOfMailsSkipped;
long totalSize;
struct TimeVal lastUpdateTime;
struct BusyNode *busy;
};
/// ApplyRemoteFilters
// Applies remote filters to a message
static void ApplyRemoteFilters(struct TransferContext *tc, struct MailTransferNode *tnode)
{
struct FilterNode *filter;
ENTER();
D(DBF_NET, "apply remote filters, from='%s', to='%s', subject='%s'", tnode->mail->From.Address, tnode->mail->To.Address, tnode->mail->Subject);
IterateList(tc->remoteFilters, struct FilterNode *, filter)
{
if(DoFilterSearch(filter, tnode->mail) == TRUE)
{
if(hasExecuteAction(filter) && filter->executeCmd[0] != '\0')
LaunchCommand(filter->executeCmd, 0, OUT_STDOUT);
if(hasPlaySoundAction(filter) && filter->playSound[0] != '\0')
PlaySound(filter->playSound);
if(hasDeleteAction(filter))
setFlag(tnode->tflags, TRF_DELETE);
else
clearFlag(tnode->tflags, TRF_DELETE);
if(hasSkipMsgAction(filter))
clearFlag(tnode->tflags, TRF_TRANSFER);
else
setFlag(tnode->tflags, TRF_TRANSFER);
// get out of this loop after a successful search
break;
}
}
// remember that the remote filters have been applied for this mail already
setFlag(tnode->tflags, TRF_REMOTE_FILTER_APPLIED);
LEAVE();
}
///
/// SendPOP3Command
// Sends a command to the POP3 server
static char *SendPOP3Command(struct TransferContext *tc, const enum POPCommand command, const char *parmtext, const char *errorMsg)
{
char *result = NULL;
ENTER();
// if we specified a parameter for the pop command lets add it now
if(IsStrEmpty(parmtext))
snprintf(tc->pop3Buffer, sizeof(tc->pop3Buffer), "%s\r\n", POPcmd[command]);
else
snprintf(tc->pop3Buffer, sizeof(tc->pop3Buffer), "%s %s\r\n", POPcmd[command], parmtext);
D(DBF_NET, "send POP3 cmd '%s' with param '%s'", POPcmd[command], (command == POPCMD_PASS) ? "XXX" : SafeStr(parmtext));
// send the pop command to the server and see if it was received somehow
// and for a connect we don't send something or the server will get
// confused.
if(command == POPCMD_CONNECT || SendLineToHost(tc->connection, tc->pop3Buffer) > 0)
{
int received;
// let us read the next line from the server and check if
// some status message can be retrieved.
if((received = ReceiveLineFromHost(tc->connection, tc->pop3Buffer, sizeof(tc->pop3Buffer))) > 0)
{
D(DBF_NET, "received POP3 answer '%s'", tc->pop3Buffer);
if(strncmp(tc->pop3Buffer, POP_RESP_OKAY, strlen(POP_RESP_OKAY)) == 0)
{
// everything worked out fine so lets set
// the result to our allocated buffer
result = tc->pop3Buffer;
}
}
if(result == NULL)
{
BOOL showError;
// don't show an error message for a failed QUIT command with no answer at all
if(command == POPCMD_QUIT && received == -1)
showError = FALSE;
else
showError = TRUE;
// only report an error if explicitly wanted
if(showError == TRUE && errorMsg != NULL)
{
// if we just issued a PASS command and that failed, then overwrite the visible
// password with X chars now, so that nobody else can read your password
if(command == POPCMD_PASS)
{
char *p;
// find the beginning of the password
if((p = strstr(tc->pop3Buffer, POPcmd[POPCMD_PASS])) != NULL &&
(p = strchr(p, ' ')) != NULL)
{
// now cross it out
while(*p != '\0' && *p != ' ' && *p != '\n' && *p != '\r')
*p++ = 'X';
}
}
ER_NewError(errorMsg, tc->msn->hostname, tc->msn->description, (char *)POPcmd[command], tc->pop3Buffer);
}
}
}
RETURN(result);
return result;
}
///
/// ReceiveToFile
static int ReceiveToFile(struct TransferContext *tc, FILE *fh, const char *filename, const BOOL isTemp)
{
int count;
int l=0, read, state=0;
BOOL error = FALSE;
BOOL done = FALSE;
char *lineptr = tc->lineBuffer;
ENTER();
// get the first data the pop server returns after the TOP command
if((read = count = ReceiveFromHost(tc->connection, tc->pop3Buffer, sizeof(tc->pop3Buffer))) <= 0)
tc->connection->error = CONNECTERR_UNKNOWN_ERROR;
else
{
// the first line we write out to our mail file is a X-YAM-MailAccount: header in which we
// mark through which mail account this mail was received.
fprintf(fh, "X-YAM-MailAccount: %s@%s\n", tc->msn->username, tc->msn->hostname);
}
while(tc->connection->abort == FALSE && tc->connection->error == CONNECTERR_NO_ERROR)
{
char *bufptr;
// now we iterate through the received string
// and strip out the '\r' character.
// we iterate through it because the strings we receive
// from the socket can be splitted somehow.
for(bufptr = tc->pop3Buffer; read > 0; bufptr++, read--)
{
// first we check if our buffer is full and if so we
// write it to the file.
if(l == sizeof(tc->lineBuffer) || done == TRUE)
{
// update the transfer status during the final download
if(isTemp == FALSE)
PushMethodOnStack(tc->transferGroup, 3, MUIM_TransferControlGroup_Update, l, tr(MSG_TR_Downloading));
// write the line to the file now
if(fwrite(tc->lineBuffer, 1, l, fh) != (size_t)l)
{
error = TRUE;
ER_NewError(tr(MSG_ER_ErrorWriteMailfile), filename);
break;
}
// if we end up here and done is true we have to break that iteration
if(done == TRUE)
break;
// set l to zero so that the next char gets written to the beginning
l = 0;
lineptr = tc->lineBuffer;
}
// we have to analyze different states because we iterate through our
// received buffer char by char.
switch(state)
{
// stat==1 is only reached if a "\r" was found previously
case 1:
{
// if we previously found a \r and the actual char is not a \n, then
// we write the \r in the buffer and iterate to the next char.
if(*bufptr != '\n')
{
*lineptr++ = '\r';
l++;
read++;
bufptr--;
state = 0;
continue;
}
// write the actual char "\n" in the line buffer
*lineptr++ = *bufptr;
l++;
state = 2; // we found a "\r\n" so we move to stat 2 on the next iteration.
continue;
}
break;
// stat==2 is only reached if we previously found a "\r\n"
// stat==5 if we found a lonely "\n"
case 2:
case 5:
{
if(*bufptr == '.')
{
state++; // now it's 3 or 6
continue;
}
state = 0;
}
break;
// stat==3 is only reached if we previously found a "\r\n."
// stat==6 if we found a lonely "\n."
case 3:
case 6:
{
if(state == 3 && *bufptr == '\r')
{
state = 4; // we found a \r directly after a "\r\n.", so it can be the start of a TERM
}
else if(*bufptr == '.')
{
// (RFC 1939) - the server handles "." as "..", so we only write "."
*lineptr++ = '.';
l++;
state = 0;
}
else
{
// write the actual char in the line buffer
*lineptr++ = '.';
l++;
read++;
bufptr--;
state = 0;
}
continue;
}
break;
// stat==4 is only reached if we previsouly found a "\r\n.\r"
case 4:
{
if(*bufptr != '\n')
{
*lineptr++ = '.';
l++;
read += 2;
bufptr -= 2;
state = 0;
continue;
}
// so if we end up here we finally found our termination line "\r\n.\r\n"
// and make sure the buffer is written before we break out here.
read = 2;
done = TRUE;
continue;
}
break;
}
// if we find a \r we set the stat to 1 and check on the next iteration if
// the following char is a \n and if so we have to strip it out.
if(*bufptr == '\r')
{
state = 1;
continue;
}
else if(*bufptr == '\n')
{
state=5;
}
// write the actual char in the line buffer
*lineptr++ = *bufptr;
l++;
}
// if we received the term octet we can exit the while loop now
if(done == TRUE || error == TRUE || tc->connection->abort == TRUE || tc->connection->error != CONNECTERR_NO_ERROR)
break;
// if not, we get another bunch of data and start over again.
if((read = ReceiveFromHost(tc->connection, tc->pop3Buffer, sizeof(tc->pop3Buffer))) <= 0)
break;
count += read;
}
if(done == FALSE || error == TRUE)
count = 0;
RETURN(count);
return count;
}
///
/// CheckAbort
// check for an abortion while obtaining the message details
static int CheckAbort(struct TransferContext *tc)
{
int success = 1;
ENTER();
// check for transmission errors
if(tc->connection->abort == TRUE || tc->connection->error != CONNECTERR_NO_ERROR)
{
D(DBF_NET, "get message details aborted");
success = 0;
}
else
{
ULONG signals;
// check for pending signals and clear them
signals = SetSignal(0UL, tc->abortMask|tc->wakeupMask);
// check for abortion
if(success == 1 && isFlagSet(signals, tc->abortMask))
{
D(DBF_NET, "get message details aborted");
tc->connection->abort = TRUE;
success = 0;
}
// check for an early reaction from the user in the preselection window
if(success == 1 && isFlagSet(signals, tc->wakeupMask) && tc->preselectWindow != NULL)
{
ULONG result = FALSE;
PushMethodOnStackWait(tc->preselectWindow, 3, OM_GET, MUIA_PreselectionWindow_Result, &result);
if(result == TRUE)
success = 2;
else
success = 0;
}
}
RETURN(success);
return success;
}
///
/// GetMessageList
// Collects messages waiting on a POP3 server
static BOOL GetMessageList(struct TransferContext *tc)
{
BOOL success;
ENTER();
// we issue a LIST command without argument to get a list
// of all messages available on the server. This command will
// return TRUE if the server responded with a +OK
if(SendPOP3Command(tc, POPCMD_LIST, NULL, tr(MSG_ER_BADRESPONSE_POP3)) != NULL)
{
success = TRUE;
// get the first line the pop server returns after the LINE command
if(ReceiveLineFromHost(tc->connection, tc->pop3Buffer, sizeof(tc->pop3Buffer)) > 0)
{
// we get the "scan listing" as long as we haven't received a a
// finishing octet
while(tc->connection->abort == FALSE && tc->connection->error == CONNECTERR_NO_ERROR && strncmp(tc->pop3Buffer, ".\r\n", 3) != 0)
{
int serverIndex;
int size;
struct Mail *newMail;
// read the index and size of the first message
sscanf(tc->pop3Buffer, "%d %d", &serverIndex, &size);
if(serverIndex > 0 && (newMail = AllocMail()) != NULL)
{
int mode;
struct MailTransferNode *tnode;
static const int mode2tflags[16] =
{
TRF_TRANSFER,
TRF_TRANSFER,
TRF_TRANSFER|TRF_DELETE,
TRF_TRANSFER|TRF_DELETE,
TRF_TRANSFER,
TRF_TRANSFER,
TRF_TRANSFER|TRF_DELETE,
TRF_TRANSFER|TRF_DELETE,
TRF_NONE,
TRF_TRANSFER|TRF_PRESELECT|TRF_SIZE_EXCEEDED,
TRF_NONE,
TRF_TRANSFER|TRF_DELETE|TRF_PRESELECT|TRF_SIZE_EXCEEDED,
TRF_PRESELECT,
TRF_TRANSFER|TRF_PRESELECT|TRF_SIZE_EXCEEDED,
TRF_PRESELECT,
TRF_TRANSFER|TRF_DELETE|TRF_PRESELECT|TRF_SIZE_EXCEEDED
};
int tflags;
newMail->Size = size;
mode = (hasServerDownloadLargeMails(tc->msn) == TRUE ? 1 : 0) +
(hasServerPurge(tc->msn) == TRUE ? 2 : 0) +
(isFlagSet(tc->flags, RECEIVEF_USER) ? 4 : 0) +
((tc->msn->preselection >= PSM_LARGE && tc->msn->largeMailSizeLimit > 0 && newMail->Size >= (tc->msn->largeMailSizeLimit*1024)) ? 8 : 0);
tflags = mode2tflags[mode];
// if preselection is configured then force displaying this mail in the list
if(tc->msn->preselection >= PSM_ALWAYS)
setFlag(tflags, TRF_PRESELECT);
D(DBF_NET, "mail %6ld, transfer mode %2ld, tflags %08lx (dl large %ld, purge %ld, user %ld, warnsize %8ld, size %8ld, presel %ld)", serverIndex, mode, tflags, hasServerDownloadLargeMails(tc->msn), hasServerPurge(tc->msn), isFlagSet(tc->flags, RECEIVEF_USER), tc->msn->largeMailSizeLimit*1024, newMail->Size, tc->msn->preselection);
// allocate a new MailTransferNode and add it to our
// new transferlist
if((tnode = CreateMailTransferNode(newMail, tflags)) != NULL)
{
tnode->index = serverIndex;
AddMailTransferNode(tc->transferList, tnode);
}
else
{
FreeMail(newMail);
success = FALSE;
break;
}
}
// now read the next Line
if(ReceiveLineFromHost(tc->connection, tc->pop3Buffer, sizeof(tc->pop3Buffer)) <= 0)
{
success = FALSE;
break;
}
}
// check for abortion another time to set the correct return code
if(tc->connection->abort == TRUE || tc->connection->error != CONNECTERR_NO_ERROR)
success = FALSE;
}
else
success = FALSE;
}
else
success = FALSE;
// remember the total number of mails on the server
tc->downloadResult.onServer = tc->transferList->count;
RETURN(success);
return success;
}
///
/// GetSingleMessageDetails
// Gets header from a message stored on the POP3 server
static void GetSingleMessageDetails(struct TransferContext *tc, struct MailTransferNode *tnode, int lline)
{
struct Mail *mail = tnode->mail;
ENTER();
D(DBF_NET, "get details for mail %ld", lline);
if(IsStrEmpty(mail->From.Address) &&
tc->connection->abort == FALSE && tc->connection->error == CONNECTERR_NO_ERROR)
{
char cmdbuf[SIZE_SMALL];
// we issue a TOP command with a one line message body.
//
// This command is optional within the RFC 1939 specification
// and therefore we don't throw any error
snprintf(cmdbuf, sizeof(cmdbuf), "%d 1", tnode->index);
if(SendPOP3Command(tc, POPCMD_TOP, cmdbuf, NULL) != NULL)
{
struct TempFile *tf;
// we generate a temporary file to buffer the TOP list into
if((tf = OpenTempFile("w")) != NULL)
{
struct ExtendedMail *email;
BOOL done = FALSE;
// now we call a subfunction to receive data from the POP3 server
// and write it in the filehandle as long as there is no termination \r\n.\r\n
if(ReceiveToFile(tc, tf->FP, tf->Filename, TRUE) > 0)
done = TRUE;
// close the filehandle now
fclose(tf->FP);
tf->FP = NULL;
// If we end up here because of an error, abort or the upper loop wasn't finished
// we exit immediatly with deleting the temp file also.
if(tc->connection->abort == TRUE || tc->connection->error != CONNECTERR_NO_ERROR || done == FALSE)
{
lline = -1;
}
else if((email = MA_ExamineMail(NULL, FilePart(tf->Filename), TRUE)) != NULL)
{
memcpy(&mail->From, &email->Mail.From, sizeof(mail->From));
memcpy(&mail->To, &email->Mail.To, sizeof(mail->To));
memcpy(&mail->ReplyTo, &email->Mail.ReplyTo, sizeof(mail->ReplyTo));
strlcpy(mail->Subject, email->Mail.Subject, sizeof(mail->Subject));
strlcpy(mail->MailFile, email->Mail.MailFile, sizeof(mail->MailFile));
memcpy(&mail->Date, &email->Mail.Date, sizeof(mail->Date));
// if this function was called with -1, then the POP3 server
// doesn't have the UIDL command and we have to generate our
// own one by using the MsgID.
if(lline == -1)
tnode->uidl = strdup(email->messageID);
// apply possible remote filters
if(isFlagClear(tnode->tflags, TRF_REMOTE_FILTER_APPLIED) && hasServerApplyRemoteFilters(tc->msn) == TRUE && IsMinListEmpty(tc->remoteFilters) == FALSE)
ApplyRemoteFilters(tc, tnode);
MA_FreeEMailStruct(email);
}
else
E(DBF_NET, "couldn't examine mail file '%s'", tf->Filename);
CloseTempFile(tf);
}
else
ER_NewError(tr(MSG_ER_CantCreateTempfile));
}
}
// now we got the details
setFlag(tnode->tflags, TRF_GOT_DETAILS);
if(lline >= 0 && tc->preselectWindow != NULL)
PushMethodOnStack(tc->preselectWindow, 2, MUIM_PreselectionWindow_RefreshMail, lline);
LEAVE();
}
///
/// GetAllMessageDetails
// get the details of all mails
// success will be:
// 0: transmission failed or preselection was aborted
// 1: all details have been obtained, wait for the user to finish the preselection
// 2: preselection was aborted early by pressing "Start"
static int GetAllMessageDetails(struct TransferContext *tc, BOOL remoteFilters)
{
int success = 1;
LONG pass;
ULONG handledMails;
ENTER();
// count the number of mails for which we got the details in a previous call already
handledMails = CountMailTransferNodes(tc->transferList, TRF_GOT_DETAILS);
// get the details in 3 passes
// 1. start at first to be preselected mail (i.e. size limit exceeded)
// 2. start at first to be downloaded mail
// 3. start at first available mail
for(pass = 0; pass < 3; pass++)
{
ULONG flags;
struct MailTransferNode *tnode;
if(remoteFilters == TRUE)
{
// only handle mails to be transferred if remote filters are to be applied
flags = TRF_TRANSFER;
}
else
{
switch(pass)
{
default:
case 0:
{
// handle all mails to be transferred and which exceed the size limit
flags = TRF_TRANSFER|TRF_SIZE_EXCEEDED;
}
break;
case 1:
{
// handle all mails to be transferred
flags = TRF_TRANSFER;
}
break;
case 2:
{
// handle all mails
flags = TRF_NONE;
}
break;
}
}
// find a yet unhandled mail with all the given flags
tnode = ScanMailTransferList(tc->transferList, flags|TRF_GOT_DETAILS, flags, TRUE);
if(tc->firstPreselect == NULL && tnode != NULL)
{
// remember this mail as the one to be highlighted
tc->firstPreselect = tnode;
// immediately highlight the mail if the preselection window is opened
if(tc->preselectWindow != NULL)
PushMethodOnStack(tc->preselectWindow, 3, MUIM_Set, MUIA_PreselectionWindow_ActiveMail, tnode);
// if only the mail sizes are requested for preselection and no remote filters
// are to be applied we can bail out here immediately
if(remoteFilters == FALSE && tc->msn->preselection == PSM_ALWAYSLARGE)
break;
}
if(tnode != NULL)
{
D(DBF_NET, "pass %ld start at mail %ld", pass, tnode->index-1);
// get all message details until the end of the list
do
{
// get the message details only if this has not been done before already
if(isFlagClear(tnode->tflags, TRF_GOT_DETAILS))
{
GetSingleMessageDetails(tc, tnode, tnode->index-1);
// update the progress bar
handledMails++;
if(tc->preselectWindow != NULL)
PushMethodOnStack(tc->preselectWindow, 3, MUIM_Set, MUIA_PreselectionWindow_Progress, handledMails);
}
if((success = CheckAbort(tc)) != 1)
{
// bail out completely
tnode = NULL;
pass = 3;
}
else
{
// continue with the next mail
tnode = NextMailTransferNode(tnode);
}
}
while(tnode != NULL);
}
else
{
D(DBF_NET, "nothing to be done in pass %ld", pass);
}
// bail out after the first pass if remote filters are to be applied
if(remoteFilters == TRUE)
break;
}
// hide the progress bar after the scan
PushMethodOnStack(tc->preselectWindow, 3, MUIM_Set, MUIA_PreselectionWindow_Progress, 0);
RETURN(success);
return success;
}
///
/// FilterDuplicates
//
static BOOL FilterDuplicates(struct TransferContext *tc)
{
BOOL result = FALSE;
ENTER();
// check if there is anything to transfer at all
if(IsMailTransferListEmpty(tc->transferList) == FALSE)
{
// inform the user of the operation
PushMethodOnStack(tc->transferGroup, 2, MUIM_TransferControlGroup_ShowStatus, tr(MSG_TR_CHECKUIDL));
// before we go and request the UIDL of each message we check whether the server
// supports the UIDL command at all
if(SendPOP3Command(tc, POPCMD_UIDL, NULL, NULL) != NULL)
{
// get the first line the pop server returns after the UIDL command
if(ReceiveLineFromHost(tc->connection, tc->lineBuffer, sizeof(tc->lineBuffer)) > 0)
{
struct MailTransferNode *lastTNode = NULL;
// we get the "unique-id list" as long as we haven't received a
// finishing octet
while(tc->connection->abort == FALSE && tc->connection->error == CONNECTERR_NO_ERROR && strncmp(tc->lineBuffer, ".\r\n", 3) != 0)
{
char *p;
// now parse the line and get the message number and UIDL
// each UIDL entry is transmitted as "number uidl"
if((p = strchr(tc->lineBuffer, ' ')) != NULL)
{
int num;
char *uidl;
struct MailTransferNode *tnode;
// replace the space by a NUL byte and convert the first part to an integer
*p++ = '\0';
num = atoi(tc->lineBuffer);
// strip the trailing CR+LF
uidl = p;
if((p = strchr(uidl, '\r')) != NULL)
*p = '\0';
if(lastTNode != NULL)
tnode = NextMailTransferNode(lastTNode);
else
tnode = FirstMailTransferNode(tc->transferList);
// search through our transferList
if(tnode != NULL)
{
if(tnode->index == num)
{
if((tnode->uidl = strdup(uidl)) != NULL)
{
struct UIDLtoken *token;
// check if this UIDL is known already
if((token = FindUIDL(tc->UIDLhashTable, tnode->uidl)) != NULL)
{
D(DBF_UIDL, "mail %ld: found UIDL '%s', flags=%08lx", tnode->index, tnode->uidl, token->flags);
// check if we knew this UIDL before
if(isFlagSet(token->flags, UIDLF_OLD))
{
// make sure the mail is flagged as being ignoreable
tc->downloadResult.dupeSkipped++;
// don't download this mail, because it has been downloaded before
clearFlag(tnode->tflags, TRF_TRANSFER);
// exclude this mail from preselection for the same reason
clearFlag(tnode->tflags, TRF_PRESELECT);
// mark this UIDL as old+new, thus it will be saved upon cleanup
setFlag(token->flags, UIDLF_NEW);
}
}
}
}
else
{
W(DBF_UIDL, "mail enumeration doesn't match, expected %ld, got %ld", tnode->index, num);
break;
}
}
else
{
W(DBF_UTIL, "premature end of list");
break;
}
lastTNode = tnode;
}
if(tc->connection->abort == TRUE || tc->connection->error != CONNECTERR_NO_ERROR)
break;
// now read the next Line
if(ReceiveLineFromHost(tc->connection, tc->lineBuffer, sizeof(tc->lineBuffer)) <= 0)
{
E(DBF_UIDL, "unexpected end of data stream during UIDL.");
break;
}
}
}
else
E(DBF_UIDL, "error on first readline!");
}
else
{
struct MailTransferNode *tnode;
W(DBF_UIDL, "POP3 server '%s' doesn't support UIDL command!", tc->msn->hostname);
// search through our transferList
ForEachMailTransferNode(tc->transferList, tnode)
{
// if the server doesn't support the UIDL command we
// use the TOP command and generate our own UIDL within
// the GetMessageDetails function
GetSingleMessageDetails(tc, tnode, -1);
// now that we should successfully obtained the UIDL of the
// mailtransfernode we go and check if that UIDL is already in our UIDLhash
// and if so we go and flag the mail as a mail that should not be downloaded
// automatically
if(tnode->uidl != NULL)
{
struct UIDLtoken *token;
if((token = AddUIDLtoHash(tc->UIDLhashTable, tnode->uidl, UIDLF_NEW)) != NULL)
{
D(DBF_UIDL, "mail %ld: found UIDL '%s', flags=%08lx", tnode->index, tnode->uidl, token->flags);
// check if we knew this UIDL before
if(isFlagSet(token->flags, UIDLF_OLD))
{
tc->downloadResult.dupeSkipped++;
// don't download this mail, because it has been downloaded before
clearFlag(tnode->tflags, TRF_TRANSFER);
// mark this UIDL as old+new, thus it will be saved upon cleanup
setFlag(token->flags, UIDLF_NEW);
}
}
}
if(tc->connection->abort == TRUE || tc->connection->error != CONNECTERR_NO_ERROR)
break;
}
}
result = (tc->connection->abort == FALSE && tc->connection->error == CONNECTERR_NO_ERROR);
}
else
result = TRUE;
RETURN(result);
return result;
}
///
/// ConnectToPOP3
// Connects to a POP3 mail server
static int ConnectToPOP3(struct TransferContext *tc)
{
char *p;
char *welcomemsg = NULL;
int msgs = -1;
char *resp;
enum ConnectError err;
#if defined(DEBUG)
long bytes = 0;
#endif
ENTER();
D(DBF_NET, "connect to POP3 server '%s'", tc->msn->hostname);
PushMethodOnStack(tc->transferGroup, 2, MUIM_TransferControlGroup_ShowStatus, tr(MSG_TR_Connecting));
// show some busy text in the main window
tc->busy = BusyBegin(BUSY_TEXT);
BusyText(tc->busy, tr(MSG_TR_MAILCHECKFROM), tc->msn->description);
// now we start our connection to the POP3 server
if((err = ConnectToHost(tc->connection, tc->msn)) != CONNECTERR_SUCCESS)
{
if(isFlagSet(tc->flags, RECEIVEF_USER))
{
switch(err)
{
case CONNECTERR_SUCCESS:
case CONNECTERR_ABORTED:
case CONNECTERR_NO_ERROR:
// do nothing
break;
// socket is already in use
case CONNECTERR_SOCKET_IN_USE:
ER_NewError(tr(MSG_ER_CONNECTERR_SOCKET_IN_USE_POP3), tc->msn->hostname, tc->msn->description);
break;
// socket() execution failed
case CONNECTERR_NO_SOCKET:
ER_NewError(tr(MSG_ER_CONNECTERR_NO_SOCKET_POP3), tc->msn->hostname, tc->msn->description);
break;
// couldn't establish non-blocking IO
case CONNECTERR_NO_NONBLOCKIO:
ER_NewError(tr(MSG_ER_CONNECTERR_NO_NONBLOCKIO_POP3), tc->msn->hostname, tc->msn->description);
break;
// connection request timed out
case CONNECTERR_TIMEDOUT:
ER_NewError(tr(MSG_ER_CONNECTERR_TIMEDOUT_POP3), tc->msn->hostname, tc->msn->description);
break;
// unknown host - gethostbyname() failed
case CONNECTERR_UNKNOWN_HOST:
ER_NewError(tr(MSG_ER_UNKNOWN_HOST_POP3), tc->msn->hostname, tc->msn->description);
break;
// general connection error
case CONNECTERR_UNKNOWN_ERROR:
ER_NewError(tr(MSG_ER_CANNOT_CONNECT_POP3), tc->msn->hostname, tc->msn->description);
break;
case CONNECTERR_SSLFAILED:
case CONNECTERR_INVALID8BIT:
case CONNECTERR_NO_CONNECTION:
case CONNECTERR_NOT_CONNECTED:
// can't occur, do nothing
break;
}
}
goto out;
}
// If this connection should be a STLS like connection we have to get the welcome
// message now and then send the STLS command to start TLS negotiation
if(hasServerTLS(tc->msn))
{
PushMethodOnStack(tc->transferGroup, 2, MUIM_TransferControlGroup_ShowStatus, tr(MSG_TR_WaitWelcome));
// Initiate a connect and see if we succeed
if((resp = SendPOP3Command(tc, POPCMD_CONNECT, NULL, tr(MSG_ER_POP3WELCOME))) == NULL)
goto out;
dstrcpy(&welcomemsg, resp);
// If the user selected STLS support we have to first send the command
// to start TLS negotiation (RFC 2595)
if(SendPOP3Command(tc, POPCMD_STLS, NULL, tr(MSG_ER_BADRESPONSE_POP3)) == NULL)
goto out;
}
// Here start the TLS/SSL Connection stuff
if(hasServerSSL(tc->msn) || hasServerTLS(tc->msn))
{
PushMethodOnStack(tc->transferGroup, 2, MUIM_TransferControlGroup_ShowStatus, tr(MSG_TR_INITTLS));
// Now we have to Initialize and Start the TLS stuff if requested
if(MakeSecureConnection(tc->connection) == TRUE)
tc->useTLS = TRUE;
else
{
ER_NewError(tr(MSG_ER_INITTLS_POP3), tc->msn->hostname, tc->msn->description);
goto out;
}
}
// If this was a connection on a stunnel on port 995 or a non-ssl connection
// we have to get the welcome message now
if(hasServerSSL(tc->msn) == TRUE || hasServerTLS(tc->msn) == FALSE)
{
// Initiate a connect and see if we succeed
if((resp = SendPOP3Command(tc, POPCMD_CONNECT, NULL, tr(MSG_ER_POP3WELCOME))) == NULL)
goto out;
dstrcpy(&welcomemsg, resp);
}
if(IsStrEmpty(tc->password))
{
Object *passwordWin;
snprintf(tc->windowTitle, sizeof(tc->windowTitle), tr(MSG_TR_ENTER_POP3_PASSWORD), tc->msn->description);
if((passwordWin = (Object *)PushMethodOnStackWait(G->App, 5, MUIM_YAMApplication_CreatePasswordWindow, CurrentThread(), tr(MSG_TR_PopLogin), tc->windowTitle, sizeof(tc->password))) != NULL)
{
PushMethodOnStack(tc->transferGroup, 2, MUIM_TransferControlGroup_ShowStatus, tr(MSG_TR_WAIT_FOR_PASSWORD));
if(SleepThread() == TRUE)
{
ULONG result = 0;
PushMethodOnStackWait(passwordWin, 3, OM_GET, MUIA_StringRequestWindow_Result, &result);
if(result != 0)
PushMethodOnStackWait(passwordWin, 3, OM_GET, MUIA_StringRequestWindow_StringContents, tc->password);
}
else
{
// force "no password" if we were aborted
tc->password[0] = '\0';
}
PushMethodOnStack(G->App, 2, MUIM_YAMApplication_DisposeWindow, passwordWin);
}
// bail out if we still got no password
if(IsStrEmpty(tc->password))
goto out;
}
// if the user has selected APOP for that POP3 host
// we have to process it now
if(hasServerAPOP(tc->msn))
{
// Now we get the APOP Identifier out of the welcome
// message
if((p = strchr(welcomemsg, '<')) != NULL)
{
struct MD5Context context;
unsigned char digest[16];
char digestHex[33];
strlcpy(tc->lineBuffer, p, sizeof(tc->lineBuffer));
if((p = strchr(tc->lineBuffer, '>')) != NULL)
p[1] = '\0';
// then we send the APOP command to authenticate via APOP
strlcat(tc->lineBuffer, tc->msn->password, sizeof(tc->lineBuffer));
md5init(&context);
md5update(&context, tc->lineBuffer, strlen(tc->lineBuffer));
md5final(digest, &context);
md5digestToHex(digest, digestHex);
snprintf(tc->lineBuffer, sizeof(tc->lineBuffer), "%s %s", tc->msn->username, digestHex);
PushMethodOnStack(tc->transferGroup, 2, MUIM_TransferControlGroup_ShowStatus, tr(MSG_TR_SendAPOPLogin));
if(SendPOP3Command(tc, POPCMD_APOP, tc->lineBuffer, tr(MSG_ER_BADRESPONSE_POP3)) == NULL)
goto out;
}
else
{
ER_NewError(tr(MSG_ER_NO_APOP), tc->msn->hostname, tc->msn->description);
goto out;
}
}
else
{
PushMethodOnStack(tc->transferGroup, 2, MUIM_TransferControlGroup_ShowStatus, tr(MSG_TR_SendUserID));
if(SendPOP3Command(tc, POPCMD_USER, tc->msn->username, tr(MSG_ER_BADRESPONSE_POP3)) == NULL)
goto out;
PushMethodOnStack(tc->transferGroup, 2, MUIM_TransferControlGroup_ShowStatus, tr(MSG_TR_SendPassword));
if(SendPOP3Command(tc, POPCMD_PASS, tc->password, tr(MSG_ER_BADRESPONSE_POP3)) == NULL)
goto out;
}
PushMethodOnStack(tc->transferGroup, 2, MUIM_TransferControlGroup_ShowStatus, tr(MSG_TR_GetStats));
if((resp = SendPOP3Command(tc, POPCMD_STAT, NULL, tr(MSG_ER_BADRESPONSE_POP3))) == NULL)
goto out;
msgs = strtoul(&resp[4], &p, 10);
#if defined(DEBUG)
if(p != NULL)
bytes = strtoul(p+1, NULL, 10);
D(DBF_NET, "STAT signaled %ld messages with %ld bytes on server", msgs, bytes);
#endif
if(msgs != 0)
AppendToLogfile(LF_VERBOSE, 31, tr(MSG_LOG_CONNECT_POP3), tc->msn->description, msgs);
out:
dstrfree(welcomemsg);
RETURN(msgs);
return msgs;
}
///
/// DisconnectFromPOP3
static void DisconnectFromPOP3(struct TransferContext *tc)
{
ENTER();
D(DBF_NET, "disconnecting from POP3 server '%s'", tc->msn->hostname);
PushMethodOnStack(tc->transferGroup, 2, MUIM_TransferControlGroup_ShowStatus, tr(MSG_TR_Disconnecting));
if(tc->connection->error == CONNECTERR_NO_ERROR)
SendPOP3Command(tc, POPCMD_QUIT, NULL, tr(MSG_ER_BADRESPONSE_POP3));
DisconnectFromHost(tc->connection);
BusyEnd(tc->busy);
tc->busy = NULL;
LEAVE();
}
///
/// LoadMessage
static BOOL LoadMessage(struct TransferContext *tc, struct Folder *inFolder, const int number)
{
BOOL result = FALSE;
char msgfile[SIZE_PATHFILE];
FILE *fh;
ENTER();
MA_NewMailFile(inFolder, msgfile, sizeof(msgfile));
// open the new mailfile for writing out the retrieved
// data
if((fh = fopen(msgfile, "w")) != NULL)
{
char msgnum[SIZE_SMALL];
BOOL done = FALSE;
setvbuf(fh, NULL, _IOFBF, SIZE_FILEBUF);
snprintf(msgnum, sizeof(msgnum), "%d", number);
if(SendPOP3Command(tc, POPCMD_RETR, msgnum, tr(MSG_ER_BADRESPONSE_POP3)) != NULL)
{
// now we call a subfunction to receive data from the POP3 server
// and write it in the filehandle as long as there is no termination \r\n.\r\n
if(ReceiveToFile(tc, fh, msgfile, FALSE) > 0)
done = TRUE;
}
fclose(fh);
if(tc->connection->abort == FALSE && tc->connection->error == CONNECTERR_NO_ERROR && done == TRUE)
{
struct ExtendedMail *email;
if((email = MA_ExamineMail(inFolder, FilePart(msgfile), FALSE)) != NULL)
{
struct Mail *mail;
if((mail = CloneMail(&email->Mail)) != NULL)
{
AddMailToFolder(mail, inFolder);
// we have to get the actual Time and place it in the transDate, so that we know at
// which time this mail arrived
GetSysTimeUTC(&mail->transDate);
mail->sflags = SFLAG_NEW;
MA_UpdateMailFile(mail);
D(DBF_NET, "adding mail to downloaded list");
// add the mail to the list of downloaded mails
LockMailList(tc->msn->downloadedMails);
AddNewMailNode(tc->msn->downloadedMails, mail);
UnlockMailList(tc->msn->downloadedMails);
// if the current folder is the inbox we can go and add the mail instantly to the maillist
if(inFolder == GetCurrentFolder())
PushMethodOnStack(G->MA->GUI.PG_MAILLIST, 3, MUIM_NList_InsertSingle, mail, MUIV_NList_Insert_Sorted);
AppendToLogfile(LF_VERBOSE, 32, tr(MSG_LOG_RetrievingVerbose), AddrName(mail->From), mail->Subject, mail->Size);
PushMethodOnStackWait(G->App, 3, MUIM_YAMApplication_StartMacro, MACRO_NEWMSG, msgfile);
result = TRUE;
}
MA_FreeEMailStruct(email);
}
}
if(result == FALSE)
{
DeleteFile(msgfile);
// we need to set the folder flags to modified so that the .index will be saved later.
setFlag(inFolder->Flags, FOFL_MODIFY);
}
}
else
ER_NewError(tr(MSG_ER_ErrorWriteMailfile), msgfile);
RETURN(result);
return result;
}
///
/// DeleteMessage
static BOOL DeleteMessage(struct TransferContext *tc, const int number)
{
BOOL result = FALSE;
char msgnum[SIZE_SMALL];
ENTER();
// update the transfer status
PushMethodOnStack(tc->transferGroup, 3, MUIM_TransferControlGroup_Update, TCG_SETMAX, tr(MSG_TR_DeletingServerMail));
snprintf(msgnum, sizeof(msgnum), "%d", number);
if(SendPOP3Command(tc, POPCMD_DELE, msgnum, tr(MSG_ER_BADRESPONSE_POP3)) != NULL)
{
tc->downloadResult.deleted++;
result = TRUE;
}
RETURN(result);
return result;
}
///
/// DownloadMails
static void DownloadMails(struct TransferContext *tc)
{
struct MailTransferNode *tnode;
ENTER();
// Now we are actually downloading, so lets change the busy text
BusyText(tc->busy, tr(MSG_TR_MailTransferFrom), tc->msn->description);
GetSysTime(TIMEVAL(&tc->lastUpdateTime));
ForEachMailTransferNode(tc->transferList, tnode)
{
struct Mail *mail = tnode->mail;
D(DBF_NET, "download flags %08lx=%s%s%s for mail with subject '%s' and size %ld", tnode->tflags, isFlagSet(tnode->tflags, TRF_TRANSFER) ? "TR_TRANSFER " : "" , isFlagSet(tnode->tflags, TRF_DELETE) ? "TR_DELETE " : "", isFlagSet(tnode->tflags, TRF_PRESELECT) ? "TR_PRESELECT " : "", mail->Subject, mail->Size);
if(isFlagSet(tnode->tflags, TRF_TRANSFER))
{
D(DBF_NET, "downloading mail with subject '%s' and size %ld", mail->Subject, mail->Size);
// update the transfer status
PushMethodOnStack(tc->transferGroup, 5, MUIM_TransferControlGroup_Next, tnode->index - tc->numberOfMailsSkipped, tnode->position, mail->Size, tr(MSG_TR_Downloading));
if(LoadMessage(tc, tc->incomingFolder, tnode->index) == TRUE)
{
if(TimeHasElapsed(&tc->lastUpdateTime, 250000) == TRUE)
{
// redraw the folderentry in the listtree 4 times per second at most
PushMethodOnStack(G->MA->GUI.LT_FOLDERS, 3, MUIM_NListtree_Redraw, tc->incomingFolder->Treenode, MUIF_NONE);
}
// put the transferStat for this mail to 100%
PushMethodOnStack(tc->transferGroup, 3, MUIM_TransferControlGroup_Update, TCG_SETMAX, tr(MSG_TR_Downloading));
tc->downloadResult.downloaded++;
// Remember the UIDL of this mail, no matter if it is going
// to be deleted or not. Some servers don't delete a mail
// right after the DELETE command, but only after a successful
// QUIT command. Personal experience shows that pop.gmx.de is
// one of these servers.
if(hasServerAvoidDuplicates(tc->msn) == TRUE)
{
D(DBF_NET, "adding mail with subject '%s' to UIDL hash", mail->Subject);
// add the UIDL to the hash table or update an existing entry
AddUIDLtoHash(tc->UIDLhashTable, tnode->uidl, UIDLF_NEW);
}
if(isFlagSet(tnode->tflags, TRF_DELETE))
{
D(DBF_NET, "deleting mail with subject '%s' on server", mail->Subject);
DeleteMessage(tc, tnode->index);
}
else
D(DBF_NET, "leaving mail with subject '%s' and size %ld on server to be downloaded again", mail->Subject, mail->Size);
}
}
else if(isFlagSet(tnode->tflags, TRF_DELETE))
{
D(DBF_NET, "deleting mail with subject '%s' on server", mail->Subject);
// update the transfer status, use a zero mail size
PushMethodOnStack(tc->transferGroup, 5, MUIM_TransferControlGroup_Next, tnode->index - tc->numberOfMailsSkipped, tnode->position, 0, tr(MSG_TR_DeletingServerMail));
// now we "know" that this mail had existed, don't forget this in case
// the delete operation fails
if(hasServerAvoidDuplicates(tc->msn) == TRUE)
{
D(DBF_NET, "adding mail with subject '%s' to UIDL hash", mail->Subject);
// add the UIDL to the hash table or update an existing entry
AddUIDLtoHash(tc->UIDLhashTable, tnode->uidl, UIDLF_NEW);
}
DeleteMessage(tc, tnode->index);
}
else
{
D(DBF_NET, "leaving mail with subject '%s' and size %ld on server to be downloaded again", mail->Subject, mail->Size);
// Do not modify the UIDL hash here!
// The mail was marked as "don't download", but here we don't know if that
// is due to the duplicates checking or if the user did that himself.
}
if(tc->connection->abort == TRUE || tc->connection->error != CONNECTERR_NO_ERROR)
break;
}
PushMethodOnStack(tc->transferGroup, 1, MUIM_TransferControlGroup_Finish);
// update the stats
PushMethodOnStack(G->App, 3, MUIM_YAMApplication_DisplayStatistics, tc->incomingFolder, TRUE);
// update the menu items and toolbars
PushMethodOnStack(G->App, 3, MUIM_YAMApplication_ChangeSelected, tc->incomingFolder, TRUE);
LEAVE();
}
///
/// WaitForPreselection
// wait for the user to finish the preselection
static BOOL WaitForPreselection(struct TransferContext *tc)
{
BOOL success = FALSE;
ENTER();
// start the "keep alive" timer
StartThreadTimer(C->KeepAliveInterval, 0);
while(TRUE)
{
ULONG signals;
signals = Wait(tc->abortMask|tc->wakeupMask|tc->timerMask);
if(isFlagSet(signals, tc->abortMask))
{
// we were aborted
D(DBF_NET, "preselection aborted");
tc->connection->abort = TRUE;
break;
}
if(isFlagSet(signals, tc->wakeupMask))
{
// the user finished the preselection
ULONG result = FALSE;
PushMethodOnStackWait(tc->preselectWindow, 3, OM_GET, MUIA_PreselectionWindow_Result, &result);
if(result == TRUE)
success = TRUE;
break;
}
if(isFlagSet(signals, tc->timerMask))
{
// here we send a STAT command instead of a NOOP which normally
// should do the job as well. But there are several known POP3
// servers out there which are known to ignore the NOOP commands
// for keepalive message, so STAT should be the better choice.
if(SendPOP3Command(tc, POPCMD_STAT, NULL, tr(MSG_ER_BADRESPONSE_POP3)) != NULL)
{
// restart the "keep alive" timer
StartThreadTimer(C->KeepAliveInterval, 0);
}
else
{
// bail out if sending the "keep alive" failed
break;
}
}
}
StopThreadTimer();
RETURN(success);
return success;
}
///
/// SumUpMails
static void SumUpMails(struct TransferContext *tc)
{
struct MailTransferNode *tnode;
ENTER();
tc->numberOfMailsTotal = 0;
tc->numberOfMailsSkipped = 0;
tc->totalSize = 0;
// search through our transferList
ForEachMailTransferNode(tc->transferList, tnode)
{
// count the number of mails on the server
tc->numberOfMailsTotal++;
// count the number of mail which are neither transferred nor deleted
if(isFlagClear(tnode->tflags, TRF_TRANSFER|TRF_DELETE))
{
tc->numberOfMailsSkipped++;
}
// sum up the sizes of all mails to be transferred
if(isFlagSet(tnode->tflags, TRF_TRANSFER))
{
tc->totalSize += tnode->mail->Size;
}
}
LEAVE();
}
///
/// ReceiveMails
BOOL ReceiveMails(struct MailServerNode *msn, const ULONG flags, struct DownloadResult *dlResult)
{
BOOL success = FALSE;
struct TransferContext *tc;
ENTER();
// make sure the mail server node does not vanish
ObtainSemaphoreShared(G->configSemaphore);
if((tc = calloc(1, sizeof(*tc))) != NULL)
{
tc->msn = msn;
tc->useTLS = FALSE;
tc->flags = flags;
// assume an error at first
tc->downloadResult.error = TRUE;
tc->abortMask = (1UL << ThreadAbortSignal());
tc->wakeupMask = (1UL << ThreadWakeupSignal());
// depending on the incoming folder settings in the
// POP3 server configuration we store mail either in the
// folder configured there or in the default incoming folder
if(tc->msn->mailStoreFolderID == 0 ||
(tc->incomingFolder = FindFolderByID(G->folders, tc->msn->mailStoreFolderID)) == NULL)
{
tc->incomingFolder = FO_GetFolderByType(FT_INCOMING, NULL);
}
if(tc->incomingFolder != NULL)
{
// try to open the TCP/IP stack
if((tc->connection = CreateConnection(TRUE)) != NULL && ConnectionIsOnline(tc->connection) == TRUE)
{
// copy a link to the mailservernode for which we created
// the connection
tc->connection->server = tc->msn;
if((tc->transferList = CreateMailTransferList()) != NULL)
{
if(hasServerApplyRemoteFilters(tc->msn) == FALSE || (tc->remoteFilters = CloneFilterList(APPLY_REMOTE)) != NULL)
{
if(InitThreadTimer() == TRUE)
{
BOOL uidlOk = FALSE;
tc->timerMask = (1UL << ThreadTimerSignal());
if(isFlagClear(flags, RECEIVEF_TEST_CONNECTION) && hasServerAvoidDuplicates(tc->msn) == TRUE)
{
if((tc->UIDLhashTable = InitUIDLhash(tc->msn)) != NULL)
uidlOk = TRUE;
else
ER_NewError(tr(MSG_ER_POP3_UIDLHASH_INIT_FAILED));
}
else
uidlOk = TRUE;
if(uidlOk == TRUE)
{
ULONG twFlags;
strlcpy(tc->password, msn->password, sizeof(tc->password));
snprintf(tc->transferGroupTitle, sizeof(tc->transferGroupTitle), tr(MSG_TR_MAILCHECKFROM), msn->description);
twFlags = TWF_ACTIVATE;
if(isFlagSet(tc->flags, RECEIVEF_USER))
setFlag(twFlags, TWF_OPEN);
if((tc->transferGroup = (Object *)PushMethodOnStackWait(G->App, 5, MUIM_YAMApplication_CreateTransferGroup, CurrentThread(), tc->transferGroupTitle, tc->connection, twFlags)) != NULL)
{
int msgs;
if((msgs = ConnectToPOP3(tc)) != -1)
{
// connection succeeded
success = TRUE;
// but we continue only if there is something to be downloaded at all
if(isFlagClear(flags, RECEIVEF_TEST_CONNECTION) && msgs > 0)
{
// there are messages on the server
if(GetMessageList(tc) == TRUE)
{
BOOL goOn = TRUE;
BOOL doPreselect;
BOOL doDownload;
// if the user wants to avoid to receive the same message from the
// POP3 server again we have to analyze the UIDL of it
if(goOn == TRUE && hasServerAvoidDuplicates(tc->msn) == TRUE)
goOn = FilterDuplicates(tc);
// receive all message details to be able to apply the remote filters
if(goOn == TRUE && hasServerApplyRemoteFilters(tc->msn) == TRUE && IsMinListEmpty(tc->remoteFilters) == FALSE)
{
PushMethodOnStack(tc->transferGroup, 2, MUIM_TransferControlGroup_ShowStatus, tr(MSG_TR_ApplyFilters));
goOn = GetAllMessageDetails(tc, TRUE);
}
// check the list of mails if some kind of preselection is required
if(goOn == TRUE && isFlagSet(tc->flags, RECEIVEF_USER) && ScanMailTransferList(tc->transferList, TRF_PRESELECT, TRF_PRESELECT, TRUE) != NULL)
doPreselect = TRUE;
else
doPreselect = FALSE;
if(doPreselect == TRUE)
{
// show the preselection window in case user interaction is requested
D(DBF_NET, "preselection is required");
doDownload = FALSE;
snprintf(tc->windowTitle, sizeof(tc->windowTitle), tr(MSG_TR_MAILCHECKFROM), tc->msn->description);
if((tc->preselectWindow = (Object *)PushMethodOnStackWait(G->App, 6, MUIM_YAMApplication_CreatePreselectionWindow, CurrentThread(), tc->windowTitle, tc->msn->largeMailSizeLimit, PRESELWINMODE_DOWNLOAD, tc->transferList)) != NULL)
{
int mustWait;
PushMethodOnStack(tc->preselectWindow, 3, MUIM_Set, MUIA_PreselectionWindow_ActiveMail, tc->firstPreselect);
PushMethodOnStack(tc->transferGroup, 2, MUIM_TransferControlGroup_ShowStatus, tr(MSG_TR_WAIT_FOR_PRESELECTION));
if(ThreadWasAborted() == FALSE)
{
if((mustWait = GetAllMessageDetails(tc, FALSE)) != 0)
{
if(mustWait == 1)
{
// we got all details, now wait for the user to finish the preselection
doDownload = WaitForPreselection(tc);
}
else
{
// getting the details has been aborted early by pressing the "Start" button
doDownload = TRUE;
}
}
else
D(DBF_NET, "getting message details failed/was aborted, no preselection");
PushMethodOnStack(G->App, 2, MUIM_YAMApplication_DisposeWindow, tc->preselectWindow);
}
}
}
else
{
D(DBF_NET, "no preselection required");
doDownload = goOn;
}
// is there anything left to transfer or delete?
if(ThreadWasAborted() == FALSE && doDownload == TRUE && ScanMailTransferList(tc->transferList, TRF_TRANSFER|TRF_DELETE, TRF_NONE, FALSE) != NULL)
{
SumUpMails(tc);
PushMethodOnStack(tc->transferGroup, 3, MUIM_TransferControlGroup_Start, tc->numberOfMailsTotal - tc->numberOfMailsSkipped, tc->totalSize);
DownloadMails(tc);
PushMethodOnStack(tc->transferGroup, 1, MUIM_TransferControlGroup_Finish);
if(tc->connection->abort == FALSE && tc->connection->error == CONNECTERR_NO_ERROR)
tc->downloadResult.error = FALSE;
}
else
{
W(DBF_NET, "no mails to be transferred");
if(tc->connection->abort == FALSE && tc->connection->error == CONNECTERR_NO_ERROR)
tc->downloadResult.error = FALSE;
}
}
else
E(DBF_NET, "couldn't retrieve MessageList");
}
else
{
W(DBF_NET, "no messages found on server '%s'", tc->msn->hostname);
if(tc->connection->abort == FALSE && tc->connection->error == CONNECTERR_NO_ERROR)
tc->downloadResult.error = FALSE;
}
}
// disconnect no matter if the connect operation succeeded or not
DisconnectFromPOP3(tc);
PushMethodOnStack(G->App, 2, MUIM_YAMApplication_DeleteTransferGroup, tc->transferGroup);
}
CleanupUIDLhash(tc->UIDLhashTable);
}
CleanupThreadTimer();
}
if(tc->remoteFilters != NULL)
DeleteFilterList(tc->remoteFilters);
}
else
E(DBF_NET, "could not clone remote filters");
// perform the finalizing actions only if we haven't been aborted externally
if(ThreadWasAborted() == FALSE)
{
char downloadedStr[SIZE_SMALL];
snprintf(downloadedStr, sizeof(downloadedStr), "%d", (int)tc->downloadResult.downloaded);
PushMethodOnStackWait(G->App, 3, MUIM_YAMApplication_StartMacro, MACRO_POSTGET, downloadedStr);
AppendToLogfile(LF_ALL, 30, tr(MSG_LOG_RETRIEVED_POP3), tc->downloadResult.downloaded, msn->description);
// we only apply the filters if we downloaded something, or it's wasted
D(DBF_NET, "filter %ld downloaded mails", tc->downloadResult.downloaded);
if(tc->downloadResult.downloaded > 0)
{
PushMethodOnStackWait(G->App, 3, MUIM_YAMApplication_FilterNewMails, tc->msn->downloadedMails, &tc->filterResult);
PushMethodOnStackWait(G->App, 5, MUIM_YAMApplication_NewMailAlert, tc->msn, &tc->downloadResult, &tc->filterResult, tc->flags);
}
// forget about the downloaded mails again
LockMailList(tc->msn->downloadedMails);
ClearMailList(tc->msn->downloadedMails);
UnlockMailList(tc->msn->downloadedMails);
}
else
{
// signal failure
success = FALSE;
}
// clean up the transfer list
DeleteMailTransferList(tc->transferList);
}
}
DeleteConnection(tc->connection);
}
else
{
E(DBF_FOLDER, "could not resolve incoming folder of POP3 server '%s'", tc->msn->description);
}
// finally copy the download stats of this operation if someone is interested in them
if(dlResult != NULL)
memcpy(dlResult, &tc->downloadResult, sizeof(*dlResult));
free(tc);
}
// mark the server as being no longer "in use"
LockMailServer(msn);
msn->useCount--;
UnlockMailServer(msn);
// now we are done
ReleaseSemaphore(G->configSemaphore);
// wake up the calling thread if this is requested
if(isFlagSet(flags, RECEIVEF_SIGNAL))
WakeupThread(NULL);
RETURN(success);
return success;
}
///