/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*-
*
* Author:
* Michael Zucchi <notzed@ximian.com>
*
* Copyright 1999, 2000 Ximian, Inc. (www.ximian.com)
*
* 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <glib.h>
#include "camel-pop3-engine.h"
#include "camel-pop3-stream.h"
#include "camel-service.h"
#include "camel-sasl.h"
#include "camel-i18n.h"
/* max 'outstanding' bytes in output stream, so we can't deadlock waiting
for the server to accept our data when pipelining */
#define CAMEL_POP3_SEND_LIMIT (1024)
extern int camel_verbose_debug;
#define dd(x) (camel_verbose_debug?(x):0)
static void get_capabilities(CamelPOP3Engine *pe);
static CamelObjectClass *parent_class = NULL;
/* Returns the class for a CamelStream */
#define CS_CLASS(so) CAMEL_POP3_ENGINE_CLASS(CAMEL_OBJECT_GET_CLASS(so))
static void
camel_pop3_engine_class_init (CamelPOP3EngineClass *camel_pop3_engine_class)
{
parent_class = camel_type_get_global_classfuncs( CAMEL_OBJECT_TYPE );
}
static void
camel_pop3_engine_init(CamelPOP3Engine *pe, CamelPOP3EngineClass *peclass)
{
e_dlist_init(&pe->active);
e_dlist_init(&pe->queue);
e_dlist_init(&pe->done);
pe->state = CAMEL_POP3_ENGINE_DISCONNECT;
}
static void
camel_pop3_engine_finalise(CamelPOP3Engine *pe)
{
/* FIXME: Also flush/free any outstanding requests, etc */
if (pe->stream)
camel_object_unref(pe->stream);
}
CamelType
camel_pop3_engine_get_type (void)
{
static CamelType camel_pop3_engine_type = CAMEL_INVALID_TYPE;
if (camel_pop3_engine_type == CAMEL_INVALID_TYPE) {
camel_pop3_engine_type = camel_type_register(camel_object_get_type(),
"CamelPOP3Engine",
sizeof( CamelPOP3Engine ),
sizeof( CamelPOP3EngineClass ),
(CamelObjectClassInitFunc) camel_pop3_engine_class_init,
NULL,
(CamelObjectInitFunc) camel_pop3_engine_init,
(CamelObjectFinalizeFunc) camel_pop3_engine_finalise );
}
return camel_pop3_engine_type;
}
static int
read_greeting (CamelPOP3Engine *pe)
{
extern CamelServiceAuthType camel_pop3_password_authtype;
extern CamelServiceAuthType camel_pop3_apop_authtype;
unsigned char *line, *apop, *apopend;
unsigned int len;
/* first, read the greeting */
if (camel_pop3_stream_line (pe->stream, &line, &len) == -1
|| strncmp (line, "+OK", 3) != 0)
return -1;
if ((apop = strchr (line + 3, '<'))
&& (apopend = strchr (apop, '>'))) {
apopend[1] = 0;
pe->apop = g_strdup (apop);
pe->capa = CAMEL_POP3_CAP_APOP;
pe->auth = g_list_append (pe->auth, &camel_pop3_apop_authtype);
}
pe->auth = g_list_prepend (pe->auth, &camel_pop3_password_authtype);
return 0;
}
/**
* camel_pop3_engine_new:
* @source: source stream
* @flags: engine flags
*
* Returns a NULL stream. A null stream is always at eof, and
* always returns success for all reads and writes.
*
* Return value: the stream
**/
CamelPOP3Engine *
camel_pop3_engine_new(CamelStream *source, guint32 flags)
{
CamelPOP3Engine *pe;
pe = (CamelPOP3Engine *)camel_object_new(camel_pop3_engine_get_type ());
pe->stream = (CamelPOP3Stream *)camel_pop3_stream_new(source);
pe->state = CAMEL_POP3_ENGINE_AUTH;
pe->flags = flags;
if (read_greeting (pe) == -1) {
camel_object_unref (pe);
return NULL;
}
get_capabilities (pe);
return pe;
}
/**
* camel_pop3_engine_reget_capabilities:
* @engine: pop3 engine
*
* Regets server capabilities (needed after a STLS command is issued for example).
**/
void
camel_pop3_engine_reget_capabilities (CamelPOP3Engine *engine)
{
g_return_if_fail (CAMEL_IS_POP3_ENGINE (engine));
get_capabilities (engine);
}
/* TODO: read implementation too?
etc? */
struct {
char *cap;
guint32 flag;
} capa[] = {
{ "APOP" , CAMEL_POP3_CAP_APOP },
{ "TOP" , CAMEL_POP3_CAP_TOP },
{ "UIDL", CAMEL_POP3_CAP_UIDL },
{ "PIPELINING", CAMEL_POP3_CAP_PIPE },
{ "STLS", CAMEL_POP3_CAP_STLS }, /* STARTTLS */
};
static void
cmd_capa(CamelPOP3Engine *pe, CamelPOP3Stream *stream, void *data)
{
unsigned char *line, *tok, *next;
unsigned int len;
int ret;
int i;
CamelServiceAuthType *auth;
dd(printf("cmd_capa\n"));
do {
ret = camel_pop3_stream_line(stream, &line, &len);
if (ret >= 0) {
if (strncmp(line, "SASL ", 5) == 0) {
tok = line+5;
dd(printf("scanning tokens '%s'\n", tok));
while (tok) {
next = strchr(tok, ' ');
if (next)
*next++ = 0;
auth = camel_sasl_authtype(tok);
if (auth) {
dd(printf("got auth type '%s'\n", tok));
pe->auth = g_list_prepend(pe->auth, auth);
} else {
dd(printf("unsupported auth type '%s'\n", tok));
}
tok = next;
}
} else {
for (i=0;i<sizeof(capa)/sizeof(capa[0]);i++) {
if (strcmp(capa[i].cap, line) == 0)
pe->capa |= capa[i].flag;
}
}
}
} while (ret>0);
}
static void
get_capabilities(CamelPOP3Engine *pe)
{
CamelPOP3Command *pc;
if (!(pe->flags & CAMEL_POP3_ENGINE_DISABLE_EXTENSIONS)) {
pc = camel_pop3_engine_command_new(pe, CAMEL_POP3_COMMAND_MULTI, cmd_capa, NULL, "CAPA\r\n");
while (camel_pop3_engine_iterate(pe, pc) > 0)
;
camel_pop3_engine_command_free(pe, pc);
if (pe->state == CAMEL_POP3_ENGINE_TRANSACTION && !(pe->capa & CAMEL_POP3_CAP_UIDL)) {
/* check for UIDL support manually */
pc = camel_pop3_engine_command_new (pe, CAMEL_POP3_COMMAND_SIMPLE, NULL, NULL, "UIDL 1\r\n");
while (camel_pop3_engine_iterate (pe, pc) > 0)
;
if (pc->state == CAMEL_POP3_COMMAND_OK)
pe->capa |= CAMEL_POP3_CAP_UIDL;
camel_pop3_engine_command_free (pe, pc);
}
}
}
/* returns true if the command was sent, false if it was just queued */
static int
engine_command_queue(CamelPOP3Engine *pe, CamelPOP3Command *pc)
{
if (((pe->capa & CAMEL_POP3_CAP_PIPE) == 0 || (pe->sentlen + strlen(pc->data)) > CAMEL_POP3_SEND_LIMIT)
&& pe->current != NULL) {
e_dlist_addtail(&pe->queue, (EDListNode *)pc);
return FALSE;
} else {
/* ??? */
if (camel_stream_write((CamelStream *)pe->stream, pc->data, strlen(pc->data)) == -1) {
e_dlist_addtail(&pe->queue, (EDListNode *)pc);
return FALSE;
}
pe->sentlen += strlen(pc->data);
pc->state = CAMEL_POP3_COMMAND_DISPATCHED;
if (pe->current == NULL)
pe->current = pc;
else
e_dlist_addtail(&pe->active, (EDListNode *)pc);
return TRUE;
}
}
/* returns -1 on error (sets errno), 0 when no work to do, or >0 if work remaining */
int
camel_pop3_engine_iterate(CamelPOP3Engine *pe, CamelPOP3Command *pcwait)
{
unsigned char *p;
unsigned int len;
CamelPOP3Command *pc, *pw, *pn;
if (pcwait && pcwait->state >= CAMEL_POP3_COMMAND_OK)
return 0;
pc = pe->current;
if (pc == NULL)
return 0;
/* LOCK */
if (camel_pop3_stream_line(pe->stream, &pe->line, &pe->linelen) == -1)
goto ioerror;
p = pe->line;
switch (p[0]) {
case '+':
dd(printf("Got + response\n"));
if (pc->flags & CAMEL_POP3_COMMAND_MULTI) {
pc->state = CAMEL_POP3_COMMAND_DATA;
camel_pop3_stream_set_mode(pe->stream, CAMEL_POP3_STREAM_DATA);
if (pc->func)
pc->func(pe, pe->stream, pc->func_data);
/* Make sure we get all data before going back to command mode */
while (camel_pop3_stream_getd(pe->stream, &p, &len) > 0)
;
camel_pop3_stream_set_mode(pe->stream, CAMEL_POP3_STREAM_LINE);
} else {
pc->state = CAMEL_POP3_COMMAND_OK;
}
break;
case '-':
pc->state = CAMEL_POP3_COMMAND_ERR;
break;
default:
/* what do we do now? f'knows! */
g_warning("Bad server response: %s\n", p);
pc->state = CAMEL_POP3_COMMAND_ERR;
break;
}
e_dlist_addtail(&pe->done, (EDListNode *)pc);
pe->sentlen -= strlen(pc->data);
/* Set next command */
pe->current = (CamelPOP3Command *)e_dlist_remhead(&pe->active);
/* check the queue for sending any we can now send also */
pw = (CamelPOP3Command *)pe->queue.head;
pn = pw->next;
while (pn) {
if (((pe->capa & CAMEL_POP3_CAP_PIPE) == 0 || (pe->sentlen + strlen(pw->data)) > CAMEL_POP3_SEND_LIMIT)
&& pe->current != NULL)
break;
if (camel_stream_write((CamelStream *)pe->stream, pw->data, strlen(pw->data)) == -1)
goto ioerror;
e_dlist_remove((EDListNode *)pw);
pe->sentlen += strlen(pw->data);
pw->state = CAMEL_POP3_COMMAND_DISPATCHED;
if (pe->current == NULL)
pe->current = pw;
else
e_dlist_addtail(&pe->active, (EDListNode *)pw);
pw = pn;
pn = pn->next;
}
/* UNLOCK */
if (pcwait && pcwait->state >= CAMEL_POP3_COMMAND_OK)
return 0;
return pe->current==NULL?0:1;
ioerror:
/* we assume all outstanding commands are gunna fail now */
while ( (pw = (CamelPOP3Command*)e_dlist_remhead(&pe->active)) ) {
pw->state = CAMEL_POP3_COMMAND_ERR;
e_dlist_addtail(&pe->done, (EDListNode *)pw);
}
while ( (pw = (CamelPOP3Command*)e_dlist_remhead(&pe->queue)) ) {
pw->state = CAMEL_POP3_COMMAND_ERR;
e_dlist_addtail(&pe->done, (EDListNode *)pw);
}
if (pe->current) {
pe->current->state = CAMEL_POP3_COMMAND_ERR;
e_dlist_addtail(&pe->done, (EDListNode *)pe->current);
pe->current = NULL;
}
return -1;
}
CamelPOP3Command *
camel_pop3_engine_command_new(CamelPOP3Engine *pe, guint32 flags, CamelPOP3CommandFunc func, void *data, const char *fmt, ...)
{
CamelPOP3Command *pc;
va_list ap;
pc = g_malloc0(sizeof(*pc));
pc->func = func;
pc->func_data = data;
pc->flags = flags;
va_start(ap, fmt);
pc->data = g_strdup_vprintf(fmt, ap);
pc->state = CAMEL_POP3_COMMAND_IDLE;
/* TODO: what about write errors? */
engine_command_queue(pe, pc);
return pc;
}
void
camel_pop3_engine_command_free(CamelPOP3Engine *pe, CamelPOP3Command *pc)
{
if (pe->current != pc)
e_dlist_remove((EDListNode *)pc);
g_free(pc->data);
g_free(pc);
}