Files
bongo/src/agents/imap/imapd.c
T
2026-07-16 22:39:42 +02:00

4050 lines
137 KiB
C

/****************************************************************************
* <Novell-copyright>
* Copyright (c) 2001 Novell, Inc. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public License
* as published by the Free Software Foundation.
*
* 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, contact Novell, Inc.
*
* To contact Novell about this file by physical or electronic mail, you
* may find current contact information at www.novell.com.
* </Novell-copyright>
****************************************************************************/
#include <config.h>
#define N_PLAT_NLM
#define N_IAPX386
/* Product defines */
#define PRODUCT_SHORT_NAME "imapd.nlm"
#define PRODUCT_NAME "Bongo IMAP4 Agent"
#define PRODUCT_DESCRIPTION "Provides IMAP4 access to the Bongo mail-store. (IMAP4 = Internet Mail Access Protocol, Rev4, RFC2060)"
#define PRODUCT_VERSION "$Revision: 1.8 $"
#define MAX_THREAD_LOAD 50
#undef DEBUG_FETCH
#define NMLOGID_H_NEED_LOGGING_KEY
#define NMLOGID_H_NEED_LOGGING_CERT
#include <xpl.h>
#include <logger.h>
#include <msgapi.h>
#include <bongosasl.h>
#include "imapd.h"
#include "capabilities.h"
#include "idle.h"
#include "uidplus.h"
#include "special-use.h"
ImapGlobal Imap;
#define IMAP_PROTOCOL_COMMAND(command, handler) \
{ command, IMAP_HELP_NOT_DEFINED, sizeof(command) - 1, handler, \
NULL, NULL, NULL, BlackCommand }
static ProtocolCommand ImapProtocolCommands[] = {
IMAP_PROTOCOL_COMMAND(IMAP_COMMAND_CAPABILITY, ImapCommandCapability),
IMAP_PROTOCOL_COMMAND(IMAP_COMMAND_NOOP, ImapCommandNoop),
IMAP_PROTOCOL_COMMAND(IMAP_COMMAND_LOGOUT, ImapCommandLogout),
IMAP_PROTOCOL_COMMAND(IMAP_COMMAND_IDLE, ImapCommandIdle),
IMAP_PROTOCOL_COMMAND(IMAP_COMMAND_STARTTLS, ImapCommandStartTls),
IMAP_PROTOCOL_COMMAND(IMAP_COMMAND_AUTHENTICATE, ImapCommandAuthenticate),
IMAP_PROTOCOL_COMMAND(IMAP_COMMAND_LOGIN, ImapCommandLogin),
IMAP_PROTOCOL_COMMAND(IMAP_COMMAND_SELECT, ImapCommandSelect),
IMAP_PROTOCOL_COMMAND(IMAP_COMMAND_EXAMINE, ImapCommandExamine),
IMAP_PROTOCOL_COMMAND(IMAP_COMMAND_CREATE, ImapCommandCreate),
IMAP_PROTOCOL_COMMAND(IMAP_COMMAND_DELETE, ImapCommandDelete),
IMAP_PROTOCOL_COMMAND(IMAP_COMMAND_RENAME, ImapCommandRename),
IMAP_PROTOCOL_COMMAND(IMAP_COMMAND_SUBSCRIBE, ImapCommandSubscribe),
IMAP_PROTOCOL_COMMAND(IMAP_COMMAND_UNSUBSCRIBE, ImapCommandUnsubscribe),
IMAP_PROTOCOL_COMMAND(IMAP_COMMAND_LIST, ImapCommandList),
IMAP_PROTOCOL_COMMAND(IMAP_COMMAND_LSUB, ImapCommandLsub),
IMAP_PROTOCOL_COMMAND(IMAP_COMMAND_STATUS, ImapCommandStatus),
IMAP_PROTOCOL_COMMAND(IMAP_COMMAND_APPEND, ImapCommandAppend),
IMAP_PROTOCOL_COMMAND(IMAP_COMMAND_CHECK, ImapCommandCheck),
IMAP_PROTOCOL_COMMAND(IMAP_COMMAND_CLOSE, ImapCommandClose),
IMAP_PROTOCOL_COMMAND(IMAP_COMMAND_EXPUNGE, ImapCommandExpunge),
IMAP_PROTOCOL_COMMAND(IMAP_COMMAND_UID_EXPUNGE, ImapCommandUidExpunge),
IMAP_PROTOCOL_COMMAND(IMAP_COMMAND_SEARCH, ImapCommandSearch),
IMAP_PROTOCOL_COMMAND(IMAP_COMMAND_UID_SEARCH, ImapCommandUidSearch),
IMAP_PROTOCOL_COMMAND(IMAP_COMMAND_FETCH, ImapCommandFetch),
IMAP_PROTOCOL_COMMAND(IMAP_COMMAND_UID_FETCH, ImapCommandUidFetch),
IMAP_PROTOCOL_COMMAND(IMAP_COMMAND_STORE, ImapCommandStore),
IMAP_PROTOCOL_COMMAND(IMAP_COMMAND_UID_STORE, ImapCommandUidStore),
IMAP_PROTOCOL_COMMAND(IMAP_COMMAND_COPY, ImapCommandCopy),
IMAP_PROTOCOL_COMMAND(IMAP_COMMAND_UID_COPY, ImapCommandUidCopy),
IMAP_PROTOCOL_COMMAND(IMAP_COMMAND_MOVE, ImapCommandMove),
IMAP_PROTOCOL_COMMAND(IMAP_COMMAND_UID_MOVE, ImapCommandUidMove),
IMAP_PROTOCOL_COMMAND(IMAP_COMMAND_SETACL, ImapCommandSetAcl),
IMAP_PROTOCOL_COMMAND(IMAP_COMMAND_DELETEACL, ImapCommandDeleteAcl),
IMAP_PROTOCOL_COMMAND(IMAP_COMMAND_GETACL, ImapCommandGetAcl),
IMAP_PROTOCOL_COMMAND(IMAP_COMMAND_LISTRIGHTS, ImapCommandListRights),
IMAP_PROTOCOL_COMMAND(IMAP_COMMAND_MYRIGHTS, ImapCommandMyRights),
IMAP_PROTOCOL_COMMAND(IMAP_COMMAND_SETQUOTA, ImapCommandSetQuota),
IMAP_PROTOCOL_COMMAND(IMAP_COMMAND_GETQUOTA, ImapCommandGetQuota),
IMAP_PROTOCOL_COMMAND(IMAP_COMMAND_GETQUOTAROOT, ImapCommandGetQuotaRoot),
IMAP_PROTOCOL_COMMAND(IMAP_COMMAND_NAMESPACE, ImapCommandNameSpace),
{ NULL, NULL, 0, NULL, NULL, NULL, NULL, BlackCommand }
};
#undef IMAP_PROTOCOL_COMMAND
#undef DEBUG
#define ImapSessionGet() MemPrivatePoolGetEntryDirect(IMAPConnectionPool, __FILE__, __LINE__)
void *IMAPConnectionPool = NULL;
static void
FreeReturnValueIndex(long *index)
{
MemFree(index);
}
static long *
CreateReturnValueIndex()
{
long status;
unsigned long count;
unsigned long i;
long *index;
index = MemMalloc(STATUS_MAX * sizeof(unsigned long *));
if (index) {
/* find the number of strings */
count = 0;
do {
if (ImapErrorStrings[count].formatString != NULL) {
count++;
continue;
}
break;
} while (TRUE);
for (status = 0; status < STATUS_MAX; status++) {
index[status] = -1;
for (i = 0; i < count; i++) {
if (ImapErrorStrings[i].id != status) {
continue;
}
index[status] = i;
break;
}
}
return(index);
}
return(NULL);
}
#if 0
// FIXME: Shutdown not needed atm
static BOOL
IMAPShutdown(unsigned char *Arguments, unsigned char **Response, BOOL *CloseConnection)
{
XplThreadID id;
if (Response) {
if (!Arguments) {
if (Imap.server.conn->socket != -1) {
*Response = MemStrdup("Shutting down.\r\n");
if (*Response) {
id = XplSetThreadGroupID(Imap.server.threadGroupId);
Imap.exiting = TRUE;
if (Imap.server.conn) {
ConnClose(Imap.server.conn);
Imap.server.conn = NULL;
}
XplSignalLocalSemaphore(Imap.server.shutdownSemaphore);
if (CloseConnection) {
*CloseConnection = TRUE;
}
XplSetThreadGroupID(id);
}
} else if (Imap.exiting) {
*Response = MemStrdup("Shutdown in progress.\r\n");
} else {
*Response = MemStrdup("Unknown shutdown state.\r\n");
}
if (*Response) {
return(TRUE);
}
return(FALSE);
}
*Response = MemStrdup("Arguments not allowed.\r\n");
return(TRUE);
}
return(FALSE);
}
#endif
static BOOL
IMAPSessionAllocCB(void *Buffer, void *ClientData)
{
UNUSED_PARAMETER(ClientData);
register ImapSession *session = (ImapSession *)Buffer;
memset(session, 0, sizeof(ImapSession));
session->client.state = STATE_FRESH;
return(TRUE);
}
static BOOL
IMAPSessionFreeCB(void *Buffer, void *ClientData)
{
UNUSED_PARAMETER(ClientData);
register ImapSession *session = (ImapSession *)Buffer;
if (session->command.buffer) {
MemFree(session->command.buffer);
}
return(TRUE);
}
static void
ImapSessionReturn(ImapSession *session)
{
register ImapSession *s = session;
register char *command;
if (s->command.bufferLen == BUFSIZE) {
command = s->command.buffer;
} else {
if (s->command.buffer != NULL) {
MemFree(s->command.buffer);
}
command = MemMalloc(BUFSIZE + 1);
}
if (command != NULL) {
memset(s, 0, sizeof(ImapSession));
s->client.state = STATE_FRESH;
s->command.bufferLen = BUFSIZE;
s->command.buffer = command;
s->command.buffer[0] = '\0';
}
MemPrivatePoolReturnEntry(IMAPConnectionPool, s);
return;
}
#define FOLDER_INITIAL_SIZE 5
long
FolderListInitialize(ImapSession *session)
{
if (!session->folder.list) {
session->folder.list = MemMalloc(sizeof(FolderInformation) * FOLDER_INITIAL_SIZE);
if (session->folder.list) {
session->folder.allocated = FOLDER_INITIAL_SIZE;
return(STATUS_CONTINUE);
}
return(STATUS_MEMORY_ERROR);
}
/* This should always be null when called */
return(STATUS_BUG);
}
__inline static void
FolderListClear(FolderInformation *folder, size_t count)
{
if (count > 0) {
do {
MemFree(folder->name.utf8);
folder->name.utf8 = NULL;
MemFree(folder->name.utf7);
folder->name.utf7 = NULL;
folder++;
(count)--;
} while (count > 0);
}
}
__inline static void
FolderListShutdown(ImapSession *session)
{
FolderListClear(session->folder.list, session->folder.count);
if (session->folder.list) {
MemFree(session->folder.list);
session->folder.list = NULL;
}
}
__inline static long
FolderSetRecentUid(Connection *storeConn, FolderInformation *folder)
{
long ccode;
/* Set the new recent uid equal to the uidNext */
ccode = NMAPSetHexadecimalProperty(storeConn, folder->guid, "imap.recentuid", folder->uidNext);
if (ccode == 1000) {
return(STATUS_CONTINUE);
}
return(CheckForNMAPCommError(ccode));
}
__inline static long
FolderWatchAdd(Connection *storeConn, FolderInformation *folder, const char *eventString)
{
long ccode;
if ((ccode = CheckConnResultIsNot(-1, STATUS_NMAP_COMM_ERROR, NMAPSendCommandF(storeConn, "WATCH %" PRIx64 " %s\r\n", folder->guid, eventString))) == STATUS_CONTINUE) {
ccode = CheckStoreResponseCode(1000, NMAPReadResponse(storeConn, NULL, 0, 0));
}
return(ccode);
}
__inline static long
FolderWatchRemove(Connection *storeConn, FolderInformation *folder)
{
long ccode;
if ((ccode = CheckConnResultIsNot(-1, STATUS_NMAP_COMM_ERROR, NMAPSendCommandF(storeConn, "WATCH %" PRIx64 "\r\n", folder->guid))) == STATUS_CONTINUE) {
ccode = CheckStoreResponseCode(1000, NMAPReadResponse(storeConn, NULL, 0, 0));
}
return(ccode);
}
__inline static long
FolderDeselect(ImapSession *session)
{
/* This function resets the selected folder to the unselected state */
long ccode;
OpenedFolder *selectedFolder = &(session->folder.selected);
if (session->client.state == STATE_SELECTED) {
session->client.state = STATE_AUTH;
ccode = FolderWatchRemove(session->store.conn, selectedFolder->info);
EventsFree(&(selectedFolder->events));
if ((ccode == STATUS_CONTINUE) && !(selectedFolder->readOnly)) {
ccode = FolderSetRecentUid(session->store.conn, selectedFolder->info);
}
}
FolderClose(selectedFolder);
return(ccode);
}
__inline static long
FolderGetHighestUid(Connection *storeConn, FolderInformation *folder, uint32_t *highestUid)
{
long ccode;
unsigned long propertyValue;
/* Get the highest UID for the collection */
ccode = NMAPGetHexadecimalProperty(storeConn, folder->guid, "nmap.mail.imapuid", &propertyValue);
if (ccode == 2001) {
*highestUid = (uint32_t)propertyValue;
return(STATUS_CONTINUE);
}
return(CheckForNMAPCommError(ccode));
}
__inline static long
FolderGetRecentUid(Connection *storeConn, FolderInformation *folder, uint32_t *recentUid)
{
long ccode;
unsigned long propertyValue;
/* Get the recent UID for the collection */
ccode = NMAPGetHexadecimalProperty(storeConn, folder->guid, "imap.recentuid", &propertyValue);
if (ccode == 2001) {
*recentUid = (uint32_t)propertyValue;
return(STATUS_CONTINUE);
}
if (ccode == 3245) {
*recentUid = 0;
return(STATUS_CONTINUE);
}
return(CheckForNMAPCommError(ccode));
}
__inline static unsigned long
FolderFindFirstUnseen(MessageInformation *messageList, unsigned long messageCount)
{
unsigned long firstUnseen = 0;
MessageInformation *currentMessage = &(messageList[0]);
for (;;) {
if (currentMessage->flags & STORE_MSG_FLAG_SEEN) {
firstUnseen++;
if (firstUnseen < messageCount) {
currentMessage++;
continue;
}
}
break;
}
return(firstUnseen);
}
__inline static long
FolderSendStatus(Connection *conn, OpenedFolder *folder)
{
long ccode;
unsigned long firstUnseen;
firstUnseen = FolderFindFirstUnseen(folder->message, folder->messageCount);
if (firstUnseen != folder->messageCount) {
ccode = ConnWriteF(conn, "* %lu EXISTS\r\n* %lu RECENT\r\n* OK [UNSEEN %lu] Message %lu is first unseen\r\n* OK [UIDVALIDITY %lu] UIDs valid\r\n* OK [UIDNEXT %lu] Predicted next UID\r\n* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n",
folder->messageCount,
folder->recentCount,
firstUnseen + 1,
firstUnseen + 1,
(unsigned long)(folder->info->guid & 0x00000000FFFFFFFF),
(unsigned long)folder->info->uidNext);
} else {
ccode = ConnWriteF(conn, "* %lu EXISTS\r\n* %lu RECENT\r\n* OK [UIDVALIDITY %lu] UIDs valid\r\n* OK [UIDNEXT %lu] Predicted next UID\r\n* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n",
folder->messageCount,
folder->recentCount,
(unsigned long)(folder->info->guid & 0x00000000FFFFFFFF),
(unsigned long)folder->info->uidNext);
}
if (ccode != -1) {
return(STATUS_CONTINUE);
}
return(STATUS_ABORT);
}
long
FolderOpen(Connection *storeConn, OpenedFolder *openFolder, FolderInformation *folder, BOOL readOnly)
{
long ccode;
uint32_t highestUid = 0;
ccode = FolderGetRecentUid(storeConn, folder, &folder->uidRecent);
if (ccode == STATUS_CONTINUE) {
ccode = FolderGetHighestUid(storeConn, folder, &highestUid);
if (ccode == STATUS_CONTINUE) {
openFolder->info = folder;
ccode = MessageListLoad(storeConn, openFolder);
if (ccode == STATUS_CONTINUE) {
/* the nmap.mail.imapuid property is managed by the store agent. */
/* On a collection, this property contains the highest uid ever assigned */
/* to a document in the collection. UIDNEXT needs to be at least one bigger than this value */
if ((openFolder->messageCount == 0) || (folder->uidNext <= highestUid)) {
folder->uidNext = highestUid + 1;
}
openFolder->readOnly = readOnly;
return(STATUS_CONTINUE);
}
}
FolderClose(openFolder);
}
return(ccode);
}
__inline static long
FolderSelect(ImapSession *session, char *folderName, BOOL readOnly)
{
long ccode;
FolderInformation *folder;
FolderDeselect(session);
if ((ccode = FolderGetByName(session, folderName, &folder)) == STATUS_CONTINUE) {
if ((ccode = FolderWatchAdd(session->store.conn, folder, "FLAGS MODIFIED NEW DELETED")) == STATUS_CONTINUE) {
if ((ccode = FolderOpen(session->store.conn, &session->folder.selected, folder, readOnly)) == STATUS_CONTINUE) {
session->client.state = STATE_SELECTED;
return(STATUS_CONTINUE);
}
FolderWatchRemove(session->store.conn, folder);
EventsFree(&(session->folder.selected.events));
}
}
return(ccode);
}
__inline static long
ParseCollectionLine(char *buffer, FolderInformation *folder)
{
char *ptr;
ptr = buffer;
folder->guid = HexToUInt64(ptr, &ptr);
if (*ptr == ' ') {
unsigned long type;
char *typeStart;
typeStart = ptr + 1;
do {
ptr++;
} while (isdigit(*ptr));
if ((ptr > typeStart) &&
(*ptr == ' ')) {
type = atol(typeStart);
if (type & (STORE_DOCTYPE_MAIL | STORE_DOCTYPE_FOLDER)) {
char *start;
size_t len;
start = ptr + 1;
do {
ptr++;
} while (isdigit(*ptr));
if ((ptr > start) &&
(*ptr == ' ')) {
folder->flags = atol(start);
/* Stores created before SPECIAL-USE support did not have
* role bits. The stable default-folder GUIDs preserve
* those roles across renames without a schema migration. */
if (folder->guid == STORE_MAIL_DRAFTS_GUID) {
folder->flags |= STORE_COLLECTION_FLAG_SPECIAL_DRAFTS;
} else if (folder->guid == STORE_MAIL_ARCHIVES_GUID) {
folder->flags |= STORE_COLLECTION_FLAG_SPECIAL_ARCHIVE;
}
ptr++;
len = strlen(ptr);
folder->name.utf8 = MemMalloc(len + 1);
if (folder->name.utf8) {
char *to, *from;
memcpy(folder->name.utf8, ptr, len);
folder->name.utf8[len] = '\0';
// remove any escaped spaces
to = from = folder->name.utf8;
while (*from != '\0') {
if (*from != '\\') {
*to = *from;
to++;
} else {
len--;
}
from++;
}
*to = '\0';
if (memcmp(folder->name.utf8, "/mail/", strlen("/mail/")) == 0) {
len = GetModifiedUtf7FromUtf8(folder->name.utf8 + strlen("/mail/"), len - strlen("/mail/"), &folder->name.utf7);
if (len > 0) {
return(STATUS_CONTINUE);
}
MemFree(folder->name.utf8);
folder->name.utf8 = NULL;
}
MemFree(folder->name.utf8);
return(STATUS_INVALID_FOLDER_NAME);
}
return(STATUS_MEMORY_ERROR);
}
return(STATUS_NMAP_PROTOCOL_ERROR);
}
return(STATUS_NOT_MAIL_FOLDER);
}
}
return(STATUS_NMAP_PROTOCOL_ERROR);
}
static int
FolderCompare(const void *param1, const void *param2)
{
FolderInformation *folder1 = (FolderInformation *)param1;
FolderInformation *folder2 = (FolderInformation *)param2;
if (strncmp("INBOX", folder1->name.utf7, strlen("INBOX")) == 0) {
if (folder1->name.utf7[strlen("INBOX")] == '\0') {
/* folder 1 is INBOX; INBOX is always first */
return(-1);
}
if (folder1->name.utf7[strlen("INBOX")] == '/') {
/* folder1 is a sub folder of INBOX */
if (strncmp("INBOX", folder2->name.utf7, strlen("INBOX")) != 0) {
/* folder2 is not related to INBOX; folder1 is first */
return(-1);
}
if (folder2->name.utf7[strlen("INBOX")] == '\0') {
/* folder 2 is INBOX; INBOX is always first */
return(1);
}
if (folder2->name.utf7[strlen("INBOX")] == '/') {
/* folder2 is also a sub folder of INBOX; break the tie */
return(strcmp(folder1->name.utf7 + strlen("INBOX/"), folder2->name.utf7 + strlen("INBOX/")));
}
/* folder 2 is not related to INBOX; folder 1 is first */
return(-1);
}
}
/* folder1 is not related to INBOX */
if (strncmp("INBOX", folder2->name.utf7, strlen("INBOX")) != 0) {
/* folder2 is not related to INBOX either; normal compare */
return(strcmp(folder1->name.utf7, folder2->name.utf7));
}
if (folder2->name.utf7[strlen("INBOX")] == '\0') {
/* folder 2 is INBOX; INBOX is always first */
return(1);
}
if (folder2->name.utf7[strlen("INBOX")] == '/') {
/* folder2 is a sub folder of INBOX; folder 2 is first */
return(1);
}
/* folder2 is not related to INBOX either; normal compare */
return(strcmp(folder1->name.utf7, folder2->name.utf7));
}
long
FolderListLoad(ImapSession *session)
{
char buffer[CONN_BUFSIZE];
uint64_t selectedGuid = 0;
FolderInformation *folderList;
FolderInformation *newList;
FolderInformation *currentFolder;
long ccode;
long folderCount;
long allocatedCount;
allocatedCount = session->folder.allocated;
folderList = session->folder.list;
currentFolder = folderList;
if (session->client.state == STATE_SELECTED) {
selectedGuid = session->folder.selected.info->guid;
}
/* clear anything that is already there */
FolderListClear(currentFolder, session->folder.count);
folderCount = 0;
if (NMAPSendCommand(session->store.conn, "COLLECTIONS /mail\r\n", strlen("COLLECTIONS /mail\r\n")) != -1) {
do {
ccode = NMAPReadResponse(session->store.conn, buffer, sizeof(buffer), TRUE);
if (ccode == 2001) {
if (folderCount < allocatedCount) {
;
} else {
newList = MemRealloc(folderList, sizeof(FolderInformation) * allocatedCount * 2);
if (newList) {
folderList = newList;
allocatedCount *= 2;
currentFolder = &(folderList[folderCount]);
} else {
session->folder.list = folderList;
session->folder.allocated = allocatedCount;
session->folder.count = 0;
return(STATUS_MEMORY_ERROR);
}
}
ccode = ParseCollectionLine(buffer + 5, currentFolder);
if (ccode == STATUS_CONTINUE) {
folderCount++;
currentFolder++;
continue;
}
if ((ccode == STATUS_NOT_MAIL_FOLDER) || (ccode == STATUS_INVALID_FOLDER_NAME)) {
continue;
}
session->folder.list = folderList;
session->folder.allocated = allocatedCount;
session->folder.count = 0;
return(STATUS_NMAP_PROTOCOL_ERROR);
}
if (ccode == 1000) {
break;
}
session->folder.list = folderList;
session->folder.allocated = allocatedCount;
session->folder.count = 0;
return(CheckForNMAPCommError(ccode));
} while (TRUE);
qsort(folderList, folderCount, sizeof(FolderInformation), FolderCompare);
session->folder.list = folderList;
session->folder.allocated = allocatedCount;
session->folder.count = folderCount;
if (session->client.state == STATE_SELECTED) {
FolderInformation *selectedFolder;
ccode = FolderGetByGuid(session, selectedGuid, &selectedFolder);
if (ccode == STATUS_CONTINUE) {
session->folder.selected.info = selectedFolder;
return(STATUS_CONTINUE);;
}
session->client.state = STATE_AUTH;
FolderDeselect(session);
if (ccode == STATUS_NO_SUCH_FOLDER) {
return(STATUS_SELECTED_FOLDER_LOST);
}
return(ccode);
}
return(STATUS_CONTINUE);
}
return(STATUS_NMAP_COMM_ERROR);
}
#define START_MESSAGE_LIST_ALLOCATION 25
__inline static long
MessageListReset(OpenedFolder *folder)
{
folder->recentCount = 0;
folder->messageCount = 0;
if (folder->messageAllocated > 0) {
return(STATUS_CONTINUE);
}
folder->message = MemMalloc(sizeof(MessageInformation) * START_MESSAGE_LIST_ALLOCATION);
if (folder->message) {
folder->messageAllocated = START_MESSAGE_LIST_ALLOCATION;
return(STATUS_CONTINUE);
}
folder->message = NULL;
folder->messageAllocated = 0;
folder->messageCount = 0;
return(STATUS_MEMORY_ERROR);
}
static int
CompareMessageInformation(const void *param1, const void *param2)
{
MessageInformation *msg1 = (MessageInformation *)param1;
MessageInformation *msg2 = (MessageInformation *)param2;
return(msg1->uid - msg2->uid);
}
long
MessageListLoad(Connection *storeConn, OpenedFolder *folder)
{
char reply[1024];
long ccode;
long headerSize;
if ((ccode = MessageListReset(folder)) == STATUS_CONTINUE) {
if (NMAPSendCommandF(storeConn, "LIST %" PRIx64 " Pnmap.mail.headersize\r\n", folder->info->guid) != -1) {
for (;;) {
ccode = NMAPReadResponse(storeConn, reply, sizeof(reply), TRUE);
if (ccode == 2001) {
ccode = NMAPReadDecimalPropertyResponse(storeConn, "nmap.mail.headersize", &headerSize);
if (ccode == 2001) {
// don't look at mails which are obviously bad. Bug #11199
if (headerSize > 0) {
ccode = MessageListAddMessage(folder, reply, headerSize);
} else {
// ignore this for now - we need to log a message
// bug #11200
}
continue;
}
if (ccode == 3245) {
ccode = MessageListAddMessage(folder, reply, 0);
continue;
}
return(ccode);
}
if (ccode == 1000) {
break;
}
folder->messageCount = 0;
return(CheckForNMAPCommError(ccode));
}
qsort(folder->message, folder->messageCount, sizeof(MessageInformation), CompareMessageInformation);
return(STATUS_CONTINUE);
}
return(STATUS_NMAP_COMM_ERROR);
}
return(ccode);
}
static BOOL
SessionCleanup(ImapSession *session)
{
if (session) {
if (session->client.state == STATE_EXITING) {
return(FALSE);
}
session->client.state = STATE_EXITING;
FreeUnseenFlags(session);
if (session->store.conn) {
if (session->store.conn->socket != -1) {
NMAPSendCommand(session->store.conn, "QUIT\r\n", 6);
ConnClose(session->store.conn);
session->store.conn->socket = -1;
}
ConnFree(session->store.conn);
session->store.conn = NULL;
}
if (session->client.conn) {
if (session->client.conn->socket != -1) {
ConnFlush(session->client.conn);
ConnClose(session->client.conn);
session->client.conn->socket = -1;
}
ConnFree(session->client.conn);
session->client.conn = NULL;
}
FolderDeselect(session);
FolderListShutdown(session);
if (session->user.name) {
MemFree(session->user.name);
}
ImapSessionReturn(session);
}
XplSafeDecrement(Imap.session.threads.inUse);
XplSafeIncrement(Imap.session.served);
return(TRUE);
}
static int
ImapCommandUnknown(void *param)
{
ImapSession *session = (ImapSession *)param;
Log(LOG_INFO, "Unhandled command for user %s", session->user.name);
return(SendError(session->client.conn, "*", session->command.tag, STATUS_UNKNOWN_COMMAND));
}
/********** IMAP session commands - any state (Capability, Noop, Logout) **********/
int
ImapCommandCapability(void *param)
{
long ccode;
ImapSession *session = (ImapSession *)param;
char capability[256];
size_t capabilityLen;
if ((ccode = EventsSend(session, STORE_EVENT_ALL)) == STATUS_CONTINUE) {
capabilityLen = IMAPFormatCapabilities(
capability, sizeof(capability), session->client.conn->ssl.enable,
Imap.server.ssl.context != NULL,
session->client.state >= STATE_AUTH);
if (capabilityLen == 0) return STATUS_ABORT;
if (ConnWrite(session->client.conn, capability, capabilityLen) != -1) {
return(SendOk(session, "CAPABILITY"));
}
return(STATUS_ABORT);
}
return(SendError(session->client.conn, session->command.tag, "CAPABILITY", ccode));
}
int
ImapCommandNoop(void *param)
{
long ccode;
ImapSession *session = (ImapSession *)param;
if ((ccode = EventsSend(session, STORE_EVENT_ALL)) == STATUS_CONTINUE) {
return(SendOk(session, "NOOP"));
}
return(SendError(session->client.conn, session->command.tag, "NOOP", ccode));
}
static int
IdleReadLine(void *opaque, const char **line)
{
ImapSession *session = opaque;
if (ReadCommandLine(session->client.conn, &session->command.buffer,
&session->command.bufferLen) != STATUS_CONTINUE)
return 0;
*line = session->command.buffer;
return 1;
}
static int
IdlePollEvents(void *opaque)
{
ImapSession *session = opaque;
return EventsSend(session, STORE_EVENT_ALL) == STATUS_CONTINUE &&
ConnFlush(session->client.conn) != -1;
}
int
ImapCommandIdle(void *param)
{
ImapSession *session = (ImapSession *)param;
const char *arguments = session->command.buffer + strlen(IMAP_COMMAND_IDLE);
IMAPIdleResult result;
while (IsWhiteSpace(*arguments)) arguments++;
if (*arguments != '\0')
return SendError(session->client.conn, session->command.tag, "IDLE",
STATUS_INVALID_ARGUMENT);
if (session->client.state < STATE_AUTH)
return SendError(session->client.conn, session->command.tag, "IDLE",
STATUS_INVALID_STATE);
if (ConnWrite(session->client.conn, "+ idling\r\n",
sizeof("+ idling\r\n") - 1) == -1 ||
ConnFlush(session->client.conn) == -1)
return STATUS_ABORT;
result = IMAPIdleRun(session->client.conn->socket, 5, 29 * 60,
IdleReadLine, IdlePollEvents, session);
if (result == IMAP_IDLE_DONE) return SendOk(session, "IDLE");
if (result == IMAP_IDLE_INVALID)
return SendError(session->client.conn, session->command.tag, "IDLE",
STATUS_INVALID_ARGUMENT);
if (result == IMAP_IDLE_TIMEOUT) {
ConnWrite(session->client.conn, "* BYE IDLE timeout\r\n",
sizeof("* BYE IDLE timeout\r\n") - 1);
ConnFlush(session->client.conn);
}
return STATUS_ABORT;
}
int
ImapCommandLogout(void *param)
{
ImapSession *session = (ImapSession *)param;
if (ConnWrite(session->client.conn, "* BYE IMAP4rev1 Server signing off\r\n", 36) != -1) {
if (SendOk(session,"LOGOUT") == STATUS_CONTINUE) {
ConnFlush(session->client.conn);
}
}
return(STATUS_ABORT);
}
/********** IMAP session commands - authenticated state **********/
int
ImapCommandSelect(void *param)
{
ImapSession *session = (ImapSession *)param;
long ccode;
char *ptr;
FolderPath path;
if ((ccode = CheckState(session, STATE_AUTH)) == STATUS_CONTINUE) {
ccode = GetPathArgument(session, session->command.buffer + strlen("SELECT"), &ptr, &path, FALSE);
if (ccode == STATUS_CONTINUE) {
ccode = FolderListLoad(session);
if (ccode == STATUS_CONTINUE) {
ccode = FolderSelect(session, path.name, FALSE);
if (ccode == STATUS_CONTINUE) {
ccode = FolderSendStatus(session->client.conn, &session->folder.selected);
if (ccode == STATUS_CONTINUE) {
FreePathArgument(&path);
return(SendOk(session, "[READ-WRITE] SELECT"));
}
}
}
FreePathArgument(&path);
}
return(SendError(session->client.conn, session->command.tag, "SELECT", ccode));
}
return(SendError(session->client.conn, session->command.tag, "SELECT", STATUS_INVALID_STATE));
}
int
ImapCommandExamine(void *param)
{
ImapSession *session = (ImapSession *)param;
long ccode;
char *ptr;
FolderPath path;
if ((ccode = CheckState(session, STATE_AUTH)) == STATUS_CONTINUE) {
ccode = GetPathArgument(session, session->command.buffer + strlen("EXAMINE"), &ptr, &path, FALSE);
if (ccode == STATUS_CONTINUE) {
ccode = FolderListLoad(session);
if (ccode == STATUS_CONTINUE) {
ccode = FolderSelect(session, path.name, TRUE);
if (ccode == STATUS_CONTINUE) {
ccode = FolderSendStatus(session->client.conn, &session->folder.selected);
if (ccode == STATUS_CONTINUE) {
FreePathArgument(&path);
return(SendOk(session, "[READ-ONLY] EXAMINE"));
}
}
}
FreePathArgument(&path);
}
return(SendError(session->client.conn, session->command.tag, "EXAMINE", ccode));
}
return(SendError(session->client.conn, session->command.tag, "EXAMINE", STATUS_INVALID_STATE));
}
__inline static FolderInformation *
FolderGet(ImapSession *session, char *folderName)
{
unsigned long i;
FolderInformation *folder;
folder = &session->folder.list[0];
for(i = 0; i < session->folder.count; i++) {
if (strcmp(folderName, folder->name.utf7)) {
folder++;
continue;
}
return(folder);
}
return(NULL);
}
__inline static BOOL
FolderHasSubFolders(ImapSession *session, FolderInformation *folder)
{
unsigned long folderLen;
FolderInformation *nextFolder;
if ((unsigned long)((folder - session->folder.list) + 1) < session->folder.count) {
folderLen = strlen(folder->name.utf7);
nextFolder = folder + 1;
if (strncmp(folder->name.utf7, nextFolder->name.utf7, folderLen) == 0) {
if ((nextFolder->name.utf7)[folderLen] == '/') {
return(TRUE);
}
}
}
return(FALSE);
}
__inline static BOOL
FolderIsSelected(ImapSession *session, char *folderName)
{
unsigned long i;
FolderInformation *folder;
folder = &session->folder.list[0];
for(i = 0; i < session->folder.count; i++) {
if (strcmp(folderName, folder->name.utf7)) {
folder++;
continue;
}
if (folder == session->folder.selected.info) {
return(TRUE);
}
break;
}
return(FALSE);
}
__inline static BOOL
FolderExists(ImapSession *session, char *folderPathUtf8)
{
unsigned long i;
FolderInformation *folder;
folder = &session->folder.list[0];
for(i = 0; i < session->folder.count; i++) {
if (strcmp(folderPathUtf8, folder->name.utf8 + strlen("/mail/"))) {
folder++;
continue;
}
return(TRUE);
}
return(FALSE);
}
__inline static long
FolderHierarchyCreate(ImapSession *session, char *folderPathUtf8)
{
char *ptr;
long ccode;
ptr = folderPathUtf8;
while ((ptr = strchr(ptr, '/')) != NULL) {
*ptr = '\0';
if (FolderExists(session, folderPathUtf8)) {
*ptr = '/';
ptr++;
continue;
}
do {
*ptr = '\0';
if (NMAPSendCommandF(session->store.conn, "CREATE /mail/%s\r\n", folderPathUtf8) != -1) {
ccode = NMAPReadResponse(session->store.conn, NULL, 0, 0);
if (ccode == 1000) {
if (NMAPSendCommandF(session->store.conn, "FLAG /mail/%s +%u\r\n", folderPathUtf8, STORE_COLLECTION_FLAG_HIERARCHY_ONLY) != -1) {
ccode = NMAPReadResponse(session->store.conn, NULL, 0, 0);
if (ccode == 1000) {
*ptr = '/';
ptr++;
continue;
}
*ptr = '/';
return(ccode);
}
*ptr = '/';
return(STATUS_NMAP_COMM_ERROR);
}
*ptr = '/';
return(ccode);
}
*ptr = '/';
return(STATUS_NMAP_COMM_ERROR);
} while((ptr = strchr(ptr, '/')) != NULL);
break;
}
return(STATUS_CONTINUE);
}
int
ImapCommandCreate(void *param)
{
ImapSession *session = (ImapSession *)param;
long ccode;
char *ptr;
FolderPath path;
char *pathUtf8;
int len;
FolderInformation *folder;
uint32_t specialUseFlags;
if ((ccode = CheckState(session, STATE_AUTH)) != STATUS_CONTINUE) {
return(SendError(session->client.conn, session->command.tag, "CREATE", ccode));
}
if ((ccode = EventsSend(session, STORE_EVENT_ALL)) != STATUS_CONTINUE) {
return(SendError(session->client.conn, session->command.tag, "CREATE", ccode));
}
ccode = GetPathArgument(session, session->command.buffer + strlen("CREATE"), &ptr, &path, FALSE);
if (ccode != STATUS_CONTINUE) {
return(SendError(session->client.conn, session->command.tag, "CREATE", ccode));
}
if (!IMAPSpecialUseParseCreateOptions(ptr, &specialUseFlags)) {
FreePathArgument(&path);
return(SendError(session->client.conn, session->command.tag, "CREATE",
STATUS_INVALID_ARGUMENT));
}
/* prohibition of naming a folder the same name as the userid; don't know why (rprice) */
if (XplStrCaseCmp(path.name, session->user.name) == 0) {
FreePathArgument(&path);
return(SendError(session->client.conn, session->command.tag, "CREATE", STATUS_INVALID_FOLDER_NAME));
}
len = GetUtf8FromModifiedUtf7(path.name, path.nameLen, &pathUtf8);
if (len < 1) {
if (len == 0) {
MemFree(pathUtf8);
}
FreePathArgument(&path);
return(SendError(session->client.conn, session->command.tag, "CREATE", STATUS_INVALID_FOLDER_NAME));
}
ccode = ValidateUtf7Utf8Pair(path.name, path.nameLen, pathUtf8, len);
if (ccode != UTF7_STATUS_SUCCESS) {
FreePathArgument(&path);
MemFree(pathUtf8);
return(SendError(session->client.conn, session->command.tag, "CREATE", STATUS_INVALID_FOLDER_NAME));
}
/* Make sure the folder information is up to date */
ccode = FolderListLoad(session);
if (ccode != STATUS_CONTINUE) {
FreePathArgument(&path);
MemFree(pathUtf8);
return(SendError(session->client.conn, session->command.tag, "CREATE", ccode));
}
folder = FolderGet(session, path.name);
FreePathArgument(&path);
if (folder) {
/* The collection is already there */
if ((folder->flags & STORE_COLLECTION_FLAG_HIERARCHY_ONLY) == 0) {
/* this is a full fledge folder */
MemFree(pathUtf8);
return(SendError(session->client.conn, session->command.tag, "CREATE", STATUS_FOLDER_ALREADY_EXISTS));
}
/* this folder is just hierarchy. Make it a real folder */
folder->flags &= ~(STORE_COLLECTION_FLAG_HIERARCHY_ONLY |
STORE_COLLECTION_FLAG_SPECIAL_USE_MASK);
folder->flags |= specialUseFlags;
if (NMAPSendCommandF(session->store.conn, "FLAG /mail/%s %lu\r\n",
pathUtf8, folder->flags) == -1) {
MemFree(pathUtf8);
return(STATUS_NMAP_COMM_ERROR);
}
MemFree(pathUtf8);
ccode = NMAPReadResponse(session->store.conn, NULL, 0, 0);
if (ccode != 1000) {
return(ccode);
}
return(SendOk(session,"CREATE"));
}
ccode = FolderHierarchyCreate(session, pathUtf8);
if (ccode != STATUS_CONTINUE) {
MemFree(pathUtf8);
return(SendError(session->client.conn, session->command.tag, "CREATE", ccode));
}
if (specialUseFlags != 0) {
ccode = NMAPSendCommandF(session->store.conn,
"CREATE \"/mail/%s\" 0 %u\r\n",
pathUtf8, (unsigned int)specialUseFlags);
} else {
ccode = NMAPSendCommandF(session->store.conn,
"CREATE \"/mail/%s\"\r\n", pathUtf8);
}
if (ccode == -1) {
MemFree(pathUtf8);
return(SendError(session->client.conn, session->command.tag, "CREATE", STATUS_NMAP_COMM_ERROR));
}
MemFree(pathUtf8);
ccode = NMAPReadResponse(session->store.conn, NULL, 0, 0);
if (ccode != 1000) {
return(SendError(session->client.conn, session->command.tag, "CREATE", CheckForNMAPCommError(ccode)));
}
return(SendOk(session,"CREATE"));
}
__inline static uint64_t
ParseMessageGuid(char *ptr)
{
uint64_t guid;
unsigned long type;
if (*ptr == ' ') {
ptr++;
guid = HexToUInt64(ptr, &ptr);
if (*ptr == ' ') {
ptr++;
type = strtoul(ptr, NULL, 10);
if (!(type & STORE_DOCTYPE_FOLDER) && (type & STORE_DOCTYPE_MAIL)) {
return(guid);
}
}
}
return(0);
}
__inline static long
FolderGetMessageGuids(ImapSession *session, char *collectionName, uint64_t **list, unsigned long *count)
{
long ccode;
char buffer[CONN_BUFSIZE];
uint64_t *guid;
uint64_t *tmpList;
unsigned long guidCount;
unsigned long allocCount;
if (NMAPSendCommandF(session->store.conn, "LIST %s\r\n", collectionName) != -1) {
guidCount = 0;
allocCount = 50;
guid = MemMalloc(sizeof(uint64_t) * 50);
for (;;) {
ccode = NMAPReadResponse(session->store.conn, buffer, sizeof(buffer), TRUE);
if (ccode == 2001) {
if (guid) {
if (guidCount < allocCount) {
guid[guidCount] = ParseMessageGuid(buffer + 4);
if (guid[guidCount]) {
guidCount++;
}
continue;
}
tmpList = MemRealloc(guid, sizeof(uint64_t) * allocCount * 2);
if (tmpList) {
guid = tmpList;
allocCount *= 2;
guid[guidCount] = ParseMessageGuid(buffer + 4);
if (guid[guidCount]) {
guidCount++;
}
continue;
}
MemFree(guid);
guid = NULL;
}
/* keep reading to clean out the network buffer */
continue;
}
if (ccode == 1000) {
if (guid) {
*list = guid;
*count = guidCount;
return(STATUS_CONTINUE);
}
return(STATUS_MEMORY_ERROR);
}
if (guid) {
MemFree(guid);
}
return(CheckForNMAPCommError(ccode));
}
}
return(STATUS_NMAP_COMM_ERROR);
}
__inline static long
FolderConvertToHierarchyOnly(ImapSession *session, FolderInformation *folder)
{
long ccode;
uint64_t *guid;
uint64_t *guidList = NULL;
unsigned long guidCount;
if (NMAPSendCommandF(session->store.conn, "FLAG %s +%u\r\n", folder->name.utf8, STORE_COLLECTION_FLAG_HIERARCHY_ONLY) == -1) {
return(STATUS_NMAP_COMM_ERROR);
}
ccode = NMAPReadResponse(session->store.conn, NULL, 0, 0);
if (ccode != 1000) {
return(CheckForNMAPCommError(ccode));
}
folder->flags |= STORE_COLLECTION_FLAG_HIERARCHY_ONLY;
ccode = FolderGetMessageGuids(session, folder->name.utf8, &guidList, &guidCount);
if (ccode != STATUS_CONTINUE) {
return(ccode);
}
guid = &guidList[0];
while(guidCount > 0) {
if (NMAPSendCommandF(session->store.conn, "PURGE %" PRIx64 "\r\n", *guid) != -1) {
ccode = NMAPReadResponse(session->store.conn, NULL, 0, 0);
if ((ccode == 1000) || (ccode == 4220)) {
guid++;
guidCount--;
continue;
}
MemFree(guidList);
return(CheckForNMAPCommError(ccode));
}
MemFree(guidList);
return(STATUS_NMAP_COMM_ERROR);
}
MemFree(guidList);
return(STATUS_CONTINUE);
}
int
ImapCommandDelete(void *param)
{
long ccode;
ImapSession *session = (ImapSession *)param;
char *ptr;
FolderPath path;
FolderInformation *folder;
if ((ccode = CheckState(session, STATE_AUTH)) != STATUS_CONTINUE) {
return(SendError(session->client.conn, session->command.tag, "DELETE", ccode));
}
if ((ccode = EventsSend(session, STORE_EVENT_ALL)) != STATUS_CONTINUE) {
return(SendError(session->client.conn, session->command.tag, "DELETE", ccode));
}
ccode = GetPathArgument(session, session->command.buffer + strlen("DELETE"), &ptr, &path, FALSE);
if (ccode != STATUS_CONTINUE) {
return(SendError(session->client.conn, session->command.tag, "DELETE", ccode));
}
if(strcmp(path.name, "INBOX") == 0) {
MemFree(path.buffer);
return(SendError(session->client.conn, session->command.tag, "DELETE", STATUS_INVALID_FOLDER_NAME));
}
ccode = FolderListLoad(session);
if (ccode != STATUS_CONTINUE) {
return(SendError(session->client.conn, session->command.tag, "DELETE", ccode));
}
ccode = FolderGetByName(session, path.name, &folder);
if (ccode != STATUS_CONTINUE) {
MemFree(path.buffer);
return(SendError(session->client.conn, session->command.tag, "DELETE", ccode));
}
MemFree(path.buffer);
if (FolderHasSubFolders(session, folder)) {
if (folder->flags & STORE_COLLECTION_FLAG_HIERARCHY_ONLY) {
/* Can't delete hierarchy for other folders */
return(SendError(session->client.conn, session->command.tag, "DELETE", STATUS_FOLDER_HAS_INFERIOR));
}
if (folder == session->folder.selected.info) {
FolderDeselect(session);
}
/* delete the folder and leave the hierarchy */
ccode = FolderConvertToHierarchyOnly(session, folder);
if (ccode != STATUS_CONTINUE) {
return(SendError(session->client.conn, session->command.tag, "DELETE", ccode));
}
return(SendOk(session, "DELETE"));
}
/* delete the collection */
if (folder == session->folder.selected.info) {
FolderDeselect(session);
}
if (NMAPSendCommandF(session->store.conn, "REMOVE \"%s\"\r\n", folder->name.utf8) == -1) {
return(SendError(session->client.conn, session->command.tag, "DELETE", STATUS_NMAP_COMM_ERROR));
}
ccode = NMAPReadResponse(session->store.conn, NULL, 0, 0);
if (ccode != 1000) {
return(SendError(session->client.conn, session->command.tag, "DELETE", CheckForNMAPCommError(ccode)));
}
return(SendOk(session, "DELETE"));
}
__inline static long
FolderMoveMessages(ImapSession *session, char *sourcePathUtf8, char *destinationPathUtf8)
{
long ccode;
uint64_t *guidList = NULL;
uint64_t *guid;
unsigned long guidCount;
ccode = FolderGetMessageGuids(session, sourcePathUtf8, &guidList, &guidCount);
if (ccode != STATUS_CONTINUE) {
return(ccode);
}
guid = &guidList[0];
while(guidCount > 0) {
if (NMAPSendCommandF(session->store.conn, "MOVE %" PRIx64 " /mail/%s\r\n", *guid, destinationPathUtf8) != -1) {
ccode = NMAPReadResponse(session->store.conn, NULL, 0, 0);
if ((ccode == 1000) || (ccode == 4220)) {
guid++;
guidCount--;
continue;
}
}
MemFree(guidList);
return(STATUS_NMAP_COMM_ERROR);
}
MemFree(guidList);
return(STATUS_CONTINUE);
}
__inline static BOOL
SelectedFolderWillBeRenamed(FolderInformation *source, FolderInformation *selected)
{
unsigned long len;
if (!selected) {
return(FALSE);
}
if (source == selected) {
return(TRUE);
}
/* check to see if selected is a subfolder of source */
len = strlen(source->name.utf7);
if (strncmp(source->name.utf7, selected->name.utf7, len) != 0) {
return(FALSE);
}
if (source->name.utf7[len] == '/') {
return(TRUE);
}
return(FALSE);
}
int
ImapCommandRename(void *param)
{
ImapSession *session = (ImapSession *)param;
long ccode;
unsigned long len;
char *ptr;
FolderPath sourcePath;
FolderInformation *sourceFolder;
FolderPath destinationPath;
FolderInformation *destinationFolder;
char *destinationPathUtf8;
if ((ccode = CheckState(session, STATE_AUTH)) != STATUS_CONTINUE) {
return(SendError(session->client.conn, session->command.tag, "RENAME", ccode));
}
if ((ccode = EventsSend(session, STORE_EVENT_ALL)) != STATUS_CONTINUE) {
return(SendError(session->client.conn, session->command.tag, "RENAME", ccode));
}
ccode = GetPathArgument(session, session->command.buffer + strlen("RENAME"), &ptr, &sourcePath, FALSE);
if (ccode != STATUS_CONTINUE) {
return(SendError(session->client.conn, session->command.tag, "RENAME", ccode));
}
ccode = GetPathArgument(session, ptr, &ptr, &destinationPath, FALSE);
if (ccode != STATUS_CONTINUE) {
return(SendError(session->client.conn, session->command.tag, "RENAME", ccode));
}
ccode = FolderListLoad(session);
if (ccode != STATUS_CONTINUE) {
return(SendError(session->client.conn, session->command.tag, "RENAME", ccode));
}
/* make sure source folder does exist */
ccode = FolderGetByName(session, sourcePath.name, &sourceFolder);
if (ccode != STATUS_CONTINUE) {
FreePathArgument(&sourcePath);
FreePathArgument(&destinationPath);
return(SendError(session->client.conn, session->command.tag, "RENAME", STATUS_NO_SUCH_FOLDER));
}
FreePathArgument(&sourcePath);
/* make sure destination folder does not exist */
ccode = FolderGetByName(session, destinationPath.name, &destinationFolder);
if (ccode == STATUS_CONTINUE) {
FreePathArgument(&destinationPath);
return(SendError(session->client.conn, session->command.tag, "RENAME", STATUS_FOLDER_ALREADY_EXISTS));
}
if (ccode != STATUS_NO_SUCH_FOLDER) {
FreePathArgument(&destinationPath);
return(SendError(session->client.conn, session->command.tag, "RENAME", ccode));
}
/* Generate Utf8 for destination */
len = GetUtf8FromModifiedUtf7(destinationPath.name, destinationPath.nameLen, &destinationPathUtf8);
if (len < 1) {
if (len == 0) {
MemFree(destinationPathUtf8);
}
FreePathArgument(&destinationPath);
return(SendError(session->client.conn, session->command.tag, "RENAME", STATUS_INVALID_FOLDER_NAME));
}
ccode = ValidateUtf7Utf8Pair(destinationPath.name, destinationPath.nameLen, destinationPathUtf8, len);
if (ccode != UTF7_STATUS_SUCCESS) {
FreePathArgument(&destinationPath);
MemFree(destinationPathUtf8);
return(SendError(session->client.conn, session->command.tag, "RENAME", STATUS_INVALID_FOLDER_NAME));
}
FreePathArgument(&destinationPath);
ccode = FolderHierarchyCreate(session, destinationPathUtf8);
if (ccode != STATUS_CONTINUE) {
MemFree(destinationPathUtf8);
return(SendError(session->client.conn, session->command.tag, "RENAME", ccode));
}
if (strcmp(sourceFolder->name.utf7, "INBOX") != 0) {
/* this is NOT inbox, just do the normal thing */
if (SelectedFolderWillBeRenamed(sourceFolder, session->folder.selected.info)) {
FolderDeselect(session);
}
if (NMAPSendCommandF(session->store.conn, "RENAME \"%s\" \"/mail/%s\"\r\n", sourceFolder->name.utf8, destinationPathUtf8) == -1) {
MemFree(destinationPathUtf8);
return(SendError(session->client.conn, session->command.tag, "RENAME", STATUS_NMAP_COMM_ERROR));
}
ccode = NMAPReadResponse(session->store.conn, NULL, 0, 0);
if (ccode != 1000) {
MemFree(destinationPathUtf8);
return(SendError(session->client.conn, session->command.tag, "RENAME", CheckForNMAPCommError(ccode)));
}
} else {
/* we are renaming INBOX! rfc defines special behavior */
/* create new folder, */
/* move the contents of INBOX to the new folder, and */
/* leave INBOX with it sub-hierarchy */
if (NMAPSendCommandF(session->store.conn, "CREATE /mail/%s\r\n", destinationPathUtf8) == -1) {
MemFree(destinationPathUtf8);
return(SendError(session->client.conn, session->command.tag, "RENAME", STATUS_NMAP_COMM_ERROR));
}
ccode = NMAPReadResponse(session->store.conn, NULL, 0, 0);
if (ccode != 1000) {
MemFree(destinationPathUtf8);
return(SendError(session->client.conn, session->command.tag, "RENAME", CheckForNMAPCommError(ccode)));
}
ccode = FolderMoveMessages(session, sourceFolder->name.utf8, destinationPathUtf8);
if (ccode != STATUS_CONTINUE) {
MemFree(destinationPathUtf8);
return(SendError(session->client.conn, session->command.tag, "RENAME", ccode));
}
if (sourceFolder == session->folder.selected.info) {
ccode = MessageListLoad(session->store.conn, &session->folder.selected);
if (ccode != STATUS_CONTINUE) {
MemFree(destinationPathUtf8);
return(SendError(session->client.conn, session->command.tag, "RENAME", ccode));
}
}
}
MemFree(destinationPathUtf8);
return(SendOk(session,"RENAME"));
}
int
ImapCommandSubscribe(void *param)
{
long ccode;
ImapSession *session = (ImapSession *)param;
char *ptr;
FolderPath path;
FolderInformation *folder;
if ((ccode = CheckState(session, STATE_AUTH)) != STATUS_CONTINUE) {
return(SendError(session->client.conn, session->command.tag, "SUBSCRIBE", ccode));
}
if ((ccode = EventsSend(session, STORE_EVENT_ALL)) != STATUS_CONTINUE) {
return(SendError(session->client.conn, session->command.tag, "SUBSCRIBE", ccode));
}
ccode = GetPathArgument(session, session->command.buffer + strlen("SUBSCRIBE"), &ptr, &path, FALSE);
if (ccode != STATUS_CONTINUE) {
return(SendError(session->client.conn, session->command.tag, "SUBSCRIBE", ccode));
}
ccode = FolderListLoad(session);
if (ccode != STATUS_CONTINUE) {
return(SendError(session->client.conn, session->command.tag, "SUBSCRIBE", ccode));
}
ccode = FolderGetByName(session, path.name, &folder);
if (ccode != STATUS_CONTINUE) {
FreePathArgument(&path);
return(SendError(session->client.conn, session->command.tag, "SUBSCRIBE", STATUS_NO_SUCH_FOLDER));
}
if (NMAPSendCommandF(session->store.conn, "FLAG \"%s\" -%u\r\n", folder->name.utf8, STORE_COLLECTION_FLAG_NON_SUBSCRIBED) != -1) {
FreePathArgument(&path);
ccode = NMAPReadResponse(session->store.conn, NULL, 0, 0);
if (ccode == 1000) {
return(SendOk(session, "SUBSCRIBE"));
}
return(SendError(session->client.conn, session->command.tag, "SUBSCRIBE", CheckForNMAPCommError(ccode)));
}
return(SendError(session->client.conn, session->command.tag, "SUBSCRIBE", STATUS_NMAP_COMM_ERROR));
}
int
ImapCommandUnsubscribe(void *param)
{
long ccode;
ImapSession *session = (ImapSession *)param;
char *ptr;
FolderPath path;
FolderInformation *folder;
if ((ccode = CheckState(session, STATE_AUTH)) != STATUS_CONTINUE) {
return(SendError(session->client.conn, session->command.tag, "UNSUBSCRIBE", ccode));
}
if ((ccode = EventsSend(session, STORE_EVENT_ALL)) != STATUS_CONTINUE) {
return(SendError(session->client.conn, session->command.tag, "UNSUBSCRIBE", ccode));
}
ccode = GetPathArgument(session, session->command.buffer + strlen("UNSUBSCRIBE"), &ptr, &path, FALSE);
if (ccode != STATUS_CONTINUE) {
return(SendError(session->client.conn, session->command.tag, "UNSUBSCRIBE", ccode));
}
ccode = FolderListLoad(session);
if (ccode != STATUS_CONTINUE) {
return(SendError(session->client.conn, session->command.tag, "UNSUBSCRIBE", ccode));
}
ccode = FolderGetByName(session, path.name, &folder);
if (ccode != STATUS_CONTINUE) {
FreePathArgument(&path);
return(SendError(session->client.conn, session->command.tag, "UNSUBSCRIBE", STATUS_NO_SUCH_FOLDER));
}
if (NMAPSendCommandF(session->store.conn, "FLAG \"%s\" +%u\r\n", folder->name.utf8, STORE_COLLECTION_FLAG_NON_SUBSCRIBED) != -1) {
FreePathArgument(&path);
ccode = NMAPReadResponse(session->store.conn, NULL, 0, 0);
if (ccode == 1000) {
return(SendOk(session, "UNSUBSCRIBE"));
}
return(SendError(session->client.conn, session->command.tag, "UNSUBSCRIBE", CheckForNMAPCommError(ccode)));
}
return(SendError(session->client.conn, session->command.tag, "UNSUBSCRIBE", STATUS_NMAP_COMM_ERROR));
}
__inline static long
SendFolderInfo(Connection *conn, const char *command, FolderInformation *folder)
{
char attributes[128];
char specialUse[96];
size_t length = 0;
size_t specialUseLength;
attributes[0] = '\0';
if (folder->flags & STORE_COLLECTION_FLAG_HIERARCHY_ONLY) {
memcpy(attributes, "\\NoSelect", sizeof("\\NoSelect"));
length = strlen(attributes);
}
specialUseLength = IMAPSpecialUseFormat((uint32_t)folder->flags,
specialUse, sizeof(specialUse));
if (specialUseLength != 0 &&
length + (length ? 1 : 0) + specialUseLength < sizeof(attributes)) {
if (length) attributes[length++] = ' ';
memcpy(attributes + length, specialUse, specialUseLength + 1);
}
if (ConnWriteF(conn, "* %s (%s) \"/\" \"%s\"\r\n", command,
attributes, folder->name.utf7) != -1) {
return(STATUS_CONTINUE);
}
return(STATUS_ABORT);
}
__inline static int
SendMatchingFolders(ImapSession *session, const char *command, char *pattern, const BOOL skipUnsubscribed, const BOOL skipSubFolders)
{
long ccode;
unsigned long i;
unsigned long len;
FolderInformation *folder;
folder = &session->folder.list[0];
if (*pattern == '\0') {
for (i = 0; i < session->folder.count; i++) {
if ((skipUnsubscribed) && (folder->flags & STORE_COLLECTION_FLAG_NON_SUBSCRIBED)) {
folder++;
continue;
}
if (skipSubFolders && (strchr(folder->name.utf7, '/'))) {
folder++;
continue;
}
ccode = SendFolderInfo(session->client.conn, command, folder);
if (ccode == STATUS_CONTINUE) {
folder++;
continue;
}
return(ccode);
}
return(STATUS_CONTINUE);
}
len = strlen(pattern);
for (i = 0; i < session->folder.count; i++) {
if (strncmp(folder->name.utf7, pattern, len)) {
folder++;
continue;
}
if ((skipUnsubscribed) && (folder->flags & STORE_COLLECTION_FLAG_NON_SUBSCRIBED)) {
folder++;
continue;
}
if (skipSubFolders && (strchr(folder->name.utf7 + len, '/'))) {
folder++;
continue;
}
ccode = SendFolderInfo(session->client.conn, command, folder);
if (ccode == STATUS_CONTINUE) {
folder++;
continue;
}
return(ccode);
}
return(STATUS_CONTINUE);
}
__inline static long
SendSingleFolder(ImapSession *session, const char *command, char *pattern)
{
long ccode;
FolderInformation specialFolder;
FolderInformation *folder;
/* Handle RFC special command "" on the name */
if (*pattern == '\0') {
specialFolder.name.utf7 = "";
specialFolder.flags = STORE_COLLECTION_FLAG_HIERARCHY_ONLY;
if (SendFolderInfo(session->client.conn, command, &specialFolder) == STATUS_CONTINUE) {
return(STATUS_CONTINUE);
}
return(STATUS_ABORT);
}
ccode = FolderGetByName(session, pattern, &folder);
if (ccode == STATUS_CONTINUE) {
if (SendFolderInfo(session->client.conn, command, folder) == STATUS_CONTINUE) {
return(STATUS_CONTINUE);
}
return(STATUS_ABORT);
}
return(ccode);
}
__inline static long
GetSearchPattern(ImapSession *session, char *start, char *patternBuffer, unsigned long patternBufferLen, unsigned long *patternLen)
{
long ccode;
char *ptr;
FolderPath context;
FolderPath path;
ccode = GetPathArgument(session, start, &ptr, &context, TRUE);
if (ccode == STATUS_CONTINUE) {
ccode = GetPathArgument(session, ptr, &ptr, &path, TRUE);
if (ccode == STATUS_CONTINUE) {
if ((context.nameLen + path.nameLen + 1) < patternBufferLen) {
if (context.name[0] == '\0') {
memcpy(patternBuffer, path.name, path.nameLen);
(patternBuffer)[path.nameLen] = '\0';
*patternLen = path.nameLen;
} else if (path.name[0] == '\0') {
memcpy(patternBuffer, context.name, context.nameLen);
(patternBuffer)[context.nameLen] = '\0';
*patternLen = context.nameLen;
} else {
ptr = patternBuffer;
memcpy(ptr, context.name, context.nameLen);
ptr += context.nameLen;
*ptr = '/';
ptr++;
memcpy(ptr, path.name, path.nameLen);
ptr += path.nameLen;
*ptr = '\0';
*patternLen = ptr - patternBuffer;
}
FreePathArgument(&context);
FreePathArgument(&path);
return(STATUS_CONTINUE);
}
ccode = STATUS_PATH_TOO_LONG;
FreePathArgument(&path);
}
FreePathArgument(&context);
}
return(ccode);
}
__inline static long
FolderSend(ImapSession *session, const char *command, char *pattern, unsigned long patternLen, BOOL skipUnsubscribed)
{
long ccode;
char *wildcard = pattern + patternLen - 1;
if (*wildcard == '*') {
*wildcard = '\0';
ccode = SendMatchingFolders(session, command, pattern, skipUnsubscribed, FALSE);
} else if (*wildcard == '%') {
*wildcard = '\0';
ccode = SendMatchingFolders(session, command, pattern, skipUnsubscribed, TRUE);
} else {
ccode = SendSingleFolder(session, command, pattern);
}
return(ccode);
}
int
ImapCommandList(void *param)
{
ImapSession *session = (ImapSession *)param;
long ccode;
char pattern[STORE_MAX_COLLECTION * 2];
unsigned long patternLen = 0;
if ((ccode = CheckState(session, STATE_AUTH)) == STATUS_CONTINUE) {
if ((ccode = EventsSend(session, STORE_EVENT_ALL)) == STATUS_CONTINUE) {
if ((ccode = GetSearchPattern(session, session->command.buffer + strlen("LIST"), pattern, sizeof(pattern), &patternLen)) == STATUS_CONTINUE) {
if ((ccode = FolderListLoad(session)) == STATUS_CONTINUE) {
if ((ccode = FolderSend(session, "LIST", pattern, patternLen, FALSE)) == STATUS_CONTINUE) {
return(SendOk(session, "LIST"));
}
}
}
}
}
return(SendError(session->client.conn, session->command.tag, "LIST", ccode));
}
int
ImapCommandLsub(void *param)
{
ImapSession *session = (ImapSession *)param;
long ccode;
char pattern[STORE_MAX_COLLECTION * 2];
unsigned long patternLen = 0;
if ((ccode = CheckState(session, STATE_AUTH)) == STATUS_CONTINUE) {
if ((ccode = EventsSend(session, STORE_EVENT_ALL)) == STATUS_CONTINUE) {
if ((ccode = GetSearchPattern(session, session->command.buffer + strlen("LSUB"), pattern, sizeof(pattern), &patternLen)) == STATUS_CONTINUE) {
if ((ccode = FolderListLoad(session)) == STATUS_CONTINUE) {
if ((ccode = FolderSend(session, "LSUB", pattern, patternLen, TRUE)) == STATUS_CONTINUE) {
return(SendOk(session, "LSUB"));
}
}
}
}
}
return(SendError(session->client.conn, session->command.tag, "LSUB", ccode));
}
typedef struct {
unsigned long totalCount;
unsigned long recentCount;
unsigned long readCount;
unsigned long purgedCount;
unsigned long uidNext;
unsigned long mboxUid;
} CheckResult;
static unsigned long
CheckMessages(OpenedFolder *openFolder)
{
return(openFolder->messageCount);
}
static unsigned long
CheckRecent(OpenedFolder *openFolder)
{
return(openFolder->recentCount);
}
static unsigned long
CheckUidnext(OpenedFolder *openFolder)
{
return(openFolder->info->uidNext);
}
static unsigned long
CheckUidvalidity(OpenedFolder *openFolder)
{
return((unsigned long)(openFolder->info->guid & 0x00000000FFFFFFFF));
}
static unsigned long
CheckUnseen(OpenedFolder *openFolder)
{
MessageInformation *message;
unsigned long count = openFolder->messageCount;
unsigned long unseenCount = 0;
message = &openFolder->message[0];
while (count> 0) {
if (!(message->flags & STORE_MSG_FLAG_SEEN)) {
unseenCount++;
}
message++;
count--;
}
return(unseenCount);
}
typedef unsigned long (* CheckResultFunction)(OpenedFolder *openFolder);
typedef struct {
const char *string;
unsigned long stringLen;
CheckResultFunction function;
} StatusCommandOption;
static StatusCommandOption StatusCommandOptions[] = {
{ "MESSAGES", sizeof("MESSAGES") - 1, CheckMessages },
{ "RECENT", sizeof("RECENT") - 1, CheckRecent },
{ "UIDNEXT", sizeof("UIDNEXT") - 1, CheckUidnext },
{ "UIDVALIDITY", sizeof("UIDVALIDITY") - 1, CheckUidvalidity },
{ "UNSEEN", sizeof("UNSEEN") - 1, CheckUnseen },
{ NULL, 0, 0 }
};
BOOL
CommandStatusInit()
{
BongoKeywordIndexCreateFromTable(Imap.command.status.optionIndex, StatusCommandOptions, .string, TRUE);
if (Imap.command.status.optionIndex) {
return(TRUE);
}
return(FALSE);
}
void
CommandStatusCleanup()
{
BongoKeywordIndexFree(Imap.command.status.optionIndex);
}
#if 0
if (NMAPSendCommandF(session->store.conn, "CHECK %s\r\n", path) != -1) {
ccode = NMAPReadResponse(session->store.conn, Reply, sizeof(Reply), TRUE);
if (ccode == 1000) {
CheckResult checkResults;
unsigned char *ptr,*DoSpace="";
ptr=items;
if (*ptr=='(')
ptr++;
// Total, Recent, Purged, Read, IDs
sscanf(Reply, "%lu %*u %lu %*u %lu %*u %lu %*u %lu %lu",
&checkResults.totalCount,
&checkResults.recentCount,
&checkResults.purgedCount,
&checkResults.readCount,
&checkResults.uidNext,
&checkResults.mboxUid);
AddMboxSpaces(path);
if (ConnWriteF(session->client.conn, "* STATUS \"%s\" (", path) != -1) {
long option;
while (*ptr) {
option = BongoKeywordBegins(Imap.command.status.optionIndex, ptr);
if (option != -1) {
if (ConnWriteF(session->client.conn, "%s%s %lu", DoSpace, StatusCommandOptions[option].string, StatusCommandOptions[option].function(&checkResults)) != -1) {
ptr += StatusCommandOptions[option].stringLen;
if (*ptr == ' ') {
ptr++;
DoSpace = " ";
continue;
}
break;
}
MemFree(items);
MemFree(path);
return(STATUS_ABORT);
}
MemFree(items);
MemFree(path);
if (ConnWrite(session->client.conn, "\r\n", sizeof("\r\n") - 1) != -1) {
return(SendError(session->client.conn, session->command.tag, "STATUS", STATUS_INVALID_ARGUMENT));
}
return(STATUS_ABORT);
}
MemFree(items);
MemFree(path);
if (ConnWrite(session->client.conn, ")\r\n", 3) != -1) {
return(SendOk(session, "STATUS"));
}
return(STATUS_ABORT);
}
MemFree(items);
MemFree(path);
return(STATUS_ABORT);
}
MemFree(items);
MemFree(path);
return(SendError(session->client.conn, session->command.tag, "STATUS", CheckForNMAPCommError(ccode)));
}
MemFree(path);
MemFree(items);
return(SendError(session->client.conn, session->command.tag, "STATUS", STATUS_NMAP_COMM_ERROR));
#endif
__inline static long
SendStatusResponse(Connection *conn, OpenedFolder *openFolder, char *requestString)
{
char *DoSpace = "";
long option;
if (ConnWriteF(conn, "* STATUS \"%s\" (", openFolder->info->name.utf7) != -1) {
if (*requestString) {
for (;;) {
if ((option = BongoKeywordBegins(Imap.command.status.optionIndex, requestString)) != -1) {
if ((ConnWriteF(conn, "%s%s %lu", DoSpace, StatusCommandOptions[option].string, StatusCommandOptions[option].function(openFolder))) != -1) {
requestString += StatusCommandOptions[option].stringLen;
if (*requestString == ' ') {
requestString++;
DoSpace = " ";
continue;
}
break;
}
return(STATUS_ABORT);
}
if (ConnWrite(conn, "\r\n", sizeof("\r\n") - 1) != -1) {
return(STATUS_INVALID_ARGUMENT);
}
return(STATUS_ABORT);
}
}
if (ConnWrite(conn, ")\r\n", 3) != -1) {
return(STATUS_CONTINUE);
}
}
return(STATUS_ABORT);
}
int
ImapCommandStatus(void *param)
{
ImapSession *session = (ImapSession *)param;
long ccode;
char *ptr;
char *ptr2;
char *items;
OpenedFolder openFolder;
FolderInformation *folder;
FolderPath folderPath;
if ((ccode = CheckState(session, STATE_AUTH)) == STATUS_CONTINUE) {
if ((ccode = EventsSend(session, STORE_EVENT_ALL)) == STATUS_CONTINUE) {
if ((ccode = GetPathArgument(session, session->command.buffer + strlen("STATUS"), &ptr, &folderPath, FALSE)) == STATUS_CONTINUE) {
ptr2 = ptr;
if ((ccode = GrabArgument(session, &ptr2, &items)) == STATUS_CONTINUE) {
if ((ccode = FolderListLoad(session)) == STATUS_CONTINUE) {
if ((ccode = FolderGetByName(session, folderPath.name, &folder)) == STATUS_CONTINUE) {
memset(&openFolder, 0, sizeof(OpenedFolder));
if ((ccode = FolderOpen(session->store.conn, &openFolder, folder, TRUE)) == STATUS_CONTINUE) {
ccode = SendStatusResponse(session->client.conn, &openFolder, items);
}
}
}
MemFree(items);
}
FreePathArgument(&folderPath);
}
}
}
if (ccode == STATUS_CONTINUE) {
return(SendOk(session, "STATUS"));
}
return(SendError(session->client.conn, session->command.tag, "STATUS", ccode));
}
__inline static long
GetMonthSequenceNum(char *monthString, unsigned long *monthNum)
{
unsigned long i;
for (i = 0; i < 12; i++) {
if (XplStrCaseCmp(monthString, Imap.command.months[i]) == 0) {
*monthNum = i + 1;
return(STATUS_CONTINUE);
}
}
return(STATUS_INVALID_ARGUMENT);
}
/* parses dd-mmm-yyyy (example 23-Jul-2006) */
__inline static long
ParseDate(char *dateString, unsigned long *day, unsigned long *month, unsigned long *year)
{
*(dateString + 2) = '\0';
*(dateString + 6) = '\0';
*(dateString + 11) = '\0';
*day = atol(dateString);
*year = atol(dateString + 7);
return(GetMonthSequenceNum(dateString + 3, month));
}
/* parses hh:mm:ss (example 14:02:23) */
__inline static void
ParseTime(char *timeString, unsigned long *hours, unsigned long *minutes, unsigned long *seconds)
{
*(timeString + 2) = '\0';
*(timeString + 5) = '\0';
*(timeString + 8) = '\0';
*hours = atol(timeString);
*minutes = atol(timeString + 3);
*seconds = atol(timeString + 6);
}
/* parses ("+" / "-")hhmm (example -0730) */
__inline static void
ParseOffset(char *offsetString, long *offset)
{
long hours;
long minutes;
*(offsetString + 5) = '\0';
minutes = atol(offsetString + 3);
/* the next line destroys a significant character, */
/* but we already captured its meaning above */
*(offsetString + 3) = '\0';
hours = atol(offsetString);
/* Make the sign of minutes match the sign of hours */
if (hours < 0 ) {
minutes = 0 - minutes;
}
*offset = ((hours * 3600) + (minutes * 60));
}
__inline static long
ParseDateTime(char *dateString, long dateStringLen, time_t *date)
{
unsigned long day;
unsigned long month;
unsigned long year;
unsigned long hour;
unsigned long min;
unsigned long sec;
long offset;
if (dateStringLen == 26) { /* proper date-time strings are 26 characters long */
if (ParseDate(dateString, &day, &month, &year) == STATUS_CONTINUE) {
ParseTime(dateString + 12, &hour, &min, &sec);
ParseOffset(dateString + 21, &offset);
*date = MsgGetUTC(day, month, year, hour, min, sec);
(*date) -= offset;
return(STATUS_CONTINUE);
}
}
*date = 0;
return(STATUS_INVALID_ARGUMENT);
}
__inline static long
ParseAppendArgurment(ImapSession *session, FolderPath *path, unsigned long *messageSize, unsigned long *flags, time_t *date)
{
long ccode;
char *ptr;
char *sizeArg;
char *flagArg;
char *dateArg;
long tmpSize;
if ((ccode = GetPathArgument(session, session->command.buffer + strlen("APPEND"), &ptr, path, FALSE)) == STATUS_CONTINUE) {
ccode = STATUS_INVALID_ARGUMENT;
if ((sizeArg = strchr(ptr, '{')) != NULL) {
*sizeArg = '\0';
sizeArg++;
tmpSize = atol(sizeArg);
if (tmpSize > 0) {
*messageSize = (unsigned long)tmpSize;
*flags = 0;
*date = 0;
for (;;) {
ptr = FindNextArgument(ptr);
if (!ptr) {
return(STATUS_CONTINUE);
}
if (*ptr == '(') {
ptr++;
flagArg = ptr;
ptr = strchr(ptr, ')');
if (ptr) {
*ptr = '\0';
ccode = ParseStoreFlags(flagArg, flags);
if (ccode == STATUS_CONTINUE) {
ptr++;
continue;
}
}
}
if (*ptr == '"') {
ptr++;
dateArg = ptr;
ptr = strchr(ptr, '"');
if (ptr) {
*ptr = '\0';
ccode = ParseDateTime(dateArg, ptr - dateArg, date);
if (ccode == STATUS_CONTINUE) {
ptr++;
continue;
}
}
}
if (ccode == STATUS_CONTINUE) {
ccode = STATUS_INVALID_ARGUMENT;
}
break;
}
}
}
}
return(ccode);
}
__inline static long
WriteMessageInMailbox(Connection *clientConn, Connection *storeConn,
uint64_t folderGuid, unsigned long size,
uint64_t *messageGuid, uint32_t *messageUid,
time_t creationTime)
{
long ccode;
long bytesRead;
char buffer[100];
ccode = STATUS_NMAP_COMM_ERROR;
if (NMAPSendCommandF(storeConn, "WRITE %" PRIx64 " %u %lu T%lu\r\n", folderGuid, STORE_DOCTYPE_MAIL, size, (unsigned long)creationTime) != -1) {
if ((ccode = CheckForNMAPCommError(NMAPReadResponse(storeConn, NULL, 0, 0))) == 2002) {
ccode = STATUS_ABORT;
if (ConnWrite(clientConn, "+ Ready for more data\r\n", sizeof("+ Ready for more data\r\n") - 1) != -1) {
if (ConnFlush(clientConn) != -1) {
if ((bytesRead = ConnReadToConn(clientConn, storeConn, size)) > 0) {
if ((unsigned long)bytesRead == size) {
if ((ccode = CheckForNMAPCommError(NMAPReadResponse(storeConn, buffer, sizeof(buffer), TRUE))) == 1000) {
if (!IMAPUidplusParseCreated(buffer, messageGuid,
messageUid))
return STATUS_NMAP_PROTOCOL_ERROR;
ccode = STATUS_CONTINUE;
}
}
}
}
}
}
}
return(ccode);
}
__inline static long
AppendFlags(Connection *storeConn, uint64_t guid, unsigned long flags, unsigned long *resultFlags)
{
long ccode;
if (flags) {
if ((ccode = StoreMessageFlag(storeConn, guid, "+", flags, resultFlags)) == STATUS_CONTINUE) {
return(STATUS_CONTINUE);
}
return(ccode);
}
*resultFlags = 0;
return(STATUS_CONTINUE);
}
int
ImapCommandAppend(void *param)
{
ImapSession *session = (ImapSession *)param;
long ccode;
FolderInformation *folder;
FolderPath folderPath;
unsigned long messageSize;
unsigned long flags;
time_t date;
uint64_t messageGuid = 0;
uint32_t messageUid = 0;
if ((ccode = CheckState(session, STATE_AUTH)) == STATUS_CONTINUE) {
if ((ccode = EventsSend(session, STORE_EVENT_ALL)) == STATUS_CONTINUE) {
if ((ccode = ParseAppendArgurment(session, &folderPath, &messageSize, &flags, &date)) == STATUS_CONTINUE) {
if ((ccode = FolderListLoad(session)) == STATUS_CONTINUE) {
if ((ccode = FolderGetByName(session, folderPath.name, &folder)) == STATUS_CONTINUE) {
if ((ccode = WriteMessageInMailbox(session->client.conn,
session->store.conn, folder->guid, messageSize,
&messageGuid, &messageUid, date)) == STATUS_CONTINUE) {
if ((ccode = ReadCommandLine(session->client.conn, &(session->command.buffer), &(session->command.bufferLen))) == STATUS_CONTINUE) {
if ((ccode = AppendFlags(session->store.conn, messageGuid, flags, &flags)) == STATUS_CONTINUE) {
if ((session->client.state == STATE_SELECTED) && (session->folder.selected.info == folder)) {
if ((ccode = MessageListLoad(session->store.conn, &(session->folder.selected))) == STATUS_CONTINUE) {
ccode = SendExistsAndRecent(session->client.conn, session->folder.selected.messageCount, session->folder.selected.recentCount);
}
}
}
}
}
}
}
FreePathArgument(&folderPath);
}
}
}
if (ccode == STATUS_CONTINUE) {
if (ConnWriteF(session->client.conn,
"%s OK [APPENDUID %lu %u] APPEND completed\r\n",
session->command.tag,
(unsigned long)(folder->guid & UINT64_C(0xffffffff)),
(unsigned int)messageUid) != -1)
return STATUS_CONTINUE;
return STATUS_ABORT;
}
return(SendError(session->client.conn, session->command.tag, "APPEND", ccode));
}
/********** IMAP session commands - selected state **********/
int
ImapCommandCheck(void *param)
{
long ccode;
ImapSession *session = (ImapSession *)param;
if ((ccode = CheckState(session, STATE_SELECTED)) == STATUS_CONTINUE) {
if ((ccode = EventsSend(session, STORE_EVENT_ALL)) == STATUS_CONTINUE) {
return(SendOk(session, "CHECK"));
}
}
return(SendError(session->client.conn, session->command.tag, "CHECK", ccode));
}
__inline static long
PurgeDeletedMessages(ImapSession *session, BOOL client_response,
MessageInformation *message, unsigned long messageCount,
const BOOL *selection)
{
long ccode = 0;
long count;
Connection *store = session->store.conn;
Connection *client = session->client.conn;
count = messageCount;
while (count > 0) {
if ((selection && !selection[count - 1]) ||
!(message->flags & STORE_MSG_FLAG_DELETED) ||
(message->flags & STORE_MSG_FLAG_PURGED)) {
count--;
if (count > 0) message--;
continue;
}
if (NMAPSendCommandF(store, "PURGE %" PRIx64 "\r\n", message->guid) != -1) {
ccode = NMAPReadResponse(store, NULL, 0, 0);
if (ccode == 1000) {
if (client_response) {
if (ConnWriteF(client, "* %lu EXPUNGE\r\n", count) != -1) {
count--;
if (count > 0) message--;
continue;
}
}
count--;
if (count > 0) message--;
continue;
}
}
return(CheckForNMAPCommError(ccode));
}
return(STATUS_CONTINUE);
}
int
ImapCommandClose(void *param)
{
ImapSession *session = (ImapSession *)param;
long ccode;
StartBusy(session, "* OK - Purging deleted messages");
if ((ccode = CheckState(session, STATE_SELECTED)) == STATUS_CONTINUE) {
if (session->folder.selected.readOnly) {
FolderDeselect(session);
StopBusy(session);
return(SendOk(session, "CLOSE"));
}
ccode = STATUS_CONTINUE;
if (session->folder.selected.messageCount)
ccode = PurgeDeletedMessages(session, FALSE,
&session->folder.selected.message[
session->folder.selected.messageCount - 1],
session->folder.selected.messageCount, NULL);
FolderDeselect(session);
if (ccode == STATUS_CONTINUE) {
StopBusy(session);
return(SendOk(session, "CLOSE"));
}
}
StopBusy(session);
return(SendError(session->client.conn, session->command.tag, "CLOSE", ccode));
}
int
ImapCommandExpunge(void *param)
{
ImapSession *session = (ImapSession *)param;
OpenedFolder *selected = &session->folder.selected;
long ccode;
StartBusy(session, NULL);
if ((ccode = CheckState(session, STATE_SELECTED)) == STATUS_CONTINUE) {
if ((ccode = EventsSend(session, STORE_EVENT_ALL)) == STATUS_CONTINUE) {
ccode = STATUS_READ_ONLY_FOLDER;
if (!(selected->readOnly)) {
ccode = STATUS_CONTINUE;
if (selected->messageCount)
ccode = PurgeDeletedMessages(session, TRUE,
&selected->message[selected->messageCount - 1],
selected->messageCount, NULL);
if (ccode == STATUS_CONTINUE) {
if ((ccode = MessageListLoad(session->store.conn, selected)) == STATUS_CONTINUE) {
StopBusy(session);
return(SendOk(session, "EXPUNGE"));
}
}
}
}
}
StopBusy(session);
return(SendError(session->client.conn, session->command.tag, "EXPUNGE", ccode));
}
static long
BuildUidSelection(ImapSession *session, char *messageSet, BOOL **selection)
{
char *nextRange = messageSet;
unsigned long rangeStart;
unsigned long rangeEnd;
unsigned long index;
long ccode;
*selection = NULL;
if (!session->folder.selected.messageCount) return STATUS_CONTINUE;
*selection = MemCalloc(session->folder.selected.messageCount,
sizeof(**selection));
if (!*selection) return STATUS_MEMORY_ERROR;
do {
ccode = GetMessageRange(session->folder.selected.message,
session->folder.selected.messageCount,
&nextRange, &rangeStart, &rangeEnd, TRUE);
if (ccode == STATUS_CONTINUE) {
for (index = rangeStart; index <= rangeEnd; index++)
(*selection)[index] = TRUE;
continue;
}
if (ccode == STATUS_UID_NOT_FOUND) continue;
MemFree(*selection);
*selection = NULL;
return ccode;
} while (nextRange);
return STATUS_CONTINUE;
}
int
ImapCommandUidExpunge(void *param)
{
ImapSession *session = (ImapSession *)param;
OpenedFolder *selected = &session->folder.selected;
char *messageSet = NULL;
char *ptr;
BOOL *selection = NULL;
long ccode;
StartBusy(session, NULL);
if ((ccode = CheckState(session, STATE_SELECTED)) == STATUS_CONTINUE &&
(ccode = EventsSend(session, STORE_EVENT_ALL)) == STATUS_CONTINUE) {
ccode = STATUS_READ_ONLY_FOLDER;
if (!selected->readOnly) {
ptr = session->command.buffer + strlen(IMAP_COMMAND_UID_EXPUNGE);
if ((ccode = GrabArgument(session, &ptr, &messageSet)) ==
STATUS_CONTINUE &&
(ccode = BuildUidSelection(session, messageSet, &selection)) ==
STATUS_CONTINUE) {
if (!selected->messageCount ||
(ccode = PurgeDeletedMessages(session, TRUE,
&selected->message[selected->messageCount - 1],
selected->messageCount, selection)) == STATUS_CONTINUE)
ccode = MessageListLoad(session->store.conn, selected);
}
}
}
MemFree(selection);
MemFree(messageSet);
StopBusy(session);
if (ccode == STATUS_CONTINUE) return SendOk(session, "UID EXPUNGE");
return SendError(session->client.conn, session->command.tag,
"UID EXPUNGE", ccode);
}
/********** IMAP ACL commands rfc2086 **********/
int
ImapCommandSetAcl(void *param)
{
#if defined(HAVE_SHARED_FOLDERS)
ImapSession *session = (ImapSession *)param;
long ccode;
unsigned char *ptr;
unsigned char Answer[1024];
unsigned long permissions;
unsigned long newPermissions;
unsigned long action;
unsigned char *ptr2;
unsigned char *mbox;
unsigned char *mailbox;
unsigned char *identifier;
BOOL ready;
if (Imap.command.capability.acl.enabled) {
if (session->client.state == STATE_SELECTED) {
if (((ccode = PurgeMessageNotify(session)) == STATUS_CONTINUE) && ((ccode = NewMessageNotify(session)) == STATUS_CONTINUE)) {
;
} else {
return(SendError(session->client.conn, session->command.tag, "SETACL", ccode));
}
} else if (session->client.state < STATE_AUTH) {
return(SendError(session->client.conn, session->command.tag, "SETACL", STATUS_INVALID_STATE));
}
ptr = session->command.buffer + 6;
if (IsWhiteSpace(*ptr)) {
do { ptr++; } while (IsWhiteSpace(*ptr));
} else {
return(SendError(session->client.conn, session->command.tag, "SETACL", STATUS_INVALID_ARGUMENT));
}
/* Grab the mailbox name. */
ccode = GrabArgument(session, &ptr, &mbox);
if (ccode == STATUS_CONTINUE) {
/* Grab the authentication identifier */
ccode = GrabArgument(session, &ptr, &identifier);
if (ccode == STATUS_CONTINUE) {
/* Advance as needed. */
if (IsWhiteSpace(*ptr)) {
do { ptr++; } while (IsWhiteSpace(*ptr));
}
switch (*ptr) {
case '\0': { action = 0; break; }
case '-': { action = -1; ptr++; break; }
case '+': { action = 1; ptr++; break; }
default: { action = 0; break; }
}
/* Advance as needed. */
if (IsWhiteSpace(*ptr)) {
do { ptr++; } while (IsWhiteSpace(*ptr));
}
ready = TRUE;
newPermissions = 0;
while ((ready == TRUE) && (*ptr != '\0')) {
switch(toupper(*ptr)) {
case 'A': { /* Administer (perform SETACL) */
newPermissions |= NMAP_SHARE_ADMINISTER; break; }
case 'C': { /* Create (CREATE new sub-mailboxes in any implementation-defined hierarchy. */
newPermissions |= NMAP_SHARE_CREATE; break; }
case 'D': { /* Delete (STORE DELETED flag, perform EXPUNGE). */
newPermissions |= NMAP_SHARE_DELETE; break; }
case 'I': { /* Insert (perform APPEND, COPY into mailbox). */
newPermissions |= NMAP_SHARE_INSERT; break; }
case 'L': { /* Lookup (mailbox is visible to LIST/LSUB commands). */
newPermissions |= NMAP_SHARE_VISIBLE; break; }
case 'P': { /* Post (send mail to submission address for mailbox, not enforced by IMAP4 itself. */
newPermissions |= NMAP_SHARE_POST; break; }
case 'R': { /* Read (SELECT the mailbox, perform CHECK, FETCH, PARTIAL, SEARCH, COPY from mailbox. */
newPermissions |= NMAP_SHARE_READ; break; }
case 'S': { /* Keep seen/unseen information across sessions (STORE SEEN flag) */
newPermissions |= NMAP_SHARE_SEEN; break; }
case 'W': { /* Write (STORE flags other than SEEN and DELETED). */
newPermissions |= NMAP_SHARE_WRITE; break; }
default: {
MemFree(mbox);
MemFree(identifier);
ready = FALSE;
break;
}
}
do { ptr++; } while(IsWhiteSpace(*ptr));
}
if (ready == TRUE) {
mailbox = mbox;
if ((*mailbox == '/') || (*mailbox == '\\')) {
mailbox++;
}
CheckPathLen(mailbox);
} else {
/* Invalid permissions! */
return(SendError(session->client.conn, session->command.tag, "SETACL", STATUS_PERMISSION_DENIED));
}
if (action != 0) {
permissions = 0;
/* Load the NMAP permissions for this resource. */
if (NMAPSendCommand(session->store.conn, "SHARE MBOX\r\n", 12) == 12) {
ccode = NMAPReadResponse(session->store.conn, NULL, 0, 0);
if (ccode == 2002) {
ready = FALSE;
while ((ccode = NMAPReadResponse(session->store.conn, Answer, sizeof(Answer), TRUE)) == 2002) {
if (ready == FALSE) {
ptr = strchr(Answer, ' ');
if (ptr != NULL) {
*(ptr++) = '\0';
if (XplStrCaseCmp(mailbox, Answer) != 0) {
continue;
}
} else {
continue;
}
ptr2 = strchr(ptr, ' ');
if (ptr2 != NULL) {
*(ptr2++) = '\0';
if (XplStrCaseCmp(identifier, ptr) != 0) {
continue;
}
} else {
continue;
}
permissions = atol(ptr2);
ready = TRUE;
/* Keep looping to absorb the remaining NMAP Server responses. */
}
}
if (ccode == 1000) {
;
} else {
MemFree(mbox);
MemFree(identifier);
return(SendError(session->client.conn, session->command.tag, "SETACL", CheckForNMAPCommError(ccode)));
}
} else {
MemFree(mbox);
MemFree(identifier);
return(SendError(session->client.conn, session->command.tag, "SETACL", CheckForNMAPCommError(ccode)));
}
if (action == 1) {
ccode = NMAPSendCommandF(session->store.conn, "SHARE MBOX %s %s %lu%s\r\n", mailbox, identifier, permissions | newPermissions, (ready == TRUE)? " NOMESSAGE": "");
} else {
ccode = NMAPSendCommandF(session->store.conn, "SHARE MBOX %s %s %lu%s\r\n", mailbox, identifier, permissions & ~newPermissions, (ready == TRUE)? " NOMESSAGE": "");
}
if (ccode != -1) {
;
} else {
MemFree(mbox);
MemFree(identifier);
return(SendError(session->client.conn, session->command.tag, "SETACL", STATUS_NMAP_COMM_ERROR));
}
} else {
MemFree(mbox);
MemFree(identifier);
return(SendError(session->client.conn, session->command.tag, "SETACL", STATUS_NMAP_COMM_ERROR));
}
} else {
/* Override existing permissions. */
if (NMAPSendCommandF(session->store.conn, "SHARE MBOX %s %s %lu NOMESSAGE\r\n", mailbox, identifier, newPermissions) != -1) {
;
} else {
MemFree(mbox);
MemFree(identifier);
return(SendError(session->client.conn, session->command.tag, "SETACL", STATUS_NMAP_COMM_ERROR));
}
}
MemFree(mbox);
MemFree(identifier);
ccode = NMAPReadResponse(session->store.conn, NULL, 0, 0);
if (ccode == 1000) {
return(SendOk(session, "SETACL"));
}
return(SendError(session->client.conn, session->command.tag, "SETACL", CheckForNMAPCommError(ccode)));
}
}
return(SendError(session->client.conn, session->command.tag, "SETACL", ccode));
}
return(SendError(session->client.conn, session->command.tag, "SETACL", STATUS_FEATURE_DISABLED));
#else
return(SendError(((ImapSession *)param)->client.conn, ((ImapSession *)param)->command.tag, "SETACL", STATUS_UNKNOWN_COMMAND));
#endif
}
int
ImapCommandDeleteAcl(void *param)
{
#if defined(HAVE_SHARED_FOLDERS)
ImapSession *session = (ImapSession *)param;
long ccode;
unsigned char *ptr;
unsigned char *mbox;
unsigned char *mailbox;
unsigned char *identifier;
if (Imap.command.capability.acl.enabled) {
if (session->client.state == STATE_SELECTED) {
if (((ccode = PurgeMessageNotify(session)) == STATUS_CONTINUE) && ((ccode = NewMessageNotify(session)) == STATUS_CONTINUE)) {
;
} else {
return(SendError(session->client.conn, session->command.tag, "DELETEACL", ccode));
}
} else if (session->client.state < STATE_AUTH) {
return(SendError(session->client.conn, session->command.tag, "DELETEACL", STATUS_INVALID_STATE));
}
ptr = session->command.buffer + 9;
if (IsWhiteSpace(*ptr)) {
do { ptr++; } while (IsWhiteSpace(*ptr));
} else {
return(SendError(session->client.conn, session->command.tag, "DELETEACL", STATUS_INVALID_ARGUMENT));
}
/* Grab the mailbox name. */
ccode = GrabArgument(session, &ptr, &mbox);
if (ccode == STATUS_CONTINUE) {
/* Grab the authentication identifier */
ccode = GrabArgument(session, &ptr, &identifier);
if (ccode == STATUS_CONTINUE) {
mailbox = mbox;
if ((*mailbox == '/') || (*mailbox == '\\')) {
mailbox++;
}
CheckPathLen(mailbox);
if (NMAPSendCommandF(session->store.conn, "SHARE MBOX %s %s 0 NOMESSAGE\r\n", mailbox, identifier) != -1) {
MemFree(mbox);
MemFree(identifier);
ccode = NMAPReadResponse(session->store.conn, NULL, 0, 0);
if (ccode == 1000) {
return(SendOk(session, "DELETEACL"));
}
return(SendError(session->client.conn, session->command.tag, "DELETEACL", CheckForNMAPCommError(ccode)));
}
MemFree(mbox);
MemFree(identifier);
return(SendError(session->client.conn, session->command.tag, "DELETEACL", STATUS_NMAP_COMM_ERROR));
}
}
return(SendError(session->client.conn, session->command.tag, "DELETEACL", ccode));
}
return(SendError(session->client.conn, session->command.tag, "DELETEACL", STATUS_FEATURE_DISABLED));
#else
return(SendError(((ImapSession *)param)->client.conn, ((ImapSession *)param)->command.tag, "SETACL", STATUS_UNKNOWN_COMMAND));
#endif
}
int
ImapCommandGetAcl(void *param)
{
#if defined(HAVE_SHARED_FOLDERS)
ImapSession *session = (ImapSession *)param;
long ccode;
unsigned char *ptr;
unsigned char Answer[1024];
unsigned char Reply[1024];
unsigned long permissions;
unsigned char *ptr2;
unsigned char *mbox;
unsigned char *mailbox;
if (Imap.command.capability.acl.enabled) {
if (session->client.state == STATE_SELECTED) {
if (((ccode = PurgeMessageNotify(session)) == STATUS_CONTINUE) && ((ccode = NewMessageNotify(session)) == STATUS_CONTINUE)) {
;
} else {
return(SendError(session->client.conn, session->command.tag, "GETACL", ccode));
}
} else if (session->client.state < STATE_AUTH) {
return(SendError(session->client.conn, session->command.tag, "GETACL", STATUS_INVALID_STATE));
}
ptr = session->command.buffer + 6;
if (IsWhiteSpace(*ptr)) {
do { ptr++; } while (IsWhiteSpace(*ptr));
} else {
return(SendError(session->client.conn, session->command.tag, "GETACL", STATUS_INVALID_ARGUMENT));
}
/* Grab the mailbox name. */
ccode = GrabArgument(session, &ptr, &mbox);
if (ccode == STATUS_CONTINUE) {
mailbox = mbox;
if ((*mailbox == '/') || (*mailbox == '\\')) {
mailbox++;
}
ptr2 = Reply + snprintf(Reply, sizeof(Reply), "* ACL \"%s\"", mailbox);
CheckPathLen(mailbox);
if (NMAPSendCommandF(session->store.conn, "RIGHTS %s\r\n", mailbox) != -1) {
MemFree(mbox);
ccode = NMAPReadResponse(session->store.conn, NULL, 0, 0);
if (ccode == 2002) {
while ((ccode = NMAPReadResponse(session->store.conn, Answer, sizeof(Answer), TRUE)) == 2002) {
ptr = strchr(Answer, ' ');
if (ptr != NULL) {
*(ptr++) = '\0';
permissions = atol(ptr);
/* Add the grantee name to the response. */
ptr2 += snprintf(ptr2, sizeof(Reply) - (ptr2 - Reply), " \"%s\" ", Answer);
if (permissions) {
if (permissions & NMAP_SHARE_VISIBLE) {
*(ptr2++) = 'l';
}
if (permissions & NMAP_SHARE_READ) {
*(ptr2++) = 'r';
}
if (permissions & NMAP_SHARE_SEEN) {
*(ptr2++) = 's';
}
if (permissions & NMAP_SHARE_WRITE) {
*(ptr2++) = 'w';
}
if (permissions & NMAP_SHARE_INSERT) {
*(ptr2++) = 'i';
}
if (permissions & NMAP_SHARE_POST) {
*(ptr2++) = 'p';
}
if (permissions & NMAP_SHARE_CREATE) {
*(ptr2++) = 'c';
}
if (permissions & NMAP_SHARE_DELETE) {
*(ptr2++) = 'd';
}
if (permissions & NMAP_SHARE_ADMINISTER) {
*(ptr2++) = 'a';
}
continue;
} else {
*(ptr2++) = '"';
*(ptr2++) = '"';
}
}
continue;
}
if (ccode == 1000) {
*(ptr2++) = '\r';
*(ptr2++) = '\n';
*(ptr2) = '\0';
if (ConnWrite(session->client.conn, Reply, ptr2 - Reply) != -1) {
return(SendOk(session, "GETACL"));
}
return(STATUS_ABORT);
}
}
return(SendError(session->client.conn, session->command.tag, "GETACL", CheckForNMAPCommError(ccode)));
}
MemFree(mbox);
return(SendError(session->client.conn, session->command.tag, "GETACL", STATUS_NMAP_COMM_ERROR));
}
return(SendError(session->client.conn, session->command.tag, "GETACL", ccode));
}
return(SendError(session->client.conn, session->command.tag, "GETACL", STATUS_FEATURE_DISABLED));
#else
return(SendError(((ImapSession *)param)->client.conn, ((ImapSession *)param)->command.tag, "SETACL", STATUS_UNKNOWN_COMMAND));
#endif
}
int
ImapCommandListRights(void *param)
{
#if defined(HAVE_SHARED_FOLDERS)
ImapSession *session = (ImapSession *)param;
long ccode;
unsigned char *mbox;
unsigned char *identifier;
unsigned char *ptr;
if (Imap.command.capability.acl.enabled) {
if (session->client.state == STATE_SELECTED) {
if (((ccode = PurgeMessageNotify(session)) == STATUS_CONTINUE) && ((ccode = NewMessageNotify(session)) == STATUS_CONTINUE)) {
;
} else {
return(SendError(session->client.conn, session->command.tag, "LISTRIGHTS", ccode));
}
}
if (session->client.state >= STATE_AUTH) {
ptr = session->command.buffer + 10;
if (IsWhiteSpace(*ptr)) {
do { ptr++; } while (IsWhiteSpace(*ptr));
} else {
return(SendError(session->client.conn, session->command.tag, "LISTRIGHTS", STATUS_INVALID_ARGUMENT));
}
} else {
return(SendError(session->client.conn, session->command.tag, "LISTRIGHTS", STATUS_INVALID_STATE));
}
/* Grab the mailbox name. */
ccode = GrabArgument(session, &ptr, &mbox);
if (ccode == STATUS_CONTINUE) {
identifier = NULL;
/* Grab the authentication identifier */
ccode = GrabArgument(session, &ptr, &identifier);
if (ccode == STATUS_CONTINUE) {
MemFree(mbox);
MemFree(identifier);
if (ConnWriteF(session->client.conn, "* LISTRIGHTS \"%s\" %s \"\" l r s w i p c d a\r\n", mbox, identifier) != -1) {
return(SendOk(session, "LISTRIGHTS"));
}
return(STATUS_ABORT);
}
MemFree(mbox);
}
return(SendError(session->client.conn, session->command.tag, "LISTRIGHTS", ccode));
}
return(SendError(session->client.conn, session->command.tag, "LISTRIGHTS", STATUS_FEATURE_DISABLED));
#else
return(SendError(((ImapSession *)param)->client.conn, ((ImapSession *)param)->command.tag, "SETACL", STATUS_UNKNOWN_COMMAND));
#endif
}
int
ImapCommandMyRights(void *param)
{
#if defined(HAVE_SHARED_FOLDERS)
ImapSession *session = (ImapSession *)param;
long ccode;
unsigned char *ptr;
unsigned char Answer[1024];
unsigned char Reply[1024];
unsigned long permissions;
unsigned char *ptr2;
unsigned char *mbox;
unsigned char *mailbox;
BOOL found;
if (Imap.command.capability.acl.enabled) {
if (session->client.state == STATE_SELECTED) {
if (((ccode = PurgeMessageNotify(session)) == STATUS_CONTINUE) && ((ccode = NewMessageNotify(session)) == STATUS_CONTINUE)) {
;
} else {
return(SendError(session->client.conn, session->command.tag, "MYRIGHTS", ccode));
}
} else if (session->client.state < STATE_AUTH) {
return(SendError(session->client.conn, session->command.tag, "MYRIGHTS", STATUS_INVALID_STATE));
}
ptr = session->command.buffer + 8;
if (IsWhiteSpace(*ptr)) {
do { ptr++; } while (IsWhiteSpace(*ptr));
} else {
return(SendError(session->client.conn, session->command.tag, "MYRIGHTS", STATUS_INVALID_ARGUMENT));
}
/* Grab the mailbox name. */
ccode = GrabArgument(session, &ptr, &mbox);
if (ccode == STATUS_CONTINUE) {
mailbox = mbox;
if ((*mailbox == '/') || (*mailbox == '\\')) {
mailbox++;
}
ptr2 = Reply + snprintf(Reply, sizeof(Answer), "* MYRIGHTS \"%s\" \"\"", mailbox);
CheckPathLen(mailbox);
if (NMAPSendCommandF(session->store.conn, "RIGHTS %s\r\n", mailbox) != -1) {
MemFree(mbox);
ccode = NMAPReadResponse(session->store.conn, NULL, 0, 0);
if (ccode == 2002) {
found = FALSE;
while ((ccode = NMAPReadResponse(session->store.conn, Answer, sizeof(Answer), TRUE)) == 2002) {
ptr = strchr(Answer, ' ');
if (ptr != NULL) {
*(ptr++) = '\0';
/* We are only interested in this user's permissions.
Keep looping to absorb all NMAP responses. */
if (XplStrCaseCmp(session->user.name, Answer) == 0) {
permissions = atol(ptr);
if (permissions) {
ptr2 -= 2; /* Back over the default empty permissions. */
if (permissions & NMAP_SHARE_VISIBLE) {
*(ptr2++) = 'l';
}
if (permissions & NMAP_SHARE_READ) {
*(ptr2++) = 'r';
}
if (permissions & NMAP_SHARE_SEEN) {
*(ptr2++) = 's';
}
if (permissions & NMAP_SHARE_WRITE) {
*(ptr2++) = 'w';
}
if (permissions & NMAP_SHARE_INSERT) {
*(ptr2++) = 'i';
}
if (permissions & NMAP_SHARE_POST) {
*(ptr2++) = 'p';
}
if (permissions & NMAP_SHARE_CREATE) {
*(ptr2++) = 'c';
}
if (permissions & NMAP_SHARE_DELETE) {
*(ptr2++) = 'd';
}
if (permissions & NMAP_SHARE_ADMINISTER) {
*(ptr2++) = 'a';
}
continue;
}
}
continue;
}
}
if (ccode == 1000) {
*(ptr2++) = '\r';
*(ptr2++) = '\n';
*(ptr2) = '\0';
if (ConnWrite(session->client.conn, Reply, ptr2 - Reply) != -1) {
return(SendOk(session, "MYRIGHTS"));
}
return(STATUS_ABORT);
}
}
return(SendError(session->client.conn, session->command.tag, "MYRIGHTS", CheckForNMAPCommError(ccode)));
}
MemFree(mbox);
return(SendError(session->client.conn, session->command.tag, "MYRIGHTS", STATUS_NMAP_COMM_ERROR));
}
return(SendError(session->client.conn, session->command.tag, "MYRIGHTS", ccode));
}
return(SendError(session->client.conn, session->command.tag, "MYRIGHTS", STATUS_FEATURE_DISABLED));
#else
return(SendError(((ImapSession *)param)->client.conn, ((ImapSession *)param)->command.tag, "SETACL", STATUS_UNKNOWN_COMMAND));
#endif
}
/********** IMAP Quota commands rfc2087 **********/
int
ImapCommandSetQuota(void *param)
{
long ccode;
ImapSession *session = (ImapSession *)param;
if ((ccode = CheckState(session, STATE_AUTH)) == STATUS_CONTINUE) {
if ((ccode = EventsSend(session, STORE_EVENT_ALL)) == STATUS_CONTINUE) {
return(SendError(session->client.conn, session->command.tag, "SETQUOTA", STATUS_PERMISSION_DENIED));
}
}
return(SendError(session->client.conn, session->command.tag, "SETQUOTA", ccode));
}
int
ImapCommandGetQuota(void *param)
{
ImapSession *session = (ImapSession *)param;
long ccode;
char *ptr;
char Reply[1024];
unsigned long quota = 0;
unsigned long used = 0;
if ((ccode = CheckState(session, STATE_AUTH)) == STATUS_CONTINUE) {
if ((ccode = EventsSend(session, STORE_EVENT_ALL)) == STATUS_CONTINUE) {
if (NMAPSendCommandF(session->store.conn, "SPACE\r\n") != -1) {
ccode = NMAPReadResponse(session->store.conn, Reply, sizeof(Reply), TRUE);
if(ccode != 1000) {
ccode = CheckForNMAPCommError(ccode);
if (ccode != STATUS_NMAP_COMM_ERROR) {
return(SendError(session->client.conn, session->command.tag, "GETQUOTA", STATUS_COULD_NOT_DETERMINE_QUOTA));
}
return(SendError(session->client.conn, session->command.tag, "GETQUOTA", STATUS_NMAP_COMM_ERROR));
}
ptr = strchr(Reply, ' ');
if(!ptr) {
return(SendError(session->client.conn, session->command.tag, "GETQUOTA", STATUS_COULD_NOT_DETERMINE_QUOTA));
}
quota = atol(ptr);
used = atol(Reply);
if(session->command.buffer[9] == '\"' && session->command.buffer[10] == '\"') {
if (ConnWriteF(session->client.conn, "* QUOTA \"\" (STORAGE %lu %lu)\r\n%s OK GETQUOTA completed\r\n", used, quota, session->command.tag) != -1) {
return(STATUS_CONTINUE);
}
return(STATUS_ABORT);
}
return(SendError(session->client.conn, session->command.tag, "GETQUOTA", STATUS_NO_SUCH_QUOTA_ROOT));
}
return(SendError(session->client.conn, session->command.tag, "GETQUOTA", STATUS_NMAP_COMM_ERROR));
}
}
return(SendError(session->client.conn, session->command.tag, "GETQUOTA", ccode));
}
int
ImapCommandGetQuotaRoot(void *param)
{
ImapSession *session = (ImapSession *)param;
long ccode;
char *ptr;
char Reply[1024];
unsigned long quota = 0;
unsigned long used = 0;
char *mbox;
if ((ccode = CheckState(session, STATE_AUTH)) == STATUS_CONTINUE) {
if ((ccode = EventsSend(session, STORE_EVENT_ALL)) == STATUS_CONTINUE) {
if (NMAPSendCommandF(session->store.conn, "SPACE\r\n") != -1) {
ccode = NMAPReadResponse(session->store.conn, Reply, sizeof(Reply), TRUE);
if(ccode != 1000) {
ccode = CheckForNMAPCommError(ccode);
if (ccode != STATUS_NMAP_COMM_ERROR) {
return(SendError(session->client.conn, session->command.tag, "GETQUOTA", STATUS_COULD_NOT_DETERMINE_QUOTA));
}
return(SendError(session->client.conn, session->command.tag, "GETQUOTA", STATUS_NMAP_COMM_ERROR));
}
ptr = strchr(Reply, ' ');
if(!ptr) {
return(SendError(session->client.conn, session->command.tag, "GETQUOTAROOT", STATUS_COULD_NOT_DETERMINE_QUOTA));
}
quota = atol(ptr);
used = atol(Reply);
ptr = session->command.buffer + 13;
if(*(ptr - 1) != ' ') {
return(SendError(session->client.conn, session->command.tag, "GETQUOTAROOT", STATUS_INVALID_ARGUMENT));
}
ccode = GrabArgument(session, &ptr, &mbox);
if (ccode == STATUS_CONTINUE) {
ccode = ConnWriteF(session->client.conn, "* QUOTAROOT %s \"\"\r\n* QUOTA \"\" (STORAGE %lu %lu)\r\n%s OK GETQUOTAROOT completed\r\n", mbox, used, quota, session->command.tag);
MemFree(mbox);
if (ccode != -1) {
return(STATUS_CONTINUE);
}
return(STATUS_ABORT);
}
return(SendError(session->client.conn, session->command.tag, "QUOTAROOT", ccode));
}
return(SendError(session->client.conn, session->command.tag, "QUOTAROOT", STATUS_NMAP_COMM_ERROR));
}
}
return(SendError(session->client.conn, session->command.tag, "GETQUOTAROOT", ccode));
}
/********** IMAP Namespace command rfc2342 **********/
int
ImapCommandNameSpace(void *param)
{
ImapSession *session = (ImapSession *)param;
if (session->client.state < STATE_AUTH) {
return(SendError(session->client.conn, session->command.tag, "NAMESPACE", STATUS_INVALID_STATE));
}
if (ConnWrite(session->client.conn,
"* NAMESPACE ((\"\" \"/\")) NIL NIL\r\n",
strlen("* NAMESPACE ((\"\" \"/\")) NIL NIL\r\n")) != -1) {
return(SendOk(session, "NAMESPACE"));
}
return(STATUS_ABORT);
}
static BOOL
HandleConnection(void *param)
{
long ccode;
long commandId;
ImapSession *session=(ImapSession *)param;
char *ptr;
ProtocolCommand *localCommand;
session->progress = NULL;
if (ConnNegotiate(session->client.conn, Imap.server.ssl.context)) {
if (session->client.conn->ssl.enable == FALSE) {
Log(LOG_INFO, "Non-SSL connection from %s", LOGIP(session->client.conn->socketAddress));
} else {
Log(LOG_INFO, "SSL connection from %s", LOGIP(session->client.conn->socketAddress));
}
if ((ConnWriteF(session->client.conn, "* OK %s %s server ready <%ud.%lu@%s>\r\n",BongoGlobals.hostname, PRODUCT_NAME, (unsigned int)XplGetThreadID(),time(NULL),BongoGlobals.hostname) != -1) && (ConnFlush(session->client.conn) != -1)) {
while (TRUE) {
ccode = ReadCommandLine(session->client.conn, &(session->command.buffer), &(session->command.bufferLen));
if (ccode == STATUS_CONTINUE) {
/* Grab ident from command */
ptr = strchr(session->command.buffer,' ');
if (ptr) {
if ((unsigned long)(ptr - session->command.buffer) < sizeof(session->command.tag)) {
ptr[0]='\0';
memcpy(session->command.tag, session->command.buffer, ptr - session->command.buffer + 1);
} else {
memcpy(session->command.tag, session->command.buffer, sizeof(session->command.tag));
session->command.tag[sizeof(session->command.tag)-1]='\0';
}
memmove(session->command.buffer, ptr + 1, strlen(ptr + 1) + 1);
commandId = BongoKeywordBegins(Imap.command.index, session->command.buffer);
if (commandId != -1) {
localCommand = &ImapProtocolCommands[commandId];
ccode = localCommand->handler(session);
} else {
ccode = ImapCommandUnknown(session);
}
if (ccode != STATUS_ABORT) {
/* Post-Command Processing */
if (session->client.state == STATE_SELECTED) {
SendUnseenFlags(session);
}
ConnFlush(session->client.conn);
continue;
}
break;
}
if ((ConnWrite(session->client.conn, "* BAD Missing command\r\n", 23) != -1) && (ConnFlush(session->client.conn) != -1)) {
continue;
}
break;
}
if ((ccode != STATUS_ABORT) && (ConnWrite(session->client.conn, "* BAD command line too long\r\n", 29) != -1) && (ConnFlush(session->client.conn) != -1)) {
continue;
}
break;
}
}
}
SessionCleanup(session);
return(FALSE);
}
#if defined(NETWARE) || defined(LIBC) || defined(WIN32)
static int
_NonAppCheckUnload(void)
{
int s;
static BOOL checked = FALSE;
XplThreadID id;
if (!checked) {
checked = Imap.exiting = TRUE;
XplWaitOnLocalSemaphore(Imap.server.shutdownSemaphore);
id = XplSetThreadGroupID(Imap.server.threadGroupId);
if (Imap.server.conn->socket != -1) {
s = Imap.server.conn->socket;
Imap.server.conn->socket = -1;
IPclose(s);
}
XplSetThreadGroupID(id);
XplWaitOnLocalSemaphore(Imap.server.mainSemaphore);
}
return(0);
}
#endif
static void
IMAPShutdownSigHandler(int sigtype)
{
static BOOL signaled = FALSE;
if (!signaled && (sigtype == SIGTERM || sigtype == SIGINT)) {
signaled = Imap.exiting = TRUE;
if (Imap.server.conn) {
ConnClose(Imap.server.conn);
Imap.server.conn = NULL;
}
XplSignalLocalSemaphore(Imap.server.shutdownSemaphore);
/* Allow the main thread to close the final semaphores and exit. */
XplDelay(1000);
}
return;
}
static int
ServerSocketInit(void)
{
Imap.server.conn = ConnAlloc(FALSE);
if (!Imap.server.conn) {
XplConsolePrintf("bongoimap: Could not allocate connection.\n");
return -1;
}
Imap.server.conn->socketAddress.sin_family = AF_INET;
Imap.server.conn->socketAddress.sin_port = htons(Imap.server.port);
Imap.server.conn->socketAddress.sin_addr.s_addr = MsgGetAgentBindIPAddress();
/* Get root privs back for the bind. It's ok if this fails -
* the user might not need to be root to bind to the port */
XplSetEffectiveUserId(0);
Imap.server.conn->socket = ConnServerSocket(Imap.server.conn, 2048);
if (XplSetEffectiveUser(MsgGetUnprivilegedUser()) < 0) {
Log(LOG_ERROR, "Could not drop to unprivileged user '%s'", MsgGetUnprivilegedUser());
return -1;
}
if (Imap.server.conn->socket < 0) {
int ret = Imap.server.conn->socket;
Log(LOG_ERROR, "Could not bind to port %d", Imap.server.port);
ConnFree(Imap.server.conn);
return ret;
}
return 0;
}
static int
ServerSocketSSLInit(void)
{
Imap.server.ssl.conn = ConnAlloc(FALSE);
if (Imap.server.ssl.conn == NULL) {
Log(LOG_ERROR, "Could not allocate SSL connection");
ConnFree(Imap.server.conn);
return -1;
}
Imap.server.ssl.conn->socketAddress.sin_family = AF_INET;
Imap.server.ssl.conn->socketAddress.sin_port = htons(Imap.server.ssl.port);
Imap.server.ssl.conn->socketAddress.sin_addr.s_addr = MsgGetAgentBindIPAddress();
/* Get root privs back for the bind. It's ok if this fails -
* the user might not need to be root to bind to the port */
XplSetEffectiveUserId(0);
Imap.server.ssl.conn->socket = ConnServerSocket(Imap.server.ssl.conn, 2048);
if (XplSetEffectiveUser(MsgGetUnprivilegedUser()) < 0) {
Log(LOG_ERROR, "Could not drop to unprivileged user '%s'", MsgGetUnprivilegedUser());
return -1;
}
if (Imap.server.ssl.conn->socket < 0) {
int ret = Imap.server.ssl.conn->socket;
Log(LOG_ERROR, "Could not bind to SSL port %d", Imap.server.ssl.port);
ConnFree(Imap.server.conn);
ConnFree(Imap.server.ssl.conn);
return ret;
}
return 0;
}
static BOOL
InitializeImapGlobals()
{
/* Imap.session. */
XplSafeWrite(Imap.session.threads.inUse, 0);
XplSafeWrite(Imap.session.threads.idle, 0);
XplSafeWrite(Imap.session.served, 0);
XplSafeWrite(Imap.session.badPassword, 0);
Imap.session.threads.max = 100000;
/* Imap.server. */
XplSafeWrite(Imap.server.active, 0);
Imap.server.disabled = FALSE;
Imap.server.port = 143;
Imap.server.ssl.port = 993;
Imap.server.ssl.config.options = 0;
Imap.server.ssl.config.options |= SSL_ALLOW_CHAIN;
Imap.server.allowLegacyTLS = FALSE;
Imap.server.threadGroupId = XplGetThreadGroupID();
strcpy(Imap.server.postmaster, "admin");
/* Imap.command. */
Imap.command.capability.acl.enabled = TRUE;
/* FIXME: ACL ?? */
Imap.command.months[0] = "Jan";
Imap.command.months[1] = "Feb";
Imap.command.months[2] = "Mar";
Imap.command.months[3] = "Apr";
Imap.command.months[4] = "May";
Imap.command.months[5] = "Jun";
Imap.command.months[6] = "Jul";
Imap.command.months[7] = "Aug";
Imap.command.months[8] = "Sep";
Imap.command.months[9] = "Oct";
Imap.command.months[10] = "Nov";
Imap.command.months[11] = "Dec";
/* Imap.(misc). */
Imap.exiting = FALSE;
Imap.logHandle = NULL;
/* Initialize the Busy List and Semaphore */
Imap.list_Busy = NULL;
XplOpenLocalSemaphore(Imap.sem_Busy, 1);
/* Global allocations */
BongoKeywordIndexCreateFromTable(Imap.command.index, ImapProtocolCommands, .name, TRUE);
if (Imap.command.index) {
Imap.command.returnValueIndex = CreateReturnValueIndex();
if (Imap.command.returnValueIndex) {
if (CommandFetchInit()) {
if (CommandStoreInit()) {
if (CommandStatusInit()) {
if (CommandSearchInit()) {
return(TRUE);
}
CommandSearchCleanup();
}
CommandStoreCleanup();
}
CommandFetchCleanup();
}
FreeReturnValueIndex(Imap.command.returnValueIndex);
}
BongoKeywordIndexFree(Imap.command.index);
}
return(FALSE);
}
static BOOL
FreeImapGlobals()
{
CommandSearchCleanup();
CommandStatusCleanup();
CommandStoreCleanup();
CommandFetchCleanup();
FreeReturnValueIndex(Imap.command.returnValueIndex);
BongoKeywordIndexFree(Imap.command.index);
return(TRUE);
}
static void
IMAPServer(void *unused)
{
int ccode;
unsigned long j;
XplThreadID oldTGID;
Connection *conn;
ImapSession *session;
XplThreadID id = 0;
UNUSED_PARAMETER(unused);
XplRenameThread(XplGetThreadID(), "IMAP Server");
XplSafeIncrement(Imap.server.active);
while (!Imap.exiting) {
if (ConnAccept(Imap.server.conn, &conn) != -1) {
if (!Imap.exiting) {
if (!Imap.server.disabled) {
if (XplSafeRead(Imap.session.threads.inUse) < (long)Imap.session.threads.max) {
session = ImapSessionGet();
if (session) {
session->client.conn = conn;
XplBeginCountedThread(&id, HandleConnection, IMAP_STACK_SIZE, session, ccode, Imap.session.threads.inUse);
if (ccode == 0) {
continue;
}
ImapSessionReturn(session);
}
ConnSend(conn, "* BYE IMAP server out of memory\r\n", strlen("* BYE IMAP server out of memory\r\n"));
ConnClose(conn);
ConnFree(conn);
continue;
}
ConnSend(conn, "* BYE IMAP connection limit for this server reached\r\n", strlen("* BYE IMAP connection limit for this server reached\r\n"));
ConnClose(conn);
ConnFree(conn);
continue;
}
ConnSend(conn, "* BYE IMAP access to this server currently disabled\r\n", strlen("* BYE IMAP access to this server currently disabled\r\n"));
ConnClose(conn);
ConnFree(conn);
continue;
}
ConnSend(conn, "* BYE IMAP server shutting down\r\n", strlen("* BYE IMAP server shutting down\r\n"));
ConnClose(conn);
ConnFree(conn);
break;
}
switch (errno) {
case ECONNABORTED:
#ifdef EPROTO
case EPROTO:
#endif
case EINTR: {
Log(LOG_WARN, "Connection interrupted");
continue;
}
case EIO: {
Log(LOG_WARN, "Server exiting after an accept() failure with an errno: %d\n", errno);
break;
}
}
break;
}
/* Shutting down */
Imap.exiting = TRUE;
oldTGID = XplSetThreadGroupID(Imap.server.threadGroupId);
UNUSED_PARAMETER(oldTGID);
if (Imap.server.conn) {
ConnClose(Imap.server.conn);
Imap.server.conn = NULL;
}
#if VERBOSE
XplConsolePrintf("IMAPD: Standard listener done.\r\n");
#endif
if (Imap.server.ssl.conn) {
ConnClose(Imap.server.ssl.conn);
}
/* Wake up the children and set them free! */
/* fixme - SocketShutdown; */
/* Wait for the our siblings to leave quietly. */
for (j = 0; (XplSafeRead(Imap.server.active) > 1) && (j < 60); j++) {
XplDelay(1000);
}
if (XplSafeRead(Imap.server.active) > 1) {
XplConsolePrintf("\rIMAPD: %d server threads outstanding; attempting forceful unload.\r\n", XplSafeRead(Imap.server.active) - 1);
}
#if VERBOSE
XplConsolePrintf("\rIMAPD: Shutting down %d session threads\r\n", XplSafeRead(Imap.session.threads.inUse));
#endif
/* Make sure the kids have flown the coop. */
for (j = 0; XplSafeRead(Imap.session.threads.inUse) && (j < 3 * 60); j++) {
XplDelay(1000);
}
if (XplSafeRead(Imap.session.threads.inUse)) {
XplConsolePrintf("IMAPD: %d threads outstanding; attempting forceful unload.\r\n", XplSafeRead(Imap.session.threads.inUse));
}
/* Cleanup SSL */
if (Imap.server.ssl.context) {
ConnSSLContextFree(Imap.server.ssl.context);
}
LogClose();
MsgShutdown();
CONN_TRACE_SHUTDOWN();
ConnShutdown();
FreeImapGlobals();
MemPrivatePoolFree(IMAPConnectionPool);
#if VERBOSE
XplConsolePrintf("IMAPD: Shutdown complete\r\n");
#endif
XplSignalLocalSemaphore(Imap.server.mainSemaphore);
XplWaitOnLocalSemaphore(Imap.server.shutdownSemaphore);
XplCloseLocalSemaphore(Imap.server.shutdownSemaphore);
XplCloseLocalSemaphore(Imap.server.mainSemaphore);
XplSetThreadGroupID(oldTGID);
return;
}
static void
BusyThread(void *unused)
{
UNUSED_PARAMETER(unused);
while (!Imap.exiting) {
DoUpdate();
XplDelay(10000);
}
}
static void
IMAPSSLServer(void *unused)
{
ImapSession *session;
char *message;
unsigned long messageLen;
XplThreadID id = 0;
int ccode;
Connection *conn;
UNUSED_PARAMETER(unused);
XplRenameThread(XplGetThreadID(), "IMAP SSL Server");
while (!Imap.exiting) {
if (ConnAccept(Imap.server.ssl.conn, &conn) != -1) {
conn->ssl.enable = TRUE;
if (!Imap.exiting) {
if (!Imap.server.disabled) {
if (XplSafeRead(Imap.session.threads.inUse) < (long)Imap.session.threads.max) {
session = ImapSessionGet();
if (session) {
session->client.conn = conn;
// Imap.session.threads.inUse
XplBeginCountedThread(&id, HandleConnection, IMAP_STACK_SIZE, session, ccode, (Imap.session.threads.inUse));
if (ccode == 0) {
continue;
}
ImapSessionReturn(session);
message = "* BYE IMAP server unable to create threads\r\n";
messageLen = strlen("* BYE IMAP server unable to create threads\r\n");
} else {
message = "* BYE IMAP server out of memory\r\n";
messageLen = strlen("* BYE IMAP server out of memory\r\n");
}
} else {
message = "* BYE IMAP connection limit for this server reached\r\n";
messageLen = strlen("* BYE IMAP connection limit for this server reached\r\n");
}
} else {
message = "* BYE IMAP access to this server currently disabled\r\n";
messageLen = strlen("* BYE IMAP access to this server currently disabled\r\n");
}
if (ConnNegotiate(conn, Imap.server.ssl.context)) {
if (ConnWrite(conn, message, messageLen) != -1) {
ConnFlush(conn);
}
ConnClose(conn);
ConnFree(conn);
continue;
}
ConnSend(conn, message, messageLen);
ConnClose(conn);
ConnFree(conn);
continue;
}
break;
}
if (!Imap.exiting) {
switch (errno) {
case ECONNABORTED:
#ifdef EPROTO
case EPROTO:
#endif
case EINTR: {
Log(LOG_WARN, "accept() interrupted");
continue;
}
case EIO: {
Imap.exiting = TRUE;
Log(LOG_WARN, "SSL Server exiting after an accept() failure with an errno: %d\n", errno);
break;
}
default: {
Log(LOG_WARN, "SSL Server exiting after an accept() failure with an errno: %d\n", errno);
break;
}
}
}
break;
}
#if 0
XplConsolePrintf("IMAPD: SSL listener done.\r\n");
#endif
if(Imap.server.ssl.conn) {
ConnFree(Imap.server.ssl.conn);
Imap.server.ssl.conn = NULL;
}
XplSafeDecrement(Imap.server.active);
if (!Imap.exiting) {
raise(SIGTERM);
}
return;
}
static BongoConfigItem IMAPConfig[] = {
{ BONGO_JSON_INT, "o:port/i", &Imap.server.port },
{ BONGO_JSON_INT, "o:port_ssl/i", &Imap.server.ssl.port },
{ BONGO_JSON_BOOL, "o:allow_legacy_tls/b", &Imap.server.allowLegacyTLS },
{ BONGO_JSON_INT, "o:threads_max/i", &Imap.session.threads.max },
{ BONGO_JSON_NULL, NULL, NULL }
};
static BOOL
ReadConfiguration(void)
{
time_t gm_time_of_day, time_of_day;
struct tm gm, ltm;
tzset();
time(&time_of_day);
time(&gm_time_of_day);
gmtime_r(&gm_time_of_day, &gm);
localtime_r(&time_of_day, &ltm);
gm_time_of_day=mktime(&gm);
// Imap.server.postmaster needs to be set to something?
if (! ReadBongoConfiguration(IMAPConfig, "imap")) {
return FALSE;
}
if (! ReadBongoConfiguration(GlobalConfig, "global")) {
return FALSE;
}
return(TRUE);
}
XplServiceCode(IMAPShutdownSigHandler)
int
XplServiceMain(int argc, char *argv[])
{
int ccode;
XplThreadID id=0;
UNUSED_PARAMETER(argc);
UNUSED_PARAMETER(argv);
if (XplSetEffectiveUser(MsgGetUnprivilegedUser()) < 0) {
XplConsolePrintf("bongoimap: Could not drop to unprivileged user '%s', exiting.\n", MsgGetUnprivilegedUser());
return 1;
}
XplInit();
XplSignalHandler(IMAPShutdownSigHandler);
IMAPConnectionPool = MemPrivatePoolAlloc("IMAP Connections", sizeof(ImapSession), 0, 3072, TRUE, FALSE, IMAPSessionAllocCB, IMAPSessionFreeCB, NULL);
if (IMAPConnectionPool != NULL) {
XplOpenLocalSemaphore(Imap.server.mainSemaphore, 0);
XplOpenLocalSemaphore(Imap.server.shutdownSemaphore, 1);
} else {
XplConsolePrintf("IMAPD: Unable to create command buffer pool; shutting down.\r\n");
return(-1);
}
InitializeImapGlobals();
ConnStartup(IMAP_CONNECTION_TIMEOUT);
MsgInit();
MsgAuthInit();
if (!BongoSaslInitialize()) {
XplConsolePrintf("IMAPD: Unable to initialize Cyrus SASL.\r\n");
return -1;
}
NMAPInitialize();
LogOpen("bongoimap");
ReadConfiguration();
CONN_TRACE_INIT(XPL_DEFAULT_WORK_DIR, "imap");
CONN_TRACE_SET_FLAGS(CONN_TRACE_ALL); /* uncomment this line and pass '--enable-conntrace' to autogen to get the agent to trace all connections */
if (ServerSocketInit() < 0) {
XplConsolePrintf("bongoimap: Imap.exiting\n");
return -1;
}
if (!ServerSocketSSLInit()) {
if (Imap.server.allowLegacyTLS) {
Imap.server.ssl.config.options |= SSL_ALLOW_LEGACY_PROTOCOLS;
}
Imap.server.ssl.config.certificate.file = MsgGetFile(MSGAPI_FILE_PUBKEY, NULL, 0);
Imap.server.ssl.config.key.file = MsgGetFile(MSGAPI_FILE_PRIVKEY, NULL, 0);
Imap.server.ssl.config.key.type = GNUTLS_X509_FMT_PEM;
Imap.server.ssl.context = ConnSSLContextAlloc(&(Imap.server.ssl.config));
if (Imap.server.ssl.context) {
XplBeginCountedThread(&id, IMAPSSLServer, IMAP_STACK_SIZE, NULL, ccode, Imap.server.active);
}
}
/* Done binding to ports, drop privs permanently */
if (XplSetRealUser(MsgGetUnprivilegedUser()) < 0) {
XplConsolePrintf("bongoimap: Could not drop to unprivileged user '%s', exiting\n", MsgGetUnprivilegedUser());
return 1;
}
/* start the busy thread */
XplBeginCountedThread(&id, BusyThread, IMAP_STACK_SIZE, NULL, ccode, Imap.server.active);
XplStartMainThread(PRODUCT_SHORT_NAME, &id, IMAPServer, 8192, NULL, ccode);
XplUnloadApp(XplGetThreadID());
return(0);
}