WARNING: Run the following script before configure: aclocal -I m4 autoheader automake --foreign autoconf -- Vsevolod Volkov diff -udprP mutt-1.10.0.orig/ChangeLog.nntp mutt-1.10.0/ChangeLog.nntp --- mutt-1.10.0.orig/ChangeLog.nntp 1970-01-01 03:00:00.000000000 +0300 +++ mutt-1.10.0/ChangeLog.nntp 2018-06-16 17:38:41.196958026 +0300 @@ -0,0 +1,448 @@ +* Sat Jun 16 2018 Vsevolod Volkov +- update to 1.10.0 + +* Thu Apr 19 2018 Vsevolod Volkov +- update to 1.9.5 + +* Sat Sep 9 2017 Vsevolod Volkov +- update to 1.9.0 + +* Sun Feb 26 2017 Vsevolod Volkov +- update to 1.8.0 + +* Sun Dec 18 2016 Vsevolod Volkov +- update to 1.7.2 + +* Sun Oct 9 2016 Vsevolod Volkov +- update to 1.7.1 + +* Wed Aug 31 2016 Vsevolod Volkov +- newsgroups aren't marked as new on first load + +* Wed Aug 24 2016 Vsevolod Volkov +- update to 1.7.0 +- fixed new groups marking +- fixed unread messages counter update + +* Sat Aug 6 2016 Vsevolod Volkov +- update to 1.6.2 + +* Sat Aug 6 2016 Vsevolod Volkov +- update to 1.6.1 + +* Wed Apr 6 2016 Vsevolod Volkov +- update to 1.6.0 +- %R changed to %x in format strings + +* Wed Nov 25 2015 Vsevolod Volkov +- fixed memory leaks +- fixed SIGSEGV when reading hcache in some cases + +* Tue Nov 10 2015 Vsevolod Volkov +- fixed error compiling with nntp and without imap or pop3 +- fixed error loading articles after and + +* Wed Sep 2 2015 Vsevolod Volkov +- update to 1.5.24 +- new option nntp_listgroup +- use range in LISTGROUP command if possible + +* Thu Mar 13 2014 Vsevolod Volkov +- update to 1.5.23 + +* Tue Oct 29 2013 Vsevolod Volkov +- minor bug fixed while removing new articles + +* Fri Oct 18 2013 Vsevolod Volkov +- update to 1.5.22 + +* Tue Nov 27 2012 Vsevolod Volkov +- SASL authentication +- new option nntp_authenticators + +* Fri Nov 16 2012 Vsevolod Volkov +- 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 +- internal header caching replaced with hcache +- new option newsgroups_charset + +* Wed Sep 16 2010 Vsevolod Volkov +- update to 1.5.21 + +* Thu Aug 13 2009 Vsevolod Volkov +- fixed writting references in nntp_save_cache_group() + +* Tue Jun 15 2009 Vsevolod Volkov +- update to 1.5.20 + +* Tue Mar 20 2009 Vsevolod Volkov +- save Date: header of recorded outgoing articles + +* Tue Jan 6 2009 Vsevolod Volkov +- update to 1.5.19 + +* Mon May 19 2008 Vsevolod Volkov +- update to 1.5.18 +- fixed SIGSEGV when followup or forward to newsgroup + +* Sun Nov 4 2007 Vsevolod Volkov +- update to 1.5.17 + +* Tue Jul 3 2007 Vsevolod Volkov +- fixed arguments of nntp_format_str() + +* Fri Jun 15 2007 Vsevolod Volkov +- fixed error selecting news group + +* Tue Jun 12 2007 Vsevolod Volkov +- update to 1.5.16 + +* Wed Apr 11 2007 Vsevolod Volkov +- fixed posting error if $smtp_url is set +- added support of print-style sequence %R (x-comment-to) + +* Sun Apr 8 2007 Vsevolod Volkov +- update to 1.5.15 +- nntp://... url changed to news://... +- added indicator of fetching descriptions progress + +* Tue Feb 28 2007 Vsevolod Volkov +- update to 1.5.14 + +* Tue Aug 15 2006 Vsevolod Volkov +- update to 1.5.13 + +* Mon Jul 17 2006 Vsevolod Volkov +- update to 1.5.12 +- fixed reading empty .newsrc + +* Sat Sep 17 2005 Vsevolod Volkov +- update to 1.5.11 + +* Sat Aug 13 2005 Vsevolod Volkov +- update to 1.5.10 + +* Sun Mar 13 2005 Vsevolod Volkov +- update to 1.5.9 + +* Sun Feb 13 2005 Vsevolod Volkov +- update to 1.5.8 + +* Sat Feb 5 2005 Vsevolod Volkov +- update to 1.5.7 +- function mutt_update_list_file() moved to newsrc.c and changed algorithm + +* Thu Jul 8 2004 Vsevolod Volkov +- fixed error in nntp_logout_all() + +* Sat Apr 3 2004 Vsevolod Volkov +- 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 +- update to 1.5.6 + +* Thu Dec 18 2003 Vsevolod Volkov +- fixed compose menu + +* Thu Nov 6 2003 Vsevolod Volkov +- update to 1.5.5.1 + +* Wed Nov 5 2003 Vsevolod Volkov +- update to 1.5.5 +- added space after newsgroup name in .newsrc file + +* Sun May 18 2003 Vsevolod Volkov +- fixed SIGSEGV when posting article + +* Sat Mar 22 2003 Vsevolod Volkov +- update to 1.5.4 + +* Sat Dec 21 2002 Vsevolod Volkov +- update to 1.5.3 +- replace safe_free calls by the FREE macro + +* Fri Dec 6 2002 Vsevolod Volkov +- update to 1.5.2 +- nntp authentication can be passed after any command + +* Sat May 4 2002 Vsevolod Volkov +- update to 1.5.1 + +* Thu May 2 2002 Vsevolod Volkov +- update to 1.3.99 + +* Wed Mar 13 2002 Vsevolod Volkov +- update to 1.3.28 +- fixed SIGSEGV in , , , + functions +- fixed message about nntp reconnect +- fixed 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 +- update to 1.3.27 + +* Fri Jan 18 2002 Vsevolod Volkov +- update to 1.3.26 + +* Thu Jan 3 2002 Vsevolod Volkov +- update to 1.3.25 +- accelerated speed of access to news->newsgroups hash (by ) +- added default content disposition + +* Mon Dec 3 2001 Vsevolod Volkov +- update to 1.3.24 + +* Fri Nov 9 2001 Vsevolod Volkov +- update to 1.3.23.2 +- fixed segfault if mutt_conn_find() returns null + +* Wed Oct 31 2001 Vsevolod Volkov +- update to 1.3.23.1 +- added support of LISTGROUP command +- added support for servers with broken overview +- disabled function on news server +- fixed error storing bad authentication information + +* Wed Oct 10 2001 Vsevolod Volkov +- update to 1.3.23 +- fixed typo in buffy.c +- added substitution of %s parameter in $inews variable + +* Fri Aug 31 2001 Vsevolod Volkov +- update to 1.3.22.1 +- update to 1.3.22 + +* Thu Aug 23 2001 Vsevolod Volkov +- update to 1.3.21 + +* Wed Jul 25 2001 Vsevolod Volkov +- 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 +- update to 1.3.19 + +* Sat May 5 2001 Vsevolod Volkov +- 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 +- update to 1.3.17 +- support for 'LIST NEWSGROUPS' command to read descriptions + +* Fri Mar 2 2001 Vsevolod Volkov +- update to 1.3.16 + +* Wed Feb 14 2001 Vsevolod Volkov +- update to 1.3.15 + +* Sun Jan 28 2001 Vsevolod Volkov +- update to 1.3.14 +- show number of tagged messages patch from Felix von Leitner + +* Sun Dec 31 2000 Vsevolod Volkov +- update to 1.3.13 + +* Sat Dec 30 2000 Vsevolod Volkov +- Fixed problem if last article in group is deleted + +* Fri Dec 22 2000 Vsevolod Volkov +- Fixed checking of XGTITLE command on some servers + +* Mon Dec 18 2000 Vsevolod Volkov +- Added \r in AUTHINFO commands + +* Mon Nov 27 2000 Vsevolod Volkov +- update to 1.3.12 + +* Wed Nov 1 2000 Vsevolod Volkov +- update to 1.3.11 +- fixed error opening newsgroup from mutt started with -g or -G + +* Thu Oct 12 2000 Vsevolod Volkov +- update to 1.3.10 +- hotkey 'G' (get-message) replaced with '^G' + +* Thu Sep 21 2000 Vsevolod Volkov +- 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 +- fixed catchup in index +- fixed nntp_open_mailbox() + +* Sat Sep 2 2000 Vsevolod Volkov +- functions and 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 +- update to 1.3.8 +- new option show_only_unread +- add newsgroup completion + +* Fri Aug 4 2000 Vsevolod Volkov +- update to 1.3.7 + +* Sat Jul 29 2000 Vsevolod Volkov +- update to 1.3.6 + +* Sun Jul 9 2000 Vsevolod Volkov +- 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 +- update to 1.3.4 + +* Wed Jun 14 2000 Vsevolod Volkov +- don't substitute current newsgroup with deleted new messages + +* Mon Jun 12 2000 Vsevolod Volkov +- 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 +- update to 1.3.2 + +* Sat May 20 2000 Vsevolod Volkov +- update to 1.3.1 + +* Fri May 12 2000 Vsevolod Volkov +- update to 1.3 + +* Thu May 11 2000 Vsevolod Volkov +- update to 1.2 + +* Thu May 4 2000 Vsevolod Volkov +- update to 1.1.14 + +* Sun Apr 23 2000 Vsevolod Volkov +- update to 1.1.12 + +* Fri Apr 7 2000 Vsevolod Volkov +- add substitution of newsgroup with new messages by default + +* Wed Apr 5 2000 Vsevolod Volkov +- 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 +- 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 +- remove 'X-FTN-Origin' header processing + +* Thu Mar 30 2000 Vsevolod Volkov +- update to 1.1.11 +- update to 1.1.10 + +* Thu Mar 23 2000 Vsevolod Volkov +- fix mutt_select_newsserver() +- remove 'toggle-mode' function +- add 'change-newsgroup' function + +* Wed Mar 22 2000 Vsevolod Volkov +- fix server-hook + +* Tue Mar 21 2000 Vsevolod Volkov +- fix error 'bounce' function after 'post' +- add 'forward to newsgroup' function + +* Mon Mar 20 2000 Vsevolod Volkov +- '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 +- update to 1.1.9 +- remove deleted newsgroups from list + +* Mon Mar 13 2000 Vsevolod Volkov +- update .newsrc in browser + +* Sun Mar 12 2000 Vsevolod Volkov +- reload .newsrc if externally modified +- fix active cache update + +* Sun Mar 5 2000 Vsevolod Volkov +- update to 1.1.8 + +* Sat Mar 4 2000 Vsevolod Volkov +- patch *.update_list_file is not required +- count lines when loading descriptions +- remove cache of unsubscribed newsgroups + +* Thu Mar 2 2000 Vsevolod Volkov +- load list of newsgroups from cache faster + +* Wed Mar 1 2000 Vsevolod Volkov +- update to 1.1.7 + +* Tue Feb 29 2000 Vsevolod Volkov +- fix unread messages in browser +- fix newsrc_gen_entries() + +* Mon Feb 28 2000 Vsevolod Volkov +- 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 +- update to 1.1.5 + +* Thu Feb 24 2000 Vsevolod Volkov +- fix updating new messages in cache + +* Mon Feb 21 2000 Vsevolod Volkov +- change default cache filenames +- fix updating new messages in cache + +* Fri Feb 18 2000 Vsevolod Volkov +- fix segmentation fault in news groups browser + +* Tue Feb 15 2000 Vsevolod Volkov +- update to 1.1.4 + +* Thu Feb 10 2000 Vsevolod Volkov +- update to 1.1.3 + +* Sun Jan 30 2000 Vsevolod Volkov +- 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 +- update to 1.1.2 diff -udprP mutt-1.10.0.orig/OPS mutt-1.10.0/OPS --- mutt-1.10.0.orig/OPS 2018-05-15 00:51:53.000000000 +0300 +++ mutt-1.10.0/OPS 2018-06-16 17:22:30.190469794 +0300 @@ -8,14 +8,16 @@ OP_BOUNCE_MESSAGE "remail a message to a 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 fiel 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)" @@ -89,8 +94,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" @@ -98,11 +108,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" @@ -142,6 +155,7 @@ OP_PAGER_HIDE_QUOTED "toggle display of 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" @@ -151,6 +165,7 @@ OP_QUERY "query external program for add 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)" @@ -165,18 +180,22 @@ OP_SEARCH_TOGGLE "toggle search pattern 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 -udprP mutt-1.10.0.orig/PATCHES mutt-1.10.0/PATCHES --- mutt-1.10.0.orig/PATCHES 2017-12-03 05:10:17.000000000 +0200 +++ mutt-1.10.0/PATCHES 2018-06-16 17:22:30.190469794 +0300 @@ -0,0 +1 @@ +vvv.nntp diff -udprP mutt-1.10.0.orig/account.c mutt-1.10.0/account.c --- mutt-1.10.0.orig/account.c 2017-12-18 22:27:19.000000000 +0200 +++ mutt-1.10.0/account.c 2018-06-16 17:22:30.191469778 +0300 @@ -51,8 +51,17 @@ int mutt_account_match (const ACCOUNT* a user = PopUser; #endif +#ifdef USE_NNTP + if (a1->type == MUTT_ACCT_TYPE_NNTP && NntpUser) + user = NntpUser; +#endif + if (a1->flags & a2->flags & MUTT_ACCT_USER) return (!strcmp (a1->user, a2->user)); +#ifdef USE_NNTP + if (a1->type == MUTT_ACCT_TYPE_NNTP) + return a1->flags & MUTT_ACCT_USER && a1->user[0] ? 0 : 1; +#endif if (a1->flags & MUTT_ACCT_USER) return (!strcmp (a1->user, user)); if (a2->flags & MUTT_ACCT_USER) @@ -130,6 +139,16 @@ void mutt_account_tourl (ACCOUNT* accoun } #endif +#ifdef USE_NNTP + if (account->type == MUTT_ACCT_TYPE_NNTP) + { + if (account->flags & MUTT_ACCT_SSL) + url->scheme = U_NNTPS; + else + url->scheme = U_NNTP; + } +#endif + url->host = account->host; if (account->flags & MUTT_ACCT_PORT) url->port = account->port; @@ -155,6 +174,10 @@ int mutt_account_getuser (ACCOUNT* accou else if ((account->type == MUTT_ACCT_TYPE_POP) && PopUser) strfcpy (account->user, PopUser, sizeof (account->user)); #endif +#ifdef USE_NNTP + else if ((account->type == MUTT_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* accou else if ((account->type == MUTT_ACCT_TYPE_SMTP) && SmtpPass) strfcpy (account->pass, SmtpPass, sizeof (account->pass)); #endif +#ifdef USE_NNTP + else if ((account->type == MUTT_ACCT_TYPE_NNTP) && NntpPass) + strfcpy (account->pass, NntpPass, sizeof (account->pass)); +#endif else if (option (OPTNOCURSES)) return -1; else diff -udprP mutt-1.10.0.orig/account.h mutt-1.10.0/account.h --- mutt-1.10.0.orig/account.h 2018-05-15 00:51:53.000000000 +0300 +++ mutt-1.10.0/account.h 2018-06-16 17:22:30.191469778 +0300 @@ -29,7 +29,8 @@ enum MUTT_ACCT_TYPE_NONE = 0, MUTT_ACCT_TYPE_IMAP, MUTT_ACCT_TYPE_POP, - MUTT_ACCT_TYPE_SMTP + MUTT_ACCT_TYPE_SMTP, + MUTT_ACCT_TYPE_NNTP }; /* account flags */ diff -udprP mutt-1.10.0.orig/attach.h mutt-1.10.0/attach.h --- mutt-1.10.0.orig/attach.h 2017-12-18 22:31:37.000000000 +0200 +++ mutt-1.10.0/attach.h 2018-06-16 17:22:30.191469778 +0300 @@ -71,7 +71,7 @@ void mutt_print_attachment_list (ATTACH_ void mutt_attach_bounce (FILE *, HEADER *, ATTACH_CONTEXT *, BODY *); void mutt_attach_resend (FILE *, HEADER *, ATTACH_CONTEXT *, BODY *); -void mutt_attach_forward (FILE *, HEADER *, ATTACH_CONTEXT *, BODY *); +void mutt_attach_forward (FILE *, HEADER *, ATTACH_CONTEXT *, BODY *, int); void mutt_attach_reply (FILE *, HEADER *, ATTACH_CONTEXT *, BODY *, int); void mutt_actx_add_attach (ATTACH_CONTEXT *actx, ATTACHPTR *attach); diff -udprP mutt-1.10.0.orig/browser.c mutt-1.10.0/browser.c --- mutt-1.10.0.orig/browser.c 2018-05-15 00:51:53.000000000 +0300 +++ mutt-1.10.0/browser.c 2018-06-16 17:22:30.192469762 +0300 @@ -32,6 +32,9 @@ #ifdef USE_IMAP #include "imap.h" #endif +#ifdef USE_NNTP +#include "nntp.h" +#endif #include #include @@ -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; @@ -134,9 +150,17 @@ static void browser_sort (struct browser 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_COUNT: @@ -367,8 +391,112 @@ folder_format_str (char *dest, size_t de return (src); } +#ifdef USE_NNTP +static const char * +newsgroup_format_str (char *dest, size_t destlen, size_t col, int cols, 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->nd->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 & MUTT_FORMAT_OPTIONAL) + { + if (folder->ff->nd->unread != 0) + mutt_FormatString (dest, destlen, col, cols, ifstring, + newsgroup_format_str, data, flags); + else + mutt_FormatString (dest, destlen, col, cols, 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, MUTT_ICONV_HOOK_FROM); + mutt_filter_unprintable (&buf); + + snprintf (tmp, sizeof (tmp), "%%%ss", fmt); + snprintf (dest, destlen, tmp, buf); + FREE (&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, BUFFY *b) + const char *name, const struct stat *s, BUFFY *b, + void *data) { if (state->entrylen == state->entrymax) { @@ -406,6 +534,10 @@ static void add_folder (MUTTMENU *m, str #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)++; } @@ -421,9 +553,35 @@ static void init_state (struct browser_s 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, NULL, nntp_data); + } + } + else +#endif /* USE_NNTP */ + { struct stat s; DIR *dp; struct dirent *de; @@ -492,17 +650,40 @@ static int examine_directory (MUTTMENU * tmp->msg_count = Context->msgcount; tmp->msg_unread = Context->unread; } - add_folder (menu, state, de->d_name, &s, tmp); + add_folder (menu, state, de->d_name, &s, tmp, NULL); } 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, NULL, nntp_data); + } + } + else +#endif + { BUFFY *tmp = Incoming; if (!Incoming) @@ -527,14 +708,21 @@ static int examine_mailboxes (MUTTMENU * #ifdef USE_IMAP if (mx_is_imap (tmp->path)) { - add_folder (menu, state, buffer, NULL, tmp); + add_folder (menu, state, buffer, NULL, tmp, NULL); continue; } #endif #ifdef USE_POP if (mx_is_pop (tmp->path)) { - add_folder (menu, state, buffer, NULL, tmp); + add_folder (menu, state, buffer, NULL, tmp, NULL); + continue; + } +#endif +#ifdef USE_NNTP + if (mx_is_nntp (tmp->path)) + { + add_folder (menu, state, buffer, NULL, tmp, NULL); continue; } #endif @@ -560,15 +748,20 @@ static int examine_mailboxes (MUTTMENU * s.st_mtime = st2.st_mtime; } - add_folder (menu, state, buffer, &s, tmp); + add_folder (menu, state, buffer, &s, tmp, NULL); } 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)); } @@ -579,6 +772,12 @@ static void folder_entry (char *s, size_ folder.ff = &((struct folder_file *) menu->data)[num]; folder.num = num; +#ifdef USE_NNTP + if (option (OPTNEWS)) + mutt_FormatString (s, slen, 0, MuttIndexWindow->cols, NONULL(GroupFormat), + newsgroup_format_str, (unsigned long) &folder, MUTT_FORMAT_ARROWCURSOR); + else +#endif mutt_FormatString (s, slen, 0, MuttIndexWindow->cols, NONULL(FolderFormat), folder_format_str, (unsigned long) &folder, MUTT_FORMAT_ARROWCURSOR); } @@ -599,6 +798,17 @@ static void init_menu (struct browser_st 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) snprintf (title, titlelen, _("Mailboxes [%d]"), mutt_buffy_check (0)); else @@ -654,6 +864,31 @@ void _mutt_select_file (char *f, size_t 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); @@ -750,6 +985,9 @@ void _mutt_select_file (char *f, size_t menu->tag = file_tag; menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_FOLDER, +#ifdef USE_NNTP + option (OPTNEWS) ? FolderNewsHelp : +#endif FolderHelp); mutt_push_current_menu (menu); @@ -889,7 +1127,11 @@ void _mutt_select_file (char *f, size_t } } +#ifdef USE_NNTP + if (buffy || option (OPTNEWS)) +#else if (buffy) +#endif { strfcpy (f, state.entry[menu->current].name, flen); mutt_expand_path (f, flen); @@ -946,14 +1188,6 @@ void _mutt_select_file (char *f, size_t 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); @@ -1055,6 +1289,11 @@ void _mutt_select_file (char *f, size_t case OP_CHANGE_DIRECTORY: +#ifdef USE_NNTP + if (option (OPTNEWS)) + break; +#endif + strfcpy (buf, LastDir, sizeof (buf)); #ifdef USE_IMAP if (!state.imap_browse) @@ -1321,6 +1560,209 @@ void _mutt_select_file (char *f, size_t 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, MuttIndexWindow->cols, + NONULL(GroupFormat), newsgroup_format_str, + (unsigned long) &folder, MUTT_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, 1); + 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, MuttIndexWindow->cols, + NONULL(GroupFormat), newsgroup_format_str, + (unsigned long) &folder, MUTT_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, NULL, nntp_data); + } + } + } + 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 -udprP mutt-1.10.0.orig/browser.h mutt-1.10.0/browser.h --- mutt-1.10.0.orig/browser.h 2017-12-18 22:27:19.000000000 +0200 +++ mutt-1.10.0/browser.h 2018-06-16 17:22:30.192469762 +0300 @@ -19,6 +19,10 @@ #ifndef _BROWSER_H #define _BROWSER_H 1 +#ifdef USE_NNTP +#include "nntp.h" +#endif + struct folder_file { mode_t mode; @@ -44,6 +48,9 @@ struct folder_file #endif unsigned has_buffy : 1; unsigned local : 1; /* folder is on local filesystem */ +#ifdef USE_NNTP + NNTP_DATA *nd; +#endif unsigned tagged : 1; }; diff -udprP mutt-1.10.0.orig/buffy.c mutt-1.10.0/buffy.c --- mutt-1.10.0.orig/buffy.c 2018-04-17 02:31:03.000000000 +0300 +++ mutt-1.10.0/buffy.c 2018-06-16 17:22:30.192469762 +0300 @@ -512,6 +512,9 @@ int mutt_buffy_check (int force) /* check device ID and serial number instead of comparing paths */ if (!Context || Context->magic == MUTT_IMAP || Context->magic == MUTT_POP +#ifdef USE_NNTP + || Context->magic == MUTT_NNTP +#endif || stat (Context->path, &contex_sb) != 0) { contex_sb.st_dev=0; @@ -535,6 +538,11 @@ int mutt_buffy_check (int force) tmp->magic = MUTT_POP; else #endif +#ifdef USE_NNTP + if ((tmp->magic == MUTT_NNTP) || mx_is_nntp (tmp->path)) + tmp->magic = MUTT_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)) { @@ -550,7 +558,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 == MUTT_IMAP || tmp->magic == MUTT_POP || tmp->magic == MUTT_NNTP ) +#else (( tmp->magic == MUTT_IMAP || tmp->magic == MUTT_POP ) +#endif ? mutt_strcmp (tmp->path, Context->path) : (sb.st_dev != contex_sb.st_dev || sb.st_ino != contex_sb.st_ino))) { diff -udprP mutt-1.10.0.orig/complete.c mutt-1.10.0/complete.c --- mutt-1.10.0.orig/complete.c 2017-12-03 05:10:17.000000000 +0200 +++ mutt-1.10.0/complete.c 2018-06-16 17:22:30.192469762 +0300 @@ -25,6 +25,9 @@ #include "mailbox.h" #include "imap.h" #endif +#ifdef USE_NNTP +#include "nntp.h" +#endif #include #include @@ -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 -udprP mutt-1.10.0.orig/compose.c mutt-1.10.0/compose.c --- mutt-1.10.0.orig/compose.c 2018-04-17 02:31:03.000000000 +0300 +++ mutt-1.10.0/compose.c 2018-06-16 17:22:30.193469746 +0300 @@ -32,11 +32,16 @@ #include "mailbox.h" #include "sort.h" #include "charset.h" +#include "mx.h" #ifdef MIXMASTER #include "remailer.h" #endif +#ifdef USE_NNTP +#include "nntp.h" +#endif + #include #include #include @@ -68,10 +73,20 @@ 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 */ }; +#ifdef USE_NNTP +int HeaderPadding[HDR_XCOMMENTTO + 1] = {0}; +#else int HeaderPadding[HDR_CRYPTINFO + 1] = {0}; +#endif int MaxHeaderWidth = 0; #define HDR_XOFFSET MaxHeaderWidth @@ -109,6 +124,14 @@ static const char * const Prompts[] = * than 15-20 character cells. */ N_("Sign as: ") +#ifdef USE_NNTP + /* L10N: Compose menu field. May not want to translate. */ + ,N_("Newsgroups: ") + /* L10N: Compose menu field. May not want to translate. */ + ,N_("Followup-To: ") + /* L10N: Compose menu field. May not want to translate. */ + ,N_("X-Comment-To: ") +#endif }; static const struct mapping_t ComposeHelp[] = { @@ -126,6 +149,21 @@ static const struct mapping_t ComposeHel { NULL, 0 } }; +#ifdef USE_NNTP +static struct mapping_t ComposeNewsHelp[] = { + { N_("Send"), OP_COMPOSE_SEND_MESSAGE }, + { N_("Abort"), OP_EXIT }, + /* L10N: compose menu help line entry */ + { N_("Newsgroups"), OP_COMPOSE_EDIT_NEWSGROUPS }, + /* L10N: compose menu help line entry */ + { N_("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 calc_header_width_padding (int idx, const char *header, int calc_max) { int width; @@ -161,7 +199,14 @@ static void init_header_padding (void) * the other fields look funny. */ calc_header_width_padding (HDR_CRYPTINFO, _(Prompts[HDR_CRYPTINFO]), 0); +#ifdef USE_NNTP + for (i = HDR_NEWSGROUPS; i <= HDR_XCOMMENTTO; i++) + calc_header_width_padding (i, _(Prompts[i]), 1); + + for (i = 0; i <= HDR_XCOMMENTTO; i++) +#else for (i = 0; i <= HDR_CRYPTINFO; i++) +#endif { HeaderPadding[i] += MaxHeaderWidth; if (HeaderPadding[i] < 0) @@ -357,9 +402,37 @@ static void draw_envelope_addr (int line static void draw_envelope (HEADER *msg, char *fcc) { draw_envelope_addr (HDR_FROM, msg->env->from); +#ifdef USE_NNTP + if (option (OPTNEWSSEND)) + { + SETCOLOR (MT_COLOR_COMPOSE_HEADER); + mutt_window_mvprintw (MuttIndexWindow, HDR_TO, 0, + "%*s", HeaderPadding[HDR_NEWSGROUPS], _(Prompts[HDR_NEWSGROUPS])); + NORMAL_COLOR; + mutt_paddstr (W, NONULL (msg->env->newsgroups)); + + SETCOLOR (MT_COLOR_COMPOSE_HEADER); + mutt_window_mvprintw (MuttIndexWindow, HDR_CC, 0, + "%*s", HeaderPadding[HDR_FOLLOWUPTO], _(Prompts[HDR_FOLLOWUPTO])); + NORMAL_COLOR; + mutt_paddstr (W, NONULL (msg->env->followup_to)); + + if (option (OPTXCOMMENTTO)) + { + SETCOLOR (MT_COLOR_COMPOSE_HEADER); + mutt_window_mvprintw (MuttIndexWindow, HDR_BCC, 0, + "%*s", HeaderPadding[HDR_XCOMMENTTO], _(Prompts[HDR_XCOMMENTTO])); + NORMAL_COLOR; + mutt_paddstr (W, NONULL (msg->env->x_comment_to)); + } + } + else +#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); + } SETCOLOR (MT_COLOR_COMPOSE_HEADER); mutt_window_mvprintw (MuttIndexWindow, HDR_SUBJECT, 0, @@ -712,6 +785,12 @@ int mutt_compose_menu (HEADER *msg, /* int oldSort, oldSortAux; struct stat st; compose_redraw_data_t rd; +#ifdef USE_NNTP + int news = 0; /* is it a news article ? */ + + if (option (OPTNEWSSEND)) + news++; +#endif init_header_padding (); @@ -722,6 +801,11 @@ int mutt_compose_menu (HEADER *msg, /* menu->offset = HDR_ATTACH; menu->make_entry = snd_entry; menu->tag = mutt_tag_attach; +#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); menu->custom_menu_redraw = compose_menu_redraw; menu->redraw_data = &rd; @@ -733,6 +817,9 @@ int mutt_compose_menu (HEADER *msg, /* while (loop) { +#ifdef USE_NNTP + unset_option (OPTNEWS); /* for any case */ +#endif switch (op = mutt_menuLoop (menu)) { case OP_COMPOSE_EDIT_FROM: @@ -740,6 +827,10 @@ int mutt_compose_menu (HEADER *msg, /* mutt_message_hook (NULL, msg, MUTT_SEND2HOOK); break; case OP_COMPOSE_EDIT_TO: +#ifdef USE_NNTP + if (news) + break; +#endif edit_address_list (HDR_TO, &msg->env->to); if (option (OPTCRYPTOPPORTUNISTICENCRYPT)) { @@ -749,6 +840,10 @@ int mutt_compose_menu (HEADER *msg, /* mutt_message_hook (NULL, msg, MUTT_SEND2HOOK); break; case OP_COMPOSE_EDIT_BCC: +#ifdef USE_NNTP + if (news) + break; +#endif edit_address_list (HDR_BCC, &msg->env->bcc); if (option (OPTCRYPTOPPORTUNISTICENCRYPT)) { @@ -758,6 +853,10 @@ int mutt_compose_menu (HEADER *msg, /* mutt_message_hook (NULL, msg, MUTT_SEND2HOOK); break; case OP_COMPOSE_EDIT_CC: +#ifdef USE_NNTP + if (news) + break; +#endif edit_address_list (HDR_CC, &msg->env->cc); if (option (OPTCRYPTOPPORTUNISTICENCRYPT)) { @@ -766,6 +865,62 @@ int mutt_compose_menu (HEADER *msg, /* } mutt_message_hook (NULL, msg, MUTT_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) + { + mutt_str_replace (&msg->env->newsgroups, buf); + move (HDR_TO, HDR_XOFFSET); + if (msg->env->newsgroups) + mutt_paddstr (W, msg->env->newsgroups); + else + clrtoeol (); + } + } + break; + case OP_COMPOSE_EDIT_FOLLOWUP_TO: + if (news) + { + if (msg->env->followup_to) + strfcpy (buf, msg->env->followup_to, sizeof (buf)); + else + buf[0] = 0; + if (mutt_get_field ("Followup-To: ", buf, sizeof (buf), 0) == 0) + { + mutt_str_replace (&msg->env->followup_to, buf); + move (HDR_CC, HDR_XOFFSET); + if (msg->env->followup_to) + mutt_paddstr (W, msg->env->followup_to); + else + clrtoeol (); + } + } + break; + case OP_COMPOSE_EDIT_X_COMMENT_TO: + if (news && option (OPTXCOMMENTTO)) + { + if (msg->env->x_comment_to) + strfcpy (buf, msg->env->x_comment_to, sizeof (buf)); + else + buf[0] = 0; + if (mutt_get_field ("X-Comment-To: ", buf, sizeof (buf), 0) == 0) + { + mutt_str_replace (&msg->env->x_comment_to, buf); + move (HDR_BCC, HDR_XOFFSET); + if (msg->env->x_comment_to) + mutt_paddstr (W, msg->env->x_comment_to); + else + clrtoeol (); + } + } + break; +#endif case OP_COMPOSE_EDIT_SUBJECT: if (msg->env->subject) strfcpy (buf, msg->env->subject, sizeof (buf)); @@ -910,6 +1065,9 @@ int mutt_compose_menu (HEADER *msg, /* break; case OP_COMPOSE_ATTACH_MESSAGE: +#ifdef USE_NNTP + case OP_COMPOSE_ATTACH_NEWS_MESSAGE: +#endif { char *prompt; HEADER *h; @@ -917,7 +1075,22 @@ int mutt_compose_menu (HEADER *msg, /* 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 == MUTT_NNTP)) +#endif { strfcpy (fname, NONULL (Context->path), sizeof (fname)); mutt_pretty_mailbox (fname, sizeof (fname)); @@ -926,6 +1099,11 @@ int mutt_compose_menu (HEADER *msg, /* if (mutt_enter_fname (prompt, fname, sizeof (fname), 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)) @@ -933,6 +1111,9 @@ int mutt_compose_menu (HEADER *msg, /* #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 -udprP mutt-1.10.0.orig/config.h.in mutt-1.10.0/config.h.in --- mutt-1.10.0.orig/config.h.in 2018-05-15 04:09:08.000000000 +0300 +++ mutt-1.10.0/config.h.in 2018-06-16 17:22:30.193469746 +0300 @@ -640,6 +640,9 @@ /* Define if you want support for the IMAP protocol. */ #undef USE_IMAP +/* Define if you want support for the NNTP protocol. */ +#undef USE_NNTP + /* Define if you want support for the POP3 protocol. */ #undef USE_POP diff -udprP mutt-1.10.0.orig/configure.ac mutt-1.10.0/configure.ac --- mutt-1.10.0.orig/configure.ac 2018-05-15 00:51:53.000000000 +0300 +++ mutt-1.10.0/configure.ac 2018-06-16 17:22:30.193469746 +0300 @@ -633,6 +633,15 @@ AC_ARG_ENABLE(imap, AS_HELP_STRING([--en ]) 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]) @@ -640,7 +649,7 @@ AC_ARG_ENABLE(smtp, AS_HELP_STRING([--en 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 -udprP mutt-1.10.0.orig/curs_main.c mutt-1.10.0/curs_main.c --- mutt-1.10.0.orig/curs_main.c 2018-05-15 00:51:53.000000000 +0300 +++ mutt-1.10.0/curs_main.c 2018-06-16 17:37:28.758111855 +0300 @@ -22,6 +22,7 @@ #include "mutt.h" #include "mutt_curses.h" +#include "mx.h" #include "mutt_menu.h" #include "mailbox.h" #include "mapping.h" @@ -43,6 +44,10 @@ #include "mutt_crypt.h" +#ifdef USE_NNTP +#include "nntp.h" +#endif + #include #include @@ -547,12 +552,27 @@ static void index_menu_redraw (MUTTMENU menu->redraw = 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; @@ -571,7 +591,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 == MUTT_NNTP)) ? IndexNewsHelp : +#endif + IndexHelp); menu->custom_menu_redraw = index_menu_redraw; mutt_push_current_menu (menu); @@ -786,6 +810,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) { @@ -836,6 +863,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 == MUTT_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 == MUTT_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; @@ -946,11 +1128,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 (MUTT_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 (MUTT_LIMIT, NULL) == 0) || + mutt_pattern_func (MUTT_LIMIT, _("Limit to messages matching: ")) == 0) { if (menu->oldcurrent >= 0) { @@ -1195,15 +1399,22 @@ int mutt_index_menu (void) #endif 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 = MUTT_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"); @@ -1236,6 +1447,22 @@ int mutt_index_menu (void) strfcpy (buf, Context->path, sizeof (buf)); mutt_pretty_mailbox (buf, sizeof (buf)); } +#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 (mutt_enter_fname (cp, buf, sizeof (buf), 1) == -1) @@ -1255,6 +1482,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)); if (mx_get_magic (buf) <= 0) { @@ -1307,9 +1542,7 @@ int mutt_index_menu (void) * switch statement would need to be run. */ mutt_folder_hook (buf); - if ((Context = mx_open_mailbox (buf, - (option (OPTREADONLY) || op == OP_MAIN_CHANGE_FOLDER_READONLY) ? - MUTT_READONLY : 0, NULL)) != NULL) + if ((Context = mx_open_mailbox (buf, flags, NULL)) != NULL) { menu->current = ci_first_message (); } @@ -1320,6 +1553,12 @@ int mutt_index_menu (void) mutt_sb_set_open_buffy (); #endif +#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 == MUTT_NNTP)) ? IndexNewsHelp : IndexHelp); +#endif + mutt_clear_error (); mutt_buffy_check(1); /* force the buffy check after we have changed the folder */ @@ -1397,6 +1636,7 @@ int mutt_index_menu (void) CHECK_MSGCOUNT; CHECK_VISIBLE; CHECK_READONLY; + CHECK_ACL(MUTT_ACL_WRITE, _("Cannot break thread")); if ((Sort & SORT_MASK) != SORT_THREADS) mutt_error _("Threading is not enabled."); @@ -1432,7 +1672,7 @@ int mutt_index_menu (void) CHECK_VISIBLE; CHECK_READONLY; /* L10N: CHECK_ACL */ - CHECK_ACL(MUTT_ACL_DELETE, _("Cannot link threads")); + CHECK_ACL(MUTT_ACL_WRITE, _("Cannot link threads")); if ((Sort & SORT_MASK) != SORT_THREADS) mutt_error _("Threading is not enabled."); @@ -2068,6 +2308,20 @@ int mutt_index_menu (void) } break; +#ifdef USE_NNTP + case OP_CATCHUP: + CHECK_MSGCOUNT; + CHECK_READONLY; + CHECK_ATTACH + if (Context && Context->magic == MUTT_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; @@ -2326,6 +2580,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?")) != MUTT_YES) + { + if (Context && Context->magic == MUTT_NNTP && + !((NNTP_DATA *)Context->data)->allowed && + query_quadoption (OPT_TOMODERATED, + _("Posting to this group not allowed, may be moderated. Continue?")) != MUTT_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 -udprP mutt-1.10.0.orig/doc/manual.xml.head mutt-1.10.0/doc/manual.xml.head --- mutt-1.10.0.orig/doc/manual.xml.head 2018-05-15 00:51:53.000000000 +0300 +++ mutt-1.10.0/doc/manual.xml.head 2018-06-16 17:22:30.195469715 +0300 @@ -1755,6 +1755,26 @@ See also the $p + +Reading news via NNTP + + +If compiled with --enable-nntp 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 $NNTPSERVER environment +variable or from /etc/nntpserver file. Like other +news readers, info about subscribed newsgroups is saved in file by +$newsrc variable. The variable $news_cache_dir 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 header cache support. + + + + diff -udprP mutt-1.10.0.orig/doc/mutt.man mutt-1.10.0/doc/mutt.man --- mutt-1.10.0.orig/doc/mutt.man 2018-05-15 00:51:53.000000000 +0300 +++ mutt-1.10.0/doc/mutt.man 2018-06-16 17:22:30.195469715 +0300 @@ -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 [\-Enx] @@ -104,6 +104,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 -udprP mutt-1.10.0.orig/functions.h mutt-1.10.0/functions.h --- mutt-1.10.0.orig/functions.h 2018-05-15 00:51:53.000000000 +0300 +++ mutt-1.10.0/functions.h 2018-06-16 17:22:30.195469715 +0300 @@ -89,6 +89,10 @@ const struct binding_t OpMain[] = { /* m { "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" }, @@ -103,7 +107,15 @@ const struct binding_t OpMain[] = { /* m { "edit-label", OP_EDIT_LABEL, "Y" }, { "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" }, @@ -131,6 +143,9 @@ const struct binding_t OpMain[] = { /* m { "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" }, @@ -150,6 +165,10 @@ const struct binding_t OpMain[] = { /* m { "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, MUTT_ENTER_S }, { "mark-message", OP_MARK_MSG, "~" }, { "buffy-list", OP_BUFFY_LIST, "." }, @@ -162,7 +181,7 @@ const struct binding_t OpMain[] = { /* m { "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 }, { "root-message", OP_MAIN_ROOT_MESSAGE, NULL }, @@ -193,6 +212,10 @@ const struct binding_t OpPager[] = { /* { "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" }, @@ -204,8 +227,12 @@ const struct binding_t OpPager[] = { /* { "edit", OP_EDIT_MESSAGE, "e" }, { "edit-label", OP_EDIT_LABEL, "Y" }, { "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 }, @@ -227,6 +254,9 @@ const struct binding_t OpPager[] = { /* { "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 }, @@ -276,7 +306,7 @@ const struct binding_t OpPager[] = { /* { "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 }, { "root-message", OP_MAIN_ROOT_MESSAGE, NULL }, @@ -309,6 +339,10 @@ const struct binding_t OpAttach[] = { /* { "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, "|" }, @@ -334,6 +368,7 @@ const struct binding_t OpAttach[] = { /* 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" }, @@ -353,6 +388,11 @@ const struct binding_t OpCompose[] = { / { "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-attachment",OP_COMPOSE_RENAME_ATTACHMENT, "\017" }, @@ -405,14 +445,25 @@ const struct binding_t OpBrowser[] = { / { "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 -udprP mutt-1.10.0.orig/globals.h mutt-1.10.0/globals.h --- mutt-1.10.0.orig/globals.h 2018-05-15 00:51:53.000000000 +0300 +++ mutt-1.10.0/globals.h 2018-06-16 17:22:30.196469699 +0300 @@ -71,7 +71,7 @@ WHERE char *Inbox; WHERE char *Ispell; WHERE char *MailcapPath; WHERE char *Maildir; -#if defined(USE_IMAP) || defined(USE_POP) +#if defined(USE_IMAP) || defined(USE_POP) || defined(USE_NNTP) WHERE char *MessageCachedir; #endif #if USE_HCACHE @@ -99,6 +99,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; @@ -207,6 +218,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 ErrorHistSize; WHERE short HistSize; diff -udprP mutt-1.10.0.orig/hcache.c mutt-1.10.0/hcache.c --- mutt-1.10.0.orig/hcache.c 2017-12-18 22:31:37.000000000 +0200 +++ mutt-1.10.0/hcache.c 2018-06-16 17:22:30.196469699 +0300 @@ -534,6 +534,12 @@ dump_envelope(ENVELOPE * e, unsigned cha 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; } @@ -570,6 +576,12 @@ restore_envelope(ENVELOPE * e, const uns 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 -udprP mutt-1.10.0.orig/hdrline.c mutt-1.10.0/hdrline.c --- mutt-1.10.0.orig/hdrline.c 2018-05-15 00:51:53.000000000 +0300 +++ mutt-1.10.0/hdrline.c 2018-06-16 17:22:30.196469699 +0300 @@ -227,6 +227,7 @@ static char *apply_subject_mods (ENVELOP * %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 @@ -243,6 +244,8 @@ static char *apply_subject_mods (ENVELOP * %T = $to_chars * %u = user (login) name of author * %v = first name of author, unless from self + * %W = where user is (organization) + * %x = `x-comment-to:' field (if present and compiled with NNTP support) * %X = number of MIME attachments * %y = `x-label:' field (if present) * %Y = `x-label:' field (if present, tree unfolded, and != parent's x-label) @@ -474,6 +477,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 : ""); break; @@ -678,6 +687,22 @@ 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; + +#ifdef USE_NNTP + case 'x': + 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 'Z': ch = ' '; diff -udprP mutt-1.10.0.orig/headers.c mutt-1.10.0/headers.c --- mutt-1.10.0.orig/headers.c 2017-12-18 22:31:37.000000000 +0200 +++ mutt-1.10.0/headers.c 2018-06-16 17:22:30.196469699 +0300 @@ -115,6 +115,9 @@ void mutt_edit_headers (const char *edit $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 -udprP mutt-1.10.0.orig/init.c mutt-1.10.0/init.c --- mutt-1.10.0.orig/init.c 2018-05-15 00:51:53.000000000 +0300 +++ mutt-1.10.0/init.c 2018-06-16 17:22:30.197469683 +0300 @@ -3445,6 +3445,28 @@ void mutt_init (int skip_sys_rc, LIST *c Fqdn = safe_strdup(utsname.nodename); +#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 -udprP mutt-1.10.0.orig/init.h mutt-1.10.0/init.h --- mutt-1.10.0.orig/init.h 2018-05-15 00:51:53.000000000 +0300 +++ mutt-1.10.0/init.h 2018-06-16 17:29:50.406514947 +0300 @@ -200,6 +200,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 @@ -372,6 +386,14 @@ struct option_t MuttVars[] = { ** doesn't make intuitive sense. In those cases, it may be ** desirable to \fIunset\fP this variable. */ +#ifdef USE_NNTP + { "catchup_newsgroup", DT_QUAD, R_NONE, OPT_CATCHUP, MUTT_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" }, /* @@ -926,6 +948,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, MUTT_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 @@ -1024,6 +1056,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 }, /* */ @@ -1445,6 +1497,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, @@ -1468,6 +1521,8 @@ struct option_t MuttVars[] = { ** .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 ``X-Comment-To:'' field (if present and compiled with NNTP support) ** .dt %X .dd number of attachments ** (please see the ``$attachments'' section for possible speed effects) ** .dt %y .dd ``X-Label:'' field, if present @@ -1506,6 +1561,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 @@ -1793,6 +1867,15 @@ struct option_t MuttVars[] = { ** When \fIset\fP, the $$mime_type_query_command will be run before the ** mime.types lookup. */ +#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" }, /* @@ -1847,6 +1930,106 @@ struct option_t MuttVars[] = { ** See the $$status_format documentation for the values that can be formatted ** into this command. */ +#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_listgroup", DT_BOOL, R_NONE, OPTLISTGROUP, 1 }, + /* + ** .pp + ** This variable controls whether or not existence of each article is + ** checked when newsgroup is entered. + */ + { "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 @@ -2395,6 +2578,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, MUTT_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, MUTT_ASKYES }, /* ** .pp @@ -2875,6 +3068,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 #ifdef USE_SIDEBAR { "sidebar_delim_chars", DT_STR, R_SIDEBAR, UL &SidebarDelimChars, UL "/." }, /* @@ -4005,6 +4220,14 @@ struct option_t MuttVars[] = { {"xterm_set_titles", DT_SYN, R_NONE, UL "ts_enabled", 0 }, /* */ +#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 -udprP mutt-1.10.0.orig/keymap.c mutt-1.10.0/keymap.c --- mutt-1.10.0.orig/keymap.c 2018-02-03 21:18:49.000000000 +0200 +++ mutt-1.10.0/keymap.c 2018-06-16 17:22:30.198469667 +0300 @@ -800,7 +800,6 @@ void km_init (void) km_bindkey ("", MENU_MAIN, OP_DISPLAY_MESSAGE); km_bindkey ("x", MENU_PAGER, OP_EXIT); - km_bindkey ("i", MENU_PAGER, OP_EXIT); km_bindkey ("", MENU_PAGER, OP_PREV_LINE); km_bindkey ("", MENU_PAGER, OP_NEXT_PAGE); km_bindkey ("", MENU_PAGER, OP_PREV_PAGE); diff -udprP mutt-1.10.0.orig/mailbox.h mutt-1.10.0/mailbox.h --- mutt-1.10.0.orig/mailbox.h 2017-12-18 22:27:19.000000000 +0200 +++ mutt-1.10.0/mailbox.h 2018-06-16 17:22:30.198469667 +0300 @@ -78,6 +78,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 -udprP mutt-1.10.0.orig/main.c mutt-1.10.0/main.c --- mutt-1.10.0.orig/main.c 2018-05-15 00:51:53.000000000 +0300 +++ mutt-1.10.0/main.c 2018-06-16 17:22:30.198469667 +0300 @@ -65,6 +65,10 @@ #include #endif +#ifdef USE_NNTP +#include "nntp.h" +#endif + static const char *ReachingUs = N_("\ To contact the developers, please mail to .\n\ To report a bug, please contact the Mutt maintainers via gitlab:\n\ @@ -145,6 +149,8 @@ options:\n\ -e \tspecify a command to be executed after initialization\n\ -f \tspecify which mailbox to read\n\ -F \tspecify an alternate muttrc file\n\ + -g \tspecify a news server (if compiled with NNTP)\n\ + -G\t\tselect a newsgroup (if compiled with NNTP)\n\ -H \tspecify a draft file to read header and body from\n\ -i \tspecify a file which Mutt should include in the body\n\ -m \tspecify a default mailbox type\n\ @@ -298,6 +304,12 @@ static void show_version (void) "-USE_POP " #endif +#ifdef USE_NNTP + "+USE_NNTP " +#else + "-USE_NNTP " +#endif + #ifdef USE_IMAP "+USE_IMAP " #else @@ -593,6 +605,7 @@ init_extended_keys(); #define MUTT_NOSYSRC (1<<2) /* -n */ #define MUTT_RO (1<<3) /* -R */ #define MUTT_SELECT (1<<4) /* -y */ +#define MUTT_NEWS (1<<5) /* -g and -G */ int main (int argc, char **argv, char **environ) { @@ -682,7 +695,11 @@ int main (int argc, char **argv, char ** argv[nargc++] = argv[optind]; } +#ifdef USE_NNTP + if ((i = getopt (argc, argv, "+A:a:b:F:f:c:Dd:Ee:g:GH:s:i:hm:npQ:RvxyzZ")) != EOF) +#else if ((i = getopt (argc, argv, "+A:a:b:F:f:c:Dd:Ee:H:s:i:hm:npQ:RvxyzZ")) != EOF) +#endif switch (i) { case 'A': @@ -783,6 +800,20 @@ int main (int argc, char **argv, char ** flags |= MUTT_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 |= MUTT_SELECT | MUTT_NEWS; + break; +#endif + case 'z': flags |= MUTT_IGNORE; break; @@ -1229,6 +1260,18 @@ int main (int argc, char **argv, char ** } else if (flags & MUTT_SELECT) { +#ifdef USE_NNTP + if (flags & MUTT_NEWS) + { + set_option (OPTNEWS); + if(!(CurrentNewsSrv = nntp_select_server (NewsServer, 0))) + { + mutt_endwin (Errorbuf); + exit (1); + } + } + else +#endif if (!Incoming) { exit_endwin_msg = _("No incoming mailboxes defined."); @@ -1244,6 +1287,15 @@ int main (int argc, char **argv, char ** 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 -udprP mutt-1.10.0.orig/mutt.h mutt-1.10.0/mutt.h --- mutt-1.10.0.orig/mutt.h 2018-05-15 00:51:53.000000000 +0300 +++ mutt-1.10.0/mutt.h 2018-06-16 17:23:34.827448604 +0300 @@ -254,6 +254,9 @@ enum MUTT_XLABEL, MUTT_MIMEATTACH, MUTT_MIMETYPE, +#ifdef USE_NNTP + MUTT_NEWSGROUPS, +#endif /* Options for Mailcap lookup */ MUTT_EDIT, @@ -311,6 +314,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 @@ -329,6 +337,7 @@ enum #define SENDNOFREEHEADER (1<<10) /* Used by the -E flag */ #define SENDDRAFTFILE (1<<11) /* Used by the -H flag */ #define SENDTOSENDER (1<<12) +#define SENDNEWS (1<<13) /* flags for mutt_compose_menu() */ #define MUTT_COMPOSE_NOFREEHEADER (1<<0) @@ -351,6 +360,8 @@ enum OPTASCIICHARS, OPTASKBCC, OPTASKCC, + OPTASKFOLLOWUP, + OPTASKXCOMMENTTO, OPTATTACHSPLIT, OPTAUTOEDIT, OPTAUTOTAG, @@ -441,6 +452,9 @@ enum OPTMETOO, OPTMHPURGE, OPTMIMEFORWDECODE, +#ifdef USE_NNTP + OPTMIMESUBJECT, /* encode subject line with RFC2047 */ +#endif OPTMIMETYPEQUERYFIRST, OPTNARROWTREE, OPTPAGERSTOP, @@ -542,6 +556,17 @@ enum OPTPGPAUTOINLINE, OPTPGPREPLYINLINE, + /* news options */ + +#ifdef USE_NNTP + OPTSHOWNEWNEWS, + OPTSHOWONLYUNREAD, + OPTSAVEUNSUB, + OPTLISTGROUP, + OPTLOADDESC, + OPTXCOMMENTTO, +#endif + /* pseudo options */ OPTAUXSORT, /* (pseudo) using auxiliary sort function */ @@ -559,6 +584,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. @@ -569,6 +595,11 @@ enum OPTDONTHANDLEPGPKEYS, /* (pseudo) used to extract PGP keys */ OPTIGNOREMACROEVENTS, /* (pseudo) don't process macro/push/exec events while set */ +#ifdef USE_NNTP + OPTNEWS, /* (pseudo) used to change reader mode */ + OPTNEWSSEND, /* (pseudo) used to change behavior when posting */ +#endif + OPTMAX }; @@ -649,6 +680,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 */ @@ -836,7 +874,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 -udprP mutt-1.10.0.orig/mutt_sasl.c mutt-1.10.0/mutt_sasl.c --- mutt-1.10.0.orig/mutt_sasl.c 2017-12-18 22:31:37.000000000 +0200 +++ mutt-1.10.0/mutt_sasl.c 2018-06-16 17:22:30.199469651 +0300 @@ -192,6 +192,11 @@ int mutt_sasl_client_new (CONNECTION* co case MUTT_ACCT_TYPE_SMTP: service = "smtp"; break; +#ifdef USE_NNTP + case MUTT_ACCT_TYPE_NNTP: + service = "nntp"; + break; +#endif default: mutt_error (_("Unknown SASL profile")); return -1; diff -udprP mutt-1.10.0.orig/muttlib.c mutt-1.10.0/muttlib.c --- mutt-1.10.0.orig/muttlib.c 2018-04-17 02:31:03.000000000 +0300 +++ mutt-1.10.0/muttlib.c 2018-06-16 17:22:30.199469651 +0300 @@ -354,7 +354,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__ */ @@ -740,6 +740,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); @@ -1663,6 +1670,14 @@ int mutt_save_confirm (const char *s, st } } +#ifdef USE_NNTP + if (magic == MUTT_NNTP) + { + mutt_error _("Can't save message to news server."); + return 0; + } +#endif + if (stat (s, st) != -1) { if (magic == -1) diff -udprP mutt-1.10.0.orig/mx.c mutt-1.10.0/mx.c --- mutt-1.10.0.orig/mx.c 2018-04-17 02:31:03.000000000 +0300 +++ mutt-1.10.0/mx.c 2018-06-16 17:22:30.199469651 +0300 @@ -45,6 +45,10 @@ #include "pop.h" #endif +#ifdef USE_NNTP +#include "nntp.h" +#endif + #include "buffy.h" #ifdef USE_DOTLOCK @@ -88,6 +92,10 @@ struct mx_ops* mx_get_ops (int magic) case MUTT_COMPRESSED: return &mx_comp_ops; #endif +#ifdef USE_NNTP + case MUTT_NNTP: + return &mx_nntp_ops; +#endif default: return NULL; } @@ -378,6 +386,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; @@ -395,6 +419,11 @@ int mx_get_magic (const char *path) return MUTT_POP; #endif /* USE_POP */ +#ifdef USE_NNTP + if (mx_is_nntp (path)) + return MUTT_NNTP; +#endif /* USE_NNTP */ + if (stat (path, &st) == -1) { dprint (1, (debugfile, "mx_get_magic(): unable to stat %s: %s (errno %d).\n", @@ -803,6 +832,25 @@ int mx_close_mailbox (CONTEXT *ctx, int return 0; } +#ifdef USE_NNTP + if (ctx->unread && ctx->magic == MUTT_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 == MUTT_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 @@ -810,6 +858,12 @@ int mx_close_mailbox (CONTEXT *ctx, int read_msgs++; } +#ifdef USE_NNTP + /* don't need to move articles from newsgroup */ + if (ctx->magic == MUTT_NNTP) + read_msgs = 0; +#endif + if (read_msgs && quadoption (OPT_MOVE) != MUTT_NO) { char *p; diff -udprP mutt-1.10.0.orig/mx.h mutt-1.10.0/mx.h --- mutt-1.10.0.orig/mx.h 2017-12-18 22:31:37.000000000 +0200 +++ mutt-1.10.0/mx.h 2018-06-16 17:22:30.199469651 +0300 @@ -35,6 +35,9 @@ enum MUTT_MMDF, MUTT_MH, MUTT_MAILDIR, +#ifdef USE_NNTP + MUTT_NNTP, +#endif MUTT_IMAP, MUTT_POP #ifdef USE_COMPRESSED diff -udprP mutt-1.10.0.orig/newsrc.c mutt-1.10.0/newsrc.c --- mutt-1.10.0.orig/newsrc.c 1970-01-01 03:00:00.000000000 +0300 +++ mutt-1.10.0/newsrc.c 2018-06-16 17:22:30.200469636 +0300 @@ -0,0 +1,1259 @@ +/* + * Copyright (C) 1998 Brandon Long + * Copyright (C) 1999 Andrej Gritsenko + * Copyright (C) 2000-2017 Vsevolod Volkov + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +/* 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; + hash_insert (nserv->groups_hash, nntp_data->group, nntp_data); + + /* 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); + } + } + FREE (&hdata); + } + + /* 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, int cols, 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 & MUTT_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 = MUTT_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 |= MUTT_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 & MUTT_ACCT_USER) && acct.flags & MUTT_ACCT_USER) + { + conn->account.flags |= MUTT_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, sizeof (file), 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, 0); + } + + 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)); + } + } + FREE (&hdata); + } + 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], MUTT_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], MUTT_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 == MUTT_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 -udprP mutt-1.10.0.orig/nntp.c mutt-1.10.0/nntp.c --- mutt-1.10.0.orig/nntp.c 1970-01-01 03:00:00.000000000 +0300 +++ mutt-1.10.0/nntp.c 2018-06-16 17:22:30.200469636 +0300 @@ -0,0 +1,2486 @@ +/* + * Copyright (C) 1998 Brandon Long + * Copyright (C) 1999 Andrej Gritsenko + * Copyright (C) 2000-2017 Vsevolod Volkov + * + * 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 +#include +#include +#include + +#ifdef USE_SASL +#include +#include + +#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->hasLISTGROUPrange = 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; + nserv->hasLISTGROUPrange = 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->hasDATE) + 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 < MUTT_SOCK_LOG_FULL) + dprint (MUTT_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, MUTT_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 >= MUTT_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 < MUTT_SOCK_LOG_FULL) + { + if (strchr (buf, ' ')) + dprint (MUTT_SOCK_LOG_CMD, (debugfile, "%d> AUTHINFO SASL %s%s\n", + conn->fd, method, client_len ? " sasl_data" : "")); + else + dprint (MUTT_SOCK_LOG_CMD, (debugfile, "%d> sasl_data\n", conn->fd)); + } +#endif + client_len = 0; + if (mutt_socket_write_d (conn, buf, -1, MUTT_SOCK_LOG_FULL) < 0 || + mutt_socket_readln_d (inbuf, sizeof (inbuf), conn, MUTT_SOCK_LOG_FULL) < 0) + break; + if (mutt_strncmp (inbuf, "283 ", 4) && + mutt_strncmp (inbuf, "383 ", 4)) + { +#ifdef DEBUG + if (debuglevel < MUTT_SOCK_LOG_FULL) + dprint (MUTT_SOCK_LOG_CMD, (debugfile, "%d< %s\n", conn->fd, inbuf)); +#endif + break; + } +#ifdef DEBUG + if (debuglevel < MUTT_SOCK_LOG_FULL) + { + inbuf[3] = '\0'; + dprint (MUTT_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 >= MUTT_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?")) == MUTT_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 & MUTT_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, MUTT_YES) != MUTT_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, MUTT_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, MUTT_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); + FREE (&hdata); + hdr->data = 0; + 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, MUTT_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; + anum_t first_over = first; +#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 (option (OPTLISTGROUP) && nntp_data->nserv->hasLISTGROUP && + !nntp_data->deleted) + { + if (!ctx->quiet) + mutt_message _("Fetching list of articles..."); + if (nntp_data->nserv->hasLISTGROUPrange) + snprintf (buf, sizeof (buf), "LISTGROUP %s %d-%d\r\n", nntp_data->group, + first, last); + else + 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..."), + MUTT_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); + FREE (&hdata); + hdr->data = 0; + + /* 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) + if (option (OPTLISTGROUP) && nntp_data->nserv->hasLISTGROUP) + break; + else + continue; + + /* 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; + first_over = current + 1; + } + + if (!option (OPTLISTGROUP) || !nntp_data->nserv->hasLISTGROUP) + current = first_over; + + /* fetch overview information */ + if (current <= last && rc == 0 && !nntp_data->deleted) { + 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 */ +static 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, MUTT_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; + 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, MUTT_ACL_WRITE); + mutt_bit_unset (ctx->rights, MUTT_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 */ +static int nntp_open_message (CONTEXT *ctx, MESSAGE *msg, 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); + if (ctx->subj_hash && hdr->env->real_subj) + hash_insert (ctx->subj_hash, hdr->env->real_subj, hdr); + + /* 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; +} + +/* Close message */ +static int nntp_close_message (CONTEXT *ctx, MESSAGE *msg) +{ + return safe_fclose (&msg->fp); +} + +/* 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 == MUTT_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, MUTT_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, MUTT_SOCK_LOG_HDR) < 0) || + mutt_socket_write_d (nntp_data->nserv->conn, ".\r\n", -1, MUTT_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; +} + +/* Free up memory associated with the newsgroup context */ +static int nntp_close_mailbox (CONTEXT *ctx) +{ + NNTP_DATA *nntp_data = ctx->data, *nntp_tmp; + + if (!nntp_data) + return 0; + + nntp_data->unread = ctx->unread; + + 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, unsigned int new) +{ + 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; + i = nserv->groups_num; + 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 (new) + { + for (; i < nserv->groups_num; i++) + { + NNTP_DATA *nntp_data = nserv->groups_list[i]; + nntp_data->new = 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; + } + } + + if (option (OPTLOADDESC)) + rc = get_description (&nntp_data, "*", _("Loading descriptions...")); + + nntp_active_save_cache (nserv); + if (rc < 0) + return -1; + 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, leave newsrc locked: + * MUTT_REOPENED - articles have been renumbered or removed from server + * MUTT_NEW_MAIL - new articles found + * 0 - no change + * -1 - lost connection */ +static int check_mailbox (CONTEXT *ctx) +{ + 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 = MUTT_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); + FREE (&hdata); + hdr->data = 0; + 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], MUTT_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); + FREE (&hdata); + hdr->data = 0; + 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 = MUTT_REOPENED; + } + + /* some headers were removed, context must be updated */ + if (ret == MUTT_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 = MUTT_NEW_MAIL; + } + +#ifdef USE_HCACHE + mutt_hcache_close (hc); +#endif + if (ret) + nntp_newsrc_close (nserv); + mutt_clear_error (); + return ret; +} + +/* Check current newsgroup for new articles: + * MUTT_REOPENED - articles have been renumbered or removed from server + * MUTT_NEW_MAIL - new articles found + * 0 - no change + * -1 - lost connection */ +static int nntp_check_mailbox (CONTEXT *ctx, int *index_hint) +{ + int ret = check_mailbox (ctx); + if (ret == 0) + { + NNTP_DATA *nntp_data = ctx->data; + NNTP_SERVER *nserv = nntp_data->nserv; + nntp_newsrc_close (nserv); + } + return ret; +} + +/* Save changes to .newsrc and cache */ +static int nntp_sync_mailbox (CONTEXT *ctx, int *index_hint) +{ + 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 = check_mailbox (ctx); + if (rc) + return rc; + +#ifdef USE_HCACHE + nntp_data->lastCached = 0; + hc = nntp_hcache_open (nntp_data); +#endif + + 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)) + { + dprint (2, (debugfile, "nntp_sync_mailbox: mutt_hcache_store %s\n", buf)); + mutt_hcache_store (hc, buf, hdr, 0, strlen, MUTT_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; +} + +/* 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; + } + } + /* select current newsgroup */ + if (Context && Context->magic == MUTT_NNTP) + { + buf[0] = '\0'; + if (nntp_query ((NNTP_DATA *)Context->data, buf, sizeof (buf)) < 0) + return -1; + } + } + 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 == MUTT_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) + { + int groups_num = i; + + nserv->newgroups_time = now; + for (; i < nserv->groups_num; i++) + { + NNTP_DATA *nntp_data = nserv->groups_list[i]; + nntp_data->new = 1; + } + + /* loading descriptions */ + if (option (OPTLOADDESC)) + { + unsigned int count = 0; + progress_t progress; + + mutt_progress_init (&progress, _("Loading descriptions..."), + MUTT_PROGRESS_MSG, ReadInc, nserv->groups_num - i); + for (i = groups_num; 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; +} + +struct mx_ops mx_nntp_ops = { + .open = nntp_open_mailbox, + .open_append = NULL, + .close = nntp_close_mailbox, + .open_msg = nntp_open_message, + .close_msg = nntp_close_message, + .check = nntp_check_mailbox, + .commit_msg = NULL, + .open_new_msg = NULL, + .sync = nntp_sync_mailbox, +}; diff -udprP mutt-1.10.0.orig/nntp.h mutt-1.10.0/nntp.h --- mutt-1.10.0.orig/nntp.h 1970-01-01 03:00:00.000000000 +0300 +++ mutt-1.10.0/nntp.h 2018-06-16 17:22:30.200469636 +0300 @@ -0,0 +1,165 @@ +/* + * Copyright (C) 1998 Brandon Long + * Copyright (C) 1999 Andrej Gritsenko + * Copyright (C) 2000-2016 Vsevolod Volkov + * + * 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 +#include +#include + +#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 hasLISTGROUPrange : 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_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 *, unsigned int); +int nntp_newsrc_update (NNTP_SERVER *); +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, int, 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 + +extern struct mx_ops mx_nntp_ops; + +#endif /* _NNTP_H_ */ diff -udprP mutt-1.10.0.orig/pager.c mutt-1.10.0/pager.c --- mutt-1.10.0.orig/pager.c 2018-05-15 00:51:53.000000000 +0300 +++ mutt-1.10.0/pager.c 2018-06-16 17:22:30.201469620 +0300 @@ -1114,6 +1114,11 @@ fill_buffer (FILE *f, LOFF_T *last_pos, 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, @@ -1584,6 +1589,15 @@ static const struct mapping_t PagerHelpE { N_("Next"), OP_MAIN_NEXT_UNDELETED }, { 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 void mutt_clear_pager_position (void) { @@ -1923,6 +1937,10 @@ mutt_pager (const char *banner, const ch pager_redraw_data_t rd; +#ifdef USE_NNTP + char *followup_to; +#endif + if (!(flags & MUTT_SHOWCOLOR)) flags |= MUTT_SHOWFLAT; @@ -1972,7 +1990,11 @@ mutt_pager (const char *banner, const ch 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 == MUTT_NNTP)) ? PagerNewsHelpExtra : +#endif + PagerHelpExtra); snprintf (helpstr, sizeof (helpstr), "%s %s", tmphelp, buffer); } if (!InHelp) @@ -2639,6 +2661,60 @@ search_next: pager_menu->redraw = REDRAW_FULL; break; +#ifdef USE_NNTP + case OP_POST: + CHECK_MODE(IsHeader (extra) && !IsAttach (extra)); + CHECK_ATTACH; + if (extra->ctx && extra->ctx->magic == MUTT_NNTP && + !((NNTP_DATA *)extra->ctx->data)->allowed && + query_quadoption (OPT_TOMODERATED,_("Posting to this group not allowed, may be moderated. Continue?")) != MUTT_YES) + break; + ci_send_message (SENDNEWS, NULL, NULL, extra->ctx, NULL); + pager_menu->redraw = REDRAW_FULL; + break; + + case OP_FORWARD_TO_GROUP: + CHECK_MODE(IsHeader (extra) || IsMsgAttach (extra)); + CHECK_ATTACH; + if (extra->ctx && extra->ctx->magic == MUTT_NNTP && + !((NNTP_DATA *)extra->ctx->data)->allowed && + query_quadoption (OPT_TOMODERATED,_("Posting to this group not allowed, may be moderated. Continue?")) != MUTT_YES) + break; + if (IsMsgAttach (extra)) + mutt_attach_forward (extra->fp, extra->hdr, extra->actx, + extra->bdy, SENDNEWS); + else + ci_send_message (SENDNEWS|SENDFORWARD, NULL, NULL, extra->ctx, extra->hdr); + pager_menu->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?")) != MUTT_YES) + { + if (extra->ctx && extra->ctx->magic == MUTT_NNTP && + !((NNTP_DATA *)extra->ctx->data)->allowed && + query_quadoption (OPT_TOMODERATED,_("Posting to this group not allowed, may be moderated. Continue?")) != MUTT_YES) + break; + if (IsMsgAttach (extra)) + mutt_attach_reply (extra->fp, extra->hdr, extra->actx, + extra->bdy, SENDNEWS|SENDREPLY); + else + ci_send_message (SENDNEWS|SENDREPLY, NULL, NULL, + extra->ctx, extra->hdr); + pager_menu->redraw = REDRAW_FULL; + break; + } +#endif + case OP_REPLY: CHECK_MODE(IsHeader (extra) || IsMsgAttach (extra)); CHECK_ATTACH; @@ -2684,7 +2760,7 @@ search_next: CHECK_ATTACH; if (IsMsgAttach (extra)) mutt_attach_forward (extra->fp, extra->hdr, extra->actx, - extra->bdy); + extra->bdy, 0); else ci_send_message (SENDFORWARD, NULL, NULL, extra->ctx, extra->hdr); pager_menu->redraw = REDRAW_FULL; diff -udprP mutt-1.10.0.orig/parse.c mutt-1.10.0/parse.c --- mutt-1.10.0.orig/parse.c 2017-12-18 22:31:37.000000000 +0200 +++ mutt-1.10.0/parse.c 2018-06-16 17:22:30.201469620 +0300 @@ -94,7 +94,7 @@ char *mutt_read_rfc822_line (FILE *f, ch /* 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; @@ -1077,6 +1077,17 @@ int mutt_parse_rfc822_line (ENVELOPE *e, 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': @@ -1159,6 +1170,27 @@ int mutt_parse_rfc822_line (ENVELOPE *e, } 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")) { @@ -1271,6 +1303,20 @@ int mutt_parse_rfc822_line (ENVELOPE *e, 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 -udprP mutt-1.10.0.orig/pattern.c mutt-1.10.0/pattern.c --- mutt-1.10.0.orig/pattern.c 2018-05-15 00:51:53.000000000 +0300 +++ mutt-1.10.0/pattern.c 2018-06-16 17:22:30.201469620 +0300 @@ -94,6 +94,9 @@ Flags[] = { 'U', MUTT_UNREAD, 0, NULL }, { 'v', MUTT_COLLAPSED, 0, NULL }, { 'V', MUTT_CRYPT_VERIFIED, 0, NULL }, +#ifdef USE_NNTP + { 'w', MUTT_NEWSGROUPS, 0, eat_regexp }, +#endif { 'x', MUTT_REFERENCE, 0, eat_regexp }, { 'X', MUTT_MIMEATTACH, 0, eat_range }, { 'y', MUTT_XLABEL, 0, eat_regexp }, @@ -1368,6 +1371,10 @@ mutt_pattern_exec (struct pattern_t *pat return (pat->not ^ match_mime_content_type (pat, ctx, h)); case MUTT_UNREFERENCED: return (pat->not ^ (h->thread && !h->thread->child)); +#ifdef USE_NNTP + case MUTT_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); @@ -1449,6 +1456,7 @@ int mutt_pattern_func (int op, char *pro progress_t progress; strfcpy (buf, NONULL (Context->pattern), sizeof (buf)); + if (prompt || op != MUTT_LIMIT) if (mutt_get_field (prompt, buf, sizeof (buf), MUTT_PATTERN | MUTT_CLEAR) != 0 || !buf[0]) return (-1); diff -udprP mutt-1.10.0.orig/po/POTFILES.in mutt-1.10.0/po/POTFILES.in --- mutt-1.10.0.orig/po/POTFILES.in 2017-12-18 22:31:37.000000000 +0200 +++ mutt-1.10.0/po/POTFILES.in 2018-06-16 17:22:30.201469620 +0300 @@ -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 -udprP mutt-1.10.0.orig/postpone.c mutt-1.10.0/postpone.c --- mutt-1.10.0.orig/postpone.c 2018-04-17 02:31:03.000000000 +0300 +++ mutt-1.10.0/postpone.c 2018-06-16 17:22:30.201469620 +0300 @@ -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, MUTT_NOSORT | MUTT_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 -udprP mutt-1.10.0.orig/protos.h mutt-1.10.0/protos.h --- mutt-1.10.0.orig/protos.h 2018-05-15 00:51:53.000000000 +0300 +++ mutt-1.10.0/protos.h 2018-06-16 17:22:30.202469604 +0300 @@ -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 -udprP mutt-1.10.0.orig/recvattach.c mutt-1.10.0/recvattach.c --- mutt-1.10.0.orig/recvattach.c 2018-05-15 00:51:53.000000000 +0300 +++ mutt-1.10.0/recvattach.c 2018-06-16 17:22:30.202469604 +0300 @@ -1224,6 +1224,15 @@ void mutt_view_attachments (HEADER *hdr) } #endif +#ifdef USE_NNTP + if (Context->magic == MUTT_NNTP) + { + mutt_flushinp (); + mutt_error _("Can't delete attachment from news server."); + break; + } +#endif + if (WithCrypto && (hdr->security & ENCRYPT)) { mutt_message _( @@ -1318,10 +1327,32 @@ void mutt_view_attachments (HEADER *hdr) case OP_FORWARD_MESSAGE: CHECK_ATTACH; mutt_attach_forward (CURATTACH->fp, hdr, actx, - menu->tagprefix ? NULL : CURATTACH->content); + menu->tagprefix ? NULL : CURATTACH->content, 0); menu->redraw = REDRAW_FULL; break; +#ifdef USE_NNTP + case OP_FORWARD_TO_GROUP: + CHECK_ATTACH; + mutt_attach_forward (CURATTACH->fp, hdr, actx, + menu->tagprefix ? NULL : CURATTACH->content, SENDNEWS); + menu->redraw = REDRAW_FULL; + break; + + case OP_FOLLOWUP: + CHECK_ATTACH; + + if (!CURATTACH->content->hdr->env->followup_to || + mutt_strcasecmp (CURATTACH->content->hdr->env->followup_to, "poster") || + query_quadoption (OPT_FOLLOWUPTOPOSTER,_("Reply by mail as poster prefers?")) != MUTT_YES) + { + mutt_attach_reply (CURATTACH->fp, hdr, actx, + menu->tagprefix ? NULL : CURATTACH->content, SENDNEWS|SENDREPLY); + menu->redraw = REDRAW_FULL; + break; + } +#endif + case OP_COMPOSE_TO_SENDER: CHECK_ATTACH; mutt_attach_mail_sender (CURATTACH->fp, hdr, actx, diff -udprP mutt-1.10.0.orig/recvcmd.c mutt-1.10.0/recvcmd.c --- mutt-1.10.0.orig/recvcmd.c 2017-12-18 22:31:37.000000000 +0200 +++ mutt-1.10.0/recvcmd.c 2018-06-16 17:22:30.202469604 +0300 @@ -398,7 +398,7 @@ static BODY ** copy_problematic_attachme static void attach_forward_bodies (FILE * fp, HEADER * hdr, ATTACH_CONTEXT *actx, BODY * cur, - short nattach) + short nattach, int flags) { short i; short mime_fwd_all = 0; @@ -554,7 +554,7 @@ _("Can't decode all tagged attachments. tmpfp = NULL; /* now that we have the template, send it. */ - ci_send_message (0, tmphdr, tmpbody, NULL, parent_hdr); + ci_send_message (flags, tmphdr, tmpbody, NULL, parent_hdr); return; bail: @@ -581,7 +581,7 @@ _("Can't decode all tagged attachments. */ static void attach_forward_msgs (FILE * fp, HEADER * hdr, - ATTACH_CONTEXT *actx, BODY * cur) + ATTACH_CONTEXT *actx, BODY * cur, int flags) { HEADER *curhdr = NULL; HEADER *tmphdr; @@ -686,23 +686,23 @@ static void attach_forward_msgs (FILE * 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, - ATTACH_CONTEXT *actx, BODY * cur) + ATTACH_CONTEXT *actx, BODY * cur, int flags) { short nattach; if (check_all_msg (actx, cur, 0) == 0) - attach_forward_msgs (fp, hdr, actx, cur); + attach_forward_msgs (fp, hdr, actx, cur, flags); else { nattach = count_tagged (actx); - attach_forward_bodies (fp, hdr, actx, cur, nattach); + attach_forward_bodies (fp, hdr, actx, cur, nattach, flags); } } @@ -760,6 +760,17 @@ attach_reply_envelope_defaults (ENVELOPE return -1; } +#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 (parent) { if (mutt_fetch_recips (env, curenv, flags) == -1) @@ -782,6 +793,7 @@ attach_reply_envelope_defaults (ENVELOPE } mutt_fix_reply_recipients (env); + } mutt_make_misc_reply_headers (env, Context, curhdr, curenv); if (parent) @@ -844,6 +856,13 @@ void mutt_attach_reply (FILE * fp, HEADE char prefix[SHORT_STRING]; int rc; +#ifdef USE_NNTP + if (flags & SENDNEWS) + set_option (OPTNEWSSEND); + else + unset_option (OPTNEWSSEND); +#endif + if (check_all_msg (actx, cur, 0) == -1) { nattach = count_tagged (actx); diff -udprP mutt-1.10.0.orig/send.c mutt-1.10.0/send.c --- mutt-1.10.0.orig/send.c 2018-05-15 00:51:53.000000000 +0300 +++ mutt-1.10.0/send.c 2018-06-16 17:22:30.202469604 +0300 @@ -45,6 +45,11 @@ #include #include +#ifdef USE_NNTP +#include "nntp.h" +#include "mx.h" +#endif + #ifdef MIXMASTER #include "remailer.h" #endif @@ -214,17 +219,51 @@ static int edit_address (ADDRESS **a, /* return 0; } -static int edit_envelope (ENVELOPE *en) +static int edit_envelope (ENVELOPE *en, int flags) { char buf[HUGE_STRING]; LIST *uh = UserHeader; +#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) { @@ -259,6 +298,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; @@ -271,6 +318,14 @@ static void process_user_recips (ENVELOP 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 } } @@ -309,6 +364,12 @@ static void process_user_header (ENVELOP 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 (ENVE 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, CONTEX 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, @@ -1189,6 +1278,13 @@ ci_send_message (int flags, /* send mod int rv = -1; +#ifdef USE_NNTP + if (flags & SENDNEWS) + set_option (OPTNEWSSEND); + else + unset_option (OPTNEWSSEND); +#endif + if (!flags && !msg && quadoption (OPT_RECALL) != MUTT_NO && mutt_num_postponed (1)) { @@ -1224,6 +1320,22 @@ ci_send_message (int flags, /* send mod { 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)) @@ -1325,11 +1437,16 @@ ci_send_message (int flags, /* send mod if (flags & SENDREPLY) mutt_fix_reply_recipients (msg->env); +#ifdef USE_NNTP + if ((flags & SENDNEWS) && ctx && ctx->magic == MUTT_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; } @@ -1640,6 +1757,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; } @@ -1706,6 +1828,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)) { @@ -1739,6 +1864,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 /* Scan for a mention of an attachment in the message body and * prompt if there is none. */ @@ -1964,7 +2102,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 -udprP mutt-1.10.0.orig/sendlib.c mutt-1.10.0/sendlib.c --- mutt-1.10.0.orig/sendlib.c 2018-05-15 00:51:53.000000000 +0300 +++ mutt-1.10.0/sendlib.c 2018-06-16 17:22:30.203469588 +0300 @@ -46,6 +46,10 @@ #include #include +#ifdef USE_NNTP +#include "nntp.h" +#endif + #ifdef HAVE_SYSEXITS_H #include #else /* Make sure EX_OK is defined */ @@ -1576,6 +1580,14 @@ void mutt_write_references (LIST *r, FIL { 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) { @@ -1586,9 +1598,11 @@ void mutt_write_references (LIST *r, FIL 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); } @@ -2002,6 +2016,9 @@ int mutt_write_rfc822_header (FILE *fp, 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) @@ -2010,6 +2027,9 @@ int mutt_write_rfc822_header (FILE *fp, 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) @@ -2021,8 +2041,28 @@ int mutt_write_rfc822_header (FILE *fp, } } 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) @@ -2041,6 +2081,9 @@ int mutt_write_rfc822_header (FILE *fp, 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); @@ -2388,6 +2431,24 @@ mutt_invoke_sendmail (ADDRESS *from, /* size_t extra_argslen = 0, extra_argsmax = 0; int i; +#ifdef USE_NNTP + if (option (OPTNEWSSEND)) + { + char cmd[LONG_STRING]; + + mutt_FormatString (cmd, sizeof (cmd), 0, MuttIndexWindow->cols, + 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) { @@ -2437,6 +2498,10 @@ mutt_invoke_sendmail (ADDRESS *from, /* } } +#ifdef USE_NNTP + if (!option (OPTNEWSSEND)) + { +#endif if (eightbit && option (OPTUSE8BITMIME)) args = add_option (args, &argslen, &argsmax, "-B8BITMIME"); @@ -2470,6 +2535,9 @@ mutt_invoke_sendmail (ADDRESS *from, /* 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)); @@ -2558,6 +2626,9 @@ void mutt_prepare_envelope (ENVELOPE *en rfc2047_encode_string (&env->x_label); if (env->subject) +#ifdef USE_NNTP + if (!option (OPTNEWSSEND) || option (OPTMIMESUBJECT)) +#endif { rfc2047_encode_string (&env->subject); } @@ -2678,6 +2749,10 @@ int mutt_bounce_message (FILE *fp, HEADE } 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 -udprP mutt-1.10.0.orig/sort.c mutt-1.10.0/sort.c --- mutt-1.10.0.orig/sort.c 2017-12-18 22:31:37.000000000 +0200 +++ mutt-1.10.0/sort.c 2018-06-16 17:22:30.203469588 +0300 @@ -24,6 +24,11 @@ #include "sort.h" #include "mutt_idna.h" +#ifdef USE_NNTP +#include "mx.h" +#include "nntp.h" +#endif + #include #include #include @@ -151,6 +156,17 @@ static int compare_order (const void *a, HEADER **ha = (HEADER **) a; HEADER **hb = (HEADER **) b; +#ifdef USE_NNTP + if (Context && Context->magic == MUTT_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 -udprP mutt-1.10.0.orig/url.c mutt-1.10.0/url.c --- mutt-1.10.0.orig/url.c 2017-12-18 22:31:37.000000000 +0200 +++ mutt-1.10.0/url.c 2018-06-16 17:22:30.203469588 +0300 @@ -40,6 +40,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 }, @@ -224,7 +226,7 @@ int url_ciss_tostring (ciss_url_t* ciss, 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 -udprP mutt-1.10.0.orig/url.h mutt-1.10.0/url.h --- mutt-1.10.0.orig/url.h 2017-12-03 05:10:17.000000000 +0200 +++ mutt-1.10.0/url.h 2018-06-16 17:22:30.203469588 +0300 @@ -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 -udprP mutt-1.10.0.orig/Makefile.am mutt-1.10.0/Makefile.am --- mutt-1.10.0.orig/Makefile.am 2018-05-15 00:51:53.000000000 +0300 +++ mutt-1.10.0/Makefile.am 2018-06-16 17:22:30.203469588 +0300 @@ -60,6 +60,7 @@ EXTRA_mutt_SOURCES = account.c bcache.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 \ sidebar.c smime.c smtp.c utf8.c wcwidth.c \ bcache.h browser.h hcache.h mbyte.h mutt_idna.h remailer.h url.h @@ -71,6 +72,7 @@ EXTRA_DIST = COPYRIGHT GPL OPS OPS.PGP O 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 \ _mutt_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 -udprP mutt-1.10.0.orig/Makefile.in mutt-1.10.0/Makefile.in --- mutt-1.10.0.orig/Makefile.in 2018-05-15 04:08:48.000000000 +0300 +++ mutt-1.10.0/Makefile.in 2018-06-16 17:22:30.203469588 +0300 @@ -504,6 +504,7 @@ EXTRA_mutt_SOURCES = account.c bcache.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 \ sidebar.c smime.c smtp.c utf8.c wcwidth.c \ bcache.h browser.h hcache.h mbyte.h mutt_idna.h remailer.h url.h @@ -515,6 +516,7 @@ EXTRA_DIST = COPYRIGHT GPL OPS OPS.PGP O 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 \ _mutt_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 \ @@ -784,6 +786,8 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mutt_tunnel.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/muttlib.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mx.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/newsrc.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nntp.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pager.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/parse.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/patchlist.Po@am__quote@ # am--include-marker diff -udprP mutt-1.10.0.orig/configure mutt-1.10.0/configure --- mutt-1.10.0.orig/configure 2018-05-19 20:34:20.000000000 +0300 +++ mutt-1.10.0/configure 2018-06-16 17:22:30.205469557 +0300 @@ -802,6 +802,7 @@ with_docdir with_domain enable_pop enable_imap +enable_nntp enable_smtp with_gss with_ssl @@ -1487,6 +1488,7 @@ Optional Features: Force use of an external dotlock program --enable-pop Enable POP3 support --enable-imap Enable IMAP support + --enable-nntp Enable NNTP support --enable-smtp include internal SMTP relay support --enable-debug Enable debugging support --enable-flock Use flock() to lock files @@ -9750,6 +9752,20 @@ else fi +# Check whether --enable-nntp was given. +if test "${enable_nntp+set}" = set; then : + enableval=$enable_nntp; if test x$enableval = xyes ; then + +$as_echo "#define USE_NNTP 1" >>confdefs.h + + MUTT_LIB_OBJECTS="$MUTT_LIB_OBJECTS nntp.o newsrc.o" + need_nntp="yes" + need_socket="yes" + fi + +fi + + # Check whether --enable-smtp was given. if test "${enable_smtp+set}" = set; then : enableval=$enable_smtp; if test $enableval = yes; then @@ -9762,7 +9778,7 @@ $as_echo "#define USE_SMTP 1" >>confdefs 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 -udprP mutt-1.10.0.orig/doc/Muttrc mutt-1.10.0/doc/Muttrc --- mutt-1.10.0.orig/doc/Muttrc 2018-05-19 20:47:52.000000000 +0300 +++ mutt-1.10.0/doc/Muttrc 2018-06-16 17:22:30.206469541 +0300 @@ -275,6 +275,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 @@ -516,6 +538,17 @@ attachments -I message/external-body # desirable to unset this variable. # # +# 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 @@ -1294,6 +1327,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 @@ -1430,6 +1476,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 @@ -2046,6 +2113,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, @@ -2069,6 +2137,8 @@ attachments -I message/external-body # %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 ``X-Comment-To:'' field (if present and compiled with NNTP support) # %X number of attachments # (please see the ``attachments'' section for possible speed effects) # %y ``X-Label:'' field, if present @@ -2108,6 +2178,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="/usr/bin/ispell" # # Name: ispell @@ -2555,6 +2646,18 @@ attachments -I message/external-body # mime.types lookup. # # +# 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 @@ -2633,6 +2736,144 @@ attachments -I message/external-body # into this command. # # +# 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 @@ -3411,6 +3652,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 @@ -4285,6 +4539,41 @@ attachments -I message/external-body # Chinese characters. # # +# 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 @@ -5671,3 +5960,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. +# +#