Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / camel / providers / nntp / camel-nntp-store.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* 
3  *
4  * Copyright (C) 2001-2003 Ximian, Inc. <www.ximain.com>
5  *
6  * Authors: Christopher Toshok <toshok@ximian.com>
7  *          Michael Zucchi <notzed@ximian.com>
8  *
9  * This program is free software; you can redistribute it and/or 
10  * modify it under the terms of version 2 of the GNU Lesser General Public 
11  * License as published by the Free Software Foundation.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
21  * USA
22  */
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include <errno.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <unistd.h>
33 #include <sys/types.h>
34
35 #include <glib/gi18n-lib.h>
36
37 #include "camel/camel-data-cache.h"
38 #include "camel/camel-debug.h"
39 #include "camel/camel-disco-diary.h"
40 #include "camel/camel-disco-store.h"
41 #include "camel/camel-net-utils.h"
42 #include "camel/camel-private.h"
43 #include "camel/camel-session.h"
44 #include "camel/camel-stream-mem.h"
45 #include "camel/camel-string-utils.h"
46 #include "camel/camel-tcp-stream-raw.h"
47 #include "camel/camel-tcp-stream-ssl.h"
48 #include "camel/camel-url.h"
49
50 #include "camel-nntp-summary.h"
51 #include "camel-nntp-store.h"
52 #include "camel-nntp-store-summary.h"
53 #include "camel-nntp-folder.h"
54 #include "camel-nntp-private.h"
55 #include "camel-nntp-resp-codes.h"
56
57 #define w(x)
58 #define dd(x) (camel_debug("nntp")?(x):0)
59
60 #define NNTP_PORT  "119"
61 #define NNTPS_PORT "563"
62
63 #define DUMP_EXTENSIONS
64
65 static CamelDiscoStoreClass *parent_class = NULL;
66 static CamelServiceClass *service_class = NULL;
67
68 /* Returns the class for a CamelNNTPStore */
69 #define CNNTPS_CLASS(so) CAMEL_NNTP_STORE_CLASS (CAMEL_OBJECT_GET_CLASS(so))
70 #define CF_CLASS(so) CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
71 #define CNNTPF_CLASS(so) CAMEL_NNTP_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
72
73 static int camel_nntp_try_authenticate (CamelNNTPStore *store, CamelException *ex);
74
75 static void nntp_construct (CamelService *service, CamelSession *session,
76                             CamelProvider *provider, CamelURL *url,
77                             CamelException *ex);
78
79
80 static gboolean
81 nntp_can_work_offline(CamelDiscoStore *store)
82 {
83         return TRUE;
84 }
85
86 static struct {
87         const char *name;
88         int type;
89 } headers[] = {
90         { "subject", 0 },
91         { "from", 0 },
92         { "date", 0 },
93         { "message-id", 1 },
94         { "references", 0 },
95         { "bytes", 2 },
96 };
97
98 static int
99 xover_setup(CamelNNTPStore *store, CamelException *ex)
100 {
101         int ret, i;
102         char *line;
103         unsigned int len;
104         unsigned char c, *p;
105         struct _xover_header *xover, *last;
106
107         /* manual override */
108         if (store->xover || getenv("CAMEL_NNTP_DISABLE_XOVER") != NULL)
109                 return 0;
110
111         ret = camel_nntp_raw_command_auth(store, ex, &line, "list overview.fmt");
112         if (ret == -1) {
113                 return -1;
114         } else if (ret != 215)
115                 /* unsupported command?  ignore */
116                 return 0;
117
118         last = (struct _xover_header *)&store->xover;
119
120         /* supported command */
121         while ((ret = camel_nntp_stream_line(store->stream, (unsigned char **)&line, &len)) > 0) {
122                 p = line;
123                 xover = g_malloc0(sizeof(*xover));
124                 last->next = xover;
125                 last = xover;
126                 while ((c = *p++)) {
127                         if (c == ':') {
128                                 p[-1] = 0;
129                                 for (i=0;i<sizeof(headers)/sizeof(headers[0]);i++) {
130                                         if (strcmp(line, headers[i].name) == 0) {
131                                                 xover->name = headers[i].name;
132                                                 if (strncmp(p, "full", 4) == 0)
133                                                         xover->skip = strlen(xover->name)+1;
134                                                 else
135                                                         xover->skip = 0;
136                                                 xover->type = headers[i].type;
137                                                 break;
138                                         }
139                                 }
140                                 break;
141                         } else {
142                                 p[-1] = camel_tolower(c);
143                         }
144                 }
145         }
146
147         return ret;
148 }
149
150 enum {
151         MODE_CLEAR,
152         MODE_SSL,
153         MODE_TLS,
154 };
155
156 #ifdef HAVE_SSL
157 #define SSL_PORT_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_SSL2 | CAMEL_TCP_STREAM_SSL_ENABLE_SSL3)
158 #define STARTTLS_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_TLS)
159 #endif
160
161 static gboolean
162 connect_to_server (CamelService *service, struct addrinfo *ai, int ssl_mode, CamelException *ex)
163 {
164         CamelNNTPStore *store = (CamelNNTPStore *) service;
165         CamelDiscoStore *disco_store = (CamelDiscoStore*) service;
166         CamelStream *tcp_stream;
167         gboolean retval = FALSE;
168         unsigned char *buf;
169         unsigned int len;
170         char *path;
171         
172         CAMEL_SERVICE_REC_LOCK(store, connect_lock);
173
174         if (ssl_mode != MODE_CLEAR) {
175 #ifdef HAVE_SSL
176                 if (ssl_mode == MODE_TLS) {
177                         tcp_stream = camel_tcp_stream_ssl_new_raw (service->session, service->url->host, STARTTLS_FLAGS);
178                 } else {
179                         tcp_stream = camel_tcp_stream_ssl_new (service->session, service->url->host, SSL_PORT_FLAGS);
180                 }
181 #else
182                 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
183                                       _("Could not connect to %s: %s"),
184                                       service->url->host, _("SSL unavailable"));
185                 
186                 goto fail;
187 #endif /* HAVE_SSL */
188         } else {
189                 tcp_stream = camel_tcp_stream_raw_new ();
190         }
191         
192         if (camel_tcp_stream_connect ((CamelTcpStream *) tcp_stream, ai) == -1) {
193                 if (errno == EINTR)
194                         camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
195                                              _("Connection canceled"));
196                 else
197                         camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
198                                               _("Could not connect to %s: %s"),
199                                               service->url->host,
200                                               g_strerror (errno));
201                 
202                 camel_object_unref (tcp_stream);
203                 
204                 goto fail;
205         }
206         
207         store->stream = (CamelNNTPStream *) camel_nntp_stream_new (tcp_stream);
208         camel_object_unref (tcp_stream);
209         
210         /* Read the greeting, if any. */
211         if (camel_nntp_stream_line (store->stream, &buf, &len) == -1) {
212                 if (errno == EINTR)
213                         camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
214                                              _("Connection canceled"));
215                 else
216                         camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
217                                               _("Could not read greeting from %s: %s"),
218                                               service->url->host, g_strerror (errno));
219                 
220                 camel_object_unref (store->stream);
221                 store->stream = NULL;
222                 
223                 goto fail;
224         }
225         
226         len = strtoul (buf, (char **) &buf, 10);
227         if (len != 200 && len != 201) {
228                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
229                                       _("NNTP server %s returned error code %d: %s"),
230                                       service->url->host, len, buf);
231                 
232                 camel_object_unref (store->stream);
233                 store->stream = NULL;
234                 
235                 goto fail;
236         }
237
238         /* if we have username, try it here */
239         if (service->url->user != NULL
240             && service->url->user[0]
241             && camel_nntp_try_authenticate(store, ex) != NNTP_AUTH_ACCEPTED)
242                 goto fail;
243         
244         /* set 'reader' mode & ignore return code, also ping the server, inn goes offline very quickly otherwise */
245         if (camel_nntp_raw_command_auth (store, ex, (char **) &buf, "mode reader") == -1
246             || camel_nntp_raw_command_auth (store, ex, (char **) &buf, "date") == -1)
247                 goto fail;
248
249         if (xover_setup(store, ex) == -1)
250                 goto fail;
251         
252         path = g_build_filename (store->storage_path, ".ev-journal", NULL);
253         disco_store->diary = camel_disco_diary_new (disco_store, path, ex);
254         g_free (path);  
255         
256         retval = TRUE;
257
258         g_free(store->current_folder);
259         store->current_folder = NULL;
260         
261  fail:
262         CAMEL_SERVICE_REC_UNLOCK(store, connect_lock);
263         return retval;
264 }
265
266 static struct {
267         char *value;
268         char *serv;
269         char *port;
270         int mode;
271 } ssl_options[] = {
272         { "",              "nntps", NNTPS_PORT, MODE_SSL   },  /* really old (1.x) */
273         { "always",        "nntps", NNTPS_PORT, MODE_SSL   },
274         { "when-possible", "nntp",  NNTP_PORT, MODE_TLS   },
275         { "never",         "nntp",  NNTP_PORT, MODE_CLEAR },
276         { NULL,            "nntp",  NNTP_PORT, MODE_CLEAR },
277 };
278
279 static gboolean
280 nntp_connect_online (CamelService *service, CamelException *ex)
281 {
282         struct addrinfo hints, *ai;
283         const char *ssl_mode;
284         int mode, ret, i;
285         char *serv;
286         const char *port;
287         
288         if ((ssl_mode = camel_url_get_param (service->url, "use_ssl"))) {
289                 for (i = 0; ssl_options[i].value; i++)
290                         if (!strcmp (ssl_options[i].value, ssl_mode))
291                                 break;
292                 mode = ssl_options[i].mode;
293                 serv = ssl_options[i].serv;
294                 port = ssl_options[i].port;
295         } else {
296                 mode = MODE_CLEAR;
297                 serv = "nntp";
298                 port = NNTP_PORT;
299         }
300         
301         if (service->url->port) {
302                 serv = g_alloca (16);
303                 sprintf (serv, "%d", service->url->port);
304                 port = NULL;
305         }
306         
307         memset (&hints, 0, sizeof (hints));
308         hints.ai_socktype = SOCK_STREAM;
309         hints.ai_family = PF_UNSPEC;
310         ai = camel_getaddrinfo(service->url->host, serv, &hints, ex);
311         if (ai == NULL && port != NULL && camel_exception_get_id(ex) != CAMEL_EXCEPTION_USER_CANCEL) {
312                 camel_exception_clear (ex);
313                 ai = camel_getaddrinfo(service->url->host, port, &hints, ex);
314         }
315         if (ai == NULL)
316                 return FALSE;
317         
318         ret = connect_to_server (service, ai, mode, ex);
319         
320         camel_freeaddrinfo (ai);
321         
322         return ret;
323 }
324
325 static gboolean
326 nntp_connect_offline (CamelService *service, CamelException *ex)
327 {
328         CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE(service);
329         CamelDiscoStore *disco_store = (CamelDiscoStore *) nntp_store;
330         char *path;
331         
332         if (nntp_store->storage_path == NULL)
333                 return FALSE;
334         
335         /* setup store-wide cache */
336         if (nntp_store->cache == NULL) {
337                 nntp_store->cache = camel_data_cache_new (nntp_store->storage_path, 0, ex);
338                 if (nntp_store->cache == NULL)
339                         return FALSE;
340                 
341                 /* Default cache expiry - 2 weeks old, or not visited in 5 days */
342                 camel_data_cache_set_expire_age (nntp_store->cache, 60*60*24*14);
343                 camel_data_cache_set_expire_access (nntp_store->cache, 60*60*24*5);
344         }       
345         
346         path = g_build_filename (nntp_store->storage_path, ".ev-journal", NULL);
347         disco_store->diary = camel_disco_diary_new (disco_store, path, ex);
348         g_free (path);
349         
350         if (!disco_store->diary)
351                 return FALSE;
352         
353         return TRUE;
354 }
355
356 static gboolean
357 nntp_disconnect_online (CamelService *service, gboolean clean, CamelException *ex)
358 {
359         CamelNNTPStore *store = CAMEL_NNTP_STORE (service);
360         char *line;
361         
362         CAMEL_SERVICE_REC_LOCK(store, connect_lock);
363         
364         if (clean) {
365                 camel_nntp_raw_command (store, ex, &line, "quit");
366                 camel_exception_clear(ex);
367         }
368         
369         if (!service_class->disconnect (service, clean, ex)) {
370                 CAMEL_SERVICE_REC_UNLOCK(store, connect_lock);
371                 return FALSE;
372         }
373         
374         camel_object_unref (store->stream);
375         store->stream = NULL;
376         g_free(store->current_folder);
377         store->current_folder = NULL;
378
379         CAMEL_SERVICE_REC_UNLOCK(store, connect_lock);
380         
381         return TRUE;
382 }
383
384 static gboolean
385 nntp_disconnect_offline (CamelService *service, gboolean clean, CamelException *ex)
386 {
387         CamelDiscoStore *disco = CAMEL_DISCO_STORE(service);
388         
389         if (!service_class->disconnect (service, clean, ex))
390                 return FALSE;
391         
392         if (disco->diary) {
393                 camel_object_unref (disco->diary);
394                 disco->diary = NULL;
395         }
396         
397         return TRUE;
398 }
399
400 static char *
401 nntp_store_get_name (CamelService *service, gboolean brief)
402 {
403         if (brief)
404                 return g_strdup_printf ("%s", service->url->host);
405         else
406                 return g_strdup_printf (_("USENET News via %s"), service->url->host);
407         
408 }
409
410 extern CamelServiceAuthType camel_nntp_password_authtype;
411
412 static GList *
413 nntp_store_query_auth_types (CamelService *service, CamelException *ex)
414 {
415         return g_list_append (NULL, &camel_nntp_password_authtype);
416 }
417
418 static CamelFolder *
419 nntp_get_folder(CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex)
420 {
421         CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE (store);
422         CamelFolder *folder;
423         
424         CAMEL_SERVICE_REC_LOCK(nntp_store, connect_lock);
425         
426         folder = camel_nntp_folder_new(store, folder_name, ex);
427         
428         CAMEL_SERVICE_REC_UNLOCK(nntp_store, connect_lock);
429         
430         return folder;
431 }
432
433 /*
434  * Converts a fully-fledged newsgroup name to a name in short dotted notation,
435  * e.g. nl.comp.os.linux.programmeren becomes n.c.o.l.programmeren
436  */
437
438 static char *
439 nntp_newsgroup_name_short (const char *name)
440 {
441         char *resptr, *tmp;
442         const char *ptr2;
443         
444         resptr = tmp = g_malloc0 (strlen (name) + 1);
445         
446         while ((ptr2 = strchr (name, '.'))) {
447                 if (ptr2 == name) {
448                         name++;
449                         continue;
450                 }
451                 
452                 *resptr++ = *name;
453                 *resptr++ = '.';
454                 name = ptr2 + 1;
455         }
456         
457         strcpy (resptr, name);
458         return tmp;
459 }
460
461 /*
462  * This function converts a NNTPStoreSummary item to a FolderInfo item that
463  * can be returned by the get_folders() call to the store. Both structs have
464  * essentially the same fields.
465  */
466
467 static CamelFolderInfo *
468 nntp_folder_info_from_store_info (CamelNNTPStore *store, gboolean short_notation, CamelStoreInfo *si)
469 {
470         CamelURL *base_url = ((CamelService *) store)->url;
471         CamelFolderInfo *fi = g_malloc0(sizeof(*fi));
472         CamelURL *url;
473         char *path;
474         
475         fi->full_name = g_strdup (si->path);
476         
477         if (short_notation)
478                 fi->name = nntp_newsgroup_name_short (si->path);
479         else
480                 fi->name = g_strdup (si->path);
481         
482         fi->unread = si->unread;
483         fi->total = si->total;
484         fi->flags = si->flags;
485         path = alloca(strlen(fi->full_name)+2);
486         sprintf(path, "/%s", fi->full_name);
487         url = camel_url_new_with_base (base_url, path);
488         fi->uri = camel_url_to_string (url, CAMEL_URL_HIDE_ALL);
489         camel_url_free (url);
490         
491         return fi;
492 }
493
494 static CamelFolderInfo *
495 nntp_folder_info_from_name (CamelNNTPStore *store, gboolean short_notation, const char *name)
496 {
497         CamelFolderInfo *fi = g_malloc0(sizeof(*fi));
498         CamelURL *base_url = ((CamelService *)store)->url;
499         CamelURL *url;
500         char *path;
501
502         fi->full_name = g_strdup (name);
503         
504         if (short_notation)
505                 fi->name = nntp_newsgroup_name_short (name);
506         else
507                 fi->name = g_strdup (name);
508         
509         fi->unread = -1;
510         
511         path = alloca(strlen(fi->full_name)+2);
512         sprintf(path, "/%s", fi->full_name);
513         url = camel_url_new_with_base (base_url, path);
514         fi->uri = camel_url_to_string (url, CAMEL_URL_HIDE_ALL);
515         camel_url_free (url);
516         
517         return fi;
518 }
519
520 /* handle list/newgroups response */
521 static CamelNNTPStoreInfo *
522 nntp_store_info_update(CamelNNTPStore *store, char *line)
523 {
524         CamelStoreSummary *summ = (CamelStoreSummary *)store->summary;
525         CamelURL *base_url = ((CamelService *)store)->url;
526         CamelNNTPStoreInfo *si, *fsi;
527         CamelURL *url;
528         char *relpath, *tmp;
529         guint32 last = 0, first = 0, new = 0;
530
531         tmp = strchr(line, ' ');
532         if (tmp)
533                 *tmp++ = 0;
534
535         fsi = si = (CamelNNTPStoreInfo *)camel_store_summary_path((CamelStoreSummary *)store->summary, line);
536         if (si == NULL) {
537                 si = (CamelNNTPStoreInfo*)camel_store_summary_info_new(summ);
538
539                 relpath = g_alloca(strlen(line)+2);
540                 sprintf(relpath, "/%s", line);
541                 url = camel_url_new_with_base (base_url, relpath);
542                 si->info.uri = camel_url_to_string (url, CAMEL_URL_HIDE_ALL);
543                 camel_url_free (url);
544
545                 si->info.path = g_strdup (line);
546                 si->full_name = g_strdup (line); /* why do we keep this? */
547                 camel_store_summary_add((CamelStoreSummary *)store->summary, &si->info);
548         } else {
549                 first = si->first;
550                 last = si->last;
551         }
552
553         if (tmp && *tmp >= '0' && *tmp <= '9') {
554                 last = strtoul(tmp, &tmp, 10);
555                 if (*tmp == ' ' && tmp[1] >= '0' && tmp[1] <= '9') {
556                         first = strtoul(tmp+1, &tmp, 10);
557                         if (*tmp == ' ' && tmp[1] != 'y')
558                                 si->info.flags |= CAMEL_STORE_INFO_FOLDER_READONLY;
559                 }
560         }
561
562         printf("store info update '%s' first '%u' last '%u'\n", line, first, last);
563
564         if (si->last) {
565                 if (last > si->last)
566                         new = last-si->last;
567         } else {
568                 if (last > first)
569                         new = last - first;
570         }
571
572         si->info.total = last > first?last-first:0;
573         si->info.unread += new; /* this is a _guess_ */
574         si->last = last;
575         si->first = first;
576
577         if (fsi)
578                 camel_store_summary_info_free((CamelStoreSummary *)store->summary, &fsi->info);
579         else                    /* TODO see if we really did touch it */
580                 camel_store_summary_touch ((CamelStoreSummary *)store->summary);
581
582         return si;
583 }
584
585 static CamelFolderInfo *
586 nntp_store_get_subscribed_folder_info (CamelNNTPStore *store, const char *top, guint flags, CamelException *ex)
587 {
588         int i;
589         CamelStoreInfo *si;
590         CamelFolderInfo *first = NULL, *last = NULL, *fi = NULL;
591         
592         /* since we do not do a tree, any request that is not for root is sure to give no results */
593         if (top != NULL && top[0] != 0)
594                 return NULL;
595         
596         for (i=0;(si = camel_store_summary_index ((CamelStoreSummary *) store->summary, i));i++) {
597                 if (si == NULL)
598                         continue;
599
600                 if (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) {
601                         /* slow mode?  open and update the folder, always! this will implictly update
602                            our storeinfo too; in a very round-about way */
603                         if ((flags & CAMEL_STORE_FOLDER_INFO_FAST) == 0) {
604                                 CamelNNTPFolder *folder;
605                                 char *line;
606
607                                 folder = (CamelNNTPFolder *)camel_store_get_folder((CamelStore *)store, si->path, 0, ex);
608                                 if (folder) {
609                                         CamelFolderChangeInfo *changes = NULL;
610
611                                         CAMEL_SERVICE_REC_LOCK(store, connect_lock);
612                                         camel_nntp_command(store, ex, folder, &line, NULL);
613                                         if (camel_folder_change_info_changed(folder->changes)) {
614                                                 changes = folder->changes;
615                                                 folder->changes = camel_folder_change_info_new();
616                                         }
617                                         CAMEL_SERVICE_REC_UNLOCK(store, connect_lock);
618                                         if (changes) {
619                                                 camel_object_trigger_event((CamelObject *) folder, "folder_changed", changes);
620                                                 camel_folder_change_info_free(changes);
621                                         }
622                                         camel_object_unref(folder);
623                                 }
624                                 camel_exception_clear(ex);
625                         }
626                         fi = nntp_folder_info_from_store_info (store, store->do_short_folder_notation, si);
627                         fi->flags |= CAMEL_FOLDER_NOINFERIORS | CAMEL_FOLDER_NOCHILDREN | CAMEL_FOLDER_SYSTEM;
628                         if (last)
629                                 last->next = fi;
630                         else
631                                 first = fi;
632                         last = fi;
633                 }
634                 camel_store_summary_info_free ((CamelStoreSummary *) store->summary, si);
635         }
636         
637         return first;
638 }
639
640 /*
641  * get folder info, using the information in our StoreSummary
642  */
643 static CamelFolderInfo *
644 nntp_store_get_cached_folder_info (CamelNNTPStore *store, const char *orig_top, guint flags, CamelException *ex)
645 {
646         int i;
647         int subscribed_or_flag = (flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED) ? 0 : 1,
648             root_or_flag = (orig_top == NULL || orig_top[0] == '\0') ? 1 : 0,
649             recursive_flag = flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE;
650         CamelStoreInfo *si;
651         CamelFolderInfo *first = NULL, *last = NULL, *fi = NULL;
652         char *tmpname;
653         char *top = g_strconcat(orig_top?orig_top:"", ".", NULL);
654         int toplen = strlen(top);
655         
656         for (i = 0; (si = camel_store_summary_index ((CamelStoreSummary *) store->summary, i)); i++) {
657                 if ((subscribed_or_flag || (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED))
658                     && (root_or_flag || strncmp (si->path, top, toplen) == 0)) {
659                         if (recursive_flag || strchr (si->path + toplen, '.') == NULL) {
660                                 /* add the item */
661                                 fi = nntp_folder_info_from_store_info(store, FALSE, si);
662                                 if (!fi)
663                                         continue;
664                                 if (store->folder_hierarchy_relative) {
665                                         g_free (fi->name);
666                                         fi->name = g_strdup (si->path + ((toplen == 1) ? 0 : toplen));
667                                 }
668                         } else {
669                                 /* apparently, this is an indirect subitem. if it's not a subitem of
670                                    the item we added last, we need to add a portion of this item to
671                                    the list as a placeholder */
672                                 if (!last ||
673                                     strncmp(si->path, last->full_name, strlen(last->full_name)) != 0 || 
674                                     si->path[strlen(last->full_name)] != '.') {
675                                         tmpname = g_strdup(si->path);
676                                         *(strchr(tmpname + toplen, '.')) = '\0';
677                                         fi = nntp_folder_info_from_name(store, FALSE, tmpname);
678                                         fi->flags |= CAMEL_FOLDER_NOSELECT;
679                                         if (store->folder_hierarchy_relative) {
680                                                 g_free(fi->name);
681                                                 fi->name = g_strdup(tmpname + ((toplen==1) ? 0 : toplen));
682                                         }
683                                         g_free(tmpname);
684                                 } else {
685                                         continue;
686                                 }
687                         }
688                         if (last)
689                                 last->next = fi;
690                         else
691                                 first = fi;
692                         last = fi;
693                 } else if (subscribed_or_flag && first) {
694                         /* we have already added subitems, but this item is no longer a subitem */
695                         camel_store_summary_info_free((CamelStoreSummary *)store->summary, si);
696                         break;
697                 }
698                 camel_store_summary_info_free((CamelStoreSummary *)store->summary, si);
699         }
700         
701         g_free(top);
702         return first;
703 }
704
705 /* retrieves the date from the NNTP server */
706 static gboolean
707 nntp_get_date(CamelNNTPStore *nntp_store, CamelException *ex)
708 {
709         unsigned char *line;
710         int ret = camel_nntp_command(nntp_store, ex, NULL, (char **)&line, "date");
711         char *ptr;
712
713         nntp_store->summary->last_newslist[0] = 0;
714         
715         if (ret == 111) {
716                 ptr = line + 3;
717                 while (*ptr == ' ' || *ptr == '\t')
718                         ptr++;
719                 
720                 if (strlen (ptr) == NNTP_DATE_SIZE) {
721                         memcpy (nntp_store->summary->last_newslist, ptr, NNTP_DATE_SIZE);
722                         return TRUE;
723                 }
724         }
725         return FALSE;
726 }
727
728 static void
729 store_info_remove(void *key, void *value, void *data)
730 {
731         CamelStoreSummary *summary = data;
732         CamelStoreInfo *si = value;
733
734         camel_store_summary_remove(summary, si);
735 }
736
737 static gint
738 store_info_sort (gconstpointer a, gconstpointer b)
739 {
740         return strcmp ((*(CamelNNTPStoreInfo**) a)->full_name, (*(CamelNNTPStoreInfo**) b)->full_name);
741 }
742
743 static CamelFolderInfo *
744 nntp_store_get_folder_info_all(CamelNNTPStore *nntp_store, const char *top, guint32 flags, gboolean online, CamelException *ex)
745 {
746         CamelNNTPStoreSummary *summary = nntp_store->summary;
747         CamelNNTPStoreInfo *si;
748         unsigned int len;
749         unsigned char *line;
750         int ret = -1;
751         CamelFolderInfo *fi = NULL;
752
753         CAMEL_SERVICE_REC_LOCK(nntp_store, connect_lock);
754         
755         if (top == NULL)
756                 top = "";
757         
758         if (online && (top == NULL || top[0] == 0)) {
759                 /* we may need to update */
760                 if (summary->last_newslist[0] != 0) {
761                         char date[14];
762                         memcpy(date, summary->last_newslist + 2, 6); /* YYMMDDD */
763                         date[6] = ' ';
764                         memcpy(date + 7, summary->last_newslist + 8, 6); /* HHMMSS */
765                         date[13] = '\0';
766
767                         /* Some servers don't support date (!), so fallback if they dont */
768                         if (!nntp_get_date (nntp_store, NULL))
769                                 goto do_complete_list_nodate;
770                         
771                         ret = camel_nntp_command (nntp_store, ex, NULL, (char **) &line, "newgroups %s", date);
772                         if (ret == -1)
773                                 goto error;
774                         else if (ret != 231) {
775                                 /* newgroups not supported :S so reload the complete list */
776                                 summary->last_newslist[0] = 0;
777                                 goto do_complete_list;
778                         }
779
780                         while ((ret = camel_nntp_stream_line (nntp_store->stream, &line, &len)) > 0)
781                                 nntp_store_info_update(nntp_store, line);
782                 } else {
783                         GHashTable *all;
784                         int i;
785
786                 do_complete_list:
787                         /* seems we do need a complete list */
788                         /* at first, we do a DATE to find out the last load occasion */
789                         nntp_get_date (nntp_store, NULL);
790                 do_complete_list_nodate:
791                         ret = camel_nntp_command (nntp_store, ex, NULL, (char **)&line, "list");
792                         if (ret == -1)
793                                 goto error;
794                         else if (ret != 215) {
795                                 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_INVALID,
796                                                       _("Error retrieving newsgroups:\n\n%s"), line);
797                                 goto error;
798                         }
799
800                         all = g_hash_table_new(g_str_hash, g_str_equal);
801                         for (i = 0; (si = (CamelNNTPStoreInfo *)camel_store_summary_index ((CamelStoreSummary *)nntp_store->summary, i)); i++)
802                                 g_hash_table_insert(all, si->info.path, si);
803
804                         while ((ret = camel_nntp_stream_line(nntp_store->stream, &line, &len)) > 0) {
805                                 si = nntp_store_info_update(nntp_store, line);
806                                 g_hash_table_remove(all, si->info.path);
807                         }
808
809                         g_hash_table_foreach(all, store_info_remove, nntp_store->summary);
810                         g_hash_table_destroy(all);
811                 }
812                 
813                 /* sort the list */
814                 g_ptr_array_sort (CAMEL_STORE_SUMMARY (nntp_store->summary)->folders, store_info_sort);
815                 if (ret < 0)
816                         goto error;
817                 
818                 camel_store_summary_save ((CamelStoreSummary *) nntp_store->summary);
819         }
820         
821         fi = nntp_store_get_cached_folder_info (nntp_store, top, flags, ex);
822  error:
823         CAMEL_SERVICE_REC_UNLOCK(nntp_store, connect_lock);
824
825         return fi;
826 }
827
828 static CamelFolderInfo *
829 nntp_get_folder_info (CamelStore *store, const char *top, guint32 flags, gboolean online, CamelException *ex)
830 {
831         CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE(store);
832         CamelFolderInfo *first = NULL;
833         
834         dd(printf("g_f_i: fast %d subscr %d recursive %d online %d top \"%s\"\n",
835                 flags & CAMEL_STORE_FOLDER_INFO_FAST,
836                 flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED,
837                 flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE,
838                 online,
839                 top?top:""));
840         
841         if (flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED)
842                 first = nntp_store_get_subscribed_folder_info (nntp_store, top, flags, ex);
843         else
844                 first = nntp_store_get_folder_info_all (nntp_store, top, flags, online, ex);
845         
846         return first;
847 }
848
849 static CamelFolderInfo *
850 nntp_get_folder_info_online (CamelStore *store, const char *top, guint32 flags, CamelException *ex)
851 {
852         return nntp_get_folder_info (store, top, flags, TRUE, ex);
853 }
854
855 static CamelFolderInfo *
856 nntp_get_folder_info_offline(CamelStore *store, const char *top, guint32 flags, CamelException *ex)
857 {
858         return nntp_get_folder_info (store, top, flags, FALSE, ex);
859 }
860
861 static gboolean
862 nntp_store_folder_subscribed (CamelStore *store, const char *folder_name)
863 {
864         CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE (store);
865         CamelStoreInfo *si;
866         int truth = FALSE;
867         
868         si = camel_store_summary_path ((CamelStoreSummary *) nntp_store->summary, folder_name);
869         if (si) {
870                 truth = (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) != 0;
871                 camel_store_summary_info_free ((CamelStoreSummary *) nntp_store->summary, si);
872         }
873
874         return truth;
875 }
876
877 static void
878 nntp_store_subscribe_folder (CamelStore *store, const char *folder_name,
879                              CamelException *ex)
880 {
881         CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE(store);
882         CamelStoreInfo *si;
883         CamelFolderInfo *fi;
884         
885         CAMEL_SERVICE_REC_LOCK(nntp_store, connect_lock);
886         
887         si = camel_store_summary_path(CAMEL_STORE_SUMMARY(nntp_store->summary), folder_name);
888         if (!si) {
889                 camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID,
890                                       _("You cannot subscribe to this newsgroup:\n\n"
891                                         "No such newsgroup. The selected item is a probably a parent folder."));
892         } else {
893                 if (!(si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED)) {
894                         si->flags |= CAMEL_STORE_INFO_FOLDER_SUBSCRIBED;
895                         fi = nntp_folder_info_from_store_info(nntp_store, nntp_store->do_short_folder_notation, si);
896                         fi->flags |= CAMEL_FOLDER_NOINFERIORS | CAMEL_FOLDER_NOCHILDREN;
897                         camel_store_summary_touch ((CamelStoreSummary *) nntp_store->summary);
898                         camel_store_summary_save ((CamelStoreSummary *) nntp_store->summary);
899                         CAMEL_SERVICE_REC_UNLOCK(nntp_store, connect_lock);
900                         camel_object_trigger_event ((CamelObject *) nntp_store, "folder_subscribed", fi);
901                         camel_folder_info_free (fi);
902                         return;
903                 }
904         }
905         
906         CAMEL_SERVICE_REC_UNLOCK(nntp_store, connect_lock);
907 }
908
909 static void
910 nntp_store_unsubscribe_folder (CamelStore *store, const char *folder_name,
911                                CamelException *ex)
912 {
913         CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE(store);
914         CamelFolderInfo *fi;
915         CamelStoreInfo *fitem;
916         CAMEL_SERVICE_REC_LOCK(nntp_store, connect_lock);
917         
918         fitem = camel_store_summary_path(CAMEL_STORE_SUMMARY(nntp_store->summary), folder_name);
919         
920         if (!fitem) {
921                 camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID,
922                                       _("You cannot unsubscribe to this newsgroup:\n\n"
923                                         "newsgroup does not exist!"));
924         } else {
925                 if (fitem->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) {
926                         fitem->flags &= ~CAMEL_STORE_INFO_FOLDER_SUBSCRIBED;
927                         fi = nntp_folder_info_from_store_info (nntp_store, nntp_store->do_short_folder_notation, fitem);
928                         camel_store_summary_touch ((CamelStoreSummary *) nntp_store->summary);
929                         camel_store_summary_save ((CamelStoreSummary *) nntp_store->summary);
930                         CAMEL_SERVICE_REC_UNLOCK(nntp_store, connect_lock);
931                         camel_object_trigger_event ((CamelObject *) nntp_store, "folder_unsubscribed", fi);
932                         camel_folder_info_free (fi);
933                         return;
934                 }
935         }
936         
937         CAMEL_SERVICE_REC_UNLOCK(nntp_store, connect_lock);
938 }
939
940 /* stubs for various folder operations we're not implementing */
941
942 static CamelFolderInfo *
943 nntp_create_folder (CamelStore *store, const char *parent_name,
944                     const char *folder_name, CamelException *ex)
945 {
946         camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID,
947                     _("You cannot create a folder in a News store: subscribe instead."));
948         return NULL;
949 }
950
951 static void
952 nntp_rename_folder (CamelStore *store, const char *old_name, const char *new_name_in, CamelException *ex)
953 {
954         camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID,
955                   _("You cannot rename a folder in a News store."));
956 }
957
958 static void
959 nntp_delete_folder (CamelStore *store, const char *folder_name, CamelException *ex)
960 {
961         nntp_store_subscribe_folder (store, folder_name, ex);
962         camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID,
963                   _("You cannot remove a folder in a News store: unsubscribe instead."));
964         return;
965 }
966
967 static void
968 nntp_store_finalize (CamelObject *object)
969 {
970         /* call base finalize */
971         CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE (object);
972         struct _CamelNNTPStorePrivate *p = nntp_store->priv;
973         struct _xover_header *xover, *xn;
974         
975         camel_service_disconnect ((CamelService *)object, TRUE, NULL);
976         
977         if (nntp_store->summary) {
978                 camel_store_summary_save ((CamelStoreSummary *) nntp_store->summary);
979                 camel_object_unref (nntp_store->summary);
980         }       
981
982         camel_object_unref (nntp_store->mem);
983         nntp_store->mem = NULL;
984         if (nntp_store->stream)
985                 camel_object_unref (nntp_store->stream);
986         
987         if (nntp_store->base_url)
988                 g_free (nntp_store->base_url);
989         if (nntp_store->storage_path)
990                 g_free (nntp_store->storage_path);
991
992         xover = nntp_store->xover;
993         while (xover) {
994                 xn = xover->next;
995                 g_free(xover);
996                 xover = xn;
997         }
998
999         if (nntp_store->cache)
1000                 camel_object_unref(nntp_store->cache);
1001         
1002         g_free(p);
1003 }
1004
1005 static void
1006 nntp_store_class_init (CamelNNTPStoreClass *camel_nntp_store_class)
1007 {
1008         CamelDiscoStoreClass *camel_disco_store_class = CAMEL_DISCO_STORE_CLASS (camel_nntp_store_class);
1009         CamelStoreClass *camel_store_class = CAMEL_STORE_CLASS (camel_nntp_store_class);
1010         CamelServiceClass *camel_service_class = CAMEL_SERVICE_CLASS (camel_nntp_store_class);
1011
1012         parent_class = CAMEL_DISCO_STORE_CLASS (camel_type_get_global_classfuncs (camel_disco_store_get_type ()));
1013         service_class = CAMEL_SERVICE_CLASS (camel_type_get_global_classfuncs (camel_service_get_type ()));
1014         
1015         /* virtual method overload */
1016         camel_service_class->construct = nntp_construct;
1017         camel_service_class->query_auth_types = nntp_store_query_auth_types;
1018         camel_service_class->get_name = nntp_store_get_name;
1019         
1020         camel_disco_store_class->can_work_offline = nntp_can_work_offline;
1021         camel_disco_store_class->connect_online = nntp_connect_online;
1022         camel_disco_store_class->connect_offline = nntp_connect_offline;
1023         camel_disco_store_class->disconnect_online = nntp_disconnect_online;
1024         camel_disco_store_class->disconnect_offline = nntp_disconnect_offline;
1025         camel_disco_store_class->get_folder_online = nntp_get_folder;
1026         camel_disco_store_class->get_folder_resyncing = nntp_get_folder;
1027         camel_disco_store_class->get_folder_offline = nntp_get_folder;
1028         
1029         camel_disco_store_class->get_folder_info_online = nntp_get_folder_info_online;
1030         camel_disco_store_class->get_folder_info_resyncing = nntp_get_folder_info_online;
1031         camel_disco_store_class->get_folder_info_offline = nntp_get_folder_info_offline;
1032         
1033         camel_store_class->free_folder_info = camel_store_free_folder_info_full;
1034         
1035         camel_store_class->folder_subscribed = nntp_store_folder_subscribed;
1036         camel_store_class->subscribe_folder = nntp_store_subscribe_folder;
1037         camel_store_class->unsubscribe_folder = nntp_store_unsubscribe_folder;
1038
1039         camel_store_class->create_folder = nntp_create_folder;
1040         camel_store_class->delete_folder = nntp_delete_folder;
1041         camel_store_class->rename_folder = nntp_rename_folder;
1042 }
1043
1044 /* construction function in which we set some basic store properties */
1045 static void
1046 nntp_construct (CamelService *service, CamelSession *session,
1047                 CamelProvider *provider, CamelURL *url,
1048                 CamelException *ex)
1049 {
1050         CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE(service);
1051         CamelURL *summary_url;
1052         char *tmp;
1053         
1054         /* construct the parent first */
1055         CAMEL_SERVICE_CLASS (parent_class)->construct (service, session, provider, url, ex);
1056         if (camel_exception_is_set (ex))
1057                 return;
1058         
1059         /* find out the storage path, base url */
1060         nntp_store->storage_path = camel_session_get_storage_path (session, service, ex);
1061         if (!nntp_store->storage_path)
1062                 return;
1063         
1064         /* FIXME */
1065         nntp_store->base_url = camel_url_to_string (service->url, (CAMEL_URL_HIDE_PASSWORD |
1066                                                                    CAMEL_URL_HIDE_PARAMS |
1067                                                                    CAMEL_URL_HIDE_AUTH));
1068         
1069         tmp = g_build_filename (nntp_store->storage_path, ".ev-store-summary", NULL);
1070         nntp_store->summary = camel_nntp_store_summary_new ();
1071         camel_store_summary_set_filename ((CamelStoreSummary *) nntp_store->summary, tmp);      
1072         summary_url = camel_url_new (nntp_store->base_url, NULL);
1073         camel_store_summary_set_uri_base ((CamelStoreSummary *) nntp_store->summary, summary_url);
1074         g_free (tmp);
1075         
1076         camel_url_free (summary_url);
1077         if (camel_store_summary_load ((CamelStoreSummary *)nntp_store->summary) == 0)
1078                 ;
1079         
1080         /* get options */
1081         if (camel_url_get_param (url, "show_short_notation"))
1082                 nntp_store->do_short_folder_notation = TRUE;
1083         else
1084                 nntp_store->do_short_folder_notation = FALSE;
1085         if (camel_url_get_param (url, "folder_hierarchy_relative"))
1086                 nntp_store->folder_hierarchy_relative = TRUE;
1087         else
1088                 nntp_store->folder_hierarchy_relative = FALSE;
1089
1090         /* setup store-wide cache */
1091         nntp_store->cache = camel_data_cache_new(nntp_store->storage_path, 0, ex);
1092         if (nntp_store->cache == NULL)
1093                 return;
1094                 
1095         /* Default cache expiry - 2 weeks old, or not visited in 5 days */
1096         camel_data_cache_set_expire_age(nntp_store->cache, 60*60*24*14);
1097         camel_data_cache_set_expire_access(nntp_store->cache, 60*60*24*5);
1098 }
1099
1100 static void
1101 nntp_store_init (gpointer object, gpointer klass)
1102 {
1103         CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE(object);
1104         CamelStore *store = CAMEL_STORE (object);
1105         struct _CamelNNTPStorePrivate *p;
1106         
1107         store->flags = CAMEL_STORE_SUBSCRIPTIONS;
1108         
1109         nntp_store->mem = (CamelStreamMem *)camel_stream_mem_new();
1110         
1111         p = nntp_store->priv = g_malloc0(sizeof(*p));
1112 }
1113
1114 CamelType
1115 camel_nntp_store_get_type (void)
1116 {
1117         static CamelType camel_nntp_store_type = CAMEL_INVALID_TYPE;
1118         
1119         if (camel_nntp_store_type == CAMEL_INVALID_TYPE) {
1120                 camel_nntp_store_type =
1121                         camel_type_register (CAMEL_DISCO_STORE_TYPE,
1122                                              "CamelNNTPStore",
1123                                              sizeof (CamelNNTPStore),
1124                                              sizeof (CamelNNTPStoreClass),
1125                                              (CamelObjectClassInitFunc) nntp_store_class_init,
1126                                              NULL,
1127                                              (CamelObjectInitFunc) nntp_store_init,
1128                                              (CamelObjectFinalizeFunc) nntp_store_finalize);
1129         }
1130         
1131         return camel_nntp_store_type;
1132 }
1133
1134 static int
1135 camel_nntp_try_authenticate (CamelNNTPStore *store, CamelException *ex)
1136 {
1137         CamelService *service = (CamelService *) store;
1138         CamelSession *session = camel_service_get_session (service);
1139         int ret;
1140         char *line = NULL;
1141         
1142         if (!service->url->user) {
1143                 camel_exception_setv(ex, CAMEL_EXCEPTION_INVALID_PARAM,
1144                                      _("Authentication requested but no username provided"));
1145                 return -1;
1146         }
1147
1148         /* if nessecary, prompt for the password */
1149         if (!service->url->passwd) {
1150                 char *prompt, *base;
1151         retry:
1152                 base = g_strdup_printf (_("Please enter the NNTP password for %s@%s"),
1153                                         service->url->user,
1154                                         service->url->host);
1155                 if (line) {
1156                         char *top = g_strdup_printf(_("Cannot authenticate to server: %s"), line);
1157
1158                         prompt = g_strdup_printf("%s\n\n%s", top, base);
1159                         g_free(top);
1160                 } else {
1161                         prompt = base;
1162                         base = NULL;
1163                 }
1164
1165                 service->url->passwd =
1166                         camel_session_get_password (session, service, NULL,
1167                                                     prompt, "password", CAMEL_SESSION_PASSWORD_SECRET, ex);
1168                 g_free(prompt);
1169                 g_free(base);
1170                 
1171                 if (!service->url->passwd)
1172                         return -1;
1173         }
1174
1175         /* now, send auth info (currently, only authinfo user/pass is supported) */
1176         ret = camel_nntp_raw_command(store, ex, &line, "authinfo user %s", service->url->user);
1177         if (ret == NNTP_AUTH_CONTINUE)
1178                 ret = camel_nntp_raw_command(store, ex, &line, "authinfo pass %s", service->url->passwd);
1179
1180         if (ret != NNTP_AUTH_ACCEPTED) {
1181                 if (ret != -1) {
1182                         /* Need to forget the password here since we have no context on it */
1183                         camel_session_forget_password(session, service, NULL, "password", ex);
1184                         goto retry;
1185                 }
1186                 return -1;
1187         }
1188
1189         return ret;
1190 }
1191
1192 /* Enter owning lock */
1193 int
1194 camel_nntp_raw_commandv (CamelNNTPStore *store, CamelException *ex, char **line, const char *fmt, va_list ap)
1195 {
1196         const unsigned char *p, *ps;
1197         unsigned char c;
1198         char *s;
1199         int d;
1200         unsigned int u, u2;
1201         
1202         g_assert(store->stream->mode != CAMEL_NNTP_STREAM_DATA);
1203
1204         camel_nntp_stream_set_mode(store->stream, CAMEL_NNTP_STREAM_LINE);
1205         
1206         ps = p = fmt;
1207         while ((c = *p++)) {
1208                 switch (c) {
1209                 case '%':
1210                         c = *p++;
1211                         camel_stream_write ((CamelStream *) store->mem, ps, p - ps - (c == '%' ? 1 : 2));
1212                         ps = p;
1213                         switch (c) {
1214                         case 's':
1215                                 s = va_arg(ap, char *);
1216                                 camel_stream_write((CamelStream *)store->mem, s, strlen(s));
1217                                 break;
1218                         case 'd':
1219                                 d = va_arg(ap, int);
1220                                 camel_stream_printf((CamelStream *)store->mem, "%d", d);
1221                                 break;
1222                         case 'u':
1223                                 u = va_arg(ap, unsigned int);
1224                                 camel_stream_printf((CamelStream *)store->mem, "%u", u);
1225                                 break;
1226                         case 'm':
1227                                 s = va_arg(ap, char *);
1228                                 camel_stream_printf((CamelStream *)store->mem, "<%s>", s);
1229                                 break;
1230                         case 'r':
1231                                 u = va_arg(ap, unsigned int);
1232                                 u2 = va_arg(ap, unsigned int);
1233                                 if (u == u2)
1234                                         camel_stream_printf((CamelStream *)store->mem, "%u", u);
1235                                 else
1236                                         camel_stream_printf((CamelStream *)store->mem, "%u-%u", u, u2);
1237                                 break;
1238                         default:
1239                                 g_warning("Passing unknown format to nntp_command: %c\n", c);
1240                                 g_assert(0);
1241                         }
1242                 }
1243         }
1244         
1245         camel_stream_write ((CamelStream *) store->mem, ps, p-ps-1);
1246         dd(printf("NNTP_COMMAND: '%.*s'\n", (int)store->mem->buffer->len, store->mem->buffer->data));
1247         camel_stream_write ((CamelStream *) store->mem, "\r\n", 2);
1248         
1249         if (camel_stream_write((CamelStream *) store->stream, store->mem->buffer->data, store->mem->buffer->len) == -1)
1250                 goto ioerror;
1251
1252         /* FIXME: hack */
1253         camel_stream_reset ((CamelStream *) store->mem);
1254         g_byte_array_set_size (store->mem->buffer, 0);
1255         
1256         if (camel_nntp_stream_line (store->stream, (unsigned char **) line, &u) == -1)
1257                 goto ioerror;
1258         
1259         u = strtoul (*line, NULL, 10);
1260         
1261         /* Handle all switching to data mode here, to make callers job easier */
1262         if (u == 215 || (u >= 220 && u <=224) || (u >= 230 && u <= 231))
1263                 camel_nntp_stream_set_mode(store->stream, CAMEL_NNTP_STREAM_DATA);
1264         
1265         return u;
1266
1267 ioerror:
1268         if (errno == EINTR)
1269                 camel_exception_setv(ex, CAMEL_EXCEPTION_USER_CANCEL, _("Canceled."));
1270         else
1271                 camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("NNTP Command failed: %s"), g_strerror(errno));
1272         return -1;
1273 }
1274
1275 int
1276 camel_nntp_raw_command(CamelNNTPStore *store, CamelException *ex, char **line, const char *fmt, ...)
1277 {
1278         int ret;
1279         va_list ap;
1280
1281         va_start(ap, fmt);
1282         ret = camel_nntp_raw_commandv(store, ex, line, fmt, ap);
1283         va_end(ap);
1284
1285         return ret;
1286 }
1287
1288 /* use this where you also need auth to be handled, i.e. most cases where you'd try raw command */
1289 int
1290 camel_nntp_raw_command_auth(CamelNNTPStore *store, CamelException *ex, char **line, const char *fmt, ...)
1291 {
1292         int ret, retry, go;
1293         va_list ap;
1294
1295         retry = 0;
1296
1297         do {
1298                 go = FALSE;
1299                 retry++;
1300
1301                 va_start(ap, fmt);
1302                 ret = camel_nntp_raw_commandv(store, ex, line, fmt, ap);
1303                 va_end(ap);
1304
1305                 if (ret == NNTP_AUTH_REQUIRED) {
1306                         if (camel_nntp_try_authenticate(store, ex) != NNTP_AUTH_ACCEPTED)
1307                                 return -1;
1308                         go = TRUE;
1309                 }
1310         } while (retry < 3 && go);
1311
1312         return ret;
1313 }
1314
1315 int
1316 camel_nntp_command (CamelNNTPStore *store, CamelException *ex, CamelNNTPFolder *folder, char **line, const char *fmt, ...)
1317 {
1318         const unsigned char *p;
1319         va_list ap;
1320         int ret, retry;
1321         unsigned int u;
1322         
1323         if (((CamelDiscoStore *)store)->status == CAMEL_DISCO_STORE_OFFLINE) {
1324                 camel_exception_setv(ex, CAMEL_EXCEPTION_SERVICE_NOT_CONNECTED,
1325                                      _("Not connected."));
1326                 return -1;
1327         }
1328         
1329         retry = 0;
1330         do {
1331                 retry ++;
1332
1333                 if (store->stream == NULL
1334                     && !camel_service_connect (CAMEL_SERVICE (store), ex))
1335                         return -1;
1336
1337                 /* Check for unprocessed data, ! */
1338                 if (store->stream->mode == CAMEL_NNTP_STREAM_DATA) {
1339                         g_warning("Unprocessed data left in stream, flushing");
1340                         while (camel_nntp_stream_getd(store->stream, (unsigned char **)&p, &u) > 0)
1341                                 ;
1342                 }
1343                 camel_nntp_stream_set_mode(store->stream, CAMEL_NNTP_STREAM_LINE);
1344
1345                 if (folder != NULL
1346                     && (store->current_folder == NULL || strcmp(store->current_folder, ((CamelFolder *)folder)->full_name) != 0)) {
1347                         ret = camel_nntp_raw_command_auth(store, ex, line, "group %s", ((CamelFolder *)folder)->full_name);
1348                         if (ret == 211) {
1349                                 g_free(store->current_folder);
1350                                 store->current_folder = g_strdup(((CamelFolder *)folder)->full_name);
1351                                 camel_nntp_folder_selected(folder, *line, ex);
1352                                 if (camel_exception_is_set(ex)) {
1353                                         ret = -1;
1354                                         goto error;
1355                                 }
1356                         } else {
1357                                 goto error;
1358                         }
1359                 }
1360
1361                 /* dummy fmt, we just wanted to select the folder */
1362                 if (fmt == NULL)
1363                         return 0;
1364
1365                 va_start(ap, fmt);
1366                 ret = camel_nntp_raw_commandv(store, ex, line, fmt, ap);
1367                 va_end(ap);
1368         error:
1369                 switch (ret) {
1370                 case NNTP_AUTH_REQUIRED:
1371                         if (camel_nntp_try_authenticate(store, ex) != NNTP_AUTH_ACCEPTED)
1372                                 return -1;
1373                         retry--;
1374                         ret = -1;
1375                         continue;
1376                 case 411:       /* no such group */
1377                         camel_exception_setv(ex, CAMEL_EXCEPTION_FOLDER_INVALID,
1378                                              _("No such folder: %s"), line);
1379                         return -1;
1380                 case 400:       /* service discontinued */
1381                 case 401:       /* wrong client state - this should quit but this is what the old code did */
1382                 case 503:       /* information not available - this should quit but this is what the old code did (?) */
1383                         camel_service_disconnect (CAMEL_SERVICE (store), FALSE, NULL);
1384                         ret = -1;
1385                         continue;
1386                 case -1:        /* i/o error */
1387                         camel_service_disconnect (CAMEL_SERVICE (store), FALSE, NULL);
1388                         if (camel_exception_get_id(ex) == CAMEL_EXCEPTION_USER_CANCEL || retry >= 3)
1389                                 return -1;
1390                         camel_exception_clear(ex);
1391                         break;
1392                 }
1393         } while (ret == -1 && retry < 3);
1394
1395         return ret;
1396 }