fc067062ba654699485ddc7923966d92e1e7d014
[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 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 General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
21  * USA
22  */
23
24
25 #ifdef HAVE_CONFIG_H
26 #include <config.h>
27 #endif
28
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <sys/types.h>
33 #include <unistd.h>
34 #include <dirent.h>
35 #include <errno.h>
36
37 #include <camel/camel-url.h>
38 #include <camel/camel-string-utils.h>
39 #include <camel/camel-session.h>
40 #include <camel/camel-tcp-stream-raw.h>
41 #include <camel/camel-tcp-stream-ssl.h>
42
43 #include <camel/camel-disco-store.h>
44 #include <camel/camel-disco-diary.h>
45
46 #include "camel-nntp-summary.h"
47 #include "camel-nntp-store.h"
48 #include "camel-nntp-store-summary.h"
49 #include "camel-nntp-folder.h"
50 #include "camel-nntp-private.h"
51 #include "camel-nntp-resp-codes.h"
52
53 #define w(x)
54 extern int camel_verbose_debug;
55 #define dd(x) (camel_verbose_debug?(x):0)
56
57 #define NNTP_PORT  119
58 #define NNTPS_PORT 563
59
60 #define DUMP_EXTENSIONS
61
62 static CamelDiscoStoreClass *parent_class = NULL;
63 static CamelServiceClass *service_class = NULL;
64
65 /* Returns the class for a CamelNNTPStore */
66 #define CNNTPS_CLASS(so) CAMEL_NNTP_STORE_CLASS (CAMEL_OBJECT_GET_CLASS(so))
67 #define CF_CLASS(so) CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
68 #define CNNTPF_CLASS(so) CAMEL_NNTP_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
69
70 static void nntp_construct (CamelService *service, CamelSession *session,
71                             CamelProvider *provider, CamelURL *url,
72                             CamelException *ex);
73
74
75 static gboolean
76 nntp_can_work_offline(CamelDiscoStore *store)
77 {
78         return TRUE;
79 }
80
81 enum {
82         USE_SSL_NEVER,
83         USE_SSL_ALWAYS,
84         USE_SSL_WHEN_POSSIBLE
85 };
86
87 static gboolean
88 connect_to_server (CamelService *service, int ssl_mode, CamelException *ex)
89 {
90         CamelNNTPStore *store = (CamelNNTPStore *) service;
91         CamelDiscoStore *disco_store = (CamelDiscoStore*) service;
92         CamelStream *tcp_stream;
93         gboolean retval = FALSE;
94         unsigned char *buf;
95         unsigned int len;
96         struct hostent *h;
97         int port, ret;
98         char *path;
99         
100         CAMEL_NNTP_STORE_LOCK(store, command_lock);
101
102         /* setup store-wide cache */
103         if (store->cache == NULL) {
104                 if (store->storage_path == NULL)
105                         goto fail;
106                 
107                 store->cache = camel_data_cache_new (store->storage_path, 0, ex);
108                 if (store->cache == NULL)
109                         goto fail;
110                 
111                 /* Default cache expiry - 2 weeks old, or not visited in 5 days */
112                 camel_data_cache_set_expire_age (store->cache, 60*60*24*14);
113                 camel_data_cache_set_expire_access (store->cache, 60*60*24*5);
114         }
115         
116         if (!(h = camel_service_gethost (service, ex)))
117                 goto fail;
118         
119         port = service->url->port ? service->url->port : NNTP_PORT;
120         
121 #ifdef HAVE_SSL
122         if (ssl_mode != USE_SSL_NEVER) {
123                 port = service->url->port ? service->url->port : NNTPS_PORT;
124                 tcp_stream = camel_tcp_stream_ssl_new (service->session, service->url->host, CAMEL_TCP_STREAM_SSL_ENABLE_SSL2 | CAMEL_TCP_STREAM_SSL_ENABLE_SSL3);
125         } else {
126                 tcp_stream = camel_tcp_stream_raw_new ();
127         }
128 #else
129         tcp_stream = camel_tcp_stream_raw_new ();
130 #endif /* HAVE_SSL */
131         
132         ret = camel_tcp_stream_connect (CAMEL_TCP_STREAM (tcp_stream), h, port);
133         camel_free_host (h);
134         if (ret == -1) {
135                 if (errno == EINTR)
136                         camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
137                                              _("Connection cancelled"));
138                 else
139                         camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
140                                               _("Could not connect to %s (port %d): %s"),
141                                               service->url->host, port, g_strerror (errno));
142                 
143                 camel_object_unref (tcp_stream);
144                 
145                 goto fail;
146         }
147         
148         store->stream = (CamelNNTPStream *) camel_nntp_stream_new (tcp_stream);
149         camel_object_unref (tcp_stream);
150         
151         /* Read the greeting, if any. */
152         if (camel_nntp_stream_line (store->stream, &buf, &len) == -1) {
153                 if (errno == EINTR)
154                         camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
155                                              _("Connection cancelled"));
156                 else
157                         camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
158                                               _("Could not read greeting from %s: %s"),
159                                               service->url->host, g_strerror (errno));
160                 
161                 camel_object_unref (store->stream);
162                 store->stream = NULL;
163                 
164                 goto fail;
165         }
166         
167         len = strtoul (buf, (char **) &buf, 10);
168         if (len != 200 && len != 201) {
169                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
170                                       _("NNTP server %s returned error code %d: %s"),
171                                       service->url->host, len, buf);
172                 
173                 camel_object_unref (store->stream);
174                 store->stream = NULL;
175                 
176                 goto fail;
177         }
178         
179         /* set 'reader' mode & ignore return code */
180         if (camel_nntp_command (store, (char **) &buf, "mode reader") < 0 ||
181         /* hack: inn seems to close connections if nothing is done within
182            the first ten seconds. a non-existent command is enough though */
183             camel_nntp_command (store, (char **) &buf, "date") < 0)
184             goto fail;
185         
186         path = g_build_filename (store->storage_path, ".ev-journal", NULL);
187         disco_store->diary = camel_disco_diary_new (disco_store, path, ex);
188         g_free (path);  
189         
190         retval = TRUE;
191         
192  fail:
193         CAMEL_NNTP_STORE_UNLOCK(store, command_lock);
194         return retval;
195 }
196
197 static struct {
198         char *value;
199         int mode;
200 } ssl_options[] = {
201         { "",              USE_SSL_ALWAYS        },
202         { "always",        USE_SSL_ALWAYS        },
203         { "when-possible", USE_SSL_WHEN_POSSIBLE },
204         { "never",         USE_SSL_NEVER         },
205         { NULL,            USE_SSL_NEVER         },
206 };
207
208 static gboolean
209 nntp_connect_online (CamelService *service, CamelException *ex)
210 {
211 #ifdef HAVE_SSL
212         const char *use_ssl;
213         int i, ssl_mode;
214         
215         use_ssl = camel_url_get_param (service->url, "use_ssl");
216         if (use_ssl) {
217                 for (i = 0; ssl_options[i].value; i++)
218                         if (!strcmp (ssl_options[i].value, use_ssl))
219                                 break;
220                 ssl_mode = ssl_options[i].mode;
221         } else
222                 ssl_mode = USE_SSL_NEVER;
223         
224         if (ssl_mode == USE_SSL_ALWAYS) {
225                 /* Connect via SSL */
226                 return connect_to_server (service, ssl_mode, ex);
227         } else if (ssl_mode == USE_SSL_WHEN_POSSIBLE) {
228                 /* If the server supports SSL, use it */
229                 if (!connect_to_server (service, ssl_mode, ex)) {
230                         if (camel_exception_get_id (ex) == CAMEL_EXCEPTION_SERVICE_UNAVAILABLE) {
231                                 /* The ssl port seems to be unavailable, fall back to plain NNTP */
232                                 camel_exception_clear (ex);
233                                 return connect_to_server (service, USE_SSL_NEVER, ex);
234                         } else {
235                                 return FALSE;
236                         }
237                 }
238                 
239                 return TRUE;
240         } else {
241                 /* User doesn't care about SSL */
242                 return connect_to_server (service, ssl_mode, ex);
243         }
244 #else
245         return connect_to_server (service, USE_SSL_NEVER, ex);
246 #endif
247 }
248
249 static gboolean
250 nntp_connect_offline (CamelService *service, CamelException *ex)
251 {
252         CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE(service);
253         CamelDiscoStore *disco_store = (CamelDiscoStore *) nntp_store;
254         char *path;
255         
256         if (nntp_store->storage_path == NULL)
257                 return FALSE;
258         
259         /* setup store-wide cache */
260         if (nntp_store->cache == NULL) {
261                 nntp_store->cache = camel_data_cache_new (nntp_store->storage_path, 0, ex);
262                 if (nntp_store->cache == NULL)
263                         return FALSE;
264                 
265                 /* Default cache expiry - 2 weeks old, or not visited in 5 days */
266                 camel_data_cache_set_expire_age (nntp_store->cache, 60*60*24*14);
267                 camel_data_cache_set_expire_access (nntp_store->cache, 60*60*24*5);
268         }       
269         
270         path = g_build_filename (nntp_store->storage_path, ".ev-journal", NULL);
271         disco_store->diary = camel_disco_diary_new (disco_store, path, ex);
272         g_free (path);
273         
274         if (!disco_store->diary)
275                 return FALSE;
276         
277         return TRUE;
278 }
279
280 static gboolean
281 nntp_disconnect_online (CamelService *service, gboolean clean, CamelException *ex)
282 {
283         CamelNNTPStore *store = CAMEL_NNTP_STORE (service);
284         char *line;
285         
286         CAMEL_NNTP_STORE_LOCK(store, command_lock);
287         
288         if (clean)
289                 camel_nntp_command (store, &line, "quit");
290         
291         if (!service_class->disconnect (service, clean, ex)) {
292                 CAMEL_NNTP_STORE_UNLOCK(store, command_lock);   
293                 return FALSE;
294         }
295         
296         camel_object_unref (store->stream);
297         store->stream = NULL;
298         
299         CAMEL_NNTP_STORE_UNLOCK(store, command_lock);
300         
301         return TRUE;
302 }
303
304 static gboolean
305 nntp_disconnect_offline (CamelService *service, gboolean clean, CamelException *ex)
306 {
307         CamelDiscoStore *disco = CAMEL_DISCO_STORE(service);
308         
309         if (!service_class->disconnect (service, clean, ex))
310                 return FALSE;
311         
312         if (disco->diary) {
313                 camel_object_unref (disco->diary);
314                 disco->diary = NULL;
315         }
316         
317         return TRUE;
318 }
319
320 static char *
321 nntp_store_get_name (CamelService *service, gboolean brief)
322 {
323         if (brief)
324                 return g_strdup_printf ("%s", service->url->host);
325         else
326                 return g_strdup_printf (_("USENET News via %s"), service->url->host);
327         
328 }
329
330 extern CamelServiceAuthType camel_nntp_password_authtype;
331
332 static GList *
333 nntp_store_query_auth_types (CamelService *service, CamelException *ex)
334 {
335         return g_list_append (NULL, &camel_nntp_password_authtype);
336 }
337
338 static CamelFolder *
339 nntp_get_folder(CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex)
340 {
341         CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE (store);
342         CamelFolder *folder;
343         
344         CAMEL_NNTP_STORE_LOCK(nntp_store, command_lock);
345         
346         folder = camel_nntp_folder_new(store, folder_name, ex);
347         
348         CAMEL_NNTP_STORE_UNLOCK(nntp_store, command_lock);
349         
350         return folder;
351 }
352
353 /*
354  * Converts a fully-fledged newsgroup name to a name in short dotted notation,
355  * e.g. nl.comp.os.linux.programmeren becomes n.c.o.l.programmeren
356  */
357
358 static char *
359 nntp_newsgroup_name_short (const char *name)
360 {
361         char *resptr, *tmp;
362         const char *ptr2;
363         
364         resptr = tmp = g_malloc0 (strlen (name) + 1);
365         
366         while ((ptr2 = strchr (name, '.'))) {
367                 if (ptr2 == name) {
368                         name++;
369                         continue;
370                 }
371                 
372                 *resptr++ = *name;
373                 *resptr++ = '.';
374                 name = ptr2 + 1;
375         }
376         
377         strcpy (resptr, name);
378         return tmp;
379 }
380
381 /*
382  * This function converts a NNTPStoreSummary item to a FolderInfo item that
383  * can be returned by the get_folders() call to the store. Both structs have
384  * essentially the same fields.
385  */
386
387 static CamelFolderInfo *
388 nntp_folder_info_from_store_info (CamelNNTPStore *store, gboolean short_notation, CamelStoreInfo *si)
389 {
390         CamelURL *base_url = ((CamelService *) store)->url;
391         CamelFolderInfo *fi = g_malloc0(sizeof(*fi));
392         CamelURL *url;
393         
394         fi->full_name = g_strdup (si->path);
395         
396         if (short_notation)
397                 fi->name = nntp_newsgroup_name_short (si->path);
398         else
399                 fi->name = g_strdup (si->path);
400         
401         fi->unread_message_count = -1;
402         /* fi->path is the 'canonicalised' path used by the UI (folder-tree). Not
403          * as important these days, but folders used to get added to the tree based
404          * on its path rather than the structure of the CamelFolderInfo's.
405          *
406          * must be delimited by '/' which also means that if the user doesn't want
407          * a flat list of newsgroups, you'll have to replace '.' with '/' for
408          * full_name too. */    
409         /*camel_folder_info_build_path(fi, '/');*/
410         fi->path = g_strdup_printf ("/%s", si->path);
411         url = camel_url_new_with_base (base_url, fi->path);
412         fi->url = camel_url_to_string (url, CAMEL_URL_HIDE_ALL);
413         camel_url_free (url);
414         
415         return fi;
416 }
417
418 static CamelFolderInfo *
419 nntp_folder_info_from_name (CamelNNTPStore *store, gboolean short_notation, const char *name)
420 {
421         CamelFolderInfo *fi = g_malloc0(sizeof(*fi));
422         CamelURL *base_url = ((CamelService *)store)->url;
423         CamelURL *url;
424         
425         fi->full_name = g_strdup (name);
426         
427         if (short_notation)
428                 fi->name = nntp_newsgroup_name_short (name);
429         else
430                 fi->name = g_strdup (name);
431         
432         fi->unread_message_count = -1;
433         
434         fi->path = g_strdup_printf ("/%s", name);
435         
436         url = camel_url_new_with_base (base_url, fi->path);
437         fi->url = camel_url_to_string (url, CAMEL_URL_HIDE_ALL);
438         camel_url_free (url);
439         
440         return fi;
441 }
442
443 static CamelStoreInfo *
444 nntp_store_info_from_line (CamelNNTPStore *store, char *line)
445 {
446         CamelStoreSummary *summ = (CamelStoreSummary *)store->summary;
447         CamelURL *base_url = ((CamelService *)store)->url;
448         CamelNNTPStoreInfo *nsi = (CamelNNTPStoreInfo*)camel_store_summary_info_new(summ);
449         CamelStoreInfo *si = (CamelStoreInfo*)nsi;
450         CamelURL *url;
451         char *relpath;
452         
453         relpath = g_strdup_printf ("/%s", line);
454         url = camel_url_new_with_base (base_url, relpath);
455         g_free (relpath);
456         si->uri = camel_url_to_string (url, CAMEL_URL_HIDE_ALL);
457         camel_url_free (url);
458         
459         si->path = g_strdup (line);
460         nsi->full_name = g_strdup (line);
461         return (CamelStoreInfo*) si;
462 }
463
464 static CamelFolderInfo *
465 nntp_store_get_subscribed_folder_info (CamelNNTPStore *store, const char *top, guint flags, CamelException *ex)
466 {
467         int i;
468         CamelStoreInfo *si;
469         CamelFolderInfo *first = NULL, *last = NULL, *fi = NULL;
470         
471         /* since we do not do a tree, any request that is not for root is sure to give no results */
472         if (top != NULL && top[0] != 0)
473                 return NULL;
474         
475         for (i=0;(si = camel_store_summary_index ((CamelStoreSummary *) store->summary, i));i++) {
476                 if (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) {
477                         fi = nntp_folder_info_from_store_info (store, store->do_short_folder_notation, si);
478                         if (!fi)
479                                 continue;
480                         fi->flags |= CAMEL_FOLDER_NOINFERIORS | CAMEL_FOLDER_NOCHILDREN;
481                         if (last)
482                                 last->sibling = fi;
483                         else
484                                 first = fi;
485                         last = fi;
486                 }
487                 camel_store_summary_info_free ((CamelStoreSummary *) store->summary, si);
488         }
489         
490         return first;
491 }
492
493 /*
494  * get folder info, using the information in our StoreSummary
495  */
496 static CamelFolderInfo *
497 nntp_store_get_cached_folder_info (CamelNNTPStore *store, const char *orig_top, guint flags, CamelException *ex)
498 {
499         int len, i;
500         int subscribed_or_flag = (flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED) ? 0 : 1,
501             root_or_flag = (orig_top == NULL || orig_top[0] == '\0') ? 1 : 0,
502             recursive_flag = flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE;
503         CamelStoreInfo *si;
504         CamelFolderInfo *first = NULL, *last = NULL, *fi = NULL;
505         char *tmpname;
506         char *top = g_strconcat(orig_top?orig_top:"", ".", NULL);
507         int toplen = strlen(top);
508         
509         for (i = 0; (si = camel_store_summary_index ((CamelStoreSummary *) store->summary, i)); i++) {
510                 if ((subscribed_or_flag || (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED)) &&
511                      (root_or_flag || g_ascii_strncasecmp (si->path, top, toplen) == 0)) {
512                         if (recursive_flag || strchr (si->path + toplen, '.') == NULL) {
513                                 /* add the item */
514                                 fi = nntp_folder_info_from_store_info(store, FALSE, si);
515                                 if (!fi)
516                                         continue;
517                                 if (store->folder_hierarchy_relative) {
518                                         g_free (fi->name);
519                                         fi->name = g_strdup (si->path + ((toplen == 1) ? 0 : toplen));
520                                 }
521                         } else {
522                                 /* apparently, this is an indirect subitem. if it's not a subitem of
523                                    the item we added last, we need to add a portion of this item to
524                                    the list as a placeholder */
525                                 len = strlen (last->full_name);
526                                 if (!last ||
527                                     g_ascii_strncasecmp(si->path, last->full_name, len) != 0 || 
528                                     si->path[len] != '.') {
529                                         tmpname = g_strdup(si->path);
530                                         *(strchr(tmpname + toplen, '.')) = '\0';
531                                         fi = nntp_folder_info_from_name(store, FALSE, tmpname);
532                                         fi->flags |= CAMEL_FOLDER_NOSELECT;
533                                         if (store->folder_hierarchy_relative) {
534                                                 g_free(fi->name);
535                                                 fi->name = g_strdup(tmpname + ((toplen==1) ? 0 : toplen));
536                                         }
537                                         g_free(tmpname);
538                                 } else {
539                                         continue;
540                                 }
541                         }
542                         if (last)
543                                 last->sibling = fi;
544                         else
545                                 first = fi;
546                         last = fi;
547                 } else if (subscribed_or_flag && first) {
548                         /* we have already added subitems, but this item is no longer a subitem */
549                         camel_store_summary_info_free((CamelStoreSummary *)store->summary, si);
550                         break;
551                 }
552                 camel_store_summary_info_free((CamelStoreSummary *)store->summary, si);
553         }
554         
555         g_free(top);
556         return first;
557 }
558
559 /* retrieves the date from the NNTP server */
560 static gboolean
561 nntp_get_date(CamelNNTPStore *nntp_store)
562 {
563         unsigned char *line;
564         int ret = camel_nntp_command(nntp_store, (char **)&line, "date");
565         char *ptr;
566         
567         nntp_store->summary->last_newslist[0] = 0;
568         
569         if (ret == 111) {
570                 ptr = line + 3;
571                 while (*ptr == ' ' || *ptr == '\t')
572                         ptr++;
573                 
574                 if (strlen (ptr) == NNTP_DATE_SIZE) {
575                         memcpy (nntp_store->summary->last_newslist, ptr, NNTP_DATE_SIZE);
576                         return TRUE;
577                 }
578         }
579         return FALSE;
580 }
581
582 static gint
583 store_info_sort (gconstpointer a, gconstpointer b)
584 {
585         return strcmp ((*(CamelNNTPStoreInfo**) a)->full_name, (*(CamelNNTPStoreInfo**) b)->full_name);
586 }
587
588 static CamelFolderInfo *
589 nntp_store_get_folder_info_all(CamelNNTPStore *nntp_store, const char *top, guint32 flags, gboolean online, CamelException *ex)
590 {
591         CamelNNTPStoreSummary *summary = nntp_store->summary;
592         CamelStoreInfo *si;
593         unsigned int len;
594         unsigned char *line, *space;
595         int ret = -1;
596         
597         if (top == NULL)
598                 top = "";
599         
600         if (online && (top == NULL || top[0] == 0)) {
601                 /* we may need to update */
602                 if (summary->last_newslist[0] != 0) {
603                         char date[14];
604                         memcpy(date, summary->last_newslist + 2, 6); /* YYMMDDD */
605                         date[6] = ' ';
606                         memcpy(date + 7, summary->last_newslist + 8, 6); /* HHMMSS */
607                         date[13] = '\0';
608                         
609                         nntp_get_date (nntp_store);
610                         
611                         ret = camel_nntp_command (nntp_store, (char **) &line, "newgroups %s", date);
612                         if (ret != 231) {
613                                 /* newgroups not supported :S so reload the complete list */
614                                 ret = -1;
615                                 camel_store_summary_clear ((CamelStoreSummary*) summary);
616                                 summary->last_newslist[0] = 0;
617                                 goto do_complete_list;
618                         }
619                         
620                         while ((ret = camel_nntp_stream_line (nntp_store->stream, &line, &len)) > 0) {
621                                 if ((space = strchr(line, ' ')))
622                                         *space = '\0';
623                                 
624                                 si = camel_store_summary_path ((CamelStoreSummary *) nntp_store->summary, line);
625                                 if (si) {
626                                         camel_store_summary_info_free ((CamelStoreSummary *) nntp_store->summary, si);
627                                 } else {
628                                         si = nntp_store_info_from_line (nntp_store, line);
629                                         camel_store_summary_add ((CamelStoreSummary*) nntp_store->summary, si);
630                                 }
631                         }
632                 } else {
633                 do_complete_list:
634                         /* seems we do need a complete list */
635                         /* at first, we do a DATE to find out the last load occasion */
636                         nntp_get_date (nntp_store);
637                         
638                         ret = camel_nntp_command (nntp_store, (char **)&line, "list");
639                         if (ret != 215) {
640                                 if (ret < 0)
641                                         line = _("Stream error");
642                                 ret = -1;
643                                 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_INVALID,
644                                                       _("Error retrieving newsgroups:\n\n%s"), line);
645                                 goto error;
646                         }
647                         
648                         while ((ret = camel_nntp_stream_line(nntp_store->stream, &line, &len)) > 0) {
649                                 if ((space = strchr(line, ' ')))
650                                         *space = '\0';
651                                 
652                                 si = nntp_store_info_from_line (nntp_store, line);
653                                 camel_store_summary_add ((CamelStoreSummary*) nntp_store->summary, si);
654                                 /* check to see if it answers our current query */
655                         }
656                 }
657                 
658                 /* sort the list */
659                 g_ptr_array_sort (CAMEL_STORE_SUMMARY (nntp_store->summary)->folders, store_info_sort);
660                 if (ret < 0)
661                         goto error;
662                 
663                 camel_store_summary_save ((CamelStoreSummary *) nntp_store->summary);
664         }
665         
666         return nntp_store_get_cached_folder_info (nntp_store, top, flags, ex);
667  error:
668         return NULL;
669 }
670
671 static CamelFolderInfo *
672 nntp_get_folder_info (CamelStore *store, const char *top, guint32 flags, gboolean online, CamelException *ex)
673 {
674         CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE(store);
675         CamelFolderInfo *first = NULL;
676         
677         dd(printf("g_f_i: fast %d subscr %d recursive %d online %d top \"%s\"\n",
678                 flags & CAMEL_STORE_FOLDER_INFO_FAST,
679                 flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED,
680                 flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE,
681                 online,
682                 top?top:""));
683         
684         CAMEL_NNTP_STORE_LOCK(nntp_store, command_lock);
685         
686         if (flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED)
687                 first = nntp_store_get_subscribed_folder_info (nntp_store, top, flags, ex);
688         else
689                 first = nntp_store_get_folder_info_all (nntp_store, top, flags, online, ex);
690         
691         CAMEL_NNTP_STORE_UNLOCK(nntp_store, command_lock);
692         return first;
693 }
694
695 static CamelFolderInfo *
696 nntp_get_folder_info_online (CamelStore *store, const char *top, guint32 flags, CamelException *ex)
697 {
698         return nntp_get_folder_info (store, top, flags, TRUE, ex);
699 }
700
701 static CamelFolderInfo *
702 nntp_get_folder_info_offline(CamelStore *store, const char *top, guint32 flags, CamelException *ex)
703 {
704         return nntp_get_folder_info (store, top, flags, FALSE, ex);
705 }
706
707 static gboolean
708 nntp_store_folder_subscribed (CamelStore *store, const char *folder_name)
709 {
710         CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE (store);
711         CamelStoreInfo *si;
712         int truth = FALSE;
713         
714         si = camel_store_summary_path ((CamelStoreSummary *) nntp_store->summary, folder_name);
715         if (si) {
716                 truth = (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) != 0;
717                 camel_store_summary_info_free ((CamelStoreSummary *) nntp_store->summary, si);
718         }
719
720         return truth;
721 }
722
723 static void
724 nntp_store_subscribe_folder (CamelStore *store, const char *folder_name,
725                              CamelException *ex)
726 {
727         CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE(store);
728         CamelStoreInfo *si;
729         CamelFolderInfo *fi;
730         
731         CAMEL_NNTP_STORE_LOCK(nntp_store, command_lock);
732         
733         si = camel_store_summary_path(CAMEL_STORE_SUMMARY(nntp_store->summary), folder_name);
734         if (!si) {
735                 camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID,
736                                       _("You cannot subscribe to this newsgroup:\n\n"
737                                         "No such newsgroup. The selected item is a probably a parent folder."));
738         } else {
739                 if (!(si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED)) {
740                         si->flags |= CAMEL_STORE_INFO_FOLDER_SUBSCRIBED;
741                         fi = nntp_folder_info_from_store_info(nntp_store, nntp_store->do_short_folder_notation, si);
742                         fi->flags |= CAMEL_FOLDER_NOINFERIORS | CAMEL_FOLDER_NOCHILDREN;
743                         camel_store_summary_touch ((CamelStoreSummary *) nntp_store->summary);
744                         camel_store_summary_save ((CamelStoreSummary *) nntp_store->summary);
745                         CAMEL_NNTP_STORE_UNLOCK(nntp_store, command_lock);
746                         camel_object_trigger_event ((CamelObject *) nntp_store, "folder_subscribed", fi);
747                         camel_folder_info_free (fi);
748                         return;
749                 }
750         }
751         
752         CAMEL_NNTP_STORE_UNLOCK(nntp_store, command_lock);
753 }
754
755 static void
756 nntp_store_unsubscribe_folder (CamelStore *store, const char *folder_name,
757                                CamelException *ex)
758 {
759         CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE(store);
760         CamelFolderInfo *fi;
761         CamelStoreInfo *fitem;
762         CAMEL_NNTP_STORE_LOCK(nntp_store, command_lock);
763         
764         fitem = camel_store_summary_path(CAMEL_STORE_SUMMARY(nntp_store->summary), folder_name);
765         
766         if (!fitem) {
767                 camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID,
768                                       _("You cannot unsubscribe to this newsgroup:\n\n"
769                                         "newsgroup does not exist!"));
770         } else {
771                 if (fitem->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) {
772                         fitem->flags &= ~CAMEL_STORE_INFO_FOLDER_SUBSCRIBED;
773                         fi = nntp_folder_info_from_store_info (nntp_store, nntp_store->do_short_folder_notation, fitem);
774                         camel_store_summary_touch ((CamelStoreSummary *) nntp_store->summary);
775                         camel_store_summary_save ((CamelStoreSummary *) nntp_store->summary);
776                         CAMEL_NNTP_STORE_UNLOCK(nntp_store, command_lock);
777                         camel_object_trigger_event ((CamelObject *) nntp_store, "folder_unsubscribed", fi);
778                         camel_folder_info_free (fi);
779                         return;
780                 }
781         }
782         
783         CAMEL_NNTP_STORE_UNLOCK(nntp_store, command_lock);
784 }
785
786 /* stubs for various folder operations we're not implementing */
787
788 static CamelFolderInfo *
789 nntp_create_folder (CamelStore *store, const char *parent_name,
790                     const char *folder_name, CamelException *ex)
791 {
792         camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID,
793                     _("You cannot create a folder in a News store: subscribe instead."));
794         return NULL;
795 }
796
797 static void
798 nntp_rename_folder (CamelStore *store, const char *old_name, const char *new_name_in, CamelException *ex)
799 {
800         camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID,
801                   _("You cannot rename a folder in a News store."));
802 }
803
804 static void
805 nntp_delete_folder (CamelStore *store, const char *folder_name, CamelException *ex)
806 {
807         nntp_store_subscribe_folder (store, folder_name, ex);
808         camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID,
809                   _("You cannot remove a folder in a News store: unsubscribe instead."));
810         return;
811 }
812
813 static void
814 nntp_store_finalize (CamelObject *object)
815 {
816         /* call base finalize */
817         CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE (object);
818         struct _CamelNNTPStorePrivate *p = nntp_store->priv;
819         
820         camel_service_disconnect ((CamelService *)object, TRUE, NULL);
821         
822         if (nntp_store->summary) {
823                 camel_store_summary_save ((CamelStoreSummary *) nntp_store->summary);
824                 camel_object_unref (nntp_store->summary);
825         }       
826
827         camel_object_unref (nntp_store->mem);
828         nntp_store->mem = NULL;
829         if (nntp_store->stream)
830                 camel_object_unref (nntp_store->stream);
831         
832         if (nntp_store->base_url)
833                 g_free (nntp_store->base_url);
834         if (nntp_store->storage_path)
835                 g_free (nntp_store->storage_path);
836         
837         e_mutex_destroy(p->command_lock);
838         
839         g_free(p);
840 }
841
842 static void
843 nntp_store_class_init (CamelNNTPStoreClass *camel_nntp_store_class)
844 {
845         CamelDiscoStoreClass *camel_disco_store_class = CAMEL_DISCO_STORE_CLASS (camel_nntp_store_class);
846         CamelStoreClass *camel_store_class = CAMEL_STORE_CLASS (camel_nntp_store_class);
847         CamelServiceClass *camel_service_class = CAMEL_SERVICE_CLASS (camel_nntp_store_class);
848
849         parent_class = CAMEL_DISCO_STORE_CLASS (camel_type_get_global_classfuncs (camel_disco_store_get_type ()));
850         service_class = CAMEL_SERVICE_CLASS (camel_type_get_global_classfuncs (camel_service_get_type ()));
851         
852         /* virtual method overload */
853         camel_service_class->construct = nntp_construct;
854         camel_service_class->query_auth_types = nntp_store_query_auth_types;
855         camel_service_class->get_name = nntp_store_get_name;
856         
857         camel_disco_store_class->can_work_offline = nntp_can_work_offline;
858         camel_disco_store_class->connect_online = nntp_connect_online;
859         camel_disco_store_class->connect_offline = nntp_connect_offline;
860         camel_disco_store_class->disconnect_online = nntp_disconnect_online;
861         camel_disco_store_class->disconnect_offline = nntp_disconnect_offline;
862         camel_disco_store_class->get_folder_online = nntp_get_folder;
863         camel_disco_store_class->get_folder_resyncing = nntp_get_folder;
864         camel_disco_store_class->get_folder_offline = nntp_get_folder;
865         
866         camel_disco_store_class->get_folder_info_online = nntp_get_folder_info_online;
867         camel_disco_store_class->get_folder_info_resyncing = nntp_get_folder_info_online;
868         camel_disco_store_class->get_folder_info_offline = nntp_get_folder_info_offline;
869         
870         camel_store_class->free_folder_info = camel_store_free_folder_info_full;
871         
872         camel_store_class->folder_subscribed = nntp_store_folder_subscribed;
873         camel_store_class->subscribe_folder = nntp_store_subscribe_folder;
874         camel_store_class->unsubscribe_folder = nntp_store_unsubscribe_folder;
875
876         camel_store_class->create_folder = nntp_create_folder;
877         camel_store_class->delete_folder = nntp_delete_folder;
878         camel_store_class->rename_folder = nntp_rename_folder;
879 }
880
881 /* construction function in which we set some basic store properties */
882 static void
883 nntp_construct (CamelService *service, CamelSession *session,
884                 CamelProvider *provider, CamelURL *url,
885                 CamelException *ex)
886 {
887         CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE(service);
888         CamelURL *summary_url;
889         char *tmp;
890         
891         /* construct the parent first */
892         CAMEL_SERVICE_CLASS (parent_class)->construct (service, session, provider, url, ex);
893         if (camel_exception_is_set (ex))
894                 return;
895         
896         /* find out the storage path, base url */
897         nntp_store->storage_path = camel_session_get_storage_path (session, service, ex);
898         if (!nntp_store->storage_path)
899                 return;
900         
901         /* FIXME */
902         nntp_store->base_url = camel_url_to_string (service->url, (CAMEL_URL_HIDE_PASSWORD |
903                                                                    CAMEL_URL_HIDE_PARAMS |
904                                                                    CAMEL_URL_HIDE_AUTH));
905         
906         tmp = g_build_filename (nntp_store->storage_path, ".ev-store-summary", NULL);
907         nntp_store->summary = camel_nntp_store_summary_new ();
908         camel_store_summary_set_filename ((CamelStoreSummary *) nntp_store->summary, tmp);      
909         summary_url = camel_url_new (nntp_store->base_url, NULL);
910         camel_store_summary_set_uri_base ((CamelStoreSummary *) nntp_store->summary, summary_url);
911         g_free (tmp);
912         
913         camel_url_free (summary_url);
914         if (camel_store_summary_load ((CamelStoreSummary *)nntp_store->summary) == 0)
915                 ;
916         
917         /* get options */
918         if (camel_url_get_param (url, "show_short_notation"))
919                 nntp_store->do_short_folder_notation = TRUE;
920         else
921                 nntp_store->do_short_folder_notation = FALSE;
922         if (camel_url_get_param (url, "folder_hierarchy_relative"))
923                 nntp_store->folder_hierarchy_relative = TRUE;
924         else
925                 nntp_store->folder_hierarchy_relative = FALSE;
926 }
927
928
929 static void
930 nntp_store_init (gpointer object, gpointer klass)
931 {
932         CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE(object);
933         CamelStore *store = CAMEL_STORE (object);
934         struct _CamelNNTPStorePrivate *p;
935         
936         store->flags = CAMEL_STORE_SUBSCRIPTIONS;
937         
938         nntp_store->mem = (CamelStreamMem *)camel_stream_mem_new();
939         
940         p = nntp_store->priv = g_malloc0(sizeof(*p));
941         p->command_lock = e_mutex_new(E_MUTEX_REC);
942 }
943
944 CamelType
945 camel_nntp_store_get_type (void)
946 {
947         static CamelType camel_nntp_store_type = CAMEL_INVALID_TYPE;
948         
949         if (camel_nntp_store_type == CAMEL_INVALID_TYPE) {
950                 camel_nntp_store_type =
951                         camel_type_register (CAMEL_DISCO_STORE_TYPE,
952                                              "CamelNNTPStore",
953                                              sizeof (CamelNNTPStore),
954                                              sizeof (CamelNNTPStoreClass),
955                                              (CamelObjectClassInitFunc) nntp_store_class_init,
956                                              NULL,
957                                              (CamelObjectInitFunc) nntp_store_init,
958                                              (CamelObjectFinalizeFunc) nntp_store_finalize);
959         }
960         
961         return camel_nntp_store_type;
962 }
963
964 /* enter owning lock */
965 int
966 camel_nntp_store_set_folder (CamelNNTPStore *store, CamelFolder *folder, CamelFolderChangeInfo *changes, CamelException *ex)
967 {
968         int ret;
969         
970         if (store->current_folder && strcmp (folder->full_name, store->current_folder) == 0)
971                 return 0;
972         
973         /* FIXME: Do something with changeinfo */
974         ret = camel_nntp_summary_check ((CamelNNTPSummary *) folder->summary, changes, ex);
975         
976         g_free (store->current_folder);
977         store->current_folder = g_strdup (folder->full_name);
978         
979         return ret;
980 }
981
982 static gboolean
983 camel_nntp_try_authenticate (CamelNNTPStore *store)
984 {
985         CamelService *service = (CamelService *) store;
986         CamelSession *session = camel_service_get_session (service);
987         int ret;
988         char *line;
989         
990         if (!service->url->user)
991                 return FALSE;
992         
993         /* if nessecary, prompt for the password */
994         if (!service->url->passwd) {
995                 CamelException ex;
996                 char *prompt;
997                 
998                 prompt = g_strdup_printf (_("Please enter the NNTP password for %s@%s"),
999                                           service->url->user,
1000                                           service->url->host);
1001                 
1002                 camel_exception_init (&ex);
1003                 
1004                 service->url->passwd =
1005                         camel_session_get_password (session, prompt, FALSE, TRUE,
1006                                                     service, "password", &ex);
1007                 camel_exception_clear (&ex);
1008                 g_free (prompt);
1009                 
1010                 if (!service->url->passwd)
1011                         return FALSE;
1012         }
1013
1014         /* now, send auth info (currently, only authinfo user/pass is supported) */
1015         ret = camel_nntp_command(store, &line, "authinfo user %s", service->url->user);
1016         if (ret == NNTP_AUTH_ACCEPTED) {
1017                 return TRUE;
1018         } else if (ret == NNTP_AUTH_CONTINUE) {
1019                 ret = camel_nntp_command (store, &line, "authinfo pass %s", service->url->passwd);
1020                 if (ret == NNTP_AUTH_ACCEPTED)
1021                         return TRUE;
1022                 else
1023                         return FALSE;
1024         } else
1025                 return FALSE;
1026 }
1027
1028 static gboolean
1029 nntp_connected (CamelNNTPStore *store, CamelException *ex)
1030 {
1031         if (store->stream == NULL)
1032                 return camel_service_connect (CAMEL_SERVICE (store), ex);
1033         
1034         return TRUE;
1035 }
1036
1037 /* Enter owning lock */
1038 int
1039 camel_nntp_command (CamelNNTPStore *store, char **line, const char *fmt, ...)
1040 {
1041         const unsigned char *p, *ps;
1042         unsigned char c;
1043         va_list ap;
1044         char *s;
1045         int d;
1046         unsigned int u, u2;
1047         
1048         e_mutex_assert_locked(store->priv->command_lock);
1049         
1050         if (!nntp_connected (store, NULL))
1051                 return -1;
1052         
1053         /* Check for unprocessed data, ! */
1054         if (store->stream->mode == CAMEL_NNTP_STREAM_DATA) {
1055                 g_warning("Unprocessed data left in stream, flushing");
1056                 while (camel_nntp_stream_getd(store->stream, (unsigned char **)&p, &u) > 0)
1057                         ;
1058         }
1059         camel_nntp_stream_set_mode(store->stream, CAMEL_NNTP_STREAM_LINE);
1060         
1061  command_begin_send:
1062         va_start(ap, fmt);
1063         ps = p = fmt;
1064         while ((c = *p++)) {
1065                 switch (c) {
1066                 case '%':
1067                         c = *p++;
1068                         camel_stream_write ((CamelStream *) store->mem, ps, p - ps - (c == '%' ? 1 : 2));
1069                         ps = p;
1070                         switch (c) {
1071                         case 's':
1072                                 s = va_arg(ap, char *);
1073                                 camel_stream_write((CamelStream *)store->mem, s, strlen(s));
1074                                 break;
1075                         case 'd':
1076                                 d = va_arg(ap, int);
1077                                 camel_stream_printf((CamelStream *)store->mem, "%d", d);
1078                                 break;
1079                         case 'u':
1080                                 u = va_arg(ap, unsigned int);
1081                                 camel_stream_printf((CamelStream *)store->mem, "%u", u);
1082                                 break;
1083                         case 'm':
1084                                 s = va_arg(ap, char *);
1085                                 camel_stream_printf((CamelStream *)store->mem, "<%s>", s);
1086                                 break;
1087                         case 'r':
1088                                 u = va_arg(ap, unsigned int);
1089                                 u2 = va_arg(ap, unsigned int);
1090                                 if (u == u2)
1091                                         camel_stream_printf((CamelStream *)store->mem, "%u", u);
1092                                 else
1093                                         camel_stream_printf((CamelStream *)store->mem, "%u-%u", u, u2);
1094                                 break;
1095                         default:
1096                                 g_warning("Passing unknown format to nntp_command: %c\n", c);
1097                                 g_assert(0);
1098                         }
1099                 }
1100         }
1101         
1102         camel_stream_write ((CamelStream *) store->mem, ps, p-ps-1);
1103         dd(printf("NNTP_COMMAND: '%.*s'\n", (int)store->mem->buffer->len, store->mem->buffer->data));
1104         camel_stream_write ((CamelStream *) store->mem, "\r\n", 2);
1105         
1106         if (camel_stream_write ((CamelStream *) store->stream, store->mem->buffer->data, store->mem->buffer->len) == -1 && errno != EINTR) {
1107                 camel_stream_reset ((CamelStream *) store->mem);
1108                 /* FIXME: hack */
1109                 g_byte_array_set_size (store->mem->buffer, 0);
1110                 
1111         reconnect:
1112                 /* some error, re-connect */
1113                 camel_service_disconnect (CAMEL_SERVICE (store), FALSE, NULL);
1114                 
1115                 if (!nntp_connected (store, NULL))
1116                         return -1;
1117                 
1118                 goto command_begin_send;
1119         }
1120         
1121         camel_stream_reset ((CamelStream *) store->mem);
1122         /* FIXME: hack */
1123         g_byte_array_set_size (store->mem->buffer, 0);
1124         
1125         if (camel_nntp_stream_line (store->stream, (unsigned char **) line, &u) == -1)
1126                 return -1;
1127         
1128         u = strtoul (*line, NULL, 10);
1129         
1130         /* Check for 'authentication required' codes */
1131         if (u == NNTP_AUTH_REQUIRED &&
1132             camel_nntp_try_authenticate(store))
1133                 goto command_begin_send;
1134         
1135         /* the server doesn't like us anymore, but we still like her! */
1136         if (u == 401 || u == 503)
1137                 goto reconnect;
1138         
1139         /* Handle all switching to data mode here, to make callers job easier */
1140         if (u == 215 || (u >= 220 && u <=224) || (u >= 230 && u <= 231))
1141                 camel_nntp_stream_set_mode(store->stream, CAMEL_NNTP_STREAM_DATA);
1142         
1143         return u;
1144 }