Bug #615776 - Drop camel-private.h and offer a public alternative for locks
[platform/upstream/evolution-data-server.git] / camel / providers / pop3 / camel-pop3-engine.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*-
2  *
3  * Author:
4  *  Michael Zucchi <notzed@ximian.com>
5  *
6  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
7  *
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.
11  *
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.
16  *
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
20  * USA
21  */
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include <errno.h>
28 #include <stdio.h>
29 #include <string.h>
30
31 #include <glib.h>
32 #include <glib/gi18n-lib.h>
33
34 #include "camel-pop3-engine.h"
35 #include "camel-pop3-stream.h"
36
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)
40
41 extern CamelServiceAuthType camel_pop3_password_authtype;
42 extern CamelServiceAuthType camel_pop3_apop_authtype;
43
44 extern gint camel_verbose_debug;
45 #define dd(x) (camel_verbose_debug?(x):0)
46
47 static void get_capabilities(CamelPOP3Engine *pe);
48
49 static CamelObjectClass *parent_class = NULL;
50
51 /* Returns the class for a CamelStream */
52 #define CS_CLASS(so) CAMEL_POP3_ENGINE_CLASS(CAMEL_OBJECT_GET_CLASS(so))
53
54 static void
55 camel_pop3_engine_class_init (CamelPOP3EngineClass *camel_pop3_engine_class)
56 {
57         parent_class = camel_type_get_global_classfuncs( CAMEL_TYPE_OBJECT );
58 }
59
60 static void
61 camel_pop3_engine_init(CamelPOP3Engine *pe, CamelPOP3EngineClass *peclass)
62 {
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;
67 }
68
69 static void
70 camel_pop3_engine_finalize(CamelPOP3Engine *pe)
71 {
72         /* FIXME: Also flush/free any outstanding requests, etc */
73
74         if (pe->stream)
75                 camel_object_unref (pe->stream);
76
77         g_list_free(pe->auth);
78         if (pe->apop)
79                 g_free(pe->apop);
80 }
81
82 CamelType
83 camel_pop3_engine_get_type (void)
84 {
85         static CamelType camel_pop3_engine_type = CAMEL_INVALID_TYPE;
86
87         if (camel_pop3_engine_type == CAMEL_INVALID_TYPE) {
88                 camel_pop3_engine_type = camel_type_register(camel_object_get_type(),
89                                                              "CamelPOP3Engine",
90                                                              sizeof( CamelPOP3Engine ),
91                                                              sizeof( CamelPOP3EngineClass ),
92                                                              (CamelObjectClassInitFunc) camel_pop3_engine_class_init,
93                                                              NULL,
94                                                              (CamelObjectInitFunc) camel_pop3_engine_init,
95                                                              (CamelObjectFinalizeFunc) camel_pop3_engine_finalize );
96         }
97
98         return camel_pop3_engine_type;
99 }
100
101 static gint
102 read_greeting (CamelPOP3Engine *pe)
103 {
104         guchar *line, *apop, *apopend;
105         guint len;
106
107         /* first, read the greeting */
108         if (camel_pop3_stream_line (pe->stream, &line, &len) == -1
109             || strncmp ((gchar *) line, "+OK", 3) != 0)
110                 return -1;
111
112         if ((apop = (guchar *) strchr ((gchar *) line + 3, '<'))
113             && (apopend = (guchar *) strchr ((gchar *) apop, '>'))) {
114                 apopend[1] = 0;
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);
118         }
119
120         pe->auth = g_list_prepend (pe->auth, &camel_pop3_password_authtype);
121
122         return 0;
123 }
124
125 /**
126  * camel_pop3_engine_new:
127  * @source: source stream
128  * @flags: engine flags
129  *
130  * Returns a NULL stream.  A null stream is always at eof, and
131  * always returns success for all reads and writes.
132  *
133  * Returns: the stream
134  **/
135 CamelPOP3Engine *
136 camel_pop3_engine_new(CamelStream *source, guint32 flags)
137 {
138         CamelPOP3Engine *pe;
139
140         pe = (CamelPOP3Engine *)camel_object_new(camel_pop3_engine_get_type ());
141
142         pe->stream = (CamelPOP3Stream *)camel_pop3_stream_new(source);
143         pe->state = CAMEL_POP3_ENGINE_AUTH;
144         pe->flags = flags;
145
146         if (read_greeting (pe) == -1) {
147                 camel_object_unref (pe);
148                 return NULL;
149         }
150
151         get_capabilities (pe);
152
153         return pe;
154 }
155
156 /**
157  * camel_pop3_engine_reget_capabilities:
158  * @engine: pop3 engine
159  *
160  * Regets server capabilities (needed after a STLS command is issued for example).
161  **/
162 void
163 camel_pop3_engine_reget_capabilities (CamelPOP3Engine *engine)
164 {
165         g_return_if_fail (CAMEL_IS_POP3_ENGINE (engine));
166
167         get_capabilities (engine);
168 }
169
170 /* TODO: read implementation too?
171    etc? */
172 static struct {
173         const gchar *cap;
174         guint32 flag;
175 } capa[] = {
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 */
181 };
182
183 static void
184 cmd_capa(CamelPOP3Engine *pe, CamelPOP3Stream *stream, gpointer data)
185 {
186         guchar *line, *tok, *next;
187         guint len;
188         gint ret;
189         gint i;
190         CamelServiceAuthType *auth;
191
192         dd(printf("cmd_capa\n"));
193
194         do {
195                 ret = camel_pop3_stream_line(stream, &line, &len);
196                 if (ret >= 0) {
197                         if (strncmp((gchar *) line, "SASL ", 5) == 0) {
198                                 tok = line+5;
199                                 dd(printf("scanning tokens '%s'\n", tok));
200                                 while (tok) {
201                                         next = (guchar *) strchr((gchar *) tok, ' ');
202                                         if (next)
203                                                 *next++ = 0;
204                                         auth = camel_sasl_authtype((const gchar *) tok);
205                                         if (auth) {
206                                                 dd(printf("got auth type '%s'\n", tok));
207                                                 pe->auth = g_list_prepend(pe->auth, auth);
208                                         } else {
209                                                 dd(printf("unsupported auth type '%s'\n", tok));
210                                         }
211                                         tok = next;
212                                 }
213                         } else {
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;
217                                 }
218                         }
219                 }
220         } while (ret>0);
221 }
222
223 static void
224 get_capabilities(CamelPOP3Engine *pe)
225 {
226         CamelPOP3Command *pc;
227
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)
231                         ;
232                 camel_pop3_engine_command_free(pe, pc);
233
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)
238                                 ;
239
240                         if (pc->state == CAMEL_POP3_COMMAND_OK)
241                                 pe->capa |= CAMEL_POP3_CAP_UIDL;
242
243                         camel_pop3_engine_command_free (pe, pc);
244                 }
245         }
246 }
247
248 /* returns true if the command was sent, false if it was just queued */
249 static gint
250 engine_command_queue(CamelPOP3Engine *pe, CamelPOP3Command *pc)
251 {
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);
255                 return FALSE;
256         }
257
258         /* ??? */
259         if (camel_stream_write((CamelStream *)pe->stream, pc->data, strlen(pc->data)) == -1) {
260                 camel_dlist_addtail(&pe->queue, (CamelDListNode *)pc);
261                 return FALSE;
262         }
263
264         pe->sentlen += strlen(pc->data);
265
266         pc->state = CAMEL_POP3_COMMAND_DISPATCHED;
267
268         if (pe->current == NULL)
269                 pe->current = pc;
270         else
271                 camel_dlist_addtail(&pe->active, (CamelDListNode *)pc);
272
273         return TRUE;
274 }
275
276 /* returns -1 on error (sets errno), 0 when no work to do, or >0 if work remaining */
277 gint
278 camel_pop3_engine_iterate(CamelPOP3Engine *pe, CamelPOP3Command *pcwait)
279 {
280         guchar *p;
281         guint len;
282         CamelPOP3Command *pc, *pw, *pn;
283
284         if (pcwait && pcwait->state >= CAMEL_POP3_COMMAND_OK)
285                 return 0;
286
287         pc = pe->current;
288         if (pc == NULL)
289                 return 0;
290
291         /* LOCK */
292
293         if (camel_pop3_stream_line(pe->stream, &pe->line, &pe->linelen) == -1)
294                 goto ioerror;
295
296         p = pe->line;
297         switch (p[0]) {
298         case '+':
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);
303
304                         if (pc->func)
305                                 pc->func(pe, pe->stream, pc->func_data);
306
307                         /* Make sure we get all data before going back to command mode */
308                         while (camel_pop3_stream_getd(pe->stream, &p, &len) > 0)
309                                 ;
310                         camel_pop3_stream_set_mode(pe->stream, CAMEL_POP3_STREAM_LINE);
311                 } else {
312                         pc->state = CAMEL_POP3_COMMAND_OK;
313                 }
314                 break;
315         case '-':
316                 pc->state = CAMEL_POP3_COMMAND_ERR;
317                 break;
318         default:
319                 /* what do we do now?  f'knows! */
320                 g_warning("Bad server response: %s\n", p);
321                 pc->state = CAMEL_POP3_COMMAND_ERR;
322                 break;
323         }
324
325         camel_dlist_addtail(&pe->done, (CamelDListNode *)pc);
326         pe->sentlen -= strlen(pc->data);
327
328         /* Set next command */
329         pe->current = (CamelPOP3Command *)camel_dlist_remhead(&pe->active);
330
331         /* check the queue for sending any we can now send also */
332         pw = (CamelPOP3Command *)pe->queue.head;
333         pn = pw->next;
334
335         while (pn) {
336                 if (((pe->capa & CAMEL_POP3_CAP_PIPE) == 0 || (pe->sentlen + strlen(pw->data)) > CAMEL_POP3_SEND_LIMIT)
337                     && pe->current != NULL)
338                         break;
339
340                 if (camel_stream_write((CamelStream *)pe->stream, pw->data, strlen(pw->data)) == -1)
341                         goto ioerror;
342
343                 camel_dlist_remove((CamelDListNode *)pw);
344
345                 pe->sentlen += strlen(pw->data);
346                 pw->state = CAMEL_POP3_COMMAND_DISPATCHED;
347
348                 if (pe->current == NULL)
349                         pe->current = pw;
350                 else
351                         camel_dlist_addtail(&pe->active, (CamelDListNode *)pw);
352
353                 pw = pn;
354                 pn = pn->next;
355         }
356
357         /* UNLOCK */
358
359         if (pcwait && pcwait->state >= CAMEL_POP3_COMMAND_OK)
360                 return 0;
361
362         return pe->current==NULL?0:1;
363 ioerror:
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);
368         }
369
370         while ((pw = (CamelPOP3Command*)camel_dlist_remhead(&pe->queue))) {
371                 pw->state = CAMEL_POP3_COMMAND_ERR;
372                 camel_dlist_addtail(&pe->done, (CamelDListNode *)pw);
373         }
374
375         if (pe->current) {
376                 pe->current->state = CAMEL_POP3_COMMAND_ERR;
377                 camel_dlist_addtail(&pe->done, (CamelDListNode *)pe->current);
378                 pe->current = NULL;
379         }
380
381         return -1;
382 }
383
384 CamelPOP3Command *
385 camel_pop3_engine_command_new(CamelPOP3Engine *pe, guint32 flags, CamelPOP3CommandFunc func, gpointer data, const gchar *fmt, ...)
386 {
387         CamelPOP3Command *pc;
388         va_list ap;
389
390         pc = g_malloc0(sizeof(*pc));
391         pc->func = func;
392         pc->func_data = data;
393         pc->flags = flags;
394
395         va_start(ap, fmt);
396         pc->data = g_strdup_vprintf(fmt, ap);
397         pc->state = CAMEL_POP3_COMMAND_IDLE;
398
399         /* TODO: what about write errors? */
400         engine_command_queue(pe, pc);
401
402         return pc;
403 }
404
405 void
406 camel_pop3_engine_command_free(CamelPOP3Engine *pe, CamelPOP3Command *pc)
407 {
408         if (pe->current != pc)
409                 camel_dlist_remove((CamelDListNode *)pc);
410         g_free(pc->data);
411         g_free(pc);
412 }