NeoMutt  2022-04-29-81-g9c5a59
Teaching an old dog new tricks
DOXYGEN
nntp.c File Reference

Usenet network mailbox type; talk to an NNTP server. More...

#include "config.h"
#include <ctype.h>
#include <limits.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <time.h>
#include <unistd.h>
#include "private.h"
#include "mutt/lib.h"
#include "config/lib.h"
#include "email/lib.h"
#include "core/lib.h"
#include "conn/lib.h"
#include "lib.h"
#include "attach/lib.h"
#include "bcache/lib.h"
#include "hcache/lib.h"
#include "ncrypt/lib.h"
#include "progress/lib.h"
#include "question/lib.h"
#include "adata.h"
#include "edata.h"
#include "hook.h"
#include "mdata.h"
#include "mutt_logging.h"
#include "muttlib.h"
#include "mx.h"
#include "protos.h"
#include <sasl/sasl.h>
#include <sasl/saslutil.h>
#include "mutt.h"
+ Include dependency graph for nntp.c:

Go to the source code of this file.

Data Structures

struct  FetchCtx
 Keep track when getting data from a server. More...
 
struct  ChildCtx
 Keep track of the children of an article. More...
 

Functions

void nntp_hashelem_free (int type, void *obj, intptr_t data)
 Free our hash table data - Implements hash_hdata_free_t -. More...
 
static int nntp_connect_error (struct NntpAccountData *adata)
 Signal a failed connection. More...
 
static int nntp_capabilities (struct NntpAccountData *adata)
 Get capabilities. More...
 
static int nntp_attempt_features (struct NntpAccountData *adata)
 Detect supported commands. More...
 
static bool nntp_memchr (char **haystack, char *sentinel, int needle)
 Look for a char in a binary buf, conveniently. More...
 
static void nntp_log_binbuf (const char *buf, size_t len, const char *pfx, int dbg)
 Log a buffer possibly containing NUL bytes. More...
 
static int nntp_auth (struct NntpAccountData *adata)
 Get login, password and authenticate. More...
 
static int nntp_query (struct NntpMboxData *mdata, char *line, size_t linelen)
 Send data from buffer and receive answer to same buffer. More...
 
static int nntp_fetch_lines (struct NntpMboxData *mdata, char *query, size_t qlen, const char *msg, int(*func)(char *, void *), void *data)
 Read lines, calling a callback function for each. More...
 
static int fetch_description (char *line, void *data)
 Parse newsgroup description. More...
 
static int get_description (struct NntpMboxData *mdata, const char *wildmat, const char *msg)
 Fetch newsgroups descriptions. More...
 
static void nntp_parse_xref (struct Mailbox *m, struct Email *e)
 Parse cross-reference. More...
 
static int fetch_tempfile (char *line, void *data)
 Write line to temporary file. More...
 
static int fetch_numbers (char *line, void *data)
 Parse article number. More...
 
static int parse_overview_line (char *line, void *data)
 Parse overview line. More...
 
static int nntp_fetch_headers (struct Mailbox *m, void *hc, anum_t first, anum_t last, bool restore)
 Fetch headers. More...
 
static int nntp_group_poll (struct NntpMboxData *mdata, bool update_stat)
 Check newsgroup for new articles. More...
 
static enum MxStatus check_mailbox (struct Mailbox *m)
 Check current newsgroup for new articles. More...
 
static int nntp_date (struct NntpAccountData *adata, time_t *now)
 Get date and time from server. More...
 
static int fetch_children (char *line, void *data)
 Parse XPAT line. More...
 
int nntp_open_connection (struct NntpAccountData *adata)
 Connect to server, authenticate and get capabilities. More...
 
int nntp_post (struct Mailbox *m, const char *msg)
 Post article. More...
 
int nntp_active_fetch (struct NntpAccountData *adata, bool mark_new)
 Fetch list of all newsgroups from server. More...
 
int nntp_check_new_groups (struct Mailbox *m, struct NntpAccountData *adata)
 Check for new groups/articles in subscribed groups. More...
 
int nntp_check_msgid (struct Mailbox *m, const char *msgid)
 Fetch article by Message-ID. More...
 
int nntp_check_children (struct Mailbox *m, const char *msgid)
 Fetch children of article with the Message-ID. More...
 
int nntp_compare_order (const struct Email *a, const struct Email *b, bool reverse)
 Sort to mailbox order - Implements sort_mail_t -. More...
 
static bool nntp_ac_owns_path (struct Account *a, const char *path)
 Check whether an Account owns a Mailbox path - Implements MxOps::ac_owns_path() -. More...
 
static bool nntp_ac_add (struct Account *a, struct Mailbox *m)
 Add a Mailbox to an Account - Implements MxOps::ac_add() -. More...
 
static enum MxOpenReturns nntp_mbox_open (struct Mailbox *m)
 Open a Mailbox - Implements MxOps::mbox_open() -. More...
 
static enum MxStatus nntp_mbox_check (struct Mailbox *m)
 Check for new mail - Implements MxOps::mbox_check() -. More...
 
static enum MxStatus nntp_mbox_sync (struct Mailbox *m)
 Save changes to the Mailbox - Implements MxOps::mbox_sync() -. More...
 
static enum MxStatus nntp_mbox_close (struct Mailbox *m)
 Close a Mailbox - Implements MxOps::mbox_close() -. More...
 
static bool nntp_msg_open (struct Mailbox *m, struct Message *msg, int msgno)
 Open an email message in a Mailbox - Implements MxOps::msg_open() -. More...
 
static int nntp_msg_close (struct Mailbox *m, struct Message *msg)
 Close an email - Implements MxOps::msg_close() -. More...
 
enum MailboxType nntp_path_probe (const char *path, const struct stat *st)
 Is this an NNTP Mailbox? - Implements MxOps::path_probe() -. More...
 
static int nntp_path_canon (char *buf, size_t buflen)
 Canonicalise a Mailbox path - Implements MxOps::path_canon() -. More...
 
static int nntp_path_pretty (char *buf, size_t buflen, const char *folder)
 Abbreviate a Mailbox path - Implements MxOps::path_pretty() -. More...
 
static int nntp_path_parent (char *buf, size_t buflen)
 Find the parent of a Mailbox path - Implements MxOps::path_parent() -. More...
 

Variables

struct NntpAccountDataCurrentNewsSrv
 Current NNTP news server. More...
 
const char * OverviewFmt
 
struct MxOps MxNntpOps
 NNTP Mailbox - Implements MxOps -. More...
 

Detailed Description

Usenet network mailbox type; talk to an NNTP server.

Authors
  • Brandon Long
  • Andrej Gritsenko
  • Vsevolod Volkov
  • Richard Russon

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, see http://www.gnu.org/licenses/.

Definition in file nntp.c.

Function Documentation

◆ nntp_connect_error()

static int nntp_connect_error ( struct NntpAccountData adata)
static

Signal a failed connection.

Parameters
adataNNTP server
Return values
-1Always

Definition at line 126 of file nntp.c.

127 {
128  adata->status = NNTP_NONE;
129  mutt_error(_("Server closed connection"));
130  return -1;
131 }
#define mutt_error(...)
Definition: logging.h:87
#define _(a)
Definition: message.h:28
@ NNTP_NONE
No connection to server.
Definition: private.h:44
unsigned int status
Definition: adata.h:48
+ Here is the caller graph for this function:

◆ nntp_capabilities()

static int nntp_capabilities ( struct NntpAccountData adata)
static

Get capabilities.

Parameters
adataNNTP server
Return values
-1Error, connection is closed
0Mode is reader, capabilities set up
1Need to switch to reader mode

Definition at line 140 of file nntp.c.

