Fix FSF address (Tobias Mueller, #470445)
[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 1999, 2000 Ximian, Inc. (www.ximian.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 #include "camel-sasl.h"
37 #include "camel-service.h"
38
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)
42
43
44 extern int 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_OBJECT_TYPE );
58 }
59
60 static void
61 camel_pop3_engine_init(CamelPOP3Engine *pe, CamelPOP3EngineClass *peclass)
62 {
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;
67 }
68
69 static void
70 camel_pop3_engine_finalise(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_finalise );
96         }
97
98         return camel_pop3_engine_type;
99 }
100
101 static int
102 read_greeting (CamelPOP3Engine *pe)
103 {
104         extern CamelServiceAuthType camel_pop3_password_authtype;
105         extern CamelServiceAuthType camel_pop3_apop_authtype;
106         unsigned char *line, *apop, *apopend;
107         unsigned int len;
108         
109         /* first, read the greeting */
110         if (camel_pop3_stream_line (pe->stream, &line, &len) == -1
111             || strncmp ((char *) line, (char *) "+OK", 3) != 0)
112                 return -1;
113         
114         if ((apop = (unsigned char *) strchr ((char *) line + 3, '<'))
115             && (apopend = (unsigned char *) strchr ((char *) apop, '>'))) {
116                 apopend[1] = 0;
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);
120         }
121         
122         pe->auth = g_list_prepend (pe->auth, &camel_pop3_password_authtype);
123         
124         return 0;
125 }
126
127 /**
128  * camel_pop3_engine_new:
129  * @source: source stream
130  * @flags: engine flags
131  *
132  * Returns a NULL stream.  A null stream is always at eof, and
133  * always returns success for all reads and writes.
134  *
135  * Return value: the stream
136  **/
137 CamelPOP3Engine *
138 camel_pop3_engine_new(CamelStream *source, guint32 flags)
139 {
140         CamelPOP3Engine *pe;
141
142         pe = (CamelPOP3Engine *)camel_object_new(camel_pop3_engine_get_type ());
143
144         pe->stream = (CamelPOP3Stream *)camel_pop3_stream_new(source);
145         pe->state = CAMEL_POP3_ENGINE_AUTH;
146         pe->flags = flags;
147         
148         if (read_greeting (pe) == -1) {
149                 camel_object_unref (pe);
150                 return NULL;
151         }
152         
153         get_capabilities (pe);
154         
155         return pe;
156 }
157
158
159 /**
160  * camel_pop3_engine_reget_capabilities:
161  * @engine: pop3 engine
162  *
163  * Regets server capabilities (needed after a STLS command is issued for example).
164  **/
165 void
166 camel_pop3_engine_reget_capabilities (CamelPOP3Engine *engine)
167 {
168         g_return_if_fail (CAMEL_IS_POP3_ENGINE (engine));
169         
170         get_capabilities (engine);
171 }
172
173
174 /* TODO: read implementation too?
175    etc? */
176 static struct {
177         char *cap;
178         guint32 flag;
179 } capa[] = {
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 */
185 };
186
187 static void
188 cmd_capa(CamelPOP3Engine *pe, CamelPOP3Stream *stream, void *data)
189 {
190         unsigned char *line, *tok, *next;
191         unsigned int len;
192         int ret;
193         int i;
194         CamelServiceAuthType *auth;
195
196         dd(printf("cmd_capa\n"));
197
198         do {
199                 ret = camel_pop3_stream_line(stream, &line, &len);
200                 if (ret >= 0) {
201                         if (strncmp((char *) line, "SASL ", 5) == 0) {
202                                 tok = line+5;
203                                 dd(printf("scanning tokens '%s'\n", tok));
204                                 while (tok) {
205                                         next = (unsigned char *) strchr((char *) tok, ' ');
206                                         if (next)
207                                                 *next++ = 0;
208                                         auth = camel_sasl_authtype((const char *)tok);
209                                         if (auth) {
210                                                 dd(printf("got auth type '%s'\n", tok));
211                                                 pe->auth = g_list_prepend(pe->auth, auth);
212                                         } else {
213                                                 dd(printf("unsupported auth type '%s'\n", tok));
214                                         }
215                                         tok = next;
216                                 }
217                         } else {
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;
221                                 }
222                         }
223                 }
224         } while (ret>0);
225 }
226
227 static void
228 get_capabilities(CamelPOP3Engine *pe)
229 {
230         CamelPOP3Command *pc;
231         
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)
235                         ;
236                 camel_pop3_engine_command_free(pe, pc);
237                 
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)
242                                 ;
243                         
244                         if (pc->state == CAMEL_POP3_COMMAND_OK)
245                                 pe->capa |= CAMEL_POP3_CAP_UIDL;
246                         
247                         camel_pop3_engine_command_free (pe, pc);
248                 }
249         }
250 }
251
252 /* returns true if the command was sent, false if it was just queued */
253 static int
254 engine_command_queue(CamelPOP3Engine *pe, CamelPOP3Command *pc)
255 {
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);
259                 return FALSE;
260         } else {
261                 /* ??? */
262                 if (camel_stream_write((CamelStream *)pe->stream, pc->data, strlen(pc->data)) == -1) {
263                         e_dlist_addtail(&pe->queue, (EDListNode *)pc);
264                         return FALSE;
265                 }
266
267                 pe->sentlen += strlen(pc->data);
268
269                 pc->state = CAMEL_POP3_COMMAND_DISPATCHED;
270
271                 if (pe->current == NULL)
272                         pe->current = pc;
273                 else
274                         e_dlist_addtail(&pe->active, (EDListNode *)pc);
275
276                 return TRUE;
277         }
278 }
279
280 /* returns -1 on error (sets errno), 0 when no work to do, or >0 if work remaining */
281 int
282 camel_pop3_engine_iterate(CamelPOP3Engine *pe, CamelPOP3Command *pcwait)
283 {
284         unsigned char *p;
285         unsigned int len;
286         CamelPOP3Command *pc, *pw, *pn;
287
288         if (pcwait && pcwait->state >= CAMEL_POP3_COMMAND_OK)
289                 return 0;
290
291         pc = pe->current;
292         if (pc == NULL)
293                 return 0;
294
295         /* LOCK */
296
297         if (camel_pop3_stream_line(pe->stream, &pe->line, &pe->linelen) == -1)
298                 goto ioerror;
299
300         p = pe->line;
301         switch (p[0]) {
302         case '+':
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);
307
308                         if (pc->func)
309                                 pc->func(pe, pe->stream, pc->func_data);
310
311                         /* Make sure we get all data before going back to command mode */
312                         while (camel_pop3_stream_getd(pe->stream, &p, &len) > 0)
313                                 ;
314                         camel_pop3_stream_set_mode(pe->stream, CAMEL_POP3_STREAM_LINE);
315                 } else {
316                         pc->state = CAMEL_POP3_COMMAND_OK;
317                 }
318                 break;
319         case '-':
320                 pc->state = CAMEL_POP3_COMMAND_ERR;
321                 break;
322         default:
323                 /* what do we do now?  f'knows! */
324                 g_warning("Bad server response: %s\n", p);
325                 pc->state = CAMEL_POP3_COMMAND_ERR;
326                 break;
327         }
328
329         e_dlist_addtail(&pe->done, (EDListNode *)pc);
330         pe->sentlen -= strlen(pc->data);
331
332         /* Set next command */
333         pe->current = (CamelPOP3Command *)e_dlist_remhead(&pe->active);
334         
335         /* check the queue for sending any we can now send also */
336         pw = (CamelPOP3Command *)pe->queue.head;
337         pn = pw->next;
338         while (pn) {
339                 if (((pe->capa & CAMEL_POP3_CAP_PIPE) == 0 || (pe->sentlen + strlen(pw->data)) > CAMEL_POP3_SEND_LIMIT)
340                     && pe->current != NULL)
341                         break;
342
343                 if (camel_stream_write((CamelStream *)pe->stream, pw->data, strlen(pw->data)) == -1)
344                         goto ioerror;
345
346                 e_dlist_remove((EDListNode *)pw);
347
348                 pe->sentlen += strlen(pw->data);
349                 pw->state = CAMEL_POP3_COMMAND_DISPATCHED;
350
351                 if (pe->current == NULL)
352                         pe->current = pw;
353                 else
354                         e_dlist_addtail(&pe->active, (EDListNode *)pw);
355
356                 pw = pn;
357                 pn = pn->next;
358         }
359
360         /* UNLOCK */
361
362         if (pcwait && pcwait->state >= CAMEL_POP3_COMMAND_OK)
363                 return 0;
364
365         return pe->current==NULL?0:1;
366 ioerror:
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);
371         }
372
373         while ( (pw = (CamelPOP3Command*)e_dlist_remhead(&pe->queue)) ) {
374                 pw->state = CAMEL_POP3_COMMAND_ERR;
375                 e_dlist_addtail(&pe->done, (EDListNode *)pw);
376         }
377
378         if (pe->current) {
379                 pe->current->state = CAMEL_POP3_COMMAND_ERR;
380                 e_dlist_addtail(&pe->done, (EDListNode *)pe->current);
381                 pe->current = NULL;
382         }
383
384         return -1;
385 }
386
387 CamelPOP3Command *
388 camel_pop3_engine_command_new(CamelPOP3Engine *pe, guint32 flags, CamelPOP3CommandFunc func, void *data, const char *fmt, ...)
389 {
390         CamelPOP3Command *pc;
391         va_list ap;
392
393         pc = g_malloc0(sizeof(*pc));
394         pc->func = func;
395         pc->func_data = data;
396         pc->flags = flags;
397         
398         va_start(ap, fmt);
399         pc->data = g_strdup_vprintf(fmt, ap);
400         pc->state = CAMEL_POP3_COMMAND_IDLE;
401
402         /* TODO: what about write errors? */
403         engine_command_queue(pe, pc);
404
405         return pc;
406 }
407
408 void
409 camel_pop3_engine_command_free(CamelPOP3Engine *pe, CamelPOP3Command *pc)
410 {
411         if (pe->current != pc)
412                 e_dlist_remove((EDListNode *)pc);
413         g_free(pc->data);
414         g_free(pc);
415 }