/*
Copyright (C) 1999-2004 IC & S dbmail@ic-s.nl
Copyright (c) 2004-2006 NFG Net Facilities Group BV support@nfg.nl
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/*
* imap4.c
*
* implements an IMAP 4 rev 1 server.
*/
#include "dbmail.h"
#define COMMAND_SHOW_LEVEL TRACE_INFO
#define THIS_MODULE "imap"
const char *IMAP_COMMANDS[] = {
"", "capability", "noop", "logout",
"authenticate", "login",
"select", "examine", "create", "delete", "rename", "subscribe",
"unsubscribe",
"list", "lsub", "status", "append",
"check", "close", "expunge", "search", "fetch", "store", "copy",
"uid", "sort", "getquotaroot", "getquota",
"setacl", "deleteacl", "getacl", "listrights", "myrights",
"namespace","thread","unselect","idle",
"***NOMORE***"
};
const IMAP_COMMAND_HANDLER imap_handler_functions[] = {
NULL,
_ic_capability, _ic_noop, _ic_logout,
_ic_authenticate, _ic_login,
_ic_select, _ic_examine, _ic_create, _ic_delete, _ic_rename,
_ic_subscribe, _ic_unsubscribe, _ic_list, _ic_lsub, _ic_status,
_ic_append,
_ic_check, _ic_close, _ic_expunge, _ic_search, _ic_fetch,
_ic_store, _ic_copy, _ic_uid, _ic_sort,
_ic_getquotaroot, _ic_getquota,
_ic_setacl, _ic_deleteacl, _ic_getacl, _ic_listrights,
_ic_myrights,
_ic_namespace, _ic_thread, _ic_unselect, _ic_idle,
NULL
};
imap_userdata_t * dbmail_imap_userdata_new(void)
{
imap_userdata_t *ud;
ud = g_new0(imap_userdata_t,1);
ud->state = IMAPCS_NON_AUTHENTICATED;
return ud;
}
/*
* Main handling procedure
*
* returns EOF on logout/fatal error or 1 otherwise
*/
int IMAPClientHandler(clientinfo_t * ci)
{
char line[MAX_LINESIZE], *tag = NULL, *cpy, **args, *command;
int done, result, readresult, nfaultyresponses, serr;
size_t i;
imap_userdata_t *ud = NULL;
struct ImapSession *session;
session = dbmail_imap_session_new();
session->timeout = ci->login_timeout;
dbmail_imap_session_setClientinfo(session,ci);
if (! (ud = dbmail_imap_userdata_new()))
return -1;
session->ci->userData = ud;
/* greet user */
field_t banner;
GETCONFIGVALUE("banner", "IMAP", banner);
if (strlen(banner) > 0) {
if (dbmail_imap_session_printf(session,
"* OK %s\r\n", banner) < 0) {
dbmail_imap_session_delete(session);
return EOF;
}
} else {
if (dbmail_imap_session_printf(session,
"* OK dbmail imap (protocol version 4r1) server %s "
"ready to run\r\n", IMAP_SERVER_VERSION) < 0) {
dbmail_imap_session_delete(session);
return EOF;
}
}
fflush(session->ci->tx);
done = 0;
args = NULL;
nfaultyresponses = 0;
do {
if (db_check_connection()) {
TRACE(TRACE_DEBUG,"database has gone away");
done=1;
break;
}
if (nfaultyresponses >= MAX_FAULTY_RESPONSES) {
/* we have had just about it with this user */
sleep(2); /* avoid DOS attacks */
if (dbmail_imap_session_printf(session, "* BYE [TRY RFC]\r\n") < 0) {
dbmail_imap_session_delete(session);
return EOF;
}
done = 1;
break;
}
if (ferror(session->ci->rx)) {
if (errno) {
serr = errno;
TRACE(TRACE_ERROR, "[%s] on read-stream\n", strerror(serr));
if (serr == EPIPE) {
dbmail_imap_session_delete(session);
return -1; /* broken pipe */
}
}
clearerr(session->ci->rx);
}
if (ferror(session->ci->tx)) {
if (errno) {
serr = errno;
TRACE(TRACE_ERROR, "[%s] on write-stream\n", strerror(serr));
if (serr == EPIPE) {
dbmail_imap_session_delete(session);
return -1; /* broken pipe */
}
}
clearerr(session->ci->tx);
}
readresult = dbmail_imap_session_readln(session, line);
if (readresult < 0) { /* Fatal error: EOF &c. */
dbmail_imap_session_delete(session);
return -1;
}
/* There is an error possible (line too long, readresult == 0)
* that we need to note, but carry through with processing the
* command in order to generate the proper tag on the
* BAD response */
if (!session->ci->rx || !session->ci->tx) {
/* if a timeout occured the streams will be closed & set to NULL */
TRACE(TRACE_ERROR, "timeout occurred.");
dbmail_imap_session_delete(session);
return 1;
}
/* strip eol chars */
cpy = &line[strlen(line)];
cpy--;
while (cpy >= line && (*cpy == '\r' || *cpy == '\n')) {
*cpy = '\0';
cpy--;
}
TRACE(COMMAND_SHOW_LEVEL, "COMMAND: [%s]\n", line);
if (!(*line)) {
if (dbmail_imap_session_printf(session, "* BAD No tag specified\r\n") < 0) {
dbmail_imap_session_delete(session);
return EOF;
}
nfaultyresponses++;
continue;
}
/* read tag & command */
cpy = line;
i = stridx(cpy, ' '); /* find next space */
if (i == strlen(cpy)) {
if (checktag(cpy)) {
if (dbmail_imap_session_printf(session, "%s BAD No command specified\r\n", cpy) < 0) {
dbmail_imap_session_delete(session);
return EOF;
}
} else {
if (dbmail_imap_session_printf(session, "* BAD Invalid tag specified\r\n") < 0) {
dbmail_imap_session_delete(session);
return EOF;
}
}
nfaultyresponses++;
continue;
}
tag = g_strdup(cpy); /* set tag */
tag[i] = '\0';
dbmail_imap_session_setTag(session,tag);
g_free(tag);
cpy[i] = '\0';
cpy = cpy + i + 1; /* cpy points to command now */
/* check tag */
if (!checktag(session->tag)) {
if (dbmail_imap_session_printf(session, "* BAD Invalid tag specified\r\n") < 0) {
dbmail_imap_session_delete(session);
return EOF;
}
nfaultyresponses++;
continue;
}
if (readresult == 0) { /* Nonfatal error: too long line &c. */
if (dbmail_imap_session_printf(session, "%s BAD Line too long\r\n",session->tag) < 0) {
dbmail_imap_session_delete(session);
return EOF;
}
nfaultyresponses++;
continue;
}
i = stridx(cpy, ' '); /* find next space */
command = strdup(cpy); /* set command */
if (i < strlen(command))
command[i]='\0';
dbmail_imap_session_setCommand(session,command);
g_free(command);
if (i == strlen(cpy)) {
/* no arguments present */
args = build_args_array_ext(session, "");
} else {
cpy[i] = '\0'; /* terminated command */
cpy = cpy + i + 1; /* cpy points to args now */
args = build_args_array_ext(session, cpy); /* build argument array */
if (!session->ci->rx || !session->ci->tx || ferror(session->ci->rx) || ferror(session->ci->tx)) {
/* some error occurred during the read of extra command info */
TRACE(TRACE_ERROR, "error reading extra command info");
dbmail_imap_session_delete(session);
return -1;
}
}
if (!args) {
if (dbmail_imap_session_printf(session, "%s BAD invalid argument specified\r\n",session->tag) < 0) {
dbmail_imap_session_delete(session);
return EOF;
}
nfaultyresponses++;
continue;
}
for (i = IMAP_COMM_NONE; i < IMAP_COMM_LAST && strcasecmp(session->command, IMAP_COMMANDS[i]); i++);
if (i <= IMAP_COMM_NONE || i >= IMAP_COMM_LAST) {
/* unknown command */
if (dbmail_imap_session_printf(session, "%s BAD command not recognized\r\n",session->tag)) {
dbmail_imap_session_delete(session);
return EOF;
}
nfaultyresponses++;
continue;
}
/* reset the faulty responses counter. This is quick fix which
* is useful for programs that depend on sending faulty
* commands to the server, and checking the response.
* (IB: 2004-08-23) */
nfaultyresponses = 0;
session->command_type = i;
TRACE(TRACE_INFO, "Executing command %s...\n", IMAP_COMMANDS[i]);
result = (*imap_handler_functions[i]) (session);
if (result == -1) {
TRACE(TRACE_ERROR,"command return with error [%s]", IMAP_COMMANDS[i]);
done = 1; /* fatal error occurred, kick this user */
}
if (result == 1)
nfaultyresponses++; /* server returned BAD or NO response */
if (result == 0 && i == IMAP_COMM_LOGOUT)
done = 1;
fflush(session->ci->tx); /* write! */
dbmail_imap_session_args_free(session, FALSE);
TRACE(TRACE_INFO, "Finished command %s [%d]\n", IMAP_COMMANDS[i], result);
} while (!done);
/* cleanup */
dbmail_imap_session_printf(session, "%s OK completed\r\n", session->tag);
TRACE(TRACE_MESSAGE, "Closing connection for client from IP [%s]\n", session->ci->ip_src);
dbmail_imap_session_delete(session);
return EOF;
}
syntax highlighted by Code2HTML, v. 0.9.1