1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*-
4 * Michael Zucchi <notzed@ximian.com>
6 * Copyright 1999, 2000 Ximian, Inc. (www.ximian.com)
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of version 2 of the GNU Lesser General Public
10 * License as published by the Free Software Foundation.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
32 #include <glib/gi18n-lib.h>
34 #include "camel-pop3-engine.h"
35 #include "camel-pop3-stream.h"
36 #include "camel-sasl.h"
37 #include "camel-service.h"
39 /* max 'outstanding' bytes in output stream, so we can't deadlock waiting
40 for the server to accept our data when pipelining */
41 #define CAMEL_POP3_SEND_LIMIT (1024)
44 extern int camel_verbose_debug;
45 #define dd(x) (camel_verbose_debug?(x):0)
47 static void get_capabilities(CamelPOP3Engine *pe);
49 static CamelObjectClass *parent_class = NULL;
51 /* Returns the class for a CamelStream */
52 #define CS_CLASS(so) CAMEL_POP3_ENGINE_CLASS(CAMEL_OBJECT_GET_CLASS(so))
55 camel_pop3_engine_class_init (CamelPOP3EngineClass *camel_pop3_engine_class)
57 parent_class = camel_type_get_global_classfuncs( CAMEL_OBJECT_TYPE );
61 camel_pop3_engine_init(CamelPOP3Engine *pe, CamelPOP3EngineClass *peclass)
63 e_dlist_init(&pe->active);
64 e_dlist_init(&pe->queue);
65 e_dlist_init(&pe->done);
66 pe->state = CAMEL_POP3_ENGINE_DISCONNECT;
70 camel_pop3_engine_finalise(CamelPOP3Engine *pe)
72 /* FIXME: Also flush/free any outstanding requests, etc */
75 camel_object_unref(pe->stream);
77 g_list_free(pe->auth);
83 camel_pop3_engine_get_type (void)
85 static CamelType camel_pop3_engine_type = CAMEL_INVALID_TYPE;
87 if (camel_pop3_engine_type == CAMEL_INVALID_TYPE) {
88 camel_pop3_engine_type = camel_type_register(camel_object_get_type(),
90 sizeof( CamelPOP3Engine ),
91 sizeof( CamelPOP3EngineClass ),
92 (CamelObjectClassInitFunc) camel_pop3_engine_class_init,
94 (CamelObjectInitFunc) camel_pop3_engine_init,
95 (CamelObjectFinalizeFunc) camel_pop3_engine_finalise );
98 return camel_pop3_engine_type;
102 read_greeting (CamelPOP3Engine *pe)
104 extern CamelServiceAuthType camel_pop3_password_authtype;
105 extern CamelServiceAuthType camel_pop3_apop_authtype;
106 unsigned char *line, *apop, *apopend;
109 /* first, read the greeting */
110 if (camel_pop3_stream_line (pe->stream, &line, &len) == -1
111 || strncmp ((char *) line, (char *) "+OK", 3) != 0)
114 if ((apop = (unsigned char *) strchr ((char *) line + 3, '<'))
115 && (apopend = (unsigned char *) strchr ((char *) apop, '>'))) {
117 pe->apop = g_strdup ((gchar *) apop);
118 pe->capa = CAMEL_POP3_CAP_APOP;
119 pe->auth = g_list_append (pe->auth, &camel_pop3_apop_authtype);
122 pe->auth = g_list_prepend (pe->auth, &camel_pop3_password_authtype);
128 * camel_pop3_engine_new:
129 * @source: source stream
130 * @flags: engine flags
132 * Returns a NULL stream. A null stream is always at eof, and
133 * always returns success for all reads and writes.
135 * Return value: the stream
138 camel_pop3_engine_new(CamelStream *source, guint32 flags)
142 pe = (CamelPOP3Engine *)camel_object_new(camel_pop3_engine_get_type ());
144 pe->stream = (CamelPOP3Stream *)camel_pop3_stream_new(source);
145 pe->state = CAMEL_POP3_ENGINE_AUTH;
148 if (read_greeting (pe) == -1) {
149 camel_object_unref (pe);
153 get_capabilities (pe);
160 * camel_pop3_engine_reget_capabilities:
161 * @engine: pop3 engine
163 * Regets server capabilities (needed after a STLS command is issued for example).
166 camel_pop3_engine_reget_capabilities (CamelPOP3Engine *engine)
168 g_return_if_fail (CAMEL_IS_POP3_ENGINE (engine));
170 get_capabilities (engine);
174 /* TODO: read implementation too?
180 { "APOP" , CAMEL_POP3_CAP_APOP },
181 { "TOP" , CAMEL_POP3_CAP_TOP },
182 { "UIDL", CAMEL_POP3_CAP_UIDL },
183 { "PIPELINING", CAMEL_POP3_CAP_PIPE },
184 { "STLS", CAMEL_POP3_CAP_STLS }, /* STARTTLS */
188 cmd_capa(CamelPOP3Engine *pe, CamelPOP3Stream *stream, void *data)
190 unsigned char *line, *tok, *next;
194 CamelServiceAuthType *auth;
196 dd(printf("cmd_capa\n"));
199 ret = camel_pop3_stream_line(stream, &line, &len);
201 if (strncmp((char *) line, "SASL ", 5) == 0) {
203 dd(printf("scanning tokens '%s'\n", tok));
205 next = (unsigned char *) strchr((char *) tok, ' ');
208 auth = camel_sasl_authtype((const char *)tok);
210 dd(printf("got auth type '%s'\n", tok));
211 pe->auth = g_list_prepend(pe->auth, auth);
213 dd(printf("unsupported auth type '%s'\n", tok));
218 for (i=0;i<sizeof(capa)/sizeof(capa[0]);i++) {
219 if (strcmp((char *) capa[i].cap, (char *) line) == 0)
220 pe->capa |= capa[i].flag;
228 get_capabilities(CamelPOP3Engine *pe)
230 CamelPOP3Command *pc;
232 if (!(pe->flags & CAMEL_POP3_ENGINE_DISABLE_EXTENSIONS)) {
233 pc = camel_pop3_engine_command_new(pe, CAMEL_POP3_COMMAND_MULTI, cmd_capa, NULL, "CAPA\r\n");
234 while (camel_pop3_engine_iterate(pe, pc) > 0)
236 camel_pop3_engine_command_free(pe, pc);
238 if (pe->state == CAMEL_POP3_ENGINE_TRANSACTION && !(pe->capa & CAMEL_POP3_CAP_UIDL)) {
239 /* check for UIDL support manually */
240 pc = camel_pop3_engine_command_new (pe, CAMEL_POP3_COMMAND_SIMPLE, NULL, NULL, "UIDL 1\r\n");
241 while (camel_pop3_engine_iterate (pe, pc) > 0)
244 if (pc->state == CAMEL_POP3_COMMAND_OK)
245 pe->capa |= CAMEL_POP3_CAP_UIDL;
247 camel_pop3_engine_command_free (pe, pc);
252 /* returns true if the command was sent, false if it was just queued */
254 engine_command_queue(CamelPOP3Engine *pe, CamelPOP3Command *pc)
256 if (((pe->capa & CAMEL_POP3_CAP_PIPE) == 0 || (pe->sentlen + strlen(pc->data)) > CAMEL_POP3_SEND_LIMIT)
257 && pe->current != NULL) {
258 e_dlist_addtail(&pe->queue, (EDListNode *)pc);
262 if (camel_stream_write((CamelStream *)pe->stream, pc->data, strlen(pc->data)) == -1) {
263 e_dlist_addtail(&pe->queue, (EDListNode *)pc);
267 pe->sentlen += strlen(pc->data);
269 pc->state = CAMEL_POP3_COMMAND_DISPATCHED;
271 if (pe->current == NULL)
274 e_dlist_addtail(&pe->active, (EDListNode *)pc);
280 /* returns -1 on error (sets errno), 0 when no work to do, or >0 if work remaining */
282 camel_pop3_engine_iterate(CamelPOP3Engine *pe, CamelPOP3Command *pcwait)
286 CamelPOP3Command *pc, *pw, *pn;
288 if (pcwait && pcwait->state >= CAMEL_POP3_COMMAND_OK)
297 if (camel_pop3_stream_line(pe->stream, &pe->line, &pe->linelen) == -1)
303 dd(printf("Got + response\n"));
304 if (pc->flags & CAMEL_POP3_COMMAND_MULTI) {
305 pc->state = CAMEL_POP3_COMMAND_DATA;
306 camel_pop3_stream_set_mode(pe->stream, CAMEL_POP3_STREAM_DATA);
309 pc->func(pe, pe->stream, pc->func_data);
311 /* Make sure we get all data before going back to command mode */
312 while (camel_pop3_stream_getd(pe->stream, &p, &len) > 0)
314 camel_pop3_stream_set_mode(pe->stream, CAMEL_POP3_STREAM_LINE);
316 pc->state = CAMEL_POP3_COMMAND_OK;
320 pc->state = CAMEL_POP3_COMMAND_ERR;
323 /* what do we do now? f'knows! */
324 g_warning("Bad server response: %s\n", p);
325 pc->state = CAMEL_POP3_COMMAND_ERR;
329 e_dlist_addtail(&pe->done, (EDListNode *)pc);
330 pe->sentlen -= strlen(pc->data);
332 /* Set next command */
333 pe->current = (CamelPOP3Command *)e_dlist_remhead(&pe->active);
335 /* check the queue for sending any we can now send also */
336 pw = (CamelPOP3Command *)pe->queue.head;
339 if (((pe->capa & CAMEL_POP3_CAP_PIPE) == 0 || (pe->sentlen + strlen(pw->data)) > CAMEL_POP3_SEND_LIMIT)
340 && pe->current != NULL)
343 if (camel_stream_write((CamelStream *)pe->stream, pw->data, strlen(pw->data)) == -1)
346 e_dlist_remove((EDListNode *)pw);
348 pe->sentlen += strlen(pw->data);
349 pw->state = CAMEL_POP3_COMMAND_DISPATCHED;
351 if (pe->current == NULL)
354 e_dlist_addtail(&pe->active, (EDListNode *)pw);
362 if (pcwait && pcwait->state >= CAMEL_POP3_COMMAND_OK)
365 return pe->current==NULL?0:1;
367 /* we assume all outstanding commands are gunna fail now */
368 while ( (pw = (CamelPOP3Command*)e_dlist_remhead(&pe->active)) ) {
369 pw->state = CAMEL_POP3_COMMAND_ERR;
370 e_dlist_addtail(&pe->done, (EDListNode *)pw);
373 while ( (pw = (CamelPOP3Command*)e_dlist_remhead(&pe->queue)) ) {
374 pw->state = CAMEL_POP3_COMMAND_ERR;
375 e_dlist_addtail(&pe->done, (EDListNode *)pw);
379 pe->current->state = CAMEL_POP3_COMMAND_ERR;
380 e_dlist_addtail(&pe->done, (EDListNode *)pe->current);
388 camel_pop3_engine_command_new(CamelPOP3Engine *pe, guint32 flags, CamelPOP3CommandFunc func, void *data, const char *fmt, ...)
390 CamelPOP3Command *pc;
393 pc = g_malloc0(sizeof(*pc));
395 pc->func_data = data;
399 pc->data = g_strdup_vprintf(fmt, ap);
400 pc->state = CAMEL_POP3_COMMAND_IDLE;
402 /* TODO: what about write errors? */
403 engine_command_queue(pe, pc);
409 camel_pop3_engine_command_free(CamelPOP3Engine *pe, CamelPOP3Command *pc)
411 if (pe->current != pc)
412 e_dlist_remove((EDListNode *)pc);