141 {
142  struct Connection *conn = adata->conn;
143  bool mode_reader = false;
144  char buf[1024];
145  char authinfo[1024] = { 0 };
146 
147  adata->hasCAPABILITIES = false;
148  adata->hasSTARTTLS = false;
149  adata->hasDATE = false;
150  adata->hasLIST_NEWSGROUPS = false;
151  adata->hasLISTGROUP = false;
152  adata->hasLISTGROUPrange = false;
153  adata->hasOVER = false;
154  FREE(&adata->authenticators);
155 
156  if ((mutt_socket_send(conn, "CAPABILITIES\r\n") < 0) ||
157  (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
158  {
159  return nntp_connect_error(adata);
160  }
161 
162  /* no capabilities */
163  if (!mutt_str_startswith(buf, "101"))
164  return 1;
165  adata->hasCAPABILITIES = true;
166 
167  /* parse capabilities */
168  do
169  {
170  size_t plen = 0;
171  if (mutt_socket_readln(buf, sizeof(buf), conn) < 0)
172  return nntp_connect_error(adata);
173  if (mutt_str_equal("STARTTLS", buf))
174  adata->hasSTARTTLS = true;
175  else if (mutt_str_equal("MODE-READER", buf))
176  mode_reader = true;
177  else if (mutt_str_equal("READER", buf))
178  {
179  adata->hasDATE = true;
180  adata->hasLISTGROUP = true;
181  adata->hasLISTGROUPrange = true;
182  }
183  else if ((plen = mutt_str_startswith(buf, "AUTHINFO ")))
184  {
185  mutt_str_cat(buf, sizeof(buf), " ");
186  mutt_str_copy(authinfo, buf + plen - 1, sizeof(authinfo));
187  }
188 #ifdef USE_SASL
189  else if ((plen = mutt_str_startswith(buf, "SASL ")))
190  {
191  char *p = buf + plen;
192  while (*p == ' ')
193  p++;
194  adata->authenticators = mutt_str_dup(p);
195  }
196 #endif
197  else if (mutt_str_equal("OVER", buf))
198  adata->hasOVER = true;
199  else if (mutt_str_startswith(buf, "LIST "))
200  {
201  char *p = strstr(buf, " NEWSGROUPS");
202  if (p)
203  {
204  p += 11;
205  if ((*p == '\0') || (*p == ' '))
206  adata->hasLIST_NEWSGROUPS = true;
207  }
208  }
209  } while (!mutt_str_equal(".", buf));
210  *buf = '\0';
211 #ifdef USE_SASL
212  if (adata->authenticators && mutt_istr_find(authinfo, " SASL "))
213  mutt_str_copy(buf, adata->authenticators, sizeof(buf));
214 #endif
215  if (mutt_istr_find(authinfo, " USER "))
216  {
217  if (*buf != '\0')
218  mutt_str_cat(buf, sizeof(buf), " ");
219  mutt_str_cat(buf, sizeof(buf), "USER");
220  }
221  mutt_str_replace(&adata->authenticators, buf);
222 
223  /* current mode is reader */
224  if (adata->hasDATE)
225  return 0;
226 
227  /* server is mode-switching, need to switch to reader mode */
228  if (mode_reader)
229  return 1;
230 
231  mutt_socket_close(conn);
232  adata->status = NNTP_BYE;
233  mutt_error(_("Server doesn't support reader mode"));
234  return -1;
235 }
#define FREE(x)
Definition: memory.h:40
const char * mutt_istr_find(const char *haystack, const char *needle)
Find first occurrence of string (ignoring case)
Definition: string.c:569
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:250
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:784
size_t mutt_str_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix.
Definition: string.c:227
size_t mutt_str_copy(char *dest, const char *src, size_t dsize)
Copy a string into a buffer (guaranteeing NUL-termination)
Definition: string.c:629
char * mutt_str_cat(char *buf, size_t buflen, const char *s)
Concatenate two strings.
Definition: string.c:265
char * mutt_str_replace(char **p, const char *s)
Replace one string with another.
Definition: string.c:326
@ NNTP_BYE
Disconnected from server.
Definition: private.h:46
static int nntp_connect_error(struct NntpAccountData *adata)
Signal a failed connection.
Definition: nntp.c:126
static size_t plen
Length of cached packet.
Definition: pgppacket.c:39
int mutt_socket_close(struct Connection *conn)
Close a socket.
Definition: socket.c:97
#define mutt_socket_readln(buf, buflen, conn)
Definition: socket.h:57
#define mutt_socket_send(conn, buf)
Definition: socket.h:58
struct Connection * conn
Connection to NNTP Server.
Definition: adata.h:63
char * authenticators
Definition: adata.h:53
bool hasCAPABILITIES
Server supports CAPABILITIES command.
Definition: adata.h:38
bool hasSTARTTLS
Server supports STARTTLS command.
Definition: adata.h:39
bool hasLISTGROUPrange
Server supports LISTGROUPrange command.
Definition: adata.h:44
bool hasLISTGROUP
Server supports LISTGROUP command.
Definition: adata.h:43
bool hasOVER
Server supports OVER command.
Definition: adata.h:45
bool hasDATE
Server supports DATE command.
Definition: adata.h:40
bool hasLIST_NEWSGROUPS
Server supports LIST_NEWSGROUPS command.
Definition: adata.h:41
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ nntp_attempt_features()

static int nntp_attempt_features ( struct NntpAccountData adata)
static

Detect supported commands.

Parameters
adataNNTP server
Return values
0Success
-1Failure

Definition at line 243 of file nntp.c.

244 {
245  struct Connection *conn = adata->conn;
246  char buf[1024];
247 
248  /* no CAPABILITIES, trying DATE, LISTGROUP, LIST NEWSGROUPS */
249  if (!adata->hasCAPABILITIES)
250  {
251  if ((mutt_socket_send(conn, "DATE\r\n") < 0) ||
252  (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
253  {
254  return nntp_connect_error(adata);
255  }
256  if (!mutt_str_startswith(buf, "500"))
257  adata->hasDATE = true;
258 
259  if ((mutt_socket_send(conn, "LISTGROUP\r\n") < 0) ||
260  (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
261  {
262  return nntp_connect_error(adata);
263  }
264  if (!mutt_str_startswith(buf, "500"))
265  adata->hasLISTGROUP = true;
266 
267  if ((mutt_socket_send(conn, "LIST NEWSGROUPS +\r\n") < 0) ||
268  (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
269  {
270  return nntp_connect_error(adata);
271  }
272  if (!mutt_str_startswith(buf, "500"))
273  adata->hasLIST_NEWSGROUPS = true;
274  if (mutt_str_startswith(buf, "215"))
275  {
276  do
277  {
278  if (mutt_socket_readln(buf, sizeof(buf), conn) < 0)
279  return nntp_connect_error(adata);
280  } while (!mutt_str_equal(".", buf));
281  }
282  }
283 
284  /* no LIST NEWSGROUPS, trying XGTITLE */
285  if (!adata->hasLIST_NEWSGROUPS)
286  {
287  if ((mutt_socket_send(conn, "XGTITLE\r\n") < 0) ||
288  (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
289  {
290  return nntp_connect_error(adata);
291  }
292  if (!mutt_str_startswith(buf, "500"))
293  adata->hasXGTITLE = true;
294  }
295 
296  /* no OVER, trying XOVER */
297  if (!adata->hasOVER)
298  {
299  if ((mutt_socket_send(conn, "XOVER\r\n") < 0) ||
300  (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
301  {
302  return nntp_connect_error(adata);
303  }
304  if (!mutt_str_startswith(buf, "500"))
305  adata->hasXOVER = true;
306  }
307 
308  /* trying LIST OVERVIEW.FMT */
309  if (adata->hasOVER || adata->hasXOVER)
310  {
311  if ((mutt_socket_send(conn, "LIST OVERVIEW.FMT\r\n") < 0) ||
312  (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
313  {
314  return nntp_connect_error(adata);
315  }
316  if (!mutt_str_startswith(buf, "215"))
318  else
319  {
320  bool cont = false;
321  size_t buflen = 2048, off = 0, b = 0;
322 
323  FREE(&adata->overview_fmt);
324  adata->overview_fmt = mutt_mem_malloc(buflen);
325 
326  while (true)
327  {
328  if ((buflen - off) < 1024)
329  {
330  buflen *= 2;
331  mutt_mem_realloc(&adata->overview_fmt, buflen);
332  }
333 
334  const int chunk = mutt_socket_readln_d(adata->overview_fmt + off,
335  buflen - off, conn, MUTT_SOCK_LOG_HDR);
336  if (chunk < 0)
337  {
338  FREE(&adata->overview_fmt);
339  return nntp_connect_error(adata);
340  }
341 
342  if (!cont && mutt_str_equal(".", adata->overview_fmt + off))
343  break;
344 
345  cont = (chunk >= (buflen - off));
346  off += strlen(adata->overview_fmt + off);
347  if (!cont)
348  {
349  if (adata->overview_fmt[b] == ':')
350  {
351  memmove(adata->overview_fmt + b, adata->overview_fmt + b + 1, off - b - 1);
352  adata->overview_fmt[off - 1] = ':';
353  }
354  char *colon = strchr(adata->overview_fmt + b, ':');
355  if (!colon)
356  adata->overview_fmt[off++] = ':';
357  else if (strcmp(colon + 1, "full") != 0)
358  off = colon + 1 - adata->overview_fmt;
359  if (strcasecmp(adata->overview_fmt + b, "Bytes:") == 0)
360  {
361  size_t len = strlen(adata->overview_fmt + b);
362  mutt_str_copy(adata->overview_fmt + b, "Content-Length:", len + 1);
363  off = b + len;
364  }
365  adata->overview_fmt[off++] = '\0';
366  b = off;
367  }
368  }
369  adata->overview_fmt[off++] = '\0';
370  mutt_mem_realloc(&adata->overview_fmt, off);
371  }
372  }
373  return 0;
374 }
void * mutt_mem_malloc(size_t size)
Allocate memory on the heap.
Definition: memory.c:90
void mutt_mem_realloc(void *ptr, size_t size)
Resize a block of memory on the heap.
Definition: memory.c:114
const char * OverviewFmt
Definition: nntp.c:79
int mutt_socket_readln_d(char *buf, size_t buflen, struct Connection *conn, int dbg)
Read a line from a socket.
Definition: socket.c:246
#define MUTT_SOCK_LOG_HDR
Definition: socket.h:54
bool hasXOVER
Server supports XOVER command.
Definition: adata.h:46
char * overview_fmt
Definition: adata.h:54
bool hasXGTITLE
Server supports XGTITLE command.
Definition: adata.h:42
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ nntp_memchr()

static bool nntp_memchr ( char **  haystack,
char *  sentinel,
int  needle 
)
static

Look for a char in a binary buf, conveniently.

Parameters
haystack[in/out] input: start here, output: store address of hit
sentinelpoints just beyond (1 byte after) search area
needlethe character to search for
Return values
truefound and updated haystack
falsenot found

Definition at line 385 of file nntp.c.

386 {
387  char *start = *haystack;
388  size_t max_offset = sentinel - start;
389  void *vp = memchr(start, max_offset, needle);
390  if (!vp)
391  return false;
392  *haystack = vp;
393  return true;
394 }
+ Here is the caller graph for this function:

◆ nntp_log_binbuf()

static void nntp_log_binbuf ( const char *  buf,
size_t  len,
const char *  pfx,
int  dbg 
)
static

Log a buffer possibly containing NUL bytes.

Parameters
bufsource buffer
lenhow many bytes from buf
pfxlogging prefix (protocol etc.)
dbgwhich loglevel does message belong

Definition at line 403 of file nntp.c.

404 {
405  char tmp[1024];
406  char *p = tmp;
407  char *sentinel = tmp + len;
408 
409  const short c_debug_level = cs_subset_number(NeoMutt->sub, "debug_level");
410  if (c_debug_level < dbg)
411  return;
412  memcpy(tmp, buf, len);
413  tmp[len] = '\0';
414  while (nntp_memchr(&p, sentinel, '\0'))
415  *p = '.';
416  mutt_debug(dbg, "%s> %s\n", pfx, tmp);
417 }
#define mutt_debug(LEVEL,...)
Definition: logging.h:84
short cs_subset_number(const struct ConfigSubset *sub, const char *name)
Get a number config item by name.
Definition: helpers.c:169
static bool nntp_memchr(char **haystack, char *sentinel, int needle)
Look for a char in a binary buf, conveniently.
Definition: nntp.c:385
Container for Accounts, Notifications.
Definition: neomutt.h:37
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:39
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ nntp_auth()

static int nntp_auth ( struct NntpAccountData adata)
static

Get login, password and authenticate.

Parameters
adataNNTP server
Return values
0Success
-1Failure

Definition at line 426 of file nntp.c.

427 {
428  struct Connection *conn = adata->conn;
429  char buf[1024];
430  char authenticators[1024] = "USER";
431  char *method = NULL, *a = NULL, *p = NULL;
432  unsigned char flags = conn->account.flags;
433 
434  while (true)
435  {
436  /* get login and password */
437  if ((mutt_account_getuser(&conn->account) < 0) || (conn->account.user[0] == '\0') ||
438  (mutt_account_getpass(&conn->account) < 0) || (conn->account.pass[0] == '\0'))
439  {
440  break;
441  }
442 
443  /* get list of authenticators */
444  const char *const c_nntp_authenticators = cs_subset_string(NeoMutt->sub, "nntp_authenticators");
445  if (c_nntp_authenticators)
446  mutt_str_copy(authenticators, c_nntp_authenticators, sizeof(authenticators));
447  else if (adata->hasCAPABILITIES)
448  {
449  mutt_str_copy(authenticators, adata->authenticators, sizeof(authenticators));
450  p = authenticators;
451  while (*p)
452  {
453  if (*p == ' ')
454  *p = ':';
455  p++;
456  }
457  }
458  p = authenticators;
459  while (*p)
460  {
461  *p = toupper(*p);
462  p++;
463  }
464 
465  mutt_debug(LL_DEBUG1, "available methods: %s\n", adata->authenticators);
466  a = authenticators;
467  while (true)
468  {
469  if (!a)
470  {
471  mutt_error(_("No authenticators available"));
472  break;
473  }
474 
475  method = a;
476  a = strchr(a, ':');
477  if (a)
478  *a++ = '\0';
479 
480  /* check authenticator */
481  if (adata->hasCAPABILITIES)
482  {
483  if (!adata->authenticators)
484  continue;
485  const char *m = mutt_istr_find(adata->authenticators, method);
486  if (!m)
487  continue;
488  if ((m > adata->authenticators) && (*(m - 1) != ' '))
489  continue;
490  m += strlen(method);
491  if ((*m != '\0') && (*m != ' '))
492  continue;
493  }
494  mutt_debug(LL_DEBUG1, "trying method %s\n", method);
495 
496  /* AUTHINFO USER authentication */
497  if (strcmp(method, "USER") == 0)
498  {
499  // L10N: (%s) is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
500  mutt_message(_("Authenticating (%s)..."), method);
501  snprintf(buf, sizeof(buf), "AUTHINFO USER %s\r\n", conn->account.user);
502  if ((mutt_socket_send(conn, buf) < 0) ||
503  (mutt_socket_readln_d(buf, sizeof(buf), conn, MUTT_SOCK_LOG_FULL) < 0))
504  {
505  break;
506  }
507 
508  /* authenticated, password is not required */
509  if (mutt_str_startswith(buf, "281"))
510  return 0;
511 
512  /* username accepted, sending password */
513  if (mutt_str_startswith(buf, "381"))
514  {
515  mutt_debug(MUTT_SOCK_LOG_FULL, "%d> AUTHINFO PASS *\n", conn->fd);
516  snprintf(buf, sizeof(buf), "AUTHINFO PASS %s\r\n", conn->account.pass);
517  if ((mutt_socket_send_d(conn, buf, MUTT_SOCK_LOG_FULL) < 0) ||
518  (mutt_socket_readln_d(buf, sizeof(buf), conn, MUTT_SOCK_LOG_FULL) < 0))
519  {
520  break;
521  }
522 
523  /* authenticated */
524  if (mutt_str_startswith(buf, "281"))
525  return 0;
526  }
527 
528  /* server doesn't support AUTHINFO USER, trying next method */
529  if (*buf == '5')
530  continue;
531  }
532  else
533  {
534 #ifdef USE_SASL
535  sasl_conn_t *saslconn = NULL;
536  sasl_interact_t *interaction = NULL;
537  int rc;
538  char inbuf[1024] = { 0 };
539  const char *mech = NULL;
540  const char *client_out = NULL;
541  unsigned int client_len, len;
542 
543  if (mutt_sasl_client_new(conn, &saslconn) < 0)
544  {
545  mutt_debug(LL_DEBUG1, "error allocating SASL connection\n");
546  continue;
547  }
548 
549  while (true)
550  {
551  rc = sasl_client_start(saslconn, method, &interaction, &client_out,
552  &client_len, &mech);
553  if (rc != SASL_INTERACT)
554  break;
555  mutt_sasl_interact(interaction);
556  }
557  if ((rc != SASL_OK) && (rc != SASL_CONTINUE))
558  {
559  sasl_dispose(&saslconn);
560  mutt_debug(LL_DEBUG1, "error starting SASL authentication exchange\n");
561  continue;
562  }
563 
564  // L10N: (%s) is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
565  mutt_message(_("Authenticating (%s)..."), method);
566  snprintf(buf, sizeof(buf), "AUTHINFO SASL %s", method);
567 
568  /* looping protocol */
569  while ((rc == SASL_CONTINUE) || ((rc == SASL_OK) && client_len))
570  {
571  /* send out client response */
572  if (client_len)
573  {
574  nntp_log_binbuf(client_out, client_len, "SASL", MUTT_SOCK_LOG_FULL);
575  if (*buf != '\0')
576  mutt_str_cat(buf, sizeof(buf), " ");
577  len = strlen(buf);
578  if (sasl_encode64(client_out, client_len, buf + len,
579  sizeof(buf) - len, &len) != SASL_OK)
580  {
581  mutt_debug(LL_DEBUG1, "error base64-encoding client response\n");
582  break;
583  }
584  }
585 
586  mutt_str_cat(buf, sizeof(buf), "\r\n");
587  if (strchr(buf, ' '))
588  {
589  mutt_debug(MUTT_SOCK_LOG_CMD, "%d> AUTHINFO SASL %s%s\n", conn->fd,
590  method, client_len ? " sasl_data" : "");
591  }
592  else
593  mutt_debug(MUTT_SOCK_LOG_CMD, "%d> sasl_data\n", conn->fd);
594  client_len = 0;
595  if ((mutt_socket_send_d(conn, buf, MUTT_SOCK_LOG_FULL) < 0) ||
596  (mutt_socket_readln_d(inbuf, sizeof(inbuf), conn, MUTT_SOCK_LOG_FULL) < 0))
597  {
598  break;
599  }
600  if (!mutt_str_startswith(inbuf, "283 ") && !mutt_str_startswith(inbuf, "383 "))
601  {
602  mutt_debug(MUTT_SOCK_LOG_FULL, "%d< %s\n", conn->fd, inbuf);
603  break;
604  }
605  inbuf[3] = '\0';
606  mutt_debug(MUTT_SOCK_LOG_FULL, "%d< %s sasl_data\n", conn->fd, inbuf);
607 
608  if (strcmp("=", inbuf + 4) == 0)
609  len = 0;
610  else if (sasl_decode64(inbuf + 4, strlen(inbuf + 4), buf,
611  sizeof(buf) - 1, &len) != SASL_OK)
612  {
613  mutt_debug(LL_DEBUG1, "error base64-decoding server response\n");
614  break;
615  }
616  else
617  nntp_log_binbuf(buf, len, "SASL", MUTT_SOCK_LOG_FULL);
618 
619  while (true)
620  {
621  rc = sasl_client_step(saslconn, buf, len, &interaction, &client_out, &client_len);
622  if (rc != SASL_INTERACT)
623  break;
624  mutt_sasl_interact(interaction);
625  }
626  if (*inbuf != '3')
627  break;
628 
629  *buf = '\0';
630  } /* looping protocol */
631 
632  if ((rc == SASL_OK) && (client_len == 0) && (*inbuf == '2'))
633  {
634  mutt_sasl_setup_conn(conn, saslconn);
635  return 0;
636  }
637 
638  /* terminate SASL session */
639  sasl_dispose(&saslconn);
640  if (conn->fd < 0)
641  break;
642  if (mutt_str_startswith(inbuf, "383 "))
643  {
644  if ((mutt_socket_send(conn, "*\r\n") < 0) ||
645  (mutt_socket_readln(inbuf, sizeof(inbuf), conn) < 0))
646  {
647  break;
648  }
649  }
650 
651  /* server doesn't support AUTHINFO SASL, trying next method */
652  if (*inbuf == '5')
653  continue;
654 #else
655  continue;
656 #endif /* USE_SASL */
657  }
658 
659  // L10N: %s is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
660  mutt_error(_("%s authentication failed"), method);
661  break;
662  }
663  break;
664  }
665 
666  /* error */
667  adata->status = NNTP_BYE;
668  conn->account.flags = flags;
669  if (conn->fd < 0)
670  {
671  mutt_error(_("Server closed connection"));
672  }
673  else
674  mutt_socket_close(conn);
675  return -1;
676 }
int mutt_account_getpass(struct ConnAccount *cac)
Fetch password into ConnAccount, if necessary.
Definition: connaccount.c:128
int mutt_account_getuser(struct ConnAccount *cac)
Retrieve username into ConnAccount, if necessary.
Definition: connaccount.c:49
#define mutt_message(...)
Definition: logging.h:86
const char * cs_subset_string(const struct ConfigSubset *sub, const char *name)
Get a string config item by name.
Definition: helpers.c:317
@ LL_DEBUG1
Log at debug level 1.
Definition: logging.h:40
static void nntp_log_binbuf(const char *buf, size_t len, const char *pfx, int dbg)
Log a buffer possibly containing NUL bytes.
Definition: nntp.c:403
int mutt_sasl_interact(sasl_interact_t *interaction)
Perform an SASL interaction with the user.
Definition: sasl.c:694
int mutt_sasl_client_new(struct Connection *conn, sasl_conn_t **saslconn)
Wrapper for sasl_client_new()
Definition: sasl.c:599
void mutt_sasl_setup_conn(struct Connection *conn, sasl_conn_t *saslconn)
Set up an SASL connection.
Definition: sasl.c:731
#define MUTT_SOCK_LOG_FULL
Definition: socket.h:55
#define MUTT_SOCK_LOG_CMD
Definition: socket.h:53
#define mutt_socket_send_d(conn, buf, dbg)
Definition: socket.h:59
char user[128]
Username.
Definition: connaccount.h:56
char pass[256]
Password.
Definition: connaccount.h:57
MuttAccountFlags flags
Which fields are initialised, e.g. MUTT_ACCT_USER.
Definition: connaccount.h:60
char inbuf[1024]
Buffer for incoming traffic.
Definition: connection.h:52
struct ConnAccount account
Account details: username, password, etc.
Definition: connection.h:50
int fd
Socket file descriptor.
Definition: connection.h:54
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ nntp_query()

static int nntp_query ( struct NntpMboxData mdata,
char *  line,
size_t  linelen 
)
static

Send data from buffer and receive answer to same buffer.

Parameters
mdataNNTP Mailbox data
lineBuffer containing data
linelenLength of buffer
Return values
0Success
-1Failure

Definition at line 686 of file nntp.c.

687 {
688  struct NntpAccountData *adata = mdata->adata;
689  char buf[1024] = { 0 };
690 
691  if (adata->status == NNTP_BYE)
692  return -1;
693 
694  while (true)
695  {
696  if (adata->status == NNTP_OK)
697  {
698  int rc = 0;
699 
700  if (*line)
701  rc = mutt_socket_send(adata->conn, line);
702  else if (mdata->group)
703  {
704  snprintf(buf, sizeof(buf), "GROUP %s\r\n", mdata->group);
705  rc = mutt_socket_send(adata->conn, buf);
706  }
707  if (rc >= 0)
708  rc = mutt_socket_readln(buf, sizeof(buf), adata->conn);
709  if (rc >= 0)
710  break;
711  }
712 
713  /* reconnect */
714  while (true)
715  {
716  adata->status = NNTP_NONE;
717  if (nntp_open_connection(adata) == 0)
718  break;
719 
720  snprintf(buf, sizeof(buf), _("Connection to %s lost. Reconnect?"),
721  adata->conn->account.host);
722  if (mutt_yesorno(buf, MUTT_YES) != MUTT_YES)
723  {
724  adata->status = NNTP_BYE;
725  return -1;
726  }
727  }
728 
729  /* select newsgroup after reconnection */
730  if (mdata->group)
731  {
732  snprintf(buf, sizeof(buf), "GROUP %s\r\n", mdata->group);
733  if ((mutt_socket_send(adata->conn, buf) < 0) ||
734  (mutt_socket_readln(buf, sizeof(buf), adata->conn) < 0))
735  {
736  return nntp_connect_error(adata);
737  }
738  }
739  if (*line == '\0')
740  break;
741  }
742 
743  mutt_str_copy(line, buf, linelen);
744  return 0;
745 }
@ NNTP_OK
Connected to server.
Definition: private.h:45
int nntp_open_connection(struct NntpAccountData *adata)
Connect to server, authenticate and get capabilities.
Definition: nntp.c:1706
@ MUTT_YES
User answered 'Yes', or assume 'Yes'.
Definition: quad.h:39
enum QuadOption mutt_yesorno(const char *msg, enum QuadOption def)
Ask the user a Yes/No question.
Definition: question.c:194
void * adata
Private data (for Mailbox backends)
Definition: account.h:43
NNTP-specific Account data -.
Definition: adata.h:37
char * group
Name of newsgroup.
Definition: mdata.h:34
struct NntpAccountData * adata
Definition: mdata.h:47
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ nntp_fetch_lines()

static int nntp_fetch_lines ( struct NntpMboxData mdata,
char *  query,
size_t  qlen,
const char *  msg,
int(*)(char *, void *)  func,
void *  data 
)
static

Read lines, calling a callback function for each.

Parameters
mdataNNTP Mailbox data
queryQuery to match
qlenLength of query
msgProgress message (OPTIONAL)
funcCallback function
dataData for callback function
Return values
0Success
1Bad response (answer in query buffer)
-1Connection lost
-2Error in func(*line, *data)

This function calls func(*line, *data) for each received line, func(NULL, *data) if rewind(*data) needs, exits when fail or done:

Definition at line 763 of file nntp.c.

765 {
766  bool done = false;
767  int rc;
768 
769  while (!done)
770  {
771  char buf[1024];
772  char *line = NULL;
773  unsigned int lines = 0;
774  size_t off = 0;
775  struct Progress *progress = NULL;
776 
777  mutt_str_copy(buf, query, sizeof(buf));
778  if (nntp_query(mdata, buf, sizeof(buf)) < 0)
779  return -1;
780  if (buf[0] != '2')
781  {
782  mutt_str_copy(query, buf, qlen);
783  return 1;
784  }
785 
786  line = mutt_mem_malloc(sizeof(buf));
787  rc = 0;
788 
789  if (msg)
790  progress = progress_new(msg, MUTT_PROGRESS_READ, 0);
791 
792  while (true)
793  {
794  char *p = NULL;
795  int chunk = mutt_socket_readln_d(buf, sizeof(buf), mdata->adata->conn, MUTT_SOCK_LOG_FULL);
796  if (chunk < 0)
797  {
798  mdata->adata->status = NNTP_NONE;
799  break;
800  }
801 
802  p = buf;
803  if (!off && (buf[0] == '.'))
804  {
805  if (buf[1] == '\0')
806  {
807  done = true;
808  break;
809  }
810  if (buf[1] == '.')
811  p++;
812  }
813 
814  mutt_str_copy(line + off, p, sizeof(buf));
815 
816  if (chunk >= sizeof(buf))
817  off += strlen(p);
818  else
819  {
820  if (msg)
821  progress_update(progress, ++lines, -1);
822 
823  if ((rc == 0) && (func(line, data) < 0))
824  rc = -2;
825  off = 0;
826  }
827 
828  mutt_mem_realloc(&line, off + sizeof(buf));
829  }
830  FREE(&line);
831  func(NULL, data);
832  progress_free(&progress);
833  }
834 
835  return rc;
836 }
static int nntp_query(struct NntpMboxData *mdata, char *line, size_t linelen)
Send data from buffer and receive answer to same buffer.
Definition: nntp.c:686
@ MUTT_PROGRESS_READ
Progress tracks elements, according to $read_inc
Definition: lib.h:46
void progress_update(struct Progress *progress, size_t pos, int percent)
Update the state of the progress bar.
Definition: progress.c:176
void progress_free(struct Progress **ptr)
Free a Progress Bar.
Definition: progress.c:231
struct Progress * progress_new(const char *msg, enum ProgressType type, size_t size)
Create a new Progress Bar.
Definition: progress.c:251
A Progress Bar.
Definition: progress.c:48
char msg[1024]
Message to display.
Definition: progress.c:51
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ fetch_description()

static int fetch_description ( char *  line,
void *  data 
)
static

Parse newsgroup description.

Parameters
lineString to parse
dataNNTP Server
Return values
0Always

Definition at line 844 of file nntp.c.

845 {
846  if (!line)
847  return 0;
848 
849  struct NntpAccountData *adata = data;
850 
851  char *desc = strpbrk(line, " \t");
852  if (desc)
853  {
854  *desc++ = '\0';
855  desc += strspn(desc, " \t");
856  }
857  else
858  desc = strchr(line, '\0');
859 
861  if (mdata && !mutt_str_equal(desc, mdata->desc))
862  {
863  mutt_str_replace(&mdata->desc, desc);
864  mutt_debug(LL_DEBUG2, "group: %s, desc: %s\n", line, desc);
865  }
866  return 0;
867 }
void * mutt_hash_find(const struct HashTable *table, const char *strkey)
Find the HashElem data in a Hash Table element using a key.
Definition: hash.c:362
@ LL_DEBUG2
Log at debug level 2.
Definition: logging.h:41
void * mdata
Driver specific data.
Definition: mailbox.h:133
struct HashTable * groups_hash
Definition: adata.h:62
NNTP-specific Mailbox data -.
Definition: mdata.h:33
char * desc
Description of newsgroup.
Definition: mdata.h:35
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ get_description()

static int get_description ( struct NntpMboxData mdata,
const char *  wildmat,
const char *  msg 
)
static

Fetch newsgroups descriptions.

Parameters
mdataNNTP Mailbox data
wildmatString to match
msgProgress message
Return values
0Success
1Bad response (answer in query buffer)
-1Connection lost
-2Error

Definition at line 879 of file nntp.c.

880 {
881  char buf[256];
882  const char *cmd = NULL;
883 
884  /* get newsgroup description, if possible */
885  struct NntpAccountData *adata = mdata->adata;
886  if (!wildmat)
887  wildmat = mdata->group;
888  if (adata->hasLIST_NEWSGROUPS)
889  cmd = "LIST NEWSGROUPS";
890  else if (adata->hasXGTITLE)
891  cmd = "XGTITLE";
892  else
893  return 0;
894 
895  snprintf(buf, sizeof(buf), "%s %s\r\n", cmd, wildmat);
896  int rc = nntp_fetch_lines(mdata, buf, sizeof(buf), msg, fetch_description, adata);
897  if (rc > 0)
898  {
899  mutt_error("%s: %s", cmd, buf);
900  }
901  return rc;
902 }
static int nntp_fetch_lines(struct NntpMboxData *mdata, char *query, size_t qlen, const char *msg, int(*func)(char *, void *), void *data)
Read lines, calling a callback function for each.
Definition: nntp.c:763
static int fetch_description(char *line, void *data)
Parse newsgroup description.
Definition: nntp.c:844
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ nntp_parse_xref()

static void nntp_parse_xref ( struct Mailbox m,
struct Email e 
)
static

Parse cross-reference.

Parameters
mMailbox
eEmail

Update read flag and set article number if empty

Definition at line 911 of file nntp.c.

912 {
913  struct NntpMboxData *mdata = m->mdata;
914 
915  char *buf = mutt_str_dup(e->env->xref);
916  char *p = buf;
917  while (p)
918  {
919  anum_t anum;
920 
921  /* skip to next word */
922  p += strspn(p, " \t");
923  char *grp = p;
924 
925  /* skip to end of word */
926  p = strpbrk(p, " \t");
927  if (p)
928  *p++ = '\0';
929 
930  /* find colon */
931  char *colon = strchr(grp, ':');
932  if (!colon)
933  continue;
934  *colon++ = '\0';
935  if (sscanf(colon, ANUM, &anum) != 1)
936  continue;
937 
938  nntp_article_status(m, e, grp, anum);
939  if (!nntp_edata_get(e)->article_num && mutt_str_equal(mdata->group, grp))
940  nntp_edata_get(e)->article_num = anum;
941  }
942  FREE(&buf);
943 }
void nntp_article_status(struct Mailbox *m, struct Email *e, char *group, anum_t anum)
Get status of articles from .newsrc.
Definition: newsrc.c:1233
struct NntpEmailData * nntp_edata_get(struct Email *e)
Get the private data for this Email.
Definition: edata.c:58
#define ANUM
Definition: lib.h:61
#define anum_t
Definition: lib.h:60
struct Envelope * env
Envelope information.
Definition: email.h:66
char * xref
List of cross-references.
Definition: envelope.h:80
anum_t article_num
NNTP article number.
Definition: edata.h:36
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ fetch_tempfile()

static int fetch_tempfile ( char *  line,
void *  data 
)
static

Write line to temporary file.

Parameters
lineText to write
dataFILE pointer
Return values
0Success
-1Failure

Definition at line 952 of file nntp.c.

953 {
954  FILE *fp = data;
955 
956  if (!line)
957  rewind(fp);
958  else if ((fputs(line, fp) == EOF) || (fputc('\n', fp) == EOF))
959  return -1;
960  return 0;
961 }
+ Here is the caller graph for this function:

◆ fetch_numbers()

static int fetch_numbers ( char *  line,
void *  data 
)
static

Parse article number.

Parameters
lineArticle number
dataFetchCtx
Return values
0Always

Definition at line 969 of file nntp.c.

970 {
971  struct FetchCtx *fc = data;
972  anum_t anum;
973 
974  if (!line)
975  return 0;
976  if (sscanf(line, ANUM, &anum) != 1)
977  return 0;
978  if ((anum < fc->first) || (anum > fc->last))
979  return 0;
980  fc->messages[anum - fc->first] = 1;
981  return 0;
982 }
Keep track when getting data from a server.
Definition: nntp.c:92
anum_t first
Definition: nntp.c:94
anum_t last
Definition: nntp.c:95
unsigned char * messages
Definition: nntp.c:97
+ Here is the caller graph for this function:

◆ parse_overview_line()

static int parse_overview_line ( char *  line,
void *  data 
)
static

Parse overview line.

Parameters
lineString to parse
dataFetchCtx
Return values
0Success
-1Failure

Definition at line 991 of file nntp.c.

992 {
993  if (!line || !data)
994  return 0;
995 
996  struct FetchCtx *fc = data;
997  struct Mailbox *m = fc->mailbox;
998  if (!m)
999  return -1;
1000 
1001  struct NntpMboxData *mdata = m->mdata;
1002  struct Email *e = NULL;
1003  char *header = NULL, *field = NULL;
1004  bool save = true;
1005  anum_t anum;
1006 
1007  /* parse article number */
1008  field = strchr(line, '\t');
1009  if (field)
1010  *field++ = '\0';
1011  if (sscanf(line, ANUM, &anum) != 1)
1012  return 0;
1013  mutt_debug(LL_DEBUG2, "" ANUM "\n", anum);
1014 
1015  /* out of bounds */
1016  if ((anum < fc->first) || (anum > fc->last))
1017  return 0;
1018 
1019  /* not in LISTGROUP */
1020  if (!fc->messages[anum - fc->first])
1021  {
1022  /* progress */
1023  if (m->verbose)
1024  progress_update(fc->progress, anum - fc->first + 1, -1);
1025  return 0;
1026  }
1027 
1028  /* convert overview line to header */
1029  FILE *fp = mutt_file_mkstemp();
1030  if (!fp)
1031  return -1;
1032 
1033  header = mdata->adata->overview_fmt;
1034  while (field)
1035  {
1036  char *b = field;
1037 
1038  if (*header)
1039  {
1040  if (!strstr(header, ":full") && (fputs(header, fp) == EOF))
1041  {
1042  mutt_file_fclose(&fp);
1043  return -1;
1044  }
1045  header = strchr(header, '\0') + 1;
1046  }
1047 
1048  field = strchr(field, '\t');
1049  if (field)
1050  *field++ = '\0';
1051  if ((fputs(b, fp) == EOF) || (fputc('\n', fp) == EOF))
1052  {
1053  mutt_file_fclose(&fp);
1054  return -1;
1055  }
1056  }
1057  rewind(fp);
1058 
1059  /* allocate memory for headers */
1060  if (m->msg_count >= m->email_max)
1061  mx_alloc_memory(m);
1062 
1063  /* parse header */
1064  m->emails[m->msg_count] = email_new();
1065  e = m->emails[m->msg_count];
1066  e->env = mutt_rfc822_read_header(fp, e, false, false);
1067  e->env->newsgroups = mutt_str_dup(mdata->group);
1068  e->received = e->date_sent;
1069  mutt_file_fclose(&fp);
1070 
1071 #ifdef USE_HCACHE
1072  if (fc->hc)
1073  {
1074  char buf[16];
1075 
1076  /* try to replace with header from cache */
1077  snprintf(buf, sizeof(buf), "%u", anum);
1078  struct HCacheEntry hce = mutt_hcache_fetch(fc->hc, buf, strlen(buf), 0);
1079  if (hce.email)
1080  {
1081  mutt_debug(LL_DEBUG2, "mutt_hcache_fetch %s\n", buf);
1082  email_free(&e);
1083  e = hce.email;
1084  m->emails[m->msg_count] = e;
1085  e->edata = NULL;
1086  e->read = false;
1087  e->old = false;
1088 
1089  /* skip header marked as deleted in cache */
1090  if (e->deleted && !fc->restore)
1091  {
1092  if (mdata->bcache)
1093  {
1094  mutt_debug(LL_DEBUG2, "mutt_bcache_del %s\n", buf);
1095  mutt_bcache_del(mdata->bcache, buf);
1096  }
1097  save = false;
1098  }
1099  }
1100 
1101  /* not cached yet, store header */
1102  else
1103  {
1104  mutt_debug(LL_DEBUG2, "mutt_hcache_store %s\n", buf);
1105  mutt_hcache_store(fc->hc, buf, strlen(buf), e, 0);
1106  }
1107  }
1108 #endif
1109 
1110  if (save)
1111  {
1112  e->index = m->msg_count++;
1113  e->read = false;
1114  e->old = false;
1115  e->deleted = false;
1116  e->edata = nntp_edata_new();
1118  nntp_edata_get(e)->article_num = anum;
1119  if (fc->restore)
1120  e->changed = true;
1121  else
1122  {
1123  nntp_article_status(m, e, NULL, anum);
1124  if (!e->read)
1125  nntp_parse_xref(m, e);
1126  }
1127  if (anum > mdata->last_loaded)
1128  mdata->last_loaded = anum;
1129  }
1130  else
1131  email_free(&e);
1132 
1133  /* progress */
1134  if (m->verbose)
1135  progress_update(fc->progress, anum - fc->first + 1, -1);
1136  return 0;
1137 }
int mutt_bcache_del(struct BodyCache *bcache, const char *id)
Delete a file from the Body Cache.
Definition: bcache.c:265
struct Email * email_new(void)
Create a new Email.
Definition: email.c:78
void email_free(struct Email **ptr)
Free an Email.
Definition: email.c:44
int mutt_file_fclose(FILE **fp)
Close a FILE handle (and NULL the pointer)
Definition: file.c:152
#define mutt_file_mkstemp()
Definition: file.h:112
int mutt_hcache_store(struct HeaderCache *hc, const char *key, size_t keylen, struct Email *e, uint32_t uidvalidity)
Multiplexor for StoreOps::store.
Definition: hcache.c:548
struct HCacheEntry mutt_hcache_fetch(struct HeaderCache *hc, const char *key, size_t keylen, uint32_t uidvalidity)
Multiplexor for StoreOps::fetch.
Definition: hcache.c:456
void mx_alloc_memory(struct Mailbox *m)
Create storage for the emails.
Definition: mx.c:1219
struct NntpEmailData * nntp_edata_new(void)
Create a new NntpEmailData for an Email.
Definition: edata.c:48
void nntp_edata_free(void **ptr)
Free the private Email data - Implements Email::edata_free()
Definition: edata.c:38
static void nntp_parse_xref(struct Mailbox *m, struct Email *e)
Parse cross-reference.
Definition: nntp.c:911
struct Envelope * mutt_rfc822_read_header(FILE *fp, struct Email *e, bool user_hdrs, bool weed)
Parses an RFC822 header.
Definition: parse.c:1158
The envelope/body of an email.
Definition: email.h:37
bool read
Email is read.
Definition: email.h:48
void * edata
Driver-specific data.
Definition: email.h:72
bool old
Email is seen, but unread.
Definition: email.h:47
void(* edata_free)(void **ptr)
Free the private data attached to the Email.
Definition: email.h:87
bool changed
Email has been edited.
Definition: email.h:75
time_t date_sent
Time when the message was sent (UTC)
Definition: email.h:58
bool deleted
Email is deleted.
Definition: email.h:76
int index
The absolute (unsorted) message number.
Definition: email.h:110
time_t received
Time when the message was placed in the mailbox.
Definition: email.h:59
char * newsgroups
List of newsgroups.
Definition: envelope.h:79
struct HeaderCache * hc
Definition: nntp.c:99
struct Progress * progress
Definition: nntp.c:98
struct Mailbox * mailbox
Definition: nntp.c:93
bool restore
Definition: nntp.c:96
Wrapper for Email retrieved from the header cache.
Definition: lib.h:98
struct Email * email
Retrieved email.
Definition: lib.h:101
A mailbox.
Definition: mailbox.h:79
int msg_count
Total number of messages.
Definition: mailbox.h:88
int email_max
Number of pointers in emails.
Definition: mailbox.h:97
struct Email ** emails
Array of Emails.
Definition: mailbox.h:96
bool verbose
Display status messages?
Definition: mailbox.h:115
struct BodyCache * bcache
Definition: mdata.h:49
anum_t last_loaded
Definition: mdata.h:38
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ nntp_fetch_headers()

static int nntp_fetch_headers ( struct Mailbox m,
void *  hc,
anum_t  first,
anum_t  last,
bool  restore 
)
static

Fetch headers.

Parameters
mMailbox
hcHeader cache
firstNumber of first header to fetch
lastNumber of last header to fetch
restoreRestore message listed as deleted
Return values
0Success
-1Failure

Definition at line 1149 of file nntp.c.

1150 {
1151  if (!m)
1152  return -1;
1153 
1154  struct NntpMboxData *mdata = m->mdata;
1155  struct FetchCtx fc = { 0 };
1156  struct Email *e = NULL;
1157  char buf[8192];
1158  int rc = 0;
1159  anum_t current;
1160  anum_t first_over = first;
1161 
1162  /* if empty group or nothing to do */
1163  if (!last || (first > last))
1164  return 0;
1165 
1166  /* init fetch context */
1167  fc.mailbox = m;
1168  fc.first = first;
1169  fc.last = last;
1170  fc.restore = restore;
1171  fc.messages = mutt_mem_calloc(last - first + 1, sizeof(unsigned char));
1172  if (!fc.messages)
1173  return -1;
1174  fc.hc = hc;
1175 
1176  /* fetch list of articles */
1177  const bool c_nntp_listgroup = cs_subset_bool(NeoMutt->sub, "nntp_listgroup");
1178  if (c_nntp_listgroup && mdata->adata->hasLISTGROUP && !mdata->deleted)
1179  {
1180  if (m->verbose)
1181  mutt_message(_("Fetching list of articles..."));
1182  if (mdata->adata->hasLISTGROUPrange)
1183  snprintf(buf, sizeof(buf), "LISTGROUP %s %u-%u\r\n", mdata->group, first, last);
1184  else
1185  snprintf(buf, sizeof(buf), "LISTGROUP %s\r\n", mdata->group);
1186  rc = nntp_fetch_lines(mdata, buf, sizeof(buf), NULL, fetch_numbers, &fc);
1187  if (rc > 0)
1188  {
1189  mutt_error("LISTGROUP: %s", buf);
1190  }
1191  if (rc == 0)
1192  {
1193  for (current = first; (current <= last) && (rc == 0); current++)
1194  {
1195  if (fc.messages[current - first])
1196  continue;
1197 
1198  snprintf(buf, sizeof(buf), "%u", current);
1199  if (mdata->bcache)
1200  {
1201  mutt_debug(LL_DEBUG2, "#1 mutt_bcache_del %s\n", buf);
1202  mutt_bcache_del(mdata->bcache, buf);
1203  }
1204 
1205 #ifdef USE_HCACHE
1206  if (fc.hc)
1207  {
1208  mutt_debug(LL_DEBUG2, "mutt_hcache_delete_record %s\n", buf);
1209  mutt_hcache_delete_record(fc.hc, buf, strlen(buf));
1210  }
1211 #endif
1212  }
1213  }
1214  }
1215  else
1216  {
1217  for (current = first; current <= last; current++)
1218  fc.messages[current - first] = 1;
1219  }
1220 
1221  /* fetching header from cache or server, or fallback to fetch overview */
1222  if (m->verbose)
1223  {
1224  fc.progress = progress_new(_("Fetching message headers..."),
1225  MUTT_PROGRESS_READ, last - first + 1);
1226  }
1227  for (current = first; (current <= last) && (rc == 0); current++)
1228  {
1229  if (m->verbose)
1230  progress_update(fc.progress, current - first + 1, -1);
1231 
1232 #ifdef USE_HCACHE
1233  snprintf(buf, sizeof(buf), "%u", current);
1234 #endif
1235 
1236  /* delete header from cache that does not exist on server */
1237  if (!fc.messages[current - first])
1238  continue;
1239 
1240  /* allocate memory for headers */
1241  if (m->msg_count >= m->email_max)
1242  mx_alloc_memory(m);
1243 
1244 #ifdef USE_HCACHE
1245  /* try to fetch header from cache */
1246  struct HCacheEntry hce = mutt_hcache_fetch(fc.hc, buf, strlen(buf), 0);
1247  if (hce.email)
1248  {
1249  mutt_debug(LL_DEBUG2, "mutt_hcache_fetch %s\n", buf);
1250  e = hce.email;
1251  m->emails[m->msg_count] = e;
1252  e->edata = NULL;
1253 
1254  /* skip header marked as deleted in cache */
1255  if (e->deleted && !restore)
1256  {
1257  email_free(&e);
1258  if (mdata->bcache)
1259  {
1260  mutt_debug(LL_DEBUG2, "#2 mutt_bcache_del %s\n", buf);
1261  mutt_bcache_del(mdata->bcache, buf);
1262  }
1263  continue;
1264  }
1265 
1266  e->read = false;
1267  e->old = false;
1268  }
1269  else
1270 #endif
1271  if (mdata->deleted)
1272  {
1273  /* don't try to fetch header from removed newsgroup */
1274  continue;
1275  }
1276 
1277  /* fallback to fetch overview */
1278  else if (mdata->adata->hasOVER || mdata->adata->hasXOVER)
1279  {
1280  if (c_nntp_listgroup && mdata->adata->hasLISTGROUP)
1281  break;
1282  else
1283  continue;
1284  }
1285 
1286  /* fetch header from server */
1287  else
1288  {
1289  FILE *fp = mutt_file_mkstemp();
1290  if (!fp)
1291  {
1292  mutt_perror(_("Can't create temporary file"));
1293  rc = -1;
1294  break;
1295  }
1296 
1297  snprintf(buf, sizeof(buf), "HEAD %u\r\n", current);
1298  rc = nntp_fetch_lines(mdata, buf, sizeof(buf), NULL, fetch_tempfile, fp);
1299  if (rc)
1300  {
1301  mutt_file_fclose(&fp);
1302  if (rc < 0)
1303  break;
1304 
1305  /* invalid response */
1306  if (!mutt_str_startswith(buf, "423"))
1307  {
1308  mutt_error("HEAD: %s", buf);
1309  break;
1310  }
1311 
1312  /* no such article */
1313  if (mdata->bcache)
1314  {
1315  snprintf(buf, sizeof(buf), "%u", current);
1316  mutt_debug(LL_DEBUG2, "#3 mutt_bcache_del %s\n", buf);
1317  mutt_bcache_del(mdata->bcache, buf);
1318  }
1319  rc = 0;
1320  continue;
1321  }
1322 
1323  /* parse header */
1324  m->emails[m->msg_count] = email_new();
1325  e = m->emails[m->msg_count];
1326  e->env = mutt_rfc822_read_header(fp, e, false, false);
1327  e->received = e->date_sent;
1328  mutt_file_fclose(&fp);
1329  }
1330 
1331  /* save header in context */
1332  e->index = m->msg_count++;
1333  e->read = false;
1334  e->old = false;
1335  e->deleted = false;
1336  e->edata = nntp_edata_new();
1338  nntp_edata_get(e)->article_num = current;
1339  if (restore)
1340  e->changed = true;
1341  else
1342  {
1343  nntp_article_status(m, e, NULL, nntp_edata_get(e)->article_num);
1344  if (!e->read)
1345  nntp_parse_xref(m, e);
1346  }
1347  if (current > mdata->last_loaded)
1348  mdata->last_loaded = current;
1349  first_over = current + 1;
1350  }
1351 
1352  if (!c_nntp_listgroup || !mdata->adata->hasLISTGROUP)
1353  current = first_over;
1354 
1355  /* fetch overview information */
1356  if ((current <= last) && (rc == 0) && !mdata->deleted)
1357  {
1358  char *cmd = mdata->adata->hasOVER ? "OVER" : "XOVER";
1359  snprintf(buf, sizeof(buf), "%s %u-%u\r\n", cmd, current, last);
1360  rc = nntp_fetch_lines(mdata, buf, sizeof(buf), NULL, parse_overview_line, &fc);
1361  if (rc > 0)
1362  {
1363  mutt_error("%s: %s", cmd, buf);
1364  }
1365  }
1366 
1367  FREE(&fc.messages);
1368  progress_free(&fc.progress);
1369  if (rc != 0)
1370  return -1;
1371  mutt_clear_error();
1372  return 0;
1373 }
#define mutt_perror(...)
Definition: logging.h:88
int mutt_hcache_delete_record(struct HeaderCache *hc, const char *key, size_t keylen)
Multiplexor for StoreOps::delete_record.
Definition: hcache.c:627
static struct Email * restore(const unsigned char *d)
Restore an Email from data retrieved from the cache.
Definition: hcache.c:143
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition: helpers.c:73
void * mutt_mem_calloc(size_t nmemb, size_t size)
Allocate zeroed memory on the heap.
Definition: memory.c:50
void mutt_clear_error(void)
Clear the message line (bottom line of screen)
Definition: mutt_logging.c:74
static int parse_overview_line(char *line, void *data)
Parse overview line.
Definition: nntp.c:991
static int fetch_tempfile(char *line, void *data)
Write line to temporary file.
Definition: nntp.c:952
static int fetch_numbers(char *line, void *data)
Parse article number.
Definition: nntp.c:969
bool deleted
Definition: mdata.h:44
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ nntp_group_poll()

static int nntp_group_poll ( struct NntpMboxData mdata,
bool  update_stat 
)
static

Check newsgroup for new articles.

Parameters
mdataNNTP Mailbox data
update_statUpdate the stats?
Return values
1New articles found
0No change
-1Lost connection

Definition at line 1383 of file nntp.c.

1384 {
1385  char buf[1024] = { 0 };
1386  anum_t count, first, last;
1387 
1388  /* use GROUP command to poll newsgroup */
1389  if (nntp_query(mdata, buf, sizeof(buf)) < 0)
1390  return -1;
1391  if (sscanf(buf, "211 " ANUM " " ANUM " " ANUM, &count, &first, &last) != 3)
1392  return 0;
1393  if ((first == mdata->first_message) && (last == mdata->last_message))
1394  return 0;
1395 
1396  /* articles have been renumbered */
1397  if (last < mdata->last_message)
1398  {
1399  mdata->last_cached = 0;
1400  if (mdata->newsrc_len)
1401  {
1402  mutt_mem_realloc(&mdata->newsrc_ent, sizeof(struct NewsrcEntry));
1403  mdata->newsrc_len = 1;
1404  mdata->newsrc_ent[0].first = 1;
1405  mdata->newsrc_ent[0].last = 0;
1406  }
1407  }
1408  mdata->first_message = first;
1409  mdata->last_message = last;
1410  if (!update_stat)
1411  return 1;
1412 
1413  /* update counters */
1414  else if (!last || (!mdata->newsrc_ent && !mdata->last_cached))
1415  mdata->unread = count;
1416  else
1417  nntp_group_unread_stat(mdata);
1418  return 1;
1419 }
void nntp_group_unread_stat(struct NntpMboxData *mdata)
Count number of unread articles using .newsrc data.
Definition: newsrc.c:132
An entry in a .newsrc (subscribed newsgroups)
Definition: lib.h:76
anum_t last
Last article number in run.
Definition: lib.h:78
anum_t first
First article number in run.
Definition: lib.h:77
anum_t last_cached
Definition: mdata.h:39
anum_t last_message
Definition: mdata.h:37
struct NewsrcEntry * newsrc_ent
Definition: mdata.h:46
anum_t unread
Definition: mdata.h:40
unsigned int newsrc_len
Definition: mdata.h:45
anum_t first_message
Definition: mdata.h:36
+ Here is the caller graph for this function:

◆ check_mailbox()

static enum MxStatus check_mailbox ( struct Mailbox m)
static

Check current newsgroup for new articles.

Parameters
mMailbox
Return values
enumMxStatus

Leave newsrc locked

Definition at line 1383 of file nntp.c.

1429 {
1430  if (!m)
1431  return MX_STATUS_ERROR;
1432 
1433  struct NntpMboxData *mdata = m->mdata;
1434  struct NntpAccountData *adata = mdata->adata;
1435  time_t now = mutt_date_epoch();
1436  enum MxStatus rc = MX_STATUS_OK;
1437  void *hc = NULL;
1438 
1439  const short c_nntp_poll = cs_subset_number(NeoMutt->sub, "nntp_poll");
1440  if (adata->check_time + c_nntp_poll > now)
1441  return MX_STATUS_OK;
1442 
1443  mutt_message(_("Checking for new messages..."));
1444  if (nntp_newsrc_parse(adata) < 0)
1445  return MX_STATUS_ERROR;
1446 
1447  adata->check_time = now;
1448  int rc2 = nntp_group_poll(mdata, false);
1449  if (rc2 < 0)
1450  {
1452  return -1;
1453  }
1454  if (rc2 != 0)
1456 
1457  /* articles have been renumbered, remove all headers */
1458  if (mdata->last_message < mdata->last_loaded)
1459  {
1460  for (int i = 0; i < m->msg_count; i++)
1461  email_free(&m->emails[i]);
1462  m->msg_count = 0;
1463  m->msg_tagged = 0;
1464 
1465  if (mdata->last_message < mdata->last_loaded)
1466  {
1467  mdata->last_loaded = mdata->first_message - 1;
1468  const short c_nntp_context = cs_subset_number(NeoMutt->sub, "nntp_context");
1469  if (c_nntp_context && (mdata->last_message - mdata->last_loaded > c_nntp_context))
1470  mdata->last_loaded = mdata->last_message - c_nntp_context;
1471  }
1472  rc = MX_STATUS_REOPENED;
1473  }
1474 
1475  /* .newsrc has been externally modified */
1476  if (adata->newsrc_modified)
1477  {
1478 #ifdef USE_HCACHE
1479  unsigned char *messages = NULL;
1480  char buf[16];
1481  struct Email *e = NULL;
1482  anum_t first = mdata->first_message;
1483 
1484  const short c_nntp_context = cs_subset_number(NeoMutt->sub, "nntp_context");
1485  if (c_nntp_context && (mdata->last_message - first + 1 > c_nntp_context))
1486  first = mdata->last_message - c_nntp_context + 1;
1487  messages = mutt_mem_calloc(mdata->last_loaded - first + 1, sizeof(unsigned char));
1488  hc = nntp_hcache_open(mdata);
1489  nntp_hcache_update(mdata, hc);
1490 #endif
1491 
1492  /* update flags according to .newsrc */
1493  int j = 0;
1494  for (int i = 0; i < m->msg_count; i++)
1495  {
1496  if (!m->emails[i])
1497  continue;
1498  bool flagged = false;
1499  anum_t anum = nntp_edata_get(m->emails[i])->article_num;
1500 
1501 #ifdef USE_HCACHE
1502  /* check hcache for flagged and deleted flags */
1503  if (hc)
1504  {
1505  if ((anum >= first) && (anum <= mdata->last_loaded))
1506  messages[anum - first] = 1;
1507 
1508  snprintf(buf, sizeof(buf), "%u", anum);
1509  struct HCacheEntry hce = mutt_hcache_fetch(hc, buf, strlen(buf), 0);
1510  if (hce.email)
1511  {
1512  bool deleted;
1513 
1514  mutt_debug(LL_DEBUG2, "#1 mutt_hcache_fetch %s\n", buf);
1515  e = hce.email;
1516  e->edata = NULL;
1517  deleted = e->deleted;
1518  flagged = e->flagged;
1519  email_free(&e);
1520 
1521  /* header marked as deleted, removing from context */
1522  if (deleted)
1523  {
1524  mutt_set_flag(m, m->emails[i], MUTT_TAG, false);
1525  email_free(&m->emails[i]);
1526  continue;
1527  }
1528  }
1529  }
1530 #endif
1531 
1532  if (!m->emails[i]->changed)
1533  {
1534  m->emails[i]->flagged = flagged;
1535  m->emails[i]->read = false;
1536  m->emails[i]->old = false;
1537  nntp_article_status(m, m->emails[i], NULL, anum);
1538  if (!m->emails[i]->read)
1539  nntp_parse_xref(m, m->emails[i]);
1540  }
1541  m->emails[j++] = m->emails[i];
1542  }
1543 
1544 #ifdef USE_HCACHE
1545  m->msg_count = j;
1546 
1547  /* restore headers without "deleted" flag */
1548  for (anum_t anum = first; anum <= mdata->last_loaded; anum++)
1549  {
1550  if (messages[anum - first])
1551  continue;
1552 
1553  snprintf(buf, sizeof(buf), "%u", anum);
1554  struct HCacheEntry hce = mutt_hcache_fetch(hc, buf, strlen(buf), 0);
1555  if (hce.email)
1556  {
1557  mutt_debug(LL_DEBUG2, "#2 mutt_hcache_fetch %s\n", buf);
1558  if (m->msg_count >= m->email_max)
1559  mx_alloc_memory(m);
1560 
1561  e = hce.email;
1562  m->emails[m->msg_count] = e;
1563  e->edata = NULL;
1564  if (e->deleted)
1565  {
1566  email_free(&e);
1567  if (mdata->bcache)
1568  {
1569  mutt_debug(LL_DEBUG2, "mutt_bcache_del %s\n", buf);
1570  mutt_bcache_del(mdata->bcache, buf);
1571  }
1572  continue;
1573  }
1574 
1575  m->msg_count++;
1576  e->read = false;
1577  e->old = false;
1578  e->edata = nntp_edata_new();
1580  nntp_edata_get(e)->article_num = anum;
1581  nntp_article_status(m, e, NULL, anum);
1582  if (!e->read)
1583  nntp_parse_xref(m, e);
1584  }
1585  }
1586  FREE(&messages);
1587 #endif
1588 
1589  adata->newsrc_modified = false;
1590  rc = MX_STATUS_REOPENED;
1591  }
1592 
1593  /* some headers were removed, context must be updated */
1594  if (rc == MX_STATUS_REOPENED)
1596 
1597  /* fetch headers of new articles */
1598  if (mdata->last_message > mdata->last_loaded)
1599  {
1600  int oldmsgcount = m->msg_count;
1601  bool verbose = m->verbose;
1602  m->verbose = false;
1603 #ifdef USE_HCACHE
1604  if (!hc)
1605  {
1606  hc = nntp_hcache_open(mdata);
1607  nntp_hcache_update(mdata, hc);
1608  }
1609 #endif
1610  int old_msg_count = m->msg_count;
1611  rc2 = nntp_fetch_headers(m, hc, mdata->last_loaded + 1, mdata->last_message, false);
1612  m->verbose = verbose;
1613  if (rc2 == 0)
1614  {
1615  if (m->msg_count > old_msg_count)
1617  mdata->last_loaded = mdata->last_message;
1618  }
1619  if ((rc == MX_STATUS_OK) && (m->msg_count > oldmsgcount))
1620  rc = MX_STATUS_NEW_MAIL;
1621  }
1622 
1623 #ifdef USE_HCACHE
1624  mutt_hcache_close(hc);
1625 #endif
1626  if (rc != MX_STATUS_OK)
1627  nntp_newsrc_close(adata);
1628  mutt_clear_error();
1629  return rc;
1630 }
time_t mutt_date_epoch(void)
Return the number of seconds since the Unix epoch.
Definition: date.c:427
void mutt_hcache_close(struct HeaderCache *hc)
Multiplexor for StoreOps::close.
Definition: hcache.c:432
void mailbox_changed(struct Mailbox *m, enum NotifyMailbox action)
Notify observers of a change to a Mailbox.
Definition: mailbox.c:222
@ NT_MAILBOX_INVALID
Email list was changed.
Definition: mailbox.h:177
@ MUTT_TAG
Tagged messages.
Definition: mutt.h:99
MxStatus
Return values from mbox_check(), mbox_check_stats(), mbox_snc(), and mbox_close()
Definition: mxapi.h:84
@ MX_STATUS_ERROR
An error occurred.
Definition: mxapi.h:85
@ MX_STATUS_OK
No changes.
Definition: mxapi.h:86
@ MX_STATUS_REOPENED
Mailbox was reopened.
Definition: mxapi.h:89
@ MX_STATUS_NEW_MAIL
New mail received in Mailbox.
Definition: mxapi.h:87
void nntp_hcache_update(struct NntpMboxData *mdata, struct HeaderCache *hc)
Remove stale cached headers.
Definition: newsrc.c:735
int nntp_active_save_cache(struct NntpAccountData *adata)
Save list of all newsgroups to cache.
Definition: newsrc.c:652
struct HeaderCache * nntp_hcache_open(struct NntpMboxData *mdata)
Open newsgroup hcache.
Definition: newsrc.c:711
int nntp_newsrc_parse(struct NntpAccountData *adata)
Parse .newsrc file.
Definition: newsrc.c:162
void nntp_newsrc_close(struct NntpAccountData *adata)
Unlock and close .newsrc file.
Definition: newsrc.c:118
static int nntp_group_poll(struct NntpMboxData *mdata, bool update_stat)
Check newsgroup for new articles.
Definition: nntp.c:1383
static int nntp_fetch_headers(struct Mailbox *m, void *hc, anum_t first, anum_t last, bool restore)
Fetch headers.
Definition: nntp.c:1149
#define mutt_set_flag(m, e, flag, bf)
Definition: protos.h:64
bool flagged
Marked important?
Definition: email.h:45
int msg_tagged
How many messages are tagged?
Definition: mailbox.h:94
bool newsrc_modified
Definition: adata.h:50
+ Here is the call graph for this function:

◆ nntp_date()

static int nntp_date ( struct NntpAccountData adata,
time_t *  now 
)
static

Get date and time from server.

Parameters
adataNNTP server
nowServer time
Return values
0Success
-1Failure

Definition at line 1639 of file nntp.c.

1640 {
1641  if (adata->hasDATE)
1642  {
1643  struct NntpMboxData mdata = { 0 };
1644  char buf[1024];
1645  struct tm tm = { 0 };
1646 
1647  mdata.adata = adata;
1648  mdata.group = NULL;
1649  mutt_str_copy(buf, "DATE\r\n", sizeof(buf));
1650  if (nntp_query(&mdata, buf, sizeof(buf)) < 0)
1651  return -1;
1652 
1653  if (sscanf(buf, "111 %4d%2d%2d%2d%2d%2d%*s", &tm.tm_year, &tm.tm_mon,
1654  &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) == 6)
1655  {
1656  tm.tm_year -= 1900;
1657  tm.tm_mon--;
1658  *now = timegm(&tm);
1659  if (*now >= 0)
1660  {
1661  mutt_debug(LL_DEBUG1, "server time is %lu\n", *now);
1662  return 0;
1663  }
1664  }
1665  }
1666  *now = mutt_date_epoch();
1667  return 0;
1668 }
time_t timegm(struct tm *tm)
Convert struct tm to time_t seconds since epoch.
Definition: timegm.c:69
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ fetch_children()

static int fetch_children ( char *  line,
void *  data 
)
static

Parse XPAT line.

Parameters
lineString to parse
dataChildCtx
Return values
0Always

Definition at line 1676 of file nntp.c.

1677 {
1678  struct ChildCtx *cc = data;
1679  anum_t anum;
1680 
1681  if (!line || (sscanf(line, ANUM, &anum) != 1))
1682  return 0;
1683  for (unsigned int i = 0; i < cc->mailbox->msg_count; i++)
1684  {
1685  struct Email *e = cc->mailbox->emails[i];
1686  if (!e)
1687  break;
1688  if (nntp_edata_get(e)->article_num == anum)
1689  return 0;
1690  }
1691  if (cc->num >= cc->max)
1692  {
1693  cc->max *= 2;
1694  mutt_mem_realloc(&cc->child, sizeof(anum_t) * cc->max);
1695  }
1696  cc->child[cc->num++] = anum;
1697  return 0;
1698 }
Keep track of the children of an article.
Definition: nntp.c:106
anum_t * child
Definition: nntp.c:110
struct Mailbox * mailbox
Definition: nntp.c:107
unsigned int max
Definition: nntp.c:109
unsigned int num
Definition: nntp.c:108
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ nntp_open_connection()

int nntp_open_connection ( struct NntpAccountData adata)

Connect to server, authenticate and get capabilities.

Parameters
adataNNTP server
Return values
0Success
-1Failure

Definition at line 1706 of file nntp.c.

1707 {
1708  struct Connection *conn = adata->conn;
1709  char buf[256];
1710  int cap;
1711  bool posting = false, auth = true;
1712 
1713  if (adata->status == NNTP_OK)
1714  return 0;
1715  if (adata->status == NNTP_BYE)
1716  return -1;
1717  adata->status = NNTP_NONE;
1718 
1719  if (mutt_socket_open(conn) < 0)
1720  return -1;
1721 
1722  if (mutt_socket_readln(buf, sizeof(buf), conn) < 0)
1723  return nntp_connect_error(adata);
1724 
1725  if (mutt_str_startswith(buf, "200"))
1726  posting = true;
1727  else if (!mutt_str_startswith(buf, "201"))
1728  {
1729  mutt_socket_close(conn);
1731  mutt_error("%s", buf);
1732  return -1;
1733  }
1734 
1735  /* get initial capabilities */
1736  cap = nntp_capabilities(adata);
1737  if (cap < 0)
1738  return -1;
1739 
1740  /* tell news server to switch to mode reader if it isn't so */
1741  if (cap > 0)
1742  {
1743  if ((mutt_socket_send(conn, "MODE READER\r\n") < 0) ||
1744  (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
1745  {
1746  return nntp_connect_error(adata);
1747  }
1748 
1749  if (mutt_str_startswith(buf, "200"))
1750  posting = true;
1751  else if (mutt_str_startswith(buf, "201"))
1752  posting = false;
1753  /* error if has capabilities, ignore result if no capabilities */
1754  else if (adata->hasCAPABILITIES)
1755  {
1756  mutt_socket_close(conn);
1757  mutt_error(_("Could not switch to reader mode"));
1758  return -1;
1759  }
1760 
1761  /* recheck capabilities after MODE READER */
1762  if (adata->hasCAPABILITIES)
1763  {
1764  cap = nntp_capabilities(adata);
1765  if (cap < 0)
1766  return -1;
1767  }
1768  }
1769 
1770  mutt_message(_("Connected to %s. %s"), conn->account.host,
1771  posting ? _("Posting is ok") : _("Posting is NOT ok"));
1772  mutt_sleep(1);
1773 
1774 #ifdef USE_SSL
1775  /* Attempt STARTTLS if available and desired. */
1776  const bool c_ssl_force_tls = cs_subset_bool(NeoMutt->sub, "ssl_force_tls");
1777  if ((adata->use_tls != 1) && (adata->hasSTARTTLS || c_ssl_force_tls))
1778  {
1779  if (adata->use_tls == 0)
1780  {
1781  const enum QuadOption c_ssl_starttls = cs_subset_quad(NeoMutt->sub, "ssl_starttls");
1782  adata->use_tls = c_ssl_force_tls ||
1783  (query_quadoption(c_ssl_starttls,
1784  _("Secure connection with TLS?")) == MUTT_YES) ?
1785  2 :
1786  1;
1787  }
1788  if (adata->use_tls == 2)
1789  {
1790  if ((mutt_socket_send(conn, "STARTTLS\r\n") < 0) ||
1791  (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
1792  {
1793  return nntp_connect_error(adata);
1794  }
1795  // Clear any data after the STARTTLS acknowledgement
1796  mutt_socket_empty(conn);
1797  if (!mutt_str_startswith(buf, "382"))
1798  {
1799  adata->use_tls = 0;
1800  mutt_error("STARTTLS: %s", buf);
1801  }
1802  else if (mutt_ssl_starttls(conn))
1803  {
1804  adata->use_tls = 0;
1805  adata->status = NNTP_NONE;
1806  mutt_socket_close(adata->conn);
1807  mutt_error(_("Could not negotiate TLS connection"));
1808  return -1;
1809  }
1810  else
1811  {
1812  /* recheck capabilities after STARTTLS */
1813  cap = nntp_capabilities(adata);
1814  if (cap < 0)
1815  return -1;
1816  }
1817  }
1818  }
1819 #endif
1820 
1821  /* authentication required? */
1822  if (conn->account.flags & MUTT_ACCT_USER)
1823  {
1824  if (!conn->account.user[0])
1825  auth = false;
1826  }
1827  else
1828  {
1829  if ((mutt_socket_send(conn, "STAT\r\n") < 0) ||
1830  (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
1831  {
1832  return nntp_connect_error(adata);
1833  }
1834  if (!mutt_str_startswith(buf, "480"))
1835  auth = false;
1836  }
1837 
1838  /* authenticate */
1839  if (auth && (nntp_auth(adata) < 0))
1840  return -1;
1841 
1842  /* get final capabilities after authentication */
1843  if (adata->hasCAPABILITIES && (auth || (cap > 0)))
1844  {
1845  cap = nntp_capabilities(adata);
1846  if (cap < 0)
1847  return -1;
1848  if (cap > 0)
1849  {
1850  mutt_socket_close(conn);
1851  mutt_error(_("Could not switch to reader mode"));
1852  return -1;
1853  }
1854  }
1855 
1856  /* attempt features */
1857  if (nntp_attempt_features(adata) < 0)
1858  return -1;
1859 
1860  adata->status = NNTP_OK;
1861  return 0;
1862 }
#define MUTT_ACCT_USER
User field has been set.
Definition: connaccount.h:44
int mutt_ssl_starttls(struct Connection *conn)
Negotiate TLS over an already opened connection.
Definition: gnutls.c:1143
enum QuadOption cs_subset_quad(const struct ConfigSubset *sub, const char *name)
Get a quad-value config item by name.
Definition: helpers.c:218
void mutt_str_remove_trailing_ws(char *s)
Trim trailing whitespace from a string.
Definition: string.c:613
void mutt_sleep(short s)
Sleep for a while.
Definition: muttlib.c:1454
static int nntp_auth(struct NntpAccountData *adata)
Get login, password and authenticate.
Definition: nntp.c:426
static int nntp_capabilities(struct NntpAccountData *adata)
Get capabilities.
Definition: nntp.c:140
static int nntp_attempt_features(struct NntpAccountData *adata)
Detect supported commands.
Definition: nntp.c:243
QuadOption
Possible values for a quad-option.
Definition: quad.h:36
enum QuadOption query_quadoption(enum QuadOption opt, const char *prompt)
Ask the user a quad-question.
Definition: question.c:386
void mutt_socket_empty(struct Connection *conn)
Clear out any queued data.
Definition: socket.c:313
int mutt_socket_open(struct Connection *conn)
Simple wrapper.
Definition: socket.c:76
char host[128]
Server to login to.
Definition: connaccount.h:54
unsigned int use_tls
Definition: adata.h:47
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ nntp_post()

int nntp_post ( struct Mailbox m,
const char *  msg 
)

Post article.

Parameters
mMailbox
msgMessage to post
Return values
0Success
-1Failure

Definition at line 1871 of file nntp.c.

1872 {
1873  struct NntpMboxData *mdata = NULL;
1874  struct NntpMboxData tmp_mdata = { 0 };
1875  char buf[1024];
1876 
1877  if (m && (m->type == MUTT_NNTP))
1878  mdata = m->mdata;
1879  else
1880  {
1881  const char *const c_news_server = cs_subset_string(NeoMutt->sub, "news_server");
1882  CurrentNewsSrv = nntp_select_server(m, c_news_server, false);
1883  if (!CurrentNewsSrv)
1884  return -1;
1885 
1886  mdata = &tmp_mdata;
1887  mdata->adata = CurrentNewsSrv;
1888  mdata->group = NULL;
1889  }
1890 
1891  FILE *fp = mutt_file_fopen(msg, "r");
1892  if (!fp)
1893  {
1894  mutt_perror(msg);
1895  return -1;
1896  }
1897 
1898  mutt_str_copy(buf, "POST\r\n", sizeof(buf));
1899  if (nntp_query(mdata, buf, sizeof(buf)) < 0)
1900  {
1901  mutt_file_fclose(&fp);
1902  return -1;
1903  }
1904  if (buf[0] != '3')
1905  {
1906  mutt_error(_("Can't post article: %s"), buf);
1907  mutt_file_fclose(&fp);
1908  return -1;
1909  }
1910 
1911  buf[0] = '.';
1912  buf[1] = '\0';
1913  while (fgets(buf + 1, sizeof(buf) - 2, fp))
1914  {
1915  size_t len = strlen(buf);
1916  if (buf[len - 1] == '\n')
1917  {
1918  buf[len - 1] = '\r';
1919  buf[len] = '\n';
1920  len++;
1921  buf[len] = '\0';
1922  }
1923  if (mutt_socket_send_d(mdata->adata->conn, (buf[1] == '.') ? buf : buf + 1,
1924  MUTT_SOCK_LOG_FULL) < 0)
1925  {
1926  mutt_file_fclose(&fp);
1927  return nntp_connect_error(mdata->adata);
1928  }
1929  }
1930  mutt_file_fclose(&fp);
1931 
1932  if (((buf[strlen(buf) - 1] != '\n') &&
1933  (mutt_socket_send_d(mdata->adata->conn, "\r\n", MUTT_SOCK_LOG_FULL) < 0)) ||
1934  (mutt_socket_send_d(mdata->adata->conn, ".\r\n", MUTT_SOCK_LOG_FULL) < 0) ||
1935  (mutt_socket_readln(buf, sizeof(buf), mdata->adata->conn) < 0))
1936  {
1937  return nntp_connect_error(mdata->adata);
1938  }
1939  if (buf[0] != '2')
1940  {
1941  mutt_error(_("Can't post article: %s"), buf);
1942  return -1;
1943  }
1944  return 0;
1945 }
FILE * mutt_file_fopen(const char *path, const char *mode)
Call fopen() safely.
Definition: file.c:618
@ MUTT_NNTP
'NNTP' (Usenet) Mailbox type
Definition: mailbox.h:49
struct NntpAccountData * nntp_select_server(struct Mailbox *m, const char *server, bool leave_lock)
Open a connection to an NNTP server.
Definition: newsrc.c:1018
struct NntpAccountData * CurrentNewsSrv
Current NNTP news server.
Definition: nntp.c:77
enum MailboxType type
Mailbox type.
Definition: mailbox.h:102
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ nntp_active_fetch()

int nntp_active_fetch ( struct NntpAccountData adata,
bool  mark_new 
)

Fetch list of all newsgroups from server.

Parameters
adataNNTP server
mark_newMark the groups as new
Return values
0Success
-1Failure

Definition at line 1954 of file nntp.c.

1955 {
1956  struct NntpMboxData tmp_mdata = { 0 };
1957  char msg[256];
1958  char buf[1024];
1959  unsigned int i;
1960  int rc;
1961 
1962  snprintf(msg, sizeof(msg), _("Loading list of groups from server %s..."),
1963  adata->conn->account.host);
1964  mutt_message(msg);
1965  if (nntp_date(adata, &adata->newgroups_time) < 0)
1966  return -1;
1967 
1968  tmp_mdata.adata = adata;
1969  tmp_mdata.group = NULL;
1970  i = adata->groups_num;
1971  mutt_str_copy(buf, "LIST\r\n", sizeof(buf));
1972  rc = nntp_fetch_lines(&tmp_mdata, buf, sizeof(buf), msg, nntp_add_group, adata);
1973  if (rc)
1974  {
1975  if (rc > 0)
1976  {
1977  mutt_error("LIST: %s", buf);
1978  }
1979  return -1;
1980  }
1981 
1982  if (mark_new)
1983  {
1984  for (; i < adata->groups_num; i++)
1985  {
1986  struct NntpMboxData *mdata = adata->groups_list[i];
1987  mdata->has_new_mail = true;
1988  }
1989  }
1990 
1991  for (i = 0; i < adata->groups_num; i++)
1992  {
1993  struct NntpMboxData *mdata = adata->groups_list[i];
1994 
1995  if (mdata && mdata->deleted && !mdata->newsrc_ent)
1996  {
1998  mutt_hash_delete(adata->groups_hash, mdata->group, NULL);
1999  adata->groups_list[i] = NULL;
2000  }
2001  }
2002 
2003  const bool c_nntp_load_description = cs_subset_bool(NeoMutt->sub, "nntp_load_description");
2004  if (c_nntp_load_description)
2005  rc = get_description(&tmp_mdata, "*", _("Loading descriptions..."));
2006 
2008  if (rc < 0)
2009  return -1;
2010  mutt_clear_error();
2011  return 0;
2012 }
void mutt_hash_delete(struct HashTable *table, const char *strkey, const void *data)
Remove an element from a Hash Table.
Definition: hash.c:427
void nntp_delete_group_cache(struct NntpMboxData *mdata)
Remove hcache and bcache of newsgroup.
Definition: newsrc.c:812
int nntp_add_group(char *line, void *data)
Parse newsgroup.
Definition: newsrc.c:577
static int nntp_date(struct NntpAccountData *adata, time_t *now)
Get date and time from server.
Definition: nntp.c:1639
static int get_description(struct NntpMboxData *mdata, const char *wildmat, const char *msg)
Fetch newsgroups descriptions.
Definition: nntp.c:879
time_t newgroups_time
Definition: adata.h:57
unsigned int groups_num
Definition: adata.h:59
void ** groups_list
Definition: adata.h:61
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ nntp_check_new_groups()

int nntp_check_new_groups ( struct Mailbox m,
struct NntpAccountData adata 
)

Check for new groups/articles in subscribed groups.

Parameters
mMailbox
adataNNTP server
Return values
1New groups found
0No new groups
-1Error

Definition at line 2022 of file nntp.c.

2023 {
2024  struct NntpMboxData tmp_mdata = { 0 };
2025  time_t now;
2026  char buf[1024];
2027  char *msg = _("Checking for new newsgroups...");
2028  unsigned int i;
2029  int rc, update_active = false;
2030 
2031  if (!adata || !adata->newgroups_time)
2032  return -1;
2033 
2034  /* check subscribed newsgroups for new articles */
2035  const bool c_show_new_news = cs_subset_bool(NeoMutt->sub, "show_new_news");
2036  if (c_show_new_news)
2037  {
2038  mutt_message(_("Checking for new messages..."));
2039  for (i = 0; i < adata->groups_num; i++)
2040  {
2041  struct NntpMboxData *mdata = adata->groups_list[i];
2042 
2043  if (mdata && mdata->subscribed)
2044  {
2045  rc = nntp_group_poll(mdata, true);
2046  if (rc < 0)
2047  return -1;
2048  if (rc > 0)
2049  update_active = true;
2050  }
2051  }
2052  }
2053  else if (adata->newgroups_time)
2054  return 0;
2055 
2056  /* get list of new groups */
2057  mutt_message(msg);
2058  if (nntp_date(adata, &now) < 0)
2059  return -1;
2060  tmp_mdata.adata = adata;
2061  if (m && m->mdata)
2062  tmp_mdata.group = ((struct NntpMboxData *) m->mdata)->group;
2063  else
2064  tmp_mdata.group = NULL;
2065  i = adata->groups_num;
2066  struct tm tm = mutt_date_gmtime(adata->newgroups_time);
2067  snprintf(buf, sizeof(buf), "NEWGROUPS %02d%02d%02d %02d%02d%02d GMT\r\n",
2068  tm.tm_year % 100, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
2069  rc = nntp_fetch_lines(&tmp_mdata, buf, sizeof(buf), msg, nntp_add_group, adata);
2070  if (rc)
2071  {
2072  if (rc > 0)
2073  {
2074  mutt_error("NEWGROUPS: %s", buf);
2075  }
2076  return -1;
2077  }
2078 
2079  /* new groups found */
2080  rc = 0;
2081  if (adata->groups_num != i)
2082  {
2083  int groups_num = i;
2084 
2085  adata->newgroups_time = now;
2086  for (; i < adata->groups_num; i++)
2087  {
2088  struct NntpMboxData *mdata = adata->groups_list[i];
2089  mdata->has_new_mail = true;
2090  }
2091 
2092  /* loading descriptions */
2093  const bool c_nntp_load_description = cs_subset_bool(NeoMutt->sub, "nntp_load_description");
2094  if (c_nntp_load_description)
2095  {
2096  unsigned int count = 0;
2097  struct Progress *progress = progress_new(_("Loading descriptions..."), MUTT_PROGRESS_READ,
2098  adata->groups_num - i);
2099 
2100  for (i = groups_num; i < adata->groups_num; i++)
2101  {
2102  struct NntpMboxData *mdata = adata->groups_list[i];
2103 
2104  if (get_description(mdata, NULL, NULL) < 0)
2105  {
2106  progress_free(&progress);
2107  return -1;
2108  }
2109  progress_update(progress, ++count, -1);
2110  }
2111  progress_free(&progress);
2112  }
2113  update_active = true;
2114  rc = 1;
2115  }
2116  if (update_active)
2118  mutt_clear_error();
2119  return rc;
2120 }
struct tm mutt_date_gmtime(time_t t)
Converts calendar time to a broken-down time structure expressed in UTC timezone.
Definition: date.c:672
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ nntp_check_msgid()

int nntp_check_msgid ( struct Mailbox m,
const char *  msgid 
)

Fetch article by Message-ID.

Parameters
mMailbox
msgidMessage ID
Return values
0Success
1No such article
-1Error

Definition at line 2130 of file nntp.c.

2131 {
2132  if (!m)
2133  return -1;
2134 
2135  struct NntpMboxData *mdata = m->mdata;
2136  char buf[1024];
2137 
2138  FILE *fp = mutt_file_mkstemp();
2139  if (!fp)
2140  {
2141  mutt_perror(_("Can't create temporary file"));
2142  return -1;
2143  }
2144 
2145  snprintf(buf, sizeof(buf), "HEAD %s\r\n", msgid);
2146  int rc = nntp_fetch_lines(mdata, buf, sizeof(buf), NULL, fetch_tempfile, fp);
2147  if (rc)
2148  {
2149  mutt_file_fclose(&fp);
2150  if (rc < 0)
2151  return -1;
2152  if (mutt_str_startswith(buf, "430"))
2153  return 1;
2154  mutt_error("HEAD: %s", buf);
2155  return -1;
2156  }
2157 
2158  /* parse header */
2159  if (m->msg_count == m->email_max)
2160  mx_alloc_memory(m);
2161  m->emails[m->msg_count] = email_new();
2162  struct Email *e = m->emails[m->msg_count];
2163  e->edata = nntp_edata_new();
2165  e->env = mutt_rfc822_read_header(fp, e, false, false);
2166  mutt_file_fclose(&fp);
2167 
2168  /* get article number */
2169  if (e->env->xref)
2170  nntp_parse_xref(m, e);
2171  else
2172  {
2173  snprintf(buf, sizeof(buf), "STAT %s\r\n", msgid);
2174  if (nntp_query(mdata, buf, sizeof(buf)) < 0)
2175  {
2176  email_free(&e);
2177  return -1;
2178  }
2179  sscanf(buf + 4, ANUM, &nntp_edata_get(e)->article_num);
2180  }
2181 
2182  /* reset flags */
2183  e->read = false;
2184  e->old = false;
2185  e->deleted = false;
2186  e->changed = true;
2187  e->received = e->date_sent;
2188  e->index = m->msg_count++;
2190  return 0;
2191 }
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ nntp_check_children()

int nntp_check_children ( struct Mailbox m,
const char *  msgid 
)

Fetch children of article with the Message-ID.

Parameters
mMailbox
msgidMessage ID to find
Return values
0Success
-1Failure

Definition at line 2200 of file nntp.c.

2201 {
2202  if (!m)
2203  return -1;
2204 
2205  struct NntpMboxData *mdata = m->mdata;
2206  struct ChildCtx cc;
2207  char buf[256];
2208  int rc;
2209  void *hc = NULL;
2210 
2211  if (!mdata || !mdata->adata)
2212  return -1;
2213  if (mdata->first_message > mdata->last_loaded)
2214  return 0;
2215 
2216  /* init context */
2217  cc.mailbox = m;
2218  cc.num = 0;
2219  cc.max = 10;
2220  cc.child = mutt_mem_malloc(sizeof(anum_t) * cc.max);
2221 
2222  /* fetch numbers of child messages */
2223  snprintf(buf, sizeof(buf), "XPAT References %u-%u *%s*\r\n",
2224  mdata->first_message, mdata->last_loaded, msgid);
2225  rc = nntp_fetch_lines(mdata, buf, sizeof(buf), NULL, fetch_children, &cc);
2226  if (rc)
2227  {
2228  FREE(&cc.child);
2229  if (rc > 0)
2230  {
2231  if (!mutt_str_startswith(buf, "500"))
2232  mutt_error("XPAT: %s", buf);
2233  else
2234  {
2235  mutt_error(_("Unable to find child articles because server does not support XPAT command"));
2236  }
2237  }
2238  return -1;
2239  }
2240 
2241  /* fetch all found messages */
2242  bool verbose = m->verbose;
2243  m->verbose = false;
2244 #ifdef USE_HCACHE
2245  hc = nntp_hcache_open(mdata);
2246 #endif
2247  int old_msg_count = m->msg_count;
2248  for (int i = 0; i < cc.num; i++)
2249  {
2250  rc = nntp_fetch_headers(m, hc, cc.child[i], cc.child[i], true);
2251  if (rc < 0)
2252  break;
2253  }
2254  if (m->msg_count > old_msg_count)
2256 
2257 #ifdef USE_HCACHE
2258  mutt_hcache_close(hc);
2259 #endif
2260  m->verbose = verbose;
2261  FREE(&cc.child);
2262  return (rc < 0) ? -1 : 0;
2263 }
static int fetch_children(char *line, void *data)
Parse XPAT line.
Definition: nntp.c:1676
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

Variable Documentation

◆ CurrentNewsSrv

struct NntpAccountData* CurrentNewsSrv

Current NNTP news server.

Definition at line 77 of file nntp.c.

◆ OverviewFmt

const char* OverviewFmt
Initial value:
= "Subject:\0"
"From:\0"
"Date:\0"
"Message-ID:\0"
"References:\0"
"Content-Length:\0"
"Lines:\0"
"\0"

Definition at line 79 of file nntp.c.