WARNING: Run the following script before configure: aclocal -I m4 autoheader automake --foreign autoconf -- Vsevolod Volkov diff -udprP mutt-1.5.20.orig/ChangeLog.nntp mutt-1.5.20/ChangeLog.nntp --- mutt-1.5.20.orig/ChangeLog.nntp 1970-01-01 03:00:00.000000000 +0300 +++ mutt-1.5.20/ChangeLog.nntp 2009-06-15 21:56:06.000000000 +0300 @@ -0,0 +1,369 @@ +* 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 newsserver name from file /etc/nntpserver +- use case insensitive server names +- add print-style sequence %s to $newsrc +- add -g option + +* Sat Apr 1 2000 Vsevolod Volkov +- remove 'X-FTN-Origin' header processing + +* Thu Mar 30 2000 Vsevolod Volkov +- update to 1.1.11 +- update to 1.1.10 + +* Thu Mar 23 2000 Vsevolod Volkov +- fix mutt_select_newsserver() +- remove 'toggle-mode' function +- add 'change-newsgroup' function + +* Wed Mar 22 2000 Vsevolod Volkov +- fix server-hook + +* Tue Mar 21 2000 Vsevolod Volkov +- fix error 'bounce' function after 'post' +- add 'forward to newsgroup' function + +* Mon Mar 20 2000 Vsevolod Volkov +- 'forward' function works in newsreader mode +- add 'post' and 'followup' functions to pager and attachment menu +- fix active descriptions and allowed flag reload + +* Tue Mar 14 2000 Vsevolod Volkov +- update to 1.1.9 +- remove deleted newsgroups from list + +* Mon Mar 13 2000 Vsevolod Volkov +- update .newsrc in browser + +* Sun Mar 12 2000 Vsevolod Volkov +- reload .newsrc if externally modified +- fix active cache update + +* Sun Mar 5 2000 Vsevolod Volkov +- update to 1.1.8 + +* Sat Mar 4 2000 Vsevolod Volkov +- patch *.update_list_file is not required +- count lines when loading descriptions +- remove cache of unsubscribed newsgroups + +* Thu Mar 2 2000 Vsevolod Volkov +- load list of newsgroups from cache faster + +* Wed Mar 1 2000 Vsevolod Volkov +- update to 1.1.7 + +* Tue Feb 29 2000 Vsevolod Volkov +- fix unread messages in browser +- fix newsrc_gen_entries() + +* Mon Feb 28 2000 Vsevolod Volkov +- fix mutt_newsgroup_stat() +- fix nntp_delete_cache() +- fix nntp_get_status() +- fix check_children() +- fix nntp_fetch_headers() + +* Fri Feb 25 2000 Vsevolod Volkov +- update to 1.1.5 + +* Thu Feb 24 2000 Vsevolod Volkov +- fix updating new messages in cache + +* Mon Feb 21 2000 Vsevolod Volkov +- change default cache filenames +- fix updating new messages in cache + +* Fri Feb 18 2000 Vsevolod Volkov +- fix segmentation fault in news groups browser + +* Tue Feb 15 2000 Vsevolod Volkov +- update to 1.1.4 + +* Thu Feb 10 2000 Vsevolod Volkov +- update to 1.1.3 + +* Sun Jan 30 2000 Vsevolod Volkov +- add X-Comment-To editing +- add my_hdr support for Newsgroups:, Followup-To: and X-Comment-To: headers +- add variables $ask_followup_to and $ask_x_comment_to + +* Fri Jan 28 2000 Vsevolod Volkov +- update to 1.1.2 diff -udprP mutt-1.5.20.orig/OPS mutt-1.5.20/OPS --- mutt-1.5.20.orig/OPS 2009-05-13 08:01:13.000000000 +0300 +++ mutt-1.5.20/OPS 2009-06-15 21:05:24.000000000 +0300 @@ -8,14 +8,16 @@ OP_BOUNCE_MESSAGE "remail a message to a OP_BROWSER_NEW_FILE "select a new file in this directory" OP_BROWSER_VIEW_FILE "view file" OP_BROWSER_TELL "display the currently selected file's name" -OP_BROWSER_SUBSCRIBE "subscribe to current mailbox (IMAP only)" -OP_BROWSER_UNSUBSCRIBE "unsubscribe from current mailbox (IMAP only)" +OP_BROWSER_SUBSCRIBE "subscribe to current mbox (IMAP/NNTP only)" +OP_BROWSER_UNSUBSCRIBE "unsubscribe from current mbox (IMAP/NNTP only)" OP_BROWSER_TOGGLE_LSUB "toggle view all/subscribed mailboxes (IMAP only)" OP_BUFFY_LIST "list mailboxes with new mail" +OP_CATCHUP "mark all articles in newsgroup as read" OP_CHANGE_DIRECTORY "change directories" OP_CHECK_NEW "check mailboxes for new mail" OP_COMPOSE_ATTACH_FILE "attach file(s) to this message" OP_COMPOSE_ATTACH_MESSAGE "attach message(s) to this message" +OP_COMPOSE_ATTACH_NEWS_MESSAGE "attach newsmessage(s) to this message" OP_COMPOSE_EDIT_BCC "edit the BCC list" OP_COMPOSE_EDIT_CC "edit the CC list" OP_COMPOSE_EDIT_DESCRIPTION "edit attachment description" @@ -26,7 +28,10 @@ OP_COMPOSE_EDIT_FROM "edit the from fiel OP_COMPOSE_EDIT_HEADERS "edit the message with headers" OP_COMPOSE_EDIT_MESSAGE "edit the message" OP_COMPOSE_EDIT_MIME "edit attachment using mailcap entry" +OP_COMPOSE_EDIT_NEWSGROUPS "edit the newsgroups list" OP_COMPOSE_EDIT_REPLY_TO "edit the Reply-To field" +OP_COMPOSE_EDIT_FOLLOWUP_TO "edit the Followup-To field" +OP_COMPOSE_EDIT_X_COMMENT_TO "edit the X-Comment-To field" OP_COMPOSE_EDIT_SUBJECT "edit the subject of this message" OP_COMPOSE_EDIT_TO "edit the TO list" OP_CREATE_MAILBOX "create a new mailbox (IMAP only)" @@ -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 active file 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" @@ -137,6 +150,7 @@ OP_PAGER_HIDE_QUOTED "toggle display of OP_PAGER_SKIP_QUOTED "skip beyond quoted text" OP_PAGER_TOP "jump to the top of the message" OP_PIPE "pipe message/attachment to a shell command" +OP_POST "post message to newsgroup" OP_PREV_ENTRY "move to the previous entry" OP_PREV_LINE "scroll up one line" OP_PREV_PAGE "move to the previous page" @@ -145,6 +159,7 @@ OP_QUERY "query external program for add OP_QUERY_APPEND "append new query results to current results" OP_QUIT "save changes to mailbox and quit" OP_RECALL_MESSAGE "recall a postponed message" +OP_RECONSTRUCT_THREAD "reconstruct thread containing current message" OP_REDRAW "clear and redraw the screen" OP_REFORMAT_WINCH "{internal}" OP_RENAME_MAILBOX "rename the current mailbox (IMAP only)" @@ -159,18 +174,22 @@ OP_SEARCH_TOGGLE "toggle search pattern OP_SHELL_ESCAPE "invoke a command in a subshell" OP_SORT "sort messages" OP_SORT_REVERSE "sort messages in reverse order" +OP_SUBSCRIBE_PATTERN "subscribe to newsgroups matching a pattern" OP_TAG "tag the current entry" OP_TAG_PREFIX "apply next function to tagged messages" OP_TAG_PREFIX_COND "apply next function ONLY to tagged messages" OP_TAG_SUBTHREAD "tag the current subthread" OP_TAG_THREAD "tag the current thread" OP_TOGGLE_NEW "toggle a message's 'new' flag" +OP_TOGGLE_READ "toggle view of read messages" OP_TOGGLE_WRITE "toggle whether the mailbox will be rewritten" OP_TOGGLE_MAILBOXES "toggle whether to browse mailboxes or all files" OP_TOP_PAGE "move to the top of the page" +OP_UNCATCHUP "mark all articles in newsgroup as unread" OP_UNDELETE "undelete the current entry" OP_UNDELETE_THREAD "undelete all messages in thread" OP_UNDELETE_SUBTHREAD "undelete all messages in subthread" +OP_UNSUBSCRIBE_PATTERN "unsubscribe from newsgroups matching a pattern" OP_VERSION "show the Mutt version number and date" OP_VIEW_ATTACH "view attachment using mailcap entry if necessary" OP_VIEW_ATTACHMENTS "show MIME attachments" diff -udprP mutt-1.5.20.orig/account.c mutt-1.5.20/account.c --- mutt-1.5.20.orig/account.c 2008-11-11 21:55:46.000000000 +0200 +++ mutt-1.5.20/account.c 2009-06-15 21:05:24.000000000 +0300 @@ -51,6 +51,11 @@ int mutt_account_match (const ACCOUNT* a user = PopUser; #endif +#ifdef USE_NNTP + if (a1->type == M_ACCT_TYPE_NNTP && NntpUser) + user = NntpUser; +#endif + if (a1->flags & a2->flags & M_ACCT_USER) return (!strcmp (a1->user, a2->user)); if (a1->flags & M_ACCT_USER) @@ -130,6 +135,16 @@ void mutt_account_tourl (ACCOUNT* accoun } #endif +#ifdef USE_NNTP + if (account->type == M_ACCT_TYPE_NNTP) + { + if (account->flags & M_ACCT_SSL) + url->scheme = U_NNTPS; + else + url->scheme = U_NNTP; + } +#endif + url->host = account->host; if (account->flags & M_ACCT_PORT) url->port = account->port; @@ -155,6 +170,10 @@ int mutt_account_getuser (ACCOUNT* accou else if ((account->type == M_ACCT_TYPE_POP) && PopUser) strfcpy (account->user, PopUser, sizeof (account->user)); #endif +#ifdef USE_NNTP + else if ((account->type == M_ACCT_TYPE_NNTP) && NntpUser) + strfcpy (account->user, NntpUser, sizeof (account->user)); +#endif /* prompt (defaults to unix username), copy into account->user */ else { @@ -215,6 +234,10 @@ int mutt_account_getpass (ACCOUNT* accou 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 { snprintf (prompt, sizeof (prompt), _("Password for %s@%s: "), diff -udprP mutt-1.5.20.orig/account.h mutt-1.5.20/account.h --- mutt-1.5.20.orig/account.h 2008-11-11 21:55:46.000000000 +0200 +++ mutt-1.5.20/account.h 2009-06-15 21:05:24.000000000 +0300 @@ -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 -udprP mutt-1.5.20.orig/attach.h mutt-1.5.20/attach.h --- mutt-1.5.20.orig/attach.h 2008-11-11 21:55:46.000000000 +0200 +++ mutt-1.5.20/attach.h 2009-06-15 21:05:24.000000000 +0300 @@ -50,7 +50,7 @@ void mutt_print_attachment_list (FILE *f void mutt_attach_bounce (FILE *, HEADER *, ATTACHPTR **, short, BODY *); void mutt_attach_resend (FILE *, HEADER *, ATTACHPTR **, short, BODY *); -void mutt_attach_forward (FILE *, HEADER *, ATTACHPTR **, short, BODY *); +void mutt_attach_forward (FILE *, HEADER *, ATTACHPTR **, short, BODY *, int); void mutt_attach_reply (FILE *, HEADER *, ATTACHPTR **, short, BODY *, int); #endif /* _ATTACH_H_ */ diff -udprP mutt-1.5.20.orig/browser.c mutt-1.5.20/browser.c --- mutt-1.5.20.orig/browser.c 2009-06-11 20:52:54.000000000 +0300 +++ mutt-1.5.20/browser.c 2009-06-15 21:05:24.000000000 +0300 @@ -32,6 +32,9 @@ #ifdef USE_IMAP #include "imap.h" #endif +#ifdef USE_NNTP +#include "nntp.h" +#endif #include #include @@ -49,6 +52,19 @@ static 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 } +}; +#endif + typedef struct folder_t { struct folder_file *ff; @@ -114,9 +130,17 @@ static void browser_sort (struct browser case SORT_ORDER: return; case SORT_DATE: +#ifdef USE_NNTP + if (option (OPTNEWS)) + return; +#endif f = browser_compare_date; break; case SORT_SIZE: +#ifdef USE_NNTP + if (option (OPTNEWS)) + return; +#endif f = browser_compare_size; break; case SORT_SUBJECT: @@ -307,8 +331,106 @@ folder_format_str (char *dest, size_t de return (src); } +#ifdef USE_NNTP +static const char * +newsgroup_format_str (char *dest, size_t destlen, 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) + { + snprintf (tmp, sizeof (tmp), "%%%ss", fmt); + snprintf (dest, destlen, tmp, folder->ff->nd->desc); + } + else + { + snprintf (tmp, sizeof (tmp), "%%%ss", fmt); + snprintf (dest, destlen, tmp, ""); + } + break; + } + return (src); +} +#endif /* USE_NNTP */ + static void add_folder (MUTTMENU *m, struct browser_state *state, - const char *name, const struct stat *s, int new) + const char *name, const struct stat *s, + void *data, int new) { if (state->entrylen == state->entrymax) { @@ -337,6 +459,10 @@ static void add_folder (MUTTMENU *m, str #ifdef USE_IMAP (state->entry)[state->entrylen].imap = 0; #endif +#ifdef USE_NNTP + if (option (OPTNEWS)) + (state->entry)[state->entrylen].nd = (NNTP_DATA *) data; +#endif (state->entrylen)++; } @@ -352,9 +478,35 @@ static void init_state (struct browser_s menu->data = state->entry; } +/* get list of all files/newsgroups with mask */ static int examine_directory (MUTTMENU *menu, struct browser_state *state, char *d, const char *prefix) { +#ifdef USE_NNTP + if (option (OPTNEWS)) + { + LIST *tmp; + NNTP_DATA *data; + NNTP_SERVER *news = CurrentNewsSrv; + +/* mutt_buffy_check (0); */ + init_state (state, menu); + + for (tmp = news->list; tmp; tmp = tmp->next) + { + if (!(data = (NNTP_DATA *)tmp->data)) + continue; + if (prefix && *prefix && strncmp (prefix, data->group, + strlen (prefix)) != 0) + continue; + if (!((regexec (Mask.rx, data->group, 0, NULL, 0) == 0) ^ Mask.not)) + continue; + add_folder (menu, state, data->group, NULL, data, data->new); + } + } + else +#endif /* USE_NNTP */ + { struct stat s; DIR *dp; struct dirent *de; @@ -415,17 +567,40 @@ static int examine_directory (MUTTMENU * tmp = Incoming; while (tmp && mutt_strcmp (buffer, tmp->path)) tmp = tmp->next; - add_folder (menu, state, de->d_name, &s, (tmp) ? tmp->new : 0); + add_folder (menu, state, de->d_name, &s, NULL, (tmp) ? tmp->new : 0); + } + closedir (dp); } - closedir (dp); browser_sort (state); return 0; } +/* get list of mailboxes/subscribed newsgroups */ static int examine_mailboxes (MUTTMENU *menu, struct browser_state *state) { struct stat s; char buffer[LONG_STRING]; + +#ifdef USE_NNTP + if (option (OPTNEWS)) + { + LIST *tmp; + NNTP_DATA *data; + NNTP_SERVER *news = CurrentNewsSrv; + +/* mutt_buffy_check (0); */ + init_state (state, menu); + + for (tmp = news->list; tmp; tmp = tmp->next) + { + if ((data = (NNTP_DATA *) tmp->data) != NULL && (data->new || + (data->subscribed && (!option (OPTSHOWONLYUNREAD) || data->unread)))) + add_folder (menu, state, data->group, NULL, data, data->new); + } + } + else +#endif + { BUFFY *tmp = Incoming; #ifdef USE_IMAP struct mailbox_state mbox; @@ -443,14 +618,21 @@ static int examine_mailboxes (MUTTMENU * 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 @@ -479,15 +661,20 @@ static int examine_mailboxes (MUTTMENU * 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)); } @@ -498,6 +685,12 @@ static void folder_entry (char *s, size_ folder.ff = &((struct folder_file *) menu->data)[num]; folder.num = num; +#ifdef USE_NNTP + if (option (OPTNEWS)) + mutt_FormatString (s, slen, 0, 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); } @@ -518,6 +711,17 @@ static void init_menu (struct browser_st menu->tagged = 0; +#ifdef USE_NNTP + if (option (OPTNEWS)) + { + if (buffy) + snprintf (title, titlelen, _("Subscribed newsgroups")); + else + snprintf (title, titlelen, _("Newsgroups on server [%s]"), + CurrentNewsSrv->conn->account.host); + } + else +#endif if (buffy) snprintf (title, titlelen, _("Mailboxes [%d]"), mutt_buffy_check (0)); else @@ -573,6 +777,31 @@ void _mutt_select_file (char *f, size_t if (!folder) strfcpy (LastDirBackup, LastDir, sizeof (LastDirBackup)); +#ifdef USE_NNTP + if (option (OPTNEWS)) + { + if (*f) + strfcpy (prefix, f, sizeof (prefix)); + else + { + LIST *list; + + /* default state for news reader mode is browse subscribed newsgroups */ + buffy = 0; + for (list = CurrentNewsSrv->list; list; list = list->next) + { + NNTP_DATA *data = (NNTP_DATA *) list->data; + + if (data && data->subscribed) + { + buffy = 1; + break; + } + } + } + } + else +#endif if (*f) { mutt_expand_path (f, flen); @@ -669,6 +898,9 @@ void _mutt_select_file (char *f, size_t menu->tag = file_tag; menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_FOLDER, +#ifdef USE_NNTP + (option (OPTNEWS)) ? FolderNewsHelp : +#endif FolderHelp); init_menu (&state, menu, title, sizeof (title), buffy); @@ -807,7 +1039,11 @@ void _mutt_select_file (char *f, size_t } } +#ifdef USE_NNTP + if (buffy || option (OPTNEWS)) /* news have not path */ +#else if (buffy) +#endif { strfcpy (f, state.entry[menu->current].name, flen); mutt_expand_path (f, flen); @@ -865,14 +1101,6 @@ void _mutt_select_file (char *f, size_t break; #ifdef USE_IMAP - case OP_BROWSER_SUBSCRIBE: - imap_subscribe (state.entry[menu->current].name, 1); - break; - - case OP_BROWSER_UNSUBSCRIBE: - imap_subscribe (state.entry[menu->current].name, 0); - break; - case OP_BROWSER_TOGGLE_LSUB: if (option (OPTIMAPLSUB)) unset_option (OPTIMAPLSUB); @@ -973,6 +1201,11 @@ void _mutt_select_file (char *f, size_t case OP_CHANGE_DIRECTORY: +#ifdef USE_NNTP + if (option (OPTNEWS)) + break; +#endif + strfcpy (buf, LastDir, sizeof (buf)); #ifdef USE_IMAP if (!state.imap_browse) @@ -1239,6 +1472,190 @@ void _mutt_select_file (char *f, size_t else mutt_error _("Error trying to view file"); } + break; + +#ifdef USE_NNTP + case OP_CATCHUP: + case OP_UNCATCHUP: + if (option (OPTNEWS)) + { + struct folder_file *f = &state.entry[menu->current]; + NNTP_DATA *nd; + + if (i == OP_CATCHUP) + nd = mutt_newsgroup_catchup (CurrentNewsSrv, f->name); + else + nd = mutt_newsgroup_uncatchup (CurrentNewsSrv, f->name); + + if (nd) + { +/* FOLDER folder; + struct folder_file ff; + char buffer[_POSIX_PATH_MAX + SHORT_STRING]; + + folder.ff = &ff; + folder.ff->name = f->name; + folder.ff->st = NULL; + folder.ff->is_new = nd->new; + folder.ff->nd = nd; + FREE (&f->desc); + mutt_FormatString (buffer, sizeof (buffer), 0, NONULL(GroupFormat), + newsgroup_format_str, (unsigned long) &folder, + M_FORMAT_ARROWCURSOR); + f->desc = safe_strdup (buffer); */ + if (menu->current + 1 < menu->max) + menu->current++; + menu->redraw = REDRAW_MOTION_RESYNCH; + } + } + break; + + case OP_LOAD_ACTIVE: + if (!option (OPTNEWS)) + break; + + { + LIST *tmp; + NNTP_DATA *data; + + for (tmp = CurrentNewsSrv->list; tmp; tmp = tmp->next) + { + if ((data = (NNTP_DATA *)tmp->data)) + data->deleted = 1; + } + } + nntp_get_active (CurrentNewsSrv); + + destroy_state (&state); + if (buffy) + examine_mailboxes (menu, &state); + else + examine_directory (menu, &state, NULL, NULL); + init_menu (&state, menu, title, sizeof (title), buffy); + break; +#endif /* USE_NNTP */ + +#if defined USE_IMAP || defined USE_NNTP + case OP_BROWSER_SUBSCRIBE: + case OP_BROWSER_UNSUBSCRIBE: +#endif +#ifdef USE_NNTP + case OP_SUBSCRIBE_PATTERN: + case OP_UNSUBSCRIBE_PATTERN: + if (option (OPTNEWS)) + { + regex_t *rx = (regex_t *) safe_malloc (sizeof (regex_t)); + char *s = buf; + int j = menu->current; + NNTP_DATA *nd; + NNTP_SERVER *news = CurrentNewsSrv; + + if (i == OP_SUBSCRIBE_PATTERN || i == OP_UNSUBSCRIBE_PATTERN) + { + char tmp[STRING]; + int err; + + buf[0] = 0; + if (i == OP_SUBSCRIBE_PATTERN) + snprintf (tmp, sizeof (tmp), _("Subscribe pattern: ")); + else + snprintf (tmp, sizeof (tmp), _("Unsubscribe pattern: ")); + if (mutt_get_field (tmp, buf, sizeof (buf), 0) != 0 || !buf[0]) + { + FREE (&rx); + break; + } + + if ((err = REGCOMP (rx, s, REG_NOSUB)) != 0) + { + regerror (err, rx, buf, sizeof (buf)); + regfree (rx); + FREE (&rx); + mutt_error ("%s", buf); + break; + } + menu->redraw = REDRAW_FULL; + j = 0; + } + else if (!state.entrylen) + { + mutt_error _("No newsgroups match the mask"); + break; + } + + for ( ; j < state.entrylen; j++) + { + struct folder_file *f = &state.entry[j]; + + if (i == OP_BROWSER_SUBSCRIBE || i == OP_BROWSER_UNSUBSCRIBE || + regexec (rx, f->name, 0, NULL, 0) == 0) + { + if (i == OP_BROWSER_SUBSCRIBE || i == OP_SUBSCRIBE_PATTERN) + nd = mutt_newsgroup_subscribe (news, f->name); + else + nd = mutt_newsgroup_unsubscribe (news, f->name); +/* if (nd) + { + FOLDER folder; + char buffer[_POSIX_PATH_MAX + SHORT_STRING]; + + folder.name = f->name; + folder.f = NULL; + folder.new = nd->new; + folder.nd = nd; + FREE (&f->desc); + mutt_FormatString (buffer, sizeof (buffer), 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) + { + LIST *grouplist = NULL; + + if (news) + grouplist = news->list; + for (; grouplist; grouplist = grouplist->next) + { + nd = (NNTP_DATA *) grouplist->data; + if (nd && nd->group && !nd->subscribed) + { + if (regexec (rx, nd->group, 0, NULL, 0) == 0) + { + mutt_newsgroup_subscribe (news, nd->group); + add_folder (menu, &state, nd->group, NULL, nd, nd->new); + } + } + } + init_menu (&state, menu, title, sizeof (title), buffy); + } + mutt_newsrc_update (news); + nntp_clear_cacheindex (news); + if (i != OP_BROWSER_SUBSCRIBE && i != OP_BROWSER_UNSUBSCRIBE) + regfree (rx); + FREE (&rx); + } +#ifdef USE_IMAP + else +#endif /* USE_IMAP && USE_NNTP */ +#endif /* USE_NNTP */ +#ifdef USE_IMAP + { + if (i == OP_BROWSER_SUBSCRIBE) + imap_subscribe (state.entry[menu->current].name, 1); + else + imap_subscribe (state.entry[menu->current].name, 0); + } +#endif /* USE_IMAP */ } } diff -udprP mutt-1.5.20.orig/browser.h mutt-1.5.20/browser.h --- mutt-1.5.20.orig/browser.h 2009-01-05 00:34:12.000000000 +0200 +++ mutt-1.5.20/browser.h 2009-06-15 21:05:24.000000000 +0300 @@ -19,6 +19,10 @@ #ifndef _BROWSER_H #define _BROWSER_H 1 +#ifdef USE_NNTP +#include "nntp.h" +#endif + struct folder_file { mode_t mode; @@ -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 -udprP mutt-1.5.20.orig/buffy.c mutt-1.5.20/buffy.c --- mutt-1.5.20.orig/buffy.c 2009-06-02 20:16:26.000000000 +0300 +++ mutt-1.5.20/buffy.c 2009-06-15 21:05:24.000000000 +0300 @@ -320,6 +320,9 @@ int mutt_buffy_check (int force) #ifdef USE_POP if (!Context || Context->magic != M_POP) #endif +#ifdef USE_NNTP + if (!Context || Context->magic != M_NNTP) +#endif /* check device ID and serial number instead of comparing paths */ if (!Context || !Context->path || stat (Context->path, &contex_sb) != 0) { @@ -343,6 +346,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)) { @@ -360,25 +368,21 @@ int mutt_buffy_check (int force) /* check to see if the folder is the currently selected folder * before polling */ if (!Context || !Context->path || -#if defined USE_IMAP || defined USE_POP - (( + ( + (0 #ifdef USE_IMAP - tmp->magic == M_IMAP + || tmp->magic == M_IMAP #endif #ifdef USE_POP -#ifdef USE_IMAP - || -#endif - tmp->magic == M_POP -#endif - ) ? mutt_strcmp (tmp->path, Context->path) : + || tmp->magic == M_POP #endif - (sb.st_dev != contex_sb.st_dev || sb.st_ino != contex_sb.st_ino) -#if defined USE_IMAP || defined USE_POP - ) +#ifdef USE_NNTP + || tmp->magic == M_NNTP #endif - ) - + ) ? mutt_strcmp (tmp->path, Context->path) : + (sb.st_dev != contex_sb.st_dev || sb.st_ino != contex_sb.st_ino) + ) + ) { switch (tmp->magic) { diff -udprP mutt-1.5.20.orig/complete.c mutt-1.5.20/complete.c --- mutt-1.5.20.orig/complete.c 2009-01-05 00:38:16.000000000 +0200 +++ mutt-1.5.20/complete.c 2009-06-15 21:05:24.000000000 +0300 @@ -25,6 +25,9 @@ #include "mailbox.h" #include "imap.h" #endif +#ifdef USE_NNTP +#include "nntp.h" +#endif #include #include @@ -48,9 +51,71 @@ int mutt_complete (char *s, size_t slen) char filepart[_POSIX_PATH_MAX]; #ifdef USE_IMAP char imap_path[LONG_STRING]; +#endif dprint (2, (debugfile, "mutt_complete: completing %s\n", s)); +#ifdef USE_NNTP + if (option (OPTNEWS)) + { + LIST *l = CurrentNewsSrv->list; + + strfcpy (filepart, s, sizeof (filepart)); + + /* + * special case to handle when there is no filepart yet. + * find the first subscribed newsgroup + */ + if ((len = mutt_strlen (filepart)) == 0) + { + for (; l; l = l->next) + { + NNTP_DATA *data = (NNTP_DATA *)l->data; + + if (data && data->subscribed) + { + strfcpy (filepart, data->group, sizeof (filepart)); + init++; + l = l->next; + break; + } + } + } + + for (; l; l = l->next) + { + NNTP_DATA *data = (NNTP_DATA *)l->data; + + if (data && data->subscribed && + mutt_strncmp (data->group, filepart, len) == 0) + { + if (init) + { + for (i = 0; filepart[i] && data->group[i]; i++) + { + if (filepart[i] != data->group[i]) + { + filepart[i] = 0; + break; + } + } + filepart[i] = 0; + } + else + { + strfcpy (filepart, data->group, sizeof (filepart)); + init = 1; + } + } + } + + strcpy (s, filepart); + + return (init ? 0 : -1); + } +#endif + +#ifdef USE_IMAP /* we can use '/' as a delimiter, imap_complete rewrites it */ if (*s == '=' || *s == '+' || *s == '!') { diff -udprP mutt-1.5.20.orig/compose.c mutt-1.5.20/compose.c --- mutt-1.5.20.orig/compose.c 2009-03-31 09:52:43.000000000 +0300 +++ mutt-1.5.20/compose.c 2009-06-15 21:05:24.000000000 +0300 @@ -32,10 +32,15 @@ #include "mailbox.h" #include "sort.h" #include "charset.h" +#include "mx.h" #ifdef MIXMASTER #include "remailer.h" #endif + +#ifdef USE_NNTP +#include "nntp.h" +#endif #include #include @@ -60,18 +65,21 @@ enum HDR_REPLYTO, HDR_FCC, -#ifdef MIXMASTER - HDR_MIX, -#endif 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) static char *Prompts[] = @@ -83,6 +91,16 @@ static char *Prompts[] = "Subject: ", "Reply-To: ", "Fcc: " +#ifdef USE_NNTP +#ifdef MIXMASTER + ,"" +#endif + ,"" + ,"" + ,"Newsgroups: " + ,"Followup-To: " + ,"X-Comment-To: " +#endif }; static struct mapping_t ComposeHelp[] = { @@ -97,6 +115,19 @@ static 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 } +}; +#endif + static void snd_entry (char *b, size_t blen, MUTTMENU *menu, int num) { mutt_FormatString (b, blen, 0, NONULL (AttachFormat), mutt_attach_fmt, @@ -115,16 +146,16 @@ static void redraw_crypt_lines (HEADER * if ((WithCrypto & APPLICATION_PGP) && (WithCrypto & APPLICATION_SMIME)) { if (!msg->security) - mvaddstr (HDR_CRYPT, 0, "Security: "); + mvaddstr (HDR_CRYPT, 0, " Security: "); else if (msg->security & APPLICATION_SMIME) - mvaddstr (HDR_CRYPT, 0, " S/MIME: "); + mvaddstr (HDR_CRYPT, 0, " S/MIME: "); else if (msg->security & APPLICATION_PGP) - mvaddstr (HDR_CRYPT, 0, " PGP: "); + mvaddstr (HDR_CRYPT, 0, " PGP: "); } else if ((WithCrypto & APPLICATION_SMIME)) - mvaddstr (HDR_CRYPT, 0, " S/MIME: "); + mvaddstr (HDR_CRYPT, 0, " S/MIME: "); else if ((WithCrypto & APPLICATION_PGP)) - mvaddstr (HDR_CRYPT, 0, " PGP: "); + mvaddstr (HDR_CRYPT, 0, " PGP: "); else return; @@ -252,9 +283,28 @@ static void draw_envelope_addr (int line static void draw_envelope (HEADER *msg, char *fcc) { draw_envelope_addr (HDR_FROM, msg->env->from); +#ifdef USE_NNTP + if (!option (OPTNEWSSEND)) + { +#endif draw_envelope_addr (HDR_TO, msg->env->to); draw_envelope_addr (HDR_CC, msg->env->cc); draw_envelope_addr (HDR_BCC, msg->env->bcc); +#ifdef USE_NNTP + } + else + { + mvprintw (HDR_TO, 0, TITLE_FMT , Prompts[HDR_NEWSGROUPS - 1]); + mutt_paddstr (W, NONULL (msg->env->newsgroups)); + mvprintw (HDR_CC, 0, TITLE_FMT , Prompts[HDR_FOLLOWUPTO - 1]); + mutt_paddstr (W, NONULL (msg->env->followup_to)); + if (option (OPTXCOMMENTTO)) + { + mvprintw (HDR_BCC, 0, TITLE_FMT , Prompts[HDR_XCOMMENTTO - 1]); + mutt_paddstr (W, NONULL (msg->env->x_comment_to)); + } + } +#endif mvprintw (HDR_SUBJECT, 0, TITLE_FMT, Prompts[HDR_SUBJECT - 1]); mutt_paddstr (W, NONULL (msg->env->subject)); draw_envelope_addr (HDR_REPLYTO, msg->env->reply_to); @@ -507,6 +557,12 @@ int mutt_compose_menu (HEADER *msg, /* /* Sort, SortAux could be changed in mutt_index_menu() */ int oldSort, oldSortAux; struct stat st; +#ifdef USE_NNTP + int news = 0; /* is it a news article ? */ + + if (option (OPTNEWSSEND)) + news++; +#endif mutt_attach_init (msg->content); idx = mutt_gen_attach_list (msg->content, -1, idx, &idxlen, &idxmax, 0, 1); @@ -517,10 +573,18 @@ int mutt_compose_menu (HEADER *msg, /* menu->make_entry = snd_entry; menu->tag = mutt_tag_attach; menu->data = idx; +#ifdef USE_NNTP + if (news) + menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_COMPOSE, ComposeNewsHelp); + else +#endif menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_COMPOSE, ComposeHelp); while (loop) { +#ifdef USE_NNTP + unset_option (OPTNEWS); /* for any case */ +#endif switch (op = mutt_menuLoop (menu)) { case OP_REDRAW: @@ -533,17 +597,87 @@ int mutt_compose_menu (HEADER *msg, /* mutt_message_hook (NULL, msg, M_SEND2HOOK); break; case OP_COMPOSE_EDIT_TO: +#ifdef USE_NNTP + if (news) + break; +#endif menu->redraw = edit_address_list (HDR_TO, &msg->env->to); mutt_message_hook (NULL, msg, M_SEND2HOOK); break; case OP_COMPOSE_EDIT_BCC: +#ifdef USE_NNTP + if (news) + break; +#endif menu->redraw = edit_address_list (HDR_BCC, &msg->env->bcc); mutt_message_hook (NULL, msg, M_SEND2HOOK); break; case OP_COMPOSE_EDIT_CC: +#ifdef USE_NNTP + if (news) + break; +#endif menu->redraw = edit_address_list (HDR_CC, &msg->env->cc); mutt_message_hook (NULL, msg, M_SEND2HOOK); break; +#ifdef USE_NNTP + case OP_COMPOSE_EDIT_NEWSGROUPS: + if (news) + { + if (msg->env->newsgroups) + strfcpy (buf, msg->env->newsgroups, sizeof (buf)); + else + buf[0] = 0; + if (mutt_get_field ("Newsgroups: ", buf, sizeof (buf), 0) == 0 && buf[0]) + { + FREE (&msg->env->newsgroups); + mutt_remove_trailing_ws (buf); + msg->env->newsgroups = safe_strdup (mutt_skip_whitespace (buf)); + move (HDR_TO, HDR_XOFFSET); + clrtoeol (); + if (msg->env->newsgroups) + printw ("%-*.*s", W, W, msg->env->newsgroups); + } + } + break; + + case OP_COMPOSE_EDIT_FOLLOWUP_TO: + if (news) + { + buf[0] = 0; + if (msg->env->followup_to) + strfcpy (buf, msg->env->followup_to, sizeof (buf)); + if (mutt_get_field ("Followup-To: ", buf, sizeof (buf), 0) == 0 && buf[0]) + { + FREE (&msg->env->followup_to); + mutt_remove_trailing_ws (buf); + msg->env->followup_to = safe_strdup (mutt_skip_whitespace (buf)); + move (HDR_CC, HDR_XOFFSET); + clrtoeol(); + if (msg->env->followup_to) + printw ("%-*.*s", W, W, msg->env->followup_to); + } + } + break; + + case OP_COMPOSE_EDIT_X_COMMENT_TO: + if (news && option (OPTXCOMMENTTO)) + { + buf[0] = 0; + if (msg->env->x_comment_to) + strfcpy (buf, msg->env->x_comment_to, sizeof (buf)); + if (mutt_get_field ("X-Comment-To: ", buf, sizeof (buf), 0) == 0 && buf[0]) + { + FREE (&msg->env->x_comment_to); + msg->env->x_comment_to = safe_strdup (buf); + move (HDR_BCC, HDR_XOFFSET); + clrtoeol(); + if (msg->env->x_comment_to) + printw ("%-*.*s", W, W, msg->env->x_comment_to); + } + } + break; +#endif case OP_COMPOSE_EDIT_SUBJECT: if (msg->env->subject) strfcpy (buf, msg->env->subject, sizeof (buf)); @@ -706,6 +840,9 @@ int mutt_compose_menu (HEADER *msg, /* break; case OP_COMPOSE_ATTACH_MESSAGE: +#ifdef USE_NNTP + case OP_COMPOSE_ATTACH_NEWS_MESSAGE: +#endif { char *prompt; HEADER *h; @@ -713,7 +850,22 @@ int mutt_compose_menu (HEADER *msg, /* fname[0] = 0; prompt = _("Open mailbox to attach message from"); +#ifdef USE_NNTP + unset_option (OPTNEWS); + if (op == OP_COMPOSE_ATTACH_NEWS_MESSAGE) + { + if (!(CurrentNewsSrv = mutt_select_newsserver (NewsServer))) + break; + + prompt = _("Open newsgroup to attach message from"); + set_option (OPTNEWS); + } +#endif + if (Context) +#ifdef USE_NNTP + if ((op == OP_COMPOSE_ATTACH_MESSAGE) ^ (Context->magic == M_NNTP)) +#endif { strfcpy (fname, NONULL (Context->path), sizeof (fname)); mutt_pretty_mailbox (fname, sizeof (fname)); @@ -722,6 +874,11 @@ int mutt_compose_menu (HEADER *msg, /* if (mutt_enter_fname (prompt, fname, sizeof (fname), &menu->redraw, 1) == -1 || !fname[0]) break; +#ifdef USE_NNTP + if (option (OPTNEWS)) + nntp_expand_path (fname, sizeof (fname), &CurrentNewsSrv->conn->account); + else +#endif mutt_expand_path (fname, sizeof (fname)); #ifdef USE_IMAP if (!mx_is_imap (fname)) @@ -729,6 +886,9 @@ int mutt_compose_menu (HEADER *msg, /* #ifdef USE_POP if (!mx_is_pop (fname)) #endif +#ifdef USE_NNTP + if (!mx_is_nntp (fname) && !option (OPTNEWS)) +#endif /* check to make sure the file exists and is readable */ if (access (fname, R_OK) == -1) { diff -udprP mutt-1.5.20.orig/config.h.in mutt-1.5.20/config.h.in --- mutt-1.5.20.orig/config.h.in 2009-06-09 09:51:15.000000000 +0300 +++ mutt-1.5.20/config.h.in 2009-06-15 21:05:24.000000000 +0300 @@ -37,6 +37,9 @@ significant more memory when defined. */ #undef EXACT_ADDRESS +/* Compiling with newsreading support with NNTP */ +#undef USE_NNTP + /* program to use for shell commands */ #undef EXECSHELL diff -udprP mutt-1.5.20.orig/configure.ac mutt-1.5.20/configure.ac --- mutt-1.5.20.orig/configure.ac 2009-06-09 09:50:33.000000000 +0300 +++ mutt-1.5.20/configure.ac 2009-06-15 21:05:24.000000000 +0300 @@ -599,6 +599,14 @@ if test x"$need_imap" = xyes -o x"$need_ MUTT_LIB_OBJECTS="$MUTT_LIB_OBJECTS bcache.o" fi +AC_ARG_ENABLE(nntp, [ --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_socket="yes" + fi +]) + dnl -- end socket dependencies -- if test "$need_socket" = "yes" diff -udprP mutt-1.5.20.orig/curs_main.c mutt-1.5.20/curs_main.c --- mutt-1.5.20.orig/curs_main.c 2009-06-14 05:48:36.000000000 +0300 +++ mutt-1.5.20/curs_main.c 2009-06-15 21:47:09.000000000 +0300 @@ -22,6 +22,7 @@ #include "mutt.h" #include "mutt_curses.h" +#include "mx.h" #include "mutt_menu.h" #include "mailbox.h" #include "mapping.h" @@ -38,6 +39,10 @@ #include "mutt_crypt.h" +#ifdef USE_NNTP +#include "nntp.h" +#endif + #include #include @@ -413,12 +418,27 @@ static 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 } +}; +#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; @@ -439,7 +459,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 */ @@ -690,6 +714,9 @@ int mutt_index_menu (void) imap_disallow_reopen (Context); #endif +#ifdef USE_NNTP + unset_option (OPTNEWS); /* for any case */ +#endif switch (op) { @@ -740,6 +767,120 @@ int mutt_index_menu (void) menu_current_bottom (menu); break; +#ifdef USE_NNTP + case OP_GET_MESSAGE: + case OP_GET_PARENT: + CHECK_MSGCOUNT; + if (Context->magic == M_NNTP) + { + HEADER *h; + + if (op == OP_GET_MESSAGE) + { + buf[0] = 0; + if (mutt_get_field (_("Enter Message-Id: "), buf, sizeof (buf), 0) != 0 + || !buf[0]) + break; + } + else + { + LIST *ref = CURHDR->env->references; + if (!ref) + { + mutt_error _("Article has no parent reference!"); + break; + } + strfcpy (buf, ref->data, sizeof (buf)); + } + if (!Context->id_hash) + Context->id_hash = mutt_make_id_hash (Context); + if ((h = hash_find (Context->id_hash, buf))) + { + if (h->virtual != -1) + { + menu->current = h->virtual; + menu->redraw = REDRAW_MOTION_RESYNCH; + } + else if (h->collapsed) + { + mutt_uncollapse_thread (Context, h); + mutt_set_virtual (Context); + menu->current = h->virtual; + menu->redraw = REDRAW_MOTION_RESYNCH; + } + else + mutt_error _("Message not visible in limited view."); + } + else + { + if (nntp_check_msgid (Context, buf) == 0) + { + h = Context->hdrs[Context->msgcount-1]; + mutt_sort_headers (Context, 0); + menu->current = h->virtual; + menu->redraw = REDRAW_FULL; + } + else + mutt_error (_("Article %s not found on server"), buf); + } + } + break; + + case OP_GET_CHILDREN: + case OP_RECONSTRUCT_THREAD: + CHECK_MSGCOUNT; + if (Context->magic == M_NNTP) + { + HEADER *h; + int old = CURHDR->index, i; + + if (!CURHDR->env->message_id) + { + mutt_error _("No Message-Id. Unable to perform operation"); + break; + } + + if (!Context->id_hash) + Context->id_hash = mutt_make_id_hash (Context); + strfcpy (buf, CURHDR->env->message_id, sizeof (buf)); + + if (op == OP_RECONSTRUCT_THREAD) + { + LIST *ref = CURHDR->env->references; + while (ref) + { + nntp_check_msgid (Context, ref->data); + /* the last msgid in References is the root message */ + if (!ref->next) + strfcpy (buf, ref->data, sizeof (buf)); + ref = ref->next; + } + } + mutt_message _("Check for children of message..."); + if (nntp_check_children (Context, buf) == 0) + { + mutt_sort_headers (Context, (op == OP_RECONSTRUCT_THREAD)); + h = hash_find (Context->id_hash, buf); + /* if the root message was retrieved, move to it */ + if (h) + menu->current = h->virtual; + else /* try to restore old position */ + for (i = 0; i < Context->msgcount; i++) + if (Context->hdrs[i]->index == old) + { + menu->current = Context->hdrs[i]->virtual; + /* As an added courtesy, recenter the menu + * with the current entry at the middle of the screen */ + menu_check_recenter (menu); + menu_current_middle (menu); + } + } + menu->redraw = REDRAW_FULL; + mutt_clear_error (); + } + break; +#endif + case OP_JUMP: CHECK_MSGCOUNT; @@ -836,11 +977,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) { @@ -1057,15 +1220,22 @@ int mutt_index_menu (void) 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"); @@ -1084,6 +1254,21 @@ int mutt_index_menu (void) } else { +#ifdef USE_NNTP + if (op == OP_MAIN_CHANGE_GROUP || + op == OP_MAIN_CHANGE_GROUP_READONLY) + { + set_option (OPTNEWS); + if (!(CurrentNewsSrv = mutt_select_newsserver (NewsServer))) + break; + if (flags) + cp = _("Open newsgroup in read-only mode"); + else + cp = _("Open newsgroup"); + nntp_buffy (buf); + } + else +#endif mutt_buffy (buf, sizeof (buf)); if (mutt_enter_fname (cp, buf, sizeof (buf), &menu->redraw, 1) == -1) @@ -1103,6 +1288,14 @@ int mutt_index_menu (void) } } +#ifdef USE_NNTP + if (option (OPTNEWS)) + { + unset_option (OPTNEWS); + nntp_expand_path (buf, sizeof (buf), &CurrentNewsSrv->conn->account); + } + else +#endif mutt_expand_path (buf, sizeof (buf)); if (mx_get_magic (buf) <= 0) { @@ -1140,15 +1333,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 */ @@ -1519,6 +1715,15 @@ int mutt_index_menu (void) CHECK_READONLY; CHECK_ACL(M_ACL_WRITE, _("flag message")); +#ifdef USE_NNTP + if (Context->magic == M_NNTP) + { + mutt_flushinp (); + mutt_error _("Can't change 'important' flag on NNTP server."); + break; + } +#endif + if (tag) { for (j = 0; j < Context->vcount; j++) @@ -1866,6 +2071,17 @@ int mutt_index_menu (void) } break; +#ifdef USE_NNTP + case OP_CATCHUP: + if (Context && Context->magic == M_NNTP) + { + if (mutt_newsgroup_catchup (CurrentNewsSrv, + ((NNTP_DATA *)Context->data)->group)) + menu->redraw = REDRAW_INDEX | REDRAW_STATUS; + } + break; +#endif + case OP_DISPLAY_ADDRESS: CHECK_MSGCOUNT; @@ -1993,6 +2209,15 @@ int mutt_index_menu (void) menu->redraw = (tag ? REDRAW_INDEX : REDRAW_CURRENT) | REDRAW_STATUS; } #endif + +#ifdef USE_NNTP + if (Context->magic == M_NNTP) + { + mutt_flushinp (); + mutt_error _("Can't edit message on newsserver."); + break; + } +#endif MAYBE_REDRAW (menu->redraw); break; @@ -2065,6 +2290,41 @@ 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; + if (op == OP_FOLLOWUP) + ci_send_message (SENDNEWS|SENDREPLY, NULL, NULL, Context, + tag ? NULL : CURHDR); + else + ci_send_message (SENDNEWS|SENDFORWARD, NULL, NULL, Context, + tag ? NULL : CURHDR); + } + menu->redraw = REDRAW_FULL; + break; + } +#endif + case OP_REPLY: CHECK_ATTACH; @@ -2140,6 +2400,12 @@ int mutt_index_menu (void) CHECK_READONLY; CHECK_ACL(M_ACL_DELETE, _("undelete message(s)")); +#ifdef USE_NNTP + /* Close all open NNTP connections */ + if (!attach_msg) + nntp_logout_all (); +#endif + rc = mutt_thread_set_flag (CURHDR, M_DELETE, 0, op == OP_UNDELETE_THREAD ? 0 : 1); diff -udprP mutt-1.5.20.orig/doc/manual.xml.head mutt-1.5.20/doc/manual.xml.head --- mutt-1.5.20.orig/doc/manual.xml.head 2009-05-30 20:20:08.000000000 +0300 +++ mutt-1.5.20/doc/manual.xml.head 2009-06-15 21:05:24.000000000 +0300 @@ -1568,6 +1568,22 @@ fo-table for details. + +Reading news via NNTP + + +If compiled with --enable-nntp option, Mutt can +read news from newsserver via NNTP. You can open a newsgroup with +function ``change-newsgroup'' (default: ``i''). Default newsserver +can be obtained from NNTPSERVER environment +variable. Like other news readers, info about subscribed newsgroups +is saved in file by $newsrc +variable. Article headers are cached and can be loaded from file when +newsgroup entered instead loading from newsserver. + + + + diff -udprP mutt-1.5.20.orig/doc/mutt.man mutt-1.5.20/doc/mutt.man --- mutt-1.5.20.orig/doc/mutt.man 2009-06-07 03:32:44.000000000 +0300 +++ mutt-1.5.20/doc/mutt.man 2009-06-15 21:07:47.000000000 +0300 @@ -23,8 +23,8 @@ mutt \- The Mutt Mail User Agent .SH SYNOPSIS .PP .B mutt -[-nRyzZ] -[\-e \fIcmd\fP] [\-F \fIfile\fP] [\-m \fItype\fP] [\-f \fIfile\fP] +[-GnRyzZ] +[\-e \fIcmd\fP] [\-F \fIfile\fP] [\-g \fIserver\fP] [\-m \fItype\fP] [\-f \fIfile\fP] .PP .B mutt [\-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 newsserver. +.IP "-G" +Start Mutt with a listing of subscribed newsgroups. .IP "-h" Display help. .IP "-H \fIdraft\fP" diff -udprP mutt-1.5.20.orig/functions.h mutt-1.5.20/functions.h --- mutt-1.5.20.orig/functions.h 2009-04-30 08:36:17.000000000 +0300 +++ mutt-1.5.20/functions.h 2009-06-15 21:05:24.000000000 +0300 @@ -88,6 +88,10 @@ struct binding_t OpMain[] = { /* map: in { "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 @@ struct binding_t OpMain[] = { /* map: in { "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" }, @@ -127,6 +139,9 @@ struct binding_t OpMain[] = { /* map: in { "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" }, @@ -146,6 +161,10 @@ struct binding_t OpMain[] = { /* map: in { "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, "$" }, @@ -157,7 +176,7 @@ struct binding_t OpMain[] = { /* map: in { "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" }, @@ -177,6 +196,10 @@ struct binding_t OpPager[] = { /* map: p { "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" }, @@ -187,8 +210,12 @@ struct binding_t OpPager[] = { /* map: p { "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 }, @@ -207,6 +234,9 @@ struct binding_t OpPager[] = { /* map: p { "next-thread", OP_MAIN_NEXT_THREAD, "\016" }, { "next-subthread", OP_MAIN_NEXT_SUBTHREAD, "\033n" }, { "print-message", OP_PRINT, "p" }, +#ifdef USE_NNTP + { "post-message", OP_POST, "P" }, +#endif { "previous-thread", OP_MAIN_PREV_THREAD, "\020" }, { "previous-subthread",OP_MAIN_PREV_SUBTHREAD, "\033p" }, { "quit", OP_QUIT, "Q" }, @@ -254,7 +284,7 @@ struct binding_t OpPager[] = { /* map: p { "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 }, @@ -275,6 +305,10 @@ struct binding_t OpAttach[] = { /* map: { "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, "|" }, @@ -300,6 +334,7 @@ struct binding_t OpAttach[] = { /* map: 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" }, @@ -319,6 +354,11 @@ struct binding_t OpCompose[] = { /* map: { "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" }, @@ -370,14 +410,25 @@ struct binding_t OpBrowser[] = { /* map: { "select-new", OP_BROWSER_NEW_FILE, "N" }, { "check-new", OP_CHECK_NEW, NULL }, { "toggle-mailboxes", OP_TOGGLE_MAILBOXES, "\t" }, +#ifdef USE_NNTP + { "reload-active", OP_LOAD_ACTIVE, "g" }, + { "subscribe-pattern", OP_SUBSCRIBE_PATTERN, "S" }, + { "unsubscribe-pattern", OP_UNSUBSCRIBE_PATTERN, "U" }, + { "catchup", OP_CATCHUP, "y" }, + { "uncatchup", OP_UNCATCHUP, "Y" }, +#endif { "view-file", OP_BROWSER_VIEW_FILE, " " }, { "buffy-list", OP_BUFFY_LIST, "." }, #ifdef USE_IMAP { "create-mailbox", OP_CREATE_MAILBOX, "C" }, { "delete-mailbox", OP_DELETE_MAILBOX, "d" }, { "rename-mailbox", OP_RENAME_MAILBOX, "r" }, +#endif +#if defined USE_IMAP || defined USE_NNTP { "subscribe", OP_BROWSER_SUBSCRIBE, "s" }, { "unsubscribe", OP_BROWSER_UNSUBSCRIBE, "u" }, +#endif +#ifdef USE_IMAP { "toggle-subscribed", OP_BROWSER_TOGGLE_LSUB, "T" }, #endif { NULL, 0, NULL } diff -udprP mutt-1.5.20.orig/globals.h mutt-1.5.20/globals.h --- mutt-1.5.20.orig/globals.h 2009-06-03 23:48:31.000000000 +0300 +++ mutt-1.5.20/globals.h 2009-06-15 21:05:24.000000000 +0300 @@ -95,6 +95,15 @@ WHERE char *MixEntryFormat; #endif WHERE char *Muttrc INITVAL (NULL); +#ifdef USE_NNTP +WHERE char *NewsCacheDir; +WHERE char *GroupFormat; +WHERE char *Inews; +WHERE char *NewsServer; +WHERE char *NntpUser; +WHERE char *NntpPass; +WHERE char *NewsRc; +#endif WHERE char *Outbox; WHERE char *Pager; WHERE char *PagerFmt; @@ -188,6 +197,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 -udprP mutt-1.5.20.orig/hash.c mutt-1.5.20/hash.c --- mutt-1.5.20.orig/hash.c 2009-03-31 09:52:43.000000000 +0300 +++ mutt-1.5.20/hash.c 2009-06-15 21:19:59.000000000 +0300 @@ -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 cha { ptr->next = table->table[h]; table->table[h] = ptr; + table->curnelem++; } else { @@ -112,6 +137,7 @@ int hash_insert (HASH * table, const cha else table->table[h] = ptr; ptr->next = tmp; + table->curnelem++; } return h; } @@ -142,6 +168,7 @@ void hash_delete_hash (HASH * table, int if (destroy) destroy (ptr->data); FREE (&ptr); + table->curnelem--; ptr = *last; } diff -udprP mutt-1.5.20.orig/hash.h mutt-1.5.20/hash.h --- mutt-1.5.20.orig/hash.h 2009-03-31 09:52:43.000000000 +0300 +++ mutt-1.5.20/hash.h 2009-06-15 21:05:24.000000000 +0300 @@ -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 -udprP mutt-1.5.20.orig/hdrline.c mutt-1.5.20/hdrline.c --- mutt-1.5.20.orig/hdrline.c 2009-04-13 19:24:55.000000000 +0300 +++ mutt-1.5.20/hdrline.c 2009-06-15 21:05:24.000000000 +0300 @@ -211,6 +211,7 @@ int mutt_user_is_recipient (HEADER *h) * %E = number of messages in current thread * %f = entire from line * %F = like %n, unless from self + * %g = newsgroup name (if compiled with nntp support) * %i = message-id * %l = number of lines in the message * %L = like %F, except `lists' are displayed first @@ -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 -udprP mutt-1.5.20.orig/headers.c mutt-1.5.20/headers.c --- mutt-1.5.20.orig/headers.c 2009-04-30 08:36:17.000000000 +0300 +++ mutt-1.5.20/headers.c 2009-06-15 21:17:07.000000000 +0300 @@ -114,6 +114,9 @@ void mutt_edit_headers (const char *edit $edit_headers set, we remove References: as they're likely invalid; we can simply compare strings as we don't generate References for multiple Message-Ids in IRT anyways */ +#ifdef USE_NNTP + if (!option (OPTNEWSSEND)) +#endif if (!n->in_reply_to || (msg->env->in_reply_to && mutt_strcmp (n->in_reply_to->data, msg->env->in_reply_to->data) != 0)) diff -udprP mutt-1.5.20.orig/init.c mutt-1.5.20/init.c --- mutt-1.5.20.orig/init.c 2009-06-01 19:29:32.000000000 +0300 +++ mutt-1.5.20/init.c 2009-06-15 21:05:24.000000000 +0300 @@ -2966,6 +2966,28 @@ void mutt_init (int skip_sys_rc, LIST *c else Fqdn = safe_strdup(NONULL(Hostname)); +#ifdef USE_NNTP + { + FILE *f; + char *i; + + if ((f = safe_fopen (SYSCONFDIR "/nntpserver", "r"))) + { + buffer[0] = '\0'; + fgets (buffer, sizeof (buffer), f); + p = &buffer; + SKIPWS (p); + i = p; + while (*i && (*i != ' ') && (*i != '\t') && (*i != '\r') && (*i != '\n')) i++; + *i = '\0'; + NewsServer = safe_strdup (p); + fclose (f); + } + } + if ((p = getenv ("NNTPSERVER"))) + NewsServer = safe_strdup (p); +#endif + if ((p = getenv ("MAIL"))) Spoolfile = safe_strdup (p); else if ((p = getenv ("MAILDIR"))) diff -udprP mutt-1.5.20.orig/init.h mutt-1.5.20/init.h --- mutt-1.5.20.orig/init.h 2009-06-14 00:35:21.000000000 +0300 +++ mutt-1.5.20/init.h 2009-06-15 21:15:03.000000000 +0300 @@ -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 @@ -322,6 +336,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" }, /* @@ -797,6 +819,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 @@ -879,6 +911,27 @@ struct option_t MuttVars[] = { ** a regular expression that will match the whole name so mutt will expand ** ``Franklin'' to ``Franklin, Steve''. */ +#ifdef USE_NNTP + { "group_index_format", DT_STR, R_BOTH, UL &GroupFormat, UL "%4C %M%N %5s %-45.45f %d" }, + /* + ** .pp + ** This variable allows you to customize the newsgroup browser display to + ** your personal taste. This string is similar to ``$index_format'', but + ** has its own set of printf()-like sequences: + ** .pp + ** .ts + ** %C current newsgroup number + ** %d description of newsgroup (becomes from server) + ** %f newsgroup name + ** %M - if newsgroup not allowed for direct post (moderated for example) + ** %N N if newsgroup is new, u if unsubscribed, blank otherwise + ** %n number of new articles in newsgroup + ** %s number of unread articles in newsgroup + ** %>X right justify the rest of the string and pad with character "X" + ** %|X pad to the end of the line with character "X" + ** .te + */ +#endif { "hdr_format", DT_SYN, R_NONE, UL "index_format", 0 }, /* */ @@ -1255,6 +1308,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, @@ -1270,12 +1324,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 builtin 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 @@ -1310,6 +1366,21 @@ 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: + ** .pp + ** .ts + ** %s newsserver name + ** .te + ** .pp + ** Example: set inews="/usr/local/bin/inews -hS" + */ +#endif { "ispell", DT_PATH, R_NONE, UL &Ispell, UL ISPELL }, /* ** .pp @@ -1533,6 +1604,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" }, /* @@ -1580,6 +1660,77 @@ 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 headers in. If \fIunset\fP, headers will not be saved at all + ** and will be reloaded each time when you enter to newsgroup. + */ + { "news_server", DT_STR, R_NONE, UL &NewsServer, 0 }, + /* + ** .pp + ** This variable specifies domain name or address of NNTP server. It + ** defaults to the newsserver specified in the environment variable + ** $$$NNTPSERVER or contained in the file /etc/nntpserver. You can also + ** specify username and an alternative port for each newsserver, ie: + ** .pp + ** [news[s]://][username[:password]@]newsserver[:port] + */ + { "newsrc", DT_PATH, R_NONE, UL &NewsRc, UL "~/.newsrc" }, + /* + ** .pp + ** The file, containing info about subscribed newsgroups - names and + ** indexes of read articles. The following printf-style sequence + ** is understood: + ** .pp + ** .ts + ** %s newsserver name + ** .te + */ + { "nntp_context", DT_NUM, R_NONE, UL &NntpContext, 1000 }, + /* + ** .pp + ** This variable defines number of articles which will be in index when + ** newsgroup entered. If active newsgroup have more articles than this + ** number, oldest articles will be ignored. Also controls how many + ** articles headers will be saved in cache when you quit newsgroup. + */ + { "nntp_load_description", DT_BOOL, R_NONE, OPTLOADDESC, 1 }, + /* + ** .pp + ** This variable controls whether or not descriptions for each newsgroup + ** must be loaded when newsgroup is added to list (first time list + ** loading or new newsgroup adding). + */ + { "nntp_user", DT_STR, R_NONE, UL &NntpUser, UL "" }, + /* + ** .pp + ** Your login name on the NNTP server. If \fIunset\fP and NNTP server requires + ** authentification, Mutt will prompt you for your account name when you + ** connect to newsserver. + */ + { "nntp_pass", DT_STR, R_NONE, UL &NntpPass, UL "" }, + /* + ** .pp + ** Your password for NNTP account. + */ + { "nntp_poll", DT_NUM, R_NONE, UL &NewsPollTimeout, 60 }, + /* + ** .pp + ** The time in seconds until any operations on newsgroup except post new + ** article will cause recheck for new news. If set to 0, Mutt will + ** recheck newsgroup on each operation in index (stepping, read article, + ** etc.). + */ + { "nntp_reconnect", DT_QUAD, R_NONE, OPT_NNTPRECONNECT, M_ASKYES }, + /* + ** .pp + ** Controls whether or not Mutt will try to reconnect to newsserver when + ** connection lost. + */ +#endif { "pager", DT_PATH, R_NONE, UL &Pager, UL "builtin" }, /* ** .pp @@ -2079,6 +2230,16 @@ struct option_t MuttVars[] = { { "post_indent_str", DT_SYN, R_NONE, UL "post_indent_string", 0 }, /* */ +#ifdef USE_NNTP + { "post_moderated", DT_QUAD, R_NONE, OPT_TOMODERATED, M_ASKYES }, + /* + ** .pp + ** If set to \fIyes\fP, Mutt will post article to newsgroup that have + ** not permissions to posting (e.g. moderated). \fBNote:\fP if newsserver + ** does not support posting to that newsgroup or totally read-only, that + ** posting will not have an effect. + */ +#endif { "postpone", DT_QUAD, R_NONE, OPT_POSTPONE, M_ASKYES }, /* ** .pp @@ -2479,6 +2640,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, newsserver will be asked for new newsgroups on entering + ** the browser. Otherwise, it will be done only once for a newsserver. + ** Also controls whether or not number of new articles of subscribed + ** newsgroups will be then checked. + */ + { "show_only_unread", DT_BOOL, R_NONE, OPTSHOWONLYUNREAD, 0 }, + /* + ** .pp + ** If \fIset\fP, only subscribed newsgroups that contain unread articles + ** will be displayed in browser. + */ +#endif { "sig_dashes", DT_BOOL, R_NONE, OPTSIGDASHES, 1 }, /* ** .pp @@ -3337,6 +3520,14 @@ struct option_t MuttVars[] = { ** Also see the $$read_inc, $$net_inc and $$time_inc variables and the ** ``$tuning'' section of the manual for performance considerations. */ +#ifdef USE_NNTP + { "x_comment_to", DT_BOOL, R_NONE, OPTXCOMMENTTO, 0 }, + /* + ** .pp + ** If \fIset\fP, Mutt will add ``X-Comment-To:'' field (that contains full + ** name of original article author) to article that followuped to newsgroup. + */ +#endif /*--*/ { NULL, 0, 0, 0, 0 } }; diff -udprP mutt-1.5.20.orig/keymap.c mutt-1.5.20/keymap.c --- mutt-1.5.20.orig/keymap.c 2008-11-29 23:09:10.000000000 +0200 +++ mutt-1.5.20/keymap.c 2009-06-15 21:05:24.000000000 +0300 @@ -654,7 +654,6 @@ void km_init (void) km_bindkey ("", MENU_MAIN, OP_DISPLAY_MESSAGE); km_bindkey ("x", MENU_PAGER, OP_EXIT); - km_bindkey ("i", MENU_PAGER, OP_EXIT); km_bindkey ("", MENU_PAGER, OP_PREV_LINE); km_bindkey ("", MENU_PAGER, OP_NEXT_PAGE); km_bindkey ("", MENU_PAGER, OP_PREV_PAGE); diff -udprP mutt-1.5.20.orig/mailbox.h mutt-1.5.20/mailbox.h --- mutt-1.5.20.orig/mailbox.h 2009-04-30 08:36:17.000000000 +0300 +++ mutt-1.5.20/mailbox.h 2009-06-15 21:05:24.000000000 +0300 @@ -74,6 +74,9 @@ int mx_is_imap (const char *); #ifdef USE_POP int mx_is_pop (const char *); #endif +#ifdef USE_NNTP +int mx_is_nntp (const char *); +#endif int mx_access (const char*, int); int mx_check_empty (const char *); diff -udprP mutt-1.5.20.orig/main.c mutt-1.5.20/main.c --- mutt-1.5.20.orig/main.c 2009-06-01 19:29:32.000000000 +0300 +++ mutt-1.5.20/main.c 2009-06-15 21:05:24.000000000 +0300 @@ -60,6 +60,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"); @@ -133,6 +137,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 newsserver (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\ @@ -255,6 +261,12 @@ static void show_version (void) "-USE_POP " #endif +#ifdef USE_NNTP + "+USE_NNTP " +#else + "-USE_NNTP " +#endif + #ifdef USE_IMAP "+USE_IMAP " #else @@ -522,6 +534,9 @@ static void start_curses (void) #define M_NOSYSRC (1<<2) /* -n */ #define M_RO (1<<3) /* -R */ #define M_SELECT (1<<4) /* -y */ +#ifdef USE_NNTP +#define M_NEWS (1<<5) /* -g and -G */ +#endif int main (int argc, char **argv) { @@ -594,7 +609,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': @@ -691,6 +710,20 @@ int main (int argc, char **argv) flags |= M_SELECT; break; +#ifdef USE_NNTP + case 'g': /* Specify a newsserver */ + { + char buf[LONG_STRING]; + + snprintf (buf, sizeof (buf), "set news_server=%s", optarg); + commands = mutt_add_list (commands, buf); + } + + case 'G': /* List of newsgroups */ + flags |= M_SELECT | M_NEWS; + break; +#endif + case 'z': flags |= M_IGNORE; break; @@ -978,6 +1011,18 @@ int main (int argc, char **argv) } else if (flags & M_SELECT) { +#ifdef USE_NNTP + if (flags & M_NEWS) + { + set_option (OPTNEWS); + if(!(CurrentNewsSrv = mutt_select_newsserver (NewsServer))) + { + mutt_endwin (Errorbuf); + exit (1); + } + } + else +#endif if (!Incoming) { mutt_endwin _("No incoming mailboxes defined."); exit (1); @@ -993,6 +1038,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 -udprP mutt-1.5.20.orig/mutt.h mutt-1.5.20/mutt.h --- mutt-1.5.20.orig/mutt.h 2009-06-13 01:15:42.000000000 +0300 +++ mutt-1.5.20/mutt.h 2009-06-15 21:05:24.000000000 +0300 @@ -229,6 +229,9 @@ enum M_PGP_KEY, M_XLABEL, M_MIMEATTACH, +#ifdef USE_NNTP + M_NEWSGROUPS, +#endif /* Options for Mailcap lookup */ M_EDIT, @@ -285,6 +288,12 @@ enum #endif OPT_SUBJECT, OPT_VERIFYSIG, /* verify PGP signatures */ +#ifdef USE_NNTP + OPT_TOMODERATED, + OPT_NNTPRECONNECT, + OPT_CATCHUP, + OPT_FOLLOWUPTOPOSTER, +#endif /* USE_NNTP */ /* THIS MUST BE THE LAST VALUE. */ OPT_MAX @@ -300,6 +309,7 @@ enum #define SENDMAILX (1<<6) #define SENDKEY (1<<7) #define SENDRESEND (1<<8) +#define SENDNEWS (1<<9) /* flags to _mutt_select_file() */ #define M_SEL_BUFFY (1<<0) @@ -319,6 +329,8 @@ enum OPTASCIICHARS, OPTASKBCC, OPTASKCC, + OPTASKFOLLOWUP, + OPTASKXCOMMENTTO, OPTATTACHSPLIT, OPTAUTOEDIT, OPTAUTOTAG, @@ -396,6 +408,9 @@ enum OPTMETOO, OPTMHPURGE, OPTMIMEFORWDECODE, +#ifdef USE_NNTP + OPTMIMESUBJECT, /* encode subject line with RFC2047 */ +#endif OPTNARROWTREE, OPTPAGERSTOP, OPTPIPEDECODE, @@ -477,6 +492,16 @@ enum OPTPGPAUTOINLINE, OPTPGPREPLYINLINE, + /* news options */ + +#ifdef USE_NNTP + OPTSHOWNEWNEWS, + OPTSHOWONLYUNREAD, + OPTSAVEUNSUB, + OPTLOADDESC, + OPTXCOMMENTTO, +#endif /* USE_NNTP */ + /* pseudo options */ OPTAUXSORT, /* (pseudo) using auxillary sort function */ @@ -497,6 +522,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. @@ -507,6 +533,12 @@ enum OPTDONTHANDLEPGPKEYS, /* (pseudo) used to extract PGP keys */ OPTUNBUFFEREDINPUT, /* (pseudo) don't use key buffer */ +#ifdef USE_NNTP + OPTNEWS, /* (pseudo) used to change reader mode */ + OPTNEWSSEND, /* (pseudo) used to change behavior when posting */ + OPTNEWSCACHE, /* (pseudo) used to indicate if news cache exist */ +#endif + OPTMAX }; @@ -585,6 +617,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 */ @@ -751,6 +790,9 @@ typedef struct header ENVELOPE *env; /* envelope information */ BODY *content; /* list of MIME parts */ char *path; +#ifdef USE_NNTP + int article_num; +#endif char *tree; /* character string to print thread tree */ struct thread *thread; @@ -766,7 +808,7 @@ typedef struct header int refno; /* message number on server */ #endif -#if defined USE_POP || defined USE_IMAP +#if defined USE_POP || defined USE_IMAP || defined USE_NNTP void *data; /* driver-specific data */ #endif diff -udprP mutt-1.5.20.orig/muttlib.c mutt-1.5.20/muttlib.c --- mutt-1.5.20.orig/muttlib.c 2009-05-19 03:11:35.000000000 +0300 +++ mutt-1.5.20/muttlib.c 2009-06-15 21:05:24.000000000 +0300 @@ -301,7 +301,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__ */ @@ -689,6 +689,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); @@ -1470,6 +1477,14 @@ int mutt_save_confirm (const char *s, st } } +#ifdef USE_NNTP + if (magic == M_NNTP) + { + mutt_error _("Can't save message to newsserver."); + return 0; + } +#endif + if (stat (s, st) != -1) { if (magic == -1) diff -udprP mutt-1.5.20.orig/mx.c mutt-1.5.20/mx.c --- mutt-1.5.20.orig/mx.c 2009-06-11 07:29:41.000000000 +0300 +++ mutt-1.5.20/mx.c 2009-06-15 21:05:24.000000000 +0300 @@ -343,6 +343,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; @@ -360,6 +376,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", @@ -669,6 +690,12 @@ CONTEXT *mx_open_mailbox (const char *pa break; #endif /* USE_POP */ +#ifdef USE_NNTP + case M_NNTP: + rc = nntp_open_mailbox (ctx); + break; +#endif /* USE_NNTP */ + default: rc = -1; break; @@ -761,6 +788,12 @@ static int sync_mailbox (CONTEXT *ctx, i rc = pop_sync_mailbox (ctx, index_hint); break; #endif /* USE_POP */ + +#ifdef USE_NNTP + case M_NNTP: + rc = nntp_sync_mailbox (ctx); + break; +#endif /* USE_NNTP */ } #if 0 @@ -787,6 +820,16 @@ int mx_close_mailbox (CONTEXT *ctx, int ctx->closing = 1; +#ifdef USE_NNTP + if (ctx->magic == M_NNTP) + { + int ret; + + ret = nntp_close_mailbox (ctx); + mx_fastclose_mailbox (ctx); + return ret; + } +#endif if (ctx->readonly || ctx->dontwrite) { /* mailbox is readonly or we don't want to write */ @@ -1336,6 +1379,11 @@ int mx_check_mailbox (CONTEXT *ctx, int case M_POP: return (pop_check_mailbox (ctx, index_hint)); #endif /* USE_POP */ + +#ifdef USE_NNTP + case M_NNTP: + return (nntp_check_mailbox (ctx)); +#endif /* USE_NNTP */ } } @@ -1396,6 +1444,15 @@ MESSAGE *mx_open_message (CONTEXT *ctx, } #endif /* USE_POP */ +#ifdef USE_NNTP + case M_NNTP: + { + if (nntp_fetch_message (msg, ctx, msgno) != 0) + FREE (&msg); + break; + } +#endif /* USE_NNTP */ + default: dprint (1, (debugfile, "mx_open_message(): function not implemented for mailbox type %d.\n", ctx->magic)); FREE (&msg); @@ -1477,6 +1534,9 @@ int mx_close_message (MESSAGE **msg) #ifdef USE_POP || (*msg)->magic == M_POP #endif +#ifdef USE_NNTP + || (*msg)->magic == M_NNTP +#endif ) { r = safe_fclose (&(*msg)->fp); diff -udprP mutt-1.5.20.orig/mx.h mutt-1.5.20/mx.h --- mutt-1.5.20.orig/mx.h 2008-11-11 21:55:47.000000000 +0200 +++ mutt-1.5.20/mx.h 2009-06-15 21:05:24.000000000 +0300 @@ -40,6 +40,9 @@ enum #ifdef USE_POP , M_POP #endif +#ifdef USE_NNTP + , M_NNTP +#endif }; WHERE short DefaultMagic INITVAL (M_MBOX); diff -udprP mutt-1.5.20.orig/newsrc.c mutt-1.5.20/newsrc.c --- mutt-1.5.20.orig/newsrc.c 1970-01-01 03:00:00.000000000 +0300 +++ mutt-1.5.20/newsrc.c 2009-06-15 21:05:24.000000000 +0300 @@ -0,0 +1,1170 @@ +/* + * Copyright (C) 1998 Brandon Long + * Copyright (C) 1999 Andrej Gritsenko + * Copyright (C) 2000-2009 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 +#include +#include +#include +#include + +void nntp_add_to_list (NNTP_SERVER *s, NNTP_DATA *d) +{ + LIST *l; + + if (!s || !d) + return; + + l = safe_calloc (1, sizeof (LIST)); + if (s->list) + s->tail->next = l; + else + s->list = l; + s->tail = l; + l->data = (void *) d; +} + +static int nntp_parse_newsrc_line (NNTP_SERVER *news, char *line) +{ + NNTP_DATA *data; + char group[LONG_STRING]; + int x = 1; + char *p = line, *b, *h; + size_t len; + + while (*p) + { + if (*p++ == ',') + x++; + } + + p = line; + while (*p && (*p != ':' && *p != '!')) p++; + if (!*p) + return -1; + len = p + 1 - line; + if (len > sizeof (group)) + len = sizeof (group); + strfcpy (group, line, len); + if ((data = (NNTP_DATA *)hash_find (news->newsgroups, group)) == NULL) + { + data = (NNTP_DATA *) safe_calloc (1, sizeof (NNTP_DATA) + strlen (group) + 1); + data->group = (char *) data + sizeof (NNTP_DATA); + strcpy (data->group, group); + data->nserv = news; + data->deleted = 1; + if (news->newsgroups->nelem < news->newsgroups->curnelem * 2) + news->newsgroups = hash_resize (news->newsgroups, news->newsgroups->nelem * 2, 0); + hash_insert (news->newsgroups, data->group, data, 0); + nntp_add_to_list (news, data); + } + else + FREE ((void **) &data->entries); + + data->rc = 1; + data->entries = safe_calloc (x*2, sizeof (NEWSRC_ENTRY)); + data->max = x*2; + + if (*p == ':') + data->subscribed = 1; + else + data->subscribed = 0; + + p++; + b = p; + x = 0; + while (*b) + { + while (*p && *p != ',' && *p != '\n') p++; + if (*p) + { + *p = '\0'; + p++; + } + if ((h = strchr(b, '-'))) + { + *h = '\0'; + h++; + data->entries[x].first = atoi(b); + data->entries[x].last = atoi(h); + } + else + { + data->entries[x].first = atoi(b); + data->entries[x].last = data->entries[x].first; + } + b = p; + if (data->entries[x].last != 0) + x++; + } + if (x && !data->lastMessage) + data->lastMessage = data->entries[x-1].last; + data->num = x; + mutt_newsgroup_stat (data); + dprint (2, (debugfile, "parse_line: Newsgroup %s\n", data->group)); + + return 0; +} + +static int slurp_newsrc (NNTP_SERVER *news) +{ + FILE *fp; + char *buf; + struct stat sb; + + news->stat = stat (news->newsrc, &sb); + news->size = sb.st_size; + news->mtime = sb.st_mtime; + + if ((fp = safe_fopen (news->newsrc, "r")) == NULL) + return -1; + /* hmm, should we use dotlock? */ + if (mx_lock_file (news->newsrc, fileno (fp), 0, 0, 1)) + { + fclose (fp); + return -1; + } + + buf = safe_malloc (sb.st_size + 1); + while (sb.st_size && fgets (buf, sb.st_size + 1, fp)) + nntp_parse_newsrc_line (news, buf); + FREE (&buf); + + mx_unlock_file (news->newsrc, fileno (fp), 0); + fclose (fp); + return 0; +} + +void nntp_cache_expand (char *dst, const char *src) +{ + snprintf (dst, _POSIX_PATH_MAX, "%s/%s", NewsCacheDir, src); + mutt_expand_path (dst, _POSIX_PATH_MAX); +} + +/* Loads $news_cache_dir/.index into memory, loads newsserver data + * and newsgroup cache names */ +static int nntp_parse_cacheindex (NNTP_SERVER *news) +{ + struct stat st; + char buf[HUGE_STRING], *cp; + char dir[_POSIX_PATH_MAX], file[_POSIX_PATH_MAX]; + FILE *index; + NNTP_DATA *data; + int l, m, t; + + /* check is server name defined or not */ + if (!news || !news->conn || !news->conn->account.host) + return -1; + unset_option (OPTNEWSCACHE); + if (!NewsCacheDir || !*NewsCacheDir) + return 0; + + strfcpy (dir, NewsCacheDir, sizeof (dir)); + mutt_expand_path (dir, sizeof(dir)); + + if (lstat (dir, &st) || (st.st_mode & S_IFDIR) == 0) + { + snprintf (buf, sizeof(buf), _("Directory %s not exist. Create it?"), dir); + if (mutt_yesorno (buf, M_YES) != M_YES || mkdir (dir, (S_IRWXU+S_IRWXG+ + S_IRWXO))) + { + mutt_error _("Cache directory not created!"); + return -1; + } + mutt_clear_error(); + } + + set_option (OPTNEWSCACHE); + + FREE (&news->cache); + snprintf (buf, sizeof(buf), "%s/.index", dir); + if (!(index = safe_fopen (buf, "a+"))) + return 0; + rewind (index); + while (fgets (buf, sizeof(buf), index)) + { + buf[strlen(buf) - 1] = 0; /* strip ending '\n' */ + if (!mutt_strncmp (buf, "#: ", 3) && + !mutt_strcasecmp (buf+3, news->conn->account.host)) + break; + } + while (fgets (buf, sizeof(buf), index)) + { + cp = buf; + while (*cp && *cp != ' ') cp++; + if (!*cp) continue; + cp[0] = 0; + if (!mutt_strcmp (buf, "#:")) + break; + sscanf (cp + 1, "%s %d %d", file, &l, &m); + if (!mutt_strcmp (buf, "ALL")) + { + news->cache = safe_strdup (file); + news->newgroups_time = m; + } + else if (news->newsgroups) + { + if ((data = (NNTP_DATA *)hash_find (news->newsgroups, buf)) == NULL) + { + data = (NNTP_DATA *) safe_calloc (1, sizeof (NNTP_DATA) + strlen (buf) + 1); + data->group = (char *) data + sizeof (NNTP_DATA); + strcpy(data->group, buf); + data->nserv = news; + data->deleted = 1; + if (news->newsgroups->nelem < news->newsgroups->curnelem * 2) + news->newsgroups = hash_resize (news->newsgroups, news->newsgroups->nelem * 2, 0); + hash_insert (news->newsgroups, data->group, data, 0); + nntp_add_to_list (news, data); + } + data->cache = safe_strdup (file); + t = 0; + if (!data->firstMessage || data->lastMessage < m) + t = 1; + if (!data->firstMessage) + data->firstMessage = l; + if (data->lastMessage < m) + data->lastMessage = m; + data->lastCached = m; + if (t || !data->unread) + mutt_newsgroup_stat (data); + } + } + fclose (index); + return 0; +} + +const char * +nntp_format_str (char *dest, size_t destlen, 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]; + + switch (op) + { + case 's': + strncpy (fn, NewsServer, sizeof(fn) - 1); + mutt_strlower (fn); + snprintf (tmp, sizeof (tmp), "%%%ss", fmt); + snprintf (dest, destlen, tmp, fn); + break; + } + return (src); +} + +/* nntp_parse_url: given an NNPT URL, return host, port, + * username, password and newsgroup will recognise. */ +int nntp_parse_url (const char *server, ACCOUNT *acct, + char *group, size_t group_len) +{ + ciss_url_t url; + char *c; + int ret = -1; + + /* Defaults */ + acct->flags = 0; + acct->port = NNTP_PORT; + acct->type = M_ACCT_TYPE_NNTP; + + c = safe_strdup (server); + url_parse_ciss (&url, c); + + if (url.scheme == U_NNTP || url.scheme == U_NNTPS) + { + if (url.scheme == U_NNTPS) + { + acct->flags |= M_ACCT_SSL; + acct->port = NNTP_SSL_PORT; + } + + *group = '\0'; + if (url.path) + strfcpy (group, url.path, group_len); + + ret = mutt_account_fromurl (acct, &url); + } + + FREE (&c); + return ret; +} + +void nntp_expand_path (char *line, size_t len, ACCOUNT *acct) +{ + ciss_url_t url; + + url.path = safe_strdup (line); + mutt_account_tourl (acct, &url); + url_ciss_tostring (&url, line, len, 0); + FREE (&url.path); +} + +/* + * Automatically loads a newsrc into memory, if necessary. + * Checks the size/mtime of a newsrc file, if it doesn't match, load + * again. Hmm, if a system has broken mtimes, this might mean the file + * is reloaded every time, which we'd have to fix. + * + * a newsrc file is a line per newsgroup, with the newsgroup, then a + * ':' denoting subscribed or '!' denoting unsubscribed, then a + * comma separated list of article numbers and ranges. + */ +NNTP_SERVER *mutt_select_newsserver (char *server) +{ + char file[_POSIX_PATH_MAX]; + char *buf, *p; + LIST *list; + ACCOUNT acct; + NNTP_SERVER *serv; + CONNECTION *conn; + + if (!server || !*server) + { + mutt_error _("No newsserver defined!"); + return NULL; + } + + buf = p = safe_calloc (strlen (server) + 10, sizeof (char)); + if (url_check_scheme (server) == U_UNKNOWN) + { + strcpy (buf, "news://"); + p = strchr (buf, '\0'); + } + strcpy (p, server); + + if ((nntp_parse_url (buf, &acct, file, sizeof (file))) < 0 || *file) + { + FREE (&buf); + mutt_error (_("%s is an invalid newsserver specification!"), server); + return NULL; + } + FREE (&buf); + + conn = mutt_conn_find (NULL, &acct); + if (!conn) + return NULL; + + mutt_FormatString (file, sizeof (file), 0, NONULL (NewsRc), nntp_format_str, 0, 0); + mutt_expand_path (file, sizeof (file)); + + serv = (NNTP_SERVER *)conn->data; + if (serv) + { + struct stat sb; + + /* externally modified? */ + if (serv->stat != stat (file, &sb) || (!serv->stat && + (serv->size != sb.st_size || serv->mtime != sb.st_mtime))) + { + for (list = serv->list; list; list = list->next) + { + NNTP_DATA *data = (NNTP_DATA *) list->data; + + if (data) + { + data->subscribed = 0; + data->rc = 0; + data->num = 0; + } + } + slurp_newsrc (serv); + nntp_clear_cacheindex (serv); + } + + if (serv->status == NNTP_BYE) + serv->status = NNTP_NONE; + nntp_check_newgroups (serv, 0); + return serv; + } + + /* New newsserver */ + serv = safe_calloc (1, sizeof (NNTP_SERVER)); + serv->conn = conn; + serv->newsrc = safe_strdup (file); + serv->newsgroups = hash_create (1009, 0); + slurp_newsrc (serv); /* load .newsrc */ + nntp_parse_cacheindex (serv); /* load .index */ + if (option (OPTNEWSCACHE) && serv->cache && nntp_get_cache_all (serv) >= 0) + nntp_check_newgroups (serv, 1); + else if (nntp_get_active (serv) < 0) + { + hash_destroy (&serv->newsgroups, nntp_delete_data); + for (list = serv->list; list; list = list->next) + list->data = NULL; + mutt_free_list (&serv->list); + FREE (&serv->newsrc); + FREE (&serv->cache); + FREE (&serv); + return NULL; + } + nntp_clear_cacheindex (serv); + conn->data = (void *)serv; + + return serv; +} + +/* + * full status flags are not supported by nntp, but we can fake some + * of them. This is how: + * Read = a read message number is in the .newsrc + * New = a message is new since we last read this newsgroup + * Old = anything else + * So, Read is marked as such in the newsrc, old is anything that is + * "skipped" in the newsrc, and new is anything not in the newsrc nor + * in the cache. By skipped, I mean before the last unread message + */ +void nntp_get_status (CONTEXT *ctx, HEADER *h, char *group, int article) +{ + NNTP_DATA *data = (NNTP_DATA *) ctx->data; + int x; + + if (group) + data = (NNTP_DATA *) hash_find (data->nserv->newsgroups, group); + + if (!data) + { +#ifdef DEBUG + if (group) + dprint (3, (debugfile, "newsgroup %s not found\n", group)); +#endif + return; + } + + for (x = 0; x < data->num; x++) + { + if ((article >= data->entries[x].first) && + (article <= data->entries[x].last)) + { + /* we cannot use mutt_set_flag() because mx_update_context() + didn't called yet */ + h->read = 1; + return; + } + } + /* If article was not cached yet, it is new! :) */ + if (!data->cache || article > data->lastCached) + return; + /* Old articles are articles which aren't read but an article after them + * has been cached */ + if (option (OPTMARKOLD)) + h->old = 1; +} + +void mutt_newsgroup_stat (NNTP_DATA *data) +{ + int i; + unsigned int first, last; + + data->unread = 0; + if (data->lastMessage == 0 || data->firstMessage > data->lastMessage) + return; + + data->unread = data->lastMessage - data->firstMessage + 1; + for (i = 0; i < data->num; i++) + { + first = data->entries[i].first; + if (first < data->firstMessage) + first = data->firstMessage; + last = data->entries[i].last; + if (last > data->lastMessage) + last = data->lastMessage; + if (first <= last) + data->unread -= last - first + 1; + } +} + +static int puti (char *line, int num) +{ + char *p, s[32]; + + for (p = s; num; ) + { + *p++ = '0' + num % 10; + num /= 10; + } + while (p > s) + *line++ = *--p, num++; + *line = '\0'; + return num; +} + +static void nntp_create_newsrc_line (NNTP_DATA *data, char **buf, char **pline, size_t *buflen) +{ + char *line = *pline; + size_t len = *buflen - (*pline - *buf); + int x, i; + + if (len < LONG_STRING * 10) + { + len += *buflen; + *buflen *= 2; + line = *buf; + safe_realloc (buf, *buflen); + line = *buf + (*pline - line); + } + strcpy (line, data->group); + len -= strlen (line) + 1; + line += strlen (line); + *line++ = data->subscribed ? ':' : '!'; + *line++ = ' '; + *line = '\0'; + + for (x = 0; x < data->num; x++) + { + if (len < LONG_STRING) + { + len += *buflen; + *buflen *= 2; + *pline = line; + line = *buf; + safe_realloc (buf, *buflen); + line = *buf + (*pline - line); + } + if (x) + { + *line++ = ','; + len--; + } + +#if 0 + if (data->entries[x].first == data->entries[x].last) + snprintf (line, len, "%d%n", data->entries[x].first, &i); + else + snprintf (line, len, "%d-%d%n", + data->entries[x].first, data->entries[x].last, &i); + len -= i; + line += i; +#else + i = puti (line, data->entries[x].first); + line +=i; len -= i; + if (data->entries[x].first != data->entries[x].last) + { + *line++ = '-'; + len--; + i = puti (line, data->entries[x].last); + line +=i; len -= i; + } +#endif + } + *line++ = '\n'; + *line = '\0'; + *pline = line; +} + +void newsrc_gen_entries (CONTEXT *ctx) +{ + NNTP_DATA *data = (NNTP_DATA *)ctx->data; + int series, x; + unsigned int last = 0, first = 1; + int save_sort = SORT_ORDER; + + if (Sort != SORT_ORDER) + { + save_sort = Sort; + Sort = SORT_ORDER; + mutt_sort_headers (ctx, 0); + } + + if (!data->max) + { + data->entries = safe_calloc (5, sizeof (NEWSRC_ENTRY)); + data->max = 5; + } + + /* + * Set up to fake initial sequence from 1 to the article before the + * first article in our list + */ + data->num = 0; + series = 1; + + for (x = 0; x < ctx->msgcount; x++) + { + if (series) /* search for first unread */ + { + /* + * We don't actually check sequential order, since we mark + * "missing" entries as read/deleted + */ + last = ctx->hdrs[x]->article_num; + if (last >= data->firstMessage && !ctx->hdrs[x]->deleted && + !ctx->hdrs[x]->read) + { + if (data->num >= data->max) + { + data->max = data->max * 2; + safe_realloc (&data->entries, + data->max * sizeof (NEWSRC_ENTRY)); + } + data->entries[data->num].first = first; + data->entries[data->num].last = last - 1; + data->num++; + series = 0; + } + } + else /* search for first read */ + { + if (ctx->hdrs[x]->deleted || ctx->hdrs[x]->read) + { + first = last + 1; + series = 1; + } + last = ctx->hdrs[x]->article_num; + } + } + if (series && first <= data->lastLoaded) + { + if (data->num >= data->max) + { + data->max = data->max * 2; + safe_realloc (&data->entries, + data->max * sizeof (NEWSRC_ENTRY)); + } + data->entries[data->num].first = first; + data->entries[data->num].last = data->lastLoaded; + data->num++; + } + + if (save_sort != Sort) + { + Sort = save_sort; + mutt_sort_headers (ctx, 0); + } +} + +static int mutt_update_list_file (char *filename, char *section, + char *key, char *line) +{ + FILE *ifp; + FILE *ofp; + char buf[HUGE_STRING]; + char tmpfile[_POSIX_PATH_MAX]; + char *c; + int ext = 0, done = 0, r = 0; + + /* if file not exist, create it */ + if ((ifp = safe_fopen (filename, "a"))) + fclose (ifp); + dprint (1, (debugfile, "Opening %s\n", filename)); + if (!(ifp = safe_fopen (filename, "r"))) + { + mutt_error (_("Unable to open %s for reading"), filename); + return -1; + } + if (mx_lock_file (filename, fileno (ifp), 0, 0, 1)) + { + fclose (ifp); + mutt_error (_("Unable to lock %s"), filename); + return -1; + } + snprintf (tmpfile, sizeof(tmpfile), "%s.tmp", filename); + dprint (1, (debugfile, "Opening %s\n", tmpfile)); + if (!(ofp = fopen (tmpfile, "w"))) + { + fclose (ifp); + mutt_error (_("Unable to open %s for writing"), tmpfile); + return -1; + } + + if (section) + { + while (r != EOF && !done && fgets (buf, sizeof (buf), ifp)) + { + r = fputs (buf, ofp); + c = buf; + while (*c && *c != '\n') c++; + c[0] = 0; /* strip EOL */ + if (!strncmp (buf, "#: ", 3) && !mutt_strcasecmp (buf+3, section)) + done++; + } + if (r != EOF && !done) + { + snprintf (buf, sizeof(buf), "#: %s\n", section); + r = fputs (buf, ofp); + } + done = 0; + } + + while (r != EOF && fgets (buf, sizeof (buf), ifp)) + { + if (ext) + { + c = buf; + while (*c && (*c != '\r') && (*c != '\n')) c++; + c--; + if (*c != '\\') ext = 0; + } + else if ((section && !strncmp (buf, "#: ", 3))) + { + if (!done && line) + { + fputs (line, ofp); + fputc ('\n', ofp); + } + r = fputs (buf, ofp); + done++; + break; + } + else if (key && !strncmp (buf, key, strlen(key)) && + (!*key || buf[strlen(key)] == ' ')) + { + c = buf; + ext = 0; + while (*c && (*c != '\r') && (*c != '\n')) c++; + c--; + if (*c == '\\') ext = 1; + if (!done && line) + { + r = fputs (line, ofp); + if (*key) + r = fputc ('\n', ofp); + done++; + } + } + else + { + r = fputs (buf, ofp); + } + } + + while (r != EOF && fgets (buf, sizeof (buf), ifp)) + r = fputs (buf, ofp); + + /* If there wasn't a line to replace, put it on the end of the file */ + if (r != EOF && !done && line) + { + fputs (line, ofp); + r = fputc ('\n', ofp); + } + mx_unlock_file (filename, fileno (ifp), 0); + fclose (ofp); + fclose (ifp); + if (r == EOF) + { + unlink (tmpfile); + mutt_error (_("Can't write %s"), tmpfile); + return -1; + } + if (rename (tmpfile, filename) < 0) + { + unlink (tmpfile); + mutt_error (_("Can't rename %s to %s"), tmpfile, filename); + return -1; + } + return 0; +} + +int mutt_newsrc_update (NNTP_SERVER *news) +{ + char *buf, *line; + NNTP_DATA *data; + LIST *tmp; + int r = -1; + size_t len, llen; + + if (!news) + return -1; + llen = len = 10 * LONG_STRING; + line = buf = safe_calloc (1, len); + /* we will generate full newsrc here */ + for (tmp = news->list; tmp; tmp = tmp->next) + { + data = (NNTP_DATA *) tmp->data; + if (!data || !data->rc) + continue; + nntp_create_newsrc_line (data, &buf, &line, &llen); + if (*line) + dprint (2, (debugfile, "Added to newsrc: %s\n", line)); + line += strlen (line); + } + /* newrc being fully rewritten */ + if (news->newsrc && + (r = mutt_update_list_file (news->newsrc, NULL, "", buf)) == 0) + { + struct stat st; + + stat (news->newsrc, &st); + news->size = st.st_size; + news->mtime = st.st_mtime; + } + FREE (&buf); + return r; +} + +static FILE *mutt_mkname (char *s) +{ + char buf[_POSIX_PATH_MAX], *pc; + int fd; + FILE *fp; + + nntp_cache_expand (buf, s); + if ((fp = safe_fopen (buf, "w"))) + return fp; + + nntp_cache_expand (buf, "cache-XXXXXX"); + pc = buf + strlen (buf) - 12; /* positioning to "cache-XXXXXX" */ + if ((fd = mkstemp (buf)) == -1) + return NULL; + strcpy (s, pc); /* generated name */ + return fdopen (fd, "w"); +} + +/* Updates info into .index file: ALL or about selected newsgroup */ +static int nntp_update_cacheindex (NNTP_SERVER *serv, NNTP_DATA *data) +{ + char buf[LONG_STRING], *key = "ALL"; + char file[_POSIX_PATH_MAX]; + + if (!serv || !serv->conn || !serv->conn->account.host) + return -1; + + if (data && data->group) + { + key = data->group; + snprintf (buf, sizeof (buf), "%s %s %d %d", key, data->cache, + data->firstMessage, data->lastLoaded); + } + else + { + strfcpy (file, serv->cache, sizeof (file)); + snprintf (buf, sizeof (buf), "ALL %s 0 %d", file, (int)serv->newgroups_time); + } + nntp_cache_expand (file, ".index"); + return mutt_update_list_file (file, serv->conn->account.host, key, buf); +} + +/* Remove cache files of unsubscribed newsgroups */ +void nntp_clear_cacheindex (NNTP_SERVER *news) +{ + NNTP_DATA *data; + LIST *tmp; + + if (option (OPTSAVEUNSUB) || !news) + return; + + for (tmp = news->list; tmp; tmp = tmp->next) + { + data = (NNTP_DATA *) tmp->data; + if (!data || data->subscribed || !data->cache) + continue; + nntp_delete_cache (data); + dprint (2, (debugfile, "Removed from .index: %s\n", data->group)); + } + return; +} + +int nntp_save_cache_index (NNTP_SERVER *news) +{ + char buf[HUGE_STRING]; + char file[_POSIX_PATH_MAX]; + NNTP_DATA *d; + FILE *f; + LIST *l; + + if (!news || !news->newsgroups) + return -1; + if (!option (OPTNEWSCACHE)) + return 0; + + if (news->cache) + { + nntp_cache_expand (file, news->cache); + unlink (file); + f = safe_fopen (file, "w"); + } + else + { + strfcpy (buf, news->conn->account.host, sizeof(buf)); + f = mutt_mkname (buf); + news->cache = safe_strdup (buf); + nntp_cache_expand (file, buf); + } + if (!f) + return -1; + + for (l = news->list; l; l = l->next) + { + if ((d = (NNTP_DATA *)l->data) && !d->deleted) + { + if (d->desc) + snprintf (buf, sizeof(buf), "%s %d %d %c %s\n", d->group, + d->lastMessage, d->firstMessage, d->allowed ? 'y' : 'n', + d->desc); + else + snprintf (buf, sizeof(buf), "%s %d %d %c\n", d->group, + d->lastMessage, d->firstMessage, d->allowed ? 'y' : 'n'); + if (fputs (buf, f) == EOF) + { + fclose (f); + unlink (file); + return -1; + } + } + } + fclose (f); + + if (nntp_update_cacheindex (news, NULL)) + { + unlink (file); + return -1; + } + return 0; +} + +int nntp_save_cache_group (CONTEXT *ctx) +{ + char buf[HUGE_STRING], addr[STRING]; + char file[_POSIX_PATH_MAX]; + FILE *f; + HEADER *h; + struct tm *tm; + int i = 0, save = SORT_ORDER; + int prev = 0; + + if (!option (OPTNEWSCACHE)) + return 0; + if (!ctx || !ctx->data || ctx->magic != M_NNTP) + return -1; + + if (((NNTP_DATA *)ctx->data)->cache) + { + nntp_cache_expand (file, ((NNTP_DATA *)ctx->data)->cache); + unlink (file); + f = safe_fopen (file, "w"); + } + else + { + snprintf (buf, sizeof(buf), "%s-%s", + ((NNTP_DATA *)ctx->data)->nserv->conn->account.host, + ((NNTP_DATA *)ctx->data)->group); + f = mutt_mkname (buf); + ((NNTP_DATA *)ctx->data)->cache = safe_strdup (buf); + nntp_cache_expand (file, buf); + } + if (!f) + return -1; + + if (Sort != SORT_ORDER) + { + save = Sort; + Sort = SORT_ORDER; + mutt_sort_headers (ctx, 0); + } + + /* Save only $nntp_context messages... */ + ((NNTP_DATA *)ctx->data)->lastCached = 0; + if (NntpContext && ctx->msgcount > NntpContext) + i = ctx->msgcount - NntpContext; + for (; i < ctx->msgcount; i++) + { + if (!ctx->hdrs[i]->deleted && ctx->hdrs[i]->article_num != prev) + { + h = ctx->hdrs[i]; + addr[0] = 0; + rfc822_write_address (addr, sizeof(addr), h->env->from, 0); + tm = gmtime (&h->date_sent); + snprintf (buf, sizeof(buf), + "%d\t%s\t%s\t%d %s %d %02d:%02d:%02d GMT\t%s\t", + h->article_num, h->env->subject, addr, tm->tm_mday, + Months[tm->tm_mon], tm->tm_year+1900, tm->tm_hour, tm->tm_min, + tm->tm_sec, h->env->message_id); + fputs (buf, f); + if (h->env->references) + mutt_write_references (h->env->references, f, 10); + snprintf (buf, sizeof(buf), "\t%ld\t%d\tXref: %s\n", (long int) h->content->length, + (int) h->lines, NONULL(h->env->xref)); + if (fputs (buf, f) == EOF) + { + fclose (f); + unlink (file); + return -1; + } + } + prev = ctx->hdrs[i]->article_num; + } + + if (save != Sort) + { + Sort = save; + mutt_sort_headers (ctx, 0); + } + fclose (f); + + if (nntp_update_cacheindex (((NNTP_DATA *)ctx->data)->nserv, + (NNTP_DATA *)ctx->data)) + { + unlink (file); + return -1; + } + ((NNTP_DATA *)ctx->data)->lastCached = ((NNTP_DATA *)ctx->data)->lastLoaded; + return 0; +} + +void nntp_delete_cache (NNTP_DATA *data) +{ + char buf[_POSIX_PATH_MAX]; + + if (!option (OPTNEWSCACHE) || !data || !data->cache || !data->nserv) + return; + + nntp_cache_expand (buf, data->cache); + unlink (buf); + FREE (&data->cache); + data->lastCached = 0; + nntp_cache_expand (buf, ".index"); + mutt_update_list_file (buf, data->nserv->conn->account.host, data->group, NULL); +} + +NNTP_DATA *mutt_newsgroup_subscribe (NNTP_SERVER *news, char *group) +{ + NNTP_DATA *data; + + if (!news || !news->newsgroups || !group || !*group) + return NULL; + if (!(data = (NNTP_DATA *)hash_find (news->newsgroups, group))) + { + data = (NNTP_DATA *) safe_calloc (1, sizeof (NNTP_DATA) + strlen (group) + 1); + data->group = (char *) data + sizeof (NNTP_DATA); + strcpy (data->group, group); + data->nserv = news; + data->deleted = 1; + if (news->newsgroups->nelem < news->newsgroups->curnelem * 2) + news->newsgroups = hash_resize (news->newsgroups, news->newsgroups->nelem * 2, 0); + hash_insert (news->newsgroups, data->group, data, 0); + nntp_add_to_list (news, data); + } + if (!data->subscribed) + { + data->subscribed = 1; + data->rc = 1; + } + return data; +} + +NNTP_DATA *mutt_newsgroup_unsubscribe (NNTP_SERVER *news, char *group) +{ + NNTP_DATA *data; + + if (!news || !news->newsgroups || !group || !*group || + !(data = (NNTP_DATA *)hash_find (news->newsgroups, group))) + return NULL; + if (data->subscribed) + { + data->subscribed = 0; + if (!option (OPTSAVEUNSUB)) + data->rc = 0; + } + return data; +} + +NNTP_DATA *mutt_newsgroup_catchup (NNTP_SERVER *news, char *group) +{ + NNTP_DATA *data; + + if (!news || !news->newsgroups || !group || !*group || + !(data = (NNTP_DATA *)hash_find (news->newsgroups, group))) + return NULL; + if (!data->max) + { + data->entries = safe_calloc (5, sizeof (NEWSRC_ENTRY)); + data->max = 5; + } + data->num = 1; + data->entries[0].first = 1; + data->unread = 0; + data->entries[0].last = data->lastMessage; + if (Context && Context->data == data) + { + int x; + + for (x = 0; x < Context->msgcount; x++) + mutt_set_flag (Context, Context->hdrs[x], M_READ, 1); + } + return data; +} + +NNTP_DATA *mutt_newsgroup_uncatchup (NNTP_SERVER *news, char *group) +{ + NNTP_DATA *data; + + if (!news || !news->newsgroups || !group || !*group || + !(data = (NNTP_DATA *)hash_find (news->newsgroups, group))) + return NULL; + if (!data->max) + { + data->entries = safe_calloc (5, sizeof (NEWSRC_ENTRY)); + data->max = 5; + } + data->num = 1; + data->entries[0].first = 1; + data->entries[0].last = data->firstMessage - 1; + if (Context && Context->data == data) + { + int x; + + data->unread = Context->msgcount; + for (x = 0; x < Context->msgcount; x++) + mutt_set_flag (Context, Context->hdrs[x], M_READ, 0); + } + else + data->unread = data->lastMessage - data->entries[0].last; + return data; +} + +/* this routine gives the first newsgroup with new messages */ +void nntp_buffy (char *s) +{ + LIST *list; + + for (list = CurrentNewsSrv->list; list; list = list->next) + { + NNTP_DATA *data = (NNTP_DATA *) list->data; + + if (data && data->subscribed && data->unread) + { + if (Context && Context->magic == M_NNTP && + !mutt_strcmp (data->group, ((NNTP_DATA *) Context->data)->group)) + { + unsigned int i, unread = 0; + + for (i = 0; i < Context->msgcount; i++) + if (!Context->hdrs[i]->read && !Context->hdrs[i]->deleted) + unread++; + if (!unread) + continue; + } + strcpy (s, data->group); + break; + } + } +} diff -udprP mutt-1.5.20.orig/nntp.c mutt-1.5.20/nntp.c --- mutt-1.5.20.orig/nntp.c 1970-01-01 03:00:00.000000000 +0300 +++ mutt-1.5.20/nntp.c 2009-06-15 21:05:24.000000000 +0300 @@ -0,0 +1,1588 @@ +/* + * Copyright (C) 1998 Brandon Long + * Copyright (C) 1999 Andrej Gritsenko + * Copyright (C) 2000-2007 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 "nntp.h" + +#ifdef HAVE_PGP +#include "pgp.h" +#endif + +#ifdef HAVE_SMIME +#include "smime.h" +#endif + +#include +#include +#include +#include + +static unsigned int _checked = 0; + +#ifdef DEBUG +static void nntp_error (const char *where, const char *msg) +{ + dprint (1, (debugfile, "nntp_error(): unexpected response in %s: %s\n", where, msg)); +} +#endif /* DEBUG */ + +static int nntp_auth (NNTP_SERVER *serv) +{ + CONNECTION *conn = serv->conn; + char buf[STRING]; + unsigned char flags = conn->account.flags; + + if (mutt_account_getuser (&conn->account) || !conn->account.user[0] || + mutt_account_getpass (&conn->account) || !conn->account.pass[0]) + { + conn->account.flags = flags; + return -2; + } + + mutt_message _("Logging in..."); + + snprintf (buf, sizeof (buf), "AUTHINFO USER %s\r\n", conn->account.user); + mutt_socket_write (conn, buf); + if (mutt_socket_readln (buf, sizeof (buf), conn) < 0) + { + conn->account.flags = flags; + return -1; + } + +#ifdef DEBUG + /* don't print the password unless we're at the ungodly debugging level */ + if (debuglevel < M_SOCK_LOG_FULL) + dprint (M_SOCK_LOG_CMD, (debugfile, "> AUTHINFO PASS *\n")); +#endif + snprintf (buf, sizeof (buf), "AUTHINFO PASS %s\r\n", conn->account.pass); + mutt_socket_write_d (conn, buf, -1, M_SOCK_LOG_FULL); + if (mutt_socket_readln (buf, sizeof (buf), conn) < 0) + { + conn->account.flags = flags; + return -1; + } + + if (mutt_strncmp ("281", buf, 3)) + { + conn->account.flags = flags; + mutt_error _("Login failed."); + sleep (2); + return -3; + } + + return 0; +} + +static int nntp_connect_error (NNTP_SERVER *serv) +{ + serv->status = NNTP_NONE; + mutt_socket_close (serv->conn); + mutt_error _("Server closed connection!"); + sleep (2); + return -1; +} + +static int nntp_connect_and_auth (NNTP_SERVER *serv) +{ + CONNECTION *conn = serv->conn; + char buf[STRING]; + int rc; + + serv->status = NNTP_NONE; + + if (mutt_socket_open (conn) < 0) + return -1; + + if (mutt_socket_readln (buf, sizeof (buf), conn) < 0) + return nntp_connect_error (serv); + + if (!mutt_strncmp ("200", buf, 3)) + mutt_message (_("Connected to %s. Posting ok."), conn->account.host); + else if (!mutt_strncmp ("201", buf, 3)) + mutt_message (_("Connected to %s. Posting NOT ok."), conn->account.host); + else + { + mutt_socket_close (conn); + mutt_remove_trailing_ws (buf); + mutt_error ("%s", buf); + sleep (2); + return -1; + } + + sleep (1); + + /* Tell INN to switch to mode reader if it isn't so. Ignore all + returned codes and messages. */ + mutt_socket_write (conn, "MODE READER\r\n"); + if (mutt_socket_readln (buf, sizeof (buf), conn) < 0) + return nntp_connect_error (serv); + + mutt_socket_write (conn, "STAT\r\n"); + if (mutt_socket_readln (buf, sizeof (buf), conn) < 0) + return nntp_connect_error (serv); + + if (!(conn->account.flags & M_ACCT_USER) && mutt_strncmp ("480", buf, 3)) + { + serv->status = NNTP_OK; + return 0; + } + + rc = nntp_auth (serv); + if (rc == -1) + return nntp_connect_error (serv); + if (rc == -2) + { + mutt_socket_close (conn); + serv->status = NNTP_BYE; + return -1; + } + if (rc < 0) + { + mutt_socket_close (conn); + mutt_error _("Login failed."); + sleep (2); + return -1; + } + serv->status = NNTP_OK; + return 0; +} + +static int nntp_attempt_features (NNTP_SERVER *serv) +{ + char buf[LONG_STRING]; + CONNECTION *conn = serv->conn; + + mutt_socket_write (conn, "XOVER\r\n"); + if (mutt_socket_readln (buf, sizeof (buf), conn) < 0) + return nntp_connect_error (serv); + if (mutt_strncmp ("500", buf, 3)) + serv->hasXOVER = 1; + + mutt_socket_write (conn, "XPAT\r\n"); + if (mutt_socket_readln (buf, sizeof (buf), conn) < 0) + return nntp_connect_error (serv); + if (mutt_strncmp ("500", buf, 3)) + serv->hasXPAT = 1; + + mutt_socket_write (conn, "LISTGROUP\r\n"); + if (mutt_socket_readln (buf, sizeof (buf), conn) < 0) + return nntp_connect_error (serv); + if (mutt_strncmp ("500", buf, 3)) + serv->hasLISTGROUP = 1; + + mutt_socket_write (conn, "XGTITLE +\r\n"); + if (mutt_socket_readln (buf, sizeof (buf), conn) < 0) + return nntp_connect_error (serv); + if (mutt_strncmp ("500", buf, 3)) + serv->hasXGTITLE = 1; + + if (!mutt_strncmp ("282", buf, 3)) + { + do + { + if (mutt_socket_readln (buf, sizeof (buf), conn) < 0) + return nntp_connect_error (serv); + } while (!(buf[0] == '.' && buf[1] == '\0')); + } + + return 0; +} + +static int nntp_open_connection (NNTP_SERVER *serv) +{ + if (serv->status == NNTP_OK) + return 0; + if (serv->status == NNTP_BYE) + return -1; + if (nntp_connect_and_auth (serv) < 0) + return -1; + if (nntp_attempt_features (serv) < 0) + return -1; + return 0; +} + +static int nntp_reconnect (NNTP_SERVER *serv) +{ + char buf[SHORT_STRING]; + + mutt_socket_close (serv->conn); + + FOREVER + { + if (nntp_connect_and_auth (serv) == 0) + return 0; + + snprintf (buf, sizeof (buf), _("Connection to %s lost. Reconnect?"), + serv->conn->account.host); + if (query_quadoption (OPT_NNTPRECONNECT, buf) != M_YES) + { + serv->status = NNTP_BYE; + return -1; + } + } +} + +/* Send data from line[LONG_STRING] and receive answer to same line */ +static int mutt_nntp_query (NNTP_DATA *data, char *line, size_t linelen) +{ + char buf[LONG_STRING]; + int done = TRUE; + + if (data->nserv->status == NNTP_BYE) + return -1; + + do + { + if (*line) + { + mutt_socket_write (data->nserv->conn, line); + } + else if (data->group) + { + snprintf (buf, sizeof (buf), "GROUP %s\r\n", data->group); + mutt_socket_write (data->nserv->conn, buf); + } + + done = TRUE; + if (mutt_socket_readln (buf, sizeof (buf), data->nserv->conn) < 0) + { + if (nntp_reconnect (data->nserv) < 0) + return -1; + + if (data->group) + { + snprintf (buf, sizeof (buf), "GROUP %s\r\n", data->group); + mutt_socket_write (data->nserv->conn, buf); + if (mutt_socket_readln (buf, sizeof (buf), data->nserv->conn) < 0) + return -1; + } + if (*line) + done = FALSE; + } + else if ((!mutt_strncmp ("480", buf, 3)) && nntp_auth (data->nserv) < 0) + return -1; + } while (!done); + + strfcpy (line, buf, linelen); + return 0; +} + +/* + * This function calls funct(*line, *data) for each received line, + * funct(NULL, *data) if rewind(*data) needs, exits when fail or done. + * Returned codes: + * 0 - successful, + * 1 - correct but not performed (may be, have to be continued), + * -1 - conection lost, + * -2 - invalid command or execution error, + * -3 - error in funct(*line, *data). + */ +static int mutt_nntp_fetch (NNTP_DATA *nntp_data, char *query, char *msg, + int (*funct) (char *, void *), void *data, int tagged) +{ + char buf[LONG_STRING]; + char *inbuf, *p; + int done = FALSE; + int chunk, line; + size_t lenbuf = 0; + int ret; + + do + { + strfcpy (buf, query, sizeof (buf)); + if (mutt_nntp_query (nntp_data, buf, sizeof (buf)) < 0) + return -1; + if (buf[0] == '5') + return -2; + if (buf[0] != '2') + return 1; + + ret = 0; + line = 0; + inbuf = safe_malloc (sizeof (buf)); + + FOREVER + { + chunk = mutt_socket_readln_d (buf, sizeof (buf), nntp_data->nserv->conn, + M_SOCK_LOG_HDR); + if (chunk < 0) + break; + + p = buf; + if (!lenbuf && buf[0] == '.') + { + if (buf[1] == '\0') + { + done = TRUE; + break; + } + if (buf[1] == '.') + p++; + } + + strfcpy (inbuf + lenbuf, p, sizeof (buf)); + + if (chunk >= sizeof (buf)) + { + lenbuf += strlen (p); + } + else + { + line++; + if (msg && ReadInc && (line % ReadInc == 0)) { + if (tagged) + mutt_message (_("%s (tagged: %d) %d"), msg, tagged, line); + else + mutt_message ("%s %d", msg, line); + } + + if (ret == 0 && funct (inbuf, data) < 0) + ret = -3; + lenbuf = 0; + } + + safe_realloc (&inbuf, lenbuf + sizeof (buf)); + } + FREE (&inbuf); + funct (NULL, data); + } + while (!done); + return ret; +} + +static int nntp_read_tempfile (char *line, void *file) +{ + FILE *f = (FILE *)file; + + if (!line) + rewind (f); + else + { + fputs (line, f); + if (fputc ('\n', f) == EOF) + return -1; + } + return 0; +} + +static void nntp_parse_xref (CONTEXT *ctx, char *group, char *xref, HEADER *h) +{ + register char *p, *b; + register char *colon = NULL; + + b = p = xref; + while (*p) + { + /* skip to next word */ + b = p; + while (*b && ((*b == ' ') || (*b == '\t'))) b++; + p = b; + colon = NULL; + /* skip to end of word */ + while (*p && (*p != ' ') && (*p != '\t')) + { + if (*p == ':') + colon = p; + p++; + } + if (*p) + { + *p = '\0'; + p++; + } + if (colon) + { + *colon = '\0'; + colon++; + nntp_get_status (ctx, h, b, atoi(colon)); + if (h && h->article_num == 0 && mutt_strcmp (group, b) == 0) + h->article_num = atoi(colon); + } + } +} + +/* + * returns: + * 0 on success + * 1 if article not found + * -1 if read or write error on tempfile or socket + */ +static int nntp_read_header (CONTEXT *ctx, const char *msgid, int article_num) +{ + NNTP_DATA *nntp_data = ((NNTP_DATA *)ctx->data); + FILE *f; + char buf[LONG_STRING]; + char tempfile[_POSIX_PATH_MAX]; + int ret; + HEADER *h = ctx->hdrs[ctx->msgcount]; + + mutt_mktemp (tempfile); + if (!(f = safe_fopen (tempfile, "w+"))) + return -1; + + if (!msgid) + snprintf (buf, sizeof (buf), "HEAD %d\r\n", article_num); + else + snprintf (buf, sizeof (buf), "HEAD %s\r\n", msgid); + + ret = mutt_nntp_fetch (nntp_data, buf, NULL, nntp_read_tempfile, f, 0); + if (ret) + { +#ifdef DEBUG + if (ret != -1) + dprint(1, (debugfile, "nntp_read_header: %s\n", buf)); +#endif + fclose (f); + unlink (tempfile); + return (ret == -1 ? -1 : 1); + } + + h->article_num = article_num; + h->env = mutt_read_rfc822_header (f, h, 0, 0); + fclose (f); + unlink (tempfile); + + if (h->env->xref != NULL) + nntp_parse_xref (ctx, nntp_data->group, h->env->xref, h); + else if (h->article_num == 0 && msgid) + { + snprintf (buf, sizeof (buf), "STAT %s\r\n", msgid); + if (mutt_nntp_query (nntp_data, buf, sizeof (buf)) == 0) + h->article_num = atoi (buf + 4); + } + + return 0; +} + +static int parse_description (char *line, void *n) +{ +#define news ((NNTP_SERVER *) n) + register char *d = line; + NNTP_DATA *data; + + if (!line) + return 0; + while (*d && *d != '\t' && *d != ' ') d++; + *d = 0; + d++; + while (*d && (*d == '\t' || *d == ' ')) d++; + dprint (2, (debugfile, "group: %s, desc: %s\n", line, d)); + if ((data = (NNTP_DATA *) hash_find (news->newsgroups, line)) != NULL && + mutt_strcmp (d, data->desc)) + { + FREE (&data->desc); + data->desc = safe_strdup (d); + } + return 0; +#undef news +} + +static void nntp_get_desc (NNTP_DATA *data, char *mask, char *msg) +{ + char buf[STRING]; + + if (!option (OPTLOADDESC) || !data || !data->nserv) + return; + + /* Get newsgroup description, if we can */ + if (data->nserv->hasXGTITLE) + snprintf (buf, sizeof (buf), "XGTITLE %s\r\n", mask); + else + snprintf (buf, sizeof (buf), "LIST NEWSGROUPS %s\r\n", mask); + if (mutt_nntp_fetch (data, buf, msg, parse_description, data->nserv, 0) != 0) + { +#ifdef DEBUG + nntp_error ("nntp_get_desc()", buf); +#endif + } +} + +/* + * XOVER returns a tab separated list of: + * id|subject|from|date|Msgid|references|bytes|lines|xref + * + * This has to duplicate some of the functionality of + * mutt_read_rfc822_header(), since it replaces the call to that (albeit with + * a limited number of headers which are "parsed" by placement in the list) + */ +static int nntp_parse_xover (CONTEXT *ctx, char *buf, HEADER *hdr) +{ + NNTP_DATA *nntp_data = (NNTP_DATA *) ctx->data; + char *p, *b; + int x, done = 0; + + hdr->env = mutt_new_envelope(); + hdr->env->newsgroups = safe_strdup (nntp_data->group); + hdr->content = mutt_new_body(); + hdr->content->type = TYPETEXT; + hdr->content->subtype = safe_strdup ("plain"); + hdr->content->encoding = ENC7BIT; + hdr->content->disposition = DISPINLINE; + hdr->content->length = -1; + b = p = buf; + + for (x = 0; !done && x < 9; x++) + { + /* if from file, need to skip newline character */ + while (*p && *p != '\n' && *p != '\t') p++; + if (!*p) done++; + *p = '\0'; + p++; + switch (x) + { + case 0: + + hdr->article_num = atoi (b); + nntp_get_status (ctx, hdr, NULL, hdr->article_num); + break; + case 1: + hdr->env->subject = safe_strdup (b); + /* Now we need to do the things which would normally be done in + * mutt_read_rfc822_header() */ + if (hdr->env->subject) + { + regmatch_t pmatch[1]; + + rfc2047_decode (&hdr->env->subject); + + if (regexec (ReplyRegexp.rx, hdr->env->subject, 1, pmatch, 0) == 0) + hdr->env->real_subj = hdr->env->subject + pmatch[0].rm_eo; + else + hdr->env->real_subj = hdr->env->subject; + } + break; + case 2: + rfc822_free_address (&hdr->env->from); + hdr->env->from = rfc822_parse_adrlist (hdr->env->from, b); + rfc2047_decode_adrlist (hdr->env->from); + break; + case 3: + hdr->date_sent = mutt_parse_date (b, hdr); + hdr->received = hdr->date_sent; + break; + case 4: + FREE (&hdr->env->message_id); + hdr->env->message_id = safe_strdup (b); + break; + case 5: + mutt_free_list (&hdr->env->references); + hdr->env->references = mutt_parse_references (b, 0); + break; + case 6: + hdr->content->length = atoi (b); + break; + case 7: + hdr->lines = atoi (b); + break; + case 8: + if (!hdr->read) + FREE (&hdr->env->xref); + b = b + 6; /* skips the "Xref: " */ + hdr->env->xref = safe_strdup (b); + nntp_parse_xref (ctx, nntp_data->group, b, hdr); + } + if (!*p) + return -1; + b = p; + } + return 0; +} + +typedef struct +{ + CONTEXT *ctx; + unsigned int base; + unsigned int first; + unsigned int last; + unsigned short *messages; + char* msg; +} FETCH_CONTEXT; + +#define fc ((FETCH_CONTEXT *) c) +static int nntp_fetch_numbers (char *line, void *c) +{ + unsigned int num; + + if (!line) + return 0; + num = atoi (line); + if (num < fc->base || num > fc->last) + return 0; + fc->messages[num - fc->base] = 1; + return 0; +} + +static int add_xover_line (char *line, void *c) +{ + unsigned int num, total; + CONTEXT *ctx = fc->ctx; + NNTP_DATA *data = (NNTP_DATA *)ctx->data; + + if (!line) + return 0; + + if (ctx->msgcount >= ctx->hdrmax) + mx_alloc_memory (ctx); + ctx->hdrs[ctx->msgcount] = mutt_new_header (); + ctx->hdrs[ctx->msgcount]->index = ctx->msgcount; + + nntp_parse_xover (ctx, line, ctx->hdrs[ctx->msgcount]); + num = ctx->hdrs[ctx->msgcount]->article_num; + + if (num >= fc->first && num <= fc->last && fc->messages[num - fc->base]) + { + ctx->msgcount++; + if (num > data->lastLoaded) + data->lastLoaded = num; + num = num - fc->first + 1; + total = fc->last - fc->first + 1; + if (!ctx->quiet && fc->msg && ReadInc && (num % ReadInc == 0)) + mutt_message ("%s %d/%d", fc->msg, num, total); + } + else + mutt_free_header (&ctx->hdrs[ctx->msgcount]); /* skip it */ + + return 0; +} +#undef fc + +static int nntp_fetch_headers (CONTEXT *ctx, unsigned int first, + unsigned int last) +{ + char buf[HUGE_STRING]; + char *msg = _("Fetching message headers..."); + NNTP_DATA *nntp_data = ((NNTP_DATA *)ctx->data); + int ret; + int num; + int oldmsgcount; + unsigned int current; + FILE *f; + FETCH_CONTEXT fc; + + /* if empty group or nothing to do */ + if (!last || first > last) + return 0; + + /* fetch list of articles */ + fc.ctx = ctx; + fc.base = first; + fc.last = last; + fc.messages = safe_calloc (last - first + 1, sizeof (unsigned short)); + if (nntp_data->nserv->hasLISTGROUP) + { + mutt_message _("Fetching list of articles..."); + snprintf (buf, sizeof (buf), "LISTGROUP %s\r\n", nntp_data->group); + if (mutt_nntp_fetch (nntp_data, buf, NULL, nntp_fetch_numbers, &fc, 0) != 0) + { + mutt_error (_("LISTGROUP command failed: %s"), buf); +#ifdef DEBUG + nntp_error ("nntp_fetch_headers()", buf); +#endif + FREE (&fc.messages); + return -1; + } + } + else + { + for (num = 0; num < last - first + 1; num++) + fc.messages[num] = 1; + } + + /* CACHE: must be loaded xover cache here */ + num = nntp_data->lastCached - first + 1; + if (option (OPTNEWSCACHE) && nntp_data->cache && num > 0) + { + nntp_cache_expand (buf, nntp_data->cache); + mutt_message _("Fetching headers from cache..."); + if ((f = safe_fopen (buf, "r"))) + { + int r = 0; + + /* counting number of lines */ + while (fgets (buf, sizeof (buf), f) != NULL) + r++; + rewind (f); + while (r > num && fgets (buf, sizeof (buf), f) != NULL) + r--; + oldmsgcount = ctx->msgcount; + fc.first = first; + fc.last = first + num - 1; + fc.msg = NULL; + while (fgets (buf, sizeof (buf), f) != NULL) + add_xover_line (buf, &fc); + fclose (f); + nntp_data->lastLoaded = fc.last; + first = fc.last + 1; + if (ctx->msgcount > oldmsgcount) + mx_update_context (ctx, ctx->msgcount - oldmsgcount); + } + else + nntp_delete_cache (nntp_data); + } + num = last - first + 1; + if (num <= 0) + { + FREE (&fc.messages); + return 0; + } + + /* + * Without XOVER, we have to fetch each article header and parse + * it. With XOVER, we ask for all of them + */ + mutt_message (msg); + if (nntp_data->nserv->hasXOVER) + { + oldmsgcount = ctx->msgcount; + fc.first = first; + fc.last = last; + fc.msg = msg; + snprintf (buf, sizeof (buf), "XOVER %d-%d\r\n", first, last); + ret = mutt_nntp_fetch (nntp_data, buf, NULL, add_xover_line, &fc, 0); + if (ctx->msgcount > oldmsgcount) + mx_update_context (ctx, ctx->msgcount - oldmsgcount); + if (ret != 0) + { + mutt_error (_("XOVER command failed: %s"), buf); +#ifdef DEBUG + nntp_error ("nntp_fetch_headers()", buf); +#endif + FREE (&fc.messages); + return -1; + } + /* fetched OK */ + } + else + for (current = first; current <= last; current++) + { + HEADER *h; + + ret = current - first + 1; + mutt_message ("%s %d/%d", msg, ret, num); + + if (!fc.messages[current - fc.base]) + continue; + + if (ctx->msgcount >= ctx->hdrmax) + mx_alloc_memory (ctx); + h = ctx->hdrs[ctx->msgcount] = mutt_new_header (); + h->index = ctx->msgcount; + + ret = nntp_read_header (ctx, NULL, current); + if (ret == 0) /* Got article. Fetch next header */ + { + nntp_get_status (ctx, h, NULL, h->article_num); + ctx->msgcount++; + mx_update_context (ctx, 1); + } + else + mutt_free_header (&h); /* skip it */ + if (ret == -1) + { + FREE (&fc.messages); + return -1; + } + + if (current > nntp_data->lastLoaded) + nntp_data->lastLoaded = current; + } + FREE (&fc.messages); + nntp_data->lastLoaded = last; + mutt_clear_error (); + return 0; +} + +/* + * currently, nntp "mailbox" is "newsgroup" + */ +int nntp_open_mailbox (CONTEXT *ctx) +{ + NNTP_DATA *nntp_data; + NNTP_SERVER *serv; + char buf[HUGE_STRING]; + char server[LONG_STRING]; + int count = 0; + unsigned int first; + ACCOUNT acct; + + if (nntp_parse_url (ctx->path, &acct, buf, sizeof (buf)) < 0 || !*buf) + { + mutt_error (_("%s is an invalid newsgroup specification!"), ctx->path); + mutt_sleep (2); + return -1; + } + + server[0] = '\0'; + nntp_expand_path (server, sizeof (server), &acct); + if (!(serv = mutt_select_newsserver (server)) || serv->status != NNTP_OK) + return -1; + + CurrentNewsSrv = serv; + + /* create NNTP-specific state struct if nof found in list */ + if ((nntp_data = (NNTP_DATA *) hash_find (serv->newsgroups, buf)) == NULL) + { + nntp_data = safe_calloc (1, sizeof (NNTP_DATA) + strlen (buf) + 1); + nntp_data->group = (char *) nntp_data + sizeof (NNTP_DATA); + strcpy (nntp_data->group, buf); + hash_insert (serv->newsgroups, nntp_data->group, nntp_data, 0); + nntp_add_to_list (serv, nntp_data); + } + ctx->data = nntp_data; + ctx->mx_close = nntp_fastclose_mailbox; + nntp_data->nserv = serv; + + mutt_message (_("Selecting %s..."), nntp_data->group); + + if (!nntp_data->desc) + { + nntp_get_desc (nntp_data, nntp_data->group, NULL); + if (nntp_data->desc) + nntp_save_cache_index (serv); + } + + buf[0] = 0; + if (mutt_nntp_query (nntp_data, buf, sizeof(buf)) < 0) + { +#ifdef DEBUG + nntp_error ("nntp_open_mailbox()", buf); +#endif + return -1; + } + + if (mutt_strncmp ("211", buf, 3)) + { + LIST *l = serv->list; + + /* GROUP command failed */ + if (!mutt_strncmp ("411", buf, 3)) + { + mutt_error (_("Newsgroup %s not found on server %s"), + nntp_data->group, serv->conn->account.host); + + /* CACHE: delete cache and line from .index */ + nntp_delete_cache (nntp_data); + hash_delete (serv->newsgroups, nntp_data->group, NULL, nntp_delete_data); + while (l && l->data != (void *) nntp_data) l = l->next; + if (l) + l->data = NULL; + + sleep (2); + } + + return -1; + } + + sscanf (buf + 4, "%d %u %u %s", &count, &nntp_data->firstMessage, + &nntp_data->lastMessage, buf); + + nntp_data->deleted = 0; + + time (&serv->check_time); + + /* + * Check for max adding context. If it is greater than $nntp_context, + * strip off extra articles + */ + first = nntp_data->firstMessage; + if (NntpContext && nntp_data->lastMessage - first + 1 > NntpContext) + first = nntp_data->lastMessage - NntpContext + 1; + if (first) + nntp_data->lastLoaded = first - 1; + return nntp_fetch_headers (ctx, first, nntp_data->lastMessage); +} + +int nntp_fetch_message (MESSAGE *msg, CONTEXT *ctx, int msgno) +{ + char buf[LONG_STRING]; + char path[_POSIX_PATH_MAX]; + NNTP_CACHE *cache; + char *m = _("Fetching message..."); + int ret; + + /* see if we already have the message in our cache */ + cache = &((NNTP_DATA *) ctx->data)->acache[ctx->hdrs[msgno]->index % NNTP_CACHE_LEN]; + + /* if everything is fine, assign msg->fp and return */ + if (cache->path && cache->index == ctx->hdrs[msgno]->index && + (msg->fp = fopen (cache->path, "r"))) + return 0; + + /* clear the previous entry */ + unlink (cache->path); + free (cache->path); + + mutt_message (m); + + cache->index = ctx->hdrs[msgno]->index; + mutt_mktemp (path); + cache->path = safe_strdup (path); + if (!(msg->fp = safe_fopen (path, "w+"))) + { + FREE (&cache->path); + return -1; + } + + if (ctx->hdrs[msgno]->article_num == 0) + snprintf (buf, sizeof (buf), "ARTICLE %s\r\n", + ctx->hdrs[msgno]->env->message_id); + else + snprintf (buf, sizeof (buf), "ARTICLE %d\r\n", + ctx->hdrs[msgno]->article_num); + + ret = mutt_nntp_fetch ((NNTP_DATA *)ctx->data, buf, m, nntp_read_tempfile, + msg->fp, ctx->tagged); + if (ret == 1) + { + mutt_error (_("Article %d not found on server"), + ctx->hdrs[msgno]->article_num); + dprint (1, (debugfile, "nntp_fetch_message: %s\n", buf)); + } + + if (ret) + { + fclose (msg->fp); + unlink (path); + FREE (&cache->path); + return -1; + } + + mutt_free_envelope (&ctx->hdrs[msgno]->env); + ctx->hdrs[msgno]->env = mutt_read_rfc822_header (msg->fp, ctx->hdrs[msgno], 0, 0); + /* fix content length */ + fseek(msg->fp, 0, SEEK_END); + ctx->hdrs[msgno]->content->length = ftell (msg->fp) - + ctx->hdrs[msgno]->content->offset; + + /* this is called in mutt before the open which fetches the message, + * which is probably wrong, but we just call it again here to handle + * the problem instead of fixing it. + */ + mutt_parse_mime_message (ctx, ctx->hdrs[msgno]); + + /* These would normally be updated in mx_update_context(), but the + * full headers aren't parsed with XOVER, so the information wasn't + * available then. + */ +#if defined(HAVE_PGP) || defined(HAVE_SMIME) + ctx->hdrs[msgno]->security = crypt_query (ctx->hdrs[msgno]->content); +#endif /* HAVE_PGP || HAVE_SMIME */ + + mutt_clear_error(); + rewind (msg->fp); + + return 0; +} + +/* Post article */ +int nntp_post (const char *msg) { + char buf[LONG_STRING]; + size_t len; + FILE *f; + NNTP_DATA *nntp_data; + + if (Context && Context->magic == M_NNTP) + nntp_data = (NNTP_DATA *)Context->data; + else + { + if (!(CurrentNewsSrv = mutt_select_newsserver (NewsServer)) || + !CurrentNewsSrv->list || !CurrentNewsSrv->list->data) + { + mutt_error (_("Can't post article. No connection to news server.")); + return -1; + } + nntp_data = (NNTP_DATA *)CurrentNewsSrv->list->data; + } + + if (!(f = safe_fopen (msg, "r"))) + { + mutt_error (_("Can't post article. Unable to open %s"), msg); + return -1; + } + + strfcpy (buf, "POST\r\n", sizeof (buf)); + if (mutt_nntp_query (nntp_data, buf, sizeof (buf)) < 0) + { + mutt_error (_("Can't post article. Connection to %s lost."), + nntp_data->nserv->conn->account.host); + return -1; + } + if (buf[0] != '3') + { + mutt_error (_("Can't post article: %s"), buf); + return -1; + } + + buf[0] = '.'; + buf[1] = '\0'; + while (fgets (buf + 1, sizeof (buf) - 2, f) != NULL) + { + len = strlen (buf); + if (buf[len - 1] == '\n') + { + buf[len - 1] = '\r'; + buf[len] = '\n'; + len++; + buf[len] = '\0'; + } + if (buf[1] == '.') + mutt_socket_write_d (nntp_data->nserv->conn, buf, -1, M_SOCK_LOG_HDR); + else + mutt_socket_write_d (nntp_data->nserv->conn, buf + 1, -1, M_SOCK_LOG_HDR); + } + fclose (f); + + if (buf[strlen (buf) - 1] != '\n') + mutt_socket_write_d (nntp_data->nserv->conn, "\r\n", -1, M_SOCK_LOG_HDR); + mutt_socket_write_d (nntp_data->nserv->conn, ".\r\n", -1, M_SOCK_LOG_HDR); + if (mutt_socket_readln (buf, sizeof (buf), nntp_data->nserv->conn) < 0) + { + mutt_error (_("Can't post article. Connection to %s lost."), + nntp_data->nserv->conn->account.host); + return -1; + } + if (buf[0] != '2') + { + mutt_error (_("Can't post article: %s"), buf); + return -1; + } + + return 0; +} + +/* nntp_logout_all: close all open connections. */ +void nntp_logout_all (void) +{ + char buf[LONG_STRING]; + CONNECTION* conn; + + conn = mutt_socket_head (); + + while (conn) + { + CONNECTION *next = conn->next; + + if (conn->account.type == M_ACCT_TYPE_NNTP) + { + mutt_message (_("Closing connection to %s..."), conn->account.host); + mutt_socket_write (conn, "QUIT\r\n"); + mutt_socket_readln (buf, sizeof (buf), conn); + mutt_clear_error (); + mutt_socket_close (conn); + mutt_socket_free (conn); + } + + conn = next; + } +} + +static void nntp_free_acache (NNTP_DATA *data) +{ + int i; + + for (i = 0; i < NNTP_CACHE_LEN; i++) + { + if (data->acache[i].path) + { + unlink (data->acache[i].path); + FREE (&data->acache[i].path); + } + } +} + +void nntp_delete_data (void *p) +{ + NNTP_DATA *data = (NNTP_DATA *)p; + + if (!p) + return; + FREE (&data->entries); + FREE (&data->desc); + FREE (&data->cache); + nntp_free_acache (data); + FREE (p); +} + +int nntp_sync_mailbox (CONTEXT *ctx) +{ + NNTP_DATA *data = ctx->data; + + /* CACHE: update cache and .index files */ + if ((option (OPTSAVEUNSUB) || data->subscribed)) + nntp_save_cache_group (ctx); + nntp_free_acache (data); + + data->nserv->check_time = 0; /* next nntp_check_mailbox() will really check */ + return 0; +} + +int nntp_fastclose_mailbox (CONTEXT *ctx) +{ + NNTP_DATA *data = (NNTP_DATA *) ctx->data, *tmp; + + if (!data) + return 0; + nntp_free_acache (data); + if (!data->nserv || !data->nserv->newsgroups || !data->group) + return 0; + nntp_save_cache_index (data->nserv); + if ((tmp = hash_find (data->nserv->newsgroups, data->group)) == NULL + || tmp != data) + nntp_delete_data (data); + return 0; +} + +/* commit changes and terminate connection */ +int nntp_close_mailbox (CONTEXT *ctx) +{ + if (!ctx) + return -1; + mutt_message _("Quitting newsgroup..."); + if (ctx->data) + { + NNTP_DATA *data = (NNTP_DATA *) ctx->data; + int ret; + + if (data->nserv && data->nserv->conn && ctx->unread) + { + ret = query_quadoption (OPT_CATCHUP, _("Mark all articles read?")); + if (ret == M_YES) + mutt_newsgroup_catchup (data->nserv, data->group); + else if (ret < 0) + return -1; + } + } + nntp_sync_mailbox (ctx); + if (ctx->data && ((NNTP_DATA *)ctx->data)->nserv) + { + NNTP_SERVER *news; + + news = ((NNTP_DATA *)ctx->data)->nserv; + newsrc_gen_entries (ctx); + ((NNTP_DATA *)ctx->data)->unread = ctx->unread; + mutt_newsrc_update (news); + } + mutt_clear_error(); + return 0; +} + +/* use the GROUP command to poll for new mail */ +static int _nntp_check_mailbox (CONTEXT *ctx, NNTP_DATA *nntp_data) +{ + char buf[LONG_STRING]; + int count = 0; + + if (nntp_data->nserv->check_time + NewsPollTimeout > time (NULL)) + return 0; + + buf[0] = 0; + if (mutt_nntp_query (nntp_data, buf, sizeof (buf)) < 0) + { +#ifdef DEBUG + nntp_error ("nntp_check_mailbox()", buf); +#endif + return -1; + } + if (mutt_strncmp ("211", buf, 3)) + { + buf[0] = 0; + if (mutt_nntp_query (nntp_data, buf, sizeof (buf)) < 0) + { +#ifdef DEBUG + nntp_error ("nntp_check_mailbox()", buf); +#endif + return -1; + } + } + if (!mutt_strncmp ("211", buf, 3)) + { + int first; + int last; + + sscanf (buf + 4, "%d %d %d", &count, &first, &last); + nntp_data->firstMessage = first; + nntp_data->lastMessage = last; + if (ctx && last > nntp_data->lastLoaded) + { + nntp_fetch_headers (ctx, nntp_data->lastLoaded + 1, last); + time (&nntp_data->nserv->check_time); + return 1; + } + if (!last || (!nntp_data->rc && !nntp_data->lastCached)) + nntp_data->unread = count; + else + mutt_newsgroup_stat (nntp_data); + /* active was renumbered? */ + if (last < nntp_data->lastLoaded) + { + if (!nntp_data->max) + { + nntp_data->entries = safe_calloc (5, sizeof (NEWSRC_ENTRY)); + nntp_data->max = 5; + } + nntp_data->lastCached = 0; + nntp_data->num = 1; + nntp_data->entries[0].first = 1; + nntp_data->entries[0].last = 0; + } + } + + time (&nntp_data->nserv->check_time); + return 0; +} + +int nntp_check_mailbox (CONTEXT *ctx) +{ + return _nntp_check_mailbox (ctx, (NNTP_DATA *)ctx->data); +} + +static int add_group (char *buf, void *serv) +{ +#define s ((NNTP_SERVER *) serv) + char group[LONG_STRING], mod, desc[HUGE_STRING]; + int first, last; + NNTP_DATA *nntp_data; + static int n = 0; + + _checked = n; /* _checked have N, where N = number of groups */ + if (!buf) /* at EOF must be zerouth */ + n = 0; + + if (!s || !buf) + return 0; + + *desc = 0; + sscanf (buf, "%s %d %d %c %[^\n]", group, &last, &first, &mod, desc); + if (!group) + return 0; + if ((nntp_data = (NNTP_DATA *) hash_find (s->newsgroups, group)) == NULL) + { + n++; + nntp_data = safe_calloc (1, sizeof (NNTP_DATA) + strlen (group) + 1); + nntp_data->group = (char *) nntp_data + sizeof (NNTP_DATA); + strcpy (nntp_data->group, group); + nntp_data->nserv = s; + if (s->newsgroups->nelem < s->newsgroups->curnelem * 2) + s->newsgroups = hash_resize (s->newsgroups, s->newsgroups->nelem * 2, 0); + hash_insert (s->newsgroups, nntp_data->group, nntp_data, 0); + nntp_add_to_list (s, nntp_data); + } + nntp_data->deleted = 0; + nntp_data->firstMessage = first; + nntp_data->lastMessage = last; + if (mod == 'y') + nntp_data->allowed = 1; + else + nntp_data->allowed = 0; + if (nntp_data->desc) + FREE (&nntp_data->desc); + if (*desc) + nntp_data->desc = safe_strdup (desc); + if (nntp_data->rc || nntp_data->lastCached) + mutt_newsgroup_stat (nntp_data); + else if (nntp_data->lastMessage && + nntp_data->firstMessage <= nntp_data->lastMessage) + nntp_data->unread = nntp_data->lastMessage - nntp_data->firstMessage + 1; + else + nntp_data->unread = 0; + + return 0; +#undef s +} + +int nntp_check_newgroups (NNTP_SERVER *serv, int force) +{ + char buf[LONG_STRING]; + char msg[SHORT_STRING]; + NNTP_DATA nntp_data; + LIST *l; + LIST emp; + time_t now; + struct tm *t; + unsigned int count = 0; + unsigned int total = 0; + + if (!serv || !serv->newgroups_time) + return -1; + + if (nntp_open_connection (serv) < 0) + return -1; + + /* check subscribed groups for new news */ + if (option (OPTSHOWNEWNEWS)) + { + mutt_message _("Checking for new messages..."); + for (l = serv->list; l; l = l->next) + { + serv->check_time = 0; /* really check! */ + if (l->data && ((NNTP_DATA *) l->data)->subscribed) + _nntp_check_mailbox (NULL, (NNTP_DATA *) l->data); + } + } + else if (!force) + return 0; + + mutt_message _("Checking for new newsgroups..."); + now = serv->newgroups_time; + time (&serv->newgroups_time); + t = gmtime (&now); + snprintf (buf, sizeof (buf), "NEWGROUPS %02d%02d%02d %02d%02d%02d GMT\r\n", + (t->tm_year % 100), t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, + t->tm_sec); + nntp_data.nserv = serv; + if (Context && Context->magic == M_NNTP) + nntp_data.group = ((NNTP_DATA *)Context->data)->group; + else + nntp_data.group = NULL; + l = serv->tail; + if (mutt_nntp_fetch (&nntp_data, buf, _("Adding new newsgroups..."), + add_group, serv, 0) != 0) + { +#ifdef DEBUG + nntp_error ("nntp_check_newgroups()", buf); +#endif + return -1; + } + + strfcpy (msg, _("Loading descriptions..."), sizeof (msg)); + mutt_message (msg); + if (l) + emp.next = l->next; + else + emp.next = serv->list; + l = &emp; + while (l->next) + { + l = l->next; + ((NNTP_DATA *) l->data)->new = 1; + total++; + } + l = &emp; + while (l->next) + { + l = l->next; + nntp_get_desc ((NNTP_DATA *) l->data, ((NNTP_DATA *) l->data)->group, NULL); + count++; + if (ReadInc && (count % ReadInc == 0)) + mutt_message ("%s %d/%d", msg, count, total); + } + if (emp.next) + nntp_save_cache_index (serv); + mutt_clear_error (); + return _checked; +} + +/* Load list of all newsgroups from cache ALL */ +int nntp_get_cache_all (NNTP_SERVER *serv) +{ + char buf[HUGE_STRING]; + FILE *f; + + nntp_cache_expand (buf, serv->cache); + if ((f = safe_fopen (buf, "r"))) + { + int i = 0; + + while (fgets (buf, sizeof(buf), f) != NULL) + { + if (ReadInc && (i % ReadInc == 0)) + mutt_message (_("Loading list from cache... %d"), i); + add_group (buf, serv); + i++; + } + add_group (NULL, NULL); + fclose (f); + mutt_clear_error (); + return 0; + } + else + { + FREE (&serv->cache); + return -1; + } +} + +/* Load list of all newsgroups from active */ +int nntp_get_active (NNTP_SERVER *serv) +{ + char msg[SHORT_STRING]; + NNTP_DATA nntp_data; + LIST *tmp; + + if (nntp_open_connection (serv) < 0) + return -1; + + snprintf (msg, sizeof(msg), _("Loading list of all newsgroups on server %s..."), + serv->conn->account.host); + mutt_message (msg); + time (&serv->newgroups_time); + nntp_data.nserv = serv; + nntp_data.group = NULL; + + if (mutt_nntp_fetch (&nntp_data, "LIST\r\n", msg, add_group, serv, 0) < 0) + { +#ifdef DEBUG + nntp_error ("nntp_get_active()", "LIST\r\n"); +#endif + return -1; + } + + strfcpy (msg, _("Loading descriptions..."), sizeof (msg)); + mutt_message (msg); + nntp_get_desc (&nntp_data, "*", msg); + + for (tmp = serv->list; tmp; tmp = tmp->next) + { + NNTP_DATA *data = (NNTP_DATA *)tmp->data; + + if (data && data->deleted && !data->rc) + { + nntp_delete_cache (data); + hash_delete (serv->newsgroups, data->group, NULL, nntp_delete_data); + tmp->data = NULL; + } + } + nntp_save_cache_index (serv); + + mutt_clear_error (); + return _checked; +} + +/* + * returns -1 if error ocurred while retrieving header, + * number of articles which ones exist in context on success. + */ +int nntp_check_msgid (CONTEXT *ctx, const char *msgid) +{ + int ret; + + /* if msgid is already in context, don't reload them */ + if (hash_find (ctx->id_hash, msgid)) + return 1; + if (ctx->msgcount == ctx->hdrmax) + mx_alloc_memory (ctx); + ctx->hdrs[ctx->msgcount] = mutt_new_header (); + ctx->hdrs[ctx->msgcount]->index = ctx->msgcount; + + mutt_message (_("Fetching %s from server..."), msgid); + ret = nntp_read_header (ctx, msgid, 0); + /* since nntp_read_header() may set read flag, we must reset it */ + ctx->hdrs[ctx->msgcount]->read = 0; + if (ret != 0) + mutt_free_header (&ctx->hdrs[ctx->msgcount]); + else + { + ctx->msgcount++; + mx_update_context (ctx, 1); + ctx->changed = 1; + } + return ret; +} + +typedef struct +{ + CONTEXT *ctx; + unsigned int num; + unsigned int max; + unsigned int *child; +} CHILD_CONTEXT; + +static int check_children (char *s, void *c) +{ +#define cc ((CHILD_CONTEXT *) c) + unsigned int i, n; + + if (!s || (n = atoi (s)) == 0) + return 0; + for (i = 0; i < cc->ctx->msgcount; i++) + if (cc->ctx->hdrs[i]->article_num == n) + return 0; + if (cc->num >= cc->max) + safe_realloc (&cc->child, sizeof (unsigned int) * (cc->max += 25)); + cc->child[cc->num++] = n; + + return 0; +#undef cc +} + +int nntp_check_children (CONTEXT *ctx, const char *msgid) +{ + NNTP_DATA *nntp_data = (NNTP_DATA *)ctx->data; + char buf[STRING]; + int i, ret = 0, tmp = 0; + CHILD_CONTEXT cc; + + if (!nntp_data || !nntp_data->nserv || !nntp_data->nserv->conn || + !nntp_data->nserv->conn->account.host) + return -1; + if (nntp_data->firstMessage > nntp_data->lastLoaded) + return 0; + if (!nntp_data->nserv->hasXPAT) + { + mutt_error (_("Server %s does not support this operation!"), + nntp_data->nserv->conn->account.host); + return -1; + } + + snprintf (buf, sizeof (buf), "XPAT References %d-%d *%s*\r\n", + nntp_data->firstMessage, nntp_data->lastLoaded, msgid); + + cc.ctx = ctx; + cc.num = 0; + cc.max = 25; + cc.child = safe_malloc (sizeof (unsigned int) * 25); + if (mutt_nntp_fetch (nntp_data, buf, NULL, check_children, &cc, 0)) + { + FREE (&cc.child); + return -1; + } + /* dont try to read the xover cache. check_children() already + * made sure that we dont have the article, so we need to visit + * the server. Reading the cache at this point is also bad + * because it would duplicate messages */ + if (option (OPTNEWSCACHE)) + { + tmp++; + unset_option (OPTNEWSCACHE); + } + for (i = 0; i < cc.num; i++) + { + if ((ret = nntp_fetch_headers (ctx, cc.child[i], cc.child[i]))) + break; + if (ctx->msgcount && + ctx->hdrs[ctx->msgcount - 1]->article_num == cc.child[i]) + ctx->hdrs[ctx->msgcount - 1]->read = 0; + } + if (tmp) + set_option (OPTNEWSCACHE); + FREE (&cc.child); + return ret; +} diff -udprP mutt-1.5.20.orig/nntp.h mutt-1.5.20/nntp.h --- mutt-1.5.20.orig/nntp.h 1970-01-01 03:00:00.000000000 +0300 +++ mutt-1.5.20/nntp.h 2009-06-15 21:05:24.000000000 +0300 @@ -0,0 +1,136 @@ +/* + * Copyright (C) 1998 Brandon Long + * Copyright (C) 1999 Andrej Gritsenko + * Copyright (C) 2000-2007 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 + +#define NNTP_PORT 119 +#define NNTP_SSL_PORT 563 + +/* number of entries in the hash table */ +#define NNTP_CACHE_LEN 10 + +enum +{ + NNTP_NONE = 0, + NNTP_OK, + NNTP_BYE +}; + +typedef struct +{ + int first; + int last; +} NEWSRC_ENTRY; + +typedef struct +{ + unsigned int hasXPAT : 1; + unsigned int hasXGTITLE : 1; + unsigned int hasXOVER : 1; + unsigned int hasLISTGROUP : 1; + unsigned int status : 3; + char *newsrc; + char *cache; + int stat; + off_t size; + time_t mtime; + time_t newgroups_time; + time_t check_time; + HASH *newsgroups; + LIST *list; /* list of newsgroups */ + LIST *tail; /* last entry of list */ + CONNECTION *conn; +} NNTP_SERVER; + +typedef struct +{ + unsigned int index; + char *path; +} NNTP_CACHE; + +typedef struct +{ + NEWSRC_ENTRY *entries; + unsigned int num; /* number of used entries */ + unsigned int max; /* number of allocated entries */ + unsigned int unread; + unsigned int firstMessage; + unsigned int lastMessage; + unsigned int lastLoaded; + unsigned int lastCached; + unsigned int subscribed : 1; + unsigned int rc : 1; + unsigned int new : 1; + unsigned int allowed : 1; + unsigned int deleted : 1; + char *group; + char *desc; + char *cache; + NNTP_SERVER *nserv; + NNTP_CACHE acache[NNTP_CACHE_LEN]; +} NNTP_DATA; + +/* internal functions */ +int nntp_get_active (NNTP_SERVER *); +int nntp_get_cache_all (NNTP_SERVER *); +int nntp_save_cache_index (NNTP_SERVER *); +int nntp_check_newgroups (NNTP_SERVER *, int); +int nntp_save_cache_group (CONTEXT *); +int nntp_parse_url (const char *, ACCOUNT *, char *, size_t); +void newsrc_gen_entries (CONTEXT *); +void nntp_get_status (CONTEXT *, HEADER *, char *, int); +void mutt_newsgroup_stat (NNTP_DATA *); +void nntp_delete_cache (NNTP_DATA *); +void nntp_add_to_list (NNTP_SERVER *, NNTP_DATA *); +void nntp_cache_expand (char *, const char *); +void nntp_delete_data (void *); + +/* exposed interface */ +NNTP_SERVER *mutt_select_newsserver (char *); +NNTP_DATA *mutt_newsgroup_subscribe (NNTP_SERVER *, char *); +NNTP_DATA *mutt_newsgroup_unsubscribe (NNTP_SERVER *, char *); +NNTP_DATA *mutt_newsgroup_catchup (NNTP_SERVER *, char *); +NNTP_DATA *mutt_newsgroup_uncatchup (NNTP_SERVER *, char *); +void nntp_clear_cacheindex (NNTP_SERVER *); +int mutt_newsrc_update (NNTP_SERVER *); +int nntp_open_mailbox (CONTEXT *); +int nntp_sync_mailbox (CONTEXT *); +int nntp_check_mailbox (CONTEXT *); +int nntp_close_mailbox (CONTEXT *); +int nntp_fastclose_mailbox (CONTEXT *); +int nntp_fetch_message (MESSAGE *, CONTEXT *, int); +int nntp_post (const char *); +int nntp_check_msgid (CONTEXT *, const char *); +int nntp_check_children (CONTEXT *, const char *); +void nntp_buffy (char *); +void nntp_expand_path (char *, size_t, ACCOUNT *); +void nntp_logout_all (); +const char *nntp_format_str (char *, size_t, size_t, char, const char *, const char *, + const char *, const char *, unsigned long, format_flag); + +NNTP_SERVER *CurrentNewsSrv INITVAL (NULL); + +#endif /* _NNTP_H_ */ diff -udprP mutt-1.5.20.orig/pager.c mutt-1.5.20/pager.c --- mutt-1.5.20.orig/pager.c 2009-06-03 23:48:31.000000000 +0300 +++ mutt-1.5.20/pager.c 2009-06-15 21:05:24.000000000 +0300 @@ -1059,6 +1059,11 @@ fill_buffer (FILE *f, LOFF_T *last_pos, return b_read; } +#ifdef USE_NNTP +#include "mx.h" +#include "nntp.h" +#endif + static int format_line (struct line_t **lineInfo, int n, unsigned char *buf, int flags, ansi_attr *pa, int cnt, @@ -1512,6 +1517,16 @@ static struct mapping_t PagerHelpExtra[] { NULL, 0 } }; +#ifdef USE_NNTP +static struct mapping_t PagerNewsHelpExtra[] = { + { N_("Post"), OP_POST }, + { N_("Followup"), OP_FOLLOWUP }, + { N_("Del"), OP_DELETE }, + { N_("Next"), OP_MAIN_NEXT_UNDELETED }, + { NULL, 0 } +}; +#endif + /* This pager is actually not so simple as it once was. It now operates in @@ -1553,6 +1568,10 @@ mutt_pager (const char *banner, const ch int old_PagerIndexLines; /* some people want to resize it * while inside the pager... */ +#ifdef USE_NNTP + char *followup_to; +#endif + if (!(flags & M_SHOWCOLOR)) flags |= M_SHOWFLAT; @@ -1592,7 +1611,11 @@ mutt_pager (const char *banner, const ch if (IsHeader (extra)) { strfcpy (tmphelp, helpstr, sizeof (tmphelp)); - mutt_compile_help (buffer, sizeof (buffer), MENU_PAGER, PagerHelpExtra); + mutt_compile_help (buffer, sizeof (buffer), MENU_PAGER, +#ifdef USE_NNTP + (Context && (Context->magic == M_NNTP)) ? PagerNewsHelpExtra : +#endif + PagerHelpExtra); snprintf (helpstr, sizeof (helpstr), "%s %s", tmphelp, buffer); } if (!InHelp) @@ -2465,6 +2488,15 @@ search_next: CHECK_READONLY; CHECK_ACL(M_ACL_WRITE, "flag message"); +#ifdef USE_NNTP + if (Context->magic == M_NNTP) + { + mutt_flushinp (); + mutt_error _("Can't change 'important' flag on NNTP server."); + break; + } +#endif + mutt_set_flag (Context, extra->hdr, M_FLAG, !extra->hdr->flagged); redraw = REDRAW_STATUS | REDRAW_INDEX; if (option (OPTRESOLVE)) @@ -2498,6 +2530,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; @@ -2544,7 +2630,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 -udprP mutt-1.5.20.orig/parse.c mutt-1.5.20/parse.c --- mutt-1.5.20.orig/parse.c 2009-06-01 19:29:32.000000000 +0300 +++ mutt-1.5.20/parse.c 2009-06-15 21:05:24.000000000 +0300 @@ -89,7 +89,7 @@ char *mutt_read_rfc822_line (FILE *f, ch /* not reached */ } -static LIST *mutt_parse_references (char *s, int in_reply_to) +LIST *mutt_parse_references (char *s, int in_reply_to) { LIST *t, *lst = NULL; char *m; @@ -1067,6 +1067,17 @@ int mutt_parse_rfc822_line (ENVELOPE *e, e->from = rfc822_parse_adrlist (e->from, p); matched = 1; } +#ifdef USE_NNTP + else if (!mutt_strcasecmp (line+1, "ollowup-to")) + { + if (!e->followup_to) + { + mutt_remove_trailing_ws (p); + e->followup_to = safe_strdup (mutt_skip_whitespace (p)); + } + matched = 1; + } +#endif break; case 'i': @@ -1149,6 +1160,27 @@ int mutt_parse_rfc822_line (ENVELOPE *e, } break; +#ifdef USE_NNTP + case 'n': + if (!mutt_strcasecmp (line + 1, "ewsgroups")) + { + FREE (&e->newsgroups); + mutt_remove_trailing_ws (p); + e->newsgroups = safe_strdup (mutt_skip_whitespace (p)); + matched = 1; + } + break; +#endif + + case 'o': + /* field `Organization:' saves only for pager! */ + if (!mutt_strcasecmp (line + 1, "rganization")) + { + if (!e->organization && mutt_strcasecmp (p, "unknown")) + e->organization = safe_strdup (p); + } + break; + case 'r': if (!ascii_strcasecmp (line + 1, "eferences")) { @@ -1257,6 +1289,20 @@ int mutt_parse_rfc822_line (ENVELOPE *e, e->x_label = safe_strdup(p); matched = 1; } +#ifdef USE_NNTP + else if (!mutt_strcasecmp (line + 1, "-comment-to")) + { + if (!e->x_comment_to) + e->x_comment_to = safe_strdup (p); + matched = 1; + } + else if (!mutt_strcasecmp (line + 1, "ref")) + { + if (!e->xref) + e->xref = safe_strdup (p); + matched = 1; + } +#endif default: break; diff -udprP mutt-1.5.20.orig/pattern.c mutt-1.5.20/pattern.c --- mutt-1.5.20.orig/pattern.c 2009-06-03 23:48:31.000000000 +0300 +++ mutt-1.5.20/pattern.c 2009-06-15 21:05:24.000000000 +0300 @@ -91,6 +91,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 }, @@ -1204,6 +1207,10 @@ mutt_pattern_exec (struct pattern_t *pat } 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); @@ -1285,6 +1292,7 @@ int mutt_pattern_func (int op, char *pro 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 -udprP mutt-1.5.20.orig/po/POTFILES.in mutt-1.5.20/po/POTFILES.in --- mutt-1.5.20.orig/po/POTFILES.in 2008-11-11 21:55:47.000000000 +0200 +++ mutt-1.5.20/po/POTFILES.in 2009-06-15 21:05:24.000000000 +0300 @@ -46,6 +46,8 @@ mutt_ssl_gnutls.c mutt_tunnel.c muttlib.c mx.c +newsrc.c +nntp.c pager.c parse.c pattern.c diff -udprP mutt-1.5.20.orig/postpone.c mutt-1.5.20/postpone.c --- mutt-1.5.20.orig/postpone.c 2009-06-14 00:28:37.000000000 +0300 +++ mutt-1.5.20/postpone.c 2009-06-15 21:05:24.000000000 +0300 @@ -124,15 +124,26 @@ int mutt_num_postponed (int force) if (LastModify < st.st_mtime) { +#ifdef USE_NNTP + int optnews = option (OPTNEWS); +#endif LastModify = st.st_mtime; if (access (Postponed, R_OK | F_OK) != 0) return (PostCount = 0); +#ifdef USE_NNTP + if (optnews) + unset_option (OPTNEWS); +#endif if (mx_open_mailbox (Postponed, M_NOSORT | M_QUIET, &ctx) == NULL) PostCount = 0; else PostCount = ctx.msgcount; mx_fastclose_mailbox (&ctx); +#ifdef USE_NNTP + if (optnews) + set_option (OPTNEWS); +#endif } return (PostCount); diff -udprP mutt-1.5.20.orig/protos.h mutt-1.5.20/protos.h --- mutt-1.5.20.orig/protos.h 2009-06-13 02:38:52.000000000 +0300 +++ mutt-1.5.20/protos.h 2009-06-15 21:05:24.000000000 +0300 @@ -115,6 +115,7 @@ HASH *mutt_make_id_hash (CONTEXT *); HASH *mutt_make_subj_hash (CONTEXT *); LIST *mutt_make_references(ENVELOPE *e); +LIST *mutt_parse_references (char *, int); char *mutt_read_rfc822_line (FILE *, char *, size_t *); ENVELOPE *mutt_read_rfc822_header (FILE *, HEADER *, short, short); diff -udprP mutt-1.5.20.orig/recvattach.c mutt-1.5.20/recvattach.c --- mutt-1.5.20.orig/recvattach.c 2009-05-19 03:11:35.000000000 +0300 +++ mutt-1.5.20/recvattach.c 2009-06-15 21:05:24.000000000 +0300 @@ -1110,6 +1110,15 @@ void mutt_view_attachments (HEADER *hdr) } #endif +#ifdef USE_NNTP + if (Context->magic == M_NNTP) + { + mutt_flushinp (); + mutt_error _("Can't delete attachment from newsserver."); + break; + } +#endif + if (WithCrypto && hdr->security & ~PGP_TRADITIONAL_CHECKED) { mutt_message _( @@ -1201,10 +1210,33 @@ void mutt_view_attachments (HEADER *hdr) case OP_FORWARD_MESSAGE: CHECK_ATTACH; mutt_attach_forward (fp, hdr, idx, idxlen, - menu->tagprefix ? NULL : idx[menu->current]->content); + menu->tagprefix ? NULL : idx[menu->current]->content, 0); menu->redraw = REDRAW_FULL; break; +#ifdef USE_NNTP + case OP_FORWARD_TO_GROUP: + CHECK_ATTACH; + mutt_attach_forward (fp, hdr, idx, idxlen, + menu->tagprefix ? NULL : idx[menu->current]->content, SENDNEWS); + menu->redraw = REDRAW_FULL; + break; + + case OP_FOLLOWUP: + CHECK_ATTACH; + + if (!idx[menu->current]->content->hdr->env->followup_to || + mutt_strcasecmp (idx[menu->current]->content->hdr->env->followup_to, "poster") || + query_quadoption (OPT_FOLLOWUPTOPOSTER,_("Reply by mail as poster prefers?")) != M_YES) + { + mutt_attach_reply (fp, hdr, idx, idxlen, + menu->tagprefix ? NULL : idx[menu->current]->content, + SENDNEWS|SENDREPLY); + menu->redraw = REDRAW_FULL; + break; + } +#endif + case OP_REPLY: case OP_GROUP_REPLY: case OP_LIST_REPLY: diff -udprP mutt-1.5.20.orig/recvcmd.c mutt-1.5.20/recvcmd.c --- mutt-1.5.20.orig/recvcmd.c 2009-06-12 20:24:17.000000000 +0300 +++ mutt-1.5.20/recvcmd.c 2009-06-15 21:05:24.000000000 +0300 @@ -401,7 +401,7 @@ static BODY ** copy_problematic_attachme static void attach_forward_bodies (FILE * fp, HEADER * hdr, ATTACHPTR ** idx, short idxlen, BODY * cur, - short nattach) + short nattach, int flags) { short i; short mime_fwd_all = 0; @@ -547,7 +547,7 @@ _("Can't decode all tagged attachments. tmpfp = NULL; /* now that we have the template, send it. */ - ci_send_message (0, tmphdr, tmpbody, NULL, parent); + ci_send_message (flags, tmphdr, tmpbody, NULL, parent); return; bail: @@ -574,7 +574,7 @@ _("Can't decode all tagged attachments. */ static void attach_forward_msgs (FILE * fp, HEADER * hdr, - ATTACHPTR ** idx, short idxlen, BODY * cur) + ATTACHPTR ** idx, short idxlen, BODY * cur, int flags) { HEADER *curhdr = NULL; HEADER *tmphdr; @@ -679,23 +679,23 @@ static void attach_forward_msgs (FILE * else mutt_free_header (&tmphdr); - ci_send_message (0, tmphdr, *tmpbody ? tmpbody : NULL, + ci_send_message (flags, tmphdr, *tmpbody ? tmpbody : NULL, NULL, curhdr); } void mutt_attach_forward (FILE * fp, HEADER * hdr, - ATTACHPTR ** idx, short idxlen, BODY * cur) + ATTACHPTR ** idx, short idxlen, BODY * cur, int flags) { short nattach; if (check_all_msg (idx, idxlen, cur, 0) == 0) - attach_forward_msgs (fp, hdr, idx, idxlen, cur); + attach_forward_msgs (fp, hdr, idx, idxlen, cur, flags); else { nattach = count_tagged (idx, idxlen); - attach_forward_bodies (fp, hdr, idx, idxlen, cur, nattach); + attach_forward_bodies (fp, hdr, idx, idxlen, cur, nattach, flags); } } @@ -753,28 +753,40 @@ attach_reply_envelope_defaults (ENVELOPE return -1; } - if (parent) +#ifdef USE_NNTP + if ((flags & SENDNEWS)) { - if (mutt_fetch_recips (env, curenv, flags) == -1) - return -1; + /* in case followup set Newsgroups: with Followup-To: if it present */ + if (!env->newsgroups && curenv && + mutt_strcasecmp (curenv->followup_to, "poster")) + env->newsgroups = safe_strdup (curenv->followup_to); } else +#endif { - for (i = 0; i < idxlen; i++) + if (parent) { - if (idx[i]->content->tagged - && mutt_fetch_recips (env, idx[i]->content->hdr->env, flags) == -1) + if (mutt_fetch_recips (env, curenv, flags) == -1) return -1; } + else + { + for (i = 0; i < idxlen; i++) + { + if (idx[i]->content->tagged + && mutt_fetch_recips (env, idx[i]->content->hdr->env, flags) == -1) + return -1; + } + } + + if ((flags & SENDLISTREPLY) && !env->to) + { + mutt_error _("No mailing lists found!"); + return (-1); + } + + mutt_fix_reply_recipients (env); } - - if ((flags & SENDLISTREPLY) && !env->to) - { - mutt_error _("No mailing lists found!"); - return (-1); - } - - mutt_fix_reply_recipients (env); mutt_make_misc_reply_headers (env, Context, curhdr, curenv); if (parent) @@ -835,6 +847,13 @@ void mutt_attach_reply (FILE * fp, HEADE char prefix[SHORT_STRING]; int rc; +#ifdef USE_NNTP + if (flags & SENDNEWS) + set_option (OPTNEWSSEND); + else + unset_option (OPTNEWSSEND); +#endif + if (check_all_msg (idx, idxlen, cur, 0) == -1) { nattach = count_tagged (idx, idxlen); diff -udprP mutt-1.5.20.orig/rfc1524.c mutt-1.5.20/rfc1524.c --- mutt-1.5.20.orig/rfc1524.c 2009-05-30 20:20:08.000000000 +0300 +++ mutt-1.5.20/rfc1524.c 2009-06-15 21:05:24.000000000 +0300 @@ -569,13 +569,13 @@ int rfc1524_expand_filename (char *namet * safe_fopen(). */ -int mutt_rename_file (char *oldfile, char *newfile) +int _mutt_rename_file (char *oldfile, char *newfile, int overwrite) { FILE *ofp, *nfp; if (access (oldfile, F_OK) != 0) return 1; - if (access (newfile, F_OK) == 0) + if (!overwrite && access (newfile, F_OK) == 0) return 2; if ((ofp = fopen (oldfile,"r")) == NULL) return 3; @@ -590,3 +590,8 @@ int mutt_rename_file (char *oldfile, cha mutt_unlink (oldfile); return 0; } + +int mutt_rename_file (char *oldfile, char *newfile) +{ + return _mutt_rename_file (oldfile, newfile, 0); +} diff -udprP mutt-1.5.20.orig/rfc1524.h mutt-1.5.20/rfc1524.h --- mutt-1.5.20.orig/rfc1524.h 2008-11-11 21:55:47.000000000 +0200 +++ mutt-1.5.20/rfc1524.h 2009-06-15 21:05:24.000000000 +0300 @@ -40,5 +40,6 @@ int rfc1524_expand_command (BODY *, char int rfc1524_expand_filename (char *, char *, char *, size_t); int rfc1524_mailcap_lookup (BODY *, char *, rfc1524_entry *, int); int mutt_rename_file (char *, char *); +int _mutt_rename_file (char *, char *, int); #endif /* _RFC1524_H */ diff -udprP mutt-1.5.20.orig/send.c mutt-1.5.20/send.c --- mutt-1.5.20.orig/send.c 2009-06-13 02:38:52.000000000 +0300 +++ mutt-1.5.20/send.c 2009-06-15 21:13:13.000000000 +0300 @@ -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, /* 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) { @@ -259,6 +298,14 @@ static int edit_envelope (ENVELOPE *en) return 0; } +#ifdef USE_NNTP +char *nntp_get_header (const char *s) +{ + SKIPWS (s); + return safe_strdup (s); +} +#endif + static void process_user_recips (ENVELOPE *env) { LIST *uh = UserHeader; @@ -271,6 +318,14 @@ static void process_user_recips (ENVELOP env->cc = rfc822_parse_adrlist (env->cc, uh->data + 3); else if (ascii_strncasecmp ("bcc:", uh->data, 4) == 0) env->bcc = rfc822_parse_adrlist (env->bcc, uh->data + 4); +#ifdef USE_NNTP + else if (ascii_strncasecmp ("newsgroups:", uh->data, 11) == 0) + env->newsgroups = nntp_get_header (uh->data + 11); + else if (ascii_strncasecmp ("followup-to:", uh->data, 12) == 0) + env->followup_to = nntp_get_header (uh->data + 12); + else if (ascii_strncasecmp ("x-comment-to:", uh->data, 13) == 0) + env->x_comment_to = nntp_get_header (uh->data + 13); +#endif } } @@ -309,6 +364,12 @@ static void process_user_header (ENVELOP else if (ascii_strncasecmp ("to:", uh->data, 3) != 0 && ascii_strncasecmp ("cc:", uh->data, 3) != 0 && ascii_strncasecmp ("bcc:", uh->data, 4) != 0 && +#ifdef USE_NNTP + ascii_strncasecmp ("newsgroups:", uh->data, 11) != 0 && + ascii_strncasecmp ("followup-to:", uh->data, 12) != 0 && + ascii_strncasecmp ("x-comment-to:", uh->data, 13) != 0 && +#endif + ascii_strncasecmp ("supersedes:", uh->data, 11) != 0 && ascii_strncasecmp ("subject:", uh->data, 8) != 0 && ascii_strncasecmp ("return-path:", uh->data, 12) != 0) { @@ -657,6 +718,10 @@ void mutt_add_to_reference_headers (ENVE if (pp) *pp = p; if (qq) *qq = q; +#ifdef USE_NNTP + if (option (OPTNEWSSEND) && option (OPTXCOMMENTTO) && curenv->from) + env->x_comment_to = safe_strdup (mutt_get_name (curenv->from)); +#endif } static void @@ -719,6 +784,16 @@ envelope_defaults (ENVELOPE *env, CONTEX if (flags & SENDREPLY) { +#ifdef USE_NNTP + if ((flags & SENDNEWS)) + { + /* in case followup set Newsgroups: with Followup-To: if it present */ + if (!env->newsgroups && curenv && + mutt_strcasecmp (curenv->followup_to, "poster")) + env->newsgroups = safe_strdup (curenv->followup_to); + } + else +#endif if (tag) { HEADER *h; @@ -865,7 +940,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 /* USE_NNTP */ if (SmtpUrl) return mutt_smtp_send (msg->env->from, msg->env->to, msg->env->cc, msg->env->bcc, tempfile, @@ -1137,6 +1226,13 @@ ci_send_message (int flags, /* send mod int rv = -1; +#ifdef USE_NNTP + if (flags & SENDNEWS) + set_option (OPTNEWSSEND); + else + unset_option (OPTNEWSSEND); +#endif + if (!flags && !msg && quadoption (OPT_RECALL) != M_NO && mutt_num_postponed (1)) { @@ -1167,6 +1263,22 @@ ci_send_message (int flags, /* send mod { if ((flags = mutt_get_postponed (ctx, msg, &cur, fcc, sizeof (fcc))) < 0) goto cleanup; +#ifdef USE_NNTP + /* + * If postponed message is a news article, it have + * a "Newsgroups:" header line, then set appropriate flag. + */ + if (msg->env->newsgroups) + { + flags |= SENDNEWS; + set_option (OPTNEWSSEND); + } + else + { + flags &= ~SENDNEWS; + unset_option (OPTNEWSSEND); + } +#endif } if (flags & (SENDPOSTPONED|SENDRESEND)) @@ -1278,11 +1390,16 @@ ci_send_message (int flags, /* send mod if (flags & SENDREPLY) mutt_fix_reply_recipients (msg->env); +#ifdef USE_NNTP + if ((flags & SENDNEWS) && ctx && ctx->magic == M_NNTP && !msg->env->newsgroups) + msg->env->newsgroups = safe_strdup (((NNTP_DATA *)ctx->data)->group); +#endif + if (! (flags & SENDMAILX) && ! (option (OPTAUTOEDIT) && option (OPTEDITHDRS)) && ! ((flags & SENDREPLY) && option (OPTFASTREPLY))) { - if (edit_envelope (msg->env) == -1) + if (edit_envelope (msg->env, flags) == -1) goto cleanup; } @@ -1539,6 +1656,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; } @@ -1571,6 +1693,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)) { @@ -1604,6 +1729,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); @@ -1810,7 +1948,12 @@ full_fcc: } } else if (!option (OPTNOCURSES) && ! (flags & SENDMAILX)) - mutt_message (i == 0 ? _("Mail sent.") : _("Sending in background.")); + mutt_message (i != 0 ? _("Sending in background.") : +#ifdef USE_NNTP + (flags & SENDNEWS) ? _("Article posted.") : _("Mail sent.")); +#else + _("Mail sent.")); +#endif if (WithCrypto && (msg->security & ENCRYPT)) FREE (&pgpkeylist); diff -udprP mutt-1.5.20.orig/sendlib.c mutt-1.5.20/sendlib.c --- mutt-1.5.20.orig/sendlib.c 2009-06-14 18:46:11.000000000 +0300 +++ mutt-1.5.20/sendlib.c 2009-06-15 21:51:17.000000000 +0300 @@ -46,6 +46,10 @@ #include #include +#ifdef USE_NNTP +#include "nntp.h" +#endif + #ifdef HAVE_SYSEXITS_H #include #else /* Make sure EX_OK is defined */ @@ -1868,6 +1872,9 @@ int mutt_write_rfc822_header (FILE *fp, mutt_write_address_list (env->to, fp, 4, 0); } else if (mode > 0) +#ifdef USE_NNTP + if (!option (OPTNEWSSEND)) +#endif fputs ("To: \n", fp); if (env->cc) @@ -1876,6 +1883,9 @@ int mutt_write_rfc822_header (FILE *fp, mutt_write_address_list (env->cc, fp, 4, 0); } else if (mode > 0) +#ifdef USE_NNTP + if (!option (OPTNEWSSEND)) +#endif fputs ("Cc: \n", fp); if (env->bcc) @@ -1887,8 +1897,28 @@ int mutt_write_rfc822_header (FILE *fp, } } else if (mode > 0) +#ifdef USE_NNTP + if (!option (OPTNEWSSEND)) +#endif fputs ("Bcc: \n", fp); +#ifdef USE_NNTP + if (env->newsgroups) + fprintf (fp, "Newsgroups: %s\n", env->newsgroups); + else if (mode == 1 && option (OPTNEWSSEND)) + fputs ("Newsgroups: \n", fp); + + if (env->followup_to) + fprintf (fp, "Followup-To: %s\n", env->followup_to); + else if (mode == 1 && option (OPTNEWSSEND)) + fputs ("Followup-To: \n", fp); + + if (env->x_comment_to) + fprintf (fp, "X-Comment-To: %s\n", env->x_comment_to); + else if (mode == 1 && option (OPTNEWSSEND) && option (OPTXCOMMENTTO)) + fputs ("X-Comment-To: \n", fp); +#endif + if (env->subject) mutt_write_one_header (fp, "Subject", env->subject, NULL, 0, 0); else if (mode == 1) @@ -1907,6 +1937,9 @@ int mutt_write_rfc822_header (FILE *fp, fputs ("Reply-To: \n", fp); if (env->mail_followup_to) +#ifdef USE_NNTP + if (!option (OPTNEWSSEND)) +#endif { fputs ("Mail-Followup-To: ", fp); mutt_write_address_list (env->mail_followup_to, fp, 18, 0); @@ -2245,11 +2278,30 @@ mutt_invoke_sendmail (ADDRESS *from, /* const char *msg, /* file containing message */ int eightbit) /* message contains 8bit chars */ { - char *ps = NULL, *path = NULL, *s = safe_strdup (Sendmail), *childout = NULL; + char *ps = NULL, *path = NULL, *s = NULL, *childout = NULL; char **args = NULL; size_t argslen = 0, argsmax = 0; int i; +#ifdef USE_NNTP + if (option (OPTNEWSSEND)) + { + char cmd[LONG_STRING]; + + mutt_FormatString (cmd, sizeof (cmd), 0, NONULL (Inews), nntp_format_str, 0, 0); + if (!*cmd) + { + i = nntp_post (msg); + unlink (msg); + return i; + } + + s = safe_strdup (cmd); + } + else +#endif + s = safe_strdup (Sendmail); + ps = s; i = 0; while ((ps = strtok (ps, " "))) @@ -2273,6 +2325,10 @@ mutt_invoke_sendmail (ADDRESS *from, /* i++; } +#ifdef USE_NNTP + if (!option (OPTNEWSSEND)) + { +#endif if (eightbit && option (OPTUSE8BITMIME)) args = add_option (args, &argslen, &argsmax, "-B8BITMIME"); @@ -2304,6 +2360,9 @@ mutt_invoke_sendmail (ADDRESS *from, /* args = add_args (args, &argslen, &argsmax, to); args = add_args (args, &argslen, &argsmax, cc); args = add_args (args, &argslen, &argsmax, bcc); +#ifdef USE_NNTP + } +#endif if (argslen == argsmax) safe_realloc (&args, sizeof (char *) * (++argsmax)); @@ -2384,6 +2443,9 @@ void mutt_prepare_envelope (ENVELOPE *en rfc2047_encode_string (&env->x_label); if (env->subject) +#ifdef USE_NNTP + if (!option (OPTNEWSSEND) || option (OPTMIMESUBJECT)) +#endif { rfc2047_encode_string (&env->subject); } @@ -2504,6 +2566,10 @@ int mutt_bounce_message (FILE *fp, HEADE } rfc822_write_address (resent_from, sizeof (resent_from), from, 0); +#ifdef USE_NNTP + unset_option (OPTNEWSSEND); +#endif + ret = _mutt_bounce_message (fp, h, to, resent_from, from); rfc822_free_address (&from); diff -udprP mutt-1.5.20.orig/sort.c mutt-1.5.20/sort.c --- mutt-1.5.20.orig/sort.c 2008-11-11 21:55:47.000000000 +0200 +++ mutt-1.5.20/sort.c 2009-06-15 21:05:24.000000000 +0300 @@ -151,6 +151,15 @@ static int compare_order (const void *a, HEADER **ha = (HEADER **) a; HEADER **hb = (HEADER **) b; +#ifdef USE_NNTP + if ((*ha)->article_num && (*hb)->article_num) + { + int result = (*ha)->article_num - (*hb)->article_num; + AUXSORT(result,a,b); + return (SORTCODE (result)); + } + else +#endif /* no need to auxsort because you will never have equality here */ return (SORTCODE ((*ha)->index - (*hb)->index)); } diff -udprP mutt-1.5.20.orig/url.c mutt-1.5.20/url.c --- mutt-1.5.20.orig/url.c 2009-06-01 19:29:32.000000000 +0300 +++ mutt-1.5.20/url.c 2009-06-15 21:05:24.000000000 +0300 @@ -39,6 +39,8 @@ static struct mapping_t UrlMap[] = { "imaps", U_IMAPS }, { "pop", U_POP }, { "pops", U_POPS }, + { "news", U_NNTP }, + { "newss", U_NNTPS }, { "mailto", U_MAILTO }, { "smtp", U_SMTP }, { "smtps", U_SMTPS }, diff -udprP mutt-1.5.20.orig/url.h mutt-1.5.20/url.h --- mutt-1.5.20.orig/url.h 2008-11-11 21:55:47.000000000 +0200 +++ mutt-1.5.20/url.h 2009-06-15 21:05:24.000000000 +0300 @@ -8,6 +8,8 @@ typedef enum url_scheme U_POPS, U_IMAP, U_IMAPS, + U_NNTP, + U_NNTPS, U_SMTP, U_SMTPS, U_MAILTO, diff -udprP mutt-1.5.20.orig/Makefile.am mutt-1.5.20/Makefile.am --- mutt-1.5.20.orig/Makefile.am 2009-01-05 04:11:29.000000000 +0200 +++ mutt-1.5.20/Makefile.am 2009-06-15 21:05:24.000000000 +0300 @@ -53,6 +53,7 @@ EXTRA_mutt_SOURCES = account.c bcache.c mutt_idna.c mutt_sasl.c mutt_socket.c mutt_ssl.c mutt_ssl_gnutls.c \ mutt_tunnel.c pgp.c pgpinvoke.c pgpkey.c pgplib.c pgpmicalg.c \ pgppacket.c pop.c pop_auth.c pop_lib.c remailer.c resize.c sha1.c \ + nntp.c newsrc.c \ smime.c smtp.c utf8.c wcwidth.c \ bcache.h browser.h hcache.h mbyte.h mutt_idna.h remailer.h url.h @@ -64,6 +65,7 @@ EXTRA_DIST = COPYRIGHT GPL OPS OPS.PGP O mutt_regex.h mutt_sasl.h mutt_socket.h mutt_ssl.h mutt_tunnel.h \ mx.h pager.h pgp.h pop.h protos.h rfc1524.h rfc2047.h \ rfc2231.h rfc822.h rfc3676.h sha1.h sort.h mime.types VERSION prepare \ + nntp.h ChangeLog.nntp \ _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 \ diff -udprP mutt-1.5.20.orig/Makefile.in mutt-1.5.20/Makefile.in --- mutt-1.5.20.orig/Makefile.in 2009-06-09 09:50:44.000000000 +0300 +++ mutt-1.5.20/Makefile.in 2009-06-15 21:05:24.000000000 +0300 @@ -372,6 +372,7 @@ EXTRA_mutt_SOURCES = account.c bcache.c mutt_idna.c mutt_sasl.c mutt_socket.c mutt_ssl.c mutt_ssl_gnutls.c \ mutt_tunnel.c pgp.c pgpinvoke.c pgpkey.c pgplib.c pgpmicalg.c \ pgppacket.c pop.c pop_auth.c pop_lib.c remailer.c resize.c sha1.c \ + nntp.c newsrc.c \ smime.c smtp.c utf8.c wcwidth.c \ bcache.h browser.h hcache.h mbyte.h mutt_idna.h remailer.h url.h @@ -383,6 +384,7 @@ EXTRA_DIST = COPYRIGHT GPL OPS OPS.PGP O mutt_regex.h mutt_sasl.h mutt_socket.h mutt_ssl.h mutt_tunnel.h \ mx.h pager.h pgp.h pop.h protos.h rfc1524.h rfc2047.h \ rfc2231.h rfc822.h rfc3676.h sha1.h sort.h mime.types VERSION prepare \ + nntp.h ChangeLog.nntp \ _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 \ @@ -637,6 +639,8 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mutt_tunnel.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/muttlib.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mx.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/newsrc.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nntp.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pager.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/parse.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/patchlist.Po@am__quote@ diff -udprP mutt-1.5.20.orig/configure mutt-1.5.20/configure --- mutt-1.5.20.orig/configure 2009-06-09 09:50:42.000000000 +0300 +++ mutt-1.5.20/configure 2009-06-15 21:05:24.000000000 +0300 @@ -1478,6 +1478,7 @@ Optional Features: Force use of an external dotlock program --enable-pop Enable POP3 support --enable-imap Enable IMAP support + --enable-nntp Enable NNTP support --enable-smtp include internal SMTP relay support --enable-debug Enable debugging support --enable-flock Use flock() to lock files @@ -14198,6 +14199,20 @@ fi fi done +# Check whether --enable-nntp or --disable-nntp was given. +if test "${enable_nntp+set}" = set; then + enableval="$enable_nntp" + if test x$enableval = xyes ; then + cat >>confdefs.h <<\_ACEOF +#define USE_NNTP 1 +_ACEOF + + MUTT_LIB_OBJECTS="$MUTT_LIB_OBJECTS nntp.o newsrc.o" + need_socket="yes" + fi + +fi; + for ac_func in strftime diff -udprP mutt-1.5.20.orig/doc/Muttrc mutt-1.5.20/doc/Muttrc --- mutt-1.5.20.orig/doc/Muttrc 2009-06-14 21:53:24.000000000 +0300 +++ mutt-1.5.20/doc/Muttrc 2009-06-15 21:05:24.000000000 +0300 @@ -281,6 +281,28 @@ attachments -I message/external-body # of the value as shown above if included. # # +# set ask_follow_up=no +# +# Name: ask_follow_up +# Type: boolean +# Default: no +# +# +# If set, Mutt will prompt you for follow-up groups before editing +# the body of an outgoing message. +# +# +# set ask_x_comment_to=no +# +# Name: ask_x_comment_to +# Type: boolean +# Default: no +# +# +# If set, Mutt will prompt you for x-comment-to field before editing +# the body of an outgoing message. +# +# # set attach_format="%u%D%I %t%4n %T%.40d%> [%.7m/%.10M, %.6e%?C?, %C?, %s] " # # Name: attach_format @@ -466,6 +488,17 @@ attachments -I message/external-body # set certificate_file=~/.mutt/certificates # # +# set catchup_newsgroup=ask-yes +# +# Name: catchup_newsgroup +# Type: quadoption +# Default: ask-yes +# +# +# If this variable is set, Mutt will mark all articles in newsgroup +# as read when you quit the newsgroup (catchup newsgroup). +# +# # set charset="" # # Name: charset @@ -1121,6 +1154,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 @@ -1231,6 +1277,28 @@ 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 @@ -1779,6 +1847,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, @@ -1794,12 +1863,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 builtin 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 @@ -1835,6 +1906,22 @@ 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: +# +# %s newsserver name +# +# Example: set inews="/usr/local/bin/inews -hS" +# +# # set ispell="ispell" # # Name: ispell @@ -2188,6 +2275,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 @@ -2254,6 +2353,118 @@ 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 headers in. If unset, headers will not be saved at all +# and will be reloaded each time when you enter to newsgroup. +# +# +# set news_server="" +# +# Name: news_server +# Type: string +# Default: "" +# +# +# This variable specifies domain name or address of NNTP server. It +# defaults to the newsserver specified in the environment variable +# $NNTPSERVER or contained in the file /etc/nntpserver. You can also +# specify username and an alternative port for each newsserver, ie: +# +# [news[s]://][username[:password]@]newsserver[:port] +# +# +# set newsrc="~/.newsrc" +# +# Name: newsrc +# Type: path +# Default: "~/.newsrc" +# +# +# The file, containing info about subscribed newsgroups - names and +# indexes of read articles. The following printf-style sequence +# is understood: +# +# %s newsserver name +# +# +# set nntp_context=1000 +# +# Name: nntp_context +# Type: number +# Default: 1000 +# +# +# This variable defines number of articles which will be in index when +# newsgroup entered. If active newsgroup have more articles than this +# number, oldest articles will be ignored. Also controls how many +# articles headers will be saved in cache when you quit newsgroup. +# +# +# set nntp_load_description=yes +# +# Name: nntp_load_description +# Type: boolean +# Default: yes +# +# +# This variable controls whether or not descriptions for each newsgroup +# must be loaded when newsgroup is added to list (first time list +# loading or new newsgroup adding). +# +# +# set nntp_user="" +# +# Name: nntp_user +# Type: string +# Default: "" +# +# +# Your login name on the NNTP server. If unset and NNTP server requires +# authentification, Mutt will prompt you for your account name when you +# connect to newsserver. +# +# +# set nntp_pass="" +# +# Name: nntp_pass +# Type: string +# Default: "" +# +# +# Your password for NNTP account. +# +# +# set nntp_poll=60 +# +# Name: nntp_poll +# Type: number +# Default: 60 +# +# +# The time in seconds until any operations on newsgroup except post new +# article will cause recheck for new news. If set to 0, Mutt will +# recheck newsgroup on each operation in index (stepping, read article, +# etc.). +# +# +# set nntp_reconnect=ask-yes +# +# Name: nntp_reconnect +# Type: quadoption +# Default: ask-yes +# +# +# Controls whether or not Mutt will try to reconnect to newsserver when +# connection lost. +# +# # set pager="builtin" # # Name: pager @@ -2969,6 +3180,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 newsserver +# does not support posting to that newsgroup or totally read-only, that +# posting will not have an effect. +# +# # set postpone=ask-yes # # Name: postpone @@ -3543,6 +3767,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, newsserver will be asked for new newsgroups on entering +# the browser. Otherwise, it will be done only once for a newsserver. +# Also controls whether or not number of new articles of subscribed +# newsgroups will be then checked. +# +# +# set show_only_unread=no +# +# Name: show_only_unread +# Type: boolean +# Default: no +# +# +# If set, only subscribed newsgroups that contain unread articles +# will be displayed in browser. +# +# # set sig_dashes=yes # # Name: sig_dashes @@ -4748,3 +5007,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. +# +# --- a/PATCHES +++ b/PATCHES @@ -0,0 +1 @@ +vvv.nntp