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 (C) 1999-2008 Novell, Inc. (www.novell.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"
37 /* max 'outstanding' bytes in output stream, so we can't deadlock waiting
38 for the server to accept our data when pipelining */
39 #define CAMEL_POP3_SEND_LIMIT (1024)
41 extern CamelServiceAuthType camel_pop3_password_authtype;
42 extern CamelServiceAuthType camel_pop3_apop_authtype;
44 extern gint 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_TYPE_OBJECT );
61 camel_pop3_engine_init(CamelPOP3Engine *pe, CamelPOP3EngineClass *peclass)
63 camel_dlist_init(&pe->active);
64 camel_dlist_init(&pe->queue);
65 camel_dlist_init(&pe->done);
66 pe->state = CAMEL_POP3_ENGINE_DISCONNECT;
70 camel_pop3_engine_finalize(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_finalize );
98 return camel_pop3_engine_type;
102 read_greeting (CamelPOP3Engine *pe)
104 guchar *line, *apop, *apopend;
107 /* first, read the greeting */
108 if (camel_pop3_stream_line (pe->stream, &line, &len) == -1
109 || strncmp ((gchar *) line, "+OK", 3) != 0)
112 if ((apop = (guchar *) strchr ((gchar *) line + 3, '<'))
113 && (apopend = (guchar *) strchr ((gchar *) apop, '>'))) {
115 pe->apop = g_strdup ((gchar *) apop);
116 pe->capa = CAMEL_POP3_CAP_APOP;
117 pe->auth = g_list_append (pe->auth, &camel_pop3_apop_authtype);
120 pe->auth = g_list_prepend (pe->auth, &camel_pop3_password_authtype);
126 * camel_pop3_engine_new:
127 * @source: source stream
128 * @flags: engine flags
130 * Returns a NULL stream. A null stream is always at eof, and
131 * always returns success for all reads and writes.
133 * Returns: the stream
136 camel_pop3_engine_new(CamelStream *source, guint32 flags)
140 pe = (CamelPOP3Engine *)camel_object_new(camel_pop3_engine_get_type ());
142 pe->stream = (CamelPOP3Stream *)camel_pop3_stream_new(source);
143 pe->state = CAMEL_POP3_ENGINE_AUTH;
146 if (read_greeting (pe) == -1) {
147 camel_object_unref (pe);
151 get_capabilities (pe);
157 * camel_pop3_engine_reget_capabilities:
158 * @engine: pop3 engine
160 * Regets server capabilities (needed after a STLS command is issued for example).
163 camel_pop3_engine_reget_capabilities (CamelPOP3Engine *engine)
165 g_return_if_fail (CAMEL_IS_POP3_ENGINE (engine));
167 get_capabilities (engine);
170 /* TODO: read implementation too?
176 { "APOP" , CAMEL_POP3_CAP_APOP },
177 { "TOP" , CAMEL_POP3_CAP_TOP },
178 { "UIDL", CAMEL_POP3_CAP_UIDL },
179 { "PIPELINING", CAMEL_POP3_CAP_PIPE },
180 { "STLS", CAMEL_POP3_CAP_STLS }, /* STARTTLS */
184 cmd_capa(CamelPOP3Engine *pe, CamelPOP3Stream *stream, gpointer data)
186 guchar *line, *tok, *next;
190 CamelServiceAuthType *auth;
192 dd(printf("cmd_capa\n"));
195 ret = camel_pop3_stream_line(stream, &line, &len);
197 if (strncmp((gchar *) line, "SASL ", 5) == 0) {
199 dd(printf("scanning tokens '%s'\n", tok));
201 next = (guchar *) strchr((gchar *) tok, ' ');
204 auth = camel_sasl_authtype((const gchar *) tok);
206 dd(printf("got auth type '%s'\n", tok));
207 pe->auth = g_list_prepend(pe->auth, auth);
209 dd(printf("unsupported auth type '%s'\n", tok));
214 for (i = 0; i < G_N_ELEMENTS (capa); i++) {
215 if (strcmp((gchar *) capa[i].cap, (gchar *) line) == 0)
216 pe->capa |= capa[i].flag;
224 get_capabilities(CamelPOP3Engine *pe)
226 CamelPOP3Command *pc;
228 if (!(pe->flags & CAMEL_POP3_ENGINE_DISABLE_EXTENSIONS)) {
229 pc = camel_pop3_engine_command_new(pe, CAMEL_POP3_COMMAND_MULTI, cmd_capa, NULL, "CAPA\r\n");
230 while (camel_pop3_engine_iterate(pe, pc) > 0)
232 camel_pop3_engine_command_free(pe, pc);
234 if (pe->state == CAMEL_POP3_ENGINE_TRANSACTION && !(pe->capa & CAMEL_POP3_CAP_UIDL)) {
235 /* check for UIDL support manually */
236 pc = camel_pop3_engine_command_new (pe, CAMEL_POP3_COMMAND_SIMPLE, NULL, NULL, "UIDL 1\r\n");
237 while (camel_pop3_engine_iterate (pe, pc) > 0)
240 if (pc->state == CAMEL_POP3_COMMAND_OK)
241 pe->capa |= CAMEL_POP3_CAP_UIDL;
243 camel_pop3_engine_command_free (pe, pc);
248 /* returns true if the command was sent, false if it was just queued */
250 engine_command_queue(CamelPOP3Engine *pe, CamelPOP3Command *pc)
252 if (((pe->capa & CAMEL_POP3_CAP_PIPE) == 0 || (pe->sentlen + strlen(pc->data)) > CAMEL_POP3_SEND_LIMIT)
253 && pe->current != NULL) {
254 camel_dlist_addtail(&pe->queue, (CamelDListNode *)pc);
259 if (camel_stream_write((CamelStream *)pe->stream, pc->data, strlen(pc->data)) == -1) {
260 camel_dlist_addtail(&pe->queue, (CamelDListNode *)pc);
264 pe->sentlen += strlen(pc->data);
266 pc->state = CAMEL_POP3_COMMAND_DISPATCHED;
268 if (pe->current == NULL)
271 camel_dlist_addtail(&pe->active, (CamelDListNode *)pc);
276 /* returns -1 on error (sets errno), 0 when no work to do, or >0 if work remaining */
278 camel_pop3_engine_iterate(CamelPOP3Engine *pe, CamelPOP3Command *pcwait)
282 CamelPOP3Command *pc, *pw, *pn;
284 if (pcwait && pcwait->state >= CAMEL_POP3_COMMAND_OK)
293 if (camel_pop3_stream_line(pe->stream, &pe->line, &pe->linelen) == -1)
299 dd(printf("Got + response\n"));
300 if (pc->flags & CAMEL_POP3_COMMAND_MULTI) {
301 pc->state = CAMEL_POP3_COMMAND_DATA;
302 camel_pop3_stream_set_mode(pe->stream, CAMEL_POP3_STREAM_DATA);
305 pc->func(pe, pe->stream, pc->func_data);
307 /* Make sure we get all data before going back to command mode */
308 while (camel_pop3_stream_getd(pe->stream, &p, &len) > 0)
310 camel_pop3_stream_set_mode(pe->stream, CAMEL_POP3_STREAM_LINE);
312 pc->state = CAMEL_POP3_COMMAND_OK;
316 pc->state = CAMEL_POP3_COMMAND_ERR;
319 /* what do we do now? f'knows! */
320 g_warning("Bad server response: %s\n", p);
321 pc->state = CAMEL_POP3_COMMAND_ERR;
325 camel_dlist_addtail(&pe->done, (CamelDListNode *)pc);
326 pe->sentlen -= strlen(pc->data);
328 /* Set next command */
329 pe->current = (CamelPOP3Command *)camel_dlist_remhead(&pe->active);
331 /* check the queue for sending any we can now send also */
332 pw = (CamelPOP3Command *)pe->queue.head;
336 if (((pe->capa & CAMEL_POP3_CAP_PIPE) == 0 || (pe->sentlen + strlen(pw->data)) > CAMEL_POP3_SEND_LIMIT)
337 && pe->current != NULL)
340 if (camel_stream_write((CamelStream *)pe->stream, pw->data, strlen(pw->data)) == -1)
343 camel_dlist_remove((CamelDListNode *)pw);
345 pe->sentlen += strlen(pw->data);
346 pw->state = CAMEL_POP3_COMMAND_DISPATCHED;
348 if (pe->current == NULL)
351 camel_dlist_addtail(&pe->active, (CamelDListNode *)pw);
359 if (pcwait && pcwait->state >= CAMEL_POP3_COMMAND_OK)
362 return pe->current==NULL?0:1;
364 /* we assume all outstanding commands are gunna fail now */
365 while ((pw = (CamelPOP3Command*)camel_dlist_remhead(&pe->active))) {
366 pw->state = CAMEL_POP3_COMMAND_ERR;
367 camel_dlist_addtail(&pe->done, (CamelDListNode *)pw);
370 while ((pw = (CamelPOP3Command*)camel_dlist_remhead(&pe->queue))) {
371 pw->state = CAMEL_POP3_COMMAND_ERR;
372 camel_dlist_addtail(&pe->done, (CamelDListNode *)pw);
376 pe->current->state = CAMEL_POP3_COMMAND_ERR;
377 camel_dlist_addtail(&pe->done, (CamelDListNode *)pe->current);
385 camel_pop3_engine_command_new(CamelPOP3Engine *pe, guint32 flags, CamelPOP3CommandFunc func, gpointer data, const gchar *fmt, ...)
387 CamelPOP3Command *pc;
390 pc = g_malloc0(sizeof(*pc));
392 pc->func_data = data;
396 pc->data = g_strdup_vprintf(fmt, ap);
397 pc->state = CAMEL_POP3_COMMAND_IDLE;
399 /* TODO: what about write errors? */
400 engine_command_queue(pe, pc);
406 camel_pop3_engine_command_free(CamelPOP3Engine *pe, CamelPOP3Command *pc)
408 if (pe->current != pc)
409 camel_dlist_remove((CamelDListNode *)pc);