WARNING: Run the following script before configure: aclocal -I m4 autoheader cd m4 make -f Makefile.am.in cd .. automake --foreign autoconf -- Vsevolod Volkov <vvv@mutt.org.ua> diff -udprP mutt-1.4.2.2.orig/ChangeLog.nntp mutt-1.4.2.2/ChangeLog.nntp --- mutt-1.4.2.2.orig/ChangeLog.nntp 1970-01-01 03:00:00.000000000 +0300 +++ mutt-1.4.2.2/ChangeLog.nntp 2006-07-18 12:52:21.000000000 +0300 @@ -0,0 +1,435 @@ +* Tue Jul 18 2006 Vsevolod Volkov <vvv@mutt.org.ua> +- update to 1.4.2.2 +- fixed reading empty .newsrc + +* Sat Feb 14 2004 Vsevolod Volkov <vvv@mutt.org.ua> +- update to 1.4.2.1 +- added space after newsgroup name in .newsrc file + +* Sat Mar 22 2003 Vsevolod Volkov <vvv@mutt.org.ua> +- update to 1.4.1 +- replace safe_free calls by the FREE macro +- nntp authentication can be passed after any command + +* Wed May 29 2002 Vsevolod Volkov <vvv@mutt.org.ua> +- update to 1.4 + +* Thu May 2 2002 Vsevolod Volkov <vvv@mutt.org.ua> +- update to 1.3.99 + +* Wed Mar 13 2002 Vsevolod Volkov <vvv@mutt.org.ua> +- update to 1.3.28 +- fixed SIGSEGV in <get-message>, <get-parent>, <get-children>, + <reconstruct-thread> functions +- fixed message about nntp reconnect +- fixed <attach-news-message> function using browser +- added support of Followup-To: poster +- added %n (new articles) in group_index_format +- posting articles without inews by default + +* Wed Jan 23 2002 Vsevolod Volkov <vvv@mutt.org.ua> +- update to 1.3.27 + +* Fri Jan 18 2002 Vsevolod Volkov <vvv@mutt.org.ua> +- update to 1.3.26 + +* Thu Jan 3 2002 Vsevolod Volkov <vvv@mutt.org.ua> +- update to 1.3.25 +- accelerated speed of access to news->newsgroups hash (by <gul@gul.kiev.ua>) +- added default content disposition + +* Mon Dec 3 2001 Vsevolod Volkov <vvv@mutt.org.ua> +- update to 1.3.24 + +* Fri Nov 9 2001 Vsevolod Volkov <vvv@mutt.org.ua> +- update to 1.3.23.2 +- fixed segfault if mutt_conn_find() returns null + +* Wed Oct 31 2001 Vsevolod Volkov <vvv@mutt.org.ua> +- update to 1.3.23.1 +- added support of LISTGROUP command +- added support for servers with broken overview +- disabled <flag-message> function on news server +- fixed error storing bad authentication information + +* Wed Oct 10 2001 Vsevolod Volkov <vvv@mutt.org.ua> +- update to 1.3.23 +- fixed typo in buffy.c +- added substitution of %s parameter in $inews variable + +* Fri Aug 31 2001 Vsevolod Volkov <vvv@mutt.org.ua> +- update to 1.3.22.1 +- update to 1.3.22 + +* Thu Aug 23 2001 Vsevolod Volkov <vvv@mutt.org.ua> +- update to 1.3.21 + +* Wed Jul 25 2001 Vsevolod Volkov <vvv@mutt.org.ua> +- update to 1.3.20 +- removed 'server-hook', use 'account-hook' instead +- fixed error opening NNTP server without newsgroup using -f option + +* Fri Jun 8 2001 Vsevolod Volkov <vvv@mutt.org.ua> +- update to 1.3.19 + +* Sat May 5 2001 Vsevolod Volkov <vvv@mutt.org.ua> +- update to 1.3.18 +- fixed typo in nntp_attempt_features() +- changed algorithm of XGTITLE command testing +- disabled writing of NNTP password in debug file +- fixed reading and writing of long newsrc lines +- changed checking of last line while reading lines from server +- fixed possible buffer overrun in nntp_parse_newsrc_line() +- removed checking of XHDR command +- compare NNTP return codes without trailing space + +* Thu Mar 29 2001 Vsevolod Volkov <vvv@mutt.org.ua> +- update to 1.3.17 +- support for 'LIST NEWSGROUPS' command to read descriptions + +* Fri Mar 2 2001 Vsevolod Volkov <vvv@mutt.org.ua> +- update to 1.3.16 + +* Wed Feb 14 2001 Vsevolod Volkov <vvv@mutt.org.ua> +- update to 1.3.15 + +* Sun Jan 28 2001 Vsevolod Volkov <vvv@mutt.org.ua> +- update to 1.3.14 +- show number of tagged messages patch from Felix von Leitner <leitner@fefe.de> + +* Sun Dec 31 2000 Vsevolod Volkov <vvv@mutt.org.ua> +- update to 1.3.13 + +* Sat Dec 30 2000 Vsevolod Volkov <vvv@mutt.org.ua> +- Fixed problem if last article in group is deleted + +* Fri Dec 22 2000 Vsevolod Volkov <vvv@mutt.org.ua> +- Fixed checking of XGTITLE command on some servers + +* Mon Dec 18 2000 Vsevolod Volkov <vvv@mutt.org.ua> +- Added \r in AUTHINFO commands + +* Mon Nov 27 2000 Vsevolod Volkov <vvv@mutt.org.ua> +- update to 1.3.12 + +* Wed Nov 1 2000 Vsevolod Volkov <vvv@mutt.org.ua> +- update to 1.3.11 +- fixed error opening newsgroup from mutt started with -g or -G + +* Thu Oct 12 2000 Vsevolod Volkov <vvv@mutt.org.ua> +- update to 1.3.10 +- hotkey 'G' (get-message) replaced with '^G' + +* Thu Sep 21 2000 Vsevolod Volkov <vvv@mutt.org.ua> +- update to 1.3.9 +- changed delay displaying error messages from 1 to 2 seconds +- fixed error compiling with nntp and without imap + +* Wed Sep 6 2000 Vsevolod Volkov <vvv@mutt.org.ua> +- fixed catchup in index +- fixed nntp_open_mailbox() + +* Sat Sep 2 2000 Vsevolod Volkov <vvv@mutt.org.ua> +- functions <edit> and <delete-entry> disabled +- format of news mailbox names changed to url form +- option nntp_attempts removed +- option reconnect_news renamed to nntp_reconnect +- default value of nntp_poll changed from 30 to 60 +- error handling improved + +* Wed Aug 30 2000 Vsevolod Volkov <vvv@mutt.org.ua> +- update to 1.3.8 +- new option show_only_unread +- add newsgroup completion + +* Fri Aug 4 2000 Vsevolod Volkov <vvv@mutt.kiev.ua> +- update to 1.3.7 + +* Sat Jul 29 2000 Vsevolod Volkov <vvv@mutt.kiev.ua> +- update to 1.3.6 + +* Sun Jul 9 2000 Vsevolod Volkov <vvv@mutt.kiev.ua> +- update to 1.3.5 +- authentication code update +- fix for changing to newsgroup from mailbox with read messages +- socket code optimization + +* Wed Jun 21 2000 Vsevolod Volkov <vvv@mutt.kiev.ua> +- update to 1.3.4 + +* Wed Jun 14 2000 Vsevolod Volkov <vvv@mutt.kiev.ua> +- don't substitute current newsgroup with deleted new messages + +* Mon Jun 12 2000 Vsevolod Volkov <vvv@mutt.kiev.ua> +- update to 1.3.3 +- fix for substitution of newsgroup after reconnection +- fix for loading newsgroups with very long names +- fix for loading more than 32768 newsgroups + +* Wed May 24 2000 Vsevolod Volkov <vvv@mutt.kiev.ua> +- update to 1.3.2 + +* Sat May 20 2000 Vsevolod Volkov <vvv@mutt.kiev.ua> +- update to 1.3.1 + +* Fri May 12 2000 Vsevolod Volkov <vvv@mutt.kiev.ua> +- update to 1.3 + +* Thu May 11 2000 Vsevolod Volkov <vvv@mutt.kiev.ua> +- update to 1.2 + +* Thu May 4 2000 Vsevolod Volkov <vvv@mutt.kiev.ua> +- update to 1.1.14 + +* Sun Apr 23 2000 Vsevolod Volkov <vvv@mutt.kiev.ua> +- update to 1.1.12 + +* Fri Apr 7 2000 Vsevolod Volkov <vvv@mutt.kiev.ua> +- add substitution of newsgroup with new messages by default + +* Wed Apr 5 2000 Vsevolod Volkov <vvv@mutt.kiev.ua> +- add attach message from newsgroup +- add one-line help in newsreader mode +- disable 'change-dir' command in newsgroups browser +- add -G option + +* Tue Apr 4 2000 Vsevolod Volkov <vvv@mutt.kiev.ua> +- get default newsserver name from file /etc/nntpserver +- use case insensitive server names +- add print-style sequence %s to $newsrc +- add -g option + +* Sat Apr 1 2000 Vsevolod Volkov <vvv@mutt.kiev.ua> +- remove 'X-FTN-Origin' header processing + +* Thu Mar 30 2000 Vsevolod Volkov <vvv@mutt.kiev.ua> +- update to 1.1.11 +- update to 1.1.10 + +* Thu Mar 23 2000 Vsevolod Volkov <vvv@mutt.kiev.ua> +- fix mutt_select_newsserver() +- remove 'toggle-mode' function +- add 'change-newsgroup' function + +* Wed Mar 22 2000 Vsevolod Volkov <vvv@mutt.kiev.ua> +- fix server-hook + +* Tue Mar 21 2000 Vsevolod Volkov <vvv@mutt.kiev.ua> +- fix error 'bounce' function after 'post' +- add 'forward to newsgroup' function + +* Mon Mar 20 2000 Vsevolod Volkov <vvv@mutt.kiev.ua> +- 'forward' function works in newsreader mode +- add 'post' and 'followup' functions to pager and attachment menu +- fix active descriptions and allowed flag reload + +* Tue Mar 14 2000 Vsevolod Volkov <vvv@mutt.kiev.ua> +- update to 1.1.9 +- remove deleted newsgroups from list + +* Mon Mar 13 2000 Vsevolod Volkov <vvv@mutt.kiev.ua> +- update .newsrc in browser + +* Sun Mar 12 2000 Vsevolod Volkov <vvv@mutt.kiev.ua> +- reload .newsrc if externally modified +- fix active cache update + +* Sun Mar 5 2000 Vsevolod Volkov <vvv@mutt.kiev.ua> +- update to 1.1.8 + +* Sat Mar 4 2000 Vsevolod Volkov <vvv@mutt.kiev.ua> +- patch *.update_list_file is not required +- count lines when loading descriptions +- remove cache of unsubscribed newsgroups + +* Thu Mar 2 2000 Vsevolod Volkov <vvv@mutt.kiev.ua> +- load list of newsgroups from cache faster + +* Wed Mar 1 2000 Vsevolod Volkov <vvv@mutt.kiev.ua> +- update to 1.1.7 + +* Tue Feb 29 2000 Vsevolod Volkov <vvv@mutt.kiev.ua> +- fix unread messages in browser +- fix newsrc_gen_entries() + +* Mon Feb 28 2000 Vsevolod Volkov <vvv@mutt.kiev.ua> +- fix mutt_newsgroup_stat() +- fix nntp_delete_cache() +- fix nntp_get_status() +- fix check_children() +- fix nntp_fetch_headers() + +* Fri Feb 25 2000 Vsevolod Volkov <vvv@mutt.kiev.ua> +- update to 1.1.5 + +* Thu Feb 24 2000 Vsevolod Volkov <vvv@mutt.kiev.ua> +- fix updating new messages in cache + +* Mon Feb 21 2000 Vsevolod Volkov <vvv@mutt.kiev.ua> +- change default cache filenames +- fix updating new messages in cache + +* Fri Feb 18 2000 Vsevolod Volkov <vvv@mutt.kiev.ua> +- fix segmentation fault in news groups browser + +* Tue Feb 15 2000 Vsevolod Volkov <vvv@mutt.kiev.ua> +- update to 1.1.4 + +* Thu Feb 10 2000 Vsevolod Volkov <vvv@mutt.kiev.ua> +- update to 1.1.3 + +* Sun Jan 30 2000 Vsevolod Volkov <vvv@mutt.kiev.ua> +- add X-Comment-To editing +- add my_hdr support for Newsgroups:, Followup-To: and X-Comment-To: headers +- add variables $ask_followup_to and $ask_x_comment_to + +* Fri Jan 28 2000 Vsevolod Volkov <vvv@mutt.kiev.ua> +- update to 1.1.2 + +* Wed Aug 23 2000 Andrej Gritsenko <andrej@lucky.net> +- nntp.c: inserted server hook when opened a newsgroup - for + difference between mail and news reader environment +- parse.c: `X-FTN-Origin:' processed for bad Fido gates + +* Tue Aug 22 2000 Vsevolod Volkov <vvv@mutt.org.ua> +- browser.c, complete.c: newsgroup completion patch + +* Wed Dec 22 1999 Andrej Gritsenko <andrej@lucky.net> +- send.c: fixed bug with `Subject:' on followup + +* Sat Dec 18 1999 Andrej Gritsenko <andrej@lucky.net> +- updated to 1.1.1 + +* Tue Oct 26 1999 Andrej Gritsenko <andrej@lucky.net> +- updated to 1.1 + +* Mon Jun 14 1999 Andrej Gritsenko <andrej@lucky.net> +- sendlib.c: use $mime_subject for news articles only + +* Fri Jun 11 1999 Edouard G. Parmelan <Edouard.Parmelan@quadratec.fr> +- nntp.c: bugfix with number of new articles + +* Thu Jun 10 1999 Andrej Gritsenko <andrej@lucky.net> +- nntp.c: fixed incomplete creating of index file + +* Wed Jun 9 1999 Andrej Gritsenko <andrej@lucky.net> +- updated to 0.96.3 +- doc/manual*: documentation updated + +* Sun Feb 21 1999 Andrej Gritsenko <andrej@lucky.net> +- browser.c, newsrc.c, nntp.c: couple of fixes + +* Wed Dec 16 1998 Andrej Gritsenko <andrej@lucky.net> +- curs_main.c, newsrc.c, nntp.c: fixes on number of read + articles + +* Tue Dec 15 1998 Vikas Agnihotri <vikasa@att.com> +- nntp.c: fix with gone cache files + +* Tue Dec 15 1998 Andrej Gritsenko <andrej@lucky.net> +- newsrc.c, nntp.c: fixes on number of read articles + +* Mon Dec 14 1998 Andrej Gritsenko <andrej@lucky.net> +- browser.c, newsrc.c, nntp.c, nntp.h: changed then simplified + nntp_parse_xover() + +* Mon Dec 14 1998 Vikas Agnihotri <vikasa@att.com> +- browser.c: cosmetic patch +- nntp.c: say "Fetching %s from server..." + +* Sun Dec 13 1998 Andrej Gritsenko <andrej@lucky.net> +- commands.c, newsrc.c, nntp.c, nntp.h: don't save newsrc file + so often (noted by Vikas Agnihotri) + +* Sat Dec 12 1998 Andrej Gritsenko <andrej@lucky.net> +- browser.c, functions.h, nntp.c, nntp.h: fixed incorrect first + active loading + +* Wed Dec 9 1998 Vikas Agnihotri <vikasa@att.com> +- nntp.c: fixed bug in `reconstruct-thread' function +- curs_main.c, nntp.c: fixed the bug in non-XOVER case of get + headers + +* Tue Dec 8 1998 Andrej Gritsenko <andrej@lucky.net> +- nntp.c: fixed error in getting status of article + +* Tue Dec 8 1998 Vikas Agnihotri <vikasa@att.com> +- mutt.h, pattern.c: added `~w' search pattern + +* Tue Dec 8 1998 Andrej Gritsenko <andrej@lucky.net> +- nntp.c: bugfix when get children + +* Tue Dec 8 1998 Vikas Agnihotri <vikasa@att.com> +- newsrc.c: bugfix in mutt_newsgroup_catchup() + +* Tue Dec 8 1998 Andrej Gritsenko <andrej@lucky.net> +- browser.c: bugfix in _mutt_select_file() + +* Sat Dec 5 1998 Andrej Gritsenko <andrej@lucky.net> +- browser.c, newsrc.c, nntp.c, nntp.h: fix the sorting of + newsgroups in browser (from Vikas Agnihotri) + +* Fri Dec 4 1998 Vikas Agnihotri <vikasa@att.com> +- OPS, curs_main.c, functions.h, nntp.c, nntp.h: new functions: + `get-children' and `reconstruct-thread' + +* Thu Dec 3 1998 Vikas Agnihotri <vikasa@att.com> +- OPS, curs_main.c, functions.h, nntp.c, nntp.h: new functions: + `get-message' and `get-parent' + +* Wed Dec 2 1998 Vikas Agnihotri <vikasa@att.com> +- OPS, compose.c, curs_main.c, functions.h, parse.c, send.c, + sendlib.c: added `Followup-To:' field editing + +* Wed Dec 2 1998 Vikas Agnihotri <vikasa@att.com> +- OPS, browser.c, functions.h: new `load-active' function + +* Tue Dec 1 1998 Andrej Gritsenko <andrej@lucky.net> +- browser.c: added option for blank %s in $group_index_format +- hdrline.c, lib.c, mutt.h, parse.c: added %g and %W parameters + to $pager_format +- hook.c, newsrc.c, nntp.c, sendlib.c: some small bugfixes + +* Mon Nov 30 1998 Andrej Gritsenko <andrej@lucky.net> +- send.c: bugfix in `Followup-To:' field + +* Sun Nov 29 1998 Vikas Agnihotri <vikasa@att.com> +- compose.c, lib.c, parse.c, protos.h: remove white spaces from + `Newsgroup:' field to prevent a unnecessary rejection of article + +* Sat Nov 28 1998 Vikas Agnihotri <vikasa@att.com> +- newsrc.c: fixed bug in catchup + +* Sat Nov 28 1998 Vikas Agnihotri <vikasa@att.com> +- browser.c: make indicator advance when (un)subscribe + +* Sat Nov 28 1998 Andrej Gritsenko <andrej@lucky.net> +- newsrc.c: fixed segmentation fault in newsrc_update_line() + +* Sat Nov 28 1998 Andrej Gritsenko <andrej@lucky.net> +- OPS, browser.c, functions.h, init.h, mutt.h, mx.c, newsrc.c, + nntp.c, nntp.h: corrected reading of newsrc file and added new + `catchup' function + +* Thu Nov 26 1998 Andrej Gritsenko <andrej@lucky.net> +- browser.c, newsrc.c: implementation of `(un)subscribe-pattern' + functions from Vikas Agnihotri +- globals.h, init.h, nntp.h, nntp.c: added variable $nntp_poll + to avoid of polling newsserver on each keypressing + +* Thu Nov 26 1998 Andrej Gritsenko <andrej@lucky.net> +- nntp.c, socket.c: fixed parsing of long XOVER lines +- send.c: removed silly ask for newsgroup name (noted by Vikas + Agnihotri) + +* Wed Nov 25 1998 +- commands.c: bugfix in mutt_update_list_file() + +* Wed Nov 25 1998 Andrej Gritsenko <andrej@lucky.net> +- updated to mutt-0.94.17 +- init.c, nntp.c, nntp.h, parse.c, send.c: couple of bugfixes + from Vikas Agnihotri + +* Sat Nov 21 1998 Andrej Gritsenko <andrej@lucky.net> +- [mutt-0.94.13] couple of files: pre-alpha version of patch, + partially based on code that written by Brandon Long diff -udprP mutt-1.4.2.2.orig/OPS mutt-1.4.2.2/OPS --- mutt-1.4.2.2.orig/OPS 2002-04-25 16:26:56.000000000 +0300 +++ mutt-1.4.2.2/OPS 2006-07-18 12:45:02.000000000 +0300 @@ -7,13 +7,15 @@ 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 to current mailbox (IMAP only)" +OP_CATCHUP "mark all articles in newsgroup as read" +OP_BROWSER_SUBSCRIBE "subscribe to current (IMAP/NNTP only)" +OP_BROWSER_UNSUBSCRIBE "unsubscribe to current (IMAP/NNTP only)" OP_BROWSER_TOGGLE_LSUB "toggle view all/subscribed mailboxes (IMAP only)" OP_CHANGE_DIRECTORY "change directories" OP_CHECK_NEW "check mailboxes for new mail" OP_COMPOSE_ATTACH_FILE "attach a file(s) to this message" OP_COMPOSE_ATTACH_MESSAGE "attach message(s) to this message" +OP_COMPOSE_ATTACH_NEWS_MESSAGE "attach newsmessage(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" @@ -24,7 +26,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)" @@ -83,8 +88,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" @@ -92,12 +102,16 @@ 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 active file from NNTP server" OP_MACRO "execute a macro" OP_MAIL "compose a new mail message" 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_RECONSTRUCT_THREAD "reconstruct thread containing current message" OP_MAIN_IMAP_FETCH "force retrieval of mail from IMAP server" OP_MAIN_FETCH_MAIL "retrieve mail from POP server" OP_MAIN_FIRST_MESSAGE "move to the first message" @@ -130,6 +144,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,17 +166,21 @@ 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_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.4.2.2.orig/PATCHES mutt-1.4.2.2/PATCHES --- mutt-1.4.2.2.orig/PATCHES 2001-11-26 21:16:52.000000000 +0200 +++ mutt-1.4.2.2/PATCHES 2006-07-18 12:45:02.000000000 +0300 @@ -0,0 +1 @@ +vvv.nntp diff -udprP mutt-1.4.2.2.orig/acconfig.h mutt-1.4.2.2/acconfig.h --- mutt-1.4.2.2.orig/acconfig.h 2002-01-16 00:08:12.000000000 +0200 +++ mutt-1.4.2.2/acconfig.h 2006-07-18 12:45:02.000000000 +0300 @@ -14,6 +14,9 @@ */ #undef HOMESPOOL +/* Compiling with newsreading support with NNTP */ +#undef USE_NNTP + /* program to use for shell commands */ #define EXECSHELL "/bin/sh" diff -udprP mutt-1.4.2.2.orig/account.c mutt-1.4.2.2/account.c --- mutt-1.4.2.2.orig/account.c 2001-04-26 16:36:33.000000000 +0300 +++ mutt-1.4.2.2/account.c 2006-07-18 12:45:02.000000000 +0300 @@ -44,6 +44,11 @@ int mutt_account_match (const ACCOUNT* a user = PopUser; #endif +#ifdef USE_NNTP + if (a1->type == M_ACCT_TYPE_NNTP && NntpUser) + user = NntpUser; +#endif + if (a1->flags & a2->flags & M_ACCT_USER) return (!strcmp (a1->user, a2->user)); if (a1->flags & M_ACCT_USER) @@ -113,6 +118,16 @@ void mutt_account_tourl (ACCOUNT* accoun } #endif +#ifdef USE_NNTP + if (account->type == M_ACCT_TYPE_NNTP) + { + if (account->flags & M_ACCT_SSL) + url->scheme = U_NNTPS; + else + url->scheme = U_NNTP; + } +#endif + url->host = account->host; if (account->flags & M_ACCT_PORT) url->port = account->port; @@ -138,6 +153,10 @@ int mutt_account_getuser (ACCOUNT* accou else if ((account->type == M_ACCT_TYPE_POP) && PopUser) strfcpy (account->user, PopUser, sizeof (account->user)); #endif +#ifdef USE_NNTP + else if ((account->type == M_ACCT_TYPE_NNTP) && NntpUser) + strfcpy (account->user, NntpUser, sizeof (account->user)); +#endif /* prompt (defaults to unix username), copy into account->user */ else { @@ -167,6 +186,10 @@ int mutt_account_getpass (ACCOUNT* accou else if ((account->type == M_ACCT_TYPE_POP) && PopPass) strfcpy (account->pass, PopPass, sizeof (account->pass)); #endif +#ifdef USE_NNTP + else if ((account->type == M_ACCT_TYPE_NNTP) && NntpPass) + strfcpy (account->pass, NntpPass, sizeof (account->pass)); +#endif else { snprintf (prompt, sizeof (prompt), _("Password for %s@%s: "), diff -udprP mutt-1.4.2.2.orig/account.h mutt-1.4.2.2/account.h --- mutt-1.4.2.2.orig/account.h 2001-01-02 11:34:30.000000000 +0200 +++ mutt-1.4.2.2/account.h 2006-07-18 12:45:02.000000000 +0300 @@ -28,6 +28,7 @@ enum { M_ACCT_TYPE_NONE = 0, M_ACCT_TYPE_IMAP, + M_ACCT_TYPE_NNTP, M_ACCT_TYPE_POP }; diff -udprP mutt-1.4.2.2.orig/attach.h mutt-1.4.2.2/attach.h --- mutt-1.4.2.2.orig/attach.h 2001-09-11 14:20:34.000000000 +0300 +++ mutt-1.4.2.2/attach.h 2006-07-18 12:45:02.000000000 +0300 @@ -30,5 +30,5 @@ void mutt_print_attachment_list (FILE *f void mutt_attach_bounce (FILE *, HEADER *, ATTACHPTR **, short, BODY *); void mutt_attach_resend (FILE *, HEADER *, ATTACHPTR **, short, BODY *); -void mutt_attach_forward (FILE *, HEADER *, ATTACHPTR **, short, BODY *); +void mutt_attach_forward (FILE *, HEADER *, ATTACHPTR **, short, BODY *, int); void mutt_attach_reply (FILE *, HEADER *, ATTACHPTR **, short, BODY *, int); diff -udprP mutt-1.4.2.2.orig/browser.c mutt-1.4.2.2/browser.c --- mutt-1.4.2.2.orig/browser.c 2002-07-24 12:33:59.000000000 +0300 +++ mutt-1.4.2.2/browser.c 2006-07-18 12:45:02.000000000 +0300 @@ -27,6 +27,9 @@ #ifdef USE_IMAP #include "imap.h" #endif +#ifdef USE_NNTP +#include "nntp.h" +#endif #include <stdlib.h> #include <dirent.h> @@ -44,6 +47,19 @@ static struct mapping_t FolderHelp[] = { { NULL } }; +#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 } +}; +#endif + typedef struct folder_t { struct folder_file *ff; @@ -109,9 +125,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_SUBJECT: @@ -301,8 +325,106 @@ folder_format_str (char *dest, size_t de return (src); } +#ifdef USE_NNTP +static const char * +newsgroup_format_str (char *dest, size_t destlen, char op, const char *src, + const char *fmt, const char *ifstring, const char *elsestring, + unsigned long data, format_flag flags) +{ + char fn[SHORT_STRING], tmp[SHORT_STRING]; + FOLDER *folder = (FOLDER *) data; + + switch (op) + { + case 'C': + snprintf (tmp, sizeof (tmp), "%%%sd", fmt); + snprintf (dest, destlen, tmp, folder->num + 1); + break; + + case 'f': + strncpy (fn, folder->ff->name, sizeof(fn) - 1); + snprintf (tmp, sizeof (tmp), "%%%ss", fmt); + snprintf (dest, destlen, tmp, fn); + break; + + case 'N': + snprintf (tmp, sizeof (tmp), "%%%sc", fmt); + if (folder->ff->nd->subscribed) + snprintf (dest, destlen, tmp, ' '); + else + snprintf (dest, destlen, tmp, folder->ff->new ? 'N' : 'u'); + break; + + case 'M': + snprintf (tmp, sizeof (tmp), "%%%sc", fmt); + if (folder->ff->nd->deleted) + snprintf (dest, destlen, tmp, 'D'); + else + snprintf (dest, destlen, tmp, folder->ff->nd->allowed ? ' ' : '-'); + break; + + case 's': + if (flags & M_FORMAT_OPTIONAL) + { + if (folder->ff->nd->unread != 0) + mutt_FormatString (dest, destlen, ifstring, newsgroup_format_str, + data, flags); + else + mutt_FormatString (dest, destlen, 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) + { + snprintf (tmp, sizeof (tmp), "%%%ss", fmt); + snprintf (dest, destlen, tmp, folder->ff->nd->desc); + } + 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, int new) + const char *name, const struct stat *s, + void *data, int new) { if (state->entrylen == state->entrymax) { @@ -331,6 +453,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)++; } @@ -346,9 +472,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)) + { + LIST *tmp; + NNTP_DATA *data; + NNTP_SERVER *news = CurrentNewsSrv; + +/* mutt_buffy_check (0); */ + init_state (state, menu); + + for (tmp = news->list; tmp; tmp = tmp->next) + { + if (!(data = (NNTP_DATA *)tmp->data)) + continue; + if (prefix && *prefix && strncmp (prefix, data->group, + strlen (prefix)) != 0) + continue; + if (!((regexec (Mask.rx, data->group, 0, NULL, 0) == 0) ^ Mask.not)) + continue; + add_folder (menu, state, data->group, NULL, data, data->new); + } + } + else +#endif /* USE_NNTP */ + { struct stat s; DIR *dp; struct dirent *de; @@ -409,60 +561,95 @@ static int examine_directory (MUTTMENU * tmp = Incoming; while (tmp && mutt_strcmp (buffer, tmp->path)) tmp = tmp->next; - add_folder (menu, state, de->d_name, &s, (tmp) ? tmp->new : 0); + add_folder (menu, state, de->d_name, &s, NULL, (tmp) ? tmp->new : 0); + } + closedir (dp); } - 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]; - BUFFY *tmp = Incoming; - if (!Incoming) - return (-1); - mutt_buffy_check (0); +#ifdef USE_NNTP + if (option (OPTNEWS)) + { + LIST *tmp; + NNTP_DATA *data; + NNTP_SERVER *news = CurrentNewsSrv; - init_state (state, menu); +/* mutt_buffy_check (0); */ + init_state (state, menu); - do - { -#ifdef USE_IMAP - if (mx_is_imap (tmp->path)) + for (tmp = news->list; tmp; tmp = tmp->next) { - add_folder (menu, state, tmp->path, NULL, tmp->new); - continue; + if ((data = (NNTP_DATA *) tmp->data) != NULL && (data->new || + (data->subscribed && (!option (OPTSHOWONLYUNREAD) || data->unread)))) + add_folder (menu, state, data->group, NULL, data, data->new); } + } + else #endif -#ifdef USE_POP - if (mx_is_pop (tmp->path)) + { + BUFFY *tmp = Incoming; + + if (!Incoming) + return (-1); + mutt_buffy_check (0); + + init_state (state, menu); + + do { - add_folder (menu, state, tmp->path, NULL, tmp->new); - continue; - } +#ifdef USE_IMAP + if (mx_is_imap (tmp->path)) + { + add_folder (menu, state, tmp->path, NULL, NULL, tmp->new); + continue; + } #endif - if (lstat (tmp->path, &s) == -1) - continue; +#ifdef USE_POP + if (mx_is_pop (tmp->path)) + { + add_folder (menu, state, tmp->path, NULL, NULL, tmp->new); + continue; + } +#endif +#ifdef USE_NNTP + if (mx_is_nntp (tmp->path)) + { + add_folder (menu, state, tmp->path, NULL, NULL, tmp->new); + continue; + } +#endif + if (lstat (tmp->path, &s) == -1) + continue; - if ((! S_ISREG (s.st_mode)) && (! S_ISDIR (s.st_mode)) && - (! S_ISLNK (s.st_mode))) - continue; + if ((! S_ISREG (s.st_mode)) && (! S_ISDIR (s.st_mode)) && + (! S_ISLNK (s.st_mode))) + continue; - strfcpy (buffer, NONULL(tmp->path), sizeof (buffer)); - mutt_pretty_mailbox (buffer); + strfcpy (buffer, NONULL(tmp->path), sizeof (buffer)); + mutt_pretty_mailbox (buffer); - add_folder (menu, state, buffer, &s, tmp->new); + add_folder (menu, state, buffer, &s, NULL, tmp->new); + } + while ((tmp = tmp->next)); } - while ((tmp = tmp->next)); browser_sort (state); return 0; } 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)); } @@ -473,7 +660,13 @@ void folder_entry (char *s, size_t slen, folder.ff = &((struct folder_file *) menu->data)[num]; folder.num = num; - mutt_FormatString (s, slen, NONULL(FolderFormat), folder_format_str, +#ifdef USE_NNTP + if (option (OPTNEWS)) + mutt_FormatString (s, slen, NONULL(GroupFormat), newsgroup_format_str, + (unsigned long) &folder, M_FORMAT_ARROWCURSOR); + else +#endif + mutt_FormatString (s, slen, NONULL(FolderFormat), folder_format_str, (unsigned long) &folder, M_FORMAT_ARROWCURSOR); } @@ -492,7 +685,18 @@ static void init_menu (struct browser_st menu->top = 0; 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 @@ -548,6 +752,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 + { + LIST *list; + + /* default state for news reader mode is browse subscribed newsgroups */ + buffy = 0; + for (list = CurrentNewsSrv->list; list; list = list->next) + { + NNTP_DATA *data = (NNTP_DATA *) list->data; + + if (data && data->subscribed) + { + buffy = 1; + break; + } + } + } + } + else +#endif if (*f) { mutt_expand_path (f, flen); @@ -636,6 +865,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); init_menu (&state, menu, title, sizeof (title), buffy); @@ -774,7 +1006,11 @@ void _mutt_select_file (char *f, size_t } } +#ifdef USE_NNTP + if (buffy || option (OPTNEWS)) /* news have not path */ +#else if (buffy) +#endif { strfcpy (f, state.entry[menu->current].name, flen); mutt_expand_path (f, flen); @@ -832,14 +1068,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)) { @@ -910,6 +1138,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) @@ -1160,6 +1393,190 @@ 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]; + NNTP_DATA *nd; + + if (i == OP_CATCHUP) + nd = mutt_newsgroup_catchup (CurrentNewsSrv, f->name); + else + nd = mutt_newsgroup_uncatchup (CurrentNewsSrv, f->name); + + if (nd) + { +/* 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 = nd->new; + folder.ff->nd = nd; + FREE (&f->desc); + mutt_FormatString (buffer, sizeof (buffer), NONULL(GroupFormat), + newsgroup_format_str, (unsigned long) &folder, + M_FORMAT_ARROWCURSOR); + f->desc = safe_strdup (buffer); */ + if (menu->current + 1 < menu->max) + menu->current++; + menu->redraw = REDRAW_MOTION_RESYNCH; + } + } + break; + + case OP_LOAD_ACTIVE: + if (!option (OPTNEWS)) + break; + + { + LIST *tmp; + NNTP_DATA *data; + + for (tmp = CurrentNewsSrv->list; tmp; tmp = tmp->next) + { + if ((data = (NNTP_DATA *)tmp->data)) + data->deleted = 1; + } + } + nntp_get_active (CurrentNewsSrv); + + 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)) + { + regex_t *rx = (regex_t *) safe_malloc (sizeof (regex_t)); + char *s = buf; + int j = menu->current; + NNTP_DATA *nd; + NNTP_SERVER *news = CurrentNewsSrv; + + 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; + } + + if ((err = REGCOMP (rx, s, REG_NOSUB)) != 0) + { + 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; + } + + 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) + nd = mutt_newsgroup_subscribe (news, f->name); + else + nd = mutt_newsgroup_unsubscribe (news, f->name); +/* if (nd) + { + FOLDER folder; + char buffer[_POSIX_PATH_MAX + SHORT_STRING]; + + folder.name = f->name; + folder.f = NULL; + folder.new = nd->new; + folder.nd = nd; + FREE (&f->desc); + mutt_FormatString (buffer, sizeof (buffer), NONULL(GroupFormat), + newsgroup_format_str, (unsigned long) &folder, + M_FORMAT_ARROWCURSOR); + f->desc = safe_strdup (buffer); + } */ + } + if (i == OP_BROWSER_SUBSCRIBE || i == OP_BROWSER_UNSUBSCRIBE) + { + if (menu->current + 1 < menu->max) + menu->current++; + menu->redraw = REDRAW_MOTION_RESYNCH; + break; + } + } + if (i == OP_SUBSCRIBE_PATTERN) + { + LIST *grouplist = NULL; + + if (news) + grouplist = news->list; + for (; grouplist; grouplist = grouplist->next) + { + nd = (NNTP_DATA *) grouplist->data; + if (nd && nd->group && !nd->subscribed) + { + if (regexec (rx, nd->group, 0, NULL, 0) == 0) + { + mutt_newsgroup_subscribe (news, nd->group); + add_folder (menu, &state, nd->group, NULL, nd, nd->new); + } + } + } + init_menu (&state, menu, title, sizeof (title), buffy); + } + mutt_newsrc_update (news); + nntp_clear_cacheindex (news); + 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.4.2.2.orig/browser.h mutt-1.4.2.2/browser.h --- mutt-1.4.2.2.orig/browser.h 2000-08-21 18:42:20.000000000 +0300 +++ mutt-1.4.2.2/browser.h 2006-07-18 12:45:02.000000000 +0300 @@ -20,6 +20,10 @@ #ifndef _BROWSER_H #define _BROWSER_H 1 +#ifdef USE_NNTP +#include "nntp.h" +#endif + struct folder_file { mode_t mode; @@ -38,14 +42,17 @@ struct folder_file unsigned selectable : 1; unsigned inferiors : 1; #endif +#ifdef USE_NNTP + NNTP_DATA *nd; +#endif unsigned tagged : 1; }; struct browser_state { struct folder_file *entry; - short entrylen; /* number of real entries */ - short entrymax; /* max entry */ + unsigned int entrylen; /* number of real entries */ + unsigned int entrymax; /* max entry */ #ifdef USE_IMAP short imap_browse; char *folder; diff -udprP mutt-1.4.2.2.orig/buffy.c mutt-1.4.2.2/buffy.c --- mutt-1.4.2.2.orig/buffy.c 2002-03-25 13:30:23.000000000 +0200 +++ mutt-1.4.2.2/buffy.c 2006-07-18 12:45:02.000000000 +0300 @@ -257,6 +257,9 @@ int mutt_buffy_check (int force) #ifdef USE_POP if (!Context || Context->magic != M_POP) #endif +#ifdef USE_NNTP + if (!Context || Context->magic != M_NNTP) +#endif /* check device ID and serial number instead of comparing paths */ if (!Context || !Context->path || stat (Context->path, &contex_sb) != 0) { @@ -278,6 +281,11 @@ int mutt_buffy_check (int force) tmp->magic = M_POP; else #endif +#ifdef USE_NNTP + if ((tmp->magic == M_NNTP) || mx_is_nntp (tmp->path)) + tmp->magic = M_NNTP; + else +#endif if (stat (tmp->path, &sb) != 0 || sb.st_size == 0 || (!tmp->magic && (tmp->magic = mx_get_magic (tmp->path)) <= 0)) { @@ -294,25 +302,21 @@ int mutt_buffy_check (int force) /* check to see if the folder is the currently selected folder * before polling */ if (!Context || !Context->path || -#if defined USE_IMAP || defined USE_POP - (( + ( + (0 #ifdef USE_IMAP - tmp->magic == M_IMAP + || tmp->magic == M_IMAP #endif #ifdef USE_POP -#ifdef USE_IMAP - || -#endif - tmp->magic == M_POP -#endif - ) ? mutt_strcmp (tmp->path, Context->path) : + || tmp->magic == M_POP #endif - (sb.st_dev != contex_sb.st_dev || sb.st_ino != contex_sb.st_ino) -#if defined USE_IMAP || defined USE_POP - ) +#ifdef USE_NNTP + || tmp->magic == M_NNTP #endif - ) - + ) ? mutt_strcmp (tmp->path, Context->path) : + (sb.st_dev != contex_sb.st_dev || sb.st_ino != contex_sb.st_ino) + ) + ) { switch (tmp->magic) { @@ -380,6 +384,11 @@ int mutt_buffy_check (int force) case M_POP: break; #endif + +#ifdef USE_NNTP + case M_NNTP: + break; +#endif } } #ifdef BUFFY_SIZE diff -udprP mutt-1.4.2.2.orig/commands.c mutt-1.4.2.2/commands.c --- mutt-1.4.2.2.orig/commands.c 2002-04-03 13:55:49.000000000 +0300 +++ mutt-1.4.2.2/commands.c 2006-07-18 12:45:02.000000000 +0300 @@ -26,6 +26,7 @@ #include "copy.h" #include "mx.h" #include "pager.h" +#include "rfc1524.h" #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> @@ -787,6 +788,131 @@ int mutt_save_message (HEADER *h, int de return -1; } +int mutt_update_list_file (char *filename, char *section, char *key, char *line) +{ + FILE *ifp; + FILE *ofp; + char buf[HUGE_STRING]; + char oldfile[_POSIX_PATH_MAX]; + char *c; + int ext = 0, done = 0, r = 0; + + snprintf (oldfile, sizeof(oldfile), "%s.bak", filename); + dprint (1, (debugfile, "Renaming %s to %s\n", filename, oldfile)); + + /* if file not exist, create it */ + if ((ifp = safe_fopen (filename, "a"))) + fclose (ifp); + if (_mutt_rename_file (filename, oldfile, 1)) + { + mutt_perror _("Unable to create backup file"); + return (-1); + } + dprint (1, (debugfile, "Opening %s\n", oldfile)); + if (!(ifp = safe_fopen (oldfile, "r"))) + { + mutt_perror _("Unable to open backup file for reading"); + return (-1); + } + dprint (1, (debugfile, "Opening %s\n", filename)); + if (!(ofp = fopen (filename, "w"))) + { + fclose (ifp); + mutt_perror _("Unable to open new file for writing"); + return (-1); + } + if (mx_lock_file (filename, fileno (ofp), 1, 0, 1)) + { + fclose (ofp); + fclose (ifp); + mutt_error (_("Unable to lock %s, old file saved as %s"), filename, oldfile); + return (-1); + } + + if (section) + { + while (r != EOF && !done && fgets (buf, sizeof (buf), ifp)) + { + r = fputs (buf, ofp); + c = buf; + while (*c && *c != '\n') c++; + c[0] = 0; /* strip EOL */ + if (!strncmp (buf, "#: ", 3) && !mutt_strcasecmp (buf+3, section)) + done++; + } + if (r != EOF && !done) + { + snprintf (buf, sizeof(buf), "#: %s\n", section); + r = fputs (buf, ofp); + } + done = 0; + } + + while (r != EOF && fgets (buf, sizeof (buf), ifp)) + { + if (ext) + { + c = buf; + while (*c && (*c != '\r') && (*c != '\n')) c++; + c--; + if (*c != '\\') ext = 0; + } + else if ((section && !strncmp (buf, "#: ", 3))) + { + if (!done && line) + { + fputs (line, ofp); + fputc ('\n', ofp); + } + r = fputs (buf, ofp); + done++; + break; + } + else if (key && !strncmp (buf, key, strlen(key)) && + (!*key || buf[strlen(key)] == ' ')) + { + c = buf; + ext = 0; + while (*c && (*c != '\r') && (*c != '\n')) c++; + c--; + if (*c == '\\') ext = 1; + if (!done && line) + { + r = fputs (line, ofp); + if (*key) + r = fputc ('\n', ofp); + done++; + } + } + else + { + r = fputs (buf, ofp); + } + } + + while (r != EOF && fgets (buf, sizeof (buf), ifp)) + r = fputs (buf, ofp); + + /* If there wasn't a line to replace, put it on the end of the file */ + if (r != EOF && !done && line) + { + fputs (line, ofp); + r = fputc ('\n', ofp); + } + mx_unlock_file (filename, fileno (ofp), 0); + fclose (ofp); + fclose (ifp); + if (r != EOF) + { + unlink (oldfile); + return 0; + } + unlink (filename); + mutt_error (_("Cannot write new %s, old file saved as %s"), filename, + oldfile); + return (-1); +} + void mutt_version (void) { diff -udprP mutt-1.4.2.2.orig/complete.c mutt-1.4.2.2/complete.c --- mutt-1.4.2.2.orig/complete.c 2001-11-07 12:22:08.000000000 +0200 +++ mutt-1.4.2.2/complete.c 2006-07-18 12:45:02.000000000 +0300 @@ -21,6 +21,9 @@ #include "mailbox.h" #include "imap.h" #endif +#ifdef USE_NNTP +#include "nntp.h" +#endif #include <dirent.h> #include <string.h> @@ -44,9 +47,71 @@ 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)) + { + LIST *l = CurrentNewsSrv->list; + + strfcpy (filepart, s, sizeof (filepart)); + + /* + * special case to handle when there is no filepart yet. + * find the first subscribed newsgroup + */ + if ((len = mutt_strlen (filepart)) == 0) + { + for (; l; l = l->next) + { + NNTP_DATA *data = (NNTP_DATA *)l->data; + + if (data && data->subscribed) + { + strfcpy (filepart, data->group, sizeof (filepart)); + init++; + l = l->next; + break; + } + } + } + + for (; l; l = l->next) + { + NNTP_DATA *data = (NNTP_DATA *)l->data; + + if (data && data->subscribed && + mutt_strncmp (data->group, filepart, len) == 0) + { + if (init) + { + for (i = 0; filepart[i] && data->group[i]; i++) + { + if (filepart[i] != data->group[i]) + { + filepart[i] = 0; + break; + } + } + filepart[i] = 0; + } + else + { + strfcpy (filepart, 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.4.2.2.orig/compose.c mutt-1.4.2.2/compose.c --- mutt-1.4.2.2.orig/compose.c 2002-07-24 11:41:29.000000000 +0300 +++ mutt-1.4.2.2/compose.c 2006-07-18 12:45:02.000000000 +0300 @@ -26,10 +26,15 @@ #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 <errno.h> #include <string.h> @@ -63,12 +68,18 @@ enum HDR_PGPSIGINFO, #endif +#ifdef USE_NNTP + HDR_NEWSGROUPS, + HDR_FOLLOWUPTO, + HDR_XCOMMENTTO, +#endif + HDR_ATTACH = (HDR_FCC + 5) /* where to start printing the attachments */ }; -#define HDR_XOFFSET 10 -#define TITLE_FMT "%10s" /* Used for Prompts, which are ASCII */ +#define HDR_XOFFSET 14 +#define TITLE_FMT "%14s" /* Used for Prompts, which are ASCII */ #define W (COLS - HDR_XOFFSET) static char *Prompts[] = @@ -80,6 +91,15 @@ static char *Prompts[] = "Subject: ", "Reply-To: ", "Fcc: " +#ifdef HAVE_PGP + ,"" + ,"" +#endif +#ifdef USE_NNTP + ,"Newsgroups: " + ,"Followup-To: " + ,"X-Comment-To: " +#endif }; static struct mapping_t ComposeHelp[] = { @@ -94,6 +114,19 @@ static struct mapping_t ComposeHelp[] = { NULL } }; +#ifdef USE_NNTP +static struct mapping_t ComposeNewsHelp[] = { + { N_("Send"), OP_COMPOSE_SEND_MESSAGE }, + { N_("Abort"), OP_EXIT }, + { "Newsgroups", OP_COMPOSE_EDIT_NEWSGROUPS }, + { "Subj", OP_COMPOSE_EDIT_SUBJECT }, + { N_("Attach file"), OP_COMPOSE_ATTACH_FILE }, + { N_("Descrip"), OP_COMPOSE_EDIT_DESCRIPTION }, + { N_("Help"), OP_HELP }, + { NULL } +}; +#endif + static void snd_entry (char *b, size_t blen, MUTTMENU *menu, int num) { mutt_FormatString (b, blen, NONULL (AttachFormat), mutt_attach_fmt, @@ -108,7 +141,7 @@ static void snd_entry (char *b, size_t b static void redraw_pgp_lines (int pgp) { - mvaddstr (HDR_PGP, 0, " PGP: "); + mvaddstr (HDR_PGP, 0, " PGP: "); if ((pgp & (PGPENCRYPT | PGPSIGN)) == (PGPENCRYPT | PGPSIGN)) addstr (_("Sign, Encrypt")); else if (pgp & PGPENCRYPT) @@ -122,7 +155,7 @@ static void redraw_pgp_lines (int pgp) move (HDR_PGPSIGINFO, 0); clrtoeol (); if (pgp & PGPSIGN) - printw ("%s%s", _(" sign as: "), PgpSignAs ? PgpSignAs : _("<default>")); + printw (" %s%s", _(" sign as: "), PgpSignAs ? PgpSignAs : _("<default>")); } static int pgp_send_menu (int bits, int *redraw) @@ -258,9 +291,28 @@ 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)) + { +#endif draw_envelope_addr (HDR_TO, msg->env->to); draw_envelope_addr (HDR_CC, msg->env->cc); draw_envelope_addr (HDR_BCC, msg->env->bcc); +#ifdef USE_NNTP + } + else + { + mvprintw (HDR_TO, 0, TITLE_FMT , Prompts[HDR_NEWSGROUPS - 1]); + mutt_paddstr (W, NONULL (msg->env->newsgroups)); + mvprintw (HDR_CC, 0, TITLE_FMT , Prompts[HDR_FOLLOWUPTO - 1]); + mutt_paddstr (W, NONULL (msg->env->followup_to)); + if (option (OPTXCOMMENTTO)) + { + mvprintw (HDR_BCC, 0, TITLE_FMT , Prompts[HDR_XCOMMENTTO - 1]); + mutt_paddstr (W, NONULL (msg->env->x_comment_to)); + } + } +#endif mvprintw (HDR_SUBJECT, 0, TITLE_FMT, Prompts[HDR_SUBJECT - 1]); mutt_paddstr (W, NONULL (msg->env->subject)); draw_envelope_addr (HDR_REPLYTO, msg->env->reply_to); @@ -506,6 +558,12 @@ int mutt_compose_menu (HEADER *msg, /* /* Sort, SortAux could be changed in mutt_index_menu() */ int oldSort, oldSortAux; struct stat st; +#ifdef USE_NNTP + int news = 0; /* is it a news article ? */ + + if (option (OPTNEWSSEND)) + news++; +#endif mutt_attach_init (msg->content); idx = mutt_gen_attach_list (msg->content, -1, idx, &idxlen, &idxmax, 0, 1); @@ -517,10 +575,18 @@ int mutt_compose_menu (HEADER *msg, /* menu->make_entry = snd_entry; menu->tag = mutt_tag_attach; menu->data = idx; +#ifdef USE_NNTP + if (news) + menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_COMPOSE, ComposeNewsHelp); + else +#endif menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_COMPOSE, ComposeHelp); while (loop) { +#ifdef USE_NNTP + unset_option (OPTNEWS); /* for any case */ +#endif switch (op = mutt_menuLoop (menu)) { case OP_REDRAW: @@ -532,14 +598,81 @@ int mutt_compose_menu (HEADER *msg, /* menu->redraw = edit_address_list (HDR_FROM, &msg->env->from); break; case OP_COMPOSE_EDIT_TO: - menu->redraw = edit_address_list (HDR_TO, &msg->env->to); +#ifdef USE_NNTP + if (!news) +#endif + menu->redraw = edit_address_list (HDR_TO, &msg->env->to); break; case OP_COMPOSE_EDIT_BCC: - menu->redraw = edit_address_list (HDR_BCC, &msg->env->bcc); +#ifdef USE_NNTP + if (!news) +#endif + menu->redraw = edit_address_list (HDR_BCC, &msg->env->bcc); break; case OP_COMPOSE_EDIT_CC: - menu->redraw = edit_address_list (HDR_CC, &msg->env->cc); +#ifdef USE_NNTP + if (!news) +#endif + menu->redraw = edit_address_list (HDR_CC, &msg->env->cc); + break; +#ifdef USE_NNTP + case OP_COMPOSE_EDIT_NEWSGROUPS: + if (news) + { + if (msg->env->newsgroups) + strfcpy (buf, msg->env->newsgroups, sizeof (buf)); + else + buf[0] = 0; + if (mutt_get_field ("Newsgroups: ", buf, sizeof (buf), 0) == 0 && buf[0]) + { + FREE (&msg->env->newsgroups); + mutt_remove_trailing_ws (buf); + msg->env->newsgroups = safe_strdup (mutt_skip_whitespace (buf)); + move (HDR_TO, HDR_XOFFSET); + clrtoeol (); + if (msg->env->newsgroups) + printw ("%-*.*s", W, W, msg->env->newsgroups); + } + } + break; + + case OP_COMPOSE_EDIT_FOLLOWUP_TO: + if (news) + { + buf[0] = 0; + if (msg->env->followup_to) + strfcpy (buf, msg->env->followup_to, sizeof (buf)); + if (mutt_get_field ("Followup-To: ", buf, sizeof (buf), 0) == 0 && buf[0]) + { + FREE (&msg->env->followup_to); + mutt_remove_trailing_ws (buf); + msg->env->followup_to = safe_strdup (mutt_skip_whitespace (buf)); + move (HDR_CC, HDR_XOFFSET); + clrtoeol(); + if (msg->env->followup_to) + printw ("%-*.*s", W, W, msg->env->followup_to); + } + } + break; + + case OP_COMPOSE_EDIT_X_COMMENT_TO: + if (news && option (OPTXCOMMENTTO)) + { + buf[0] = 0; + if (msg->env->x_comment_to) + strfcpy (buf, msg->env->x_comment_to, sizeof (buf)); + if (mutt_get_field ("X-Comment-To: ", buf, sizeof (buf), 0) == 0 && buf[0]) + { + FREE (&msg->env->x_comment_to); + msg->env->x_comment_to = safe_strdup (buf); + move (HDR_BCC, HDR_XOFFSET); + clrtoeol(); + if (msg->env->x_comment_to) + printw ("%-*.*s", W, W, msg->env->x_comment_to); + } + } break; +#endif case OP_COMPOSE_EDIT_SUBJECT: if (msg->env->subject) strfcpy (buf, msg->env->subject, sizeof (buf)); @@ -695,6 +828,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; @@ -702,7 +838,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 = mutt_select_newsserver (NewsServer))) + break; + + prompt = _("Open newsgroup to attach message from"); + set_option (OPTNEWS); + } +#endif + if (Context) +#ifdef USE_NNTP + if ((op == OP_COMPOSE_ATTACH_MESSAGE) ^ (Context->magic == M_NNTP)) +#endif { strfcpy (fname, NONULL (Context->path), sizeof (fname)); mutt_pretty_mailbox (fname); @@ -711,6 +862,11 @@ int mutt_compose_menu (HEADER *msg, /* if (mutt_enter_fname (prompt, fname, sizeof (fname), &menu->redraw, 1) == -1 || !fname[0]) break; +#ifdef USE_NNTP + if (option (OPTNEWS)) + nntp_expand_path (fname, sizeof (fname), &CurrentNewsSrv->conn->account); + else +#endif mutt_expand_path (fname, sizeof (fname)); #ifdef USE_IMAP if (!mx_is_imap (fname)) @@ -718,6 +874,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.4.2.2.orig/config.h.in mutt-1.4.2.2/config.h.in --- mutt-1.4.2.2.orig/config.h.in 2006-07-14 21:38:56.000000000 +0300 +++ mutt-1.4.2.2/config.h.in 2006-07-18 12:45:02.000000000 +0300 @@ -15,6 +15,9 @@ */ #undef HOMESPOOL +/* Compiling with newsreading support with NNTP */ +#undef USE_NNTP + /* program to use for shell commands */ #define EXECSHELL "/bin/sh" diff -udprP mutt-1.4.2.2.orig/configure.in mutt-1.4.2.2/configure.in --- mutt-1.4.2.2.orig/configure.in 2006-07-14 21:38:34.000000000 +0300 +++ mutt-1.4.2.2/configure.in 2006-07-18 12:45:02.000000000 +0300 @@ -461,6 +461,14 @@ AC_ARG_ENABLE(imap, [ --enable-imap ]) AM_CONDITIONAL(BUILD_IMAP, test x$need_imap = xyes) +AC_ARG_ENABLE(nntp, [ --enable-nntp Enable NNTP support], +[ if test x$enableval = xyes ; then + AC_DEFINE(USE_NNTP) + MUTT_LIB_OBJECTS="$MUTT_LIB_OBJECTS nntp.o newsrc.o" + need_socket="yes" + fi +]) + dnl -- end socket dependencies -- if test "$need_socket" = "yes" diff -udprP mutt-1.4.2.2.orig/curs_main.c mutt-1.4.2.2/curs_main.c --- mutt-1.4.2.2.orig/curs_main.c 2002-01-16 22:44:25.000000000 +0200 +++ mutt-1.4.2.2/curs_main.c 2006-07-18 12:45:02.000000000 +0300 @@ -18,6 +18,7 @@ #include "mutt.h" #include "mutt_curses.h" +#include "mx.h" #include "mutt_menu.h" #include "mailbox.h" #include "mapping.h" @@ -33,6 +34,10 @@ #include "imap.h" #endif +#ifdef USE_NNTP +#include "nntp.h" +#endif + #ifdef HAVE_PGP @@ -405,12 +410,27 @@ struct mapping_t IndexHelp[] = { { NULL } }; +#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 } +}; +#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[SHORT_STRING]; + int flags; int op = OP_NULL; int done = 0; /* controls when to exit the "event" loop */ int i = 0, j; @@ -432,7 +452,11 @@ int mutt_index_menu (void) menu->make_entry = index_make_entry; menu->color = index_color; menu->current = ci_first_message (); - menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_MAIN, IndexHelp); + menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_MAIN, +#ifdef USE_NNTP + (Context && (Context->magic == M_NNTP)) ? IndexNewsHelp : +#endif + IndexHelp); if (!attach_msg) mutt_buffy_check(1); /* force the buffy check after we enter the folder */ @@ -633,6 +657,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) { @@ -683,6 +710,120 @@ int mutt_index_menu (void) menu_current_bottom (menu); break; +#ifdef USE_NNTP + case OP_GET_MESSAGE: + case OP_GET_PARENT: + CHECK_MSGCOUNT; + if (Context->magic == M_NNTP) + { + HEADER *h; + + 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); + if ((h = hash_find (Context->id_hash, buf))) + { + if (h->virtual != -1) + { + menu->current = h->virtual; + menu->redraw = REDRAW_MOTION_RESYNCH; + } + else if (h->collapsed) + { + mutt_uncollapse_thread (Context, h); + mutt_set_virtual (Context); + menu->current = h->virtual; + menu->redraw = REDRAW_MOTION_RESYNCH; + } + else + mutt_error _("Message not visible in limited view."); + } + else + { + if (nntp_check_msgid (Context, buf) == 0) + { + h = Context->hdrs[Context->msgcount-1]; + mutt_sort_headers (Context, 0); + menu->current = h->virtual; + menu->redraw = REDRAW_FULL; + } + else + mutt_error (_("Article %s not found on server"), buf); + } + } + break; + + case OP_GET_CHILDREN: + case OP_RECONSTRUCT_THREAD: + CHECK_MSGCOUNT; + if (Context->magic == M_NNTP) + { + HEADER *h; + int old = CURHDR->index, i; + + if (!CURHDR->env->message_id) + { + mutt_error _("No Message-Id. Unable to perform operation"); + break; + } + + if (!Context->id_hash) + Context->id_hash = mutt_make_id_hash (Context); + strfcpy (buf, CURHDR->env->message_id, sizeof (buf)); + + if (op == OP_RECONSTRUCT_THREAD) + { + LIST *ref = CURHDR->env->references; + while (ref) + { + nntp_check_msgid (Context, ref->data); + /* the last msgid in References is the root message */ + if (!ref->next) + strfcpy (buf, ref->data, sizeof (buf)); + ref = ref->next; + } + } + mutt_message _("Check for children of message..."); + if (nntp_check_children (Context, buf) == 0) + { + mutt_sort_headers (Context, (op == OP_RECONSTRUCT_THREAD)); + h = hash_find (Context->id_hash, buf); + /* if the root message was retrieved, move to it */ + if (h) + menu->current = h->virtual; + else /* try to restore old position */ + for (i = 0; i < Context->msgcount; i++) + if (Context->hdrs[i]->index == old) + { + 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; + mutt_clear_error (); + } + break; +#endif + case OP_JUMP: CHECK_MSGCOUNT; @@ -778,11 +919,33 @@ int mutt_index_menu (void) break; case OP_MAIN_LIMIT: + case OP_TOGGLE_READ: CHECK_MSGCOUNT; menu->oldcurrent = (Context->vcount && menu->current >= 0 && menu->current < Context->vcount) ? CURHDR->index : -1; - if (mutt_pattern_func (M_LIMIT, _("Limit to messages matching: ")) == 0) + if (op == OP_TOGGLE_READ) + { + char buf[LONG_STRING]; + + if (!Context->pattern || strncmp (Context->pattern, "!~R!~D~s", 8) != 0) + { + snprintf (buf, sizeof (buf), "!~R!~D~s%s", + Context->pattern ? Context->pattern : ".*"); + set_option (OPTHIDEREAD); + } + else + { + strfcpy (buf, Context->pattern + 8, sizeof(buf)); + if (!*buf || strncmp (buf, ".*", 2) == 0) + snprintf (buf, sizeof(buf), "~A"); + unset_option (OPTHIDEREAD); + } + FREE (&Context->pattern); + Context->pattern = safe_strdup (buf); + } + if ((op == OP_TOGGLE_READ && mutt_pattern_func (M_LIMIT, NULL) == 0) || + mutt_pattern_func (M_LIMIT, _("Limit to messages matching: ")) == 0) { if (menu->oldcurrent >= 0) { @@ -975,20 +1138,43 @@ int mutt_index_menu (void) break; case OP_MAIN_CHANGE_FOLDER: - - 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: +#endif - if ((op == OP_MAIN_CHANGE_FOLDER_READONLY) || option (OPTREADONLY)) + if (attach_msg || option (OPTREADONLY) || +#ifdef USE_NNTP + op == OP_MAIN_CHANGE_GROUP_READONLY || +#endif + op == OP_MAIN_CHANGE_FOLDER_READONLY) + flags = M_READONLY; + else + flags = 0; + + if (flags) cp = _("Open mailbox in read-only mode"); else cp = _("Open mailbox"); buf[0] = '\0'; +#ifdef USE_NNTP + unset_option (OPTNEWS); + if (op == OP_MAIN_CHANGE_GROUP || + op == OP_MAIN_CHANGE_GROUP_READONLY) + { + set_option (OPTNEWS); + if (!(CurrentNewsSrv = mutt_select_newsserver (NewsServer))) + break; + if (flags) + cp = _("Open newsgroup in read-only mode"); + else + cp = _("Open newsgroup"); + nntp_buffy (buf); + } + else +#endif mutt_buffy (buf, sizeof (buf)); if (mutt_enter_fname (cp, buf, sizeof (buf), &menu->redraw, 1) == -1) @@ -999,6 +1185,14 @@ int mutt_index_menu (void) break; } +#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) { @@ -1029,15 +1223,18 @@ int mutt_index_menu (void) mutt_folder_hook (buf); - if ((Context = mx_open_mailbox (buf, - (option (OPTREADONLY) || op == OP_MAIN_CHANGE_FOLDER_READONLY) ? - M_READONLY : 0, NULL)) != NULL) + if ((Context = mx_open_mailbox (buf, flags, NULL)) != NULL) { menu->current = ci_first_message (); } else menu->current = 0; +#ifdef USE_NNTP + /* mutt_buffy_check() must be done with mail-reader mode! */ + menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_MAIN, + (Context && (Context->magic == M_NNTP)) ? IndexNewsHelp : IndexHelp); +#endif mutt_clear_error (); mutt_buffy_check(1); /* force the buffy check after we have changed the folder */ @@ -1333,6 +1530,15 @@ int mutt_index_menu (void) } #endif +#ifdef USE_NNTP + if (Context->magic == M_NNTP) + { + mutt_flushinp (); + mutt_error _("Can't change 'important' flag on NNTP server."); + break; + } +#endif + if (tag) { for (j = 0; j < Context->vcount; j++) @@ -1673,6 +1879,17 @@ int mutt_index_menu (void) } break; +#ifdef USE_NNTP + case OP_CATCHUP: + if (Context && Context->magic == M_NNTP) + { + if (mutt_newsgroup_catchup (CurrentNewsSrv, + ((NNTP_DATA *)Context->data)->group)) + menu->redraw = REDRAW_INDEX | REDRAW_STATUS; + } + break; +#endif + case OP_DISPLAY_ADDRESS: CHECK_MSGCOUNT; @@ -1707,6 +1924,15 @@ int mutt_index_menu (void) } #endif +#ifdef USE_NNTP + if (Context->magic == M_NNTP) + { + mutt_flushinp (); + mutt_error _("Can't edit message on newsserver."); + break; + } +#endif + mutt_edit_message (Context, tag ? NULL : CURHDR); menu->redraw = REDRAW_FULL; @@ -1861,6 +2087,37 @@ int mutt_index_menu (void) menu->redraw = REDRAW_FULL; break; + +#ifdef USE_NNTP + case OP_POST: + case OP_FOLLOWUP: + case OP_FORWARD_TO_GROUP: + + CHECK_ATTACH; + if (op != OP_FOLLOWUP || !CURHDR->env->followup_to || + mutt_strcasecmp (CURHDR->env->followup_to, "poster") || + query_quadoption (OPT_FOLLOWUPTOPOSTER,_("Reply by mail as poster prefers?")) != M_YES) + { + if (Context && Context->magic == M_NNTP && + !((NNTP_DATA *)Context->data)->allowed && + query_quadoption (OPT_TOMODERATED, _("Posting to this group not allowed, may be moderated. Continue?")) != M_YES) + break; + if (op == OP_POST) + ci_send_message (SENDNEWS, NULL, NULL, Context, NULL); + else + { + CHECK_MSGCOUNT; + if (op == OP_FOLLOWUP) + ci_send_message (SENDNEWS|SENDREPLY, NULL, NULL, Context, + tag ? NULL : CURHDR); + else + ci_send_message (SENDNEWS|SENDFORWARD, NULL, NULL, Context, + tag ? NULL : CURHDR); + } + menu->redraw = REDRAW_FULL; + break; + } +#endif case OP_REPLY: @@ -1983,6 +2240,12 @@ int mutt_index_menu (void) if (!attach_msg) imap_logout_all (); #endif +#ifdef USE_NNTP + /* Close all open NNTP connections */ + if (!attach_msg) + nntp_logout_all (); +#endif + mutt_menuDestroy (&menu); return (close); diff -udprP mutt-1.4.2.2.orig/doc/manual.sgml.head mutt-1.4.2.2/doc/manual.sgml.head --- mutt-1.4.2.2.orig/doc/manual.sgml.head 2002-10-10 12:43:11.000000000 +0300 +++ mutt-1.4.2.2/doc/manual.sgml.head 2006-07-18 12:45:02.000000000 +0300 @@ -723,6 +723,17 @@ replied to for the status of the message See also the <ref id="postpone" name="$postpone"> quad-option. +<sect1>Reading news via NNTP<label id="reading_news"> +<p> + +If compiled with ``--enable-nntp'' option, Mutt can read news from newsserver +via NNTP. You can open a newsgroup with function ``change-newsgroup'' +(default: i). Default newsserver can be obtained from <em/NNTPSERVER/ +environment variable. Like other news readers, info about subscribed +newsgroups is saved in file by <ref id="newsrc" name="$newsrc"> +variable. Article headers are cached and can be loaded from file when +newsgroup entered instead loading from newsserver. + <sect>Configuration <p> diff -udprP mutt-1.4.2.2.orig/doc/mutt.man mutt-1.4.2.2/doc/mutt.man --- mutt-1.4.2.2.orig/doc/mutt.man 2002-07-24 11:41:31.000000000 +0300 +++ mutt-1.4.2.2/doc/mutt.man 2006-07-18 12:45:03.000000000 +0300 @@ -24,8 +24,8 @@ mutt \- The Mutt Mail User Agent .SH SYNOPSIS .PP .B mutt -[-nRyzZ] -[-e \fIcmd\fP] [-F \fIfile\fP] [-m \fItype\fP] [-f \fIfile\fP] +[-GnRyzZ] +[-e \fIcmd\fP] [-F \fIfile\fP] [-g \fIserver\fP] [-m \fItype\fP] [-f \fIfile\fP] .PP .B mutt [-nx] @@ -63,6 +63,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 newsserver. +.IP "-G" +Start Mutt with a listing of subscribed newsgroups. .IP "-h" Display help. .IP "-H \fIdraft\fP" diff -udprP mutt-1.4.2.2.orig/doc/muttrc.man mutt-1.4.2.2/doc/muttrc.man --- mutt-1.4.2.2.orig/doc/muttrc.man 2006-07-14 21:34:51.000000000 +0300 +++ mutt-1.4.2.2/doc/muttrc.man 2006-07-18 12:45:03.000000000 +0300 @@ -604,6 +604,28 @@ editing the body of an outgoing message. .TP +.B ask_follow_up +.nf +Type: boolean +Default: no +.fi +.IP +If set, Mutt will prompt you for follow-up groups before editing +the body of an outgoing message. + + +.TP +.B ask_x_comment_to +.nf +Type: boolean +Default: no +.fi +.IP +If set, Mutt will prompt you for x-comment-to field before editing +the body of an outgoing message. + + +.TP .B attach_format .nf Type: string @@ -755,6 +777,17 @@ bouncing messages. Postfix users may wi .TP +.B catchup_newsgroup +.nf +Type: quadoption +Default: ask-yes +.fi +.IP +If this variable is \fIset\fP, Mutt will mark all articles in newsgroup +as read when you quit the newsgroup (catchup newsgroup). + + +.TP .B charset .nf Type: string @@ -1217,6 +1250,19 @@ resulting in two copies of the same emai .TP +.B followup_to_poster +.nf +Type: quadoption +Default: ask-yes +.fi +.IP +If this variable is \fIset\fP and the keyword \(rqposter\(rq 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. + + +.TP .B force_name .nf Type: boolean @@ -1301,6 +1347,39 @@ a regular expression that will match the .TP +.B group_index_format +.nf +Type: string +Default: \(lq%4C %M%N %5s %-45.45f %d\(rq +.fi +.IP +This variable allows you to customize the newsgroup browser display to +your personal taste. This string is similar to \(lqindex_format\(rq, but +has its own set of printf()-like sequences: +.IP + +.IP +.DS +.sp +.ft CR +.nf +%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 \(rqX\(rq +%|X pad to the end of the line with character \(rqX\(rq + +.fi +.ec +.ft P +.sp + + +.TP .B hdrs .nf Type: boolean @@ -1693,6 +1772,9 @@ entire From: line (address + real name) .IP %F author name, or recipient name if the message is from you +.IP %g +newsgroup name (if compiled with nntp support) + .IP %i message-id of the current message @@ -1738,6 +1820,9 @@ user (login) name of the author .IP %v first name of the author, or the recipient if the message is from you +.IP %W +name of organization of author (`organization:' field) + .IP %y `x-label:' field, if present @@ -1780,6 +1865,33 @@ See also: \(lq$to_chars\(rq. .TP +.B inews +.nf +Type: path +Default: \(lq\(rq +.fi +.IP +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: +.IP + +.IP +.DS +.sp +.ft CR +.nf +%s newsserver name + +.fi +.ec +.ft P +.sp +.IP +Example: set inews=\(rq/usr/local/bin/inews -hS\(rq + + +.TP .B ispell .nf Type: path @@ -2049,6 +2161,18 @@ be attached to the newly composed messag .TP +.B mime_subject +.nf +Type: boolean +Default: yes +.fi +.IP +If \fIunset\fP, 8-bit \(lqsubject:\(rq 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. + + +.TP .B mix_entry_format .nf Type: string @@ -2113,6 +2237,129 @@ printf()-like sequences see the section .TP +.B news_cache_dir +.nf +Type: path +Default: \(lq~/.mutt\(rq +.fi +.IP +This variable pointing to directory where Mutt will save cached news +articles headers in. If \fIunset\fP, headers will not be saved at all +and will be reloaded each time when you enter to newsgroup. + + +.TP +.B news_server +.nf +Type: string +Default: \(lq\(rq +.fi +.IP +This variable specifies domain name or address of NNTP server. It +defaults to the newsserver specified in the environment variable +$NNTPSERVER or contained in the file /etc/nntpserver. You can also +specify username and an alternative port for each newsserver, ie: +.IP +[nntp[s]://][username[:password]@]newsserver[:port] + + +.TP +.B newsrc +.nf +Type: path +Default: \(lq~/.newsrc\(rq +.fi +.IP +The file, containing info about subscribed newsgroups - names and +indexes of read articles. The following printf-style sequence +is understood: +.IP + +.IP +.DS +.sp +.ft CR +.nf +%s newsserver name + +.fi +.ec +.ft P +.sp + + +.TP +.B nntp_context +.nf +Type: number +Default: 1000 +.fi +.IP +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. + + +.TP +.B nntp_load_description +.nf +Type: boolean +Default: yes +.fi +.IP +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). + + +.TP +.B nntp_user +.nf +Type: string +Default: \(lq\(rq +.fi +.IP +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 newsserver. + + +.TP +.B nntp_pass +.nf +Type: string +Default: \(lq\(rq +.fi +.IP +Your password for NNTP account. + + +.TP +.B nntp_poll +.nf +Type: number +Default: 60 +.fi +.IP +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.). + + +.TP +.B nntp_reconnect +.nf +Type: quadoption +Default: ask-yes +.fi +.IP +Controls whether or not Mutt will try to reconnect to newsserver when +connection lost. + + +.TP .B pager .nf Type: path @@ -2898,6 +3145,19 @@ string after the inclusion of a message .TP +.B post_moderated +.nf +Type: quadoption +Default: ask-yes +.fi +.IP +If set to \fIyes\fP, Mutt will post article to newsgroup that have +not permissions to posting (e.g. moderated). \fBNote:\fP if newsserver +does not support posting to that newsgroup or totally read-only, that +posting will not have an effect. + + +.TP .B postpone .nf Type: quadoption @@ -3437,6 +3697,41 @@ shell from /etc/passwd is used. .TP +.B save_unsubscribed +.nf +Type: boolean +Default: no +.fi +.IP +When \fIset\fP, info about unsubscribed newsgroups will be saved into +\(lqnewsrc\(rq file and into cache. + + +.TP +.B show_new_news +.nf +Type: boolean +Default: yes +.fi +.IP +If \fIset\fP, newsserver will be asked for new newsgroups on entering +the browser. Otherwise, it will be done only once for a newsserver. +Also controls whether or not number of new articles of subscribed +newsgroups will be then checked. + + +.TP +.B show_only_unread +.nf +Type: boolean +Default: no +.fi +.IP +If \fIset\fP, only subscribed newsgroups that contain unread articles +will be displayed in browser. + + +.TP .B sig_dashes .nf Type: boolean @@ -3999,6 +4294,18 @@ command. .TP +.B use_ipv6 +.nf +Type: boolean +Default: yes +.fi +.IP +When \fIset\fP, Mutt will look for IPv6 addresses of hosts it tries to +contact. If this option is unset, Mutt will restrict itself to IPv4 addresses. +Normally, the default should work. + + +.TP .B user_agent .nf Type: boolean @@ -4100,6 +4407,17 @@ Controls whether mutt writes out the Bcc messages to be sent. Exim users may wish to use this. +.TP +.B x_comment_to +.nf +Type: boolean +Default: no +.fi +.IP +If \fIset\fP, Mutt will add \(lqX-Comment-To:\(rq field (that contains full +name of original article author) to article that followuped to newsgroup. + + .\" -*-nroff-*- .SH SEE ALSO .PP diff -udprP mutt-1.4.2.2.orig/functions.h mutt-1.4.2.2/functions.h --- mutt-1.4.2.2.orig/functions.h 2001-09-11 14:20:34.000000000 +0300 +++ mutt-1.4.2.2/functions.h 2006-07-18 12:45:03.000000000 +0300 @@ -68,6 +68,10 @@ struct binding_t OpMain[] = { { "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 { "collapse-thread", OP_MAIN_COLLAPSE_THREAD, "\033v" }, { "collapse-all", OP_MAIN_COLLAPSE_ALL, "\033V" }, { "copy-message", OP_COPY_MESSAGE, "C" }, @@ -80,7 +84,15 @@ struct binding_t OpMain[] = { { "edit", OP_EDIT_MESSAGE, "e" }, { "edit-type", OP_EDIT_TYPE, "\005" }, { "forward-message", OP_FORWARD_MESSAGE, "f" }, - { "flag-message", OP_FLAG_MESSAGE, "F" }, +#ifdef USE_NNTP + { "forward-to-group", OP_FORWARD_TO_GROUP, "\033F" }, + { "followup-message", OP_FOLLOWUP, "F" }, + { "get-children", OP_GET_CHILDREN, NULL }, + { "get-message", OP_GET_MESSAGE, "\007" }, + { "get-parent", OP_GET_PARENT, "\033G" }, + { "reconstruct-thread", OP_RECONSTRUCT_THREAD, NULL }, +#endif + { "flag-message", OP_FLAG_MESSAGE, "\033f" }, { "group-reply", OP_GROUP_REPLY, "g" }, #ifdef USE_POP { "fetch-mail", OP_MAIN_FETCH_MAIL, "G" }, @@ -105,6 +117,9 @@ struct binding_t OpMain[] = { { "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" }, @@ -124,6 +139,10 @@ struct binding_t OpMain[] = { { "show-version", OP_VERSION, "V" }, { "set-flag", OP_MAIN_SET_FLAG, "w" }, { "clear-flag", OP_MAIN_CLEAR_FLAG, "W" }, + { "toggle-read", OP_TOGGLE_READ, "X" }, +#ifdef USE_NNTP + { "catchup", OP_CATCHUP, "y" }, +#endif { "display-message", OP_DISPLAY_MESSAGE, M_ENTER_S }, { "sync-mailbox", OP_MAIN_SYNC_FOLDER, "$" }, { "display-address", OP_DISPLAY_ADDRESS, "@" }, @@ -132,7 +151,7 @@ struct binding_t OpMain[] = { { "previous-new", OP_MAIN_PREV_NEW, "\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 }, #ifdef HAVE_PGP @@ -154,6 +173,10 @@ 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 { "copy-message", OP_COPY_MESSAGE, "C" }, { "decode-copy", OP_DECODE_COPY, "\033C" }, { "delete-message", OP_DELETE, "d" }, @@ -161,8 +184,12 @@ struct binding_t OpPager[] = { { "delete-subthread", OP_DELETE_SUBTHREAD, "\033d" }, { "edit", OP_EDIT_MESSAGE, "e" }, { "edit-type", OP_EDIT_TYPE, "\005" }, +#ifdef USE_NNTP + { "followup-message", OP_FOLLOWUP, "F" }, + { "forward-to-group", OP_FORWARD_TO_GROUP, "\033F" }, +#endif { "forward-message", OP_FORWARD_MESSAGE, "f" }, - { "flag-message", OP_FLAG_MESSAGE, "F" }, + { "flag-message", OP_FLAG_MESSAGE, "\033f" }, { "group-reply", OP_GROUP_REPLY, "g" }, #ifdef USE_IMAP { "imap-fetch-mail", OP_MAIN_IMAP_FETCH, NULL }, @@ -180,6 +207,9 @@ struct binding_t OpPager[] = { { "next-thread", OP_MAIN_NEXT_THREAD, "\016" }, { "next-subthread", OP_MAIN_NEXT_SUBTHREAD, "\033n" }, { "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" }, { "quit", OP_QUIT, "Q" }, @@ -222,7 +252,7 @@ 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 }, @@ -246,6 +276,10 @@ 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, "|" }, @@ -276,6 +310,7 @@ struct binding_t OpAttach[] = { struct binding_t OpCompose[] = { { "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" }, @@ -295,6 +330,11 @@ 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-file", OP_COMPOSE_RENAME_FILE, "R" }, @@ -345,12 +385,23 @@ 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, " " }, #ifdef USE_IMAP { "create-mailbox", OP_CREATE_MAILBOX, "C" }, { "delete-mailbox", OP_DELETE_MAILBOX, "d" }, +#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.4.2.2.orig/globals.h mutt-1.4.2.2/globals.h --- mutt-1.4.2.2.orig/globals.h 2002-01-03 22:57:19.000000000 +0200 +++ mutt-1.4.2.2/globals.h 2006-07-18 12:45:03.000000000 +0300 @@ -78,6 +78,15 @@ WHERE char *MixEntryFormat; #endif WHERE char *Muttrc INITVAL (NULL); +#ifdef USE_NNTP +WHERE char *NewsCacheDir; +WHERE char *GroupFormat; +WHERE char *Inews; +WHERE char *NewsServer; +WHERE char *NntpUser; +WHERE char *NntpPass; +WHERE char *NewsRc; +#endif WHERE char *Outbox; WHERE char *Pager; WHERE char *PagerFmt; @@ -142,6 +151,11 @@ extern unsigned char QuadOptions[]; WHERE unsigned short Counter INITVAL (0); +#ifdef USE_NNTP +WHERE short NewsPollTimeout; +WHERE short NntpContext; +#endif + WHERE short ConnectTimeout; WHERE short HistSize; WHERE short PagerContext; diff -udprP mutt-1.4.2.2.orig/hash.c mutt-1.4.2.2/hash.c --- mutt-1.4.2.2.orig/hash.c 2001-11-19 12:41:32.000000000 +0200 +++ mutt-1.4.2.2/hash.c 2006-07-18 12:45:03.000000000 +0300 @@ -47,10 +47,35 @@ HASH *hash_create (int nelem) if (nelem == 0) nelem = 2; table->nelem = nelem; + table->curnelem = 0; table->table = safe_calloc (nelem, sizeof (struct hash_elem *)); return table; } +HASH *hash_resize (HASH *ptr, int nelem) +{ + HASH *table; + struct hash_elem *elem, *tmp; + int i; + + table = hash_create (nelem); + + for (i = 0; i < ptr->nelem; i++) + { + for (elem = ptr->table[i]; elem; ) + { + tmp = elem; + elem = elem->next; + hash_insert (table, tmp->key, tmp->data, 1); + FREE (&tmp); + } + } + FREE (&ptr->table); + FREE (&ptr); + + return table; +} + /* table hash table to update * key key to hash on * data data to associate with `key' @@ -70,6 +95,7 @@ int hash_insert (HASH * table, const cha { ptr->next = table->table[h]; table->table[h] = ptr; + table->curnelem++; } else { @@ -92,6 +118,7 @@ int hash_insert (HASH * table, const cha else table->table[h] = ptr; ptr->next = tmp; + table->curnelem++; } return h; } @@ -124,6 +151,7 @@ void hash_delete_hash (HASH * table, int *last = ptr->next; if (destroy) destroy (ptr->data); FREE (&ptr); + table->curnelem--; return; } } diff -udprP mutt-1.4.2.2.orig/hash.h mutt-1.4.2.2/hash.h --- mutt-1.4.2.2.orig/hash.h 2000-03-03 12:10:08.000000000 +0200 +++ mutt-1.4.2.2/hash.h 2006-07-18 12:45:03.000000000 +0300 @@ -28,7 +28,7 @@ struct hash_elem typedef struct { - int nelem; + int nelem, curnelem; struct hash_elem **table; } HASH; @@ -40,6 +40,7 @@ HASH; HASH *hash_create (int nelem); int hash_string (const unsigned char *s, int n); int hash_insert (HASH * table, const char *key, void *data, int allow_dup); +HASH *hash_resize (HASH * table, int nelem); void *hash_find_hash (const HASH * table, int hash, const char *key); void hash_delete_hash (HASH * table, int hash, const char *key, const void *data, void (*destroy) (void *)); diff -udprP mutt-1.4.2.2.orig/hdrline.c mutt-1.4.2.2/hdrline.c --- mutt-1.4.2.2.orig/hdrline.c 2002-03-25 13:29:32.000000000 +0200 +++ mutt-1.4.2.2/hdrline.c 2006-07-18 12:45:03.000000000 +0300 @@ -211,6 +211,7 @@ int mutt_user_is_recipient (HEADER *h) * %E = number of messages in current thread * %f = entire from line * %F = like %n, unless from self + * %g = newsgroup name (if compiled with nntp support) * %i = message-id * %l = number of lines in the message * %L = like %F, except `lists' are displayed first @@ -224,6 +225,7 @@ int mutt_user_is_recipient (HEADER *h) * %T = $to_chars * %u = user (login) name of author * %v = first name of author, unless from self + * %W = where user is (organization) * %y = `x-label:' field (if present) * %Y = `x-label:' field (if present, tree unfolded, and != parent's x-label) * %Z = status flags */ @@ -440,6 +442,12 @@ hdr_format_str (char *dest, optional = 0; break; +#ifdef USE_NNTP + case 'g': + mutt_format_s (dest, destlen, prefix, hdr->env->newsgroups ? hdr->env->newsgroups : ""); + break; +#endif + case 'i': mutt_format_s (dest, destlen, prefix, hdr->env->message_id ? hdr->env->message_id : "<no.id>"); break; @@ -619,6 +627,13 @@ hdr_format_str (char *dest, mutt_format_s (dest, destlen, prefix, buf2); break; + case 'W': + if (!optional) + mutt_format_s (dest, destlen, prefix, hdr->env->organization ? hdr->env->organization : ""); + else if (!hdr->env->organization) + optional = 0; + break; + case 'Z': ch = ' '; diff -udprP mutt-1.4.2.2.orig/headers.c mutt-1.4.2.2/headers.c --- mutt-1.4.2.2.orig/headers.c 2001-11-05 23:19:33.000000000 +0200 +++ mutt-1.4.2.2/headers.c 2006-07-18 12:45:03.000000000 +0300 @@ -115,6 +115,9 @@ void mutt_edit_headers (const char *edit msg->env = n; n = NULL; if (!msg->env->in_reply_to) +#ifdef USE_NNTP + if (!option (OPTNEWSSEND)) +#endif mutt_free_list (&msg->env->references); mutt_expand_aliases_env (msg->env); diff -udprP mutt-1.4.2.2.orig/init.c mutt-1.4.2.2/init.c --- mutt-1.4.2.2.orig/init.c 2002-07-24 11:41:29.000000000 +0300 +++ mutt-1.4.2.2/init.c 2006-07-18 12:45:03.000000000 +0300 @@ -1790,6 +1790,28 @@ void mutt_init (int skip_sys_rc, LIST *c else Fqdn = safe_strdup(NONULL(Hostname)); +#ifdef USE_NNTP + { + FILE *f; + char *i; + + if ((f = safe_fopen (SYSCONFDIR "/nntpserver", "r"))) + { + buffer[0] = '\0'; + fgets (buffer, sizeof (buffer), f); + p = &buffer; + SKIPWS (p); + i = p; + while (*i && (*i != ' ') && (*i != '\t') && (*i != '\r') && (*i != '\n')) i++; + *i = '\0'; + NewsServer = safe_strdup (p); + fclose (f); + } + } + if ((p = getenv ("NNTPSERVER"))) + NewsServer = safe_strdup (p); +#endif + if ((p = getenv ("MAIL"))) Spoolfile = safe_strdup (p); else if ((p = getenv ("MAILDIR"))) diff -udprP mutt-1.4.2.2.orig/init.h mutt-1.4.2.2/init.h --- mutt-1.4.2.2.orig/init.h 2002-07-24 11:41:29.000000000 +0300 +++ mutt-1.4.2.2/init.h 2006-07-18 12:45:03.000000000 +0300 @@ -193,6 +193,20 @@ struct option_t MuttVars[] = { ** If set, 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 { "attach_format", DT_STR, R_NONE, UL &AttachFormat, UL "%u%D%I %t%4n %T%.40d%> [%.7m/%.10M, %.6e%?C?, %C?, %s] " }, /* ** .pp @@ -273,6 +287,14 @@ struct option_t MuttVars[] = { ** When this variable is set, mutt will include Delivered-To headers when ** bouncing messages. Postfix users may wish to unset this variable. */ +#ifdef USE_NNTP + { "catchup_newsgroup", DT_QUAD, R_NONE, OPT_CATCHUP, M_ASKYES }, + /* + ** .pp + ** If this variable is \fIset\fP, Mutt will mark all articles in newsgroup + ** as read when you quit the newsgroup (catchup newsgroup). + */ +#endif { "charset", DT_STR, R_NONE, UL &Charset, UL 0 }, /* ** .pp @@ -559,6 +581,16 @@ struct option_t MuttVars[] = { ** subscribed list will be sent to both the list and your address, ** resulting in two copies of the same email for you. */ +#ifdef USE_NNTP + { "followup_to_poster", DT_QUAD, R_NONE, OPT_FOLLOWUPTOPOSTER, M_ASKYES }, + /* + ** .pp + ** If this variable is \fIset\fP and the keyword "poster" is present in + ** \fIFollowup-To\fP header, follow-up to newsgroup function is not + ** permitted. The message will be mailed to the submitter of the + ** message via mail. + */ +#endif { "force_name", DT_BOOL, R_NONE, OPTFORCENAME, 0 }, /* ** .pp @@ -622,6 +654,27 @@ 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: + ** .pp + ** .ts + ** %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" + ** .te + */ +#endif { "hdr_format", DT_SYN, R_NONE, UL "index_format", 0 }, /* */ @@ -868,6 +921,7 @@ struct option_t MuttVars[] = { ** .dt %E .dd number of messages in current thread ** .dt %f .dd entire From: line (address + real name) ** .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 %i .dd message-id of the current message ** .dt %l .dd number of lines in the message ** .dt %L .dd If an address in the To or CC header field matches an address @@ -885,6 +939,7 @@ 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 %y .dd `x-label:' field, if present ** .dt %Y .dd `x-label' field, if present, and (1) not at part of a thread tree, ** (2) at the top of a thread, or (3) `x-label' is different from @@ -907,6 +962,21 @@ struct option_t MuttVars[] = { ** .pp ** See also: ``$$to_chars''. */ +#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: + ** .pp + ** .ts + ** %s newsserver name + ** .te + ** .pp + ** Example: set inews="/usr/local/bin/inews -hS" + */ +#endif { "ispell", DT_PATH, R_NONE, UL &Ispell, UL ISPELL }, /* ** .pp @@ -1071,6 +1141,16 @@ struct option_t MuttVars[] = { ** be attached to the newly composed message if this option is set. */ +#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" }, /* @@ -1114,6 +1194,77 @@ struct option_t MuttVars[] = { { "msg_format", DT_SYN, R_NONE, UL "message_format", 0 }, /* */ +#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 headers in. If \fIunset\fP, headers will not be saved at all + ** and will be reloaded each time when you enter to newsgroup. + */ + { "news_server", DT_STR, R_NONE, UL &NewsServer, 0 }, + /* + ** .pp + ** This variable specifies domain name or address of NNTP server. It + ** defaults to the newsserver specified in the environment variable + ** $$$NNTPSERVER or contained in the file /etc/nntpserver. You can also + ** specify username and an alternative port for each newsserver, ie: + ** .pp + ** [nntp[s]://][username[:password]@]newsserver[:port] + */ + { "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: + ** .pp + ** .ts + ** %s newsserver name + ** .te + */ + { "nntp_context", DT_NUM, R_NONE, UL &NntpContext, 1000 }, + /* + ** .pp + ** This variable defines number of articles which will be in index when + ** newsgroup entered. If active newsgroup have more articles than this + ** number, oldest articles will be ignored. Also controls how many + ** articles headers will be saved in cache when you quit newsgroup. + */ + { "nntp_load_description", DT_BOOL, R_NONE, OPTLOADDESC, 1 }, + /* + ** .pp + ** This variable controls whether or not descriptions for each newsgroup + ** must be loaded when newsgroup is added to list (first time list + ** loading or new newsgroup adding). + */ + { "nntp_user", DT_STR, R_NONE, UL &NntpUser, UL "" }, + /* + ** .pp + ** Your login name on the NNTP server. If \fIunset\fP and NNTP server requires + ** authentification, Mutt will prompt you for your account name when you + ** connect to newsserver. + */ + { "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.). + */ + { "nntp_reconnect", DT_QUAD, R_NONE, OPT_NNTPRECONNECT, M_ASKYES }, + /* + ** .pp + ** Controls whether or not Mutt will try to reconnect to newsserver when + ** connection lost. + */ +#endif { "pager", DT_PATH, R_NONE, UL &Pager, UL "builtin" }, /* ** .pp @@ -1603,6 +1754,16 @@ struct option_t MuttVars[] = { { "post_indent_str", DT_SYN, R_NONE, UL "post_indent_string", 0 }, /* */ +#ifdef USE_NNTP + { "post_moderated", DT_QUAD, R_NONE, OPT_TOMODERATED, M_ASKYES }, + /* + ** .pp + ** If set to \fIyes\fP, Mutt will post article to newsgroup that have + ** not permissions to posting (e.g. moderated). \fBNote:\fP if newsserver + ** does not support posting to that newsgroup or totally read-only, that + ** posting will not have an effect. + */ +#endif { "postpone", DT_QUAD, R_NONE, OPT_POSTPONE, M_ASKYES }, /* ** .pp @@ -1948,6 +2109,28 @@ struct option_t MuttVars[] = { ** Command to use when spawning a subshell. By default, the user's login ** shell from /etc/passwd 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, newsserver will be asked for new newsgroups on entering + ** the browser. Otherwise, it will be done only once for a newsserver. + ** Also controls whether or not number of new articles of subscribed + ** newsgroups will be then checked. + */ + { "show_only_unread", DT_BOOL, R_NONE, OPTSHOWONLYUNREAD, 0 }, + /* + ** .pp + ** If \fIset\fP, only subscribed newsgroups that contain unread articles + ** will be displayed in browser. + */ +#endif { "sig_dashes", DT_BOOL, R_NONE, OPTSIGDASHES, 1 }, /* ** .pp @@ -2366,6 +2549,14 @@ struct option_t MuttVars[] = { ** Controls whether mutt writes out the Bcc header when preparing ** messages to be sent. Exim users may wish to use this. */ +#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 } }; diff -udprP mutt-1.4.2.2.orig/keymap.c mutt-1.4.2.2/keymap.c --- mutt-1.4.2.2.orig/keymap.c 2001-10-16 17:29:27.000000000 +0300 +++ mutt-1.4.2.2/keymap.c 2006-07-18 12:45:03.000000000 +0300 @@ -589,7 +589,6 @@ void km_init (void) km_bindkey ("<enter>", MENU_MAIN, OP_DISPLAY_MESSAGE); km_bindkey ("x", MENU_PAGER, OP_EXIT); - km_bindkey ("i", MENU_PAGER, OP_EXIT); km_bindkey ("<backspace>", MENU_PAGER, OP_PREV_LINE); km_bindkey ("<pagedown>", MENU_PAGER, OP_NEXT_PAGE); km_bindkey ("<pageup>", MENU_PAGER, OP_PREV_PAGE); diff -udprP mutt-1.4.2.2.orig/mailbox.h mutt-1.4.2.2/mailbox.h --- mutt-1.4.2.2.orig/mailbox.h 2002-03-20 11:53:42.000000000 +0200 +++ mutt-1.4.2.2/mailbox.h 2006-07-18 12:45:03.000000000 +0300 @@ -73,6 +73,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); diff -udprP mutt-1.4.2.2.orig/main.c mutt-1.4.2.2/main.c --- mutt-1.4.2.2.orig/main.c 2002-07-16 10:28:00.000000000 +0300 +++ mutt-1.4.2.2/main.c 2006-07-18 12:45:03.000000000 +0300 @@ -37,6 +37,10 @@ #include <getopt.h> #endif +#ifdef USE_NNTP +#include <nntp.h> +#endif + static const char *ReachingUs = N_("\ To contact the developers, please mail to <mutt-dev@mutt.org>.\n\ To report a bug, please use the flea(1) utility.\n"); @@ -97,6 +101,8 @@ options:\n\ -e <command>\tspecify a command to be executed after initialization\n\ -f <file>\tspecify which mailbox to read\n\ -F <file>\tspecify an alternate muttrc file\n\ + -g <server>\tspecify a newsserver (if compiled with NNTP)\n\ + -G\t\tselect a newsgroup (if compiled with NNTP)\n\ -H <file>\tspecify a draft file to read header from\n\ -i <file>\tspecify a file which Mutt should include in the reply\n\ -m <type>\tspecify a default mailbox type\n\ @@ -202,6 +208,12 @@ static void show_version (void) "-USE_POP " #endif +#ifdef USE_NNTP + "+USE_NNTP " +#else + "-USE_NNTP " +#endif + #ifdef USE_IMAP "+USE_IMAP " #else @@ -438,6 +450,9 @@ static void start_curses (void) #define M_NOSYSRC (1<<2) /* -n */ #define M_RO (1<<3) /* -R */ #define M_SELECT (1<<4) /* -y */ +#ifdef USE_NNTP +#define M_NEWS (1<<5) /* -g and -G */ +#endif int main (int argc, char **argv) { @@ -483,7 +498,11 @@ int main (int argc, char **argv) memset (Options, 0, sizeof (Options)); memset (QuadOptions, 0, sizeof (QuadOptions)); +#ifdef USE_NNTP + while ((i = getopt (argc, argv, "a:b:F:f:c:d:e:H:s:i:hm:npRvxyg:GzZ")) != EOF) +#else while ((i = getopt (argc, argv, "a:b:F:f:c:d:e:H:s:i:hm:npRvxyzZ")) != EOF) +#endif switch (i) { case 'a': @@ -565,6 +584,20 @@ int main (int argc, char **argv) flags |= M_SELECT; break; +#ifdef USE_NNTP + case 'g': /* Specify a newsserver */ + { + char buf[LONG_STRING]; + + snprintf (buf, sizeof (buf), "set news_server=%s", optarg); + commands = mutt_add_list (commands, buf); + } + + case 'G': /* List of newsgroups */ + flags |= M_SELECT | M_NEWS; + break; +#endif + case 'z': flags |= M_IGNORE; break; @@ -796,6 +829,18 @@ int main (int argc, char **argv) } else if (flags & M_SELECT) { +#ifdef USE_NNTP + if (flags & M_NEWS) + { + set_option (OPTNEWS); + if(!(CurrentNewsSrv = mutt_select_newsserver (NewsServer))) + { + mutt_endwin (Errorbuf); + exit (1); + } + } + else +#endif if (!Incoming) { mutt_endwin _("No incoming mailboxes defined."); exit (1); @@ -811,6 +856,15 @@ int main (int argc, char **argv) if (!folder[0]) strfcpy (folder, NONULL(Spoolfile), sizeof (folder)); + +#ifdef USE_NNTP + if (option (OPTNEWS)) + { + unset_option (OPTNEWS); + nntp_expand_path (folder, sizeof (folder), &CurrentNewsSrv->conn->account); + } + else +#endif mutt_expand_path (folder, sizeof (folder)); mutt_str_replace (&LastFolder, folder); diff -udprP mutt-1.4.2.2.orig/mutt.h mutt-1.4.2.2/mutt.h --- mutt-1.4.2.2.orig/mutt.h 2002-07-24 12:46:58.000000000 +0300 +++ mutt-1.4.2.2/mutt.h 2006-07-18 12:45:03.000000000 +0300 @@ -229,6 +229,9 @@ enum M_PGP_KEY, #endif M_XLABEL, +#ifdef USE_NNTP + M_NEWSGROUPS, +#endif /* Options for Mailcap lookup */ M_EDIT, @@ -264,6 +267,12 @@ enum OPT_VERIFYSIG, /* verify PGP signatures */ OPT_PGPTRADITIONAL, /* create old-style PGP messages */ #endif +#ifdef USE_NNTP + OPT_TOMODERATED, + OPT_NNTPRECONNECT, + OPT_CATCHUP, + OPT_FOLLOWUPTOPOSTER, +#endif /* USE_NNTP */ #ifdef USE_SSL OPT_SSLSTARTTLS, @@ -300,6 +309,7 @@ enum #define SENDMAILX (1<<6) #define SENDKEY (1<<7) #define SENDRESEND (1<<8) +#define SENDNEWS (1<<9) /* flags to _mutt_select_file() */ #define M_SEL_BUFFY (1<<0) @@ -316,6 +326,8 @@ enum OPTASCIICHARS, OPTASKBCC, OPTASKCC, + OPTASKFOLLOWUP, + OPTASKXCOMMENTTO, OPTATTACHSPLIT, OPTAUTOEDIT, OPTAUTOTAG, @@ -374,6 +386,9 @@ enum OPTMETOO, OPTMHPURGE, OPTMIMEFORWDECODE, +#ifdef USE_NNTP + OPTMIMESUBJECT, /* encode subject line with RFC2047 */ +#endif OPTPAGERSTOP, OPTPIPEDECODE, OPTPIPESPLIT, @@ -438,6 +453,16 @@ enum OPTPGPSHOWUNUSABLE, #endif + /* news options */ + +#ifdef USE_NNTP + OPTSHOWNEWNEWS, + OPTSHOWONLYUNREAD, + OPTSAVEUNSUB, + OPTLOADDESC, + OPTXCOMMENTTO, +#endif /* USE_NNTP */ + /* pseudo options */ OPTAUXSORT, /* (pseudo) using auxillary sort function */ @@ -458,6 +483,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. @@ -468,6 +494,12 @@ enum OPTDONTHANDLEPGPKEYS, /* (pseudo) used to extract PGP keys */ #endif +#ifdef USE_NNTP + OPTNEWS, /* (pseudo) used to change reader mode */ + OPTNEWSSEND, /* (pseudo) used to change behavior when posting */ + OPTNEWSCACHE, /* (pseudo) used to indicate if news cache exist */ +#endif + @@ -531,6 +563,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 LIST *references; /* message references (in reverse order) */ LIST *in_reply_to; /* in-reply-to header content */ LIST *userhdrs; /* user defined headers */ @@ -674,6 +713,9 @@ typedef struct header ENVELOPE *env; /* envelope information */ BODY *content; /* list of MIME parts */ char *path; +#ifdef USE_NNTP + int article_num; +#endif char *tree; /* character string to print thread tree */ struct thread *thread; @@ -686,7 +728,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 } HEADER; @@ -755,7 +797,7 @@ typedef struct int deleted; /* how many deleted messages */ int flagged; /* how many flagged messages */ int msgnotreadyet; /* which msg "new" in pager, -1 if none */ -#if defined USE_POP || defined USE_IMAP +#if defined USE_POP || defined USE_IMAP || defined USE_NNTP void *data; /* driver specific data */ #endif /* USE_IMAP */ diff -udprP mutt-1.4.2.2.orig/muttlib.c mutt-1.4.2.2/muttlib.c --- mutt-1.4.2.2.orig/muttlib.c 2002-03-25 13:29:32.000000000 +0200 +++ mutt-1.4.2.2/muttlib.c 2006-07-18 12:45:03.000000000 +0300 @@ -270,7 +270,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 safe_free ((void**) &(*h)->data); #endif safe_free ((void **) h); @@ -648,6 +648,13 @@ void mutt_free_envelope (ENVELOPE **p) safe_free ((void **) &(*p)->message_id); safe_free ((void **) &(*p)->supersedes); safe_free ((void **) &(*p)->date); + safe_free ((void **) &(*p)->organization); +#ifdef USE_NNTP + safe_free ((void **) &(*p)->newsgroups); + safe_free ((void **) &(*p)->xref); + safe_free ((void **) &(*p)->followup_to); + safe_free ((void **) &(*p)->x_comment_to); +#endif mutt_free_list (&(*p)->references); mutt_free_list (&(*p)->in_reply_to); mutt_free_list (&(*p)->userhdrs); @@ -1144,6 +1151,14 @@ int mutt_save_confirm (const char *s, st } #endif +#ifdef USE_NNTP + if (magic == M_NNTP) + { + mutt_error _("Can't save message to newsserver."); + return 0; + } +#endif + if (stat (s, st) != -1) { if (magic == -1) diff -udprP mutt-1.4.2.2.orig/mx.c mutt-1.4.2.2/mx.c --- mutt-1.4.2.2.orig/mx.c 2002-03-20 11:53:42.000000000 +0200 +++ mutt-1.4.2.2/mx.c 2006-07-18 12:45:03.000000000 +0300 @@ -38,6 +38,10 @@ #include "pop.h" #endif +#ifdef USE_NNTP +#include "nntp.h" +#endif + #ifdef BUFFY_SIZE #include "buffy.h" #endif @@ -350,6 +354,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; @@ -367,6 +387,11 @@ int mx_get_magic (const char *path) return M_POP; #endif /* USE_POP */ +#ifdef USE_NNTP + if (mx_is_nntp (path)) + return M_NNTP; +#endif /* USE_NNTP */ + if (stat (path, &st) == -1) { dprint (1, (debugfile, "mx_get_magic(): unable to stat %s: %s (errno %d).\n", @@ -701,6 +726,12 @@ CONTEXT *mx_open_mailbox (const char *pa break; #endif /* USE_POP */ +#ifdef USE_NNTP + case M_NNTP: + rc = nntp_open_mailbox (ctx); + break; +#endif /* USE_NNTP */ + default: rc = -1; break; @@ -746,6 +777,10 @@ void mx_fastclose_mailbox (CONTEXT *ctx) if (ctx->magic == M_POP) pop_close_mailbox (ctx); #endif /* USE_POP */ +#ifdef USE_NNTP + if (ctx->magic == M_NNTP) + nntp_fastclose_mailbox (ctx); +#endif /* USE_NNTP */ if (ctx->id_hash) hash_destroy (&ctx->id_hash, NULL); mutt_clear_threads (ctx); @@ -799,6 +834,12 @@ static int sync_mailbox (CONTEXT *ctx, i rc = pop_sync_mailbox (ctx, index_hint); break; #endif /* USE_POP */ + +#ifdef USE_NNTP + case M_NNTP: + rc = nntp_sync_mailbox (ctx); + break; +#endif /* USE_NNTP */ } #if 0 @@ -827,6 +868,16 @@ int mx_close_mailbox (CONTEXT *ctx, int ctx->closing = 1; +#ifdef USE_NNTP + if (ctx->magic == M_NNTP) + { + int ret; + + ret = nntp_close_mailbox (ctx); + mx_fastclose_mailbox (ctx); + return ret; + } +#endif if (ctx->readonly || ctx->dontwrite) { /* mailbox is readonly or we don't want to write */ @@ -1364,6 +1415,11 @@ int mx_check_mailbox (CONTEXT *ctx, int case M_POP: return (pop_check_mailbox (ctx, index_hint)); #endif /* USE_POP */ + +#ifdef USE_NNTP + case M_NNTP: + return (nntp_check_mailbox (ctx)); +#endif /* USE_NNTP */ } } @@ -1424,6 +1480,15 @@ MESSAGE *mx_open_message (CONTEXT *ctx, } #endif /* USE_POP */ +#ifdef USE_NNTP + case M_NNTP: + { + if (nntp_fetch_message (msg, ctx, msgno) != 0) + FREE (&msg); + break; + } +#endif /* USE_NNTP */ + default: dprint (1, (debugfile, "mx_open_message(): function not implemented for mailbox type %d.\n", ctx->magic)); FREE (&msg); @@ -1512,6 +1577,9 @@ int mx_close_message (MESSAGE **msg) #ifdef USE_POP || (*msg)->magic == M_POP #endif +#ifdef USE_NNTP + || (*msg)->magic == M_NNTP +#endif ) { r = safe_fclose (&(*msg)->fp); diff -udprP mutt-1.4.2.2.orig/mx.h mutt-1.4.2.2/mx.h --- mutt-1.4.2.2.orig/mx.h 2001-10-31 11:20:38.000000000 +0200 +++ mutt-1.4.2.2/mx.h 2006-07-18 12:45:03.000000000 +0300 @@ -41,6 +41,9 @@ enum #ifdef USE_POP , M_POP #endif +#ifdef USE_NNTP + , M_NNTP +#endif }; WHERE short DefaultMagic INITVAL (M_MBOX); diff -udprP mutt-1.4.2.2.orig/newsrc.c mutt-1.4.2.2/newsrc.c --- mutt-1.4.2.2.orig/newsrc.c 1970-01-01 03:00:00.000000000 +0300 +++ mutt-1.4.2.2/newsrc.c 2006-07-18 13:01:35.000000000 +0300 @@ -0,0 +1,1043 @@ +/* + * Copyright (C) 1998 Brandon Long <blong@fiction.net> + * Copyright (C) 1999 Andrej Gritsenko <andrej@lucky.net> + * Copyright (C) 2000-2006 Vsevolod Volkov <vvv@mutt.org.ua> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#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 <unistd.h> +#include <string.h> +#include <ctype.h> +#include <stdlib.h> +#include <sys/stat.h> + +void nntp_add_to_list (NNTP_SERVER *s, NNTP_DATA *d) +{ + LIST *l; + + if (!s || !d) + return; + + l = safe_calloc (1, sizeof (LIST)); + if (s->list) + s->tail->next = l; + else + s->list = l; + s->tail = l; + l->data = (void *) d; +} + +static int nntp_parse_newsrc_line (NNTP_SERVER *news, char *line) +{ + NNTP_DATA *data; + char group[LONG_STRING]; + int x = 1; + char *p = line, *b, *h; + size_t len; + + while (*p) + { + if (*p++ == ',') + x++; + } + + p = line; + while (*p && (*p != ':' && *p != '!')) p++; + if (!*p) + return -1; + len = p + 1 - line; + if (len > sizeof (group)) + len = sizeof (group); + strfcpy (group, line, len); + if ((data = (NNTP_DATA *)hash_find (news->newsgroups, group)) == NULL) + { + data = (NNTP_DATA *) safe_calloc (1, sizeof (NNTP_DATA) + strlen (group) + 1); + data->group = (char *) data + sizeof (NNTP_DATA); + strcpy (data->group, group); + data->nserv = news; + data->deleted = 1; + if (news->newsgroups->nelem < news->newsgroups->curnelem * 2) + news->newsgroups = hash_resize (news->newsgroups, news->newsgroups->nelem * 2); + hash_insert (news->newsgroups, data->group, data, 0); + nntp_add_to_list (news, data); + } + else + FREE ((void **) &data->entries); + + data->rc = 1; + data->entries = safe_calloc (x*2, sizeof (NEWSRC_ENTRY)); + data->max = x*2; + + if (*p == ':') + data->subscribed = 1; + else + data->subscribed = 0; + + p++; + b = p; + x = 0; + while (*b) + { + while (*p && *p != ',' && *p != '\n') p++; + if (*p) + { + *p = '\0'; + p++; + } + if ((h = strchr(b, '-'))) + { + *h = '\0'; + h++; + data->entries[x].first = atoi(b); + data->entries[x].last = atoi(h); + } + else + { + data->entries[x].first = atoi(b); + data->entries[x].last = data->entries[x].first; + } + b = p; + if (data->entries[x].last != 0) + x++; + } + if (x && !data->lastMessage) + data->lastMessage = data->entries[x-1].last; + data->num = x; + mutt_newsgroup_stat (data); + dprint (2, (debugfile, "parse_line: Newsgroup %s\n", data->group)); + + return 0; +} + +static int slurp_newsrc (NNTP_SERVER *news) +{ + FILE *fp; + char *buf; + struct stat sb; + + news->stat = stat (news->newsrc, &sb); + news->size = sb.st_size; + news->mtime = sb.st_mtime; + + if ((fp = safe_fopen (news->newsrc, "r")) == NULL) + return -1; + /* hmm, should we use dotlock? */ + if (mx_lock_file (news->newsrc, fileno (fp), 0, 0, 1)) + { + fclose (fp); + return -1; + } + + buf = safe_malloc (sb.st_size + 1); + while (sb.st_size && fgets (buf, sb.st_size + 1, fp)) + nntp_parse_newsrc_line (news, buf); + FREE (&buf); + + mx_unlock_file (news->newsrc, fileno (fp), 0); + fclose (fp); + return 0; +} + +void nntp_cache_expand (char *dst, const char *src) +{ + snprintf (dst, _POSIX_PATH_MAX, "%s/%s", NewsCacheDir, src); + mutt_expand_path (dst, _POSIX_PATH_MAX); +} + +/* Loads $news_cache_dir/.index into memory, loads newsserver data + * and newsgroup cache names */ +static int nntp_parse_cacheindex (NNTP_SERVER *news) +{ + struct stat st; + char buf[HUGE_STRING], *cp; + char dir[_POSIX_PATH_MAX], file[_POSIX_PATH_MAX]; + FILE *index; + NNTP_DATA *data; + int l, m, t; + + /* check is server name defined or not */ + if (!news || !news->conn || !news->conn->account.host) + return -1; + unset_option (OPTNEWSCACHE); + if (!NewsCacheDir || !*NewsCacheDir) + return 0; + + strfcpy (dir, NewsCacheDir, sizeof (dir)); + mutt_expand_path (dir, sizeof(dir)); + + if (lstat (dir, &st) || (st.st_mode & S_IFDIR) == 0) + { + snprintf (buf, sizeof(buf), _("Directory %s not exist. Create it?"), dir); + if (mutt_yesorno (buf, M_YES) != M_YES || mkdir (dir, (S_IRWXU+S_IRWXG+ + S_IRWXO))) + { + mutt_error _("Cache directory not created!"); + return -1; + } + mutt_clear_error(); + } + + set_option (OPTNEWSCACHE); + + FREE (&news->cache); + snprintf (buf, sizeof(buf), "%s/.index", dir); + if (!(index = safe_fopen (buf, "a+"))) + return 0; + rewind (index); + while (fgets (buf, sizeof(buf), index)) + { + buf[strlen(buf) - 1] = 0; /* strip ending '\n' */ + if (!mutt_strncmp (buf, "#: ", 3) && + !mutt_strcasecmp (buf+3, news->conn->account.host)) + break; + } + while (fgets (buf, sizeof(buf), index)) + { + cp = buf; + while (*cp && *cp != ' ') cp++; + if (!*cp) continue; + cp[0] = 0; + if (!mutt_strcmp (buf, "#:")) + break; + sscanf (cp + 1, "%s %d %d", file, &l, &m); + if (!mutt_strcmp (buf, "ALL")) + { + news->cache = safe_strdup (file); + news->newgroups_time = m; + } + else if (news->newsgroups) + { + if ((data = (NNTP_DATA *)hash_find (news->newsgroups, buf)) == NULL) + { + data = (NNTP_DATA *) safe_calloc (1, sizeof (NNTP_DATA) + strlen (buf) + 1); + data->group = (char *) data + sizeof (NNTP_DATA); + strcpy(data->group, buf); + data->nserv = news; + data->deleted = 1; + if (news->newsgroups->nelem < news->newsgroups->curnelem * 2) + news->newsgroups = hash_resize (news->newsgroups, news->newsgroups->nelem * 2); + hash_insert (news->newsgroups, data->group, data, 0); + nntp_add_to_list (news, data); + } + data->cache = safe_strdup (file); + t = 0; + if (!data->firstMessage || data->lastMessage < m) + t = 1; + if (!data->firstMessage) + data->firstMessage = l; + if (data->lastMessage < m) + data->lastMessage = m; + data->lastCached = m; + if (t || !data->unread) + mutt_newsgroup_stat (data); + } + } + fclose (index); + return 0; +} + +const char * +nntp_format_str (char *dest, size_t destlen, 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]; + + switch (op) + { + case 's': + strncpy (fn, NewsServer, sizeof(fn) - 1); + mutt_strlower (fn); + snprintf (tmp, sizeof (tmp), "%%%ss", fmt); + snprintf (dest, destlen, tmp, fn); + break; + } + return (src); +} + +/* nntp_parse_url: given an NNPT URL, return host, port, + * username, password and newsgroup will recognise. */ +int nntp_parse_url (const char *server, ACCOUNT *acct, + char *group, size_t group_len) +{ + ciss_url_t url; + char *c; + int ret = -1; + + /* Defaults */ + acct->flags = 0; + acct->port = NNTP_PORT; + acct->type = M_ACCT_TYPE_NNTP; + + c = safe_strdup (server); + url_parse_ciss (&url, c); + + if (url.scheme == U_NNTP || url.scheme == U_NNTPS) + { + if (url.scheme == U_NNTPS) + { + acct->flags |= M_ACCT_SSL; + acct->port = NNTP_SSL_PORT; + } + + *group = '\0'; + if (url.path) + strfcpy (group, url.path, group_len); + + ret = mutt_account_fromurl (acct, &url); + } + + FREE (&c); + return ret; +} + +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); +} + +/* + * 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. + * + * a newsrc file is a line per newsgroup, with the newsgroup, then a + * ':' denoting subscribed or '!' denoting unsubscribed, then a + * comma separated list of article numbers and ranges. + */ +NNTP_SERVER *mutt_select_newsserver (char *server) +{ + char file[_POSIX_PATH_MAX]; + char *buf, *p; + LIST *list; + ACCOUNT acct; + NNTP_SERVER *serv; + CONNECTION *conn; + + if (!server || !*server) + { + mutt_error _("No newsserver defined!"); + return NULL; + } + + buf = p = safe_calloc (strlen (server) + 10, sizeof (char)); + if (url_check_scheme (server) == U_UNKNOWN) + { + strcpy (buf, "nntp://"); + p = strchr (buf, '\0'); + } + strcpy (p, server); + + if ((nntp_parse_url (buf, &acct, file, sizeof (file))) < 0 || *file) + { + FREE (&buf); + mutt_error (_("%s is an invalid newsserver specification!"), server); + return NULL; + } + FREE (&buf); + + conn = mutt_conn_find (NULL, &acct); + if (!conn) + return NULL; + + mutt_FormatString (file, sizeof (file), NONULL (NewsRc), nntp_format_str, 0, 0); + mutt_expand_path (file, sizeof (file)); + + serv = (NNTP_SERVER *)conn->data; + if (serv) + { + struct stat sb; + + /* externally modified? */ + if (serv->stat != stat (file, &sb) || (!serv->stat && + (serv->size != sb.st_size || serv->mtime != sb.st_mtime))) + { + for (list = serv->list; list; list = list->next) + { + NNTP_DATA *data = (NNTP_DATA *) list->data; + + if (data) + { + data->subscribed = 0; + data->rc = 0; + data->num = 0; + } + } + slurp_newsrc (serv); + nntp_clear_cacheindex (serv); + } + + if (serv->status == NNTP_BYE) + serv->status = NNTP_NONE; + nntp_check_newgroups (serv, 0); + return serv; + } + + /* New newsserver */ + serv = safe_calloc (1, sizeof (NNTP_SERVER)); + serv->conn = conn; + serv->newsrc = safe_strdup (file); + serv->newsgroups = hash_create (1009); + slurp_newsrc (serv); /* load .newsrc */ + nntp_parse_cacheindex (serv); /* load .index */ + if (option (OPTNEWSCACHE) && serv->cache && nntp_get_cache_all (serv) >= 0) + nntp_check_newgroups (serv, 1); + else if (nntp_get_active (serv) < 0) + { + hash_destroy (&serv->newsgroups, nntp_delete_data); + for (list = serv->list; list; list = list->next) + list->data = NULL; + mutt_free_list (&serv->list); + FREE (&serv->newsrc); + FREE (&serv->cache); + FREE (&serv); + return NULL; + } + nntp_clear_cacheindex (serv); + conn->data = (void *)serv; + + return serv; +} + +/* + * full status flags are not supported by nntp, but we can fake some + * of them. This is how: + * Read = a read message number is in the .newsrc + * New = a message is new since we last read this newsgroup + * Old = anything else + * So, Read is marked as such in the newsrc, old is anything that is + * "skipped" in the newsrc, and new is anything not in the newsrc nor + * in the cache. By skipped, I mean before the last unread message + */ +void nntp_get_status (CONTEXT *ctx, HEADER *h, char *group, int article) +{ + NNTP_DATA *data = (NNTP_DATA *) ctx->data; + int x; + + if (group) + data = (NNTP_DATA *) hash_find (data->nserv->newsgroups, group); + + if (!data) + { +#ifdef DEBUG + if (group) + dprint (3, (debugfile, "newsgroup %s not found\n", group)); +#endif + return; + } + + for (x = 0; x < data->num; x++) + { + if ((article >= data->entries[x].first) && + (article <= data->entries[x].last)) + { + /* we cannot use mutt_set_flag() because mx_update_context() + didn't called yet */ + h->read = 1; + return; + } + } + /* If article was not cached yet, it is new! :) */ + if (!data->cache || article > data->lastCached) + return; + /* Old articles are articles which aren't read but an article after them + * has been cached */ + if (option (OPTMARKOLD)) + h->old = 1; +} + +void mutt_newsgroup_stat (NNTP_DATA *data) +{ + int i; + unsigned int first, last; + + data->unread = 0; + if (data->lastMessage == 0 || data->firstMessage > data->lastMessage) + return; + + data->unread = data->lastMessage - data->firstMessage + 1; + for (i = 0; i < data->num; i++) + { + first = data->entries[i].first; + if (first < data->firstMessage) + first = data->firstMessage; + last = data->entries[i].last; + if (last > data->lastMessage) + last = data->lastMessage; + if (first <= last) + data->unread -= last - first + 1; + } +} + +static int puti (char *line, int num) +{ + char *p, s[32]; + + for (p = s; num; ) + { + *p++ = '0' + num % 10; + num /= 10; + } + while (p > s) + *line++ = *--p, num++; + *line = '\0'; + return num; +} + +static void nntp_create_newsrc_line (NNTP_DATA *data, char **buf, char **pline, size_t *buflen) +{ + char *line = *pline; + size_t len = *buflen - (*pline - *buf); + int x, i; + + if (len < LONG_STRING * 10) + { + len += *buflen; + *buflen *= 2; + line = *buf; + safe_realloc ((void **) buf, *buflen); + line = *buf + (*pline - line); + } + strcpy (line, data->group); + len -= strlen (line) + 1; + line += strlen (line); + *line++ = data->subscribed ? ':' : '!'; + *line++ = ' '; + *line = '\0'; + + for (x = 0; x < data->num; x++) + { + if (len < LONG_STRING) + { + len += *buflen; + *buflen *= 2; + *pline = line; + line = *buf; + safe_realloc ((void **) buf, *buflen); + line = *buf + (*pline - line); + } + if (x) + { + *line++ = ','; + len--; + } + +#if 0 + if (data->entries[x].first == data->entries[x].last) + snprintf (line, len, "%d%n", data->entries[x].first, &i); + else + snprintf (line, len, "%d-%d%n", + data->entries[x].first, data->entries[x].last, &i); + len -= i; + line += i; +#else + i = puti (line, data->entries[x].first); + line +=i; len -= i; + if (data->entries[x].first != data->entries[x].last) + { + *line++ = '-'; + len--; + i = puti (line, data->entries[x].last); + line +=i; len -= i; + } +#endif + } + *line++ = '\n'; + *line = '\0'; + *pline = line; +} + +void newsrc_gen_entries (CONTEXT *ctx) +{ + NNTP_DATA *data = (NNTP_DATA *)ctx->data; + int series, x; + unsigned int last = 0, first = 1; + int save_sort = SORT_ORDER; + + if (Sort != SORT_ORDER) + { + save_sort = Sort; + Sort = SORT_ORDER; + mutt_sort_headers (ctx, 0); + } + + if (!data->max) + { + data->entries = safe_calloc (5, sizeof (NEWSRC_ENTRY)); + data->max = 5; + } + + /* + * Set up to fake initial sequence from 1 to the article before the + * first article in our list + */ + data->num = 0; + series = 1; + + for (x = 0; x < ctx->msgcount; x++) + { + if (series) /* search for first unread */ + { + /* + * We don't actually check sequential order, since we mark + * "missing" entries as read/deleted + */ + last = ctx->hdrs[x]->article_num; + if (last >= data->firstMessage && !ctx->hdrs[x]->deleted && + !ctx->hdrs[x]->read) + { + if (data->num >= data->max) + { + data->max = data->max * 2; + safe_realloc ((void **)&data->entries, + data->max * sizeof (NEWSRC_ENTRY)); + } + data->entries[data->num].first = first; + data->entries[data->num].last = last - 1; + data->num++; + series = 0; + } + } + else /* search for first read */ + { + if (ctx->hdrs[x]->deleted || ctx->hdrs[x]->read) + { + first = last + 1; + series = 1; + } + last = ctx->hdrs[x]->article_num; + } + } + if (series && first <= data->lastLoaded) + { + if (data->num >= data->max) + { + data->max = data->max * 2; + safe_realloc ((void **)&data->entries, + data->max * sizeof (NEWSRC_ENTRY)); + } + data->entries[data->num].first = first; + data->entries[data->num].last = data->lastLoaded; + data->num++; + } + + if (save_sort != Sort) + { + Sort = save_sort; + mutt_sort_headers (ctx, 0); + } +} + +int mutt_newsrc_update (NNTP_SERVER *news) +{ + char *buf, *line; + NNTP_DATA *data; + LIST *tmp; + int r = -1; + size_t len, llen; + + if (!news) + return -1; + llen = len = 10 * LONG_STRING; + line = buf = safe_calloc (1, len); + /* we will generate full newsrc here */ + for (tmp = news->list; tmp; tmp = tmp->next) + { + data = (NNTP_DATA *) tmp->data; + if (!data || !data->rc) + continue; + nntp_create_newsrc_line (data, &buf, &line, &llen); + dprint (2, (debugfile, "Added to newsrc: %s", line)); + line += strlen (line); + } + /* newrc being fully rewritten */ + if (news->newsrc && + (r = mutt_update_list_file (news->newsrc, NULL, "", buf)) == 0) + { + struct stat st; + + stat (news->newsrc, &st); + news->size = st.st_size; + news->mtime = st.st_mtime; + } + FREE (&buf); + return r; +} + +static FILE *mutt_mkname (char *s) +{ + char buf[_POSIX_PATH_MAX], *pc; + int fd; + FILE *fp; + + nntp_cache_expand (buf, s); + if ((fp = safe_fopen (buf, "w"))) + return fp; + + nntp_cache_expand (buf, "cache-XXXXXX"); + pc = buf + strlen (buf) - 12; /* positioning to "cache-XXXXXX" */ + if ((fd = mkstemp (buf)) == -1) + return NULL; + strcpy (s, pc); /* generated name */ + return fdopen (fd, "w"); +} + +/* Updates info into .index file: ALL or about selected newsgroup */ +static int nntp_update_cacheindex (NNTP_SERVER *serv, NNTP_DATA *data) +{ + char buf[LONG_STRING], *key = "ALL"; + char file[_POSIX_PATH_MAX]; + + if (!serv || !serv->conn || !serv->conn->account.host) + return -1; + + if (data && data->group) + { + key = data->group; + snprintf (buf, sizeof (buf), "%s %s %d %d", key, data->cache, + data->firstMessage, data->lastLoaded); + } + else + { + strfcpy (file, serv->cache, sizeof (file)); + snprintf (buf, sizeof (buf), "ALL %s 0 %d", file, (int)serv->newgroups_time); + } + nntp_cache_expand (file, ".index"); + return mutt_update_list_file (file, serv->conn->account.host, key, buf); +} + +/* Remove cache files of unsubscribed newsgroups */ +void nntp_clear_cacheindex (NNTP_SERVER *news) +{ + NNTP_DATA *data; + LIST *tmp; + + if (option (OPTSAVEUNSUB) || !news) + return; + + for (tmp = news->list; tmp; tmp = tmp->next) + { + data = (NNTP_DATA *) tmp->data; + if (!data || data->subscribed || !data->cache) + continue; + nntp_delete_cache (data); + dprint (2, (debugfile, "Removed from .index: %s\n", data->group)); + } + return; +} + +int nntp_save_cache_index (NNTP_SERVER *news) +{ + char buf[HUGE_STRING]; + char file[_POSIX_PATH_MAX]; + NNTP_DATA *d; + FILE *f; + LIST *l; + + if (!news || !news->newsgroups) + return -1; + if (!option (OPTNEWSCACHE)) + return 0; + + if (news->cache) + { + nntp_cache_expand (file, news->cache); + unlink (file); + f = safe_fopen (file, "w"); + } + else + { + strfcpy (buf, news->conn->account.host, sizeof(buf)); + f = mutt_mkname (buf); + news->cache = safe_strdup (buf); + nntp_cache_expand (file, buf); + } + if (!f) + return -1; + + for (l = news->list; l; l = l->next) + { + if ((d = (NNTP_DATA *)l->data) && !d->deleted) + { + if (d->desc) + snprintf (buf, sizeof(buf), "%s %d %d %c %s\n", d->group, + d->lastMessage, d->firstMessage, d->allowed ? 'y' : 'n', + d->desc); + else + snprintf (buf, sizeof(buf), "%s %d %d %c\n", d->group, + d->lastMessage, d->firstMessage, d->allowed ? 'y' : 'n'); + if (fputs (buf, f) == EOF) + { + fclose (f); + unlink (file); + return -1; + } + } + } + fclose (f); + + if (nntp_update_cacheindex (news, NULL)) + { + unlink (file); + return -1; + } + return 0; +} + +int nntp_save_cache_group (CONTEXT *ctx) +{ + char buf[HUGE_STRING], addr[STRING]; + char file[_POSIX_PATH_MAX]; + FILE *f; + HEADER *h; + struct tm *tm; + int i = 0, save = SORT_ORDER; + int prev = 0; + + if (!option (OPTNEWSCACHE)) + return 0; + if (!ctx || !ctx->data || ctx->magic != M_NNTP) + return -1; + + if (((NNTP_DATA *)ctx->data)->cache) + { + nntp_cache_expand (file, ((NNTP_DATA *)ctx->data)->cache); + unlink (file); + f = safe_fopen (file, "w"); + } + else + { + snprintf (buf, sizeof(buf), "%s-%s", + ((NNTP_DATA *)ctx->data)->nserv->conn->account.host, + ((NNTP_DATA *)ctx->data)->group); + f = mutt_mkname (buf); + ((NNTP_DATA *)ctx->data)->cache = safe_strdup (buf); + nntp_cache_expand (file, buf); + } + if (!f) + return -1; + + if (Sort != SORT_ORDER) + { + save = Sort; + Sort = SORT_ORDER; + mutt_sort_headers (ctx, 0); + } + + /* Save only $nntp_context messages... */ + ((NNTP_DATA *)ctx->data)->lastCached = 0; + if (NntpContext && ctx->msgcount > NntpContext) + i = ctx->msgcount - NntpContext; + for (; i < ctx->msgcount; i++) + { + if (!ctx->hdrs[i]->deleted && ctx->hdrs[i]->article_num != prev) + { + h = ctx->hdrs[i]; + addr[0] = 0; + rfc822_write_address (addr, sizeof(addr), h->env->from); + tm = gmtime (&h->date_sent); + snprintf (buf, sizeof(buf), + "%d\t%s\t%s\t%d %s %d %02d:%02d:%02d GMT\t%s\t", + h->article_num, h->env->subject, addr, tm->tm_mday, + Months[tm->tm_mon], tm->tm_year+1900, tm->tm_hour, tm->tm_min, + tm->tm_sec, h->env->message_id); + fputs (buf, f); + if (h->env->references) + mutt_write_references (h->env->references, f); + snprintf (buf, sizeof(buf), "\t%ld\t%d\tXref: %s\n", NONULL(h->content->length), + NONULL(h->lines), NONULL(h->env->xref)); + if (fputs (buf, f) == EOF) + { + fclose (f); + unlink (file); + return -1; + } + } + prev = ctx->hdrs[i]->article_num; + } + + if (save != Sort) + { + Sort = save; + mutt_sort_headers (ctx, 0); + } + fclose (f); + + if (nntp_update_cacheindex (((NNTP_DATA *)ctx->data)->nserv, + (NNTP_DATA *)ctx->data)) + { + unlink (file); + return -1; + } + ((NNTP_DATA *)ctx->data)->lastCached = ((NNTP_DATA *)ctx->data)->lastLoaded; + return 0; +} + +void nntp_delete_cache (NNTP_DATA *data) +{ + char buf[_POSIX_PATH_MAX]; + + if (!option (OPTNEWSCACHE) || !data || !data->cache || !data->nserv) + return; + + nntp_cache_expand (buf, data->cache); + unlink (buf); + FREE (&data->cache); + data->lastCached = 0; + nntp_cache_expand (buf, ".index"); + mutt_update_list_file (buf, data->nserv->conn->account.host, data->group, NULL); +} + +NNTP_DATA *mutt_newsgroup_subscribe (NNTP_SERVER *news, char *group) +{ + NNTP_DATA *data; + + if (!news || !news->newsgroups || !group || !*group) + return NULL; + if (!(data = (NNTP_DATA *)hash_find (news->newsgroups, group))) + { + data = (NNTP_DATA *) safe_calloc (1, sizeof (NNTP_DATA) + strlen (group) + 1); + data->group = (char *) data + sizeof (NNTP_DATA); + strcpy (data->group, group); + data->nserv = news; + data->deleted = 1; + if (news->newsgroups->nelem < news->newsgroups->curnelem * 2) + news->newsgroups = hash_resize (news->newsgroups, news->newsgroups->nelem * 2); + hash_insert (news->newsgroups, data->group, data, 0); + nntp_add_to_list (news, data); + } + if (!data->subscribed) + { + data->subscribed = 1; + data->rc = 1; + } + return data; +} + +NNTP_DATA *mutt_newsgroup_unsubscribe (NNTP_SERVER *news, char *group) +{ + NNTP_DATA *data; + + if (!news || !news->newsgroups || !group || !*group || + !(data = (NNTP_DATA *)hash_find (news->newsgroups, group))) + return NULL; + if (data->subscribed) + { + data->subscribed = 0; + if (!option (OPTSAVEUNSUB)) + data->rc = 0; + } + return data; +} + +NNTP_DATA *mutt_newsgroup_catchup (NNTP_SERVER *news, char *group) +{ + NNTP_DATA *data; + + if (!news || !news->newsgroups || !group || !*group || + !(data = (NNTP_DATA *)hash_find (news->newsgroups, group))) + return NULL; + if (!data->max) + { + data->entries = safe_calloc (5, sizeof (NEWSRC_ENTRY)); + data->max = 5; + } + data->num = 1; + data->entries[0].first = 1; + data->unread = 0; + data->entries[0].last = data->lastMessage; + if (Context && Context->data == data) + { + int x; + + for (x = 0; x < Context->msgcount; x++) + mutt_set_flag (Context, Context->hdrs[x], M_READ, 1); + } + return data; +} + +NNTP_DATA *mutt_newsgroup_uncatchup (NNTP_SERVER *news, char *group) +{ + NNTP_DATA *data; + + if (!news || !news->newsgroups || !group || !*group || + !(data = (NNTP_DATA *)hash_find (news->newsgroups, group))) + return NULL; + if (!data->max) + { + data->entries = safe_calloc (5, sizeof (NEWSRC_ENTRY)); + data->max = 5; + } + data->num = 1; + data->entries[0].first = 1; + data->entries[0].last = data->firstMessage - 1; + if (Context && Context->data == data) + { + int x; + + data->unread = Context->msgcount; + for (x = 0; x < Context->msgcount; x++) + mutt_set_flag (Context, Context->hdrs[x], M_READ, 0); + } + else + data->unread = data->lastMessage - data->entries[0].last; + return data; +} + +/* this routine gives the first newsgroup with new messages */ +void nntp_buffy (char *s) +{ + LIST *list; + + for (list = CurrentNewsSrv->list; list; list = list->next) + { + NNTP_DATA *data = (NNTP_DATA *) list->data; + + if (data && data->subscribed && data->unread) + { + if (Context && Context->magic == M_NNTP && + !mutt_strcmp (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; + } + strcpy (s, data->group); + break; + } + } +} diff -udprP mutt-1.4.2.2.orig/nntp.c mutt-1.4.2.2/nntp.c --- mutt-1.4.2.2.orig/nntp.c 1970-01-01 03:00:00.000000000 +0300 +++ mutt-1.4.2.2/nntp.c 2006-07-18 12:45:03.000000000 +0300 @@ -0,0 +1,1553 @@ +/* + * Copyright (C) 1998 Brandon Long <blong@fiction.net> + * Copyright (C) 1999 Andrej Gritsenko <andrej@lucky.net> + * Copyright (C) 2000-2002 Vsevolod Volkov <vvv@mutt.org.ua> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#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 "nntp.h" + +#ifdef HAVE_PGP +#include "pgp.h" +#endif + +#include <unistd.h> +#include <string.h> +#include <ctype.h> +#include <stdlib.h> + +static unsigned int _checked = 0; + +#ifdef DEBUG +static void nntp_error (const char *where, const char *msg) +{ + dprint (1, (debugfile, "nntp_error(): unexpected response in %s: %s\n", where, msg)); +} +#endif /* DEBUG */ + +static int nntp_auth (NNTP_SERVER *serv) +{ + CONNECTION *conn = serv->conn; + char buf[STRING]; + unsigned char flags = conn->account.flags; + + if (mutt_account_getuser (&conn->account) || !conn->account.user[0] || + mutt_account_getpass (&conn->account) || !conn->account.pass[0]) + { + conn->account.flags = flags; + return -2; + } + + mutt_message _("Logging in..."); + + snprintf (buf, sizeof (buf), "AUTHINFO USER %s\r\n", conn->account.user); + mutt_socket_write (conn, buf); + if (mutt_socket_readln (buf, sizeof (buf), conn) < 0) + { + conn->account.flags = flags; + return -1; + } + +#ifdef DEBUG + /* don't print the password unless we're at the ungodly debugging level */ + if (debuglevel < M_SOCK_LOG_FULL) + dprint (M_SOCK_LOG_CMD, (debugfile, "> AUTHINFO PASS *\n")); +#endif + snprintf (buf, sizeof (buf), "AUTHINFO PASS %s\r\n", conn->account.pass); + mutt_socket_write_d (conn, buf, M_SOCK_LOG_FULL); + if (mutt_socket_readln (buf, sizeof (buf), conn) < 0) + { + conn->account.flags = flags; + return -1; + } + + if (mutt_strncmp ("281", buf, 3)) + { + conn->account.flags = flags; + mutt_error _("Login failed."); + sleep (2); + return -3; + } + + return 0; +} + +static int nntp_connect_error (NNTP_SERVER *serv) +{ + serv->status = NNTP_NONE; + mutt_socket_close (serv->conn); + mutt_error _("Server closed connection!"); + sleep (2); + return -1; +} + +static int nntp_connect_and_auth (NNTP_SERVER *serv) +{ + CONNECTION *conn = serv->conn; + char buf[STRING]; + int rc; + + serv->status = NNTP_NONE; + + if (mutt_socket_open (conn) < 0) + return -1; + + if (mutt_socket_readln (buf, sizeof (buf), conn) < 0) + return nntp_connect_error (serv); + + if (!mutt_strncmp ("200", buf, 3)) + mutt_message (_("Connected to %s. Posting ok."), conn->account.host); + else if (!mutt_strncmp ("201", buf, 3)) + mutt_message (_("Connected to %s. Posting NOT ok."), conn->account.host); + else + { + mutt_socket_close (conn); + mutt_remove_trailing_ws (buf); + mutt_error ("%s", buf); + sleep (2); + return -1; + } + + sleep (1); + + /* Tell INN to switch to mode reader if it isn't so. Ignore all + returned codes and messages. */ + mutt_socket_write (conn, "MODE READER\r\n"); + if (mutt_socket_readln (buf, sizeof (buf), conn) < 0) + return nntp_connect_error (serv); + + mutt_socket_write (conn, "STAT\r\n"); + if (mutt_socket_readln (buf, sizeof (buf), conn) < 0) + return nntp_connect_error (serv); + + if (!(conn->account.flags & M_ACCT_USER) && mutt_strncmp ("480", buf, 3)) + { + serv->status = NNTP_OK; + return 0; + } + + rc = nntp_auth (serv); + if (rc == -1) + return nntp_connect_error (serv); + if (rc == -2) + { + mutt_socket_close (conn); + serv->status = NNTP_BYE; + return -1; + } + if (rc < 0) + { + mutt_socket_close (conn); + mutt_error _("Login failed."); + sleep (2); + return -1; + } + serv->status = NNTP_OK; + return 0; +} + +static int nntp_attempt_features (NNTP_SERVER *serv) +{ + char buf[LONG_STRING]; + CONNECTION *conn = serv->conn; + + mutt_socket_write (conn, "XOVER\r\n"); + if (mutt_socket_readln (buf, sizeof (buf), conn) < 0) + return nntp_connect_error (serv); + if (mutt_strncmp ("500", buf, 3)) + serv->hasXOVER = 1; + + mutt_socket_write (conn, "XPAT\r\n"); + if (mutt_socket_readln (buf, sizeof (buf), conn) < 0) + return nntp_connect_error (serv); + if (mutt_strncmp ("500", buf, 3)) + serv->hasXPAT = 1; + + mutt_socket_write (conn, "XGTITLE +\r\n"); + if (mutt_socket_readln (buf, sizeof (buf), conn) < 0) + return nntp_connect_error (serv); + if (mutt_strncmp ("500", buf, 3)) + serv->hasXGTITLE = 1; + + if (!mutt_strncmp ("282", buf, 3)) + { + do + { + if (mutt_socket_readln (buf, sizeof (buf), conn) < 0) + return nntp_connect_error (serv); + } while (!(buf[0] == '.' && buf[1] == '\0')); + } + + return 0; +} + +static int nntp_open_connection (NNTP_SERVER *serv) +{ + if (serv->status == NNTP_OK) + return 0; + if (serv->status == NNTP_BYE) + return -1; + if (nntp_connect_and_auth (serv) < 0) + return -1; + if (nntp_attempt_features (serv) < 0) + return -1; + return 0; +} + +static int nntp_reconnect (NNTP_SERVER *serv) +{ + char buf[SHORT_STRING]; + + mutt_socket_close (serv->conn); + + FOREVER + { + if (nntp_connect_and_auth (serv) == 0) + return 0; + + snprintf (buf, sizeof (buf), _("Connection to %s lost. Reconnect?"), + serv->conn->account.host); + if (query_quadoption (OPT_NNTPRECONNECT, buf) != M_YES) + { + serv->status = NNTP_BYE; + return -1; + } + } +} + +/* Send data from line[LONG_STRING] and receive answer to same line */ +static int mutt_nntp_query (NNTP_DATA *data, char *line, size_t linelen) +{ + char buf[LONG_STRING]; + int done = TRUE; + + if (data->nserv->status == NNTP_BYE) + return -1; + + do + { + if (*line) + { + mutt_socket_write (data->nserv->conn, line); + } + else if (data->group) + { + snprintf (buf, sizeof (buf), "GROUP %s\r\n", data->group); + mutt_socket_write (data->nserv->conn, buf); + } + + done = TRUE; + if (mutt_socket_readln (buf, sizeof (buf), data->nserv->conn) < 0) + { + if (nntp_reconnect (data->nserv) < 0) + return -1; + + if (data->group) + { + snprintf (buf, sizeof (buf), "GROUP %s\r\n", data->group); + mutt_socket_write (data->nserv->conn, buf); + if (mutt_socket_readln (buf, sizeof (buf), data->nserv->conn) < 0) + return -1; + } + if (*line) + done = FALSE; + } + else if ((!mutt_strncmp ("480", buf, 3)) && nntp_auth (data->nserv) < 0) + return -1; + } while (!done); + + 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. + * Returned codes: + * 0 - successful, + * 1 - correct but not performed (may be, have to be continued), + * -1 - conection lost, + * -2 - invalid command or execution error, + * -3 - error in funct(*line, *data). + */ +static int mutt_nntp_fetch (NNTP_DATA *nntp_data, char *query, char *msg, + int (*funct) (char *, void *), void *data, int tagged) +{ + char buf[LONG_STRING]; + char *inbuf, *p; + int done = FALSE; + int chunk, line; + size_t lenbuf = 0; + int ret; + + do + { + strfcpy (buf, query, sizeof (buf)); + if (mutt_nntp_query (nntp_data, buf, sizeof (buf)) < 0) + return -1; + if (buf[0] == '5') + return -2; + if (buf[0] != '2') + return 1; + + ret = 0; + line = 0; + inbuf = safe_malloc (sizeof (buf)); + + FOREVER + { + chunk = mutt_socket_readln_d (buf, sizeof (buf), nntp_data->nserv->conn, + M_SOCK_LOG_HDR); + if (chunk < 0) + break; + + p = buf; + if (!lenbuf && buf[0] == '.') + { + if (buf[1] == '\0') + { + done = TRUE; + break; + } + if (buf[1] == '.') + p++; + } + + strfcpy (inbuf + lenbuf, p, sizeof (buf)); + + if (chunk >= sizeof (buf)) + { + lenbuf += strlen (p); + } + else + { + line++; + if (msg && ReadInc && (line % ReadInc == 0)) { + if (tagged) + mutt_message (_("%s (tagged: %d) %d"), msg, tagged, line); + else + mutt_message ("%s %d", msg, line); + } + + if (ret == 0 && funct (inbuf, data) < 0) + ret = -3; + lenbuf = 0; + } + + safe_realloc ((void **) &inbuf, lenbuf + sizeof (buf)); + } + FREE (&inbuf); + funct (NULL, data); + } + while (!done); + return ret; +} + +static int nntp_read_tempfile (char *line, void *file) +{ + FILE *f = (FILE *)file; + + if (!line) + rewind (f); + else + { + fputs (line, f); + if (fputc ('\n', f) == EOF) + return -1; + } + return 0; +} + +static void nntp_parse_xref (CONTEXT *ctx, char *group, char *xref, HEADER *h) +{ + register char *p, *b; + register char *colon = NULL; + + b = p = xref; + while (*p) + { + /* skip to next word */ + b = p; + while (*b && ((*b == ' ') || (*b == '\t'))) b++; + p = b; + colon = NULL; + /* skip to end of word */ + while (*p && (*p != ' ') && (*p != '\t')) + { + if (*p == ':') + colon = p; + p++; + } + if (*p) + { + *p = '\0'; + p++; + } + if (colon) + { + *colon = '\0'; + colon++; + nntp_get_status (ctx, h, p, atoi(colon)); + if (h && h->article_num == 0 && mutt_strcmp (group, b) == 0) + h->article_num = atoi(colon); + } + } +} + +/* + * returns: + * 0 on success + * 1 if article not found + * -1 if read or write error on tempfile or socket + */ +static int nntp_read_header (CONTEXT *ctx, const char *msgid, int article_num) +{ + NNTP_DATA *nntp_data = ((NNTP_DATA *)ctx->data); + FILE *f; + char buf[LONG_STRING]; + char tempfile[_POSIX_PATH_MAX]; + int ret; + HEADER *h = ctx->hdrs[ctx->msgcount]; + + mutt_mktemp (tempfile); + if (!(f = safe_fopen (tempfile, "w+"))) + return -1; + + if (!msgid) + snprintf (buf, sizeof (buf), "HEAD %d\r\n", article_num); + else + snprintf (buf, sizeof (buf), "HEAD %s\r\n", msgid); + + ret = mutt_nntp_fetch (nntp_data, buf, NULL, nntp_read_tempfile, f, 0); + if (ret) + { +#ifdef DEBUG + if (ret != -1) + dprint(1, (debugfile, "nntp_read_header: %s\n", buf)); +#endif + fclose (f); + unlink (tempfile); + return (ret == -1 ? -1 : 1); + } + + h->article_num = article_num; + h->env = mutt_read_rfc822_header (f, h, 0, 0); + fclose (f); + unlink (tempfile); + + if (h->env->xref != NULL) + nntp_parse_xref (ctx, nntp_data->group, h->env->xref, h); + else if (h->article_num == 0 && msgid) + { + snprintf (buf, sizeof (buf), "STAT %s\r\n", msgid); + if (mutt_nntp_query (nntp_data, buf, sizeof (buf)) == 0) + h->article_num = atoi (buf + 4); + } + + return 0; +} + +static int parse_description (char *line, void *n) +{ +#define news ((NNTP_SERVER *) n) + register char *d = line; + NNTP_DATA *data; + + if (!line) + return 0; + while (*d && *d != '\t' && *d != ' ') d++; + *d = 0; + d++; + while (*d && (*d == '\t' || *d == ' ')) d++; + dprint (2, (debugfile, "group: %s, desc: %s\n", line, d)); + if ((data = (NNTP_DATA *) hash_find (news->newsgroups, line)) != NULL && + mutt_strcmp (d, data->desc)) + { + FREE (&data->desc); + data->desc = safe_strdup (d); + } + return 0; +#undef news +} + +static void nntp_get_desc (NNTP_DATA *data, char *mask, char *msg) +{ + char buf[STRING]; + + if (!option (OPTLOADDESC) || !data || !data->nserv) + return; + + /* Get newsgroup description, if we can */ + if (data->nserv->hasXGTITLE) + snprintf (buf, sizeof (buf), "XGTITLE %s\r\n", mask); + else + snprintf (buf, sizeof (buf), "LIST NEWSGROUPS %s\r\n", mask); + if (mutt_nntp_fetch (data, buf, msg, parse_description, data->nserv, 0) != 0) + { +#ifdef DEBUG + nntp_error ("nntp_get_desc()", buf); +#endif + } +} + +/* + * XOVER returns a tab separated list of: + * id|subject|from|date|Msgid|references|bytes|lines|xref + * + * This has to duplicate some of the functionality of + * mutt_read_rfc822_header(), since it replaces the call to that (albeit with + * a limited number of headers which are "parsed" by placement in the list) + */ +static int nntp_parse_xover (CONTEXT *ctx, char *buf, HEADER *hdr) +{ + NNTP_DATA *nntp_data = (NNTP_DATA *) ctx->data; + char *p, *b; + int x, done = 0; + + hdr->env = mutt_new_envelope(); + hdr->env->newsgroups = safe_strdup (nntp_data->group); + hdr->content = mutt_new_body(); + hdr->content->type = TYPETEXT; + hdr->content->subtype = safe_strdup ("plain"); + hdr->content->encoding = ENC7BIT; + hdr->content->disposition = DISPINLINE; + hdr->content->length = -1; + b = p = buf; + + for (x = 0; !done && x < 9; x++) + { + /* if from file, need to skip newline character */ + while (*p && *p != '\n' && *p != '\t') p++; + if (!*p) done++; + *p = '\0'; + p++; + switch (x) + { + case 0: + + hdr->article_num = atoi (b); + nntp_get_status (ctx, hdr, NULL, hdr->article_num); + break; + case 1: + hdr->env->subject = safe_strdup (b); + /* Now we need to do the things which would normally be done in + * mutt_read_rfc822_header() */ + if (hdr->env->subject) + { + regmatch_t pmatch[1]; + + rfc2047_decode (&hdr->env->subject); + + if (regexec (ReplyRegexp.rx, hdr->env->subject, 1, pmatch, 0) == 0) + hdr->env->real_subj = hdr->env->subject + pmatch[0].rm_eo; + else + hdr->env->real_subj = hdr->env->subject; + } + break; + case 2: + rfc822_free_address (&hdr->env->from); + hdr->env->from = rfc822_parse_adrlist (hdr->env->from, b); + rfc2047_decode_adrlist (hdr->env->from); + break; + case 3: + hdr->date_sent = mutt_parse_date (b, hdr); + hdr->received = hdr->date_sent; + break; + case 4: + FREE (&hdr->env->message_id); + hdr->env->message_id = safe_strdup (b); + break; + case 5: + mutt_free_list (&hdr->env->references); + hdr->env->references = mutt_parse_references (b, 0); + break; + case 6: + hdr->content->length = atoi (b); + break; + case 7: + hdr->lines = atoi (b); + break; + case 8: + if (!hdr->read) + FREE (&hdr->env->xref); + b = b + 6; /* skips the "Xref: " */ + hdr->env->xref = safe_strdup (b); + nntp_parse_xref (ctx, nntp_data->group, b, hdr); + } + if (!*p) + return -1; + b = p; + } + return 0; +} + +typedef struct +{ + CONTEXT *ctx; + unsigned int base; + unsigned int first; + unsigned int last; + unsigned short *messages; + char* msg; +} FETCH_CONTEXT; + +#define fc ((FETCH_CONTEXT *) c) +static int nntp_fetch_numbers (char *line, void *c) +{ + unsigned int num; + + if (!line) + return 0; + num = atoi (line); + if (num < fc->base || num > fc->last) + return 0; + fc->messages[num - fc->base] = 1; + return 0; +} + +static int add_xover_line (char *line, void *c) +{ + unsigned int num, total; + CONTEXT *ctx = fc->ctx; + NNTP_DATA *data = (NNTP_DATA *)ctx->data; + + if (!line) + return 0; + + if (ctx->msgcount >= ctx->hdrmax) + mx_alloc_memory (ctx); + ctx->hdrs[ctx->msgcount] = mutt_new_header (); + ctx->hdrs[ctx->msgcount]->index = ctx->msgcount; + + nntp_parse_xover (ctx, line, ctx->hdrs[ctx->msgcount]); + num = ctx->hdrs[ctx->msgcount]->article_num; + + if (num >= fc->first && num <= fc->last && fc->messages[num - fc->base]) + { + ctx->msgcount++; + if (num > data->lastLoaded) + data->lastLoaded = num; + num = num - fc->first + 1; + total = fc->last - fc->first + 1; + if (!ctx->quiet && fc->msg && ReadInc && (num % ReadInc == 0)) + mutt_message ("%s %d/%d", fc->msg, num, total); + } + else + mutt_free_header (&ctx->hdrs[ctx->msgcount]); /* skip it */ + + return 0; +} +#undef fc + +static int nntp_fetch_headers (CONTEXT *ctx, unsigned int first, + unsigned int last) +{ + char buf[HUGE_STRING]; + char *msg = _("Fetching message headers..."); + NNTP_DATA *nntp_data = ((NNTP_DATA *)ctx->data); + int ret; + int num; + int oldmsgcount; + unsigned int current; + FILE *f; + FETCH_CONTEXT fc; + + /* if empty group or nothing to do */ + if (!last || first > last) + return 0; + + /* fetch list of articles */ + mutt_message _("Fetching list of articles..."); + fc.ctx = ctx; + fc.base = first; + fc.last = last; + fc.messages = safe_calloc (last - first + 1, sizeof (unsigned short)); + snprintf (buf, sizeof (buf), "LISTGROUP %s\r\n", nntp_data->group); + if (mutt_nntp_fetch (nntp_data, buf, NULL, nntp_fetch_numbers, &fc, 0) != 0) + { + mutt_error (_("LISTGROUP command failed: %s"), buf); +#ifdef DEBUG + nntp_error ("nntp_fetch_headers()", buf); +#endif + FREE (&fc.messages); + return -1; + } + + /* CACHE: must be loaded xover cache here */ + num = nntp_data->lastCached - first + 1; + if (option (OPTNEWSCACHE) && nntp_data->cache && num > 0) + { + nntp_cache_expand (buf, nntp_data->cache); + mutt_message _("Fetching headers from cache..."); + if ((f = safe_fopen (buf, "r"))) + { + int r = 0; + + /* counting number of lines */ + while (fgets (buf, sizeof (buf), f) != NULL) + r++; + rewind (f); + while (r > num && fgets (buf, sizeof (buf), f) != NULL) + r--; + oldmsgcount = ctx->msgcount; + fc.first = first; + fc.last = first + num - 1; + fc.msg = NULL; + while (fgets (buf, sizeof (buf), f) != NULL) + add_xover_line (buf, &fc); + fclose (f); + nntp_data->lastLoaded = fc.last; + first = fc.last + 1; + if (ctx->msgcount > oldmsgcount) + mx_update_context (ctx, ctx->msgcount - oldmsgcount); + } + else + nntp_delete_cache (nntp_data); + } + num = last - first + 1; + if (num <= 0) + { + FREE (&fc.messages); + return 0; + } + + /* + * Without XOVER, we have to fetch each article header and parse + * it. With XOVER, we ask for all of them + */ + mutt_message (msg); + if (nntp_data->nserv->hasXOVER) + { + oldmsgcount = ctx->msgcount; + fc.first = first; + fc.last = last; + fc.msg = msg; + snprintf (buf, sizeof (buf), "XOVER %d-%d\r\n", first, last); + ret = mutt_nntp_fetch (nntp_data, buf, NULL, add_xover_line, &fc, 0); + if (ctx->msgcount > oldmsgcount) + mx_update_context (ctx, ctx->msgcount - oldmsgcount); + if (ret != 0) + { + mutt_error (_("XOVER command failed: %s"), buf); +#ifdef DEBUG + nntp_error ("nntp_fetch_headers()", buf); +#endif + FREE (&fc.messages); + return -1; + } + /* fetched OK */ + } + else + for (current = first; current <= last; current++) + { + HEADER *h; + + ret = current - first + 1; + mutt_message ("%s %d/%d", msg, ret, num); + + if (!fc.messages[current - fc.base]) + continue; + + if (ctx->msgcount >= ctx->hdrmax) + mx_alloc_memory (ctx); + h = ctx->hdrs[ctx->msgcount] = mutt_new_header (); + h->index = ctx->msgcount; + + ret = nntp_read_header (ctx, NULL, current); + if (ret == 0) /* Got article. Fetch next header */ + { + nntp_get_status (ctx, h, NULL, h->article_num); + ctx->msgcount++; + mx_update_context (ctx, 1); + } + else + mutt_free_header (&h); /* skip it */ + if (ret == -1) + { + FREE (&fc.messages); + return -1; + } + + if (current > nntp_data->lastLoaded) + nntp_data->lastLoaded = current; + } + FREE (&fc.messages); + nntp_data->lastLoaded = last; + mutt_clear_error (); + return 0; +} + +/* + * currently, nntp "mailbox" is "newsgroup" + */ +int nntp_open_mailbox (CONTEXT *ctx) +{ + NNTP_DATA *nntp_data; + NNTP_SERVER *serv; + char buf[HUGE_STRING]; + char server[LONG_STRING]; + int count = 0; + unsigned int first; + ACCOUNT acct; + + if (nntp_parse_url (ctx->path, &acct, buf, sizeof (buf)) < 0 || !*buf) + { + mutt_error (_("%s is an invalid newsgroup specification!"), ctx->path); + mutt_sleep (2); + return -1; + } + + server[0] = '\0'; + nntp_expand_path (server, sizeof (server), &acct); + if (!(serv = mutt_select_newsserver (server)) || serv->status != NNTP_OK) + return -1; + + CurrentNewsSrv = serv; + + /* create NNTP-specific state struct if nof found in list */ + if ((nntp_data = (NNTP_DATA *) hash_find (serv->newsgroups, buf)) == NULL) + { + nntp_data = safe_calloc (1, sizeof (NNTP_DATA) + strlen (buf) + 1); + nntp_data->group = (char *) nntp_data + sizeof (NNTP_DATA); + strcpy (nntp_data->group, buf); + hash_insert (serv->newsgroups, nntp_data->group, nntp_data, 0); + nntp_add_to_list (serv, nntp_data); + } + ctx->data = nntp_data; + nntp_data->nserv = serv; + + mutt_message (_("Selecting %s..."), nntp_data->group); + + if (!nntp_data->desc) + { + nntp_get_desc (nntp_data, nntp_data->group, NULL); + if (nntp_data->desc) + nntp_save_cache_index (serv); + } + + buf[0] = 0; + if (mutt_nntp_query (nntp_data, buf, sizeof(buf)) < 0) + { +#ifdef DEBUG + nntp_error ("nntp_open_mailbox()", buf); +#endif + return -1; + } + + if (mutt_strncmp ("211", buf, 3)) + { + LIST *l = serv->list; + + /* GROUP command failed */ + if (!mutt_strncmp ("411", buf, 3)) + { + mutt_error (_("Newsgroup %s not found on server %s"), + nntp_data->group, serv->conn->account.host); + + /* CACHE: delete cache and line from .index */ + nntp_delete_cache (nntp_data); + hash_delete (serv->newsgroups, nntp_data->group, NULL, nntp_delete_data); + while (l && l->data != (void *) nntp_data) l = l->next; + if (l) + l->data = NULL; + + sleep (2); + } + + return -1; + } + + sscanf (buf + 4, "%d %u %u %s", &count, &nntp_data->firstMessage, + &nntp_data->lastMessage, buf); + + nntp_data->deleted = 0; + + time (&serv->check_time); + + /* + * Check for max adding context. If it is greater than $nntp_context, + * strip off extra articles + */ + first = nntp_data->firstMessage; + if (NntpContext && nntp_data->lastMessage - first + 1 > NntpContext) + first = nntp_data->lastMessage - NntpContext + 1; + if (first) + nntp_data->lastLoaded = first - 1; + return nntp_fetch_headers (ctx, first, nntp_data->lastMessage); +} + +int nntp_fetch_message (MESSAGE *msg, CONTEXT *ctx, int msgno) +{ + char buf[LONG_STRING]; + char path[_POSIX_PATH_MAX]; + NNTP_CACHE *cache; + char *m = _("Fetching message..."); + int ret; + + /* see if we already have the message in our cache */ + cache = &((NNTP_DATA *) ctx->data)->acache[ctx->hdrs[msgno]->index % NNTP_CACHE_LEN]; + + /* if everything is fine, assign msg->fp and return */ + if (cache->path && cache->index == ctx->hdrs[msgno]->index && + (msg->fp = fopen (cache->path, "r"))) + return 0; + + /* clear the previous entry */ + unlink (cache->path); + free (cache->path); + + mutt_message (m); + + cache->index = ctx->hdrs[msgno]->index; + mutt_mktemp (path); + cache->path = safe_strdup (path); + if (!(msg->fp = safe_fopen (path, "w+"))) + { + FREE (&cache->path); + return -1; + } + + if (ctx->hdrs[msgno]->article_num == 0) + snprintf (buf, sizeof (buf), "ARTICLE %s\r\n", + ctx->hdrs[msgno]->env->message_id); + else + snprintf (buf, sizeof (buf), "ARTICLE %d\r\n", + ctx->hdrs[msgno]->article_num); + + ret = mutt_nntp_fetch ((NNTP_DATA *)ctx->data, buf, m, nntp_read_tempfile, + msg->fp, ctx->tagged); + if (ret == 1) + { + mutt_error (_("Article %d not found on server"), + ctx->hdrs[msgno]->article_num); + dprint (1, (debugfile, "nntp_fetch_message: %s\n", buf)); + } + + if (ret) + { + fclose (msg->fp); + unlink (path); + FREE (&cache->path); + return -1; + } + + mutt_free_envelope (&ctx->hdrs[msgno]->env); + ctx->hdrs[msgno]->env = mutt_read_rfc822_header (msg->fp, ctx->hdrs[msgno], 0, 0); + /* fix content length */ + fseek(msg->fp, 0, SEEK_END); + ctx->hdrs[msgno]->content->length = ftell (msg->fp) - + ctx->hdrs[msgno]->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. + */ + mutt_parse_mime_message (ctx, ctx->hdrs[msgno]); + + /* These would normally be updated in mx_update_context(), but the + * full headers aren't parsed with XOVER, so the information wasn't + * available then. + */ +#ifdef HAVE_PGP + ctx->hdrs[msgno]->pgp = pgp_query (ctx->hdrs[msgno]->content); +#endif /* HAVE_PGP */ + + mutt_clear_error(); + rewind (msg->fp); + + return 0; +} + +/* Post article */ +int nntp_post (const char *msg) { + char buf[LONG_STRING]; + size_t len; + FILE *f; + NNTP_DATA *nntp_data; + + if (Context && Context->magic == M_NNTP) + nntp_data = (NNTP_DATA *)Context->data; + else + { + if (!(CurrentNewsSrv = mutt_select_newsserver (NewsServer)) || + !CurrentNewsSrv->list || !CurrentNewsSrv->list->data) + { + mutt_error (_("Can't post article. No connection to news server.")); + return -1; + } + nntp_data = (NNTP_DATA *)CurrentNewsSrv->list->data; + } + + if (!(f = safe_fopen (msg, "r"))) + { + mutt_error (_("Can't post article. Unable to open %s"), msg); + return -1; + } + + strfcpy (buf, "POST\r\n", sizeof (buf)); + if (mutt_nntp_query (nntp_data, buf, sizeof (buf)) < 0) + { + mutt_error (_("Can't post article. Connection to %s lost."), + nntp_data->nserv->conn->account.host); + 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, f) != NULL) + { + len = strlen (buf); + if (buf[len - 1] == '\n') + { + buf[len - 1] = '\r'; + buf[len] = '\n'; + len++; + buf[len] = '\0'; + } + if (buf[1] == '.') + mutt_socket_write_d (nntp_data->nserv->conn, buf, M_SOCK_LOG_HDR); + else + mutt_socket_write_d (nntp_data->nserv->conn, buf + 1, M_SOCK_LOG_HDR); + } + fclose (f); + + if (buf[strlen (buf) - 1] != '\n') + mutt_socket_write_d (nntp_data->nserv->conn, "\r\n", M_SOCK_LOG_HDR); + mutt_socket_write_d (nntp_data->nserv->conn, ".\r\n", M_SOCK_LOG_HDR); + if (mutt_socket_readln (buf, sizeof (buf), nntp_data->nserv->conn) < 0) + { + mutt_error (_("Can't post article. Connection to %s lost."), + nntp_data->nserv->conn->account.host); + return -1; + } + if (buf[0] != '2') + { + mutt_error (_("Can't post article: %s"), buf); + return -1; + } + + return 0; +} + +/* nntp_logout_all: close all open connections. */ +void nntp_logout_all (void) +{ + char buf[LONG_STRING]; + CONNECTION* conn; + CONNECTION* tmp; + + conn = mutt_socket_head (); + + while (conn) + { + tmp = conn; + + if (conn->account.type == M_ACCT_TYPE_NNTP) + { + mutt_message (_("Closing connection to %s..."), conn->account.host); + mutt_socket_write (conn, "QUIT\r\n"); + mutt_socket_readln (buf, sizeof (buf), conn); + mutt_clear_error (); + mutt_socket_close (conn); + + mutt_socket_free (tmp); + } + + conn = conn->next; + } +} + +static void nntp_free_acache (NNTP_DATA *data) +{ + int i; + + for (i = 0; i < NNTP_CACHE_LEN; i++) + { + if (data->acache[i].path) + { + unlink (data->acache[i].path); + FREE (&data->acache[i].path); + } + } +} + +void nntp_delete_data (void *p) +{ + NNTP_DATA *data = (NNTP_DATA *)p; + + if (!p) + return; + FREE (&data->entries); + FREE (&data->desc); + FREE (&data->cache); + nntp_free_acache (data); + FREE (p); +} + +int nntp_sync_mailbox (CONTEXT *ctx) +{ + NNTP_DATA *data = ctx->data; + + /* CACHE: update cache and .index files */ + if ((option (OPTSAVEUNSUB) || data->subscribed)) + nntp_save_cache_group (ctx); + nntp_free_acache (data); + + data->nserv->check_time = 0; /* next nntp_check_mailbox() will really check */ + return 0; +} + +void nntp_fastclose_mailbox (CONTEXT *ctx) +{ + NNTP_DATA *data = (NNTP_DATA *) ctx->data, *tmp; + + if (!data) + return; + nntp_free_acache (data); + if (!data->nserv || !data->nserv->newsgroups || !data->group) + return; + nntp_save_cache_index (data->nserv); + if ((tmp = hash_find (data->nserv->newsgroups, data->group)) == NULL + || tmp != data) + nntp_delete_data (data); +} + +/* commit changes and terminate connection */ +int nntp_close_mailbox (CONTEXT *ctx) +{ + if (!ctx) + return -1; + mutt_message _("Quitting newsgroup..."); + if (ctx->data) + { + NNTP_DATA *data = (NNTP_DATA *) ctx->data; + int ret; + + if (data->nserv && data->nserv->conn && ctx->unread) + { + ret = query_quadoption (OPT_CATCHUP, _("Mark all articles read?")); + if (ret == M_YES) + mutt_newsgroup_catchup (data->nserv, data->group); + else if (ret < 0) + return -1; + } + } + nntp_sync_mailbox (ctx); + if (ctx->data && ((NNTP_DATA *)ctx->data)->nserv) + { + NNTP_SERVER *news; + + news = ((NNTP_DATA *)ctx->data)->nserv; + newsrc_gen_entries (ctx); + ((NNTP_DATA *)ctx->data)->unread = ctx->unread; + mutt_newsrc_update (news); + } + mutt_clear_error(); + return 0; +} + +/* use the GROUP command to poll for new mail */ +static int _nntp_check_mailbox (CONTEXT *ctx, NNTP_DATA *nntp_data) +{ + char buf[LONG_STRING]; + int count = 0; + + if (nntp_data->nserv->check_time + NewsPollTimeout > time (NULL)) + return 0; + + buf[0] = 0; + if (mutt_nntp_query (nntp_data, buf, sizeof (buf)) < 0) + { +#ifdef DEBUG + nntp_error ("nntp_check_mailbox()", buf); +#endif + return -1; + } + if (mutt_strncmp ("211", buf, 3)) + { + buf[0] = 0; + if (mutt_nntp_query (nntp_data, buf, sizeof (buf)) < 0) + { +#ifdef DEBUG + nntp_error ("nntp_check_mailbox()", buf); +#endif + return -1; + } + } + if (!mutt_strncmp ("211", buf, 3)) + { + int first; + int last; + + sscanf (buf + 4, "%d %d %d", &count, &first, &last); + nntp_data->firstMessage = first; + nntp_data->lastMessage = last; + if (ctx && last > nntp_data->lastLoaded) + { + nntp_fetch_headers (ctx, nntp_data->lastLoaded + 1, last); + time (&nntp_data->nserv->check_time); + return 1; + } + if (!last || (!nntp_data->rc && !nntp_data->lastCached)) + nntp_data->unread = count; + else + mutt_newsgroup_stat (nntp_data); + /* active was renumbered? */ + if (last < nntp_data->lastLoaded) + { + if (!nntp_data->max) + { + nntp_data->entries = safe_calloc (5, sizeof (NEWSRC_ENTRY)); + nntp_data->max = 5; + } + nntp_data->lastCached = 0; + nntp_data->num = 1; + nntp_data->entries[0].first = 1; + nntp_data->entries[0].last = 0; + } + } + + time (&nntp_data->nserv->check_time); + return 0; +} + +int nntp_check_mailbox (CONTEXT *ctx) +{ + return _nntp_check_mailbox (ctx, (NNTP_DATA *)ctx->data); +} + +static int add_group (char *buf, void *serv) +{ +#define s ((NNTP_SERVER *) serv) + char group[LONG_STRING], mod, desc[HUGE_STRING]; + int first, last; + NNTP_DATA *nntp_data; + static int n = 0; + + _checked = n; /* _checked have N, where N = number of groups */ + if (!buf) /* at EOF must be zerouth */ + n = 0; + + if (!s || !buf) + return 0; + + *desc = 0; + sscanf (buf, "%s %d %d %c %[^\n]", group, &last, &first, &mod, desc); + if (!group) + return 0; + if ((nntp_data = (NNTP_DATA *) hash_find (s->newsgroups, group)) == NULL) + { + n++; + 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 = s; + if (s->newsgroups->nelem < s->newsgroups->curnelem * 2) + s->newsgroups = hash_resize (s->newsgroups, s->newsgroups->nelem * 2); + hash_insert (s->newsgroups, nntp_data->group, nntp_data, 0); + nntp_add_to_list (s, nntp_data); + } + nntp_data->deleted = 0; + nntp_data->firstMessage = first; + nntp_data->lastMessage = last; + if (mod == 'y') + nntp_data->allowed = 1; + else + nntp_data->allowed = 0; + if (nntp_data->desc) + FREE (&nntp_data->desc); + if (*desc) + nntp_data->desc = safe_strdup (desc); + if (nntp_data->rc || nntp_data->lastCached) + mutt_newsgroup_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; +#undef s +} + +int nntp_check_newgroups (NNTP_SERVER *serv, int force) +{ + char buf[LONG_STRING]; + NNTP_DATA nntp_data; + LIST *l; + LIST emp; + time_t now; + struct tm *t; + + if (!serv || !serv->newgroups_time) + return -1; + + if (nntp_open_connection (serv) < 0) + return -1; + + /* check subscribed groups for new news */ + if (option (OPTSHOWNEWNEWS)) + { + mutt_message _("Checking for new messages..."); + for (l = serv->list; l; l = l->next) + { + serv->check_time = 0; /* really check! */ + if (l->data && ((NNTP_DATA *) l->data)->subscribed) + _nntp_check_mailbox (NULL, (NNTP_DATA *) l->data); + } + } + else if (!force) + return 0; + + mutt_message _("Checking for new newsgroups..."); + now = serv->newgroups_time; + time (&serv->newgroups_time); + t = gmtime (&now); + snprintf (buf, sizeof (buf), "NEWGROUPS %02d%02d%02d %02d%02d%02d GMT\r\n", + (t->tm_year % 100), t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, + t->tm_sec); + nntp_data.nserv = serv; + if (Context && Context->magic == M_NNTP) + nntp_data.group = ((NNTP_DATA *)Context->data)->group; + else + nntp_data.group = NULL; + l = serv->tail; + if (mutt_nntp_fetch (&nntp_data, buf, _("Adding new newsgroups..."), + add_group, serv, 0) != 0) + { +#ifdef DEBUG + nntp_error ("nntp_check_newgroups()", buf); +#endif + return -1; + } + + mutt_message _("Loading descriptions..."); + if (l) + emp.next = l->next; + else + emp.next = serv->list; + l = &emp; + while (l->next) + { + l = l->next; + ((NNTP_DATA *) l->data)->new = 1; + nntp_get_desc ((NNTP_DATA *) l->data, ((NNTP_DATA *) l->data)->group, NULL); + } + if (emp.next) + nntp_save_cache_index (serv); + mutt_clear_error (); + return _checked; +} + +/* Load list of all newsgroups from cache ALL */ +int nntp_get_cache_all (NNTP_SERVER *serv) +{ + char buf[HUGE_STRING]; + FILE *f; + + nntp_cache_expand (buf, serv->cache); + if ((f = safe_fopen (buf, "r"))) + { + int i = 0; + + while (fgets (buf, sizeof(buf), f) != NULL) + { + if (ReadInc && (i % ReadInc == 0)) + mutt_message (_("Loading list from cache... %d"), i); + add_group (buf, serv); + i++; + } + add_group (NULL, NULL); + fclose (f); + mutt_clear_error (); + return 0; + } + else + { + FREE (&serv->cache); + return -1; + } +} + +/* Load list of all newsgroups from active */ +int nntp_get_active (NNTP_SERVER *serv) +{ + char msg[SHORT_STRING]; + NNTP_DATA nntp_data; + LIST *tmp; + + if (nntp_open_connection (serv) < 0) + return -1; + + snprintf (msg, sizeof(msg), _("Loading list of all newsgroups on server %s..."), + serv->conn->account.host); + mutt_message (msg); + time (&serv->newgroups_time); + nntp_data.nserv = serv; + nntp_data.group = NULL; + + if (mutt_nntp_fetch (&nntp_data, "LIST\r\n", msg, add_group, serv, 0) < 0) + { +#ifdef DEBUG + nntp_error ("nntp_get_active()", "LIST\r\n"); +#endif + return -1; + } + + strfcpy (msg, _("Loading descriptions..."), sizeof (msg)); + mutt_message (msg); + nntp_get_desc (&nntp_data, "*", msg); + + for (tmp = serv->list; tmp; tmp = tmp->next) + { + NNTP_DATA *data = (NNTP_DATA *)tmp->data; + + if (data && data->deleted && !data->rc) + { + nntp_delete_cache (data); + hash_delete (serv->newsgroups, data->group, NULL, nntp_delete_data); + tmp->data = NULL; + } + } + nntp_save_cache_index (serv); + + mutt_clear_error (); + return _checked; +} + +/* + * returns -1 if error ocurred while retrieving header, + * number of articles which ones exist in context on success. + */ +int nntp_check_msgid (CONTEXT *ctx, const char *msgid) +{ + int ret; + + /* if msgid is already in context, don't reload them */ + if (hash_find (ctx->id_hash, msgid)) + return 1; + if (ctx->msgcount == ctx->hdrmax) + mx_alloc_memory (ctx); + ctx->hdrs[ctx->msgcount] = mutt_new_header (); + ctx->hdrs[ctx->msgcount]->index = ctx->msgcount; + + mutt_message (_("Fetching %s from server..."), msgid); + ret = nntp_read_header (ctx, msgid, 0); + /* since nntp_read_header() may set read flag, we must reset it */ + ctx->hdrs[ctx->msgcount]->read = 0; + if (ret != 0) + mutt_free_header (&ctx->hdrs[ctx->msgcount]); + else + { + ctx->msgcount++; + mx_update_context (ctx, 1); + ctx->changed = 1; + } + return ret; +} + +typedef struct +{ + CONTEXT *ctx; + unsigned int num; + unsigned int max; + unsigned int *child; +} CHILD_CONTEXT; + +static int check_children (char *s, void *c) +{ +#define cc ((CHILD_CONTEXT *) c) + unsigned int i, n; + + if (!s || (n = atoi (s)) == 0) + return 0; + for (i = 0; i < cc->ctx->msgcount; i++) + if (cc->ctx->hdrs[i]->article_num == n) + return 0; + if (cc->num >= cc->max) + safe_realloc ((void **) &cc->child, sizeof (unsigned int) * (cc->max += 25)); + cc->child[cc->num++] = n; + + return 0; +#undef cc +} + +int nntp_check_children (CONTEXT *ctx, const char *msgid) +{ + NNTP_DATA *nntp_data = (NNTP_DATA *)ctx->data; + char buf[STRING]; + int i, ret = 0, tmp = 0; + CHILD_CONTEXT cc; + + if (!nntp_data || !nntp_data->nserv || !nntp_data->nserv->conn || + !nntp_data->nserv->conn->account.host) + return -1; + if (nntp_data->firstMessage > nntp_data->lastLoaded) + return 0; + if (!nntp_data->nserv->hasXPAT) + { + mutt_error (_("Server %s does not support this operation!"), + nntp_data->nserv->conn->account.host); + return -1; + } + + snprintf (buf, sizeof (buf), "XPAT References %d-%d *%s*\r\n", + nntp_data->firstMessage, nntp_data->lastLoaded, msgid); + + cc.ctx = ctx; + cc.num = 0; + cc.max = 25; + cc.child = safe_malloc (sizeof (unsigned int) * 25); + if (mutt_nntp_fetch (nntp_data, buf, NULL, check_children, &cc, 0)) + { + FREE (&cc.child); + return -1; + } + /* dont try to read the xover cache. check_children() already + * made sure that we dont have the article, so we need to visit + * the server. Reading the cache at this point is also bad + * because it would duplicate messages */ + if (option (OPTNEWSCACHE)) + { + tmp++; + unset_option (OPTNEWSCACHE); + } + for (i = 0; i < cc.num; i++) + { + if ((ret = nntp_fetch_headers (ctx, cc.child[i], cc.child[i]))) + break; + if (ctx->msgcount && + ctx->hdrs[ctx->msgcount - 1]->article_num == cc.child[i]) + ctx->hdrs[ctx->msgcount - 1]->read = 0; + } + if (tmp) + set_option (OPTNEWSCACHE); + FREE (&cc.child); + return ret; +} diff -udprP mutt-1.4.2.2.orig/nntp.h mutt-1.4.2.2/nntp.h --- mutt-1.4.2.2.orig/nntp.h 1970-01-01 03:00:00.000000000 +0300 +++ mutt-1.4.2.2/nntp.h 2006-07-18 12:45:03.000000000 +0300 @@ -0,0 +1,135 @@ +/* + * Copyright (C) 1998 Brandon Long <blong@fiction.net> + * Copyright (C) 1999 Andrej Gritsenko <andrej@lucky.net> + * Copyright (C) 2000-2002 Vsevolod Volkov <vvv@mutt.org.ua> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _NNTP_H_ +#define _NNTP_H_ 1 + +#include "mutt_socket.h" +#include "mailbox.h" + +#include <time.h> + +#define NNTP_PORT 119 +#define NNTP_SSL_PORT 563 + +/* number of entries in the hash table */ +#define NNTP_CACHE_LEN 10 + +enum +{ + NNTP_NONE = 0, + NNTP_OK, + NNTP_BYE +}; + +typedef struct +{ + int first; + int last; +} NEWSRC_ENTRY; + +typedef struct +{ + unsigned int hasXPAT : 1; + unsigned int hasXGTITLE : 1; + unsigned int hasXOVER : 1; + unsigned int status : 3; + char *newsrc; + char *cache; + int stat; + off_t size; + time_t mtime; + time_t newgroups_time; + time_t check_time; + HASH *newsgroups; + LIST *list; /* list of newsgroups */ + LIST *tail; /* last entry of list */ + CONNECTION *conn; +} NNTP_SERVER; + +typedef struct +{ + unsigned int index; + char *path; +} NNTP_CACHE; + +typedef struct +{ + NEWSRC_ENTRY *entries; + unsigned int num; /* number of used entries */ + unsigned int max; /* number of allocated entries */ + unsigned int unread; + unsigned int firstMessage; + unsigned int lastMessage; + unsigned int lastLoaded; + unsigned int lastCached; + unsigned int subscribed : 1; + unsigned int rc : 1; + unsigned int new : 1; + unsigned int allowed : 1; + unsigned int deleted : 1; + char *group; + char *desc; + char *cache; + NNTP_SERVER *nserv; + NNTP_CACHE acache[NNTP_CACHE_LEN]; +} NNTP_DATA; + +/* internal functions */ +int nntp_get_active (NNTP_SERVER *); +int nntp_get_cache_all (NNTP_SERVER *); +int nntp_save_cache_index (NNTP_SERVER *); +int nntp_check_newgroups (NNTP_SERVER *, int); +int nntp_save_cache_group (CONTEXT *); +int nntp_parse_url (const char *, ACCOUNT *, char *, size_t); +void newsrc_gen_entries (CONTEXT *); +void nntp_get_status (CONTEXT *, HEADER *, char *, int); +void mutt_newsgroup_stat (NNTP_DATA *); +void nntp_delete_cache (NNTP_DATA *); +void nntp_add_to_list (NNTP_SERVER *, NNTP_DATA *); +void nntp_cache_expand (char *, const char *); +void nntp_delete_data (void *); + +/* exposed interface */ +NNTP_SERVER *mutt_select_newsserver (char *); +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 *); +void nntp_clear_cacheindex (NNTP_SERVER *); +int mutt_newsrc_update (NNTP_SERVER *); +int nntp_open_mailbox (CONTEXT *); +int nntp_sync_mailbox (CONTEXT *); +int nntp_check_mailbox (CONTEXT *); +int nntp_close_mailbox (CONTEXT *); +void nntp_fastclose_mailbox (CONTEXT *); +int nntp_fetch_message (MESSAGE *, CONTEXT *, int); +int nntp_post (const char *); +int nntp_check_msgid (CONTEXT *, const char *); +int nntp_check_children (CONTEXT *, const char *); +void nntp_buffy (char *); +void nntp_expand_path (char *, size_t, ACCOUNT *); +void nntp_logout_all (); +const char *nntp_format_str (char *, size_t, char, const char *, const char *, + const char *, const char *, unsigned long, format_flag); + +NNTP_SERVER *CurrentNewsSrv INITVAL (NULL); + +#endif /* _NNTP_H_ */ diff -udprP mutt-1.4.2.2.orig/pager.c mutt-1.4.2.2/pager.c --- mutt-1.4.2.2.orig/pager.c 2002-01-13 10:52:15.000000000 +0200 +++ mutt-1.4.2.2/pager.c 2006-07-18 12:45:03.000000000 +0300 @@ -33,6 +33,11 @@ #include "imap.h" #endif +#ifdef USE_NNTP +#include "mx.h" +#include "nntp.h" +#endif + #ifdef HAVE_PGP #include "pgp.h" @@ -1445,6 +1450,16 @@ static struct mapping_t PagerHelpExtra[] { NULL, 0 } }; +#ifdef USE_NNTP +static struct mapping_t PagerNewsHelpExtra[] = { + { N_("Post"), OP_POST }, + { N_("Followup"), OP_FOLLOWUP }, + { N_("Del"), OP_DELETE }, + { N_("Next"), OP_MAIN_NEXT_UNDELETED }, + { NULL, 0 } +}; +#endif + /* This pager is actually not so simple as it once was. It now operates in @@ -1486,6 +1501,10 @@ mutt_pager (const char *banner, const ch int old_PagerIndexLines; /* some people want to resize it * while inside the pager... */ +#ifdef USE_NNTP + char *followup_to; +#endif + if (!(flags & M_SHOWCOLOR)) flags |= M_SHOWFLAT; @@ -1525,7 +1544,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 == M_NNTP)) ? PagerNewsHelpExtra : +#endif + PagerHelpExtra); snprintf (helpstr, sizeof (helpstr), "%s %s", tmphelp, buffer); } if (!InHelp) @@ -2298,6 +2321,15 @@ mutt_pager (const char *banner, const ch } #endif +#ifdef USE_NNTP + if (Context->magic == M_NNTP) + { + mutt_flushinp (); + mutt_error _("Can't change 'important' flag on NNTP server."); + break; + } +#endif + mutt_set_flag (Context, extra->hdr, M_FLAG, !extra->hdr->flagged); redraw = REDRAW_STATUS | REDRAW_INDEX; if (option (OPTRESOLVE)) @@ -2331,6 +2363,60 @@ mutt_pager (const char *banner, const ch redraw = REDRAW_FULL; break; +#ifdef USE_NNTP + case OP_POST: + CHECK_MODE(IsHeader (extra) && !IsAttach (extra)); + CHECK_ATTACH; + if (extra->ctx && extra->ctx->magic == M_NNTP && + !((NNTP_DATA *)extra->ctx->data)->allowed && + query_quadoption (OPT_TOMODERATED,_("Posting to this group not allowed, may be moderated. Continue?")) != M_YES) + break; + ci_send_message (SENDNEWS, NULL, NULL, extra->ctx, NULL); + redraw = REDRAW_FULL; + break; + + case OP_FORWARD_TO_GROUP: + CHECK_MODE(IsHeader (extra) || IsMsgAttach (extra)); + CHECK_ATTACH; + if (extra->ctx && extra->ctx->magic == M_NNTP && + !((NNTP_DATA *)extra->ctx->data)->allowed && + query_quadoption (OPT_TOMODERATED,_("Posting to this group not allowed, may be moderated. Continue?")) != M_YES) + break; + if (IsMsgAttach (extra)) + mutt_attach_forward (extra->fp, extra->hdr, extra->idx, + extra->idxlen, extra->bdy, SENDNEWS); + else + ci_send_message (SENDNEWS|SENDFORWARD, NULL, NULL, extra->ctx, extra->hdr); + redraw = REDRAW_FULL; + break; + + case OP_FOLLOWUP: + CHECK_MODE(IsHeader (extra) || IsMsgAttach (extra)); + CHECK_ATTACH; + + if (IsMsgAttach (extra)) + followup_to = extra->bdy->hdr->env->followup_to; + else + followup_to = extra->hdr->env->followup_to; + + if (!followup_to || mutt_strcasecmp (followup_to, "poster") || + query_quadoption (OPT_FOLLOWUPTOPOSTER,_("Reply by mail as poster prefers?")) != M_YES) + { + if (extra->ctx && extra->ctx->magic == M_NNTP && + !((NNTP_DATA *)extra->ctx->data)->allowed && + query_quadoption (OPT_TOMODERATED,_("Posting to this group not allowed, may be moderated. Continue?")) != M_YES) + break; + if (IsMsgAttach (extra)) + mutt_attach_reply (extra->fp, extra->hdr, extra->idx, + extra->idxlen, extra->bdy, SENDNEWS|SENDREPLY); + else + ci_send_message (SENDNEWS|SENDREPLY, NULL, NULL, + extra->ctx, extra->hdr); + redraw = REDRAW_FULL; + break; + } +#endif + case OP_REPLY: CHECK_MODE(IsHeader (extra) || IsMsgAttach (extra)); CHECK_ATTACH; @@ -2377,7 +2463,7 @@ mutt_pager (const char *banner, const ch CHECK_ATTACH; if (IsMsgAttach (extra)) mutt_attach_forward (extra->fp, extra->hdr, extra->idx, - extra->idxlen, extra->bdy); + extra->idxlen, extra->bdy, 0); else ci_send_message (SENDFORWARD, NULL, NULL, extra->ctx, extra->hdr); redraw = REDRAW_FULL; diff -udprP mutt-1.4.2.2.orig/parse.c mutt-1.4.2.2/parse.c --- mutt-1.4.2.2.orig/parse.c 2002-01-29 12:05:44.000000000 +0200 +++ mutt-1.4.2.2/parse.c 2006-07-18 12:45:03.000000000 +0300 @@ -90,7 +90,7 @@ static char *read_rfc822_line (FILE *f, /* 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; int m, n = 0; @@ -1047,6 +1047,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': @@ -1098,6 +1109,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")) { @@ -1207,7 +1239,21 @@ 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.4.2.2.orig/pattern.c mutt-1.4.2.2/pattern.c --- mutt-1.4.2.2.orig/pattern.c 2002-05-18 08:39:55.000000000 +0300 +++ mutt-1.4.2.2/pattern.c 2006-07-18 12:45:03.000000000 +0300 @@ -87,6 +87,9 @@ Flags[] = { 't', M_TO, 0, eat_regexp }, { 'U', M_UNREAD, 0, NULL }, { 'v', M_COLLAPSED, 0, NULL }, +#ifdef USE_NNTP + { 'w', M_NEWSGROUPS, 0, eat_regexp }, +#endif { 'x', M_REFERENCE, 0, eat_regexp }, { 'y', M_XLABEL, 0, eat_regexp }, { 'z', M_SIZE, 0, eat_range }, @@ -1053,6 +1056,10 @@ mutt_pattern_exec (struct pattern_t *pat return (pat->not ^ (h->env->x_label && regexec (pat->rx, h->env->x_label, 0, NULL, 0) == 0)); case M_DUPLICATED: return (pat->not ^ (h->thread && h->thread->duplicate_thread)); +#ifdef USE_NNTP + case M_NEWSGROUPS: + return (pat->not ^ (h->env->newsgroups && regexec (pat->rx, h->env->newsgroups, 0, NULL, 0) == 0)); +#endif } mutt_error (_("error: unknown op %d (report this error)."), pat->op); return (-1); @@ -1120,6 +1127,7 @@ int mutt_pattern_func (int op, char *pro int i; strfcpy (buf, NONULL (Context->pattern), sizeof (buf)); + if (prompt || op != M_LIMIT) if (mutt_get_field (prompt, buf, sizeof (buf), M_PATTERN | M_CLEAR) != 0 || !buf[0]) return (-1); diff -udprP mutt-1.4.2.2.orig/po/POTFILES.in mutt-1.4.2.2/po/POTFILES.in --- mutt-1.4.2.2.orig/po/POTFILES.in 2000-10-10 22:22:48.000000000 +0300 +++ mutt-1.4.2.2/po/POTFILES.in 2006-07-18 12:45:03.000000000 +0300 @@ -40,6 +40,8 @@ muttlib.c mutt_socket.c mutt_ssl.c mx.c +newsrc.c +nntp.c pager.c parse.c pattern.c diff -udprP mutt-1.4.2.2.orig/postpone.c mutt-1.4.2.2/postpone.c --- mutt-1.4.2.2.orig/postpone.c 2001-10-31 11:20:38.000000000 +0200 +++ mutt-1.4.2.2/postpone.c 2006-07-18 12:45:03.000000000 +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, M_NOSORT | M_QUIET, &ctx) == NULL) PostCount = 0; else PostCount = ctx.msgcount; mx_fastclose_mailbox (&ctx); +#ifdef USE_NNTP + if (optnews) + set_option (OPTNEWS); +#endif } return (PostCount); diff -udprP mutt-1.4.2.2.orig/protos.h mutt-1.4.2.2/protos.h --- mutt-1.4.2.2.orig/protos.h 2002-04-29 20:12:18.000000000 +0300 +++ mutt-1.4.2.2/protos.h 2006-07-18 12:45:03.000000000 +0300 @@ -95,6 +95,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); ENVELOPE *mutt_read_rfc822_header (FILE *, HEADER *, short, short); HEADER *mutt_dup_header (HEADER *); @@ -228,6 +229,7 @@ void mutt_update_encoding (BODY *a); void mutt_update_tree (ATTACHPTR **, short); void mutt_version (void); void mutt_view_attachments (HEADER *); +void mutt_write_references (LIST *, FILE *); void mutt_set_virtual (CONTEXT *); int mutt_addr_is_user (ADDRESS *); @@ -313,6 +315,7 @@ int mutt_save_message (HEADER *, int, in int mutt_search_command (int, int); int mutt_compose_menu (HEADER *, char *, size_t, HEADER *); int mutt_thread_set_flag (HEADER *, int, int, int); +int mutt_update_list_file (char *, char *, char *, char *); int mutt_user_is_recipient (HEADER *); void mutt_update_num_postponed (void); int mutt_view_attachment (FILE*, BODY *, int, HEADER *, ATTACHPTR **, short); diff -udprP mutt-1.4.2.2.orig/recvattach.c mutt-1.4.2.2/recvattach.c --- mutt-1.4.2.2.orig/recvattach.c 2001-09-26 13:33:09.000000000 +0300 +++ mutt-1.4.2.2/recvattach.c 2006-07-18 12:45:03.000000000 +0300 @@ -989,6 +989,15 @@ void mutt_view_attachments (HEADER *hdr) } #endif +#ifdef USE_NNTP + if (Context->magic == M_NNTP) + { + mutt_flushinp (); + mutt_error _("Can't delete attachment from newsserver."); + break; + } +#endif + #ifdef HAVE_PGP @@ -1084,10 +1093,33 @@ void mutt_view_attachments (HEADER *hdr) case OP_FORWARD_MESSAGE: CHECK_ATTACH; mutt_attach_forward (fp, hdr, idx, idxlen, - menu->tagprefix ? NULL : idx[menu->current]->content); + menu->tagprefix ? NULL : idx[menu->current]->content, 0); menu->redraw = REDRAW_FULL; break; +#ifdef USE_NNTP + case OP_FORWARD_TO_GROUP: + CHECK_ATTACH; + mutt_attach_forward (fp, hdr, idx, idxlen, + menu->tagprefix ? NULL : idx[menu->current]->content, SENDNEWS); + menu->redraw = REDRAW_FULL; + break; + + case OP_FOLLOWUP: + CHECK_ATTACH; + + if (!idx[menu->current]->content->hdr->env->followup_to || + mutt_strcasecmp (idx[menu->current]->content->hdr->env->followup_to, "poster") || + query_quadoption (OPT_FOLLOWUPTOPOSTER,_("Reply by mail as poster prefers?")) != M_YES) + { + mutt_attach_reply (fp, hdr, idx, idxlen, + menu->tagprefix ? NULL : idx[menu->current]->content, + SENDNEWS|SENDREPLY); + menu->redraw = REDRAW_FULL; + break; + } +#endif + case OP_REPLY: case OP_GROUP_REPLY: case OP_LIST_REPLY: diff -udprP mutt-1.4.2.2.orig/recvcmd.c mutt-1.4.2.2/recvcmd.c --- mutt-1.4.2.2.orig/recvcmd.c 2002-01-02 11:33:38.000000000 +0200 +++ mutt-1.4.2.2/recvcmd.c 2006-07-18 12:45:03.000000000 +0300 @@ -334,7 +334,7 @@ static BODY ** copy_problematic_attachme static void attach_forward_bodies (FILE * fp, HEADER * hdr, ATTACHPTR ** idx, short idxlen, BODY * cur, - short nattach) + short nattach, int flags) { short i; short mime_fwd_all = 0; @@ -480,7 +480,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); + ci_send_message (flags, tmphdr, tmpbody, NULL, parent); return; bail: @@ -507,7 +507,7 @@ _("Can't decode all tagged attachments. */ static void attach_forward_msgs (FILE * fp, HEADER * hdr, - ATTACHPTR ** idx, short idxlen, BODY * cur) + ATTACHPTR ** idx, short idxlen, BODY * cur, int flags) { HEADER *curhdr = NULL; HEADER *tmphdr; @@ -612,23 +612,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, - ATTACHPTR ** idx, short idxlen, BODY * cur) + ATTACHPTR ** idx, short idxlen, BODY * cur, int flags) { short nattach; if (check_all_msg (idx, idxlen, cur, 0) == 0) - attach_forward_msgs (fp, hdr, idx, idxlen, cur); + attach_forward_msgs (fp, hdr, idx, idxlen, cur, flags); else { nattach = count_tagged (idx, idxlen); - attach_forward_bodies (fp, hdr, idx, idxlen, cur, nattach); + attach_forward_bodies (fp, hdr, idx, idxlen, cur, nattach, flags); } } @@ -686,28 +686,40 @@ attach_reply_envelope_defaults (ENVELOPE return -1; } - if (parent) +#ifdef USE_NNTP + if ((flags & SENDNEWS)) { - if (mutt_fetch_recips (env, curenv, flags) == -1) - return -1; + /* in case followup set Newsgroups: with Followup-To: if it present */ + if (!env->newsgroups && curenv && + mutt_strcasecmp (curenv->followup_to, "poster")) + env->newsgroups = safe_strdup (curenv->followup_to); } else +#endif { - for (i = 0; i < idxlen; i++) + if (parent) { - if (idx[i]->content->tagged - && mutt_fetch_recips (env, idx[i]->content->hdr->env, flags) == -1) + if (mutt_fetch_recips (env, curenv, flags) == -1) return -1; } + else + { + for (i = 0; i < idxlen; i++) + { + if (idx[i]->content->tagged + && mutt_fetch_recips (env, idx[i]->content->hdr->env, flags) == -1) + return -1; + } + } + + if ((flags & SENDLISTREPLY) && !env->to) + { + mutt_error _("No mailing lists found!"); + return (-1); + } + + mutt_fix_reply_recipients (env); } - - if ((flags & SENDLISTREPLY) && !env->to) - { - mutt_error _("No mailing lists found!"); - return (-1); - } - - mutt_fix_reply_recipients (env); mutt_make_misc_reply_headers (env, Context, curhdr, curenv); if (parent) @@ -768,6 +780,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 (idx, idxlen, cur, 0) == -1) { nattach = count_tagged (idx, idxlen); diff -udprP mutt-1.4.2.2.orig/rfc1524.c mutt-1.4.2.2/rfc1524.c --- mutt-1.4.2.2.orig/rfc1524.c 2001-05-15 10:23:49.000000000 +0300 +++ mutt-1.4.2.2/rfc1524.c 2006-07-18 12:45:03.000000000 +0300 @@ -563,13 +563,13 @@ int rfc1524_expand_filename (char *namet * safe_fopen(). */ -int mutt_rename_file (char *oldfile, char *newfile) +int _mutt_rename_file (char *oldfile, char *newfile, int overwrite) { FILE *ofp, *nfp; if (access (oldfile, F_OK) != 0) return 1; - if (access (newfile, F_OK) == 0) + if (!overwrite && access (newfile, F_OK) == 0) return 2; if ((ofp = fopen (oldfile,"r")) == NULL) return 3; @@ -584,3 +584,8 @@ int mutt_rename_file (char *oldfile, cha mutt_unlink (oldfile); return 0; } + +int mutt_rename_file (char *oldfile, char *newfile) +{ + return _mutt_rename_file (oldfile, newfile, 0); +} diff -udprP mutt-1.4.2.2.orig/rfc1524.h mutt-1.4.2.2/rfc1524.h --- mutt-1.4.2.2.orig/rfc1524.h 2000-03-03 12:10:13.000000000 +0200 +++ mutt-1.4.2.2/rfc1524.h 2006-07-18 12:45:03.000000000 +0300 @@ -40,5 +40,6 @@ int rfc1524_expand_command (BODY *, char int rfc1524_expand_filename (char *, char *, char *, size_t); int rfc1524_mailcap_lookup (BODY *, char *, rfc1524_entry *, int); int mutt_rename_file (char *, char *); +int _mutt_rename_file (char *, char *, int); #endif /* _RFC1524_H */ diff -udprP mutt-1.4.2.2.orig/send.c mutt-1.4.2.2/send.c --- mutt-1.4.2.2.orig/send.c 2002-01-31 00:50:59.000000000 +0200 +++ mutt-1.4.2.2/send.c 2006-07-18 12:45:03.000000000 +0300 @@ -41,6 +41,10 @@ #include "pgp.h" #endif +#ifdef USE_NNTP +#include "nntp.h" +#endif + #ifdef MIXMASTER #include "remailer.h" #endif @@ -196,17 +200,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; - if (edit_address (&en->to, "To: ") == -1 || en->to == NULL) - return (-1); - if (option (OPTASKCC) && edit_address (&en->cc, "Cc: ") == -1) - return (-1); - if (option (OPTASKBCC) && edit_address (&en->bcc, "Bcc: ") == -1) - return (-1); +#ifdef USE_NNTP + if (option (OPTNEWSSEND)) + { + if (en->newsgroups) + strfcpy (buf, en->newsgroups, sizeof (buf)); + else + buf[0] = 0; + if (mutt_get_field ("Newsgroups: ", buf, sizeof (buf), 0) != 0) + return (-1); + FREE (&en->newsgroups); + en->newsgroups = safe_strdup (buf); + + if (en->followup_to) + strfcpy (buf, en->followup_to, sizeof (buf)); + else + buf[0] = 0; + if (option (OPTASKFOLLOWUP) && mutt_get_field ("Followup-To: ", buf, sizeof (buf), 0) != 0) + return (-1); + FREE (&en->followup_to); + en->followup_to = safe_strdup (buf); + + if (en->x_comment_to) + strfcpy (buf, en->x_comment_to, sizeof (buf)); + else + buf[0] = 0; + if (option (OPTXCOMMENTTO) && option (OPTASKXCOMMENTTO) && mutt_get_field ("X-Comment-To: ", buf, sizeof (buf), 0) != 0) + return (-1); + FREE (&en->x_comment_to); + en->x_comment_to = safe_strdup (buf); + } + else +#endif + { + if (edit_address (&en->to, "To: ") == -1 || en->to == NULL) + return (-1); + if (option (OPTASKCC) && edit_address (&en->cc, "Cc: ") == -1) + return (-1); + if (option (OPTASKBCC) && edit_address (&en->bcc, "Bcc: ") == -1) + return (-1); + } if (en->subject) { @@ -242,6 +280,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; @@ -254,6 +300,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 } } @@ -284,6 +338,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) { if (last) @@ -597,6 +657,10 @@ void mutt_make_misc_reply_headers (ENVEL else env->subject = safe_strdup ("Re: your mail"); +#ifdef USE_NNTP + if (option (OPTNEWSSEND) && option (OPTXCOMMENTTO) && curenv->from) + env->x_comment_to = safe_strdup (mutt_get_name (curenv->from)); +#endif } void mutt_add_to_reference_headers (ENVELOPE *env, ENVELOPE *curenv, LIST ***pp, LIST ***qq) @@ -679,27 +743,39 @@ envelope_defaults (ENVELOPE *env, CONTEX if (flags & SENDREPLY) { - if (tag) +#ifdef USE_NNTP + if ((flags & SENDNEWS)) { - HEADER *h; - - for (i = 0; i < ctx->vcount; i++) + /* 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) { - h = ctx->hdrs[ctx->v2r[i]]; - if (h->tagged && mutt_fetch_recips (env, h->env, flags) == -1) + HEADER *h; + + for (i = 0; i < ctx->vcount; i++) + { + h = ctx->hdrs[ctx->v2r[i]]; + if (h->tagged && mutt_fetch_recips (env, h->env, flags) == -1) + return -1; + } + } + else if (mutt_fetch_recips (env, curenv, flags) == -1) return -1; + + if ((flags & SENDLISTREPLY) && !env->to) + { + mutt_error _("No mailing lists found!"); + return (-1); } - } - else if (mutt_fetch_recips (env, curenv, flags) == -1) - return -1; - if ((flags & SENDLISTREPLY) && !env->to) - { - mutt_error _("No mailing lists found!"); - return (-1); + mutt_fix_reply_recipients (env); } - - mutt_fix_reply_recipients (env); mutt_make_misc_reply_headers (env, ctx, cur, curenv); mutt_make_reference_headers (tag ? NULL : curenv, env, ctx); } @@ -828,7 +904,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)) { @@ -1084,6 +1171,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) != M_NO && mutt_num_postponed (1)) { @@ -1115,6 +1209,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)) @@ -1195,11 +1305,16 @@ ci_send_message (int flags, /* send mod if (option (OPTHDRS)) process_user_recips (msg->env); +#ifdef USE_NNTP + if ((flags & SENDNEWS) && ctx && ctx->magic == M_NNTP && !msg->env->newsgroups) + msg->env->newsgroups = safe_strdup (((NNTP_DATA *)ctx->data)->group); +#endif + if (! (flags & SENDMAILX) && ! (option (OPTAUTOEDIT) && option (OPTEDITHDRS)) && ! ((flags & SENDREPLY) && option (OPTFASTREPLY))) { - if (edit_envelope (msg->env) == -1) + if (edit_envelope (msg->env, flags) == -1) goto cleanup; } @@ -1349,6 +1464,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; } @@ -1380,6 +1500,9 @@ main_loop: } } +#ifdef USE_NNTP + if (!(flags & SENDNEWS)) +#endif if (!msg->env->to && !msg->env->cc && !msg->env->bcc) { if (! (flags & SENDBATCH)) @@ -1402,6 +1525,19 @@ main_loop: mutt_error _("No subject specified."); goto main_loop; } +#ifdef USE_NNTP + if ((flags & SENDNEWS) && !msg->env->subject) + { + mutt_error _("No subject specified."); + goto main_loop; + } + + if ((flags & SENDNEWS) && !msg->env->newsgroups) + { + mutt_error _("No newsgroup specified."); + goto main_loop; + } +#endif if (msg->content->next) msg->content = mutt_make_multipart (msg->content); @@ -1588,7 +1724,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 #ifdef HAVE_PGP if (msg->pgp & PGPENCRYPT) diff -udprP mutt-1.4.2.2.orig/sendlib.c mutt-1.4.2.2/sendlib.c --- mutt-1.4.2.2.orig/sendlib.c 2002-05-31 19:59:39.000000000 +0300 +++ mutt-1.4.2.2/sendlib.c 2006-07-18 12:45:03.000000000 +0300 @@ -40,6 +40,10 @@ #include <sys/wait.h> #include <fcntl.h> +#ifdef USE_NNTP +#include <nntp.h> +#endif + #ifdef HAVE_SYSEXITS_H #include <sysexits.h> #else /* Make sure EX_OK is defined <philiph@pobox.com> */ @@ -1537,7 +1541,7 @@ void mutt_write_address_list (ADDRESS *a /* need to write the list in reverse because they are stored in reverse order * when parsed to speed up threading */ -static void write_references (LIST *r, FILE *f) +void mutt_write_references (LIST *r, FILE *f) { LIST **ref = NULL; int refcnt = 0, refmax = 0; @@ -1579,6 +1583,9 @@ int mutt_write_rfc822_header (FILE *fp, char *p; LIST *tmp = env->userhdrs; +#ifdef USE_NNTP + if (!option (OPTNEWSSEND)) +#endif if (mode == 0 && !privacy) fputs (mutt_make_date (buffer, sizeof(buffer)), fp); @@ -1598,6 +1605,9 @@ int mutt_write_rfc822_header (FILE *fp, mutt_write_address_list (env->to, fp, 4); } else if (mode > 0) +#ifdef USE_NNTP + if (!option (OPTNEWSSEND)) +#endif fputs ("To: \n", fp); if (env->cc) @@ -1606,6 +1616,9 @@ int mutt_write_rfc822_header (FILE *fp, mutt_write_address_list (env->cc, fp, 4); } else if (mode > 0) +#ifdef USE_NNTP + if (!option (OPTNEWSSEND)) +#endif fputs ("Cc: \n", fp); if (env->bcc) @@ -1617,8 +1630,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) fprintf (fp, "Subject: %s\n", env->subject); else if (mode == 1) @@ -1637,6 +1670,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); @@ -1647,7 +1683,7 @@ int mutt_write_rfc822_header (FILE *fp, if (env->references) { fputs ("References:", fp); - write_references (env->references, fp); + mutt_write_references (env->references, fp); fputc('\n', fp); } @@ -1659,7 +1695,7 @@ int mutt_write_rfc822_header (FILE *fp, if (env->in_reply_to) { fputs ("In-Reply-To:", fp); - write_references (env->in_reply_to, fp); + mutt_write_references (env->in_reply_to, fp); fputc ('\n', fp); } @@ -1967,11 +2003,30 @@ mutt_invoke_sendmail (ADDRESS *from, /* const char *msg, /* file containing message */ int eightbit) /* message contains 8bit chars */ { - char *ps = NULL, *path = NULL, *s = safe_strdup (Sendmail), *childout = NULL; + char *ps = NULL, *path = NULL, *s = NULL, *childout = NULL; char **args = NULL; size_t argslen = 0, argsmax = 0; int i; +#ifdef USE_NNTP + if (option (OPTNEWSSEND)) + { + char cmd[LONG_STRING]; + + mutt_FormatString (cmd, sizeof (cmd), NONULL (Inews), nntp_format_str, 0, 0); + if (!*cmd) + { + i = nntp_post (msg); + unlink (msg); + return i; + } + + s = safe_strdup (cmd); + } + else +#endif + s = safe_strdup (Sendmail); + ps = s; i = 0; while ((ps = strtok (ps, " "))) @@ -1995,6 +2050,10 @@ mutt_invoke_sendmail (ADDRESS *from, /* i++; } +#ifdef USE_NNTP + if (!option (OPTNEWSSEND)) + { +#endif if (eightbit && option (OPTUSE8BITMIME)) args = add_option (args, &argslen, &argsmax, "-B8BITMIME"); @@ -2017,6 +2076,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 ((void **) &args, sizeof (char *) * (++argsmax)); @@ -2148,6 +2210,9 @@ void mutt_prepare_envelope (ENVELOPE *en rfc2047_encode_adrlist (env->reply_to, "Reply-To"); if (env->subject) +#ifdef USE_NNTP + if (!option (OPTNEWSSEND) || option (OPTMIMESUBJECT)) +#endif { rfc2047_encode_string (&env->subject); } @@ -2234,6 +2299,10 @@ void mutt_bounce_message (FILE *fp, HEAD rfc822_write_address (resent_from, sizeof (resent_from), from); +#ifdef USE_NNTP + unset_option (OPTNEWSSEND); +#endif + _mutt_bounce_message (fp, h, to, resent_from, from); rfc822_free_address (&from); diff -udprP mutt-1.4.2.2.orig/sort.c mutt-1.4.2.2/sort.c --- mutt-1.4.2.2.orig/sort.c 2001-12-13 14:10:59.000000000 +0200 +++ mutt-1.4.2.2/sort.c 2006-07-18 12:45:03.000000000 +0300 @@ -144,6 +144,15 @@ int compare_order (const void *a, const HEADER **ha = (HEADER **) a; HEADER **hb = (HEADER **) b; +#ifdef USE_NNTP + if ((*ha)->article_num && (*hb)->article_num) + { + int result = (*ha)->article_num - (*hb)->article_num; + 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.4.2.2.orig/url.c mutt-1.4.2.2/url.c --- mutt-1.4.2.2.orig/url.c 2001-10-29 13:53:45.000000000 +0200 +++ mutt-1.4.2.2/url.c 2006-07-18 12:45:03.000000000 +0300 @@ -35,6 +35,8 @@ static struct mapping_t UrlMap[] = { "imaps", U_IMAPS }, { "pop", U_POP }, { "pops", U_POPS }, + { "nntp", U_NNTP }, + { "nntps", U_NNTPS }, { "mailto", U_MAILTO }, { NULL, U_UNKNOWN} }; diff -udprP mutt-1.4.2.2.orig/url.h mutt-1.4.2.2/url.h --- mutt-1.4.2.2.orig/url.h 2001-09-04 12:30:04.000000000 +0300 +++ mutt-1.4.2.2/url.h 2006-07-18 12:45:03.000000000 +0300 @@ -8,6 +8,8 @@ typedef enum url_scheme U_POPS, U_IMAP, U_IMAPS, + U_NNTP, + U_NNTPS, U_MAILTO, U_UNKNOWN } diff -udprP mutt-1.4.2.2.orig/Makefile.am mutt-1.4.2.2/Makefile.am --- mutt-1.4.2.2.orig/Makefile.am 2004-02-12 10:46:17.000000000 +0200 +++ mutt-1.4.2.2/Makefile.am 2006-07-18 12:45:03.000000000 +0300 @@ -67,7 +67,8 @@ non_us_sources = pgp.c pgpinvoke.c pgpke EXTRA_mutt_SOURCES = account.c md5c.c mutt_sasl.c mutt_socket.c mutt_ssl.c \ mutt_tunnel.c pop.c pop_auth.c pop_lib.c pgp.c pgpinvoke.c pgpkey.c \ pgplib.c sha1.c pgpmicalg.c gnupgparse.c resize.c dotlock.c remailer.c \ - browser.h mbyte.h remailer.h url.h mutt_ssl_nss.c pgppacket.c + browser.h mbyte.h remailer.h url.h mutt_ssl_nss.c pgppacket.c \ + nntp.c newsrc.c EXTRA_DIST = COPYRIGHT GPL OPS OPS.PGP TODO configure acconfig.h account.h \ attach.h buffy.h charset.h copy.h dotlock.h functions.h gen_defs \ @@ -76,6 +77,7 @@ EXTRA_DIST = COPYRIGHT GPL OPS OPS.PGP T 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 reldate.h rfc1524.h rfc2047.h \ rfc2231.h rfc822.h sha1.h sort.h mime.types VERSION prepare \ + nntp.h ChangeLog.nntp \ _regex.h OPS.MIX README.SECURITY remailer.c remailer.h browser.h \ mbyte.h lib.h extlib.c pgpewrap.c pgplib.h Muttrc.head Muttrc \ makedoc.c stamp-doc-rc README.SSL \ diff -udprP mutt-1.4.2.2.orig/Muttrc mutt-1.4.2.2/Muttrc --- mutt-1.4.2.2.orig/Muttrc 2006-07-14 21:38:52.000000000 +0300 +++ mutt-1.4.2.2/Muttrc 2006-07-18 12:45:03.000000000 +0300 @@ -176,6 +176,28 @@ macro pager <f1> "!less /usr/local/doc # 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 attach_format="%u%D%I %t%4n %T%.40d%> [%.7m/%.10M, %.6e%?C?, %C?, %s] " # # Name: attach_format @@ -299,6 +321,17 @@ macro pager <f1> "!less /usr/local/doc # bouncing messages. Postfix users may wish 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 charset="" # # Name: charset @@ -725,6 +758,19 @@ macro pager <f1> "!less /usr/local/doc # resulting in two copies 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 @@ -809,6 +855,28 @@ macro pager <f1> "!less /usr/local/doc # "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 @@ -1178,6 +1246,7 @@ macro pager <f1> "!less /usr/local/doc # %E number of messages in current thread # %f entire From: line (address + real name) # %F author name, or recipient name if the message is from you +# %g newsgroup name (if compiled with nntp support) # %i message-id of the current message # %l number of lines in the message # %L If an address in the To or CC header field matches an address @@ -1195,6 +1264,7 @@ macro pager <f1> "!less /usr/local/doc # %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) # %y `x-label:' field, if present # %Y `x-label' field, if present, and (1) not at part of a thread tree, # (2) at the top of a thread, or (3) `x-label' is different from @@ -1218,6 +1288,22 @@ macro pager <f1> "!less /usr/local/doc # See also: ``$to_chars''. # # +# 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: +# +# %s newsserver name +# +# Example: set inews="/usr/local/bin/inews -hS" +# +# # set ispell="ispell" # # Name: ispell @@ -1487,6 +1573,18 @@ macro pager <f1> "!less /usr/local/doc # be attached to the newly composed message if this option is set. # # +# set mime_subject=yes +# +# Name: mime_subject +# Type: boolean +# Default: yes +# +# +# If unset, 8-bit ``subject:'' line in article header will not be +# encoded according to RFC2047 to base64. This is useful when message +# is Usenet article, because MIME for news is nonstandard feature. +# +# # set mix_entry_format="%4n %c %-16s %a" # # Name: mix_entry_format @@ -1541,6 +1639,118 @@ macro pager <f1> "!less /usr/local/doc # printf()-like sequences see the section on ``$index_format''. # # +# 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 headers in. If unset, headers will not be saved at all +# and will be reloaded each time when you enter to newsgroup. +# +# +# set news_server="" +# +# Name: news_server +# Type: string +# Default: "" +# +# +# This variable specifies domain name or address of NNTP server. It +# defaults to the newsserver specified in the environment variable +# $NNTPSERVER or contained in the file /etc/nntpserver. You can also +# specify username and an alternative port for each newsserver, ie: +# +# [nntp[s]://][username[:password]@]newsserver[:port] +# +# +# 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: +# +# %s newsserver name +# +# +# 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 newsserver. +# +# +# 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 nntp_reconnect=ask-yes +# +# Name: nntp_reconnect +# Type: quadoption +# Default: ask-yes +# +# +# Controls whether or not Mutt will try to reconnect to newsserver when +# connection lost. +# +# # set pager="builtin" # # Name: pager @@ -2284,6 +2494,19 @@ macro pager <f1> "!less /usr/local/doc # 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 newsserver +# does not support posting to that newsgroup or totally read-only, that +# posting will not have an effect. +# +# # set postpone=ask-yes # # Name: postpone @@ -2793,6 +3016,41 @@ macro pager <f1> "!less /usr/local/doc # shell from /etc/passwd is used. # # +# set save_unsubscribed=no +# +# Name: save_unsubscribed +# Type: boolean +# Default: no +# +# +# When set, info about unsubscribed newsgroups will be saved into +# ``newsrc'' file and into cache. +# +# +# set show_new_news=yes +# +# Name: show_new_news +# Type: boolean +# Default: yes +# +# +# If set, newsserver will be asked for new newsgroups on entering +# the browser. Otherwise, it will be done only once for a newsserver. +# 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 @@ -3276,6 +3534,18 @@ macro pager <f1> "!less /usr/local/doc # command. # # +# set use_ipv6=yes +# +# Name: use_ipv6 +# Type: boolean +# Default: yes +# +# +# When set, Mutt will look for IPv6 addresses of hosts it tries to +# contact. If this option is unset, Mutt will restrict itself to IPv4 addresses. +# Normally, the default should work. +# +# # set user_agent=yes # # Name: user_agent @@ -3378,3 +3648,14 @@ macro pager <f1> "!less /usr/local/doc # messages to be sent. Exim users may wish to use this. # # +# set x_comment_to=no +# +# Name: x_comment_to +# Type: boolean +# Default: no +# +# +# If set, Mutt will add ``X-Comment-To:'' field (that contains full +# name of original article author) to article that followuped to newsgroup. +# +# diff -udprP mutt-1.4.2.2.orig/configure mutt-1.4.2.2/configure --- mutt-1.4.2.2.orig/configure 2006-07-14 21:38:42.000000000 +0300 +++ mutt-1.4.2.2/configure 2006-07-18 13:04:57.000000000 +0300 @@ -855,6 +855,7 @@ Optional Features: --enable-external-dotlock Force use of an external dotlock program --enable-pop Enable POP3 support --enable-imap Enable IMAP support + --enable-nntp Enable NNTP support --enable-debug Enable debugging support --enable-flock Use flock() to lock files --disable-fcntl Do NOT use fcntl() to lock files @@ -10387,6 +10388,20 @@ else fi +# Check whether --enable-nntp or --disable-nntp was given. +if test "${enable_nntp+set}" = set; then + enableval="$enable_nntp" + if test x$enableval = xyes ; then + cat >>confdefs.h <<\_ACEOF +#define USE_NNTP 1 +_ACEOF + + MUTT_LIB_OBJECTS="$MUTT_LIB_OBJECTS nntp.o newsrc.o" + need_socket="yes" + fi + +fi; + if test "$need_socket" = "yes" then