From: Vsevolod Volkov Date: Sun, 16 Mar 2014 15:54:12 +0100 Subject: nntp http://mutt.org.ua/download/mutt-1.5.23/patch-1.5.23.vvv.nntp.gz Signed-off-by: Matteo F. Vescovi Gbp-Pq: Topic mutt-patched --- ChangeLog.nntp | 399 +++++++++ Makefile.am | 2 + OPS | 23 +- account.c | 27 + account.h | 3 +- attach.h | 2 +- browser.c | 470 +++++++++- browser.h | 7 + buffy.c | 12 + complete.c | 64 ++ compose.c | 177 +++- configure.ac | 11 +- curs_main.c | 312 ++++++- doc/Muttrc | 300 +++++++ doc/manual.xml.head | 20 + doc/mutt.man | 8 +- functions.h | 59 +- globals.h | 16 + hash.c | 27 + hash.h | 3 +- hcache.c | 12 + hdrline.c | 25 + headers.c | 3 + init.c | 22 + init.h | 217 +++++ keymap.c | 1 - mailbox.h | 3 + main.c | 54 ++ mutt.h | 39 +- mutt_sasl.c | 5 + muttlib.c | 17 +- mx.c | 75 ++ mx.h | 3 + newsrc.c | 1260 +++++++++++++++++++++++++++ nntp.c | 2404 +++++++++++++++++++++++++++++++++++++++++++++++++++ nntp.h | 167 ++++ pager.c | 81 +- parse.c | 48 +- pattern.c | 8 + po/POTFILES.in | 2 + postpone.c | 11 + protos.h | 1 + recvattach.c | 34 +- recvcmd.c | 61 +- send.c | 163 +++- sendlib.c | 78 +- sort.c | 16 + url.c | 4 +- url.h | 2 + 49 files changed, 6674 insertions(+), 84 deletions(-) create mode 100644 ChangeLog.nntp create mode 100644 newsrc.c create mode 100644 nntp.c create mode 100644 nntp.h diff --git a/ChangeLog.nntp b/ChangeLog.nntp new file mode 100644 index 0000000..70d1126 --- /dev/null +++ b/ChangeLog.nntp @@ -0,0 +1,399 @@ +* Thu Mar 13 2014 Vsevolod Volkov +- update to 1.5.23 + +* Tue Oct 29 2013 Vsevolod Volkov +- minor bug fixed while removing new articles + +* Fri Oct 18 2013 Vsevolod Volkov +- update to 1.5.22 + +* Tue Nov 27 2012 Vsevolod Volkov +- SASL authentication +- new option nntp_authenticators + +* Fri Nov 16 2012 Vsevolod Volkov +- support of NNTP commands: CAPABILITIES, STARTTLS, LIST NEWSGROUPS, + LIST OVERVIEW.FMT, OVER, DATE +- added bcache support +- newss URI scheme renamed to snews +- removed option nntp_reconnect + +* Sun Sep 16 2012 Vsevolod Volkov +- internal header caching replaced with hcache +- new option newsgroups_charset + +* Wed Sep 16 2010 Vsevolod Volkov +- update to 1.5.21 + +* Thu Aug 13 2009 Vsevolod Volkov +- fixed writting references in nntp_save_cache_group() + +* Tue Jun 15 2009 Vsevolod Volkov +- update to 1.5.20 + +* Tue Mar 20 2009 Vsevolod Volkov +- save Date: header of recorded outgoing articles + +* Tue Jan 6 2009 Vsevolod Volkov +- update to 1.5.19 + +* Mon May 19 2008 Vsevolod Volkov +- update to 1.5.18 +- fixed SIGSEGV when followup or forward to newsgroup + +* Sun Nov 4 2007 Vsevolod Volkov +- update to 1.5.17 + +* Tue Jul 3 2007 Vsevolod Volkov +- fixed arguments of nntp_format_str() + +* Fri Jun 15 2007 Vsevolod Volkov +- fixed error selecting news group + +* Tue Jun 12 2007 Vsevolod Volkov +- update to 1.5.16 + +* Wed Apr 11 2007 Vsevolod Volkov +- fixed posting error if $smtp_url is set +- added support of print-style sequence %R (x-comment-to) + +* Sun Apr 8 2007 Vsevolod Volkov +- update to 1.5.15 +- nntp://... url changed to news://... +- added indicator of fetching descriptions progress + +* Tue Feb 28 2007 Vsevolod Volkov +- update to 1.5.14 + +* Tue Aug 15 2006 Vsevolod Volkov +- update to 1.5.13 + +* Mon Jul 17 2006 Vsevolod Volkov +- update to 1.5.12 +- fixed reading empty .newsrc + +* Sat Sep 17 2005 Vsevolod Volkov +- update to 1.5.11 + +* Sat Aug 13 2005 Vsevolod Volkov +- update to 1.5.10 + +* Sun Mar 13 2005 Vsevolod Volkov +- update to 1.5.9 + +* Sun Feb 13 2005 Vsevolod Volkov +- update to 1.5.8 + +* Sat Feb 5 2005 Vsevolod Volkov +- update to 1.5.7 +- function mutt_update_list_file() moved to newsrc.c and changed algorithm + +* Thu Jul 8 2004 Vsevolod Volkov +- fixed error in nntp_logout_all() + +* Sat Apr 3 2004 Vsevolod Volkov +- fixed debug output in mutt_newsrc_update() +- added optional support of LISTGROUP command +- fixed typo in nntp_parse_xref() + +* Tue Feb 3 2004 Vsevolod Volkov +- update to 1.5.6 + +* Thu Dec 18 2003 Vsevolod Volkov +- fixed compose menu + +* Thu Nov 6 2003 Vsevolod Volkov +- update to 1.5.5.1 + +* Wed Nov 5 2003 Vsevolod Volkov +- update to 1.5.5 +- added space after newsgroup name in .newsrc file + +* Sun May 18 2003 Vsevolod Volkov +- nntp patch: fixed SIGSEGV when posting article + +* Sat Mar 22 2003 Vsevolod Volkov +- update to 1.5.4 + +* Sat Dec 21 2002 Vsevolod Volkov +- update to 1.5.3 +- replace safe_free calls by the FREE macro + +* Fri Dec 6 2002 Vsevolod Volkov +- update to 1.5.2 +- nntp authentication can be passed after any command + +* Sat May 4 2002 Vsevolod Volkov +- update to 1.5.1 + +* Thu May 2 2002 Vsevolod Volkov +- update to 1.3.99 + +* Wed Mar 13 2002 Vsevolod Volkov +- update to 1.3.28 +- fixed SIGSEGV in , , , + functions +- fixed message about nntp reconnect +- fixed function using browser +- added support of Followup-To: poster +- added %n (new articles) in group_index_format +- posting articles without inews by default + +* Wed Jan 23 2002 Vsevolod Volkov +- update to 1.3.27 + +* Fri Jan 18 2002 Vsevolod Volkov +- update to 1.3.26 + +* Thu Jan 3 2002 Vsevolod Volkov +- update to 1.3.25 +- accelerated speed of access to news->newsgroups hash (by ) +- added default content disposition + +* Mon Dec 3 2001 Vsevolod Volkov +- update to 1.3.24 + +* Fri Nov 9 2001 Vsevolod Volkov +- update to 1.3.23.2 +- fixed segfault if mutt_conn_find() returns null + +* Wed Oct 31 2001 Vsevolod Volkov +- update to 1.3.23.1 +- added support of LISTGROUP command +- added support for servers with broken overview +- disabled function on news server +- fixed error storing bad authentication information + +* Wed Oct 10 2001 Vsevolod Volkov +- update to 1.3.23 +- fixed typo in buffy.c +- added substitution of %s parameter in $inews variable + +* Fri Aug 31 2001 Vsevolod Volkov +- update to 1.3.22.1 +- update to 1.3.22 + +* Thu Aug 23 2001 Vsevolod Volkov +- update to 1.3.21 + +* Wed Jul 25 2001 Vsevolod Volkov +- update to 1.3.20 +- removed 'server-hook', use 'account-hook' instead +- fixed error opening NNTP server without newsgroup using -f option + +* Fri Jun 8 2001 Vsevolod Volkov +- update to 1.3.19 + +* Sat May 5 2001 Vsevolod Volkov +- update to 1.3.18 +- fixed typo in nntp_attempt_features() +- changed algorithm of XGTITLE command testing +- disabled writing of NNTP password in debug file +- fixed reading and writing of long newsrc lines +- changed checking of last line while reading lines from server +- fixed possible buffer overrun in nntp_parse_newsrc_line() +- removed checking of XHDR command +- compare NNTP return codes without trailing space + +* Thu Mar 29 2001 Vsevolod Volkov +- update to 1.3.17 +- support for 'LIST NEWSGROUPS' command to read descriptions + +* Fri Mar 2 2001 Vsevolod Volkov +- update to 1.3.16 + +* Wed Feb 14 2001 Vsevolod Volkov +- update to 1.3.15 + +* Sun Jan 28 2001 Vsevolod Volkov +- update to 1.3.14 +- show number of tagged messages patch from Felix von Leitner + +* Sun Dec 31 2000 Vsevolod Volkov +- update to 1.3.13 + +* Sat Dec 30 2000 Vsevolod Volkov +- Fixed problem if last article in group is deleted + +* Fri Dec 22 2000 Vsevolod Volkov +- Fixed checking of XGTITLE command on some servers + +* Mon Dec 18 2000 Vsevolod Volkov +- Added \r in AUTHINFO commands + +* Mon Nov 27 2000 Vsevolod Volkov +- update to 1.3.12 + +* Wed Nov 1 2000 Vsevolod Volkov +- update to 1.3.11 +- fixed error opening newsgroup from mutt started with -g or -G + +* Thu Oct 12 2000 Vsevolod Volkov +- update to 1.3.10 +- hotkey 'G' (get-message) replaced with '^G' + +* Thu Sep 21 2000 Vsevolod Volkov +- update to 1.3.9 +- changed delay displaying error messages from 1 to 2 seconds +- fixed error compiling with nntp and without imap + +* Wed Sep 6 2000 Vsevolod Volkov +- fixed catchup in index +- fixed nntp_open_mailbox() + +* Sat Sep 2 2000 Vsevolod Volkov +- functions and disabled +- format of news mailbox names changed to url form +- option nntp_attempts removed +- option reconnect_news renamed to nntp_reconnect +- default value of nntp_poll changed from 30 to 60 +- error handling improved + +* Wed Aug 30 2000 Vsevolod Volkov +- update to 1.3.8 +- new option show_only_unread +- add newsgroup completion + +* Fri Aug 4 2000 Vsevolod Volkov +- update to 1.3.7 + +* Sat Jul 29 2000 Vsevolod Volkov +- update to 1.3.6 + +* Sun Jul 9 2000 Vsevolod Volkov +- update to 1.3.5 +- authentication code update +- fix for changing to newsgroup from mailbox with read messages +- socket code optimization + +* Wed Jun 21 2000 Vsevolod Volkov +- update to 1.3.4 + +* Wed Jun 14 2000 Vsevolod Volkov +- don't substitute current newsgroup with deleted new messages + +* Mon Jun 12 2000 Vsevolod Volkov +- update to 1.3.3 +- fix for substitution of newsgroup after reconnection +- fix for loading newsgroups with very long names +- fix for loading more than 32768 newsgroups + +* Wed May 24 2000 Vsevolod Volkov +- update to 1.3.2 + +* Sat May 20 2000 Vsevolod Volkov +- update to 1.3.1 + +* Fri May 12 2000 Vsevolod Volkov +- update to 1.3 + +* Thu May 11 2000 Vsevolod Volkov +- update to 1.2 + +* Thu May 4 2000 Vsevolod Volkov +- update to 1.1.14 + +* Sun Apr 23 2000 Vsevolod Volkov +- update to 1.1.12 + +* Fri Apr 7 2000 Vsevolod Volkov +- add substitution of newsgroup with new messages by default + +* Wed Apr 5 2000 Vsevolod Volkov +- add attach message from newsgroup +- add one-line help in newsreader mode +- disable 'change-dir' command in newsgroups browser +- add -G option + +* Tue Apr 4 2000 Vsevolod Volkov +- get default news server name from file /etc/nntpserver +- use case insensitive server names +- add print-style sequence %s to $newsrc +- add -g option + +* Sat Apr 1 2000 Vsevolod Volkov +- remove 'X-FTN-Origin' header processing + +* Thu Mar 30 2000 Vsevolod Volkov +- update to 1.1.11 +- update to 1.1.10 + +* Thu Mar 23 2000 Vsevolod Volkov +- fix mutt_select_newsserver() +- remove 'toggle-mode' function +- add 'change-newsgroup' function + +* Wed Mar 22 2000 Vsevolod Volkov +- fix server-hook + +* Tue Mar 21 2000 Vsevolod Volkov +- fix error 'bounce' function after 'post' +- add 'forward to newsgroup' function + +* Mon Mar 20 2000 Vsevolod Volkov +- 'forward' function works in newsreader mode +- add 'post' and 'followup' functions to pager and attachment menu +- fix active descriptions and allowed flag reload + +* Tue Mar 14 2000 Vsevolod Volkov +- update to 1.1.9 +- remove deleted newsgroups from list + +* Mon Mar 13 2000 Vsevolod Volkov +- update .newsrc in browser + +* Sun Mar 12 2000 Vsevolod Volkov +- reload .newsrc if externally modified +- fix active cache update + +* Sun Mar 5 2000 Vsevolod Volkov +- update to 1.1.8 + +* Sat Mar 4 2000 Vsevolod Volkov +- patch *.update_list_file is not required +- count lines when loading descriptions +- remove cache of unsubscribed newsgroups + +* Thu Mar 2 2000 Vsevolod Volkov +- load list of newsgroups from cache faster + +* Wed Mar 1 2000 Vsevolod Volkov +- update to 1.1.7 + +* Tue Feb 29 2000 Vsevolod Volkov +- fix unread messages in browser +- fix newsrc_gen_entries() + +* Mon Feb 28 2000 Vsevolod Volkov +- fix mutt_newsgroup_stat() +- fix nntp_delete_cache() +- fix nntp_get_status() +- fix check_children() +- fix nntp_fetch_headers() + +* Fri Feb 25 2000 Vsevolod Volkov +- update to 1.1.5 + +* Thu Feb 24 2000 Vsevolod Volkov +- fix updating new messages in cache + +* Mon Feb 21 2000 Vsevolod Volkov +- change default cache filenames +- fix updating new messages in cache + +* Fri Feb 18 2000 Vsevolod Volkov +- fix segmentation fault in news groups browser + +* Tue Feb 15 2000 Vsevolod Volkov +- update to 1.1.4 + +* Thu Feb 10 2000 Vsevolod Volkov +- update to 1.1.3 + +* Sun Jan 30 2000 Vsevolod Volkov +- add X-Comment-To editing +- add my_hdr support for Newsgroups:, Followup-To: and X-Comment-To: headers +- add variables $ask_followup_to and $ask_x_comment_to + +* Fri Jan 28 2000 Vsevolod Volkov +- update to 1.1.2 diff --git a/Makefile.am b/Makefile.am index cf1ac98..20b4fad 100644 --- a/Makefile.am +++ b/Makefile.am @@ -57,6 +57,7 @@ EXTRA_mutt_SOURCES = account.c bcache.c crypt-gpgme.c crypt-mod-pgp-classic.c \ mutt_idna.c mutt_sasl.c mutt_socket.c mutt_ssl.c mutt_ssl_gnutls.c \ mutt_tunnel.c pgp.c pgpinvoke.c pgpkey.c pgplib.c pgpmicalg.c \ pgppacket.c pop.c pop_auth.c pop_lib.c remailer.c resize.c sha1.c \ + nntp.c newsrc.c \ smime.c smtp.c utf8.c wcwidth.c \ bcache.h browser.h hcache.h mbyte.h mutt_idna.h remailer.h url.h @@ -68,6 +69,7 @@ EXTRA_DIST = COPYRIGHT GPL OPS OPS.PGP OPS.CRYPT OPS.SMIME TODO UPDATING \ mutt_regex.h mutt_sasl.h mutt_socket.h mutt_ssl.h mutt_tunnel.h \ mx.h pager.h pgp.h pop.h protos.h rfc1524.h rfc2047.h \ rfc2231.h rfc822.h rfc3676.h sha1.h sort.h mime.types VERSION prepare \ + nntp.h ChangeLog.nntp \ _regex.h OPS.MIX README.SECURITY remailer.c remailer.h browser.h \ mbyte.h lib.h extlib.c pgpewrap.c smime_keys.pl pgplib.h \ README.SSL smime.h group.h \ diff --git a/OPS b/OPS index 3ffb82a..62db4b4 100644 --- a/OPS +++ b/OPS @@ -8,14 +8,16 @@ OP_BOUNCE_MESSAGE "remail a message to another user" OP_BROWSER_NEW_FILE "select a new file in this directory" OP_BROWSER_VIEW_FILE "view file" OP_BROWSER_TELL "display the currently selected file's name" -OP_BROWSER_SUBSCRIBE "subscribe to current mailbox (IMAP only)" -OP_BROWSER_UNSUBSCRIBE "unsubscribe from current mailbox (IMAP only)" +OP_BROWSER_SUBSCRIBE "subscribe to current mbox (IMAP/NNTP only)" +OP_BROWSER_UNSUBSCRIBE "unsubscribe from current mbox (IMAP/NNTP only)" OP_BROWSER_TOGGLE_LSUB "toggle view all/subscribed mailboxes (IMAP only)" OP_BUFFY_LIST "list mailboxes with new mail" +OP_CATCHUP "mark all articles in newsgroup as read" OP_CHANGE_DIRECTORY "change directories" OP_CHECK_NEW "check mailboxes for new mail" OP_COMPOSE_ATTACH_FILE "attach file(s) to this message" OP_COMPOSE_ATTACH_MESSAGE "attach message(s) to this message" +OP_COMPOSE_ATTACH_NEWS_MESSAGE "attach news article(s) to this message" OP_COMPOSE_EDIT_BCC "edit the BCC list" OP_COMPOSE_EDIT_CC "edit the CC list" OP_COMPOSE_EDIT_DESCRIPTION "edit attachment description" @@ -26,7 +28,10 @@ OP_COMPOSE_EDIT_FROM "edit the from field" OP_COMPOSE_EDIT_HEADERS "edit the message with headers" OP_COMPOSE_EDIT_MESSAGE "edit the message" OP_COMPOSE_EDIT_MIME "edit attachment using mailcap entry" +OP_COMPOSE_EDIT_NEWSGROUPS "edit the newsgroups list" OP_COMPOSE_EDIT_REPLY_TO "edit the Reply-To field" +OP_COMPOSE_EDIT_FOLLOWUP_TO "edit the Followup-To field" +OP_COMPOSE_EDIT_X_COMMENT_TO "edit the X-Comment-To field" OP_COMPOSE_EDIT_SUBJECT "edit the subject of this message" OP_COMPOSE_EDIT_TO "edit the TO list" OP_CREATE_MAILBOX "create a new mailbox (IMAP only)" @@ -85,8 +90,13 @@ OP_EXIT "exit this menu" OP_FILTER "filter attachment through a shell command" OP_FIRST_ENTRY "move to the first entry" OP_FLAG_MESSAGE "toggle a message's 'important' flag" +OP_FOLLOWUP "followup to newsgroup" +OP_FORWARD_TO_GROUP "forward to newsgroup" OP_FORWARD_MESSAGE "forward a message with comments" OP_GENERIC_SELECT_ENTRY "select the current entry" +OP_GET_CHILDREN "get all children of the current message" +OP_GET_MESSAGE "get message with Message-Id" +OP_GET_PARENT "get parent of the current message" OP_GROUP_REPLY "reply to all recipients" OP_HALF_DOWN "scroll down 1/2 page" OP_HALF_UP "scroll up 1/2 page" @@ -94,11 +104,14 @@ OP_HELP "this screen" OP_JUMP "jump to an index number" OP_LAST_ENTRY "move to the last entry" OP_LIST_REPLY "reply to specified mailing list" +OP_LOAD_ACTIVE "load list of all newsgroups from NNTP server" OP_MACRO "execute a macro" OP_MAIL "compose a new mail message" OP_MAIN_BREAK_THREAD "break the thread in two" OP_MAIN_CHANGE_FOLDER "open a different folder" OP_MAIN_CHANGE_FOLDER_READONLY "open a different folder in read only mode" +OP_MAIN_CHANGE_GROUP "open a different newsgroup" +OP_MAIN_CHANGE_GROUP_READONLY "open a different newsgroup in read only mode" OP_MAIN_CLEAR_FLAG "clear a status flag from a message" OP_MAIN_DELETE_PATTERN "delete messages matching a pattern" OP_MAIN_IMAP_FETCH "force retrieval of mail from IMAP server" @@ -138,6 +151,7 @@ OP_PAGER_HIDE_QUOTED "toggle display of quoted text" OP_PAGER_SKIP_QUOTED "skip beyond quoted text" OP_PAGER_TOP "jump to the top of the message" OP_PIPE "pipe message/attachment to a shell command" +OP_POST "post message to newsgroup" OP_PREV_ENTRY "move to the previous entry" OP_PREV_LINE "scroll up one line" OP_PREV_PAGE "move to the previous page" @@ -147,6 +161,7 @@ OP_QUERY "query external program for addresses" OP_QUERY_APPEND "append new query results to current results" OP_QUIT "save changes to mailbox and quit" OP_RECALL_MESSAGE "recall a postponed message" +OP_RECONSTRUCT_THREAD "reconstruct thread containing current message" OP_REDRAW "clear and redraw the screen" OP_REFORMAT_WINCH "{internal}" OP_RENAME_MAILBOX "rename the current mailbox (IMAP only)" @@ -161,18 +176,22 @@ OP_SEARCH_TOGGLE "toggle search pattern coloring" OP_SHELL_ESCAPE "invoke a command in a subshell" OP_SORT "sort messages" OP_SORT_REVERSE "sort messages in reverse order" +OP_SUBSCRIBE_PATTERN "subscribe to newsgroups matching a pattern" OP_TAG "tag the current entry" OP_TAG_PREFIX "apply next function to tagged messages" OP_TAG_PREFIX_COND "apply next function ONLY to tagged messages" OP_TAG_SUBTHREAD "tag the current subthread" OP_TAG_THREAD "tag the current thread" OP_TOGGLE_NEW "toggle a message's 'new' flag" +OP_TOGGLE_READ "toggle view of read messages" OP_TOGGLE_WRITE "toggle whether the mailbox will be rewritten" OP_TOGGLE_MAILBOXES "toggle whether to browse mailboxes or all files" OP_TOP_PAGE "move to the top of the page" +OP_UNCATCHUP "mark all articles in newsgroup as unread" OP_UNDELETE "undelete the current entry" OP_UNDELETE_THREAD "undelete all messages in thread" OP_UNDELETE_SUBTHREAD "undelete all messages in subthread" +OP_UNSUBSCRIBE_PATTERN "unsubscribe from newsgroups matching a pattern" OP_VERSION "show the Mutt version number and date" OP_VIEW_ATTACH "view attachment using mailcap entry if necessary" OP_VIEW_ATTACHMENTS "show MIME attachments" diff --git a/account.c b/account.c index bf59995..624f931 100644 --- a/account.c +++ b/account.c @@ -51,8 +51,17 @@ int mutt_account_match (const ACCOUNT* a1, const ACCOUNT* a2) user = PopUser; #endif +#ifdef USE_NNTP + if (a1->type == M_ACCT_TYPE_NNTP && NntpUser) + user = NntpUser; +#endif + if (a1->flags & a2->flags & M_ACCT_USER) return (!strcmp (a1->user, a2->user)); +#ifdef USE_NNTP + if (a1->type == M_ACCT_TYPE_NNTP) + return a1->flags & M_ACCT_USER && a1->user[0] ? 0 : 1; +#endif if (a1->flags & M_ACCT_USER) return (!strcmp (a1->user, user)); if (a2->flags & M_ACCT_USER) @@ -130,6 +139,16 @@ void mutt_account_tourl (ACCOUNT* account, ciss_url_t* url) } #endif +#ifdef USE_NNTP + if (account->type == M_ACCT_TYPE_NNTP) + { + if (account->flags & M_ACCT_SSL) + url->scheme = U_NNTPS; + else + url->scheme = U_NNTP; + } +#endif + url->host = account->host; if (account->flags & M_ACCT_PORT) url->port = account->port; @@ -155,6 +174,10 @@ int mutt_account_getuser (ACCOUNT* account) else if ((account->type == M_ACCT_TYPE_POP) && PopUser) strfcpy (account->user, PopUser, sizeof (account->user)); #endif +#ifdef USE_NNTP + else if ((account->type == M_ACCT_TYPE_NNTP) && NntpUser) + strfcpy (account->user, NntpUser, sizeof (account->user)); +#endif else if (option (OPTNOCURSES)) return -1; /* prompt (defaults to unix username), copy into account->user */ @@ -217,6 +240,10 @@ int mutt_account_getpass (ACCOUNT* account) else if ((account->type == M_ACCT_TYPE_SMTP) && SmtpPass) strfcpy (account->pass, SmtpPass, sizeof (account->pass)); #endif +#ifdef USE_NNTP + else if ((account->type == M_ACCT_TYPE_NNTP) && NntpPass) + strfcpy (account->pass, NntpPass, sizeof (account->pass)); +#endif else if (option (OPTNOCURSES)) return -1; else diff --git a/account.h b/account.h index 662fb32..9febee6 100644 --- a/account.h +++ b/account.h @@ -29,7 +29,8 @@ enum M_ACCT_TYPE_NONE = 0, M_ACCT_TYPE_IMAP, M_ACCT_TYPE_POP, - M_ACCT_TYPE_SMTP + M_ACCT_TYPE_SMTP, + M_ACCT_TYPE_NNTP }; /* account flags */ diff --git a/attach.h b/attach.h index 928408a..071f22c 100644 --- a/attach.h +++ b/attach.h @@ -50,7 +50,7 @@ void mutt_print_attachment_list (FILE *fp, int tag, BODY *top); void mutt_attach_bounce (FILE *, HEADER *, ATTACHPTR **, short, BODY *); void mutt_attach_resend (FILE *, HEADER *, ATTACHPTR **, short, BODY *); -void mutt_attach_forward (FILE *, HEADER *, ATTACHPTR **, short, BODY *); +void mutt_attach_forward (FILE *, HEADER *, ATTACHPTR **, short, BODY *, int); void mutt_attach_reply (FILE *, HEADER *, ATTACHPTR **, short, BODY *, int); #endif /* _ATTACH_H_ */ diff --git a/browser.c b/browser.c index dbb31c8..3c84044 100644 --- a/browser.c +++ b/browser.c @@ -32,6 +32,9 @@ #ifdef USE_IMAP #include "imap.h" #endif +#ifdef USE_NNTP +#include "nntp.h" +#endif #include #include @@ -50,6 +53,19 @@ static const struct mapping_t FolderHelp[] = { { NULL, 0 } }; +#ifdef USE_NNTP +static struct mapping_t FolderNewsHelp[] = { + { N_("Exit"), OP_EXIT }, + { N_("List"), OP_TOGGLE_MAILBOXES }, + { N_("Subscribe"), OP_BROWSER_SUBSCRIBE }, + { N_("Unsubscribe"), OP_BROWSER_UNSUBSCRIBE }, + { N_("Catchup"), OP_CATCHUP }, + { N_("Mask"), OP_ENTER_MASK }, + { N_("Help"), OP_HELP }, + { NULL, 0 } +}; +#endif + typedef struct folder_t { struct folder_file *ff; @@ -116,9 +132,17 @@ static void browser_sort (struct browser_state *state) case SORT_ORDER: return; case SORT_DATE: +#ifdef USE_NNTP + if (option (OPTNEWS)) + return; +#endif f = browser_compare_date; break; case SORT_SIZE: +#ifdef USE_NNTP + if (option (OPTNEWS)) + return; +#endif f = browser_compare_size; break; case SORT_SUBJECT: @@ -325,8 +349,111 @@ folder_format_str (char *dest, size_t destlen, size_t col, char op, const char * return (src); } +#ifdef USE_NNTP +static const char * +newsgroup_format_str (char *dest, size_t destlen, size_t col, char op, const char *src, + const char *fmt, const char *ifstring, const char *elsestring, + unsigned long data, format_flag flags) +{ + char fn[SHORT_STRING], tmp[SHORT_STRING]; + FOLDER *folder = (FOLDER *) data; + + switch (op) + { + case 'C': + snprintf (tmp, sizeof (tmp), "%%%sd", fmt); + snprintf (dest, destlen, tmp, folder->num + 1); + break; + + case 'f': + strncpy (fn, folder->ff->name, sizeof(fn) - 1); + snprintf (tmp, sizeof (tmp), "%%%ss", fmt); + snprintf (dest, destlen, tmp, fn); + break; + + case 'N': + snprintf (tmp, sizeof (tmp), "%%%sc", fmt); + if (folder->ff->nd->subscribed) + snprintf (dest, destlen, tmp, ' '); + else + snprintf (dest, destlen, tmp, folder->ff->new ? 'N' : 'u'); + break; + + case 'M': + snprintf (tmp, sizeof (tmp), "%%%sc", fmt); + if (folder->ff->nd->deleted) + snprintf (dest, destlen, tmp, 'D'); + else + snprintf (dest, destlen, tmp, folder->ff->nd->allowed ? ' ' : '-'); + break; + + case 's': + if (flags & M_FORMAT_OPTIONAL) + { + if (folder->ff->nd->unread != 0) + mutt_FormatString (dest, destlen, col, ifstring, newsgroup_format_str, + data, flags); + else + mutt_FormatString (dest, destlen, col, elsestring, newsgroup_format_str, + data, flags); + } + else if (Context && Context->data == folder->ff->nd) + { + snprintf (tmp, sizeof (tmp), "%%%sd", fmt); + snprintf (dest, destlen, tmp, Context->unread); + } + else + { + snprintf (tmp, sizeof (tmp), "%%%sd", fmt); + snprintf (dest, destlen, tmp, folder->ff->nd->unread); + } + break; + + case 'n': + if (Context && Context->data == folder->ff->nd) + { + snprintf (tmp, sizeof (tmp), "%%%sd", fmt); + snprintf (dest, destlen, tmp, Context->new); + } + else if (option (OPTMARKOLD) && + folder->ff->nd->lastCached >= folder->ff->nd->firstMessage && + folder->ff->nd->lastCached <= folder->ff->nd->lastMessage) + { + snprintf (tmp, sizeof (tmp), "%%%sd", fmt); + snprintf (dest, destlen, tmp, folder->ff->nd->lastMessage - folder->ff->nd->lastCached); + } + else + { + snprintf (tmp, sizeof (tmp), "%%%sd", fmt); + snprintf (dest, destlen, tmp, folder->ff->nd->unread); + } + break; + + case 'd': + if (folder->ff->nd->desc != NULL) + { + char *buf = safe_strdup (folder->ff->nd->desc); + if (NewsgroupsCharset && *NewsgroupsCharset) + mutt_convert_string (&buf, NewsgroupsCharset, Charset, M_ICONV_HOOK_FROM); + mutt_filter_unprintable (&buf); + + snprintf (tmp, sizeof (tmp), "%%%ss", fmt); + snprintf (dest, destlen, tmp, buf); + } + else + { + snprintf (tmp, sizeof (tmp), "%%%ss", fmt); + snprintf (dest, destlen, tmp, ""); + } + break; + } + return (src); +} +#endif /* USE_NNTP */ + static void add_folder (MUTTMENU *m, struct browser_state *state, - const char *name, const struct stat *s, unsigned int new) + const char *name, const struct stat *s, + void *data, unsigned int new) { if (state->entrylen == state->entrymax) { @@ -355,6 +482,10 @@ static void add_folder (MUTTMENU *m, struct browser_state *state, #ifdef USE_IMAP (state->entry)[state->entrylen].imap = 0; #endif +#ifdef USE_NNTP + if (option (OPTNEWS)) + (state->entry)[state->entrylen].nd = (NNTP_DATA *)data; +#endif (state->entrylen)++; } @@ -370,9 +501,36 @@ static void init_state (struct browser_state *state, MUTTMENU *menu) menu->data = state->entry; } +/* get list of all files/newsgroups with mask */ static int examine_directory (MUTTMENU *menu, struct browser_state *state, char *d, const char *prefix) { +#ifdef USE_NNTP + if (option (OPTNEWS)) + { + NNTP_SERVER *nserv = CurrentNewsSrv; + unsigned int i; + +/* mutt_buffy_check (0); */ + init_state (state, menu); + + for (i = 0; i < nserv->groups_num; i++) + { + NNTP_DATA *nntp_data = nserv->groups_list[i]; + if (!nntp_data) + continue; + if (prefix && *prefix && + strncmp (prefix, nntp_data->group, strlen (prefix))) + continue; + if (!((regexec (Mask.rx, nntp_data->group, 0, NULL, 0) == 0) ^ Mask.not)) + continue; + add_folder (menu, state, nntp_data->group, NULL, + nntp_data, nntp_data->new); + } + } + else +#endif /* USE_NNTP */ + { struct stat s; DIR *dp; struct dirent *de; @@ -433,17 +591,41 @@ static int examine_directory (MUTTMENU *menu, struct browser_state *state, tmp = Incoming; while (tmp && mutt_strcmp (buffer, tmp->path)) tmp = tmp->next; - add_folder (menu, state, de->d_name, &s, (tmp) ? tmp->new : 0); + add_folder (menu, state, de->d_name, &s, NULL, (tmp) ? tmp->new : 0); } closedir (dp); + } browser_sort (state); return 0; } +/* get list of mailboxes/subscribed newsgroups */ static int examine_mailboxes (MUTTMENU *menu, struct browser_state *state) { struct stat s; char buffer[LONG_STRING]; + +#ifdef USE_NNTP + if (option (OPTNEWS)) + { + NNTP_SERVER *nserv = CurrentNewsSrv; + unsigned int i; + +/* mutt_buffy_check (0); */ + init_state (state, menu); + + for (i = 0; i < nserv->groups_num; i++) + { + NNTP_DATA *nntp_data = nserv->groups_list[i]; + if (nntp_data && (nntp_data->new || (nntp_data->subscribed && + (nntp_data->unread || !option (OPTSHOWONLYUNREAD))))) + add_folder (menu, state, nntp_data->group, NULL, + nntp_data, nntp_data->new); + } + } + else +#endif + { BUFFY *tmp = Incoming; #ifdef USE_IMAP struct mailbox_state mbox; @@ -461,14 +643,21 @@ static int examine_mailboxes (MUTTMENU *menu, struct browser_state *state) if (mx_is_imap (tmp->path)) { imap_mailbox_state (tmp->path, &mbox); - add_folder (menu, state, tmp->path, NULL, mbox.new); + add_folder (menu, state, tmp->path, NULL, NULL, mbox.new); continue; } #endif #ifdef USE_POP if (mx_is_pop (tmp->path)) { - add_folder (menu, state, tmp->path, NULL, tmp->new); + add_folder (menu, state, tmp->path, NULL, NULL, tmp->new); + continue; + } +#endif +#ifdef USE_NNTP + if (mx_is_nntp (tmp->path)) + { + add_folder (menu, state, tmp->path, NULL, NULL, tmp->new); continue; } #endif @@ -497,15 +686,20 @@ static int examine_mailboxes (MUTTMENU *menu, struct browser_state *state) strfcpy (buffer, NONULL(tmp->path), sizeof (buffer)); mutt_pretty_mailbox (buffer, sizeof (buffer)); - add_folder (menu, state, buffer, &s, tmp->new); + add_folder (menu, state, buffer, &s, NULL, tmp->new); } while ((tmp = tmp->next)); + } browser_sort (state); return 0; } static int select_file_search (MUTTMENU *menu, regex_t *re, int n) { +#ifdef USE_NNTP + if (option (OPTNEWS)) + return (regexec (re, ((struct folder_file *) menu->data)[n].desc, 0, NULL, 0)); +#endif return (regexec (re, ((struct folder_file *) menu->data)[n].name, 0, NULL, 0)); } @@ -516,6 +710,12 @@ static void folder_entry (char *s, size_t slen, MUTTMENU *menu, int num) folder.ff = &((struct folder_file *) menu->data)[num]; folder.num = num; +#ifdef USE_NNTP + if (option (OPTNEWS)) + mutt_FormatString (s, slen, 0, NONULL(GroupFormat), newsgroup_format_str, + (unsigned long) &folder, M_FORMAT_ARROWCURSOR); + else +#endif mutt_FormatString (s, slen, 0, NONULL(FolderFormat), folder_format_str, (unsigned long) &folder, M_FORMAT_ARROWCURSOR); } @@ -536,6 +736,17 @@ static void init_menu (struct browser_state *state, MUTTMENU *menu, char *title, menu->tagged = 0; +#ifdef USE_NNTP + if (option (OPTNEWS)) + { + if (buffy) + snprintf (title, titlelen, _("Subscribed newsgroups")); + else + snprintf (title, titlelen, _("Newsgroups on server [%s]"), + CurrentNewsSrv->conn->account.host); + } + else +#endif if (buffy) { menu->is_mailbox_list = 1; @@ -609,6 +820,31 @@ void _mutt_select_file (char *f, size_t flen, int flags, char ***files, int *num if (!folder) strfcpy (LastDirBackup, LastDir, sizeof (LastDirBackup)); +#ifdef USE_NNTP + if (option (OPTNEWS)) + { + if (*f) + strfcpy (prefix, f, sizeof (prefix)); + else + { + NNTP_SERVER *nserv = CurrentNewsSrv; + unsigned int i; + + /* default state for news reader mode is browse subscribed newsgroups */ + buffy = 0; + for (i = 0; i < nserv->groups_num; i++) + { + NNTP_DATA *nntp_data = nserv->groups_list[i]; + if (nntp_data && nntp_data->subscribed) + { + buffy = 1; + break; + } + } + } + } + else +#endif if (*f) { mutt_expand_path (f, flen); @@ -705,6 +941,9 @@ void _mutt_select_file (char *f, size_t flen, int flags, char ***files, int *num menu->tag = file_tag; menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_FOLDER, +#ifdef USE_NNTP + option (OPTNEWS) ? FolderNewsHelp : +#endif FolderHelp); init_menu (&state, menu, title, sizeof (title), buffy); @@ -842,7 +1081,11 @@ void _mutt_select_file (char *f, size_t flen, int flags, char ***files, int *num } } +#ifdef USE_NNTP + if (buffy || option (OPTNEWS)) +#else if (buffy) +#endif { strfcpy (f, state.entry[menu->current].name, flen); mutt_expand_path (f, flen); @@ -900,14 +1143,6 @@ void _mutt_select_file (char *f, size_t flen, int flags, char ***files, int *num break; #ifdef USE_IMAP - case OP_BROWSER_SUBSCRIBE: - imap_subscribe (state.entry[menu->current].name, 1); - break; - - case OP_BROWSER_UNSUBSCRIBE: - imap_subscribe (state.entry[menu->current].name, 0); - break; - case OP_BROWSER_TOGGLE_LSUB: if (option (OPTIMAPLSUB)) unset_option (OPTIMAPLSUB); @@ -1008,6 +1243,11 @@ void _mutt_select_file (char *f, size_t flen, int flags, char ***files, int *num case OP_CHANGE_DIRECTORY: +#ifdef USE_NNTP + if (option (OPTNEWS)) + break; +#endif + strfcpy (buf, LastDir, sizeof (buf)); #ifdef USE_IMAP if (!state.imap_browse) @@ -1273,6 +1513,210 @@ void _mutt_select_file (char *f, size_t flen, int flags, char ***files, int *num else mutt_error _("Error trying to view file"); } + break; + +#ifdef USE_NNTP + case OP_CATCHUP: + case OP_UNCATCHUP: + if (option (OPTNEWS)) + { + struct folder_file *f = &state.entry[menu->current]; + int rc; + NNTP_DATA *nntp_data; + + rc = nntp_newsrc_parse (CurrentNewsSrv); + if (rc < 0) + break; + + if (i == OP_CATCHUP) + nntp_data = mutt_newsgroup_catchup (CurrentNewsSrv, f->name); + else + nntp_data = mutt_newsgroup_uncatchup (CurrentNewsSrv, f->name); + + if (nntp_data) + { +/* FOLDER folder; + struct folder_file ff; + char buffer[_POSIX_PATH_MAX + SHORT_STRING]; + + folder.ff = &ff; + folder.ff->name = f->name; + folder.ff->st = NULL; + folder.ff->is_new = nntp_data->new; + folder.ff->nntp_data = nntp_data; + FREE (&f->desc); + mutt_FormatString (buffer, sizeof (buffer), 0, NONULL(GroupFormat), + newsgroup_format_str, (unsigned long) &folder, + M_FORMAT_ARROWCURSOR); + f->desc = safe_strdup (buffer); */ + nntp_newsrc_update (CurrentNewsSrv); + if (menu->current + 1 < menu->max) + menu->current++; + menu->redraw = REDRAW_MOTION_RESYNCH; + } + if (rc) + menu->redraw = REDRAW_INDEX; + nntp_newsrc_close (CurrentNewsSrv); + } + break; + + case OP_LOAD_ACTIVE: + if (option (OPTNEWS)) + { + NNTP_SERVER *nserv = CurrentNewsSrv; + unsigned int i; + + if (nntp_newsrc_parse (nserv) < 0) + break; + + for (i = 0; i < nserv->groups_num; i++) + { + NNTP_DATA *nntp_data = nserv->groups_list[i]; + if (nntp_data) + nntp_data->deleted = 1; + } + nntp_active_fetch (nserv); + nntp_newsrc_update (nserv); + nntp_newsrc_close (nserv); + + destroy_state (&state); + if (buffy) + examine_mailboxes (menu, &state); + else + examine_directory (menu, &state, NULL, NULL); + init_menu (&state, menu, title, sizeof (title), buffy); + } + break; +#endif /* USE_NNTP */ + +#if defined USE_IMAP || defined USE_NNTP + case OP_BROWSER_SUBSCRIBE: + case OP_BROWSER_UNSUBSCRIBE: +#endif +#ifdef USE_NNTP + case OP_SUBSCRIBE_PATTERN: + case OP_UNSUBSCRIBE_PATTERN: + if (option (OPTNEWS)) + { + NNTP_SERVER *nserv = CurrentNewsSrv; + NNTP_DATA *nntp_data; + regex_t *rx = (regex_t *) safe_malloc (sizeof (regex_t)); + char *s = buf; + int rc, j = menu->current; + + if (i == OP_SUBSCRIBE_PATTERN || i == OP_UNSUBSCRIBE_PATTERN) + { + char tmp[STRING]; + int err; + + buf[0] = 0; + if (i == OP_SUBSCRIBE_PATTERN) + snprintf (tmp, sizeof (tmp), _("Subscribe pattern: ")); + else + snprintf (tmp, sizeof (tmp), _("Unsubscribe pattern: ")); + if (mutt_get_field (tmp, buf, sizeof (buf), 0) != 0 || !buf[0]) + { + FREE (&rx); + break; + } + + err = REGCOMP (rx, s, REG_NOSUB); + if (err) + { + regerror (err, rx, buf, sizeof (buf)); + regfree (rx); + FREE (&rx); + mutt_error ("%s", buf); + break; + } + menu->redraw = REDRAW_FULL; + j = 0; + } + else if (!state.entrylen) + { + mutt_error _("No newsgroups match the mask"); + break; + } + + rc = nntp_newsrc_parse (nserv); + if (rc < 0) + break; + + for ( ; j < state.entrylen; j++) + { + struct folder_file *f = &state.entry[j]; + + if (i == OP_BROWSER_SUBSCRIBE || i == OP_BROWSER_UNSUBSCRIBE || + regexec (rx, f->name, 0, NULL, 0) == 0) + { + if (i == OP_BROWSER_SUBSCRIBE || i == OP_SUBSCRIBE_PATTERN) + nntp_data = mutt_newsgroup_subscribe (nserv, f->name); + else + nntp_data = mutt_newsgroup_unsubscribe (nserv, f->name); +/* if (nntp_data) + { + FOLDER folder; + char buffer[_POSIX_PATH_MAX + SHORT_STRING]; + + folder.name = f->name; + folder.f = NULL; + folder.new = nntp_data->new; + folder.nd = nntp_data; + FREE (&f->desc); + mutt_FormatString (buffer, sizeof (buffer), 0, NONULL(GroupFormat), + newsgroup_format_str, (unsigned long) &folder, + M_FORMAT_ARROWCURSOR); + f->desc = safe_strdup (buffer); + } */ + } + if (i == OP_BROWSER_SUBSCRIBE || i == OP_BROWSER_UNSUBSCRIBE) + { + if (menu->current + 1 < menu->max) + menu->current++; + menu->redraw = REDRAW_MOTION_RESYNCH; + break; + } + } + if (i == OP_SUBSCRIBE_PATTERN) + { + unsigned int i; + + for (i = 0; nserv && i < nserv->groups_num; i++) + { + nntp_data = nserv->groups_list[i]; + if (nntp_data && nntp_data->group && !nntp_data->subscribed) + { + if (regexec (rx, nntp_data->group, 0, NULL, 0) == 0) + { + mutt_newsgroup_subscribe (nserv, nntp_data->group); + add_folder (menu, &state, nntp_data->group, NULL, + nntp_data, nntp_data->new); + } + } + } + init_menu (&state, menu, title, sizeof (title), buffy); + } + if (rc > 0) + menu->redraw = REDRAW_FULL; + nntp_newsrc_update (nserv); + nntp_clear_cache (nserv); + nntp_newsrc_close (nserv); + if (i != OP_BROWSER_SUBSCRIBE && i != OP_BROWSER_UNSUBSCRIBE) + regfree (rx); + FREE (&rx); + } +#ifdef USE_IMAP + else +#endif /* USE_IMAP && USE_NNTP */ +#endif /* USE_NNTP */ +#ifdef USE_IMAP + { + if (i == OP_BROWSER_SUBSCRIBE) + imap_subscribe (state.entry[menu->current].name, 1); + else + imap_subscribe (state.entry[menu->current].name, 0); + } +#endif /* USE_IMAP */ } } diff --git a/browser.h b/browser.h index 515d69f..ad89ab2 100644 --- a/browser.h +++ b/browser.h @@ -19,6 +19,10 @@ #ifndef _BROWSER_H #define _BROWSER_H 1 +#ifdef USE_NNTP +#include "nntp.h" +#endif + struct folder_file { mode_t mode; @@ -37,6 +41,9 @@ struct folder_file unsigned selectable : 1; unsigned inferiors : 1; #endif +#ifdef USE_NNTP + NNTP_DATA *nd; +#endif unsigned tagged : 1; }; diff --git a/buffy.c b/buffy.c index 90ca6db..bd026df 100644 --- a/buffy.c +++ b/buffy.c @@ -543,6 +543,9 @@ int mutt_buffy_check (int force) /* check device ID and serial number instead of comparing paths */ if (!Context || Context->magic == M_IMAP || Context->magic == M_POP +#ifdef USE_NNTP + || Context->magic == M_NNTP +#endif || stat (Context->path, &contex_sb) != 0) { contex_sb.st_dev=0; @@ -559,6 +562,11 @@ int mutt_buffy_check (int force) tmp->magic = M_POP; else #endif +#ifdef USE_NNTP + if ((tmp->magic == M_NNTP) || mx_is_nntp (tmp->path)) + tmp->magic = M_NNTP; + else +#endif if (stat (tmp->path, &sb) != 0 || (S_ISREG(sb.st_mode) && sb.st_size == 0) || (!tmp->magic && (tmp->magic = mx_get_magic (tmp->path)) <= 0)) { @@ -574,7 +582,11 @@ int mutt_buffy_check (int force) /* check to see if the folder is the currently selected folder * before polling */ if (!Context || !Context->path || +#ifdef USE_NNTP + (( tmp->magic == M_IMAP || tmp->magic == M_POP || tmp->magic == M_NNTP ) +#else (( tmp->magic == M_IMAP || tmp->magic == M_POP ) +#endif ? mutt_strcmp (tmp->path, Context->path) : (sb.st_dev != contex_sb.st_dev || sb.st_ino != contex_sb.st_ino))) { diff --git a/complete.c b/complete.c index d0ee4af..8dc48cd 100644 --- a/complete.c +++ b/complete.c @@ -25,6 +25,9 @@ #include "mailbox.h" #include "imap.h" #endif +#ifdef USE_NNTP +#include "nntp.h" +#endif #include #include @@ -48,9 +51,70 @@ int mutt_complete (char *s, size_t slen) char filepart[_POSIX_PATH_MAX]; #ifdef USE_IMAP char imap_path[LONG_STRING]; +#endif dprint (2, (debugfile, "mutt_complete: completing %s\n", s)); +#ifdef USE_NNTP + if (option (OPTNEWS)) + { + NNTP_SERVER *nserv = CurrentNewsSrv; + unsigned int n = 0; + + strfcpy (filepart, s, sizeof (filepart)); + + /* special case to handle when there is no filepart yet + * find the first subscribed newsgroup */ + len = mutt_strlen (filepart); + if (len == 0) + { + for (; n < nserv->groups_num; n++) + { + NNTP_DATA *nntp_data = nserv->groups_list[n]; + + if (nntp_data && nntp_data->subscribed) + { + strfcpy (filepart, nntp_data->group, sizeof (filepart)); + init = 1; + n++; + break; + } + } + } + + for (; n < nserv->groups_num; n++) + { + NNTP_DATA *nntp_data = nserv->groups_list[n]; + + if (nntp_data && nntp_data->subscribed && + mutt_strncmp (nntp_data->group, filepart, len) == 0) + { + if (init) + { + for (i = 0; filepart[i] && nntp_data->group[i]; i++) + { + if (filepart[i] != nntp_data->group[i]) + { + filepart[i] = 0; + break; + } + } + filepart[i] = 0; + } + else + { + strfcpy (filepart, nntp_data->group, sizeof (filepart)); + init = 1; + } + } + } + + strcpy (s, filepart); + return (init ? 0 : -1); + } +#endif + +#ifdef USE_IMAP /* we can use '/' as a delimiter, imap_complete rewrites it */ if (*s == '=' || *s == '+' || *s == '!') { diff --git a/compose.c b/compose.c index 0fa6df2..901ffc0 100644 --- a/compose.c +++ b/compose.c @@ -33,11 +33,16 @@ #include "sort.h" #include "charset.h" #include "sidebar.h" +#include "mx.h" #ifdef MIXMASTER #include "remailer.h" #endif +#ifdef USE_NNTP +#include "nntp.h" +#endif + #include #include #include @@ -68,11 +73,17 @@ enum HDR_CRYPT, HDR_CRYPTINFO, +#ifdef USE_NNTP + HDR_NEWSGROUPS, + HDR_FOLLOWUPTO, + HDR_XCOMMENTTO, +#endif + HDR_ATTACH = (HDR_FCC + 5) /* where to start printing the attachments */ }; -#define HDR_XOFFSET 10 -#define TITLE_FMT "%10s" /* Used for Prompts, which are ASCII */ +#define HDR_XOFFSET 14 +#define TITLE_FMT "%14s" /* Used for Prompts, which are ASCII */ #define W (COLS - HDR_XOFFSET - SidebarWidth) static const char * const Prompts[] = @@ -84,6 +95,16 @@ static const char * const Prompts[] = "Subject: ", "Reply-To: ", "Fcc: " +#ifdef USE_NNTP +#ifdef MIXMASTER + ,"" +#endif + ,"" + ,"" + ,"Newsgroups: " + ,"Followup-To: " + ,"X-Comment-To: " +#endif }; static const struct mapping_t ComposeHelp[] = { @@ -98,6 +119,19 @@ static const struct mapping_t ComposeHelp[] = { { NULL, 0 } }; +#ifdef USE_NNTP +static struct mapping_t ComposeNewsHelp[] = { + { N_("Send"), OP_COMPOSE_SEND_MESSAGE }, + { N_("Abort"), OP_EXIT }, + { "Newsgroups", OP_COMPOSE_EDIT_NEWSGROUPS }, + { "Subj", OP_COMPOSE_EDIT_SUBJECT }, + { N_("Attach file"), OP_COMPOSE_ATTACH_FILE }, + { N_("Descrip"), OP_COMPOSE_EDIT_DESCRIPTION }, + { N_("Help"), OP_HELP }, + { NULL, 0 } +}; +#endif + static void snd_entry (char *b, size_t blen, MUTTMENU *menu, int num) { mutt_FormatString (b, blen, 0, NONULL (AttachFormat), mutt_attach_fmt, @@ -111,7 +145,7 @@ static void snd_entry (char *b, size_t blen, MUTTMENU *menu, int num) static void redraw_crypt_lines (HEADER *msg) { - mvaddstr (HDR_CRYPT, SidebarWidth, "Security: "); + mvprintw (HDR_CRYPT, SidebarWidth, TITLE_FMT, "Security: "); if ((WithCrypto & (APPLICATION_PGP | APPLICATION_SMIME)) == 0) { @@ -152,10 +186,11 @@ static void redraw_crypt_lines (HEADER *msg) if ((WithCrypto & APPLICATION_PGP) && (msg->security & APPLICATION_PGP) && (msg->security & SIGN)) printw ("%s%s", _(" sign as: "), PgpSignAs ? PgpSignAs : _("")); + printw (TITLE_FMT "%s", _(" sign as: "), PgpSignAs ? PgpSignAs : _("")); if ((WithCrypto & APPLICATION_SMIME) && (msg->security & APPLICATION_SMIME) && (msg->security & SIGN)) { - printw ("%s%s", _(" sign as: "), SmimeDefaultKey ? SmimeDefaultKey : _("")); + printw (TITLE_FMT "%s", _(" sign as: "), SmimeDefaultKey ? SmimeDefaultKey : _("")); } if ((WithCrypto & APPLICATION_SMIME) @@ -176,7 +211,7 @@ static void redraw_mix_line (LIST *chain) int c; char *t; - mvaddstr (HDR_MIX, SidebarWidth, " Mix: "); + mvprintw (HDR_MIX, SidebarWidth, TITLE_FMT, "Mix: "); if (!chain) { @@ -251,9 +286,28 @@ static void draw_envelope (HEADER *msg, char *fcc) { draw_sidebar (MENU_COMPOSE); draw_envelope_addr (HDR_FROM, msg->env->from); +#ifdef USE_NNTP + if (!option (OPTNEWSSEND)) + { +#endif draw_envelope_addr (HDR_TO, msg->env->to); draw_envelope_addr (HDR_CC, msg->env->cc); draw_envelope_addr (HDR_BCC, msg->env->bcc); +#ifdef USE_NNTP + } + else + { + mvprintw (HDR_TO, 0, TITLE_FMT , Prompts[HDR_NEWSGROUPS - 1]); + mutt_paddstr (W, NONULL (msg->env->newsgroups)); + mvprintw (HDR_CC, 0, TITLE_FMT , Prompts[HDR_FOLLOWUPTO - 1]); + mutt_paddstr (W, NONULL (msg->env->followup_to)); + if (option (OPTXCOMMENTTO)) + { + mvprintw (HDR_BCC, 0, TITLE_FMT , Prompts[HDR_XCOMMENTTO - 1]); + mutt_paddstr (W, NONULL (msg->env->x_comment_to)); + } + } +#endif mvprintw (HDR_SUBJECT, SidebarWidth, TITLE_FMT, Prompts[HDR_SUBJECT - 1]); mutt_paddstr (W, NONULL (msg->env->subject)); draw_envelope_addr (HDR_REPLYTO, msg->env->reply_to); @@ -504,6 +558,12 @@ int mutt_compose_menu (HEADER *msg, /* structure for new message */ /* Sort, SortAux could be changed in mutt_index_menu() */ int oldSort, oldSortAux; struct stat st; +#ifdef USE_NNTP + int news = 0; /* is it a news article ? */ + + if (option (OPTNEWSSEND)) + news++; +#endif mutt_attach_init (msg->content); idx = mutt_gen_attach_list (msg->content, -1, idx, &idxlen, &idxmax, 0, 1); @@ -514,10 +574,18 @@ int mutt_compose_menu (HEADER *msg, /* structure for new message */ menu->make_entry = snd_entry; menu->tag = mutt_tag_attach; menu->data = idx; +#ifdef USE_NNTP + if (news) + menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_COMPOSE, ComposeNewsHelp); + else +#endif menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_COMPOSE, ComposeHelp); while (loop) { +#ifdef USE_NNTP + unset_option (OPTNEWS); /* for any case */ +#endif switch (op = mutt_menuLoop (menu)) { case OP_REDRAW: @@ -530,6 +598,10 @@ int mutt_compose_menu (HEADER *msg, /* structure for new message */ mutt_message_hook (NULL, msg, M_SEND2HOOK); break; case OP_COMPOSE_EDIT_TO: +#ifdef USE_NNTP + if (news) + break; +#endif menu->redraw = edit_address_list (HDR_TO, &msg->env->to); if (option (OPTCRYPTOPPORTUNISTICENCRYPT)) { @@ -539,6 +611,10 @@ int mutt_compose_menu (HEADER *msg, /* structure for new message */ mutt_message_hook (NULL, msg, M_SEND2HOOK); break; case OP_COMPOSE_EDIT_BCC: +#ifdef USE_NNTP + if (news) + break; +#endif menu->redraw = edit_address_list (HDR_BCC, &msg->env->bcc); if (option (OPTCRYPTOPPORTUNISTICENCRYPT)) { @@ -548,6 +624,10 @@ int mutt_compose_menu (HEADER *msg, /* structure for new message */ mutt_message_hook (NULL, msg, M_SEND2HOOK); break; case OP_COMPOSE_EDIT_CC: +#ifdef USE_NNTP + if (news) + break; +#endif menu->redraw = edit_address_list (HDR_CC, &msg->env->cc); if (option (OPTCRYPTOPPORTUNISTICENCRYPT)) { @@ -556,6 +636,67 @@ int mutt_compose_menu (HEADER *msg, /* structure for new message */ } mutt_message_hook (NULL, msg, M_SEND2HOOK); break; +#ifdef USE_NNTP + case OP_COMPOSE_EDIT_NEWSGROUPS: + if (news) + { + if (msg->env->newsgroups) + strfcpy (buf, msg->env->newsgroups, sizeof (buf)); + else + buf[0] = 0; + if (mutt_get_field ("Newsgroups: ", buf, sizeof (buf), 0) == 0 && + buf[0]) + { + FREE (&msg->env->newsgroups); + mutt_remove_trailing_ws (buf); + msg->env->newsgroups = safe_strdup (mutt_skip_whitespace (buf)); + move (HDR_TO, HDR_XOFFSET); + clrtoeol (); + if (msg->env->newsgroups) + printw ("%-*.*s", W, W, msg->env->newsgroups); + } + } + break; + + case OP_COMPOSE_EDIT_FOLLOWUP_TO: + if (news) + { + buf[0] = 0; + if (msg->env->followup_to) + strfcpy (buf, msg->env->followup_to, sizeof (buf)); + if (mutt_get_field ("Followup-To: ", buf, sizeof (buf), 0) == 0 && + buf[0]) + { + FREE (&msg->env->followup_to); + mutt_remove_trailing_ws (buf); + msg->env->followup_to = safe_strdup (mutt_skip_whitespace (buf)); + move (HDR_CC, HDR_XOFFSET); + clrtoeol (); + if (msg->env->followup_to) + printw ("%-*.*s", W, W, msg->env->followup_to); + } + } + break; + + case OP_COMPOSE_EDIT_X_COMMENT_TO: + if (news && option (OPTXCOMMENTTO)) + { + buf[0] = 0; + if (msg->env->x_comment_to) + strfcpy (buf, msg->env->x_comment_to, sizeof (buf)); + if (mutt_get_field ("X-Comment-To: ", buf, sizeof (buf), 0) == 0 && + buf[0]) + { + FREE (&msg->env->x_comment_to); + msg->env->x_comment_to = safe_strdup (buf); + move (HDR_BCC, HDR_XOFFSET); + clrtoeol (); + if (msg->env->x_comment_to) + printw ("%-*.*s", W, W, msg->env->x_comment_to); + } + } + break; +#endif case OP_COMPOSE_EDIT_SUBJECT: if (msg->env->subject) strfcpy (buf, msg->env->subject, sizeof (buf)); @@ -721,6 +862,9 @@ int mutt_compose_menu (HEADER *msg, /* structure for new message */ break; case OP_COMPOSE_ATTACH_MESSAGE: +#ifdef USE_NNTP + case OP_COMPOSE_ATTACH_NEWS_MESSAGE: +#endif { char *prompt; HEADER *h; @@ -728,7 +872,22 @@ int mutt_compose_menu (HEADER *msg, /* structure for new message */ fname[0] = 0; prompt = _("Open mailbox to attach message from"); +#ifdef USE_NNTP + unset_option (OPTNEWS); + if (op == OP_COMPOSE_ATTACH_NEWS_MESSAGE) + { + if (!(CurrentNewsSrv = nntp_select_server (NewsServer, 0))) + break; + + prompt = _("Open newsgroup to attach message from"); + set_option (OPTNEWS); + } +#endif + if (Context) +#ifdef USE_NNTP + if ((op == OP_COMPOSE_ATTACH_MESSAGE) ^ (Context->magic == M_NNTP)) +#endif { strfcpy (fname, NONULL (Context->path), sizeof (fname)); mutt_pretty_mailbox (fname, sizeof (fname)); @@ -737,6 +896,11 @@ int mutt_compose_menu (HEADER *msg, /* structure for new message */ if (mutt_enter_fname (prompt, fname, sizeof (fname), &menu->redraw, 1) == -1 || !fname[0]) break; +#ifdef USE_NNTP + if (option (OPTNEWS)) + nntp_expand_path (fname, sizeof (fname), &CurrentNewsSrv->conn->account); + else +#endif mutt_expand_path (fname, sizeof (fname)); #ifdef USE_IMAP if (!mx_is_imap (fname)) @@ -744,6 +908,9 @@ int mutt_compose_menu (HEADER *msg, /* structure for new message */ #ifdef USE_POP if (!mx_is_pop (fname)) #endif +#ifdef USE_NNTP + if (!mx_is_nntp (fname) && !option (OPTNEWS)) +#endif /* check to make sure the file exists and is readable */ if (access (fname, R_OK) == -1) { diff --git a/configure.ac b/configure.ac index 1d260aa..d17abaa 100644 --- a/configure.ac +++ b/configure.ac @@ -600,6 +600,15 @@ AC_ARG_ENABLE(imap, AS_HELP_STRING([--enable-imap],[Enable IMAP support]), ]) AM_CONDITIONAL(BUILD_IMAP, test x$need_imap = xyes) +AC_ARG_ENABLE(nntp, AC_HELP_STRING([--enable-nntp],[Enable NNTP support]), +[ if test x$enableval = xyes ; then + AC_DEFINE(USE_NNTP,1,[ Define if you want support for the NNTP protocol. ]) + MUTT_LIB_OBJECTS="$MUTT_LIB_OBJECTS nntp.o newsrc.o" + need_nntp="yes" + need_socket="yes" + fi +]) + AC_ARG_ENABLE(smtp, AS_HELP_STRING([--enable-smtp],[include internal SMTP relay support]), [if test $enableval = yes; then AC_DEFINE(USE_SMTP, 1, [Include internal SMTP relay support]) @@ -607,7 +616,7 @@ AC_ARG_ENABLE(smtp, AS_HELP_STRING([--enable-smtp],[include internal SMTP relay need_socket="yes" fi]) -if test x"$need_imap" = xyes -o x"$need_pop" = xyes ; then +if test x"$need_imap" = xyes -o x"$need_pop" = xyes -o x"$need_nntp" = xyes ; then MUTT_LIB_OBJECTS="$MUTT_LIB_OBJECTS bcache.o" fi diff --git a/curs_main.c b/curs_main.c index 2e35f90..d052c38 100644 --- a/curs_main.c +++ b/curs_main.c @@ -22,6 +22,7 @@ #include "mutt.h" #include "mutt_curses.h" +#include "mx.h" #include "mutt_menu.h" #include "mailbox.h" #include "mapping.h" @@ -40,6 +41,10 @@ #include "mutt_crypt.h" +#ifdef USE_NNTP +#include "nntp.h" +#endif + #include #include @@ -492,12 +497,27 @@ static const struct mapping_t IndexHelp[] = { { NULL, 0 } }; +#ifdef USE_NNTP +struct mapping_t IndexNewsHelp[] = { + { N_("Quit"), OP_QUIT }, + { N_("Del"), OP_DELETE }, + { N_("Undel"), OP_UNDELETE }, + { N_("Save"), OP_SAVE }, + { N_("Post"), OP_POST }, + { N_("Followup"), OP_FOLLOWUP }, + { N_("Catchup"), OP_CATCHUP }, + { N_("Help"), OP_HELP }, + { NULL, 0 } +}; +#endif + /* This function handles the message index window as well as commands returned * from the pager (MENU_PAGER). */ int mutt_index_menu (void) { char buf[LONG_STRING], helpstr[LONG_STRING]; + int flags; int op = OP_NULL; int done = 0; /* controls when to exit the "event" loop */ int i = 0, j; @@ -518,7 +538,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 */ @@ -774,6 +798,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) { @@ -824,6 +851,161 @@ int mutt_index_menu (void) menu_current_bottom (menu); break; +#ifdef USE_NNTP + case OP_GET_PARENT: + CHECK_MSGCOUNT; + CHECK_VISIBLE; + + case OP_GET_MESSAGE: + CHECK_IN_MAILBOX; + CHECK_READONLY; + CHECK_ATTACH; + if (Context->magic == M_NNTP) + { + HEADER *hdr; + + if (op == OP_GET_MESSAGE) + { + buf[0] = 0; + if (mutt_get_field (_("Enter Message-Id: "), + buf, sizeof (buf), 0) != 0 || !buf[0]) + break; + } + else + { + LIST *ref = CURHDR->env->references; + if (!ref) + { + mutt_error _("Article has no parent reference."); + break; + } + strfcpy (buf, ref->data, sizeof (buf)); + } + if (!Context->id_hash) + Context->id_hash = mutt_make_id_hash (Context); + hdr = hash_find (Context->id_hash, buf); + if (hdr) + { + if (hdr->virtual != -1) + { + menu->current = hdr->virtual; + menu->redraw = REDRAW_MOTION_RESYNCH; + } + else if (hdr->collapsed) + { + mutt_uncollapse_thread (Context, hdr); + mutt_set_virtual (Context); + menu->current = hdr->virtual; + menu->redraw = REDRAW_MOTION_RESYNCH; + } + else + mutt_error _("Message is not visible in limited view."); + } + else + { + int rc; + + mutt_message (_("Fetching %s from server..."), buf); + rc = nntp_check_msgid (Context, buf); + if (rc == 0) + { + hdr = Context->hdrs[Context->msgcount - 1]; + mutt_sort_headers (Context, 0); + menu->current = hdr->virtual; + menu->redraw = REDRAW_FULL; + } + else if (rc > 0) + mutt_error (_("Article %s not found on the server."), buf); + } + } + break; + + case OP_GET_CHILDREN: + case OP_RECONSTRUCT_THREAD: + CHECK_MSGCOUNT; + CHECK_VISIBLE; + CHECK_READONLY; + CHECK_ATTACH; + if (Context->magic == M_NNTP) + { + int oldmsgcount = Context->msgcount; + int oldindex = CURHDR->index; + int rc = 0; + + if (!CURHDR->env->message_id) + { + mutt_error _("No Message-Id. Unable to perform operation."); + break; + } + + mutt_message _("Fetching message headers..."); + if (!Context->id_hash) + Context->id_hash = mutt_make_id_hash (Context); + strfcpy (buf, CURHDR->env->message_id, sizeof (buf)); + + /* trying to find msgid of the root message */ + if (op == OP_RECONSTRUCT_THREAD) + { + LIST *ref = CURHDR->env->references; + while (ref) + { + if (hash_find (Context->id_hash, ref->data) == NULL) + { + rc = nntp_check_msgid (Context, ref->data); + if (rc < 0) + break; + } + + /* the last msgid in References is the root message */ + if (!ref->next) + strfcpy (buf, ref->data, sizeof (buf)); + ref = ref->next; + } + } + + /* fetching all child messages */ + if (rc >= 0) + rc = nntp_check_children (Context, buf); + + /* at least one message has been loaded */ + if (Context->msgcount > oldmsgcount) + { + HEADER *hdr; + int i, quiet = Context->quiet; + + if (rc < 0) + Context->quiet = 1; + mutt_sort_headers (Context, (op == OP_RECONSTRUCT_THREAD)); + Context->quiet = quiet; + + /* if the root message was retrieved, move to it */ + hdr = hash_find (Context->id_hash, buf); + if (hdr) + menu->current = hdr->virtual; + + /* try to restore old position */ + else + { + for (i = 0; i < Context->msgcount; i++) + { + if (Context->hdrs[i]->index == oldindex) + { + menu->current = Context->hdrs[i]->virtual; + /* as an added courtesy, recenter the menu + * with the current entry at the middle of the screen */ + menu_check_recenter (menu); + menu_current_middle (menu); + } + } + } + menu->redraw = REDRAW_FULL; + } + else if (rc >= 0) + mutt_error _("No deleted messages found in the thread."); + } + break; +#endif + case OP_JUMP: CHECK_MSGCOUNT; @@ -920,11 +1102,33 @@ int mutt_index_menu (void) break; case OP_MAIN_LIMIT: + case OP_TOGGLE_READ: CHECK_IN_MAILBOX; menu->oldcurrent = (Context->vcount && menu->current >= 0 && menu->current < Context->vcount) ? CURHDR->index : -1; - if (mutt_pattern_func (M_LIMIT, _("Limit to messages matching: ")) == 0) + if (op == OP_TOGGLE_READ) + { + char buf[LONG_STRING]; + + if (!Context->pattern || strncmp (Context->pattern, "!~R!~D~s", 8) != 0) + { + snprintf (buf, sizeof (buf), "!~R!~D~s%s", + Context->pattern ? Context->pattern : ".*"); + set_option (OPTHIDEREAD); + } + else + { + strfcpy (buf, Context->pattern + 8, sizeof(buf)); + if (!*buf || strncmp (buf, ".*", 2) == 0) + snprintf (buf, sizeof(buf), "~A"); + unset_option (OPTHIDEREAD); + } + FREE (&Context->pattern); + Context->pattern = safe_strdup (buf); + } + if ((op == OP_TOGGLE_READ && mutt_pattern_func (M_LIMIT, NULL) == 0) || + mutt_pattern_func (M_LIMIT, _("Limit to messages matching: ")) == 0) { if (menu->oldcurrent >= 0) { @@ -1167,15 +1371,22 @@ int mutt_index_menu (void) case OP_SIDEBAR_OPEN: case OP_MAIN_CHANGE_FOLDER: case OP_MAIN_NEXT_UNREAD_MAILBOX: - - if (attach_msg) - op = OP_MAIN_CHANGE_FOLDER_READONLY; - - /* fallback to the readonly case */ - case OP_MAIN_CHANGE_FOLDER_READONLY: +#ifdef USE_NNTP + case OP_MAIN_CHANGE_GROUP: + case OP_MAIN_CHANGE_GROUP_READONLY: + unset_option (OPTNEWS); +#endif + if (attach_msg || option (OPTREADONLY) || +#ifdef USE_NNTP + op == OP_MAIN_CHANGE_GROUP_READONLY || +#endif + op == OP_MAIN_CHANGE_FOLDER_READONLY) + flags = M_READONLY; + else + flags = 0; - if ((op == OP_MAIN_CHANGE_FOLDER_READONLY) || option (OPTREADONLY)) + if (flags) cp = _("Open mailbox in read-only mode"); else cp = _("Open mailbox"); @@ -1194,6 +1405,22 @@ int mutt_index_menu (void) } else { +#ifdef USE_NNTP + if (op == OP_MAIN_CHANGE_GROUP || + op == OP_MAIN_CHANGE_GROUP_READONLY) + { + set_option (OPTNEWS); + CurrentNewsSrv = nntp_select_server (NewsServer, 0); + if (!CurrentNewsSrv) + break; + if (flags) + cp = _("Open newsgroup in read-only mode"); + else + cp = _("Open newsgroup"); + nntp_buffy (buf, sizeof (buf)); + } + else +#endif mutt_buffy (buf, sizeof (buf)); if ( op == OP_SIDEBAR_OPEN ) { @@ -1217,6 +1444,14 @@ int mutt_index_menu (void) } } +#ifdef USE_NNTP + if (option (OPTNEWS)) + { + unset_option (OPTNEWS); + nntp_expand_path (buf, sizeof (buf), &CurrentNewsSrv->conn->account); + } + else +#endif mutt_expand_path (buf, sizeof (buf)); set_curbuffy(buf); if (mx_get_magic (buf) <= 0) @@ -1264,15 +1499,18 @@ int mutt_index_menu (void) CurrentMenu = MENU_MAIN; mutt_folder_hook (buf); - if ((Context = mx_open_mailbox (buf, - (option (OPTREADONLY) || op == OP_MAIN_CHANGE_FOLDER_READONLY) ? - M_READONLY : 0, NULL)) != NULL) + if ((Context = mx_open_mailbox (buf, flags, NULL)) != NULL) { menu->current = ci_first_message (); } else menu->current = 0; +#ifdef USE_NNTP + /* mutt_buffy_check() must be done with mail-reader mode! */ + menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_MAIN, + (Context && (Context->magic == M_NNTP)) ? IndexNewsHelp : IndexHelp); +#endif mutt_clear_error (); mutt_buffy_check(1); /* force the buffy check after we have changed the folder */ @@ -1341,6 +1579,7 @@ int mutt_index_menu (void) CHECK_MSGCOUNT; CHECK_VISIBLE; CHECK_READONLY; + CHECK_ACL(M_ACL_WRITE, _("break thread")); if ((Sort & SORT_MASK) != SORT_THREADS) mutt_error _("Threading is not enabled."); @@ -1375,7 +1614,7 @@ int mutt_index_menu (void) CHECK_MSGCOUNT; CHECK_VISIBLE; CHECK_READONLY; - CHECK_ACL(M_ACL_DELETE, _("link threads")); + CHECK_ACL(M_ACL_WRITE, _("link threads")); if ((Sort & SORT_MASK) != SORT_THREADS) mutt_error _("Threading is not enabled."); @@ -1996,6 +2235,20 @@ int mutt_index_menu (void) } break; +#ifdef USE_NNTP + case OP_CATCHUP: + CHECK_MSGCOUNT; + CHECK_READONLY; + CHECK_ATTACH + if (Context && Context->magic == M_NNTP) + { + NNTP_DATA *nntp_data = Context->data; + if (mutt_newsgroup_catchup (nntp_data->nserv, nntp_data->group)) + menu->redraw = REDRAW_INDEX | REDRAW_STATUS; + } + break; +#endif + case OP_DISPLAY_ADDRESS: CHECK_MSGCOUNT; @@ -2200,6 +2453,39 @@ int mutt_index_menu (void) menu->redraw = REDRAW_FULL; break; +#ifdef USE_NNTP + case OP_FOLLOWUP: + case OP_FORWARD_TO_GROUP: + + CHECK_MSGCOUNT; + CHECK_VISIBLE; + + case OP_POST: + + CHECK_ATTACH; + if (op != OP_FOLLOWUP || !CURHDR->env->followup_to || + mutt_strcasecmp (CURHDR->env->followup_to, "poster") || + query_quadoption (OPT_FOLLOWUPTOPOSTER, + _("Reply by mail as poster prefers?")) != M_YES) + { + if (Context && Context->magic == M_NNTP && + !((NNTP_DATA *)Context->data)->allowed && + query_quadoption (OPT_TOMODERATED, + _("Posting to this group not allowed, may be moderated. Continue?")) != M_YES) + break; + if (op == OP_POST) + ci_send_message (SENDNEWS, NULL, NULL, Context, NULL); + else + { + CHECK_MSGCOUNT; + ci_send_message ((op == OP_FOLLOWUP ? SENDREPLY : SENDFORWARD) | + SENDNEWS, NULL, NULL, Context, tag ? NULL : CURHDR); + } + menu->redraw = REDRAW_FULL; + break; + } +#endif + case OP_REPLY: CHECK_ATTACH; diff --git a/doc/Muttrc b/doc/Muttrc index d1a96a2..7fdc12f 100644 --- a/doc/Muttrc +++ b/doc/Muttrc @@ -240,6 +240,28 @@ attachments -I message/external-body # editing the body of an outgoing message. # # +# set ask_follow_up=no +# +# Name: ask_follow_up +# Type: boolean +# Default: no +# +# +# If set, Mutt will prompt you for follow-up groups before editing +# the body of an outgoing message. +# +# +# set ask_x_comment_to=no +# +# Name: ask_x_comment_to +# Type: boolean +# Default: no +# +# +# If set, Mutt will prompt you for x-comment-to field before editing +# the body of an outgoing message. +# +# # set assumed_charset="" # # Name: assumed_charset @@ -445,6 +467,17 @@ attachments -I message/external-body # visual terminals don't permit making the cursor invisible. # # +# set catchup_newsgroup=ask-yes +# +# Name: catchup_newsgroup +# Type: quadoption +# Default: ask-yes +# +# +# If this variable is set, Mutt will mark all articles in newsgroup +# as read when you quit the newsgroup (catchup newsgroup). +# +# # set certificate_file="~/.mutt_certificates" # # Name: certificate_file @@ -1170,6 +1203,19 @@ attachments -I message/external-body # of the same email for you. # # +# set followup_to_poster=ask-yes +# +# Name: followup_to_poster +# Type: quadoption +# Default: ask-yes +# +# +# If this variable is set and the keyword "poster" is present in +# Followup-To header, follow-up to newsgroup function is not +# permitted. The message will be mailed to the submitter of the +# message via mail. +# +# # set force_name=no # # Name: force_name @@ -1280,6 +1326,27 @@ attachments -I message/external-body # ``Franklin'' to ``Franklin, Steve''. # # +# set group_index_format="%4C %M%N %5s %-45.45f %d" +# +# Name: group_index_format +# Type: string +# Default: "%4C %M%N %5s %-45.45f %d" +# +# +# This variable allows you to customize the newsgroup browser display to +# your personal taste. This string is similar to ``index_format'', but +# has its own set of printf()-like sequences: +# %C current newsgroup number +# %d description of newsgroup (becomes from server) +# %f newsgroup name +# %M - if newsgroup not allowed for direct post (moderated for example) +# %N N if newsgroup is new, u if unsubscribed, blank otherwise +# %n number of new articles in newsgroup +# %s number of unread articles in newsgroup +# %>X right justify the rest of the string and pad with character "X" +# %|X pad to the end of the line with character "X" +# +# # set hdrs=yes # # Name: hdrs @@ -1828,6 +1895,7 @@ attachments -I message/external-body # %E number of messages in current thread # %f sender (address + real name), either From: or Return-Path: # %F author name, or recipient name if the message is from you +# %g newsgroup name (if compiled with NNTP support) # %H spam attribute(s) of this message # %i message-id of the current message # %l number of lines in the message (does not work with maildir, @@ -1843,12 +1911,14 @@ attachments -I message/external-body # stashed the message: list name or recipient name # if not sent to a list # %P progress indicator for the built-in pager (how much of the file has been displayed) +# %R ``X-Comment-To:'' field (if present and compiled with NNTP support) # %s subject of the message # %S status of the message (``N''/``D''/``d''/``!''/``r''/*) # %t ``To:'' field (recipients) # %T the appropriate character from the $to_chars string # %u user (login) name of the author # %v first name of the author, or the recipient if the message is from you +# %W name of organization of author (``Organization:'' field) # %X number of attachments # (please see the ``attachments'' section for possible speed effects) # %y ``X-Label:'' field, if present @@ -1884,6 +1954,27 @@ attachments -I message/external-body # ``save-hook'', ``fcc-hook'' and ``fcc-save-hook'', too. # # +# set inews="" +# +# Name: inews +# Type: path +# Default: "" +# +# +# If set, specifies the program and arguments used to deliver news posted +# by Mutt. Otherwise, mutt posts article using current connection to +# news server. The following printf-style sequence is understood: +# %a account url +# %p port +# %P port if specified +# %s news server name +# %S url schema +# %u username +# +# +# Example: set inews="/usr/local/bin/inews -hS" +# +# # set ispell="ispell" # # Name: ispell @@ -2268,6 +2359,18 @@ attachments -I message/external-body # be attached to the newly composed message if this option is set. # # +# set mime_subject=yes +# +# Name: mime_subject +# Type: boolean +# Default: yes +# +# +# If unset, 8-bit ``subject:'' line in article header will not be +# encoded according to RFC2047 to base64. This is useful when message +# is Usenet article, because MIME for news is nonstandard feature. +# +# # set mix_entry_format="%4n %c %-16s %a" # # Name: mix_entry_format @@ -2334,6 +2437,144 @@ attachments -I message/external-body # See also $read_inc, $write_inc and $net_inc. # # +# set news_cache_dir="~/.mutt" +# +# Name: news_cache_dir +# Type: path +# Default: "~/.mutt" +# +# +# This variable pointing to directory where Mutt will save cached news +# articles and headers in. If unset, articles and headers will not be +# saved at all and will be reloaded from the server each time. +# +# +# set news_server="" +# +# Name: news_server +# Type: string +# Default: "" +# +# +# This variable specifies domain name or address of NNTP server. It +# defaults to the news server specified in the environment variable +# $NNTPSERVER or contained in the file /etc/nntpserver. You can also +# specify username and an alternative port for each news server, ie: +# +# [[s]news://][username[:password]@]server[:port] +# +# +# set newsgroups_charset="utf-8" +# +# Name: newsgroups_charset +# Type: string +# Default: "utf-8" +# +# +# Character set of newsgroups descriptions. +# +# +# set newsrc="~/.newsrc" +# +# Name: newsrc +# Type: path +# Default: "~/.newsrc" +# +# +# The file, containing info about subscribed newsgroups - names and +# indexes of read articles. The following printf-style sequence +# is understood: +# %a account url +# %p port +# %P port if specified +# %s news server name +# %S url schema +# %u username +# +# +# set nntp_authenticators="" +# +# Name: nntp_authenticators +# Type: string +# Default: "" +# +# +# This is a colon-delimited list of authentication methods mutt may +# attempt to use to log in to a news server, in the order mutt should +# try them. Authentication methods are either ``user'' or any +# SASL mechanism, e.g. ``digest-md5'', ``gssapi'' or ``cram-md5''. +# This option is case-insensitive. If it's unset (the default) +# mutt will try all available methods, in order from most-secure to +# least-secure. +# +# Example: +# set nntp_authenticators="digest-md5:user" +# +# Note: Mutt will only fall back to other authentication methods if +# the previous methods are unavailable. If a method is available but +# authentication fails, mutt will not connect to the IMAP server. +# +# +# set nntp_context=1000 +# +# Name: nntp_context +# Type: number +# Default: 1000 +# +# +# This variable defines number of articles which will be in index when +# newsgroup entered. If active newsgroup have more articles than this +# number, oldest articles will be ignored. Also controls how many +# articles headers will be saved in cache when you quit newsgroup. +# +# +# set nntp_load_description=yes +# +# Name: nntp_load_description +# Type: boolean +# Default: yes +# +# +# This variable controls whether or not descriptions for each newsgroup +# must be loaded when newsgroup is added to list (first time list +# loading or new newsgroup adding). +# +# +# set nntp_user="" +# +# Name: nntp_user +# Type: string +# Default: "" +# +# +# Your login name on the NNTP server. If unset and NNTP server requires +# authentification, Mutt will prompt you for your account name when you +# connect to news server. +# +# +# set nntp_pass="" +# +# Name: nntp_pass +# Type: string +# Default: "" +# +# +# Your password for NNTP account. +# +# +# set nntp_poll=60 +# +# Name: nntp_poll +# Type: number +# Default: 60 +# +# +# The time in seconds until any operations on newsgroup except post new +# article will cause recheck for new news. If set to 0, Mutt will +# recheck newsgroup on each operation in index (stepping, read article, +# etc.). +# +# # set pager="builtin" # # Name: pager @@ -3064,6 +3305,19 @@ attachments -I message/external-body # string after the inclusion of a message which is being replied to. # # +# set post_moderated=ask-yes +# +# Name: post_moderated +# Type: quadoption +# Default: ask-yes +# +# +# If set to yes, Mutt will post article to newsgroup that have +# not permissions to posting (e.g. moderated). Note: if news server +# does not support posting to that newsgroup or totally read-only, that +# posting will not have an effect. +# +# # set postpone=ask-yes # # Name: postpone @@ -3697,6 +3951,41 @@ attachments -I message/external-body # shell from /etc/passwd is used. # # +# set save_unsubscribed=no +# +# Name: save_unsubscribed +# Type: boolean +# Default: no +# +# +# When set, info about unsubscribed newsgroups will be saved into +# ``newsrc'' file and into cache. +# +# +# set show_new_news=yes +# +# Name: show_new_news +# Type: boolean +# Default: yes +# +# +# If set, news server will be asked for new newsgroups on entering +# the browser. Otherwise, it will be done only once for a news server. +# Also controls whether or not number of new articles of subscribed +# newsgroups will be then checked. +# +# +# set show_only_unread=no +# +# Name: show_only_unread +# Type: boolean +# Default: no +# +# +# If set, only subscribed newsgroups that contain unread articles +# will be displayed in browser. +# +# # set sig_dashes=yes # # Name: sig_dashes @@ -4995,3 +5284,14 @@ attachments -I message/external-body # ``tuning'' section of the manual for performance considerations. # # +# set x_comment_to=no +# +# Name: x_comment_to +# Type: boolean +# Default: no +# +# +# If set, Mutt will add ``X-Comment-To:'' field (that contains full +# name of original article author) to article that followuped to newsgroup. +# +# diff --git a/doc/manual.xml.head b/doc/manual.xml.head index f7a9387..aeefa5c 100644 --- a/doc/manual.xml.head +++ b/doc/manual.xml.head @@ -1696,6 +1696,26 @@ See also the $postpone quad-option. + +Reading news via NNTP + + +If compiled with --enable-nntp option, Mutt can +read news from news server via NNTP. You can open a newsgroup with +function ``change-newsgroup'' (default: ``i''). Default news server +can be obtained from $NNTPSERVER environment +variable or from /etc/nntpserver file. Like other +news readers, info about subscribed newsgroups is saved in file by +$newsrc variable. The variable $news_cache_dir can be used to point +to a directory. Mutt will create a hierarchy of subdirectories named +like the account and newsgroup the cache is for. Also the hierarchy +is used to store header cache if Mutt was compiled with header cache support. + + + + diff --git a/doc/mutt.man b/doc/mutt.man index 753953e..0e61ae2 100644 --- a/doc/mutt.man +++ b/doc/mutt.man @@ -23,8 +23,8 @@ mutt \- The Mutt Mail User Agent .SH SYNOPSIS .PP .B mutt -[\-nRyzZ] -[\-e \fIcmd\fP] [\-F \fIfile\fP] [\-m \fItype\fP] [\-f \fIfile\fP] +[\-GnRyzZ] +[\-e \fIcmd\fP] [\-F \fIfile\fP] [\-g \fIserver\fP] [\-m \fItype\fP] [\-f \fIfile\fP] .PP .B mutt [\-nx] @@ -101,6 +101,10 @@ files. Specify which mailbox to load. .IP "-F \fImuttrc\fP" Specify an initialization file to read instead of ~/.muttrc +.IP "-g \fIserver\fP" +Start Mutt with a listing of subscribed newsgroups at specified news server. +.IP "-G" +Start Mutt with a listing of subscribed newsgroups. .IP "-h" Display help. .IP "-H \fIdraft\fP" diff --git a/functions.h b/functions.h index 1485080..8ca5411 100644 --- a/functions.h +++ b/functions.h @@ -88,6 +88,10 @@ const struct binding_t OpMain[] = { /* map: index */ { "break-thread", OP_MAIN_BREAK_THREAD, "#" }, { "change-folder", OP_MAIN_CHANGE_FOLDER, "c" }, { "change-folder-readonly", OP_MAIN_CHANGE_FOLDER_READONLY, "\033c" }, +#ifdef USE_NNTP + { "change-newsgroup", OP_MAIN_CHANGE_GROUP, "i" }, + { "change-newsgroup-readonly",OP_MAIN_CHANGE_GROUP_READONLY, "\033i" }, +#endif { "next-unread-mailbox", OP_MAIN_NEXT_UNREAD_MAILBOX, NULL }, { "collapse-thread", OP_MAIN_COLLAPSE_THREAD, "\033v" }, { "collapse-all", OP_MAIN_COLLAPSE_ALL, "\033V" }, @@ -101,7 +105,15 @@ const struct binding_t OpMain[] = { /* map: index */ { "edit", OP_EDIT_MESSAGE, "e" }, { "edit-type", OP_EDIT_TYPE, "\005" }, { "forward-message", OP_FORWARD_MESSAGE, "f" }, - { "flag-message", OP_FLAG_MESSAGE, "F" }, +#ifdef USE_NNTP + { "forward-to-group", OP_FORWARD_TO_GROUP, "\033F" }, + { "followup-message", OP_FOLLOWUP, "F" }, + { "get-children", OP_GET_CHILDREN, NULL }, + { "get-message", OP_GET_MESSAGE, "\007" }, + { "get-parent", OP_GET_PARENT, "\033G" }, + { "reconstruct-thread", OP_RECONSTRUCT_THREAD, NULL }, +#endif + { "flag-message", OP_FLAG_MESSAGE, "\033f" }, { "group-reply", OP_GROUP_REPLY, "g" }, #ifdef USE_POP { "fetch-mail", OP_MAIN_FETCH_MAIL, "G" }, @@ -129,6 +141,9 @@ const struct binding_t OpMain[] = { /* map: index */ { "sort-mailbox", OP_SORT, "o" }, { "sort-reverse", OP_SORT_REVERSE, "O" }, { "print-message", OP_PRINT, "p" }, +#ifdef USE_NNTP + { "post-message", OP_POST, "P" }, +#endif { "previous-thread", OP_MAIN_PREV_THREAD, "\020" }, { "previous-subthread", OP_MAIN_PREV_SUBTHREAD, "\033p" }, { "recall-message", OP_RECALL_MESSAGE, "R" }, @@ -148,6 +163,10 @@ const struct binding_t OpMain[] = { /* map: index */ { "show-version", OP_VERSION, "V" }, { "set-flag", OP_MAIN_SET_FLAG, "w" }, { "clear-flag", OP_MAIN_CLEAR_FLAG, "W" }, + { "toggle-read", OP_TOGGLE_READ, "X" }, +#ifdef USE_NNTP + { "catchup", OP_CATCHUP, "y" }, +#endif { "display-message", OP_DISPLAY_MESSAGE, M_ENTER_S }, { "buffy-list", OP_BUFFY_LIST, "." }, { "sync-mailbox", OP_MAIN_SYNC_FOLDER, "$" }, @@ -159,7 +178,7 @@ const struct binding_t OpMain[] = { /* map: index */ { "previous-new-then-unread", OP_MAIN_PREV_NEW_THEN_UNREAD, "\033\t" }, { "next-unread", OP_MAIN_NEXT_UNREAD, NULL }, { "previous-unread", OP_MAIN_PREV_UNREAD, NULL }, - { "parent-message", OP_MAIN_PARENT_MESSAGE, "P" }, + { "parent-message", OP_MAIN_PARENT_MESSAGE, NULL }, { "extract-keys", OP_EXTRACT_KEYS, "\013" }, @@ -187,6 +206,10 @@ const struct binding_t OpPager[] = { /* map: pager */ { "bounce-message", OP_BOUNCE_MESSAGE, "b" }, { "change-folder", OP_MAIN_CHANGE_FOLDER, "c" }, { "change-folder-readonly", OP_MAIN_CHANGE_FOLDER_READONLY, "\033c" }, +#ifdef USE_NNTP + { "change-newsgroup", OP_MAIN_CHANGE_GROUP, "i" }, + { "change-newsgroup-readonly",OP_MAIN_CHANGE_GROUP_READONLY, "\033i" }, +#endif { "next-unread-mailbox", OP_MAIN_NEXT_UNREAD_MAILBOX, NULL }, { "copy-message", OP_COPY_MESSAGE, "C" }, { "decode-copy", OP_DECODE_COPY, "\033C" }, @@ -197,8 +220,12 @@ const struct binding_t OpPager[] = { /* map: pager */ { "clear-flag", OP_MAIN_CLEAR_FLAG, "W" }, { "edit", OP_EDIT_MESSAGE, "e" }, { "edit-type", OP_EDIT_TYPE, "\005" }, +#ifdef USE_NNTP + { "followup-message", OP_FOLLOWUP, "F" }, + { "forward-to-group", OP_FORWARD_TO_GROUP, "\033F" }, +#endif { "forward-message", OP_FORWARD_MESSAGE, "f" }, - { "flag-message", OP_FLAG_MESSAGE, "F" }, + { "flag-message", OP_FLAG_MESSAGE, "\033f" }, { "group-reply", OP_GROUP_REPLY, "g" }, #ifdef USE_IMAP { "imap-fetch-mail", OP_MAIN_IMAP_FETCH, NULL }, @@ -220,6 +247,9 @@ const struct binding_t OpPager[] = { /* map: pager */ { "sort-mailbox", OP_SORT, "o" }, { "sort-reverse", OP_SORT_REVERSE, "O" }, { "print-message", OP_PRINT, "p" }, +#ifdef USE_NNTP + { "post-message", OP_POST, "P" }, +#endif { "previous-thread", OP_MAIN_PREV_THREAD, "\020" }, { "previous-subthread",OP_MAIN_PREV_SUBTHREAD, "\033p" }, { "purge-message", OP_PURGE_MESSAGE, NULL }, @@ -268,7 +298,7 @@ const struct binding_t OpPager[] = { /* map: pager */ { "half-down", OP_HALF_DOWN, NULL }, { "previous-line", OP_PREV_LINE, NULL }, { "bottom", OP_PAGER_BOTTOM, NULL }, - { "parent-message", OP_MAIN_PARENT_MESSAGE, "P" }, + { "parent-message", OP_MAIN_PARENT_MESSAGE, NULL }, @@ -297,6 +327,10 @@ const struct binding_t OpAttach[] = { /* map: attachment */ { "bounce-message", OP_BOUNCE_MESSAGE, "b" }, { "display-toggle-weed", OP_DISPLAY_HEADERS, "h" }, { "edit-type", OP_EDIT_TYPE, "\005" }, +#ifdef USE_NNTP + { "followup-message", OP_FOLLOWUP, "F" }, + { "forward-to-group", OP_FORWARD_TO_GROUP, "\033F" }, +#endif { "print-entry", OP_PRINT, "p" }, { "save-entry", OP_SAVE, "s" }, { "pipe-entry", OP_PIPE, "|" }, @@ -322,6 +356,7 @@ const struct binding_t OpAttach[] = { /* map: attachment */ const struct binding_t OpCompose[] = { /* map: compose */ { "attach-file", OP_COMPOSE_ATTACH_FILE, "a" }, { "attach-message", OP_COMPOSE_ATTACH_MESSAGE, "A" }, + { "attach-news-message",OP_COMPOSE_ATTACH_NEWS_MESSAGE,"\033a" }, { "edit-bcc", OP_COMPOSE_EDIT_BCC, "b" }, { "edit-cc", OP_COMPOSE_EDIT_CC, "c" }, { "copy-file", OP_SAVE, "C" }, @@ -341,6 +376,11 @@ const struct binding_t OpCompose[] = { /* map: compose */ { "print-entry", OP_PRINT, "l" }, { "edit-mime", OP_COMPOSE_EDIT_MIME, "m" }, { "new-mime", OP_COMPOSE_NEW_MIME, "n" }, +#ifdef USE_NNTP + { "edit-newsgroups", OP_COMPOSE_EDIT_NEWSGROUPS, "N" }, + { "edit-followup-to", OP_COMPOSE_EDIT_FOLLOWUP_TO, "o" }, + { "edit-x-comment-to",OP_COMPOSE_EDIT_X_COMMENT_TO, "x" }, +#endif { "postpone-message", OP_COMPOSE_POSTPONE_MESSAGE, "P" }, { "edit-reply-to", OP_COMPOSE_EDIT_REPLY_TO, "r" }, { "rename-file", OP_COMPOSE_RENAME_FILE, "R" }, @@ -392,14 +432,25 @@ const struct binding_t OpBrowser[] = { /* map: browser */ { "select-new", OP_BROWSER_NEW_FILE, "N" }, { "check-new", OP_CHECK_NEW, NULL }, { "toggle-mailboxes", OP_TOGGLE_MAILBOXES, "\t" }, +#ifdef USE_NNTP + { "reload-active", OP_LOAD_ACTIVE, "g" }, + { "subscribe-pattern", OP_SUBSCRIBE_PATTERN, "S" }, + { "unsubscribe-pattern", OP_UNSUBSCRIBE_PATTERN, "U" }, + { "catchup", OP_CATCHUP, "y" }, + { "uncatchup", OP_UNCATCHUP, "Y" }, +#endif { "view-file", OP_BROWSER_VIEW_FILE, " " }, { "buffy-list", OP_BUFFY_LIST, "." }, #ifdef USE_IMAP { "create-mailbox", OP_CREATE_MAILBOX, "C" }, { "delete-mailbox", OP_DELETE_MAILBOX, "d" }, { "rename-mailbox", OP_RENAME_MAILBOX, "r" }, +#endif +#if defined USE_IMAP || defined USE_NNTP { "subscribe", OP_BROWSER_SUBSCRIBE, "s" }, { "unsubscribe", OP_BROWSER_UNSUBSCRIBE, "u" }, +#endif +#ifdef USE_IMAP { "toggle-subscribed", OP_BROWSER_TOGGLE_LSUB, "T" }, #endif { NULL, 0, NULL } diff --git a/globals.h b/globals.h index 602f932..814eb05 100644 --- a/globals.h +++ b/globals.h @@ -95,6 +95,17 @@ WHERE char *MixEntryFormat; #endif WHERE char *Muttrc INITVAL (NULL); +#ifdef USE_NNTP +WHERE char *GroupFormat; +WHERE char *Inews; +WHERE char *NewsCacheDir; +WHERE char *NewsServer; +WHERE char *NewsgroupsCharset; +WHERE char *NewsRc; +WHERE char *NntpAuthenticators; +WHERE char *NntpUser; +WHERE char *NntpPass; +#endif WHERE char *Outbox; WHERE char *Pager; WHERE char *PagerFmt; @@ -196,6 +207,11 @@ extern unsigned char QuadOptions[]; WHERE unsigned short Counter INITVAL (0); +#ifdef USE_NNTP +WHERE short NewsPollTimeout; +WHERE short NntpContext; +#endif + WHERE short ConnectTimeout; WHERE short HistSize; WHERE short MenuContext; diff --git a/hash.c b/hash.c index 08f7171..983a7fd 100644 --- a/hash.c +++ b/hash.c @@ -57,6 +57,7 @@ HASH *hash_create (int nelem, int lower) if (nelem == 0) nelem = 2; table->nelem = nelem; + table->curnelem = 0; table->table = safe_calloc (nelem, sizeof (struct hash_elem *)); if (lower) { @@ -71,6 +72,29 @@ HASH *hash_create (int nelem, int lower) return table; } +HASH *hash_resize (HASH *ptr, int nelem, int lower) +{ + HASH *table; + struct hash_elem *elem, *tmp; + int i; + + table = hash_create (nelem, lower); + + for (i = 0; i < ptr->nelem; i++) + { + for (elem = ptr->table[i]; elem; ) + { + tmp = elem; + elem = elem->next; + hash_insert (table, tmp->key, tmp->data, 1); + FREE (&tmp); + } + } + FREE (&ptr->table); + FREE (&ptr); + return table; +} + /* table hash table to update * key key to hash on * data data to associate with `key' @@ -90,6 +114,7 @@ int hash_insert (HASH * table, const char *key, void *data, int allow_dup) { ptr->next = table->table[h]; table->table[h] = ptr; + table->curnelem++; } else { @@ -112,6 +137,7 @@ int hash_insert (HASH * table, const char *key, void *data, int allow_dup) else table->table[h] = ptr; ptr->next = tmp; + table->curnelem++; } return h; } @@ -142,6 +168,7 @@ void hash_delete_hash (HASH * table, int hash, const char *key, const void *data if (destroy) destroy (ptr->data); FREE (&ptr); + table->curnelem--; ptr = *last; } diff --git a/hash.h b/hash.h index fb77d0c..9e7df82 100644 --- a/hash.h +++ b/hash.h @@ -28,7 +28,7 @@ struct hash_elem typedef struct { - int nelem; + int nelem, curnelem; struct hash_elem **table; unsigned int (*hash_string)(const unsigned char *, unsigned int); int (*cmp_string)(const char *, const char *); @@ -41,6 +41,7 @@ HASH; HASH *hash_create (int nelem, int lower); int hash_insert (HASH * table, const char *key, void *data, int allow_dup); +HASH *hash_resize (HASH * table, int nelem, int lower); void *hash_find_hash (const HASH * table, int hash, const char *key); void hash_delete_hash (HASH * table, int hash, const char *key, const void *data, void (*destroy) (void *)); diff --git a/hcache.c b/hcache.c index 561dce3..d4f33b5 100644 --- a/hcache.c +++ b/hcache.c @@ -447,6 +447,12 @@ dump_envelope(ENVELOPE * e, unsigned char *d, int *off, int convert) d = dump_list(e->in_reply_to, d, off, 0); d = dump_list(e->userhdrs, d, off, convert); +#ifdef USE_NNTP + d = dump_char(e->xref, d, off, 0); + d = dump_char(e->followup_to, d, off, 0); + d = dump_char(e->x_comment_to, d, off, convert); +#endif + return d; } @@ -483,6 +489,12 @@ restore_envelope(ENVELOPE * e, const unsigned char *d, int *off, int convert) restore_list(&e->references, d, off, 0); restore_list(&e->in_reply_to, d, off, 0); restore_list(&e->userhdrs, d, off, convert); + +#ifdef USE_NNTP + restore_char(&e->xref, d, off, 0); + restore_char(&e->followup_to, d, off, 0); + restore_char(&e->x_comment_to, d, off, convert); +#endif } static int diff --git a/hdrline.c b/hdrline.c index 21adc28..c05d28a 100644 --- a/hdrline.c +++ b/hdrline.c @@ -211,6 +211,7 @@ int mutt_user_is_recipient (HEADER *h) * %E = number of messages in current thread * %f = entire from line * %F = like %n, unless from self + * %g = newsgroup name (if compiled with NNTP support) * %i = message-id * %l = number of lines in the message * %L = like %F, except `lists' are displayed first @@ -219,12 +220,14 @@ int mutt_user_is_recipient (HEADER *h) * %N = score * %O = like %L, except using address instead of name * %P = progress indicator for builtin pager + * %R = `x-comment-to:' field (if present and compiled with NNTP support) * %s = subject * %S = short message status (e.g., N/O/D/!/r/-) * %t = `to:' field (recipients) * %T = $to_chars * %u = user (login) name of author * %v = first name of author, unless from self + * %W = where user is (organization) * %X = number of MIME attachments * %y = `x-label:' field (if present) * %Y = `x-label:' field (if present, tree unfolded, and != parent's x-label) @@ -457,6 +460,12 @@ hdr_format_str (char *dest, break; +#ifdef USE_NNTP + case 'g': + mutt_format_s (dest, destlen, prefix, hdr->env->newsgroups ? hdr->env->newsgroups : ""); + break; +#endif + case 'i': mutt_format_s (dest, destlen, prefix, hdr->env->message_id ? hdr->env->message_id : ""); break; @@ -548,6 +557,15 @@ hdr_format_str (char *dest, strfcpy(dest, NONULL(hfi->pager_progress), destlen); break; +#ifdef USE_NNTP + case 'R': + if (!optional) + mutt_format_s (dest, destlen, prefix, hdr->env->x_comment_to ? hdr->env->x_comment_to : ""); + else if (!hdr->env->x_comment_to) + optional = 0; + break; +#endif + case 's': if (flags & M_FORMAT_TREE && !hdr->collapsed) @@ -637,6 +655,13 @@ hdr_format_str (char *dest, mutt_format_s (dest, destlen, prefix, buf2); break; + case 'W': + if (!optional) + mutt_format_s (dest, destlen, prefix, hdr->env->organization ? hdr->env->organization : ""); + else if (!hdr->env->organization) + optional = 0; + break; + case 'Z': ch = ' '; diff --git a/headers.c b/headers.c index f701c8e..e817dad 100644 --- a/headers.c +++ b/headers.c @@ -114,6 +114,9 @@ void mutt_edit_headers (const char *editor, $edit_headers set, we remove References: as they're likely invalid; we can simply compare strings as we don't generate References for multiple Message-Ids in IRT anyways */ +#ifdef USE_NNTP + if (!option (OPTNEWSSEND)) +#endif if (msg->env->in_reply_to && (!n->in_reply_to || mutt_strcmp (n->in_reply_to->data, msg->env->in_reply_to->data) != 0)) diff --git a/init.c b/init.c index d82772c..3de1f1f 100644 --- a/init.c +++ b/init.c @@ -3082,6 +3082,28 @@ void mutt_init (int skip_sys_rc, LIST *commands) else Fqdn = safe_strdup(NONULL(Hostname)); +#ifdef USE_NNTP + { + FILE *f; + char *i; + + if ((f = safe_fopen (SYSCONFDIR "/nntpserver", "r"))) + { + buffer[0] = '\0'; + fgets (buffer, sizeof (buffer), f); + p = &buffer; + SKIPWS (p); + i = p; + while (*i && (*i != ' ') && (*i != '\t') && (*i != '\r') && (*i != '\n')) i++; + *i = '\0'; + NewsServer = safe_strdup (p); + fclose (f); + } + } + if ((p = getenv ("NNTPSERVER"))) + NewsServer = safe_strdup (p); +#endif + if ((p = getenv ("MAIL"))) Spoolfile = safe_strdup (p); else if ((p = getenv ("MAILDIR"))) diff --git a/init.h b/init.h index a5d4238..b9cd406 100644 --- a/init.h +++ b/init.h @@ -176,6 +176,20 @@ struct option_t MuttVars[] = { ** If \fIset\fP, Mutt will prompt you for carbon-copy (Cc) recipients before ** editing the body of an outgoing message. */ +#ifdef USE_NNTP + { "ask_follow_up", DT_BOOL, R_NONE, OPTASKFOLLOWUP, 0 }, + /* + ** .pp + ** If set, Mutt will prompt you for follow-up groups before editing + ** the body of an outgoing message. + */ + { "ask_x_comment_to", DT_BOOL, R_NONE, OPTASKXCOMMENTTO, 0 }, + /* + ** .pp + ** If set, Mutt will prompt you for x-comment-to field before editing + ** the body of an outgoing message. + */ +#endif { "assumed_charset", DT_STR, R_NONE, UL &AssumedCharset, UL 0}, /* ** .pp @@ -328,6 +342,14 @@ struct option_t MuttVars[] = { ** follow these menus. The option is \fIunset\fP by default because many ** visual terminals don't permit making the cursor invisible. */ +#ifdef USE_NNTP + { "catchup_newsgroup", DT_QUAD, R_NONE, OPT_CATCHUP, M_ASKYES }, + /* + ** .pp + ** If this variable is \fIset\fP, Mutt will mark all articles in newsgroup + ** as read when you quit the newsgroup (catchup newsgroup). + */ +#endif #if defined(USE_SSL) { "certificate_file", DT_PATH, R_NONE, UL &SslCertFile, UL "~/.mutt_certificates" }, /* @@ -844,6 +866,16 @@ struct option_t MuttVars[] = { ** sent to both the list and your address, resulting in two copies ** of the same email for you. */ +#ifdef USE_NNTP + { "followup_to_poster", DT_QUAD, R_NONE, OPT_FOLLOWUPTOPOSTER, M_ASKYES }, + /* + ** .pp + ** If this variable is \fIset\fP and the keyword "poster" is present in + ** \fIFollowup-To\fP header, follow-up to newsgroup function is not + ** permitted. The message will be mailed to the submitter of the + ** message via mail. + */ +#endif { "force_name", DT_BOOL, R_NONE, OPTFORCENAME, 0 }, /* ** .pp @@ -926,6 +958,26 @@ struct option_t MuttVars[] = { ** a regular expression that will match the whole name so mutt will expand ** ``Franklin'' to ``Franklin, Steve''. */ +#ifdef USE_NNTP + { "group_index_format", DT_STR, R_BOTH, UL &GroupFormat, UL "%4C %M%N %5s %-45.45f %d" }, + /* + ** .pp + ** This variable allows you to customize the newsgroup browser display to + ** your personal taste. This string is similar to ``$index_format'', but + ** has its own set of printf()-like sequences: + ** .dl + ** .dt %C .dd current newsgroup number + ** .dt %d .dd description of newsgroup (becomes from server) + ** .dt %f .dd newsgroup name + ** .dt %M .dd - if newsgroup not allowed for direct post (moderated for example) + ** .dt %N .dd N if newsgroup is new, u if unsubscribed, blank otherwise + ** .dt %n .dd number of new articles in newsgroup + ** .dt %s .dd number of unread articles in newsgroup + ** .dt %>X .dd right justify the rest of the string and pad with character "X" + ** .dt %|X .dd pad to the end of the line with character "X" + ** .de + */ +#endif { "hdr_format", DT_SYN, R_NONE, UL "index_format", 0 }, /* */ @@ -1307,6 +1359,7 @@ struct option_t MuttVars[] = { ** .dt %E .dd number of messages in current thread ** .dt %f .dd sender (address + real name), either From: or Return-Path: ** .dt %F .dd author name, or recipient name if the message is from you + ** .dt %g .dd newsgroup name (if compiled with NNTP support) ** .dt %H .dd spam attribute(s) of this message ** .dt %i .dd message-id of the current message ** .dt %l .dd number of lines in the message (does not work with maildir, @@ -1322,12 +1375,14 @@ struct option_t MuttVars[] = { ** stashed the message: list name or recipient name ** if not sent to a list ** .dt %P .dd progress indicator for the built-in pager (how much of the file has been displayed) + ** .dt %R .dd ``X-Comment-To:'' field (if present and compiled with NNTP support) ** .dt %s .dd subject of the message ** .dt %S .dd status of the message (``N''/``D''/``d''/``!''/``r''/\(as) ** .dt %t .dd ``To:'' field (recipients) ** .dt %T .dd the appropriate character from the $$to_chars string ** .dt %u .dd user (login) name of the author ** .dt %v .dd first name of the author, or the recipient if the message is from you + ** .dt %W .dd name of organization of author (``Organization:'' field) ** .dt %X .dd number of attachments ** (please see the ``$attachments'' section for possible speed effects) ** .dt %y .dd ``X-Label:'' field, if present @@ -1362,6 +1417,25 @@ struct option_t MuttVars[] = { ** Note that these expandos are supported in ** ``$save-hook'', ``$fcc-hook'' and ``$fcc-save-hook'', too. */ +#ifdef USE_NNTP + { "inews", DT_PATH, R_NONE, UL &Inews, UL "" }, + /* + ** .pp + ** If set, specifies the program and arguments used to deliver news posted + ** by Mutt. Otherwise, mutt posts article using current connection to + ** news server. The following printf-style sequence is understood: + ** .dl + ** .dt %a .dd account url + ** .dt %p .dd port + ** .dt %P .dd port if specified + ** .dt %s .dd news server name + ** .dt %S .dd url schema + ** .dt %u .dd username + ** .de + ** .pp + ** Example: set inews="/usr/local/bin/inews -hS" + */ +#endif { "ispell", DT_PATH, R_NONE, UL &Ispell, UL ISPELL }, /* ** .pp @@ -1606,6 +1680,15 @@ struct option_t MuttVars[] = { ** menu, attachments which cannot be decoded in a reasonable manner will ** be attached to the newly composed message if this option is \fIset\fP. */ +#ifdef USE_NNTP + { "mime_subject", DT_BOOL, R_NONE, OPTMIMESUBJECT, 1 }, + /* + ** .pp + ** If \fIunset\fP, 8-bit ``subject:'' line in article header will not be + ** encoded according to RFC2047 to base64. This is useful when message + ** is Usenet article, because MIME for news is nonstandard feature. + */ +#endif #ifdef MIXMASTER { "mix_entry_format", DT_STR, R_NONE, UL &MixEntryFormat, UL "%4n %c %-16s %a" }, /* @@ -1656,6 +1739,100 @@ struct option_t MuttVars[] = { ** See also $$read_inc, $$write_inc and $$net_inc. */ #endif +#ifdef USE_NNTP + { "news_cache_dir", DT_PATH, R_NONE, UL &NewsCacheDir, UL "~/.mutt" }, + /* + ** .pp + ** This variable pointing to directory where Mutt will save cached news + ** articles and headers in. If \fIunset\fP, articles and headers will not be + ** saved at all and will be reloaded from the server each time. + */ + { "news_server", DT_STR, R_NONE, UL &NewsServer, 0 }, + /* + ** .pp + ** This variable specifies domain name or address of NNTP server. It + ** defaults to the news server specified in the environment variable + ** $$$NNTPSERVER or contained in the file /etc/nntpserver. You can also + ** specify username and an alternative port for each news server, ie: + ** .pp + ** [[s]news://][username[:password]@]server[:port] + */ + { "newsgroups_charset", DT_STR, R_NONE, UL &NewsgroupsCharset, UL "utf-8" }, + /* + ** .pp + ** Character set of newsgroups descriptions. + */ + { "newsrc", DT_PATH, R_NONE, UL &NewsRc, UL "~/.newsrc" }, + /* + ** .pp + ** The file, containing info about subscribed newsgroups - names and + ** indexes of read articles. The following printf-style sequence + ** is understood: + ** .dl + ** .dt %a .dd account url + ** .dt %p .dd port + ** .dt %P .dd port if specified + ** .dt %s .dd news server name + ** .dt %S .dd url schema + ** .dt %u .dd username + ** .de + */ + { "nntp_authenticators", DT_STR, R_NONE, UL &NntpAuthenticators, UL 0 }, + /* + ** .pp + ** This is a colon-delimited list of authentication methods mutt may + ** attempt to use to log in to a news server, in the order mutt should + ** try them. Authentication methods are either ``user'' or any + ** SASL mechanism, e.g. ``digest-md5'', ``gssapi'' or ``cram-md5''. + ** This option is case-insensitive. If it's \fIunset\fP (the default) + ** mutt will try all available methods, in order from most-secure to + ** least-secure. + ** .pp + ** Example: + ** .ts + ** set nntp_authenticators="digest-md5:user" + ** .te + ** .pp + ** \fBNote:\fP Mutt will only fall back to other authentication methods if + ** the previous methods are unavailable. If a method is available but + ** authentication fails, mutt will not connect to the IMAP server. + */ + { "nntp_context", DT_NUM, R_NONE, UL &NntpContext, 1000 }, + /* + ** .pp + ** This variable defines number of articles which will be in index when + ** newsgroup entered. If active newsgroup have more articles than this + ** number, oldest articles will be ignored. Also controls how many + ** articles headers will be saved in cache when you quit newsgroup. + */ + { "nntp_load_description", DT_BOOL, R_NONE, OPTLOADDESC, 1 }, + /* + ** .pp + ** This variable controls whether or not descriptions for each newsgroup + ** must be loaded when newsgroup is added to list (first time list + ** loading or new newsgroup adding). + */ + { "nntp_user", DT_STR, R_NONE, UL &NntpUser, UL "" }, + /* + ** .pp + ** Your login name on the NNTP server. If \fIunset\fP and NNTP server requires + ** authentification, Mutt will prompt you for your account name when you + ** connect to news server. + */ + { "nntp_pass", DT_STR, R_NONE, UL &NntpPass, UL "" }, + /* + ** .pp + ** Your password for NNTP account. + */ + { "nntp_poll", DT_NUM, R_NONE, UL &NewsPollTimeout, 60 }, + /* + ** .pp + ** The time in seconds until any operations on newsgroup except post new + ** article will cause recheck for new news. If set to 0, Mutt will + ** recheck newsgroup on each operation in index (stepping, read article, + ** etc.). + */ +#endif { "pager", DT_PATH, R_NONE, UL &Pager, UL "builtin" }, /* ** .pp @@ -2204,6 +2381,16 @@ struct option_t MuttVars[] = { { "post_indent_str", DT_SYN, R_NONE, UL "post_indent_string", 0 }, /* */ +#ifdef USE_NNTP + { "post_moderated", DT_QUAD, R_NONE, OPT_TOMODERATED, M_ASKYES }, + /* + ** .pp + ** If set to \fIyes\fP, Mutt will post article to newsgroup that have + ** not permissions to posting (e.g. moderated). \fBNote:\fP if news server + ** does not support posting to that newsgroup or totally read-only, that + ** posting will not have an effect. + */ +#endif { "postpone", DT_QUAD, R_NONE, OPT_POSTPONE, M_ASKYES }, /* ** .pp @@ -2643,6 +2830,28 @@ struct option_t MuttVars[] = { ** Command to use when spawning a subshell. By default, the user's login ** shell from \fC/etc/passwd\fP is used. */ +#ifdef USE_NNTP + { "save_unsubscribed", DT_BOOL, R_NONE, OPTSAVEUNSUB, 0 }, + /* + ** .pp + ** When \fIset\fP, info about unsubscribed newsgroups will be saved into + ** ``newsrc'' file and into cache. + */ + { "show_new_news", DT_BOOL, R_NONE, OPTSHOWNEWNEWS, 1 }, + /* + ** .pp + ** If \fIset\fP, news server will be asked for new newsgroups on entering + ** the browser. Otherwise, it will be done only once for a news server. + ** Also controls whether or not number of new articles of subscribed + ** newsgroups will be then checked. + */ + { "show_only_unread", DT_BOOL, R_NONE, OPTSHOWONLYUNREAD, 0 }, + /* + ** .pp + ** If \fIset\fP, only subscribed newsgroups that contain unread articles + ** will be displayed in browser. + */ +#endif { "sig_dashes", DT_BOOL, R_NONE, OPTSIGDASHES, 1 }, /* ** .pp @@ -3598,6 +3807,14 @@ struct option_t MuttVars[] = { {"xterm_set_titles", DT_SYN, R_NONE, UL "ts_enabled", 0 }, /* */ +#ifdef USE_NNTP + { "x_comment_to", DT_BOOL, R_NONE, OPTXCOMMENTTO, 0 }, + /* + ** .pp + ** If \fIset\fP, Mutt will add ``X-Comment-To:'' field (that contains full + ** name of original article author) to article that followuped to newsgroup. + */ +#endif /*--*/ { NULL, 0, 0, 0, 0 } }; diff --git a/keymap.c b/keymap.c index 1710b17..9d90807 100644 --- a/keymap.c +++ b/keymap.c @@ -786,7 +786,6 @@ void km_init (void) km_bindkey ("", MENU_MAIN, OP_DISPLAY_MESSAGE); km_bindkey ("x", MENU_PAGER, OP_EXIT); - km_bindkey ("i", MENU_PAGER, OP_EXIT); km_bindkey ("", MENU_PAGER, OP_PREV_LINE); km_bindkey ("", MENU_PAGER, OP_NEXT_PAGE); km_bindkey ("", MENU_PAGER, OP_PREV_PAGE); diff --git a/mailbox.h b/mailbox.h index 000503d..9052e46 100644 --- a/mailbox.h +++ b/mailbox.h @@ -77,6 +77,9 @@ int mx_is_imap (const char *); #ifdef USE_POP int mx_is_pop (const char *); #endif +#ifdef USE_NNTP +int mx_is_nntp (const char *); +#endif int mx_access (const char*, int); int mx_check_empty (const char *); diff --git a/main.c b/main.c index e71bc9b..01a8121 100644 --- a/main.c +++ b/main.c @@ -62,6 +62,10 @@ #include #endif +#ifdef USE_NNTP +#include "nntp.h" +#endif + static const char *ReachingUs = N_("\ To contact the developers, please mail to .\n\ To report a bug, please visit http://bugs.mutt.org/.\n"); @@ -136,6 +140,8 @@ options:\n\ " -e \tspecify a command to be executed after initialization\n\ -f \tspecify which mailbox to read\n\ -F \tspecify an alternate muttrc file\n\ + -g \tspecify a news server (if compiled with NNTP)\n\ + -G\t\tselect a newsgroup (if compiled with NNTP)\n\ -H \tspecify a draft file to read header and body from\n\ -i \tspecify a file which Mutt should include in the body\n\ -m \tspecify a default mailbox type\n\ @@ -284,6 +290,12 @@ static void show_version (void) "-USE_POP " #endif +#ifdef USE_NNTP + "+USE_NNTP " +#else + "-USE_NNTP " +#endif + #ifdef USE_IMAP "+USE_IMAP " #else @@ -558,6 +570,9 @@ init_extended_keys(); #define M_NOSYSRC (1<<2) /* -n */ #define M_RO (1<<3) /* -R */ #define M_SELECT (1<<4) /* -y */ +#ifdef USE_NNTP +#define M_NEWS (1<<5) /* -g and -G */ +#endif int main (int argc, char **argv) { @@ -630,7 +645,11 @@ int main (int argc, char **argv) argv[nargc++] = argv[optind]; } +#ifdef USE_NNTP + if ((i = getopt (argc, argv, "+A:a:b:F:f:c:Dd:e:g:GH:s:i:hm:npQ:RvxyzZ")) != EOF) +#else if ((i = getopt (argc, argv, "+A:a:b:F:f:c:Dd:e:H:s:i:hm:npQ:RvxyzZ")) != EOF) +#endif switch (i) { case 'A': @@ -727,6 +746,20 @@ int main (int argc, char **argv) flags |= M_SELECT; break; +#ifdef USE_NNTP + case 'g': /* Specify a news server */ + { + char buf[LONG_STRING]; + + snprintf (buf, sizeof (buf), "set news_server=%s", optarg); + commands = mutt_add_list (commands, buf); + } + + case 'G': /* List of newsgroups */ + flags |= M_SELECT | M_NEWS; + break; +#endif + case 'z': flags |= M_IGNORE; break; @@ -1025,6 +1058,18 @@ int main (int argc, char **argv) } else if (flags & M_SELECT) { +#ifdef USE_NNTP + if (flags & M_NEWS) + { + set_option (OPTNEWS); + if(!(CurrentNewsSrv = nntp_select_server (NewsServer, 0))) + { + mutt_endwin (Errorbuf); + exit (1); + } + } + else +#endif if (!Incoming) { mutt_endwin _("No incoming mailboxes defined."); exit (1); @@ -1040,6 +1085,15 @@ int main (int argc, char **argv) if (!folder[0]) strfcpy (folder, NONULL(Spoolfile), sizeof (folder)); + +#ifdef USE_NNTP + if (option (OPTNEWS)) + { + unset_option (OPTNEWS); + nntp_expand_path (folder, sizeof (folder), &CurrentNewsSrv->conn->account); + } + else +#endif mutt_expand_path (folder, sizeof (folder)); mutt_str_replace (&CurrentFolder, folder); diff --git a/mutt.h b/mutt.h index d73e514..1ee1583 100644 --- a/mutt.h +++ b/mutt.h @@ -239,6 +239,9 @@ enum M_PGP_KEY, M_XLABEL, M_MIMEATTACH, +#ifdef USE_NNTP + M_NEWSGROUPS, +#endif /* Options for Mailcap lookup */ M_EDIT, @@ -295,6 +298,11 @@ enum #endif OPT_SUBJECT, OPT_VERIFYSIG, /* verify PGP signatures */ +#ifdef USE_NNTP + OPT_TOMODERATED, + OPT_CATCHUP, + OPT_FOLLOWUPTOPOSTER, +#endif /* THIS MUST BE THE LAST VALUE. */ OPT_MAX @@ -311,6 +319,7 @@ enum #define SENDKEY (1<<7) #define SENDRESEND (1<<8) #define SENDPOSTPONEDFCC (1<<9) /* used by mutt_get_postponed() to signal that the x-mutt-fcc header field was present */ +#define SENDNEWS (1<<10) /* flags to _mutt_select_file() */ #define M_SEL_BUFFY (1<<0) @@ -330,6 +339,8 @@ enum OPTASCIICHARS, OPTASKBCC, OPTASKCC, + OPTASKFOLLOWUP, + OPTASKXCOMMENTTO, OPTATTACHSPLIT, OPTAUTOEDIT, OPTAUTOTAG, @@ -411,6 +422,9 @@ enum OPTMETOO, OPTMHPURGE, OPTMIMEFORWDECODE, +#ifdef USE_NNTP + OPTMIMESUBJECT, /* encode subject line with RFC2047 */ +#endif OPTNARROWTREE, OPTPAGERSTOP, OPTPIPEDECODE, @@ -499,6 +513,16 @@ enum OPTPGPAUTOINLINE, OPTPGPREPLYINLINE, + /* news options */ + +#ifdef USE_NNTP + OPTSHOWNEWNEWS, + OPTSHOWONLYUNREAD, + OPTSAVEUNSUB, + OPTLOADDESC, + OPTXCOMMENTTO, +#endif + /* pseudo options */ OPTAUXSORT, /* (pseudo) using auxiliary sort function */ @@ -519,6 +543,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. @@ -531,6 +556,11 @@ enum OPTSIDEBARNEWMAILONLY, +#ifdef USE_NNTP + OPTNEWS, /* (pseudo) used to change reader mode */ + OPTNEWSSEND, /* (pseudo) used to change behavior when posting */ +#endif + OPTMAX }; @@ -610,6 +640,13 @@ typedef struct envelope char *supersedes; char *date; char *x_label; + char *organization; +#ifdef USE_NNTP + char *newsgroups; + char *xref; + char *followup_to; + char *x_comment_to; +#endif BUFFER *spam; LIST *references; /* message references (in reverse order) */ LIST *in_reply_to; /* in-reply-to header content */ @@ -796,7 +833,7 @@ typedef struct header int refno; /* message number on server */ #endif -#if defined USE_POP || defined USE_IMAP +#if defined USE_POP || defined USE_IMAP || defined USE_NNTP void *data; /* driver-specific data */ #endif diff --git a/mutt_sasl.c b/mutt_sasl.c index 7d7388c..0ba4f4e 100644 --- a/mutt_sasl.c +++ b/mutt_sasl.c @@ -190,6 +190,11 @@ int mutt_sasl_client_new (CONNECTION* conn, sasl_conn_t** saslconn) case M_ACCT_TYPE_SMTP: service = "smtp"; break; +#ifdef USE_NNTP + case M_ACCT_TYPE_NNTP: + service = "nntp"; + break; +#endif default: mutt_error (_("Unknown SASL profile")); return -1; diff --git a/muttlib.c b/muttlib.c index 039e7c3..cc3a681 100644 --- a/muttlib.c +++ b/muttlib.c @@ -329,7 +329,7 @@ void mutt_free_header (HEADER **h) #ifdef MIXMASTER mutt_free_list (&(*h)->chain); #endif -#if defined USE_POP || defined USE_IMAP +#if defined USE_POP || defined USE_IMAP || defined USE_NNTP FREE (&(*h)->data); #endif FREE (h); /* __FREE_CHECKED__ */ @@ -717,6 +717,13 @@ void mutt_free_envelope (ENVELOPE **p) FREE (&(*p)->supersedes); FREE (&(*p)->date); FREE (&(*p)->x_label); + FREE (&(*p)->organization); +#ifdef USE_NNTP + FREE (&(*p)->newsgroups); + FREE (&(*p)->xref); + FREE (&(*p)->followup_to); + FREE (&(*p)->x_comment_to); +#endif mutt_buffer_free (&(*p)->spam); @@ -1568,6 +1575,14 @@ int mutt_save_confirm (const char *s, struct stat *st) } } +#ifdef USE_NNTP + if (magic == M_NNTP) + { + mutt_error _("Can't save message to news server."); + return 0; + } +#endif + if (stat (s, st) != -1) { if (magic == -1) diff --git a/mx.c b/mx.c index e80b8ff..b2ac0b7 100644 --- a/mx.c +++ b/mx.c @@ -347,6 +347,22 @@ int mx_is_pop (const char *p) } #endif +#ifdef USE_NNTP +int mx_is_nntp (const char *p) +{ + url_scheme_t scheme; + + if (!p) + return 0; + + scheme = url_check_scheme (p); + if (scheme == U_NNTP || scheme == U_NNTPS) + return 1; + + return 0; +} +#endif + int mx_get_magic (const char *path) { struct stat st; @@ -364,6 +380,11 @@ int mx_get_magic (const char *path) return M_POP; #endif /* USE_POP */ +#ifdef USE_NNTP + if (mx_is_nntp (path)) + return M_NNTP; +#endif /* USE_NNTP */ + if (stat (path, &st) == -1) { dprint (1, (debugfile, "mx_get_magic(): unable to stat %s: %s (errno %d).\n", @@ -691,6 +712,12 @@ CONTEXT *mx_open_mailbox (const char *path, int flags, CONTEXT *pctx) break; #endif /* USE_POP */ +#ifdef USE_NNTP + case M_NNTP: + rc = nntp_open_mailbox (ctx); + break; +#endif /* USE_NNTP */ + default: rc = -1; break; @@ -803,6 +830,12 @@ static int sync_mailbox (CONTEXT *ctx, int *index_hint) rc = pop_sync_mailbox (ctx, index_hint); break; #endif /* USE_POP */ + +#ifdef USE_NNTP + case M_NNTP: + rc = nntp_sync_mailbox (ctx); + break; +#endif /* USE_NNTP */ } #if 0 @@ -905,6 +938,25 @@ int mx_close_mailbox (CONTEXT *ctx, int *index_hint) return 0; } +#ifdef USE_NNTP + if (ctx->unread && ctx->magic == M_NNTP) + { + NNTP_DATA *nntp_data = ctx->data; + + if (nntp_data && nntp_data->nserv && nntp_data->group) + { + int rc = query_quadoption (OPT_CATCHUP, _("Mark all articles read?")); + if (rc < 0) + { + ctx->closing = 0; + return -1; + } + else if (rc == M_YES) + mutt_newsgroup_catchup (nntp_data->nserv, nntp_data->group); + } + } +#endif + for (i = 0; i < ctx->msgcount; i++) { if (!ctx->hdrs[i]->deleted && ctx->hdrs[i]->read @@ -912,6 +964,12 @@ int mx_close_mailbox (CONTEXT *ctx, int *index_hint) read_msgs++; } +#ifdef USE_NNTP + /* don't need to move articles from newsgroup */ + if (ctx->magic == M_NNTP) + read_msgs = 0; +#endif + if (read_msgs && quadoption (OPT_MOVE) != M_NO) { char *p; @@ -1465,6 +1523,11 @@ int mx_check_mailbox (CONTEXT *ctx, int *index_hint, int lock) case M_POP: return (pop_check_mailbox (ctx, index_hint)); #endif /* USE_POP */ + +#ifdef USE_NNTP + case M_NNTP: + return (nntp_check_mailbox (ctx, 0)); +#endif /* USE_NNTP */ } } @@ -1525,6 +1588,15 @@ MESSAGE *mx_open_message (CONTEXT *ctx, int msgno) } #endif /* USE_POP */ +#ifdef USE_NNTP + case M_NNTP: + { + if (nntp_fetch_message (msg, ctx, msgno) != 0) + FREE (&msg); + break; + } +#endif /* USE_NNTP */ + default: dprint (1, (debugfile, "mx_open_message(): function not implemented for mailbox type %d.\n", ctx->magic)); FREE (&msg); @@ -1600,6 +1672,9 @@ int mx_close_message (MESSAGE **msg) int r = 0; if ((*msg)->magic == M_MH || (*msg)->magic == M_MAILDIR +#ifdef USE_NNTP + || (*msg)->magic == M_NNTP +#endif || (*msg)->magic == M_IMAP || (*msg)->magic == M_POP) { r = safe_fclose (&(*msg)->fp); diff --git a/mx.h b/mx.h index 4aabadf..9980c4e 100644 --- a/mx.h +++ b/mx.h @@ -34,6 +34,9 @@ enum M_MMDF, M_MH, M_MAILDIR, +#ifdef USE_NNTP + M_NNTP, +#endif M_IMAP, M_POP #ifdef USE_COMPRESSED diff --git a/newsrc.c b/newsrc.c new file mode 100644 index 0000000..482214c --- /dev/null +++ b/newsrc.c @@ -0,0 +1,1260 @@ +/* + * Copyright (C) 1998 Brandon Long + * Copyright (C) 1999 Andrej Gritsenko + * Copyright (C) 2000-2012 Vsevolod Volkov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include "mutt.h" +#include "mutt_curses.h" +#include "sort.h" +#include "mx.h" +#include "mime.h" +#include "mailbox.h" +#include "nntp.h" +#include "rfc822.h" +#include "rfc1524.h" +#include "rfc2047.h" +#include "bcache.h" + +#if USE_HCACHE +#include "hcache.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Find NNTP_DATA for given newsgroup or add it */ +static NNTP_DATA *nntp_data_find (NNTP_SERVER *nserv, const char *group) +{ + NNTP_DATA *nntp_data = hash_find (nserv->groups_hash, group); + + if (!nntp_data) + { + /* create NNTP_DATA structure and add it to hash */ + nntp_data = safe_calloc (1, sizeof (NNTP_DATA) + strlen (group) + 1); + nntp_data->group = (char *)nntp_data + sizeof (NNTP_DATA); + strcpy (nntp_data->group, group); + nntp_data->nserv = nserv; + nntp_data->deleted = 1; + if (nserv->groups_hash->nelem < nserv->groups_hash->curnelem * 2) + nserv->groups_hash = hash_resize (nserv->groups_hash, + nserv->groups_hash->nelem * 2, 0); + hash_insert (nserv->groups_hash, nntp_data->group, nntp_data, 0); + + /* add NNTP_DATA to list */ + if (nserv->groups_num >= nserv->groups_max) + { + nserv->groups_max *= 2; + safe_realloc (&nserv->groups_list, + nserv->groups_max * sizeof (nntp_data)); + } + nserv->groups_list[nserv->groups_num++] = nntp_data; + } + return nntp_data; +} + +/* Remove all temporarily cache files */ +void nntp_acache_free (NNTP_DATA *nntp_data) +{ + int i; + + for (i = 0; i < NNTP_ACACHE_LEN; i++) + { + if (nntp_data->acache[i].path) + { + unlink (nntp_data->acache[i].path); + FREE (&nntp_data->acache[i].path); + } + } +} + +/* Free NNTP_DATA, used to destroy hash elements */ +void nntp_data_free (void *data) +{ + NNTP_DATA *nntp_data = data; + + if (!nntp_data) + return; + nntp_acache_free (nntp_data); + mutt_bcache_close (&nntp_data->bcache); + FREE (&nntp_data->newsrc_ent); + FREE (&nntp_data->desc); + FREE (&data); +} + +/* Unlock and close .newsrc file */ +void nntp_newsrc_close (NNTP_SERVER *nserv) +{ + if (!nserv->newsrc_fp) + return; + + dprint (1, (debugfile, "Unlocking %s\n", nserv->newsrc_file)); + mx_unlock_file (nserv->newsrc_file, fileno (nserv->newsrc_fp), 0); + safe_fclose (&nserv->newsrc_fp); +} + +/* Parse .newsrc file: + * 0 - not changed + * 1 - parsed + * -1 - error */ +int nntp_newsrc_parse (NNTP_SERVER *nserv) +{ + unsigned int i; + char *line; + struct stat sb; + + /* if file doesn't exist, create it */ + nserv->newsrc_fp = safe_fopen (nserv->newsrc_file, "a"); + safe_fclose (&nserv->newsrc_fp); + + /* open .newsrc */ + nserv->newsrc_fp = safe_fopen (nserv->newsrc_file, "r"); + if (!nserv->newsrc_fp) + { + mutt_perror (nserv->newsrc_file); + mutt_sleep (2); + return -1; + } + + /* lock it */ + dprint (1, (debugfile, "Locking %s\n", nserv->newsrc_file)); + if (mx_lock_file (nserv->newsrc_file, fileno (nserv->newsrc_fp), 0, 0, 1)) + { + safe_fclose (&nserv->newsrc_fp); + return -1; + } + + if (stat (nserv->newsrc_file, &sb)) + { + mutt_perror (nserv->newsrc_file); + nntp_newsrc_close (nserv); + mutt_sleep (2); + return -1; + } + + if (nserv->size == sb.st_size && nserv->mtime == sb.st_mtime) + return 0; + + nserv->size = sb.st_size; + nserv->mtime = sb.st_mtime; + nserv->newsrc_modified = 1; + dprint (1, (debugfile, "Parsing %s\n", nserv->newsrc_file)); + + /* .newsrc has been externally modified or hasn't been loaded yet */ + for (i = 0; i < nserv->groups_num; i++) + { + NNTP_DATA *nntp_data = nserv->groups_list[i]; + + if (!nntp_data) + continue; + + nntp_data->subscribed = 0; + nntp_data->newsrc_len = 0; + FREE (&nntp_data->newsrc_ent); + } + + line = safe_malloc (sb.st_size + 1); + while (sb.st_size && fgets (line, sb.st_size + 1, nserv->newsrc_fp)) + { + char *b, *h, *p; + unsigned int subs = 0, i = 1; + NNTP_DATA *nntp_data; + + /* find end of newsgroup name */ + p = strpbrk (line, ":!"); + if (!p) + continue; + + /* ":" - subscribed, "!" - unsubscribed */ + if (*p == ':') + subs++; + *p++ = '\0'; + + /* get newsgroup data */ + nntp_data = nntp_data_find (nserv, line); + FREE (&nntp_data->newsrc_ent); + + /* count number of entries */ + b = p; + while (*b) + if (*b++ == ',') + i++; + nntp_data->newsrc_ent = safe_calloc (i, sizeof (NEWSRC_ENTRY)); + nntp_data->subscribed = subs; + + /* parse entries */ + i = 0; + while (p) + { + b = p; + + /* find end of entry */ + p = strchr (p, ','); + if (p) + *p++ = '\0'; + + /* first-last or single number */ + h = strchr (b, '-'); + if (h) + *h++ = '\0'; + else + h = b; + + if (sscanf (b, ANUM, &nntp_data->newsrc_ent[i].first) == 1 && + sscanf (h, ANUM, &nntp_data->newsrc_ent[i].last) == 1) + i++; + } + if (i == 0) + { + nntp_data->newsrc_ent[i].first = 1; + nntp_data->newsrc_ent[i].last = 0; + i++; + } + if (nntp_data->lastMessage == 0) + nntp_data->lastMessage = nntp_data->newsrc_ent[i - 1].last; + nntp_data->newsrc_len = i; + safe_realloc (&nntp_data->newsrc_ent, i * sizeof (NEWSRC_ENTRY)); + nntp_group_unread_stat (nntp_data); + dprint (2, (debugfile, "nntp_newsrc_parse: %s\n", nntp_data->group)); + } + FREE (&line); + return 1; +} + +/* Generate array of .newsrc entries */ +void nntp_newsrc_gen_entries (CONTEXT *ctx) +{ + NNTP_DATA *nntp_data = ctx->data; + anum_t last = 0, first = 1; + int series, i; + int save_sort = SORT_ORDER; + unsigned int entries; + + if (Sort != SORT_ORDER) + { + save_sort = Sort; + Sort = SORT_ORDER; + mutt_sort_headers (ctx, 0); + } + + entries = nntp_data->newsrc_len; + if (!entries) + { + entries = 5; + nntp_data->newsrc_ent = safe_calloc (entries, sizeof (NEWSRC_ENTRY)); + } + + /* Set up to fake initial sequence from 1 to the article before the + * first article in our list */ + nntp_data->newsrc_len = 0; + series = 1; + for (i = 0; i < ctx->msgcount; i++) + { + /* search for first unread */ + if (series) + { + /* We don't actually check sequential order, since we mark + * "missing" entries as read/deleted */ + last = NHDR (ctx->hdrs[i])->article_num; + if (last >= nntp_data->firstMessage && !ctx->hdrs[i]->deleted && + !ctx->hdrs[i]->read) + { + if (nntp_data->newsrc_len >= entries) + { + entries *= 2; + safe_realloc (&nntp_data->newsrc_ent, entries * sizeof (NEWSRC_ENTRY)); + } + nntp_data->newsrc_ent[nntp_data->newsrc_len].first = first; + nntp_data->newsrc_ent[nntp_data->newsrc_len].last = last - 1; + nntp_data->newsrc_len++; + series = 0; + } + } + + /* search for first read */ + else + { + if (ctx->hdrs[i]->deleted || ctx->hdrs[i]->read) + { + first = last + 1; + series = 1; + } + last = NHDR (ctx->hdrs[i])->article_num; + } + } + + if (series && first <= nntp_data->lastLoaded) + { + if (nntp_data->newsrc_len >= entries) + { + entries++; + safe_realloc (&nntp_data->newsrc_ent, entries * sizeof (NEWSRC_ENTRY)); + } + nntp_data->newsrc_ent[nntp_data->newsrc_len].first = first; + nntp_data->newsrc_ent[nntp_data->newsrc_len].last = nntp_data->lastLoaded; + nntp_data->newsrc_len++; + } + safe_realloc (&nntp_data->newsrc_ent, + nntp_data->newsrc_len * sizeof (NEWSRC_ENTRY)); + + if (save_sort != Sort) + { + Sort = save_sort; + mutt_sort_headers (ctx, 0); + } +} + +/* Update file with new contents */ +static int update_file (char *filename, char *buf) +{ + FILE *fp; + char tmpfile[_POSIX_PATH_MAX]; + int rc = -1; + + while (1) + { + snprintf (tmpfile, sizeof (tmpfile), "%s.tmp", filename); + fp = fopen (tmpfile, "w"); + if (!fp) + { + mutt_perror (tmpfile); + *tmpfile = '\0'; + break; + } + if (fputs (buf, fp) == EOF) + { + mutt_perror (tmpfile); + break; + } + if (fclose (fp) == EOF) + { + mutt_perror (tmpfile); + fp = NULL; + break; + } + fp = NULL; + if (rename (tmpfile, filename) < 0) + { + mutt_perror (filename); + break; + } + *tmpfile = '\0'; + rc = 0; + break; + } + if (fp) + fclose (fp); + if (*tmpfile) + unlink (tmpfile); + if (rc) + mutt_sleep (2); + return rc; +} + +/* Update .newsrc file */ +int nntp_newsrc_update (NNTP_SERVER *nserv) +{ + char *buf; + size_t buflen, off; + unsigned int i; + int rc = -1; + + if (!nserv) + return -1; + + buflen = 10 * LONG_STRING; + buf = safe_calloc (1, buflen); + off = 0; + + /* we will generate full newsrc here */ + for (i = 0; i < nserv->groups_num; i++) + { + NNTP_DATA *nntp_data = nserv->groups_list[i]; + unsigned int n; + + if (!nntp_data || !nntp_data->newsrc_ent) + continue; + + /* write newsgroup name */ + if (off + strlen (nntp_data->group) + 3 > buflen) + { + buflen *= 2; + safe_realloc (&buf, buflen); + } + snprintf (buf + off, buflen - off, "%s%c ", nntp_data->group, + nntp_data->subscribed ? ':' : '!'); + off += strlen (buf + off); + + /* write entries */ + for (n = 0; n < nntp_data->newsrc_len; n++) + { + if (off + LONG_STRING > buflen) + { + buflen *= 2; + safe_realloc (&buf, buflen); + } + if (n) + buf[off++] = ','; + if (nntp_data->newsrc_ent[n].first == nntp_data->newsrc_ent[n].last) + snprintf (buf + off, buflen - off, "%d", nntp_data->newsrc_ent[n].first); + else if (nntp_data->newsrc_ent[n].first < nntp_data->newsrc_ent[n].last) + snprintf (buf + off, buflen - off, "%d-%d", + nntp_data->newsrc_ent[n].first, nntp_data->newsrc_ent[n].last); + off += strlen (buf + off); + } + buf[off++] = '\n'; + } + buf[off] = '\0'; + + /* newrc being fully rewritten */ + dprint (1, (debugfile, "Updating %s\n", nserv->newsrc_file)); + if (nserv->newsrc_file && update_file (nserv->newsrc_file, buf) == 0) + { + struct stat sb; + + rc = stat (nserv->newsrc_file, &sb); + if (rc == 0) + { + nserv->size = sb.st_size; + nserv->mtime = sb.st_mtime; + } + else + { + mutt_perror (nserv->newsrc_file); + mutt_sleep (2); + } + } + FREE (&buf); + return rc; +} + +/* Make fully qualified cache file name */ +static void cache_expand (char *dst, size_t dstlen, ACCOUNT *acct, char *src) +{ + char *c; + char file[_POSIX_PATH_MAX]; + + /* server subdirectory */ + if (acct) + { + ciss_url_t url; + + mutt_account_tourl (acct, &url); + url.path = src; + url_ciss_tostring (&url, file, sizeof (file), U_PATH); + } + else + strfcpy (file, src ? src : "", sizeof (file)); + + snprintf (dst, dstlen, "%s/%s", NewsCacheDir, file); + + /* remove trailing slash */ + c = dst + strlen (dst) - 1; + if (*c == '/') + *c = '\0'; + mutt_expand_path (dst, dstlen); +} + +/* Make fully qualified url from newsgroup name */ +void nntp_expand_path (char *line, size_t len, ACCOUNT *acct) +{ + ciss_url_t url; + + url.path = safe_strdup (line); + mutt_account_tourl (acct, &url); + url_ciss_tostring (&url, line, len, 0); + FREE (&url.path); +} + +/* Parse newsgroup */ +int nntp_add_group (char *line, void *data) +{ + NNTP_SERVER *nserv = data; + NNTP_DATA *nntp_data; + char group[LONG_STRING]; + char desc[HUGE_STRING] = ""; + char mod; + anum_t first, last; + + if (!nserv || !line) + return 0; + + if (sscanf (line, "%s " ANUM " " ANUM " %c %[^\n]", group, + &last, &first, &mod, desc) < 4) + return 0; + + nntp_data = nntp_data_find (nserv, group); + nntp_data->deleted = 0; + nntp_data->firstMessage = first; + nntp_data->lastMessage = last; + nntp_data->allowed = mod == 'y' || mod == 'm' ? 1 : 0; + mutt_str_replace (&nntp_data->desc, desc); + if (nntp_data->newsrc_ent || nntp_data->lastCached) + nntp_group_unread_stat (nntp_data); + else if (nntp_data->lastMessage && + nntp_data->firstMessage <= nntp_data->lastMessage) + nntp_data->unread = nntp_data->lastMessage - nntp_data->firstMessage + 1; + else + nntp_data->unread = 0; + return 0; +} + +/* Load list of all newsgroups from cache */ +static int active_get_cache (NNTP_SERVER *nserv) +{ + char buf[HUGE_STRING]; + char file[_POSIX_PATH_MAX]; + time_t t; + FILE *fp; + + cache_expand (file, sizeof (file), &nserv->conn->account, ".active"); + dprint (1, (debugfile, "Parsing %s\n", file)); + fp = safe_fopen (file, "r"); + if (!fp) + return -1; + + if (fgets (buf, sizeof (buf), fp) == NULL || + sscanf (buf, "%ld%s", &t, file) != 1 || t == 0) + { + fclose (fp); + return -1; + } + nserv->newgroups_time = t; + + mutt_message _("Loading list of groups from cache..."); + while (fgets (buf, sizeof (buf), fp)) + nntp_add_group (buf, nserv); + nntp_add_group (NULL, NULL); + fclose (fp); + mutt_clear_error (); + return 0; +} + +/* Save list of all newsgroups to cache */ +int nntp_active_save_cache (NNTP_SERVER *nserv) +{ + char file[_POSIX_PATH_MAX]; + char *buf; + size_t buflen, off; + unsigned int i; + int rc; + + if (!nserv->cacheable) + return 0; + + buflen = 10 * LONG_STRING; + buf = safe_calloc (1, buflen); + snprintf (buf, buflen, "%lu\n", (unsigned long)nserv->newgroups_time); + off = strlen (buf); + + for (i = 0; i < nserv->groups_num; i++) + { + NNTP_DATA *nntp_data = nserv->groups_list[i]; + + if (!nntp_data || nntp_data->deleted) + continue; + + if (off + strlen (nntp_data->group) + + (nntp_data->desc ? strlen (nntp_data->desc) : 0) + 50 > buflen) + { + buflen *= 2; + safe_realloc (&buf, buflen); + } + snprintf (buf + off, buflen - off, "%s %d %d %c%s%s\n", nntp_data->group, + nntp_data->lastMessage, nntp_data->firstMessage, + nntp_data->allowed ? 'y' : 'n', nntp_data->desc ? " " : "", + nntp_data->desc ? nntp_data->desc : ""); + off += strlen (buf + off); + } + + cache_expand (file, sizeof (file), &nserv->conn->account, ".active"); + dprint (1, (debugfile, "Updating %s\n", file)); + rc = update_file (file, buf); + FREE (&buf); + return rc; +} + +#ifdef USE_HCACHE +/* Used by mutt_hcache_open() to compose hcache file name */ +static int nntp_hcache_namer (const char *path, char *dest, size_t destlen) +{ + return snprintf (dest, destlen, "%s.hcache", path); +} + +/* Open newsgroup hcache */ +header_cache_t *nntp_hcache_open (NNTP_DATA *nntp_data) +{ + ciss_url_t url; + char file[_POSIX_PATH_MAX]; + + if (!nntp_data->nserv || !nntp_data->nserv->cacheable || + !nntp_data->nserv->conn || !nntp_data->group || + !(nntp_data->newsrc_ent || nntp_data->subscribed || + option (OPTSAVEUNSUB))) + return NULL; + + mutt_account_tourl (&nntp_data->nserv->conn->account, &url); + url.path = nntp_data->group; + url_ciss_tostring (&url, file, sizeof (file), U_PATH); + return mutt_hcache_open (NewsCacheDir, file, nntp_hcache_namer); +} + +/* Remove stale cached headers */ +void nntp_hcache_update (NNTP_DATA *nntp_data, header_cache_t *hc) +{ + char buf[16]; + int old = 0; + void *hdata; + anum_t first, last, current; + + if (!hc) + return; + + /* fetch previous values of first and last */ + hdata = mutt_hcache_fetch_raw (hc, "index", strlen); + if (hdata) + { + dprint (2, (debugfile, + "nntp_hcache_update: mutt_hcache_fetch index: %s\n", hdata)); + if (sscanf (hdata, ANUM " " ANUM, &first, &last) == 2) + { + old = 1; + nntp_data->lastCached = last; + + /* clean removed headers from cache */ + for (current = first; current <= last; current++) + { + if (current >= nntp_data->firstMessage && + current <= nntp_data->lastMessage) + continue; + + snprintf (buf, sizeof (buf), "%d", current); + dprint (2, (debugfile, + "nntp_hcache_update: mutt_hcache_delete %s\n", buf)); + mutt_hcache_delete (hc, buf, strlen); + } + } + } + + /* store current values of first and last */ + if (!old || nntp_data->firstMessage != first || + nntp_data->lastMessage != last) + { + snprintf (buf, sizeof (buf), "%u %u", nntp_data->firstMessage, + nntp_data->lastMessage); + dprint (2, (debugfile, + "nntp_hcache_update: mutt_hcache_store index: %s\n", buf)); + mutt_hcache_store_raw (hc, "index", buf, strlen (buf) + 1, strlen); + } +} +#endif + +/* Remove bcache file */ +static int nntp_bcache_delete (const char *id, body_cache_t *bcache, void *data) +{ + NNTP_DATA *nntp_data = data; + anum_t anum; + char c; + + if (!nntp_data || sscanf (id, ANUM "%c", &anum, &c) != 1 || + anum < nntp_data->firstMessage || anum > nntp_data->lastMessage) + { + if (nntp_data) + dprint (2, (debugfile, "nntp_bcache_delete: mutt_bcache_del %s\n", id)); + mutt_bcache_del (bcache, id); + } + return 0; +} + +/* Remove stale cached messages */ +void nntp_bcache_update (NNTP_DATA *nntp_data) +{ + mutt_bcache_list (nntp_data->bcache, nntp_bcache_delete, nntp_data); +} + +/* Remove hcache and bcache of newsgroup */ +void nntp_delete_group_cache (NNTP_DATA *nntp_data) +{ + char file[_POSIX_PATH_MAX]; + + if (!nntp_data || !nntp_data->nserv || !nntp_data->nserv->cacheable) + return; + +#ifdef USE_HCACHE + nntp_hcache_namer (nntp_data->group, file, sizeof (file)); + cache_expand (file, sizeof (file), &nntp_data->nserv->conn->account, file); + unlink (file); + nntp_data->lastCached = 0; + dprint (2, (debugfile, "nntp_delete_group_cache: %s\n", file)); +#endif + + if (!nntp_data->bcache) + nntp_data->bcache = mutt_bcache_open (&nntp_data->nserv->conn->account, + nntp_data->group); + if (nntp_data->bcache) + { + dprint (2, (debugfile, "nntp_delete_group_cache: %s/*\n", nntp_data->group)); + mutt_bcache_list (nntp_data->bcache, nntp_bcache_delete, NULL); + mutt_bcache_close (&nntp_data->bcache); + } +} + +/* Remove hcache and bcache of all unexistent and unsubscribed newsgroups */ +void nntp_clear_cache (NNTP_SERVER *nserv) +{ + char file[_POSIX_PATH_MAX]; + char *fp; + struct dirent *entry; + DIR *dp; + + if (!nserv || !nserv->cacheable) + return; + + cache_expand (file, sizeof (file), &nserv->conn->account, NULL); + dp = opendir (file); + if (dp) + { + safe_strncat (file, sizeof (file), "/", 1); + fp = file + strlen (file); + while ((entry = readdir (dp))) + { + char *group = entry->d_name; + struct stat sb; + NNTP_DATA *nntp_data; + NNTP_DATA nntp_tmp; + + if (mutt_strcmp (group, ".") == 0 || + mutt_strcmp (group, "..") == 0) + continue; + *fp = '\0'; + safe_strncat (file, sizeof (file), group, strlen (group)); + if (stat (file, &sb)) + continue; + +#ifdef USE_HCACHE + if (S_ISREG (sb.st_mode)) + { + char *ext = group + strlen (group) - 7; + if (strlen (group) < 8 || mutt_strcmp (ext, ".hcache")) + continue; + *ext = '\0'; + } + else +#endif + if (!S_ISDIR (sb.st_mode)) + continue; + + nntp_data = hash_find (nserv->groups_hash, group); + if (!nntp_data) + { + nntp_data = &nntp_tmp; + nntp_data->nserv = nserv; + nntp_data->group = group; + nntp_data->bcache = NULL; + } + else if (nntp_data->newsrc_ent || nntp_data->subscribed || + option (OPTSAVEUNSUB)) + continue; + + nntp_delete_group_cache (nntp_data); + if (S_ISDIR (sb.st_mode)) + { + rmdir (file); + dprint (2, (debugfile, "nntp_clear_cache: %s\n", file)); + } + } + closedir (dp); + } + return; +} + +/* %a = account url + * %p = port + * %P = port if specified + * %s = news server name + * %S = url schema + * %u = username */ +const char * +nntp_format_str (char *dest, size_t destlen, size_t col, char op, const char *src, + const char *fmt, const char *ifstring, const char *elsestring, + unsigned long data, format_flag flags) +{ + NNTP_SERVER *nserv = (NNTP_SERVER *)data; + ACCOUNT *acct = &nserv->conn->account; + ciss_url_t url; + char fn[SHORT_STRING], tmp[SHORT_STRING], *p; + + switch (op) + { + case 'a': + mutt_account_tourl (acct, &url); + url_ciss_tostring (&url, fn, sizeof (fn), U_PATH); + p = strchr (fn, '/'); + if (p) + *p = '\0'; + snprintf (tmp, sizeof (tmp), "%%%ss", fmt); + snprintf (dest, destlen, tmp, fn); + break; + case 'p': + snprintf (tmp, sizeof (tmp), "%%%su", fmt); + snprintf (dest, destlen, tmp, acct->port); + break; + case 'P': + *dest = '\0'; + if (acct->flags & M_ACCT_PORT) + { + snprintf (tmp, sizeof (tmp), "%%%su", fmt); + snprintf (dest, destlen, tmp, acct->port); + } + break; + case 's': + strncpy (fn, acct->host, sizeof (fn) - 1); + mutt_strlower (fn); + snprintf (tmp, sizeof (tmp), "%%%ss", fmt); + snprintf (dest, destlen, tmp, fn); + break; + case 'S': + mutt_account_tourl (acct, &url); + url_ciss_tostring (&url, fn, sizeof (fn), U_PATH); + p = strchr (fn, ':'); + if (p) + *p = '\0'; + snprintf (tmp, sizeof (tmp), "%%%ss", fmt); + snprintf (dest, destlen, tmp, fn); + break; + case 'u': + snprintf (tmp, sizeof (tmp), "%%%ss", fmt); + snprintf (dest, destlen, tmp, acct->user); + break; + } + return (src); +} + +/* Automatically loads a newsrc into memory, if necessary. + * Checks the size/mtime of a newsrc file, if it doesn't match, load + * again. Hmm, if a system has broken mtimes, this might mean the file + * is reloaded every time, which we'd have to fix. */ +NNTP_SERVER *nntp_select_server (char *server, int leave_lock) +{ + char file[_POSIX_PATH_MAX]; + char *p; + int rc; + struct stat sb; + ACCOUNT acct; + NNTP_SERVER *nserv; + NNTP_DATA *nntp_data; + CONNECTION *conn; + ciss_url_t url; + + if (!server || !*server) + { + mutt_error _("No news server defined!"); + mutt_sleep (2); + return NULL; + } + + /* create account from news server url */ + acct.flags = 0; + acct.port = NNTP_PORT; + acct.type = M_ACCT_TYPE_NNTP; + snprintf (file, sizeof (file), "%s%s", + strstr (server, "://") ? "" : "news://", server); + if (url_parse_ciss (&url, file) < 0 || + (url.path && *url.path) || + !(url.scheme == U_NNTP || url.scheme == U_NNTPS) || + mutt_account_fromurl (&acct, &url) < 0) + { + mutt_error (_("%s is an invalid news server specification!"), server); + mutt_sleep (2); + return NULL; + } + if (url.scheme == U_NNTPS) + { + acct.flags |= M_ACCT_SSL; + acct.port = NNTP_SSL_PORT; + } + + /* find connection by account */ + conn = mutt_conn_find (NULL, &acct); + if (!conn) + return NULL; + if (!(conn->account.flags & M_ACCT_USER) && acct.flags & M_ACCT_USER) + { + conn->account.flags |= M_ACCT_USER; + conn->account.user[0] = '\0'; + } + + /* news server already exists */ + nserv = conn->data; + if (nserv) + { + if (nserv->status == NNTP_BYE) + nserv->status = NNTP_NONE; + if (nntp_open_connection (nserv) < 0) + return NULL; + + rc = nntp_newsrc_parse (nserv); + if (rc < 0) + return NULL; + + /* check for new newsgroups */ + if (!leave_lock && nntp_check_new_groups (nserv) < 0) + rc = -1; + + /* .newsrc has been externally modified */ + if (rc > 0) + nntp_clear_cache (nserv); + if (rc < 0 || !leave_lock) + nntp_newsrc_close (nserv); + return rc < 0 ? NULL : nserv; + } + + /* new news server */ + nserv = safe_calloc (1, sizeof (NNTP_SERVER)); + nserv->conn = conn; + nserv->groups_hash = hash_create (1009, 0); + nserv->groups_max = 16; + nserv->groups_list = safe_malloc (nserv->groups_max * sizeof (nntp_data)); + + rc = nntp_open_connection (nserv); + + /* try to create cache directory and enable caching */ + nserv->cacheable = 0; + if (rc >= 0 && NewsCacheDir && *NewsCacheDir) + { + cache_expand (file, sizeof (file), &conn->account, NULL); + p = *file == '/' ? file + 1 : file; + while (1) + { + p = strchr (p, '/'); + if (p) + *p = '\0'; + if ((stat (file, &sb) || (sb.st_mode & S_IFDIR) == 0) && + mkdir (file, 0700)) + { + mutt_error (_("Can't create %s: %s."), file, strerror (errno)); + mutt_sleep (2); + break; + } + if (!p) + { + nserv->cacheable = 1; + break; + } + *p++ = '/'; + } + } + + /* load .newsrc */ + if (rc >= 0) + { + mutt_FormatString (file, sizeof (file), 0, NONULL (NewsRc), + nntp_format_str, (unsigned long)nserv, 0); + mutt_expand_path (file, sizeof (file)); + nserv->newsrc_file = safe_strdup (file); + rc = nntp_newsrc_parse (nserv); + } + if (rc >= 0) + { + /* try to load list of newsgroups from cache */ + if (nserv->cacheable && active_get_cache (nserv) == 0) + rc = nntp_check_new_groups (nserv); + + /* load list of newsgroups from server */ + else + rc = nntp_active_fetch (nserv); + } + + if (rc >= 0) + nntp_clear_cache (nserv); + +#ifdef USE_HCACHE + /* check cache files */ + if (rc >= 0 && nserv->cacheable) + { + struct dirent *entry; + DIR *dp = opendir (file); + + if (dp) + { + while ((entry = readdir (dp))) + { + header_cache_t *hc; + void *hdata; + char *group = entry->d_name; + + p = group + strlen (group) - 7; + if (strlen (group) < 8 || strcmp (p, ".hcache")) + continue; + *p = '\0'; + nntp_data = hash_find (nserv->groups_hash, group); + if (!nntp_data) + continue; + + hc = nntp_hcache_open (nntp_data); + if (!hc) + continue; + + /* fetch previous values of first and last */ + hdata = mutt_hcache_fetch_raw (hc, "index", strlen); + if (hdata) + { + anum_t first, last; + + if (sscanf (hdata, ANUM " " ANUM, &first, &last) == 2) + { + if (nntp_data->deleted) + { + nntp_data->firstMessage = first; + nntp_data->lastMessage = last; + } + if (last >= nntp_data->firstMessage && + last <= nntp_data->lastMessage) + { + nntp_data->lastCached = last; + dprint (2, (debugfile, "nntp_select_server: %s lastCached=%u\n", + nntp_data->group, last)); + } + } + } + mutt_hcache_close (hc); + } + closedir (dp); + } + } +#endif + + if (rc < 0 || !leave_lock) + nntp_newsrc_close (nserv); + + if (rc < 0) + { + hash_destroy (&nserv->groups_hash, nntp_data_free); + FREE (&nserv->groups_list); + FREE (&nserv->newsrc_file); + FREE (&nserv->authenticators); + FREE (&nserv); + mutt_socket_close (conn); + mutt_socket_free (conn); + return NULL; + } + + conn->data = nserv; + return nserv; +} + +/* Full status flags are not supported by nntp, but we can fake some of them: + * Read = a read message number is in the .newsrc + * New = not read and not cached + * Old = not read but cached */ +void nntp_article_status (CONTEXT *ctx, HEADER *hdr, char *group, anum_t anum) +{ + NNTP_DATA *nntp_data = ctx->data; + unsigned int i; + + if (group) + nntp_data = hash_find (nntp_data->nserv->groups_hash, group); + + if (!nntp_data) + return; + + for (i = 0; i < nntp_data->newsrc_len; i++) + { + if ((anum >= nntp_data->newsrc_ent[i].first) && + (anum <= nntp_data->newsrc_ent[i].last)) + { + /* can't use mutt_set_flag() because mx_update_context() + didn't called yet */ + hdr->read = 1; + return; + } + } + + /* article was not cached yet, it's new */ + if (anum > nntp_data->lastCached) + return; + + /* article isn't read but cached, it's old */ + if (option (OPTMARKOLD)) + hdr->old = 1; +} + +/* calculate number of unread articles using .newsrc data */ +void nntp_group_unread_stat (NNTP_DATA *nntp_data) +{ + unsigned int i; + anum_t first, last; + + nntp_data->unread = 0; + if (nntp_data->lastMessage == 0 || + nntp_data->firstMessage > nntp_data->lastMessage) + return; + + nntp_data->unread = nntp_data->lastMessage - nntp_data->firstMessage + 1; + for (i = 0; i < nntp_data->newsrc_len; i++) + { + first = nntp_data->newsrc_ent[i].first; + if (first < nntp_data->firstMessage) + first = nntp_data->firstMessage; + last = nntp_data->newsrc_ent[i].last; + if (last > nntp_data->lastMessage) + last = nntp_data->lastMessage; + if (first <= last) + nntp_data->unread -= last - first + 1; + } +} + +/* Subscribe newsgroup */ +NNTP_DATA *mutt_newsgroup_subscribe (NNTP_SERVER *nserv, char *group) +{ + NNTP_DATA *nntp_data; + + if (!nserv || !nserv->groups_hash || !group || !*group) + return NULL; + + nntp_data = nntp_data_find (nserv, group); + nntp_data->subscribed = 1; + if (!nntp_data->newsrc_ent) + { + nntp_data->newsrc_ent = safe_calloc (1, sizeof (NEWSRC_ENTRY)); + nntp_data->newsrc_len = 1; + nntp_data->newsrc_ent[0].first = 1; + nntp_data->newsrc_ent[0].last = 0; + } + return nntp_data; +} + +/* Unsubscribe newsgroup */ +NNTP_DATA *mutt_newsgroup_unsubscribe (NNTP_SERVER *nserv, char *group) +{ + NNTP_DATA *nntp_data; + + if (!nserv || !nserv->groups_hash || !group || !*group) + return NULL; + + nntp_data = hash_find (nserv->groups_hash, group); + if (!nntp_data) + return NULL; + + nntp_data->subscribed = 0; + if (!option (OPTSAVEUNSUB)) + { + nntp_data->newsrc_len = 0; + FREE (&nntp_data->newsrc_ent); + } + return nntp_data; +} + +/* Catchup newsgroup */ +NNTP_DATA *mutt_newsgroup_catchup (NNTP_SERVER *nserv, char *group) +{ + NNTP_DATA *nntp_data; + + if (!nserv || !nserv->groups_hash || !group || !*group) + return NULL; + + nntp_data = hash_find (nserv->groups_hash, group); + if (!nntp_data) + return NULL; + + if (nntp_data->newsrc_ent) + { + safe_realloc (&nntp_data->newsrc_ent, sizeof (NEWSRC_ENTRY)); + nntp_data->newsrc_len = 1; + nntp_data->newsrc_ent[0].first = 1; + nntp_data->newsrc_ent[0].last = nntp_data->lastMessage; + } + nntp_data->unread = 0; + if (Context && Context->data == nntp_data) + { + unsigned int i; + + for (i = 0; i < Context->msgcount; i++) + mutt_set_flag (Context, Context->hdrs[i], M_READ, 1); + } + return nntp_data; +} + +/* Uncatchup newsgroup */ +NNTP_DATA *mutt_newsgroup_uncatchup (NNTP_SERVER *nserv, char *group) +{ + NNTP_DATA *nntp_data; + + if (!nserv || !nserv->groups_hash || !group || !*group) + return NULL; + + nntp_data = hash_find (nserv->groups_hash, group); + if (!nntp_data) + return NULL; + + if (nntp_data->newsrc_ent) + { + safe_realloc (&nntp_data->newsrc_ent, sizeof (NEWSRC_ENTRY)); + nntp_data->newsrc_len = 1; + nntp_data->newsrc_ent[0].first = 1; + nntp_data->newsrc_ent[0].last = nntp_data->firstMessage - 1; + } + if (Context && Context->data == nntp_data) + { + unsigned int i; + + nntp_data->unread = Context->msgcount; + for (i = 0; i < Context->msgcount; i++) + mutt_set_flag (Context, Context->hdrs[i], M_READ, 0); + } + else + nntp_data->unread = nntp_data->lastMessage - nntp_data->newsrc_ent[0].last; + return nntp_data; +} + +/* Get first newsgroup with new messages */ +void nntp_buffy (char *buf, size_t len) +{ + unsigned int i; + + for (i = 0; i < CurrentNewsSrv->groups_num; i++) + { + NNTP_DATA *nntp_data = CurrentNewsSrv->groups_list[i]; + + if (!nntp_data || !nntp_data->subscribed || !nntp_data->unread) + continue; + + if (Context && Context->magic == M_NNTP && + !mutt_strcmp (nntp_data->group, ((NNTP_DATA *)Context->data)->group)) + { + unsigned int i, unread = 0; + + for (i = 0; i < Context->msgcount; i++) + if (!Context->hdrs[i]->read && !Context->hdrs[i]->deleted) + unread++; + if (!unread) + continue; + } + strfcpy (buf, nntp_data->group, len); + break; + } +} diff --git a/nntp.c b/nntp.c new file mode 100644 index 0000000..c78d3fa --- /dev/null +++ b/nntp.c @@ -0,0 +1,2404 @@ +/* + * Copyright (C) 1998 Brandon Long + * Copyright (C) 1999 Andrej Gritsenko + * Copyright (C) 2000-2012 Vsevolod Volkov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include "mutt.h" +#include "mutt_curses.h" +#include "sort.h" +#include "mx.h" +#include "mime.h" +#include "rfc1524.h" +#include "rfc2047.h" +#include "mailbox.h" +#include "mutt_crypt.h" +#include "nntp.h" + +#if defined(USE_SSL) +#include "mutt_ssl.h" +#endif + +#ifdef HAVE_PGP +#include "pgp.h" +#endif + +#ifdef HAVE_SMIME +#include "smime.h" +#endif + +#if USE_HCACHE +#include "hcache.h" +#endif + +#include +#include +#include +#include + +#ifdef USE_SASL +#include +#include + +#include "mutt_sasl.h" +#endif + +static int nntp_connect_error (NNTP_SERVER *nserv) +{ + nserv->status = NNTP_NONE; + mutt_error _("Server closed connection!"); + mutt_sleep (2); + return -1; +} + +/* Get capabilities: + * -1 - error, connection is closed + * 0 - mode is reader, capabilities setted up + * 1 - need to switch to reader mode */ +static int nntp_capabilities (NNTP_SERVER *nserv) +{ + CONNECTION *conn = nserv->conn; + unsigned int mode_reader = 0; + char buf[LONG_STRING]; + char authinfo[LONG_STRING] = ""; + + nserv->hasCAPABILITIES = 0; + nserv->hasSTARTTLS = 0; + nserv->hasDATE = 0; + nserv->hasLIST_NEWSGROUPS = 0; + nserv->hasLISTGROUP = 0; + nserv->hasOVER = 0; + FREE (&nserv->authenticators); + + if (mutt_socket_write (conn, "CAPABILITIES\r\n") < 0 || + mutt_socket_readln (buf, sizeof (buf), conn) < 0) + return nntp_connect_error (nserv); + + /* no capabilities */ + if (mutt_strncmp ("101", buf, 3)) + return 1; + nserv->hasCAPABILITIES = 1; + + /* parse capabilities */ + do + { + if (mutt_socket_readln (buf, sizeof (buf), conn) < 0) + return nntp_connect_error (nserv); + if (!mutt_strcmp ("STARTTLS", buf)) + nserv->hasSTARTTLS = 1; + else if (!mutt_strcmp ("MODE-READER", buf)) + mode_reader = 1; + else if (!mutt_strcmp ("READER", buf)) + { + nserv->hasDATE = 1; + nserv->hasLISTGROUP = 1; + } + else if (!mutt_strncmp ("AUTHINFO ", buf, 9)) + { + safe_strcat (buf, sizeof (buf), " "); + strfcpy (authinfo, buf + 8, sizeof (authinfo)); + } +#ifdef USE_SASL + else if (!mutt_strncmp ("SASL ", buf, 5)) + { + char *p = buf + 5; + while (*p == ' ') + p++; + nserv->authenticators = safe_strdup (p); + } +#endif + else if (!mutt_strcmp ("OVER", buf)) + nserv->hasOVER = 1; + else if (!mutt_strncmp ("LIST ", buf, 5)) + { + char *p = strstr (buf, " NEWSGROUPS"); + if (p) + { + p += 11; + if (*p == '\0' || *p == ' ') + nserv->hasLIST_NEWSGROUPS = 1; + } + } + } while (mutt_strcmp (".", buf)); + *buf = '\0'; +#ifdef USE_SASL + if (nserv->authenticators && strcasestr (authinfo, " SASL ")) + strfcpy (buf, nserv->authenticators, sizeof (buf)); +#endif + if (strcasestr (authinfo, " USER ")) + { + if (*buf) + safe_strcat (buf, sizeof (buf), " "); + safe_strcat (buf, sizeof (buf), "USER"); + } + mutt_str_replace (&nserv->authenticators, buf); + + /* current mode is reader */ + if (nserv->hasLISTGROUP) + return 0; + + /* server is mode-switching, need to switch to reader mode */ + if (mode_reader) + return 1; + + mutt_socket_close (conn); + nserv->status = NNTP_BYE; + mutt_error _("Server doesn't support reader mode."); + mutt_sleep (2); + return -1; +} + +char *OverviewFmt = + "Subject:\0" + "From:\0" + "Date:\0" + "Message-ID:\0" + "References:\0" + "Content-Length:\0" + "Lines:\0" + "\0"; + +/* Detect supported commands */ +static int nntp_attempt_features (NNTP_SERVER *nserv) +{ + CONNECTION *conn = nserv->conn; + char buf[LONG_STRING]; + + /* no CAPABILITIES, trying DATE, LISTGROUP, LIST NEWSGROUPS */ + if (!nserv->hasCAPABILITIES) + { + if (mutt_socket_write (conn, "DATE\r\n") < 0 || + mutt_socket_readln (buf, sizeof (buf), conn) < 0) + return nntp_connect_error (nserv); + if (mutt_strncmp ("500", buf, 3)) + nserv->hasDATE = 1; + + if (mutt_socket_write (conn, "LISTGROUP\r\n") < 0 || + mutt_socket_readln (buf, sizeof (buf), conn) < 0) + return nntp_connect_error (nserv); + if (mutt_strncmp ("500", buf, 3)) + nserv->hasLISTGROUP = 1; + + if (mutt_socket_write (conn, "LIST NEWSGROUPS +\r\n") < 0 || + mutt_socket_readln (buf, sizeof (buf), conn) < 0) + return nntp_connect_error (nserv); + if (mutt_strncmp ("500", buf, 3)) + nserv->hasLIST_NEWSGROUPS = 1; + if (!mutt_strncmp ("215", buf, 3)) + { + do + { + if (mutt_socket_readln (buf, sizeof (buf), conn) < 0) + return nntp_connect_error (nserv); + } while (mutt_strcmp (".", buf)); + } + } + + /* no LIST NEWSGROUPS, trying XGTITLE */ + if (!nserv->hasLIST_NEWSGROUPS) + { + if (mutt_socket_write (conn, "XGTITLE\r\n") < 0 || + mutt_socket_readln (buf, sizeof (buf), conn) < 0) + return nntp_connect_error (nserv); + if (mutt_strncmp ("500", buf, 3)) + nserv->hasXGTITLE = 1; + } + + /* no OVER, trying XOVER */ + if (!nserv->hasOVER) + { + if (mutt_socket_write (conn, "XOVER\r\n") < 0 || + mutt_socket_readln (buf, sizeof (buf), conn) < 0) + return nntp_connect_error (nserv); + if (mutt_strncmp ("500", buf, 3)) + nserv->hasXOVER = 1; + } + + /* trying LIST OVERVIEW.FMT */ + if (nserv->hasOVER || nserv->hasXOVER) + { + if (mutt_socket_write (conn, "LIST OVERVIEW.FMT\r\n") < 0 || + mutt_socket_readln (buf, sizeof (buf), conn) < 0) + return nntp_connect_error (nserv); + if (mutt_strncmp ("215", buf, 3)) + nserv->overview_fmt = OverviewFmt; + else + { + int chunk, cont = 0; + size_t buflen = 2 * LONG_STRING, off = 0, b = 0; + + if (nserv->overview_fmt) + FREE (&nserv->overview_fmt); + nserv->overview_fmt = safe_malloc (buflen); + + while (1) + { + if (buflen - off < LONG_STRING) + { + buflen *= 2; + safe_realloc (&nserv->overview_fmt, buflen); + } + + chunk = mutt_socket_readln (nserv->overview_fmt + off, + buflen - off, conn); + if (chunk < 0) + { + FREE (&nserv->overview_fmt); + return nntp_connect_error (nserv); + } + + if (!cont && !mutt_strcmp (".", nserv->overview_fmt + off)) + break; + + cont = chunk >= buflen - off ? 1 : 0; + off += strlen (nserv->overview_fmt + off); + if (!cont) + { + char *colon; + + if (nserv->overview_fmt[b] == ':') + { + memmove (nserv->overview_fmt + b, + nserv->overview_fmt + b + 1, off - b - 1); + nserv->overview_fmt[off - 1] = ':'; + } + colon = strchr (nserv->overview_fmt + b, ':'); + if (!colon) + nserv->overview_fmt[off++] = ':'; + else if (strcmp (colon + 1, "full")) + off = colon + 1 - nserv->overview_fmt; + if (!strcasecmp (nserv->overview_fmt + b, "Bytes:")) + { + strcpy (nserv->overview_fmt + b, "Content-Length:"); + off = b + strlen (nserv->overview_fmt + b); + } + nserv->overview_fmt[off++] = '\0'; + b = off; + } + } + nserv->overview_fmt[off++] = '\0'; + safe_realloc (&nserv->overview_fmt, off); + } + } + return 0; +} + +/* Get login, password and authenticate */ +static int nntp_auth (NNTP_SERVER *nserv) +{ + CONNECTION *conn = nserv->conn; + char buf[LONG_STRING]; + char authenticators[LONG_STRING] = "USER"; + char *method, *a, *p; + unsigned char flags = conn->account.flags; + + while (1) + { + /* get login and password */ + if (mutt_account_getuser (&conn->account) || !conn->account.user[0] || + mutt_account_getpass (&conn->account) || !conn->account.pass[0]) + break; + + /* get list of authenticators */ + if (NntpAuthenticators && *NntpAuthenticators) + strfcpy (authenticators, NntpAuthenticators, sizeof (authenticators)); + else if (nserv->hasCAPABILITIES) + { + strfcpy (authenticators, NONULL (nserv->authenticators), + sizeof (authenticators)); + p = authenticators; + while (*p) + { + if (*p == ' ') + *p = ':'; + p++; + } + } + p = authenticators; + while (*p) + { + *p = ascii_toupper (*p); + p++; + } + + dprint (1, (debugfile, + "nntp_auth: available methods: %s\n", nserv->authenticators)); + a = authenticators; + while (1) + { + if (!a) + { + mutt_error _("No authenticators available"); + mutt_sleep (2); + break; + } + + method = a; + a = strchr (a, ':'); + if (a) + *a++ = '\0'; + + /* check authenticator */ + if (nserv->hasCAPABILITIES) + { + char *m; + + if (!nserv->authenticators) + continue; + m = strcasestr (nserv->authenticators, method); + if (!m) + continue; + if (m > nserv->authenticators && *(m - 1) != ' ') + continue; + m += strlen (method); + if (*m != '\0' && *m != ' ') + continue; + } + dprint (1, (debugfile, "nntp_auth: trying method %s\n", method)); + + /* AUTHINFO USER authentication */ + if (!strcmp (method, "USER")) + { + mutt_message (_("Authenticating (%s)..."), method); + snprintf (buf, sizeof (buf), "AUTHINFO USER %s\r\n", conn->account.user); + if (mutt_socket_write (conn, buf) < 0 || + mutt_socket_readln (buf, sizeof (buf), conn) < 0) + break; + + /* authenticated, password is not required */ + if (!mutt_strncmp ("281", buf, 3)) + return 0; + + /* username accepted, sending password */ + if (!mutt_strncmp ("381", buf, 3)) + { +#ifdef DEBUG + if (debuglevel < M_SOCK_LOG_FULL) + dprint (M_SOCK_LOG_CMD, (debugfile, + "%d> AUTHINFO PASS *\n", conn->fd)); +#endif + snprintf (buf, sizeof (buf), "AUTHINFO PASS %s\r\n", + conn->account.pass); + if (mutt_socket_write_d (conn, buf, -1, M_SOCK_LOG_FULL) < 0 || + mutt_socket_readln (buf, sizeof (buf), conn) < 0) + break; + + /* authenticated */ + if (!mutt_strncmp ("281", buf, 3)) + return 0; + } + + /* server doesn't support AUTHINFO USER, trying next method */ + if (*buf == '5') + continue; + } + + else + { +#ifdef USE_SASL + sasl_conn_t *saslconn; + sasl_interact_t *interaction = NULL; + int rc; + char inbuf[LONG_STRING] = ""; + const char *mech; + const char *client_out = NULL; + unsigned int client_len, len; + + if (mutt_sasl_client_new (conn, &saslconn) < 0) + { + dprint (1, (debugfile, + "nntp_auth: error allocating SASL connection.\n")); + continue; + } + + while (1) + { + rc = sasl_client_start (saslconn, method, &interaction, + &client_out, &client_len, &mech); + if (rc != SASL_INTERACT) + break; + mutt_sasl_interact (interaction); + } + if (rc != SASL_OK && rc != SASL_CONTINUE) + { + sasl_dispose (&saslconn); + dprint (1, (debugfile, + "nntp_auth: error starting SASL authentication exchange.\n")); + continue; + } + + mutt_message (_("Authenticating (%s)..."), method); + snprintf (buf, sizeof (buf), "AUTHINFO SASL %s", method); + + /* looping protocol */ + while (rc == SASL_CONTINUE || (rc == SASL_OK && client_len)) + { + /* send out client response */ + if (client_len) + { +#ifdef DEBUG + if (debuglevel >= M_SOCK_LOG_FULL) + { + char tmp[LONG_STRING]; + memcpy (tmp, client_out, client_len); + for (p = tmp; p < tmp + client_len; p++) + { + if (*p == '\0') + *p = '.'; + } + *p = '\0'; + dprint (1, (debugfile, "SASL> %s\n", tmp)); + } +#endif + + if (*buf) + safe_strcat (buf, sizeof (buf), " "); + len = strlen (buf); + if (sasl_encode64 (client_out, client_len, + buf + len, sizeof (buf) - len, &len) != SASL_OK) + { + dprint (1, (debugfile, + "nntp_auth: error base64-encoding client response.\n")); + break; + } + } + + safe_strcat (buf, sizeof (buf), "\r\n"); +#ifdef DEBUG + if (debuglevel < M_SOCK_LOG_FULL) + { + if (strchr (buf, ' ')) + dprint (M_SOCK_LOG_CMD, (debugfile, "%d> AUTHINFO SASL %s%s\n", + conn->fd, method, client_len ? " sasl_data" : "")); + else + dprint (M_SOCK_LOG_CMD, (debugfile, "%d> sasl_data\n", conn->fd)); + } +#endif + client_len = 0; + if (mutt_socket_write_d (conn, buf, -1, M_SOCK_LOG_FULL) < 0 || + mutt_socket_readln_d (inbuf, sizeof (inbuf), conn, M_SOCK_LOG_FULL) < 0) + break; + if (mutt_strncmp (inbuf, "283 ", 4) && + mutt_strncmp (inbuf, "383 ", 4)) + { +#ifdef DEBUG + if (debuglevel < M_SOCK_LOG_FULL) + dprint (M_SOCK_LOG_CMD, (debugfile, "%d< %s\n", conn->fd, inbuf)); +#endif + break; + } +#ifdef DEBUG + if (debuglevel < M_SOCK_LOG_FULL) + { + inbuf[3] = '\0'; + dprint (M_SOCK_LOG_CMD, (debugfile, + "%d< %s sasl_data\n", conn->fd, inbuf)); + } +#endif + + if (!strcmp ("=", inbuf + 4)) + len = 0; + else if (sasl_decode64 (inbuf + 4, strlen (inbuf + 4), + buf, sizeof (buf) - 1, &len) != SASL_OK) + { + dprint (1, (debugfile, + "nntp_auth: error base64-decoding server response.\n")); + break; + } +#ifdef DEBUG + else if (debuglevel >= M_SOCK_LOG_FULL) + { + char tmp[LONG_STRING]; + memcpy (tmp, buf, len); + for (p = tmp; p < tmp + len; p++) + { + if (*p == '\0') + *p = '.'; + } + *p = '\0'; + dprint (1, (debugfile, "SASL< %s\n", tmp)); + } +#endif + + while (1) + { + rc = sasl_client_step (saslconn, buf, len, + &interaction, &client_out, &client_len); + if (rc != SASL_INTERACT) + break; + mutt_sasl_interact (interaction); + } + if (*inbuf != '3') + break; + + *buf = '\0'; + } /* looping protocol */ + + if (rc == SASL_OK && client_len == 0 && *inbuf == '2') + { + mutt_sasl_setup_conn (conn, saslconn); + return 0; + } + + /* terminate SASL sessoin */ + sasl_dispose (&saslconn); + if (conn->fd < 0) + break; + if (!mutt_strncmp (inbuf, "383 ", 4)) + { + if (mutt_socket_write (conn, "*\r\n") < 0 || + mutt_socket_readln (inbuf, sizeof (inbuf), conn) < 0) + break; + } + + /* server doesn't support AUTHINFO SASL, trying next method */ + if (*inbuf == '5') + continue; +#else + continue; +#endif /* USE_SASL */ + } + + mutt_error (_("%s authentication failed."), method); + mutt_sleep (2); + break; + } + break; + } + + /* error */ + nserv->status = NNTP_BYE; + conn->account.flags = flags; + if (conn->fd < 0) + { + mutt_error _("Server closed connection!"); + mutt_sleep (2); + } + else + mutt_socket_close (conn); + return -1; +} + +/* Connect to server, authenticate and get capabilities */ +int nntp_open_connection (NNTP_SERVER *nserv) +{ + CONNECTION *conn = nserv->conn; + char buf[STRING]; + int cap; + unsigned int posting = 0, auth = 1; + + if (nserv->status == NNTP_OK) + return 0; + if (nserv->status == NNTP_BYE) + return -1; + nserv->status = NNTP_NONE; + + if (mutt_socket_open (conn) < 0) + return -1; + + if (mutt_socket_readln (buf, sizeof (buf), conn) < 0) + return nntp_connect_error (nserv); + + if (!mutt_strncmp ("200", buf, 3)) + posting = 1; + else if (mutt_strncmp ("201", buf, 3)) + { + mutt_socket_close (conn); + mutt_remove_trailing_ws (buf); + mutt_error ("%s", buf); + mutt_sleep (2); + return -1; + } + + /* get initial capabilities */ + cap = nntp_capabilities (nserv); + if (cap < 0) + return -1; + + /* tell news server to switch to mode reader if it isn't so */ + if (cap > 0) + { + if (mutt_socket_write (conn, "MODE READER\r\n") < 0 || + mutt_socket_readln (buf, sizeof (buf), conn) < 0) + return nntp_connect_error (nserv); + + if (!mutt_strncmp ("200", buf, 3)) + posting = 1; + else if (!mutt_strncmp ("201", buf, 3)) + posting = 0; + /* error if has capabilities, ignore result if no capabilities */ + else if (nserv->hasCAPABILITIES) + { + mutt_socket_close (conn); + mutt_error _("Could not switch to reader mode."); + mutt_sleep (2); + return -1; + } + + /* recheck capabilities after MODE READER */ + if (nserv->hasCAPABILITIES) + { + cap = nntp_capabilities (nserv); + if (cap < 0) + return -1; + } + } + + mutt_message (_("Connected to %s. %s"), conn->account.host, + posting ? _("Posting is ok.") : _("Posting is NOT ok.")); + mutt_sleep (1); + +#if defined(USE_SSL) + /* Attempt STARTTLS if available and desired. */ + if (nserv->use_tls != 1 && (nserv->hasSTARTTLS || option (OPTSSLFORCETLS))) + { + if (nserv->use_tls == 0) + nserv->use_tls = option (OPTSSLFORCETLS) || + query_quadoption (OPT_SSLSTARTTLS, + _("Secure connection with TLS?")) == M_YES ? 2 : 1; + if (nserv->use_tls == 2) + { + if (mutt_socket_write (conn, "STARTTLS\r\n") < 0 || + mutt_socket_readln (buf, sizeof (buf), conn) < 0) + return nntp_connect_error (nserv); + if (mutt_strncmp ("382", buf, 3)) + { + nserv->use_tls = 0; + mutt_error ("STARTTLS: %s", buf); + mutt_sleep (2); + } + else if (mutt_ssl_starttls (conn)) + { + nserv->use_tls = 0; + nserv->status = NNTP_NONE; + mutt_socket_close (nserv->conn); + mutt_error _("Could not negotiate TLS connection"); + mutt_sleep (2); + return -1; + } + else + { + /* recheck capabilities after STARTTLS */ + cap = nntp_capabilities (nserv); + if (cap < 0) + return -1; + } + } + } +#endif + + /* authentication required? */ + if (conn->account.flags & M_ACCT_USER) + { + if (!conn->account.user[0]) + auth = 0; + } + else + { + if (mutt_socket_write (conn, "STAT\r\n") < 0 || + mutt_socket_readln (buf, sizeof (buf), conn) < 0) + return nntp_connect_error (nserv); + if (mutt_strncmp ("480", buf, 3)) + auth = 0; + } + + /* authenticate */ + if (auth && nntp_auth (nserv) < 0) + return -1; + + /* get final capabilities after authentication */ + if (nserv->hasCAPABILITIES && (auth || cap > 0)) + { + cap = nntp_capabilities (nserv); + if (cap < 0) + return -1; + if (cap > 0) + { + mutt_socket_close (conn); + mutt_error _("Could not switch to reader mode."); + mutt_sleep (2); + return -1; + } + } + + /* attempt features */ + if (nntp_attempt_features (nserv) < 0) + return -1; + + nserv->status = NNTP_OK; + return 0; +} + +/* Send data from buffer and receive answer to same buffer */ +static int nntp_query (NNTP_DATA *nntp_data, char *line, size_t linelen) +{ + NNTP_SERVER *nserv = nntp_data->nserv; + char buf[LONG_STRING]; + + if (nserv->status == NNTP_BYE) + return -1; + + while (1) + { + if (nserv->status == NNTP_OK) + { + int rc = 0; + + if (*line) + rc = mutt_socket_write (nserv->conn, line); + else if (nntp_data->group) + { + snprintf (buf, sizeof (buf), "GROUP %s\r\n", nntp_data->group); + rc = mutt_socket_write (nserv->conn, buf); + } + if (rc >= 0) + rc = mutt_socket_readln (buf, sizeof (buf), nserv->conn); + if (rc >= 0) + break; + } + + /* reconnect */ + while (1) + { + nserv->status = NNTP_NONE; + if (nntp_open_connection (nserv) == 0) + break; + + snprintf (buf, sizeof (buf), _("Connection to %s lost. Reconnect?"), + nserv->conn->account.host); + if (mutt_yesorno (buf, M_YES) != M_YES) + { + nserv->status = NNTP_BYE; + return -1; + } + } + + /* select newsgroup after reconnection */ + if (nntp_data->group) + { + snprintf (buf, sizeof (buf), "GROUP %s\r\n", nntp_data->group); + if (mutt_socket_write (nserv->conn, buf) < 0 || + mutt_socket_readln (buf, sizeof (buf), nserv->conn) < 0) + return nntp_connect_error (nserv); + } + if (!*line) + break; + } + + strfcpy (line, buf, linelen); + return 0; +} + +/* This function calls funct(*line, *data) for each received line, + * funct(NULL, *data) if rewind(*data) needs, exits when fail or done: + * 0 - success + * 1 - bad response (answer in query buffer) + * -1 - conection lost + * -2 - error in funct(*line, *data) */ +static int nntp_fetch_lines (NNTP_DATA *nntp_data, char *query, size_t qlen, + char *msg, int (*funct) (char *, void *), void *data) +{ + int done = FALSE; + int rc; + + while (!done) + { + char buf[LONG_STRING]; + char *line; + unsigned int lines = 0; + size_t off = 0; + progress_t progress; + + if (msg) + mutt_progress_init (&progress, msg, M_PROGRESS_MSG, ReadInc, -1); + + strfcpy (buf, query, sizeof (buf)); + if (nntp_query (nntp_data, buf, sizeof (buf)) < 0) + return -1; + if (buf[0] != '2') + { + strfcpy (query, buf, qlen); + return 1; + } + + line = safe_malloc (sizeof (buf)); + rc = 0; + + while (1) + { + char *p; + int chunk = mutt_socket_readln_d (buf, sizeof (buf), + nntp_data->nserv->conn, M_SOCK_LOG_HDR); + if (chunk < 0) + { + nntp_data->nserv->status = NNTP_NONE; + break; + } + + p = buf; + if (!off && buf[0] == '.') + { + if (buf[1] == '\0') + { + done = TRUE; + break; + } + if (buf[1] == '.') + p++; + } + + strfcpy (line + off, p, sizeof (buf)); + + if (chunk >= sizeof (buf)) + off += strlen (p); + else + { + if (msg) + mutt_progress_update (&progress, ++lines, -1); + + if (rc == 0 && funct (line, data) < 0) + rc = -2; + off = 0; + } + + safe_realloc (&line, off + sizeof (buf)); + } + FREE (&line); + funct (NULL, data); + } + return rc; +} + +/* Parse newsgroup description */ +static int fetch_description (char *line, void *data) +{ + NNTP_SERVER *nserv = data; + NNTP_DATA *nntp_data; + char *desc; + + if (!line) + return 0; + + desc = strpbrk (line, " \t"); + if (desc) + { + *desc++ = '\0'; + desc += strspn (desc, " \t"); + } + else + desc = strchr (line, '\0'); + + nntp_data = hash_find (nserv->groups_hash, line); + if (nntp_data && mutt_strcmp (desc, nntp_data->desc)) + { + mutt_str_replace (&nntp_data->desc, desc); + dprint (2, (debugfile, "group: %s, desc: %s\n", line, desc)); + } + return 0; +} + +/* Fetch newsgroups descriptions. + * Returns the same code as nntp_fetch_lines() */ +static int get_description (NNTP_DATA *nntp_data, char *wildmat, char *msg) +{ + NNTP_SERVER *nserv; + char buf[STRING]; + char *cmd; + int rc; + + /* get newsgroup description, if possible */ + nserv = nntp_data->nserv; + if (!wildmat) + wildmat = nntp_data->group; + if (nserv->hasLIST_NEWSGROUPS) + cmd = "LIST NEWSGROUPS"; + else if (nserv->hasXGTITLE) + cmd = "XGTITLE"; + else + return 0; + + snprintf (buf, sizeof (buf), "%s %s\r\n", cmd, wildmat); + rc = nntp_fetch_lines (nntp_data, buf, sizeof (buf), msg, + fetch_description, nserv); + if (rc > 0) + { + mutt_error ("%s: %s", cmd, buf); + mutt_sleep (2); + } + return rc; +} + +/* Update read flag and set article number if empty */ +static void nntp_parse_xref (CONTEXT *ctx, HEADER *hdr) +{ + NNTP_DATA *nntp_data = ctx->data; + char *buf, *p; + + buf = p = safe_strdup (hdr->env->xref); + while (p) + { + char *grp, *colon; + anum_t anum; + + /* skip to next word */ + p += strspn (p, " \t"); + grp = p; + + /* skip to end of word */ + p = strpbrk (p, " \t"); + if (p) + *p++ = '\0'; + + /* find colon */ + colon = strchr (grp, ':'); + if (!colon) + continue; + *colon++ = '\0'; + if (sscanf (colon, ANUM, &anum) != 1) + continue; + + nntp_article_status (ctx, hdr, grp, anum); + if (hdr && !NHDR (hdr)->article_num && !mutt_strcmp (nntp_data->group, grp)) + NHDR (hdr)->article_num = anum; + } + FREE (&buf); +} + +/* Write line to temporarily file */ +static int fetch_tempfile (char *line, void *data) +{ + FILE *fp = data; + + if (!line) + rewind (fp); + else if (fputs (line, fp) == EOF || fputc ('\n', fp) == EOF) + return -1; + return 0; +} + +typedef struct +{ + CONTEXT *ctx; + anum_t first; + anum_t last; + int restore; + unsigned char *messages; + progress_t progress; +#ifdef USE_HCACHE + header_cache_t *hc; +#endif +} FETCH_CTX; + +/* Parse article number */ +static int fetch_numbers (char *line, void *data) +{ + FETCH_CTX *fc = data; + anum_t anum; + + if (!line) + return 0; + if (sscanf (line, ANUM, &anum) != 1) + return 0; + if (anum < fc->first || anum > fc->last) + return 0; + fc->messages[anum - fc->first] = 1; + return 0; +} + +/* Parse overview line */ +static int parse_overview_line (char *line, void *data) +{ + FETCH_CTX *fc = data; + CONTEXT *ctx = fc->ctx; + NNTP_DATA *nntp_data = ctx->data; + HEADER *hdr; + FILE *fp; + char tempfile[_POSIX_PATH_MAX]; + char *header, *field; + int save = 1; + anum_t anum; + + if (!line) + return 0; + + /* parse article number */ + field = strchr (line, '\t'); + if (field) + *field++ = '\0'; + if (sscanf (line, ANUM, &anum) != 1) + return 0; + dprint (2, (debugfile, "parse_overview_line: " ANUM "\n", anum)); + + /* out of bounds */ + if (anum < fc->first || anum > fc->last) + return 0; + + /* not in LISTGROUP */ + if (!fc->messages[anum - fc->first]) + { + /* progress */ + if (!ctx->quiet) + mutt_progress_update (&fc->progress, anum - fc->first + 1, -1); + return 0; + } + + /* convert overview line to header */ + mutt_mktemp (tempfile, sizeof (tempfile)); + fp = safe_fopen (tempfile, "w+"); + if (!fp) + return -1; + + header = nntp_data->nserv->overview_fmt; + while (field) + { + char *b = field; + + if (*header) + { + if (strstr (header, ":full") == NULL && fputs (header, fp) == EOF) + { + fclose (fp); + unlink (tempfile); + return -1; + } + header = strchr (header, '\0') + 1; + } + + field = strchr (field, '\t'); + if (field) + *field++ = '\0'; + if (fputs (b, fp) == EOF || fputc ('\n', fp) == EOF) + { + fclose (fp); + unlink (tempfile); + return -1; + } + } + rewind (fp); + + /* allocate memory for headers */ + if (ctx->msgcount >= ctx->hdrmax) + mx_alloc_memory (ctx); + + /* parse header */ + hdr = ctx->hdrs[ctx->msgcount] = mutt_new_header (); + hdr->env = mutt_read_rfc822_header (fp, hdr, 0, 0); + hdr->env->newsgroups = safe_strdup (nntp_data->group); + hdr->received = hdr->date_sent; + fclose (fp); + unlink (tempfile); + +#ifdef USE_HCACHE + if (fc->hc) + { + void *hdata; + char buf[16]; + + /* try to replace with header from cache */ + snprintf (buf, sizeof (buf), "%d", anum); + hdata = mutt_hcache_fetch (fc->hc, buf, strlen); + if (hdata) + { + dprint (2, (debugfile, + "parse_overview_line: mutt_hcache_fetch %s\n", buf)); + mutt_free_header (&hdr); + ctx->hdrs[ctx->msgcount] = + hdr = mutt_hcache_restore (hdata, NULL); + hdr->read = 0; + hdr->old = 0; + + /* skip header marked as deleted in cache */ + if (hdr->deleted && !fc->restore) + { + if (nntp_data->bcache) + { + dprint (2, (debugfile, + "parse_overview_line: mutt_bcache_del %s\n", buf)); + mutt_bcache_del (nntp_data->bcache, buf); + } + save = 0; + } + } + + /* not chached yet, store header */ + else + { + dprint (2, (debugfile, + "parse_overview_line: mutt_hcache_store %s\n", buf)); + mutt_hcache_store (fc->hc, buf, hdr, 0, strlen, M_GENERATE_UIDVALIDITY); + } + } +#endif + + if (save) + { + hdr->index = ctx->msgcount++; + hdr->read = 0; + hdr->old = 0; + hdr->deleted = 0; + hdr->data = safe_calloc (1, sizeof (NNTP_HEADER_DATA)); + NHDR (hdr)->article_num = anum; + if (fc->restore) + hdr->changed = 1; + else + { + nntp_article_status (ctx, hdr, NULL, anum); + if (!hdr->read) + nntp_parse_xref (ctx, hdr); + } + if (anum > nntp_data->lastLoaded) + nntp_data->lastLoaded = anum; + } + else + mutt_free_header (&hdr); + + /* progress */ + if (!ctx->quiet) + mutt_progress_update (&fc->progress, anum - fc->first + 1, -1); + return 0; +} + +/* Fetch headers */ +static int nntp_fetch_headers (CONTEXT *ctx, void *hc, + anum_t first, anum_t last, int restore) +{ + NNTP_DATA *nntp_data = ctx->data; + FETCH_CTX fc; + HEADER *hdr; + char buf[HUGE_STRING]; + int rc = 0; + int oldmsgcount = ctx->msgcount; + anum_t current; +#ifdef USE_HCACHE + void *hdata; +#endif + + /* if empty group or nothing to do */ + if (!last || first > last) + return 0; + + /* init fetch context */ + fc.ctx = ctx; + fc.first = first; + fc.last = last; + fc.restore = restore; + fc.messages = safe_calloc (last - first + 1, sizeof (unsigned char)); +#ifdef USE_HCACHE + fc.hc = hc; +#endif + + /* fetch list of articles */ + if (nntp_data->nserv->hasLISTGROUP && !nntp_data->deleted) + { + if (!ctx->quiet) + mutt_message _("Fetching list of articles..."); + snprintf (buf, sizeof (buf), "LISTGROUP %s\r\n", nntp_data->group); + rc = nntp_fetch_lines (nntp_data, buf, sizeof (buf), NULL, + fetch_numbers, &fc); + if (rc > 0) + { + mutt_error ("LISTGROUP: %s", buf); + mutt_sleep (2); + } + if (rc == 0) + { + for (current = first; current <= last && rc == 0; current++) + { + if (fc.messages[current - first]) + continue; + + snprintf (buf, sizeof (buf), "%d", current); + if (nntp_data->bcache) + { + dprint (2, (debugfile, + "nntp_fetch_headers: mutt_bcache_del %s\n", buf)); + mutt_bcache_del (nntp_data->bcache, buf); + } + +#ifdef USE_HCACHE + if (fc.hc) + { + dprint (2, (debugfile, + "nntp_fetch_headers: mutt_hcache_delete %s\n", buf)); + mutt_hcache_delete (fc.hc, buf, strlen); + } +#endif + } + } + } + else + for (current = first; current <= last; current++) + fc.messages[current - first] = 1; + + /* fetching header from cache or server, or fallback to fetch overview */ + if (!ctx->quiet) + mutt_progress_init (&fc.progress, _("Fetching message headers..."), + M_PROGRESS_MSG, ReadInc, last - first + 1); + for (current = first; current <= last && rc == 0; current++) + { + if (!ctx->quiet) + mutt_progress_update (&fc.progress, current - first + 1, -1); + +#ifdef USE_HCACHE + snprintf (buf, sizeof (buf), "%d", current); +#endif + + /* delete header from cache that does not exist on server */ + if (!fc.messages[current - first]) + continue; + + /* allocate memory for headers */ + if (ctx->msgcount >= ctx->hdrmax) + mx_alloc_memory (ctx); + +#ifdef USE_HCACHE + /* try to fetch header from cache */ + hdata = mutt_hcache_fetch (fc.hc, buf, strlen); + if (hdata) + { + dprint (2, (debugfile, + "nntp_fetch_headers: mutt_hcache_fetch %s\n", buf)); + ctx->hdrs[ctx->msgcount] = + hdr = mutt_hcache_restore (hdata, NULL); + + /* skip header marked as deleted in cache */ + if (hdr->deleted && !restore) + { + mutt_free_header (&hdr); + if (nntp_data->bcache) + { + dprint (2, (debugfile, + "nntp_fetch_headers: mutt_bcache_del %s\n", buf)); + mutt_bcache_del (nntp_data->bcache, buf); + } + continue; + } + + hdr->read = 0; + hdr->old = 0; + } + else +#endif + + /* don't try to fetch header from removed newsgroup */ + if (nntp_data->deleted) + continue; + + /* fallback to fetch overview */ + else if (nntp_data->nserv->hasOVER || nntp_data->nserv->hasXOVER) + break; + + /* fetch header from server */ + else + { + FILE *fp; + char tempfile[_POSIX_PATH_MAX]; + + mutt_mktemp (tempfile, sizeof (tempfile)); + fp = safe_fopen (tempfile, "w+"); + if (!fp) + { + mutt_perror (tempfile); + mutt_sleep (2); + unlink (tempfile); + rc = -1; + break; + } + + snprintf (buf, sizeof (buf), "HEAD %d\r\n", current); + rc = nntp_fetch_lines (nntp_data, buf, sizeof (buf), NULL, + fetch_tempfile, fp); + if (rc) + { + fclose (fp); + unlink (tempfile); + if (rc < 0) + break; + + /* invalid response */ + if (mutt_strncmp ("423", buf, 3)) + { + mutt_error ("HEAD: %s", buf); + mutt_sleep (2); + break; + } + + /* no such article */ + if (nntp_data->bcache) + { + snprintf (buf, sizeof (buf), "%d", current); + dprint (2, (debugfile, + "nntp_fetch_headers: mutt_bcache_del %s\n", buf)); + mutt_bcache_del (nntp_data->bcache, buf); + } + rc = 0; + continue; + } + + /* parse header */ + hdr = ctx->hdrs[ctx->msgcount] = mutt_new_header (); + hdr->env = mutt_read_rfc822_header (fp, hdr, 0, 0); + hdr->received = hdr->date_sent; + fclose (fp); + unlink (tempfile); + } + + /* save header in context */ + hdr->index = ctx->msgcount++; + hdr->read = 0; + hdr->old = 0; + hdr->deleted = 0; + hdr->data = safe_calloc (1, sizeof (NNTP_HEADER_DATA)); + NHDR (hdr)->article_num = current; + if (restore) + hdr->changed = 1; + else + { + nntp_article_status (ctx, hdr, NULL, NHDR (hdr)->article_num); + if (!hdr->read) + nntp_parse_xref (ctx, hdr); + } + if (current > nntp_data->lastLoaded) + nntp_data->lastLoaded = current; + } + + /* fetch overview information */ + if (current <= last && rc == 0) { + char *cmd = nntp_data->nserv->hasOVER ? "OVER" : "XOVER"; + snprintf (buf, sizeof (buf), "%s %d-%d\r\n", cmd, current, last); + rc = nntp_fetch_lines (nntp_data, buf, sizeof (buf), NULL, + parse_overview_line, &fc); + if (rc > 0) + { + mutt_error ("%s: %s", cmd, buf); + mutt_sleep (2); + } + } + + if (ctx->msgcount > oldmsgcount) + mx_update_context (ctx, ctx->msgcount - oldmsgcount); + + FREE (&fc.messages); + if (rc != 0) + return -1; + mutt_clear_error (); + return 0; +} + +/* Open newsgroup */ +int nntp_open_mailbox (CONTEXT *ctx) +{ + NNTP_SERVER *nserv; + NNTP_DATA *nntp_data; + char buf[HUGE_STRING]; + char server[LONG_STRING]; + char *group; + int rc; + void *hc = NULL; + anum_t first, last, count = 0; + ciss_url_t url; + + strfcpy (buf, ctx->path, sizeof (buf)); + if (url_parse_ciss (&url, buf) < 0 || !url.path || + !(url.scheme == U_NNTP || url.scheme == U_NNTPS)) + { + mutt_error (_("%s is an invalid newsgroup specification!"), ctx->path); + mutt_sleep (2); + return -1; + } + + group = url.path; + url.path = strchr (url.path, '\0'); + url_ciss_tostring (&url, server, sizeof (server), 0); + nserv = nntp_select_server (server, 1); + if (!nserv) + return -1; + CurrentNewsSrv = nserv; + + /* find news group data structure */ + nntp_data = hash_find (nserv->groups_hash, group); + if (!nntp_data) + { + nntp_newsrc_close (nserv); + mutt_error (_("Newsgroup %s not found on the server."), group); + mutt_sleep (2); + return -1; + } + + mutt_bit_unset (ctx->rights, M_ACL_INSERT); + if (!nntp_data->newsrc_ent && !nntp_data->subscribed && + !option (OPTSAVEUNSUB)) + ctx->readonly = 1; + + /* select newsgroup */ + mutt_message (_("Selecting %s..."), group); + buf[0] = '\0'; + if (nntp_query (nntp_data, buf, sizeof (buf)) < 0) + { + nntp_newsrc_close (nserv); + return -1; + } + + /* newsgroup not found, remove it */ + if (!mutt_strncmp ("411", buf, 3)) + { + mutt_error (_("Newsgroup %s has been removed from the server."), + nntp_data->group); + if (!nntp_data->deleted) + { + nntp_data->deleted = 1; + nntp_active_save_cache (nserv); + } + if (nntp_data->newsrc_ent && !nntp_data->subscribed && + !option (OPTSAVEUNSUB)) + { + FREE (&nntp_data->newsrc_ent); + nntp_data->newsrc_len = 0; + nntp_delete_group_cache (nntp_data); + nntp_newsrc_update (nserv); + } + mutt_sleep (2); + } + + /* parse newsgroup info */ + else { + if (sscanf (buf, "211 " ANUM " " ANUM " " ANUM, &count, &first, &last) != 3) + { + nntp_newsrc_close (nserv); + mutt_error ("GROUP: %s", buf); + mutt_sleep (2); + return -1; + } + nntp_data->firstMessage = first; + nntp_data->lastMessage = last; + nntp_data->deleted = 0; + + /* get description if empty */ + if (option (OPTLOADDESC) && !nntp_data->desc) + { + if (get_description (nntp_data, NULL, NULL) < 0) + { + nntp_newsrc_close (nserv); + return -1; + } + if (nntp_data->desc) + nntp_active_save_cache (nserv); + } + } + + time (&nserv->check_time); + ctx->data = nntp_data; + ctx->mx_close = nntp_fastclose_mailbox; + if (!nntp_data->bcache && (nntp_data->newsrc_ent || + nntp_data->subscribed || option (OPTSAVEUNSUB))) + nntp_data->bcache = mutt_bcache_open (&nserv->conn->account, + nntp_data->group); + + /* strip off extra articles if adding context is greater than $nntp_context */ + first = nntp_data->firstMessage; + if (NntpContext && nntp_data->lastMessage - first + 1 > NntpContext) + first = nntp_data->lastMessage - NntpContext + 1; + nntp_data->lastLoaded = first ? first - 1 : 0; + count = nntp_data->firstMessage; + nntp_data->firstMessage = first; + nntp_bcache_update (nntp_data); + nntp_data->firstMessage = count; +#ifdef USE_HCACHE + hc = nntp_hcache_open (nntp_data); + nntp_hcache_update (nntp_data, hc); +#endif + if (!hc) + { + mutt_bit_unset (ctx->rights, M_ACL_WRITE); + mutt_bit_unset (ctx->rights, M_ACL_DELETE); + } + nntp_newsrc_close (nserv); + rc = nntp_fetch_headers (ctx, hc, first, nntp_data->lastMessage, 0); +#ifdef USE_HCACHE + mutt_hcache_close (hc); +#endif + if (rc < 0) + return -1; + nntp_data->lastLoaded = nntp_data->lastMessage; + nserv->newsrc_modified = 0; + return 0; +} + +/* Fetch message */ +int nntp_fetch_message (MESSAGE *msg, CONTEXT *ctx, int msgno) +{ + NNTP_DATA *nntp_data = ctx->data; + NNTP_ACACHE *acache; + HEADER *hdr = ctx->hdrs[msgno]; + char buf[_POSIX_PATH_MAX]; + char article[16]; + char *fetch_msg = _("Fetching message..."); + int rc; + + /* try to get article from cache */ + acache = &nntp_data->acache[hdr->index % NNTP_ACACHE_LEN]; + if (acache->path) + { + if (acache->index == hdr->index) + { + msg->fp = fopen (acache->path, "r"); + if (msg->fp) + return 0; + } + /* clear previous entry */ + else + { + unlink (acache->path); + FREE (&acache->path); + } + } + snprintf (article, sizeof (article), "%d", NHDR (hdr)->article_num); + msg->fp = mutt_bcache_get (nntp_data->bcache, article); + if (msg->fp) + { + if (NHDR (hdr)->parsed) + return 0; + } + else + { + /* don't try to fetch article from removed newsgroup */ + if (nntp_data->deleted) + return -1; + + /* create new cache file */ + mutt_message (fetch_msg); + msg->fp = mutt_bcache_put (nntp_data->bcache, article, 1); + if (!msg->fp) + { + mutt_mktemp (buf, sizeof (buf)); + acache->path = safe_strdup (buf); + acache->index = hdr->index; + msg->fp = safe_fopen (acache->path, "w+"); + if (!msg->fp) + { + mutt_perror (acache->path); + unlink (acache->path); + FREE (&acache->path); + return -1; + } + } + + /* fetch message to cache file */ + snprintf (buf, sizeof (buf), "ARTICLE %s\r\n", + NHDR (hdr)->article_num ? article : hdr->env->message_id); + rc = nntp_fetch_lines (nntp_data, buf, sizeof (buf), fetch_msg, + fetch_tempfile, msg->fp); + if (rc) + { + safe_fclose (&msg->fp); + if (acache->path) + { + unlink (acache->path); + FREE (&acache->path); + } + if (rc > 0) + { + if (!mutt_strncmp (NHDR (hdr)->article_num ? "423" : "430", buf, 3)) + mutt_error (_("Article %d not found on the server."), + NHDR (hdr)->article_num ? article : hdr->env->message_id); + else + mutt_error ("ARTICLE: %s", buf); + } + return -1; + } + + if (!acache->path) + mutt_bcache_commit (nntp_data->bcache, article); + } + + /* replace envelope with new one + * hash elements must be updated because pointers will be changed */ + if (ctx->id_hash && hdr->env->message_id) + hash_delete (ctx->id_hash, hdr->env->message_id, hdr, NULL); + if (ctx->subj_hash && hdr->env->real_subj) + hash_delete (ctx->subj_hash, hdr->env->real_subj, hdr, NULL); + + mutt_free_envelope (&hdr->env); + hdr->env = mutt_read_rfc822_header (msg->fp, hdr, 0, 0); + + if (ctx->id_hash && hdr->env->message_id) + hash_insert (ctx->id_hash, hdr->env->message_id, hdr, 0); + if (ctx->subj_hash && hdr->env->real_subj) + hash_insert (ctx->subj_hash, hdr->env->real_subj, hdr, 1); + + /* fix content length */ + fseek (msg->fp, 0, SEEK_END); + hdr->content->length = ftell (msg->fp) - hdr->content->offset; + + /* this is called in mutt before the open which fetches the message, + * which is probably wrong, but we just call it again here to handle + * the problem instead of fixing it */ + NHDR (hdr)->parsed = 1; + mutt_parse_mime_message (ctx, hdr); + + /* these would normally be updated in mx_update_context(), but the + * full headers aren't parsed with overview, so the information wasn't + * available then */ + if (WithCrypto) + hdr->security = crypt_query (hdr->content); + + rewind (msg->fp); + mutt_clear_error(); + return 0; +} + +/* Post article */ +int nntp_post (const char *msg) { + NNTP_DATA *nntp_data, nntp_tmp; + FILE *fp; + char buf[LONG_STRING]; + size_t len; + + if (Context && Context->magic == M_NNTP) + nntp_data = Context->data; + else + { + CurrentNewsSrv = nntp_select_server (NewsServer, 0); + if (!CurrentNewsSrv) + return -1; + + nntp_data = &nntp_tmp; + nntp_data->nserv = CurrentNewsSrv; + nntp_data->group = NULL; + } + + fp = safe_fopen (msg, "r"); + if (!fp) + { + mutt_perror (msg); + return -1; + } + + strfcpy (buf, "POST\r\n", sizeof (buf)); + if (nntp_query (nntp_data, buf, sizeof (buf)) < 0) + return -1; + if (buf[0] != '3') + { + mutt_error (_("Can't post article: %s"), buf); + return -1; + } + + buf[0] = '.'; + buf[1] = '\0'; + while (fgets (buf + 1, sizeof (buf) - 2, fp)) + { + len = strlen (buf); + if (buf[len - 1] == '\n') + { + buf[len - 1] = '\r'; + buf[len] = '\n'; + len++; + buf[len] = '\0'; + } + if (mutt_socket_write_d (nntp_data->nserv->conn, + buf[1] == '.' ? buf : buf + 1, -1, M_SOCK_LOG_HDR) < 0) + return nntp_connect_error (nntp_data->nserv); + } + fclose (fp); + + if ((buf[strlen (buf) - 1] != '\n' && + mutt_socket_write_d (nntp_data->nserv->conn, "\r\n", -1, M_SOCK_LOG_HDR) < 0) || + mutt_socket_write_d (nntp_data->nserv->conn, ".\r\n", -1, M_SOCK_LOG_HDR) < 0 || + mutt_socket_readln (buf, sizeof (buf), nntp_data->nserv->conn) < 0) + return nntp_connect_error (nntp_data->nserv); + if (buf[0] != '2') + { + mutt_error (_("Can't post article: %s"), buf); + return -1; + } + return 0; +} + +/* Save changes to .newsrc and cache */ +int nntp_sync_mailbox (CONTEXT *ctx) +{ + NNTP_DATA *nntp_data = ctx->data; + int rc, i; +#ifdef USE_HCACHE + header_cache_t *hc; +#endif + + /* check for new articles */ + nntp_data->nserv->check_time = 0; + rc = nntp_check_mailbox (ctx, 1); + if (rc) + return rc; + +#ifdef USE_HCACHE + nntp_data->lastCached = 0; + hc = nntp_hcache_open (nntp_data); +#endif + + nntp_data->unread = ctx->unread; + for (i = 0; i < ctx->msgcount; i++) + { + HEADER *hdr = ctx->hdrs[i]; + char buf[16]; + + snprintf (buf, sizeof (buf), "%d", NHDR (hdr)->article_num); + if (nntp_data->bcache && hdr->deleted) + { + dprint (2, (debugfile, "nntp_sync_mailbox: mutt_bcache_del %s\n", buf)); + mutt_bcache_del (nntp_data->bcache, buf); + } + +#ifdef USE_HCACHE + if (hc && (hdr->changed || hdr->deleted)) + { + if (hdr->deleted && !hdr->read) + nntp_data->unread--; + dprint (2, (debugfile, "nntp_sync_mailbox: mutt_hcache_store %s\n", buf)); + mutt_hcache_store (hc, buf, hdr, 0, strlen, M_GENERATE_UIDVALIDITY); + } +#endif + } + +#ifdef USE_HCACHE + if (hc) + { + mutt_hcache_close (hc); + nntp_data->lastCached = nntp_data->lastLoaded; + } +#endif + + /* save .newsrc entries */ + nntp_newsrc_gen_entries (ctx); + nntp_newsrc_update (nntp_data->nserv); + nntp_newsrc_close (nntp_data->nserv); + return 0; +} + +/* Free up memory associated with the newsgroup context */ +int nntp_fastclose_mailbox (CONTEXT *ctx) +{ + NNTP_DATA *nntp_data = ctx->data, *nntp_tmp; + + if (!nntp_data) + return 0; + + nntp_acache_free (nntp_data); + if (!nntp_data->nserv || !nntp_data->nserv->groups_hash || !nntp_data->group) + return 0; + + nntp_tmp = hash_find (nntp_data->nserv->groups_hash, nntp_data->group); + if (nntp_tmp == NULL || nntp_tmp != nntp_data) + nntp_data_free (nntp_data); + return 0; +} + +/* Get date and time from server */ +int nntp_date (NNTP_SERVER *nserv, time_t *now) +{ + if (nserv->hasDATE) + { + NNTP_DATA nntp_data; + char buf[LONG_STRING]; + struct tm tm; + + nntp_data.nserv = nserv; + nntp_data.group = NULL; + strfcpy (buf, "DATE\r\n", sizeof (buf)); + if (nntp_query (&nntp_data, buf, sizeof (buf)) < 0) + return -1; + + if (sscanf (buf, "111 %4d%2d%2d%2d%2d%2d%*s", &tm.tm_year, &tm.tm_mon, + &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) == 6) + { + tm.tm_year -= 1900; + tm.tm_mon--; + *now = timegm (&tm); + if (*now >= 0) + { + dprint (1, (debugfile, "nntp_date: server time is %d\n", *now)); + return 0; + } + } + } + time (now); + return 0; +} + +/* Fetch list of all newsgroups from server */ +int nntp_active_fetch (NNTP_SERVER *nserv) +{ + NNTP_DATA nntp_data; + char msg[SHORT_STRING]; + char buf[LONG_STRING]; + unsigned int i; + int rc; + + snprintf (msg, sizeof (msg), _("Loading list of groups from server %s..."), + nserv->conn->account.host); + mutt_message (msg); + if (nntp_date (nserv, &nserv->newgroups_time) < 0) + return -1; + + nntp_data.nserv = nserv; + nntp_data.group = NULL; + strfcpy (buf, "LIST\r\n", sizeof (buf)); + rc = nntp_fetch_lines (&nntp_data, buf, sizeof (buf), msg, + nntp_add_group, nserv); + if (rc) + { + if (rc > 0) + { + mutt_error ("LIST: %s", buf); + mutt_sleep (2); + } + return -1; + } + + if (option (OPTLOADDESC) && + get_description (&nntp_data, "*", _("Loading descriptions...")) < 0) + return -1; + + for (i = 0; i < nserv->groups_num; i++) + { + NNTP_DATA *nntp_data = nserv->groups_list[i]; + + if (nntp_data && nntp_data->deleted && !nntp_data->newsrc_ent) + { + nntp_delete_group_cache (nntp_data); + hash_delete (nserv->groups_hash, nntp_data->group, NULL, nntp_data_free); + nserv->groups_list[i] = NULL; + } + } + nntp_active_save_cache (nserv); + mutt_clear_error (); + return 0; +} + +/* Check newsgroup for new articles: + * 1 - new articles found + * 0 - no change + * -1 - lost connection */ +static int nntp_group_poll (NNTP_DATA *nntp_data, int update_stat) +{ + char buf[LONG_STRING] = ""; + anum_t count, first, last; + + /* use GROUP command to poll newsgroup */ + if (nntp_query (nntp_data, buf, sizeof (buf)) < 0) + return -1; + if (sscanf (buf, "211 " ANUM " " ANUM " " ANUM, &count, &first, &last) != 3) + return 0; + if (first == nntp_data->firstMessage && last == nntp_data->lastMessage) + return 0; + + /* articles have been renumbered */ + if (last < nntp_data->lastMessage) + { + nntp_data->lastCached = 0; + if (nntp_data->newsrc_len) + { + safe_realloc (&nntp_data->newsrc_ent, sizeof (NEWSRC_ENTRY)); + nntp_data->newsrc_len = 1; + nntp_data->newsrc_ent[0].first = 1; + nntp_data->newsrc_ent[0].last = 0; + } + } + nntp_data->firstMessage = first; + nntp_data->lastMessage = last; + if (!update_stat) + return 1; + + /* update counters */ + else if (!last || (!nntp_data->newsrc_ent && !nntp_data->lastCached)) + nntp_data->unread = count; + else + nntp_group_unread_stat (nntp_data); + return 1; +} + +/* Check current newsgroup for new articles: + * M_REOPENED - articles have been renumbered or removed from server + * M_NEW_MAIL - new articles found + * 0 - no change + * -1 - lost connection */ +int nntp_check_mailbox (CONTEXT *ctx, int leave_lock) +{ + NNTP_DATA *nntp_data = ctx->data; + NNTP_SERVER *nserv = nntp_data->nserv; + time_t now = time (NULL); + int i, j; + int rc, ret = 0; + void *hc = NULL; + + if (nserv->check_time + NewsPollTimeout > now) + return 0; + + mutt_message _("Checking for new messages..."); + if (nntp_newsrc_parse (nserv) < 0) + return -1; + + nserv->check_time = now; + rc = nntp_group_poll (nntp_data, 0); + if (rc < 0) + { + nntp_newsrc_close (nserv); + return -1; + } + if (rc) + nntp_active_save_cache (nserv); + + /* articles have been renumbered, remove all headers */ + if (nntp_data->lastMessage < nntp_data->lastLoaded) + { + for (i = 0; i < ctx->msgcount; i++) + mutt_free_header (&ctx->hdrs[i]); + ctx->msgcount = 0; + ctx->tagged = 0; + + if (nntp_data->lastMessage < nntp_data->lastLoaded) + { + nntp_data->lastLoaded = nntp_data->firstMessage - 1; + if (NntpContext && nntp_data->lastMessage - nntp_data->lastLoaded > + NntpContext) + nntp_data->lastLoaded = nntp_data->lastMessage - NntpContext; + } + ret = M_REOPENED; + } + + /* .newsrc has been externally modified */ + if (nserv->newsrc_modified) + { + anum_t anum; +#ifdef USE_HCACHE + unsigned char *messages; + char buf[16]; + void *hdata; + HEADER *hdr; + anum_t first = nntp_data->firstMessage; + + if (NntpContext && nntp_data->lastMessage - first + 1 > NntpContext) + first = nntp_data->lastMessage - NntpContext + 1; + messages = safe_calloc (nntp_data->lastLoaded - first + 1, + sizeof (unsigned char)); + hc = nntp_hcache_open (nntp_data); + nntp_hcache_update (nntp_data, hc); +#endif + + /* update flags according to .newsrc */ + for (i = j = 0; i < ctx->msgcount; i++) + { + int flagged = 0; + anum = NHDR (ctx->hdrs[i])->article_num; + +#ifdef USE_HCACHE + /* check hcache for flagged and deleted flags */ + if (hc) + { + if (anum >= first && anum <= nntp_data->lastLoaded) + messages[anum - first] = 1; + + snprintf (buf, sizeof (buf), "%d", anum); + hdata = mutt_hcache_fetch (hc, buf, strlen); + if (hdata) + { + int deleted; + + dprint (2, (debugfile, + "nntp_check_mailbox: mutt_hcache_fetch %s\n", buf)); + hdr = mutt_hcache_restore (hdata, NULL); + deleted = hdr->deleted; + flagged = hdr->flagged; + mutt_free_header (&hdr); + + /* header marked as deleted, removing from context */ + if (deleted) + { + mutt_set_flag (ctx, ctx->hdrs[i], M_TAG, 0); + mutt_free_header (&ctx->hdrs[i]); + continue; + } + } + } +#endif + + if (!ctx->hdrs[i]->changed) + { + ctx->hdrs[i]->flagged = flagged; + ctx->hdrs[i]->read = 0; + ctx->hdrs[i]->old = 0; + nntp_article_status (ctx, ctx->hdrs[i], NULL, anum); + if (!ctx->hdrs[i]->read) + nntp_parse_xref (ctx, ctx->hdrs[i]); + } + ctx->hdrs[j++] = ctx->hdrs[i]; + } + +#ifdef USE_HCACHE + ctx->msgcount = j; + + /* restore headers without "deleted" flag */ + for (anum = first; anum <= nntp_data->lastLoaded; anum++) + { + if (messages[anum - first]) + continue; + + snprintf (buf, sizeof (buf), "%d", anum); + hdata = mutt_hcache_fetch (hc, buf, strlen); + if (hdata) + { + dprint (2, (debugfile, + "nntp_check_mailbox: mutt_hcache_fetch %s\n", buf)); + if (ctx->msgcount >= ctx->hdrmax) + mx_alloc_memory (ctx); + + ctx->hdrs[ctx->msgcount] = + hdr = mutt_hcache_restore (hdata, NULL); + if (hdr->deleted) + { + mutt_free_header (&hdr); + if (nntp_data->bcache) + { + dprint (2, (debugfile, + "nntp_check_mailbox: mutt_bcache_del %s\n", buf)); + mutt_bcache_del (nntp_data->bcache, buf); + } + continue; + } + + ctx->msgcount++; + hdr->read = 0; + hdr->old = 0; + hdr->data = safe_calloc (1, sizeof (NNTP_HEADER_DATA)); + NHDR (hdr)->article_num = anum; + nntp_article_status (ctx, hdr, NULL, anum); + if (!hdr->read) + nntp_parse_xref (ctx, hdr); + } + } + FREE (&messages); +#endif + + nserv->newsrc_modified = 0; + ret = M_REOPENED; + } + + /* some headers were removed, context must be updated */ + if (ret == M_REOPENED) + { + if (ctx->subj_hash) + hash_destroy (&ctx->subj_hash, NULL); + if (ctx->id_hash) + hash_destroy (&ctx->id_hash, NULL); + mutt_clear_threads (ctx); + + ctx->vcount = 0; + ctx->deleted = 0; + ctx->new = 0; + ctx->unread = 0; + ctx->flagged = 0; + ctx->changed = 0; + ctx->id_hash = NULL; + ctx->subj_hash = NULL; + mx_update_context (ctx, ctx->msgcount); + } + + /* fetch headers of new articles */ + if (nntp_data->lastMessage > nntp_data->lastLoaded) + { + int oldmsgcount = ctx->msgcount; + int quiet = ctx->quiet; + ctx->quiet = 1; +#ifdef USE_HCACHE + if (!hc) + { + hc = nntp_hcache_open (nntp_data); + nntp_hcache_update (nntp_data, hc); + } +#endif + rc = nntp_fetch_headers (ctx, hc, nntp_data->lastLoaded + 1, + nntp_data->lastMessage, 0); + ctx->quiet = quiet; + if (rc >= 0) + nntp_data->lastLoaded = nntp_data->lastMessage; + if (ret == 0 && ctx->msgcount > oldmsgcount) + ret = M_NEW_MAIL; + } + +#ifdef USE_HCACHE + mutt_hcache_close (hc); +#endif + if (ret || !leave_lock) + nntp_newsrc_close (nserv); + mutt_clear_error (); + return ret; +} + +/* Check for new groups and new articles in subscribed groups: + * 1 - new groups found + * 0 - no new groups + * -1 - error */ +int nntp_check_new_groups (NNTP_SERVER *nserv) +{ + NNTP_DATA nntp_data; + time_t now; + struct tm *tm; + char buf[LONG_STRING]; + char *msg = _("Checking for new newsgroups..."); + unsigned int i; + int rc, update_active = FALSE; + + if (!nserv || !nserv->newgroups_time) + return -1; + + /* check subscribed newsgroups for new articles */ + if (option (OPTSHOWNEWNEWS)) + { + mutt_message _("Checking for new messages..."); + for (i = 0; i < nserv->groups_num; i++) + { + NNTP_DATA *nntp_data = nserv->groups_list[i]; + + if (nntp_data && nntp_data->subscribed) + { + rc = nntp_group_poll (nntp_data, 1); + if (rc < 0) + return -1; + if (rc > 0) + update_active = TRUE; + } + } + } + else if (nserv->newgroups_time) + return 0; + + /* get list of new groups */ + mutt_message (msg); + if (nntp_date (nserv, &now) < 0) + return -1; + nntp_data.nserv = nserv; + if (Context && Context->magic == M_NNTP) + nntp_data.group = ((NNTP_DATA *)Context->data)->group; + else + nntp_data.group = NULL; + i = nserv->groups_num; + tm = gmtime (&nserv->newgroups_time); + snprintf (buf, sizeof (buf), "NEWGROUPS %02d%02d%02d %02d%02d%02d GMT\r\n", + tm->tm_year % 100, tm->tm_mon + 1, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec); + rc = nntp_fetch_lines (&nntp_data, buf, sizeof (buf), msg, + nntp_add_group, nserv); + if (rc) + { + if (rc > 0) + { + mutt_error ("NEWGROUPS: %s", buf); + mutt_sleep (2); + } + return -1; + } + + /* new groups found */ + rc = 0; + if (nserv->groups_num != i) + { + nserv->newgroups_time = now; + + /* loading descriptions */ + if (option (OPTLOADDESC)) + { + unsigned int count = 0; + progress_t progress; + + mutt_progress_init (&progress, _("Loading descriptions..."), + M_PROGRESS_MSG, ReadInc, nserv->groups_num - i); + for (; i < nserv->groups_num; i++) + { + NNTP_DATA *nntp_data = nserv->groups_list[i]; + + if (get_description (nntp_data, NULL, NULL) < 0) + return -1; + mutt_progress_update (&progress, ++count, -1); + } + } + update_active = TRUE; + rc = 1; + } + if (update_active) + nntp_active_save_cache (nserv); + mutt_clear_error (); + return rc; +} + +/* Fetch article by Message-ID: + * 0 - success + * 1 - no such article + * -1 - error */ +int nntp_check_msgid (CONTEXT *ctx, const char *msgid) +{ + NNTP_DATA *nntp_data = ctx->data; + HEADER *hdr; + FILE *fp; + char tempfile[_POSIX_PATH_MAX]; + char buf[LONG_STRING]; + int rc; + + mutt_mktemp (tempfile, sizeof (tempfile)); + fp = safe_fopen (tempfile, "w+"); + if (!fp) + { + mutt_perror (tempfile); + unlink (tempfile); + return -1; + } + + snprintf (buf, sizeof (buf), "HEAD %s\r\n", msgid); + rc = nntp_fetch_lines (nntp_data, buf, sizeof (buf), NULL, + fetch_tempfile, fp); + if (rc) + { + fclose (fp); + unlink (tempfile); + if (rc < 0) + return -1; + if (!mutt_strncmp ("430", buf, 3)) + return 1; + mutt_error ("HEAD: %s", buf); + return -1; + } + + /* parse header */ + if (ctx->msgcount == ctx->hdrmax) + mx_alloc_memory (ctx); + hdr = ctx->hdrs[ctx->msgcount] = mutt_new_header (); + hdr->data = safe_calloc (1, sizeof (NNTP_HEADER_DATA)); + hdr->env = mutt_read_rfc822_header (fp, hdr, 0, 0); + fclose (fp); + unlink (tempfile); + + /* get article number */ + if (hdr->env->xref) + nntp_parse_xref (ctx, hdr); + else + { + snprintf (buf, sizeof (buf), "STAT %s\r\n", msgid); + if (nntp_query (nntp_data, buf, sizeof (buf)) < 0) + { + mutt_free_header (&hdr); + return -1; + } + sscanf (buf + 4, ANUM, &NHDR (hdr)->article_num); + } + + /* reset flags */ + hdr->read = 0; + hdr->old = 0; + hdr->deleted = 0; + hdr->changed = 1; + hdr->received = hdr->date_sent; + hdr->index = ctx->msgcount++; + mx_update_context (ctx, 1); + return 0; +} + +typedef struct +{ + CONTEXT *ctx; + unsigned int num; + unsigned int max; + anum_t *child; +} CHILD_CTX; + +/* Parse XPAT line */ +static int fetch_children (char *line, void *data) +{ + CHILD_CTX *cc = data; + anum_t anum; + unsigned int i; + + if (!line || sscanf (line, ANUM, &anum) != 1) + return 0; + for (i = 0; i < cc->ctx->msgcount; i++) + if (NHDR (cc->ctx->hdrs[i])->article_num == anum) + return 0; + if (cc->num >= cc->max) + { + cc->max *= 2; + safe_realloc (&cc->child, sizeof (anum_t) * cc->max); + } + cc->child[cc->num++] = anum; + return 0; +} + +/* Fetch children of article with the Message-ID */ +int nntp_check_children (CONTEXT *ctx, const char *msgid) +{ + NNTP_DATA *nntp_data = ctx->data; + CHILD_CTX cc; + char buf[STRING]; + int i, rc, quiet; + void *hc = NULL; + + if (!nntp_data || !nntp_data->nserv) + return -1; + if (nntp_data->firstMessage > nntp_data->lastLoaded) + return 0; + + /* init context */ + cc.ctx = ctx; + cc.num = 0; + cc.max = 10; + cc.child = safe_malloc (sizeof (anum_t) * cc.max); + + /* fetch numbers of child messages */ + snprintf (buf, sizeof (buf), "XPAT References %d-%d *%s*\r\n", + nntp_data->firstMessage, nntp_data->lastLoaded, msgid); + rc = nntp_fetch_lines (nntp_data, buf, sizeof (buf), NULL, + fetch_children, &cc); + if (rc) + { + FREE (&cc.child); + if (rc > 0) { + if (mutt_strncmp ("500", buf, 3)) + mutt_error ("XPAT: %s", buf); + else + mutt_error _("Unable to find child articles because server does not support XPAT command."); + } + return -1; + } + + /* fetch all found messages */ + quiet = ctx->quiet; + ctx->quiet = 1; +#ifdef USE_HCACHE + hc = nntp_hcache_open (nntp_data); +#endif + for (i = 0; i < cc.num; i++) + { + rc = nntp_fetch_headers (ctx, hc, cc.child[i], cc.child[i], 1); + if (rc < 0) + break; + } +#ifdef USE_HCACHE + mutt_hcache_close (hc); +#endif + ctx->quiet = quiet; + FREE (&cc.child); + return rc < 0 ? -1 : 0; +} diff --git a/nntp.h b/nntp.h new file mode 100644 index 0000000..c937034 --- /dev/null +++ b/nntp.h @@ -0,0 +1,167 @@ +/* + * Copyright (C) 1998 Brandon Long + * Copyright (C) 1999 Andrej Gritsenko + * Copyright (C) 2000-2012 Vsevolod Volkov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _NNTP_H_ +#define _NNTP_H_ 1 + +#include "mutt_socket.h" +#include "mailbox.h" +#include "bcache.h" + +#if USE_HCACHE +#include "hcache.h" +#endif + +#include +#include +#include + +#define NNTP_PORT 119 +#define NNTP_SSL_PORT 563 + +/* number of entries in article cache */ +#define NNTP_ACACHE_LEN 10 + +/* article number type and format */ +#define anum_t uint32_t +#define ANUM "%u" + +enum +{ + NNTP_NONE = 0, + NNTP_OK, + NNTP_BYE +}; + +typedef struct +{ + unsigned int hasCAPABILITIES : 1; + unsigned int hasSTARTTLS : 1; + unsigned int hasDATE : 1; + unsigned int hasLIST_NEWSGROUPS : 1; + unsigned int hasXGTITLE : 1; + unsigned int hasLISTGROUP : 1; + unsigned int hasOVER : 1; + unsigned int hasXOVER : 1; + unsigned int use_tls : 3; + unsigned int status : 3; + unsigned int cacheable : 1; + unsigned int newsrc_modified : 1; + FILE *newsrc_fp; + char *newsrc_file; + char *authenticators; + char *overview_fmt; + off_t size; + time_t mtime; + time_t newgroups_time; + time_t check_time; + unsigned int groups_num; + unsigned int groups_max; + void **groups_list; + HASH *groups_hash; + CONNECTION *conn; +} NNTP_SERVER; + +typedef struct +{ + anum_t first; + anum_t last; +} NEWSRC_ENTRY; + +typedef struct +{ + unsigned int index; + char *path; +} NNTP_ACACHE; + +typedef struct +{ + char *group; + char *desc; + anum_t firstMessage; + anum_t lastMessage; + anum_t lastLoaded; + anum_t lastCached; + anum_t unread; + unsigned int subscribed : 1; + unsigned int new : 1; + unsigned int allowed : 1; + unsigned int deleted : 1; + unsigned int newsrc_len; + NEWSRC_ENTRY *newsrc_ent; + NNTP_SERVER *nserv; + NNTP_ACACHE acache[NNTP_ACACHE_LEN]; + body_cache_t *bcache; +} NNTP_DATA; + +typedef struct +{ + anum_t article_num; + unsigned int parsed : 1; +} NNTP_HEADER_DATA; + +#define NHDR(hdr) ((NNTP_HEADER_DATA*)((hdr)->data)) + +/* internal functions */ +int nntp_add_group (char *, void *); +int nntp_active_save_cache (NNTP_SERVER *); +int nntp_check_new_groups (NNTP_SERVER *); +int nntp_fastclose_mailbox (CONTEXT *); +int nntp_open_connection (NNTP_SERVER *); +void nntp_newsrc_gen_entries (CONTEXT *); +void nntp_bcache_update (NNTP_DATA *); +void nntp_article_status (CONTEXT *, HEADER *, char *, anum_t); +void nntp_group_unread_stat (NNTP_DATA *); +void nntp_data_free (void *); +void nntp_acache_free (NNTP_DATA *); +void nntp_delete_group_cache (NNTP_DATA *); + +/* exposed interface */ +NNTP_SERVER *nntp_select_server (char *, int); +NNTP_DATA *mutt_newsgroup_subscribe (NNTP_SERVER *, char *); +NNTP_DATA *mutt_newsgroup_unsubscribe (NNTP_SERVER *, char *); +NNTP_DATA *mutt_newsgroup_catchup (NNTP_SERVER *, char *); +NNTP_DATA *mutt_newsgroup_uncatchup (NNTP_SERVER *, char *); +int nntp_active_fetch (NNTP_SERVER *); +int nntp_newsrc_update (NNTP_SERVER *); +int nntp_open_mailbox (CONTEXT *); +int nntp_sync_mailbox (CONTEXT *); +int nntp_check_mailbox (CONTEXT *, int); +int nntp_fetch_message (MESSAGE *, CONTEXT *, int); +int nntp_post (const char *); +int nntp_check_msgid (CONTEXT *, const char *); +int nntp_check_children (CONTEXT *, const char *); +int nntp_newsrc_parse (NNTP_SERVER *); +void nntp_newsrc_close (NNTP_SERVER *); +void nntp_buffy (char *, size_t); +void nntp_expand_path (char *, size_t, ACCOUNT *); +void nntp_clear_cache (NNTP_SERVER *); +const char *nntp_format_str (char *, size_t, size_t, char, const char *, + const char *, const char *, const char *, + unsigned long, format_flag); + +NNTP_SERVER *CurrentNewsSrv INITVAL (NULL); + +#ifdef USE_HCACHE +header_cache_t *nntp_hcache_open (NNTP_DATA *); +void nntp_hcache_update (NNTP_DATA *, header_cache_t *); +#endif + +#endif /* _NNTP_H_ */ diff --git a/pager.c b/pager.c index 696e55c..8e38e38 100644 --- a/pager.c +++ b/pager.c @@ -1085,6 +1085,11 @@ fill_buffer (FILE *f, LOFF_T *last_pos, LOFF_T offset, unsigned char **buf, return b_read; } +#ifdef USE_NNTP +#include "mx.h" +#include "nntp.h" +#endif + static int format_line (struct line_t **lineInfo, int n, unsigned char *buf, int flags, ansi_attr *pa, int cnt, @@ -1543,6 +1548,16 @@ static const struct mapping_t PagerHelpExtra[] = { { NULL, 0 } }; +#ifdef USE_NNTP +static struct mapping_t PagerNewsHelpExtra[] = { + { N_("Post"), OP_POST }, + { N_("Followup"), OP_FOLLOWUP }, + { N_("Del"), OP_DELETE }, + { N_("Next"), OP_MAIN_NEXT_UNDELETED }, + { NULL, 0 } +}; +#endif + /* This pager is actually not so simple as it once was. It now operates in @@ -1584,6 +1599,10 @@ mutt_pager (const char *banner, const char *fname, int flags, pager_t *extra) int old_PagerIndexLines; /* some people want to resize it * while inside the pager... */ +#ifdef USE_NNTP + char *followup_to; +#endif + if (!(flags & M_SHOWCOLOR)) flags |= M_SHOWFLAT; @@ -1623,7 +1642,11 @@ mutt_pager (const char *banner, const char *fname, int flags, pager_t *extra) if (IsHeader (extra)) { strfcpy (tmphelp, helpstr, sizeof (tmphelp)); - mutt_compile_help (buffer, sizeof (buffer), MENU_PAGER, PagerHelpExtra); + mutt_compile_help (buffer, sizeof (buffer), MENU_PAGER, +#ifdef USE_NNTP + (Context && (Context->magic == M_NNTP)) ? PagerNewsHelpExtra : +#endif + PagerHelpExtra); snprintf (helpstr, sizeof (helpstr), "%s %s", tmphelp, buffer); } if (!InHelp) @@ -2551,6 +2574,60 @@ search_next: redraw = REDRAW_FULL; break; +#ifdef USE_NNTP + case OP_POST: + CHECK_MODE(IsHeader (extra) && !IsAttach (extra)); + CHECK_ATTACH; + if (extra->ctx && extra->ctx->magic == M_NNTP && + !((NNTP_DATA *)extra->ctx->data)->allowed && + query_quadoption (OPT_TOMODERATED,_("Posting to this group not allowed, may be moderated. Continue?")) != M_YES) + break; + ci_send_message (SENDNEWS, NULL, NULL, extra->ctx, NULL); + redraw = REDRAW_FULL; + break; + + case OP_FORWARD_TO_GROUP: + CHECK_MODE(IsHeader (extra) || IsMsgAttach (extra)); + CHECK_ATTACH; + if (extra->ctx && extra->ctx->magic == M_NNTP && + !((NNTP_DATA *)extra->ctx->data)->allowed && + query_quadoption (OPT_TOMODERATED,_("Posting to this group not allowed, may be moderated. Continue?")) != M_YES) + break; + if (IsMsgAttach (extra)) + mutt_attach_forward (extra->fp, extra->hdr, extra->idx, + extra->idxlen, extra->bdy, SENDNEWS); + else + ci_send_message (SENDNEWS|SENDFORWARD, NULL, NULL, extra->ctx, extra->hdr); + redraw = REDRAW_FULL; + break; + + case OP_FOLLOWUP: + CHECK_MODE(IsHeader (extra) || IsMsgAttach (extra)); + CHECK_ATTACH; + + if (IsMsgAttach (extra)) + followup_to = extra->bdy->hdr->env->followup_to; + else + followup_to = extra->hdr->env->followup_to; + + if (!followup_to || mutt_strcasecmp (followup_to, "poster") || + query_quadoption (OPT_FOLLOWUPTOPOSTER,_("Reply by mail as poster prefers?")) != M_YES) + { + if (extra->ctx && extra->ctx->magic == M_NNTP && + !((NNTP_DATA *)extra->ctx->data)->allowed && + query_quadoption (OPT_TOMODERATED,_("Posting to this group not allowed, may be moderated. Continue?")) != M_YES) + break; + if (IsMsgAttach (extra)) + mutt_attach_reply (extra->fp, extra->hdr, extra->idx, + extra->idxlen, extra->bdy, SENDNEWS|SENDREPLY); + else + ci_send_message (SENDNEWS|SENDREPLY, NULL, NULL, + extra->ctx, extra->hdr); + redraw = REDRAW_FULL; + break; + } +#endif + case OP_REPLY: CHECK_MODE(IsHeader (extra) || IsMsgAttach (extra)); CHECK_ATTACH; @@ -2597,7 +2674,7 @@ search_next: CHECK_ATTACH; if (IsMsgAttach (extra)) mutt_attach_forward (extra->fp, extra->hdr, extra->idx, - extra->idxlen, extra->bdy); + extra->idxlen, extra->bdy, 0); else ci_send_message (SENDFORWARD, NULL, NULL, extra->ctx, extra->hdr); redraw = REDRAW_FULL; diff --git a/parse.c b/parse.c index 58c7774..36046e9 100644 --- a/parse.c +++ b/parse.c @@ -89,7 +89,7 @@ char *mutt_read_rfc822_line (FILE *f, char *line, size_t *linelen) /* not reached */ } -static LIST *mutt_parse_references (char *s, int in_reply_to) +LIST *mutt_parse_references (char *s, int in_reply_to) { LIST *t, *lst = NULL; char *m; @@ -1072,6 +1072,17 @@ int mutt_parse_rfc822_line (ENVELOPE *e, HEADER *hdr, char *line, char *p, short e->from = rfc822_parse_adrlist (e->from, p); matched = 1; } +#ifdef USE_NNTP + else if (!mutt_strcasecmp (line+1, "ollowup-to")) + { + if (!e->followup_to) + { + mutt_remove_trailing_ws (p); + e->followup_to = safe_strdup (mutt_skip_whitespace (p)); + } + matched = 1; + } +#endif break; case 'i': @@ -1154,6 +1165,27 @@ int mutt_parse_rfc822_line (ENVELOPE *e, HEADER *hdr, char *line, char *p, short } break; +#ifdef USE_NNTP + case 'n': + if (!mutt_strcasecmp (line + 1, "ewsgroups")) + { + FREE (&e->newsgroups); + mutt_remove_trailing_ws (p); + e->newsgroups = safe_strdup (mutt_skip_whitespace (p)); + matched = 1; + } + break; +#endif + + case 'o': + /* field `Organization:' saves only for pager! */ + if (!mutt_strcasecmp (line + 1, "rganization")) + { + if (!e->organization && mutt_strcasecmp (p, "unknown")) + e->organization = safe_strdup (p); + } + break; + case 'r': if (!ascii_strcasecmp (line + 1, "eferences")) { @@ -1266,6 +1298,20 @@ int mutt_parse_rfc822_line (ENVELOPE *e, HEADER *hdr, char *line, char *p, short e->x_label = safe_strdup(p); matched = 1; } +#ifdef USE_NNTP + else if (!mutt_strcasecmp (line + 1, "-comment-to")) + { + if (!e->x_comment_to) + e->x_comment_to = safe_strdup (p); + matched = 1; + } + else if (!mutt_strcasecmp (line + 1, "ref")) + { + if (!e->xref) + e->xref = safe_strdup (p); + matched = 1; + } +#endif default: break; diff --git a/pattern.c b/pattern.c index 4cdbd05..9375a4e 100644 --- a/pattern.c +++ b/pattern.c @@ -92,6 +92,9 @@ Flags[] = { 'U', M_UNREAD, 0, NULL }, { 'v', M_COLLAPSED, 0, NULL }, { 'V', M_CRYPT_VERIFIED, 0, NULL }, +#ifdef USE_NNTP + { 'w', M_NEWSGROUPS, 0, eat_regexp }, +#endif { 'x', M_REFERENCE, 0, eat_regexp }, { 'X', M_MIMEATTACH, 0, eat_range }, { 'y', M_XLABEL, 0, eat_regexp }, @@ -1213,6 +1216,10 @@ mutt_pattern_exec (struct pattern_t *pat, pattern_exec_flag flags, CONTEXT *ctx, } case M_UNREFERENCED: return (pat->not ^ (h->thread && !h->thread->child)); +#ifdef USE_NNTP + case M_NEWSGROUPS: + return (pat->not ^ (h->env->newsgroups && patmatch (pat, h->env->newsgroups) == 0)); +#endif } mutt_error (_("error: unknown op %d (report this error)."), pat->op); return (-1); @@ -1294,6 +1301,7 @@ int mutt_pattern_func (int op, char *prompt) progress_t progress; strfcpy (buf, NONULL (Context->pattern), sizeof (buf)); + if (prompt || op != M_LIMIT) if (mutt_get_field (prompt, buf, sizeof (buf), M_PATTERN | M_CLEAR) != 0 || !buf[0]) return (-1); diff --git a/po/POTFILES.in b/po/POTFILES.in index 3654ad1..1e499ec 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -47,6 +47,8 @@ mutt_ssl_gnutls.c mutt_tunnel.c muttlib.c mx.c +newsrc.c +nntp.c pager.c parse.c pattern.c diff --git a/postpone.c b/postpone.c index 7a4cbb1..569edb6 100644 --- a/postpone.c +++ b/postpone.c @@ -125,15 +125,26 @@ int mutt_num_postponed (int force) if (LastModify < st.st_mtime) { +#ifdef USE_NNTP + int optnews = option (OPTNEWS); +#endif LastModify = st.st_mtime; if (access (Postponed, R_OK | F_OK) != 0) return (PostCount = 0); +#ifdef USE_NNTP + if (optnews) + unset_option (OPTNEWS); +#endif if (mx_open_mailbox (Postponed, M_NOSORT | M_QUIET, &ctx) == NULL) PostCount = 0; else PostCount = ctx.msgcount; mx_fastclose_mailbox (&ctx); +#ifdef USE_NNTP + if (optnews) + set_option (OPTNEWS); +#endif } return (PostCount); diff --git a/protos.h b/protos.h index 5d2fafb..81d09db 100644 --- a/protos.h +++ b/protos.h @@ -111,6 +111,7 @@ HASH *mutt_make_id_hash (CONTEXT *); HASH *mutt_make_subj_hash (CONTEXT *); LIST *mutt_make_references(ENVELOPE *e); +LIST *mutt_parse_references (char *, int); char *mutt_read_rfc822_line (FILE *, char *, size_t *); ENVELOPE *mutt_read_rfc822_header (FILE *, HEADER *, short, short); diff --git a/recvattach.c b/recvattach.c index fddbc2f..b76a4ea 100644 --- a/recvattach.c +++ b/recvattach.c @@ -1120,6 +1120,15 @@ void mutt_view_attachments (HEADER *hdr) } #endif +#ifdef USE_NNTP + if (Context->magic == M_NNTP) + { + mutt_flushinp (); + mutt_error _("Can't delete attachment from news server."); + break; + } +#endif + if (WithCrypto && (hdr->security & ENCRYPT)) { mutt_message _( @@ -1214,10 +1223,33 @@ void mutt_view_attachments (HEADER *hdr) case OP_FORWARD_MESSAGE: CHECK_ATTACH; mutt_attach_forward (fp, hdr, idx, idxlen, - menu->tagprefix ? NULL : idx[menu->current]->content); + menu->tagprefix ? NULL : idx[menu->current]->content, 0); menu->redraw = REDRAW_FULL; break; +#ifdef USE_NNTP + case OP_FORWARD_TO_GROUP: + CHECK_ATTACH; + mutt_attach_forward (fp, hdr, idx, idxlen, + menu->tagprefix ? NULL : idx[menu->current]->content, SENDNEWS); + menu->redraw = REDRAW_FULL; + break; + + case OP_FOLLOWUP: + CHECK_ATTACH; + + if (!idx[menu->current]->content->hdr->env->followup_to || + mutt_strcasecmp (idx[menu->current]->content->hdr->env->followup_to, "poster") || + query_quadoption (OPT_FOLLOWUPTOPOSTER,_("Reply by mail as poster prefers?")) != M_YES) + { + mutt_attach_reply (fp, hdr, idx, idxlen, + menu->tagprefix ? NULL : idx[menu->current]->content, + SENDNEWS|SENDREPLY); + menu->redraw = REDRAW_FULL; + break; + } +#endif + case OP_REPLY: case OP_GROUP_REPLY: case OP_LIST_REPLY: diff --git a/recvcmd.c b/recvcmd.c index a6a3a91..e633119 100644 --- a/recvcmd.c +++ b/recvcmd.c @@ -401,7 +401,7 @@ static BODY ** copy_problematic_attachments (FILE *fp, static void attach_forward_bodies (FILE * fp, HEADER * hdr, ATTACHPTR ** idx, short idxlen, BODY * cur, - short nattach) + short nattach, int flags) { short i; short mime_fwd_all = 0; @@ -547,7 +547,7 @@ _("Can't decode all tagged attachments. MIME-forward the others?"))) == -1) tmpfp = NULL; /* now that we have the template, send it. */ - ci_send_message (0, tmphdr, tmpbody, NULL, parent); + ci_send_message (flags, tmphdr, tmpbody, NULL, parent); return; bail: @@ -574,7 +574,7 @@ _("Can't decode all tagged attachments. MIME-forward the others?"))) == -1) */ static void attach_forward_msgs (FILE * fp, HEADER * hdr, - ATTACHPTR ** idx, short idxlen, BODY * cur) + ATTACHPTR ** idx, short idxlen, BODY * cur, int flags) { HEADER *curhdr = NULL; HEADER *tmphdr; @@ -679,23 +679,23 @@ static void attach_forward_msgs (FILE * fp, HEADER * hdr, else mutt_free_header (&tmphdr); - ci_send_message (0, tmphdr, *tmpbody ? tmpbody : NULL, + ci_send_message (flags, tmphdr, *tmpbody ? tmpbody : NULL, NULL, curhdr); } void mutt_attach_forward (FILE * fp, HEADER * hdr, - ATTACHPTR ** idx, short idxlen, BODY * cur) + ATTACHPTR ** idx, short idxlen, BODY * cur, int flags) { short nattach; if (check_all_msg (idx, idxlen, cur, 0) == 0) - attach_forward_msgs (fp, hdr, idx, idxlen, cur); + attach_forward_msgs (fp, hdr, idx, idxlen, cur, flags); else { nattach = count_tagged (idx, idxlen); - attach_forward_bodies (fp, hdr, idx, idxlen, cur, nattach); + attach_forward_bodies (fp, hdr, idx, idxlen, cur, nattach, flags); } } @@ -753,28 +753,40 @@ attach_reply_envelope_defaults (ENVELOPE *env, ATTACHPTR **idx, short idxlen, return -1; } - if (parent) +#ifdef USE_NNTP + if ((flags & SENDNEWS)) { - if (mutt_fetch_recips (env, curenv, flags) == -1) - return -1; + /* in case followup set Newsgroups: with Followup-To: if it present */ + if (!env->newsgroups && curenv && + mutt_strcasecmp (curenv->followup_to, "poster")) + env->newsgroups = safe_strdup (curenv->followup_to); } else +#endif { - for (i = 0; i < idxlen; i++) + if (parent) { - if (idx[i]->content->tagged - && mutt_fetch_recips (env, idx[i]->content->hdr->env, flags) == -1) + if (mutt_fetch_recips (env, curenv, flags) == -1) return -1; } + else + { + for (i = 0; i < idxlen; i++) + { + if (idx[i]->content->tagged + && mutt_fetch_recips (env, idx[i]->content->hdr->env, flags) == -1) + return -1; + } + } + + if ((flags & SENDLISTREPLY) && !env->to) + { + mutt_error _("No mailing lists found!"); + return (-1); + } + + mutt_fix_reply_recipients (env); } - - if ((flags & SENDLISTREPLY) && !env->to) - { - mutt_error _("No mailing lists found!"); - return (-1); - } - - mutt_fix_reply_recipients (env); mutt_make_misc_reply_headers (env, Context, curhdr, curenv); if (parent) @@ -835,6 +847,13 @@ void mutt_attach_reply (FILE * fp, HEADER * hdr, char prefix[SHORT_STRING]; int rc; +#ifdef USE_NNTP + if (flags & SENDNEWS) + set_option (OPTNEWSSEND); + else + unset_option (OPTNEWSSEND); +#endif + if (check_all_msg (idx, idxlen, cur, 0) == -1) { nattach = count_tagged (idx, idxlen); diff --git a/send.c b/send.c index 0b45171..f4a3611 100644 --- a/send.c +++ b/send.c @@ -44,6 +44,11 @@ #include #include +#ifdef USE_NNTP +#include "nntp.h" +#include "mx.h" +#endif + #ifdef MIXMASTER #include "remailer.h" #endif @@ -213,17 +218,51 @@ static int edit_address (ADDRESS **a, /* const */ char *field) return 0; } -static int edit_envelope (ENVELOPE *en) +static int edit_envelope (ENVELOPE *en, int flags) { char buf[HUGE_STRING]; LIST *uh = UserHeader; - if (edit_address (&en->to, "To: ") == -1 || en->to == NULL) - return (-1); - if (option (OPTASKCC) && edit_address (&en->cc, "Cc: ") == -1) - return (-1); - if (option (OPTASKBCC) && edit_address (&en->bcc, "Bcc: ") == -1) - return (-1); +#ifdef USE_NNTP + if (option (OPTNEWSSEND)) + { + if (en->newsgroups) + strfcpy (buf, en->newsgroups, sizeof (buf)); + else + buf[0] = 0; + if (mutt_get_field ("Newsgroups: ", buf, sizeof (buf), 0) != 0) + return (-1); + FREE (&en->newsgroups); + en->newsgroups = safe_strdup (buf); + + if (en->followup_to) + strfcpy (buf, en->followup_to, sizeof (buf)); + else + buf[0] = 0; + if (option (OPTASKFOLLOWUP) && mutt_get_field ("Followup-To: ", buf, sizeof (buf), 0) != 0) + return (-1); + FREE (&en->followup_to); + en->followup_to = safe_strdup (buf); + + if (en->x_comment_to) + strfcpy (buf, en->x_comment_to, sizeof (buf)); + else + buf[0] = 0; + if (option (OPTXCOMMENTTO) && option (OPTASKXCOMMENTTO) && mutt_get_field ("X-Comment-To: ", buf, sizeof (buf), 0) != 0) + return (-1); + FREE (&en->x_comment_to); + en->x_comment_to = safe_strdup (buf); + } + else +#endif + { + if (edit_address (&en->to, "To: ") == -1 || en->to == NULL) + return (-1); + if (option (OPTASKCC) && edit_address (&en->cc, "Cc: ") == -1) + return (-1); + if (option (OPTASKBCC) && edit_address (&en->bcc, "Bcc: ") == -1) + return (-1); + } if (en->subject) { @@ -258,6 +297,14 @@ static int edit_envelope (ENVELOPE *en) return 0; } +#ifdef USE_NNTP +char *nntp_get_header (const char *s) +{ + SKIPWS (s); + return safe_strdup (s); +} +#endif + static void process_user_recips (ENVELOPE *env) { LIST *uh = UserHeader; @@ -270,6 +317,14 @@ static void process_user_recips (ENVELOPE *env) env->cc = rfc822_parse_adrlist (env->cc, uh->data + 3); else if (ascii_strncasecmp ("bcc:", uh->data, 4) == 0) env->bcc = rfc822_parse_adrlist (env->bcc, uh->data + 4); +#ifdef USE_NNTP + else if (ascii_strncasecmp ("newsgroups:", uh->data, 11) == 0) + env->newsgroups = nntp_get_header (uh->data + 11); + else if (ascii_strncasecmp ("followup-to:", uh->data, 12) == 0) + env->followup_to = nntp_get_header (uh->data + 12); + else if (ascii_strncasecmp ("x-comment-to:", uh->data, 13) == 0) + env->x_comment_to = nntp_get_header (uh->data + 13); +#endif } } @@ -308,6 +363,12 @@ static void process_user_header (ENVELOPE *env) else if (ascii_strncasecmp ("to:", uh->data, 3) != 0 && ascii_strncasecmp ("cc:", uh->data, 3) != 0 && ascii_strncasecmp ("bcc:", uh->data, 4) != 0 && +#ifdef USE_NNTP + ascii_strncasecmp ("newsgroups:", uh->data, 11) != 0 && + ascii_strncasecmp ("followup-to:", uh->data, 12) != 0 && + ascii_strncasecmp ("x-comment-to:", uh->data, 13) != 0 && +#endif + ascii_strncasecmp ("supersedes:", uh->data, 11) != 0 && ascii_strncasecmp ("subject:", uh->data, 8) != 0 && ascii_strncasecmp ("return-path:", uh->data, 12) != 0) { @@ -656,6 +717,10 @@ void mutt_add_to_reference_headers (ENVELOPE *env, ENVELOPE *curenv, LIST ***pp, if (pp) *pp = p; if (qq) *qq = q; +#ifdef USE_NNTP + if (option (OPTNEWSSEND) && option (OPTXCOMMENTTO) && curenv->from) + env->x_comment_to = safe_strdup (mutt_get_name (curenv->from)); +#endif } static void @@ -718,6 +783,16 @@ envelope_defaults (ENVELOPE *env, CONTEXT *ctx, HEADER *cur, int flags) if (flags & SENDREPLY) { +#ifdef USE_NNTP + if ((flags & SENDNEWS)) + { + /* in case followup set Newsgroups: with Followup-To: if it present */ + if (!env->newsgroups && curenv && + mutt_strcasecmp (curenv->followup_to, "poster")) + env->newsgroups = safe_strdup (curenv->followup_to); + } + else +#endif if (tag) { HEADER *h; @@ -864,7 +939,18 @@ void mutt_set_followup_to (ENVELOPE *e) * it hasn't already been set */ - if (option (OPTFOLLOWUPTO) && !e->mail_followup_to) + if (!option (OPTFOLLOWUPTO)) + return; +#ifdef USE_NNTP + if (option (OPTNEWSSEND)) + { + if (!e->followup_to && e->newsgroups && (strrchr (e->newsgroups, ','))) + e->followup_to = safe_strdup (e->newsgroups); + return; + } +#endif + + if (!e->mail_followup_to) { if (mutt_is_list_cc (0, e->to, e->cc)) { @@ -1026,6 +1112,9 @@ static int send_message (HEADER *msg) #endif #if USE_SMTP +#ifdef USE_NNTP + if (!option (OPTNEWSSEND)) +#endif if (SmtpUrl) return mutt_smtp_send (msg->env->from, msg->env->to, msg->env->cc, msg->env->bcc, tempfile, @@ -1159,6 +1248,13 @@ ci_send_message (int flags, /* send mode */ int rv = -1; +#ifdef USE_NNTP + if (flags & SENDNEWS) + set_option (OPTNEWSSEND); + else + unset_option (OPTNEWSSEND); +#endif + if (!flags && !msg && quadoption (OPT_RECALL) != M_NO && mutt_num_postponed (1)) { @@ -1194,6 +1290,22 @@ ci_send_message (int flags, /* send mode */ { if ((flags = mutt_get_postponed (ctx, msg, &cur, fcc, sizeof (fcc))) < 0) goto cleanup; +#ifdef USE_NNTP + /* + * If postponed message is a news article, it have + * a "Newsgroups:" header line, then set appropriate flag. + */ + if (msg->env->newsgroups) + { + flags |= SENDNEWS; + set_option (OPTNEWSSEND); + } + else + { + flags &= ~SENDNEWS; + unset_option (OPTNEWSSEND); + } +#endif } if (flags & (SENDPOSTPONED|SENDRESEND)) @@ -1286,11 +1398,16 @@ ci_send_message (int flags, /* send mode */ if (flags & SENDREPLY) mutt_fix_reply_recipients (msg->env); +#ifdef USE_NNTP + if ((flags & SENDNEWS) && ctx && ctx->magic == M_NNTP && !msg->env->newsgroups) + msg->env->newsgroups = safe_strdup (((NNTP_DATA *)ctx->data)->group); +#endif + if (! (flags & (SENDMAILX|SENDBATCH)) && ! (option (OPTAUTOEDIT) && option (OPTEDITHDRS)) && ! ((flags & SENDREPLY) && option (OPTFASTREPLY))) { - if (edit_envelope (msg->env) == -1) + if (edit_envelope (msg->env, flags) == -1) goto cleanup; } @@ -1587,6 +1704,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; } @@ -1641,6 +1763,9 @@ main_loop: } } +#ifdef USE_NNTP + if (!(flags & SENDNEWS)) +#endif if (!has_recips (msg->env->to) && !has_recips (msg->env->cc) && !has_recips (msg->env->bcc)) { @@ -1674,6 +1799,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); @@ -1880,7 +2018,12 @@ full_fcc: } } else if (!option (OPTNOCURSES) && ! (flags & SENDMAILX)) - mutt_message (i == 0 ? _("Mail sent.") : _("Sending in background.")); + mutt_message (i != 0 ? _("Sending in background.") : +#ifdef USE_NNTP + (flags & SENDNEWS) ? _("Article posted.") : _("Mail sent.")); +#else + _("Mail sent.")); +#endif if (WithCrypto && (msg->security & ENCRYPT)) FREE (&pgpkeylist); diff --git a/sendlib.c b/sendlib.c index 2144156..cddbe32 100644 --- a/sendlib.c +++ b/sendlib.c @@ -46,6 +46,10 @@ #include #include +#ifdef USE_NNTP +#include "nntp.h" +#endif + #ifdef HAVE_SYSEXITS_H #include #else /* Make sure EX_OK is defined */ @@ -1543,6 +1547,14 @@ void mutt_write_references (LIST *r, FILE *f, int trim) { LIST **ref = NULL; int refcnt = 0, refmax = 0; + int multiline = 1; + int space = 0; + + if (trim < 0) + { + trim = -trim; + multiline = 0; + } for ( ; (trim == 0 || refcnt < trim) && r ; r = r->next) { @@ -1553,9 +1565,11 @@ void mutt_write_references (LIST *r, FILE *f, int trim) while (refcnt-- > 0) { - fputc (' ', f); + if (multiline || space) + fputc (' ', f); + space = 1; fputs (ref[refcnt]->data, f); - if (refcnt >= 1) + if (multiline && refcnt >= 1) fputc ('\n', f); } @@ -1969,6 +1983,9 @@ int mutt_write_rfc822_header (FILE *fp, ENVELOPE *env, BODY *attach, mutt_write_address_list (env->to, fp, 4, 0); } else if (mode > 0) +#ifdef USE_NNTP + if (!option (OPTNEWSSEND)) +#endif fputs ("To: \n", fp); if (env->cc) @@ -1977,6 +1994,9 @@ int mutt_write_rfc822_header (FILE *fp, ENVELOPE *env, BODY *attach, mutt_write_address_list (env->cc, fp, 4, 0); } else if (mode > 0) +#ifdef USE_NNTP + if (!option (OPTNEWSSEND)) +#endif fputs ("Cc: \n", fp); if (env->bcc && should_write_bcc) @@ -1988,8 +2008,28 @@ int mutt_write_rfc822_header (FILE *fp, ENVELOPE *env, BODY *attach, } } else if (mode > 0) +#ifdef USE_NNTP + if (!option (OPTNEWSSEND)) +#endif fputs ("Bcc: \n", fp); +#ifdef USE_NNTP + if (env->newsgroups) + fprintf (fp, "Newsgroups: %s\n", env->newsgroups); + else if (mode == 1 && option (OPTNEWSSEND)) + fputs ("Newsgroups: \n", fp); + + if (env->followup_to) + fprintf (fp, "Followup-To: %s\n", env->followup_to); + else if (mode == 1 && option (OPTNEWSSEND)) + fputs ("Followup-To: \n", fp); + + if (env->x_comment_to) + fprintf (fp, "X-Comment-To: %s\n", env->x_comment_to); + else if (mode == 1 && option (OPTNEWSSEND) && option (OPTXCOMMENTTO)) + fputs ("X-Comment-To: \n", fp); +#endif + if (env->subject) mutt_write_one_header (fp, "Subject", env->subject, NULL, 0, 0); else if (mode == 1) @@ -2008,6 +2048,9 @@ int mutt_write_rfc822_header (FILE *fp, ENVELOPE *env, BODY *attach, fputs ("Reply-To: \n", fp); if (env->mail_followup_to) +#ifdef USE_NNTP + if (!option (OPTNEWSSEND)) +#endif { fputs ("Mail-Followup-To: ", fp); mutt_write_address_list (env->mail_followup_to, fp, 18, 0); @@ -2351,6 +2394,23 @@ mutt_invoke_sendmail (ADDRESS *from, /* the sender */ size_t argslen = 0, argsmax = 0; int i; +#ifdef USE_NNTP + if (option (OPTNEWSSEND)) + { + char cmd[LONG_STRING]; + + mutt_FormatString (cmd, sizeof (cmd), 0, NONULL (Inews), nntp_format_str, 0, 0); + if (!*cmd) + { + i = nntp_post (msg); + unlink (msg); + return i; + } + + s = safe_strdup (cmd); + } +#endif + /* ensure that $sendmail is set to avoid a crash. http://dev.mutt.org/trac/ticket/3548 */ if (!s) { @@ -2381,6 +2441,10 @@ mutt_invoke_sendmail (ADDRESS *from, /* the sender */ i++; } +#ifdef USE_NNTP + if (!option (OPTNEWSSEND)) + { +#endif if (eightbit && option (OPTUSE8BITMIME)) args = add_option (args, &argslen, &argsmax, "-B8BITMIME"); @@ -2412,6 +2476,9 @@ mutt_invoke_sendmail (ADDRESS *from, /* the sender */ args = add_args (args, &argslen, &argsmax, to); args = add_args (args, &argslen, &argsmax, cc); args = add_args (args, &argslen, &argsmax, bcc); +#ifdef USE_NNTP + } +#endif if (argslen == argsmax) safe_realloc (&args, sizeof (char *) * (++argsmax)); @@ -2492,6 +2559,9 @@ void mutt_prepare_envelope (ENVELOPE *env, int final) rfc2047_encode_string (&env->x_label); if (env->subject) +#ifdef USE_NNTP + if (!option (OPTNEWSSEND) || option (OPTMIMESUBJECT)) +#endif { rfc2047_encode_string (&env->subject); } @@ -2612,6 +2682,10 @@ int mutt_bounce_message (FILE *fp, HEADER *h, ADDRESS *to) } rfc822_write_address (resent_from, sizeof (resent_from), from, 0); +#ifdef USE_NNTP + unset_option (OPTNEWSSEND); +#endif + /* * prepare recipient list. idna conversion appears to happen before this * function is called, since the user receives confirmation of the address diff --git a/sort.c b/sort.c index 76e9e79..9aa259c 100644 --- a/sort.c +++ b/sort.c @@ -24,6 +24,11 @@ #include "sort.h" #include "mutt_idna.h" +#ifdef USE_NNTP +#include "mx.h" +#include "nntp.h" +#endif + #include #include #include @@ -151,6 +156,17 @@ static int compare_order (const void *a, const void *b) HEADER **ha = (HEADER **) a; HEADER **hb = (HEADER **) b; +#ifdef USE_NNTP + if (Context && Context->magic == M_NNTP) + { + anum_t na = NHDR (*ha)->article_num; + anum_t nb = NHDR (*hb)->article_num; + int result = na == nb ? 0 : na > nb ? 1 : -1; + AUXSORT (result, a, b); + return (SORTCODE (result)); + } + else +#endif /* no need to auxsort because you will never have equality here */ return (SORTCODE ((*ha)->index - (*hb)->index)); } diff --git a/url.c b/url.c index 988cf59..3fd4079 100644 --- a/url.c +++ b/url.c @@ -39,6 +39,8 @@ static const struct mapping_t UrlMap[] = { "imaps", U_IMAPS }, { "pop", U_POP }, { "pops", U_POPS }, + { "news", U_NNTP }, + { "snews", U_NNTPS }, { "mailto", U_MAILTO }, { "smtp", U_SMTP }, { "smtps", U_SMTPS }, @@ -214,7 +216,7 @@ int url_ciss_tostring (ciss_url_t* ciss, char* dest, size_t len, int flags) safe_strcat (dest, len, "//"); len -= (l = strlen (dest)); dest += l; - if (ciss->user) + if (ciss->user && (ciss->user[0] || !(flags & U_PATH))) { char u[STRING]; url_pct_encode (u, sizeof (u), ciss->user); diff --git a/url.h b/url.h index 926416e..15ec9ce 100644 --- a/url.h +++ b/url.h @@ -8,6 +8,8 @@ typedef enum url_scheme U_POPS, U_IMAP, U_IMAPS, + U_NNTP, + U_NNTPS, U_SMTP, U_SMTPS, U_MAILTO,