summarylogtreecommitdiffstats
diff options
context:
space:
mode:
authorOscar Morante2015-06-11 14:34:58 +0300
committerOscar Morante2015-06-11 14:35:29 +0300
commite6384b51f68c57d993111a2a0faf8a2bdd69bb32 (patch)
tree30ecb81ba7e885e11deb6ba536bf6893bacc0c5f
downloadaur-e6384b51f68c57d993111a2a0faf8a2bdd69bb32.tar.gz
initial import
-rw-r--r--.SRCINFO120
-rw-r--r--228671-pipe-mime.patch48
-rw-r--r--383769-score-match.patch25
-rw-r--r--467432-write_bcc.patch127
-rw-r--r--528233-readonly-open.patch39
-rw-r--r--531430-imapuser.patch26
-rw-r--r--542817-smimekeys-tmpdir.patch25
-rw-r--r--543467-thread-segfault.patch29
-rw-r--r--547980-smime_keys-chaining.patch95
-rw-r--r--548577-gpgme-1.2.patch39
-rw-r--r--553321-ansi-escape-segfault.patch29
-rw-r--r--566076-build_doc_adjustments.patch39
-rw-r--r--603288-split-fetches.patch33
-rw-r--r--611410-no-implicit_autoview-for-text-html.patch52
-rw-r--r--Md.etc_mailname_gethostbyname.patch82
-rw-r--r--Muttrc.patch81
-rw-r--r--PKGBUILD211
-rw-r--r--am-maintainer-mode.patch22
-rw-r--r--assumed_charset-compat.patch27
-rw-r--r--backport-gnutls-fix.patch162
-rw-r--r--compressed-folders.debian.patch22
-rw-r--r--compressed-folders.patch1188
-rw-r--r--correct_docdir_in_man_page.patch25
-rw-r--r--define-pgp_getkeys_command.patch24
-rw-r--r--document_debian_defaults.patch87
-rw-r--r--dont_document_not_present_features.patch39
-rw-r--r--fix-configure-test-operator.patch22
-rw-r--r--gpg.rc-paths.patch29
-rw-r--r--ifdef.patch160
-rw-r--r--imap_fast_trash.patch114
-rw-r--r--install8
-rw-r--r--multiple-fcc.patch83
-rw-r--r--nntp.patch8748
-rw-r--r--patch-1.5.4.vk.pgp_verbose_mime.patch63
-rw-r--r--path_max.patch25
-rw-r--r--purge-message.patch241
-rw-r--r--sensible_browser_position.patch115
-rw-r--r--sidebar-compose.patch40
-rw-r--r--sidebar-delimnullwide.patch38
-rw-r--r--sidebar-dotpathsep.patch98
-rw-r--r--sidebar-new.patch97
-rw-r--r--sidebar-newonly.patch198
-rw-r--r--sidebar-utf8.patch132
-rw-r--r--sidebar.patch1476
-rw-r--r--smime.rc.patch60
-rw-r--r--trash-folder.patch316
-rw-r--r--update_german_translation.patch115
-rw-r--r--use_usr_bin_editor.patch40
-rw-r--r--xtitles.patch198
49 files changed, 15112 insertions, 0 deletions
diff --git a/.SRCINFO b/.SRCINFO
new file mode 100644
index 000000000000..4c9a5a355bb3
--- /dev/null
+++ b/.SRCINFO
@@ -0,0 +1,120 @@
+pkgbase = mutt-patched
+ pkgdesc = Small but very powerful text-based mail client (plus a huge pile of patches mostly from debian)
+ pkgver = 1.5.23
+ pkgrel = 5
+ url = http://www.mutt.org/
+ install = install
+ arch = i686
+ arch = x86_64
+ license = GPL
+ makedepends = w3m
+ makedepends = docbook-xsl
+ depends = gpgme
+ depends = ncurses
+ depends = gnutls
+ depends = libsasl
+ depends = libidn
+ depends = mime-types
+ depends = krb5
+ depends = tokyocabinet
+ optdepends = smtp-forwarder: to send mail
+ provides = mutt
+ conflicts = mutt
+ backup = etc/Muttrc
+ source = https://bitbucket.org/mutt/mutt/downloads/mutt-1.5.23.tar.gz
+ source = backport-gnutls-fix.patch
+ source = am-maintainer-mode.patch
+ source = ifdef.patch
+ source = xtitles.patch
+ source = trash-folder.patch
+ source = purge-message.patch
+ source = imap_fast_trash.patch
+ source = sensible_browser_position.patch
+ source = patch-1.5.4.vk.pgp_verbose_mime.patch
+ source = compressed-folders.patch
+ source = compressed-folders.debian.patch
+ source = Muttrc.patch
+ source = Md.etc_mailname_gethostbyname.patch
+ source = use_usr_bin_editor.patch
+ source = correct_docdir_in_man_page.patch
+ source = dont_document_not_present_features.patch
+ source = document_debian_defaults.patch
+ source = assumed_charset-compat.patch
+ source = 467432-write_bcc.patch
+ source = 566076-build_doc_adjustments.patch
+ source = define-pgp_getkeys_command.patch
+ source = gpg.rc-paths.patch
+ source = smime.rc.patch
+ source = fix-configure-test-operator.patch
+ source = 531430-imapuser.patch
+ source = 543467-thread-segfault.patch
+ source = 542817-smimekeys-tmpdir.patch
+ source = 548577-gpgme-1.2.patch
+ source = 553321-ansi-escape-segfault.patch
+ source = 547980-smime_keys-chaining.patch
+ source = 528233-readonly-open.patch
+ source = 228671-pipe-mime.patch
+ source = 383769-score-match.patch
+ source = 603288-split-fetches.patch
+ source = 611410-no-implicit_autoview-for-text-html.patch
+ source = path_max.patch
+ source = update_german_translation.patch
+ source = sidebar.patch
+ source = sidebar-dotpathsep.patch
+ source = sidebar-utf8.patch
+ source = multiple-fcc.patch
+ source = sidebar-newonly.patch
+ source = sidebar-delimnullwide.patch
+ source = sidebar-compose.patch
+ source = sidebar-new.patch
+ source = nntp.patch
+ sha1sums = 8ac821d8b1e25504a31bf5fda9c08d93a4acc862
+ sha1sums = 018575fea153952423d4d512ca2fdfc2e49d669f
+ sha1sums = 1ad77bdf742ff584b5695f1908dde83044195c0e
+ sha1sums = 3aca6a18ac32b8f6c15216e6fb928a05320c5b74
+ sha1sums = ff3b66e57faaa163e3e6512f81fdbbea9e02a0bd
+ sha1sums = 5c1eae78cb8636025916e7d83c192f6c32981ff3
+ sha1sums = c6eaf7fb1829809e7f07d039f75ab30de02d7873
+ sha1sums = 72a35dfd4476238db3500f3a5651996691c1ce20
+ sha1sums = e3e7b9c5b17be4032e111edb6a6999a3185c8f7b
+ sha1sums = 61630d6a6c3fdf7480c7b6a0e75552500c48f5f4
+ sha1sums = a7546f9616460da23e87957b0ce98776113b5f7b
+ sha1sums = 6a53e85ee55c14beda0000de89a397f589cb29a7
+ sha1sums = b841ad389301d336ba533356d057ea1f09bc150b
+ sha1sums = ee72ecb042e337427b5d9c31ff1ecb78b440e2ff
+ sha1sums = 773c59af8755b6daa1d73749fa9c38ccf953285c
+ sha1sums = 0acf230d3b7e03b90239b256a6944ccf8a0b4e91
+ sha1sums = 12c74c647238e653c4db5f8eb7590c5723635b9c
+ sha1sums = 4dd818f69c4f302c5ca3e5a7fd95223112c853ac
+ sha1sums = 464ca1c57f8a25177a586cb79b94398f694b32e3
+ sha1sums = c44f9b5a3104917417d09dbcb0dda77f4566c30a
+ sha1sums = 206daf4a78f801d9835c8096c83181569ae74136
+ sha1sums = 061afb767a30fb78d48b0de1e30aea2f0b3b9d5d
+ sha1sums = 42ab3017961819669c678e7456657ca677c8eb56
+ sha1sums = 8ae07c06cc9a552b7152c4e264b6284cd770a29c
+ sha1sums = ad645521bcd36a758e10cf0e2e2e8123c3c9a229
+ sha1sums = f1ee08450995fe21e0dc3807563e7120d2e3d04a
+ sha1sums = f2b98a258d5ee18f731aab975f5b0dd17945f228
+ sha1sums = 98d958231c05fa1ddf5f1a0ab67a2dc6cefa8418
+ sha1sums = c8ac575a36876d711a16be09425301c4800bf21c
+ sha1sums = 6ec4a7614dc78d230a9ebe8ec645393a98be41a4
+ sha1sums = f91ac0baf4eda090da7b80e272d422f234b459cd
+ sha1sums = 03a42b8914c9ef1f684275cba355046de14b1c03
+ sha1sums = 1b53316b0c09333dbee428448a6f565378ff5357
+ sha1sums = 8121dd0a07f6f98377e1b1ad3659deaec3a83b6b
+ sha1sums = d4803ee5f29e9e495e12bf012dc938d7cd4adc47
+ sha1sums = 561b600c79988975f034c9237158fb991ec92541
+ sha1sums = f91499aa8bee5827924e6c860bd853e0344d55bc
+ sha1sums = f99082007d3cb4486b96cb22d1fae1c71fdbe51e
+ sha1sums = fcdeb12e49735268c080e34c2d88211a7ccb8493
+ sha1sums = 487850ffc2a6d639d9d17b3ced844257799a84a9
+ sha1sums = c01c8f84e3ea4ac71caf31edeb6adb062eb04fe0
+ sha1sums = 2ba91afb71dc40793bac8198fd9f8d6500bd1ce3
+ sha1sums = e7181b191f06bd6c74c6e35372dba6dc3f8c4653
+ sha1sums = 19fd36f9a1f15d4481f08d90f9a88d3912e12cdb
+ sha1sums = b648cb9ab1a0b447417647a82856af01a9a418e5
+ sha1sums = 230973eb243265ee75bd098f0f8286690bed2d08
+ sha1sums = 8d7d858e47bbb2c736c9d09de843d3892888e9c5
+
+pkgname = mutt-patched
+
diff --git a/228671-pipe-mime.patch b/228671-pipe-mime.patch
new file mode 100644
index 000000000000..04debc45232d
--- /dev/null
+++ b/228671-pipe-mime.patch
@@ -0,0 +1,48 @@
+From: Antonio Radici <antonio@debian.org>
+Date: Thu, 27 Feb 2014 17:11:56 +0100
+Subject: 228671-pipe-mime
+
+Don't draw imap fetch progress if we aren't in visual mode.
+Drawing progress leaves terminal in confusing state when
+piping a message from pager to less(1).
+See http://bugs.mutt.org/1771
+
+Updated in Debian bug #569279
+
+Gbp-Pq: Topic upstream
+---
+ imap/message.c | 14 ++++++++++----
+ 1 file changed, 10 insertions(+), 4 deletions(-)
+
+diff --git a/imap/message.c b/imap/message.c
+index 3c9822f..3504be8 100644
+--- a/imap/message.c
++++ b/imap/message.c
+@@ -398,7 +398,7 @@ int imap_fetch_message (MESSAGE *msg, CONTEXT *ctx, int msgno)
+ char path[_POSIX_PATH_MAX];
+ char *pc;
+ long bytes;
+- progress_t progressbar;
++ progress_t progressbar, *pbar;
+ int uid;
+ int cacheno;
+ IMAP_CACHE *cache;
+@@ -495,9 +495,15 @@ int imap_fetch_message (MESSAGE *msg, CONTEXT *ctx, int msgno)
+ imap_error ("imap_fetch_message()", buf);
+ goto bail;
+ }
+- mutt_progress_init (&progressbar, _("Fetching message..."),
+- M_PROGRESS_SIZE, NetInc, bytes);
+- if (imap_read_literal (msg->fp, idata, bytes, &progressbar) < 0)
++ if (!isendwin())
++ {
++ mutt_progress_init (&progressbar, _("Fetching message..."),
++ M_PROGRESS_SIZE, NetInc, bytes);
++ pbar = &progressbar;
++ }
++ else
++ pbar = NULL;
++ if (imap_read_literal (msg->fp, idata, bytes, pbar) < 0)
+ goto bail;
+ /* pick up trailing line */
+ if ((rc = imap_cmd_step (idata)) != IMAP_CMD_CONTINUE)
diff --git a/383769-score-match.patch b/383769-score-match.patch
new file mode 100644
index 000000000000..096ad688ffb6
--- /dev/null
+++ b/383769-score-match.patch
@@ -0,0 +1,25 @@
+From: Antonio Radici <antonio@debian.org>
+Date: Thu, 27 Feb 2014 17:14:35 +0100
+Subject: 383769-score-match
+
+Better matching for ~f, same as mutt-ng did.
+(see upstream http://bugs.mutt.org/2179)
+
+Gbp-Pq: Topic upstream
+---
+ score.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/score.c b/score.c
+index 6f952a5..7bc4d83 100644
+--- a/score.c
++++ b/score.c
+@@ -133,7 +133,7 @@ void mutt_score_message (CONTEXT *ctx, HEADER *hdr, int upd_ctx)
+ hdr->score = 0; /* in case of re-scoring */
+ for (tmp = Score; tmp; tmp = tmp->next)
+ {
+- if (mutt_pattern_exec (tmp->pat, 0, NULL, hdr) > 0)
++ if (mutt_pattern_exec (tmp->pat, M_MATCH_FULL_ADDRESS, NULL, hdr) > 0)
+ {
+ if (tmp->exact || tmp->val == 9999 || tmp->val == -9999)
+ {
diff --git a/467432-write_bcc.patch b/467432-write_bcc.patch
new file mode 100644
index 000000000000..1948e87abf07
--- /dev/null
+++ b/467432-write_bcc.patch
@@ -0,0 +1,127 @@
+From: Antonio Radici <antonio@debian.org>
+Date: Thu, 27 Feb 2014 16:22:51 +0100
+Subject: 467432-write_bcc
+MIME-Version: 1.0
+Content-Type: text/plain; charset="utf-8"
+Content-Transfer-Encoding: 8bit
+
+The purpose of this patch is to alter the behavior of the write_bcc option
+because exim4, the default SMTP in Debian, does not strip the Bcc headers; by
+default write_bcc is set so this could cause a privacy leak.
+
+The behavior that this patch introduces is: never write the Bcc header when the
+message is sent, otherwise, if the message is sent to Fcc, then this optin
+will be evaluated and the Bcc header will be written based on that.
+
+Background discussion on this is in the following bugs:
+http://bugs.debian.org/304718
+http://bugs.debian.org/467432
+
+This patch is a slightly modified version of the patch provided by
+Stefan Völkel <stefan@bc-bd.org> in the second bug.
+
+Gbp-Pq: Topic debian-specific
+---
+ headers.c | 2 +-
+ init.h | 11 +++--------
+ protos.h | 2 +-
+ send.c | 4 ++--
+ sendlib.c | 6 +++---
+ 5 files changed, 10 insertions(+), 15 deletions(-)
+
+diff --git a/headers.c b/headers.c
+index 578b33d..f701c8e 100644
+--- a/headers.c
++++ b/headers.c
+@@ -52,7 +52,7 @@ void mutt_edit_headers (const char *editor,
+ }
+
+ mutt_env_to_local (msg->env);
+- mutt_write_rfc822_header (ofp, msg->env, NULL, 1, 0);
++ mutt_write_rfc822_header (ofp, msg->env, NULL, 1, 0, 1);
+ fputc ('\n', ofp); /* tie off the header. */
+
+ /* now copy the body of the message. */
+diff --git a/init.h b/init.h
+index 875cad8..42b79d6 100644
+--- a/init.h
++++ b/init.h
+@@ -3440,14 +3440,9 @@ struct option_t MuttVars[] = {
+ { "write_bcc", DT_BOOL, R_NONE, OPTWRITEBCC, 1},
+ /*
+ ** .pp
+- ** Controls whether mutt writes out the ``Bcc:'' header when preparing
+- ** messages to be sent. Exim users may wish to unset this. If mutt
+- ** is set to deliver directly via SMTP (see $$smtp_url), this
+- ** option does nothing: mutt will never write out the ``Bcc:'' header
+- ** in this case.
+- ** .pp
+- ** \fBNote:\fP On Debian systems, exim4 and postfix strip BCC headers by
+- ** default. The above warning applies to exim3 users, see /etc/Muttrc.
++ ** Controls whether mutt writes out the Bcc header when saving
++ ** messages to FCC. Bcc headers will never be written to a message
++ ** when sending it. Note: this behavior is Debian specific.
+ */
+ { "write_inc", DT_NUM, R_NONE, UL &WriteInc, 10 },
+ /*
+diff --git a/protos.h b/protos.h
+index 8542f91..38c8c01 100644
+--- a/protos.h
++++ b/protos.h
+@@ -366,7 +366,7 @@ int mutt_write_fcc (const char *path, HEADER *hdr, const char *msgid, int, char
+ int mutt_write_mime_body (BODY *, FILE *);
+ int mutt_write_mime_header (BODY *, FILE *);
+ int mutt_write_one_header (FILE *fp, const char *tag, const char *value, const char *pfx, int wraplen, int flags);
+-int mutt_write_rfc822_header (FILE *, ENVELOPE *, BODY *, int, int);
++int mutt_write_rfc822_header (FILE *, ENVELOPE *, BODY *, int, int, int);
+ void mutt_write_references (LIST *, FILE *, int);
+ int mutt_yesorno (const char *, int);
+ void mutt_set_header_color(CONTEXT *, HEADER *);
+diff --git a/send.c b/send.c
+index fce003c..18b9390 100644
+--- a/send.c
++++ b/send.c
+@@ -994,10 +994,10 @@ static int send_message (HEADER *msg)
+ unset_option (OPTWRITEBCC);
+ #endif
+ #ifdef MIXMASTER
+- mutt_write_rfc822_header (tempfp, msg->env, msg->content, 0, msg->chain ? 1 : 0);
++ mutt_write_rfc822_header (tempfp, msg->env, msg->content, 0, msg->chain ? 1 : 0, 0);
+ #endif
+ #ifndef MIXMASTER
+- mutt_write_rfc822_header (tempfp, msg->env, msg->content, 0, 0);
++ mutt_write_rfc822_header (tempfp, msg->env, msg->content, 0, 0, 0);
+ #endif
+ #ifdef USE_SMTP
+ if (old_write_bcc)
+diff --git a/sendlib.c b/sendlib.c
+index cede853..0f05298 100644
+--- a/sendlib.c
++++ b/sendlib.c
+@@ -1929,7 +1929,7 @@ out:
+
+
+ int mutt_write_rfc822_header (FILE *fp, ENVELOPE *env, BODY *attach,
+- int mode, int privacy)
++ int mode, int privacy, int should_write_bcc)
+ {
+ char buffer[LONG_STRING];
+ char *p, *q;
+@@ -1972,7 +1972,7 @@ int mutt_write_rfc822_header (FILE *fp, ENVELOPE *env, BODY *attach,
+ else if (mode > 0)
+ fputs ("Cc: \n", fp);
+
+- if (env->bcc)
++ if (env->bcc && should_write_bcc)
+ {
+ if(mode != 0 || option(OPTWRITEBCC))
+ {
+@@ -2727,7 +2727,7 @@ int mutt_write_fcc (const char *path, HEADER *hdr, const char *msgid, int post,
+ /* post == 1 => postpone message. Set mode = -1 in mutt_write_rfc822_header()
+ * post == 0 => Normal mode. Set mode = 0 in mutt_write_rfc822_header()
+ * */
+- mutt_write_rfc822_header (msg->fp, hdr->env, hdr->content, post ? -post : 0, 0);
++ mutt_write_rfc822_header (msg->fp, hdr->env, hdr->content, post ? -post : 0, 0, 1);
+
+ /* (postponment) if this was a reply of some sort, <msgid> contians the
+ * Message-ID: of message replied to. Save it using a special X-Mutt-
diff --git a/528233-readonly-open.patch b/528233-readonly-open.patch
new file mode 100644
index 000000000000..55ea401306ce
--- /dev/null
+++ b/528233-readonly-open.patch
@@ -0,0 +1,39 @@
+From: Antonio Radici <antonio@debian.org>
+Date: Thu, 27 Feb 2014 17:07:35 +0100
+Subject: 528233-readonly-open
+
+Open attachments as read-only so the editor won't be able to modify it;
+otherwise the user can believe that he/she can edit it and risk to lose
+his/her work (see upstream bug http://bugs.mutt.org/3261)
+
+Debian bugs: #528233, updated in #572203
+
+Gbp-Pq: Topic upstream
+---
+ attach.c | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/attach.c b/attach.c
+index 0efeb79..c5f47d9 100644
+--- a/attach.c
++++ b/attach.c
+@@ -415,6 +415,7 @@ int mutt_view_attachment (FILE *fp, BODY *a, int flag, HEADER *hdr,
+ FREE (&fname);
+ if (mutt_save_attachment (fp, a, tempfile, 0, NULL) == -1)
+ goto return_error;
++ chmod (tempfile, 0400);
+ }
+
+ use_pipe = rfc1524_expand_command (a, tempfile, type,
+@@ -587,7 +588,11 @@ int mutt_view_attachment (FILE *fp, BODY *a, int flag, HEADER *hdr,
+ if (entry)
+ rfc1524_free_entry (&entry);
+ if (fp && tempfile[0])
++ {
++ /* Restore write permission so mutt_unlink can open the file for writing */
++ chmod(tempfile, 0600);
+ mutt_unlink (tempfile);
++ }
+ else if (unlink_tempfile)
+ unlink(tempfile);
+
diff --git a/531430-imapuser.patch b/531430-imapuser.patch
new file mode 100644
index 000000000000..1c41b5939fcf
--- /dev/null
+++ b/531430-imapuser.patch
@@ -0,0 +1,26 @@
+From: Antonio Radici <antonio@debian.org>
+Date: Thu, 27 Feb 2014 16:36:23 +0100
+Subject: 531430-imapuser
+
+Fixes a problem where the imap->login has the precedence
+so the user is asked with wrong informaton,
+see upstream #3240.
+
+Gbp-Pq: Topic upstream
+---
+ account.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/account.c b/account.c
+index f738991..bf59995 100644
+--- a/account.c
++++ b/account.c
+@@ -222,7 +222,7 @@ int mutt_account_getpass (ACCOUNT* account)
+ else
+ {
+ snprintf (prompt, sizeof (prompt), _("Password for %s@%s: "),
+- account->flags & M_ACCT_LOGIN ? account->login : account->user,
++ ((account->flags & M_ACCT_LOGIN) && !(account->user)) ? account->login : account->user,
+ account->host);
+ account->pass[0] = '\0';
+ if (mutt_get_password (prompt, account->pass, sizeof (account->pass)))
diff --git a/542817-smimekeys-tmpdir.patch b/542817-smimekeys-tmpdir.patch
new file mode 100644
index 000000000000..a4c51345384f
--- /dev/null
+++ b/542817-smimekeys-tmpdir.patch
@@ -0,0 +1,25 @@
+From: Antonio Radici <antonio@debian.org>
+Date: Thu, 27 Feb 2014 16:45:38 +0100
+Subject: 542817-smimekeys-tmpdir
+
+Skip the configured tmpdir if it contains '=',
+that convention is used only by Mutt.
+(see upstream http://bugs.mutt.org/3324)
+
+Gbp-Pq: Topic upstream
+---
+ smime_keys.pl | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/smime_keys.pl b/smime_keys.pl
+index a825358..2f4544a 100755
+--- a/smime_keys.pl
++++ b/smime_keys.pl
+@@ -946,6 +946,7 @@ sub newfile ($;$$) {
+ $option = "notemp" if (not defined($option));
+ if (! $tmpdir and $option eq "temp") {
+ $tmpdir = mutt_Q 'tmpdir';
++ $tmpdir = '/tmp' if ($tmpdir =~ m/=/); # if the tmpdir contains '=', use the default
+ $tmpdir = newfile("$tmpdir/smime");
+ mkdir $tmpdir, 0700 || die "Can't create $tmpdir: $!\n";
+ }
diff --git a/543467-thread-segfault.patch b/543467-thread-segfault.patch
new file mode 100644
index 000000000000..ee9fce16ab62
--- /dev/null
+++ b/543467-thread-segfault.patch
@@ -0,0 +1,29 @@
+From: Antonio Radici <antonio@debian.org>
+Date: Thu, 27 Feb 2014 16:38:41 +0100
+Subject: 543467-thread-segfault
+
+Added a patch to prevent segfault when Ctrl+R is hit
+on a malformed group of messages.
+(see upstream bug http://bugs.mutt.org/3335)
+
+Gbp-Pq: Topic upstream
+---
+ flags.c | 5 ++++-
+ 1 file changed, 4 insertions(+), 1 deletion(-)
+
+diff --git a/flags.c b/flags.c
+index 5309bb7..133fa35 100644
+--- a/flags.c
++++ b/flags.c
+@@ -303,7 +303,10 @@ void mutt_tag_set_flag (int flag, int bf)
+ int mutt_thread_set_flag (HEADER *hdr, int flag, int bf, int subthread)
+ {
+ THREAD *start, *cur = hdr->thread;
+-
++
++ if ( !hdr->thread )
++ return (-1);
++
+ if ((Sort & SORT_MASK) != SORT_THREADS)
+ {
+ mutt_error _("Threading is not enabled.");
diff --git a/547980-smime_keys-chaining.patch b/547980-smime_keys-chaining.patch
new file mode 100644
index 000000000000..0b4c56a9d78e
--- /dev/null
+++ b/547980-smime_keys-chaining.patch
@@ -0,0 +1,95 @@
+From: Antonio Radici <antonio@debian.org>
+Date: Thu, 27 Feb 2014 17:03:15 +0100
+Subject: 547980-smime_keys-chaining
+
+To suppose certificate chaining in smime_keys,
+see upstream http://bugs.mutt.org/3339
+
+Gbp-Pq: Topic upstream
+---
+ smime_keys.pl | 57 ++++++++++++++++++++++++++++++++++++++++++++++++---------
+ 1 file changed, 48 insertions(+), 9 deletions(-)
+
+diff --git a/smime_keys.pl b/smime_keys.pl
+index 2f4544a..2a78e39 100755
+--- a/smime_keys.pl
++++ b/smime_keys.pl
+@@ -81,6 +81,30 @@ if ( -d $root_certs_path) {
+ # OPS
+ #
+
++
++sub get_certs {
++ my $file = shift;
++ return undef unless (defined($file) && -e $file);
++
++ open IN, "<$file";
++
++ my @certs = ();
++ my $in_cert = 0;
++ my $cert = q{};
++ while ( <IN> ) {
++ $in_cert = 1 if ( /^-----BEGIN CERTIFICATE-----$/ );
++ $cert .= $_;
++
++ if ( /^-----END CERTIFICATE-----$/ ) {
++ push @certs, $cert;
++ $cert = q{};
++ $in_cert = 0;
++ }
++ }
++
++ return @certs;
++}
++
+ if(@ARGV == 1 and $ARGV[0] eq "init") {
+ init_paths;
+ }
+@@ -91,13 +115,27 @@ elsif(@ARGV == 2 and $ARGV[0] eq "label") {
+ change_label($ARGV[1]);
+ }
+ elsif(@ARGV == 2 and $ARGV[0] eq "add_cert") {
+- my $format = -B $ARGV[1] ? 'DER' : 'PEM';
+- my $cmd = "$opensslbin x509 -noout -hash -in $ARGV[1] -inform $format";
+- my $cert_hash = `$cmd`;
+- $? and die "'$cmd' returned $?";
+- chomp($cert_hash);
+- my $label = query_label;
+- &add_certificate($ARGV[1], \$cert_hash, 1, $label, '?');
++ foreach my $cert ( get_certs( $ARGV[1] ) ) {
++
++ my $file = sprintf( '/tmp/smime-%d.%d', $$, int(rand( 999999 ) ) );
++ print STDERR "TMPFILE: $file\n";
++ if ( -e $file ) {
++ die( "ERROR: TMPFILE $file existss?!?!" );
++ }
++ open OUT, ">$file";
++ print OUT $cert;
++ close OUT;
++
++ my $format = -B $file ? 'DER' : 'PEM';
++ my $cmd = "$opensslbin x509 -noout -hash -in $file -inform $format";
++
++ my $cert_hash = `$cmd`;
++ $? and die "'$cmd' returned $?";
++ chomp($cert_hash);
++ my $label = query_label;
++ &add_certificate($ARGV[1], \$cert_hash, 1, $label, '?');
++ unlink $file;
++ }
+ }
+ elsif(@ARGV == 2 and $ARGV[0] eq "add_pem") {
+ -e $ARGV[1] and -s $ARGV[1] or die("$ARGV[1] is nonexistent or empty.");
+@@ -381,9 +419,10 @@ sub query_label () {
+ print "the key ID. This has to be _one_ word (no whitespaces).\n\n";
+
+ print "Enter label: ";
+- chomp($input = <STDIN>);
++ $input = <STDIN>;
++ chomp($input) if ( defined($input) );
+
+- my ($label, $junk) = split(/\s/, $input, 2);
++ my ($label, $junk) = split(/\s/, $input, 2) if ( defined($input) );
+
+ defined $junk
+ and print "\nUsing '$label' as label; ignoring '$junk'\n";
diff --git a/548577-gpgme-1.2.patch b/548577-gpgme-1.2.patch
new file mode 100644
index 000000000000..a0675e728f83
--- /dev/null
+++ b/548577-gpgme-1.2.patch
@@ -0,0 +1,39 @@
+From: Antonio Radici <antonio@debian.org>
+Date: Thu, 27 Feb 2014 16:50:21 +0100
+Subject: 548577-gpgme-1.2
+
+This patch fixes upstream bug http://bugs.mutt.org/3300,
+so that Mutt will be able to open pgp crypted/signed
+message with gpgme.
+
+Gbp-Pq: Topic upstream
+---
+ init.c | 8 ++++++++
+ 1 file changed, 8 insertions(+)
+
+diff --git a/init.c b/init.c
+index e0a0757..20a66bd 100644
+--- a/init.c
++++ b/init.c
+@@ -53,6 +53,10 @@
+ #include <sys/wait.h>
+ #include <sys/time.h>
+
++#if defined(CRYPT_BACKEND_GPGME)
++#include <gpgme.h>
++#endif
++
+ #define CHECK_PAGER \
+ if ((CurrentMenu == MENU_PAGER) && (idx >= 0) && \
+ (MuttVars[idx].flags & R_RESORT)) \
+@@ -3277,6 +3281,10 @@ void mutt_init (int skip_sys_rc, LIST *commands)
+
+ mutt_read_histfile ();
+
++#ifdef CRYPT_BACKEND_GPGME
++ gpgme_check_version (NULL);
++#endif
++
+ #if 0
+ set_option (OPTWEED); /* turn weeding on by default */
+ #endif
diff --git a/553321-ansi-escape-segfault.patch b/553321-ansi-escape-segfault.patch
new file mode 100644
index 000000000000..3e2492baa962
--- /dev/null
+++ b/553321-ansi-escape-segfault.patch
@@ -0,0 +1,29 @@
+From: Antonio Radici <antonio@debian.org>
+Date: Thu, 27 Feb 2014 16:56:37 +0100
+Subject: 553321-ansi-escape-segfault
+
+This patch prevents Mutt from crashing when *buf is freed,
+the root cause is the fact that an adjacent memory segment
+(*fmt) overruns and overwrite prev_size field in the heap.
+
+The bug and the patch were forwarded upstream,
+see http://bugs.mutt.org/3371
+
+Gbp-Pq: Topic upstream
+---
+ pager.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/pager.c b/pager.c
+index b17afb4..7b61266 100644
+--- a/pager.c
++++ b/pager.c
+@@ -1053,7 +1053,7 @@ fill_buffer (FILE *f, LOFF_T *last_pos, LOFF_T offset, unsigned char **buf,
+ q = *fmt;
+ while (*p)
+ {
+- if (*p == '\010' && (p > *buf))
++ if (*p == '\010' && (p > *buf) && (q > *fmt))
+ {
+ if (*(p+1) == '_') /* underline */
+ p += 2;
diff --git a/566076-build_doc_adjustments.patch b/566076-build_doc_adjustments.patch
new file mode 100644
index 000000000000..a7e46749cd2b
--- /dev/null
+++ b/566076-build_doc_adjustments.patch
@@ -0,0 +1,39 @@
+From: Antonio Radici <antonio@debian.org>
+Date: Thu, 27 Feb 2014 16:25:39 +0100
+Subject: 566076-build_doc_adjustments
+
+Use w3m to build the txt manual.
+
+Gbp-Pq: Topic debian-specific
+---
+ doc/Makefile.am | 8 +++-----
+ 1 file changed, 3 insertions(+), 5 deletions(-)
+
+diff --git a/doc/Makefile.am b/doc/Makefile.am
+index f4e21bc..3676245 100644
+--- a/doc/Makefile.am
++++ b/doc/Makefile.am
+@@ -106,9 +106,7 @@ uninstall-local:
+
+ check:
+ manual.txt: manual.html
+- -LC_ALL=C lynx -dump -nolist -with_backspaces -display_charset=us-ascii manual.html > $@ || \
+- LC_ALL=C w3m -dump manual.html > $@ || \
+- LC_ALL=C elinks -dump -no-numbering -no-references manual.html | sed -e 's,\\001, ,g' > $@
++ LC_ALL=C w3m -dump -O UTF-8 manual.html > $@
+
+ Muttrc: stamp-doc-rc
+
+@@ -120,10 +118,10 @@ stamp-doc-rc: $(top_srcdir)/init.h makedoc$(EXEEXT) $(srcdir)/Muttrc.head
+ touch stamp-doc-rc
+
+ manual.html: $(srcdir)/html.xsl $(srcdir)/mutt.xsl stamp-doc-xml $(srcdir)/mutt.css
+- -xsltproc --nonet -o $@ $(srcdir)/html.xsl manual.xml
++ xsltproc --nonet -o $@ $(srcdir)/html.xsl manual.xml
+
+ stamp-doc-chunked: $(srcdir)/chunk.xsl $(srcdir)/mutt.xsl stamp-doc-xml $(srcdir)/mutt.css
+- -xsltproc --nonet $(srcdir)/chunk.xsl manual.xml
++ xsltproc --nonet $(srcdir)/chunk.xsl manual.xml
+ touch stamp-doc-chunked
+
+ $(CHUNKED_DOCFILES): stamp-doc-chunked
diff --git a/603288-split-fetches.patch b/603288-split-fetches.patch
new file mode 100644
index 000000000000..a6deee5839cf
--- /dev/null
+++ b/603288-split-fetches.patch
@@ -0,0 +1,33 @@
+From: Antonio Radici <antonio@debian.org>
+Date: Thu, 27 Feb 2014 17:16:58 +0100
+Subject: 603288-split-fetches
+
+A workaround for Exchange 2010 IMAP protocol,
+tracked in http://bugs.mutt.org/3459,
+already merged in the ubuntu version of Mutt.
+
+Gbp-Pq: Topic upstream
+---
+ imap/message.c | 9 +++++++++
+ 1 file changed, 9 insertions(+)
+
+diff --git a/imap/message.c b/imap/message.c
+index 3504be8..4234e22 100644
+--- a/imap/message.c
++++ b/imap/message.c
+@@ -240,6 +240,15 @@ int imap_read_headers (IMAP_DATA* idata, int msgbegin, int msgend)
+ char *cmd;
+
+ fetchlast = msgend + 1;
++ /* Microsoft Exchange 2010 violates the IMAP protocol and
++ * starts omitting messages if one FETCHes more than 2047 (or
++ * or somewhere around that number. We therefore split the
++ * FETCH into chunks of 2000 messages each */
++ if (fetchlast - msgno - 1 > 2000)
++ {
++ fetchlast = msgno+1 + 2000;
++ }
++
+ safe_asprintf (&cmd, "FETCH %d:%d (UID FLAGS INTERNALDATE RFC822.SIZE %s)",
+ msgno + 1, fetchlast, hdrreq);
+ imap_cmd_start (idata, cmd);
diff --git a/611410-no-implicit_autoview-for-text-html.patch b/611410-no-implicit_autoview-for-text-html.patch
new file mode 100644
index 000000000000..4ccc2d86345d
--- /dev/null
+++ b/611410-no-implicit_autoview-for-text-html.patch
@@ -0,0 +1,52 @@
+From: Antonio Radici <antonio@debian.org>
+Date: Thu, 27 Feb 2014 17:36:31 +0100
+Subject: 611410-no-implicit_autoview-for-text-html
+MIME-Version: 1.0
+Content-Type: text/plain; charset="utf-8"
+Content-Transfer-Encoding: 8bit
+
+This patch blacklist text/html from the list of documents that will be
+shown automatically, the patch (the muttlib.c part) has been written by
+Loïc Minier <lool@dooz.org>, I've added the documentation bit.
+
+The patch has been forwarded upstream originally by Loïc on:
+http://bugs.mutt.org/3496
+
+The original Debian bug for this problem is:
+http://bugs.debian.org/611410
+
+Gbp-Pq: Topic upstream
+---
+ init.h | 4 +++-
+ muttlib.c | 3 +++
+ 2 files changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/init.h b/init.h
+index 42b79d6..08e004c 100644
+--- a/init.h
++++ b/init.h
+@@ -1222,7 +1222,9 @@ struct option_t MuttVars[] = {
+ ** ``\fCcopiousoutput\fP'' flag set for \fIevery\fP MIME attachment it doesn't have
+ ** an internal viewer defined for. If such an entry is found, mutt will
+ ** use the viewer defined in that entry to convert the body part to text
+- ** form.
++ ** form. MIME attachments with 'text' types, with the only exception
++ ** of text/html, are excluded: they will be shown as they are unless auto_view
++ ** is specified.
+ */
+ { "include", DT_QUAD, R_NONE, OPT_INCLUDE, M_ASKYES },
+ /*
+diff --git a/muttlib.c b/muttlib.c
+index 0425d45..c0d2026 100644
+--- a/muttlib.c
++++ b/muttlib.c
+@@ -658,6 +658,9 @@ int mutt_needs_mailcap (BODY *m)
+ switch (m->type)
+ {
+ case TYPETEXT:
++ /* we don't want to display text/html */
++ if (!ascii_strcasecmp ("html", m->subtype))
++ return 1;
+ /* we can display any text, overridable by auto_view */
+ return 0;
+ break;
diff --git a/Md.etc_mailname_gethostbyname.patch b/Md.etc_mailname_gethostbyname.patch
new file mode 100644
index 000000000000..849ec54d032b
--- /dev/null
+++ b/Md.etc_mailname_gethostbyname.patch
@@ -0,0 +1,82 @@
+From: Antonio Radici <antonio@debian.org>
+Date: Thu, 27 Feb 2014 15:58:54 +0100
+Subject: Md.etc_mailname_gethostbyname
+
+If /etc/mailname is present, the hostname inside the file will be
+used, rather than calling gethostbyname() on the actual hostname.
+
+Gbp-Pq: Topic debian-specific
+---
+ init.c | 41 +++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 41 insertions(+)
+
+diff --git a/init.c b/init.c
+index 81bb9e7..1401664 100644
+--- a/init.c
++++ b/init.c
+@@ -48,6 +48,7 @@
+ #include <unistd.h>
+ #include <string.h>
+ #include <sys/utsname.h>
++#include <netdb.h>
+ #include <errno.h>
+ #include <sys/wait.h>
+ #include <sys/time.h>
+@@ -2965,6 +2966,31 @@ static void mutt_srandom (void)
+ srandom(seed);
+ }
+
++int getmailname(char *s, size_t l)
++{
++ FILE *f;
++ char tmp[512];
++ char *p = tmp;
++
++ if ((f = fopen ("/etc/mailname", "r")) == NULL)
++ return (-1);
++
++ if (fgets (tmp, 510, f) != NULL) {
++ while (*p && !ISSPACE(*p) && l > 0) {
++ *s++ = *p++;
++ l--;
++ }
++ if (*(s-1) == '.')
++ s--;
++ *s = 0;
++
++ fclose (f);
++ return 0;
++ }
++ fclose (f);
++ return (-1);
++}
++
+ void mutt_init (int skip_sys_rc, LIST *commands)
+ {
+ struct passwd *pw;
+@@ -3041,10 +3067,25 @@ void mutt_init (int skip_sys_rc, LIST *commands)
+ Hostname = mutt_substrdup (utsname.nodename, p);
+ p++;
+ strfcpy (buffer, p, sizeof (buffer)); /* save the domain for below */
++ Fqdn = safe_strdup (utsname.nodename);
+ }
+ else
+ Hostname = safe_strdup (utsname.nodename);
+
++ /* if /etc/mailname exists use it and ignore everything else */
++ if (getmailname(buffer, sizeof (buffer)) != -1)
++ Fqdn = safe_strdup(buffer);
++
++ /* try gethostbyname(3) if /etc/mailname does not exists */
++ if (!Fqdn) {
++ struct hostent *hp;
++
++ if ((hp = gethostbyname(Hostname)))
++ Fqdn = safe_strdup(hp->h_name);
++ }
++
++ if (Fqdn) {
++ } else
+ #ifndef DOMAIN
+ #define DOMAIN buffer
+ if (!p && getdnsdomainname (buffer, sizeof (buffer)) == -1)
diff --git a/Muttrc.patch b/Muttrc.patch
new file mode 100644
index 000000000000..25e026f38d07
--- /dev/null
+++ b/Muttrc.patch
@@ -0,0 +1,81 @@
+From: Cristoph Berg <myon@debian.org>
+Date: Thu, 27 Feb 2014 15:52:59 +0100
+Subject: Muttrc
+
+ * 2009-01-15 myon: refreshed for mutt-1.5.19; drop our list of ignored headers
+ in favor of upstream's new unignore list
+
+Signed-off-by: Matteo F. Vescovi <mfvescovi@gmail.com>
+
+Gbp-Pq: Topic debian-specific
+---
+ doc/Muttrc.head | 36 ++++++++++++++++++++++++++++++++++--
+ 1 file changed, 34 insertions(+), 2 deletions(-)
+
+diff --git a/doc/Muttrc.head b/doc/Muttrc.head
+index 1f7012e..477c4b6 100644
+--- a/doc/Muttrc.head
++++ b/doc/Muttrc.head
+@@ -12,6 +12,34 @@ unignore from: subject to cc date x-mailer x-url user-agent
+ # Display the fields in this order
+ hdr_order date from to cc subject
+
++# emacs-like bindings
++bind editor "\e<delete>" kill-word
++bind editor "\e<backspace>" kill-word
++
++# map delete-char to a sane value
++bind editor <delete> delete-char
++
++# some people actually like these settings
++#set pager_stop
++#bind pager <up> previous-line
++#bind pager <down> next-line
++
++# Specifies how to sort messages in the index menu.
++set sort=threads
++
++# The behavior of this option on the Debian mutt package is
++# not the original one because exim4, the default SMTP on Debian
++# does not strip bcc headers so this can cause privacy problems;
++# see man muttrc for more info
++#unset write_bcc
++# Postfix and qmail use Delivered-To for detecting loops
++unset bounce_delivered
++
++set mixmaster="mixmaster-filter"
++
++# System-wide CA file managed by the ca-certificates package
++set ssl_ca_certificates_file="/etc/ssl/certs/ca-certificates.crt"
++
+ # imitate the old search-body function
+ macro index \eb "<search>~b " "search in message bodies"
+
+@@ -23,14 +51,14 @@ macro index,pager,attach,compose \cb "\
+ "call urlview to extract URLs out of a message"
+
+ # Show documentation when pressing F1
+-macro generic,pager <F1> "<shell-escape> less @docdir@/manual.txt<Enter>" "show Mutt documentation"
++macro generic,pager <F1> "<shell-escape> zcat @docdir@/mutt/manual.txt.gz | sensible-pager<enter>" "show Mutt documentation"
+
+ # show the incoming mailboxes list (just like "mutt -y") and back when pressing "y"
+ macro index,pager y "<change-folder>?<toggle-mailboxes>" "show incoming mailboxes list"
+ bind browser y exit
+
+ # If Mutt is unable to determine your site's domain name correctly, you can
+-# set the default here.
++# set the default here. (better: fix /etc/mailname)
+ #
+ # set hostname=cs.hmc.edu
+
+@@ -42,6 +70,10 @@ bind browser y exit
+ # be undone with unmime_lookup.
+ mime_lookup application/octet-stream
+
++# Upgrade the progress counter every 250ms, good for mutt over SSH
++# see http://bugs.debian.org/537746
++set time_inc=250
++
+ ##
+ ## *** DEFAULT SETTINGS FOR THE ATTACHMENTS PATCH ***
+ ##
diff --git a/PKGBUILD b/PKGBUILD
new file mode 100644
index 000000000000..f900d27d7945
--- /dev/null
+++ b/PKGBUILD
@@ -0,0 +1,211 @@
+# Maintainer: Oscar Morante <oscar@morante.eu>
+# Contributor: tobias [tobias [at] archlinux.org]
+# Contributor: Gaetan Bisson <bisson@archlinux.org>
+
+pkgname=mutt-patched
+pkgver=1.5.23
+pkgrel=5
+pkgdesc='Small but very powerful text-based mail client (plus a huge pile of patches mostly from debian)'
+url='http://www.mutt.org/'
+license=('GPL')
+backup=('etc/Muttrc')
+arch=('i686' 'x86_64')
+optdepends=('smtp-forwarder: to send mail')
+depends=('gpgme' 'ncurses' 'gnutls' 'libsasl' 'libidn' 'mime-types'
+ 'krb5' 'tokyocabinet')
+makedepends=( 'w3m' 'docbook-xsl' )
+conflicts=('mutt')
+provides=('mutt')
+source=(
+ "https://bitbucket.org/mutt/mutt/downloads/mutt-${pkgver}.tar.gz"
+ 'backport-gnutls-fix.patch'
+ 'am-maintainer-mode.patch'
+ 'ifdef.patch'
+ 'xtitles.patch'
+ 'trash-folder.patch'
+ 'purge-message.patch'
+ 'imap_fast_trash.patch'
+ 'sensible_browser_position.patch'
+ 'patch-1.5.4.vk.pgp_verbose_mime.patch'
+ 'compressed-folders.patch'
+ 'compressed-folders.debian.patch'
+ 'Muttrc.patch'
+ 'Md.etc_mailname_gethostbyname.patch'
+ 'use_usr_bin_editor.patch'
+ 'correct_docdir_in_man_page.patch'
+ 'dont_document_not_present_features.patch'
+ 'document_debian_defaults.patch'
+ 'assumed_charset-compat.patch'
+ '467432-write_bcc.patch'
+ '566076-build_doc_adjustments.patch'
+ 'define-pgp_getkeys_command.patch'
+ 'gpg.rc-paths.patch'
+ 'smime.rc.patch'
+ 'fix-configure-test-operator.patch'
+ '531430-imapuser.patch'
+ '543467-thread-segfault.patch'
+ '542817-smimekeys-tmpdir.patch'
+ '548577-gpgme-1.2.patch'
+ '553321-ansi-escape-segfault.patch'
+ '547980-smime_keys-chaining.patch'
+ '528233-readonly-open.patch'
+ '228671-pipe-mime.patch'
+ '383769-score-match.patch'
+ '603288-split-fetches.patch'
+ '611410-no-implicit_autoview-for-text-html.patch'
+ 'path_max.patch'
+ 'update_german_translation.patch'
+ 'sidebar.patch'
+ 'sidebar-dotpathsep.patch'
+ 'sidebar-utf8.patch'
+ 'multiple-fcc.patch'
+ 'sidebar-newonly.patch'
+ 'sidebar-delimnullwide.patch'
+ 'sidebar-compose.patch'
+ 'sidebar-new.patch'
+ 'nntp.patch'
+)
+sha1sums=('8ac821d8b1e25504a31bf5fda9c08d93a4acc862'
+ '018575fea153952423d4d512ca2fdfc2e49d669f'
+ '1ad77bdf742ff584b5695f1908dde83044195c0e'
+ '3aca6a18ac32b8f6c15216e6fb928a05320c5b74'
+ 'ff3b66e57faaa163e3e6512f81fdbbea9e02a0bd'
+ '5c1eae78cb8636025916e7d83c192f6c32981ff3'
+ 'c6eaf7fb1829809e7f07d039f75ab30de02d7873'
+ '72a35dfd4476238db3500f3a5651996691c1ce20'
+ 'e3e7b9c5b17be4032e111edb6a6999a3185c8f7b'
+ '61630d6a6c3fdf7480c7b6a0e75552500c48f5f4'
+ 'a7546f9616460da23e87957b0ce98776113b5f7b'
+ '6a53e85ee55c14beda0000de89a397f589cb29a7'
+ 'b841ad389301d336ba533356d057ea1f09bc150b'
+ 'ee72ecb042e337427b5d9c31ff1ecb78b440e2ff'
+ '773c59af8755b6daa1d73749fa9c38ccf953285c'
+ '0acf230d3b7e03b90239b256a6944ccf8a0b4e91'
+ '12c74c647238e653c4db5f8eb7590c5723635b9c'
+ '4dd818f69c4f302c5ca3e5a7fd95223112c853ac'
+ '464ca1c57f8a25177a586cb79b94398f694b32e3'
+ 'c44f9b5a3104917417d09dbcb0dda77f4566c30a'
+ '206daf4a78f801d9835c8096c83181569ae74136'
+ '061afb767a30fb78d48b0de1e30aea2f0b3b9d5d'
+ '42ab3017961819669c678e7456657ca677c8eb56'
+ '8ae07c06cc9a552b7152c4e264b6284cd770a29c'
+ 'ad645521bcd36a758e10cf0e2e2e8123c3c9a229'
+ 'f1ee08450995fe21e0dc3807563e7120d2e3d04a'
+ 'f2b98a258d5ee18f731aab975f5b0dd17945f228'
+ '98d958231c05fa1ddf5f1a0ab67a2dc6cefa8418'
+ 'c8ac575a36876d711a16be09425301c4800bf21c'
+ '6ec4a7614dc78d230a9ebe8ec645393a98be41a4'
+ 'f91ac0baf4eda090da7b80e272d422f234b459cd'
+ '03a42b8914c9ef1f684275cba355046de14b1c03'
+ '1b53316b0c09333dbee428448a6f565378ff5357'
+ '8121dd0a07f6f98377e1b1ad3659deaec3a83b6b'
+ 'd4803ee5f29e9e495e12bf012dc938d7cd4adc47'
+ '561b600c79988975f034c9237158fb991ec92541'
+ 'f91499aa8bee5827924e6c860bd853e0344d55bc'
+ 'f99082007d3cb4486b96cb22d1fae1c71fdbe51e'
+ 'fcdeb12e49735268c080e34c2d88211a7ccb8493'
+ '487850ffc2a6d639d9d17b3ced844257799a84a9'
+ 'c01c8f84e3ea4ac71caf31edeb6adb062eb04fe0'
+ '2ba91afb71dc40793bac8198fd9f8d6500bd1ce3'
+ 'e7181b191f06bd6c74c6e35372dba6dc3f8c4653'
+ '19fd36f9a1f15d4481f08d90f9a88d3912e12cdb'
+ 'b648cb9ab1a0b447417647a82856af01a9a418e5'
+ '230973eb243265ee75bd098f0f8286690bed2d08'
+ '8d7d858e47bbb2c736c9d09de843d3892888e9c5')
+
+install=install
+
+prepare() {
+ cd "${srcdir}/mutt-$pkgver"
+
+ patch -Np1 -i "${srcdir}/backport-gnutls-fix.patch"
+ patch -Np1 -i "${srcdir}/am-maintainer-mode.patch"
+ patch -Np1 -i "${srcdir}/ifdef.patch"
+ patch -Np1 -i "${srcdir}/xtitles.patch"
+ patch -Np1 -i "${srcdir}/trash-folder.patch"
+ patch -Np1 -i "${srcdir}/purge-message.patch"
+ patch -Np1 -i "${srcdir}/imap_fast_trash.patch"
+ patch -Np1 -i "${srcdir}/sensible_browser_position.patch"
+ patch -Np1 -i "${srcdir}/patch-1.5.4.vk.pgp_verbose_mime.patch"
+ patch -Np1 -i "${srcdir}/compressed-folders.patch"
+ patch -Np1 -i "${srcdir}/compressed-folders.debian.patch"
+ patch -Np1 -i "${srcdir}/Muttrc.patch"
+ patch -Np1 -i "${srcdir}/Md.etc_mailname_gethostbyname.patch"
+ patch -Np1 -i "${srcdir}/use_usr_bin_editor.patch"
+ patch -Np1 -i "${srcdir}/correct_docdir_in_man_page.patch"
+ patch -Np1 -i "${srcdir}/dont_document_not_present_features.patch"
+ patch -Np1 -i "${srcdir}/document_debian_defaults.patch"
+ patch -Np1 -i "${srcdir}/assumed_charset-compat.patch"
+ patch -Np1 -i "${srcdir}/467432-write_bcc.patch"
+ patch -Np1 -i "${srcdir}/566076-build_doc_adjustments.patch"
+ patch -Np1 -i "${srcdir}/define-pgp_getkeys_command.patch"
+ patch -Np1 -i "${srcdir}/gpg.rc-paths.patch"
+ patch -Np1 -i "${srcdir}/smime.rc.patch"
+ patch -Np1 -i "${srcdir}/fix-configure-test-operator.patch"
+ patch -Np1 -i "${srcdir}/531430-imapuser.patch"
+ patch -Np1 -i "${srcdir}/543467-thread-segfault.patch"
+ patch -Np1 -i "${srcdir}/542817-smimekeys-tmpdir.patch"
+ patch -Np1 -i "${srcdir}/548577-gpgme-1.2.patch"
+ patch -Np1 -i "${srcdir}/553321-ansi-escape-segfault.patch"
+ patch -Np1 -i "${srcdir}/547980-smime_keys-chaining.patch"
+ patch -Np1 -i "${srcdir}/528233-readonly-open.patch"
+ patch -Np1 -i "${srcdir}/228671-pipe-mime.patch"
+ patch -Np1 -i "${srcdir}/383769-score-match.patch"
+ patch -Np1 -i "${srcdir}/603288-split-fetches.patch"
+ patch -Np1 -i "${srcdir}/611410-no-implicit_autoview-for-text-html.patch"
+ patch -Np1 -i "${srcdir}/path_max.patch"
+ patch -Np1 -i "${srcdir}/update_german_translation.patch"
+ patch -Np1 -i "${srcdir}/sidebar.patch"
+ patch -Np1 -i "${srcdir}/sidebar-dotpathsep.patch"
+ patch -Np1 -i "${srcdir}/sidebar-utf8.patch"
+ patch -Np1 -i "${srcdir}/multiple-fcc.patch"
+ patch -Np1 -i "${srcdir}/sidebar-newonly.patch"
+ patch -Np1 -i "${srcdir}/sidebar-delimnullwide.patch"
+ patch -Np1 -i "${srcdir}/sidebar-compose.patch"
+ patch -Np1 -i "${srcdir}/sidebar-new.patch"
+ patch -Np1 -i "${srcdir}/nntp.patch"
+
+ autoreconf -vfi
+}
+
+build() {
+ cd "${srcdir}/mutt-${pkgver}"
+
+ ./configure \
+ --prefix=/usr \
+ --sysconfdir=/etc \
+ --mandir=/usr/share/man \
+ --with-docdir=/usr/share/doc \
+ --with-mailpath=/var/mail \
+ --disable-dependency-tracking \
+ --enable-compressed \
+ --enable-debug \
+ --enable-fcntl \
+ --enable-gpgme \
+ --enable-hcache \
+ --enable-imap \
+ --enable-pop \
+ --enable-smtp \
+ --with-curses \
+ --with-gss \
+ --with-idn \
+ --with-mixmaster \
+ --without-bdb \
+ --without-gdbm \
+ --without-qdbm \
+ --with-regex \
+ --with-sasl \
+ --with-gnutls
+
+ make
+}
+
+package() {
+ cd "${srcdir}/mutt-${pkgver}"
+ make DESTDIR="${pkgdir}" install
+
+ rm "${pkgdir}"/etc/mime.types{,.dist}
+ rm "${pkgdir}"/usr/bin/{flea,muttbug}
+ rm "${pkgdir}"/usr/share/man/man1/{flea,muttbug}.1
+ install -D -m 644 contrib/gpg.rc "$pkgdir"/etc/Muttrc.gpg.dist
+}
diff --git a/am-maintainer-mode.patch b/am-maintainer-mode.patch
new file mode 100644
index 000000000000..ec77fd5767f7
--- /dev/null
+++ b/am-maintainer-mode.patch
@@ -0,0 +1,22 @@
+From: Antonio Radici <antonio@debian.org>
+Date: Mon, 10 Feb 2014 18:14:45 +0100
+Subject: am-maintainer-mode
+
+Gbp-Pq: Topic misc
+---
+ configure.ac | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/configure.ac b/configure.ac
+index 1b6e010..3d5f11b 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -7,7 +7,7 @@ AC_INIT([mutt],[m4_esyscmd(tr -d \\n <VERSION)])
+ AC_CONFIG_SRCDIR(mutt.h)
+ AM_INIT_AUTOMAKE
+ AC_CONFIG_HEADERS([config.h])
+-
++AM_MAINTAINER_MODE
+ AC_SUBST([CONFIG_STATUS_DEPENDENCIES], ['$(top_srcdir)/VERSION'])
+
+ MUTT_VERSION=`env sh "$srcdir/version.sh"`
diff --git a/assumed_charset-compat.patch b/assumed_charset-compat.patch
new file mode 100644
index 000000000000..3201042174e8
--- /dev/null
+++ b/assumed_charset-compat.patch
@@ -0,0 +1,27 @@
+From: Antonio Radici <antonio@debian.org>
+Date: Thu, 27 Feb 2014 16:20:58 +0100
+Subject: assumed_charset-compat
+
+On upstream inclusion of the assumed_charset patch (past 1.5.14),
+file_charset got renamed to attach_charset. We add it back
+for compatibility.
+
+Gbp-Pq: Topic debian-specific
+---
+ init.h | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/init.h b/init.h
+index 7d319ad..875cad8 100644
+--- a/init.h
++++ b/init.h
+@@ -211,6 +211,9 @@ struct option_t MuttVars[] = {
+ ** Note: for Japanese users, ``iso-2022-*'' must be put at the head
+ ** of the value as shown above if included.
+ */
++ { "file_charset", DT_SYN, R_NONE, UL "attach_charset", 0 },
++ /*
++ */
+ { "attach_format", DT_STR, R_NONE, UL &AttachFormat, UL "%u%D%I %t%4n %T%.40d%> [%.7m/%.10M, %.6e%?C?, %C?, %s] " },
+ /*
+ ** .pp
diff --git a/backport-gnutls-fix.patch b/backport-gnutls-fix.patch
new file mode 100644
index 000000000000..5997592a0a45
--- /dev/null
+++ b/backport-gnutls-fix.patch
@@ -0,0 +1,162 @@
+commit f94101b0a9d1a321efe1d3079be2c9d4646fbd29
+Author: Kevin McCarthy <kevin@8t8.us>
+Date: Fri Aug 22 18:51:38 2014 -0700
+
+ Fix deprecated gnutls_protocol_set_priority. (closes #3515)
+
+ This patch is based off of the patch at
+ http://dev.mutt.org/trac/attachment/ticket/3515/624058-gnutls-deprecated-set-priority.patch
+
+ The mutt source has changed since then, with more TLS options being
+ added. This patch therefore uses a slightly different strategy for generating the
+ priority string.
+
+ The patch also adds autoconf support, as requested by brendan at
+ http://dev.mutt.org/trac/ticket/3515#comment:1
+
+diff --git a/configure.ac b/configure.ac
+index 20c59e0..8df1689 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -707,6 +707,9 @@ then
+ AC_CHECK_DECLS([GNUTLS_VERIFY_DISABLE_TIME_CHECKS], [], [],
+ [[#include <gnutls/x509.h>]])
+
++ LIBS="$LIBS -lgnutls"
++ AC_CHECK_FUNCS(gnutls_priority_set_direct)
++
+ LIBS="$saved_LIBS"
+ MUTTLIBS="$MUTTLIBS -lgnutls"
+
+diff --git a/mutt_ssl_gnutls.c b/mutt_ssl_gnutls.c
+index 551dde7..3ff879b 100644
+--- a/mutt_ssl_gnutls.c
++++ b/mutt_ssl_gnutls.c
+@@ -238,19 +238,93 @@ err_crt:
+ gnutls_x509_crt_deinit (clientcrt);
+ }
+
++#if HAVE_GNUTLS_PRIORITY_SET_DIRECT
++static int tls_set_priority(tlssockdata *data)
++{
++ size_t nproto = 4;
++ char priority[SHORT_STRING];
++ int err;
++
++ priority[0] = 0;
++ safe_strcat (priority, sizeof (priority), "NORMAL");
++
++ if (! option(OPTTLSV1_2))
++ {
++ nproto--;
++ safe_strcat (priority, sizeof (priority), ":-VERS-TLS1.2");
++ }
++ if (! option(OPTTLSV1_1))
++ {
++ nproto--;
++ safe_strcat (priority, sizeof (priority), ":-VERS-TLS1.1");
++ }
++ if (! option(OPTTLSV1))
++ {
++ nproto--;
++ safe_strcat (priority, sizeof (priority), ":-VERS-TLS1.0");
++ }
++ if (! option(OPTSSLV3))
++ {
++ nproto--;
++ safe_strcat (priority, sizeof (priority), ":-VERS-SSL3.0");
++ }
++
++ if (nproto == 0)
++ {
++ mutt_error (_("All available protocols for TLS/SSL connection disabled"));
++ return -1;
++ }
++
++ if ((err = gnutls_priority_set_direct (data->state, priority, NULL)) < 0)
++ {
++ mutt_error ("gnutls_priority_set_direct(%s): %s", priority, gnutls_strerror(err));
++ mutt_sleep (2);
++ return -1;
++ }
++
++ return 0;
++}
++#else
+ /* This array needs to be large enough to hold all the possible values support
+ * by Mutt. The initialized values are just placeholders--the array gets
+ * overwrriten in tls_negotiate() depending on the $ssl_use_* options.
+ */
+ static int protocol_priority[] = {GNUTLS_TLS1_2, GNUTLS_TLS1_1, GNUTLS_TLS1, GNUTLS_SSL3, 0};
+
++static int tls_set_priority(tlssockdata *data)
++{
++ size_t nproto = 0; /* number of tls/ssl protocols */
++
++ if (option(OPTTLSV1_2))
++ protocol_priority[nproto++] = GNUTLS_TLS1_2;
++ if (option(OPTTLSV1_1))
++ protocol_priority[nproto++] = GNUTLS_TLS1_1;
++ if (option(OPTTLSV1))
++ protocol_priority[nproto++] = GNUTLS_TLS1;
++ if (option(OPTSSLV3))
++ protocol_priority[nproto++] = GNUTLS_SSL3;
++ protocol_priority[nproto] = 0;
++
++ if (nproto == 0)
++ {
++ mutt_error (_("All available protocols for TLS/SSL connection disabled"));
++ return -1;
++ }
++
++ /* We use default priorities (see gnutls documentation),
++ except for protocol version */
++ gnutls_set_default_priority (data->state);
++ gnutls_protocol_set_priority (data->state, protocol_priority);
++ return 0;
++}
++#endif
++
+ /* tls_negotiate: After TLS state has been initialized, attempt to negotiate
+ * TLS over the wire, including certificate checks. */
+ static int tls_negotiate (CONNECTION * conn)
+ {
+ tlssockdata *data;
+ int err;
+- size_t nproto = 0; /* number of tls/ssl protocols */
+
+ data = (tlssockdata *) safe_calloc (1, sizeof (tlssockdata));
+ conn->sockdata = data;
+@@ -296,31 +370,9 @@ static int tls_negotiate (CONNECTION * conn)
+ /* set socket */
+ gnutls_transport_set_ptr (data->state, (gnutls_transport_ptr)conn->fd);
+
+- if (option(OPTTLSV1_2))
+- protocol_priority[nproto++] = GNUTLS_TLS1_2;
+- if (option(OPTTLSV1_1))
+- protocol_priority[nproto++] = GNUTLS_TLS1_1;
+- if (option(OPTTLSV1))
+- protocol_priority[nproto++] = GNUTLS_TLS1;
+- if (option(OPTSSLV3))
+- protocol_priority[nproto++] = GNUTLS_SSL3;
+- protocol_priority[nproto] = 0;
+-
+- /* disable TLS/SSL protocols as needed */
+- if (nproto == 0)
+- {
+- mutt_error (_("All available protocols for TLS/SSL connection disabled"));
++ if (tls_set_priority(data) < 0) {
+ goto fail;
+ }
+- /*
+- else
+- use the list set above
+- */
+-
+- /* We use default priorities (see gnutls documentation),
+- except for protocol version */
+- gnutls_set_default_priority (data->state);
+- gnutls_protocol_set_priority (data->state, protocol_priority);
+
+ if (SslDHPrimeBits > 0)
+ {
diff --git a/compressed-folders.debian.patch b/compressed-folders.debian.patch
new file mode 100644
index 000000000000..55733ea96d46
--- /dev/null
+++ b/compressed-folders.debian.patch
@@ -0,0 +1,22 @@
+From: Antonio Radici <antonio@debian.org>
+Date: Thu, 27 Feb 2014 15:49:18 +0100
+Subject: compressed-folders.debian
+
+To enable the use of compressed folders.
+
+Gbp-Pq: Topic features
+---
+ config-debian.h | 4 ++++
+ 1 file changed, 4 insertions(+)
+ create mode 100644 config-debian.h
+
+diff --git a/config-debian.h b/config-debian.h
+new file mode 100644
+index 0000000..fdf5322
+--- /dev/null
++++ b/config-debian.h
+@@ -0,0 +1,4 @@
++#ifndef USE_COMPRESSED
++#error "You forgot to update debian/patches/autotools-update."
++#error "Run sh debian/update-autotools.sh"
++#endif
diff --git a/compressed-folders.patch b/compressed-folders.patch
new file mode 100644
index 000000000000..b752564cd7f4
--- /dev/null
+++ b/compressed-folders.patch
@@ -0,0 +1,1188 @@
+From: Roland Rosenfeld <roland@spinnaker.de>
+Date: Thu, 27 Feb 2014 15:46:00 +0100
+Subject: compressed-folders
+
+The home page for this patch is:
+
+ http://www.spinnaker.de/mutt/compressed/
+
+* Patch last synced with upstream:
+ - Date: 2008-05-20
+ - File: http://www.spinnaker.de/mutt/compressed/patch-1.5.18.rr.compressed.1.gz
+
+* Changes made:
+ - 2008-05-20 myon: refreshed to remove hunks in auto* files
+ - 2009-09-15 myon: refreshed for mutt-1.5.19
+ status.c:103: add sizeof (tmp) to mutt_pretty_mailbox
+ - 2009-09-15 scotton: removed doc/Muttrc for mutt-1.5.19 (only patch doc/Muttrc.head)
+ - 2009-09-11 antonio: removed DefaultMagic, see 541360
+ - 2010-05-31 myon: remove commented paragraph "Use folders..." in
+ doc/Muttrc.head, see #578096
+ - 2014-03-17 evgeni: move remove_file and restore_path declaration before
+ their first use
+
+Signed-off-by: Matteo F. Vescovi <mfvescovi@gmail.com>
+
+Gbp-Pq: Topic features
+---
+ Makefile.am | 4 +-
+ compress.c | 499 ++++++++++++++++++++++++++++++++++++++++++++++++++++
+ compress.h | 27 +++
+ configure.ac | 5 +
+ curs_main.c | 5 +
+ doc/manual.xml.head | 199 +++++++++++++++++++++
+ doc/muttrc.man.head | 18 ++
+ hook.c | 14 ++
+ init.h | 5 +
+ main.c | 6 +
+ mbox.c | 10 ++
+ mutt.h | 10 ++
+ mx.c | 42 ++++-
+ mx.h | 3 +
+ po/POTFILES.in | 1 +
+ po/de.po | 30 ++++
+ status.c | 8 +
+ 17 files changed, 883 insertions(+), 3 deletions(-)
+ create mode 100644 compress.c
+ create mode 100644 compress.h
+
+diff --git a/Makefile.am b/Makefile.am
+index 8166b1b..09dd64b 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -22,7 +22,7 @@ BUILT_SOURCES = keymap_defs.h patchlist.c reldate.h conststrings.c $(HCVERSION)
+ bin_PROGRAMS = mutt @DOTLOCK_TARGET@ @PGPAUX_TARGET@
+ mutt_SOURCES = \
+ addrbook.c alias.c attach.c base64.c browser.c buffy.c color.c \
+- crypt.c cryptglue.c \
++ crypt.c cryptglue.c compress.c \
+ commands.c complete.c compose.c copy.c curs_lib.c curs_main.c date.c \
+ edit.c enter.c flags.c init.c filter.c from.c \
+ getdomain.c group.c \
+@@ -61,7 +61,7 @@ EXTRA_mutt_SOURCES = account.c bcache.c crypt-gpgme.c crypt-mod-pgp-classic.c \
+ bcache.h browser.h hcache.h mbyte.h mutt_idna.h remailer.h url.h
+
+ EXTRA_DIST = COPYRIGHT GPL OPS OPS.PGP OPS.CRYPT OPS.SMIME TODO UPDATING \
+- configure account.h \
++ configure account.h compress.h \
+ attach.h buffy.h charset.h copy.h crypthash.h dotlock.h functions.h gen_defs \
+ globals.h hash.h history.h init.h keymap.h mutt_crypt.h \
+ mailbox.h mapping.h md5.h mime.h mutt.h mutt_curses.h mutt_menu.h \
+diff --git a/compress.c b/compress.c
+new file mode 100644
+index 0000000..9bbf211
+--- /dev/null
++++ b/compress.c
+@@ -0,0 +1,499 @@
++/*
++ * Copyright (C) 1997 Alain Penders <Alain@Finale-Dev.com>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation; either version 2 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program; if not, write to the Free Software
++ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
++ */
++
++#if HAVE_CONFIG_H
++# include "config.h"
++#endif
++
++#include "mutt.h"
++
++#ifdef USE_COMPRESSED
++
++#include "mx.h"
++#include "mailbox.h"
++#include "mutt_curses.h"
++
++#include <errno.h>
++#include <string.h>
++#include <unistd.h>
++#include <sys/stat.h>
++
++typedef struct
++{
++ const char *close; /* close-hook command */
++ const char *open; /* open-hook command */
++ const char *append; /* append-hook command */
++ off_t size; /* size of real folder */
++} COMPRESS_INFO;
++
++
++/*
++ * ctx - context to lock
++ * excl - exclusive lock?
++ * retry - should retry if unable to lock?
++ */
++int mbox_lock_compressed (CONTEXT *ctx, FILE *fp, int excl, int retry)
++{
++ int r;
++
++ if ((r = mx_lock_file (ctx->realpath, fileno (fp), excl, 1, retry)) == 0)
++ ctx->locked = 1;
++ else if (retry && !excl)
++ {
++ ctx->readonly = 1;
++ return 0;
++ }
++
++ return (r);
++}
++
++void mbox_unlock_compressed (CONTEXT *ctx, FILE *fp)
++{
++ if (ctx->locked)
++ {
++ fflush (fp);
++
++ mx_unlock_file (ctx->realpath, fileno (fp), 1);
++ ctx->locked = 0;
++ }
++}
++
++static int is_new (const char *path)
++{
++ return (access (path, W_OK) != 0 && errno == ENOENT) ? 1 : 0;
++}
++
++static const char* find_compress_hook (int type, const char *path)
++{
++ const char* c = mutt_find_hook (type, path);
++ return (!c || !*c) ? NULL : c;
++}
++
++int mutt_can_read_compressed (const char *path)
++{
++ return find_compress_hook (M_OPENHOOK, path) ? 1 : 0;
++}
++
++/*
++ * if the file is new, we really do not append, but create, and so use
++ * close-hook, and not append-hook
++ */
++static const char* get_append_command (const char *path, const CONTEXT* ctx)
++{
++ COMPRESS_INFO *ci = (COMPRESS_INFO *) ctx->compressinfo;
++ return (is_new (path)) ? ci->close : ci->append;
++}
++
++int mutt_can_append_compressed (const char *path)
++{
++ int magic;
++
++ if (is_new (path))
++ {
++ char *dir_path = safe_strdup(path);
++ char *aux = strrchr(dir_path, '/');
++ int dir_valid = 1;
++ if (aux)
++ {
++ *aux='\0';
++ if (access(dir_path, W_OK|X_OK))
++ dir_valid = 0;
++ }
++ safe_free((void**)&dir_path);
++ return dir_valid && (find_compress_hook (M_CLOSEHOOK, path) ? 1 : 0);
++ }
++
++ magic = mx_get_magic (path);
++
++ if (magic != 0 && magic != M_COMPRESSED)
++ return 0;
++
++ return (find_compress_hook (M_APPENDHOOK, path)
++ || (find_compress_hook (M_OPENHOOK, path)
++ && find_compress_hook (M_CLOSEHOOK, path))) ? 1 : 0;
++}
++
++/* open a compressed mailbox */
++static COMPRESS_INFO *set_compress_info (CONTEXT *ctx)
++{
++ COMPRESS_INFO *ci;
++
++ /* Now lets uncompress this thing */
++ ci = safe_malloc (sizeof (COMPRESS_INFO));
++ ctx->compressinfo = (void*) ci;
++ ci->append = find_compress_hook (M_APPENDHOOK, ctx->path);
++ ci->open = find_compress_hook (M_OPENHOOK, ctx->path);
++ ci->close = find_compress_hook (M_CLOSEHOOK, ctx->path);
++ return ci;
++}
++
++static void set_path (CONTEXT* ctx)
++{
++ char tmppath[_POSIX_PATH_MAX];
++
++ /* Setup the right paths */
++ ctx->realpath = ctx->path;
++
++ /* Uncompress to /tmp */
++ mutt_mktemp (tmppath, sizeof(tmppath));
++ ctx->path = safe_malloc (strlen (tmppath) + 1);
++ strcpy (ctx->path, tmppath);
++}
++
++static int get_size (const char* path)
++{
++ struct stat sb;
++ if (stat (path, &sb) != 0)
++ return 0;
++ return (sb.st_size);
++}
++
++static void store_size (CONTEXT* ctx)
++{
++ COMPRESS_INFO *ci = (COMPRESS_INFO *) ctx->compressinfo;
++ ci->size = get_size (ctx->realpath);
++}
++
++static const char *
++compresshook_format_str (char *dest, size_t destlen, size_t col, char op,
++ const char *src, const char *fmt,
++ const char *ifstring, const char *elsestring,
++ unsigned long data, format_flag flags)
++{
++ char tmp[SHORT_STRING];
++
++ CONTEXT *ctx = (CONTEXT *) data;
++ switch (op)
++ {
++ case 'f':
++ snprintf (tmp, sizeof (tmp), "%%%ss", fmt);
++ snprintf (dest, destlen, tmp, ctx->realpath);
++ break;
++ case 't':
++ snprintf (tmp, sizeof (tmp), "%%%ss", fmt);
++ snprintf (dest, destlen, tmp, ctx->path);
++ break;
++ }
++ return (src);
++}
++
++/*
++ * check that the command has both %f and %t
++ * 0 means OK, -1 means error
++ */
++int mutt_test_compress_command (const char* cmd)
++{
++ return (strstr (cmd, "%f") && strstr (cmd, "%t")) ? 0 : -1;
++}
++
++static char *get_compression_cmd (const char* cmd, const CONTEXT* ctx)
++{
++ char expanded[_POSIX_PATH_MAX];
++ mutt_FormatString (expanded, sizeof (expanded), 0, cmd,
++ compresshook_format_str, (unsigned long) ctx, 0);
++ return safe_strdup (expanded);
++}
++
++int mutt_check_mailbox_compressed (CONTEXT* ctx)
++{
++ COMPRESS_INFO *ci = (COMPRESS_INFO *) ctx->compressinfo;
++ if (ci->size != get_size (ctx->realpath))
++ {
++ FREE (&ctx->compressinfo);
++ FREE (&ctx->realpath);
++ mutt_error _("Mailbox was corrupted!");
++ return (-1);
++ }
++ return (0);
++}
++
++void restore_path (CONTEXT* ctx)
++{
++ FREE (&ctx->path);
++ ctx->path = ctx->realpath;
++}
++
++/* remove the temporary mailbox */
++void remove_file (CONTEXT* ctx)
++{
++ if (ctx->magic == M_MBOX || ctx->magic == M_MMDF)
++ remove (ctx->path);
++}
++
++int mutt_open_read_compressed (CONTEXT *ctx)
++{
++ char *cmd;
++ FILE *fp;
++ int rc;
++
++ COMPRESS_INFO *ci = set_compress_info (ctx);
++ if (!ci->open) {
++ ctx->magic = 0;
++ FREE (&ctx->compressinfo);
++ return (-1);
++ }
++ if (!ci->close || access (ctx->path, W_OK) != 0)
++ ctx->readonly = 1;
++
++ set_path (ctx);
++ store_size (ctx);
++
++ if (!ctx->quiet)
++ mutt_message (_("Decompressing %s..."), ctx->realpath);
++
++ cmd = get_compression_cmd (ci->open, ctx);
++ if (cmd == NULL)
++ return (-1);
++ dprint (2, (debugfile, "DecompressCmd: '%s'\n", cmd));
++
++ if ((fp = fopen (ctx->realpath, "r")) == NULL)
++ {
++ mutt_perror (ctx->realpath);
++ FREE (&cmd);
++ return (-1);
++ }
++ mutt_block_signals ();
++ if (mbox_lock_compressed (ctx, fp, 0, 1) == -1)
++ {
++ fclose (fp);
++ mutt_unblock_signals ();
++ mutt_error _("Unable to lock mailbox!");
++ FREE (&cmd);
++ return (-1);
++ }
++
++ endwin ();
++ fflush (stdout);
++ fprintf (stderr, _("Decompressing %s...\n"),ctx->realpath);
++ rc = mutt_system (cmd);
++ mbox_unlock_compressed (ctx, fp);
++ mutt_unblock_signals ();
++ fclose (fp);
++
++ if (rc)
++ {
++ mutt_any_key_to_continue (NULL);
++ ctx->magic = 0;
++ FREE (&ctx->compressinfo);
++ mutt_error (_("Error executing: %s : unable to open the mailbox!\n"), cmd);
++ // remove the partial uncompressed file
++ remove_file (ctx);
++ restore_path (ctx);
++ }
++ FREE (&cmd);
++ if (rc)
++ return (-1);
++
++ if (mutt_check_mailbox_compressed (ctx))
++ return (-1);
++
++ ctx->magic = mx_get_magic (ctx->path);
++
++ return (0);
++}
++
++int mutt_open_append_compressed (CONTEXT *ctx)
++{
++ FILE *fh;
++ COMPRESS_INFO *ci = set_compress_info (ctx);
++
++ if (!get_append_command (ctx->path, ctx))
++ {
++ if (ci->open && ci->close)
++ return (mutt_open_read_compressed (ctx));
++
++ ctx->magic = 0;
++ FREE (&ctx->compressinfo);
++ return (-1);
++ }
++
++ set_path (ctx);
++
++ if (!is_new (ctx->realpath))
++ if ((fh = fopen (ctx->path, "w")))
++ fclose (fh);
++ /* No error checking - the parent function will catch it */
++
++ return (0);
++}
++
++/* close a compressed mailbox */
++void mutt_fast_close_compressed (CONTEXT *ctx)
++{
++ dprint (2, (debugfile, "mutt_fast_close_compressed called on '%s'\n",
++ ctx->path));
++
++ if (ctx->compressinfo)
++ {
++ if (ctx->fp)
++ fclose (ctx->fp);
++ ctx->fp = NULL;
++ /* if the folder was removed, remove the gzipped folder too */
++ if ((ctx->magic > 0)
++ && (access (ctx->path, F_OK) != 0)
++ && ! option (OPTSAVEEMPTY))
++ remove (ctx->realpath);
++ else
++ remove_file (ctx);
++
++ restore_path (ctx);
++ FREE (&ctx->compressinfo);
++ }
++}
++
++/* return 0 on success, -1 on failure */
++int mutt_sync_compressed (CONTEXT* ctx)
++{
++ char *cmd;
++ int rc = 0;
++ FILE *fp;
++ COMPRESS_INFO *ci = (COMPRESS_INFO *) ctx->compressinfo;
++
++ if (!ctx->quiet)
++ mutt_message (_("Compressing %s..."), ctx->realpath);
++
++ cmd = get_compression_cmd (ci->close, ctx);
++ if (cmd == NULL)
++ return (-1);
++
++ if ((fp = fopen (ctx->realpath, "a")) == NULL)
++ {
++ mutt_perror (ctx->realpath);
++ FREE (&cmd);
++ return (-1);
++ }
++ mutt_block_signals ();
++ if (mbox_lock_compressed (ctx, fp, 1, 1) == -1)
++ {
++ fclose (fp);
++ mutt_unblock_signals ();
++ mutt_error _("Unable to lock mailbox!");
++ store_size (ctx);
++ FREE (&cmd);
++ return (-1);
++ }
++
++ dprint (2, (debugfile, "CompressCommand: '%s'\n", cmd));
++
++ endwin ();
++ fflush (stdout);
++ fprintf (stderr, _("Compressing %s...\n"), ctx->realpath);
++ if (mutt_system (cmd))
++ {
++ mutt_any_key_to_continue (NULL);
++ mutt_error (_("%s: Error compressing mailbox! Original mailbox deleted, uncompressed one kept!\n"), ctx->path);
++ rc = -1;
++ }
++
++ mbox_unlock_compressed (ctx, fp);
++ mutt_unblock_signals ();
++ fclose (fp);
++
++ FREE (&cmd);
++
++ store_size (ctx);
++
++ return (rc);
++}
++
++int mutt_slow_close_compressed (CONTEXT *ctx)
++{
++ FILE *fp;
++ const char *append;
++ char *cmd;
++ COMPRESS_INFO *ci = (COMPRESS_INFO *) ctx->compressinfo;
++
++ dprint (2, (debugfile, "mutt_slow_close_compressed called on '%s'\n",
++ ctx->path));
++
++ if (! (ctx->append
++ && ((append = get_append_command (ctx->realpath, ctx))
++ || (append = ci->close))))
++ {
++ /* if we can not or should not append, we only have to remove the */
++ /* compressed info, because sync was already called */
++ mutt_fast_close_compressed (ctx);
++ return (0);
++ }
++
++ if (ctx->fp)
++ fclose (ctx->fp);
++ ctx->fp = NULL;
++
++ if (!ctx->quiet)
++ {
++ if (append == ci->close)
++ mutt_message (_("Compressing %s..."), ctx->realpath);
++ else
++ mutt_message (_("Compressed-appending to %s..."), ctx->realpath);
++ }
++
++ cmd = get_compression_cmd (append, ctx);
++ if (cmd == NULL)
++ return (-1);
++
++ if ((fp = fopen (ctx->realpath, "a")) == NULL)
++ {
++ mutt_perror (ctx->realpath);
++ FREE (&cmd);
++ return (-1);
++ }
++ mutt_block_signals ();
++ if (mbox_lock_compressed (ctx, fp, 1, 1) == -1)
++ {
++ fclose (fp);
++ mutt_unblock_signals ();
++ mutt_error _("Unable to lock mailbox!");
++ FREE (&cmd);
++ return (-1);
++ }
++
++ dprint (2, (debugfile, "CompressCmd: '%s'\n", cmd));
++
++ endwin ();
++ fflush (stdout);
++
++ if (append == ci->close)
++ fprintf (stderr, _("Compressing %s...\n"), ctx->realpath);
++ else
++ fprintf (stderr, _("Compressed-appending to %s...\n"), ctx->realpath);
++
++ if (mutt_system (cmd))
++ {
++ mutt_any_key_to_continue (NULL);
++ mutt_error (_(" %s: Error compressing mailbox! Uncompressed one kept!\n"),
++ ctx->path);
++ FREE (&cmd);
++ mbox_unlock_compressed (ctx, fp);
++ mutt_unblock_signals ();
++ fclose (fp);
++ return (-1);
++ }
++
++ mbox_unlock_compressed (ctx, fp);
++ mutt_unblock_signals ();
++ fclose (fp);
++ remove_file (ctx);
++ restore_path (ctx);
++ FREE (&cmd);
++ FREE (&ctx->compressinfo);
++
++ return (0);
++}
++
++#endif /* USE_COMPRESSED */
+diff --git a/compress.h b/compress.h
+new file mode 100644
+index 0000000..9dbf027
+--- /dev/null
++++ b/compress.h
+@@ -0,0 +1,27 @@
++/*
++ * Copyright (C) 1997 Alain Penders <Alain@Finale-Dev.com>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation; either version 2 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program; if not, write to the Free Software
++ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
++ */
++
++int mutt_can_read_compressed (const char *);
++int mutt_can_append_compressed (const char *);
++int mutt_open_read_compressed (CONTEXT *);
++int mutt_open_append_compressed (CONTEXT *);
++int mutt_slow_close_compressed (CONTEXT *);
++int mutt_sync_compressed (CONTEXT *);
++int mutt_test_compress_command (const char *);
++int mutt_check_mailbox_compressed (CONTEXT *);
++void mutt_fast_close_compressed (CONTEXT *);
+diff --git a/configure.ac b/configure.ac
+index 3d5f11b..e7ebe01 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -812,6 +812,11 @@ AC_ARG_ENABLE(locales-fix, AS_HELP_STRING([--enable-locales-fix],[The result of
+ AC_DEFINE(LOCALES_HACK,1,[ Define if the result of isprint() is unreliable. ])
+ fi])
+
++AC_ARG_ENABLE(compressed, AC_HELP_STRING([--enable-compressed], [Enable compressed folders support]),
++ [if test x$enableval = xyes; then
++ AC_DEFINE(USE_COMPRESSED,1, [ Define to support compressed folders. ])
++ fi])
++
+ AC_ARG_WITH(exec-shell, AS_HELP_STRING([--with-exec-shell=SHELL],[Specify alternate shell (ONLY if /bin/sh is broken)]),
+ [if test $withval != yes; then
+ AC_DEFINE_UNQUOTED(EXECSHELL, "$withval",
+diff --git a/curs_main.c b/curs_main.c
+index d266708..e7f11bd 100644
+--- a/curs_main.c
++++ b/curs_main.c
+@@ -1154,6 +1154,11 @@ int mutt_index_menu (void)
+ {
+ int check;
+
++#ifdef USE_COMPRESSED
++ if (Context->compressinfo && Context->realpath)
++ mutt_str_replace (&LastFolder, Context->realpath);
++ else
++#endif
+ mutt_str_replace (&LastFolder, Context->path);
+ oldcount = Context ? Context->msgcount : 0;
+
+diff --git a/doc/manual.xml.head b/doc/manual.xml.head
+index 18ae918..4366758 100644
+--- a/doc/manual.xml.head
++++ b/doc/manual.xml.head
+@@ -6121,6 +6121,205 @@ selection. Highest priority has the mailbox given with the
+
+ </chapter>
+
++<sect1 id="compressedfolders">
++<title>Compressed folders Support (OPTIONAL)</title>
++
++<para>
++If Mutt was compiled with compressed folders support (by running the
++<emphasis>configure</emphasis> script with the
++<emphasis>--enable-compressed</emphasis> flag), Mutt can open folders
++stored in an arbitrary format, provided that the user has a script to
++convert from/to this format to one of the accepted.
++
++The most common use is to open compressed archived folders e.g. with
++gzip.
++
++In addition, the user can provide a script that gets a folder in an
++accepted format and appends its context to the folder in the
++user-defined format, which may be faster than converting the entire
++folder to the accepted format, appending to it and converting back to
++the user-defined format.
++
++There are three hooks defined (<link
++linkend="open-hook">open-hook</link>, <link
++linkend="close-hook">close-hook</link> and <link
++linkend="append-hook">append-hook</link>) which define commands to
++uncompress and compress a folder and to append messages to an existing
++compressed folder respectively.
++
++For example:
++
++<screen>
++open-hook \\.gz$ "gzip -cd %f &gt; %t"
++close-hook \\.gz$ "gzip -c %t &gt; %f"
++append-hook \\.gz$ "gzip -c %t &gt;&gt; %f"
++</screen>
++
++You do not have to specify all of the commands. If you omit <link
++linkend="append-hook">append-hook</link>, the folder will be open and
++closed again each time you will add to it. If you omit <link
++linkend="close-hook">close-hook</link> (or give empty command) , the
++folder will be open in the mode. If you specify <link
++linkend="append-hook">append-hook</link> though you'll be able to
++append to the folder.
++
++Note that Mutt will only try to use hooks if the file is not in one of
++the accepted formats. In particular, if the file is empty, mutt
++supposes it is not compressed. This is important because it allows the
++use of programs that do not have well defined extensions. Just use
++&quot;.&quot; as a regexp. But this may be surprising if your
++compressing script produces empty files. In this situation, unset
++<link linkend="save-empty">&dollar;save&lowbar;empty</link>, so that
++the compressed file will be removed if you delete all of the messages.
++</para>
++
++<sect2 id="open-hook">
++<title>Open a compressed mailbox for reading</title>
++
++<para>
++Usage: <literal>open-hook</literal> <emphasis>regexp</emphasis> &quot;<emphasis>command</emphasis>&quot;
++
++The <emphasis>command</emphasis> is the command that can be used for
++opening the folders whose names match <emphasis>regexp</emphasis>.
++
++The <emphasis>command</emphasis> string is the printf-like format
++string, and it should accept two parameters: &percnt;f, which is
++replaced with the (compressed) folder name, and &percnt;t which is
++replaced with the name of the temporary folder to which to write.
++
++&percnt;f and &percnt;t can be repeated any number of times in the
++command string, and all of the entries are replaced with the
++appropriate folder name. In addition, &percnt;&percnt; is replaced by
++&percnt;, as in printf, and any other &percnt;anything is left as is.
++
++The <emphasis>command</emphasis> should <emphasis
++role="bold">not</emphasis> remove the original compressed file. The
++<emphasis>command</emphasis> should return non-zero exit status if it
++fails, so mutt knows something's wrong.
++
++Example:
++
++<screen>
++open-hook \\.gz$ "gzip -cd %f &gt; %t"
++</screen>
++
++If the <emphasis>command</emphasis> is empty, this operation is
++disabled for this file type.
++</para>
++</sect2>
++
++<sect2 id="close-hook">
++<title>Write a compressed mailbox</title>
++
++<para>
++Usage: <literal>close-hook</literal> <emphasis>regexp</emphasis> &quot;<emphasis>command</emphasis>&quot;
++
++This is used to close the folder that was open with the <link
++linkend="open-hook">open-hook</link> command after some changes were
++made to it.
++
++The <emphasis>command</emphasis> string is the command that can be
++used for closing the folders whose names match
++<emphasis>regexp</emphasis>. It has the same format as in the <link
++linkend="open-hook">open-hook</link> command. Temporary folder in this
++case is the folder previously produced by the <link
++linkend="open-hook">open-hook</link> command.
++
++The <emphasis>command</emphasis> should <emphasis
++role="bold">not</emphasis> remove the decompressed file. The
++<emphasis>command</emphasis> should return non-zero exit status if it
++fails, so mutt knows something's wrong.
++
++Example:
++
++<screen>
++close-hook \\.gz$ "gzip -c %t &gt; %f"
++</screen>
++
++If the <emphasis>command</emphasis> is empty, this operation is
++disabled for this file type, and the file can only be open in the
++read-only mode.
++
++<link linkend="close-hook">close-hook</link> is not called when you
++exit from the folder if the folder was not changed.
++</para>
++</sect2>
++
++<sect2 id="append-hook">
++<title>Append a message to a compressed mailbox</title>
++
++<para>
++Usage: <literal>append-hook</literal> <emphasis>regexp</emphasis> &quot;<emphasis>command</emphasis>&quot;
++
++This command is used for saving to an existing compressed folder. The
++<emphasis>command</emphasis> is the command that can be used for
++appending to the folders whose names match
++<emphasis>regexp</emphasis>. It has the same format as in the <link
++linkend="open-hook">open-hook</link> command. The temporary folder in
++this case contains the messages that are being appended.
++
++The <emphasis>command</emphasis> should <emphasis
++role="bold">not</emphasis> remove the decompressed file. The
++<emphasis>command</emphasis> should return non-zero exit status if it
++fails, so mutt knows something's wrong.
++
++Example:
++
++<screen>
++append-hook \\.gz$ "gzip -c %t &gt;&gt; %f"
++</screen>
++
++When <link linkend="append-hook">append-hook</link> is used, the folder
++is not opened, which saves time, but this means that we can not find
++out what the folder type is. Thus the default (<link
++linkend="mbox-type">&dollar;mbox&lowbar;type</link>) type is always
++supposed (i.e. this is the format used for the temporary folder).
++
++If the file does not exist when you save to it, <link
++linkend="close-hook">close-hook</link> is called, and not <link
++linkend="append-hook">append-hook</link>. <link
++linkend="append-hook">append-hook</link> is only for appending to
++existing folders.
++
++If the <emphasis>command</emphasis> is empty, this operation is
++disabled for this file type. In this case, the folder will be open and
++closed again (using <link linkend="open-hook">open-hook</link> and
++<link linkend="close-hook">close-hook</link>respectively) each time you
++will add to it.
++</para>
++</sect2>
++
++<sect2>
++<title>Encrypted folders</title>
++
++<para>
++The compressed folders support can also be used to handle encrypted
++folders. If you want to encrypt a folder with PGP, you may want to use
++the following hooks:
++
++<screen>
++open-hook \\.pgp$ "pgp -f &lt; %f &gt; %t"
++close-hook \\.pgp$ "pgp -fe YourPgpUserIdOrKeyId &lt; %t &gt; %f"
++</screen>
++
++Please note, that PGP does not support appending to an encrypted
++folder, so there is no append-hook defined.
++
++If you are using GnuPG instead of PGP, you may use the following hooks
++instead:
++
++<screen>
++open-hook \\.gpg$ "gpg --decrypt &lt; %f &gt; %t"
++close-hook \\.gpg$ "gpg --encrypt --recipient YourGpgUserIdOrKeyId &lt; %t &gt; %f"
++</screen>
++
++<emphasis role="bold">Note:</emphasis> the folder is temporary stored
++decrypted in the /tmp directory, where it can be read by your system
++administrator. So think about the security aspects of this.
++</para>
++</sect2>
++</sect1>
++
+ <chapter id="mimesupport">
+ <title>Mutt's MIME Support</title>
+
+diff --git a/doc/muttrc.man.head b/doc/muttrc.man.head
+index 30b96a2..b0ed18c 100644
+--- a/doc/muttrc.man.head
++++ b/doc/muttrc.man.head
+@@ -354,6 +354,24 @@ specify the ID of the public key to be used when encrypting messages
+ to a certain recipient. The meaning of "key ID" is to be taken
+ broadly: This can be a different e-mail address, a numerical key ID,
+ or even just an arbitrary search string.
++.PP
++.nf
++\fBopen-hook\fP \fIregexp\fP "\fIcommand\fP"
++\fBclose-hook\fP \fIregexp\fP "\fIcommand\fP"
++\fBappend-hook\fP \fIregexp\fP "\fIcommand\fP"
++.fi
++.IP
++These commands provide a way to handle compressed folders. The given
++\fBregexp\fP specifies which folders are taken as compressed (e.g.
++"\fI\\\\.gz$\fP"). The commands tell Mutt how to uncompress a folder
++(\fBopen-hook\fP), compress a folder (\fBclose-hook\fP) or append a
++compressed mail to a compressed folder (\fBappend-hook\fP). The
++\fIcommand\fP string is the
++.BR printf (3)
++like format string, and it should accept two parameters: \fB%f\fP,
++which is replaced with the (compressed) folder name, and \fB%t\fP
++which is replaced with the name of the temporary folder to which to
++write.
+ .TP
+ \fBpush\fP \fIstring\fP
+ This command adds the named \fIstring\fP to the keyboard buffer.
+diff --git a/hook.c b/hook.c
+index 34f3106..2a27419 100644
+--- a/hook.c
++++ b/hook.c
+@@ -24,6 +24,10 @@
+ #include "mailbox.h"
+ #include "mutt_crypt.h"
+
++#ifdef USE_COMPRESSED
++#include "compress.h"
++#endif
++
+ #include <limits.h>
+ #include <string.h>
+ #include <stdlib.h>
+@@ -92,6 +96,16 @@ int mutt_parse_hook (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err)
+ memset (&pattern, 0, sizeof (pattern));
+ pattern.data = safe_strdup (path);
+ }
++#ifdef USE_COMPRESSED
++ else if (data & (M_APPENDHOOK | M_OPENHOOK | M_CLOSEHOOK))
++ {
++ if (mutt_test_compress_command (command.data))
++ {
++ strfcpy (err->data, _("badly formatted command string"), err->dsize);
++ return (-1);
++ }
++ }
++#endif
+ else if (DefaultHook && !(data & (M_CHARSETHOOK | M_ICONVHOOK | M_ACCOUNTHOOK))
+ && (!WithCrypto || !(data & M_CRYPTHOOK))
+ )
+diff --git a/init.h b/init.h
+index a64992a..0e4f47f 100644
+--- a/init.h
++++ b/init.h
+@@ -3578,6 +3578,11 @@ const struct command_t Commands[] = {
+ { "fcc-hook", mutt_parse_hook, M_FCCHOOK },
+ { "fcc-save-hook", mutt_parse_hook, M_FCCHOOK | M_SAVEHOOK },
+ { "folder-hook", mutt_parse_hook, M_FOLDERHOOK },
++#ifdef USE_COMPRESSED
++ { "open-hook", mutt_parse_hook, M_OPENHOOK },
++ { "close-hook", mutt_parse_hook, M_CLOSEHOOK },
++ { "append-hook", mutt_parse_hook, M_APPENDHOOK },
++#endif
+ { "group", parse_group, M_GROUP },
+ { "ungroup", parse_group, M_UNGROUP },
+ { "hdr_order", parse_list, UL &HeaderOrderList },
+diff --git a/main.c b/main.c
+index 0ce245b..5ab1868 100644
+--- a/main.c
++++ b/main.c
+@@ -431,6 +431,12 @@ static void show_version (void)
+ #else
+ "-LOCALES_HACK "
+ #endif
++
++#ifdef USE_COMPRESSED
++ "+COMPRESSED "
++#else
++ "-COMPRESSED "
++#endif
+
+ #ifdef HAVE_WC_FUNCS
+ "+HAVE_WC_FUNCS "
+diff --git a/mbox.c b/mbox.c
+index 253061a..6d3b6bd 100644
+--- a/mbox.c
++++ b/mbox.c
+@@ -29,6 +29,10 @@
+ #include "copy.h"
+ #include "mutt_curses.h"
+
++#ifdef USE_COMPRESSED
++#include "compress.h"
++#endif
++
+ #include <sys/stat.h>
+ #include <dirent.h>
+ #include <string.h>
+@@ -1069,6 +1073,12 @@ bail: /* Come here in case of disaster */
+ int mbox_close_mailbox (CONTEXT *ctx)
+ {
+ mx_unlock_file (ctx->path, fileno (ctx->fp), 1);
++
++#ifdef USE_COMPRESSED
++ if (ctx->compressinfo)
++ mutt_slow_close_compressed (ctx);
++#endif
++
+ mutt_unblock_signals ();
+ mx_fastclose_mailbox (ctx);
+ return 0;
+diff --git a/mutt.h b/mutt.h
+index 8cee3d2..b71f071 100644
+--- a/mutt.h
++++ b/mutt.h
+@@ -144,6 +144,11 @@ typedef enum
+ #define M_ACCOUNTHOOK (1<<9)
+ #define M_REPLYHOOK (1<<10)
+ #define M_SEND2HOOK (1<<11)
++#ifdef USE_COMPRESSED
++#define M_OPENHOOK (1<<12)
++#define M_APPENDHOOK (1<<13)
++#define M_CLOSEHOOK (1<<14)
++#endif
+
+ /* tree characters for linearize_tree and print_enriched_string */
+ #define M_TREE_LLCORNER 1
+@@ -889,6 +894,11 @@ typedef struct _context
+ int flagged; /* how many flagged messages */
+ int msgnotreadyet; /* which msg "new" in pager, -1 if none */
+
++#ifdef USE_COMPRESSED
++ void *compressinfo; /* compressed mbox module private data */
++ char *realpath; /* path to compressed mailbox */
++#endif /* USE_COMPRESSED */
++
+ short magic; /* mailbox type */
+
+ unsigned char rights[(RIGHTSMAX + 7)/8]; /* ACL bits */
+diff --git a/mx.c b/mx.c
+index cc60517..07dba0c 100644
+--- a/mx.c
++++ b/mx.c
+@@ -30,6 +30,10 @@
+ #include "keymap.h"
+ #include "url.h"
+
++#ifdef USE_COMPRESSED
++#include "compress.h"
++#endif
++
+ #ifdef USE_IMAP
+ #include "imap.h"
+ #endif
+@@ -414,6 +418,10 @@ int mx_get_magic (const char *path)
+ return (-1);
+ }
+
++#ifdef USE_COMPRESSED
++ if (magic == 0 && mutt_can_read_compressed (path))
++ return M_COMPRESSED;
++#endif
+ return (magic);
+ }
+
+@@ -453,6 +461,13 @@ static int mx_open_mailbox_append (CONTEXT *ctx, int flags)
+ {
+ struct stat sb;
+
++#ifdef USE_COMPRESSED
++ /* special case for appending to compressed folders -
++ * even if we can not open them for reading */
++ if (mutt_can_append_compressed (ctx->path))
++ mutt_open_append_compressed (ctx);
++#endif
++
+ ctx->append = 1;
+
+ #ifdef USE_IMAP
+@@ -616,7 +631,12 @@ CONTEXT *mx_open_mailbox (const char *path, int flags, CONTEXT *pctx)
+ }
+
+ ctx->magic = mx_get_magic (path);
+-
++
++#ifdef USE_COMPRESSED
++ if (ctx->magic == M_COMPRESSED)
++ mutt_open_read_compressed (ctx);
++#endif
++
+ if(ctx->magic == 0)
+ mutt_error (_("%s is not a mailbox."), path);
+
+@@ -721,6 +741,10 @@ void mx_fastclose_mailbox (CONTEXT *ctx)
+ mutt_free_header (&ctx->hdrs[i]);
+ FREE (&ctx->hdrs);
+ FREE (&ctx->v2r);
++#ifdef USE_COMPRESSED
++ if (ctx->compressinfo)
++ mutt_fast_close_compressed (ctx);
++#endif
+ FREE (&ctx->path);
+ FREE (&ctx->pattern);
+ if (ctx->limit_pattern)
+@@ -773,6 +797,12 @@ static int sync_mailbox (CONTEXT *ctx, int *index_hint)
+
+ if (tmp && tmp->new == 0)
+ mutt_update_mailbox (tmp);
++
++#ifdef USE_COMPRESSED
++ if (rc == 0 && ctx->compressinfo)
++ return mutt_sync_compressed (ctx);
++#endif
++
+ return rc;
+ }
+
+@@ -1043,6 +1073,11 @@ int mx_close_mailbox (CONTEXT *ctx, int *index_hint)
+ !mutt_is_spool(ctx->path) && !option (OPTSAVEEMPTY))
+ mx_unlink_empty (ctx->path);
+
++#ifdef USE_COMPRESSED
++ if (ctx->compressinfo && mutt_slow_close_compressed (ctx))
++ return (-1);
++#endif
++
+ mx_fastclose_mailbox (ctx);
+
+ return 0;
+@@ -1361,6 +1396,11 @@ int mx_check_mailbox (CONTEXT *ctx, int *index_hint, int lock)
+ {
+ int rc;
+
++#ifdef USE_COMPRESSED
++ if (ctx->compressinfo)
++ return mutt_check_mailbox_compressed (ctx);
++#endif
++
+ if (ctx)
+ {
+ if (ctx->locked) lock = 0;
+diff --git a/mx.h b/mx.h
+index 4a00715..2ef4ec7 100644
+--- a/mx.h
++++ b/mx.h
+@@ -36,6 +36,9 @@ enum
+ M_MAILDIR,
+ M_IMAP,
+ M_POP
++#ifdef USE_COMPRESSED
++ , M_COMPRESSED
++#endif
+ };
+
+ WHERE short DefaultMagic INITVAL (M_MBOX);
+diff --git a/po/POTFILES.in b/po/POTFILES.in
+index 2d01add..3654ad1 100644
+--- a/po/POTFILES.in
++++ b/po/POTFILES.in
+@@ -8,6 +8,7 @@ charset.c
+ color.c
+ commands.c
+ compose.c
++compress.c
+ crypt-gpgme.c
+ crypt.c
+ cryptglue.c
+diff --git a/po/de.po b/po/de.po
+index dd2dda5..75d56f3 100644
+--- a/po/de.po
++++ b/po/de.po
+@@ -5178,6 +5178,36 @@ msgstr "Extrahiere unterst
+ msgid "show S/MIME options"
+ msgstr "Zeige S/MIME Optionen"
+
++
++#: compress.c:228 compress.c:253
++#, c-format
++msgid "Decompressing %s...\n"
++msgstr "Entpacke %s...\n"
++
++#: compress.c:350 compress.c:377 compress.c:423 compress.c:454
++#, c-format
++msgid "Compressing %s...\n"
++msgstr "Komprimiere %s...\n"
++
++#: compress.c:381
++#, c-format
++msgid ""
++"%s: Error compressing mailbox! Original mailbox deleted, uncompressed one "
++"kept!\n"
++msgstr ""
++"%s: Fehler beim Komprimieren der Mailbox! Ursprüngliche Mailbox gelöscht, "
++"entpackte gespeichert!\n"
++
++#: compress.c:425 compress.c:456
++#, c-format
++msgid "Compressed-appending to %s...\n"
++msgstr "Hänge komprimiert an %s... an\n"
++
++#: compress.c:461
++#, c-format
++msgid " %s: Error compressing mailbox! Uncompressed one kept!\n"
++msgstr " %s: Fehler beim packen der Mailbox! Entpackte Mailbox gespeichert!\n"
++
+ #~ msgid "Clear"
+ #~ msgstr "Klartext"
+
+diff --git a/status.c b/status.c
+index 6051e3a..e8693c8 100644
+--- a/status.c
++++ b/status.c
+@@ -96,6 +96,14 @@ status_format_str (char *buf, size_t buflen, size_t col, char op, const char *sr
+
+ case 'f':
+ snprintf (fmt, sizeof(fmt), "%%%ss", prefix);
++#ifdef USE_COMPRESSED
++ if (Context && Context->compressinfo && Context->realpath)
++ {
++ strfcpy (tmp, Context->realpath, sizeof (tmp));
++ mutt_pretty_mailbox (tmp, sizeof (tmp));
++ }
++ else
++#endif
+ if (Context && Context->path)
+ {
+ strfcpy (tmp, Context->path, sizeof (tmp));
diff --git a/correct_docdir_in_man_page.patch b/correct_docdir_in_man_page.patch
new file mode 100644
index 000000000000..2e7173190c5a
--- /dev/null
+++ b/correct_docdir_in_man_page.patch
@@ -0,0 +1,25 @@
+From: Antonio Radici <antonio@debian.org>
+Date: Thu, 27 Feb 2014 16:08:26 +0100
+Subject: correct_docdir_in_man_page
+
+Fix the link so it points to the correct docdir
+and gzipped manual.
+
+Gbp-Pq: Topic debian-specific
+---
+ doc/mutt.man | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/doc/mutt.man b/doc/mutt.man
+index 181ef62..718f87a 100644
+--- a/doc/mutt.man
++++ b/doc/mutt.man
+@@ -185,7 +185,7 @@ User's personal mapping between MIME types and file extensions.
+ System mapping between MIME types and file extensions.
+ .IP "@bindir@/mutt_dotlock"
+ The privileged dotlocking program.
+-.IP "@docdir@/manual.txt"
++.IP "/usr/share/doc/mutt/manual.txt.gz"
+ The Mutt manual.
+ .SH BUGS
+ .PP
diff --git a/define-pgp_getkeys_command.patch b/define-pgp_getkeys_command.patch
new file mode 100644
index 000000000000..cfead7a18228
--- /dev/null
+++ b/define-pgp_getkeys_command.patch
@@ -0,0 +1,24 @@
+From: Antonio Radici <antonio@debian.org>
+Date: Thu, 27 Feb 2014 16:27:19 +0100
+Subject: define-pgp_getkeys_command
+
+Disabling pgp_getkeys_command until upstream fixes it.
+
+Gbp-Pq: Topic misc
+---
+ contrib/gpg.rc | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/contrib/gpg.rc b/contrib/gpg.rc
+index d6c9def..7511f3c 100644
+--- a/contrib/gpg.rc
++++ b/contrib/gpg.rc
+@@ -72,6 +72,8 @@ set pgp_list_secring_command="gpg --no-verbose --batch --quiet --with-colons --l
+
+ # fetch keys
+ # set pgp_getkeys_command="pkspxycwrap %r"
++# This will work when #172960 will be fixed upstream
++# set pgp_getkeys_command="gpg --recv-keys %r"
+
+ # pattern for good signature - may need to be adapted to locale!
+
diff --git a/document_debian_defaults.patch b/document_debian_defaults.patch
new file mode 100644
index 000000000000..c7bb94ed6871
--- /dev/null
+++ b/document_debian_defaults.patch
@@ -0,0 +1,87 @@
+From: Antonio Radici <antonio@debian.org>
+Date: Thu, 27 Feb 2014 16:19:30 +0100
+Subject: document_debian_defaults
+
+Some customization of the option which are straying
+from the default only on Debian systems.
+
+Gbp-Pq: Topic debian-specific
+---
+ init.h | 22 ++++++++++++++++++++++
+ 1 file changed, 22 insertions(+)
+
+diff --git a/init.h b/init.h
+index dc69e2c..7d319ad 100644
+--- a/init.h
++++ b/init.h
+@@ -312,6 +312,9 @@ struct option_t MuttVars[] = {
+ ** .pp
+ ** When this variable is \fIset\fP, mutt will include Delivered-To headers when
+ ** bouncing messages. Postfix users may wish to \fIunset\fP this variable.
++ ** .pp
++ ** \fBNote:\fP On Debian systems, this option is unset by default in
++ ** /etc/Muttrc.
+ */
+ { "braille_friendly", DT_BOOL, R_NONE, OPTBRAILLEFRIENDLY, 0 },
+ /*
+@@ -1044,6 +1047,9 @@ struct option_t MuttVars[] = {
+ ** one is not used.
+ ** .pp
+ ** Also see $$use_domain and $$hidden_host.
++ ** .pp
++ ** \fBNote:\fP On Debian systems, the default for this variable is obtained
++ ** from /etc/mailname when Mutt starts.
+ */
+ { "ignore_linear_white_space", DT_BOOL, R_NONE, OPTIGNORELWS, 0 },
+ /*
+@@ -1577,6 +1583,9 @@ struct option_t MuttVars[] = {
+ ** system. It is used with various sets of parameters to gather the
+ ** list of known remailers, and to finally send a message through the
+ ** mixmaster chain.
++ ** .pp
++ ** \fBNote:\fP On Debian systems, this option is set by default to
++ ** ``mixmaster-filter'' in /etc/Muttrc.
+ */
+ #endif
+ { "move", DT_QUAD, R_NONE, OPT_MOVE, M_NO },
+@@ -2619,6 +2628,10 @@ struct option_t MuttVars[] = {
+ ** This variable contains the name of either a directory, or a file which
+ ** contains trusted certificates for use with OpenSSL.
+ ** (S/MIME only)
++ ** .pp
++ ** \fBNote:\fP On Debian systems, this defaults to the first existing file in
++ ** the following list: ~/.smime/ca-certificates.crt ~/.smime/ca-bundle.crt
++ ** /etc/ssl/certs/ca-certificates.crt.
+ */
+ { "smime_certificates", DT_PATH, R_NONE, UL &SmimeCertificates, 0 },
+ /*
+@@ -2825,6 +2838,9 @@ struct option_t MuttVars[] = {
+ ** .ts
+ ** set smtp_authenticators="digest-md5:cram-md5"
+ ** .te
++ ** .pp
++ ** \fBNote:\fP On Debian systems, this variable defaults to the example
++ ** mentioned. This file is managed by the ``ca-certificates'' package.
+ */
+ # endif /* USE_SASL */
+ { "smtp_pass", DT_STR, R_NONE, UL &SmtpPass, UL 0 },
+@@ -2872,6 +2888,9 @@ struct option_t MuttVars[] = {
+ ** .pp
+ ** You may optionally use the ``reverse-'' prefix to specify reverse sorting
+ ** order (example: ``\fCset sort=reverse-date-sent\fP'').
++ ** .pp
++ ** \fBNote:\fP On Debian systems, this option is set by default to
++ ** ``threads'' in /etc/Muttrc.
+ */
+ { "sort_alias", DT_SORT|DT_SORT_ALIAS, R_NONE, UL &SortAlias, SORT_ALIAS },
+ /*
+@@ -3423,6 +3442,9 @@ struct option_t MuttVars[] = {
+ ** is set to deliver directly via SMTP (see $$smtp_url), this
+ ** option does nothing: mutt will never write out the ``Bcc:'' header
+ ** in this case.
++ ** .pp
++ ** \fBNote:\fP On Debian systems, exim4 and postfix strip BCC headers by
++ ** default. The above warning applies to exim3 users, see /etc/Muttrc.
+ */
+ { "write_inc", DT_NUM, R_NONE, UL &WriteInc, 10 },
+ /*
diff --git a/dont_document_not_present_features.patch b/dont_document_not_present_features.patch
new file mode 100644
index 000000000000..bdc30f272043
--- /dev/null
+++ b/dont_document_not_present_features.patch
@@ -0,0 +1,39 @@
+From: Antonio Radici <antonio@debian.org>
+Date: Thu, 27 Feb 2014 16:17:42 +0100
+Subject: dont_document_not_present_features
+
+As the patch says, this will add an include so only
+documented options for Debian will be used.
+
+Gbp-Pq: Topic debian-specific
+---
+ functions.h | 2 +-
+ init.h | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/functions.h b/functions.h
+index a29388c..26171a0 100644
+--- a/functions.h
++++ b/functions.h
+@@ -35,7 +35,7 @@
+
+ #ifdef _MAKEDOC
+ # include "config.h"
+-# include "doc/makedoc-defs.h"
++/* # include "doc/makedoc-defs.h" include only documented options for Debian */
+ #endif
+
+ const struct binding_t OpGeneric[] = { /* map: generic */
+diff --git a/init.h b/init.h
+index 7768683..dc69e2c 100644
+--- a/init.h
++++ b/init.h
+@@ -19,7 +19,7 @@
+
+ #ifdef _MAKEDOC
+ # include "config.h"
+-# include "doc/makedoc-defs.h"
++/* # include "makedoc-defs.h" include only documented options for Debian */
+ #else
+ # include "sort.h"
+ #endif
diff --git a/fix-configure-test-operator.patch b/fix-configure-test-operator.patch
new file mode 100644
index 000000000000..41ef5e51c5f4
--- /dev/null
+++ b/fix-configure-test-operator.patch
@@ -0,0 +1,22 @@
+From: Evgeni Golov <evgeni@debian.org>
+Date: Wed, 5 Mar 2014 08:11:47 +0100
+Subject: fix-configure-test-operator
+
+Gbp-Pq: Topic misc
+---
+ configure.ac | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/configure.ac b/configure.ac
+index e7ebe01..2d57b76 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -1187,7 +1187,7 @@ if test "x$with_idn" != "xno"; then
+ ])
+
+ if test "$with_idn" != auto; then
+- if test $have_stringprep_h = no || test $have_idna_h = no || test $ac_cv_search_STRINGPREP_CHECK_VERSION = no; then
++ if test "$have_stringprep_h" = no || test "$have_idna_h" = no || test "$ac_cv_search_STRINGPREP_CHECK_VERSION" = no; then
+ AC_MSG_ERROR([IDN was requested, but libidn was not usable on this system])
+ fi
+ fi
diff --git a/gpg.rc-paths.patch b/gpg.rc-paths.patch
new file mode 100644
index 000000000000..e6d21fc32854
--- /dev/null
+++ b/gpg.rc-paths.patch
@@ -0,0 +1,29 @@
+From: Antonio Radici <antonio@debian.org>
+Date: Thu, 27 Feb 2014 16:30:20 +0100
+Subject: gpg.rc-paths
+
+Use the correct path of pgpewrap.
+
+Gbp-Pq: Topic misc
+---
+ contrib/gpg.rc | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/contrib/gpg.rc b/contrib/gpg.rc
+index 7511f3c..27a76b9 100644
+--- a/contrib/gpg.rc
++++ b/contrib/gpg.rc
+@@ -49,11 +49,11 @@ set pgp_clearsign_command="gpg --no-verbose --batch --quiet --output - %?p?--pas
+
+ # create a pgp/mime encrypted attachment
+ # set pgp_encrypt_only_command="pgpewrap gpg-2comp -v --batch --output - --encrypt --textmode --armor --always-trust -- -r %r -- %f"
+-set pgp_encrypt_only_command="pgpewrap gpg --batch --quiet --no-verbose --output - --encrypt --textmode --armor --always-trust -- -r %r -- %f"
++set pgp_encrypt_only_command="/usr/lib/mutt/pgpewrap gpg --batch --quiet --no-verbose --output - --encrypt --textmode --armor --always-trust -- -r %r -- %f"
+
+ # create a pgp/mime encrypted and signed attachment
+ # set pgp_encrypt_sign_command="pgpewrap gpg-2comp %?p?--passphrase-fd 0? -v --batch --output - --encrypt --sign %?a?-u %a? --armor --always-trust -- -r %r -- %f"
+-set pgp_encrypt_sign_command="pgpewrap gpg %?p?--passphrase-fd 0? --batch --quiet --no-verbose --textmode --output - --encrypt --sign %?a?-u %a? --armor --always-trust -- -r %r -- %f"
++set pgp_encrypt_sign_command="/usr/lib/mutt/pgpewrap gpg %?p?--passphrase-fd 0? --batch --quiet --no-verbose --textmode --output - --encrypt --sign %?a?-u %a? --armor --always-trust -- -r %r -- %f"
+
+ # import a key into the public key ring
+ set pgp_import_command="gpg --no-verbose --import %f"
diff --git a/ifdef.patch b/ifdef.patch
new file mode 100644
index 000000000000..5527d3b100f1
--- /dev/null
+++ b/ifdef.patch
@@ -0,0 +1,160 @@
+From: Cedric Duval <cedricduval@free.fr>
+Date: Thu, 27 Feb 2014 12:06:21 +0100
+Subject: ifdef
+
+This command allows to test if a feature has been compiled in before
+actually attempting to configure / use it.
+
+Syntax:
+
+ifdef <item> <command>
+
+where <item> can be the name of a variable, function, or command.
+
+Examples:
+
+ifdef imap-fetch-mail 'source ~/.mutt/imap_setup'
+ifdef trash set trash=~/Mail/trash
+
+* Patch last synced with upstream:
+ - Date: 2007-02-15
+ - File:
+ http://cedricduval.free.fr/mutt/patches/download/patch-1.5.4.cd.ifdef.1
+
+* Changes made:
+ - Updated to 1.5.13
+ - Also look for commands
+ - Use mutt_strcmp in favor of ascii_strncasecmp to compare strings.
+
+Signed-off-by: Matteo F. Vescovi <mfvescovi@gmail.com>
+
+Gbp-Pq: Topic features
+---
+ doc/manual.xml.head | 22 ++++++++++++++++++++
+ init.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++
+ init.h | 2 ++
+ 3 files changed, 83 insertions(+)
+
+diff --git a/doc/manual.xml.head b/doc/manual.xml.head
+index 0093685..18ae918 100644
+--- a/doc/manual.xml.head
++++ b/doc/manual.xml.head
+@@ -4378,6 +4378,28 @@ from which to read input (e.g. <literal><command>source</command>
+
+ </sect1>
+
++<sect1 id="ifdef">
++
++<title>Configuring features conditionally</title>
++
++<para>
++Usage: <literal>ifdef</literal> <emphasis>item</emphasis> <emphasis>command</emphasis>
++</para>
++
++<para>
++This command allows to test if a feature has been compiled in, before
++actually executing the command. Item can be either the name of a
++function, variable, or command. Example:
++</para>
++
++<para>
++<screen>
++ifdef imap_keepalive 'source ~/.mutt/imap_setup'
++</screen>
++</para>
++
++</sect1>
++
+ <sect1 id="unhook">
+ <title>Removing Hooks</title>
+
+diff --git a/init.c b/init.c
+index 4897b9e..cc3cf4b 100644
+--- a/init.c
++++ b/init.c
+@@ -601,6 +601,65 @@ static void remove_from_list (LIST **l, const char *str)
+ }
+ }
+
++static int parse_ifdef (BUFFER *tmp, BUFFER *s, unsigned long data, BUFFER *err)
++{
++ int i, j, res = 0;
++ BUFFER token;
++
++ memset (&token, 0, sizeof (token));
++ mutt_extract_token (tmp, s, 0);
++
++ /* is the item defined as a variable? */
++ res = (mutt_option_index (tmp->data) != -1);
++
++ /* a function? */
++ if (!res)
++ for (i = 0; !res && i < MENU_MAX; i++)
++ {
++ struct binding_t *b = km_get_table (Menus[i].value);
++
++ if (!b)
++ continue;
++
++ for (j = 0; b[j].name; j++)
++ if (!mutt_strcmp (tmp->data, b[j].name))
++ {
++ res = 1;
++ break;
++ }
++ }
++
++ /* a command? */
++ if (!res)
++ for (i = 0; Commands[i].name; i++)
++ {
++ if (!mutt_strcmp (tmp->data, Commands[i].name))
++ {
++ res = 1;
++ break;
++ }
++ }
++
++ if (!MoreArgs (s))
++ {
++ snprintf (err->data, err->dsize, _("ifdef: too few arguments"));
++ return (-1);
++ }
++ mutt_extract_token (tmp, s, M_TOKEN_SPACE);
++
++ if (res)
++ {
++ if (mutt_parse_rc_line (tmp->data, &token, err) == -1)
++ {
++ mutt_error ("Erreur: %s", err->data);
++ FREE (&token.data);
++ return (-1);
++ }
++ FREE (&token.data);
++ }
++ return 0;
++}
++
+ static int parse_unignore (BUFFER *buf, BUFFER *s, unsigned long data, BUFFER *err)
+ {
+ do
+diff --git a/init.h b/init.h
+index 395cd3f..80d05ce 100644
+--- a/init.h
++++ b/init.h
+@@ -3487,6 +3487,7 @@ static int parse_lists (BUFFER *, BUFFER *, unsigned long, BUFFER *);
+ static int parse_unlists (BUFFER *, BUFFER *, unsigned long, BUFFER *);
+ static int parse_alias (BUFFER *, BUFFER *, unsigned long, BUFFER *);
+ static int parse_unalias (BUFFER *, BUFFER *, unsigned long, BUFFER *);
++static int parse_ifdef (BUFFER *, BUFFER *, unsigned long, BUFFER *);
+ static int parse_ignore (BUFFER *, BUFFER *, unsigned long, BUFFER *);
+ static int parse_unignore (BUFFER *, BUFFER *, unsigned long, BUFFER *);
+ static int parse_source (BUFFER *, BUFFER *, unsigned long, BUFFER *);
+@@ -3537,6 +3538,7 @@ const struct command_t Commands[] = {
+ { "group", parse_group, M_GROUP },
+ { "ungroup", parse_group, M_UNGROUP },
+ { "hdr_order", parse_list, UL &HeaderOrderList },
++ { "ifdef", parse_ifdef, 0 },
+ #ifdef HAVE_ICONV
+ { "iconv-hook", mutt_parse_hook, M_ICONVHOOK },
+ #endif
diff --git a/imap_fast_trash.patch b/imap_fast_trash.patch
new file mode 100644
index 000000000000..f8f97b3b93a7
--- /dev/null
+++ b/imap_fast_trash.patch
@@ -0,0 +1,114 @@
+From: Antonio Radici <antonio@debian.org>
+Date: Thu, 27 Feb 2014 14:29:03 +0100
+Subject: imap_fast_trash
+
+Make "move to trash folder" use IMAP COPY.
+By Paul Miller
+
+Gbp-Pq: Topic features
+---
+ imap/imap.c | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ imap/imap.h | 3 +++
+ mx.c | 5 +++++
+ 3 files changed, 65 insertions(+)
+
+diff --git a/imap/imap.c b/imap/imap.c
+index 93dc06a..393d4ec 100644
+--- a/imap/imap.c
++++ b/imap/imap.c
+@@ -886,6 +886,12 @@ static int imap_make_msg_set (IMAP_DATA* idata, BUFFER* buf, int flag,
+ if (hdrs[n]->deleted != HEADER_DATA(hdrs[n])->deleted)
+ match = invert ^ hdrs[n]->deleted;
+ break;
++ case M_EXPIRED: /* imap_fast_trash version of M_DELETED */
++ if (hdrs[n]->purged)
++ break;
++ if (hdrs[n]->deleted != HEADER_DATA(hdrs[n])->deleted)
++ match = invert ^ (hdrs[n]->deleted && !hdrs[n]->appended);
++ break;
+ case M_FLAG:
+ if (hdrs[n]->flagged != HEADER_DATA(hdrs[n])->flagged)
+ match = invert ^ hdrs[n]->flagged;
+@@ -2017,3 +2023,54 @@ int imap_complete(char* dest, size_t dlen, char* path) {
+
+ return -1;
+ }
++
++int imap_fast_trash() {
++
++ if( Context->magic == M_IMAP && mx_is_imap(TrashPath) ) {
++ IMAP_MBOX mx;
++ IMAP_DATA *idata = (IMAP_DATA *) Context->data;
++ char mbox[LONG_STRING];
++ char mmbox[LONG_STRING];
++ int rc;
++ dprint(1, (debugfile, "[itf] trashcan seems to be on imap.\n"));
++
++ if ( imap_parse_path(TrashPath, &mx) == 0 ) {
++ if( mutt_account_match(&(idata->conn->account), &(mx.account)) ) {
++ dprint(1, (debugfile, "[itf] trashcan seems to be on the same account.\n"));
++
++ imap_fix_path (idata, mx.mbox, mbox, sizeof (mbox));
++ if (!*mbox)
++ strfcpy (mbox, "INBOX", sizeof (mbox));
++ imap_munge_mbox_name (mmbox, sizeof (mmbox), mbox);
++
++ rc = imap_exec_msgset (idata, "UID COPY", mmbox, M_EXPIRED, 0, 0);
++ if (!rc) {
++ dprint (1, (debugfile, "imap_copy_messages: No messages del-tagged\n"));
++ rc = -1;
++ goto old_way;
++
++ } else if (rc < 0) {
++ dprint (1, (debugfile, "could not queue copy\n"));
++ goto old_way;
++
++ } else {
++ mutt_message (_("Copying %d messages to %s..."), rc, mbox);
++ return 0;
++ }
++
++ } else {
++ dprint(1, (debugfile, "[itf] trashcan seems to be on a different account.\n"));
++ }
++
++ old_way:
++ FREE (&mx.mbox); /* we probably only need to free this when the parse works */
++
++ } else {
++ dprint(1, (debugfile, "[itf] failed to parse TrashPath.\n" ));
++ }
++
++ dprint(1, (debugfile, "[itf] giving up and trying old fasioned way.\n" ));
++ }
++
++ return 1;
++}
+diff --git a/imap/imap.h b/imap/imap.h
+index 74d7e13..99cd454 100644
+--- a/imap/imap.h
++++ b/imap/imap.h
+@@ -72,4 +72,7 @@ void imap_keepalive (void);
+
+ int imap_account_match (const ACCOUNT* a1, const ACCOUNT* a2);
+
++/* trash */
++int imap_fast_trash();
++
+ #endif
+diff --git a/mx.c b/mx.c
+index 89b9431..cc60517 100644
+--- a/mx.c
++++ b/mx.c
+@@ -802,6 +802,11 @@ static int trash_append (CONTEXT *ctx)
+ && stc.st_dev == st.st_dev && stc.st_rdev == st.st_rdev)
+ return 0; /* we are in the trash folder: simple sync */
+
++ #ifdef USE_IMAP
++ if( !imap_fast_trash() )
++ return 0;
++ #endif
++
+ if ((ctx_trash = mx_open_mailbox (TrashPath, M_APPEND, NULL)) != NULL)
+ {
+ for (i = 0 ; i < ctx->msgcount ; i++)
diff --git a/install b/install
new file mode 100644
index 000000000000..d65675c06f64
--- /dev/null
+++ b/install
@@ -0,0 +1,8 @@
+post_install() {
+ cat <<EOF
+
+==> For GPG support, add the following to your muttrc:
+==> source /etc/Muttrc.gpg.dist
+
+EOF
+}
diff --git a/multiple-fcc.patch b/multiple-fcc.patch
new file mode 100644
index 000000000000..f3edff230bd7
--- /dev/null
+++ b/multiple-fcc.patch
@@ -0,0 +1,83 @@
+From: Antonio Radici <antonio@debian.org>
+Date: Tue, 4 Mar 2014 15:40:45 +0100
+Subject: multiple-fcc
+
+A patch that allows multiple FCC separated by commas,
+written by Omen Wild.
+
+Original website: http://www.mandarb.com/mutt/
+Bug asking for the inclusion: #586454
+
+Gbp-Pq: Topic mutt-patched
+---
+ protos.h | 1 +
+ send.c | 2 +-
+ sendlib.c | 30 ++++++++++++++++++++++++++++++
+ 3 files changed, 32 insertions(+), 1 deletion(-)
+
+diff --git a/protos.h b/protos.h
+index 38c8c01..551b142 100644
+--- a/protos.h
++++ b/protos.h
+@@ -362,6 +362,7 @@ int mutt_user_is_recipient (HEADER *);
+ void mutt_update_num_postponed (void);
+ int mutt_wait_filter (pid_t);
+ int mutt_which_case (const char *);
++int mutt_write_multiple_fcc (const char *path, HEADER *hdr, const char *msgid, int, char *);
+ int mutt_write_fcc (const char *path, HEADER *hdr, const char *msgid, int, char *);
+ int mutt_write_mime_body (BODY *, FILE *);
+ int mutt_write_mime_header (BODY *, FILE *);
+diff --git a/send.c b/send.c
+index 18b9390..893c859 100644
+--- a/send.c
++++ b/send.c
+@@ -1747,7 +1747,7 @@ full_fcc:
+ * message was first postponed.
+ */
+ msg->received = time (NULL);
+- if (mutt_write_fcc (fcc, msg, NULL, 0, NULL) == -1)
++ if (mutt_write_multiple_fcc (fcc, msg, NULL, 0, NULL) == -1)
+ {
+ /*
+ * Error writing FCC, we should abort sending.
+diff --git a/sendlib.c b/sendlib.c
+index 0f05298..9d83401 100644
+--- a/sendlib.c
++++ b/sendlib.c
+@@ -2680,6 +2680,36 @@ static void set_noconv_flags (BODY *b, short flag)
+ }
+ }
+
++/* Handle a Fcc with multiple, comma separated entries. */
++int mutt_write_multiple_fcc (const char *path, HEADER *hdr, const char *msgid, int post, char *fcc) {
++ char fcc_tok[_POSIX_PATH_MAX];
++ char fcc_expanded[_POSIX_PATH_MAX];
++ char *tok = NULL;
++ int status;
++
++ strfcpy(fcc_tok, path, _POSIX_PATH_MAX);
++
++ tok = strtok(fcc_tok, ",");
++ dprint(1, (debugfile, "Fcc: initial mailbox = '%s'\n", tok));
++ /* mutt_expand_path already called above for the first token */
++ if((status = mutt_write_fcc (tok, hdr, NULL, 0, NULL)) != 0)
++ return status;
++
++ while((tok = strtok(NULL, ",")) != NULL) {
++ if(*tok) {
++ /* Only call mutt_expand_path iff tok has some data */
++ dprint(1, (debugfile, "Fcc: additional mailbox token = '%s'\n", tok));
++ strfcpy(fcc_expanded, tok, sizeof(fcc_expanded));
++ mutt_expand_path(fcc_expanded, sizeof(fcc_expanded));
++ dprint(1, (debugfile, " Additional mailbox expanded = '%s'\n", fcc_expanded));
++ if((status = mutt_write_fcc (fcc_expanded, hdr, NULL, 0, NULL)) != 0)
++ return status;
++ }
++ }
++
++ return 0;
++}
++
+ int mutt_write_fcc (const char *path, HEADER *hdr, const char *msgid, int post, char *fcc)
+ {
+ CONTEXT f;
diff --git a/nntp.patch b/nntp.patch
new file mode 100644
index 000000000000..0024aa8d5e9e
--- /dev/null
+++ b/nntp.patch
@@ -0,0 +1,8748 @@
+From: Vsevolod Volkov <vvv@mutt.org.ua>
+Date: Sun, 16 Mar 2014 15:54:12 +0100
+Subject: nntp
+
+http://mutt.org.ua/download/mutt-1.5.23/patch-1.5.23.vvv.nntp.gz
+
+Signed-off-by: Matteo F. Vescovi <mfvescovi@gmail.com>
+
+Gbp-Pq: Topic mutt-patched
+---
+ ChangeLog.nntp | 399 +++++++++
+ Makefile.am | 2 +
+ OPS | 23 +-
+ account.c | 27 +
+ account.h | 3 +-
+ attach.h | 2 +-
+ browser.c | 470 +++++++++-
+ browser.h | 7 +
+ buffy.c | 12 +
+ complete.c | 64 ++
+ compose.c | 177 +++-
+ configure.ac | 11 +-
+ curs_main.c | 312 ++++++-
+ doc/Muttrc | 300 +++++++
+ doc/manual.xml.head | 20 +
+ doc/mutt.man | 8 +-
+ functions.h | 59 +-
+ globals.h | 16 +
+ hash.c | 27 +
+ hash.h | 3 +-
+ hcache.c | 12 +
+ hdrline.c | 25 +
+ headers.c | 3 +
+ init.c | 22 +
+ init.h | 217 +++++
+ keymap.c | 1 -
+ mailbox.h | 3 +
+ main.c | 54 ++
+ mutt.h | 39 +-
+ mutt_sasl.c | 5 +
+ muttlib.c | 17 +-
+ mx.c | 75 ++
+ mx.h | 3 +
+ newsrc.c | 1260 +++++++++++++++++++++++++++
+ nntp.c | 2404 +++++++++++++++++++++++++++++++++++++++++++++++++++
+ nntp.h | 167 ++++
+ pager.c | 81 +-
+ parse.c | 48 +-
+ pattern.c | 8 +
+ po/POTFILES.in | 2 +
+ postpone.c | 11 +
+ protos.h | 1 +
+ recvattach.c | 34 +-
+ recvcmd.c | 61 +-
+ send.c | 163 +++-
+ sendlib.c | 78 +-
+ sort.c | 16 +
+ url.c | 4 +-
+ url.h | 2 +
+ 49 files changed, 6674 insertions(+), 84 deletions(-)
+ create mode 100644 ChangeLog.nntp
+ create mode 100644 newsrc.c
+ create mode 100644 nntp.c
+ create mode 100644 nntp.h
+
+diff --git a/ChangeLog.nntp b/ChangeLog.nntp
+new file mode 100644
+index 0000000..70d1126
+--- /dev/null
++++ b/ChangeLog.nntp
+@@ -0,0 +1,399 @@
++* Thu Mar 13 2014 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.5.23
++
++* Tue Oct 29 2013 Vsevolod Volkov <vvv@mutt.org.ua>
++- minor bug fixed while removing new articles
++
++* Fri Oct 18 2013 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.5.22
++
++* Tue Nov 27 2012 Vsevolod Volkov <vvv@mutt.org.ua>
++- SASL authentication
++- new option nntp_authenticators
++
++* Fri Nov 16 2012 Vsevolod Volkov <vvv@mutt.org.ua>
++- support of NNTP commands: CAPABILITIES, STARTTLS, LIST NEWSGROUPS,
++ LIST OVERVIEW.FMT, OVER, DATE
++- added bcache support
++- newss URI scheme renamed to snews
++- removed option nntp_reconnect
++
++* Sun Sep 16 2012 Vsevolod Volkov <vvv@mutt.org.ua>
++- internal header caching replaced with hcache
++- new option newsgroups_charset
++
++* Wed Sep 16 2010 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.5.21
++
++* Thu Aug 13 2009 Vsevolod Volkov <vvv@mutt.org.ua>
++- fixed writting references in nntp_save_cache_group()
++
++* Tue Jun 15 2009 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.5.20
++
++* Tue Mar 20 2009 Vsevolod Volkov <vvv@mutt.org.ua>
++- save Date: header of recorded outgoing articles
++
++* Tue Jan 6 2009 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.5.19
++
++* Mon May 19 2008 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.5.18
++- fixed SIGSEGV when followup or forward to newsgroup
++
++* Sun Nov 4 2007 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.5.17
++
++* Tue Jul 3 2007 Vsevolod Volkov <vvv@mutt.org.ua>
++- fixed arguments of nntp_format_str()
++
++* Fri Jun 15 2007 Vsevolod Volkov <vvv@mutt.org.ua>
++- fixed error selecting news group
++
++* Tue Jun 12 2007 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.5.16
++
++* Wed Apr 11 2007 Vsevolod Volkov <vvv@mutt.org.ua>
++- fixed posting error if $smtp_url is set
++- added support of print-style sequence %R (x-comment-to)
++
++* Sun Apr 8 2007 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.5.15
++- nntp://... url changed to news://...
++- added indicator of fetching descriptions progress
++
++* Tue Feb 28 2007 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.5.14
++
++* Tue Aug 15 2006 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.5.13
++
++* Mon Jul 17 2006 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.5.12
++- fixed reading empty .newsrc
++
++* Sat Sep 17 2005 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.5.11
++
++* Sat Aug 13 2005 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.5.10
++
++* Sun Mar 13 2005 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.5.9
++
++* Sun Feb 13 2005 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.5.8
++
++* Sat Feb 5 2005 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.5.7
++- function mutt_update_list_file() moved to newsrc.c and changed algorithm
++
++* Thu Jul 8 2004 Vsevolod Volkov <vvv@mutt.org.ua>
++- fixed error in nntp_logout_all()
++
++* Sat Apr 3 2004 Vsevolod Volkov <vvv@mutt.org.ua>
++- fixed debug output in mutt_newsrc_update()
++- added optional support of LISTGROUP command
++- fixed typo in nntp_parse_xref()
++
++* Tue Feb 3 2004 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.5.6
++
++* Thu Dec 18 2003 Vsevolod Volkov <vvv@mutt.org.ua>
++- fixed compose menu
++
++* Thu Nov 6 2003 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.5.5.1
++
++* Wed Nov 5 2003 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.5.5
++- added space after newsgroup name in .newsrc file
++
++* Sun May 18 2003 Vsevolod Volkov <vvv@mutt.org.ua>
++- nntp patch: fixed SIGSEGV when posting article
++
++* Sat Mar 22 2003 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.5.4
++
++* Sat Dec 21 2002 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.5.3
++- replace safe_free calls by the FREE macro
++
++* Fri Dec 6 2002 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.5.2
++- nntp authentication can be passed after any command
++
++* Sat May 4 2002 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.5.1
++
++* Thu May 2 2002 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.3.99
++
++* Wed Mar 13 2002 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.3.28
++- fixed SIGSEGV in <get-message>, <get-parent>, <get-children>,
++ <reconstruct-thread> functions
++- fixed message about nntp reconnect
++- fixed <attach-news-message> function using browser
++- added support of Followup-To: poster
++- added %n (new articles) in group_index_format
++- posting articles without inews by default
++
++* Wed Jan 23 2002 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.3.27
++
++* Fri Jan 18 2002 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.3.26
++
++* Thu Jan 3 2002 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.3.25
++- accelerated speed of access to news->newsgroups hash (by <gul@gul.kiev.ua>)
++- added default content disposition
++
++* Mon Dec 3 2001 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.3.24
++
++* Fri Nov 9 2001 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.3.23.2
++- fixed segfault if mutt_conn_find() returns null
++
++* Wed Oct 31 2001 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.3.23.1
++- added support of LISTGROUP command
++- added support for servers with broken overview
++- disabled <flag-message> function on news server
++- fixed error storing bad authentication information
++
++* Wed Oct 10 2001 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.3.23
++- fixed typo in buffy.c
++- added substitution of %s parameter in $inews variable
++
++* Fri Aug 31 2001 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.3.22.1
++- update to 1.3.22
++
++* Thu Aug 23 2001 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.3.21
++
++* Wed Jul 25 2001 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.3.20
++- removed 'server-hook', use 'account-hook' instead
++- fixed error opening NNTP server without newsgroup using -f option
++
++* Fri Jun 8 2001 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.3.19
++
++* Sat May 5 2001 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.3.18
++- fixed typo in nntp_attempt_features()
++- changed algorithm of XGTITLE command testing
++- disabled writing of NNTP password in debug file
++- fixed reading and writing of long newsrc lines
++- changed checking of last line while reading lines from server
++- fixed possible buffer overrun in nntp_parse_newsrc_line()
++- removed checking of XHDR command
++- compare NNTP return codes without trailing space
++
++* Thu Mar 29 2001 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.3.17
++- support for 'LIST NEWSGROUPS' command to read descriptions
++
++* Fri Mar 2 2001 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.3.16
++
++* Wed Feb 14 2001 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.3.15
++
++* Sun Jan 28 2001 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.3.14
++- show number of tagged messages patch from Felix von Leitner <leitner@fefe.de>
++
++* Sun Dec 31 2000 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.3.13
++
++* Sat Dec 30 2000 Vsevolod Volkov <vvv@mutt.org.ua>
++- Fixed problem if last article in group is deleted
++
++* Fri Dec 22 2000 Vsevolod Volkov <vvv@mutt.org.ua>
++- Fixed checking of XGTITLE command on some servers
++
++* Mon Dec 18 2000 Vsevolod Volkov <vvv@mutt.org.ua>
++- Added \r in AUTHINFO commands
++
++* Mon Nov 27 2000 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.3.12
++
++* Wed Nov 1 2000 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.3.11
++- fixed error opening newsgroup from mutt started with -g or -G
++
++* Thu Oct 12 2000 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.3.10
++- hotkey 'G' (get-message) replaced with '^G'
++
++* Thu Sep 21 2000 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.3.9
++- changed delay displaying error messages from 1 to 2 seconds
++- fixed error compiling with nntp and without imap
++
++* Wed Sep 6 2000 Vsevolod Volkov <vvv@mutt.org.ua>
++- fixed catchup in index
++- fixed nntp_open_mailbox()
++
++* Sat Sep 2 2000 Vsevolod Volkov <vvv@mutt.org.ua>
++- functions <edit> and <delete-entry> disabled
++- format of news mailbox names changed to url form
++- option nntp_attempts removed
++- option reconnect_news renamed to nntp_reconnect
++- default value of nntp_poll changed from 30 to 60
++- error handling improved
++
++* Wed Aug 30 2000 Vsevolod Volkov <vvv@mutt.org.ua>
++- update to 1.3.8
++- new option show_only_unread
++- add newsgroup completion
++
++* Fri Aug 4 2000 Vsevolod Volkov <vvv@mutt.kiev.ua>
++- update to 1.3.7
++
++* Sat Jul 29 2000 Vsevolod Volkov <vvv@mutt.kiev.ua>
++- update to 1.3.6
++
++* Sun Jul 9 2000 Vsevolod Volkov <vvv@mutt.kiev.ua>
++- update to 1.3.5
++- authentication code update
++- fix for changing to newsgroup from mailbox with read messages
++- socket code optimization
++
++* Wed Jun 21 2000 Vsevolod Volkov <vvv@mutt.kiev.ua>
++- update to 1.3.4
++
++* Wed Jun 14 2000 Vsevolod Volkov <vvv@mutt.kiev.ua>
++- don't substitute current newsgroup with deleted new messages
++
++* Mon Jun 12 2000 Vsevolod Volkov <vvv@mutt.kiev.ua>
++- update to 1.3.3
++- fix for substitution of newsgroup after reconnection
++- fix for loading newsgroups with very long names
++- fix for loading more than 32768 newsgroups
++
++* Wed May 24 2000 Vsevolod Volkov <vvv@mutt.kiev.ua>
++- update to 1.3.2
++
++* Sat May 20 2000 Vsevolod Volkov <vvv@mutt.kiev.ua>
++- update to 1.3.1
++
++* Fri May 12 2000 Vsevolod Volkov <vvv@mutt.kiev.ua>
++- update to 1.3
++
++* Thu May 11 2000 Vsevolod Volkov <vvv@mutt.kiev.ua>
++- update to 1.2
++
++* Thu May 4 2000 Vsevolod Volkov <vvv@mutt.kiev.ua>
++- update to 1.1.14
++
++* Sun Apr 23 2000 Vsevolod Volkov <vvv@mutt.kiev.ua>
++- update to 1.1.12
++
++* Fri Apr 7 2000 Vsevolod Volkov <vvv@mutt.kiev.ua>
++- add substitution of newsgroup with new messages by default
++
++* Wed Apr 5 2000 Vsevolod Volkov <vvv@mutt.kiev.ua>
++- add attach message from newsgroup
++- add one-line help in newsreader mode
++- disable 'change-dir' command in newsgroups browser
++- add -G option
++
++* Tue Apr 4 2000 Vsevolod Volkov <vvv@mutt.kiev.ua>
++- get default news server name from file /etc/nntpserver
++- use case insensitive server names
++- add print-style sequence %s to $newsrc
++- add -g option
++
++* Sat Apr 1 2000 Vsevolod Volkov <vvv@mutt.kiev.ua>
++- remove 'X-FTN-Origin' header processing
++
++* Thu Mar 30 2000 Vsevolod Volkov <vvv@mutt.kiev.ua>
++- update to 1.1.11
++- update to 1.1.10
++
++* Thu Mar 23 2000 Vsevolod Volkov <vvv@mutt.kiev.ua>
++- fix mutt_select_newsserver()
++- remove 'toggle-mode' function
++- add 'change-newsgroup' function
++
++* Wed Mar 22 2000 Vsevolod Volkov <vvv@mutt.kiev.ua>
++- fix server-hook
++
++* Tue Mar 21 2000 Vsevolod Volkov <vvv@mutt.kiev.ua>
++- fix error 'bounce' function after 'post'
++- add 'forward to newsgroup' function
++
++* Mon Mar 20 2000 Vsevolod Volkov <vvv@mutt.kiev.ua>
++- 'forward' function works in newsreader mode
++- add 'post' and 'followup' functions to pager and attachment menu
++- fix active descriptions and allowed flag reload
++
++* Tue Mar 14 2000 Vsevolod Volkov <vvv@mutt.kiev.ua>
++- update to 1.1.9
++- remove deleted newsgroups from list
++
++* Mon Mar 13 2000 Vsevolod Volkov <vvv@mutt.kiev.ua>
++- update .newsrc in browser
++
++* Sun Mar 12 2000 Vsevolod Volkov <vvv@mutt.kiev.ua>
++- reload .newsrc if externally modified
++- fix active cache update
++
++* Sun Mar 5 2000 Vsevolod Volkov <vvv@mutt.kiev.ua>
++- update to 1.1.8
++
++* Sat Mar 4 2000 Vsevolod Volkov <vvv@mutt.kiev.ua>
++- patch *.update_list_file is not required
++- count lines when loading descriptions
++- remove cache of unsubscribed newsgroups
++
++* Thu Mar 2 2000 Vsevolod Volkov <vvv@mutt.kiev.ua>
++- load list of newsgroups from cache faster
++
++* Wed Mar 1 2000 Vsevolod Volkov <vvv@mutt.kiev.ua>
++- update to 1.1.7
++
++* Tue Feb 29 2000 Vsevolod Volkov <vvv@mutt.kiev.ua>
++- fix unread messages in browser
++- fix newsrc_gen_entries()
++
++* Mon Feb 28 2000 Vsevolod Volkov <vvv@mutt.kiev.ua>
++- fix mutt_newsgroup_stat()
++- fix nntp_delete_cache()
++- fix nntp_get_status()
++- fix check_children()
++- fix nntp_fetch_headers()
++
++* Fri Feb 25 2000 Vsevolod Volkov <vvv@mutt.kiev.ua>
++- update to 1.1.5
++
++* Thu Feb 24 2000 Vsevolod Volkov <vvv@mutt.kiev.ua>
++- fix updating new messages in cache
++
++* Mon Feb 21 2000 Vsevolod Volkov <vvv@mutt.kiev.ua>
++- change default cache filenames
++- fix updating new messages in cache
++
++* Fri Feb 18 2000 Vsevolod Volkov <vvv@mutt.kiev.ua>
++- fix segmentation fault in news groups browser
++
++* Tue Feb 15 2000 Vsevolod Volkov <vvv@mutt.kiev.ua>
++- update to 1.1.4
++
++* Thu Feb 10 2000 Vsevolod Volkov <vvv@mutt.kiev.ua>
++- update to 1.1.3
++
++* Sun Jan 30 2000 Vsevolod Volkov <vvv@mutt.kiev.ua>
++- add X-Comment-To editing
++- add my_hdr support for Newsgroups:, Followup-To: and X-Comment-To: headers
++- add variables $ask_followup_to and $ask_x_comment_to
++
++* Fri Jan 28 2000 Vsevolod Volkov <vvv@mutt.kiev.ua>
++- update to 1.1.2
+diff --git a/Makefile.am b/Makefile.am
+index 2fc6b1d..6d56156 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -58,6 +58,7 @@ EXTRA_mutt_SOURCES = account.c bcache.c crypt-gpgme.c crypt-mod-pgp-classic.c \
+ mutt_idna.c mutt_sasl.c mutt_socket.c mutt_ssl.c mutt_ssl_gnutls.c \
+ mutt_tunnel.c pgp.c pgpinvoke.c pgpkey.c pgplib.c pgpmicalg.c \
+ pgppacket.c pop.c pop_auth.c pop_lib.c remailer.c resize.c sha1.c \
++ nntp.c newsrc.c \
+ smime.c smtp.c utf8.c wcwidth.c \
+ bcache.h browser.h hcache.h mbyte.h mutt_idna.h remailer.h url.h
+
+@@ -69,6 +70,7 @@ EXTRA_DIST = COPYRIGHT GPL OPS OPS.PGP OPS.CRYPT OPS.SMIME TODO UPDATING \
+ mutt_regex.h mutt_sasl.h mutt_socket.h mutt_ssl.h mutt_tunnel.h \
+ mx.h pager.h pgp.h pop.h protos.h rfc1524.h rfc2047.h \
+ rfc2231.h rfc822.h rfc3676.h sha1.h sort.h mime.types VERSION prepare \
++ nntp.h ChangeLog.nntp \
+ _regex.h OPS.MIX README.SECURITY remailer.c remailer.h browser.h \
+ mbyte.h lib.h extlib.c pgpewrap.c smime_keys.pl pgplib.h \
+ README.SSL smime.h group.h \
+diff --git a/OPS b/OPS
+index 1ed9c96..b900373 100644
+--- a/OPS
++++ b/OPS
+@@ -8,14 +8,16 @@ OP_BOUNCE_MESSAGE "remail a message to another user"
+ OP_BROWSER_NEW_FILE "select a new file in this directory"
+ OP_BROWSER_VIEW_FILE "view file"
+ OP_BROWSER_TELL "display the currently selected file's name"
+-OP_BROWSER_SUBSCRIBE "subscribe to current mailbox (IMAP only)"
+-OP_BROWSER_UNSUBSCRIBE "unsubscribe from current mailbox (IMAP only)"
++OP_BROWSER_SUBSCRIBE "subscribe to current mbox (IMAP/NNTP only)"
++OP_BROWSER_UNSUBSCRIBE "unsubscribe from current mbox (IMAP/NNTP only)"
+ OP_BROWSER_TOGGLE_LSUB "toggle view all/subscribed mailboxes (IMAP only)"
+ OP_BUFFY_LIST "list mailboxes with new mail"
++OP_CATCHUP "mark all articles in newsgroup as read"
+ OP_CHANGE_DIRECTORY "change directories"
+ OP_CHECK_NEW "check mailboxes for new mail"
+ OP_COMPOSE_ATTACH_FILE "attach file(s) to this message"
+ OP_COMPOSE_ATTACH_MESSAGE "attach message(s) to this message"
++OP_COMPOSE_ATTACH_NEWS_MESSAGE "attach news article(s) to this message"
+ OP_COMPOSE_EDIT_BCC "edit the BCC list"
+ OP_COMPOSE_EDIT_CC "edit the CC list"
+ OP_COMPOSE_EDIT_DESCRIPTION "edit attachment description"
+@@ -26,7 +28,10 @@ OP_COMPOSE_EDIT_FROM "edit the from field"
+ OP_COMPOSE_EDIT_HEADERS "edit the message with headers"
+ OP_COMPOSE_EDIT_MESSAGE "edit the message"
+ OP_COMPOSE_EDIT_MIME "edit attachment using mailcap entry"
++OP_COMPOSE_EDIT_NEWSGROUPS "edit the newsgroups list"
+ OP_COMPOSE_EDIT_REPLY_TO "edit the Reply-To field"
++OP_COMPOSE_EDIT_FOLLOWUP_TO "edit the Followup-To field"
++OP_COMPOSE_EDIT_X_COMMENT_TO "edit the X-Comment-To field"
+ OP_COMPOSE_EDIT_SUBJECT "edit the subject of this message"
+ OP_COMPOSE_EDIT_TO "edit the TO list"
+ OP_CREATE_MAILBOX "create a new mailbox (IMAP only)"
+@@ -85,8 +90,13 @@ OP_EXIT "exit this menu"
+ OP_FILTER "filter attachment through a shell command"
+ OP_FIRST_ENTRY "move to the first entry"
+ OP_FLAG_MESSAGE "toggle a message's 'important' flag"
++OP_FOLLOWUP "followup to newsgroup"
++OP_FORWARD_TO_GROUP "forward to newsgroup"
+ OP_FORWARD_MESSAGE "forward a message with comments"
+ OP_GENERIC_SELECT_ENTRY "select the current entry"
++OP_GET_CHILDREN "get all children of the current message"
++OP_GET_MESSAGE "get message with Message-Id"
++OP_GET_PARENT "get parent of the current message"
+ OP_GROUP_REPLY "reply to all recipients"
+ OP_HALF_DOWN "scroll down 1/2 page"
+ OP_HALF_UP "scroll up 1/2 page"
+@@ -94,11 +104,14 @@ OP_HELP "this screen"
+ OP_JUMP "jump to an index number"
+ OP_LAST_ENTRY "move to the last entry"
+ OP_LIST_REPLY "reply to specified mailing list"
++OP_LOAD_ACTIVE "load list of all newsgroups from NNTP server"
+ OP_MACRO "execute a macro"
+ OP_MAIL "compose a new mail message"
+ OP_MAIN_BREAK_THREAD "break the thread in two"
+ OP_MAIN_CHANGE_FOLDER "open a different folder"
+ OP_MAIN_CHANGE_FOLDER_READONLY "open a different folder in read only mode"
++OP_MAIN_CHANGE_GROUP "open a different newsgroup"
++OP_MAIN_CHANGE_GROUP_READONLY "open a different newsgroup in read only mode"
+ OP_MAIN_CLEAR_FLAG "clear a status flag from a message"
+ OP_MAIN_DELETE_PATTERN "delete messages matching a pattern"
+ OP_MAIN_IMAP_FETCH "force retrieval of mail from IMAP server"
+@@ -138,6 +151,7 @@ OP_PAGER_HIDE_QUOTED "toggle display of quoted text"
+ OP_PAGER_SKIP_QUOTED "skip beyond quoted text"
+ OP_PAGER_TOP "jump to the top of the message"
+ OP_PIPE "pipe message/attachment to a shell command"
++OP_POST "post message to newsgroup"
+ OP_PREV_ENTRY "move to the previous entry"
+ OP_PREV_LINE "scroll up one line"
+ OP_PREV_PAGE "move to the previous page"
+@@ -147,6 +161,7 @@ OP_QUERY "query external program for addresses"
+ OP_QUERY_APPEND "append new query results to current results"
+ OP_QUIT "save changes to mailbox and quit"
+ OP_RECALL_MESSAGE "recall a postponed message"
++OP_RECONSTRUCT_THREAD "reconstruct thread containing current message"
+ OP_REDRAW "clear and redraw the screen"
+ OP_REFORMAT_WINCH "{internal}"
+ OP_RENAME_MAILBOX "rename the current mailbox (IMAP only)"
+@@ -161,18 +176,22 @@ OP_SEARCH_TOGGLE "toggle search pattern coloring"
+ OP_SHELL_ESCAPE "invoke a command in a subshell"
+ OP_SORT "sort messages"
+ OP_SORT_REVERSE "sort messages in reverse order"
++OP_SUBSCRIBE_PATTERN "subscribe to newsgroups matching a pattern"
+ OP_TAG "tag the current entry"
+ OP_TAG_PREFIX "apply next function to tagged messages"
+ OP_TAG_PREFIX_COND "apply next function ONLY to tagged messages"
+ OP_TAG_SUBTHREAD "tag the current subthread"
+ OP_TAG_THREAD "tag the current thread"
+ OP_TOGGLE_NEW "toggle a message's 'new' flag"
++OP_TOGGLE_READ "toggle view of read messages"
+ OP_TOGGLE_WRITE "toggle whether the mailbox will be rewritten"
+ OP_TOGGLE_MAILBOXES "toggle whether to browse mailboxes or all files"
+ OP_TOP_PAGE "move to the top of the page"
++OP_UNCATCHUP "mark all articles in newsgroup as unread"
+ OP_UNDELETE "undelete the current entry"
+ OP_UNDELETE_THREAD "undelete all messages in thread"
+ OP_UNDELETE_SUBTHREAD "undelete all messages in subthread"
++OP_UNSUBSCRIBE_PATTERN "unsubscribe from newsgroups matching a pattern"
+ OP_VERSION "show the Mutt version number and date"
+ OP_VIEW_ATTACH "view attachment using mailcap entry if necessary"
+ OP_VIEW_ATTACHMENTS "show MIME attachments"
+diff --git a/account.c b/account.c
+index bf59995..624f931 100644
+--- a/account.c
++++ b/account.c
+@@ -51,8 +51,17 @@ int mutt_account_match (const ACCOUNT* a1, const ACCOUNT* a2)
+ user = PopUser;
+ #endif
+
++#ifdef USE_NNTP
++ if (a1->type == M_ACCT_TYPE_NNTP && NntpUser)
++ user = NntpUser;
++#endif
++
+ if (a1->flags & a2->flags & M_ACCT_USER)
+ return (!strcmp (a1->user, a2->user));
++#ifdef USE_NNTP
++ if (a1->type == M_ACCT_TYPE_NNTP)
++ return a1->flags & M_ACCT_USER && a1->user[0] ? 0 : 1;
++#endif
+ if (a1->flags & M_ACCT_USER)
+ return (!strcmp (a1->user, user));
+ if (a2->flags & M_ACCT_USER)
+@@ -130,6 +139,16 @@ void mutt_account_tourl (ACCOUNT* account, ciss_url_t* url)
+ }
+ #endif
+
++#ifdef USE_NNTP
++ if (account->type == M_ACCT_TYPE_NNTP)
++ {
++ if (account->flags & M_ACCT_SSL)
++ url->scheme = U_NNTPS;
++ else
++ url->scheme = U_NNTP;
++ }
++#endif
++
+ url->host = account->host;
+ if (account->flags & M_ACCT_PORT)
+ url->port = account->port;
+@@ -155,6 +174,10 @@ int mutt_account_getuser (ACCOUNT* account)
+ else if ((account->type == M_ACCT_TYPE_POP) && PopUser)
+ strfcpy (account->user, PopUser, sizeof (account->user));
+ #endif
++#ifdef USE_NNTP
++ else if ((account->type == M_ACCT_TYPE_NNTP) && NntpUser)
++ strfcpy (account->user, NntpUser, sizeof (account->user));
++#endif
+ else if (option (OPTNOCURSES))
+ return -1;
+ /* prompt (defaults to unix username), copy into account->user */
+@@ -217,6 +240,10 @@ int mutt_account_getpass (ACCOUNT* account)
+ else if ((account->type == M_ACCT_TYPE_SMTP) && SmtpPass)
+ strfcpy (account->pass, SmtpPass, sizeof (account->pass));
+ #endif
++#ifdef USE_NNTP
++ else if ((account->type == M_ACCT_TYPE_NNTP) && NntpPass)
++ strfcpy (account->pass, NntpPass, sizeof (account->pass));
++#endif
+ else if (option (OPTNOCURSES))
+ return -1;
+ else
+diff --git a/account.h b/account.h
+index 662fb32..9febee6 100644
+--- a/account.h
++++ b/account.h
+@@ -29,7 +29,8 @@ enum
+ M_ACCT_TYPE_NONE = 0,
+ M_ACCT_TYPE_IMAP,
+ M_ACCT_TYPE_POP,
+- M_ACCT_TYPE_SMTP
++ M_ACCT_TYPE_SMTP,
++ M_ACCT_TYPE_NNTP
+ };
+
+ /* account flags */
+diff --git a/attach.h b/attach.h
+index 928408a..071f22c 100644
+--- a/attach.h
++++ b/attach.h
+@@ -50,7 +50,7 @@ void mutt_print_attachment_list (FILE *fp, int tag, BODY *top);
+
+ void mutt_attach_bounce (FILE *, HEADER *, ATTACHPTR **, short, BODY *);
+ void mutt_attach_resend (FILE *, HEADER *, ATTACHPTR **, short, BODY *);
+-void mutt_attach_forward (FILE *, HEADER *, ATTACHPTR **, short, BODY *);
++void mutt_attach_forward (FILE *, HEADER *, ATTACHPTR **, short, BODY *, int);
+ void mutt_attach_reply (FILE *, HEADER *, ATTACHPTR **, short, BODY *, int);
+
+ #endif /* _ATTACH_H_ */
+diff --git a/browser.c b/browser.c
+index dbb31c8..3c84044 100644
+--- a/browser.c
++++ b/browser.c
+@@ -32,6 +32,9 @@
+ #ifdef USE_IMAP
+ #include "imap.h"
+ #endif
++#ifdef USE_NNTP
++#include "nntp.h"
++#endif
+
+ #include <stdlib.h>
+ #include <dirent.h>
+@@ -50,6 +53,19 @@ static const struct mapping_t FolderHelp[] = {
+ { NULL, 0 }
+ };
+
++#ifdef USE_NNTP
++static struct mapping_t FolderNewsHelp[] = {
++ { N_("Exit"), OP_EXIT },
++ { N_("List"), OP_TOGGLE_MAILBOXES },
++ { N_("Subscribe"), OP_BROWSER_SUBSCRIBE },
++ { N_("Unsubscribe"), OP_BROWSER_UNSUBSCRIBE },
++ { N_("Catchup"), OP_CATCHUP },
++ { N_("Mask"), OP_ENTER_MASK },
++ { N_("Help"), OP_HELP },
++ { NULL, 0 }
++};
++#endif
++
+ typedef struct folder_t
+ {
+ struct folder_file *ff;
+@@ -116,9 +132,17 @@ static void browser_sort (struct browser_state *state)
+ case SORT_ORDER:
+ return;
+ case SORT_DATE:
++#ifdef USE_NNTP
++ if (option (OPTNEWS))
++ return;
++#endif
+ f = browser_compare_date;
+ break;
+ case SORT_SIZE:
++#ifdef USE_NNTP
++ if (option (OPTNEWS))
++ return;
++#endif
+ f = browser_compare_size;
+ break;
+ case SORT_SUBJECT:
+@@ -325,8 +349,111 @@ folder_format_str (char *dest, size_t destlen, size_t col, char op, const char *
+ return (src);
+ }
+
++#ifdef USE_NNTP
++static const char *
++newsgroup_format_str (char *dest, size_t destlen, size_t col, char op, const char *src,
++ const char *fmt, const char *ifstring, const char *elsestring,
++ unsigned long data, format_flag flags)
++{
++ char fn[SHORT_STRING], tmp[SHORT_STRING];
++ FOLDER *folder = (FOLDER *) data;
++
++ switch (op)
++ {
++ case 'C':
++ snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
++ snprintf (dest, destlen, tmp, folder->num + 1);
++ break;
++
++ case 'f':
++ strncpy (fn, folder->ff->name, sizeof(fn) - 1);
++ snprintf (tmp, sizeof (tmp), "%%%ss", fmt);
++ snprintf (dest, destlen, tmp, fn);
++ break;
++
++ case 'N':
++ snprintf (tmp, sizeof (tmp), "%%%sc", fmt);
++ if (folder->ff->nd->subscribed)
++ snprintf (dest, destlen, tmp, ' ');
++ else
++ snprintf (dest, destlen, tmp, folder->ff->new ? 'N' : 'u');
++ break;
++
++ case 'M':
++ snprintf (tmp, sizeof (tmp), "%%%sc", fmt);
++ if (folder->ff->nd->deleted)
++ snprintf (dest, destlen, tmp, 'D');
++ else
++ snprintf (dest, destlen, tmp, folder->ff->nd->allowed ? ' ' : '-');
++ break;
++
++ case 's':
++ if (flags & M_FORMAT_OPTIONAL)
++ {
++ if (folder->ff->nd->unread != 0)
++ mutt_FormatString (dest, destlen, col, ifstring, newsgroup_format_str,
++ data, flags);
++ else
++ mutt_FormatString (dest, destlen, col, elsestring, newsgroup_format_str,
++ data, flags);
++ }
++ else if (Context && Context->data == folder->ff->nd)
++ {
++ snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
++ snprintf (dest, destlen, tmp, Context->unread);
++ }
++ else
++ {
++ snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
++ snprintf (dest, destlen, tmp, folder->ff->nd->unread);
++ }
++ break;
++
++ case 'n':
++ if (Context && Context->data == folder->ff->nd)
++ {
++ snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
++ snprintf (dest, destlen, tmp, Context->new);
++ }
++ else if (option (OPTMARKOLD) &&
++ folder->ff->nd->lastCached >= folder->ff->nd->firstMessage &&
++ folder->ff->nd->lastCached <= folder->ff->nd->lastMessage)
++ {
++ snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
++ snprintf (dest, destlen, tmp, folder->ff->nd->lastMessage - folder->ff->nd->lastCached);
++ }
++ else
++ {
++ snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
++ snprintf (dest, destlen, tmp, folder->ff->nd->unread);
++ }
++ break;
++
++ case 'd':
++ if (folder->ff->nd->desc != NULL)
++ {
++ char *buf = safe_strdup (folder->ff->nd->desc);
++ if (NewsgroupsCharset && *NewsgroupsCharset)
++ mutt_convert_string (&buf, NewsgroupsCharset, Charset, M_ICONV_HOOK_FROM);
++ mutt_filter_unprintable (&buf);
++
++ snprintf (tmp, sizeof (tmp), "%%%ss", fmt);
++ snprintf (dest, destlen, tmp, buf);
++ }
++ else
++ {
++ snprintf (tmp, sizeof (tmp), "%%%ss", fmt);
++ snprintf (dest, destlen, tmp, "");
++ }
++ break;
++ }
++ return (src);
++}
++#endif /* USE_NNTP */
++
+ static void add_folder (MUTTMENU *m, struct browser_state *state,
+- const char *name, const struct stat *s, unsigned int new)
++ const char *name, const struct stat *s,
++ void *data, unsigned int new)
+ {
+ if (state->entrylen == state->entrymax)
+ {
+@@ -355,6 +482,10 @@ static void add_folder (MUTTMENU *m, struct browser_state *state,
+ #ifdef USE_IMAP
+ (state->entry)[state->entrylen].imap = 0;
+ #endif
++#ifdef USE_NNTP
++ if (option (OPTNEWS))
++ (state->entry)[state->entrylen].nd = (NNTP_DATA *)data;
++#endif
+ (state->entrylen)++;
+ }
+
+@@ -370,9 +501,36 @@ static void init_state (struct browser_state *state, MUTTMENU *menu)
+ menu->data = state->entry;
+ }
+
++/* get list of all files/newsgroups with mask */
+ static int examine_directory (MUTTMENU *menu, struct browser_state *state,
+ char *d, const char *prefix)
+ {
++#ifdef USE_NNTP
++ if (option (OPTNEWS))
++ {
++ NNTP_SERVER *nserv = CurrentNewsSrv;
++ unsigned int i;
++
++/* mutt_buffy_check (0); */
++ init_state (state, menu);
++
++ for (i = 0; i < nserv->groups_num; i++)
++ {
++ NNTP_DATA *nntp_data = nserv->groups_list[i];
++ if (!nntp_data)
++ continue;
++ if (prefix && *prefix &&
++ strncmp (prefix, nntp_data->group, strlen (prefix)))
++ continue;
++ if (!((regexec (Mask.rx, nntp_data->group, 0, NULL, 0) == 0) ^ Mask.not))
++ continue;
++ add_folder (menu, state, nntp_data->group, NULL,
++ nntp_data, nntp_data->new);
++ }
++ }
++ else
++#endif /* USE_NNTP */
++ {
+ struct stat s;
+ DIR *dp;
+ struct dirent *de;
+@@ -433,17 +591,41 @@ static int examine_directory (MUTTMENU *menu, struct browser_state *state,
+ tmp = Incoming;
+ while (tmp && mutt_strcmp (buffer, tmp->path))
+ tmp = tmp->next;
+- add_folder (menu, state, de->d_name, &s, (tmp) ? tmp->new : 0);
++ add_folder (menu, state, de->d_name, &s, NULL, (tmp) ? tmp->new : 0);
+ }
+ closedir (dp);
++ }
+ browser_sort (state);
+ return 0;
+ }
+
++/* get list of mailboxes/subscribed newsgroups */
+ static int examine_mailboxes (MUTTMENU *menu, struct browser_state *state)
+ {
+ struct stat s;
+ char buffer[LONG_STRING];
++
++#ifdef USE_NNTP
++ if (option (OPTNEWS))
++ {
++ NNTP_SERVER *nserv = CurrentNewsSrv;
++ unsigned int i;
++
++/* mutt_buffy_check (0); */
++ init_state (state, menu);
++
++ for (i = 0; i < nserv->groups_num; i++)
++ {
++ NNTP_DATA *nntp_data = nserv->groups_list[i];
++ if (nntp_data && (nntp_data->new || (nntp_data->subscribed &&
++ (nntp_data->unread || !option (OPTSHOWONLYUNREAD)))))
++ add_folder (menu, state, nntp_data->group, NULL,
++ nntp_data, nntp_data->new);
++ }
++ }
++ else
++#endif
++ {
+ BUFFY *tmp = Incoming;
+ #ifdef USE_IMAP
+ struct mailbox_state mbox;
+@@ -461,14 +643,21 @@ static int examine_mailboxes (MUTTMENU *menu, struct browser_state *state)
+ if (mx_is_imap (tmp->path))
+ {
+ imap_mailbox_state (tmp->path, &mbox);
+- add_folder (menu, state, tmp->path, NULL, mbox.new);
++ add_folder (menu, state, tmp->path, NULL, NULL, mbox.new);
+ continue;
+ }
+ #endif
+ #ifdef USE_POP
+ if (mx_is_pop (tmp->path))
+ {
+- add_folder (menu, state, tmp->path, NULL, tmp->new);
++ add_folder (menu, state, tmp->path, NULL, NULL, tmp->new);
++ continue;
++ }
++#endif
++#ifdef USE_NNTP
++ if (mx_is_nntp (tmp->path))
++ {
++ add_folder (menu, state, tmp->path, NULL, NULL, tmp->new);
+ continue;
+ }
+ #endif
+@@ -497,15 +686,20 @@ static int examine_mailboxes (MUTTMENU *menu, struct browser_state *state)
+ strfcpy (buffer, NONULL(tmp->path), sizeof (buffer));
+ mutt_pretty_mailbox (buffer, sizeof (buffer));
+
+- add_folder (menu, state, buffer, &s, tmp->new);
++ add_folder (menu, state, buffer, &s, NULL, tmp->new);
+ }
+ while ((tmp = tmp->next));
++ }
+ browser_sort (state);
+ return 0;
+ }
+
+ static int select_file_search (MUTTMENU *menu, regex_t *re, int n)
+ {
++#ifdef USE_NNTP
++ if (option (OPTNEWS))
++ return (regexec (re, ((struct folder_file *) menu->data)[n].desc, 0, NULL, 0));
++#endif
+ return (regexec (re, ((struct folder_file *) menu->data)[n].name, 0, NULL, 0));
+ }
+
+@@ -516,6 +710,12 @@ static void folder_entry (char *s, size_t slen, MUTTMENU *menu, int num)
+ folder.ff = &((struct folder_file *) menu->data)[num];
+ folder.num = num;
+
++#ifdef USE_NNTP
++ if (option (OPTNEWS))
++ mutt_FormatString (s, slen, 0, NONULL(GroupFormat), newsgroup_format_str,
++ (unsigned long) &folder, M_FORMAT_ARROWCURSOR);
++ else
++#endif
+ mutt_FormatString (s, slen, 0, NONULL(FolderFormat), folder_format_str,
+ (unsigned long) &folder, M_FORMAT_ARROWCURSOR);
+ }
+@@ -536,6 +736,17 @@ static void init_menu (struct browser_state *state, MUTTMENU *menu, char *title,
+
+ menu->tagged = 0;
+
++#ifdef USE_NNTP
++ if (option (OPTNEWS))
++ {
++ if (buffy)
++ snprintf (title, titlelen, _("Subscribed newsgroups"));
++ else
++ snprintf (title, titlelen, _("Newsgroups on server [%s]"),
++ CurrentNewsSrv->conn->account.host);
++ }
++ else
++#endif
+ if (buffy)
+ {
+ menu->is_mailbox_list = 1;
+@@ -609,6 +820,31 @@ void _mutt_select_file (char *f, size_t flen, int flags, char ***files, int *num
+ if (!folder)
+ strfcpy (LastDirBackup, LastDir, sizeof (LastDirBackup));
+
++#ifdef USE_NNTP
++ if (option (OPTNEWS))
++ {
++ if (*f)
++ strfcpy (prefix, f, sizeof (prefix));
++ else
++ {
++ NNTP_SERVER *nserv = CurrentNewsSrv;
++ unsigned int i;
++
++ /* default state for news reader mode is browse subscribed newsgroups */
++ buffy = 0;
++ for (i = 0; i < nserv->groups_num; i++)
++ {
++ NNTP_DATA *nntp_data = nserv->groups_list[i];
++ if (nntp_data && nntp_data->subscribed)
++ {
++ buffy = 1;
++ break;
++ }
++ }
++ }
++ }
++ else
++#endif
+ if (*f)
+ {
+ mutt_expand_path (f, flen);
+@@ -705,6 +941,9 @@ void _mutt_select_file (char *f, size_t flen, int flags, char ***files, int *num
+ menu->tag = file_tag;
+
+ menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_FOLDER,
++#ifdef USE_NNTP
++ option (OPTNEWS) ? FolderNewsHelp :
++#endif
+ FolderHelp);
+
+ init_menu (&state, menu, title, sizeof (title), buffy);
+@@ -842,7 +1081,11 @@ void _mutt_select_file (char *f, size_t flen, int flags, char ***files, int *num
+ }
+ }
+
++#ifdef USE_NNTP
++ if (buffy || option (OPTNEWS))
++#else
+ if (buffy)
++#endif
+ {
+ strfcpy (f, state.entry[menu->current].name, flen);
+ mutt_expand_path (f, flen);
+@@ -900,14 +1143,6 @@ void _mutt_select_file (char *f, size_t flen, int flags, char ***files, int *num
+ break;
+
+ #ifdef USE_IMAP
+- case OP_BROWSER_SUBSCRIBE:
+- imap_subscribe (state.entry[menu->current].name, 1);
+- break;
+-
+- case OP_BROWSER_UNSUBSCRIBE:
+- imap_subscribe (state.entry[menu->current].name, 0);
+- break;
+-
+ case OP_BROWSER_TOGGLE_LSUB:
+ if (option (OPTIMAPLSUB))
+ unset_option (OPTIMAPLSUB);
+@@ -1008,6 +1243,11 @@ void _mutt_select_file (char *f, size_t flen, int flags, char ***files, int *num
+
+ case OP_CHANGE_DIRECTORY:
+
++#ifdef USE_NNTP
++ if (option (OPTNEWS))
++ break;
++#endif
++
+ strfcpy (buf, LastDir, sizeof (buf));
+ #ifdef USE_IMAP
+ if (!state.imap_browse)
+@@ -1273,6 +1513,210 @@ void _mutt_select_file (char *f, size_t flen, int flags, char ***files, int *num
+ else
+ mutt_error _("Error trying to view file");
+ }
++ break;
++
++#ifdef USE_NNTP
++ case OP_CATCHUP:
++ case OP_UNCATCHUP:
++ if (option (OPTNEWS))
++ {
++ struct folder_file *f = &state.entry[menu->current];
++ int rc;
++ NNTP_DATA *nntp_data;
++
++ rc = nntp_newsrc_parse (CurrentNewsSrv);
++ if (rc < 0)
++ break;
++
++ if (i == OP_CATCHUP)
++ nntp_data = mutt_newsgroup_catchup (CurrentNewsSrv, f->name);
++ else
++ nntp_data = mutt_newsgroup_uncatchup (CurrentNewsSrv, f->name);
++
++ if (nntp_data)
++ {
++/* FOLDER folder;
++ struct folder_file ff;
++ char buffer[_POSIX_PATH_MAX + SHORT_STRING];
++
++ folder.ff = &ff;
++ folder.ff->name = f->name;
++ folder.ff->st = NULL;
++ folder.ff->is_new = nntp_data->new;
++ folder.ff->nntp_data = nntp_data;
++ FREE (&f->desc);
++ mutt_FormatString (buffer, sizeof (buffer), 0, NONULL(GroupFormat),
++ newsgroup_format_str, (unsigned long) &folder,
++ M_FORMAT_ARROWCURSOR);
++ f->desc = safe_strdup (buffer); */
++ nntp_newsrc_update (CurrentNewsSrv);
++ if (menu->current + 1 < menu->max)
++ menu->current++;
++ menu->redraw = REDRAW_MOTION_RESYNCH;
++ }
++ if (rc)
++ menu->redraw = REDRAW_INDEX;
++ nntp_newsrc_close (CurrentNewsSrv);
++ }
++ break;
++
++ case OP_LOAD_ACTIVE:
++ if (option (OPTNEWS))
++ {
++ NNTP_SERVER *nserv = CurrentNewsSrv;
++ unsigned int i;
++
++ if (nntp_newsrc_parse (nserv) < 0)
++ break;
++
++ for (i = 0; i < nserv->groups_num; i++)
++ {
++ NNTP_DATA *nntp_data = nserv->groups_list[i];
++ if (nntp_data)
++ nntp_data->deleted = 1;
++ }
++ nntp_active_fetch (nserv);
++ nntp_newsrc_update (nserv);
++ nntp_newsrc_close (nserv);
++
++ destroy_state (&state);
++ if (buffy)
++ examine_mailboxes (menu, &state);
++ else
++ examine_directory (menu, &state, NULL, NULL);
++ init_menu (&state, menu, title, sizeof (title), buffy);
++ }
++ break;
++#endif /* USE_NNTP */
++
++#if defined USE_IMAP || defined USE_NNTP
++ case OP_BROWSER_SUBSCRIBE:
++ case OP_BROWSER_UNSUBSCRIBE:
++#endif
++#ifdef USE_NNTP
++ case OP_SUBSCRIBE_PATTERN:
++ case OP_UNSUBSCRIBE_PATTERN:
++ if (option (OPTNEWS))
++ {
++ NNTP_SERVER *nserv = CurrentNewsSrv;
++ NNTP_DATA *nntp_data;
++ regex_t *rx = (regex_t *) safe_malloc (sizeof (regex_t));
++ char *s = buf;
++ int rc, j = menu->current;
++
++ if (i == OP_SUBSCRIBE_PATTERN || i == OP_UNSUBSCRIBE_PATTERN)
++ {
++ char tmp[STRING];
++ int err;
++
++ buf[0] = 0;
++ if (i == OP_SUBSCRIBE_PATTERN)
++ snprintf (tmp, sizeof (tmp), _("Subscribe pattern: "));
++ else
++ snprintf (tmp, sizeof (tmp), _("Unsubscribe pattern: "));
++ if (mutt_get_field (tmp, buf, sizeof (buf), 0) != 0 || !buf[0])
++ {
++ FREE (&rx);
++ break;
++ }
++
++ err = REGCOMP (rx, s, REG_NOSUB);
++ if (err)
++ {
++ regerror (err, rx, buf, sizeof (buf));
++ regfree (rx);
++ FREE (&rx);
++ mutt_error ("%s", buf);
++ break;
++ }
++ menu->redraw = REDRAW_FULL;
++ j = 0;
++ }
++ else if (!state.entrylen)
++ {
++ mutt_error _("No newsgroups match the mask");
++ break;
++ }
++
++ rc = nntp_newsrc_parse (nserv);
++ if (rc < 0)
++ break;
++
++ for ( ; j < state.entrylen; j++)
++ {
++ struct folder_file *f = &state.entry[j];
++
++ if (i == OP_BROWSER_SUBSCRIBE || i == OP_BROWSER_UNSUBSCRIBE ||
++ regexec (rx, f->name, 0, NULL, 0) == 0)
++ {
++ if (i == OP_BROWSER_SUBSCRIBE || i == OP_SUBSCRIBE_PATTERN)
++ nntp_data = mutt_newsgroup_subscribe (nserv, f->name);
++ else
++ nntp_data = mutt_newsgroup_unsubscribe (nserv, f->name);
++/* if (nntp_data)
++ {
++ FOLDER folder;
++ char buffer[_POSIX_PATH_MAX + SHORT_STRING];
++
++ folder.name = f->name;
++ folder.f = NULL;
++ folder.new = nntp_data->new;
++ folder.nd = nntp_data;
++ FREE (&f->desc);
++ mutt_FormatString (buffer, sizeof (buffer), 0, NONULL(GroupFormat),
++ newsgroup_format_str, (unsigned long) &folder,
++ M_FORMAT_ARROWCURSOR);
++ f->desc = safe_strdup (buffer);
++ } */
++ }
++ if (i == OP_BROWSER_SUBSCRIBE || i == OP_BROWSER_UNSUBSCRIBE)
++ {
++ if (menu->current + 1 < menu->max)
++ menu->current++;
++ menu->redraw = REDRAW_MOTION_RESYNCH;
++ break;
++ }
++ }
++ if (i == OP_SUBSCRIBE_PATTERN)
++ {
++ unsigned int i;
++
++ for (i = 0; nserv && i < nserv->groups_num; i++)
++ {
++ nntp_data = nserv->groups_list[i];
++ if (nntp_data && nntp_data->group && !nntp_data->subscribed)
++ {
++ if (regexec (rx, nntp_data->group, 0, NULL, 0) == 0)
++ {
++ mutt_newsgroup_subscribe (nserv, nntp_data->group);
++ add_folder (menu, &state, nntp_data->group, NULL,
++ nntp_data, nntp_data->new);
++ }
++ }
++ }
++ init_menu (&state, menu, title, sizeof (title), buffy);
++ }
++ if (rc > 0)
++ menu->redraw = REDRAW_FULL;
++ nntp_newsrc_update (nserv);
++ nntp_clear_cache (nserv);
++ nntp_newsrc_close (nserv);
++ if (i != OP_BROWSER_SUBSCRIBE && i != OP_BROWSER_UNSUBSCRIBE)
++ regfree (rx);
++ FREE (&rx);
++ }
++#ifdef USE_IMAP
++ else
++#endif /* USE_IMAP && USE_NNTP */
++#endif /* USE_NNTP */
++#ifdef USE_IMAP
++ {
++ if (i == OP_BROWSER_SUBSCRIBE)
++ imap_subscribe (state.entry[menu->current].name, 1);
++ else
++ imap_subscribe (state.entry[menu->current].name, 0);
++ }
++#endif /* USE_IMAP */
+ }
+ }
+
+diff --git a/browser.h b/browser.h
+index 515d69f..ad89ab2 100644
+--- a/browser.h
++++ b/browser.h
+@@ -19,6 +19,10 @@
+ #ifndef _BROWSER_H
+ #define _BROWSER_H 1
+
++#ifdef USE_NNTP
++#include "nntp.h"
++#endif
++
+ struct folder_file
+ {
+ mode_t mode;
+@@ -37,6 +41,9 @@ struct folder_file
+ unsigned selectable : 1;
+ unsigned inferiors : 1;
+ #endif
++#ifdef USE_NNTP
++ NNTP_DATA *nd;
++#endif
+ unsigned tagged : 1;
+ };
+
+diff --git a/buffy.c b/buffy.c
+index 225104d..b1abfa9 100644
+--- a/buffy.c
++++ b/buffy.c
+@@ -525,6 +525,9 @@ int mutt_buffy_check (int force)
+
+ /* check device ID and serial number instead of comparing paths */
+ if (!Context || Context->magic == M_IMAP || Context->magic == M_POP
++#ifdef USE_NNTP
++ || Context->magic == M_NNTP
++#endif
+ || stat (Context->path, &contex_sb) != 0)
+ {
+ contex_sb.st_dev=0;
+@@ -541,6 +544,11 @@ int mutt_buffy_check (int force)
+ tmp->magic = M_POP;
+ else
+ #endif
++#ifdef USE_NNTP
++ if ((tmp->magic == M_NNTP) || mx_is_nntp (tmp->path))
++ tmp->magic = M_NNTP;
++ else
++#endif
+ if (stat (tmp->path, &sb) != 0 || (S_ISREG(sb.st_mode) && sb.st_size == 0) ||
+ (!tmp->magic && (tmp->magic = mx_get_magic (tmp->path)) <= 0))
+ {
+@@ -556,7 +564,11 @@ int mutt_buffy_check (int force)
+ /* check to see if the folder is the currently selected folder
+ * before polling */
+ if (!Context || !Context->path ||
++#ifdef USE_NNTP
++ (( tmp->magic == M_IMAP || tmp->magic == M_POP || tmp->magic == M_NNTP )
++#else
+ (( tmp->magic == M_IMAP || tmp->magic == M_POP )
++#endif
+ ? mutt_strcmp (tmp->path, Context->path) :
+ (sb.st_dev != contex_sb.st_dev || sb.st_ino != contex_sb.st_ino)))
+ {
+diff --git a/complete.c b/complete.c
+index d0ee4af..8dc48cd 100644
+--- a/complete.c
++++ b/complete.c
+@@ -25,6 +25,9 @@
+ #include "mailbox.h"
+ #include "imap.h"
+ #endif
++#ifdef USE_NNTP
++#include "nntp.h"
++#endif
+
+ #include <dirent.h>
+ #include <string.h>
+@@ -48,9 +51,70 @@ int mutt_complete (char *s, size_t slen)
+ char filepart[_POSIX_PATH_MAX];
+ #ifdef USE_IMAP
+ char imap_path[LONG_STRING];
++#endif
+
+ dprint (2, (debugfile, "mutt_complete: completing %s\n", s));
+
++#ifdef USE_NNTP
++ if (option (OPTNEWS))
++ {
++ NNTP_SERVER *nserv = CurrentNewsSrv;
++ unsigned int n = 0;
++
++ strfcpy (filepart, s, sizeof (filepart));
++
++ /* special case to handle when there is no filepart yet
++ * find the first subscribed newsgroup */
++ len = mutt_strlen (filepart);
++ if (len == 0)
++ {
++ for (; n < nserv->groups_num; n++)
++ {
++ NNTP_DATA *nntp_data = nserv->groups_list[n];
++
++ if (nntp_data && nntp_data->subscribed)
++ {
++ strfcpy (filepart, nntp_data->group, sizeof (filepart));
++ init = 1;
++ n++;
++ break;
++ }
++ }
++ }
++
++ for (; n < nserv->groups_num; n++)
++ {
++ NNTP_DATA *nntp_data = nserv->groups_list[n];
++
++ if (nntp_data && nntp_data->subscribed &&
++ mutt_strncmp (nntp_data->group, filepart, len) == 0)
++ {
++ if (init)
++ {
++ for (i = 0; filepart[i] && nntp_data->group[i]; i++)
++ {
++ if (filepart[i] != nntp_data->group[i])
++ {
++ filepart[i] = 0;
++ break;
++ }
++ }
++ filepart[i] = 0;
++ }
++ else
++ {
++ strfcpy (filepart, nntp_data->group, sizeof (filepart));
++ init = 1;
++ }
++ }
++ }
++
++ strcpy (s, filepart);
++ return (init ? 0 : -1);
++ }
++#endif
++
++#ifdef USE_IMAP
+ /* we can use '/' as a delimiter, imap_complete rewrites it */
+ if (*s == '=' || *s == '+' || *s == '!')
+ {
+diff --git a/compose.c b/compose.c
+index 16576f2..9e23dfd 100644
+--- a/compose.c
++++ b/compose.c
+@@ -33,11 +33,16 @@
+ #include "sort.h"
+ #include "charset.h"
+ #include "sidebar.h"
++#include "mx.h"
+
+ #ifdef MIXMASTER
+ #include "remailer.h"
+ #endif
+
++#ifdef USE_NNTP
++#include "nntp.h"
++#endif
++
+ #include <errno.h>
+ #include <string.h>
+ #include <sys/stat.h>
+@@ -68,11 +73,17 @@ enum
+ HDR_CRYPT,
+ HDR_CRYPTINFO,
+
++#ifdef USE_NNTP
++ HDR_NEWSGROUPS,
++ HDR_FOLLOWUPTO,
++ HDR_XCOMMENTTO,
++#endif
++
+ HDR_ATTACH = (HDR_FCC + 5) /* where to start printing the attachments */
+ };
+
+-#define HDR_XOFFSET 10
+-#define TITLE_FMT "%10s" /* Used for Prompts, which are ASCII */
++#define HDR_XOFFSET 14
++#define TITLE_FMT "%14s" /* Used for Prompts, which are ASCII */
+ #define W (COLS - HDR_XOFFSET - SidebarWidth)
+
+ static const char * const Prompts[] =
+@@ -84,6 +95,16 @@ static const char * const Prompts[] =
+ "Subject: ",
+ "Reply-To: ",
+ "Fcc: "
++#ifdef USE_NNTP
++#ifdef MIXMASTER
++ ,""
++#endif
++ ,""
++ ,""
++ ,"Newsgroups: "
++ ,"Followup-To: "
++ ,"X-Comment-To: "
++#endif
+ };
+
+ static const struct mapping_t ComposeHelp[] = {
+@@ -98,6 +119,19 @@ static const struct mapping_t ComposeHelp[] = {
+ { NULL, 0 }
+ };
+
++#ifdef USE_NNTP
++static struct mapping_t ComposeNewsHelp[] = {
++ { N_("Send"), OP_COMPOSE_SEND_MESSAGE },
++ { N_("Abort"), OP_EXIT },
++ { "Newsgroups", OP_COMPOSE_EDIT_NEWSGROUPS },
++ { "Subj", OP_COMPOSE_EDIT_SUBJECT },
++ { N_("Attach file"), OP_COMPOSE_ATTACH_FILE },
++ { N_("Descrip"), OP_COMPOSE_EDIT_DESCRIPTION },
++ { N_("Help"), OP_HELP },
++ { NULL, 0 }
++};
++#endif
++
+ static void snd_entry (char *b, size_t blen, MUTTMENU *menu, int num)
+ {
+ mutt_FormatString (b, blen, 0, NONULL (AttachFormat), mutt_attach_fmt,
+@@ -111,7 +145,7 @@ static void snd_entry (char *b, size_t blen, MUTTMENU *menu, int num)
+
+ static void redraw_crypt_lines (HEADER *msg)
+ {
+- mvaddstr (HDR_CRYPT, SidebarWidth, "Security: ");
++ mvprintw (HDR_CRYPT, SidebarWidth, TITLE_FMT, "Security: ");
+
+ if ((WithCrypto & (APPLICATION_PGP | APPLICATION_SMIME)) == 0)
+ {
+@@ -149,10 +183,11 @@ static void redraw_crypt_lines (HEADER *msg)
+ if ((WithCrypto & APPLICATION_PGP)
+ && (msg->security & APPLICATION_PGP) && (msg->security & SIGN))
+ printw ("%s%s", _(" sign as: "), PgpSignAs ? PgpSignAs : _("<default>"));
++ printw (TITLE_FMT "%s", _(" sign as: "), PgpSignAs ? PgpSignAs : _("<default>"));
+
+ if ((WithCrypto & APPLICATION_SMIME)
+ && (msg->security & APPLICATION_SMIME) && (msg->security & SIGN)) {
+- printw ("%s%s", _(" sign as: "), SmimeDefaultKey ? SmimeDefaultKey : _("<default>"));
++ printw (TITLE_FMT "%s", _(" sign as: "), SmimeDefaultKey ? SmimeDefaultKey : _("<default>"));
+ }
+
+ if ((WithCrypto & APPLICATION_SMIME)
+@@ -173,7 +208,7 @@ static void redraw_mix_line (LIST *chain)
+ int c;
+ char *t;
+
+- mvaddstr (HDR_MIX, SidebarWidth, " Mix: ");
++ mvprintw (HDR_MIX, SidebarWidth, TITLE_FMT, "Mix: ");
+
+ if (!chain)
+ {
+@@ -248,9 +283,28 @@ static void draw_envelope (HEADER *msg, char *fcc)
+ {
+ draw_sidebar (MENU_COMPOSE);
+ draw_envelope_addr (HDR_FROM, msg->env->from);
++#ifdef USE_NNTP
++ if (!option (OPTNEWSSEND))
++ {
++#endif
+ draw_envelope_addr (HDR_TO, msg->env->to);
+ draw_envelope_addr (HDR_CC, msg->env->cc);
+ draw_envelope_addr (HDR_BCC, msg->env->bcc);
++#ifdef USE_NNTP
++ }
++ else
++ {
++ mvprintw (HDR_TO, 0, TITLE_FMT , Prompts[HDR_NEWSGROUPS - 1]);
++ mutt_paddstr (W, NONULL (msg->env->newsgroups));
++ mvprintw (HDR_CC, 0, TITLE_FMT , Prompts[HDR_FOLLOWUPTO - 1]);
++ mutt_paddstr (W, NONULL (msg->env->followup_to));
++ if (option (OPTXCOMMENTTO))
++ {
++ mvprintw (HDR_BCC, 0, TITLE_FMT , Prompts[HDR_XCOMMENTTO - 1]);
++ mutt_paddstr (W, NONULL (msg->env->x_comment_to));
++ }
++ }
++#endif
+ mvprintw (HDR_SUBJECT, SidebarWidth, TITLE_FMT, Prompts[HDR_SUBJECT - 1]);
+ mutt_paddstr (W, NONULL (msg->env->subject));
+ draw_envelope_addr (HDR_REPLYTO, msg->env->reply_to);
+@@ -501,6 +555,12 @@ int mutt_compose_menu (HEADER *msg, /* structure for new message */
+ /* Sort, SortAux could be changed in mutt_index_menu() */
+ int oldSort, oldSortAux;
+ struct stat st;
++#ifdef USE_NNTP
++ int news = 0; /* is it a news article ? */
++
++ if (option (OPTNEWSSEND))
++ news++;
++#endif
+
+ mutt_attach_init (msg->content);
+ idx = mutt_gen_attach_list (msg->content, -1, idx, &idxlen, &idxmax, 0, 1);
+@@ -511,10 +571,18 @@ int mutt_compose_menu (HEADER *msg, /* structure for new message */
+ menu->make_entry = snd_entry;
+ menu->tag = mutt_tag_attach;
+ menu->data = idx;
++#ifdef USE_NNTP
++ if (news)
++ menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_COMPOSE, ComposeNewsHelp);
++ else
++#endif
+ menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_COMPOSE, ComposeHelp);
+
+ while (loop)
+ {
++#ifdef USE_NNTP
++ unset_option (OPTNEWS); /* for any case */
++#endif
+ switch (op = mutt_menuLoop (menu))
+ {
+ case OP_REDRAW:
+@@ -527,17 +595,90 @@ int mutt_compose_menu (HEADER *msg, /* structure for new message */
+ mutt_message_hook (NULL, msg, M_SEND2HOOK);
+ break;
+ case OP_COMPOSE_EDIT_TO:
++#ifdef USE_NNTP
++ if (news)
++ break;
++#endif
+ menu->redraw = edit_address_list (HDR_TO, &msg->env->to);
+ mutt_message_hook (NULL, msg, M_SEND2HOOK);
+ break;
+ case OP_COMPOSE_EDIT_BCC:
++#ifdef USE_NNTP
++ if (news)
++ break;
++#endif
+ menu->redraw = edit_address_list (HDR_BCC, &msg->env->bcc);
+ mutt_message_hook (NULL, msg, M_SEND2HOOK);
+ break;
+ case OP_COMPOSE_EDIT_CC:
++#ifdef USE_NNTP
++ if (news)
++ break;
++#endif
+ menu->redraw = edit_address_list (HDR_CC, &msg->env->cc);
+ mutt_message_hook (NULL, msg, M_SEND2HOOK);
+ break;
++#ifdef USE_NNTP
++ case OP_COMPOSE_EDIT_NEWSGROUPS:
++ if (news)
++ {
++ if (msg->env->newsgroups)
++ strfcpy (buf, msg->env->newsgroups, sizeof (buf));
++ else
++ buf[0] = 0;
++ if (mutt_get_field ("Newsgroups: ", buf, sizeof (buf), 0) == 0 &&
++ buf[0])
++ {
++ FREE (&msg->env->newsgroups);
++ mutt_remove_trailing_ws (buf);
++ msg->env->newsgroups = safe_strdup (mutt_skip_whitespace (buf));
++ move (HDR_TO, HDR_XOFFSET);
++ clrtoeol ();
++ if (msg->env->newsgroups)
++ printw ("%-*.*s", W, W, msg->env->newsgroups);
++ }
++ }
++ break;
++
++ case OP_COMPOSE_EDIT_FOLLOWUP_TO:
++ if (news)
++ {
++ buf[0] = 0;
++ if (msg->env->followup_to)
++ strfcpy (buf, msg->env->followup_to, sizeof (buf));
++ if (mutt_get_field ("Followup-To: ", buf, sizeof (buf), 0) == 0 &&
++ buf[0])
++ {
++ FREE (&msg->env->followup_to);
++ mutt_remove_trailing_ws (buf);
++ msg->env->followup_to = safe_strdup (mutt_skip_whitespace (buf));
++ move (HDR_CC, HDR_XOFFSET);
++ clrtoeol ();
++ if (msg->env->followup_to)
++ printw ("%-*.*s", W, W, msg->env->followup_to);
++ }
++ }
++ break;
++
++ case OP_COMPOSE_EDIT_X_COMMENT_TO:
++ if (news && option (OPTXCOMMENTTO))
++ {
++ buf[0] = 0;
++ if (msg->env->x_comment_to)
++ strfcpy (buf, msg->env->x_comment_to, sizeof (buf));
++ if (mutt_get_field ("X-Comment-To: ", buf, sizeof (buf), 0) == 0 &&
++ buf[0])
++ {
++ FREE (&msg->env->x_comment_to);
++ msg->env->x_comment_to = safe_strdup (buf);
++ move (HDR_BCC, HDR_XOFFSET);
++ clrtoeol ();
++ if (msg->env->x_comment_to)
++ printw ("%-*.*s", W, W, msg->env->x_comment_to);
++ }
++ }
++ break;
++#endif
+ case OP_COMPOSE_EDIT_SUBJECT:
+ if (msg->env->subject)
+ strfcpy (buf, msg->env->subject, sizeof (buf));
+@@ -701,6 +842,9 @@ int mutt_compose_menu (HEADER *msg, /* structure for new message */
+ break;
+
+ case OP_COMPOSE_ATTACH_MESSAGE:
++#ifdef USE_NNTP
++ case OP_COMPOSE_ATTACH_NEWS_MESSAGE:
++#endif
+ {
+ char *prompt;
+ HEADER *h;
+@@ -708,7 +852,22 @@ int mutt_compose_menu (HEADER *msg, /* structure for new message */
+ fname[0] = 0;
+ prompt = _("Open mailbox to attach message from");
+
++#ifdef USE_NNTP
++ unset_option (OPTNEWS);
++ if (op == OP_COMPOSE_ATTACH_NEWS_MESSAGE)
++ {
++ if (!(CurrentNewsSrv = nntp_select_server (NewsServer, 0)))
++ break;
++
++ prompt = _("Open newsgroup to attach message from");
++ set_option (OPTNEWS);
++ }
++#endif
++
+ if (Context)
++#ifdef USE_NNTP
++ if ((op == OP_COMPOSE_ATTACH_MESSAGE) ^ (Context->magic == M_NNTP))
++#endif
+ {
+ strfcpy (fname, NONULL (Context->path), sizeof (fname));
+ mutt_pretty_mailbox (fname, sizeof (fname));
+@@ -717,6 +876,11 @@ int mutt_compose_menu (HEADER *msg, /* structure for new message */
+ if (mutt_enter_fname (prompt, fname, sizeof (fname), &menu->redraw, 1) == -1 || !fname[0])
+ break;
+
++#ifdef USE_NNTP
++ if (option (OPTNEWS))
++ nntp_expand_path (fname, sizeof (fname), &CurrentNewsSrv->conn->account);
++ else
++#endif
+ mutt_expand_path (fname, sizeof (fname));
+ #ifdef USE_IMAP
+ if (!mx_is_imap (fname))
+@@ -724,6 +888,9 @@ int mutt_compose_menu (HEADER *msg, /* structure for new message */
+ #ifdef USE_POP
+ if (!mx_is_pop (fname))
+ #endif
++#ifdef USE_NNTP
++ if (!mx_is_nntp (fname) && !option (OPTNEWS))
++#endif
+ /* check to make sure the file exists and is readable */
+ if (access (fname, R_OK) == -1)
+ {
+diff --git a/configure.ac b/configure.ac
+index 2d57b76..86bd542 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -592,6 +592,15 @@ AC_ARG_ENABLE(imap, AS_HELP_STRING([--enable-imap],[Enable IMAP support]),
+ ])
+ AM_CONDITIONAL(BUILD_IMAP, test x$need_imap = xyes)
+
++AC_ARG_ENABLE(nntp, AC_HELP_STRING([--enable-nntp],[Enable NNTP support]),
++[ if test x$enableval = xyes ; then
++ AC_DEFINE(USE_NNTP,1,[ Define if you want support for the NNTP protocol. ])
++ MUTT_LIB_OBJECTS="$MUTT_LIB_OBJECTS nntp.o newsrc.o"
++ need_nntp="yes"
++ need_socket="yes"
++ fi
++])
++
+ AC_ARG_ENABLE(smtp, AS_HELP_STRING([--enable-smtp],[include internal SMTP relay support]),
+ [if test $enableval = yes; then
+ AC_DEFINE(USE_SMTP, 1, [Include internal SMTP relay support])
+@@ -599,7 +608,7 @@ AC_ARG_ENABLE(smtp, AS_HELP_STRING([--enable-smtp],[include internal SMTP relay
+ need_socket="yes"
+ fi])
+
+-if test x"$need_imap" = xyes -o x"$need_pop" = xyes ; then
++if test x"$need_imap" = xyes -o x"$need_pop" = xyes -o x"$need_nntp" = xyes ; then
+ MUTT_LIB_OBJECTS="$MUTT_LIB_OBJECTS bcache.o"
+ fi
+
+diff --git a/curs_main.c b/curs_main.c
+index 5c58f1c..8cec507 100644
+--- a/curs_main.c
++++ b/curs_main.c
+@@ -22,6 +22,7 @@
+
+ #include "mutt.h"
+ #include "mutt_curses.h"
++#include "mx.h"
+ #include "mutt_menu.h"
+ #include "mailbox.h"
+ #include "mapping.h"
+@@ -40,6 +41,10 @@
+
+ #include "mutt_crypt.h"
+
++#ifdef USE_NNTP
++#include "nntp.h"
++#endif
++
+
+ #include <ctype.h>
+ #include <stdlib.h>
+@@ -428,12 +433,27 @@ static const struct mapping_t IndexHelp[] = {
+ { NULL, 0 }
+ };
+
++#ifdef USE_NNTP
++struct mapping_t IndexNewsHelp[] = {
++ { N_("Quit"), OP_QUIT },
++ { N_("Del"), OP_DELETE },
++ { N_("Undel"), OP_UNDELETE },
++ { N_("Save"), OP_SAVE },
++ { N_("Post"), OP_POST },
++ { N_("Followup"), OP_FOLLOWUP },
++ { N_("Catchup"), OP_CATCHUP },
++ { N_("Help"), OP_HELP },
++ { NULL, 0 }
++};
++#endif
++
+ /* This function handles the message index window as well as commands returned
+ * from the pager (MENU_PAGER).
+ */
+ int mutt_index_menu (void)
+ {
+ char buf[LONG_STRING], helpstr[LONG_STRING];
++ int flags;
+ int op = OP_NULL;
+ int done = 0; /* controls when to exit the "event" loop */
+ int i = 0, j;
+@@ -454,7 +474,11 @@ int mutt_index_menu (void)
+ menu->make_entry = index_make_entry;
+ menu->color = index_color;
+ menu->current = ci_first_message ();
+- menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_MAIN, IndexHelp);
++ menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_MAIN,
++#ifdef USE_NNTP
++ (Context && (Context->magic == M_NNTP)) ? IndexNewsHelp :
++#endif
++ IndexHelp);
+
+ if (!attach_msg)
+ mutt_buffy_check(1); /* force the buffy check after we enter the folder */
+@@ -710,6 +734,9 @@ int mutt_index_menu (void)
+ mutt_curs_set (1); /* fallback from the pager */
+ }
+
++#ifdef USE_NNTP
++ unset_option (OPTNEWS); /* for any case */
++#endif
+ switch (op)
+ {
+
+@@ -760,6 +787,161 @@ int mutt_index_menu (void)
+ menu_current_bottom (menu);
+ break;
+
++#ifdef USE_NNTP
++ case OP_GET_PARENT:
++ CHECK_MSGCOUNT;
++ CHECK_VISIBLE;
++
++ case OP_GET_MESSAGE:
++ CHECK_IN_MAILBOX;
++ CHECK_READONLY;
++ CHECK_ATTACH;
++ if (Context->magic == M_NNTP)
++ {
++ HEADER *hdr;
++
++ if (op == OP_GET_MESSAGE)
++ {
++ buf[0] = 0;
++ if (mutt_get_field (_("Enter Message-Id: "),
++ buf, sizeof (buf), 0) != 0 || !buf[0])
++ break;
++ }
++ else
++ {
++ LIST *ref = CURHDR->env->references;
++ if (!ref)
++ {
++ mutt_error _("Article has no parent reference.");
++ break;
++ }
++ strfcpy (buf, ref->data, sizeof (buf));
++ }
++ if (!Context->id_hash)
++ Context->id_hash = mutt_make_id_hash (Context);
++ hdr = hash_find (Context->id_hash, buf);
++ if (hdr)
++ {
++ if (hdr->virtual != -1)
++ {
++ menu->current = hdr->virtual;
++ menu->redraw = REDRAW_MOTION_RESYNCH;
++ }
++ else if (hdr->collapsed)
++ {
++ mutt_uncollapse_thread (Context, hdr);
++ mutt_set_virtual (Context);
++ menu->current = hdr->virtual;
++ menu->redraw = REDRAW_MOTION_RESYNCH;
++ }
++ else
++ mutt_error _("Message is not visible in limited view.");
++ }
++ else
++ {
++ int rc;
++
++ mutt_message (_("Fetching %s from server..."), buf);
++ rc = nntp_check_msgid (Context, buf);
++ if (rc == 0)
++ {
++ hdr = Context->hdrs[Context->msgcount - 1];
++ mutt_sort_headers (Context, 0);
++ menu->current = hdr->virtual;
++ menu->redraw = REDRAW_FULL;
++ }
++ else if (rc > 0)
++ mutt_error (_("Article %s not found on the server."), buf);
++ }
++ }
++ break;
++
++ case OP_GET_CHILDREN:
++ case OP_RECONSTRUCT_THREAD:
++ CHECK_MSGCOUNT;
++ CHECK_VISIBLE;
++ CHECK_READONLY;
++ CHECK_ATTACH;
++ if (Context->magic == M_NNTP)
++ {
++ int oldmsgcount = Context->msgcount;
++ int oldindex = CURHDR->index;
++ int rc = 0;
++
++ if (!CURHDR->env->message_id)
++ {
++ mutt_error _("No Message-Id. Unable to perform operation.");
++ break;
++ }
++
++ mutt_message _("Fetching message headers...");
++ if (!Context->id_hash)
++ Context->id_hash = mutt_make_id_hash (Context);
++ strfcpy (buf, CURHDR->env->message_id, sizeof (buf));
++
++ /* trying to find msgid of the root message */
++ if (op == OP_RECONSTRUCT_THREAD)
++ {
++ LIST *ref = CURHDR->env->references;
++ while (ref)
++ {
++ if (hash_find (Context->id_hash, ref->data) == NULL)
++ {
++ rc = nntp_check_msgid (Context, ref->data);
++ if (rc < 0)
++ break;
++ }
++
++ /* the last msgid in References is the root message */
++ if (!ref->next)
++ strfcpy (buf, ref->data, sizeof (buf));
++ ref = ref->next;
++ }
++ }
++
++ /* fetching all child messages */
++ if (rc >= 0)
++ rc = nntp_check_children (Context, buf);
++
++ /* at least one message has been loaded */
++ if (Context->msgcount > oldmsgcount)
++ {
++ HEADER *hdr;
++ int i, quiet = Context->quiet;
++
++ if (rc < 0)
++ Context->quiet = 1;
++ mutt_sort_headers (Context, (op == OP_RECONSTRUCT_THREAD));
++ Context->quiet = quiet;
++
++ /* if the root message was retrieved, move to it */
++ hdr = hash_find (Context->id_hash, buf);
++ if (hdr)
++ menu->current = hdr->virtual;
++
++ /* try to restore old position */
++ else
++ {
++ for (i = 0; i < Context->msgcount; i++)
++ {
++ if (Context->hdrs[i]->index == oldindex)
++ {
++ menu->current = Context->hdrs[i]->virtual;
++ /* as an added courtesy, recenter the menu
++ * with the current entry at the middle of the screen */
++ menu_check_recenter (menu);
++ menu_current_middle (menu);
++ }
++ }
++ }
++ menu->redraw = REDRAW_FULL;
++ }
++ else if (rc >= 0)
++ mutt_error _("No deleted messages found in the thread.");
++ }
++ break;
++#endif
++
+ case OP_JUMP:
+
+ CHECK_MSGCOUNT;
+@@ -856,11 +1038,33 @@ int mutt_index_menu (void)
+ break;
+
+ case OP_MAIN_LIMIT:
++ case OP_TOGGLE_READ:
+
+ CHECK_IN_MAILBOX;
+ menu->oldcurrent = (Context->vcount && menu->current >= 0 && menu->current < Context->vcount) ?
+ CURHDR->index : -1;
+- if (mutt_pattern_func (M_LIMIT, _("Limit to messages matching: ")) == 0)
++ if (op == OP_TOGGLE_READ)
++ {
++ char buf[LONG_STRING];
++
++ if (!Context->pattern || strncmp (Context->pattern, "!~R!~D~s", 8) != 0)
++ {
++ snprintf (buf, sizeof (buf), "!~R!~D~s%s",
++ Context->pattern ? Context->pattern : ".*");
++ set_option (OPTHIDEREAD);
++ }
++ else
++ {
++ strfcpy (buf, Context->pattern + 8, sizeof(buf));
++ if (!*buf || strncmp (buf, ".*", 2) == 0)
++ snprintf (buf, sizeof(buf), "~A");
++ unset_option (OPTHIDEREAD);
++ }
++ FREE (&Context->pattern);
++ Context->pattern = safe_strdup (buf);
++ }
++ if ((op == OP_TOGGLE_READ && mutt_pattern_func (M_LIMIT, NULL) == 0) ||
++ mutt_pattern_func (M_LIMIT, _("Limit to messages matching: ")) == 0)
+ {
+ if (menu->oldcurrent >= 0)
+ {
+@@ -1103,15 +1307,22 @@ int mutt_index_menu (void)
+ case OP_SIDEBAR_OPEN:
+ case OP_MAIN_CHANGE_FOLDER:
+ case OP_MAIN_NEXT_UNREAD_MAILBOX:
+-
+- if (attach_msg)
+- op = OP_MAIN_CHANGE_FOLDER_READONLY;
+-
+- /* fallback to the readonly case */
+-
+ case OP_MAIN_CHANGE_FOLDER_READONLY:
++#ifdef USE_NNTP
++ case OP_MAIN_CHANGE_GROUP:
++ case OP_MAIN_CHANGE_GROUP_READONLY:
++ unset_option (OPTNEWS);
++#endif
++ if (attach_msg || option (OPTREADONLY) ||
++#ifdef USE_NNTP
++ op == OP_MAIN_CHANGE_GROUP_READONLY ||
++#endif
++ op == OP_MAIN_CHANGE_FOLDER_READONLY)
++ flags = M_READONLY;
++ else
++ flags = 0;
+
+- if ((op == OP_MAIN_CHANGE_FOLDER_READONLY) || option (OPTREADONLY))
++ if (flags)
+ cp = _("Open mailbox in read-only mode");
+ else
+ cp = _("Open mailbox");
+@@ -1130,6 +1341,22 @@ int mutt_index_menu (void)
+ }
+ else
+ {
++#ifdef USE_NNTP
++ if (op == OP_MAIN_CHANGE_GROUP ||
++ op == OP_MAIN_CHANGE_GROUP_READONLY)
++ {
++ set_option (OPTNEWS);
++ CurrentNewsSrv = nntp_select_server (NewsServer, 0);
++ if (!CurrentNewsSrv)
++ break;
++ if (flags)
++ cp = _("Open newsgroup in read-only mode");
++ else
++ cp = _("Open newsgroup");
++ nntp_buffy (buf, sizeof (buf));
++ }
++ else
++#endif
+ mutt_buffy (buf, sizeof (buf));
+
+ if ( op == OP_SIDEBAR_OPEN ) {
+@@ -1153,6 +1380,14 @@ int mutt_index_menu (void)
+ }
+ }
+
++#ifdef USE_NNTP
++ if (option (OPTNEWS))
++ {
++ unset_option (OPTNEWS);
++ nntp_expand_path (buf, sizeof (buf), &CurrentNewsSrv->conn->account);
++ }
++ else
++#endif
+ mutt_expand_path (buf, sizeof (buf));
+ set_curbuffy(buf);
+ if (mx_get_magic (buf) <= 0)
+@@ -1200,15 +1435,18 @@ int mutt_index_menu (void)
+ CurrentMenu = MENU_MAIN;
+ mutt_folder_hook (buf);
+
+- if ((Context = mx_open_mailbox (buf,
+- (option (OPTREADONLY) || op == OP_MAIN_CHANGE_FOLDER_READONLY) ?
+- M_READONLY : 0, NULL)) != NULL)
++ if ((Context = mx_open_mailbox (buf, flags, NULL)) != NULL)
+ {
+ menu->current = ci_first_message ();
+ }
+ else
+ menu->current = 0;
+
++#ifdef USE_NNTP
++ /* mutt_buffy_check() must be done with mail-reader mode! */
++ menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_MAIN,
++ (Context && (Context->magic == M_NNTP)) ? IndexNewsHelp : IndexHelp);
++#endif
+ mutt_clear_error ();
+ mutt_buffy_check(1); /* force the buffy check after we have changed
+ the folder */
+@@ -1277,6 +1515,7 @@ int mutt_index_menu (void)
+ CHECK_MSGCOUNT;
+ CHECK_VISIBLE;
+ CHECK_READONLY;
++ CHECK_ACL(M_ACL_WRITE, _("break thread"));
+
+ if ((Sort & SORT_MASK) != SORT_THREADS)
+ mutt_error _("Threading is not enabled.");
+@@ -1311,7 +1550,7 @@ int mutt_index_menu (void)
+ CHECK_MSGCOUNT;
+ CHECK_VISIBLE;
+ CHECK_READONLY;
+- CHECK_ACL(M_ACL_DELETE, _("link threads"));
++ CHECK_ACL(M_ACL_WRITE, _("link threads"));
+
+ if ((Sort & SORT_MASK) != SORT_THREADS)
+ mutt_error _("Threading is not enabled.");
+@@ -1932,6 +2171,20 @@ int mutt_index_menu (void)
+ }
+ break;
+
++#ifdef USE_NNTP
++ case OP_CATCHUP:
++ CHECK_MSGCOUNT;
++ CHECK_READONLY;
++ CHECK_ATTACH
++ if (Context && Context->magic == M_NNTP)
++ {
++ NNTP_DATA *nntp_data = Context->data;
++ if (mutt_newsgroup_catchup (nntp_data->nserv, nntp_data->group))
++ menu->redraw = REDRAW_INDEX | REDRAW_STATUS;
++ }
++ break;
++#endif
++
+ case OP_DISPLAY_ADDRESS:
+
+ CHECK_MSGCOUNT;
+@@ -2136,6 +2389,39 @@ int mutt_index_menu (void)
+ menu->redraw = REDRAW_FULL;
+ break;
+
++#ifdef USE_NNTP
++ case OP_FOLLOWUP:
++ case OP_FORWARD_TO_GROUP:
++
++ CHECK_MSGCOUNT;
++ CHECK_VISIBLE;
++
++ case OP_POST:
++
++ CHECK_ATTACH;
++ if (op != OP_FOLLOWUP || !CURHDR->env->followup_to ||
++ mutt_strcasecmp (CURHDR->env->followup_to, "poster") ||
++ query_quadoption (OPT_FOLLOWUPTOPOSTER,
++ _("Reply by mail as poster prefers?")) != M_YES)
++ {
++ if (Context && Context->magic == M_NNTP &&
++ !((NNTP_DATA *)Context->data)->allowed &&
++ query_quadoption (OPT_TOMODERATED,
++ _("Posting to this group not allowed, may be moderated. Continue?")) != M_YES)
++ break;
++ if (op == OP_POST)
++ ci_send_message (SENDNEWS, NULL, NULL, Context, NULL);
++ else
++ {
++ CHECK_MSGCOUNT;
++ ci_send_message ((op == OP_FOLLOWUP ? SENDREPLY : SENDFORWARD) |
++ SENDNEWS, NULL, NULL, Context, tag ? NULL : CURHDR);
++ }
++ menu->redraw = REDRAW_FULL;
++ break;
++ }
++#endif
++
+ case OP_REPLY:
+
+ CHECK_ATTACH;
+diff --git a/doc/Muttrc b/doc/Muttrc
+index bf0e6d0..e03da88 100644
+--- a/doc/Muttrc
++++ b/doc/Muttrc
+@@ -240,6 +240,28 @@ attachments -I message/external-body
+ # editing the body of an outgoing message.
+ #
+ #
++# set ask_follow_up=no
++#
++# Name: ask_follow_up
++# Type: boolean
++# Default: no
++#
++#
++# If set, Mutt will prompt you for follow-up groups before editing
++# the body of an outgoing message.
++#
++#
++# set ask_x_comment_to=no
++#
++# Name: ask_x_comment_to
++# Type: boolean
++# Default: no
++#
++#
++# If set, Mutt will prompt you for x-comment-to field before editing
++# the body of an outgoing message.
++#
++#
+ # set assumed_charset=""
+ #
+ # Name: assumed_charset
+@@ -445,6 +467,17 @@ attachments -I message/external-body
+ # visual terminals don't permit making the cursor invisible.
+ #
+ #
++# set catchup_newsgroup=ask-yes
++#
++# Name: catchup_newsgroup
++# Type: quadoption
++# Default: ask-yes
++#
++#
++# If this variable is set, Mutt will mark all articles in newsgroup
++# as read when you quit the newsgroup (catchup newsgroup).
++#
++#
+ # set certificate_file="~/.mutt_certificates"
+ #
+ # Name: certificate_file
+@@ -1131,6 +1164,19 @@ attachments -I message/external-body
+ # of the same email for you.
+ #
+ #
++# set followup_to_poster=ask-yes
++#
++# Name: followup_to_poster
++# Type: quadoption
++# Default: ask-yes
++#
++#
++# If this variable is set and the keyword "poster" is present in
++# Followup-To header, follow-up to newsgroup function is not
++# permitted. The message will be mailed to the submitter of the
++# message via mail.
++#
++#
+ # set force_name=no
+ #
+ # Name: force_name
+@@ -1241,6 +1287,27 @@ attachments -I message/external-body
+ # ``Franklin'' to ``Franklin, Steve''.
+ #
+ #
++# set group_index_format="%4C %M%N %5s %-45.45f %d"
++#
++# Name: group_index_format
++# Type: string
++# Default: "%4C %M%N %5s %-45.45f %d"
++#
++#
++# This variable allows you to customize the newsgroup browser display to
++# your personal taste. This string is similar to ``index_format'', but
++# has its own set of printf()-like sequences:
++# %C current newsgroup number
++# %d description of newsgroup (becomes from server)
++# %f newsgroup name
++# %M - if newsgroup not allowed for direct post (moderated for example)
++# %N N if newsgroup is new, u if unsubscribed, blank otherwise
++# %n number of new articles in newsgroup
++# %s number of unread articles in newsgroup
++# %>X right justify the rest of the string and pad with character "X"
++# %|X pad to the end of the line with character "X"
++#
++#
+ # set hdrs=yes
+ #
+ # Name: hdrs
+@@ -1789,6 +1856,7 @@ attachments -I message/external-body
+ # %E number of messages in current thread
+ # %f sender (address + real name), either From: or Return-Path:
+ # %F author name, or recipient name if the message is from you
++# %g newsgroup name (if compiled with NNTP support)
+ # %H spam attribute(s) of this message
+ # %i message-id of the current message
+ # %l number of lines in the message (does not work with maildir,
+@@ -1804,12 +1872,14 @@ attachments -I message/external-body
+ # stashed the message: list name or recipient name
+ # if not sent to a list
+ # %P progress indicator for the built-in pager (how much of the file has been displayed)
++# %R ``X-Comment-To:'' field (if present and compiled with NNTP support)
+ # %s subject of the message
+ # %S status of the message (``N''/``D''/``d''/``!''/``r''/*)
+ # %t ``To:'' field (recipients)
+ # %T the appropriate character from the $to_chars string
+ # %u user (login) name of the author
+ # %v first name of the author, or the recipient if the message is from you
++# %W name of organization of author (``Organization:'' field)
+ # %X number of attachments
+ # (please see the ``attachments'' section for possible speed effects)
+ # %y ``X-Label:'' field, if present
+@@ -1845,6 +1915,27 @@ attachments -I message/external-body
+ # ``save-hook'', ``fcc-hook'' and ``fcc-save-hook'', too.
+ #
+ #
++# set inews=""
++#
++# Name: inews
++# Type: path
++# Default: ""
++#
++#
++# If set, specifies the program and arguments used to deliver news posted
++# by Mutt. Otherwise, mutt posts article using current connection to
++# news server. The following printf-style sequence is understood:
++# %a account url
++# %p port
++# %P port if specified
++# %s news server name
++# %S url schema
++# %u username
++#
++#
++# Example: set inews="/usr/local/bin/inews -hS"
++#
++#
+ # set ispell="ispell"
+ #
+ # Name: ispell
+@@ -2214,6 +2305,18 @@ attachments -I message/external-body
+ # be attached to the newly composed message if this option is set.
+ #
+ #
++# set mime_subject=yes
++#
++# Name: mime_subject
++# Type: boolean
++# Default: yes
++#
++#
++# If unset, 8-bit ``subject:'' line in article header will not be
++# encoded according to RFC2047 to base64. This is useful when message
++# is Usenet article, because MIME for news is nonstandard feature.
++#
++#
+ # set mix_entry_format="%4n %c %-16s %a"
+ #
+ # Name: mix_entry_format
+@@ -2280,6 +2383,144 @@ attachments -I message/external-body
+ # See also $read_inc, $write_inc and $net_inc.
+ #
+ #
++# set news_cache_dir="~/.mutt"
++#
++# Name: news_cache_dir
++# Type: path
++# Default: "~/.mutt"
++#
++#
++# This variable pointing to directory where Mutt will save cached news
++# articles and headers in. If unset, articles and headers will not be
++# saved at all and will be reloaded from the server each time.
++#
++#
++# set news_server=""
++#
++# Name: news_server
++# Type: string
++# Default: ""
++#
++#
++# This variable specifies domain name or address of NNTP server. It
++# defaults to the news server specified in the environment variable
++# $NNTPSERVER or contained in the file /etc/nntpserver. You can also
++# specify username and an alternative port for each news server, ie:
++#
++# [[s]news://][username[:password]@]server[:port]
++#
++#
++# set newsgroups_charset="utf-8"
++#
++# Name: newsgroups_charset
++# Type: string
++# Default: "utf-8"
++#
++#
++# Character set of newsgroups descriptions.
++#
++#
++# set newsrc="~/.newsrc"
++#
++# Name: newsrc
++# Type: path
++# Default: "~/.newsrc"
++#
++#
++# The file, containing info about subscribed newsgroups - names and
++# indexes of read articles. The following printf-style sequence
++# is understood:
++# %a account url
++# %p port
++# %P port if specified
++# %s news server name
++# %S url schema
++# %u username
++#
++#
++# set nntp_authenticators=""
++#
++# Name: nntp_authenticators
++# Type: string
++# Default: ""
++#
++#
++# This is a colon-delimited list of authentication methods mutt may
++# attempt to use to log in to a news server, in the order mutt should
++# try them. Authentication methods are either ``user'' or any
++# SASL mechanism, e.g. ``digest-md5'', ``gssapi'' or ``cram-md5''.
++# This option is case-insensitive. If it's unset (the default)
++# mutt will try all available methods, in order from most-secure to
++# least-secure.
++#
++# Example:
++# set nntp_authenticators="digest-md5:user"
++#
++# Note: Mutt will only fall back to other authentication methods if
++# the previous methods are unavailable. If a method is available but
++# authentication fails, mutt will not connect to the IMAP server.
++#
++#
++# set nntp_context=1000
++#
++# Name: nntp_context
++# Type: number
++# Default: 1000
++#
++#
++# This variable defines number of articles which will be in index when
++# newsgroup entered. If active newsgroup have more articles than this
++# number, oldest articles will be ignored. Also controls how many
++# articles headers will be saved in cache when you quit newsgroup.
++#
++#
++# set nntp_load_description=yes
++#
++# Name: nntp_load_description
++# Type: boolean
++# Default: yes
++#
++#
++# This variable controls whether or not descriptions for each newsgroup
++# must be loaded when newsgroup is added to list (first time list
++# loading or new newsgroup adding).
++#
++#
++# set nntp_user=""
++#
++# Name: nntp_user
++# Type: string
++# Default: ""
++#
++#
++# Your login name on the NNTP server. If unset and NNTP server requires
++# authentification, Mutt will prompt you for your account name when you
++# connect to news server.
++#
++#
++# set nntp_pass=""
++#
++# Name: nntp_pass
++# Type: string
++# Default: ""
++#
++#
++# Your password for NNTP account.
++#
++#
++# set nntp_poll=60
++#
++# Name: nntp_poll
++# Type: number
++# Default: 60
++#
++#
++# The time in seconds until any operations on newsgroup except post new
++# article will cause recheck for new news. If set to 0, Mutt will
++# recheck newsgroup on each operation in index (stepping, read article,
++# etc.).
++#
++#
+ # set pager="builtin"
+ #
+ # Name: pager
+@@ -2997,6 +3238,19 @@ attachments -I message/external-body
+ # string after the inclusion of a message which is being replied to.
+ #
+ #
++# set post_moderated=ask-yes
++#
++# Name: post_moderated
++# Type: quadoption
++# Default: ask-yes
++#
++#
++# If set to yes, Mutt will post article to newsgroup that have
++# not permissions to posting (e.g. moderated). Note: if news server
++# does not support posting to that newsgroup or totally read-only, that
++# posting will not have an effect.
++#
++#
+ # set postpone=ask-yes
+ #
+ # Name: postpone
+@@ -3605,6 +3859,41 @@ attachments -I message/external-body
+ # shell from /etc/passwd is used.
+ #
+ #
++# set save_unsubscribed=no
++#
++# Name: save_unsubscribed
++# Type: boolean
++# Default: no
++#
++#
++# When set, info about unsubscribed newsgroups will be saved into
++# ``newsrc'' file and into cache.
++#
++#
++# set show_new_news=yes
++#
++# Name: show_new_news
++# Type: boolean
++# Default: yes
++#
++#
++# If set, news server will be asked for new newsgroups on entering
++# the browser. Otherwise, it will be done only once for a news server.
++# Also controls whether or not number of new articles of subscribed
++# newsgroups will be then checked.
++#
++#
++# set show_only_unread=no
++#
++# Name: show_only_unread
++# Type: boolean
++# Default: no
++#
++#
++# If set, only subscribed newsgroups that contain unread articles
++# will be displayed in browser.
++#
++#
+ # set sig_dashes=yes
+ #
+ # Name: sig_dashes
+@@ -4851,3 +5140,14 @@ attachments -I message/external-body
+ # ``tuning'' section of the manual for performance considerations.
+ #
+ #
++# set x_comment_to=no
++#
++# Name: x_comment_to
++# Type: boolean
++# Default: no
++#
++#
++# If set, Mutt will add ``X-Comment-To:'' field (that contains full
++# name of original article author) to article that followuped to newsgroup.
++#
++#
+diff --git a/doc/manual.xml.head b/doc/manual.xml.head
+index 4366758..5b523ce 100644
+--- a/doc/manual.xml.head
++++ b/doc/manual.xml.head
+@@ -1694,6 +1694,26 @@ See also the <link linkend="postpone">$postpone</link> quad-option.
+
+ </sect1>
+
++<sect1 id="nntp">
++<title>Reading news via NNTP</title>
++
++<para>
++If compiled with <emphasis>--enable-nntp</emphasis> option, Mutt can
++read news from news server via NNTP. You can open a newsgroup with
++function ``change-newsgroup'' (default: ``i''). Default news server
++can be obtained from <literal>$NNTPSERVER</literal> environment
++variable or from <literal>/etc/nntpserver</literal> file. Like other
++news readers, info about subscribed newsgroups is saved in file by
++<link linkend="newsrc">$newsrc</link> variable. The variable <link
++linkend="news-cache-dir">$news_cache_dir</link> can be used to point
++to a directory. Mutt will create a hierarchy of subdirectories named
++like the account and newsgroup the cache is for. Also the hierarchy
++is used to store header cache if Mutt was compiled with <link
++linkend="header-caching">header cache</link> support.
++</para>
++
++</sect1>
++
+ </chapter>
+
+ <chapter id="configuration">
+diff --git a/doc/mutt.man b/doc/mutt.man
+index 718f87a..c4f44d1 100644
+--- a/doc/mutt.man
++++ b/doc/mutt.man
+@@ -23,8 +23,8 @@ mutt \- The Mutt Mail User Agent
+ .SH SYNOPSIS
+ .PP
+ .B mutt
+-[\-nRyzZ]
+-[\-e \fIcmd\fP] [\-F \fIfile\fP] [\-m \fItype\fP] [\-f \fIfile\fP]
++[\-GnRyzZ]
++[\-e \fIcmd\fP] [\-F \fIfile\fP] [\-g \fIserver\fP] [\-m \fItype\fP] [\-f \fIfile\fP]
+ .PP
+ .B mutt
+ [\-nx]
+@@ -101,6 +101,10 @@ files.
+ Specify which mailbox to load.
+ .IP "-F \fImuttrc\fP"
+ Specify an initialization file to read instead of ~/.muttrc
++.IP "-g \fIserver\fP"
++Start Mutt with a listing of subscribed newsgroups at specified news server.
++.IP "-G"
++Start Mutt with a listing of subscribed newsgroups.
+ .IP "-h"
+ Display help.
+ .IP "-H \fIdraft\fP"
+diff --git a/functions.h b/functions.h
+index 363b4d5..66f3345 100644
+--- a/functions.h
++++ b/functions.h
+@@ -88,6 +88,10 @@ const struct binding_t OpMain[] = { /* map: index */
+ { "break-thread", OP_MAIN_BREAK_THREAD, "#" },
+ { "change-folder", OP_MAIN_CHANGE_FOLDER, "c" },
+ { "change-folder-readonly", OP_MAIN_CHANGE_FOLDER_READONLY, "\033c" },
++#ifdef USE_NNTP
++ { "change-newsgroup", OP_MAIN_CHANGE_GROUP, "i" },
++ { "change-newsgroup-readonly",OP_MAIN_CHANGE_GROUP_READONLY, "\033i" },
++#endif
+ { "next-unread-mailbox", OP_MAIN_NEXT_UNREAD_MAILBOX, NULL },
+ { "collapse-thread", OP_MAIN_COLLAPSE_THREAD, "\033v" },
+ { "collapse-all", OP_MAIN_COLLAPSE_ALL, "\033V" },
+@@ -101,7 +105,15 @@ const struct binding_t OpMain[] = { /* map: index */
+ { "edit", OP_EDIT_MESSAGE, "e" },
+ { "edit-type", OP_EDIT_TYPE, "\005" },
+ { "forward-message", OP_FORWARD_MESSAGE, "f" },
+- { "flag-message", OP_FLAG_MESSAGE, "F" },
++#ifdef USE_NNTP
++ { "forward-to-group", OP_FORWARD_TO_GROUP, "\033F" },
++ { "followup-message", OP_FOLLOWUP, "F" },
++ { "get-children", OP_GET_CHILDREN, NULL },
++ { "get-message", OP_GET_MESSAGE, "\007" },
++ { "get-parent", OP_GET_PARENT, "\033G" },
++ { "reconstruct-thread", OP_RECONSTRUCT_THREAD, NULL },
++#endif
++ { "flag-message", OP_FLAG_MESSAGE, "\033f" },
+ { "group-reply", OP_GROUP_REPLY, "g" },
+ #ifdef USE_POP
+ { "fetch-mail", OP_MAIN_FETCH_MAIL, "G" },
+@@ -129,6 +141,9 @@ const struct binding_t OpMain[] = { /* map: index */
+ { "sort-mailbox", OP_SORT, "o" },
+ { "sort-reverse", OP_SORT_REVERSE, "O" },
+ { "print-message", OP_PRINT, "p" },
++#ifdef USE_NNTP
++ { "post-message", OP_POST, "P" },
++#endif
+ { "previous-thread", OP_MAIN_PREV_THREAD, "\020" },
+ { "previous-subthread", OP_MAIN_PREV_SUBTHREAD, "\033p" },
+ { "recall-message", OP_RECALL_MESSAGE, "R" },
+@@ -148,6 +163,10 @@ const struct binding_t OpMain[] = { /* map: index */
+ { "show-version", OP_VERSION, "V" },
+ { "set-flag", OP_MAIN_SET_FLAG, "w" },
+ { "clear-flag", OP_MAIN_CLEAR_FLAG, "W" },
++ { "toggle-read", OP_TOGGLE_READ, "X" },
++#ifdef USE_NNTP
++ { "catchup", OP_CATCHUP, "y" },
++#endif
+ { "display-message", OP_DISPLAY_MESSAGE, M_ENTER_S },
+ { "buffy-list", OP_BUFFY_LIST, "." },
+ { "sync-mailbox", OP_MAIN_SYNC_FOLDER, "$" },
+@@ -159,7 +178,7 @@ const struct binding_t OpMain[] = { /* map: index */
+ { "previous-new-then-unread", OP_MAIN_PREV_NEW_THEN_UNREAD, "\033\t" },
+ { "next-unread", OP_MAIN_NEXT_UNREAD, NULL },
+ { "previous-unread", OP_MAIN_PREV_UNREAD, NULL },
+- { "parent-message", OP_MAIN_PARENT_MESSAGE, "P" },
++ { "parent-message", OP_MAIN_PARENT_MESSAGE, NULL },
+
+
+ { "extract-keys", OP_EXTRACT_KEYS, "\013" },
+@@ -186,6 +205,10 @@ const struct binding_t OpPager[] = { /* map: pager */
+ { "bounce-message", OP_BOUNCE_MESSAGE, "b" },
+ { "change-folder", OP_MAIN_CHANGE_FOLDER, "c" },
+ { "change-folder-readonly", OP_MAIN_CHANGE_FOLDER_READONLY, "\033c" },
++#ifdef USE_NNTP
++ { "change-newsgroup", OP_MAIN_CHANGE_GROUP, "i" },
++ { "change-newsgroup-readonly",OP_MAIN_CHANGE_GROUP_READONLY, "\033i" },
++#endif
+ { "next-unread-mailbox", OP_MAIN_NEXT_UNREAD_MAILBOX, NULL },
+ { "copy-message", OP_COPY_MESSAGE, "C" },
+ { "decode-copy", OP_DECODE_COPY, "\033C" },
+@@ -196,8 +219,12 @@ const struct binding_t OpPager[] = { /* map: pager */
+ { "clear-flag", OP_MAIN_CLEAR_FLAG, "W" },
+ { "edit", OP_EDIT_MESSAGE, "e" },
+ { "edit-type", OP_EDIT_TYPE, "\005" },
++#ifdef USE_NNTP
++ { "followup-message", OP_FOLLOWUP, "F" },
++ { "forward-to-group", OP_FORWARD_TO_GROUP, "\033F" },
++#endif
+ { "forward-message", OP_FORWARD_MESSAGE, "f" },
+- { "flag-message", OP_FLAG_MESSAGE, "F" },
++ { "flag-message", OP_FLAG_MESSAGE, "\033f" },
+ { "group-reply", OP_GROUP_REPLY, "g" },
+ #ifdef USE_IMAP
+ { "imap-fetch-mail", OP_MAIN_IMAP_FETCH, NULL },
+@@ -219,6 +246,9 @@ const struct binding_t OpPager[] = { /* map: pager */
+ { "sort-mailbox", OP_SORT, "o" },
+ { "sort-reverse", OP_SORT_REVERSE, "O" },
+ { "print-message", OP_PRINT, "p" },
++#ifdef USE_NNTP
++ { "post-message", OP_POST, "P" },
++#endif
+ { "previous-thread", OP_MAIN_PREV_THREAD, "\020" },
+ { "previous-subthread",OP_MAIN_PREV_SUBTHREAD, "\033p" },
+ { "purge-message", OP_PURGE_MESSAGE, NULL },
+@@ -267,7 +297,7 @@ const struct binding_t OpPager[] = { /* map: pager */
+ { "half-down", OP_HALF_DOWN, NULL },
+ { "previous-line", OP_PREV_LINE, NULL },
+ { "bottom", OP_PAGER_BOTTOM, NULL },
+- { "parent-message", OP_MAIN_PARENT_MESSAGE, "P" },
++ { "parent-message", OP_MAIN_PARENT_MESSAGE, NULL },
+
+
+
+@@ -295,6 +325,10 @@ const struct binding_t OpAttach[] = { /* map: attachment */
+ { "bounce-message", OP_BOUNCE_MESSAGE, "b" },
+ { "display-toggle-weed", OP_DISPLAY_HEADERS, "h" },
+ { "edit-type", OP_EDIT_TYPE, "\005" },
++#ifdef USE_NNTP
++ { "followup-message", OP_FOLLOWUP, "F" },
++ { "forward-to-group", OP_FORWARD_TO_GROUP, "\033F" },
++#endif
+ { "print-entry", OP_PRINT, "p" },
+ { "save-entry", OP_SAVE, "s" },
+ { "pipe-entry", OP_PIPE, "|" },
+@@ -320,6 +354,7 @@ const struct binding_t OpAttach[] = { /* map: attachment */
+ const struct binding_t OpCompose[] = { /* map: compose */
+ { "attach-file", OP_COMPOSE_ATTACH_FILE, "a" },
+ { "attach-message", OP_COMPOSE_ATTACH_MESSAGE, "A" },
++ { "attach-news-message",OP_COMPOSE_ATTACH_NEWS_MESSAGE,"\033a" },
+ { "edit-bcc", OP_COMPOSE_EDIT_BCC, "b" },
+ { "edit-cc", OP_COMPOSE_EDIT_CC, "c" },
+ { "copy-file", OP_SAVE, "C" },
+@@ -339,6 +374,11 @@ const struct binding_t OpCompose[] = { /* map: compose */
+ { "print-entry", OP_PRINT, "l" },
+ { "edit-mime", OP_COMPOSE_EDIT_MIME, "m" },
+ { "new-mime", OP_COMPOSE_NEW_MIME, "n" },
++#ifdef USE_NNTP
++ { "edit-newsgroups", OP_COMPOSE_EDIT_NEWSGROUPS, "N" },
++ { "edit-followup-to", OP_COMPOSE_EDIT_FOLLOWUP_TO, "o" },
++ { "edit-x-comment-to",OP_COMPOSE_EDIT_X_COMMENT_TO, "x" },
++#endif
+ { "postpone-message", OP_COMPOSE_POSTPONE_MESSAGE, "P" },
+ { "edit-reply-to", OP_COMPOSE_EDIT_REPLY_TO, "r" },
+ { "rename-file", OP_COMPOSE_RENAME_FILE, "R" },
+@@ -390,14 +430,25 @@ const struct binding_t OpBrowser[] = { /* map: browser */
+ { "select-new", OP_BROWSER_NEW_FILE, "N" },
+ { "check-new", OP_CHECK_NEW, NULL },
+ { "toggle-mailboxes", OP_TOGGLE_MAILBOXES, "\t" },
++#ifdef USE_NNTP
++ { "reload-active", OP_LOAD_ACTIVE, "g" },
++ { "subscribe-pattern", OP_SUBSCRIBE_PATTERN, "S" },
++ { "unsubscribe-pattern", OP_UNSUBSCRIBE_PATTERN, "U" },
++ { "catchup", OP_CATCHUP, "y" },
++ { "uncatchup", OP_UNCATCHUP, "Y" },
++#endif
+ { "view-file", OP_BROWSER_VIEW_FILE, " " },
+ { "buffy-list", OP_BUFFY_LIST, "." },
+ #ifdef USE_IMAP
+ { "create-mailbox", OP_CREATE_MAILBOX, "C" },
+ { "delete-mailbox", OP_DELETE_MAILBOX, "d" },
+ { "rename-mailbox", OP_RENAME_MAILBOX, "r" },
++#endif
++#if defined USE_IMAP || defined USE_NNTP
+ { "subscribe", OP_BROWSER_SUBSCRIBE, "s" },
+ { "unsubscribe", OP_BROWSER_UNSUBSCRIBE, "u" },
++#endif
++#ifdef USE_IMAP
+ { "toggle-subscribed", OP_BROWSER_TOGGLE_LSUB, "T" },
+ #endif
+ { NULL, 0, NULL }
+diff --git a/globals.h b/globals.h
+index 61765a4..0529f94 100644
+--- a/globals.h
++++ b/globals.h
+@@ -95,6 +95,17 @@ WHERE char *MixEntryFormat;
+ #endif
+
+ WHERE char *Muttrc INITVAL (NULL);
++#ifdef USE_NNTP
++WHERE char *GroupFormat;
++WHERE char *Inews;
++WHERE char *NewsCacheDir;
++WHERE char *NewsServer;
++WHERE char *NewsgroupsCharset;
++WHERE char *NewsRc;
++WHERE char *NntpAuthenticators;
++WHERE char *NntpUser;
++WHERE char *NntpPass;
++#endif
+ WHERE char *Outbox;
+ WHERE char *Pager;
+ WHERE char *PagerFmt;
+@@ -193,6 +204,11 @@ extern unsigned char QuadOptions[];
+
+ WHERE unsigned short Counter INITVAL (0);
+
++#ifdef USE_NNTP
++WHERE short NewsPollTimeout;
++WHERE short NntpContext;
++#endif
++
+ WHERE short ConnectTimeout;
+ WHERE short HistSize;
+ WHERE short MenuContext;
+diff --git a/hash.c b/hash.c
+index 08f7171..983a7fd 100644
+--- a/hash.c
++++ b/hash.c
+@@ -57,6 +57,7 @@ HASH *hash_create (int nelem, int lower)
+ if (nelem == 0)
+ nelem = 2;
+ table->nelem = nelem;
++ table->curnelem = 0;
+ table->table = safe_calloc (nelem, sizeof (struct hash_elem *));
+ if (lower)
+ {
+@@ -71,6 +72,29 @@ HASH *hash_create (int nelem, int lower)
+ return table;
+ }
+
++HASH *hash_resize (HASH *ptr, int nelem, int lower)
++{
++ HASH *table;
++ struct hash_elem *elem, *tmp;
++ int i;
++
++ table = hash_create (nelem, lower);
++
++ for (i = 0; i < ptr->nelem; i++)
++ {
++ for (elem = ptr->table[i]; elem; )
++ {
++ tmp = elem;
++ elem = elem->next;
++ hash_insert (table, tmp->key, tmp->data, 1);
++ FREE (&tmp);
++ }
++ }
++ FREE (&ptr->table);
++ FREE (&ptr);
++ return table;
++}
++
+ /* table hash table to update
+ * key key to hash on
+ * data data to associate with `key'
+@@ -90,6 +114,7 @@ int hash_insert (HASH * table, const char *key, void *data, int allow_dup)
+ {
+ ptr->next = table->table[h];
+ table->table[h] = ptr;
++ table->curnelem++;
+ }
+ else
+ {
+@@ -112,6 +137,7 @@ int hash_insert (HASH * table, const char *key, void *data, int allow_dup)
+ else
+ table->table[h] = ptr;
+ ptr->next = tmp;
++ table->curnelem++;
+ }
+ return h;
+ }
+@@ -142,6 +168,7 @@ void hash_delete_hash (HASH * table, int hash, const char *key, const void *data
+ if (destroy)
+ destroy (ptr->data);
+ FREE (&ptr);
++ table->curnelem--;
+
+ ptr = *last;
+ }
+diff --git a/hash.h b/hash.h
+index fb77d0c..9e7df82 100644
+--- a/hash.h
++++ b/hash.h
+@@ -28,7 +28,7 @@ struct hash_elem
+
+ typedef struct
+ {
+- int nelem;
++ int nelem, curnelem;
+ struct hash_elem **table;
+ unsigned int (*hash_string)(const unsigned char *, unsigned int);
+ int (*cmp_string)(const char *, const char *);
+@@ -41,6 +41,7 @@ HASH;
+
+ HASH *hash_create (int nelem, int lower);
+ int hash_insert (HASH * table, const char *key, void *data, int allow_dup);
++HASH *hash_resize (HASH * table, int nelem, int lower);
+ void *hash_find_hash (const HASH * table, int hash, const char *key);
+ void hash_delete_hash (HASH * table, int hash, const char *key, const void *data,
+ void (*destroy) (void *));
+diff --git a/hcache.c b/hcache.c
+index af17932..555e1d1 100644
+--- a/hcache.c
++++ b/hcache.c
+@@ -447,6 +447,12 @@ dump_envelope(ENVELOPE * e, unsigned char *d, int *off, int convert)
+ d = dump_list(e->in_reply_to, d, off, 0);
+ d = dump_list(e->userhdrs, d, off, convert);
+
++#ifdef USE_NNTP
++ d = dump_char(e->xref, d, off, 0);
++ d = dump_char(e->followup_to, d, off, 0);
++ d = dump_char(e->x_comment_to, d, off, convert);
++#endif
++
+ return d;
+ }
+
+@@ -483,6 +489,12 @@ restore_envelope(ENVELOPE * e, const unsigned char *d, int *off, int convert)
+ restore_list(&e->references, d, off, 0);
+ restore_list(&e->in_reply_to, d, off, 0);
+ restore_list(&e->userhdrs, d, off, convert);
++
++#ifdef USE_NNTP
++ restore_char(&e->xref, d, off, 0);
++ restore_char(&e->followup_to, d, off, 0);
++ restore_char(&e->x_comment_to, d, off, convert);
++#endif
+ }
+
+ static int
+diff --git a/hdrline.c b/hdrline.c
+index 21adc28..c05d28a 100644
+--- a/hdrline.c
++++ b/hdrline.c
+@@ -211,6 +211,7 @@ int mutt_user_is_recipient (HEADER *h)
+ * %E = number of messages in current thread
+ * %f = entire from line
+ * %F = like %n, unless from self
++ * %g = newsgroup name (if compiled with NNTP support)
+ * %i = message-id
+ * %l = number of lines in the message
+ * %L = like %F, except `lists' are displayed first
+@@ -219,12 +220,14 @@ int mutt_user_is_recipient (HEADER *h)
+ * %N = score
+ * %O = like %L, except using address instead of name
+ * %P = progress indicator for builtin pager
++ * %R = `x-comment-to:' field (if present and compiled with NNTP support)
+ * %s = subject
+ * %S = short message status (e.g., N/O/D/!/r/-)
+ * %t = `to:' field (recipients)
+ * %T = $to_chars
+ * %u = user (login) name of author
+ * %v = first name of author, unless from self
++ * %W = where user is (organization)
+ * %X = number of MIME attachments
+ * %y = `x-label:' field (if present)
+ * %Y = `x-label:' field (if present, tree unfolded, and != parent's x-label)
+@@ -457,6 +460,12 @@ hdr_format_str (char *dest,
+
+ break;
+
++#ifdef USE_NNTP
++ case 'g':
++ mutt_format_s (dest, destlen, prefix, hdr->env->newsgroups ? hdr->env->newsgroups : "");
++ break;
++#endif
++
+ case 'i':
+ mutt_format_s (dest, destlen, prefix, hdr->env->message_id ? hdr->env->message_id : "<no.id>");
+ break;
+@@ -548,6 +557,15 @@ hdr_format_str (char *dest,
+ strfcpy(dest, NONULL(hfi->pager_progress), destlen);
+ break;
+
++#ifdef USE_NNTP
++ case 'R':
++ if (!optional)
++ mutt_format_s (dest, destlen, prefix, hdr->env->x_comment_to ? hdr->env->x_comment_to : "");
++ else if (!hdr->env->x_comment_to)
++ optional = 0;
++ break;
++#endif
++
+ case 's':
+
+ if (flags & M_FORMAT_TREE && !hdr->collapsed)
+@@ -637,6 +655,13 @@ hdr_format_str (char *dest,
+ mutt_format_s (dest, destlen, prefix, buf2);
+ break;
+
++ case 'W':
++ if (!optional)
++ mutt_format_s (dest, destlen, prefix, hdr->env->organization ? hdr->env->organization : "");
++ else if (!hdr->env->organization)
++ optional = 0;
++ break;
++
+ case 'Z':
+
+ ch = ' ';
+diff --git a/headers.c b/headers.c
+index f701c8e..e817dad 100644
+--- a/headers.c
++++ b/headers.c
+@@ -114,6 +114,9 @@ void mutt_edit_headers (const char *editor,
+ $edit_headers set, we remove References: as they're likely invalid;
+ we can simply compare strings as we don't generate References for
+ multiple Message-Ids in IRT anyways */
++#ifdef USE_NNTP
++ if (!option (OPTNEWSSEND))
++#endif
+ if (msg->env->in_reply_to &&
+ (!n->in_reply_to || mutt_strcmp (n->in_reply_to->data,
+ msg->env->in_reply_to->data) != 0))
+diff --git a/init.c b/init.c
+index 20a66bd..6ab1020 100644
+--- a/init.c
++++ b/init.c
+@@ -3104,6 +3104,28 @@ void mutt_init (int skip_sys_rc, LIST *commands)
+ else
+ Fqdn = safe_strdup(NONULL(Hostname));
+
++#ifdef USE_NNTP
++ {
++ FILE *f;
++ char *i;
++
++ if ((f = safe_fopen (SYSCONFDIR "/nntpserver", "r")))
++ {
++ buffer[0] = '\0';
++ fgets (buffer, sizeof (buffer), f);
++ p = &buffer;
++ SKIPWS (p);
++ i = p;
++ while (*i && (*i != ' ') && (*i != '\t') && (*i != '\r') && (*i != '\n')) i++;
++ *i = '\0';
++ NewsServer = safe_strdup (p);
++ fclose (f);
++ }
++ }
++ if ((p = getenv ("NNTPSERVER")))
++ NewsServer = safe_strdup (p);
++#endif
++
+ if ((p = getenv ("MAIL")))
+ Spoolfile = safe_strdup (p);
+ else if ((p = getenv ("MAILDIR")))
+diff --git a/init.h b/init.h
+index e20a24e..6efadb2 100644
+--- a/init.h
++++ b/init.h
+@@ -176,6 +176,20 @@ struct option_t MuttVars[] = {
+ ** If \fIset\fP, Mutt will prompt you for carbon-copy (Cc) recipients before
+ ** editing the body of an outgoing message.
+ */
++#ifdef USE_NNTP
++ { "ask_follow_up", DT_BOOL, R_NONE, OPTASKFOLLOWUP, 0 },
++ /*
++ ** .pp
++ ** If set, Mutt will prompt you for follow-up groups before editing
++ ** the body of an outgoing message.
++ */
++ { "ask_x_comment_to", DT_BOOL, R_NONE, OPTASKXCOMMENTTO, 0 },
++ /*
++ ** .pp
++ ** If set, Mutt will prompt you for x-comment-to field before editing
++ ** the body of an outgoing message.
++ */
++#endif
+ { "assumed_charset", DT_STR, R_NONE, UL &AssumedCharset, UL 0},
+ /*
+ ** .pp
+@@ -328,6 +342,14 @@ struct option_t MuttVars[] = {
+ ** follow these menus. The option is \fIunset\fP by default because many
+ ** visual terminals don't permit making the cursor invisible.
+ */
++#ifdef USE_NNTP
++ { "catchup_newsgroup", DT_QUAD, R_NONE, OPT_CATCHUP, M_ASKYES },
++ /*
++ ** .pp
++ ** If this variable is \fIset\fP, Mutt will mark all articles in newsgroup
++ ** as read when you quit the newsgroup (catchup newsgroup).
++ */
++#endif
+ #if defined(USE_SSL)
+ { "certificate_file", DT_PATH, R_NONE, UL &SslCertFile, UL "~/.mutt_certificates" },
+ /*
+@@ -815,6 +837,16 @@ struct option_t MuttVars[] = {
+ ** sent to both the list and your address, resulting in two copies
+ ** of the same email for you.
+ */
++#ifdef USE_NNTP
++ { "followup_to_poster", DT_QUAD, R_NONE, OPT_FOLLOWUPTOPOSTER, M_ASKYES },
++ /*
++ ** .pp
++ ** If this variable is \fIset\fP and the keyword "poster" is present in
++ ** \fIFollowup-To\fP header, follow-up to newsgroup function is not
++ ** permitted. The message will be mailed to the submitter of the
++ ** message via mail.
++ */
++#endif
+ { "force_name", DT_BOOL, R_NONE, OPTFORCENAME, 0 },
+ /*
+ ** .pp
+@@ -897,6 +929,26 @@ struct option_t MuttVars[] = {
+ ** a regular expression that will match the whole name so mutt will expand
+ ** ``Franklin'' to ``Franklin, Steve''.
+ */
++#ifdef USE_NNTP
++ { "group_index_format", DT_STR, R_BOTH, UL &GroupFormat, UL "%4C %M%N %5s %-45.45f %d" },
++ /*
++ ** .pp
++ ** This variable allows you to customize the newsgroup browser display to
++ ** your personal taste. This string is similar to ``$index_format'', but
++ ** has its own set of printf()-like sequences:
++ ** .dl
++ ** .dt %C .dd current newsgroup number
++ ** .dt %d .dd description of newsgroup (becomes from server)
++ ** .dt %f .dd newsgroup name
++ ** .dt %M .dd - if newsgroup not allowed for direct post (moderated for example)
++ ** .dt %N .dd N if newsgroup is new, u if unsubscribed, blank otherwise
++ ** .dt %n .dd number of new articles in newsgroup
++ ** .dt %s .dd number of unread articles in newsgroup
++ ** .dt %>X .dd right justify the rest of the string and pad with character "X"
++ ** .dt %|X .dd pad to the end of the line with character "X"
++ ** .de
++ */
++#endif
+ { "hdr_format", DT_SYN, R_NONE, UL "index_format", 0 },
+ /*
+ */
+@@ -1278,6 +1330,7 @@ struct option_t MuttVars[] = {
+ ** .dt %E .dd number of messages in current thread
+ ** .dt %f .dd sender (address + real name), either From: or Return-Path:
+ ** .dt %F .dd author name, or recipient name if the message is from you
++ ** .dt %g .dd newsgroup name (if compiled with NNTP support)
+ ** .dt %H .dd spam attribute(s) of this message
+ ** .dt %i .dd message-id of the current message
+ ** .dt %l .dd number of lines in the message (does not work with maildir,
+@@ -1293,12 +1346,14 @@ struct option_t MuttVars[] = {
+ ** stashed the message: list name or recipient name
+ ** if not sent to a list
+ ** .dt %P .dd progress indicator for the built-in pager (how much of the file has been displayed)
++ ** .dt %R .dd ``X-Comment-To:'' field (if present and compiled with NNTP support)
+ ** .dt %s .dd subject of the message
+ ** .dt %S .dd status of the message (``N''/``D''/``d''/``!''/``r''/\(as)
+ ** .dt %t .dd ``To:'' field (recipients)
+ ** .dt %T .dd the appropriate character from the $$to_chars string
+ ** .dt %u .dd user (login) name of the author
+ ** .dt %v .dd first name of the author, or the recipient if the message is from you
++ ** .dt %W .dd name of organization of author (``Organization:'' field)
+ ** .dt %X .dd number of attachments
+ ** (please see the ``$attachments'' section for possible speed effects)
+ ** .dt %y .dd ``X-Label:'' field, if present
+@@ -1333,6 +1388,25 @@ struct option_t MuttVars[] = {
+ ** Note that these expandos are supported in
+ ** ``$save-hook'', ``$fcc-hook'' and ``$fcc-save-hook'', too.
+ */
++#ifdef USE_NNTP
++ { "inews", DT_PATH, R_NONE, UL &Inews, UL "" },
++ /*
++ ** .pp
++ ** If set, specifies the program and arguments used to deliver news posted
++ ** by Mutt. Otherwise, mutt posts article using current connection to
++ ** news server. The following printf-style sequence is understood:
++ ** .dl
++ ** .dt %a .dd account url
++ ** .dt %p .dd port
++ ** .dt %P .dd port if specified
++ ** .dt %s .dd news server name
++ ** .dt %S .dd url schema
++ ** .dt %u .dd username
++ ** .de
++ ** .pp
++ ** Example: set inews="/usr/local/bin/inews -hS"
++ */
++#endif
+ { "ispell", DT_PATH, R_NONE, UL &Ispell, UL ISPELL },
+ /*
+ ** .pp
+@@ -1567,6 +1641,15 @@ struct option_t MuttVars[] = {
+ ** menu, attachments which cannot be decoded in a reasonable manner will
+ ** be attached to the newly composed message if this option is \fIset\fP.
+ */
++#ifdef USE_NNTP
++ { "mime_subject", DT_BOOL, R_NONE, OPTMIMESUBJECT, 1 },
++ /*
++ ** .pp
++ ** If \fIunset\fP, 8-bit ``subject:'' line in article header will not be
++ ** encoded according to RFC2047 to base64. This is useful when message
++ ** is Usenet article, because MIME for news is nonstandard feature.
++ */
++#endif
+ #ifdef MIXMASTER
+ { "mix_entry_format", DT_STR, R_NONE, UL &MixEntryFormat, UL "%4n %c %-16s %a" },
+ /*
+@@ -1617,6 +1700,100 @@ struct option_t MuttVars[] = {
+ ** See also $$read_inc, $$write_inc and $$net_inc.
+ */
+ #endif
++#ifdef USE_NNTP
++ { "news_cache_dir", DT_PATH, R_NONE, UL &NewsCacheDir, UL "~/.mutt" },
++ /*
++ ** .pp
++ ** This variable pointing to directory where Mutt will save cached news
++ ** articles and headers in. If \fIunset\fP, articles and headers will not be
++ ** saved at all and will be reloaded from the server each time.
++ */
++ { "news_server", DT_STR, R_NONE, UL &NewsServer, 0 },
++ /*
++ ** .pp
++ ** This variable specifies domain name or address of NNTP server. It
++ ** defaults to the news server specified in the environment variable
++ ** $$$NNTPSERVER or contained in the file /etc/nntpserver. You can also
++ ** specify username and an alternative port for each news server, ie:
++ ** .pp
++ ** [[s]news://][username[:password]@]server[:port]
++ */
++ { "newsgroups_charset", DT_STR, R_NONE, UL &NewsgroupsCharset, UL "utf-8" },
++ /*
++ ** .pp
++ ** Character set of newsgroups descriptions.
++ */
++ { "newsrc", DT_PATH, R_NONE, UL &NewsRc, UL "~/.newsrc" },
++ /*
++ ** .pp
++ ** The file, containing info about subscribed newsgroups - names and
++ ** indexes of read articles. The following printf-style sequence
++ ** is understood:
++ ** .dl
++ ** .dt %a .dd account url
++ ** .dt %p .dd port
++ ** .dt %P .dd port if specified
++ ** .dt %s .dd news server name
++ ** .dt %S .dd url schema
++ ** .dt %u .dd username
++ ** .de
++ */
++ { "nntp_authenticators", DT_STR, R_NONE, UL &NntpAuthenticators, UL 0 },
++ /*
++ ** .pp
++ ** This is a colon-delimited list of authentication methods mutt may
++ ** attempt to use to log in to a news server, in the order mutt should
++ ** try them. Authentication methods are either ``user'' or any
++ ** SASL mechanism, e.g. ``digest-md5'', ``gssapi'' or ``cram-md5''.
++ ** This option is case-insensitive. If it's \fIunset\fP (the default)
++ ** mutt will try all available methods, in order from most-secure to
++ ** least-secure.
++ ** .pp
++ ** Example:
++ ** .ts
++ ** set nntp_authenticators="digest-md5:user"
++ ** .te
++ ** .pp
++ ** \fBNote:\fP Mutt will only fall back to other authentication methods if
++ ** the previous methods are unavailable. If a method is available but
++ ** authentication fails, mutt will not connect to the IMAP server.
++ */
++ { "nntp_context", DT_NUM, R_NONE, UL &NntpContext, 1000 },
++ /*
++ ** .pp
++ ** This variable defines number of articles which will be in index when
++ ** newsgroup entered. If active newsgroup have more articles than this
++ ** number, oldest articles will be ignored. Also controls how many
++ ** articles headers will be saved in cache when you quit newsgroup.
++ */
++ { "nntp_load_description", DT_BOOL, R_NONE, OPTLOADDESC, 1 },
++ /*
++ ** .pp
++ ** This variable controls whether or not descriptions for each newsgroup
++ ** must be loaded when newsgroup is added to list (first time list
++ ** loading or new newsgroup adding).
++ */
++ { "nntp_user", DT_STR, R_NONE, UL &NntpUser, UL "" },
++ /*
++ ** .pp
++ ** Your login name on the NNTP server. If \fIunset\fP and NNTP server requires
++ ** authentification, Mutt will prompt you for your account name when you
++ ** connect to news server.
++ */
++ { "nntp_pass", DT_STR, R_NONE, UL &NntpPass, UL "" },
++ /*
++ ** .pp
++ ** Your password for NNTP account.
++ */
++ { "nntp_poll", DT_NUM, R_NONE, UL &NewsPollTimeout, 60 },
++ /*
++ ** .pp
++ ** The time in seconds until any operations on newsgroup except post new
++ ** article will cause recheck for new news. If set to 0, Mutt will
++ ** recheck newsgroup on each operation in index (stepping, read article,
++ ** etc.).
++ */
++#endif
+ { "pager", DT_PATH, R_NONE, UL &Pager, UL "builtin" },
+ /*
+ ** .pp
+@@ -2152,6 +2329,16 @@ struct option_t MuttVars[] = {
+ { "post_indent_str", DT_SYN, R_NONE, UL "post_indent_string", 0 },
+ /*
+ */
++#ifdef USE_NNTP
++ { "post_moderated", DT_QUAD, R_NONE, OPT_TOMODERATED, M_ASKYES },
++ /*
++ ** .pp
++ ** If set to \fIyes\fP, Mutt will post article to newsgroup that have
++ ** not permissions to posting (e.g. moderated). \fBNote:\fP if news server
++ ** does not support posting to that newsgroup or totally read-only, that
++ ** posting will not have an effect.
++ */
++#endif
+ { "postpone", DT_QUAD, R_NONE, OPT_POSTPONE, M_ASKYES },
+ /*
+ ** .pp
+@@ -2576,6 +2763,28 @@ struct option_t MuttVars[] = {
+ ** Command to use when spawning a subshell. By default, the user's login
+ ** shell from \fC/etc/passwd\fP is used.
+ */
++#ifdef USE_NNTP
++ { "save_unsubscribed", DT_BOOL, R_NONE, OPTSAVEUNSUB, 0 },
++ /*
++ ** .pp
++ ** When \fIset\fP, info about unsubscribed newsgroups will be saved into
++ ** ``newsrc'' file and into cache.
++ */
++ { "show_new_news", DT_BOOL, R_NONE, OPTSHOWNEWNEWS, 1 },
++ /*
++ ** .pp
++ ** If \fIset\fP, news server will be asked for new newsgroups on entering
++ ** the browser. Otherwise, it will be done only once for a news server.
++ ** Also controls whether or not number of new articles of subscribed
++ ** newsgroups will be then checked.
++ */
++ { "show_only_unread", DT_BOOL, R_NONE, OPTSHOWONLYUNREAD, 0 },
++ /*
++ ** .pp
++ ** If \fIset\fP, only subscribed newsgroups that contain unread articles
++ ** will be displayed in browser.
++ */
++#endif
+ { "sig_dashes", DT_BOOL, R_NONE, OPTSIGDASHES, 1 },
+ /*
+ ** .pp
+@@ -3511,6 +3720,14 @@ struct option_t MuttVars[] = {
+ ** xterm_set_titles has been set. This string is identical in formatting
+ ** to the one used by ``$$status_format''.
+ */
++#ifdef USE_NNTP
++ { "x_comment_to", DT_BOOL, R_NONE, OPTXCOMMENTTO, 0 },
++ /*
++ ** .pp
++ ** If \fIset\fP, Mutt will add ``X-Comment-To:'' field (that contains full
++ ** name of original article author) to article that followuped to newsgroup.
++ */
++#endif
+ /*--*/
+ { NULL, 0, 0, 0, 0 }
+ };
+diff --git a/keymap.c b/keymap.c
+index 9dc87f0..5cdef30 100644
+--- a/keymap.c
++++ b/keymap.c
+@@ -784,7 +784,6 @@ void km_init (void)
+ km_bindkey ("<enter>", MENU_MAIN, OP_DISPLAY_MESSAGE);
+
+ km_bindkey ("x", MENU_PAGER, OP_EXIT);
+- km_bindkey ("i", MENU_PAGER, OP_EXIT);
+ km_bindkey ("<backspace>", MENU_PAGER, OP_PREV_LINE);
+ km_bindkey ("<pagedown>", MENU_PAGER, OP_NEXT_PAGE);
+ km_bindkey ("<pageup>", MENU_PAGER, OP_PREV_PAGE);
+diff --git a/mailbox.h b/mailbox.h
+index b652628..ed7954c 100644
+--- a/mailbox.h
++++ b/mailbox.h
+@@ -75,6 +75,9 @@ int mx_is_imap (const char *);
+ #ifdef USE_POP
+ int mx_is_pop (const char *);
+ #endif
++#ifdef USE_NNTP
++int mx_is_nntp (const char *);
++#endif
+
+ int mx_access (const char*, int);
+ int mx_check_empty (const char *);
+diff --git a/main.c b/main.c
+index 5ab1868..a291397 100644
+--- a/main.c
++++ b/main.c
+@@ -62,6 +62,10 @@
+ #include <idn/stringprep.h>
+ #endif
+
++#ifdef USE_NNTP
++#include "nntp.h"
++#endif
++
+ static const char *ReachingUs = N_("\
+ To contact the developers, please mail to <mutt-dev@mutt.org>.\n\
+ To report a bug, please visit http://bugs.mutt.org/.\n");
+@@ -136,6 +140,8 @@ options:\n\
+ " -e <command>\tspecify a command to be executed after initialization\n\
+ -f <file>\tspecify which mailbox to read\n\
+ -F <file>\tspecify an alternate muttrc file\n\
++ -g <server>\tspecify a news server (if compiled with NNTP)\n\
++ -G\t\tselect a newsgroup (if compiled with NNTP)\n\
+ -H <file>\tspecify a draft file to read header and body from\n\
+ -i <file>\tspecify a file which Mutt should include in the body\n\
+ -m <type>\tspecify a default mailbox type\n\
+@@ -284,6 +290,12 @@ static void show_version (void)
+ "-USE_POP "
+ #endif
+
++#ifdef USE_NNTP
++ "+USE_NNTP "
++#else
++ "-USE_NNTP "
++#endif
++
+ #ifdef USE_IMAP
+ "+USE_IMAP "
+ #else
+@@ -558,6 +570,9 @@ init_extended_keys();
+ #define M_NOSYSRC (1<<2) /* -n */
+ #define M_RO (1<<3) /* -R */
+ #define M_SELECT (1<<4) /* -y */
++#ifdef USE_NNTP
++#define M_NEWS (1<<5) /* -g and -G */
++#endif
+
+ int main (int argc, char **argv)
+ {
+@@ -630,7 +645,11 @@ int main (int argc, char **argv)
+ argv[nargc++] = argv[optind];
+ }
+
++#ifdef USE_NNTP
++ if ((i = getopt (argc, argv, "+A:a:b:F:f:c:Dd:e:g:GH:s:i:hm:npQ:RvxyzZ")) != EOF)
++#else
+ if ((i = getopt (argc, argv, "+A:a:b:F:f:c:Dd:e:H:s:i:hm:npQ:RvxyzZ")) != EOF)
++#endif
+ switch (i)
+ {
+ case 'A':
+@@ -727,6 +746,20 @@ int main (int argc, char **argv)
+ flags |= M_SELECT;
+ break;
+
++#ifdef USE_NNTP
++ case 'g': /* Specify a news server */
++ {
++ char buf[LONG_STRING];
++
++ snprintf (buf, sizeof (buf), "set news_server=%s", optarg);
++ commands = mutt_add_list (commands, buf);
++ }
++
++ case 'G': /* List of newsgroups */
++ flags |= M_SELECT | M_NEWS;
++ break;
++#endif
++
+ case 'z':
+ flags |= M_IGNORE;
+ break;
+@@ -1014,6 +1047,18 @@ int main (int argc, char **argv)
+ }
+ else if (flags & M_SELECT)
+ {
++#ifdef USE_NNTP
++ if (flags & M_NEWS)
++ {
++ set_option (OPTNEWS);
++ if(!(CurrentNewsSrv = nntp_select_server (NewsServer, 0)))
++ {
++ mutt_endwin (Errorbuf);
++ exit (1);
++ }
++ }
++ else
++#endif
+ if (!Incoming) {
+ mutt_endwin _("No incoming mailboxes defined.");
+ exit (1);
+@@ -1029,6 +1074,15 @@ int main (int argc, char **argv)
+
+ if (!folder[0])
+ strfcpy (folder, NONULL(Spoolfile), sizeof (folder));
++
++#ifdef USE_NNTP
++ if (option (OPTNEWS))
++ {
++ unset_option (OPTNEWS);
++ nntp_expand_path (folder, sizeof (folder), &CurrentNewsSrv->conn->account);
++ }
++ else
++#endif
+ mutt_expand_path (folder, sizeof (folder));
+
+ mutt_str_replace (&CurrentFolder, folder);
+diff --git a/mutt.h b/mutt.h
+index 932ef10..a95b249 100644
+--- a/mutt.h
++++ b/mutt.h
+@@ -239,6 +239,9 @@ enum
+ M_PGP_KEY,
+ M_XLABEL,
+ M_MIMEATTACH,
++#ifdef USE_NNTP
++ M_NEWSGROUPS,
++#endif
+
+ /* Options for Mailcap lookup */
+ M_EDIT,
+@@ -295,6 +298,11 @@ enum
+ #endif
+ OPT_SUBJECT,
+ OPT_VERIFYSIG, /* verify PGP signatures */
++#ifdef USE_NNTP
++ OPT_TOMODERATED,
++ OPT_CATCHUP,
++ OPT_FOLLOWUPTOPOSTER,
++#endif
+
+ /* THIS MUST BE THE LAST VALUE. */
+ OPT_MAX
+@@ -311,6 +319,7 @@ enum
+ #define SENDKEY (1<<7)
+ #define SENDRESEND (1<<8)
+ #define SENDPOSTPONEDFCC (1<<9) /* used by mutt_get_postponed() to signal that the x-mutt-fcc header field was present */
++#define SENDNEWS (1<<10)
+
+ /* flags to _mutt_select_file() */
+ #define M_SEL_BUFFY (1<<0)
+@@ -330,6 +339,8 @@ enum
+ OPTASCIICHARS,
+ OPTASKBCC,
+ OPTASKCC,
++ OPTASKFOLLOWUP,
++ OPTASKXCOMMENTTO,
+ OPTATTACHSPLIT,
+ OPTAUTOEDIT,
+ OPTAUTOTAG,
+@@ -410,6 +421,9 @@ enum
+ OPTMETOO,
+ OPTMHPURGE,
+ OPTMIMEFORWDECODE,
++#ifdef USE_NNTP
++ OPTMIMESUBJECT, /* encode subject line with RFC2047 */
++#endif
+ OPTNARROWTREE,
+ OPTPAGERSTOP,
+ OPTPIPEDECODE,
+@@ -495,6 +509,16 @@ enum
+ OPTPGPAUTOINLINE,
+ OPTPGPREPLYINLINE,
+
++ /* news options */
++
++#ifdef USE_NNTP
++ OPTSHOWNEWNEWS,
++ OPTSHOWONLYUNREAD,
++ OPTSAVEUNSUB,
++ OPTLOADDESC,
++ OPTXCOMMENTTO,
++#endif
++
+ /* pseudo options */
+
+ OPTAUXSORT, /* (pseudo) using auxiliary sort function */
+@@ -515,6 +539,7 @@ enum
+ OPTSORTSUBTHREADS, /* (pseudo) used when $sort_aux changes */
+ OPTNEEDRESCORE, /* (pseudo) set when the `score' command is used */
+ OPTATTACHMSG, /* (pseudo) used by attach-message */
++ OPTHIDEREAD, /* (pseudo) whether or not hide read messages */
+ OPTKEEPQUIET, /* (pseudo) shut up the message and refresh
+ * functions while we are executing an
+ * external program.
+@@ -527,6 +552,11 @@ enum
+
+ OPTSIDEBARNEWMAILONLY,
+
++#ifdef USE_NNTP
++ OPTNEWS, /* (pseudo) used to change reader mode */
++ OPTNEWSSEND, /* (pseudo) used to change behavior when posting */
++#endif
++
+ OPTMAX
+ };
+
+@@ -606,6 +636,13 @@ typedef struct envelope
+ char *supersedes;
+ char *date;
+ char *x_label;
++ char *organization;
++#ifdef USE_NNTP
++ char *newsgroups;
++ char *xref;
++ char *followup_to;
++ char *x_comment_to;
++#endif
+ BUFFER *spam;
+ LIST *references; /* message references (in reverse order) */
+ LIST *in_reply_to; /* in-reply-to header content */
+@@ -792,7 +829,7 @@ typedef struct header
+ int refno; /* message number on server */
+ #endif
+
+-#if defined USE_POP || defined USE_IMAP
++#if defined USE_POP || defined USE_IMAP || defined USE_NNTP
+ void *data; /* driver-specific data */
+ #endif
+
+diff --git a/mutt_sasl.c b/mutt_sasl.c
+index 896825e..21da012 100644
+--- a/mutt_sasl.c
++++ b/mutt_sasl.c
+@@ -188,6 +188,11 @@ int mutt_sasl_client_new (CONNECTION* conn, sasl_conn_t** saslconn)
+ case M_ACCT_TYPE_SMTP:
+ service = "smtp";
+ break;
++#ifdef USE_NNTP
++ case M_ACCT_TYPE_NNTP:
++ service = "nntp";
++ break;
++#endif
+ default:
+ mutt_error (_("Unknown SASL profile"));
+ return -1;
+diff --git a/muttlib.c b/muttlib.c
+index 9086f07..49389fc 100644
+--- a/muttlib.c
++++ b/muttlib.c
+@@ -337,7 +337,7 @@ void mutt_free_header (HEADER **h)
+ #ifdef MIXMASTER
+ mutt_free_list (&(*h)->chain);
+ #endif
+-#if defined USE_POP || defined USE_IMAP
++#if defined USE_POP || defined USE_IMAP || defined USE_NNTP
+ FREE (&(*h)->data);
+ #endif
+ FREE (h); /* __FREE_CHECKED__ */
+@@ -725,6 +725,13 @@ void mutt_free_envelope (ENVELOPE **p)
+ FREE (&(*p)->supersedes);
+ FREE (&(*p)->date);
+ FREE (&(*p)->x_label);
++ FREE (&(*p)->organization);
++#ifdef USE_NNTP
++ FREE (&(*p)->newsgroups);
++ FREE (&(*p)->xref);
++ FREE (&(*p)->followup_to);
++ FREE (&(*p)->x_comment_to);
++#endif
+
+ mutt_buffer_free (&(*p)->spam);
+
+@@ -1573,6 +1580,14 @@ int mutt_save_confirm (const char *s, struct stat *st)
+ }
+ }
+
++#ifdef USE_NNTP
++ if (magic == M_NNTP)
++ {
++ mutt_error _("Can't save message to news server.");
++ return 0;
++ }
++#endif
++
+ if (stat (s, st) != -1)
+ {
+ if (magic == -1)
+diff --git a/mx.c b/mx.c
+index cbee47d..5bbbf2a 100644
+--- a/mx.c
++++ b/mx.c
+@@ -347,6 +347,22 @@ int mx_is_pop (const char *p)
+ }
+ #endif
+
++#ifdef USE_NNTP
++int mx_is_nntp (const char *p)
++{
++ url_scheme_t scheme;
++
++ if (!p)
++ return 0;
++
++ scheme = url_check_scheme (p);
++ if (scheme == U_NNTP || scheme == U_NNTPS)
++ return 1;
++
++ return 0;
++}
++#endif
++
+ int mx_get_magic (const char *path)
+ {
+ struct stat st;
+@@ -364,6 +380,11 @@ int mx_get_magic (const char *path)
+ return M_POP;
+ #endif /* USE_POP */
+
++#ifdef USE_NNTP
++ if (mx_is_nntp (path))
++ return M_NNTP;
++#endif /* USE_NNTP */
++
+ if (stat (path, &st) == -1)
+ {
+ dprint (1, (debugfile, "mx_get_magic(): unable to stat %s: %s (errno %d).\n",
+@@ -691,6 +712,12 @@ CONTEXT *mx_open_mailbox (const char *path, int flags, CONTEXT *pctx)
+ break;
+ #endif /* USE_POP */
+
++#ifdef USE_NNTP
++ case M_NNTP:
++ rc = nntp_open_mailbox (ctx);
++ break;
++#endif /* USE_NNTP */
++
+ default:
+ rc = -1;
+ break;
+@@ -803,6 +830,12 @@ static int sync_mailbox (CONTEXT *ctx, int *index_hint)
+ rc = pop_sync_mailbox (ctx, index_hint);
+ break;
+ #endif /* USE_POP */
++
++#ifdef USE_NNTP
++ case M_NNTP:
++ rc = nntp_sync_mailbox (ctx);
++ break;
++#endif /* USE_NNTP */
+ }
+
+ #if 0
+@@ -905,6 +938,25 @@ int mx_close_mailbox (CONTEXT *ctx, int *index_hint)
+ return 0;
+ }
+
++#ifdef USE_NNTP
++ if (ctx->unread && ctx->magic == M_NNTP)
++ {
++ NNTP_DATA *nntp_data = ctx->data;
++
++ if (nntp_data && nntp_data->nserv && nntp_data->group)
++ {
++ int rc = query_quadoption (OPT_CATCHUP, _("Mark all articles read?"));
++ if (rc < 0)
++ {
++ ctx->closing = 0;
++ return -1;
++ }
++ else if (rc == M_YES)
++ mutt_newsgroup_catchup (nntp_data->nserv, nntp_data->group);
++ }
++ }
++#endif
++
+ for (i = 0; i < ctx->msgcount; i++)
+ {
+ if (!ctx->hdrs[i]->deleted && ctx->hdrs[i]->read
+@@ -912,6 +964,12 @@ int mx_close_mailbox (CONTEXT *ctx, int *index_hint)
+ read_msgs++;
+ }
+
++#ifdef USE_NNTP
++ /* don't need to move articles from newsgroup */
++ if (ctx->magic == M_NNTP)
++ read_msgs = 0;
++#endif
++
+ if (read_msgs && quadoption (OPT_MOVE) != M_NO)
+ {
+ char *p;
+@@ -1464,6 +1522,11 @@ int mx_check_mailbox (CONTEXT *ctx, int *index_hint, int lock)
+ case M_POP:
+ return (pop_check_mailbox (ctx, index_hint));
+ #endif /* USE_POP */
++
++#ifdef USE_NNTP
++ case M_NNTP:
++ return (nntp_check_mailbox (ctx, 0));
++#endif /* USE_NNTP */
+ }
+ }
+
+@@ -1524,6 +1587,15 @@ MESSAGE *mx_open_message (CONTEXT *ctx, int msgno)
+ }
+ #endif /* USE_POP */
+
++#ifdef USE_NNTP
++ case M_NNTP:
++ {
++ if (nntp_fetch_message (msg, ctx, msgno) != 0)
++ FREE (&msg);
++ break;
++ }
++#endif /* USE_NNTP */
++
+ default:
+ dprint (1, (debugfile, "mx_open_message(): function not implemented for mailbox type %d.\n", ctx->magic));
+ FREE (&msg);
+@@ -1599,6 +1671,9 @@ int mx_close_message (MESSAGE **msg)
+ int r = 0;
+
+ if ((*msg)->magic == M_MH || (*msg)->magic == M_MAILDIR
++#ifdef USE_NNTP
++ || (*msg)->magic == M_NNTP
++#endif
+ || (*msg)->magic == M_IMAP || (*msg)->magic == M_POP)
+ {
+ r = safe_fclose (&(*msg)->fp);
+diff --git a/mx.h b/mx.h
+index 4aabadf..9980c4e 100644
+--- a/mx.h
++++ b/mx.h
+@@ -34,6 +34,9 @@ enum
+ M_MMDF,
+ M_MH,
+ M_MAILDIR,
++#ifdef USE_NNTP
++ M_NNTP,
++#endif
+ M_IMAP,
+ M_POP
+ #ifdef USE_COMPRESSED
+diff --git a/newsrc.c b/newsrc.c
+new file mode 100644
+index 0000000..482214c
+--- /dev/null
++++ b/newsrc.c
+@@ -0,0 +1,1260 @@
++/*
++ * Copyright (C) 1998 Brandon Long <blong@fiction.net>
++ * Copyright (C) 1999 Andrej Gritsenko <andrej@lucky.net>
++ * Copyright (C) 2000-2012 Vsevolod Volkov <vvv@mutt.org.ua>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation; either version 2 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program; if not, write to the Free Software
++ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
++ */
++
++#if HAVE_CONFIG_H
++#include "config.h"
++#endif
++
++#include "mutt.h"
++#include "mutt_curses.h"
++#include "sort.h"
++#include "mx.h"
++#include "mime.h"
++#include "mailbox.h"
++#include "nntp.h"
++#include "rfc822.h"
++#include "rfc1524.h"
++#include "rfc2047.h"
++#include "bcache.h"
++
++#if USE_HCACHE
++#include "hcache.h"
++#endif
++
++#include <unistd.h>
++#include <string.h>
++#include <ctype.h>
++#include <stdlib.h>
++#include <sys/stat.h>
++#include <sys/types.h>
++#include <dirent.h>
++#include <errno.h>
++
++/* Find NNTP_DATA for given newsgroup or add it */
++static NNTP_DATA *nntp_data_find (NNTP_SERVER *nserv, const char *group)
++{
++ NNTP_DATA *nntp_data = hash_find (nserv->groups_hash, group);
++
++ if (!nntp_data)
++ {
++ /* create NNTP_DATA structure and add it to hash */
++ nntp_data = safe_calloc (1, sizeof (NNTP_DATA) + strlen (group) + 1);
++ nntp_data->group = (char *)nntp_data + sizeof (NNTP_DATA);
++ strcpy (nntp_data->group, group);
++ nntp_data->nserv = nserv;
++ nntp_data->deleted = 1;
++ if (nserv->groups_hash->nelem < nserv->groups_hash->curnelem * 2)
++ nserv->groups_hash = hash_resize (nserv->groups_hash,
++ nserv->groups_hash->nelem * 2, 0);
++ hash_insert (nserv->groups_hash, nntp_data->group, nntp_data, 0);
++
++ /* add NNTP_DATA to list */
++ if (nserv->groups_num >= nserv->groups_max)
++ {
++ nserv->groups_max *= 2;
++ safe_realloc (&nserv->groups_list,
++ nserv->groups_max * sizeof (nntp_data));
++ }
++ nserv->groups_list[nserv->groups_num++] = nntp_data;
++ }
++ return nntp_data;
++}
++
++/* Remove all temporarily cache files */
++void nntp_acache_free (NNTP_DATA *nntp_data)
++{
++ int i;
++
++ for (i = 0; i < NNTP_ACACHE_LEN; i++)
++ {
++ if (nntp_data->acache[i].path)
++ {
++ unlink (nntp_data->acache[i].path);
++ FREE (&nntp_data->acache[i].path);
++ }
++ }
++}
++
++/* Free NNTP_DATA, used to destroy hash elements */
++void nntp_data_free (void *data)
++{
++ NNTP_DATA *nntp_data = data;
++
++ if (!nntp_data)
++ return;
++ nntp_acache_free (nntp_data);
++ mutt_bcache_close (&nntp_data->bcache);
++ FREE (&nntp_data->newsrc_ent);
++ FREE (&nntp_data->desc);
++ FREE (&data);
++}
++
++/* Unlock and close .newsrc file */
++void nntp_newsrc_close (NNTP_SERVER *nserv)
++{
++ if (!nserv->newsrc_fp)
++ return;
++
++ dprint (1, (debugfile, "Unlocking %s\n", nserv->newsrc_file));
++ mx_unlock_file (nserv->newsrc_file, fileno (nserv->newsrc_fp), 0);
++ safe_fclose (&nserv->newsrc_fp);
++}
++
++/* Parse .newsrc file:
++ * 0 - not changed
++ * 1 - parsed
++ * -1 - error */
++int nntp_newsrc_parse (NNTP_SERVER *nserv)
++{
++ unsigned int i;
++ char *line;
++ struct stat sb;
++
++ /* if file doesn't exist, create it */
++ nserv->newsrc_fp = safe_fopen (nserv->newsrc_file, "a");
++ safe_fclose (&nserv->newsrc_fp);
++
++ /* open .newsrc */
++ nserv->newsrc_fp = safe_fopen (nserv->newsrc_file, "r");
++ if (!nserv->newsrc_fp)
++ {
++ mutt_perror (nserv->newsrc_file);
++ mutt_sleep (2);
++ return -1;
++ }
++
++ /* lock it */
++ dprint (1, (debugfile, "Locking %s\n", nserv->newsrc_file));
++ if (mx_lock_file (nserv->newsrc_file, fileno (nserv->newsrc_fp), 0, 0, 1))
++ {
++ safe_fclose (&nserv->newsrc_fp);
++ return -1;
++ }
++
++ if (stat (nserv->newsrc_file, &sb))
++ {
++ mutt_perror (nserv->newsrc_file);
++ nntp_newsrc_close (nserv);
++ mutt_sleep (2);
++ return -1;
++ }
++
++ if (nserv->size == sb.st_size && nserv->mtime == sb.st_mtime)
++ return 0;
++
++ nserv->size = sb.st_size;
++ nserv->mtime = sb.st_mtime;
++ nserv->newsrc_modified = 1;
++ dprint (1, (debugfile, "Parsing %s\n", nserv->newsrc_file));
++
++ /* .newsrc has been externally modified or hasn't been loaded yet */
++ for (i = 0; i < nserv->groups_num; i++)
++ {
++ NNTP_DATA *nntp_data = nserv->groups_list[i];
++
++ if (!nntp_data)
++ continue;
++
++ nntp_data->subscribed = 0;
++ nntp_data->newsrc_len = 0;
++ FREE (&nntp_data->newsrc_ent);
++ }
++
++ line = safe_malloc (sb.st_size + 1);
++ while (sb.st_size && fgets (line, sb.st_size + 1, nserv->newsrc_fp))
++ {
++ char *b, *h, *p;
++ unsigned int subs = 0, i = 1;
++ NNTP_DATA *nntp_data;
++
++ /* find end of newsgroup name */
++ p = strpbrk (line, ":!");
++ if (!p)
++ continue;
++
++ /* ":" - subscribed, "!" - unsubscribed */
++ if (*p == ':')
++ subs++;
++ *p++ = '\0';
++
++ /* get newsgroup data */
++ nntp_data = nntp_data_find (nserv, line);
++ FREE (&nntp_data->newsrc_ent);
++
++ /* count number of entries */
++ b = p;
++ while (*b)
++ if (*b++ == ',')
++ i++;
++ nntp_data->newsrc_ent = safe_calloc (i, sizeof (NEWSRC_ENTRY));
++ nntp_data->subscribed = subs;
++
++ /* parse entries */
++ i = 0;
++ while (p)
++ {
++ b = p;
++
++ /* find end of entry */
++ p = strchr (p, ',');
++ if (p)
++ *p++ = '\0';
++
++ /* first-last or single number */
++ h = strchr (b, '-');
++ if (h)
++ *h++ = '\0';
++ else
++ h = b;
++
++ if (sscanf (b, ANUM, &nntp_data->newsrc_ent[i].first) == 1 &&
++ sscanf (h, ANUM, &nntp_data->newsrc_ent[i].last) == 1)
++ i++;
++ }
++ if (i == 0)
++ {
++ nntp_data->newsrc_ent[i].first = 1;
++ nntp_data->newsrc_ent[i].last = 0;
++ i++;
++ }
++ if (nntp_data->lastMessage == 0)
++ nntp_data->lastMessage = nntp_data->newsrc_ent[i - 1].last;
++ nntp_data->newsrc_len = i;
++ safe_realloc (&nntp_data->newsrc_ent, i * sizeof (NEWSRC_ENTRY));
++ nntp_group_unread_stat (nntp_data);
++ dprint (2, (debugfile, "nntp_newsrc_parse: %s\n", nntp_data->group));
++ }
++ FREE (&line);
++ return 1;
++}
++
++/* Generate array of .newsrc entries */
++void nntp_newsrc_gen_entries (CONTEXT *ctx)
++{
++ NNTP_DATA *nntp_data = ctx->data;
++ anum_t last = 0, first = 1;
++ int series, i;
++ int save_sort = SORT_ORDER;
++ unsigned int entries;
++
++ if (Sort != SORT_ORDER)
++ {
++ save_sort = Sort;
++ Sort = SORT_ORDER;
++ mutt_sort_headers (ctx, 0);
++ }
++
++ entries = nntp_data->newsrc_len;
++ if (!entries)
++ {
++ entries = 5;
++ nntp_data->newsrc_ent = safe_calloc (entries, sizeof (NEWSRC_ENTRY));
++ }
++
++ /* Set up to fake initial sequence from 1 to the article before the
++ * first article in our list */
++ nntp_data->newsrc_len = 0;
++ series = 1;
++ for (i = 0; i < ctx->msgcount; i++)
++ {
++ /* search for first unread */
++ if (series)
++ {
++ /* We don't actually check sequential order, since we mark
++ * "missing" entries as read/deleted */
++ last = NHDR (ctx->hdrs[i])->article_num;
++ if (last >= nntp_data->firstMessage && !ctx->hdrs[i]->deleted &&
++ !ctx->hdrs[i]->read)
++ {
++ if (nntp_data->newsrc_len >= entries)
++ {
++ entries *= 2;
++ safe_realloc (&nntp_data->newsrc_ent, entries * sizeof (NEWSRC_ENTRY));
++ }
++ nntp_data->newsrc_ent[nntp_data->newsrc_len].first = first;
++ nntp_data->newsrc_ent[nntp_data->newsrc_len].last = last - 1;
++ nntp_data->newsrc_len++;
++ series = 0;
++ }
++ }
++
++ /* search for first read */
++ else
++ {
++ if (ctx->hdrs[i]->deleted || ctx->hdrs[i]->read)
++ {
++ first = last + 1;
++ series = 1;
++ }
++ last = NHDR (ctx->hdrs[i])->article_num;
++ }
++ }
++
++ if (series && first <= nntp_data->lastLoaded)
++ {
++ if (nntp_data->newsrc_len >= entries)
++ {
++ entries++;
++ safe_realloc (&nntp_data->newsrc_ent, entries * sizeof (NEWSRC_ENTRY));
++ }
++ nntp_data->newsrc_ent[nntp_data->newsrc_len].first = first;
++ nntp_data->newsrc_ent[nntp_data->newsrc_len].last = nntp_data->lastLoaded;
++ nntp_data->newsrc_len++;
++ }
++ safe_realloc (&nntp_data->newsrc_ent,
++ nntp_data->newsrc_len * sizeof (NEWSRC_ENTRY));
++
++ if (save_sort != Sort)
++ {
++ Sort = save_sort;
++ mutt_sort_headers (ctx, 0);
++ }
++}
++
++/* Update file with new contents */
++static int update_file (char *filename, char *buf)
++{
++ FILE *fp;
++ char tmpfile[_POSIX_PATH_MAX];
++ int rc = -1;
++
++ while (1)
++ {
++ snprintf (tmpfile, sizeof (tmpfile), "%s.tmp", filename);
++ fp = fopen (tmpfile, "w");
++ if (!fp)
++ {
++ mutt_perror (tmpfile);
++ *tmpfile = '\0';
++ break;
++ }
++ if (fputs (buf, fp) == EOF)
++ {
++ mutt_perror (tmpfile);
++ break;
++ }
++ if (fclose (fp) == EOF)
++ {
++ mutt_perror (tmpfile);
++ fp = NULL;
++ break;
++ }
++ fp = NULL;
++ if (rename (tmpfile, filename) < 0)
++ {
++ mutt_perror (filename);
++ break;
++ }
++ *tmpfile = '\0';
++ rc = 0;
++ break;
++ }
++ if (fp)
++ fclose (fp);
++ if (*tmpfile)
++ unlink (tmpfile);
++ if (rc)
++ mutt_sleep (2);
++ return rc;
++}
++
++/* Update .newsrc file */
++int nntp_newsrc_update (NNTP_SERVER *nserv)
++{
++ char *buf;
++ size_t buflen, off;
++ unsigned int i;
++ int rc = -1;
++
++ if (!nserv)
++ return -1;
++
++ buflen = 10 * LONG_STRING;
++ buf = safe_calloc (1, buflen);
++ off = 0;
++
++ /* we will generate full newsrc here */
++ for (i = 0; i < nserv->groups_num; i++)
++ {
++ NNTP_DATA *nntp_data = nserv->groups_list[i];
++ unsigned int n;
++
++ if (!nntp_data || !nntp_data->newsrc_ent)
++ continue;
++
++ /* write newsgroup name */
++ if (off + strlen (nntp_data->group) + 3 > buflen)
++ {
++ buflen *= 2;
++ safe_realloc (&buf, buflen);
++ }
++ snprintf (buf + off, buflen - off, "%s%c ", nntp_data->group,
++ nntp_data->subscribed ? ':' : '!');
++ off += strlen (buf + off);
++
++ /* write entries */
++ for (n = 0; n < nntp_data->newsrc_len; n++)
++ {
++ if (off + LONG_STRING > buflen)
++ {
++ buflen *= 2;
++ safe_realloc (&buf, buflen);
++ }
++ if (n)
++ buf[off++] = ',';
++ if (nntp_data->newsrc_ent[n].first == nntp_data->newsrc_ent[n].last)
++ snprintf (buf + off, buflen - off, "%d", nntp_data->newsrc_ent[n].first);
++ else if (nntp_data->newsrc_ent[n].first < nntp_data->newsrc_ent[n].last)
++ snprintf (buf + off, buflen - off, "%d-%d",
++ nntp_data->newsrc_ent[n].first, nntp_data->newsrc_ent[n].last);
++ off += strlen (buf + off);
++ }
++ buf[off++] = '\n';
++ }
++ buf[off] = '\0';
++
++ /* newrc being fully rewritten */
++ dprint (1, (debugfile, "Updating %s\n", nserv->newsrc_file));
++ if (nserv->newsrc_file && update_file (nserv->newsrc_file, buf) == 0)
++ {
++ struct stat sb;
++
++ rc = stat (nserv->newsrc_file, &sb);
++ if (rc == 0)
++ {
++ nserv->size = sb.st_size;
++ nserv->mtime = sb.st_mtime;
++ }
++ else
++ {
++ mutt_perror (nserv->newsrc_file);
++ mutt_sleep (2);
++ }
++ }
++ FREE (&buf);
++ return rc;
++}
++
++/* Make fully qualified cache file name */
++static void cache_expand (char *dst, size_t dstlen, ACCOUNT *acct, char *src)
++{
++ char *c;
++ char file[_POSIX_PATH_MAX];
++
++ /* server subdirectory */
++ if (acct)
++ {
++ ciss_url_t url;
++
++ mutt_account_tourl (acct, &url);
++ url.path = src;
++ url_ciss_tostring (&url, file, sizeof (file), U_PATH);
++ }
++ else
++ strfcpy (file, src ? src : "", sizeof (file));
++
++ snprintf (dst, dstlen, "%s/%s", NewsCacheDir, file);
++
++ /* remove trailing slash */
++ c = dst + strlen (dst) - 1;
++ if (*c == '/')
++ *c = '\0';
++ mutt_expand_path (dst, dstlen);
++}
++
++/* Make fully qualified url from newsgroup name */
++void nntp_expand_path (char *line, size_t len, ACCOUNT *acct)
++{
++ ciss_url_t url;
++
++ url.path = safe_strdup (line);
++ mutt_account_tourl (acct, &url);
++ url_ciss_tostring (&url, line, len, 0);
++ FREE (&url.path);
++}
++
++/* Parse newsgroup */
++int nntp_add_group (char *line, void *data)
++{
++ NNTP_SERVER *nserv = data;
++ NNTP_DATA *nntp_data;
++ char group[LONG_STRING];
++ char desc[HUGE_STRING] = "";
++ char mod;
++ anum_t first, last;
++
++ if (!nserv || !line)
++ return 0;
++
++ if (sscanf (line, "%s " ANUM " " ANUM " %c %[^\n]", group,
++ &last, &first, &mod, desc) < 4)
++ return 0;
++
++ nntp_data = nntp_data_find (nserv, group);
++ nntp_data->deleted = 0;
++ nntp_data->firstMessage = first;
++ nntp_data->lastMessage = last;
++ nntp_data->allowed = mod == 'y' || mod == 'm' ? 1 : 0;
++ mutt_str_replace (&nntp_data->desc, desc);
++ if (nntp_data->newsrc_ent || nntp_data->lastCached)
++ nntp_group_unread_stat (nntp_data);
++ else if (nntp_data->lastMessage &&
++ nntp_data->firstMessage <= nntp_data->lastMessage)
++ nntp_data->unread = nntp_data->lastMessage - nntp_data->firstMessage + 1;
++ else
++ nntp_data->unread = 0;
++ return 0;
++}
++
++/* Load list of all newsgroups from cache */
++static int active_get_cache (NNTP_SERVER *nserv)
++{
++ char buf[HUGE_STRING];
++ char file[_POSIX_PATH_MAX];
++ time_t t;
++ FILE *fp;
++
++ cache_expand (file, sizeof (file), &nserv->conn->account, ".active");
++ dprint (1, (debugfile, "Parsing %s\n", file));
++ fp = safe_fopen (file, "r");
++ if (!fp)
++ return -1;
++
++ if (fgets (buf, sizeof (buf), fp) == NULL ||
++ sscanf (buf, "%ld%s", &t, file) != 1 || t == 0)
++ {
++ fclose (fp);
++ return -1;
++ }
++ nserv->newgroups_time = t;
++
++ mutt_message _("Loading list of groups from cache...");
++ while (fgets (buf, sizeof (buf), fp))
++ nntp_add_group (buf, nserv);
++ nntp_add_group (NULL, NULL);
++ fclose (fp);
++ mutt_clear_error ();
++ return 0;
++}
++
++/* Save list of all newsgroups to cache */
++int nntp_active_save_cache (NNTP_SERVER *nserv)
++{
++ char file[_POSIX_PATH_MAX];
++ char *buf;
++ size_t buflen, off;
++ unsigned int i;
++ int rc;
++
++ if (!nserv->cacheable)
++ return 0;
++
++ buflen = 10 * LONG_STRING;
++ buf = safe_calloc (1, buflen);
++ snprintf (buf, buflen, "%lu\n", (unsigned long)nserv->newgroups_time);
++ off = strlen (buf);
++
++ for (i = 0; i < nserv->groups_num; i++)
++ {
++ NNTP_DATA *nntp_data = nserv->groups_list[i];
++
++ if (!nntp_data || nntp_data->deleted)
++ continue;
++
++ if (off + strlen (nntp_data->group) +
++ (nntp_data->desc ? strlen (nntp_data->desc) : 0) + 50 > buflen)
++ {
++ buflen *= 2;
++ safe_realloc (&buf, buflen);
++ }
++ snprintf (buf + off, buflen - off, "%s %d %d %c%s%s\n", nntp_data->group,
++ nntp_data->lastMessage, nntp_data->firstMessage,
++ nntp_data->allowed ? 'y' : 'n', nntp_data->desc ? " " : "",
++ nntp_data->desc ? nntp_data->desc : "");
++ off += strlen (buf + off);
++ }
++
++ cache_expand (file, sizeof (file), &nserv->conn->account, ".active");
++ dprint (1, (debugfile, "Updating %s\n", file));
++ rc = update_file (file, buf);
++ FREE (&buf);
++ return rc;
++}
++
++#ifdef USE_HCACHE
++/* Used by mutt_hcache_open() to compose hcache file name */
++static int nntp_hcache_namer (const char *path, char *dest, size_t destlen)
++{
++ return snprintf (dest, destlen, "%s.hcache", path);
++}
++
++/* Open newsgroup hcache */
++header_cache_t *nntp_hcache_open (NNTP_DATA *nntp_data)
++{
++ ciss_url_t url;
++ char file[_POSIX_PATH_MAX];
++
++ if (!nntp_data->nserv || !nntp_data->nserv->cacheable ||
++ !nntp_data->nserv->conn || !nntp_data->group ||
++ !(nntp_data->newsrc_ent || nntp_data->subscribed ||
++ option (OPTSAVEUNSUB)))
++ return NULL;
++
++ mutt_account_tourl (&nntp_data->nserv->conn->account, &url);
++ url.path = nntp_data->group;
++ url_ciss_tostring (&url, file, sizeof (file), U_PATH);
++ return mutt_hcache_open (NewsCacheDir, file, nntp_hcache_namer);
++}
++
++/* Remove stale cached headers */
++void nntp_hcache_update (NNTP_DATA *nntp_data, header_cache_t *hc)
++{
++ char buf[16];
++ int old = 0;
++ void *hdata;
++ anum_t first, last, current;
++
++ if (!hc)
++ return;
++
++ /* fetch previous values of first and last */
++ hdata = mutt_hcache_fetch_raw (hc, "index", strlen);
++ if (hdata)
++ {
++ dprint (2, (debugfile,
++ "nntp_hcache_update: mutt_hcache_fetch index: %s\n", hdata));
++ if (sscanf (hdata, ANUM " " ANUM, &first, &last) == 2)
++ {
++ old = 1;
++ nntp_data->lastCached = last;
++
++ /* clean removed headers from cache */
++ for (current = first; current <= last; current++)
++ {
++ if (current >= nntp_data->firstMessage &&
++ current <= nntp_data->lastMessage)
++ continue;
++
++ snprintf (buf, sizeof (buf), "%d", current);
++ dprint (2, (debugfile,
++ "nntp_hcache_update: mutt_hcache_delete %s\n", buf));
++ mutt_hcache_delete (hc, buf, strlen);
++ }
++ }
++ }
++
++ /* store current values of first and last */
++ if (!old || nntp_data->firstMessage != first ||
++ nntp_data->lastMessage != last)
++ {
++ snprintf (buf, sizeof (buf), "%u %u", nntp_data->firstMessage,
++ nntp_data->lastMessage);
++ dprint (2, (debugfile,
++ "nntp_hcache_update: mutt_hcache_store index: %s\n", buf));
++ mutt_hcache_store_raw (hc, "index", buf, strlen (buf) + 1, strlen);
++ }
++}
++#endif
++
++/* Remove bcache file */
++static int nntp_bcache_delete (const char *id, body_cache_t *bcache, void *data)
++{
++ NNTP_DATA *nntp_data = data;
++ anum_t anum;
++ char c;
++
++ if (!nntp_data || sscanf (id, ANUM "%c", &anum, &c) != 1 ||
++ anum < nntp_data->firstMessage || anum > nntp_data->lastMessage)
++ {
++ if (nntp_data)
++ dprint (2, (debugfile, "nntp_bcache_delete: mutt_bcache_del %s\n", id));
++ mutt_bcache_del (bcache, id);
++ }
++ return 0;
++}
++
++/* Remove stale cached messages */
++void nntp_bcache_update (NNTP_DATA *nntp_data)
++{
++ mutt_bcache_list (nntp_data->bcache, nntp_bcache_delete, nntp_data);
++}
++
++/* Remove hcache and bcache of newsgroup */
++void nntp_delete_group_cache (NNTP_DATA *nntp_data)
++{
++ char file[_POSIX_PATH_MAX];
++
++ if (!nntp_data || !nntp_data->nserv || !nntp_data->nserv->cacheable)
++ return;
++
++#ifdef USE_HCACHE
++ nntp_hcache_namer (nntp_data->group, file, sizeof (file));
++ cache_expand (file, sizeof (file), &nntp_data->nserv->conn->account, file);
++ unlink (file);
++ nntp_data->lastCached = 0;
++ dprint (2, (debugfile, "nntp_delete_group_cache: %s\n", file));
++#endif
++
++ if (!nntp_data->bcache)
++ nntp_data->bcache = mutt_bcache_open (&nntp_data->nserv->conn->account,
++ nntp_data->group);
++ if (nntp_data->bcache)
++ {
++ dprint (2, (debugfile, "nntp_delete_group_cache: %s/*\n", nntp_data->group));
++ mutt_bcache_list (nntp_data->bcache, nntp_bcache_delete, NULL);
++ mutt_bcache_close (&nntp_data->bcache);
++ }
++}
++
++/* Remove hcache and bcache of all unexistent and unsubscribed newsgroups */
++void nntp_clear_cache (NNTP_SERVER *nserv)
++{
++ char file[_POSIX_PATH_MAX];
++ char *fp;
++ struct dirent *entry;
++ DIR *dp;
++
++ if (!nserv || !nserv->cacheable)
++ return;
++
++ cache_expand (file, sizeof (file), &nserv->conn->account, NULL);
++ dp = opendir (file);
++ if (dp)
++ {
++ safe_strncat (file, sizeof (file), "/", 1);
++ fp = file + strlen (file);
++ while ((entry = readdir (dp)))
++ {
++ char *group = entry->d_name;
++ struct stat sb;
++ NNTP_DATA *nntp_data;
++ NNTP_DATA nntp_tmp;
++
++ if (mutt_strcmp (group, ".") == 0 ||
++ mutt_strcmp (group, "..") == 0)
++ continue;
++ *fp = '\0';
++ safe_strncat (file, sizeof (file), group, strlen (group));
++ if (stat (file, &sb))
++ continue;
++
++#ifdef USE_HCACHE
++ if (S_ISREG (sb.st_mode))
++ {
++ char *ext = group + strlen (group) - 7;
++ if (strlen (group) < 8 || mutt_strcmp (ext, ".hcache"))
++ continue;
++ *ext = '\0';
++ }
++ else
++#endif
++ if (!S_ISDIR (sb.st_mode))
++ continue;
++
++ nntp_data = hash_find (nserv->groups_hash, group);
++ if (!nntp_data)
++ {
++ nntp_data = &nntp_tmp;
++ nntp_data->nserv = nserv;
++ nntp_data->group = group;
++ nntp_data->bcache = NULL;
++ }
++ else if (nntp_data->newsrc_ent || nntp_data->subscribed ||
++ option (OPTSAVEUNSUB))
++ continue;
++
++ nntp_delete_group_cache (nntp_data);
++ if (S_ISDIR (sb.st_mode))
++ {
++ rmdir (file);
++ dprint (2, (debugfile, "nntp_clear_cache: %s\n", file));
++ }
++ }
++ closedir (dp);
++ }
++ return;
++}
++
++/* %a = account url
++ * %p = port
++ * %P = port if specified
++ * %s = news server name
++ * %S = url schema
++ * %u = username */
++const char *
++nntp_format_str (char *dest, size_t destlen, size_t col, char op, const char *src,
++ const char *fmt, const char *ifstring, const char *elsestring,
++ unsigned long data, format_flag flags)
++{
++ NNTP_SERVER *nserv = (NNTP_SERVER *)data;
++ ACCOUNT *acct = &nserv->conn->account;
++ ciss_url_t url;
++ char fn[SHORT_STRING], tmp[SHORT_STRING], *p;
++
++ switch (op)
++ {
++ case 'a':
++ mutt_account_tourl (acct, &url);
++ url_ciss_tostring (&url, fn, sizeof (fn), U_PATH);
++ p = strchr (fn, '/');
++ if (p)
++ *p = '\0';
++ snprintf (tmp, sizeof (tmp), "%%%ss", fmt);
++ snprintf (dest, destlen, tmp, fn);
++ break;
++ case 'p':
++ snprintf (tmp, sizeof (tmp), "%%%su", fmt);
++ snprintf (dest, destlen, tmp, acct->port);
++ break;
++ case 'P':
++ *dest = '\0';
++ if (acct->flags & M_ACCT_PORT)
++ {
++ snprintf (tmp, sizeof (tmp), "%%%su", fmt);
++ snprintf (dest, destlen, tmp, acct->port);
++ }
++ break;
++ case 's':
++ strncpy (fn, acct->host, sizeof (fn) - 1);
++ mutt_strlower (fn);
++ snprintf (tmp, sizeof (tmp), "%%%ss", fmt);
++ snprintf (dest, destlen, tmp, fn);
++ break;
++ case 'S':
++ mutt_account_tourl (acct, &url);
++ url_ciss_tostring (&url, fn, sizeof (fn), U_PATH);
++ p = strchr (fn, ':');
++ if (p)
++ *p = '\0';
++ snprintf (tmp, sizeof (tmp), "%%%ss", fmt);
++ snprintf (dest, destlen, tmp, fn);
++ break;
++ case 'u':
++ snprintf (tmp, sizeof (tmp), "%%%ss", fmt);
++ snprintf (dest, destlen, tmp, acct->user);
++ break;
++ }
++ return (src);
++}
++
++/* Automatically loads a newsrc into memory, if necessary.
++ * Checks the size/mtime of a newsrc file, if it doesn't match, load
++ * again. Hmm, if a system has broken mtimes, this might mean the file
++ * is reloaded every time, which we'd have to fix. */
++NNTP_SERVER *nntp_select_server (char *server, int leave_lock)
++{
++ char file[_POSIX_PATH_MAX];
++ char *p;
++ int rc;
++ struct stat sb;
++ ACCOUNT acct;
++ NNTP_SERVER *nserv;
++ NNTP_DATA *nntp_data;
++ CONNECTION *conn;
++ ciss_url_t url;
++
++ if (!server || !*server)
++ {
++ mutt_error _("No news server defined!");
++ mutt_sleep (2);
++ return NULL;
++ }
++
++ /* create account from news server url */
++ acct.flags = 0;
++ acct.port = NNTP_PORT;
++ acct.type = M_ACCT_TYPE_NNTP;
++ snprintf (file, sizeof (file), "%s%s",
++ strstr (server, "://") ? "" : "news://", server);
++ if (url_parse_ciss (&url, file) < 0 ||
++ (url.path && *url.path) ||
++ !(url.scheme == U_NNTP || url.scheme == U_NNTPS) ||
++ mutt_account_fromurl (&acct, &url) < 0)
++ {
++ mutt_error (_("%s is an invalid news server specification!"), server);
++ mutt_sleep (2);
++ return NULL;
++ }
++ if (url.scheme == U_NNTPS)
++ {
++ acct.flags |= M_ACCT_SSL;
++ acct.port = NNTP_SSL_PORT;
++ }
++
++ /* find connection by account */
++ conn = mutt_conn_find (NULL, &acct);
++ if (!conn)
++ return NULL;
++ if (!(conn->account.flags & M_ACCT_USER) && acct.flags & M_ACCT_USER)
++ {
++ conn->account.flags |= M_ACCT_USER;
++ conn->account.user[0] = '\0';
++ }
++
++ /* news server already exists */
++ nserv = conn->data;
++ if (nserv)
++ {
++ if (nserv->status == NNTP_BYE)
++ nserv->status = NNTP_NONE;
++ if (nntp_open_connection (nserv) < 0)
++ return NULL;
++
++ rc = nntp_newsrc_parse (nserv);
++ if (rc < 0)
++ return NULL;
++
++ /* check for new newsgroups */
++ if (!leave_lock && nntp_check_new_groups (nserv) < 0)
++ rc = -1;
++
++ /* .newsrc has been externally modified */
++ if (rc > 0)
++ nntp_clear_cache (nserv);
++ if (rc < 0 || !leave_lock)
++ nntp_newsrc_close (nserv);
++ return rc < 0 ? NULL : nserv;
++ }
++
++ /* new news server */
++ nserv = safe_calloc (1, sizeof (NNTP_SERVER));
++ nserv->conn = conn;
++ nserv->groups_hash = hash_create (1009, 0);
++ nserv->groups_max = 16;
++ nserv->groups_list = safe_malloc (nserv->groups_max * sizeof (nntp_data));
++
++ rc = nntp_open_connection (nserv);
++
++ /* try to create cache directory and enable caching */
++ nserv->cacheable = 0;
++ if (rc >= 0 && NewsCacheDir && *NewsCacheDir)
++ {
++ cache_expand (file, sizeof (file), &conn->account, NULL);
++ p = *file == '/' ? file + 1 : file;
++ while (1)
++ {
++ p = strchr (p, '/');
++ if (p)
++ *p = '\0';
++ if ((stat (file, &sb) || (sb.st_mode & S_IFDIR) == 0) &&
++ mkdir (file, 0700))
++ {
++ mutt_error (_("Can't create %s: %s."), file, strerror (errno));
++ mutt_sleep (2);
++ break;
++ }
++ if (!p)
++ {
++ nserv->cacheable = 1;
++ break;
++ }
++ *p++ = '/';
++ }
++ }
++
++ /* load .newsrc */
++ if (rc >= 0)
++ {
++ mutt_FormatString (file, sizeof (file), 0, NONULL (NewsRc),
++ nntp_format_str, (unsigned long)nserv, 0);
++ mutt_expand_path (file, sizeof (file));
++ nserv->newsrc_file = safe_strdup (file);
++ rc = nntp_newsrc_parse (nserv);
++ }
++ if (rc >= 0)
++ {
++ /* try to load list of newsgroups from cache */
++ if (nserv->cacheable && active_get_cache (nserv) == 0)
++ rc = nntp_check_new_groups (nserv);
++
++ /* load list of newsgroups from server */
++ else
++ rc = nntp_active_fetch (nserv);
++ }
++
++ if (rc >= 0)
++ nntp_clear_cache (nserv);
++
++#ifdef USE_HCACHE
++ /* check cache files */
++ if (rc >= 0 && nserv->cacheable)
++ {
++ struct dirent *entry;
++ DIR *dp = opendir (file);
++
++ if (dp)
++ {
++ while ((entry = readdir (dp)))
++ {
++ header_cache_t *hc;
++ void *hdata;
++ char *group = entry->d_name;
++
++ p = group + strlen (group) - 7;
++ if (strlen (group) < 8 || strcmp (p, ".hcache"))
++ continue;
++ *p = '\0';
++ nntp_data = hash_find (nserv->groups_hash, group);
++ if (!nntp_data)
++ continue;
++
++ hc = nntp_hcache_open (nntp_data);
++ if (!hc)
++ continue;
++
++ /* fetch previous values of first and last */
++ hdata = mutt_hcache_fetch_raw (hc, "index", strlen);
++ if (hdata)
++ {
++ anum_t first, last;
++
++ if (sscanf (hdata, ANUM " " ANUM, &first, &last) == 2)
++ {
++ if (nntp_data->deleted)
++ {
++ nntp_data->firstMessage = first;
++ nntp_data->lastMessage = last;
++ }
++ if (last >= nntp_data->firstMessage &&
++ last <= nntp_data->lastMessage)
++ {
++ nntp_data->lastCached = last;
++ dprint (2, (debugfile, "nntp_select_server: %s lastCached=%u\n",
++ nntp_data->group, last));
++ }
++ }
++ }
++ mutt_hcache_close (hc);
++ }
++ closedir (dp);
++ }
++ }
++#endif
++
++ if (rc < 0 || !leave_lock)
++ nntp_newsrc_close (nserv);
++
++ if (rc < 0)
++ {
++ hash_destroy (&nserv->groups_hash, nntp_data_free);
++ FREE (&nserv->groups_list);
++ FREE (&nserv->newsrc_file);
++ FREE (&nserv->authenticators);
++ FREE (&nserv);
++ mutt_socket_close (conn);
++ mutt_socket_free (conn);
++ return NULL;
++ }
++
++ conn->data = nserv;
++ return nserv;
++}
++
++/* Full status flags are not supported by nntp, but we can fake some of them:
++ * Read = a read message number is in the .newsrc
++ * New = not read and not cached
++ * Old = not read but cached */
++void nntp_article_status (CONTEXT *ctx, HEADER *hdr, char *group, anum_t anum)
++{
++ NNTP_DATA *nntp_data = ctx->data;
++ unsigned int i;
++
++ if (group)
++ nntp_data = hash_find (nntp_data->nserv->groups_hash, group);
++
++ if (!nntp_data)
++ return;
++
++ for (i = 0; i < nntp_data->newsrc_len; i++)
++ {
++ if ((anum >= nntp_data->newsrc_ent[i].first) &&
++ (anum <= nntp_data->newsrc_ent[i].last))
++ {
++ /* can't use mutt_set_flag() because mx_update_context()
++ didn't called yet */
++ hdr->read = 1;
++ return;
++ }
++ }
++
++ /* article was not cached yet, it's new */
++ if (anum > nntp_data->lastCached)
++ return;
++
++ /* article isn't read but cached, it's old */
++ if (option (OPTMARKOLD))
++ hdr->old = 1;
++}
++
++/* calculate number of unread articles using .newsrc data */
++void nntp_group_unread_stat (NNTP_DATA *nntp_data)
++{
++ unsigned int i;
++ anum_t first, last;
++
++ nntp_data->unread = 0;
++ if (nntp_data->lastMessage == 0 ||
++ nntp_data->firstMessage > nntp_data->lastMessage)
++ return;
++
++ nntp_data->unread = nntp_data->lastMessage - nntp_data->firstMessage + 1;
++ for (i = 0; i < nntp_data->newsrc_len; i++)
++ {
++ first = nntp_data->newsrc_ent[i].first;
++ if (first < nntp_data->firstMessage)
++ first = nntp_data->firstMessage;
++ last = nntp_data->newsrc_ent[i].last;
++ if (last > nntp_data->lastMessage)
++ last = nntp_data->lastMessage;
++ if (first <= last)
++ nntp_data->unread -= last - first + 1;
++ }
++}
++
++/* Subscribe newsgroup */
++NNTP_DATA *mutt_newsgroup_subscribe (NNTP_SERVER *nserv, char *group)
++{
++ NNTP_DATA *nntp_data;
++
++ if (!nserv || !nserv->groups_hash || !group || !*group)
++ return NULL;
++
++ nntp_data = nntp_data_find (nserv, group);
++ nntp_data->subscribed = 1;
++ if (!nntp_data->newsrc_ent)
++ {
++ nntp_data->newsrc_ent = safe_calloc (1, sizeof (NEWSRC_ENTRY));
++ nntp_data->newsrc_len = 1;
++ nntp_data->newsrc_ent[0].first = 1;
++ nntp_data->newsrc_ent[0].last = 0;
++ }
++ return nntp_data;
++}
++
++/* Unsubscribe newsgroup */
++NNTP_DATA *mutt_newsgroup_unsubscribe (NNTP_SERVER *nserv, char *group)
++{
++ NNTP_DATA *nntp_data;
++
++ if (!nserv || !nserv->groups_hash || !group || !*group)
++ return NULL;
++
++ nntp_data = hash_find (nserv->groups_hash, group);
++ if (!nntp_data)
++ return NULL;
++
++ nntp_data->subscribed = 0;
++ if (!option (OPTSAVEUNSUB))
++ {
++ nntp_data->newsrc_len = 0;
++ FREE (&nntp_data->newsrc_ent);
++ }
++ return nntp_data;
++}
++
++/* Catchup newsgroup */
++NNTP_DATA *mutt_newsgroup_catchup (NNTP_SERVER *nserv, char *group)
++{
++ NNTP_DATA *nntp_data;
++
++ if (!nserv || !nserv->groups_hash || !group || !*group)
++ return NULL;
++
++ nntp_data = hash_find (nserv->groups_hash, group);
++ if (!nntp_data)
++ return NULL;
++
++ if (nntp_data->newsrc_ent)
++ {
++ safe_realloc (&nntp_data->newsrc_ent, sizeof (NEWSRC_ENTRY));
++ nntp_data->newsrc_len = 1;
++ nntp_data->newsrc_ent[0].first = 1;
++ nntp_data->newsrc_ent[0].last = nntp_data->lastMessage;
++ }
++ nntp_data->unread = 0;
++ if (Context && Context->data == nntp_data)
++ {
++ unsigned int i;
++
++ for (i = 0; i < Context->msgcount; i++)
++ mutt_set_flag (Context, Context->hdrs[i], M_READ, 1);
++ }
++ return nntp_data;
++}
++
++/* Uncatchup newsgroup */
++NNTP_DATA *mutt_newsgroup_uncatchup (NNTP_SERVER *nserv, char *group)
++{
++ NNTP_DATA *nntp_data;
++
++ if (!nserv || !nserv->groups_hash || !group || !*group)
++ return NULL;
++
++ nntp_data = hash_find (nserv->groups_hash, group);
++ if (!nntp_data)
++ return NULL;
++
++ if (nntp_data->newsrc_ent)
++ {
++ safe_realloc (&nntp_data->newsrc_ent, sizeof (NEWSRC_ENTRY));
++ nntp_data->newsrc_len = 1;
++ nntp_data->newsrc_ent[0].first = 1;
++ nntp_data->newsrc_ent[0].last = nntp_data->firstMessage - 1;
++ }
++ if (Context && Context->data == nntp_data)
++ {
++ unsigned int i;
++
++ nntp_data->unread = Context->msgcount;
++ for (i = 0; i < Context->msgcount; i++)
++ mutt_set_flag (Context, Context->hdrs[i], M_READ, 0);
++ }
++ else
++ nntp_data->unread = nntp_data->lastMessage - nntp_data->newsrc_ent[0].last;
++ return nntp_data;
++}
++
++/* Get first newsgroup with new messages */
++void nntp_buffy (char *buf, size_t len)
++{
++ unsigned int i;
++
++ for (i = 0; i < CurrentNewsSrv->groups_num; i++)
++ {
++ NNTP_DATA *nntp_data = CurrentNewsSrv->groups_list[i];
++
++ if (!nntp_data || !nntp_data->subscribed || !nntp_data->unread)
++ continue;
++
++ if (Context && Context->magic == M_NNTP &&
++ !mutt_strcmp (nntp_data->group, ((NNTP_DATA *)Context->data)->group))
++ {
++ unsigned int i, unread = 0;
++
++ for (i = 0; i < Context->msgcount; i++)
++ if (!Context->hdrs[i]->read && !Context->hdrs[i]->deleted)
++ unread++;
++ if (!unread)
++ continue;
++ }
++ strfcpy (buf, nntp_data->group, len);
++ break;
++ }
++}
+diff --git a/nntp.c b/nntp.c
+new file mode 100644
+index 0000000..c78d3fa
+--- /dev/null
++++ b/nntp.c
+@@ -0,0 +1,2404 @@
++/*
++ * Copyright (C) 1998 Brandon Long <blong@fiction.net>
++ * Copyright (C) 1999 Andrej Gritsenko <andrej@lucky.net>
++ * Copyright (C) 2000-2012 Vsevolod Volkov <vvv@mutt.org.ua>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation; either version 2 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program; if not, write to the Free Software
++ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
++ */
++
++#if HAVE_CONFIG_H
++#include "config.h"
++#endif
++
++#include "mutt.h"
++#include "mutt_curses.h"
++#include "sort.h"
++#include "mx.h"
++#include "mime.h"
++#include "rfc1524.h"
++#include "rfc2047.h"
++#include "mailbox.h"
++#include "mutt_crypt.h"
++#include "nntp.h"
++
++#if defined(USE_SSL)
++#include "mutt_ssl.h"
++#endif
++
++#ifdef HAVE_PGP
++#include "pgp.h"
++#endif
++
++#ifdef HAVE_SMIME
++#include "smime.h"
++#endif
++
++#if USE_HCACHE
++#include "hcache.h"
++#endif
++
++#include <unistd.h>
++#include <string.h>
++#include <ctype.h>
++#include <stdlib.h>
++
++#ifdef USE_SASL
++#include <sasl/sasl.h>
++#include <sasl/saslutil.h>
++
++#include "mutt_sasl.h"
++#endif
++
++static int nntp_connect_error (NNTP_SERVER *nserv)
++{
++ nserv->status = NNTP_NONE;
++ mutt_error _("Server closed connection!");
++ mutt_sleep (2);
++ return -1;
++}
++
++/* Get capabilities:
++ * -1 - error, connection is closed
++ * 0 - mode is reader, capabilities setted up
++ * 1 - need to switch to reader mode */
++static int nntp_capabilities (NNTP_SERVER *nserv)
++{
++ CONNECTION *conn = nserv->conn;
++ unsigned int mode_reader = 0;
++ char buf[LONG_STRING];
++ char authinfo[LONG_STRING] = "";
++
++ nserv->hasCAPABILITIES = 0;
++ nserv->hasSTARTTLS = 0;
++ nserv->hasDATE = 0;
++ nserv->hasLIST_NEWSGROUPS = 0;
++ nserv->hasLISTGROUP = 0;
++ nserv->hasOVER = 0;
++ FREE (&nserv->authenticators);
++
++ if (mutt_socket_write (conn, "CAPABILITIES\r\n") < 0 ||
++ mutt_socket_readln (buf, sizeof (buf), conn) < 0)
++ return nntp_connect_error (nserv);
++
++ /* no capabilities */
++ if (mutt_strncmp ("101", buf, 3))
++ return 1;
++ nserv->hasCAPABILITIES = 1;
++
++ /* parse capabilities */
++ do
++ {
++ if (mutt_socket_readln (buf, sizeof (buf), conn) < 0)
++ return nntp_connect_error (nserv);
++ if (!mutt_strcmp ("STARTTLS", buf))
++ nserv->hasSTARTTLS = 1;
++ else if (!mutt_strcmp ("MODE-READER", buf))
++ mode_reader = 1;
++ else if (!mutt_strcmp ("READER", buf))
++ {
++ nserv->hasDATE = 1;
++ nserv->hasLISTGROUP = 1;
++ }
++ else if (!mutt_strncmp ("AUTHINFO ", buf, 9))
++ {
++ safe_strcat (buf, sizeof (buf), " ");
++ strfcpy (authinfo, buf + 8, sizeof (authinfo));
++ }
++#ifdef USE_SASL
++ else if (!mutt_strncmp ("SASL ", buf, 5))
++ {
++ char *p = buf + 5;
++ while (*p == ' ')
++ p++;
++ nserv->authenticators = safe_strdup (p);
++ }
++#endif
++ else if (!mutt_strcmp ("OVER", buf))
++ nserv->hasOVER = 1;
++ else if (!mutt_strncmp ("LIST ", buf, 5))
++ {
++ char *p = strstr (buf, " NEWSGROUPS");
++ if (p)
++ {
++ p += 11;
++ if (*p == '\0' || *p == ' ')
++ nserv->hasLIST_NEWSGROUPS = 1;
++ }
++ }
++ } while (mutt_strcmp (".", buf));
++ *buf = '\0';
++#ifdef USE_SASL
++ if (nserv->authenticators && strcasestr (authinfo, " SASL "))
++ strfcpy (buf, nserv->authenticators, sizeof (buf));
++#endif
++ if (strcasestr (authinfo, " USER "))
++ {
++ if (*buf)
++ safe_strcat (buf, sizeof (buf), " ");
++ safe_strcat (buf, sizeof (buf), "USER");
++ }
++ mutt_str_replace (&nserv->authenticators, buf);
++
++ /* current mode is reader */
++ if (nserv->hasLISTGROUP)
++ return 0;
++
++ /* server is mode-switching, need to switch to reader mode */
++ if (mode_reader)
++ return 1;
++
++ mutt_socket_close (conn);
++ nserv->status = NNTP_BYE;
++ mutt_error _("Server doesn't support reader mode.");
++ mutt_sleep (2);
++ return -1;
++}
++
++char *OverviewFmt =
++ "Subject:\0"
++ "From:\0"
++ "Date:\0"
++ "Message-ID:\0"
++ "References:\0"
++ "Content-Length:\0"
++ "Lines:\0"
++ "\0";
++
++/* Detect supported commands */
++static int nntp_attempt_features (NNTP_SERVER *nserv)
++{
++ CONNECTION *conn = nserv->conn;
++ char buf[LONG_STRING];
++
++ /* no CAPABILITIES, trying DATE, LISTGROUP, LIST NEWSGROUPS */
++ if (!nserv->hasCAPABILITIES)
++ {
++ if (mutt_socket_write (conn, "DATE\r\n") < 0 ||
++ mutt_socket_readln (buf, sizeof (buf), conn) < 0)
++ return nntp_connect_error (nserv);
++ if (mutt_strncmp ("500", buf, 3))
++ nserv->hasDATE = 1;
++
++ if (mutt_socket_write (conn, "LISTGROUP\r\n") < 0 ||
++ mutt_socket_readln (buf, sizeof (buf), conn) < 0)
++ return nntp_connect_error (nserv);
++ if (mutt_strncmp ("500", buf, 3))
++ nserv->hasLISTGROUP = 1;
++
++ if (mutt_socket_write (conn, "LIST NEWSGROUPS +\r\n") < 0 ||
++ mutt_socket_readln (buf, sizeof (buf), conn) < 0)
++ return nntp_connect_error (nserv);
++ if (mutt_strncmp ("500", buf, 3))
++ nserv->hasLIST_NEWSGROUPS = 1;
++ if (!mutt_strncmp ("215", buf, 3))
++ {
++ do
++ {
++ if (mutt_socket_readln (buf, sizeof (buf), conn) < 0)
++ return nntp_connect_error (nserv);
++ } while (mutt_strcmp (".", buf));
++ }
++ }
++
++ /* no LIST NEWSGROUPS, trying XGTITLE */
++ if (!nserv->hasLIST_NEWSGROUPS)
++ {
++ if (mutt_socket_write (conn, "XGTITLE\r\n") < 0 ||
++ mutt_socket_readln (buf, sizeof (buf), conn) < 0)
++ return nntp_connect_error (nserv);
++ if (mutt_strncmp ("500", buf, 3))
++ nserv->hasXGTITLE = 1;
++ }
++
++ /* no OVER, trying XOVER */
++ if (!nserv->hasOVER)
++ {
++ if (mutt_socket_write (conn, "XOVER\r\n") < 0 ||
++ mutt_socket_readln (buf, sizeof (buf), conn) < 0)
++ return nntp_connect_error (nserv);
++ if (mutt_strncmp ("500", buf, 3))
++ nserv->hasXOVER = 1;
++ }
++
++ /* trying LIST OVERVIEW.FMT */
++ if (nserv->hasOVER || nserv->hasXOVER)
++ {
++ if (mutt_socket_write (conn, "LIST OVERVIEW.FMT\r\n") < 0 ||
++ mutt_socket_readln (buf, sizeof (buf), conn) < 0)
++ return nntp_connect_error (nserv);
++ if (mutt_strncmp ("215", buf, 3))
++ nserv->overview_fmt = OverviewFmt;
++ else
++ {
++ int chunk, cont = 0;
++ size_t buflen = 2 * LONG_STRING, off = 0, b = 0;
++
++ if (nserv->overview_fmt)
++ FREE (&nserv->overview_fmt);
++ nserv->overview_fmt = safe_malloc (buflen);
++
++ while (1)
++ {
++ if (buflen - off < LONG_STRING)
++ {
++ buflen *= 2;
++ safe_realloc (&nserv->overview_fmt, buflen);
++ }
++
++ chunk = mutt_socket_readln (nserv->overview_fmt + off,
++ buflen - off, conn);
++ if (chunk < 0)
++ {
++ FREE (&nserv->overview_fmt);
++ return nntp_connect_error (nserv);
++ }
++
++ if (!cont && !mutt_strcmp (".", nserv->overview_fmt + off))
++ break;
++
++ cont = chunk >= buflen - off ? 1 : 0;
++ off += strlen (nserv->overview_fmt + off);
++ if (!cont)
++ {
++ char *colon;
++
++ if (nserv->overview_fmt[b] == ':')
++ {
++ memmove (nserv->overview_fmt + b,
++ nserv->overview_fmt + b + 1, off - b - 1);
++ nserv->overview_fmt[off - 1] = ':';
++ }
++ colon = strchr (nserv->overview_fmt + b, ':');
++ if (!colon)
++ nserv->overview_fmt[off++] = ':';
++ else if (strcmp (colon + 1, "full"))
++ off = colon + 1 - nserv->overview_fmt;
++ if (!strcasecmp (nserv->overview_fmt + b, "Bytes:"))
++ {
++ strcpy (nserv->overview_fmt + b, "Content-Length:");
++ off = b + strlen (nserv->overview_fmt + b);
++ }
++ nserv->overview_fmt[off++] = '\0';
++ b = off;
++ }
++ }
++ nserv->overview_fmt[off++] = '\0';
++ safe_realloc (&nserv->overview_fmt, off);
++ }
++ }
++ return 0;
++}
++
++/* Get login, password and authenticate */
++static int nntp_auth (NNTP_SERVER *nserv)
++{
++ CONNECTION *conn = nserv->conn;
++ char buf[LONG_STRING];
++ char authenticators[LONG_STRING] = "USER";
++ char *method, *a, *p;
++ unsigned char flags = conn->account.flags;
++
++ while (1)
++ {
++ /* get login and password */
++ if (mutt_account_getuser (&conn->account) || !conn->account.user[0] ||
++ mutt_account_getpass (&conn->account) || !conn->account.pass[0])
++ break;
++
++ /* get list of authenticators */
++ if (NntpAuthenticators && *NntpAuthenticators)
++ strfcpy (authenticators, NntpAuthenticators, sizeof (authenticators));
++ else if (nserv->hasCAPABILITIES)
++ {
++ strfcpy (authenticators, NONULL (nserv->authenticators),
++ sizeof (authenticators));
++ p = authenticators;
++ while (*p)
++ {
++ if (*p == ' ')
++ *p = ':';
++ p++;
++ }
++ }
++ p = authenticators;
++ while (*p)
++ {
++ *p = ascii_toupper (*p);
++ p++;
++ }
++
++ dprint (1, (debugfile,
++ "nntp_auth: available methods: %s\n", nserv->authenticators));
++ a = authenticators;
++ while (1)
++ {
++ if (!a)
++ {
++ mutt_error _("No authenticators available");
++ mutt_sleep (2);
++ break;
++ }
++
++ method = a;
++ a = strchr (a, ':');
++ if (a)
++ *a++ = '\0';
++
++ /* check authenticator */
++ if (nserv->hasCAPABILITIES)
++ {
++ char *m;
++
++ if (!nserv->authenticators)
++ continue;
++ m = strcasestr (nserv->authenticators, method);
++ if (!m)
++ continue;
++ if (m > nserv->authenticators && *(m - 1) != ' ')
++ continue;
++ m += strlen (method);
++ if (*m != '\0' && *m != ' ')
++ continue;
++ }
++ dprint (1, (debugfile, "nntp_auth: trying method %s\n", method));
++
++ /* AUTHINFO USER authentication */
++ if (!strcmp (method, "USER"))
++ {
++ mutt_message (_("Authenticating (%s)..."), method);
++ snprintf (buf, sizeof (buf), "AUTHINFO USER %s\r\n", conn->account.user);
++ if (mutt_socket_write (conn, buf) < 0 ||
++ mutt_socket_readln (buf, sizeof (buf), conn) < 0)
++ break;
++
++ /* authenticated, password is not required */
++ if (!mutt_strncmp ("281", buf, 3))
++ return 0;
++
++ /* username accepted, sending password */
++ if (!mutt_strncmp ("381", buf, 3))
++ {
++#ifdef DEBUG
++ if (debuglevel < M_SOCK_LOG_FULL)
++ dprint (M_SOCK_LOG_CMD, (debugfile,
++ "%d> AUTHINFO PASS *\n", conn->fd));
++#endif
++ snprintf (buf, sizeof (buf), "AUTHINFO PASS %s\r\n",
++ conn->account.pass);
++ if (mutt_socket_write_d (conn, buf, -1, M_SOCK_LOG_FULL) < 0 ||
++ mutt_socket_readln (buf, sizeof (buf), conn) < 0)
++ break;
++
++ /* authenticated */
++ if (!mutt_strncmp ("281", buf, 3))
++ return 0;
++ }
++
++ /* server doesn't support AUTHINFO USER, trying next method */
++ if (*buf == '5')
++ continue;
++ }
++
++ else
++ {
++#ifdef USE_SASL
++ sasl_conn_t *saslconn;
++ sasl_interact_t *interaction = NULL;
++ int rc;
++ char inbuf[LONG_STRING] = "";
++ const char *mech;
++ const char *client_out = NULL;
++ unsigned int client_len, len;
++
++ if (mutt_sasl_client_new (conn, &saslconn) < 0)
++ {
++ dprint (1, (debugfile,
++ "nntp_auth: error allocating SASL connection.\n"));
++ continue;
++ }
++
++ while (1)
++ {
++ rc = sasl_client_start (saslconn, method, &interaction,
++ &client_out, &client_len, &mech);
++ if (rc != SASL_INTERACT)
++ break;
++ mutt_sasl_interact (interaction);
++ }
++ if (rc != SASL_OK && rc != SASL_CONTINUE)
++ {
++ sasl_dispose (&saslconn);
++ dprint (1, (debugfile,
++ "nntp_auth: error starting SASL authentication exchange.\n"));
++ continue;
++ }
++
++ mutt_message (_("Authenticating (%s)..."), method);
++ snprintf (buf, sizeof (buf), "AUTHINFO SASL %s", method);
++
++ /* looping protocol */
++ while (rc == SASL_CONTINUE || (rc == SASL_OK && client_len))
++ {
++ /* send out client response */
++ if (client_len)
++ {
++#ifdef DEBUG
++ if (debuglevel >= M_SOCK_LOG_FULL)
++ {
++ char tmp[LONG_STRING];
++ memcpy (tmp, client_out, client_len);
++ for (p = tmp; p < tmp + client_len; p++)
++ {
++ if (*p == '\0')
++ *p = '.';
++ }
++ *p = '\0';
++ dprint (1, (debugfile, "SASL> %s\n", tmp));
++ }
++#endif
++
++ if (*buf)
++ safe_strcat (buf, sizeof (buf), " ");
++ len = strlen (buf);
++ if (sasl_encode64 (client_out, client_len,
++ buf + len, sizeof (buf) - len, &len) != SASL_OK)
++ {
++ dprint (1, (debugfile,
++ "nntp_auth: error base64-encoding client response.\n"));
++ break;
++ }
++ }
++
++ safe_strcat (buf, sizeof (buf), "\r\n");
++#ifdef DEBUG
++ if (debuglevel < M_SOCK_LOG_FULL)
++ {
++ if (strchr (buf, ' '))
++ dprint (M_SOCK_LOG_CMD, (debugfile, "%d> AUTHINFO SASL %s%s\n",
++ conn->fd, method, client_len ? " sasl_data" : ""));
++ else
++ dprint (M_SOCK_LOG_CMD, (debugfile, "%d> sasl_data\n", conn->fd));
++ }
++#endif
++ client_len = 0;
++ if (mutt_socket_write_d (conn, buf, -1, M_SOCK_LOG_FULL) < 0 ||
++ mutt_socket_readln_d (inbuf, sizeof (inbuf), conn, M_SOCK_LOG_FULL) < 0)
++ break;
++ if (mutt_strncmp (inbuf, "283 ", 4) &&
++ mutt_strncmp (inbuf, "383 ", 4))
++ {
++#ifdef DEBUG
++ if (debuglevel < M_SOCK_LOG_FULL)
++ dprint (M_SOCK_LOG_CMD, (debugfile, "%d< %s\n", conn->fd, inbuf));
++#endif
++ break;
++ }
++#ifdef DEBUG
++ if (debuglevel < M_SOCK_LOG_FULL)
++ {
++ inbuf[3] = '\0';
++ dprint (M_SOCK_LOG_CMD, (debugfile,
++ "%d< %s sasl_data\n", conn->fd, inbuf));
++ }
++#endif
++
++ if (!strcmp ("=", inbuf + 4))
++ len = 0;
++ else if (sasl_decode64 (inbuf + 4, strlen (inbuf + 4),
++ buf, sizeof (buf) - 1, &len) != SASL_OK)
++ {
++ dprint (1, (debugfile,
++ "nntp_auth: error base64-decoding server response.\n"));
++ break;
++ }
++#ifdef DEBUG
++ else if (debuglevel >= M_SOCK_LOG_FULL)
++ {
++ char tmp[LONG_STRING];
++ memcpy (tmp, buf, len);
++ for (p = tmp; p < tmp + len; p++)
++ {
++ if (*p == '\0')
++ *p = '.';
++ }
++ *p = '\0';
++ dprint (1, (debugfile, "SASL< %s\n", tmp));
++ }
++#endif
++
++ while (1)
++ {
++ rc = sasl_client_step (saslconn, buf, len,
++ &interaction, &client_out, &client_len);
++ if (rc != SASL_INTERACT)
++ break;
++ mutt_sasl_interact (interaction);
++ }
++ if (*inbuf != '3')
++ break;
++
++ *buf = '\0';
++ } /* looping protocol */
++
++ if (rc == SASL_OK && client_len == 0 && *inbuf == '2')
++ {
++ mutt_sasl_setup_conn (conn, saslconn);
++ return 0;
++ }
++
++ /* terminate SASL sessoin */
++ sasl_dispose (&saslconn);
++ if (conn->fd < 0)
++ break;
++ if (!mutt_strncmp (inbuf, "383 ", 4))
++ {
++ if (mutt_socket_write (conn, "*\r\n") < 0 ||
++ mutt_socket_readln (inbuf, sizeof (inbuf), conn) < 0)
++ break;
++ }
++
++ /* server doesn't support AUTHINFO SASL, trying next method */
++ if (*inbuf == '5')
++ continue;
++#else
++ continue;
++#endif /* USE_SASL */
++ }
++
++ mutt_error (_("%s authentication failed."), method);
++ mutt_sleep (2);
++ break;
++ }
++ break;
++ }
++
++ /* error */
++ nserv->status = NNTP_BYE;
++ conn->account.flags = flags;
++ if (conn->fd < 0)
++ {
++ mutt_error _("Server closed connection!");
++ mutt_sleep (2);
++ }
++ else
++ mutt_socket_close (conn);
++ return -1;
++}
++
++/* Connect to server, authenticate and get capabilities */
++int nntp_open_connection (NNTP_SERVER *nserv)
++{
++ CONNECTION *conn = nserv->conn;
++ char buf[STRING];
++ int cap;
++ unsigned int posting = 0, auth = 1;
++
++ if (nserv->status == NNTP_OK)
++ return 0;
++ if (nserv->status == NNTP_BYE)
++ return -1;
++ nserv->status = NNTP_NONE;
++
++ if (mutt_socket_open (conn) < 0)
++ return -1;
++
++ if (mutt_socket_readln (buf, sizeof (buf), conn) < 0)
++ return nntp_connect_error (nserv);
++
++ if (!mutt_strncmp ("200", buf, 3))
++ posting = 1;
++ else if (mutt_strncmp ("201", buf, 3))
++ {
++ mutt_socket_close (conn);
++ mutt_remove_trailing_ws (buf);
++ mutt_error ("%s", buf);
++ mutt_sleep (2);
++ return -1;
++ }
++
++ /* get initial capabilities */
++ cap = nntp_capabilities (nserv);
++ if (cap < 0)
++ return -1;
++
++ /* tell news server to switch to mode reader if it isn't so */
++ if (cap > 0)
++ {
++ if (mutt_socket_write (conn, "MODE READER\r\n") < 0 ||
++ mutt_socket_readln (buf, sizeof (buf), conn) < 0)
++ return nntp_connect_error (nserv);
++
++ if (!mutt_strncmp ("200", buf, 3))
++ posting = 1;
++ else if (!mutt_strncmp ("201", buf, 3))
++ posting = 0;
++ /* error if has capabilities, ignore result if no capabilities */
++ else if (nserv->hasCAPABILITIES)
++ {
++ mutt_socket_close (conn);
++ mutt_error _("Could not switch to reader mode.");
++ mutt_sleep (2);
++ return -1;
++ }
++
++ /* recheck capabilities after MODE READER */
++ if (nserv->hasCAPABILITIES)
++ {
++ cap = nntp_capabilities (nserv);
++ if (cap < 0)
++ return -1;
++ }
++ }
++
++ mutt_message (_("Connected to %s. %s"), conn->account.host,
++ posting ? _("Posting is ok.") : _("Posting is NOT ok."));
++ mutt_sleep (1);
++
++#if defined(USE_SSL)
++ /* Attempt STARTTLS if available and desired. */
++ if (nserv->use_tls != 1 && (nserv->hasSTARTTLS || option (OPTSSLFORCETLS)))
++ {
++ if (nserv->use_tls == 0)
++ nserv->use_tls = option (OPTSSLFORCETLS) ||
++ query_quadoption (OPT_SSLSTARTTLS,
++ _("Secure connection with TLS?")) == M_YES ? 2 : 1;
++ if (nserv->use_tls == 2)
++ {
++ if (mutt_socket_write (conn, "STARTTLS\r\n") < 0 ||
++ mutt_socket_readln (buf, sizeof (buf), conn) < 0)
++ return nntp_connect_error (nserv);
++ if (mutt_strncmp ("382", buf, 3))
++ {
++ nserv->use_tls = 0;
++ mutt_error ("STARTTLS: %s", buf);
++ mutt_sleep (2);
++ }
++ else if (mutt_ssl_starttls (conn))
++ {
++ nserv->use_tls = 0;
++ nserv->status = NNTP_NONE;
++ mutt_socket_close (nserv->conn);
++ mutt_error _("Could not negotiate TLS connection");
++ mutt_sleep (2);
++ return -1;
++ }
++ else
++ {
++ /* recheck capabilities after STARTTLS */
++ cap = nntp_capabilities (nserv);
++ if (cap < 0)
++ return -1;
++ }
++ }
++ }
++#endif
++
++ /* authentication required? */
++ if (conn->account.flags & M_ACCT_USER)
++ {
++ if (!conn->account.user[0])
++ auth = 0;
++ }
++ else
++ {
++ if (mutt_socket_write (conn, "STAT\r\n") < 0 ||
++ mutt_socket_readln (buf, sizeof (buf), conn) < 0)
++ return nntp_connect_error (nserv);
++ if (mutt_strncmp ("480", buf, 3))
++ auth = 0;
++ }
++
++ /* authenticate */
++ if (auth && nntp_auth (nserv) < 0)
++ return -1;
++
++ /* get final capabilities after authentication */
++ if (nserv->hasCAPABILITIES && (auth || cap > 0))
++ {
++ cap = nntp_capabilities (nserv);
++ if (cap < 0)
++ return -1;
++ if (cap > 0)
++ {
++ mutt_socket_close (conn);
++ mutt_error _("Could not switch to reader mode.");
++ mutt_sleep (2);
++ return -1;
++ }
++ }
++
++ /* attempt features */
++ if (nntp_attempt_features (nserv) < 0)
++ return -1;
++
++ nserv->status = NNTP_OK;
++ return 0;
++}
++
++/* Send data from buffer and receive answer to same buffer */
++static int nntp_query (NNTP_DATA *nntp_data, char *line, size_t linelen)
++{
++ NNTP_SERVER *nserv = nntp_data->nserv;
++ char buf[LONG_STRING];
++
++ if (nserv->status == NNTP_BYE)
++ return -1;
++
++ while (1)
++ {
++ if (nserv->status == NNTP_OK)
++ {
++ int rc = 0;
++
++ if (*line)
++ rc = mutt_socket_write (nserv->conn, line);
++ else if (nntp_data->group)
++ {
++ snprintf (buf, sizeof (buf), "GROUP %s\r\n", nntp_data->group);
++ rc = mutt_socket_write (nserv->conn, buf);
++ }
++ if (rc >= 0)
++ rc = mutt_socket_readln (buf, sizeof (buf), nserv->conn);
++ if (rc >= 0)
++ break;
++ }
++
++ /* reconnect */
++ while (1)
++ {
++ nserv->status = NNTP_NONE;
++ if (nntp_open_connection (nserv) == 0)
++ break;
++
++ snprintf (buf, sizeof (buf), _("Connection to %s lost. Reconnect?"),
++ nserv->conn->account.host);
++ if (mutt_yesorno (buf, M_YES) != M_YES)
++ {
++ nserv->status = NNTP_BYE;
++ return -1;
++ }
++ }
++
++ /* select newsgroup after reconnection */
++ if (nntp_data->group)
++ {
++ snprintf (buf, sizeof (buf), "GROUP %s\r\n", nntp_data->group);
++ if (mutt_socket_write (nserv->conn, buf) < 0 ||
++ mutt_socket_readln (buf, sizeof (buf), nserv->conn) < 0)
++ return nntp_connect_error (nserv);
++ }
++ if (!*line)
++ break;
++ }
++
++ strfcpy (line, buf, linelen);
++ return 0;
++}
++
++/* This function calls funct(*line, *data) for each received line,
++ * funct(NULL, *data) if rewind(*data) needs, exits when fail or done:
++ * 0 - success
++ * 1 - bad response (answer in query buffer)
++ * -1 - conection lost
++ * -2 - error in funct(*line, *data) */
++static int nntp_fetch_lines (NNTP_DATA *nntp_data, char *query, size_t qlen,
++ char *msg, int (*funct) (char *, void *), void *data)
++{
++ int done = FALSE;
++ int rc;
++
++ while (!done)
++ {
++ char buf[LONG_STRING];
++ char *line;
++ unsigned int lines = 0;
++ size_t off = 0;
++ progress_t progress;
++
++ if (msg)
++ mutt_progress_init (&progress, msg, M_PROGRESS_MSG, ReadInc, -1);
++
++ strfcpy (buf, query, sizeof (buf));
++ if (nntp_query (nntp_data, buf, sizeof (buf)) < 0)
++ return -1;
++ if (buf[0] != '2')
++ {
++ strfcpy (query, buf, qlen);
++ return 1;
++ }
++
++ line = safe_malloc (sizeof (buf));
++ rc = 0;
++
++ while (1)
++ {
++ char *p;
++ int chunk = mutt_socket_readln_d (buf, sizeof (buf),
++ nntp_data->nserv->conn, M_SOCK_LOG_HDR);
++ if (chunk < 0)
++ {
++ nntp_data->nserv->status = NNTP_NONE;
++ break;
++ }
++
++ p = buf;
++ if (!off && buf[0] == '.')
++ {
++ if (buf[1] == '\0')
++ {
++ done = TRUE;
++ break;
++ }
++ if (buf[1] == '.')
++ p++;
++ }
++
++ strfcpy (line + off, p, sizeof (buf));
++
++ if (chunk >= sizeof (buf))
++ off += strlen (p);
++ else
++ {
++ if (msg)
++ mutt_progress_update (&progress, ++lines, -1);
++
++ if (rc == 0 && funct (line, data) < 0)
++ rc = -2;
++ off = 0;
++ }
++
++ safe_realloc (&line, off + sizeof (buf));
++ }
++ FREE (&line);
++ funct (NULL, data);
++ }
++ return rc;
++}
++
++/* Parse newsgroup description */
++static int fetch_description (char *line, void *data)
++{
++ NNTP_SERVER *nserv = data;
++ NNTP_DATA *nntp_data;
++ char *desc;
++
++ if (!line)
++ return 0;
++
++ desc = strpbrk (line, " \t");
++ if (desc)
++ {
++ *desc++ = '\0';
++ desc += strspn (desc, " \t");
++ }
++ else
++ desc = strchr (line, '\0');
++
++ nntp_data = hash_find (nserv->groups_hash, line);
++ if (nntp_data && mutt_strcmp (desc, nntp_data->desc))
++ {
++ mutt_str_replace (&nntp_data->desc, desc);
++ dprint (2, (debugfile, "group: %s, desc: %s\n", line, desc));
++ }
++ return 0;
++}
++
++/* Fetch newsgroups descriptions.
++ * Returns the same code as nntp_fetch_lines() */
++static int get_description (NNTP_DATA *nntp_data, char *wildmat, char *msg)
++{
++ NNTP_SERVER *nserv;
++ char buf[STRING];
++ char *cmd;
++ int rc;
++
++ /* get newsgroup description, if possible */
++ nserv = nntp_data->nserv;
++ if (!wildmat)
++ wildmat = nntp_data->group;
++ if (nserv->hasLIST_NEWSGROUPS)
++ cmd = "LIST NEWSGROUPS";
++ else if (nserv->hasXGTITLE)
++ cmd = "XGTITLE";
++ else
++ return 0;
++
++ snprintf (buf, sizeof (buf), "%s %s\r\n", cmd, wildmat);
++ rc = nntp_fetch_lines (nntp_data, buf, sizeof (buf), msg,
++ fetch_description, nserv);
++ if (rc > 0)
++ {
++ mutt_error ("%s: %s", cmd, buf);
++ mutt_sleep (2);
++ }
++ return rc;
++}
++
++/* Update read flag and set article number if empty */
++static void nntp_parse_xref (CONTEXT *ctx, HEADER *hdr)
++{
++ NNTP_DATA *nntp_data = ctx->data;
++ char *buf, *p;
++
++ buf = p = safe_strdup (hdr->env->xref);
++ while (p)
++ {
++ char *grp, *colon;
++ anum_t anum;
++
++ /* skip to next word */
++ p += strspn (p, " \t");
++ grp = p;
++
++ /* skip to end of word */
++ p = strpbrk (p, " \t");
++ if (p)
++ *p++ = '\0';
++
++ /* find colon */
++ colon = strchr (grp, ':');
++ if (!colon)
++ continue;
++ *colon++ = '\0';
++ if (sscanf (colon, ANUM, &anum) != 1)
++ continue;
++
++ nntp_article_status (ctx, hdr, grp, anum);
++ if (hdr && !NHDR (hdr)->article_num && !mutt_strcmp (nntp_data->group, grp))
++ NHDR (hdr)->article_num = anum;
++ }
++ FREE (&buf);
++}
++
++/* Write line to temporarily file */
++static int fetch_tempfile (char *line, void *data)
++{
++ FILE *fp = data;
++
++ if (!line)
++ rewind (fp);
++ else if (fputs (line, fp) == EOF || fputc ('\n', fp) == EOF)
++ return -1;
++ return 0;
++}
++
++typedef struct
++{
++ CONTEXT *ctx;
++ anum_t first;
++ anum_t last;
++ int restore;
++ unsigned char *messages;
++ progress_t progress;
++#ifdef USE_HCACHE
++ header_cache_t *hc;
++#endif
++} FETCH_CTX;
++
++/* Parse article number */
++static int fetch_numbers (char *line, void *data)
++{
++ FETCH_CTX *fc = data;
++ anum_t anum;
++
++ if (!line)
++ return 0;
++ if (sscanf (line, ANUM, &anum) != 1)
++ return 0;
++ if (anum < fc->first || anum > fc->last)
++ return 0;
++ fc->messages[anum - fc->first] = 1;
++ return 0;
++}
++
++/* Parse overview line */
++static int parse_overview_line (char *line, void *data)
++{
++ FETCH_CTX *fc = data;
++ CONTEXT *ctx = fc->ctx;
++ NNTP_DATA *nntp_data = ctx->data;
++ HEADER *hdr;
++ FILE *fp;
++ char tempfile[_POSIX_PATH_MAX];
++ char *header, *field;
++ int save = 1;
++ anum_t anum;
++
++ if (!line)
++ return 0;
++
++ /* parse article number */
++ field = strchr (line, '\t');
++ if (field)
++ *field++ = '\0';
++ if (sscanf (line, ANUM, &anum) != 1)
++ return 0;
++ dprint (2, (debugfile, "parse_overview_line: " ANUM "\n", anum));
++
++ /* out of bounds */
++ if (anum < fc->first || anum > fc->last)
++ return 0;
++
++ /* not in LISTGROUP */
++ if (!fc->messages[anum - fc->first])
++ {
++ /* progress */
++ if (!ctx->quiet)
++ mutt_progress_update (&fc->progress, anum - fc->first + 1, -1);
++ return 0;
++ }
++
++ /* convert overview line to header */
++ mutt_mktemp (tempfile, sizeof (tempfile));
++ fp = safe_fopen (tempfile, "w+");
++ if (!fp)
++ return -1;
++
++ header = nntp_data->nserv->overview_fmt;
++ while (field)
++ {
++ char *b = field;
++
++ if (*header)
++ {
++ if (strstr (header, ":full") == NULL && fputs (header, fp) == EOF)
++ {
++ fclose (fp);
++ unlink (tempfile);
++ return -1;
++ }
++ header = strchr (header, '\0') + 1;
++ }
++
++ field = strchr (field, '\t');
++ if (field)
++ *field++ = '\0';
++ if (fputs (b, fp) == EOF || fputc ('\n', fp) == EOF)
++ {
++ fclose (fp);
++ unlink (tempfile);
++ return -1;
++ }
++ }
++ rewind (fp);
++
++ /* allocate memory for headers */
++ if (ctx->msgcount >= ctx->hdrmax)
++ mx_alloc_memory (ctx);
++
++ /* parse header */
++ hdr = ctx->hdrs[ctx->msgcount] = mutt_new_header ();
++ hdr->env = mutt_read_rfc822_header (fp, hdr, 0, 0);
++ hdr->env->newsgroups = safe_strdup (nntp_data->group);
++ hdr->received = hdr->date_sent;
++ fclose (fp);
++ unlink (tempfile);
++
++#ifdef USE_HCACHE
++ if (fc->hc)
++ {
++ void *hdata;
++ char buf[16];
++
++ /* try to replace with header from cache */
++ snprintf (buf, sizeof (buf), "%d", anum);
++ hdata = mutt_hcache_fetch (fc->hc, buf, strlen);
++ if (hdata)
++ {
++ dprint (2, (debugfile,
++ "parse_overview_line: mutt_hcache_fetch %s\n", buf));
++ mutt_free_header (&hdr);
++ ctx->hdrs[ctx->msgcount] =
++ hdr = mutt_hcache_restore (hdata, NULL);
++ hdr->read = 0;
++ hdr->old = 0;
++
++ /* skip header marked as deleted in cache */
++ if (hdr->deleted && !fc->restore)
++ {
++ if (nntp_data->bcache)
++ {
++ dprint (2, (debugfile,
++ "parse_overview_line: mutt_bcache_del %s\n", buf));
++ mutt_bcache_del (nntp_data->bcache, buf);
++ }
++ save = 0;
++ }
++ }
++
++ /* not chached yet, store header */
++ else
++ {
++ dprint (2, (debugfile,
++ "parse_overview_line: mutt_hcache_store %s\n", buf));
++ mutt_hcache_store (fc->hc, buf, hdr, 0, strlen, M_GENERATE_UIDVALIDITY);
++ }
++ }
++#endif
++
++ if (save)
++ {
++ hdr->index = ctx->msgcount++;
++ hdr->read = 0;
++ hdr->old = 0;
++ hdr->deleted = 0;
++ hdr->data = safe_calloc (1, sizeof (NNTP_HEADER_DATA));
++ NHDR (hdr)->article_num = anum;
++ if (fc->restore)
++ hdr->changed = 1;
++ else
++ {
++ nntp_article_status (ctx, hdr, NULL, anum);
++ if (!hdr->read)
++ nntp_parse_xref (ctx, hdr);
++ }
++ if (anum > nntp_data->lastLoaded)
++ nntp_data->lastLoaded = anum;
++ }
++ else
++ mutt_free_header (&hdr);
++
++ /* progress */
++ if (!ctx->quiet)
++ mutt_progress_update (&fc->progress, anum - fc->first + 1, -1);
++ return 0;
++}
++
++/* Fetch headers */
++static int nntp_fetch_headers (CONTEXT *ctx, void *hc,
++ anum_t first, anum_t last, int restore)
++{
++ NNTP_DATA *nntp_data = ctx->data;
++ FETCH_CTX fc;
++ HEADER *hdr;
++ char buf[HUGE_STRING];
++ int rc = 0;
++ int oldmsgcount = ctx->msgcount;
++ anum_t current;
++#ifdef USE_HCACHE
++ void *hdata;
++#endif
++
++ /* if empty group or nothing to do */
++ if (!last || first > last)
++ return 0;
++
++ /* init fetch context */
++ fc.ctx = ctx;
++ fc.first = first;
++ fc.last = last;
++ fc.restore = restore;
++ fc.messages = safe_calloc (last - first + 1, sizeof (unsigned char));
++#ifdef USE_HCACHE
++ fc.hc = hc;
++#endif
++
++ /* fetch list of articles */
++ if (nntp_data->nserv->hasLISTGROUP && !nntp_data->deleted)
++ {
++ if (!ctx->quiet)
++ mutt_message _("Fetching list of articles...");
++ snprintf (buf, sizeof (buf), "LISTGROUP %s\r\n", nntp_data->group);
++ rc = nntp_fetch_lines (nntp_data, buf, sizeof (buf), NULL,
++ fetch_numbers, &fc);
++ if (rc > 0)
++ {
++ mutt_error ("LISTGROUP: %s", buf);
++ mutt_sleep (2);
++ }
++ if (rc == 0)
++ {
++ for (current = first; current <= last && rc == 0; current++)
++ {
++ if (fc.messages[current - first])
++ continue;
++
++ snprintf (buf, sizeof (buf), "%d", current);
++ if (nntp_data->bcache)
++ {
++ dprint (2, (debugfile,
++ "nntp_fetch_headers: mutt_bcache_del %s\n", buf));
++ mutt_bcache_del (nntp_data->bcache, buf);
++ }
++
++#ifdef USE_HCACHE
++ if (fc.hc)
++ {
++ dprint (2, (debugfile,
++ "nntp_fetch_headers: mutt_hcache_delete %s\n", buf));
++ mutt_hcache_delete (fc.hc, buf, strlen);
++ }
++#endif
++ }
++ }
++ }
++ else
++ for (current = first; current <= last; current++)
++ fc.messages[current - first] = 1;
++
++ /* fetching header from cache or server, or fallback to fetch overview */
++ if (!ctx->quiet)
++ mutt_progress_init (&fc.progress, _("Fetching message headers..."),
++ M_PROGRESS_MSG, ReadInc, last - first + 1);
++ for (current = first; current <= last && rc == 0; current++)
++ {
++ if (!ctx->quiet)
++ mutt_progress_update (&fc.progress, current - first + 1, -1);
++
++#ifdef USE_HCACHE
++ snprintf (buf, sizeof (buf), "%d", current);
++#endif
++
++ /* delete header from cache that does not exist on server */
++ if (!fc.messages[current - first])
++ continue;
++
++ /* allocate memory for headers */
++ if (ctx->msgcount >= ctx->hdrmax)
++ mx_alloc_memory (ctx);
++
++#ifdef USE_HCACHE
++ /* try to fetch header from cache */
++ hdata = mutt_hcache_fetch (fc.hc, buf, strlen);
++ if (hdata)
++ {
++ dprint (2, (debugfile,
++ "nntp_fetch_headers: mutt_hcache_fetch %s\n", buf));
++ ctx->hdrs[ctx->msgcount] =
++ hdr = mutt_hcache_restore (hdata, NULL);
++
++ /* skip header marked as deleted in cache */
++ if (hdr->deleted && !restore)
++ {
++ mutt_free_header (&hdr);
++ if (nntp_data->bcache)
++ {
++ dprint (2, (debugfile,
++ "nntp_fetch_headers: mutt_bcache_del %s\n", buf));
++ mutt_bcache_del (nntp_data->bcache, buf);
++ }
++ continue;
++ }
++
++ hdr->read = 0;
++ hdr->old = 0;
++ }
++ else
++#endif
++
++ /* don't try to fetch header from removed newsgroup */
++ if (nntp_data->deleted)
++ continue;
++
++ /* fallback to fetch overview */
++ else if (nntp_data->nserv->hasOVER || nntp_data->nserv->hasXOVER)
++ break;
++
++ /* fetch header from server */
++ else
++ {
++ FILE *fp;
++ char tempfile[_POSIX_PATH_MAX];
++
++ mutt_mktemp (tempfile, sizeof (tempfile));
++ fp = safe_fopen (tempfile, "w+");
++ if (!fp)
++ {
++ mutt_perror (tempfile);
++ mutt_sleep (2);
++ unlink (tempfile);
++ rc = -1;
++ break;
++ }
++
++ snprintf (buf, sizeof (buf), "HEAD %d\r\n", current);
++ rc = nntp_fetch_lines (nntp_data, buf, sizeof (buf), NULL,
++ fetch_tempfile, fp);
++ if (rc)
++ {
++ fclose (fp);
++ unlink (tempfile);
++ if (rc < 0)
++ break;
++
++ /* invalid response */
++ if (mutt_strncmp ("423", buf, 3))
++ {
++ mutt_error ("HEAD: %s", buf);
++ mutt_sleep (2);
++ break;
++ }
++
++ /* no such article */
++ if (nntp_data->bcache)
++ {
++ snprintf (buf, sizeof (buf), "%d", current);
++ dprint (2, (debugfile,
++ "nntp_fetch_headers: mutt_bcache_del %s\n", buf));
++ mutt_bcache_del (nntp_data->bcache, buf);
++ }
++ rc = 0;
++ continue;
++ }
++
++ /* parse header */
++ hdr = ctx->hdrs[ctx->msgcount] = mutt_new_header ();
++ hdr->env = mutt_read_rfc822_header (fp, hdr, 0, 0);
++ hdr->received = hdr->date_sent;
++ fclose (fp);
++ unlink (tempfile);
++ }
++
++ /* save header in context */
++ hdr->index = ctx->msgcount++;
++ hdr->read = 0;
++ hdr->old = 0;
++ hdr->deleted = 0;
++ hdr->data = safe_calloc (1, sizeof (NNTP_HEADER_DATA));
++ NHDR (hdr)->article_num = current;
++ if (restore)
++ hdr->changed = 1;
++ else
++ {
++ nntp_article_status (ctx, hdr, NULL, NHDR (hdr)->article_num);
++ if (!hdr->read)
++ nntp_parse_xref (ctx, hdr);
++ }
++ if (current > nntp_data->lastLoaded)
++ nntp_data->lastLoaded = current;
++ }
++
++ /* fetch overview information */
++ if (current <= last && rc == 0) {
++ char *cmd = nntp_data->nserv->hasOVER ? "OVER" : "XOVER";
++ snprintf (buf, sizeof (buf), "%s %d-%d\r\n", cmd, current, last);
++ rc = nntp_fetch_lines (nntp_data, buf, sizeof (buf), NULL,
++ parse_overview_line, &fc);
++ if (rc > 0)
++ {
++ mutt_error ("%s: %s", cmd, buf);
++ mutt_sleep (2);
++ }
++ }
++
++ if (ctx->msgcount > oldmsgcount)
++ mx_update_context (ctx, ctx->msgcount - oldmsgcount);
++
++ FREE (&fc.messages);
++ if (rc != 0)
++ return -1;
++ mutt_clear_error ();
++ return 0;
++}
++
++/* Open newsgroup */
++int nntp_open_mailbox (CONTEXT *ctx)
++{
++ NNTP_SERVER *nserv;
++ NNTP_DATA *nntp_data;
++ char buf[HUGE_STRING];
++ char server[LONG_STRING];
++ char *group;
++ int rc;
++ void *hc = NULL;
++ anum_t first, last, count = 0;
++ ciss_url_t url;
++
++ strfcpy (buf, ctx->path, sizeof (buf));
++ if (url_parse_ciss (&url, buf) < 0 || !url.path ||
++ !(url.scheme == U_NNTP || url.scheme == U_NNTPS))
++ {
++ mutt_error (_("%s is an invalid newsgroup specification!"), ctx->path);
++ mutt_sleep (2);
++ return -1;
++ }
++
++ group = url.path;
++ url.path = strchr (url.path, '\0');
++ url_ciss_tostring (&url, server, sizeof (server), 0);
++ nserv = nntp_select_server (server, 1);
++ if (!nserv)
++ return -1;
++ CurrentNewsSrv = nserv;
++
++ /* find news group data structure */
++ nntp_data = hash_find (nserv->groups_hash, group);
++ if (!nntp_data)
++ {
++ nntp_newsrc_close (nserv);
++ mutt_error (_("Newsgroup %s not found on the server."), group);
++ mutt_sleep (2);
++ return -1;
++ }
++
++ mutt_bit_unset (ctx->rights, M_ACL_INSERT);
++ if (!nntp_data->newsrc_ent && !nntp_data->subscribed &&
++ !option (OPTSAVEUNSUB))
++ ctx->readonly = 1;
++
++ /* select newsgroup */
++ mutt_message (_("Selecting %s..."), group);
++ buf[0] = '\0';
++ if (nntp_query (nntp_data, buf, sizeof (buf)) < 0)
++ {
++ nntp_newsrc_close (nserv);
++ return -1;
++ }
++
++ /* newsgroup not found, remove it */
++ if (!mutt_strncmp ("411", buf, 3))
++ {
++ mutt_error (_("Newsgroup %s has been removed from the server."),
++ nntp_data->group);
++ if (!nntp_data->deleted)
++ {
++ nntp_data->deleted = 1;
++ nntp_active_save_cache (nserv);
++ }
++ if (nntp_data->newsrc_ent && !nntp_data->subscribed &&
++ !option (OPTSAVEUNSUB))
++ {
++ FREE (&nntp_data->newsrc_ent);
++ nntp_data->newsrc_len = 0;
++ nntp_delete_group_cache (nntp_data);
++ nntp_newsrc_update (nserv);
++ }
++ mutt_sleep (2);
++ }
++
++ /* parse newsgroup info */
++ else {
++ if (sscanf (buf, "211 " ANUM " " ANUM " " ANUM, &count, &first, &last) != 3)
++ {
++ nntp_newsrc_close (nserv);
++ mutt_error ("GROUP: %s", buf);
++ mutt_sleep (2);
++ return -1;
++ }
++ nntp_data->firstMessage = first;
++ nntp_data->lastMessage = last;
++ nntp_data->deleted = 0;
++
++ /* get description if empty */
++ if (option (OPTLOADDESC) && !nntp_data->desc)
++ {
++ if (get_description (nntp_data, NULL, NULL) < 0)
++ {
++ nntp_newsrc_close (nserv);
++ return -1;
++ }
++ if (nntp_data->desc)
++ nntp_active_save_cache (nserv);
++ }
++ }
++
++ time (&nserv->check_time);
++ ctx->data = nntp_data;
++ ctx->mx_close = nntp_fastclose_mailbox;
++ if (!nntp_data->bcache && (nntp_data->newsrc_ent ||
++ nntp_data->subscribed || option (OPTSAVEUNSUB)))
++ nntp_data->bcache = mutt_bcache_open (&nserv->conn->account,
++ nntp_data->group);
++
++ /* strip off extra articles if adding context is greater than $nntp_context */
++ first = nntp_data->firstMessage;
++ if (NntpContext && nntp_data->lastMessage - first + 1 > NntpContext)
++ first = nntp_data->lastMessage - NntpContext + 1;
++ nntp_data->lastLoaded = first ? first - 1 : 0;
++ count = nntp_data->firstMessage;
++ nntp_data->firstMessage = first;
++ nntp_bcache_update (nntp_data);
++ nntp_data->firstMessage = count;
++#ifdef USE_HCACHE
++ hc = nntp_hcache_open (nntp_data);
++ nntp_hcache_update (nntp_data, hc);
++#endif
++ if (!hc)
++ {
++ mutt_bit_unset (ctx->rights, M_ACL_WRITE);
++ mutt_bit_unset (ctx->rights, M_ACL_DELETE);
++ }
++ nntp_newsrc_close (nserv);
++ rc = nntp_fetch_headers (ctx, hc, first, nntp_data->lastMessage, 0);
++#ifdef USE_HCACHE
++ mutt_hcache_close (hc);
++#endif
++ if (rc < 0)
++ return -1;
++ nntp_data->lastLoaded = nntp_data->lastMessage;
++ nserv->newsrc_modified = 0;
++ return 0;
++}
++
++/* Fetch message */
++int nntp_fetch_message (MESSAGE *msg, CONTEXT *ctx, int msgno)
++{
++ NNTP_DATA *nntp_data = ctx->data;
++ NNTP_ACACHE *acache;
++ HEADER *hdr = ctx->hdrs[msgno];
++ char buf[_POSIX_PATH_MAX];
++ char article[16];
++ char *fetch_msg = _("Fetching message...");
++ int rc;
++
++ /* try to get article from cache */
++ acache = &nntp_data->acache[hdr->index % NNTP_ACACHE_LEN];
++ if (acache->path)
++ {
++ if (acache->index == hdr->index)
++ {
++ msg->fp = fopen (acache->path, "r");
++ if (msg->fp)
++ return 0;
++ }
++ /* clear previous entry */
++ else
++ {
++ unlink (acache->path);
++ FREE (&acache->path);
++ }
++ }
++ snprintf (article, sizeof (article), "%d", NHDR (hdr)->article_num);
++ msg->fp = mutt_bcache_get (nntp_data->bcache, article);
++ if (msg->fp)
++ {
++ if (NHDR (hdr)->parsed)
++ return 0;
++ }
++ else
++ {
++ /* don't try to fetch article from removed newsgroup */
++ if (nntp_data->deleted)
++ return -1;
++
++ /* create new cache file */
++ mutt_message (fetch_msg);
++ msg->fp = mutt_bcache_put (nntp_data->bcache, article, 1);
++ if (!msg->fp)
++ {
++ mutt_mktemp (buf, sizeof (buf));
++ acache->path = safe_strdup (buf);
++ acache->index = hdr->index;
++ msg->fp = safe_fopen (acache->path, "w+");
++ if (!msg->fp)
++ {
++ mutt_perror (acache->path);
++ unlink (acache->path);
++ FREE (&acache->path);
++ return -1;
++ }
++ }
++
++ /* fetch message to cache file */
++ snprintf (buf, sizeof (buf), "ARTICLE %s\r\n",
++ NHDR (hdr)->article_num ? article : hdr->env->message_id);
++ rc = nntp_fetch_lines (nntp_data, buf, sizeof (buf), fetch_msg,
++ fetch_tempfile, msg->fp);
++ if (rc)
++ {
++ safe_fclose (&msg->fp);
++ if (acache->path)
++ {
++ unlink (acache->path);
++ FREE (&acache->path);
++ }
++ if (rc > 0)
++ {
++ if (!mutt_strncmp (NHDR (hdr)->article_num ? "423" : "430", buf, 3))
++ mutt_error (_("Article %d not found on the server."),
++ NHDR (hdr)->article_num ? article : hdr->env->message_id);
++ else
++ mutt_error ("ARTICLE: %s", buf);
++ }
++ return -1;
++ }
++
++ if (!acache->path)
++ mutt_bcache_commit (nntp_data->bcache, article);
++ }
++
++ /* replace envelope with new one
++ * hash elements must be updated because pointers will be changed */
++ if (ctx->id_hash && hdr->env->message_id)
++ hash_delete (ctx->id_hash, hdr->env->message_id, hdr, NULL);
++ if (ctx->subj_hash && hdr->env->real_subj)
++ hash_delete (ctx->subj_hash, hdr->env->real_subj, hdr, NULL);
++
++ mutt_free_envelope (&hdr->env);
++ hdr->env = mutt_read_rfc822_header (msg->fp, hdr, 0, 0);
++
++ if (ctx->id_hash && hdr->env->message_id)
++ hash_insert (ctx->id_hash, hdr->env->message_id, hdr, 0);
++ if (ctx->subj_hash && hdr->env->real_subj)
++ hash_insert (ctx->subj_hash, hdr->env->real_subj, hdr, 1);
++
++ /* fix content length */
++ fseek (msg->fp, 0, SEEK_END);
++ hdr->content->length = ftell (msg->fp) - hdr->content->offset;
++
++ /* this is called in mutt before the open which fetches the message,
++ * which is probably wrong, but we just call it again here to handle
++ * the problem instead of fixing it */
++ NHDR (hdr)->parsed = 1;
++ mutt_parse_mime_message (ctx, hdr);
++
++ /* these would normally be updated in mx_update_context(), but the
++ * full headers aren't parsed with overview, so the information wasn't
++ * available then */
++ if (WithCrypto)
++ hdr->security = crypt_query (hdr->content);
++
++ rewind (msg->fp);
++ mutt_clear_error();
++ return 0;
++}
++
++/* Post article */
++int nntp_post (const char *msg) {
++ NNTP_DATA *nntp_data, nntp_tmp;
++ FILE *fp;
++ char buf[LONG_STRING];
++ size_t len;
++
++ if (Context && Context->magic == M_NNTP)
++ nntp_data = Context->data;
++ else
++ {
++ CurrentNewsSrv = nntp_select_server (NewsServer, 0);
++ if (!CurrentNewsSrv)
++ return -1;
++
++ nntp_data = &nntp_tmp;
++ nntp_data->nserv = CurrentNewsSrv;
++ nntp_data->group = NULL;
++ }
++
++ fp = safe_fopen (msg, "r");
++ if (!fp)
++ {
++ mutt_perror (msg);
++ return -1;
++ }
++
++ strfcpy (buf, "POST\r\n", sizeof (buf));
++ if (nntp_query (nntp_data, buf, sizeof (buf)) < 0)
++ return -1;
++ if (buf[0] != '3')
++ {
++ mutt_error (_("Can't post article: %s"), buf);
++ return -1;
++ }
++
++ buf[0] = '.';
++ buf[1] = '\0';
++ while (fgets (buf + 1, sizeof (buf) - 2, fp))
++ {
++ len = strlen (buf);
++ if (buf[len - 1] == '\n')
++ {
++ buf[len - 1] = '\r';
++ buf[len] = '\n';
++ len++;
++ buf[len] = '\0';
++ }
++ if (mutt_socket_write_d (nntp_data->nserv->conn,
++ buf[1] == '.' ? buf : buf + 1, -1, M_SOCK_LOG_HDR) < 0)
++ return nntp_connect_error (nntp_data->nserv);
++ }
++ fclose (fp);
++
++ if ((buf[strlen (buf) - 1] != '\n' &&
++ mutt_socket_write_d (nntp_data->nserv->conn, "\r\n", -1, M_SOCK_LOG_HDR) < 0) ||
++ mutt_socket_write_d (nntp_data->nserv->conn, ".\r\n", -1, M_SOCK_LOG_HDR) < 0 ||
++ mutt_socket_readln (buf, sizeof (buf), nntp_data->nserv->conn) < 0)
++ return nntp_connect_error (nntp_data->nserv);
++ if (buf[0] != '2')
++ {
++ mutt_error (_("Can't post article: %s"), buf);
++ return -1;
++ }
++ return 0;
++}
++
++/* Save changes to .newsrc and cache */
++int nntp_sync_mailbox (CONTEXT *ctx)
++{
++ NNTP_DATA *nntp_data = ctx->data;
++ int rc, i;
++#ifdef USE_HCACHE
++ header_cache_t *hc;
++#endif
++
++ /* check for new articles */
++ nntp_data->nserv->check_time = 0;
++ rc = nntp_check_mailbox (ctx, 1);
++ if (rc)
++ return rc;
++
++#ifdef USE_HCACHE
++ nntp_data->lastCached = 0;
++ hc = nntp_hcache_open (nntp_data);
++#endif
++
++ nntp_data->unread = ctx->unread;
++ for (i = 0; i < ctx->msgcount; i++)
++ {
++ HEADER *hdr = ctx->hdrs[i];
++ char buf[16];
++
++ snprintf (buf, sizeof (buf), "%d", NHDR (hdr)->article_num);
++ if (nntp_data->bcache && hdr->deleted)
++ {
++ dprint (2, (debugfile, "nntp_sync_mailbox: mutt_bcache_del %s\n", buf));
++ mutt_bcache_del (nntp_data->bcache, buf);
++ }
++
++#ifdef USE_HCACHE
++ if (hc && (hdr->changed || hdr->deleted))
++ {
++ if (hdr->deleted && !hdr->read)
++ nntp_data->unread--;
++ dprint (2, (debugfile, "nntp_sync_mailbox: mutt_hcache_store %s\n", buf));
++ mutt_hcache_store (hc, buf, hdr, 0, strlen, M_GENERATE_UIDVALIDITY);
++ }
++#endif
++ }
++
++#ifdef USE_HCACHE
++ if (hc)
++ {
++ mutt_hcache_close (hc);
++ nntp_data->lastCached = nntp_data->lastLoaded;
++ }
++#endif
++
++ /* save .newsrc entries */
++ nntp_newsrc_gen_entries (ctx);
++ nntp_newsrc_update (nntp_data->nserv);
++ nntp_newsrc_close (nntp_data->nserv);
++ return 0;
++}
++
++/* Free up memory associated with the newsgroup context */
++int nntp_fastclose_mailbox (CONTEXT *ctx)
++{
++ NNTP_DATA *nntp_data = ctx->data, *nntp_tmp;
++
++ if (!nntp_data)
++ return 0;
++
++ nntp_acache_free (nntp_data);
++ if (!nntp_data->nserv || !nntp_data->nserv->groups_hash || !nntp_data->group)
++ return 0;
++
++ nntp_tmp = hash_find (nntp_data->nserv->groups_hash, nntp_data->group);
++ if (nntp_tmp == NULL || nntp_tmp != nntp_data)
++ nntp_data_free (nntp_data);
++ return 0;
++}
++
++/* Get date and time from server */
++int nntp_date (NNTP_SERVER *nserv, time_t *now)
++{
++ if (nserv->hasDATE)
++ {
++ NNTP_DATA nntp_data;
++ char buf[LONG_STRING];
++ struct tm tm;
++
++ nntp_data.nserv = nserv;
++ nntp_data.group = NULL;
++ strfcpy (buf, "DATE\r\n", sizeof (buf));
++ if (nntp_query (&nntp_data, buf, sizeof (buf)) < 0)
++ return -1;
++
++ if (sscanf (buf, "111 %4d%2d%2d%2d%2d%2d%*s", &tm.tm_year, &tm.tm_mon,
++ &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) == 6)
++ {
++ tm.tm_year -= 1900;
++ tm.tm_mon--;
++ *now = timegm (&tm);
++ if (*now >= 0)
++ {
++ dprint (1, (debugfile, "nntp_date: server time is %d\n", *now));
++ return 0;
++ }
++ }
++ }
++ time (now);
++ return 0;
++}
++
++/* Fetch list of all newsgroups from server */
++int nntp_active_fetch (NNTP_SERVER *nserv)
++{
++ NNTP_DATA nntp_data;
++ char msg[SHORT_STRING];
++ char buf[LONG_STRING];
++ unsigned int i;
++ int rc;
++
++ snprintf (msg, sizeof (msg), _("Loading list of groups from server %s..."),
++ nserv->conn->account.host);
++ mutt_message (msg);
++ if (nntp_date (nserv, &nserv->newgroups_time) < 0)
++ return -1;
++
++ nntp_data.nserv = nserv;
++ nntp_data.group = NULL;
++ strfcpy (buf, "LIST\r\n", sizeof (buf));
++ rc = nntp_fetch_lines (&nntp_data, buf, sizeof (buf), msg,
++ nntp_add_group, nserv);
++ if (rc)
++ {
++ if (rc > 0)
++ {
++ mutt_error ("LIST: %s", buf);
++ mutt_sleep (2);
++ }
++ return -1;
++ }
++
++ if (option (OPTLOADDESC) &&
++ get_description (&nntp_data, "*", _("Loading descriptions...")) < 0)
++ return -1;
++
++ for (i = 0; i < nserv->groups_num; i++)
++ {
++ NNTP_DATA *nntp_data = nserv->groups_list[i];
++
++ if (nntp_data && nntp_data->deleted && !nntp_data->newsrc_ent)
++ {
++ nntp_delete_group_cache (nntp_data);
++ hash_delete (nserv->groups_hash, nntp_data->group, NULL, nntp_data_free);
++ nserv->groups_list[i] = NULL;
++ }
++ }
++ nntp_active_save_cache (nserv);
++ mutt_clear_error ();
++ return 0;
++}
++
++/* Check newsgroup for new articles:
++ * 1 - new articles found
++ * 0 - no change
++ * -1 - lost connection */
++static int nntp_group_poll (NNTP_DATA *nntp_data, int update_stat)
++{
++ char buf[LONG_STRING] = "";
++ anum_t count, first, last;
++
++ /* use GROUP command to poll newsgroup */
++ if (nntp_query (nntp_data, buf, sizeof (buf)) < 0)
++ return -1;
++ if (sscanf (buf, "211 " ANUM " " ANUM " " ANUM, &count, &first, &last) != 3)
++ return 0;
++ if (first == nntp_data->firstMessage && last == nntp_data->lastMessage)
++ return 0;
++
++ /* articles have been renumbered */
++ if (last < nntp_data->lastMessage)
++ {
++ nntp_data->lastCached = 0;
++ if (nntp_data->newsrc_len)
++ {
++ safe_realloc (&nntp_data->newsrc_ent, sizeof (NEWSRC_ENTRY));
++ nntp_data->newsrc_len = 1;
++ nntp_data->newsrc_ent[0].first = 1;
++ nntp_data->newsrc_ent[0].last = 0;
++ }
++ }
++ nntp_data->firstMessage = first;
++ nntp_data->lastMessage = last;
++ if (!update_stat)
++ return 1;
++
++ /* update counters */
++ else if (!last || (!nntp_data->newsrc_ent && !nntp_data->lastCached))
++ nntp_data->unread = count;
++ else
++ nntp_group_unread_stat (nntp_data);
++ return 1;
++}
++
++/* Check current newsgroup for new articles:
++ * M_REOPENED - articles have been renumbered or removed from server
++ * M_NEW_MAIL - new articles found
++ * 0 - no change
++ * -1 - lost connection */
++int nntp_check_mailbox (CONTEXT *ctx, int leave_lock)
++{
++ NNTP_DATA *nntp_data = ctx->data;
++ NNTP_SERVER *nserv = nntp_data->nserv;
++ time_t now = time (NULL);
++ int i, j;
++ int rc, ret = 0;
++ void *hc = NULL;
++
++ if (nserv->check_time + NewsPollTimeout > now)
++ return 0;
++
++ mutt_message _("Checking for new messages...");
++ if (nntp_newsrc_parse (nserv) < 0)
++ return -1;
++
++ nserv->check_time = now;
++ rc = nntp_group_poll (nntp_data, 0);
++ if (rc < 0)
++ {
++ nntp_newsrc_close (nserv);
++ return -1;
++ }
++ if (rc)
++ nntp_active_save_cache (nserv);
++
++ /* articles have been renumbered, remove all headers */
++ if (nntp_data->lastMessage < nntp_data->lastLoaded)
++ {
++ for (i = 0; i < ctx->msgcount; i++)
++ mutt_free_header (&ctx->hdrs[i]);
++ ctx->msgcount = 0;
++ ctx->tagged = 0;
++
++ if (nntp_data->lastMessage < nntp_data->lastLoaded)
++ {
++ nntp_data->lastLoaded = nntp_data->firstMessage - 1;
++ if (NntpContext && nntp_data->lastMessage - nntp_data->lastLoaded >
++ NntpContext)
++ nntp_data->lastLoaded = nntp_data->lastMessage - NntpContext;
++ }
++ ret = M_REOPENED;
++ }
++
++ /* .newsrc has been externally modified */
++ if (nserv->newsrc_modified)
++ {
++ anum_t anum;
++#ifdef USE_HCACHE
++ unsigned char *messages;
++ char buf[16];
++ void *hdata;
++ HEADER *hdr;
++ anum_t first = nntp_data->firstMessage;
++
++ if (NntpContext && nntp_data->lastMessage - first + 1 > NntpContext)
++ first = nntp_data->lastMessage - NntpContext + 1;
++ messages = safe_calloc (nntp_data->lastLoaded - first + 1,
++ sizeof (unsigned char));
++ hc = nntp_hcache_open (nntp_data);
++ nntp_hcache_update (nntp_data, hc);
++#endif
++
++ /* update flags according to .newsrc */
++ for (i = j = 0; i < ctx->msgcount; i++)
++ {
++ int flagged = 0;
++ anum = NHDR (ctx->hdrs[i])->article_num;
++
++#ifdef USE_HCACHE
++ /* check hcache for flagged and deleted flags */
++ if (hc)
++ {
++ if (anum >= first && anum <= nntp_data->lastLoaded)
++ messages[anum - first] = 1;
++
++ snprintf (buf, sizeof (buf), "%d", anum);
++ hdata = mutt_hcache_fetch (hc, buf, strlen);
++ if (hdata)
++ {
++ int deleted;
++
++ dprint (2, (debugfile,
++ "nntp_check_mailbox: mutt_hcache_fetch %s\n", buf));
++ hdr = mutt_hcache_restore (hdata, NULL);
++ deleted = hdr->deleted;
++ flagged = hdr->flagged;
++ mutt_free_header (&hdr);
++
++ /* header marked as deleted, removing from context */
++ if (deleted)
++ {
++ mutt_set_flag (ctx, ctx->hdrs[i], M_TAG, 0);
++ mutt_free_header (&ctx->hdrs[i]);
++ continue;
++ }
++ }
++ }
++#endif
++
++ if (!ctx->hdrs[i]->changed)
++ {
++ ctx->hdrs[i]->flagged = flagged;
++ ctx->hdrs[i]->read = 0;
++ ctx->hdrs[i]->old = 0;
++ nntp_article_status (ctx, ctx->hdrs[i], NULL, anum);
++ if (!ctx->hdrs[i]->read)
++ nntp_parse_xref (ctx, ctx->hdrs[i]);
++ }
++ ctx->hdrs[j++] = ctx->hdrs[i];
++ }
++
++#ifdef USE_HCACHE
++ ctx->msgcount = j;
++
++ /* restore headers without "deleted" flag */
++ for (anum = first; anum <= nntp_data->lastLoaded; anum++)
++ {
++ if (messages[anum - first])
++ continue;
++
++ snprintf (buf, sizeof (buf), "%d", anum);
++ hdata = mutt_hcache_fetch (hc, buf, strlen);
++ if (hdata)
++ {
++ dprint (2, (debugfile,
++ "nntp_check_mailbox: mutt_hcache_fetch %s\n", buf));
++ if (ctx->msgcount >= ctx->hdrmax)
++ mx_alloc_memory (ctx);
++
++ ctx->hdrs[ctx->msgcount] =
++ hdr = mutt_hcache_restore (hdata, NULL);
++ if (hdr->deleted)
++ {
++ mutt_free_header (&hdr);
++ if (nntp_data->bcache)
++ {
++ dprint (2, (debugfile,
++ "nntp_check_mailbox: mutt_bcache_del %s\n", buf));
++ mutt_bcache_del (nntp_data->bcache, buf);
++ }
++ continue;
++ }
++
++ ctx->msgcount++;
++ hdr->read = 0;
++ hdr->old = 0;
++ hdr->data = safe_calloc (1, sizeof (NNTP_HEADER_DATA));
++ NHDR (hdr)->article_num = anum;
++ nntp_article_status (ctx, hdr, NULL, anum);
++ if (!hdr->read)
++ nntp_parse_xref (ctx, hdr);
++ }
++ }
++ FREE (&messages);
++#endif
++
++ nserv->newsrc_modified = 0;
++ ret = M_REOPENED;
++ }
++
++ /* some headers were removed, context must be updated */
++ if (ret == M_REOPENED)
++ {
++ if (ctx->subj_hash)
++ hash_destroy (&ctx->subj_hash, NULL);
++ if (ctx->id_hash)
++ hash_destroy (&ctx->id_hash, NULL);
++ mutt_clear_threads (ctx);
++
++ ctx->vcount = 0;
++ ctx->deleted = 0;
++ ctx->new = 0;
++ ctx->unread = 0;
++ ctx->flagged = 0;
++ ctx->changed = 0;
++ ctx->id_hash = NULL;
++ ctx->subj_hash = NULL;
++ mx_update_context (ctx, ctx->msgcount);
++ }
++
++ /* fetch headers of new articles */
++ if (nntp_data->lastMessage > nntp_data->lastLoaded)
++ {
++ int oldmsgcount = ctx->msgcount;
++ int quiet = ctx->quiet;
++ ctx->quiet = 1;
++#ifdef USE_HCACHE
++ if (!hc)
++ {
++ hc = nntp_hcache_open (nntp_data);
++ nntp_hcache_update (nntp_data, hc);
++ }
++#endif
++ rc = nntp_fetch_headers (ctx, hc, nntp_data->lastLoaded + 1,
++ nntp_data->lastMessage, 0);
++ ctx->quiet = quiet;
++ if (rc >= 0)
++ nntp_data->lastLoaded = nntp_data->lastMessage;
++ if (ret == 0 && ctx->msgcount > oldmsgcount)
++ ret = M_NEW_MAIL;
++ }
++
++#ifdef USE_HCACHE
++ mutt_hcache_close (hc);
++#endif
++ if (ret || !leave_lock)
++ nntp_newsrc_close (nserv);
++ mutt_clear_error ();
++ return ret;
++}
++
++/* Check for new groups and new articles in subscribed groups:
++ * 1 - new groups found
++ * 0 - no new groups
++ * -1 - error */
++int nntp_check_new_groups (NNTP_SERVER *nserv)
++{
++ NNTP_DATA nntp_data;
++ time_t now;
++ struct tm *tm;
++ char buf[LONG_STRING];
++ char *msg = _("Checking for new newsgroups...");
++ unsigned int i;
++ int rc, update_active = FALSE;
++
++ if (!nserv || !nserv->newgroups_time)
++ return -1;
++
++ /* check subscribed newsgroups for new articles */
++ if (option (OPTSHOWNEWNEWS))
++ {
++ mutt_message _("Checking for new messages...");
++ for (i = 0; i < nserv->groups_num; i++)
++ {
++ NNTP_DATA *nntp_data = nserv->groups_list[i];
++
++ if (nntp_data && nntp_data->subscribed)
++ {
++ rc = nntp_group_poll (nntp_data, 1);
++ if (rc < 0)
++ return -1;
++ if (rc > 0)
++ update_active = TRUE;
++ }
++ }
++ }
++ else if (nserv->newgroups_time)
++ return 0;
++
++ /* get list of new groups */
++ mutt_message (msg);
++ if (nntp_date (nserv, &now) < 0)
++ return -1;
++ nntp_data.nserv = nserv;
++ if (Context && Context->magic == M_NNTP)
++ nntp_data.group = ((NNTP_DATA *)Context->data)->group;
++ else
++ nntp_data.group = NULL;
++ i = nserv->groups_num;
++ tm = gmtime (&nserv->newgroups_time);
++ snprintf (buf, sizeof (buf), "NEWGROUPS %02d%02d%02d %02d%02d%02d GMT\r\n",
++ tm->tm_year % 100, tm->tm_mon + 1, tm->tm_mday,
++ tm->tm_hour, tm->tm_min, tm->tm_sec);
++ rc = nntp_fetch_lines (&nntp_data, buf, sizeof (buf), msg,
++ nntp_add_group, nserv);
++ if (rc)
++ {
++ if (rc > 0)
++ {
++ mutt_error ("NEWGROUPS: %s", buf);
++ mutt_sleep (2);
++ }
++ return -1;
++ }
++
++ /* new groups found */
++ rc = 0;
++ if (nserv->groups_num != i)
++ {
++ nserv->newgroups_time = now;
++
++ /* loading descriptions */
++ if (option (OPTLOADDESC))
++ {
++ unsigned int count = 0;
++ progress_t progress;
++
++ mutt_progress_init (&progress, _("Loading descriptions..."),
++ M_PROGRESS_MSG, ReadInc, nserv->groups_num - i);
++ for (; i < nserv->groups_num; i++)
++ {
++ NNTP_DATA *nntp_data = nserv->groups_list[i];
++
++ if (get_description (nntp_data, NULL, NULL) < 0)
++ return -1;
++ mutt_progress_update (&progress, ++count, -1);
++ }
++ }
++ update_active = TRUE;
++ rc = 1;
++ }
++ if (update_active)
++ nntp_active_save_cache (nserv);
++ mutt_clear_error ();
++ return rc;
++}
++
++/* Fetch article by Message-ID:
++ * 0 - success
++ * 1 - no such article
++ * -1 - error */
++int nntp_check_msgid (CONTEXT *ctx, const char *msgid)
++{
++ NNTP_DATA *nntp_data = ctx->data;
++ HEADER *hdr;
++ FILE *fp;
++ char tempfile[_POSIX_PATH_MAX];
++ char buf[LONG_STRING];
++ int rc;
++
++ mutt_mktemp (tempfile, sizeof (tempfile));
++ fp = safe_fopen (tempfile, "w+");
++ if (!fp)
++ {
++ mutt_perror (tempfile);
++ unlink (tempfile);
++ return -1;
++ }
++
++ snprintf (buf, sizeof (buf), "HEAD %s\r\n", msgid);
++ rc = nntp_fetch_lines (nntp_data, buf, sizeof (buf), NULL,
++ fetch_tempfile, fp);
++ if (rc)
++ {
++ fclose (fp);
++ unlink (tempfile);
++ if (rc < 0)
++ return -1;
++ if (!mutt_strncmp ("430", buf, 3))
++ return 1;
++ mutt_error ("HEAD: %s", buf);
++ return -1;
++ }
++
++ /* parse header */
++ if (ctx->msgcount == ctx->hdrmax)
++ mx_alloc_memory (ctx);
++ hdr = ctx->hdrs[ctx->msgcount] = mutt_new_header ();
++ hdr->data = safe_calloc (1, sizeof (NNTP_HEADER_DATA));
++ hdr->env = mutt_read_rfc822_header (fp, hdr, 0, 0);
++ fclose (fp);
++ unlink (tempfile);
++
++ /* get article number */
++ if (hdr->env->xref)
++ nntp_parse_xref (ctx, hdr);
++ else
++ {
++ snprintf (buf, sizeof (buf), "STAT %s\r\n", msgid);
++ if (nntp_query (nntp_data, buf, sizeof (buf)) < 0)
++ {
++ mutt_free_header (&hdr);
++ return -1;
++ }
++ sscanf (buf + 4, ANUM, &NHDR (hdr)->article_num);
++ }
++
++ /* reset flags */
++ hdr->read = 0;
++ hdr->old = 0;
++ hdr->deleted = 0;
++ hdr->changed = 1;
++ hdr->received = hdr->date_sent;
++ hdr->index = ctx->msgcount++;
++ mx_update_context (ctx, 1);
++ return 0;
++}
++
++typedef struct
++{
++ CONTEXT *ctx;
++ unsigned int num;
++ unsigned int max;
++ anum_t *child;
++} CHILD_CTX;
++
++/* Parse XPAT line */
++static int fetch_children (char *line, void *data)
++{
++ CHILD_CTX *cc = data;
++ anum_t anum;
++ unsigned int i;
++
++ if (!line || sscanf (line, ANUM, &anum) != 1)
++ return 0;
++ for (i = 0; i < cc->ctx->msgcount; i++)
++ if (NHDR (cc->ctx->hdrs[i])->article_num == anum)
++ return 0;
++ if (cc->num >= cc->max)
++ {
++ cc->max *= 2;
++ safe_realloc (&cc->child, sizeof (anum_t) * cc->max);
++ }
++ cc->child[cc->num++] = anum;
++ return 0;
++}
++
++/* Fetch children of article with the Message-ID */
++int nntp_check_children (CONTEXT *ctx, const char *msgid)
++{
++ NNTP_DATA *nntp_data = ctx->data;
++ CHILD_CTX cc;
++ char buf[STRING];
++ int i, rc, quiet;
++ void *hc = NULL;
++
++ if (!nntp_data || !nntp_data->nserv)
++ return -1;
++ if (nntp_data->firstMessage > nntp_data->lastLoaded)
++ return 0;
++
++ /* init context */
++ cc.ctx = ctx;
++ cc.num = 0;
++ cc.max = 10;
++ cc.child = safe_malloc (sizeof (anum_t) * cc.max);
++
++ /* fetch numbers of child messages */
++ snprintf (buf, sizeof (buf), "XPAT References %d-%d *%s*\r\n",
++ nntp_data->firstMessage, nntp_data->lastLoaded, msgid);
++ rc = nntp_fetch_lines (nntp_data, buf, sizeof (buf), NULL,
++ fetch_children, &cc);
++ if (rc)
++ {
++ FREE (&cc.child);
++ if (rc > 0) {
++ if (mutt_strncmp ("500", buf, 3))
++ mutt_error ("XPAT: %s", buf);
++ else
++ mutt_error _("Unable to find child articles because server does not support XPAT command.");
++ }
++ return -1;
++ }
++
++ /* fetch all found messages */
++ quiet = ctx->quiet;
++ ctx->quiet = 1;
++#ifdef USE_HCACHE
++ hc = nntp_hcache_open (nntp_data);
++#endif
++ for (i = 0; i < cc.num; i++)
++ {
++ rc = nntp_fetch_headers (ctx, hc, cc.child[i], cc.child[i], 1);
++ if (rc < 0)
++ break;
++ }
++#ifdef USE_HCACHE
++ mutt_hcache_close (hc);
++#endif
++ ctx->quiet = quiet;
++ FREE (&cc.child);
++ return rc < 0 ? -1 : 0;
++}
+diff --git a/nntp.h b/nntp.h
+new file mode 100644
+index 0000000..c937034
+--- /dev/null
++++ b/nntp.h
+@@ -0,0 +1,167 @@
++/*
++ * Copyright (C) 1998 Brandon Long <blong@fiction.net>
++ * Copyright (C) 1999 Andrej Gritsenko <andrej@lucky.net>
++ * Copyright (C) 2000-2012 Vsevolod Volkov <vvv@mutt.org.ua>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation; either version 2 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program; if not, write to the Free Software
++ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
++ */
++
++#ifndef _NNTP_H_
++#define _NNTP_H_ 1
++
++#include "mutt_socket.h"
++#include "mailbox.h"
++#include "bcache.h"
++
++#if USE_HCACHE
++#include "hcache.h"
++#endif
++
++#include <time.h>
++#include <sys/types.h>
++#include <stdint.h>
++
++#define NNTP_PORT 119
++#define NNTP_SSL_PORT 563
++
++/* number of entries in article cache */
++#define NNTP_ACACHE_LEN 10
++
++/* article number type and format */
++#define anum_t uint32_t
++#define ANUM "%u"
++
++enum
++{
++ NNTP_NONE = 0,
++ NNTP_OK,
++ NNTP_BYE
++};
++
++typedef struct
++{
++ unsigned int hasCAPABILITIES : 1;
++ unsigned int hasSTARTTLS : 1;
++ unsigned int hasDATE : 1;
++ unsigned int hasLIST_NEWSGROUPS : 1;
++ unsigned int hasXGTITLE : 1;
++ unsigned int hasLISTGROUP : 1;
++ unsigned int hasOVER : 1;
++ unsigned int hasXOVER : 1;
++ unsigned int use_tls : 3;
++ unsigned int status : 3;
++ unsigned int cacheable : 1;
++ unsigned int newsrc_modified : 1;
++ FILE *newsrc_fp;
++ char *newsrc_file;
++ char *authenticators;
++ char *overview_fmt;
++ off_t size;
++ time_t mtime;
++ time_t newgroups_time;
++ time_t check_time;
++ unsigned int groups_num;
++ unsigned int groups_max;
++ void **groups_list;
++ HASH *groups_hash;
++ CONNECTION *conn;
++} NNTP_SERVER;
++
++typedef struct
++{
++ anum_t first;
++ anum_t last;
++} NEWSRC_ENTRY;
++
++typedef struct
++{
++ unsigned int index;
++ char *path;
++} NNTP_ACACHE;
++
++typedef struct
++{
++ char *group;
++ char *desc;
++ anum_t firstMessage;
++ anum_t lastMessage;
++ anum_t lastLoaded;
++ anum_t lastCached;
++ anum_t unread;
++ unsigned int subscribed : 1;
++ unsigned int new : 1;
++ unsigned int allowed : 1;
++ unsigned int deleted : 1;
++ unsigned int newsrc_len;
++ NEWSRC_ENTRY *newsrc_ent;
++ NNTP_SERVER *nserv;
++ NNTP_ACACHE acache[NNTP_ACACHE_LEN];
++ body_cache_t *bcache;
++} NNTP_DATA;
++
++typedef struct
++{
++ anum_t article_num;
++ unsigned int parsed : 1;
++} NNTP_HEADER_DATA;
++
++#define NHDR(hdr) ((NNTP_HEADER_DATA*)((hdr)->data))
++
++/* internal functions */
++int nntp_add_group (char *, void *);
++int nntp_active_save_cache (NNTP_SERVER *);
++int nntp_check_new_groups (NNTP_SERVER *);
++int nntp_fastclose_mailbox (CONTEXT *);
++int nntp_open_connection (NNTP_SERVER *);
++void nntp_newsrc_gen_entries (CONTEXT *);
++void nntp_bcache_update (NNTP_DATA *);
++void nntp_article_status (CONTEXT *, HEADER *, char *, anum_t);
++void nntp_group_unread_stat (NNTP_DATA *);
++void nntp_data_free (void *);
++void nntp_acache_free (NNTP_DATA *);
++void nntp_delete_group_cache (NNTP_DATA *);
++
++/* exposed interface */
++NNTP_SERVER *nntp_select_server (char *, int);
++NNTP_DATA *mutt_newsgroup_subscribe (NNTP_SERVER *, char *);
++NNTP_DATA *mutt_newsgroup_unsubscribe (NNTP_SERVER *, char *);
++NNTP_DATA *mutt_newsgroup_catchup (NNTP_SERVER *, char *);
++NNTP_DATA *mutt_newsgroup_uncatchup (NNTP_SERVER *, char *);
++int nntp_active_fetch (NNTP_SERVER *);
++int nntp_newsrc_update (NNTP_SERVER *);
++int nntp_open_mailbox (CONTEXT *);
++int nntp_sync_mailbox (CONTEXT *);
++int nntp_check_mailbox (CONTEXT *, int);
++int nntp_fetch_message (MESSAGE *, CONTEXT *, int);
++int nntp_post (const char *);
++int nntp_check_msgid (CONTEXT *, const char *);
++int nntp_check_children (CONTEXT *, const char *);
++int nntp_newsrc_parse (NNTP_SERVER *);
++void nntp_newsrc_close (NNTP_SERVER *);
++void nntp_buffy (char *, size_t);
++void nntp_expand_path (char *, size_t, ACCOUNT *);
++void nntp_clear_cache (NNTP_SERVER *);
++const char *nntp_format_str (char *, size_t, size_t, char, const char *,
++ const char *, const char *, const char *,
++ unsigned long, format_flag);
++
++NNTP_SERVER *CurrentNewsSrv INITVAL (NULL);
++
++#ifdef USE_HCACHE
++header_cache_t *nntp_hcache_open (NNTP_DATA *);
++void nntp_hcache_update (NNTP_DATA *, header_cache_t *);
++#endif
++
++#endif /* _NNTP_H_ */
+diff --git a/pager.c b/pager.c
+index d372fc0..e888452 100644
+--- a/pager.c
++++ b/pager.c
+@@ -1085,6 +1085,11 @@ fill_buffer (FILE *f, LOFF_T *last_pos, LOFF_T offset, unsigned char **buf,
+ return b_read;
+ }
+
++#ifdef USE_NNTP
++#include "mx.h"
++#include "nntp.h"
++#endif
++
+
+ static int format_line (struct line_t **lineInfo, int n, unsigned char *buf,
+ int flags, ansi_attr *pa, int cnt,
+@@ -1543,6 +1548,16 @@ static const struct mapping_t PagerHelpExtra[] = {
+ { NULL, 0 }
+ };
+
++#ifdef USE_NNTP
++static struct mapping_t PagerNewsHelpExtra[] = {
++ { N_("Post"), OP_POST },
++ { N_("Followup"), OP_FOLLOWUP },
++ { N_("Del"), OP_DELETE },
++ { N_("Next"), OP_MAIN_NEXT_UNDELETED },
++ { NULL, 0 }
++};
++#endif
++
+
+
+ /* This pager is actually not so simple as it once was. It now operates in
+@@ -1584,6 +1599,10 @@ mutt_pager (const char *banner, const char *fname, int flags, pager_t *extra)
+ int old_PagerIndexLines; /* some people want to resize it
+ * while inside the pager... */
+
++#ifdef USE_NNTP
++ char *followup_to;
++#endif
++
+ if (!(flags & M_SHOWCOLOR))
+ flags |= M_SHOWFLAT;
+
+@@ -1623,7 +1642,11 @@ mutt_pager (const char *banner, const char *fname, int flags, pager_t *extra)
+ if (IsHeader (extra))
+ {
+ strfcpy (tmphelp, helpstr, sizeof (tmphelp));
+- mutt_compile_help (buffer, sizeof (buffer), MENU_PAGER, PagerHelpExtra);
++ mutt_compile_help (buffer, sizeof (buffer), MENU_PAGER,
++#ifdef USE_NNTP
++ (Context && (Context->magic == M_NNTP)) ? PagerNewsHelpExtra :
++#endif
++ PagerHelpExtra);
+ snprintf (helpstr, sizeof (helpstr), "%s %s", tmphelp, buffer);
+ }
+ if (!InHelp)
+@@ -2551,6 +2574,60 @@ search_next:
+ redraw = REDRAW_FULL;
+ break;
+
++#ifdef USE_NNTP
++ case OP_POST:
++ CHECK_MODE(IsHeader (extra) && !IsAttach (extra));
++ CHECK_ATTACH;
++ if (extra->ctx && extra->ctx->magic == M_NNTP &&
++ !((NNTP_DATA *)extra->ctx->data)->allowed &&
++ query_quadoption (OPT_TOMODERATED,_("Posting to this group not allowed, may be moderated. Continue?")) != M_YES)
++ break;
++ ci_send_message (SENDNEWS, NULL, NULL, extra->ctx, NULL);
++ redraw = REDRAW_FULL;
++ break;
++
++ case OP_FORWARD_TO_GROUP:
++ CHECK_MODE(IsHeader (extra) || IsMsgAttach (extra));
++ CHECK_ATTACH;
++ if (extra->ctx && extra->ctx->magic == M_NNTP &&
++ !((NNTP_DATA *)extra->ctx->data)->allowed &&
++ query_quadoption (OPT_TOMODERATED,_("Posting to this group not allowed, may be moderated. Continue?")) != M_YES)
++ break;
++ if (IsMsgAttach (extra))
++ mutt_attach_forward (extra->fp, extra->hdr, extra->idx,
++ extra->idxlen, extra->bdy, SENDNEWS);
++ else
++ ci_send_message (SENDNEWS|SENDFORWARD, NULL, NULL, extra->ctx, extra->hdr);
++ redraw = REDRAW_FULL;
++ break;
++
++ case OP_FOLLOWUP:
++ CHECK_MODE(IsHeader (extra) || IsMsgAttach (extra));
++ CHECK_ATTACH;
++
++ if (IsMsgAttach (extra))
++ followup_to = extra->bdy->hdr->env->followup_to;
++ else
++ followup_to = extra->hdr->env->followup_to;
++
++ if (!followup_to || mutt_strcasecmp (followup_to, "poster") ||
++ query_quadoption (OPT_FOLLOWUPTOPOSTER,_("Reply by mail as poster prefers?")) != M_YES)
++ {
++ if (extra->ctx && extra->ctx->magic == M_NNTP &&
++ !((NNTP_DATA *)extra->ctx->data)->allowed &&
++ query_quadoption (OPT_TOMODERATED,_("Posting to this group not allowed, may be moderated. Continue?")) != M_YES)
++ break;
++ if (IsMsgAttach (extra))
++ mutt_attach_reply (extra->fp, extra->hdr, extra->idx,
++ extra->idxlen, extra->bdy, SENDNEWS|SENDREPLY);
++ else
++ ci_send_message (SENDNEWS|SENDREPLY, NULL, NULL,
++ extra->ctx, extra->hdr);
++ redraw = REDRAW_FULL;
++ break;
++ }
++#endif
++
+ case OP_REPLY:
+ CHECK_MODE(IsHeader (extra) || IsMsgAttach (extra));
+ CHECK_ATTACH;
+@@ -2597,7 +2674,7 @@ search_next:
+ CHECK_ATTACH;
+ if (IsMsgAttach (extra))
+ mutt_attach_forward (extra->fp, extra->hdr, extra->idx,
+- extra->idxlen, extra->bdy);
++ extra->idxlen, extra->bdy, 0);
+ else
+ ci_send_message (SENDFORWARD, NULL, NULL, extra->ctx, extra->hdr);
+ redraw = REDRAW_FULL;
+diff --git a/parse.c b/parse.c
+index 58c7774..36046e9 100644
+--- a/parse.c
++++ b/parse.c
+@@ -89,7 +89,7 @@ char *mutt_read_rfc822_line (FILE *f, char *line, size_t *linelen)
+ /* not reached */
+ }
+
+-static LIST *mutt_parse_references (char *s, int in_reply_to)
++LIST *mutt_parse_references (char *s, int in_reply_to)
+ {
+ LIST *t, *lst = NULL;
+ char *m;
+@@ -1072,6 +1072,17 @@ int mutt_parse_rfc822_line (ENVELOPE *e, HEADER *hdr, char *line, char *p, short
+ e->from = rfc822_parse_adrlist (e->from, p);
+ matched = 1;
+ }
++#ifdef USE_NNTP
++ else if (!mutt_strcasecmp (line+1, "ollowup-to"))
++ {
++ if (!e->followup_to)
++ {
++ mutt_remove_trailing_ws (p);
++ e->followup_to = safe_strdup (mutt_skip_whitespace (p));
++ }
++ matched = 1;
++ }
++#endif
+ break;
+
+ case 'i':
+@@ -1154,6 +1165,27 @@ int mutt_parse_rfc822_line (ENVELOPE *e, HEADER *hdr, char *line, char *p, short
+ }
+ break;
+
++#ifdef USE_NNTP
++ case 'n':
++ if (!mutt_strcasecmp (line + 1, "ewsgroups"))
++ {
++ FREE (&e->newsgroups);
++ mutt_remove_trailing_ws (p);
++ e->newsgroups = safe_strdup (mutt_skip_whitespace (p));
++ matched = 1;
++ }
++ break;
++#endif
++
++ case 'o':
++ /* field `Organization:' saves only for pager! */
++ if (!mutt_strcasecmp (line + 1, "rganization"))
++ {
++ if (!e->organization && mutt_strcasecmp (p, "unknown"))
++ e->organization = safe_strdup (p);
++ }
++ break;
++
+ case 'r':
+ if (!ascii_strcasecmp (line + 1, "eferences"))
+ {
+@@ -1266,6 +1298,20 @@ int mutt_parse_rfc822_line (ENVELOPE *e, HEADER *hdr, char *line, char *p, short
+ e->x_label = safe_strdup(p);
+ matched = 1;
+ }
++#ifdef USE_NNTP
++ else if (!mutt_strcasecmp (line + 1, "-comment-to"))
++ {
++ if (!e->x_comment_to)
++ e->x_comment_to = safe_strdup (p);
++ matched = 1;
++ }
++ else if (!mutt_strcasecmp (line + 1, "ref"))
++ {
++ if (!e->xref)
++ e->xref = safe_strdup (p);
++ matched = 1;
++ }
++#endif
+
+ default:
+ break;
+diff --git a/pattern.c b/pattern.c
+index 4cdbd05..9375a4e 100644
+--- a/pattern.c
++++ b/pattern.c
+@@ -92,6 +92,9 @@ Flags[] =
+ { 'U', M_UNREAD, 0, NULL },
+ { 'v', M_COLLAPSED, 0, NULL },
+ { 'V', M_CRYPT_VERIFIED, 0, NULL },
++#ifdef USE_NNTP
++ { 'w', M_NEWSGROUPS, 0, eat_regexp },
++#endif
+ { 'x', M_REFERENCE, 0, eat_regexp },
+ { 'X', M_MIMEATTACH, 0, eat_range },
+ { 'y', M_XLABEL, 0, eat_regexp },
+@@ -1213,6 +1216,10 @@ mutt_pattern_exec (struct pattern_t *pat, pattern_exec_flag flags, CONTEXT *ctx,
+ }
+ case M_UNREFERENCED:
+ return (pat->not ^ (h->thread && !h->thread->child));
++#ifdef USE_NNTP
++ case M_NEWSGROUPS:
++ return (pat->not ^ (h->env->newsgroups && patmatch (pat, h->env->newsgroups) == 0));
++#endif
+ }
+ mutt_error (_("error: unknown op %d (report this error)."), pat->op);
+ return (-1);
+@@ -1294,6 +1301,7 @@ int mutt_pattern_func (int op, char *prompt)
+ progress_t progress;
+
+ strfcpy (buf, NONULL (Context->pattern), sizeof (buf));
++ if (prompt || op != M_LIMIT)
+ if (mutt_get_field (prompt, buf, sizeof (buf), M_PATTERN | M_CLEAR) != 0 || !buf[0])
+ return (-1);
+
+diff --git a/po/POTFILES.in b/po/POTFILES.in
+index 3654ad1..1e499ec 100644
+--- a/po/POTFILES.in
++++ b/po/POTFILES.in
+@@ -47,6 +47,8 @@ mutt_ssl_gnutls.c
+ mutt_tunnel.c
+ muttlib.c
+ mx.c
++newsrc.c
++nntp.c
+ pager.c
+ parse.c
+ pattern.c
+diff --git a/postpone.c b/postpone.c
+index 21e96e6..5144be9 100644
+--- a/postpone.c
++++ b/postpone.c
+@@ -125,15 +125,26 @@ int mutt_num_postponed (int force)
+
+ if (LastModify < st.st_mtime)
+ {
++#ifdef USE_NNTP
++ int optnews = option (OPTNEWS);
++#endif
+ LastModify = st.st_mtime;
+
+ if (access (Postponed, R_OK | F_OK) != 0)
+ return (PostCount = 0);
++#ifdef USE_NNTP
++ if (optnews)
++ unset_option (OPTNEWS);
++#endif
+ if (mx_open_mailbox (Postponed, M_NOSORT | M_QUIET, &ctx) == NULL)
+ PostCount = 0;
+ else
+ PostCount = ctx.msgcount;
+ mx_fastclose_mailbox (&ctx);
++#ifdef USE_NNTP
++ if (optnews)
++ set_option (OPTNEWS);
++#endif
+ }
+
+ return (PostCount);
+diff --git a/protos.h b/protos.h
+index 551b142..bb44d34 100644
+--- a/protos.h
++++ b/protos.h
+@@ -111,6 +111,7 @@ HASH *mutt_make_id_hash (CONTEXT *);
+ HASH *mutt_make_subj_hash (CONTEXT *);
+
+ LIST *mutt_make_references(ENVELOPE *e);
++LIST *mutt_parse_references (char *, int);
+
+ char *mutt_read_rfc822_line (FILE *, char *, size_t *);
+ ENVELOPE *mutt_read_rfc822_header (FILE *, HEADER *, short, short);
+diff --git a/recvattach.c b/recvattach.c
+index 31dfcad..05a1cde 100644
+--- a/recvattach.c
++++ b/recvattach.c
+@@ -1119,6 +1119,15 @@ void mutt_view_attachments (HEADER *hdr)
+ }
+ #endif
+
++#ifdef USE_NNTP
++ if (Context->magic == M_NNTP)
++ {
++ mutt_flushinp ();
++ mutt_error _("Can't delete attachment from news server.");
++ break;
++ }
++#endif
++
+ if (WithCrypto && hdr->security & ~PGP_TRADITIONAL_CHECKED)
+ {
+ mutt_message _(
+@@ -1210,10 +1219,33 @@ void mutt_view_attachments (HEADER *hdr)
+ case OP_FORWARD_MESSAGE:
+ CHECK_ATTACH;
+ mutt_attach_forward (fp, hdr, idx, idxlen,
+- menu->tagprefix ? NULL : idx[menu->current]->content);
++ menu->tagprefix ? NULL : idx[menu->current]->content, 0);
+ menu->redraw = REDRAW_FULL;
+ break;
+
++#ifdef USE_NNTP
++ case OP_FORWARD_TO_GROUP:
++ CHECK_ATTACH;
++ mutt_attach_forward (fp, hdr, idx, idxlen,
++ menu->tagprefix ? NULL : idx[menu->current]->content, SENDNEWS);
++ menu->redraw = REDRAW_FULL;
++ break;
++
++ case OP_FOLLOWUP:
++ CHECK_ATTACH;
++
++ if (!idx[menu->current]->content->hdr->env->followup_to ||
++ mutt_strcasecmp (idx[menu->current]->content->hdr->env->followup_to, "poster") ||
++ query_quadoption (OPT_FOLLOWUPTOPOSTER,_("Reply by mail as poster prefers?")) != M_YES)
++ {
++ mutt_attach_reply (fp, hdr, idx, idxlen,
++ menu->tagprefix ? NULL : idx[menu->current]->content,
++ SENDNEWS|SENDREPLY);
++ menu->redraw = REDRAW_FULL;
++ break;
++ }
++#endif
++
+ case OP_REPLY:
+ case OP_GROUP_REPLY:
+ case OP_LIST_REPLY:
+diff --git a/recvcmd.c b/recvcmd.c
+index a6a3a91..e633119 100644
+--- a/recvcmd.c
++++ b/recvcmd.c
+@@ -401,7 +401,7 @@ static BODY ** copy_problematic_attachments (FILE *fp,
+ static void attach_forward_bodies (FILE * fp, HEADER * hdr,
+ ATTACHPTR ** idx, short idxlen,
+ BODY * cur,
+- short nattach)
++ short nattach, int flags)
+ {
+ short i;
+ short mime_fwd_all = 0;
+@@ -547,7 +547,7 @@ _("Can't decode all tagged attachments. MIME-forward the others?"))) == -1)
+ tmpfp = NULL;
+
+ /* now that we have the template, send it. */
+- ci_send_message (0, tmphdr, tmpbody, NULL, parent);
++ ci_send_message (flags, tmphdr, tmpbody, NULL, parent);
+ return;
+
+ bail:
+@@ -574,7 +574,7 @@ _("Can't decode all tagged attachments. MIME-forward the others?"))) == -1)
+ */
+
+ static void attach_forward_msgs (FILE * fp, HEADER * hdr,
+- ATTACHPTR ** idx, short idxlen, BODY * cur)
++ ATTACHPTR ** idx, short idxlen, BODY * cur, int flags)
+ {
+ HEADER *curhdr = NULL;
+ HEADER *tmphdr;
+@@ -679,23 +679,23 @@ static void attach_forward_msgs (FILE * fp, HEADER * hdr,
+ else
+ mutt_free_header (&tmphdr);
+
+- ci_send_message (0, tmphdr, *tmpbody ? tmpbody : NULL,
++ ci_send_message (flags, tmphdr, *tmpbody ? tmpbody : NULL,
+ NULL, curhdr);
+
+ }
+
+ void mutt_attach_forward (FILE * fp, HEADER * hdr,
+- ATTACHPTR ** idx, short idxlen, BODY * cur)
++ ATTACHPTR ** idx, short idxlen, BODY * cur, int flags)
+ {
+ short nattach;
+
+
+ if (check_all_msg (idx, idxlen, cur, 0) == 0)
+- attach_forward_msgs (fp, hdr, idx, idxlen, cur);
++ attach_forward_msgs (fp, hdr, idx, idxlen, cur, flags);
+ else
+ {
+ nattach = count_tagged (idx, idxlen);
+- attach_forward_bodies (fp, hdr, idx, idxlen, cur, nattach);
++ attach_forward_bodies (fp, hdr, idx, idxlen, cur, nattach, flags);
+ }
+ }
+
+@@ -753,28 +753,40 @@ attach_reply_envelope_defaults (ENVELOPE *env, ATTACHPTR **idx, short idxlen,
+ return -1;
+ }
+
+- if (parent)
++#ifdef USE_NNTP
++ if ((flags & SENDNEWS))
+ {
+- if (mutt_fetch_recips (env, curenv, flags) == -1)
+- return -1;
++ /* in case followup set Newsgroups: with Followup-To: if it present */
++ if (!env->newsgroups && curenv &&
++ mutt_strcasecmp (curenv->followup_to, "poster"))
++ env->newsgroups = safe_strdup (curenv->followup_to);
+ }
+ else
++#endif
+ {
+- for (i = 0; i < idxlen; i++)
++ if (parent)
+ {
+- if (idx[i]->content->tagged
+- && mutt_fetch_recips (env, idx[i]->content->hdr->env, flags) == -1)
++ if (mutt_fetch_recips (env, curenv, flags) == -1)
+ return -1;
+ }
++ else
++ {
++ for (i = 0; i < idxlen; i++)
++ {
++ if (idx[i]->content->tagged
++ && mutt_fetch_recips (env, idx[i]->content->hdr->env, flags) == -1)
++ return -1;
++ }
++ }
++
++ if ((flags & SENDLISTREPLY) && !env->to)
++ {
++ mutt_error _("No mailing lists found!");
++ return (-1);
++ }
++
++ mutt_fix_reply_recipients (env);
+ }
+-
+- if ((flags & SENDLISTREPLY) && !env->to)
+- {
+- mutt_error _("No mailing lists found!");
+- return (-1);
+- }
+-
+- mutt_fix_reply_recipients (env);
+ mutt_make_misc_reply_headers (env, Context, curhdr, curenv);
+
+ if (parent)
+@@ -835,6 +847,13 @@ void mutt_attach_reply (FILE * fp, HEADER * hdr,
+ char prefix[SHORT_STRING];
+ int rc;
+
++#ifdef USE_NNTP
++ if (flags & SENDNEWS)
++ set_option (OPTNEWSSEND);
++ else
++ unset_option (OPTNEWSSEND);
++#endif
++
+ if (check_all_msg (idx, idxlen, cur, 0) == -1)
+ {
+ nattach = count_tagged (idx, idxlen);
+diff --git a/send.c b/send.c
+index 893c859..13358b9 100644
+--- a/send.c
++++ b/send.c
+@@ -44,6 +44,11 @@
+ #include <sys/types.h>
+ #include <utime.h>
+
++#ifdef USE_NNTP
++#include "nntp.h"
++#include "mx.h"
++#endif
++
+ #ifdef MIXMASTER
+ #include "remailer.h"
+ #endif
+@@ -213,17 +218,51 @@ static int edit_address (ADDRESS **a, /* const */ char *field)
+ return 0;
+ }
+
+-static int edit_envelope (ENVELOPE *en)
++static int edit_envelope (ENVELOPE *en, int flags)
+ {
+ char buf[HUGE_STRING];
+ LIST *uh = UserHeader;
+
+- if (edit_address (&en->to, "To: ") == -1 || en->to == NULL)
+- return (-1);
+- if (option (OPTASKCC) && edit_address (&en->cc, "Cc: ") == -1)
+- return (-1);
+- if (option (OPTASKBCC) && edit_address (&en->bcc, "Bcc: ") == -1)
+- return (-1);
++#ifdef USE_NNTP
++ if (option (OPTNEWSSEND))
++ {
++ if (en->newsgroups)
++ strfcpy (buf, en->newsgroups, sizeof (buf));
++ else
++ buf[0] = 0;
++ if (mutt_get_field ("Newsgroups: ", buf, sizeof (buf), 0) != 0)
++ return (-1);
++ FREE (&en->newsgroups);
++ en->newsgroups = safe_strdup (buf);
++
++ if (en->followup_to)
++ strfcpy (buf, en->followup_to, sizeof (buf));
++ else
++ buf[0] = 0;
++ if (option (OPTASKFOLLOWUP) && mutt_get_field ("Followup-To: ", buf, sizeof (buf), 0) != 0)
++ return (-1);
++ FREE (&en->followup_to);
++ en->followup_to = safe_strdup (buf);
++
++ if (en->x_comment_to)
++ strfcpy (buf, en->x_comment_to, sizeof (buf));
++ else
++ buf[0] = 0;
++ if (option (OPTXCOMMENTTO) && option (OPTASKXCOMMENTTO) && mutt_get_field ("X-Comment-To: ", buf, sizeof (buf), 0) != 0)
++ return (-1);
++ FREE (&en->x_comment_to);
++ en->x_comment_to = safe_strdup (buf);
++ }
++ else
++#endif
++ {
++ if (edit_address (&en->to, "To: ") == -1 || en->to == NULL)
++ return (-1);
++ if (option (OPTASKCC) && edit_address (&en->cc, "Cc: ") == -1)
++ return (-1);
++ if (option (OPTASKBCC) && edit_address (&en->bcc, "Bcc: ") == -1)
++ return (-1);
++ }
+
+ if (en->subject)
+ {
+@@ -258,6 +297,14 @@ static int edit_envelope (ENVELOPE *en)
+ return 0;
+ }
+
++#ifdef USE_NNTP
++char *nntp_get_header (const char *s)
++{
++ SKIPWS (s);
++ return safe_strdup (s);
++}
++#endif
++
+ static void process_user_recips (ENVELOPE *env)
+ {
+ LIST *uh = UserHeader;
+@@ -270,6 +317,14 @@ static void process_user_recips (ENVELOPE *env)
+ env->cc = rfc822_parse_adrlist (env->cc, uh->data + 3);
+ else if (ascii_strncasecmp ("bcc:", uh->data, 4) == 0)
+ env->bcc = rfc822_parse_adrlist (env->bcc, uh->data + 4);
++#ifdef USE_NNTP
++ else if (ascii_strncasecmp ("newsgroups:", uh->data, 11) == 0)
++ env->newsgroups = nntp_get_header (uh->data + 11);
++ else if (ascii_strncasecmp ("followup-to:", uh->data, 12) == 0)
++ env->followup_to = nntp_get_header (uh->data + 12);
++ else if (ascii_strncasecmp ("x-comment-to:", uh->data, 13) == 0)
++ env->x_comment_to = nntp_get_header (uh->data + 13);
++#endif
+ }
+ }
+
+@@ -308,6 +363,12 @@ static void process_user_header (ENVELOPE *env)
+ else if (ascii_strncasecmp ("to:", uh->data, 3) != 0 &&
+ ascii_strncasecmp ("cc:", uh->data, 3) != 0 &&
+ ascii_strncasecmp ("bcc:", uh->data, 4) != 0 &&
++#ifdef USE_NNTP
++ ascii_strncasecmp ("newsgroups:", uh->data, 11) != 0 &&
++ ascii_strncasecmp ("followup-to:", uh->data, 12) != 0 &&
++ ascii_strncasecmp ("x-comment-to:", uh->data, 13) != 0 &&
++#endif
++ ascii_strncasecmp ("supersedes:", uh->data, 11) != 0 &&
+ ascii_strncasecmp ("subject:", uh->data, 8) != 0 &&
+ ascii_strncasecmp ("return-path:", uh->data, 12) != 0)
+ {
+@@ -656,6 +717,10 @@ void mutt_add_to_reference_headers (ENVELOPE *env, ENVELOPE *curenv, LIST ***pp,
+ if (pp) *pp = p;
+ if (qq) *qq = q;
+
++#ifdef USE_NNTP
++ if (option (OPTNEWSSEND) && option (OPTXCOMMENTTO) && curenv->from)
++ env->x_comment_to = safe_strdup (mutt_get_name (curenv->from));
++#endif
+ }
+
+ static void
+@@ -718,6 +783,16 @@ envelope_defaults (ENVELOPE *env, CONTEXT *ctx, HEADER *cur, int flags)
+
+ if (flags & SENDREPLY)
+ {
++#ifdef USE_NNTP
++ if ((flags & SENDNEWS))
++ {
++ /* in case followup set Newsgroups: with Followup-To: if it present */
++ if (!env->newsgroups && curenv &&
++ mutt_strcasecmp (curenv->followup_to, "poster"))
++ env->newsgroups = safe_strdup (curenv->followup_to);
++ }
++ else
++#endif
+ if (tag)
+ {
+ HEADER *h;
+@@ -864,7 +939,18 @@ void mutt_set_followup_to (ENVELOPE *e)
+ * it hasn't already been set
+ */
+
+- if (option (OPTFOLLOWUPTO) && !e->mail_followup_to)
++ if (!option (OPTFOLLOWUPTO))
++ return;
++#ifdef USE_NNTP
++ if (option (OPTNEWSSEND))
++ {
++ if (!e->followup_to && e->newsgroups && (strrchr (e->newsgroups, ',')))
++ e->followup_to = safe_strdup (e->newsgroups);
++ return;
++ }
++#endif
++
++ if (!e->mail_followup_to)
+ {
+ if (mutt_is_list_cc (0, e->to, e->cc))
+ {
+@@ -1026,6 +1112,9 @@ static int send_message (HEADER *msg)
+ #endif
+
+ #if USE_SMTP
++#ifdef USE_NNTP
++ if (!option (OPTNEWSSEND))
++#endif
+ if (SmtpUrl)
+ return mutt_smtp_send (msg->env->from, msg->env->to, msg->env->cc,
+ msg->env->bcc, tempfile,
+@@ -1137,6 +1226,13 @@ ci_send_message (int flags, /* send mode */
+
+ int rv = -1;
+
++#ifdef USE_NNTP
++ if (flags & SENDNEWS)
++ set_option (OPTNEWSSEND);
++ else
++ unset_option (OPTNEWSSEND);
++#endif
++
+ if (!flags && !msg && quadoption (OPT_RECALL) != M_NO &&
+ mutt_num_postponed (1))
+ {
+@@ -1167,6 +1263,22 @@ ci_send_message (int flags, /* send mode */
+ {
+ if ((flags = mutt_get_postponed (ctx, msg, &cur, fcc, sizeof (fcc))) < 0)
+ goto cleanup;
++#ifdef USE_NNTP
++ /*
++ * If postponed message is a news article, it have
++ * a "Newsgroups:" header line, then set appropriate flag.
++ */
++ if (msg->env->newsgroups)
++ {
++ flags |= SENDNEWS;
++ set_option (OPTNEWSSEND);
++ }
++ else
++ {
++ flags &= ~SENDNEWS;
++ unset_option (OPTNEWSSEND);
++ }
++#endif
+ }
+
+ if (flags & (SENDPOSTPONED|SENDRESEND))
+@@ -1259,11 +1371,16 @@ ci_send_message (int flags, /* send mode */
+ if (flags & SENDREPLY)
+ mutt_fix_reply_recipients (msg->env);
+
++#ifdef USE_NNTP
++ if ((flags & SENDNEWS) && ctx && ctx->magic == M_NNTP && !msg->env->newsgroups)
++ msg->env->newsgroups = safe_strdup (((NNTP_DATA *)ctx->data)->group);
++#endif
++
+ if (! (flags & (SENDMAILX|SENDBATCH)) &&
+ ! (option (OPTAUTOEDIT) && option (OPTEDITHDRS)) &&
+ ! ((flags & SENDREPLY) && option (OPTFASTREPLY)))
+ {
+- if (edit_envelope (msg->env) == -1)
++ if (edit_envelope (msg->env, flags) == -1)
+ goto cleanup;
+ }
+
+@@ -1546,6 +1663,11 @@ main_loop:
+ if (i == -1)
+ {
+ /* abort */
++#ifdef USE_NNTP
++ if (flags & SENDNEWS)
++ mutt_message _("Article not posted.");
++ else
++#endif
+ mutt_message _("Mail not sent.");
+ goto cleanup;
+ }
+@@ -1578,6 +1700,9 @@ main_loop:
+ }
+ }
+
++#ifdef USE_NNTP
++ if (!(flags & SENDNEWS))
++#endif
+ if (!has_recips (msg->env->to) && !has_recips (msg->env->cc) &&
+ !has_recips (msg->env->bcc))
+ {
+@@ -1611,6 +1736,19 @@ main_loop:
+ mutt_error _("No subject specified.");
+ goto main_loop;
+ }
++#ifdef USE_NNTP
++ if ((flags & SENDNEWS) && !msg->env->subject)
++ {
++ mutt_error _("No subject specified.");
++ goto main_loop;
++ }
++
++ if ((flags & SENDNEWS) && !msg->env->newsgroups)
++ {
++ mutt_error _("No newsgroup specified.");
++ goto main_loop;
++ }
++#endif
+
+ if (msg->content->next)
+ msg->content = mutt_make_multipart (msg->content);
+@@ -1817,7 +1955,12 @@ full_fcc:
+ }
+ }
+ else if (!option (OPTNOCURSES) && ! (flags & SENDMAILX))
+- mutt_message (i == 0 ? _("Mail sent.") : _("Sending in background."));
++ mutt_message (i != 0 ? _("Sending in background.") :
++#ifdef USE_NNTP
++ (flags & SENDNEWS) ? _("Article posted.") : _("Mail sent."));
++#else
++ _("Mail sent."));
++#endif
+
+ if (WithCrypto && (msg->security & ENCRYPT))
+ FREE (&pgpkeylist);
+diff --git a/sendlib.c b/sendlib.c
+index 9d83401..46fc511 100644
+--- a/sendlib.c
++++ b/sendlib.c
+@@ -46,6 +46,10 @@
+ #include <sys/wait.h>
+ #include <fcntl.h>
+
++#ifdef USE_NNTP
++#include "nntp.h"
++#endif
++
+ #ifdef HAVE_SYSEXITS_H
+ #include <sysexits.h>
+ #else /* Make sure EX_OK is defined <philiph@pobox.com> */
+@@ -1543,6 +1547,14 @@ void mutt_write_references (LIST *r, FILE *f, int trim)
+ {
+ LIST **ref = NULL;
+ int refcnt = 0, refmax = 0;
++ int multiline = 1;
++ int space = 0;
++
++ if (trim < 0)
++ {
++ trim = -trim;
++ multiline = 0;
++ }
+
+ for ( ; (trim == 0 || refcnt < trim) && r ; r = r->next)
+ {
+@@ -1553,9 +1565,11 @@ void mutt_write_references (LIST *r, FILE *f, int trim)
+
+ while (refcnt-- > 0)
+ {
+- fputc (' ', f);
++ if (multiline || space)
++ fputc (' ', f);
++ space = 1;
+ fputs (ref[refcnt]->data, f);
+- if (refcnt >= 1)
++ if (multiline && refcnt >= 1)
+ fputc ('\n', f);
+ }
+
+@@ -1962,6 +1976,9 @@ int mutt_write_rfc822_header (FILE *fp, ENVELOPE *env, BODY *attach,
+ mutt_write_address_list (env->to, fp, 4, 0);
+ }
+ else if (mode > 0)
++#ifdef USE_NNTP
++ if (!option (OPTNEWSSEND))
++#endif
+ fputs ("To: \n", fp);
+
+ if (env->cc)
+@@ -1970,6 +1987,9 @@ int mutt_write_rfc822_header (FILE *fp, ENVELOPE *env, BODY *attach,
+ mutt_write_address_list (env->cc, fp, 4, 0);
+ }
+ else if (mode > 0)
++#ifdef USE_NNTP
++ if (!option (OPTNEWSSEND))
++#endif
+ fputs ("Cc: \n", fp);
+
+ if (env->bcc && should_write_bcc)
+@@ -1981,8 +2001,28 @@ int mutt_write_rfc822_header (FILE *fp, ENVELOPE *env, BODY *attach,
+ }
+ }
+ else if (mode > 0)
++#ifdef USE_NNTP
++ if (!option (OPTNEWSSEND))
++#endif
+ fputs ("Bcc: \n", fp);
+
++#ifdef USE_NNTP
++ if (env->newsgroups)
++ fprintf (fp, "Newsgroups: %s\n", env->newsgroups);
++ else if (mode == 1 && option (OPTNEWSSEND))
++ fputs ("Newsgroups: \n", fp);
++
++ if (env->followup_to)
++ fprintf (fp, "Followup-To: %s\n", env->followup_to);
++ else if (mode == 1 && option (OPTNEWSSEND))
++ fputs ("Followup-To: \n", fp);
++
++ if (env->x_comment_to)
++ fprintf (fp, "X-Comment-To: %s\n", env->x_comment_to);
++ else if (mode == 1 && option (OPTNEWSSEND) && option (OPTXCOMMENTTO))
++ fputs ("X-Comment-To: \n", fp);
++#endif
++
+ if (env->subject)
+ mutt_write_one_header (fp, "Subject", env->subject, NULL, 0, 0);
+ else if (mode == 1)
+@@ -2001,6 +2041,9 @@ int mutt_write_rfc822_header (FILE *fp, ENVELOPE *env, BODY *attach,
+ fputs ("Reply-To: \n", fp);
+
+ if (env->mail_followup_to)
++#ifdef USE_NNTP
++ if (!option (OPTNEWSSEND))
++#endif
+ {
+ fputs ("Mail-Followup-To: ", fp);
+ mutt_write_address_list (env->mail_followup_to, fp, 18, 0);
+@@ -2344,6 +2387,23 @@ mutt_invoke_sendmail (ADDRESS *from, /* the sender */
+ size_t argslen = 0, argsmax = 0;
+ int i;
+
++#ifdef USE_NNTP
++ if (option (OPTNEWSSEND))
++ {
++ char cmd[LONG_STRING];
++
++ mutt_FormatString (cmd, sizeof (cmd), 0, NONULL (Inews), nntp_format_str, 0, 0);
++ if (!*cmd)
++ {
++ i = nntp_post (msg);
++ unlink (msg);
++ return i;
++ }
++
++ s = safe_strdup (cmd);
++ }
++#endif
++
+ /* ensure that $sendmail is set to avoid a crash. http://dev.mutt.org/trac/ticket/3548 */
+ if (!s)
+ {
+@@ -2374,6 +2434,10 @@ mutt_invoke_sendmail (ADDRESS *from, /* the sender */
+ i++;
+ }
+
++#ifdef USE_NNTP
++ if (!option (OPTNEWSSEND))
++ {
++#endif
+ if (eightbit && option (OPTUSE8BITMIME))
+ args = add_option (args, &argslen, &argsmax, "-B8BITMIME");
+
+@@ -2405,6 +2469,9 @@ mutt_invoke_sendmail (ADDRESS *from, /* the sender */
+ args = add_args (args, &argslen, &argsmax, to);
+ args = add_args (args, &argslen, &argsmax, cc);
+ args = add_args (args, &argslen, &argsmax, bcc);
++#ifdef USE_NNTP
++ }
++#endif
+
+ if (argslen == argsmax)
+ safe_realloc (&args, sizeof (char *) * (++argsmax));
+@@ -2485,6 +2552,9 @@ void mutt_prepare_envelope (ENVELOPE *env, int final)
+ rfc2047_encode_string (&env->x_label);
+
+ if (env->subject)
++#ifdef USE_NNTP
++ if (!option (OPTNEWSSEND) || option (OPTMIMESUBJECT))
++#endif
+ {
+ rfc2047_encode_string (&env->subject);
+ }
+@@ -2605,6 +2675,10 @@ int mutt_bounce_message (FILE *fp, HEADER *h, ADDRESS *to)
+ }
+ rfc822_write_address (resent_from, sizeof (resent_from), from, 0);
+
++#ifdef USE_NNTP
++ unset_option (OPTNEWSSEND);
++#endif
++
+ /*
+ * prepare recipient list. idna conversion appears to happen before this
+ * function is called, since the user receives confirmation of the address
+diff --git a/sort.c b/sort.c
+index 76e9e79..9aa259c 100644
+--- a/sort.c
++++ b/sort.c
+@@ -24,6 +24,11 @@
+ #include "sort.h"
+ #include "mutt_idna.h"
+
++#ifdef USE_NNTP
++#include "mx.h"
++#include "nntp.h"
++#endif
++
+ #include <stdlib.h>
+ #include <string.h>
+ #include <ctype.h>
+@@ -151,6 +156,17 @@ static int compare_order (const void *a, const void *b)
+ HEADER **ha = (HEADER **) a;
+ HEADER **hb = (HEADER **) b;
+
++#ifdef USE_NNTP
++ if (Context && Context->magic == M_NNTP)
++ {
++ anum_t na = NHDR (*ha)->article_num;
++ anum_t nb = NHDR (*hb)->article_num;
++ int result = na == nb ? 0 : na > nb ? 1 : -1;
++ AUXSORT (result, a, b);
++ return (SORTCODE (result));
++ }
++ else
++#endif
+ /* no need to auxsort because you will never have equality here */
+ return (SORTCODE ((*ha)->index - (*hb)->index));
+ }
+diff --git a/url.c b/url.c
+index 988cf59..3fd4079 100644
+--- a/url.c
++++ b/url.c
+@@ -39,6 +39,8 @@ static const struct mapping_t UrlMap[] =
+ { "imaps", U_IMAPS },
+ { "pop", U_POP },
+ { "pops", U_POPS },
++ { "news", U_NNTP },
++ { "snews", U_NNTPS },
+ { "mailto", U_MAILTO },
+ { "smtp", U_SMTP },
+ { "smtps", U_SMTPS },
+@@ -214,7 +216,7 @@ int url_ciss_tostring (ciss_url_t* ciss, char* dest, size_t len, int flags)
+ safe_strcat (dest, len, "//");
+ len -= (l = strlen (dest)); dest += l;
+
+- if (ciss->user)
++ if (ciss->user && (ciss->user[0] || !(flags & U_PATH)))
+ {
+ char u[STRING];
+ url_pct_encode (u, sizeof (u), ciss->user);
+diff --git a/url.h b/url.h
+index 926416e..15ec9ce 100644
+--- a/url.h
++++ b/url.h
+@@ -8,6 +8,8 @@ typedef enum url_scheme
+ U_POPS,
+ U_IMAP,
+ U_IMAPS,
++ U_NNTP,
++ U_NNTPS,
+ U_SMTP,
+ U_SMTPS,
+ U_MAILTO,
diff --git a/patch-1.5.4.vk.pgp_verbose_mime.patch b/patch-1.5.4.vk.pgp_verbose_mime.patch
new file mode 100644
index 000000000000..dec798aece2b
--- /dev/null
+++ b/patch-1.5.4.vk.pgp_verbose_mime.patch
@@ -0,0 +1,63 @@
+From: Antonio Radici <antonio@debian.org>
+Date: Thu, 27 Feb 2014 14:45:01 +0100
+Subject: patch-1.5.4.vk.pgp_verbose_mime
+
+Enable pgp_mime_signature_filename and
+pgp_mime_signature_description.
+
+Gbp-Pq: Topic features-old
+---
+ globals.h | 2 ++
+ init.h | 12 ++++++++++++
+ pgp.c | 2 ++
+ 3 files changed, 16 insertions(+)
+
+diff --git a/globals.h b/globals.h
+index a7e2304..5b6e56a 100644
+--- a/globals.h
++++ b/globals.h
+@@ -244,6 +244,8 @@ WHERE char *PgpVerifyKeyCommand;
+ WHERE char *PgpListSecringCommand;
+ WHERE char *PgpListPubringCommand;
+ WHERE char *PgpGetkeysCommand;
++WHERE char *PgpMimeSignatureFilename;
++WHERE char *PgpMimeSignatureDescription;
+
+ /*-- formerly in smime.h --*/
+ WHERE char *SmimeDefaultKey;
+diff --git a/init.h b/init.h
+index d4a882b..a64992a 100644
+--- a/init.h
++++ b/init.h
+@@ -2590,6 +2590,18 @@ struct option_t MuttVars[] = {
+ ** a line quoted text if it also matches $$smileys. This mostly
+ ** happens at the beginning of a line.
+ */
++ { "pgp_mime_signature_filename", DT_STR, R_NONE, UL &PgpMimeSignatureFilename, UL "signature.asc"},
++ /*
++ ** .pp
++ ** This option sets the filename used for signature parts in PGP/MIME
++ ** signed messages.
++ */
++ { "pgp_mime_signature_description", DT_STR, R_NONE, UL &PgpMimeSignatureDescription, UL "Digital signature"},
++ /*
++ ** .pp
++ ** This option sets the Content-Description used for signature parts in
++ ** PGP/MIME signed messages.
++ */
+
+
+
+diff --git a/pgp.c b/pgp.c
+index 506a078..92dee8c 100644
+--- a/pgp.c
++++ b/pgp.c
+@@ -1127,6 +1127,8 @@ BODY *pgp_sign_message (BODY *a)
+ t->disposition = DISPNONE;
+ t->encoding = ENC7BIT;
+ t->unlink = 1; /* ok to remove this file after sending. */
++ mutt_set_parameter ("name", PgpMimeSignatureFilename, &t->parameter);
++ t->description = safe_strdup (PgpMimeSignatureDescription);
+
+ return (a);
+ }
diff --git a/path_max.patch b/path_max.patch
new file mode 100644
index 000000000000..733143fc15e1
--- /dev/null
+++ b/path_max.patch
@@ -0,0 +1,25 @@
+From: Antonio Radici <antonio@debian.org>
+Date: Thu, 27 Feb 2014 17:38:03 +0100
+Subject: path_max
+
+Gbp-Pq: Topic upstream
+---
+ mutt.h | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/mutt.h b/mutt.h
+index b71f071..4db92a6 100644
+--- a/mutt.h
++++ b/mutt.h
+@@ -52,6 +52,11 @@
+ #include <limits.h>
+ #endif
+
++/* PATH_MAX is undefined on the hurd */
++#ifndef PATH_MAX
++#define PATH_MAX _POSIX_PATH_MAX
++#endif
++
+ #include <pwd.h>
+ #include <grp.h>
+
diff --git a/purge-message.patch b/purge-message.patch
new file mode 100644
index 000000000000..a9061299a01d
--- /dev/null
+++ b/purge-message.patch
@@ -0,0 +1,241 @@
+From: Cedric Duval <cedricduval@free.fr>
+Date: Thu, 27 Feb 2014 14:21:59 +0100
+Subject: purge-message
+
+(requires trash folder patch)
+
+This patch adds the purge-message function, which, unlike delete-message,
+will bypass the trash folder and really delete the mail.
+
+You can bind this function to <esc>D, for instance, by adding the following
+lines to your muttrc:
+
+bind index \eD purge-message
+bind pager \eD purge-message
+
+Please be very careful with this function, and try to use it as less as
+possible. The risk resides in getting into the habit of always using
+purge-message instead of delete-message, which would really defeat the
+purpose of having a trash folder feature.
+
+* Patch last synced with upstream:
+ - Date: 2007-02-15
+ - File: http://cedricduval.free.fr/mutt/patches/download/patch-1.5.5.1.cd.purge_message.3.4
+
+* Changes made:
+ - Updated to 1.5.13
+ - Fixed indentation of "purged" in mutt.h.
+
+Signed-off-by: Matteo F. Vescovi <mfvescovi@gmail.com>
+
+Gbp-Pq: Topic features
+---
+ OPS | 1 +
+ curs_main.c | 12 ++++++++++--
+ flags.c | 10 ++++++++++
+ functions.h | 2 ++
+ mutt.h | 2 ++
+ mx.c | 1 +
+ pager.c | 8 +++++++-
+ pattern.c | 4 +++-
+ 8 files changed, 36 insertions(+), 4 deletions(-)
+
+diff --git a/OPS b/OPS
+index 8414a8b..02cea8e 100644
+--- a/OPS
++++ b/OPS
+@@ -142,6 +142,7 @@ OP_PREV_ENTRY "move to the previous entry"
+ OP_PREV_LINE "scroll up one line"
+ OP_PREV_PAGE "move to the previous page"
+ OP_PRINT "print the current entry"
++OP_PURGE_MESSAGE "really delete the current entry, bypassing the trash folder"
+ OP_QUERY "query external program for addresses"
+ OP_QUERY_APPEND "append new query results to current results"
+ OP_QUIT "save changes to mailbox and quit"
+diff --git a/curs_main.c b/curs_main.c
+index 16ddbc9..d266708 100644
+--- a/curs_main.c
++++ b/curs_main.c
+@@ -1844,6 +1844,7 @@ int mutt_index_menu (void)
+ MAYBE_REDRAW (menu->redraw);
+ break;
+
++ case OP_PURGE_MESSAGE:
+ case OP_DELETE:
+
+ CHECK_MSGCOUNT;
+@@ -1854,6 +1855,7 @@ int mutt_index_menu (void)
+ if (tag)
+ {
+ mutt_tag_set_flag (M_DELETE, 1);
++ mutt_tag_set_flag (M_PURGED, (op != OP_PURGE_MESSAGE) ? 0 : 1);
+ if (option (OPTDELETEUNTAG))
+ mutt_tag_set_flag (M_TAG, 0);
+ menu->redraw = REDRAW_INDEX;
+@@ -1861,6 +1863,8 @@ int mutt_index_menu (void)
+ else
+ {
+ mutt_set_flag (Context, CURHDR, M_DELETE, 1);
++ mutt_set_flag (Context, CURHDR, M_PURGED,
++ (op != OP_PURGE_MESSAGE) ? 0 : 1);
+ if (option (OPTDELETEUNTAG))
+ mutt_set_flag (Context, CURHDR, M_TAG, 0);
+ if (option (OPTRESOLVE))
+@@ -2162,11 +2166,13 @@ int mutt_index_menu (void)
+ if (tag)
+ {
+ mutt_tag_set_flag (M_DELETE, 0);
++ mutt_tag_set_flag (M_PURGED, 0);
+ menu->redraw = REDRAW_INDEX;
+ }
+ else
+ {
+ mutt_set_flag (Context, CURHDR, M_DELETE, 0);
++ mutt_set_flag (Context, CURHDR, M_PURGED, 0);
+ if (option (OPTRESOLVE) && menu->current < Context->vcount - 1)
+ {
+ menu->current++;
+@@ -2187,9 +2193,11 @@ int mutt_index_menu (void)
+ CHECK_ACL(M_ACL_DELETE, _("undelete message(s)"));
+
+ rc = mutt_thread_set_flag (CURHDR, M_DELETE, 0,
+- op == OP_UNDELETE_THREAD ? 0 : 1);
++ op == OP_UNDELETE_THREAD ? 0 : 1)
++ + mutt_thread_set_flag (CURHDR, M_PURGED, 0,
++ op == OP_UNDELETE_THREAD ? 0 : 1);
+
+- if (rc != -1)
++ if (rc > -1)
+ {
+ if (option (OPTRESOLVE))
+ {
+diff --git a/flags.c b/flags.c
+index dfa6a50..5309bb7 100644
+--- a/flags.c
++++ b/flags.c
+@@ -104,6 +104,16 @@ void _mutt_set_flag (CONTEXT *ctx, HEADER *h, int flag, int bf, int upd_ctx)
+ }
+ break;
+
++ case M_PURGED:
++ if (bf)
++ {
++ if (!h->purged)
++ h->purged = 1;
++ }
++ else if (h->purged)
++ h->purged = 0;
++ break;
++
+ case M_NEW:
+
+ if (!mutt_bit_isset(ctx->rights,M_ACL_SEEN))
+diff --git a/functions.h b/functions.h
+index 7a1c5a9..a29388c 100644
+--- a/functions.h
++++ b/functions.h
+@@ -121,6 +121,7 @@ const struct binding_t OpMain[] = { /* map: index */
+ { "toggle-write", OP_TOGGLE_WRITE, "%" },
+ { "next-thread", OP_MAIN_NEXT_THREAD, "\016" },
+ { "next-subthread", OP_MAIN_NEXT_SUBTHREAD, "\033n" },
++ { "purge-message", OP_PURGE_MESSAGE, NULL },
+ { "query", OP_QUERY, "Q" },
+ { "quit", OP_QUIT, "q" },
+ { "reply", OP_REPLY, "r" },
+@@ -213,6 +214,7 @@ const struct binding_t OpPager[] = { /* map: pager */
+ { "print-message", OP_PRINT, "p" },
+ { "previous-thread", OP_MAIN_PREV_THREAD, "\020" },
+ { "previous-subthread",OP_MAIN_PREV_SUBTHREAD, "\033p" },
++ { "purge-message", OP_PURGE_MESSAGE, NULL },
+ { "quit", OP_QUIT, "Q" },
+ { "exit", OP_EXIT, "q" },
+ { "reply", OP_REPLY, "r" },
+diff --git a/mutt.h b/mutt.h
+index 0b879b9..8cee3d2 100644
+--- a/mutt.h
++++ b/mutt.h
+@@ -186,6 +186,7 @@ enum
+ M_UNDELETE,
+ M_DELETED,
+ M_APPENDED,
++ M_PURGED,
+ M_FLAG,
+ M_TAG,
+ M_UNTAG,
+@@ -711,6 +712,7 @@ typedef struct header
+ unsigned int flagged : 1; /* marked important? */
+ unsigned int tagged : 1;
+ unsigned int appended : 1; /* has been saved */
++ unsigned int purged : 1; /* bypassing the trash folder */
+ unsigned int deleted : 1;
+ unsigned int changed : 1;
+ unsigned int attach_del : 1; /* has an attachment marked for deletion */
+diff --git a/mx.c b/mx.c
+index f7fd01f..89b9431 100644
+--- a/mx.c
++++ b/mx.c
+@@ -806,6 +806,7 @@ static int trash_append (CONTEXT *ctx)
+ {
+ for (i = 0 ; i < ctx->msgcount ; i++)
+ if (ctx->hdrs[i]->deleted && !ctx->hdrs[i]->appended
++ && !ctx->hdrs[i]->purged
+ && mutt_append_message (ctx_trash, ctx, ctx->hdrs[i], 0, 0) == -1)
+ {
+ mx_close_mailbox (ctx_trash, NULL);
+diff --git a/pager.c b/pager.c
+index 23eb8ca..b17afb4 100644
+--- a/pager.c
++++ b/pager.c
+@@ -2350,12 +2350,15 @@ search_next:
+ MAYBE_REDRAW (redraw);
+ break;
+
++ case OP_PURGE_MESSAGE:
+ case OP_DELETE:
+ CHECK_MODE(IsHeader (extra));
+ CHECK_READONLY;
+ CHECK_ACL(M_ACL_DELETE, _("delete message"));
+
+ mutt_set_flag (Context, extra->hdr, M_DELETE, 1);
++ mutt_set_flag (Context, extra->hdr, M_PURGED,
++ ch != OP_PURGE_MESSAGE ? 0 : 1);
+ if (option (OPTDELETEUNTAG))
+ mutt_set_flag (Context, extra->hdr, M_TAG, 0);
+ redraw = REDRAW_STATUS | REDRAW_INDEX;
+@@ -2682,6 +2685,7 @@ search_next:
+ CHECK_ACL(M_ACL_DELETE, _("undelete message"));
+
+ mutt_set_flag (Context, extra->hdr, M_DELETE, 0);
++ mutt_set_flag (Context, extra->hdr, M_PURGED, 0);
+ redraw = REDRAW_STATUS | REDRAW_INDEX;
+ if (option (OPTRESOLVE))
+ {
+@@ -2697,9 +2701,11 @@ search_next:
+ CHECK_ACL(M_ACL_DELETE, _("undelete message(s)"));
+
+ r = mutt_thread_set_flag (extra->hdr, M_DELETE, 0,
++ ch == OP_UNDELETE_THREAD ? 0 : 1)
++ + mutt_thread_set_flag (extra->hdr, M_PURGED, 0,
+ ch == OP_UNDELETE_THREAD ? 0 : 1);
+
+- if (r != -1)
++ if (r > -1)
+ {
+ if (option (OPTRESOLVE))
+ {
+diff --git a/pattern.c b/pattern.c
+index 7af1c38..4cdbd05 100644
+--- a/pattern.c
++++ b/pattern.c
+@@ -1358,8 +1358,10 @@ int mutt_pattern_func (int op, char *prompt)
+ {
+ switch (op)
+ {
+- case M_DELETE:
+ case M_UNDELETE:
++ mutt_set_flag (Context, Context->hdrs[Context->v2r[i]], M_PURGED,
++ 0);
++ case M_DELETE:
+ mutt_set_flag (Context, Context->hdrs[Context->v2r[i]], M_DELETE,
+ (op == M_DELETE));
+ break;
diff --git a/sensible_browser_position.patch b/sensible_browser_position.patch
new file mode 100644
index 000000000000..24b7d4c1262a
--- /dev/null
+++ b/sensible_browser_position.patch
@@ -0,0 +1,115 @@
+From: Antonio Radici <antonio@debian.org>
+Date: Thu, 27 Feb 2014 14:42:36 +0100
+Subject: sensible_browser_position
+
+This is the sensible browser position patch by Haakon Riiser.
+
+ * Found in: <20050309162127.GA5656@s>
+ http://lists.df7cb.de/mutt/message/20050309.162127.a244a894.en.html
+
+Gbp-Pq: Topic features
+---
+ browser.c | 24 +++++++++++++++++++++---
+ menu.c | 11 +++++++++++
+ mutt_menu.h | 1 +
+ 3 files changed, 33 insertions(+), 3 deletions(-)
+
+diff --git a/browser.c b/browser.c
+index cda4900..dbb31c8 100644
+--- a/browser.c
++++ b/browser.c
+@@ -56,6 +56,7 @@ typedef struct folder_t
+ int num;
+ } FOLDER;
+
++static char OldLastDir[_POSIX_PATH_MAX] = "";
+ static char LastDir[_POSIX_PATH_MAX] = "";
+ static char LastDirBackup[_POSIX_PATH_MAX] = "";
+
+@@ -536,15 +537,33 @@ static void init_menu (struct browser_state *state, MUTTMENU *menu, char *title,
+ menu->tagged = 0;
+
+ if (buffy)
++ {
++ menu->is_mailbox_list = 1;
+ snprintf (title, titlelen, _("Mailboxes [%d]"), mutt_buffy_check (0));
++ }
+ else
+ {
++ menu->is_mailbox_list = 0;
+ strfcpy (path, LastDir, sizeof (path));
+ mutt_pretty_mailbox (path, sizeof (path));
+ #ifdef USE_IMAP
+ if (state->imap_browse && option (OPTIMAPLSUB))
+- snprintf (title, titlelen, _("Subscribed [%s], File mask: %s"),
+- path, NONULL (Mask.pattern));
++ {
++ char *p = strrchr (OldLastDir, '/');
++ if (p && p - OldLastDir == mutt_strlen (LastDir) &&
++ mutt_strncmp (LastDir, OldLastDir, p - OldLastDir) == 0)
++ {
++ /* If we get here, it means that LastDir is the parent directory of
++ * OldLastDir. I.e., we're returning from a subdirectory, and we want
++ * to position the cursor on the directory we're returning from. */
++ int i;
++ for (i = 0; i < state->entrymax; i++)
++ if (mutt_strcmp (state->entry[i].name, p + 1) == 0)
++ menu->current = i;
++ }
++ snprintf (title, titlelen, _("Directory [%s], File mask: %s"),
++ path, NONULL(Mask.pattern));
++ }
+ else
+ #endif
+ snprintf (title, titlelen, _("Directory [%s], File mask: %s"),
+@@ -731,7 +750,6 @@ void _mutt_select_file (char *f, size_t flen, int flags, char ***files, int *num
+ #endif
+ )
+ {
+- char OldLastDir[_POSIX_PATH_MAX];
+
+ /* save the old directory */
+ strfcpy (OldLastDir, LastDir, sizeof (OldLastDir));
+diff --git a/menu.c b/menu.c
+index e03dd33..27b5f8e 100644
+--- a/menu.c
++++ b/menu.c
+@@ -849,8 +849,17 @@ int menu_redraw (MUTTMENU *menu)
+
+ int mutt_menuLoop (MUTTMENU *menu)
+ {
++ static int last_position = -1;
+ int i = OP_NULL;
+
++ if ( menu->max && menu->is_mailbox_list ) {
++ if ( last_position > (menu->max-1) ) {
++ last_position = -1;
++ } else if (last_position >= 0) {
++ menu->current = last_position;
++ }
++ }
++
+ FOREVER
+ {
+ if (option (OPTMENUCALLER))
+@@ -1078,6 +1087,8 @@ int mutt_menuLoop (MUTTMENU *menu)
+ break;
+
+ default:
++ if (menu->is_mailbox_list)
++ last_position = menu->current;
+ return (i);
+ }
+ }
+diff --git a/mutt_menu.h b/mutt_menu.h
+index 82abecd..404b6dd 100644
+--- a/mutt_menu.h
++++ b/mutt_menu.h
+@@ -49,6 +49,7 @@ typedef struct menu_t
+ int offset; /* which screen row to start the index */
+ int pagelen; /* number of entries per screen */
+ int tagprefix;
++ int is_mailbox_list;
+
+ /* Setting dialog != NULL overrides normal menu behavior.
+ * In dialog mode menubar is hidden and prompt keys are checked before
diff --git a/sidebar-compose.patch b/sidebar-compose.patch
new file mode 100644
index 000000000000..ed12fe93ee55
--- /dev/null
+++ b/sidebar-compose.patch
@@ -0,0 +1,40 @@
+From: Evgeni Golov <evgeni@debian.org>
+Date: Fri, 14 Mar 2014 08:54:47 +0100
+Subject: sidebar-compose
+
+draw_sidebar sets SidebarWidth to 0 when sidebar_visible is false.
+However, if you start mutt in compose mode, draw_sidebar won't be
+called until the next redraw and your header lines will be off by
+the width of the sidebar, even when you did not want a sidebar at
+all.
+
+Can be tested with:
+ HOME=/ LC_ALL=C mutt -e 'unset sidebar_visible' -s test recipient
+
+Closes: #502627
+
+Gbp-Pq: Topic mutt-patched
+---
+ compose.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/compose.c b/compose.c
+index 5a14d70..16576f2 100644
+--- a/compose.c
++++ b/compose.c
+@@ -32,6 +32,7 @@
+ #include "mailbox.h"
+ #include "sort.h"
+ #include "charset.h"
++#include "sidebar.h"
+
+ #ifdef MIXMASTER
+ #include "remailer.h"
+@@ -245,6 +246,7 @@ static void draw_envelope_addr (int line, ADDRESS *addr)
+
+ static void draw_envelope (HEADER *msg, char *fcc)
+ {
++ draw_sidebar (MENU_COMPOSE);
+ draw_envelope_addr (HDR_FROM, msg->env->from);
+ draw_envelope_addr (HDR_TO, msg->env->to);
+ draw_envelope_addr (HDR_CC, msg->env->cc);
diff --git a/sidebar-delimnullwide.patch b/sidebar-delimnullwide.patch
new file mode 100644
index 000000000000..11d2ced5a0c0
--- /dev/null
+++ b/sidebar-delimnullwide.patch
@@ -0,0 +1,38 @@
+From: Evgeni Golov <sargentd@die-welt.net>
+Date: Wed, 5 Mar 2014 17:46:07 +0100
+Subject: sidebar-delimnullwide
+
+SidebarDelim can be NULL and strlen(NULL) is a bad idea, as it will segfault.
+Wrap it with NONULL().
+
+While at it, change strlen to mbstowcs for better utf8 support.
+
+Closes: #696145, #663883
+
+Gbp-Pq: Topic mutt-patched
+---
+ sidebar.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/sidebar.c b/sidebar.c
+index 51a25ca..c3ea338 100644
+--- a/sidebar.c
++++ b/sidebar.c
+@@ -88,7 +88,7 @@ char *make_sidebar_entry(char *box, int size, int new, int flagged)
+ int box_len, box_bytes;
+ int int_len;
+ int right_offset = 0;
+- int delim_len = strlen(SidebarDelim);
++ int delim_len = mbstowcs(NULL, NONULL(SidebarDelim), 0);
+ static char *entry;
+
+ right_width = left_width = 0;
+@@ -178,7 +178,7 @@ int draw_sidebar(int menu) {
+ #ifndef USE_SLANG_CURSES
+ attr_t attrs;
+ #endif
+- short delim_len = strlen(SidebarDelim);
++ short delim_len = mbstowcs(NULL, NONULL(SidebarDelim), 0);
+ short color_pair;
+
+ static bool initialized = false;
diff --git a/sidebar-dotpathsep.patch b/sidebar-dotpathsep.patch
new file mode 100644
index 000000000000..5649ca4bdb21
--- /dev/null
+++ b/sidebar-dotpathsep.patch
@@ -0,0 +1,98 @@
+From: Fabian Groffen <grobian@gentoo.org>
+Date: Tue, 4 Mar 2014 21:12:15 +0100
+Subject: sidebar-dotpathsep
+
+Make path separators for sidebar folders configurable.
+
+When using IMAP, a '.' is often used as path separator, hence make the
+path separators configurable through sidebar_delim_chars variable.
+It defaults to "/." to work for both mboxes as well as IMAP folders. It
+can be set to only "/" or "." or whichever character desired as needed.
+
+Gbp-Pq: Topic mutt-patched
+---
+ globals.h | 1 +
+ init.h | 8 ++++++++
+ sidebar.c | 31 ++++++++++++++++++++++++-------
+ 3 files changed, 33 insertions(+), 7 deletions(-)
+
+diff --git a/globals.h b/globals.h
+index 3f83328..61765a4 100644
+--- a/globals.h
++++ b/globals.h
+@@ -118,6 +118,7 @@ WHERE char *SendCharset;
+ WHERE char *Sendmail;
+ WHERE char *Shell;
+ WHERE char *SidebarDelim;
++WHERE char *SidebarDelimChars INITVAL (NULL);
+ WHERE char *Signature;
+ WHERE char *SimpleSearch;
+ #if USE_SMTP
+diff --git a/init.h b/init.h
+index 502f570..b0784d8 100644
+--- a/init.h
++++ b/init.h
+@@ -2001,6 +2001,14 @@ struct option_t MuttVars[] = {
+ ** .pp
+ ** The width of the sidebar.
+ */
++ { "sidebar_delim_chars", DT_STR, R_NONE, UL &SidebarDelimChars, UL "/." },
++ /*
++ ** .pp
++ ** This contains the list of characters which you would like to treat
++ ** as folder separators for displaying paths in the sidebar. If
++ ** you're not using IMAP folders, you probably prefer setting this to "/"
++ ** alone.
++ */
+ { "pgp_use_gpg_agent", DT_BOOL, R_NONE, OPTUSEGPGAGENT, 0},
+ /*
+ ** .pp
+diff --git a/sidebar.c b/sidebar.c
+index 6098c2a..4356ffc 100644
+--- a/sidebar.c
++++ b/sidebar.c
+@@ -249,20 +249,37 @@ int draw_sidebar(int menu) {
+ // calculate depth of current folder and generate its display name with indented spaces
+ int sidebar_folder_depth = 0;
+ char *sidebar_folder_name;
+- sidebar_folder_name = basename(tmp->path);
++ int i;
++ sidebar_folder_name = tmp->path;
++ /* disregard a trailing separator, so strlen() - 2
++ * https://bugs.gentoo.org/show_bug.cgi?id=373197#c16 */
++ for (i = strlen(sidebar_folder_name) - 2; i >= 0; i--) {
++ if (SidebarDelimChars &&
++ strchr(SidebarDelimChars, sidebar_folder_name[i]))
++ {
++ sidebar_folder_name += i + 1;
++ break;
++ }
++ }
+ if ( maildir_is_prefix ) {
+ char *tmp_folder_name;
+- int i;
++ int lastsep = 0;
+ tmp_folder_name = tmp->path + strlen(Maildir);
+- for (i = 0; i < strlen(tmp->path) - strlen(Maildir); i++) {
+- if (tmp_folder_name[i] == '/') sidebar_folder_depth++;
+- }
++ for (i = 0; i < strlen(tmp_folder_name) - 1; i++) {
++ if (SidebarDelimChars &&
++ strchr(SidebarDelimChars, tmp_folder_name[i]))
++ {
++ sidebar_folder_depth++;
++ lastsep = i + 1;
++ }
++ }
+ if (sidebar_folder_depth > 0) {
+- sidebar_folder_name = malloc(strlen(basename(tmp->path)) + sidebar_folder_depth + 1);
++ tmp_folder_name += lastsep; /* basename */
++ sidebar_folder_name = malloc(strlen(tmp_folder_name) + sidebar_folder_depth + 1);
+ for (i=0; i < sidebar_folder_depth; i++)
+ sidebar_folder_name[i]=' ';
+ sidebar_folder_name[i]=0;
+- strncat(sidebar_folder_name, basename(tmp->path), strlen(basename(tmp->path)) + sidebar_folder_depth);
++ strncat(sidebar_folder_name, tmp_folder_name, strlen(tmp_folder_name) + sidebar_folder_depth);
+ }
+ }
+ printw( "%.*s", SidebarWidth - delim_len + 1,
diff --git a/sidebar-new.patch b/sidebar-new.patch
new file mode 100644
index 000000000000..ab17b6c936cf
--- /dev/null
+++ b/sidebar-new.patch
@@ -0,0 +1,97 @@
+From ed547aeb2c6f10da3639e5212b47f4e3aea450d6 Mon Sep 17 00:00:00 2001
+From: Julius Haertl <jus@bitgrid.net>
+Date: Tue, 9 Sep 2014 22:31:49 +0200
+Subject: [PATCH] Patch for sidebar iteration functionality
+
+sidebar-new will move the selected folder to the next with new messages.
+If the end is reached, it will start at the top.
+
+Useful macros would be:
+
+ macro index <esc>a "<sidebar-new><sidebar-open>"
+ macro pager <esc>a "<exit><sidebar-new><sidebar-open>"
+---
+ OPS | 1 +
+ curs_main.c | 1 +
+ functions.h | 2 ++
+ pager.c | 1 +
+ sidebar.c | 10 ++++++++++
+ 5 files changed, 15 insertions(+)
+
+diff --git a/OPS b/OPS
+index b900373..62db4b4 100644
+--- a/OPS
++++ b/OPS
+@@ -206,3 +206,4 @@ OP_SIDEBAR_PREV "go to previous mailbox"
+ OP_SIDEBAR_OPEN "open hilighted mailbox"
+ OP_SIDEBAR_NEXT_NEW "go down to next mailbox with new mail"
+ OP_SIDEBAR_PREV_NEW "go to previous mailbox with new mail"
++OP_SIDEBAR_NEW "iterate though mailboxes with new mail"
+diff --git a/curs_main.c b/curs_main.c
+index 8cec507..fb0a74f 100644
+--- a/curs_main.c
++++ b/curs_main.c
+@@ -2550,6 +2550,7 @@ int mutt_index_menu (void)
+ case OP_SIDEBAR_PREV:
+ case OP_SIDEBAR_NEXT_NEW:
+ case OP_SIDEBAR_PREV_NEW:
++ case OP_SIDEBAR_NEW:
+ scroll_sidebar(op, menu->menu);
+ break;
+ default:
+diff --git a/functions.h b/functions.h
+index 66f3345..8ca5411 100644
+--- a/functions.h
++++ b/functions.h
+@@ -195,6 +195,7 @@ const struct binding_t OpMain[] = { /* map: index */
+ { "sidebar-prev", OP_SIDEBAR_PREV, NULL },
+ { "sidebar-next-new", OP_SIDEBAR_NEXT_NEW, NULL},
+ { "sidebar-prev-new", OP_SIDEBAR_PREV_NEW, NULL},
++ { "sidebar-new", OP_SIDEBAR_NEW, NULL },
+ { "sidebar-open", OP_SIDEBAR_OPEN, NULL },
+ { NULL, 0, NULL }
+ };
+@@ -317,6 +318,7 @@ const struct binding_t OpPager[] = { /* map: pager */
+ { "sidebar-prev", OP_SIDEBAR_PREV, NULL },
+ { "sidebar-next-new", OP_SIDEBAR_NEXT_NEW, NULL},
+ { "sidebar-prev-new", OP_SIDEBAR_PREV_NEW, NULL},
++ { "sidebar-new", OP_SIDEBAR_NEW, NULL },
+ { "sidebar-open", OP_SIDEBAR_OPEN, NULL },
+ { NULL, 0, NULL }
+ };
+diff --git a/pager.c b/pager.c
+index e888452..9a7d076 100644
+--- a/pager.c
++++ b/pager.c
+@@ -2868,6 +2868,7 @@ search_next:
+ case OP_SIDEBAR_PREV:
+ case OP_SIDEBAR_NEXT_NEW:
+ case OP_SIDEBAR_PREV_NEW:
++ case OP_SIDEBAR_NEW:
+ scroll_sidebar(ch, MENU_PAGER);
+ break;
+
+diff --git a/sidebar.c b/sidebar.c
+index c3ea338..eb8ecd2 100644
+--- a/sidebar.c
++++ b/sidebar.c
+@@ -429,6 +429,16 @@ void scroll_sidebar(int op, int menu)
+ CurBuffy = CurBuffy->next;
+ }
+ break;
++ case OP_SIDEBAR_NEW:
++ if ( (tmp = exist_next_new()) == NULL)
++ tmp = TopBuffy;
++ if ( tmp->msg_unread == 0 ) {
++ CurBuffy = tmp;
++ tmp = exist_next_new();
++ }
++ if ( tmp != NULL )
++ CurBuffy = tmp;
++ break;
+ default:
+ return;
+ }
+--
+2.1.0
+
diff --git a/sidebar-newonly.patch b/sidebar-newonly.patch
new file mode 100644
index 000000000000..f9ff91e4ffd2
--- /dev/null
+++ b/sidebar-newonly.patch
@@ -0,0 +1,198 @@
+From: Steve Kemp <steve@steve.org.uk>
+Date: Tue, 4 Mar 2014 22:07:06 +0100
+Subject: sidebar-newonly
+
+patches written by Steve Kemp, it adds two new functionalities to the sidebar,
+so only the mailbox with new messages will be shown (and/or) selected
+See Debian bug http://bugs.debian.org/532510
+
+Gbp-Pq: Topic mutt-patched
+---
+ OPS | 2 ++
+ curs_main.c | 2 ++
+ functions.h | 4 ++++
+ init.h | 5 +++++
+ mutt.h | 2 ++
+ pager.c | 2 ++
+ sidebar.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++--
+ 7 files changed, 70 insertions(+), 2 deletions(-)
+
+diff --git a/OPS b/OPS
+index b036db9..1ed9c96 100644
+--- a/OPS
++++ b/OPS
+@@ -185,3 +185,5 @@ OP_SIDEBAR_SCROLL_DOWN "scroll the mailbox pane down 1 page"
+ OP_SIDEBAR_NEXT "go down to next mailbox"
+ OP_SIDEBAR_PREV "go to previous mailbox"
+ OP_SIDEBAR_OPEN "open hilighted mailbox"
++OP_SIDEBAR_NEXT_NEW "go down to next mailbox with new mail"
++OP_SIDEBAR_PREV_NEW "go to previous mailbox with new mail"
+diff --git a/curs_main.c b/curs_main.c
+index 7b81798..5c58f1c 100644
+--- a/curs_main.c
++++ b/curs_main.c
+@@ -2262,6 +2262,8 @@ int mutt_index_menu (void)
+ case OP_SIDEBAR_SCROLL_DOWN:
+ case OP_SIDEBAR_NEXT:
+ case OP_SIDEBAR_PREV:
++ case OP_SIDEBAR_NEXT_NEW:
++ case OP_SIDEBAR_PREV_NEW:
+ scroll_sidebar(op, menu->menu);
+ break;
+ default:
+diff --git a/functions.h b/functions.h
+index ef8937a..363b4d5 100644
+--- a/functions.h
++++ b/functions.h
+@@ -174,6 +174,8 @@ const struct binding_t OpMain[] = { /* map: index */
+ { "sidebar-scroll-down", OP_SIDEBAR_SCROLL_DOWN, NULL },
+ { "sidebar-next", OP_SIDEBAR_NEXT, NULL },
+ { "sidebar-prev", OP_SIDEBAR_PREV, NULL },
++ { "sidebar-next-new", OP_SIDEBAR_NEXT_NEW, NULL},
++ { "sidebar-prev-new", OP_SIDEBAR_PREV_NEW, NULL},
+ { "sidebar-open", OP_SIDEBAR_OPEN, NULL },
+ { NULL, 0, NULL }
+ };
+@@ -283,6 +285,8 @@ const struct binding_t OpPager[] = { /* map: pager */
+ { "sidebar-scroll-down", OP_SIDEBAR_SCROLL_DOWN, NULL },
+ { "sidebar-next", OP_SIDEBAR_NEXT, NULL },
+ { "sidebar-prev", OP_SIDEBAR_PREV, NULL },
++ { "sidebar-next-new", OP_SIDEBAR_NEXT_NEW, NULL},
++ { "sidebar-prev-new", OP_SIDEBAR_PREV_NEW, NULL},
+ { "sidebar-open", OP_SIDEBAR_OPEN, NULL },
+ { NULL, 0, NULL }
+ };
+diff --git a/init.h b/init.h
+index b0784d8..e20a24e 100644
+--- a/init.h
++++ b/init.h
+@@ -2009,6 +2009,11 @@ struct option_t MuttVars[] = {
+ ** you're not using IMAP folders, you probably prefer setting this to "/"
+ ** alone.
+ */
++ {"sidebar_newmail_only", DT_BOOL, R_BOTH, OPTSIDEBARNEWMAILONLY, 0 },
++ /*
++ ** .pp
++ ** Show only new mail in the sidebar.
++ */
+ { "pgp_use_gpg_agent", DT_BOOL, R_NONE, OPTUSEGPGAGENT, 0},
+ /*
+ ** .pp
+diff --git a/mutt.h b/mutt.h
+index 61a9612..932ef10 100644
+--- a/mutt.h
++++ b/mutt.h
+@@ -525,6 +525,8 @@ enum
+ OPTDONTHANDLEPGPKEYS, /* (pseudo) used to extract PGP keys */
+ OPTUNBUFFEREDINPUT, /* (pseudo) don't use key buffer */
+
++ OPTSIDEBARNEWMAILONLY,
++
+ OPTMAX
+ };
+
+diff --git a/pager.c b/pager.c
+index 469efe4..d372fc0 100644
+--- a/pager.c
++++ b/pager.c
+@@ -2789,6 +2789,8 @@ search_next:
+ case OP_SIDEBAR_SCROLL_DOWN:
+ case OP_SIDEBAR_NEXT:
+ case OP_SIDEBAR_PREV:
++ case OP_SIDEBAR_NEXT_NEW:
++ case OP_SIDEBAR_PREV_NEW:
+ scroll_sidebar(ch, MENU_PAGER);
+ break;
+
+diff --git a/sidebar.c b/sidebar.c
+index 8f58f85..51a25ca 100644
+--- a/sidebar.c
++++ b/sidebar.c
+@@ -269,8 +269,21 @@ int draw_sidebar(int menu) {
+ SETCOLOR(MT_COLOR_NEW);
+ else if ( tmp->msg_flagged > 0 )
+ SETCOLOR(MT_COLOR_FLAGGED);
+- else
+- SETCOLOR(MT_COLOR_NORMAL);
++ else {
++ /* make sure the path is either:
++ 1. Containing new mail.
++ 2. The inbox.
++ 3. The current box.
++ */
++ if ((option (OPTSIDEBARNEWMAILONLY)) &&
++ ( (tmp->msg_unread <= 0) &&
++ ( tmp != Incoming ) &&
++ Context &&
++ ( strcmp( tmp->path, Context->path ) != 0 ) ) )
++ continue;
++ else
++ SETCOLOR(MT_COLOR_NORMAL);
++ }
+
+ move( lines, 0 );
+ if ( Context && !strcmp( tmp->path, Context->path ) ) {
+@@ -336,6 +349,29 @@ int draw_sidebar(int menu) {
+ return 0;
+ }
+
++BUFFY * exist_next_new()
++{
++ BUFFY *tmp = CurBuffy;
++ if(tmp == NULL) return NULL;
++ while (tmp->next != NULL)
++ {
++ tmp = tmp->next;
++ if(tmp->msg_unread) return tmp;
++ }
++ return NULL;
++}
++
++BUFFY * exist_prev_new()
++{
++ BUFFY *tmp = CurBuffy;
++ if(tmp == NULL) return NULL;
++ while (tmp->prev != NULL)
++ {
++ tmp = tmp->prev;
++ if(tmp->msg_unread) return tmp;
++ }
++ return NULL;
++}
+
+ void set_buffystats(CONTEXT* Context)
+ {
+@@ -352,18 +388,33 @@ void set_buffystats(CONTEXT* Context)
+
+ void scroll_sidebar(int op, int menu)
+ {
++ BUFFY *tmp;
+ if(!SidebarWidth) return;
+ if(!CurBuffy) return;
+
+ switch (op) {
+ case OP_SIDEBAR_NEXT:
++ if (!option (OPTSIDEBARNEWMAILONLY)) {
+ if ( CurBuffy->next == NULL ) return;
+ CurBuffy = CurBuffy->next;
+ break;
++ }
++ case OP_SIDEBAR_NEXT_NEW:
++ if ( (tmp = exist_next_new()) == NULL)
++ return;
++ else CurBuffy = tmp;
++ break;
+ case OP_SIDEBAR_PREV:
++ if (!option (OPTSIDEBARNEWMAILONLY)) {
+ if ( CurBuffy->prev == NULL ) return;
+ CurBuffy = CurBuffy->prev;
+ break;
++ }
++ case OP_SIDEBAR_PREV_NEW:
++ if ( (tmp = exist_prev_new()) == NULL)
++ return;
++ else CurBuffy = tmp;
++ break;
+ case OP_SIDEBAR_SCROLL_UP:
+ CurBuffy = TopBuffy;
+ if ( CurBuffy != Incoming ) {
diff --git a/sidebar-utf8.patch b/sidebar-utf8.patch
new file mode 100644
index 000000000000..d67e43c24b6e
--- /dev/null
+++ b/sidebar-utf8.patch
@@ -0,0 +1,132 @@
+From: Antonio Radici <antonio@debian.org>
+Date: Tue, 4 Mar 2014 15:39:14 +0100
+Subject: sidebar-utf8
+
+This patch fixes a problem with utf-8 strings and the sidebar,
+it rewrites entirely make_sidebar_entry so it also fixes some
+segfaults due to misallocations and overflows.
+
+See:
+ http://bugs.debian.org/584581
+ http://bugs.debian.org/603287
+
+Gbp-Pq: Topic mutt-patched
+---
+ sidebar.c | 97 +++++++++++++++++++++++++++++++++++++++++++--------------------
+ 1 file changed, 67 insertions(+), 30 deletions(-)
+
+diff --git a/sidebar.c b/sidebar.c
+index 4356ffc..8f58f85 100644
+--- a/sidebar.c
++++ b/sidebar.c
+@@ -30,6 +30,7 @@
+ #include <libgen.h>
+ #include "keymap.h"
+ #include <stdbool.h>
++#include <wchar.h>
+
+ /*BUFFY *CurBuffy = 0;*/
+ static BUFFY *TopBuffy = 0;
+@@ -82,36 +83,72 @@ void calc_boundaries (int menu)
+
+ char *make_sidebar_entry(char *box, int size, int new, int flagged)
+ {
+- static char *entry = 0;
+- char *c;
+- int i = 0;
+- int delim_len = strlen(SidebarDelim);
+-
+- c = realloc(entry, SidebarWidth - delim_len + 2);
+- if ( c ) entry = c;
+- entry[SidebarWidth - delim_len + 1] = 0;
+- for (; i < SidebarWidth - delim_len + 1; entry[i++] = ' ' );
+- i = strlen(box);
+- strncpy( entry, box, i < (SidebarWidth - delim_len + 1) ? i : (SidebarWidth - delim_len + 1) );
+-
+- if (size == -1)
+- sprintf(entry + SidebarWidth - delim_len - 3, "?");
+- else if ( new ) {
+- if (flagged > 0) {
+- sprintf(
+- entry + SidebarWidth - delim_len - 5 - quick_log10(size) - quick_log10(new) - quick_log10(flagged),
+- "% d(%d)[%d]", size, new, flagged);
+- } else {
+- sprintf(
+- entry + SidebarWidth - delim_len - 3 - quick_log10(size) - quick_log10(new),
+- "% d(%d)", size, new);
+- }
+- } else if (flagged > 0) {
+- sprintf( entry + SidebarWidth - delim_len - 3 - quick_log10(size) - quick_log10(flagged), "% d[%d]", size, flagged);
+- } else {
+- sprintf( entry + SidebarWidth - delim_len - 1 - quick_log10(size), "% d", size);
+- }
+- return entry;
++ char int_store[20]; // up to 64 bits integers
++ int right_width, left_width;
++ int box_len, box_bytes;
++ int int_len;
++ int right_offset = 0;
++ int delim_len = strlen(SidebarDelim);
++ static char *entry;
++
++ right_width = left_width = 0;
++ box_len = box_bytes = 0;
++
++ // allocate an entry big enough to contain SidebarWidth wide chars
++ entry = malloc((SidebarWidth*4)+1); // TODO: error check
++
++ // determine the right space (i.e.: how big are the numbers that we want to print)
++ if ( size > 0 ) {
++ int_len = snprintf(int_store, sizeof(int_store), "%d", size);
++ right_width += int_len;
++ } else {
++ right_width = 1; // to represent 0
++ }
++ if ( new > 0 ) {
++ int_len = snprintf(int_store, sizeof(int_store), "%d", new);
++ right_width += int_len + 2; // 2 is for ()
++ }
++ if ( flagged > 0 ) {
++ int_len = snprintf(int_store, sizeof(int_store), "%d", flagged);
++ right_width += int_len + 2; // 2 is for []
++ }
++
++ // determine how much space we have for *box and its padding (if any)
++ left_width = SidebarWidth - right_width - 1 - delim_len; // 1 is for the space
++ //fprintf(stdout, "left_width: %d right_width: %d\n", left_width, right_width);
++ // right side overflow case
++ if ( left_width <= 0 ) {
++ snprintf(entry, SidebarWidth*4, "%-*.*s ...", SidebarWidth-4-delim_len, SidebarWidth-4-delim_len, box);
++ return entry;
++ }
++ right_width -= delim_len;
++
++ // to support utf-8 chars we need to add enough space padding in case there
++ // are less chars than bytes in *box
++ box_len = mbstowcs(NULL, box, 0);
++ box_bytes = strlen(box);
++ // debug
++ //fprintf(stdout, "box_len: %d box_bytes: %d (diff: %d)\n", box_len, box_bytes, (box_bytes-box_len));
++ // if there is less string than the space we allow, then we will add the
++ // spaces
++ if ( box_len != -1 && box_len < left_width ) {
++ left_width += (box_bytes - box_len);
++ }
++ // otherwise sprintf will truncate the string for us (therefore, no else case)
++
++ // print the sidebar entry (without new and flagged messages, at the moment)
++ //fprintf(stdout, "left_width: %d right_width: %d\n", left_width, right_width);
++ right_offset = snprintf(entry, SidebarWidth*4, "%-*.*s %d", left_width, left_width, box, size);
++
++ // then pad new and flagged messages if any
++ if ( new > 0 ) {
++ right_offset += snprintf(entry+right_offset, SidebarWidth*4-right_offset, "(%d)", new);
++ }
++ if ( flagged > 0 ) {
++ right_offset += snprintf(entry+right_offset, SidebarWidth*4-right_offset, "[%d]", flagged);
++ }
++
++ return entry;
+ }
+
+ void set_curbuffy(char buf[LONG_STRING])
diff --git a/sidebar.patch b/sidebar.patch
new file mode 100644
index 000000000000..350ecc727754
--- /dev/null
+++ b/sidebar.patch
@@ -0,0 +1,1476 @@
+From: Antonio Radici <antonio@debian.org>
+Date: Tue, 4 Mar 2014 15:21:10 +0100
+Subject: sidebar
+
+When enabled, mutt will show a list of mailboxes with (new) message counts in a
+separate column on the left side of the screen.
+
+As this feature is still considered to be unstable, this patch is only applied
+in the "mutt-patched" package.
+
+* Configuration variables:
+
+ sidebar_delim (string, default "|")
+
+ This specifies the delimiter between the sidebar (if visible) and
+ other screens.
+
+ sidebar_visible (boolean, default no)
+
+ This specifies whether or not to show sidebar (left-side list of folders).
+
+ sidebar_width (integer, default 0)
+-
+ The width of the sidebar.
+
+* Patch source:
+ - http://www.lunar-linux.org/index.php?page=mutt-sidebar
+ - http://lunar-linux.org/~tchan/mutt/patch-1.5.19.sidebar.20090522.txt
+
+* Changes made:
+ - 2008-08-02 myon: Refreshed patch using quilt push -f to remove hunks we do
+ not need (Makefile.in).
+
+ - 2014-03-04 evgeni: refresh sidebar patch with the version from OpenBSD
+ Source:
+ ftp://ftp.openbsd.org/pub/OpenBSD/distfiles/mutt/sidebar-1.5.22.diff1.gz
+
+Signed-off-by: Matteo F. Vescovi <mfvescovi@gmail.com>
+Signed-off-by: Evgeni Golov <evgeni@debian.org>
+
+Gbp-Pq: Topic mutt-patched
+---
+ Makefile.am | 1 +
+ OPS | 5 +
+ buffy.c | 123 +++++++++++++++++++++
+ buffy.h | 4 +
+ color.c | 2 +
+ compose.c | 26 ++---
+ curs_main.c | 30 +++++-
+ flags.c | 3 +
+ functions.h | 10 ++
+ globals.h | 4 +
+ imap/command.c | 7 ++
+ imap/imap.c | 2 +-
+ init.h | 21 ++++
+ mailbox.h | 1 +
+ mbox.c | 2 +
+ menu.c | 20 ++--
+ mh.c | 22 ++++
+ mutt.h | 4 +
+ mutt_curses.h | 3 +
+ muttlib.c | 48 +++++++++
+ mx.c | 15 +++
+ mx.h | 1 +
+ pager.c | 30 ++++--
+ sidebar.c | 333 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ sidebar.h | 36 +++++++
+ 25 files changed, 719 insertions(+), 34 deletions(-)
+ create mode 100644 sidebar.c
+ create mode 100644 sidebar.h
+
+diff --git a/Makefile.am b/Makefile.am
+index 09dd64b..2fc6b1d 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -32,6 +32,7 @@ mutt_SOURCES = \
+ rfc822.c rfc1524.c rfc2047.c rfc2231.c rfc3676.c \
+ score.c send.c sendlib.c signal.c sort.c \
+ status.c system.c thread.c charset.c history.c lib.c \
++ sidebar.c \
+ muttlib.c editmsg.c mbyte.c \
+ url.c ascii.c crypt-mod.c crypt-mod.h safe_asprintf.c
+
+diff --git a/OPS b/OPS
+index 02cea8e..b036db9 100644
+--- a/OPS
++++ b/OPS
+@@ -180,3 +180,8 @@ OP_WHAT_KEY "display the keycode for a key press"
+ OP_MAIN_SHOW_LIMIT "show currently active limit pattern"
+ OP_MAIN_COLLAPSE_THREAD "collapse/uncollapse current thread"
+ OP_MAIN_COLLAPSE_ALL "collapse/uncollapse all threads"
++OP_SIDEBAR_SCROLL_UP "scroll the mailbox pane up 1 page"
++OP_SIDEBAR_SCROLL_DOWN "scroll the mailbox pane down 1 page"
++OP_SIDEBAR_NEXT "go down to next mailbox"
++OP_SIDEBAR_PREV "go to previous mailbox"
++OP_SIDEBAR_OPEN "open hilighted mailbox"
+diff --git a/buffy.c b/buffy.c
+index e5a0f79..225104d 100644
+--- a/buffy.c
++++ b/buffy.c
+@@ -161,6 +161,49 @@ void mutt_buffy_cleanup (const char *buf, struct stat *st)
+ }
+ }
+
++static int buffy_compare_name(const void *a, const void *b) {
++ const BUFFY *b1 = * (BUFFY * const *) a;
++ const BUFFY *b2 = * (BUFFY * const *) b;
++
++ return mutt_strcoll(b1->path, b2->path);
++}
++
++static BUFFY *buffy_sort(BUFFY *b)
++{
++ BUFFY *tmp = b;
++ int buffycount = 0;
++ BUFFY **ary;
++ int i;
++
++ if (!option(OPTSIDEBARSORT))
++ return b;
++
++ for (; tmp != NULL; tmp = tmp->next)
++ buffycount++;
++
++ ary = (BUFFY **) safe_calloc(buffycount, sizeof (*ary));
++
++ tmp = b;
++ for (i = 0; tmp != NULL; tmp = tmp->next, i++) {
++ ary[i] = tmp;
++ }
++
++ qsort(ary, buffycount, sizeof(*ary), buffy_compare_name);
++
++ for (i = 0; i < buffycount - 1; i++) {
++ ary[i]->next = ary[i+1];
++ }
++ ary[buffycount - 1]->next = NULL;
++ for (i = 1; i < buffycount; i++) {
++ ary[i]->prev = ary[i-1];
++ }
++ ary[0]->prev = NULL;
++
++ tmp = ary[0];
++ free(ary);
++ return tmp;
++}
++
+ BUFFY *mutt_find_mailbox (const char *path)
+ {
+ BUFFY *tmp = NULL;
+@@ -282,6 +325,7 @@ int mutt_parse_mailboxes (BUFFER *path, BUFFER *s, unsigned long data, BUFFER *e
+ else
+ (*tmp)->size = 0;
+ }
++ Incoming = buffy_sort(Incoming);
+ return 0;
+ }
+
+@@ -340,6 +384,68 @@ static int buffy_maildir_hasnew (BUFFY* mailbox)
+ return rc;
+ }
+
++/* update message counts for the sidebar */
++void buffy_maildir_update (BUFFY* mailbox)
++{
++ char path[_POSIX_PATH_MAX];
++ DIR *dirp;
++ struct dirent *de;
++ char *p;
++
++ mailbox->msgcount = 0;
++ mailbox->msg_unread = 0;
++ mailbox->msg_flagged = 0;
++
++ snprintf (path, sizeof (path), "%s/new", mailbox->path);
++
++ if ((dirp = opendir (path)) == NULL)
++ {
++ mailbox->magic = 0;
++ return;
++ }
++
++ while ((de = readdir (dirp)) != NULL)
++ {
++ if (*de->d_name == '.')
++ continue;
++
++ if (!(p = strstr (de->d_name, ":2,")) || !strchr (p + 3, 'T')) {
++ mailbox->new = 1;
++ mailbox->msgcount++;
++ mailbox->msg_unread++;
++ }
++ }
++
++ closedir (dirp);
++ snprintf (path, sizeof (path), "%s/cur", mailbox->path);
++
++ if ((dirp = opendir (path)) == NULL)
++ {
++ mailbox->magic = 0;
++ return;
++ }
++
++ while ((de = readdir (dirp)) != NULL)
++ {
++ if (*de->d_name == '.')
++ continue;
++
++ if (!(p = strstr (de->d_name, ":2,")) || !strchr (p + 3, 'T')) {
++ mailbox->msgcount++;
++ if ((p = strstr (de->d_name, ":2,"))) {
++ if (!strchr (p + 3, 'T')) {
++ if (!strchr (p + 3, 'S'))
++ mailbox->msg_unread++;
++ if (strchr(p + 3, 'F'))
++ mailbox->msg_flagged++;
++ }
++ }
++ }
++ }
++
++ closedir (dirp);
++}
++
+ /* returns 1 if mailbox has new mail */
+ static int buffy_mbox_hasnew (BUFFY* mailbox, struct stat *sb)
+ {
+@@ -371,6 +477,20 @@ static int buffy_mbox_hasnew (BUFFY* mailbox, struct stat *sb)
+ return rc;
+ }
+
++/* update message counts for the sidebar */
++void buffy_mbox_update (BUFFY* mailbox)
++{
++ CONTEXT *ctx = NULL;
++
++ ctx = mx_open_mailbox(mailbox->path, M_READONLY | M_QUIET | M_NOSORT | M_PEEK, NULL);
++ if(ctx)
++ {
++ mailbox->msgcount = ctx->msgcount;
++ mailbox->msg_unread = ctx->unread;
++ mx_close_mailbox(ctx, 0);
++ }
++}
++
+ int mutt_buffy_check (int force)
+ {
+ BUFFY *tmp;
+@@ -444,16 +564,19 @@ int mutt_buffy_check (int force)
+ {
+ case M_MBOX:
+ case M_MMDF:
++ buffy_mbox_update (tmp);
+ if (buffy_mbox_hasnew (tmp, &sb) > 0)
+ BuffyCount++;
+ break;
+
+ case M_MAILDIR:
++ buffy_maildir_update (tmp);
+ if (buffy_maildir_hasnew (tmp) > 0)
+ BuffyCount++;
+ break;
+
+ case M_MH:
++ mh_buffy_update (tmp->path, &tmp->msgcount, &tmp->msg_unread, &tmp->msg_flagged);
+ mh_buffy(tmp);
+ if (tmp->new)
+ BuffyCount++;
+diff --git a/buffy.h b/buffy.h
+index f9fc55a..672d178 100644
+--- a/buffy.h
++++ b/buffy.h
+@@ -25,7 +25,11 @@ typedef struct buffy_t
+ char path[_POSIX_PATH_MAX];
+ off_t size;
+ struct buffy_t *next;
++ struct buffy_t *prev;
+ short new; /* mailbox has new mail */
++ int msgcount; /* total number of messages */
++ int msg_unread; /* number of unread messages */
++ int msg_flagged; /* number of flagged messages */
+ short notified; /* user has been notified */
+ short magic; /* mailbox type */
+ short newly_created; /* mbox or mmdf just popped into existence */
+diff --git a/color.c b/color.c
+index ef97ca9..2112132 100644
+--- a/color.c
++++ b/color.c
+@@ -93,6 +93,8 @@ static const struct mapping_t Fields[] =
+ { "bold", MT_COLOR_BOLD },
+ { "underline", MT_COLOR_UNDERLINE },
+ { "index", MT_COLOR_INDEX },
++ { "sidebar_new", MT_COLOR_NEW },
++ { "sidebar_flagged", MT_COLOR_FLAGGED },
+ { NULL, 0 }
+ };
+
+diff --git a/compose.c b/compose.c
+index 9cfa2d4..5a14d70 100644
+--- a/compose.c
++++ b/compose.c
+@@ -72,7 +72,7 @@ enum
+
+ #define HDR_XOFFSET 10
+ #define TITLE_FMT "%10s" /* Used for Prompts, which are ASCII */
+-#define W (COLS - HDR_XOFFSET)
++#define W (COLS - HDR_XOFFSET - SidebarWidth)
+
+ static const char * const Prompts[] =
+ {
+@@ -110,7 +110,7 @@ static void snd_entry (char *b, size_t blen, MUTTMENU *menu, int num)
+
+ static void redraw_crypt_lines (HEADER *msg)
+ {
+- mvaddstr (HDR_CRYPT, 0, "Security: ");
++ mvaddstr (HDR_CRYPT, SidebarWidth, "Security: ");
+
+ if ((WithCrypto & (APPLICATION_PGP | APPLICATION_SMIME)) == 0)
+ {
+@@ -142,7 +142,7 @@ static void redraw_crypt_lines (HEADER *msg)
+ }
+
+ clrtoeol ();
+- move (HDR_CRYPTINFO, 0);
++ move (HDR_CRYPTINFO, SidebarWidth);
+ clrtoeol ();
+
+ if ((WithCrypto & APPLICATION_PGP)
+@@ -159,7 +159,7 @@ static void redraw_crypt_lines (HEADER *msg)
+ && (msg->security & ENCRYPT)
+ && SmimeCryptAlg
+ && *SmimeCryptAlg) {
+- mvprintw (HDR_CRYPTINFO, 40, "%s%s", _("Encrypt with: "),
++ mvprintw (HDR_CRYPTINFO, SidebarWidth + 40, "%s%s", _("Encrypt with: "),
+ NONULL(SmimeCryptAlg));
+ }
+ }
+@@ -172,7 +172,7 @@ static void redraw_mix_line (LIST *chain)
+ int c;
+ char *t;
+
+- mvaddstr (HDR_MIX, 0, " Mix: ");
++ mvaddstr (HDR_MIX, SidebarWidth, " Mix: ");
+
+ if (!chain)
+ {
+@@ -187,7 +187,7 @@ static void redraw_mix_line (LIST *chain)
+ if (t && t[0] == '0' && t[1] == '\0')
+ t = "<random>";
+
+- if (c + mutt_strlen (t) + 2 >= COLS)
++ if (c + mutt_strlen (t) + 2 >= COLS - SidebarWidth)
+ break;
+
+ addstr (NONULL(t));
+@@ -239,7 +239,7 @@ static void draw_envelope_addr (int line, ADDRESS *addr)
+
+ buf[0] = 0;
+ rfc822_write_address (buf, sizeof (buf), addr, 1);
+- mvprintw (line, 0, TITLE_FMT, Prompts[line - 1]);
++ mvprintw (line, SidebarWidth, TITLE_FMT, Prompts[line - 1]);
+ mutt_paddstr (W, buf);
+ }
+
+@@ -249,10 +249,10 @@ static void draw_envelope (HEADER *msg, char *fcc)
+ draw_envelope_addr (HDR_TO, msg->env->to);
+ draw_envelope_addr (HDR_CC, msg->env->cc);
+ draw_envelope_addr (HDR_BCC, msg->env->bcc);
+- mvprintw (HDR_SUBJECT, 0, TITLE_FMT, Prompts[HDR_SUBJECT - 1]);
++ mvprintw (HDR_SUBJECT, SidebarWidth, TITLE_FMT, Prompts[HDR_SUBJECT - 1]);
+ mutt_paddstr (W, NONULL (msg->env->subject));
+ draw_envelope_addr (HDR_REPLYTO, msg->env->reply_to);
+- mvprintw (HDR_FCC, 0, TITLE_FMT, Prompts[HDR_FCC - 1]);
++ mvprintw (HDR_FCC, SidebarWidth, TITLE_FMT, Prompts[HDR_FCC - 1]);
+ mutt_paddstr (W, fcc);
+
+ if (WithCrypto)
+@@ -263,7 +263,7 @@ static void draw_envelope (HEADER *msg, char *fcc)
+ #endif
+
+ SETCOLOR (MT_COLOR_STATUS);
+- mvaddstr (HDR_ATTACH - 1, 0, _("-- Attachments"));
++ mvaddstr (HDR_ATTACH - 1, SidebarWidth, _("-- Attachments"));
+ clrtoeol ();
+
+ NORMAL_COLOR;
+@@ -299,7 +299,7 @@ static int edit_address_list (int line, ADDRESS **addr)
+ /* redraw the expanded list so the user can see the result */
+ buf[0] = 0;
+ rfc822_write_address (buf, sizeof (buf), *addr, 1);
+- move (line, HDR_XOFFSET);
++ move (line, HDR_XOFFSET+SidebarWidth);
+ mutt_paddstr (W, buf);
+
+ return 0;
+@@ -544,7 +544,7 @@ int mutt_compose_menu (HEADER *msg, /* structure for new message */
+ if (mutt_get_field ("Subject: ", buf, sizeof (buf), 0) == 0)
+ {
+ mutt_str_replace (&msg->env->subject, buf);
+- move (HDR_SUBJECT, HDR_XOFFSET);
++ move (HDR_SUBJECT, HDR_XOFFSET + SidebarWidth);
+ if (msg->env->subject)
+ mutt_paddstr (W, msg->env->subject);
+ else
+@@ -562,7 +562,7 @@ int mutt_compose_menu (HEADER *msg, /* structure for new message */
+ {
+ strfcpy (fcc, buf, fcclen);
+ mutt_pretty_mailbox (fcc, fcclen);
+- move (HDR_FCC, HDR_XOFFSET);
++ move (HDR_FCC, HDR_XOFFSET + SidebarWidth);
+ mutt_paddstr (W, fcc);
+ fccSet = 1;
+ }
+diff --git a/curs_main.c b/curs_main.c
+index e7f11bd..7b81798 100644
+--- a/curs_main.c
++++ b/curs_main.c
+@@ -26,7 +26,9 @@
+ #include "mailbox.h"
+ #include "mapping.h"
+ #include "sort.h"
++#include "buffy.h"
+ #include "mx.h"
++#include "sidebar.h"
+
+ #ifdef USE_POP
+ #include "pop.h"
+@@ -532,8 +534,12 @@ int mutt_index_menu (void)
+ menu->redraw |= REDRAW_STATUS;
+ if (do_buffy_notify)
+ {
+- if (mutt_buffy_notify () && option (OPTBEEPNEW))
+- beep ();
++ if (mutt_buffy_notify ())
++ {
++ menu->redraw |= REDRAW_FULL;
++ if (option (OPTBEEPNEW))
++ beep ();
++ }
+ }
+ else
+ do_buffy_notify = 1;
+@@ -545,6 +551,7 @@ int mutt_index_menu (void)
+ if (menu->redraw & REDRAW_FULL)
+ {
+ menu_redraw_full (menu);
++ draw_sidebar(menu->menu);
+ mutt_show_error ();
+ }
+
+@@ -567,9 +574,12 @@ int mutt_index_menu (void)
+
+ if (menu->redraw & REDRAW_STATUS)
+ {
++ DrawFullLine = 1;
+ menu_status_line (buf, sizeof (buf), menu, NONULL (Status));
++ DrawFullLine = 0;
+ move (option (OPTSTATUSONTOP) ? 0 : LINES-2, 0);
+ SETCOLOR (MT_COLOR_STATUS);
++ set_buffystats(Context);
+ mutt_paddstr (COLS, buf);
+ NORMAL_COLOR;
+ menu->redraw &= ~REDRAW_STATUS;
+@@ -589,7 +599,7 @@ int mutt_index_menu (void)
+ menu->oldcurrent = -1;
+
+ if (option (OPTARROWCURSOR))
+- move (menu->current - menu->top + menu->offset, 2);
++ move (menu->current - menu->top + menu->offset, SidebarWidth + 2);
+ else if (option (OPTBRAILLEFRIENDLY))
+ move (menu->current - menu->top + menu->offset, 0);
+ else
+@@ -1090,6 +1100,7 @@ int mutt_index_menu (void)
+ menu->redraw = REDRAW_FULL;
+ break;
+
++ case OP_SIDEBAR_OPEN:
+ case OP_MAIN_CHANGE_FOLDER:
+ case OP_MAIN_NEXT_UNREAD_MAILBOX:
+
+@@ -1121,7 +1132,11 @@ int mutt_index_menu (void)
+ {
+ mutt_buffy (buf, sizeof (buf));
+
+- if (mutt_enter_fname (cp, buf, sizeof (buf), &menu->redraw, 1) == -1)
++ if ( op == OP_SIDEBAR_OPEN ) {
++ if(!CurBuffy)
++ break;
++ strncpy( buf, CurBuffy->path, sizeof(buf) );
++ } else if (mutt_enter_fname (cp, buf, sizeof (buf), &menu->redraw, 1) == -1)
+ {
+ if (menu->menu == MENU_PAGER)
+ {
+@@ -1139,6 +1154,7 @@ int mutt_index_menu (void)
+ }
+
+ mutt_expand_path (buf, sizeof (buf));
++ set_curbuffy(buf);
+ if (mx_get_magic (buf) <= 0)
+ {
+ mutt_error (_("%s is not a mailbox."), buf);
+@@ -2242,6 +2258,12 @@ int mutt_index_menu (void)
+ mutt_what_key();
+ break;
+
++ case OP_SIDEBAR_SCROLL_UP:
++ case OP_SIDEBAR_SCROLL_DOWN:
++ case OP_SIDEBAR_NEXT:
++ case OP_SIDEBAR_PREV:
++ scroll_sidebar(op, menu->menu);
++ break;
+ default:
+ if (menu->menu == MENU_MAIN)
+ km_error_key (MENU_MAIN);
+diff --git a/flags.c b/flags.c
+index 133fa35..48fb287 100644
+--- a/flags.c
++++ b/flags.c
+@@ -22,8 +22,10 @@
+
+ #include "mutt.h"
+ #include "mutt_curses.h"
++#include "mutt_menu.h"
+ #include "sort.h"
+ #include "mx.h"
++#include "sidebar.h"
+
+ void _mutt_set_flag (CONTEXT *ctx, HEADER *h, int flag, int bf, int upd_ctx)
+ {
+@@ -290,6 +292,7 @@ void _mutt_set_flag (CONTEXT *ctx, HEADER *h, int flag, int bf, int upd_ctx)
+ */
+ if (h->searched && (changed != h->changed || deleted != ctx->deleted || tagged != ctx->tagged || flagged != ctx->flagged))
+ h->searched = 0;
++ draw_sidebar(0);
+ }
+
+ void mutt_tag_set_flag (int flag, int bf)
+diff --git a/functions.h b/functions.h
+index 26171a0..ef8937a 100644
+--- a/functions.h
++++ b/functions.h
+@@ -170,6 +170,11 @@ const struct binding_t OpMain[] = { /* map: index */
+ { "decrypt-save", OP_DECRYPT_SAVE, NULL },
+
+
++ { "sidebar-scroll-up", OP_SIDEBAR_SCROLL_UP, NULL },
++ { "sidebar-scroll-down", OP_SIDEBAR_SCROLL_DOWN, NULL },
++ { "sidebar-next", OP_SIDEBAR_NEXT, NULL },
++ { "sidebar-prev", OP_SIDEBAR_PREV, NULL },
++ { "sidebar-open", OP_SIDEBAR_OPEN, NULL },
+ { NULL, 0, NULL }
+ };
+
+@@ -274,6 +279,11 @@ const struct binding_t OpPager[] = { /* map: pager */
+
+ { "what-key", OP_WHAT_KEY, NULL },
+
++ { "sidebar-scroll-up", OP_SIDEBAR_SCROLL_UP, NULL },
++ { "sidebar-scroll-down", OP_SIDEBAR_SCROLL_DOWN, NULL },
++ { "sidebar-next", OP_SIDEBAR_NEXT, NULL },
++ { "sidebar-prev", OP_SIDEBAR_PREV, NULL },
++ { "sidebar-open", OP_SIDEBAR_OPEN, NULL },
+ { NULL, 0, NULL }
+ };
+
+diff --git a/globals.h b/globals.h
+index 5b6e56a..3f83328 100644
+--- a/globals.h
++++ b/globals.h
+@@ -117,6 +117,7 @@ WHERE short SearchContext;
+ WHERE char *SendCharset;
+ WHERE char *Sendmail;
+ WHERE char *Shell;
++WHERE char *SidebarDelim;
+ WHERE char *Signature;
+ WHERE char *SimpleSearch;
+ #if USE_SMTP
+@@ -211,6 +212,9 @@ WHERE short ScoreThresholdDelete;
+ WHERE short ScoreThresholdRead;
+ WHERE short ScoreThresholdFlag;
+
++WHERE struct buffy_t *CurBuffy INITVAL(0);
++WHERE short DrawFullLine INITVAL(0);
++WHERE short SidebarWidth;
+ #ifdef USE_IMAP
+ WHERE short ImapKeepalive;
+ WHERE short ImapPipelineDepth;
+diff --git a/imap/command.c b/imap/command.c
+index 32f8417..d68e3ab 100644
+--- a/imap/command.c
++++ b/imap/command.c
+@@ -1012,6 +1012,13 @@ static void cmd_parse_status (IMAP_DATA* idata, char* s)
+ opened */
+ status->uidnext = oldun;
+
++ /* Added to make the sidebar show the correct numbers */
++ if (status->messages)
++ {
++ inc->msgcount = status->messages;
++ inc->msg_unread = status->unseen;
++ }
++
+ FREE (&value);
+ return;
+ }
+diff --git a/imap/imap.c b/imap/imap.c
+index 393d4ec..4b1ec86 100644
+--- a/imap/imap.c
++++ b/imap/imap.c
+@@ -1520,7 +1520,7 @@ int imap_buffy_check (int force)
+
+ imap_munge_mbox_name (munged, sizeof (munged), name);
+ snprintf (command, sizeof (command),
+- "STATUS %s (UIDNEXT UIDVALIDITY UNSEEN RECENT)", munged);
++ "STATUS %s (UIDNEXT UIDVALIDITY UNSEEN RECENT MESSAGES)", munged);
+
+ if (imap_exec (idata, command, IMAP_CMD_QUEUE) < 0)
+ {
+diff --git a/init.h b/init.h
+index 08e004c..502f570 100644
+--- a/init.h
++++ b/init.h
+@@ -1980,6 +1980,27 @@ struct option_t MuttVars[] = {
+ ** not used.
+ ** (PGP only)
+ */
++ {"sidebar_delim", DT_STR, R_BOTH, UL &SidebarDelim, "|"},
++ /*
++ ** .pp
++ ** This specifies the delimiter between the sidebar (if visible) and
++ ** other screens.
++ */
++ { "sidebar_visible", DT_BOOL, R_BOTH, OPTSIDEBAR, 0 },
++ /*
++ ** .pp
++ ** This specifies whether or not to show sidebar (left-side list of folders).
++ */
++ { "sidebar_sort", DT_BOOL, R_BOTH, OPTSIDEBARSORT, 0 },
++ /*
++ ** .pp
++ ** This specifies whether or not to sort the sidebar alphabetically.
++ */
++ { "sidebar_width", DT_NUM, R_BOTH, UL &SidebarWidth, 0 },
++ /*
++ ** .pp
++ ** The width of the sidebar.
++ */
+ { "pgp_use_gpg_agent", DT_BOOL, R_NONE, OPTUSEGPGAGENT, 0},
+ /*
+ ** .pp
+diff --git a/mailbox.h b/mailbox.h
+index 91e5dc7..b652628 100644
+--- a/mailbox.h
++++ b/mailbox.h
+@@ -27,6 +27,7 @@
+ #define M_NEWFOLDER (1<<4) /* create a new folder - same as M_APPEND, but uses
+ * safe_fopen() for mbox-style folders.
+ */
++#define M_PEEK (1<<5) /* revert atime back after taking a look (if applicable) */
+
+ /* mx_open_new_message() */
+ #define M_ADD_FROM 1 /* add a From_ line */
+diff --git a/mbox.c b/mbox.c
+index 6d3b6bd..fa82eb3 100644
+--- a/mbox.c
++++ b/mbox.c
+@@ -104,6 +104,7 @@ int mmdf_parse_mailbox (CONTEXT *ctx)
+ mutt_perror (ctx->path);
+ return (-1);
+ }
++ ctx->atime = sb.st_atime;
+ ctx->mtime = sb.st_mtime;
+ ctx->size = sb.st_size;
+
+@@ -255,6 +256,7 @@ int mbox_parse_mailbox (CONTEXT *ctx)
+
+ ctx->size = sb.st_size;
+ ctx->mtime = sb.st_mtime;
++ ctx->atime = sb.st_atime;
+
+ #ifdef NFS_ATTRIBUTE_HACK
+ if (sb.st_mtime > sb.st_atime)
+diff --git a/menu.c b/menu.c
+index 27b5f8e..bc3a02f 100644
+--- a/menu.c
++++ b/menu.c
+@@ -24,6 +24,7 @@
+ #include "mutt_curses.h"
+ #include "mutt_menu.h"
+ #include "mbyte.h"
++#include "sidebar.h"
+
+ extern size_t UngetCount;
+
+@@ -186,7 +187,7 @@ static void menu_pad_string (char *s, size_t n)
+ {
+ char *scratch = safe_strdup (s);
+ int shift = option (OPTARROWCURSOR) ? 3 : 0;
+- int cols = COLS - shift;
++ int cols = COLS - shift - SidebarWidth;
+
+ mutt_format_string (s, n, cols, cols, FMT_LEFT, ' ', scratch, mutt_strlen (scratch), 1);
+ s[n - 1] = 0;
+@@ -239,6 +240,7 @@ void menu_redraw_index (MUTTMENU *menu)
+ int do_color;
+ int attr;
+
++ draw_sidebar(1);
+ for (i = menu->top; i < menu->top + menu->pagelen; i++)
+ {
+ if (i < menu->max)
+@@ -249,7 +251,7 @@ void menu_redraw_index (MUTTMENU *menu)
+ menu_pad_string (buf, sizeof (buf));
+
+ ATTRSET(attr);
+- move(i - menu->top + menu->offset, 0);
++ move(i - menu->top + menu->offset, SidebarWidth);
+ do_color = 1;
+
+ if (i == menu->current)
+@@ -272,7 +274,7 @@ void menu_redraw_index (MUTTMENU *menu)
+ else
+ {
+ NORMAL_COLOR;
+- CLEARLINE(i - menu->top + menu->offset);
++ CLEARLINE_WIN(i - menu->top + menu->offset);
+ }
+ }
+ NORMAL_COLOR;
+@@ -289,7 +291,7 @@ void menu_redraw_motion (MUTTMENU *menu)
+ return;
+ }
+
+- move (menu->oldcurrent + menu->offset - menu->top, 0);
++ move (menu->oldcurrent + menu->offset - menu->top, SidebarWidth);
+ ATTRSET(menu->color (menu->oldcurrent));
+
+ if (option (OPTARROWCURSOR))
+@@ -301,13 +303,13 @@ void menu_redraw_motion (MUTTMENU *menu)
+ {
+ menu_make_entry (buf, sizeof (buf), menu, menu->oldcurrent);
+ menu_pad_string (buf, sizeof (buf));
+- move (menu->oldcurrent + menu->offset - menu->top, 3);
++ move (menu->oldcurrent + menu->offset - menu->top, SidebarWidth + 3);
+ print_enriched_string (menu->color(menu->oldcurrent), (unsigned char *) buf, 1);
+ }
+
+ /* now draw it in the new location */
+ SETCOLOR(MT_COLOR_INDICATOR);
+- mvaddstr(menu->current + menu->offset - menu->top, 0, "->");
++ mvaddstr(menu->current + menu->offset - menu->top, SidebarWidth, "->");
+ }
+ else
+ {
+@@ -320,7 +322,7 @@ void menu_redraw_motion (MUTTMENU *menu)
+ menu_make_entry (buf, sizeof (buf), menu, menu->current);
+ menu_pad_string (buf, sizeof (buf));
+ SETCOLOR(MT_COLOR_INDICATOR);
+- move(menu->current - menu->top + menu->offset, 0);
++ move(menu->current - menu->top + menu->offset, SidebarWidth);
+ print_enriched_string (menu->color(menu->current), (unsigned char *) buf, 0);
+ }
+ menu->redraw &= REDRAW_STATUS;
+@@ -332,7 +334,7 @@ void menu_redraw_current (MUTTMENU *menu)
+ char buf[LONG_STRING];
+ int attr = menu->color (menu->current);
+
+- move (menu->current + menu->offset - menu->top, 0);
++ move (menu->current + menu->offset - menu->top, SidebarWidth);
+ menu_make_entry (buf, sizeof (buf), menu, menu->current);
+ menu_pad_string (buf, sizeof (buf));
+
+@@ -881,7 +883,7 @@ int mutt_menuLoop (MUTTMENU *menu)
+
+
+ if (option (OPTARROWCURSOR))
+- move (menu->current - menu->top + menu->offset, 2);
++ move (menu->current - menu->top + menu->offset, SidebarWidth + 2);
+ else if (option (OPTBRAILLEFRIENDLY))
+ move (menu->current - menu->top + menu->offset, 0);
+ else
+diff --git a/mh.c b/mh.c
+index 21e6491..48a16fb 100644
+--- a/mh.c
++++ b/mh.c
+@@ -295,6 +295,28 @@ void mh_buffy(BUFFY *b)
+ mhs_free_sequences (&mhs);
+ }
+
++void mh_buffy_update (const char *path, int *msgcount, int *msg_unread, int *msg_flagged)
++{
++ int i;
++ struct mh_sequences mhs;
++ memset (&mhs, 0, sizeof (mhs));
++
++ if (mh_read_sequences (&mhs, path) < 0)
++ return;
++
++ msgcount = 0;
++ msg_unread = 0;
++ msg_flagged = 0;
++ for (i = 0; i <= mhs.max; i++)
++ msgcount++;
++ if (mhs_check (&mhs, i) & MH_SEQ_UNSEEN) {
++ msg_unread++;
++ }
++ if (mhs_check (&mhs, i) & MH_SEQ_FLAGGED)
++ msg_flagged++;
++ mhs_free_sequences (&mhs);
++}
++
+ static int mh_mkstemp (CONTEXT * dest, FILE ** fp, char **tgt)
+ {
+ int fd;
+diff --git a/mutt.h b/mutt.h
+index 4db92a6..61a9612 100644
+--- a/mutt.h
++++ b/mutt.h
+@@ -433,6 +433,8 @@ enum
+ OPTSAVEEMPTY,
+ OPTSAVENAME,
+ OPTSCORE,
++ OPTSIDEBAR,
++ OPTSIDEBARSORT,
+ OPTSIGDASHES,
+ OPTSIGONTOP,
+ OPTSORTRE,
+@@ -876,6 +878,7 @@ typedef struct _context
+ {
+ char *path;
+ FILE *fp;
++ time_t atime;
+ time_t mtime;
+ off_t size;
+ off_t vsize;
+@@ -916,6 +919,7 @@ typedef struct _context
+ unsigned int quiet : 1; /* inhibit status messages? */
+ unsigned int collapsed : 1; /* are all threads collapsed? */
+ unsigned int closing : 1; /* mailbox is being closed */
++ unsigned int peekonly : 1; /* just taking a glance, revert atime */
+
+ /* driver hooks */
+ void *data; /* driver specific data */
+diff --git a/mutt_curses.h b/mutt_curses.h
+index f8d6f88..aee797e 100644
+--- a/mutt_curses.h
++++ b/mutt_curses.h
+@@ -64,6 +64,7 @@
+ #undef lines
+ #endif /* lines */
+
++#define CLEARLINE_WIN(x) move(x,SidebarWidth), clrtoeol()
+ #define CLEARLINE(x) move(x,0), clrtoeol()
+ #define CENTERLINE(x,y) move(y, (COLS-strlen(x))/2), addstr(x)
+ #define BEEP() do { if (option (OPTBEEP)) beep(); } while (0)
+@@ -120,6 +121,8 @@ enum
+ MT_COLOR_BOLD,
+ MT_COLOR_UNDERLINE,
+ MT_COLOR_INDEX,
++ MT_COLOR_NEW,
++ MT_COLOR_FLAGGED,
+ MT_COLOR_MAX
+ };
+
+diff --git a/muttlib.c b/muttlib.c
+index c0d2026..9086f07 100644
+--- a/muttlib.c
++++ b/muttlib.c
+@@ -1284,6 +1284,8 @@ void mutt_FormatString (char *dest, /* output buffer */
+ pl = pw = 1;
+
+ /* see if there's room to add content, else ignore */
++ if ( DrawFullLine )
++ {
+ if ((col < COLS && wlen < destlen) || soft)
+ {
+ int pad;
+@@ -1327,6 +1329,52 @@ void mutt_FormatString (char *dest, /* output buffer */
+ col += wid;
+ src += pl;
+ }
++ }
++ else
++ {
++ if ((col < COLS-SidebarWidth && wlen < destlen) || soft)
++ {
++ int pad;
++
++ /* get contents after padding */
++ mutt_FormatString (buf, sizeof (buf), 0, src + pl, callback, data, flags);
++ len = mutt_strlen (buf);
++ wid = mutt_strwidth (buf);
++
++ /* try to consume as many columns as we can, if we don't have
++ * memory for that, use as much memory as possible */
++ pad = (COLS - SidebarWidth - col - wid) / pw;
++ if (pad > 0 && wlen + (pad * pl) + len > destlen)
++ pad = ((signed)(destlen - wlen - len)) / pl;
++ if (pad > 0)
++ {
++ while (pad--)
++ {
++ memcpy (wptr, src, pl);
++ wptr += pl;
++ wlen += pl;
++ col += pw;
++ }
++ }
++ else if (soft && pad < 0)
++ {
++ /* \0-terminate dest for length computation in mutt_wstr_trunc() */
++ *wptr = 0;
++ /* make sure right part is at most as wide as display */
++ len = mutt_wstr_trunc (buf, destlen, COLS, &wid);
++ /* truncate left so that right part fits completely in */
++ wlen = mutt_wstr_trunc (dest, destlen - len, col + pad, &col);
++ wptr = dest + wlen;
++ }
++ if (len + wlen > destlen)
++ len = mutt_wstr_trunc (buf, destlen - wlen, COLS - SidebarWidth - col, NULL);
++ memcpy (wptr, buf, len);
++ wptr += len;
++ wlen += len;
++ col += wid;
++ src += pl;
++ }
++ }
+ break; /* skip rest of input */
+ }
+ else if (ch == '|')
+diff --git a/mx.c b/mx.c
+index 07dba0c..cbee47d 100644
+--- a/mx.c
++++ b/mx.c
+@@ -595,6 +595,7 @@ static int mx_open_mailbox_append (CONTEXT *ctx, int flags)
+ * M_APPEND open mailbox for appending
+ * M_READONLY open mailbox in read-only mode
+ * M_QUIET only print error messages
++ * M_PEEK revert atime where applicable
+ * ctx if non-null, context struct to use
+ */
+ CONTEXT *mx_open_mailbox (const char *path, int flags, CONTEXT *pctx)
+@@ -617,6 +618,8 @@ CONTEXT *mx_open_mailbox (const char *path, int flags, CONTEXT *pctx)
+ ctx->quiet = 1;
+ if (flags & M_READONLY)
+ ctx->readonly = 1;
++ if (flags & M_PEEK)
++ ctx->peekonly = 1;
+
+ if (flags & (M_APPEND|M_NEWFOLDER))
+ {
+@@ -721,9 +724,21 @@ CONTEXT *mx_open_mailbox (const char *path, int flags, CONTEXT *pctx)
+ void mx_fastclose_mailbox (CONTEXT *ctx)
+ {
+ int i;
++#ifndef BUFFY_SIZE
++ struct utimbuf ut;
++#endif
+
+ if(!ctx)
+ return;
++#ifndef BUFFY_SIZE
++ /* fix up the times so buffy won't get confused */
++ if (ctx->peekonly && ctx->path && ctx->mtime > ctx->atime)
++ {
++ ut.actime = ctx->atime;
++ ut.modtime = ctx->mtime;
++ utime (ctx->path, &ut);
++ }
++#endif
+
+ /* never announce that a mailbox we've just left has new mail. #3290
+ * XXX: really belongs in mx_close_mailbox, but this is a nice hook point */
+diff --git a/mx.h b/mx.h
+index 2ef4ec7..4aabadf 100644
+--- a/mx.h
++++ b/mx.h
+@@ -60,6 +60,7 @@ void mbox_reset_atime (CONTEXT *, struct stat *);
+ int mh_read_dir (CONTEXT *, const char *);
+ int mh_sync_mailbox (CONTEXT *, int *);
+ int mh_check_mailbox (CONTEXT *, int *);
++void mh_buffy_update (const char *, int *, int *, int *);
+ int mh_check_empty (const char *);
+
+ int maildir_read_dir (CONTEXT *);
+diff --git a/pager.c b/pager.c
+index 7b61266..469efe4 100644
+--- a/pager.c
++++ b/pager.c
+@@ -29,6 +29,7 @@
+ #include "pager.h"
+ #include "attach.h"
+ #include "mbyte.h"
++#include "sidebar.h"
+
+ #include "mutt_crypt.h"
+
+@@ -1095,6 +1096,7 @@ static int format_line (struct line_t **lineInfo, int n, unsigned char *buf,
+ wchar_t wc;
+ mbstate_t mbstate;
+ int wrap_cols = mutt_term_width ((flags & M_PAGER_NOWRAP) ? 0 : Wrap);
++ wrap_cols -= SidebarWidth;
+
+ if (check_attachment_marker ((char *)buf) == 0)
+ wrap_cols = COLS;
+@@ -1746,7 +1748,7 @@ mutt_pager (const char *banner, const char *fname, int flags, pager_t *extra)
+ if ((redraw & REDRAW_BODY) || topline != oldtopline)
+ {
+ do {
+- move (bodyoffset, 0);
++ move (bodyoffset, SidebarWidth);
+ curline = oldtopline = topline;
+ lines = 0;
+ force_redraw = 0;
+@@ -1759,6 +1761,7 @@ mutt_pager (const char *banner, const char *fname, int flags, pager_t *extra)
+ &QuoteList, &q_level, &force_redraw, &SearchRE) > 0)
+ lines++;
+ curline++;
++ move(lines + bodyoffset, SidebarWidth);
+ }
+ last_offset = lineInfo[curline].offset;
+ } while (force_redraw);
+@@ -1771,6 +1774,7 @@ mutt_pager (const char *banner, const char *fname, int flags, pager_t *extra)
+ addch ('~');
+ addch ('\n');
+ lines++;
++ move(lines + bodyoffset, SidebarWidth);
+ }
+ NORMAL_COLOR;
+
+@@ -1794,22 +1798,22 @@ mutt_pager (const char *banner, const char *fname, int flags, pager_t *extra)
+ strfcpy(pager_progress_str, (topline == 0) ? "all" : "end", sizeof(pager_progress_str));
+
+ /* print out the pager status bar */
+- move (statusoffset, 0);
++ move (statusoffset, SidebarWidth);
+ SETCOLOR (MT_COLOR_STATUS);
+
+ if (IsHeader (extra) || IsMsgAttach (extra))
+ {
+- size_t l1 = COLS * MB_LEN_MAX;
++ size_t l1 = (COLS-SidebarWidth) * MB_LEN_MAX;
+ size_t l2 = sizeof (buffer);
+ hfi.hdr = (IsHeader (extra)) ? extra->hdr : extra->bdy->hdr;
+ mutt_make_string_info (buffer, l1 < l2 ? l1 : l2, NONULL (PagerFmt), &hfi, M_FORMAT_MAKEPRINT);
+- mutt_paddstr (COLS, buffer);
++ mutt_paddstr (COLS-SidebarWidth, buffer);
+ }
+ else
+ {
+ char bn[STRING];
+ snprintf (bn, sizeof (bn), "%s (%s)", banner, pager_progress_str);
+- mutt_paddstr (COLS, bn);
++ mutt_paddstr (COLS-SidebarWidth, bn);
+ }
+ NORMAL_COLOR;
+ if (option(OPTXTERMSETTITLES))
+@@ -1826,16 +1830,21 @@ mutt_pager (const char *banner, const char *fname, int flags, pager_t *extra)
+ /* redraw the pager_index indicator, because the
+ * flags for this message might have changed. */
+ menu_redraw_current (index);
++ draw_sidebar(MENU_PAGER);
+
+ /* print out the index status bar */
+ menu_status_line (buffer, sizeof (buffer), index, NONULL(Status));
+
+- move (indexoffset + (option (OPTSTATUSONTOP) ? 0 : (indexlen - 1)), 0);
++ move (indexoffset + (option (OPTSTATUSONTOP) ? 0 : (indexlen - 1)), SidebarWidth);
+ SETCOLOR (MT_COLOR_STATUS);
+- mutt_paddstr (COLS, buffer);
++ mutt_paddstr (COLS-SidebarWidth, buffer);
+ NORMAL_COLOR;
+ }
+
++ /* if we're not using the index, update every time */
++ if ( index == 0 )
++ draw_sidebar(MENU_PAGER);
++
+ redraw = 0;
+
+ if (option(OPTBRAILLEFRIENDLY)) {
+@@ -2776,6 +2785,13 @@ search_next:
+ mutt_what_key ();
+ break;
+
++ case OP_SIDEBAR_SCROLL_UP:
++ case OP_SIDEBAR_SCROLL_DOWN:
++ case OP_SIDEBAR_NEXT:
++ case OP_SIDEBAR_PREV:
++ scroll_sidebar(ch, MENU_PAGER);
++ break;
++
+ default:
+ ch = -1;
+ break;
+diff --git a/sidebar.c b/sidebar.c
+new file mode 100644
+index 0000000..6098c2a
+--- /dev/null
++++ b/sidebar.c
+@@ -0,0 +1,333 @@
++/*
++ * Copyright (C) ????-2004 Justin Hibbits <jrh29@po.cwru.edu>
++ * Copyright (C) 2004 Thomer M. Gil <mutt@thomer.com>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation; either version 2 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program; if not, write to the Free Software
++ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
++ */
++
++
++#if HAVE_CONFIG_H
++# include "config.h"
++#endif
++
++#include "mutt.h"
++#include "mutt_menu.h"
++#include "mutt_curses.h"
++#include "sidebar.h"
++#include "buffy.h"
++#include <libgen.h>
++#include "keymap.h"
++#include <stdbool.h>
++
++/*BUFFY *CurBuffy = 0;*/
++static BUFFY *TopBuffy = 0;
++static BUFFY *BottomBuffy = 0;
++static int known_lines = 0;
++
++static int quick_log10(int n)
++{
++ char string[32];
++ sprintf(string, "%d", n);
++ return strlen(string);
++}
++
++void calc_boundaries (int menu)
++{
++ BUFFY *tmp = Incoming;
++
++ if ( known_lines != LINES ) {
++ TopBuffy = BottomBuffy = 0;
++ known_lines = LINES;
++ }
++ for ( ; tmp->next != 0; tmp = tmp->next )
++ tmp->next->prev = tmp;
++
++ if ( TopBuffy == 0 && BottomBuffy == 0 )
++ TopBuffy = Incoming;
++ if ( BottomBuffy == 0 ) {
++ int count = LINES - 2 - (menu != MENU_PAGER || option(OPTSTATUSONTOP));
++ BottomBuffy = TopBuffy;
++ while ( --count && BottomBuffy->next )
++ BottomBuffy = BottomBuffy->next;
++ }
++ else if ( TopBuffy == CurBuffy->next ) {
++ int count = LINES - 2 - (menu != MENU_PAGER);
++ BottomBuffy = CurBuffy;
++ tmp = BottomBuffy;
++ while ( --count && tmp->prev)
++ tmp = tmp->prev;
++ TopBuffy = tmp;
++ }
++ else if ( BottomBuffy == CurBuffy->prev ) {
++ int count = LINES - 2 - (menu != MENU_PAGER);
++ TopBuffy = CurBuffy;
++ tmp = TopBuffy;
++ while ( --count && tmp->next )
++ tmp = tmp->next;
++ BottomBuffy = tmp;
++ }
++}
++
++char *make_sidebar_entry(char *box, int size, int new, int flagged)
++{
++ static char *entry = 0;
++ char *c;
++ int i = 0;
++ int delim_len = strlen(SidebarDelim);
++
++ c = realloc(entry, SidebarWidth - delim_len + 2);
++ if ( c ) entry = c;
++ entry[SidebarWidth - delim_len + 1] = 0;
++ for (; i < SidebarWidth - delim_len + 1; entry[i++] = ' ' );
++ i = strlen(box);
++ strncpy( entry, box, i < (SidebarWidth - delim_len + 1) ? i : (SidebarWidth - delim_len + 1) );
++
++ if (size == -1)
++ sprintf(entry + SidebarWidth - delim_len - 3, "?");
++ else if ( new ) {
++ if (flagged > 0) {
++ sprintf(
++ entry + SidebarWidth - delim_len - 5 - quick_log10(size) - quick_log10(new) - quick_log10(flagged),
++ "% d(%d)[%d]", size, new, flagged);
++ } else {
++ sprintf(
++ entry + SidebarWidth - delim_len - 3 - quick_log10(size) - quick_log10(new),
++ "% d(%d)", size, new);
++ }
++ } else if (flagged > 0) {
++ sprintf( entry + SidebarWidth - delim_len - 3 - quick_log10(size) - quick_log10(flagged), "% d[%d]", size, flagged);
++ } else {
++ sprintf( entry + SidebarWidth - delim_len - 1 - quick_log10(size), "% d", size);
++ }
++ return entry;
++}
++
++void set_curbuffy(char buf[LONG_STRING])
++{
++ BUFFY* tmp = CurBuffy = Incoming;
++
++ if (!Incoming)
++ return;
++
++ while(1) {
++ if(!strcmp(tmp->path, buf)) {
++ CurBuffy = tmp;
++ break;
++ }
++
++ if(tmp->next)
++ tmp = tmp->next;
++ else
++ break;
++ }
++}
++
++int draw_sidebar(int menu) {
++
++ int lines = option(OPTHELP) ? 1 : 0;
++ BUFFY *tmp;
++#ifndef USE_SLANG_CURSES
++ attr_t attrs;
++#endif
++ short delim_len = strlen(SidebarDelim);
++ short color_pair;
++
++ static bool initialized = false;
++ static int prev_show_value;
++ static short saveSidebarWidth;
++
++ /* initialize first time */
++ if(!initialized) {
++ prev_show_value = option(OPTSIDEBAR);
++ saveSidebarWidth = SidebarWidth;
++ if(!option(OPTSIDEBAR)) SidebarWidth = 0;
++ initialized = true;
++ }
++
++ /* save or restore the value SidebarWidth */
++ if(prev_show_value != option(OPTSIDEBAR)) {
++ if(prev_show_value && !option(OPTSIDEBAR)) {
++ saveSidebarWidth = SidebarWidth;
++ SidebarWidth = 0;
++ } else if(!prev_show_value && option(OPTSIDEBAR)) {
++ SidebarWidth = saveSidebarWidth;
++ }
++ prev_show_value = option(OPTSIDEBAR);
++ }
++
++
++// if ( SidebarWidth == 0 ) return 0;
++ if (SidebarWidth > 0 && option (OPTSIDEBAR)
++ && delim_len >= SidebarWidth) {
++ unset_option (OPTSIDEBAR);
++ /* saveSidebarWidth = SidebarWidth; */
++ if (saveSidebarWidth > delim_len) {
++ SidebarWidth = saveSidebarWidth;
++ mutt_error (_("Value for sidebar_delim is too long. Disabling sidebar."));
++ sleep (2);
++ } else {
++ SidebarWidth = 0;
++ mutt_error (_("Value for sidebar_delim is too long. Disabling sidebar. Please set your sidebar_width to a sane value."));
++ sleep (4); /* the advise to set a sane value should be seen long enough */
++ }
++ saveSidebarWidth = 0;
++ return (0);
++ }
++
++ if ( SidebarWidth == 0 || !option(OPTSIDEBAR)) {
++ if (SidebarWidth > 0) {
++ saveSidebarWidth = SidebarWidth;
++ SidebarWidth = 0;
++ }
++ unset_option(OPTSIDEBAR);
++ return 0;
++ }
++
++ /* get attributes for divider */
++ SETCOLOR(MT_COLOR_STATUS);
++#ifndef USE_SLANG_CURSES
++ attr_get(&attrs, &color_pair, 0);
++#else
++ color_pair = attr_get();
++#endif
++ SETCOLOR(MT_COLOR_NORMAL);
++
++ /* draw the divider */
++
++ for ( ; lines < LINES-1-(menu != MENU_PAGER || option(OPTSTATUSONTOP)); lines++ ) {
++ move(lines, SidebarWidth - delim_len);
++ addstr(NONULL(SidebarDelim));
++#ifndef USE_SLANG_CURSES
++ mvchgat(lines, SidebarWidth - delim_len, delim_len, 0, color_pair, NULL);
++#endif
++ }
++
++ if ( Incoming == 0 ) return 0;
++ lines = option(OPTHELP) ? 1 : 0; /* go back to the top */
++
++ if ( known_lines != LINES || TopBuffy == 0 || BottomBuffy == 0 )
++ calc_boundaries(menu);
++ if ( CurBuffy == 0 ) CurBuffy = Incoming;
++
++ tmp = TopBuffy;
++
++ SETCOLOR(MT_COLOR_NORMAL);
++
++ for ( ; tmp && lines < LINES-1 - (menu != MENU_PAGER || option(OPTSTATUSONTOP)); tmp = tmp->next ) {
++ if ( tmp == CurBuffy )
++ SETCOLOR(MT_COLOR_INDICATOR);
++ else if ( tmp->msg_unread > 0 )
++ SETCOLOR(MT_COLOR_NEW);
++ else if ( tmp->msg_flagged > 0 )
++ SETCOLOR(MT_COLOR_FLAGGED);
++ else
++ SETCOLOR(MT_COLOR_NORMAL);
++
++ move( lines, 0 );
++ if ( Context && !strcmp( tmp->path, Context->path ) ) {
++ tmp->msg_unread = Context->unread;
++ tmp->msgcount = Context->msgcount;
++ tmp->msg_flagged = Context->flagged;
++ }
++ // check whether Maildir is a prefix of the current folder's path
++ short maildir_is_prefix = 0;
++ if ( (strlen(tmp->path) > strlen(Maildir)) &&
++ (strncmp(Maildir, tmp->path, strlen(Maildir)) == 0) )
++ maildir_is_prefix = 1;
++ // calculate depth of current folder and generate its display name with indented spaces
++ int sidebar_folder_depth = 0;
++ char *sidebar_folder_name;
++ sidebar_folder_name = basename(tmp->path);
++ if ( maildir_is_prefix ) {
++ char *tmp_folder_name;
++ int i;
++ tmp_folder_name = tmp->path + strlen(Maildir);
++ for (i = 0; i < strlen(tmp->path) - strlen(Maildir); i++) {
++ if (tmp_folder_name[i] == '/') sidebar_folder_depth++;
++ }
++ if (sidebar_folder_depth > 0) {
++ sidebar_folder_name = malloc(strlen(basename(tmp->path)) + sidebar_folder_depth + 1);
++ for (i=0; i < sidebar_folder_depth; i++)
++ sidebar_folder_name[i]=' ';
++ sidebar_folder_name[i]=0;
++ strncat(sidebar_folder_name, basename(tmp->path), strlen(basename(tmp->path)) + sidebar_folder_depth);
++ }
++ }
++ printw( "%.*s", SidebarWidth - delim_len + 1,
++ make_sidebar_entry(sidebar_folder_name, tmp->msgcount,
++ tmp->msg_unread, tmp->msg_flagged));
++ if (sidebar_folder_depth > 0)
++ free(sidebar_folder_name);
++ lines++;
++ }
++ SETCOLOR(MT_COLOR_NORMAL);
++ for ( ; lines < LINES-1 - (menu != MENU_PAGER || option(OPTSTATUSONTOP)); lines++ ) {
++ int i = 0;
++ move( lines, 0 );
++ for ( ; i < SidebarWidth - delim_len; i++ )
++ addch(' ');
++ }
++ return 0;
++}
++
++
++void set_buffystats(CONTEXT* Context)
++{
++ BUFFY *tmp = Incoming;
++ while(tmp) {
++ if(Context && !strcmp(tmp->path, Context->path)) {
++ tmp->msg_unread = Context->unread;
++ tmp->msgcount = Context->msgcount;
++ break;
++ }
++ tmp = tmp->next;
++ }
++}
++
++void scroll_sidebar(int op, int menu)
++{
++ if(!SidebarWidth) return;
++ if(!CurBuffy) return;
++
++ switch (op) {
++ case OP_SIDEBAR_NEXT:
++ if ( CurBuffy->next == NULL ) return;
++ CurBuffy = CurBuffy->next;
++ break;
++ case OP_SIDEBAR_PREV:
++ if ( CurBuffy->prev == NULL ) return;
++ CurBuffy = CurBuffy->prev;
++ break;
++ case OP_SIDEBAR_SCROLL_UP:
++ CurBuffy = TopBuffy;
++ if ( CurBuffy != Incoming ) {
++ calc_boundaries(menu);
++ CurBuffy = CurBuffy->prev;
++ }
++ break;
++ case OP_SIDEBAR_SCROLL_DOWN:
++ CurBuffy = BottomBuffy;
++ if ( CurBuffy->next ) {
++ calc_boundaries(menu);
++ CurBuffy = CurBuffy->next;
++ }
++ break;
++ default:
++ return;
++ }
++ calc_boundaries(menu);
++ draw_sidebar(menu);
++}
++
+diff --git a/sidebar.h b/sidebar.h
+new file mode 100644
+index 0000000..d195f11
+--- /dev/null
++++ b/sidebar.h
+@@ -0,0 +1,36 @@
++/*
++ * Copyright (C) ????-2004 Justin Hibbits <jrh29@po.cwru.edu>
++ * Copyright (C) 2004 Thomer M. Gil <mutt@thomer.com>
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License as published by
++ * the Free Software Foundation; either version 2 of the License, or
++ * (at your option) any later version.
++ *
++ * This program is distributed in the hope that it will be useful,
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ * GNU General Public License for more details.
++ *
++ * You should have received a copy of the GNU General Public License
++ * along with this program; if not, write to the Free Software
++ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
++ */
++
++#ifndef SIDEBAR_H
++#define SIDEBAR_H
++
++struct MBOX_LIST {
++ char *path;
++ int msgcount;
++ int new;
++} MBLIST;
++
++/* parameter is whether or not to go to the status line */
++/* used for omitting the last | that covers up the status bar in the index */
++int draw_sidebar(int);
++void scroll_sidebar(int, int);
++void set_curbuffy(char*);
++void set_buffystats(CONTEXT*);
++
++#endif /* SIDEBAR_H */
diff --git a/smime.rc.patch b/smime.rc.patch
new file mode 100644
index 000000000000..7953f5ab9428
--- /dev/null
+++ b/smime.rc.patch
@@ -0,0 +1,60 @@
+From: Antonio Radici <antonio@debian.org>
+Date: Thu, 27 Feb 2014 16:31:48 +0100
+Subject: smime.rc
+
+Add CA's provided by ca-certificates to the ones
+distributed by the default installation of Mutt.
+
+Gbp-Pq: Topic misc
+---
+ contrib/smime.rc | 19 ++++++++++---------
+ 1 file changed, 10 insertions(+), 9 deletions(-)
+
+diff --git a/contrib/smime.rc b/contrib/smime.rc
+index 5376089..efb0b6e 100644
+--- a/contrib/smime.rc
++++ b/contrib/smime.rc
+@@ -4,33 +4,34 @@
+
+ # If you compiled mutt with support for both PGP and S/MIME, PGP
+ # will be the default method unless the following option is set
+-set smime_is_default
++#set smime_is_default
+
+ # Uncoment this if you don't want to set labels for certificates you add.
+ # unset smime_ask_cert_label
+
+ # Passphrase expiration
+-set smime_timeout=300
++#set smime_timeout=300
+
+ # Global crypto options -- these affect PGP operations as well.
+-set crypt_autosign = yes
+-set crypt_replyencrypt = yes
+-set crypt_replysign = yes
+-set crypt_replysignencrypted = yes
+-set crypt_verify_sig = yes
++#set crypt_autosign = yes
++#set crypt_replyencrypt = yes
++#set crypt_replysign = yes
++#set crypt_replysignencrypted = yes
++#set crypt_verify_sig = yes
+
+ # Section A: Key Management.
+
+ # The (default) keyfile for signing/decrypting. Uncomment the following
+ # line and replace the keyid with your own.
+-set smime_default_key="12345678.0"
++#set smime_default_key="12345678.0"
+
+ # Uncommen to make mutt ask what key to use when trying to decrypt a message.
+ # It will use the default key above (if that was set) else.
+ # unset smime_decrypt_use_default_key
+
+ # Path to a file or directory with trusted certificates
+-set smime_ca_location="~/.smime/ca-bundle.crt"
++#set smime_ca_location="~/.smime/ca-bundle.crt"
++set smime_ca_location=`for f in $HOME/.smime/ca-certificates.crt $HOME/.smime/ca-bundle.crt /etc/ssl/certs/ca-certificates.crt ; do if [ -e $f ] ; then echo $f ; exit ; fi ; done`
+
+ # Path to where all known certificates go. (must exist!)
+ set smime_certificates="~/.smime/certificates"
diff --git a/trash-folder.patch b/trash-folder.patch
new file mode 100644
index 000000000000..7acd7bb94017
--- /dev/null
+++ b/trash-folder.patch
@@ -0,0 +1,316 @@
+From: Cedric Duval <cedricduval@free.fr>
+Date: Thu, 27 Feb 2014 12:27:41 +0100
+Subject: trash-folder
+
+With this patch, if the trash variable is set to a path (unset by default), the
+deleted mails will be moved to a trash folder instead of being irremediably
+purged when syncing the mailbox.
+
+For instance, set trash="~/Mail/trash" will cause every deleted mail to go to
+this folder.
+
+Note that the append to the trash folder doesn't occur until the resync is
+done. This allows you to change your mind and undo deletes, and thus the moves
+to the trash folder are unnecessary.
+
+Notes
+
+ * You might also want to have a look at the purge message feature below
+ which is related to this patch.
+ * IMAP is now supported. To retain the previous behavior, add this to your
+ muttrc:
+ folder-hook ^imap:// 'unset trash'
+
+FAQ
+
+Every once in a while, someone asks what are the advantages of this patch over
+a macro based solution. Here's an attempt to answer this question:
+
+ * The folder history doesn't clutter up with unwanted trash entries.
+ * Delayed move to the trash allows to change one's mind.
+ * No need to treat the case of "normal folders" and trash folders
+ separately with folder-hooks, and to create two sets of macros (one for
+ the index, one for the pager).
+ * Works not only with delete-message, but also with every deletion
+ functions like delete-pattern, delete-thread or delete-subthread.
+
+To sum up, it's more integrated and transparent to the user.
+
+* Patch last synced with upstream:
+ - Date: 2007-02-15
+ - File: http://cedricduval.free.fr/mutt/patches/download/patch-1.5.5.1.cd.trash_folder.3.4
+
+* Changes made:
+ - Updated to 1.5.13:
+ - structure of _mutt_save_message changed (commands.c)
+ - context of option (OPTCONFIRMAPPEND) changed (muttlib.c)
+ - Fixed indentation of "appended" in mutt.h.
+
+Signed-off-by: Matteo F. Vescovi <mfvescovi@gmail.com>
+
+Gbp-Pq: Topic features
+---
+ commands.c | 1 +
+ flags.c | 19 +++++++++++++++++-
+ globals.h | 1 +
+ imap/message.c | 2 ++
+ init.h | 10 ++++++++++
+ mutt.h | 3 +++
+ muttlib.c | 4 +++-
+ mx.c | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ postpone.c | 3 +++
+ 9 files changed, 103 insertions(+), 2 deletions(-)
+
+diff --git a/commands.c b/commands.c
+index 6b23e39..300d95c 100644
+--- a/commands.c
++++ b/commands.c
+@@ -720,6 +720,7 @@ int _mutt_save_message (HEADER *h, CONTEXT *ctx, int delete, int decode, int dec
+ if (option (OPTDELETEUNTAG))
+ mutt_set_flag (Context, h, M_TAG, 0);
+ }
++ mutt_set_flag (Context, h, M_APPENDED, 1);
+
+ return 0;
+ }
+diff --git a/flags.c b/flags.c
+index f0f3d81..dfa6a50 100644
+--- a/flags.c
++++ b/flags.c
+@@ -65,7 +65,13 @@ void _mutt_set_flag (CONTEXT *ctx, HEADER *h, int flag, int bf, int upd_ctx)
+ {
+ h->deleted = 0;
+ update = 1;
+- if (upd_ctx) ctx->deleted--;
++ if (upd_ctx)
++ {
++ ctx->deleted--;
++ if (h->appended)
++ ctx->appended--;
++ }
++ h->appended = 0; /* when undeleting, also reset the appended flag */
+ #ifdef USE_IMAP
+ /* see my comment above */
+ if (ctx->magic == M_IMAP)
+@@ -87,6 +93,17 @@ void _mutt_set_flag (CONTEXT *ctx, HEADER *h, int flag, int bf, int upd_ctx)
+ }
+ break;
+
++ case M_APPENDED:
++ if (bf)
++ {
++ if (!h->appended)
++ {
++ h->appended = 1;
++ if (upd_ctx) ctx->appended++;
++ }
++ }
++ break;
++
+ case M_NEW:
+
+ if (!mutt_bit_isset(ctx->rights,M_ACL_SEEN))
+diff --git a/globals.h b/globals.h
+index 584cd0c..a7e2304 100644
+--- a/globals.h
++++ b/globals.h
+@@ -139,6 +139,7 @@ WHERE char *StChars;
+ WHERE char *Status;
+ WHERE char *Tempdir;
+ WHERE char *Tochars;
++WHERE char *TrashPath;
+ WHERE char *Username;
+ WHERE char *Visual;
+ WHERE char *XtermTitle;
+diff --git a/imap/message.c b/imap/message.c
+index 38da127..3c9822f 100644
+--- a/imap/message.c
++++ b/imap/message.c
+@@ -876,6 +876,7 @@ int imap_copy_messages (CONTEXT* ctx, HEADER* h, char* dest, int delete)
+ if (ctx->hdrs[n]->tagged)
+ {
+ mutt_set_flag (ctx, ctx->hdrs[n], M_DELETE, 1);
++ mutt_set_flag (ctx, ctx->hdrs[n], M_APPENDED, 1);
+ if (option (OPTDELETEUNTAG))
+ mutt_set_flag (ctx, ctx->hdrs[n], M_TAG, 0);
+ }
+@@ -883,6 +884,7 @@ int imap_copy_messages (CONTEXT* ctx, HEADER* h, char* dest, int delete)
+ else
+ {
+ mutt_set_flag (ctx, h, M_DELETE, 1);
++ mutt_set_flag (ctx, h, M_APPENDED, 1);
+ if (option (OPTDELETEUNTAG))
+ mutt_set_flag (ctx, h, M_TAG, 0);
+ }
+diff --git a/init.h b/init.h
+index cf8e75a..d4a882b 100644
+--- a/init.h
++++ b/init.h
+@@ -3241,6 +3241,16 @@ struct option_t MuttVars[] = {
+ ** by \fIyou\fP. The sixth character is used to indicate when a mail
+ ** was sent to a mailing-list you subscribe to.
+ */
++ { "trash", DT_PATH, R_NONE, UL &TrashPath, 0 },
++ /*
++ ** .pp
++ ** If set, this variable specifies the path of the trash folder where the
++ ** mails marked for deletion will be moved, instead of being irremediably
++ ** purged.
++ ** .pp
++ ** NOTE: When you delete a message in the trash folder, it is really
++ ** deleted, so that you have a way to clean the trash.
++ */
+ #ifdef USE_SOCKET
+ { "tunnel", DT_STR, R_NONE, UL &Tunnel, UL 0 },
+ /*
+diff --git a/mutt.h b/mutt.h
+index 1d7e177..0b879b9 100644
+--- a/mutt.h
++++ b/mutt.h
+@@ -185,6 +185,7 @@ enum
+ M_DELETE,
+ M_UNDELETE,
+ M_DELETED,
++ M_APPENDED,
+ M_FLAG,
+ M_TAG,
+ M_UNTAG,
+@@ -709,6 +710,7 @@ typedef struct header
+ unsigned int mime : 1; /* has a MIME-Version header? */
+ unsigned int flagged : 1; /* marked important? */
+ unsigned int tagged : 1;
++ unsigned int appended : 1; /* has been saved */
+ unsigned int deleted : 1;
+ unsigned int changed : 1;
+ unsigned int attach_del : 1; /* has an attachment marked for deletion */
+@@ -881,6 +883,7 @@ typedef struct _context
+ int new; /* how many new messages? */
+ int unread; /* how many unread messages? */
+ int deleted; /* how many deleted messages */
++ int appended; /* how many saved messages? */
+ int flagged; /* how many flagged messages */
+ int msgnotreadyet; /* which msg "new" in pager, -1 if none */
+
+diff --git a/muttlib.c b/muttlib.c
+index 8005e9c..0425d45 100644
+--- a/muttlib.c
++++ b/muttlib.c
+@@ -1510,7 +1510,9 @@ int mutt_save_confirm (const char *s, struct stat *st)
+
+ if (magic > 0 && !mx_access (s, W_OK))
+ {
+- if (option (OPTCONFIRMAPPEND))
++ if (option (OPTCONFIRMAPPEND) &&
++ (!TrashPath || (mutt_strcmp (s, TrashPath) != 0)))
++ /* if we're appending to the trash, there's no point in asking */
+ {
+ snprintf (tmp, sizeof (tmp), _("Append messages to %s?"), s);
+ if ((rc = mutt_yesorno (tmp, M_YES)) == M_NO)
+diff --git a/mx.c b/mx.c
+index f599f6c..f7fd01f 100644
+--- a/mx.c
++++ b/mx.c
+@@ -776,6 +776,53 @@ static int sync_mailbox (CONTEXT *ctx, int *index_hint)
+ return rc;
+ }
+
++/* move deleted mails to the trash folder */
++static int trash_append (CONTEXT *ctx)
++{
++ CONTEXT *ctx_trash;
++ int i = 0;
++ struct stat st, stc;
++
++ if (!TrashPath || !ctx->deleted ||
++ (ctx->magic == M_MAILDIR && option (OPTMAILDIRTRASH)))
++ return 0;
++
++ for (;i < ctx->msgcount && (!ctx->hdrs[i]->deleted ||
++ ctx->hdrs[i]->appended); i++);
++ if (i == ctx->msgcount)
++ return 0; /* nothing to be done */
++
++ if (mutt_save_confirm (TrashPath, &st) != 0)
++ {
++ mutt_error _("message(s) not deleted");
++ return -1;
++ }
++
++ if (lstat (ctx->path, &stc) == 0 && stc.st_ino == st.st_ino
++ && stc.st_dev == st.st_dev && stc.st_rdev == st.st_rdev)
++ return 0; /* we are in the trash folder: simple sync */
++
++ if ((ctx_trash = mx_open_mailbox (TrashPath, M_APPEND, NULL)) != NULL)
++ {
++ for (i = 0 ; i < ctx->msgcount ; i++)
++ if (ctx->hdrs[i]->deleted && !ctx->hdrs[i]->appended
++ && mutt_append_message (ctx_trash, ctx, ctx->hdrs[i], 0, 0) == -1)
++ {
++ mx_close_mailbox (ctx_trash, NULL);
++ return -1;
++ }
++
++ mx_close_mailbox (ctx_trash, NULL);
++ }
++ else
++ {
++ mutt_error _("Can't open trash folder");
++ return -1;
++ }
++
++ return 0;
++}
++
+ /* save changes and close mailbox */
+ int mx_close_mailbox (CONTEXT *ctx, int *index_hint)
+ {
+@@ -912,6 +959,7 @@ int mx_close_mailbox (CONTEXT *ctx, int *index_hint)
+ if (mutt_append_message (&f, ctx, ctx->hdrs[i], 0, CH_UPDATE_LEN) == 0)
+ {
+ mutt_set_flag (ctx, ctx->hdrs[i], M_DELETE, 1);
++ mutt_set_flag (ctx, ctx->hdrs[i], M_APPENDED, 1);
+ }
+ else
+ {
+@@ -936,6 +984,14 @@ int mx_close_mailbox (CONTEXT *ctx, int *index_hint)
+ return 0;
+ }
+
++ /* copy mails to the trash before expunging */
++ if (purge && ctx->deleted && mutt_strcmp(ctx->path, TrashPath))
++ if (trash_append (ctx) != 0)
++ {
++ ctx->closing = 0;
++ return -1;
++ }
++
+ #ifdef USE_IMAP
+ /* allow IMAP to preserve the deleted flag across sessions */
+ if (ctx->magic == M_IMAP)
+@@ -1133,6 +1189,12 @@ int mx_sync_mailbox (CONTEXT *ctx, int *index_hint)
+ msgcount = ctx->msgcount;
+ deleted = ctx->deleted;
+
++ if (purge && ctx->deleted && mutt_strcmp(ctx->path, TrashPath))
++ {
++ if (trash_append (ctx) == -1)
++ return -1;
++ }
++
+ #ifdef USE_IMAP
+ if (ctx->magic == M_IMAP)
+ rc = imap_sync_mailbox (ctx, purge, index_hint);
+diff --git a/postpone.c b/postpone.c
+index 801ef10..21e96e6 100644
+--- a/postpone.c
++++ b/postpone.c
+@@ -277,6 +277,9 @@ int mutt_get_postponed (CONTEXT *ctx, HEADER *hdr, HEADER **cur, char *fcc, size
+ /* finished with this message, so delete it. */
+ mutt_set_flag (PostContext, h, M_DELETE, 1);
+
++ /* and consider it saved, so that it won't be moved to the trash folder */
++ mutt_set_flag (PostContext, h, M_APPENDED, 1);
++
+ /* update the count for the status display */
+ PostCount = PostContext->msgcount - PostContext->deleted;
+
diff --git a/update_german_translation.patch b/update_german_translation.patch
new file mode 100644
index 000000000000..a7df39dd6f6a
--- /dev/null
+++ b/update_german_translation.patch
@@ -0,0 +1,115 @@
+From: Christoph Berg <myon@debian.org>
+Date: Fri, 14 Mar 2014 08:41:46 +0100
+Subject: update_german_translation
+
+Gbp-Pq: Topic translations
+---
+ po/de.po | 24 ++++++++----------------
+ 1 file changed, 8 insertions(+), 16 deletions(-)
+
+diff --git a/po/de.po b/po/de.po
+index 75d56f3..a2218c8 100644
+--- a/po/de.po
++++ b/po/de.po
+@@ -3,7 +3,7 @@ msgstr ""
+ "Project-Id-Version: 1.5.20\n"
+ "Report-Msgid-Bugs-To: \n"
+ "POT-Creation-Date: 2014-03-12 09:18-0700\n"
+-"PO-Revision-Date: 2008-05-18 10:28+0200\n"
++"PO-Revision-Date: 2014-03-14 08:34+0100\n"
+ "Last-Translator: Rocco Rutte <pdmef@gmx.net>\n"
+ "Language-Team: German <mutt-po@mutt.org>\n"
+ "Language: de\n"
+@@ -587,9 +587,8 @@ msgid "Descrip"
+ msgstr "Beschr."
+
+ #: compose.c:117
+-#, fuzzy
+ msgid "Not supported"
+-msgstr "Markieren wird nicht unterstützt."
++msgstr "Nicht unterstützt"
+
+ #: compose.c:122
+ msgid "Sign, Encrypt"
+@@ -1742,7 +1741,6 @@ msgstr "L
+ #. * declared "static" (sigh)
+ #.
+ #: edit.c:41
+-#, fuzzy
+ msgid ""
+ "~~\t\tinsert a line beginning with a single ~\n"
+ "~b users\tadd users to the Bcc: field\n"
+@@ -2654,7 +2652,6 @@ msgstr ""
+ " mutt -v[v]\n"
+
+ #: main.c:124
+-#, fuzzy
+ msgid ""
+ "options:\n"
+ " -A <alias>\texpand the given alias\n"
+@@ -2667,6 +2664,7 @@ msgstr ""
+ "Optionen:\n"
+ " -A <alias>\tExpandiere den angegebenen Alias\n"
+ " -a <file>\tHängt Datei an die Message an\n"
++"\t\tDie Dateiliste muss mit \"--\" beendet werden\n"
+ " -b <address>\tEmpfänger einer unsichtbaren Kopie (Bcc:)\n"
+ " -c <address>\tEmpfänger einer Kopie (Cc:)\n"
+ " -D\t\tGib die Werte aller Variablen aus"
+@@ -3004,9 +3002,8 @@ msgid "Unable to get certificate from peer"
+ msgstr "Kann kein Zertifikat vom Server erhalten"
+
+ #: mutt_ssl.c:424
+-#, fuzzy, c-format
+ msgid "%s connection using %s (%s)"
+-msgstr "SSL Verbindung unter Verwendung von %s (%s)"
++msgstr "%s-Verbindung unter Verwendung von %s (%s)"
+
+ #: mutt_ssl.c:526
+ msgid "Unknown"
+@@ -3396,9 +3393,8 @@ msgid "error in pattern at: %s"
+ msgstr "Fehler in Muster bei: %s"
+
+ #: pattern.c:830
+-#, fuzzy, c-format
+ msgid "missing pattern: %s"
+-msgstr "Fehlender Parameter"
++msgstr "Fehlendes Muster: %s"
+
+ #: pattern.c:840
+ #, c-format
+@@ -3516,9 +3512,8 @@ msgid "Can't invoke PGP"
+ msgstr "Kann PGP nicht aufrufen"
+
+ #: pgp.c:1590
+-#, fuzzy, c-format
+ msgid "PGP (e)ncrypt, (s)ign, sign (a)s, (b)oth, %s format, or (c)lear? "
+-msgstr "PGP (v)erschl., (s)ign., sign. (a)ls, (b)eides, %s, (k)ein PGP? "
++msgstr "PGP (v)erschl., (s)ign., sign. (a)ls, (b)eides, %s-Format, (k)ein PGP? "
+
+ #: pgp.c:1591
+ msgid "PGP/M(i)ME"
+@@ -3533,22 +3528,19 @@ msgstr "(i)nline"
+ #. * it. The 'i' key is appended in this version.
+ #.
+ #: pgp.c:1597
+-#, fuzzy
+ msgid "esabfci"
+-msgstr "vsabpku"
++msgstr "vsabfki"
+
+ #. The keys accepted *must* be a prefix of the accepted keys in the "if"
+ #. * clause above since the switch statement below depends on it.
+ #.
+ #: pgp.c:1604
+-#, fuzzy
+ msgid "PGP (e)ncrypt, (s)ign, sign (a)s, (b)oth, or (c)lear? "
+ msgstr "PGP (v)erschl., (s)ign., sign. (a)ls, (b)eides, %s, (k)ein PGP? "
+
+ #: pgp.c:1605
+-#, fuzzy
+ msgid "esabfc"
+-msgstr "vsabpku"
++msgstr "vsabfk"
+
+ #: pgpinvoke.c:308
+ msgid "Fetching PGP key..."
diff --git a/use_usr_bin_editor.patch b/use_usr_bin_editor.patch
new file mode 100644
index 000000000000..87abb8a5ee99
--- /dev/null
+++ b/use_usr_bin_editor.patch
@@ -0,0 +1,40 @@
+From: Antonio Radici <antonio@debian.org>
+Date: Thu, 27 Feb 2014 16:06:15 +0100
+Subject: use_usr_bin_editor
+
+Even if EDITOR is not set, mutt will always use /usr/bin/editor
+(which is set by update-alternatives), rather than falling back
+to vi.
+
+Gbp-Pq: Topic debian-specific
+---
+ init.c | 2 +-
+ init.h | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/init.c b/init.c
+index 1401664..e0a0757 100644
+--- a/init.c
++++ b/init.c
+@@ -3129,7 +3129,7 @@ void mutt_init (int skip_sys_rc, LIST *commands)
+ {
+ p = getenv ("EDITOR");
+ if (!p)
+- p = "vi";
++ p = "/usr/bin/editor";
+ }
+ Editor = safe_strdup (p);
+ Visual = safe_strdup (p);
+diff --git a/init.h b/init.h
+index 0e4f47f..7768683 100644
+--- a/init.h
++++ b/init.h
+@@ -686,7 +686,7 @@ struct option_t MuttVars[] = {
+ ** .pp
+ ** This variable specifies which editor is used by mutt.
+ ** It defaults to the value of the \fC$$$VISUAL\fP, or \fC$$$EDITOR\fP, environment
+- ** variable, or to the string ``vi'' if neither of those are set.
++ ** variable, or to the string ``/usr/bin/editor'' if neither of those are set.
+ ** .pp
+ ** The \fC$$editor\fP string may contain a \fI%s\fP escape, which will be replaced by the name
+ ** of the file to be edited. If the \fI%s\fP escape does not appear in \fC$$editor\fP, a
diff --git a/xtitles.patch b/xtitles.patch
new file mode 100644
index 000000000000..df64beb1485c
--- /dev/null
+++ b/xtitles.patch
@@ -0,0 +1,198 @@
+From: Cristoph Berg <myon@debian.org>
+Date: Thu, 27 Feb 2014 12:25:51 +0100
+Subject: xtitles
+
+This is the xterm title patch as found on the mutt mailing lists.
+
+* Changes made:
+ - 2007-01-27 myon: using %P caused a segfault, updated status.c to catch
+ menu==NULL.
+ - 2007-02-20 myon: make the note about the xterm_set_titles defaults a
+ comment.
+ - 2008-08-02 myon: move set_xterm_* prototypes into the proper header file
+ (cleaner code, no functional change, evades conflict with sidebar patch)
+
+Signed-off-by: Matteo F. Vescovi <mfvescovi@gmail.com>
+
+Gbp-Pq: Topic features
+---
+ curs_main.c | 20 ++++++++++++++++++++
+ globals.h | 2 ++
+ init.c | 20 ++++++++++++++++++++
+ init.h | 21 +++++++++++++++++++++
+ mutt.h | 1 +
+ mutt_menu.h | 2 ++
+ pager.c | 7 +++++++
+ status.c | 2 ++
+ 8 files changed, 75 insertions(+)
+
+diff --git a/curs_main.c b/curs_main.c
+index aa4b044..16ddbc9 100644
+--- a/curs_main.c
++++ b/curs_main.c
+@@ -110,6 +110,19 @@ static const char *No_visible = N_("No visible messages.");
+
+ extern size_t UngetCount;
+
++#define ASCII_CTRL_G 0x07
++#define ASCII_CTRL_OPEN_SQUARE_BRAKET 0x1b
++
++void set_xterm_title_bar(char *title)
++{
++ fprintf(stderr ,"%c]2;%s%c", ASCII_CTRL_OPEN_SQUARE_BRAKET, title, ASCII_CTRL_G);
++}
++
++void set_xterm_icon_name(char *name)
++{
++ fprintf(stderr, "%c]1;%s%c", ASCII_CTRL_OPEN_SQUARE_BRAKET, name, ASCII_CTRL_G);
++}
++
+ void index_make_entry (char *s, size_t l, MUTTMENU *menu, int num)
+ {
+ format_flag flag = M_FORMAT_MAKEPRINT | M_FORMAT_ARROWCURSOR | M_FORMAT_INDEX;
+@@ -560,6 +573,13 @@ int mutt_index_menu (void)
+ mutt_paddstr (COLS, buf);
+ NORMAL_COLOR;
+ menu->redraw &= ~REDRAW_STATUS;
++ if (option(OPTXTERMSETTITLES))
++ {
++ menu_status_line (buf, sizeof (buf), menu, NONULL (XtermTitle));
++ set_xterm_title_bar(buf);
++ menu_status_line (buf, sizeof (buf), menu, NONULL (XtermIcon));
++ set_xterm_icon_name(buf);
++ }
+ }
+
+ menu->redraw = 0;
+diff --git a/globals.h b/globals.h
+index 6fefe5b..584cd0c 100644
+--- a/globals.h
++++ b/globals.h
+@@ -141,6 +141,8 @@ WHERE char *Tempdir;
+ WHERE char *Tochars;
+ WHERE char *Username;
+ WHERE char *Visual;
++WHERE char *XtermTitle;
++WHERE char *XtermIcon;
+
+ WHERE char *CurrentFolder;
+ WHERE char *LastFolder;
+diff --git a/init.c b/init.c
+index cc3cf4b..81bb9e7 100644
+--- a/init.c
++++ b/init.c
+@@ -1877,6 +1877,26 @@ static int parse_set (BUFFER *tmp, BUFFER *s, unsigned long data, BUFFER *err)
+ toggle_option (MuttVars[idx].data);
+ else
+ set_option (MuttVars[idx].data);
++
++ /* sanity check for xterm */
++ if ((mutt_strcmp (MuttVars[idx].option, "xterm_set_titles") == 0)
++ && option (OPTXTERMSETTITLES))
++ {
++ char *ep = getenv ("TERM");
++ /* Make sure that the terminal can take the control codes */
++ if (ep == NULL) unset_option (MuttVars[idx].data);
++ else if (mutt_strncasecmp (ep, "xterm", 5) &&
++ mutt_strncasecmp (ep, "color-xterm", 11) &&
++ mutt_strncasecmp (ep, "eterm", 5) &&
++ mutt_strncasecmp (ep, "kterm", 5) &&
++ mutt_strncasecmp (ep, "nxterm", 6) &&
++ mutt_strncasecmp (ep, "putty", 5) &&
++ mutt_strncasecmp (ep, "screen", 6) &&
++ mutt_strncasecmp (ep, "cygwin", 6) &&
++ mutt_strncasecmp (ep, "rxvt", 4) )
++ unset_option (MuttVars[idx]. data);
++
++ }
+ }
+ else if (myvar || DTYPE (MuttVars[idx].type) == DT_STR ||
+ DTYPE (MuttVars[idx].type) == DT_PATH ||
+diff --git a/init.h b/init.h
+index 80d05ce..cf8e75a 100644
+--- a/init.h
++++ b/init.h
+@@ -3412,6 +3412,27 @@ struct option_t MuttVars[] = {
+ ** Also see the $$read_inc, $$net_inc and $$time_inc variables and the
+ ** ``$tuning'' section of the manual for performance considerations.
+ */
++ {"xterm_icon", DT_STR, R_BOTH, UL &XtermIcon, UL "M%?n?AIL&ail?"},
++ /*
++ ** .pp
++ ** Controls the format of the icon title, as long as xterm_set_titles
++ ** is enabled. This string is identical in formatting to the one used by
++ ** ``$$status_format''.
++ */
++ {"xterm_set_titles", DT_BOOL, R_BOTH, OPTXTERMSETTITLES, 0},
++ /* The default must be off to force in the validity checking. */
++ /*
++ ** .pp
++ ** Controls whether mutt sets the xterm title bar and icon name
++ ** (as long as you are in an appropriate terminal).
++ */
++ {"xterm_title", DT_STR, R_BOTH, UL &XtermTitle, UL "Mutt with %?m?%m messages&no messages?%?n? [%n NEW]?"},
++ /*
++ ** .pp
++ ** Controls the format of the title bar of the xterm provided that
++ ** xterm_set_titles has been set. This string is identical in formatting
++ ** to the one used by ``$$status_format''.
++ */
+ /*--*/
+ { NULL, 0, 0, 0, 0 }
+ };
+diff --git a/mutt.h b/mutt.h
+index dcd7dc5..1d7e177 100644
+--- a/mutt.h
++++ b/mutt.h
+@@ -449,6 +449,7 @@ enum
+ OPTWRAPSEARCH,
+ OPTWRITEBCC, /* write out a bcc header? */
+ OPTXMAILER,
++ OPTXTERMSETTITLES,
+
+ OPTCRYPTUSEGPGME,
+ OPTCRYPTUSEPKA,
+diff --git a/mutt_menu.h b/mutt_menu.h
+index d459bef..82abecd 100644
+--- a/mutt_menu.h
++++ b/mutt_menu.h
+@@ -103,6 +103,8 @@ void menu_current_middle (MUTTMENU *);
+ void menu_current_bottom (MUTTMENU *);
+ void menu_check_recenter (MUTTMENU *);
+ void menu_status_line (char *, size_t, MUTTMENU *, const char *);
++void set_xterm_title_bar (char *title);
++void set_xterm_icon_name (char *name);
+
+ MUTTMENU *mutt_new_menu (int);
+ void mutt_menuDestroy (MUTTMENU **);
+diff --git a/pager.c b/pager.c
+index 486d8c8..23eb8ca 100644
+--- a/pager.c
++++ b/pager.c
+@@ -1812,6 +1812,13 @@ mutt_pager (const char *banner, const char *fname, int flags, pager_t *extra)
+ mutt_paddstr (COLS, bn);
+ }
+ NORMAL_COLOR;
++ if (option(OPTXTERMSETTITLES))
++ {
++ menu_status_line (buffer, sizeof (buffer), index, NONULL (XtermTitle));
++ set_xterm_title_bar(buffer);
++ menu_status_line (buffer, sizeof (buffer), index, NONULL (XtermIcon));
++ set_xterm_icon_name(buffer);
++ }
+ }
+
+ if ((redraw & REDRAW_INDEX) && index)
+diff --git a/status.c b/status.c
+index 1bb9a5a..6051e3a 100644
+--- a/status.c
++++ b/status.c
+@@ -195,6 +195,8 @@ status_format_str (char *buf, size_t buflen, size_t col, char op, const char *sr
+ break;
+
+ case 'P':
++ if (!menu)
++ break;
+ if (menu->top + menu->pagelen >= menu->max)
+ cp = menu->top ? "end" : "all";
+ else