1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
4 * Copyright (C) 2001-2003 Ximian, Inc. <www.ximain.com>
6 * Authors: Christopher Toshok <toshok@ximian.com>
7 * Michael Zucchi <notzed@ximian.com>
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.
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.
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
33 #include <sys/types.h>
35 #include <glib/gi18n-lib.h>
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"
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"
58 #define dd(x) (camel_debug("nntp")?(x):0)
60 #define NNTP_PORT "119"
61 #define NNTPS_PORT "563"
63 #define DUMP_EXTENSIONS
65 static CamelDiscoStoreClass *parent_class = NULL;
66 static CamelServiceClass *service_class = NULL;
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))
73 static int camel_nntp_try_authenticate (CamelNNTPStore *store, CamelException *ex);
75 static void nntp_construct (CamelService *service, CamelSession *session,
76 CamelProvider *provider, CamelURL *url,
81 nntp_can_work_offline(CamelDiscoStore *store)
99 xover_setup(CamelNNTPStore *store, CamelException *ex)
105 struct _xover_header *xover, *last;
107 /* manual override */
108 if (store->xover || getenv("CAMEL_NNTP_DISABLE_XOVER") != NULL)
111 ret = camel_nntp_raw_command_auth(store, ex, &line, "list overview.fmt");
114 } else if (ret != 215)
115 /* unsupported command? ignore */
118 last = (struct _xover_header *)&store->xover;
120 /* supported command */
121 while ((ret = camel_nntp_stream_line(store->stream, (unsigned char **)&line, &len)) > 0) {
123 xover = g_malloc0(sizeof(*xover));
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;
136 xover->type = headers[i].type;
142 p[-1] = camel_tolower(c);
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)
162 connect_to_server (CamelService *service, struct addrinfo *ai, int ssl_mode, CamelException *ex)
164 CamelNNTPStore *store = (CamelNNTPStore *) service;
165 CamelDiscoStore *disco_store = (CamelDiscoStore*) service;
166 CamelStream *tcp_stream;
167 gboolean retval = FALSE;
172 CAMEL_SERVICE_REC_LOCK(store, connect_lock);
174 if (ssl_mode != MODE_CLEAR) {
176 if (ssl_mode == MODE_TLS) {
177 tcp_stream = camel_tcp_stream_ssl_new_raw (service->session, service->url->host, STARTTLS_FLAGS);
179 tcp_stream = camel_tcp_stream_ssl_new (service->session, service->url->host, SSL_PORT_FLAGS);
182 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
183 _("Could not connect to %s: %s"),
184 service->url->host, _("SSL unavailable"));
187 #endif /* HAVE_SSL */
189 tcp_stream = camel_tcp_stream_raw_new ();
192 if (camel_tcp_stream_connect ((CamelTcpStream *) tcp_stream, ai) == -1) {
194 camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
195 _("Connection canceled"));
197 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
198 _("Could not connect to %s: %s"),
202 camel_object_unref (tcp_stream);
207 store->stream = (CamelNNTPStream *) camel_nntp_stream_new (tcp_stream);
208 camel_object_unref (tcp_stream);
210 /* Read the greeting, if any. */
211 if (camel_nntp_stream_line (store->stream, &buf, &len) == -1) {
213 camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
214 _("Connection canceled"));
216 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
217 _("Could not read greeting from %s: %s"),
218 service->url->host, g_strerror (errno));
220 camel_object_unref (store->stream);
221 store->stream = NULL;
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);
232 camel_object_unref (store->stream);
233 store->stream = NULL;
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)
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)
249 if (xover_setup(store, ex) == -1)
252 path = g_build_filename (store->storage_path, ".ev-journal", NULL);
253 disco_store->diary = camel_disco_diary_new (disco_store, path, ex);
258 g_free(store->current_folder);
259 store->current_folder = NULL;
262 CAMEL_SERVICE_REC_UNLOCK(store, connect_lock);
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 },
280 nntp_connect_online (CamelService *service, CamelException *ex)
282 struct addrinfo hints, *ai;
283 const char *ssl_mode;
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))
292 mode = ssl_options[i].mode;
293 serv = ssl_options[i].serv;
294 port = ssl_options[i].port;
301 if (service->url->port) {
302 serv = g_alloca (16);
303 sprintf (serv, "%d", service->url->port);
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);
318 ret = connect_to_server (service, ai, mode, ex);
320 camel_freeaddrinfo (ai);
326 nntp_connect_offline (CamelService *service, CamelException *ex)
328 CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE(service);
329 CamelDiscoStore *disco_store = (CamelDiscoStore *) nntp_store;
332 if (nntp_store->storage_path == NULL)
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)
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);
346 path = g_build_filename (nntp_store->storage_path, ".ev-journal", NULL);
347 disco_store->diary = camel_disco_diary_new (disco_store, path, ex);
350 if (!disco_store->diary)
357 nntp_disconnect_online (CamelService *service, gboolean clean, CamelException *ex)
359 CamelNNTPStore *store = CAMEL_NNTP_STORE (service);
362 CAMEL_SERVICE_REC_LOCK(store, connect_lock);
365 camel_nntp_raw_command (store, ex, &line, "quit");
366 camel_exception_clear(ex);
369 if (!service_class->disconnect (service, clean, ex)) {
370 CAMEL_SERVICE_REC_UNLOCK(store, connect_lock);
374 camel_object_unref (store->stream);
375 store->stream = NULL;
376 g_free(store->current_folder);
377 store->current_folder = NULL;
379 CAMEL_SERVICE_REC_UNLOCK(store, connect_lock);
385 nntp_disconnect_offline (CamelService *service, gboolean clean, CamelException *ex)
387 CamelDiscoStore *disco = CAMEL_DISCO_STORE(service);
389 if (!service_class->disconnect (service, clean, ex))
393 camel_object_unref (disco->diary);
401 nntp_store_get_name (CamelService *service, gboolean brief)
404 return g_strdup_printf ("%s", service->url->host);
406 return g_strdup_printf (_("USENET News via %s"), service->url->host);
410 extern CamelServiceAuthType camel_nntp_password_authtype;
413 nntp_store_query_auth_types (CamelService *service, CamelException *ex)
415 return g_list_append (NULL, &camel_nntp_password_authtype);
419 nntp_get_folder(CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex)
421 CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE (store);
424 CAMEL_SERVICE_REC_LOCK(nntp_store, connect_lock);
426 folder = camel_nntp_folder_new(store, folder_name, ex);
428 CAMEL_SERVICE_REC_UNLOCK(nntp_store, connect_lock);
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
439 nntp_newsgroup_name_short (const char *name)
444 resptr = tmp = g_malloc0 (strlen (name) + 1);
446 while ((ptr2 = strchr (name, '.'))) {
457 strcpy (resptr, name);
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.
467 static CamelFolderInfo *
468 nntp_folder_info_from_store_info (CamelNNTPStore *store, gboolean short_notation, CamelStoreInfo *si)
470 CamelURL *base_url = ((CamelService *) store)->url;
471 CamelFolderInfo *fi = g_malloc0(sizeof(*fi));
475 fi->full_name = g_strdup (si->path);
478 fi->name = nntp_newsgroup_name_short (si->path);
480 fi->name = g_strdup (si->path);
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);
494 static CamelFolderInfo *
495 nntp_folder_info_from_name (CamelNNTPStore *store, gboolean short_notation, const char *name)
497 CamelFolderInfo *fi = g_malloc0(sizeof(*fi));
498 CamelURL *base_url = ((CamelService *)store)->url;
502 fi->full_name = g_strdup (name);
505 fi->name = nntp_newsgroup_name_short (name);
507 fi->name = g_strdup (name);
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);
520 /* handle list/newgroups response */
521 static CamelNNTPStoreInfo *
522 nntp_store_info_update(CamelNNTPStore *store, char *line)
524 CamelStoreSummary *summ = (CamelStoreSummary *)store->summary;
525 CamelURL *base_url = ((CamelService *)store)->url;
526 CamelNNTPStoreInfo *si, *fsi;
529 guint32 last = 0, first = 0, new = 0;
531 tmp = strchr(line, ' ');
535 fsi = si = (CamelNNTPStoreInfo *)camel_store_summary_path((CamelStoreSummary *)store->summary, line);
537 si = (CamelNNTPStoreInfo*)camel_store_summary_info_new(summ);
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);
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);
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;
562 printf("store info update '%s' first '%u' last '%u'\n", line, first, last);
572 si->info.total = last > first?last-first:0;
573 si->info.unread += new; /* this is a _guess_ */
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);
585 static CamelFolderInfo *
586 nntp_store_get_subscribed_folder_info (CamelNNTPStore *store, const char *top, guint flags, CamelException *ex)
590 CamelFolderInfo *first = NULL, *last = NULL, *fi = NULL;
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)
596 for (i=0;(si = camel_store_summary_index ((CamelStoreSummary *) store->summary, i));i++) {
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;
607 folder = (CamelNNTPFolder *)camel_store_get_folder((CamelStore *)store, si->path, 0, ex);
609 CamelFolderChangeInfo *changes = NULL;
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();
617 CAMEL_SERVICE_REC_UNLOCK(store, connect_lock);
619 camel_object_trigger_event((CamelObject *) folder, "folder_changed", changes);
620 camel_folder_change_info_free(changes);
622 camel_object_unref(folder);
624 camel_exception_clear(ex);
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;
634 camel_store_summary_info_free ((CamelStoreSummary *) store->summary, si);
641 * get folder info, using the information in our StoreSummary
643 static CamelFolderInfo *
644 nntp_store_get_cached_folder_info (CamelNNTPStore *store, const char *orig_top, guint flags, CamelException *ex)
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;
651 CamelFolderInfo *first = NULL, *last = NULL, *fi = NULL;
653 char *top = g_strconcat(orig_top?orig_top:"", ".", NULL);
654 int toplen = strlen(top);
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) {
661 fi = nntp_folder_info_from_store_info(store, FALSE, si);
664 if (store->folder_hierarchy_relative) {
666 fi->name = g_strdup (si->path + ((toplen == 1) ? 0 : toplen));
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 */
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) {
681 fi->name = g_strdup(tmpname + ((toplen==1) ? 0 : toplen));
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);
698 camel_store_summary_info_free((CamelStoreSummary *)store->summary, si);
705 /* retrieves the date from the NNTP server */
707 nntp_get_date(CamelNNTPStore *nntp_store, CamelException *ex)
710 int ret = camel_nntp_command(nntp_store, ex, NULL, (char **)&line, "date");
713 nntp_store->summary->last_newslist[0] = 0;
717 while (*ptr == ' ' || *ptr == '\t')
720 if (strlen (ptr) == NNTP_DATE_SIZE) {
721 memcpy (nntp_store->summary->last_newslist, ptr, NNTP_DATE_SIZE);
729 store_info_remove(void *key, void *value, void *data)
731 CamelStoreSummary *summary = data;
732 CamelStoreInfo *si = value;
734 camel_store_summary_remove(summary, si);
738 store_info_sort (gconstpointer a, gconstpointer b)
740 return strcmp ((*(CamelNNTPStoreInfo**) a)->full_name, (*(CamelNNTPStoreInfo**) b)->full_name);
743 static CamelFolderInfo *
744 nntp_store_get_folder_info_all(CamelNNTPStore *nntp_store, const char *top, guint32 flags, gboolean online, CamelException *ex)
746 CamelNNTPStoreSummary *summary = nntp_store->summary;
747 CamelNNTPStoreInfo *si;
751 CamelFolderInfo *fi = NULL;
753 CAMEL_SERVICE_REC_LOCK(nntp_store, connect_lock);
758 if (online && (top == NULL || top[0] == 0)) {
759 /* we may need to update */
760 if (summary->last_newslist[0] != 0) {
762 memcpy(date, summary->last_newslist + 2, 6); /* YYMMDDD */
764 memcpy(date + 7, summary->last_newslist + 8, 6); /* HHMMSS */
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;
771 ret = camel_nntp_command (nntp_store, ex, NULL, (char **) &line, "newgroups %s", date);
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;
780 while ((ret = camel_nntp_stream_line (nntp_store->stream, &line, &len)) > 0)
781 nntp_store_info_update(nntp_store, line);
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");
794 else if (ret != 215) {
795 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_INVALID,
796 _("Error retrieving newsgroups:\n\n%s"), line);
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);
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);
809 g_hash_table_foreach(all, store_info_remove, nntp_store->summary);
810 g_hash_table_destroy(all);
814 g_ptr_array_sort (CAMEL_STORE_SUMMARY (nntp_store->summary)->folders, store_info_sort);
818 camel_store_summary_save ((CamelStoreSummary *) nntp_store->summary);
821 fi = nntp_store_get_cached_folder_info (nntp_store, top, flags, ex);
823 CAMEL_SERVICE_REC_UNLOCK(nntp_store, connect_lock);
828 static CamelFolderInfo *
829 nntp_get_folder_info (CamelStore *store, const char *top, guint32 flags, gboolean online, CamelException *ex)
831 CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE(store);
832 CamelFolderInfo *first = NULL;
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,
841 if (flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED)
842 first = nntp_store_get_subscribed_folder_info (nntp_store, top, flags, ex);
844 first = nntp_store_get_folder_info_all (nntp_store, top, flags, online, ex);
849 static CamelFolderInfo *
850 nntp_get_folder_info_online (CamelStore *store, const char *top, guint32 flags, CamelException *ex)
852 return nntp_get_folder_info (store, top, flags, TRUE, ex);
855 static CamelFolderInfo *
856 nntp_get_folder_info_offline(CamelStore *store, const char *top, guint32 flags, CamelException *ex)
858 return nntp_get_folder_info (store, top, flags, FALSE, ex);
862 nntp_store_folder_subscribed (CamelStore *store, const char *folder_name)
864 CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE (store);
868 si = camel_store_summary_path ((CamelStoreSummary *) nntp_store->summary, folder_name);
870 truth = (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) != 0;
871 camel_store_summary_info_free ((CamelStoreSummary *) nntp_store->summary, si);
878 nntp_store_subscribe_folder (CamelStore *store, const char *folder_name,
881 CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE(store);
885 CAMEL_SERVICE_REC_LOCK(nntp_store, connect_lock);
887 si = camel_store_summary_path(CAMEL_STORE_SUMMARY(nntp_store->summary), folder_name);
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."));
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);
906 CAMEL_SERVICE_REC_UNLOCK(nntp_store, connect_lock);
910 nntp_store_unsubscribe_folder (CamelStore *store, const char *folder_name,
913 CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE(store);
915 CamelStoreInfo *fitem;
916 CAMEL_SERVICE_REC_LOCK(nntp_store, connect_lock);
918 fitem = camel_store_summary_path(CAMEL_STORE_SUMMARY(nntp_store->summary), folder_name);
921 camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID,
922 _("You cannot unsubscribe to this newsgroup:\n\n"
923 "newsgroup does not exist!"));
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);
937 CAMEL_SERVICE_REC_UNLOCK(nntp_store, connect_lock);
940 /* stubs for various folder operations we're not implementing */
942 static CamelFolderInfo *
943 nntp_create_folder (CamelStore *store, const char *parent_name,
944 const char *folder_name, CamelException *ex)
946 camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID,
947 _("You cannot create a folder in a News store: subscribe instead."));
952 nntp_rename_folder (CamelStore *store, const char *old_name, const char *new_name_in, CamelException *ex)
954 camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID,
955 _("You cannot rename a folder in a News store."));
959 nntp_delete_folder (CamelStore *store, const char *folder_name, CamelException *ex)
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."));
968 nntp_store_finalize (CamelObject *object)
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;
975 camel_service_disconnect ((CamelService *)object, TRUE, NULL);
977 if (nntp_store->summary) {
978 camel_store_summary_save ((CamelStoreSummary *) nntp_store->summary);
979 camel_object_unref (nntp_store->summary);
982 camel_object_unref (nntp_store->mem);
983 nntp_store->mem = NULL;
984 if (nntp_store->stream)
985 camel_object_unref (nntp_store->stream);
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);
992 xover = nntp_store->xover;
999 if (nntp_store->cache)
1000 camel_object_unref(nntp_store->cache);
1006 nntp_store_class_init (CamelNNTPStoreClass *camel_nntp_store_class)
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);
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 ()));
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;
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;
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;
1033 camel_store_class->free_folder_info = camel_store_free_folder_info_full;
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;
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;
1044 /* construction function in which we set some basic store properties */
1046 nntp_construct (CamelService *service, CamelSession *session,
1047 CamelProvider *provider, CamelURL *url,
1050 CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE(service);
1051 CamelURL *summary_url;
1054 /* construct the parent first */
1055 CAMEL_SERVICE_CLASS (parent_class)->construct (service, session, provider, url, ex);
1056 if (camel_exception_is_set (ex))
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)
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));
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);
1076 camel_url_free (summary_url);
1077 if (camel_store_summary_load ((CamelStoreSummary *)nntp_store->summary) == 0)
1081 if (camel_url_get_param (url, "show_short_notation"))
1082 nntp_store->do_short_folder_notation = TRUE;
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;
1088 nntp_store->folder_hierarchy_relative = FALSE;
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)
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);
1101 nntp_store_init (gpointer object, gpointer klass)
1103 CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE(object);
1104 CamelStore *store = CAMEL_STORE (object);
1105 struct _CamelNNTPStorePrivate *p;
1107 store->flags = CAMEL_STORE_SUBSCRIPTIONS;
1109 nntp_store->mem = (CamelStreamMem *)camel_stream_mem_new();
1111 p = nntp_store->priv = g_malloc0(sizeof(*p));
1115 camel_nntp_store_get_type (void)
1117 static CamelType camel_nntp_store_type = CAMEL_INVALID_TYPE;
1119 if (camel_nntp_store_type == CAMEL_INVALID_TYPE) {
1120 camel_nntp_store_type =
1121 camel_type_register (CAMEL_DISCO_STORE_TYPE,
1123 sizeof (CamelNNTPStore),
1124 sizeof (CamelNNTPStoreClass),
1125 (CamelObjectClassInitFunc) nntp_store_class_init,
1127 (CamelObjectInitFunc) nntp_store_init,
1128 (CamelObjectFinalizeFunc) nntp_store_finalize);
1131 return camel_nntp_store_type;
1135 camel_nntp_try_authenticate (CamelNNTPStore *store, CamelException *ex)
1137 CamelService *service = (CamelService *) store;
1138 CamelSession *session = camel_service_get_session (service);
1142 if (!service->url->user) {
1143 camel_exception_setv(ex, CAMEL_EXCEPTION_INVALID_PARAM,
1144 _("Authentication requested but no username provided"));
1148 /* if nessecary, prompt for the password */
1149 if (!service->url->passwd) {
1150 char *prompt, *base;
1152 base = g_strdup_printf (_("Please enter the NNTP password for %s@%s"),
1154 service->url->host);
1156 char *top = g_strdup_printf(_("Cannot authenticate to server: %s"), line);
1158 prompt = g_strdup_printf("%s\n\n%s", top, base);
1165 service->url->passwd =
1166 camel_session_get_password (session, service, NULL,
1167 prompt, "password", CAMEL_SESSION_PASSWORD_SECRET, ex);
1171 if (!service->url->passwd)
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);
1180 if (ret != NNTP_AUTH_ACCEPTED) {
1182 /* Need to forget the password here since we have no context on it */
1183 camel_session_forget_password(session, service, NULL, "password", ex);
1192 /* Enter owning lock */
1194 camel_nntp_raw_commandv (CamelNNTPStore *store, CamelException *ex, char **line, const char *fmt, va_list ap)
1196 const unsigned char *p, *ps;
1202 g_assert(store->stream->mode != CAMEL_NNTP_STREAM_DATA);
1204 camel_nntp_stream_set_mode(store->stream, CAMEL_NNTP_STREAM_LINE);
1207 while ((c = *p++)) {
1211 camel_stream_write ((CamelStream *) store->mem, ps, p - ps - (c == '%' ? 1 : 2));
1215 s = va_arg(ap, char *);
1216 camel_stream_write((CamelStream *)store->mem, s, strlen(s));
1219 d = va_arg(ap, int);
1220 camel_stream_printf((CamelStream *)store->mem, "%d", d);
1223 u = va_arg(ap, unsigned int);
1224 camel_stream_printf((CamelStream *)store->mem, "%u", u);
1227 s = va_arg(ap, char *);
1228 camel_stream_printf((CamelStream *)store->mem, "<%s>", s);
1231 u = va_arg(ap, unsigned int);
1232 u2 = va_arg(ap, unsigned int);
1234 camel_stream_printf((CamelStream *)store->mem, "%u", u);
1236 camel_stream_printf((CamelStream *)store->mem, "%u-%u", u, u2);
1239 g_warning("Passing unknown format to nntp_command: %c\n", c);
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);
1249 if (camel_stream_write((CamelStream *) store->stream, store->mem->buffer->data, store->mem->buffer->len) == -1)
1253 camel_stream_reset ((CamelStream *) store->mem);
1254 g_byte_array_set_size (store->mem->buffer, 0);
1256 if (camel_nntp_stream_line (store->stream, (unsigned char **) line, &u) == -1)
1259 u = strtoul (*line, NULL, 10);
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);
1269 camel_exception_setv(ex, CAMEL_EXCEPTION_USER_CANCEL, _("Canceled."));
1271 camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("NNTP Command failed: %s"), g_strerror(errno));
1276 camel_nntp_raw_command(CamelNNTPStore *store, CamelException *ex, char **line, const char *fmt, ...)
1282 ret = camel_nntp_raw_commandv(store, ex, line, fmt, ap);
1288 /* use this where you also need auth to be handled, i.e. most cases where you'd try raw command */
1290 camel_nntp_raw_command_auth(CamelNNTPStore *store, CamelException *ex, char **line, const char *fmt, ...)
1302 ret = camel_nntp_raw_commandv(store, ex, line, fmt, ap);
1305 if (ret == NNTP_AUTH_REQUIRED) {
1306 if (camel_nntp_try_authenticate(store, ex) != NNTP_AUTH_ACCEPTED)
1310 } while (retry < 3 && go);
1316 camel_nntp_command (CamelNNTPStore *store, CamelException *ex, CamelNNTPFolder *folder, char **line, const char *fmt, ...)
1318 const unsigned char *p;
1323 if (((CamelDiscoStore *)store)->status == CAMEL_DISCO_STORE_OFFLINE) {
1324 camel_exception_setv(ex, CAMEL_EXCEPTION_SERVICE_NOT_CONNECTED,
1325 _("Not connected."));
1333 if (store->stream == NULL
1334 && !camel_service_connect (CAMEL_SERVICE (store), ex))
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)
1343 camel_nntp_stream_set_mode(store->stream, CAMEL_NNTP_STREAM_LINE);
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);
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)) {
1361 /* dummy fmt, we just wanted to select the folder */
1366 ret = camel_nntp_raw_commandv(store, ex, line, fmt, ap);
1370 case NNTP_AUTH_REQUIRED:
1371 if (camel_nntp_try_authenticate(store, ex) != NNTP_AUTH_ACCEPTED)
1376 case 411: /* no such group */
1377 camel_exception_setv(ex, CAMEL_EXCEPTION_FOLDER_INVALID,
1378 _("No such folder: %s"), line);
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);
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)
1390 camel_exception_clear(ex);
1393 } while (ret == -1 && retry < 3);