NeoMutt  2021-02-05-89-gabe350
Teaching an old dog new tricks
DOXYGEN
nntp.c
Go to the documentation of this file.
1 
32 #include "config.h"
33 #include <ctype.h>
34 #include <limits.h>
35 #include <stdbool.h>
36 #include <stdint.h>
37 #include <stdio.h>
38 #include <string.h>
39 #include <strings.h>
40 #include <time.h>
41 #include <unistd.h>
42 #include "private.h"
43 #include "mutt/lib.h"
44 #include "config/lib.h"
45 #include "email/lib.h"
46 #include "core/lib.h"
47 #include "conn/lib.h"
48 #include "gui/lib.h"
49 #include "lib.h"
50 #include "bcache/lib.h"
51 #include "hcache/lib.h"
52 #include "ncrypt/lib.h"
53 #include "adata.h"
54 #include "edata.h"
55 #include "hook.h"
56 #include "init.h"
57 #include "mdata.h"
58 #include "mutt_globals.h"
59 #include "mutt_logging.h"
60 #include "mutt_parse.h"
61 #include "mutt_socket.h"
62 #include "muttlib.h"
63 #include "mx.h"
64 #include "progress.h"
65 #include "sort.h"
66 #ifdef USE_HCACHE
67 #include "protos.h"
68 #endif
69 #ifdef USE_SASL
70 #include <sasl/sasl.h>
71 #include <sasl/saslutil.h>
72 #endif
73 #if defined(USE_SSL) || defined(USE_HCACHE)
74 #include "mutt.h"
75 #endif
76 
77 struct stat;
78 
80 
81 const char *OverviewFmt = "Subject:\0"
82  "From:\0"
83  "Date:\0"
84  "Message-ID:\0"
85  "References:\0"
86  "Content-Length:\0"
87  "Lines:\0"
88  "\0";
89 
93 struct FetchCtx
94 {
95  struct Mailbox *mailbox;
98  bool restore;
99  unsigned char *messages;
101  struct HeaderCache *hc;
102 };
103 
107 struct ChildCtx
108 {
109  struct Mailbox *mailbox;
110  unsigned int num;
111  unsigned int max;
113 };
114 
118 void nntp_hashelem_free(int type, void *obj, intptr_t data)
119 {
120  nntp_mdata_free(&obj);
121 }
122 
128 static int nntp_connect_error(struct NntpAccountData *adata)
129 {
130  adata->status = NNTP_NONE;
131  mutt_error(_("Server closed connection"));
132  return -1;
133 }
134 
142 static int nntp_capabilities(struct NntpAccountData *adata)
143 {
144  struct Connection *conn = adata->conn;
145  bool mode_reader = false;
146  char buf[1024];
147  char authinfo[1024] = { 0 };
148 
149  adata->hasCAPABILITIES = false;
150  adata->hasSTARTTLS = false;
151  adata->hasDATE = false;
152  adata->hasLIST_NEWSGROUPS = false;
153  adata->hasLISTGROUP = false;
154  adata->hasLISTGROUPrange = false;
155  adata->hasOVER = false;
156  FREE(&adata->authenticators);
157 
158  if ((mutt_socket_send(conn, "CAPABILITIES\r\n") < 0) ||
159  (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
160  {
161  return nntp_connect_error(adata);
162  }
163 
164  /* no capabilities */
165  if (!mutt_str_startswith(buf, "101"))
166  return 1;
167  adata->hasCAPABILITIES = true;
168 
169  /* parse capabilities */
170  do
171  {
172  size_t plen = 0;
173  if (mutt_socket_readln(buf, sizeof(buf), conn) < 0)
174  return nntp_connect_error(adata);
175  if (mutt_str_equal("STARTTLS", buf))
176  adata->hasSTARTTLS = true;
177  else if (mutt_str_equal("MODE-READER", buf))
178  mode_reader = true;
179  else if (mutt_str_equal("READER", buf))
180  {
181  adata->hasDATE = true;
182  adata->hasLISTGROUP = true;
183  adata->hasLISTGROUPrange = true;
184  }
185  else if ((plen = mutt_str_startswith(buf, "AUTHINFO ")))
186  {
187  mutt_str_cat(buf, sizeof(buf), " ");
188  mutt_str_copy(authinfo, buf + plen - 1, sizeof(authinfo));
189  }
190 #ifdef USE_SASL
191  else if ((plen = mutt_str_startswith(buf, "SASL ")))
192  {
193  char *p = buf + plen;
194  while (*p == ' ')
195  p++;
196  adata->authenticators = mutt_str_dup(p);
197  }
198 #endif
199  else if (mutt_str_equal("OVER", buf))
200  adata->hasOVER = true;
201  else if (mutt_str_startswith(buf, "LIST "))
202  {
203  char *p = strstr(buf, " NEWSGROUPS");
204  if (p)
205  {
206  p += 11;
207  if ((*p == '\0') || (*p == ' '))
208  adata->hasLIST_NEWSGROUPS = true;
209  }
210  }
211  } while (!mutt_str_equal(".", buf));
212  *buf = '\0';
213 #ifdef USE_SASL
214  if (adata->authenticators && strcasestr(authinfo, " SASL "))
215  mutt_str_copy(buf, adata->authenticators, sizeof(buf));
216 #endif
217  if (strcasestr(authinfo, " USER "))
218  {
219  if (*buf != '\0')
220  mutt_str_cat(buf, sizeof(buf), " ");
221  mutt_str_cat(buf, sizeof(buf), "USER");
222  }
223  mutt_str_replace(&adata->authenticators, buf);
224 
225  /* current mode is reader */
226  if (adata->hasDATE)
227  return 0;
228 
229  /* server is mode-switching, need to switch to reader mode */
230  if (mode_reader)
231  return 1;
232 
233  mutt_socket_close(conn);
234  adata->status = NNTP_BYE;
235  mutt_error(_("Server doesn't support reader mode"));
236  return -1;
237 }
238 
245 static int nntp_attempt_features(struct NntpAccountData *adata)
246 {
247  struct Connection *conn = adata->conn;
248  char buf[1024];
249 
250  /* no CAPABILITIES, trying DATE, LISTGROUP, LIST NEWSGROUPS */
251  if (!adata->hasCAPABILITIES)
252  {
253  if ((mutt_socket_send(conn, "DATE\r\n") < 0) ||
254  (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
255  {
256  return nntp_connect_error(adata);
257  }
258  if (!mutt_str_startswith(buf, "500"))
259  adata->hasDATE = true;
260 
261  if ((mutt_socket_send(conn, "LISTGROUP\r\n") < 0) ||
262  (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
263  {
264  return nntp_connect_error(adata);
265  }
266  if (!mutt_str_startswith(buf, "500"))
267  adata->hasLISTGROUP = true;
268 
269  if ((mutt_socket_send(conn, "LIST NEWSGROUPS +\r\n") < 0) ||
270  (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
271  {
272  return nntp_connect_error(adata);
273  }
274  if (!mutt_str_startswith(buf, "500"))
275  adata->hasLIST_NEWSGROUPS = true;
276  if (mutt_str_startswith(buf, "215"))
277  {
278  do
279  {
280  if (mutt_socket_readln(buf, sizeof(buf), conn) < 0)
281  return nntp_connect_error(adata);
282  } while (!mutt_str_equal(".", buf));
283  }
284  }
285 
286  /* no LIST NEWSGROUPS, trying XGTITLE */
287  if (!adata->hasLIST_NEWSGROUPS)
288  {
289  if ((mutt_socket_send(conn, "XGTITLE\r\n") < 0) ||
290  (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
291  {
292  return nntp_connect_error(adata);
293  }
294  if (!mutt_str_startswith(buf, "500"))
295  adata->hasXGTITLE = true;
296  }
297 
298  /* no OVER, trying XOVER */
299  if (!adata->hasOVER)
300  {
301  if ((mutt_socket_send(conn, "XOVER\r\n") < 0) ||
302  (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
303  {
304  return nntp_connect_error(adata);
305  }
306  if (!mutt_str_startswith(buf, "500"))
307  adata->hasXOVER = true;
308  }
309 
310  /* trying LIST OVERVIEW.FMT */
311  if (adata->hasOVER || adata->hasXOVER)
312  {
313  if ((mutt_socket_send(conn, "LIST OVERVIEW.FMT\r\n") < 0) ||
314  (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
315  {
316  return nntp_connect_error(adata);
317  }
318  if (!mutt_str_startswith(buf, "215"))
320  else
321  {
322  bool cont = false;
323  size_t buflen = 2048, off = 0, b = 0;
324 
325  FREE(&adata->overview_fmt);
326  adata->overview_fmt = mutt_mem_malloc(buflen);
327 
328  while (true)
329  {
330  if ((buflen - off) < 1024)
331  {
332  buflen *= 2;
333  mutt_mem_realloc(&adata->overview_fmt, buflen);
334  }
335 
336  const int chunk = mutt_socket_readln_d(adata->overview_fmt + off,
337  buflen - off, conn, MUTT_SOCK_LOG_HDR);
338  if (chunk < 0)
339  {
340  FREE(&adata->overview_fmt);
341  return nntp_connect_error(adata);
342  }
343 
344  if (!cont && mutt_str_equal(".", adata->overview_fmt + off))
345  break;
346 
347  cont = (chunk >= (buflen - off));
348  off += strlen(adata->overview_fmt + off);
349  if (!cont)
350  {
351  if (adata->overview_fmt[b] == ':')
352  {
353  memmove(adata->overview_fmt + b, adata->overview_fmt + b + 1, off - b - 1);
354  adata->overview_fmt[off - 1] = ':';
355  }
356  char *colon = strchr(adata->overview_fmt + b, ':');
357  if (!colon)
358  adata->overview_fmt[off++] = ':';
359  else if (strcmp(colon + 1, "full") != 0)
360  off = colon + 1 - adata->overview_fmt;
361  if (strcasecmp(adata->overview_fmt + b, "Bytes:") == 0)
362  {
363  size_t len = strlen(adata->overview_fmt + b);
364  mutt_str_copy(adata->overview_fmt + b, "Content-Length:", len + 1);
365  off = b + len;
366  }
367  adata->overview_fmt[off++] = '\0';
368  b = off;
369  }
370  }
371  adata->overview_fmt[off++] = '\0';
372  mutt_mem_realloc(&adata->overview_fmt, off);
373  }
374  }
375  return 0;
376 }
377 
378 #ifdef USE_SASL
379 
387 static bool nntp_memchr(char **haystack, char *sentinel, int needle)
388 {
389  char *start = *haystack;
390  size_t max_offset = sentinel - start;
391  void *vp = memchr(start, max_offset, needle);
392  if (!vp)
393  return false;
394  *haystack = vp;
395  return true;
396 }
397 
405 static void nntp_log_binbuf(const char *buf, size_t len, const char *pfx, int dbg)
406 {
407  char tmp[1024];
408  char *p = tmp;
409  char *sentinel = tmp + len;
410 
411  if (C_DebugLevel < dbg)
412  return;
413  memcpy(tmp, buf, len);
414  tmp[len] = '\0';
415  while (nntp_memchr(&p, sentinel, '\0'))
416  *p = '.';
417  mutt_debug(dbg, "%s> %s\n", pfx, tmp);
418 }
419 #endif
420 
427 static int nntp_auth(struct NntpAccountData *adata)
428 {
429  struct Connection *conn = adata->conn;
430  char buf[1024];
431  char authenticators[1024] = "USER";
432  char *method = NULL, *a = NULL, *p = NULL;
433  unsigned char flags = conn->account.flags;
434 
435  while (true)
436  {
437  /* get login and password */
438  if ((mutt_account_getuser(&conn->account) < 0) || (conn->account.user[0] == '\0') ||
439  (mutt_account_getpass(&conn->account) < 0) || (conn->account.pass[0] == '\0'))
440  {
441  break;
442  }
443 
444  /* get list of authenticators */
446  mutt_str_copy(authenticators, C_NntpAuthenticators, 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  char *m = NULL;
484 
485  if (!adata->authenticators)
486  continue;
487  m = strcasestr(adata->authenticators, method);
488  if (!m)
489  continue;
490  if ((m > adata->authenticators) && (*(m - 1) != ' '))
491  continue;
492  m += strlen(method);
493  if ((*m != '\0') && (*m != ' '))
494  continue;
495  }
496  mutt_debug(LL_DEBUG1, "trying method %s\n", method);
497 
498  /* AUTHINFO USER authentication */
499  if (strcmp(method, "USER") == 0)
500  {
501  // L10N: (%s) is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
502  mutt_message(_("Authenticating (%s)..."), method);
503  snprintf(buf, sizeof(buf), "AUTHINFO USER %s\r\n", conn->account.user);
504  if ((mutt_socket_send(conn, buf) < 0) ||
505  (mutt_socket_readln_d(buf, sizeof(buf), conn, MUTT_SOCK_LOG_FULL) < 0))
506  {
507  break;
508  }
509 
510  /* authenticated, password is not required */
511  if (mutt_str_startswith(buf, "281"))
512  return 0;
513 
514  /* username accepted, sending password */
515  if (mutt_str_startswith(buf, "381"))
516  {
517  mutt_debug(MUTT_SOCK_LOG_FULL, "%d> AUTHINFO PASS *\n", conn->fd);
518  snprintf(buf, sizeof(buf), "AUTHINFO PASS %s\r\n", conn->account.pass);
519  if ((mutt_socket_send_d(conn, buf, MUTT_SOCK_LOG_FULL) < 0) ||
520  (mutt_socket_readln_d(buf, sizeof(buf), conn, MUTT_SOCK_LOG_FULL) < 0))
521  {
522  break;
523  }
524 
525  /* authenticated */
526  if (mutt_str_startswith(buf, "281"))
527  return 0;
528  }
529 
530  /* server doesn't support AUTHINFO USER, trying next method */
531  if (*buf == '5')
532  continue;
533  }
534  else
535  {
536 #ifdef USE_SASL
537  sasl_conn_t *saslconn = NULL;
538  sasl_interact_t *interaction = NULL;
539  int rc;
540  char inbuf[1024] = { 0 };
541  const char *mech = NULL;
542  const char *client_out = NULL;
543  unsigned int client_len, len;
544 
545  if (mutt_sasl_client_new(conn, &saslconn) < 0)
546  {
547  mutt_debug(LL_DEBUG1, "error allocating SASL connection\n");
548  continue;
549  }
550 
551  while (true)
552  {
553  rc = sasl_client_start(saslconn, method, &interaction, &client_out,
554  &client_len, &mech);
555  if (rc != SASL_INTERACT)
556  break;
557  mutt_sasl_interact(interaction);
558  }
559  if ((rc != SASL_OK) && (rc != SASL_CONTINUE))
560  {
561  sasl_dispose(&saslconn);
563  "error starting SASL authentication exchange\n");
564  continue;
565  }
566 
567  // L10N: (%s) is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
568  mutt_message(_("Authenticating (%s)..."), method);
569  snprintf(buf, sizeof(buf), "AUTHINFO SASL %s", method);
570 
571  /* looping protocol */
572  while ((rc == SASL_CONTINUE) || ((rc == SASL_OK) && client_len))
573  {
574  /* send out client response */
575  if (client_len)
576  {
577  nntp_log_binbuf(client_out, client_len, "SASL", MUTT_SOCK_LOG_FULL);
578  if (*buf != '\0')
579  mutt_str_cat(buf, sizeof(buf), " ");
580  len = strlen(buf);
581  if (sasl_encode64(client_out, client_len, buf + len,
582  sizeof(buf) - len, &len) != SASL_OK)
583  {
584  mutt_debug(LL_DEBUG1, "error base64-encoding client response\n");
585  break;
586  }
587  }
588 
589  mutt_str_cat(buf, sizeof(buf), "\r\n");
590  if (strchr(buf, ' '))
591  {
592  mutt_debug(MUTT_SOCK_LOG_CMD, "%d> AUTHINFO SASL %s%s\n", conn->fd,
593  method, client_len ? " sasl_data" : "");
594  }
595  else
596  mutt_debug(MUTT_SOCK_LOG_CMD, "%d> sasl_data\n", conn->fd);
597  client_len = 0;
598  if ((mutt_socket_send_d(conn, buf, MUTT_SOCK_LOG_FULL) < 0) ||
599  (mutt_socket_readln_d(inbuf, sizeof(inbuf), conn, MUTT_SOCK_LOG_FULL) < 0))
600  {
601  break;
602  }
603  if (!mutt_str_startswith(inbuf, "283 ") && !mutt_str_startswith(inbuf, "383 "))
604  {
605  mutt_debug(MUTT_SOCK_LOG_FULL, "%d< %s\n", conn->fd, inbuf);
606  break;
607  }
608  inbuf[3] = '\0';
609  mutt_debug(MUTT_SOCK_LOG_FULL, "%d< %s sasl_data\n", conn->fd, inbuf);
610 
611  if (strcmp("=", inbuf + 4) == 0)
612  len = 0;
613  else if (sasl_decode64(inbuf + 4, strlen(inbuf + 4), buf,
614  sizeof(buf) - 1, &len) != SASL_OK)
615  {
616  mutt_debug(LL_DEBUG1, "error base64-decoding server response\n");
617  break;
618  }
619  else
620  nntp_log_binbuf(buf, len, "SASL", MUTT_SOCK_LOG_FULL);
621 
622  while (true)
623  {
624  rc = sasl_client_step(saslconn, buf, len, &interaction, &client_out, &client_len);
625  if (rc != SASL_INTERACT)
626  break;
627  mutt_sasl_interact(interaction);
628  }
629  if (*inbuf != '3')
630  break;
631 
632  *buf = '\0';
633  } /* looping protocol */
634 
635  if ((rc == SASL_OK) && (client_len == 0) && (*inbuf == '2'))
636  {
637  mutt_sasl_setup_conn(conn, saslconn);
638  return 0;
639  }
640 
641  /* terminate SASL session */
642  sasl_dispose(&saslconn);
643  if (conn->fd < 0)
644  break;
645  if (mutt_str_startswith(inbuf, "383 "))
646  {
647  if ((mutt_socket_send(conn, "*\r\n") < 0) ||
648  (mutt_socket_readln(inbuf, sizeof(inbuf), conn) < 0))
649  {
650  break;
651  }
652  }
653 
654  /* server doesn't support AUTHINFO SASL, trying next method */
655  if (*inbuf == '5')
656  continue;
657 #else
658  continue;
659 #endif /* USE_SASL */
660  }
661 
662  // L10N: %s is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
663  mutt_error(_("%s authentication failed"), method);
664  break;
665  }
666  break;
667  }
668 
669  /* error */
670  adata->status = NNTP_BYE;
671  conn->account.flags = flags;
672  if (conn->fd < 0)
673  {
674  mutt_error(_("Server closed connection"));
675  }
676  else
677  mutt_socket_close(conn);
678  return -1;
679 }
680 
689 static int nntp_query(struct NntpMboxData *mdata, char *line, size_t linelen)
690 {
691  struct NntpAccountData *adata = mdata->adata;
692  char buf[1024] = { 0 };
693 
694  if (adata->status == NNTP_BYE)
695  return -1;
696 
697  while (true)
698  {
699  if (adata->status == NNTP_OK)
700  {
701  int rc = 0;
702 
703  if (*line)
704  rc = mutt_socket_send(adata->conn, line);
705  else if (mdata->group)
706  {
707  snprintf(buf, sizeof(buf), "GROUP %s\r\n", mdata->group);
708  rc = mutt_socket_send(adata->conn, buf);
709  }
710  if (rc >= 0)
711  rc = mutt_socket_readln(buf, sizeof(buf), adata->conn);
712  if (rc >= 0)
713  break;
714  }
715 
716  /* reconnect */
717  while (true)
718  {
719  adata->status = NNTP_NONE;
720  if (nntp_open_connection(adata) == 0)
721  break;
722 
723  snprintf(buf, sizeof(buf), _("Connection to %s lost. Reconnect?"),
724  adata->conn->account.host);
725  if (mutt_yesorno(buf, MUTT_YES) != MUTT_YES)
726  {
727  adata->status = NNTP_BYE;
728  return -1;
729  }
730  }
731 
732  /* select newsgroup after reconnection */
733  if (mdata->group)
734  {
735  snprintf(buf, sizeof(buf), "GROUP %s\r\n", mdata->group);
736  if ((mutt_socket_send(adata->conn, buf) < 0) ||
737  (mutt_socket_readln(buf, sizeof(buf), adata->conn) < 0))
738  {
739  return nntp_connect_error(adata);
740  }
741  }
742  if (*line == '\0')
743  break;
744  }
745 
746  mutt_str_copy(line, buf, linelen);
747  return 0;
748 }
749 
766 static int nntp_fetch_lines(struct NntpMboxData *mdata, char *query, size_t qlen,
767  const char *msg, int (*func)(char *, void *), void *data)
768 {
769  bool done = false;
770  int rc;
771 
772  while (!done)
773  {
774  char buf[1024];
775  char *line = NULL;
776  unsigned int lines = 0;
777  size_t off = 0;
778  struct Progress progress;
779 
780  if (msg)
781  mutt_progress_init(&progress, msg, MUTT_PROGRESS_READ, 0);
782 
783  mutt_str_copy(buf, query, sizeof(buf));
784  if (nntp_query(mdata, buf, sizeof(buf)) < 0)
785  return -1;
786  if (buf[0] != '2')
787  {
788  mutt_str_copy(query, buf, qlen);
789  return 1;
790  }
791 
792  line = mutt_mem_malloc(sizeof(buf));
793  rc = 0;
794 
795  while (true)
796  {
797  char *p = NULL;
798  int chunk = mutt_socket_readln_d(buf, sizeof(buf), mdata->adata->conn, MUTT_SOCK_LOG_FULL);
799  if (chunk < 0)
800  {
801  mdata->adata->status = NNTP_NONE;
802  break;
803  }
804 
805  p = buf;
806  if (!off && (buf[0] == '.'))
807  {
808  if (buf[1] == '\0')
809  {
810  done = true;
811  break;
812  }
813  if (buf[1] == '.')
814  p++;
815  }
816 
817  mutt_str_copy(line + off, p, sizeof(buf));
818 
819  if (chunk >= sizeof(buf))
820  off += strlen(p);
821  else
822  {
823  if (msg)
824  mutt_progress_update(&progress, ++lines, -1);
825 
826  if ((rc == 0) && (func(line, data) < 0))
827  rc = -2;
828  off = 0;
829  }
830 
831  mutt_mem_realloc(&line, off + sizeof(buf));
832  }
833  FREE(&line);
834  func(NULL, data);
835  }
836  return rc;
837 }
838 
845 static int fetch_description(char *line, void *data)
846 {
847  if (!line)
848  return 0;
849 
850  struct NntpAccountData *adata = data;
851 
852  char *desc = strpbrk(line, " \t");
853  if (desc)
854  {
855  *desc++ = '\0';
856  desc += strspn(desc, " \t");
857  }
858  else
859  desc = strchr(line, '\0');
860 
862  if (mdata && !mutt_str_equal(desc, mdata->desc))
863  {
864  mutt_str_replace(&mdata->desc, desc);
865  mutt_debug(LL_DEBUG2, "group: %s, desc: %s\n", line, desc);
866  }
867  return 0;
868 }
869 
880 static int get_description(struct NntpMboxData *mdata, const char *wildmat, const char *msg)
881 {
882  char buf[256];
883  const char *cmd = NULL;
884 
885  /* get newsgroup description, if possible */
886  struct NntpAccountData *adata = mdata->adata;
887  if (!wildmat)
888  wildmat = mdata->group;
889  if (adata->hasLIST_NEWSGROUPS)
890  cmd = "LIST NEWSGROUPS";
891  else if (adata->hasXGTITLE)
892  cmd = "XGTITLE";
893  else
894  return 0;
895 
896  snprintf(buf, sizeof(buf), "%s %s\r\n", cmd, wildmat);
897  int rc = nntp_fetch_lines(mdata, buf, sizeof(buf), msg, fetch_description, adata);
898  if (rc > 0)
899  {
900  mutt_error("%s: %s", cmd, buf);
901  }
902  return rc;
903 }
904 
912 static void nntp_parse_xref(struct Mailbox *m, struct Email *e)
913 {
914  struct NntpMboxData *mdata = m->mdata;
915 
916  char *buf = mutt_str_dup(e->env->xref);
917  char *p = buf;
918  while (p)
919  {
920  anum_t anum;
921 
922  /* skip to next word */
923  p += strspn(p, " \t");
924  char *grp = p;
925 
926  /* skip to end of word */
927  p = strpbrk(p, " \t");
928  if (p)
929  *p++ = '\0';
930 
931  /* find colon */
932  char *colon = strchr(grp, ':');
933  if (!colon)
934  continue;
935  *colon++ = '\0';
936  if (sscanf(colon, ANUM, &anum) != 1)
937  continue;
938 
939  nntp_article_status(m, e, grp, anum);
940  if (!nntp_edata_get(e)->article_num && mutt_str_equal(mdata->group, grp))
941  nntp_edata_get(e)->article_num = anum;
942  }
943  FREE(&buf);
944 }
945 
953 static int fetch_tempfile(char *line, void *data)
954 {
955  FILE *fp = data;
956 
957  if (!line)
958  rewind(fp);
959  else if ((fputs(line, fp) == EOF) || (fputc('\n', fp) == EOF))
960  return -1;
961  return 0;
962 }
963 
970 static int fetch_numbers(char *line, void *data)
971 {
972  struct FetchCtx *fc = data;
973  anum_t anum;
974 
975  if (!line)
976  return 0;
977  if (sscanf(line, ANUM, &anum) != 1)
978  return 0;
979  if ((anum < fc->first) || (anum > fc->last))
980  return 0;
981  fc->messages[anum - fc->first] = 1;
982  return 0;
983 }
984 
992 static int parse_overview_line(char *line, void *data)
993 {
994  if (!line || !data)
995  return 0;
996 
997  struct FetchCtx *fc = data;
998  struct Mailbox *m = fc->mailbox;
999  if (!m)
1000  return -1;
1001 
1002  struct NntpMboxData *mdata = m->mdata;
1003  struct Email *e = NULL;
1004  char *header = NULL, *field = NULL;
1005  bool save = true;
1006  anum_t anum;
1007 
1008  /* parse article number */
1009  field = strchr(line, '\t');
1010  if (field)
1011  *field++ = '\0';
1012  if (sscanf(line, ANUM, &anum) != 1)
1013  return 0;
1014  mutt_debug(LL_DEBUG2, "" ANUM "\n", anum);
1015 
1016  /* out of bounds */
1017  if ((anum < fc->first) || (anum > fc->last))
1018  return 0;
1019 
1020  /* not in LISTGROUP */
1021  if (!fc->messages[anum - fc->first])
1022  {
1023  /* progress */
1024  if (m->verbose)
1025  mutt_progress_update(&fc->progress, anum - fc->first + 1, -1);
1026  return 0;
1027  }
1028 
1029  /* convert overview line to header */
1030  FILE *fp = mutt_file_mkstemp();
1031  if (!fp)
1032  return -1;
1033 
1034  header = mdata->adata->overview_fmt;
1035  while (field)
1036  {
1037  char *b = field;
1038 
1039  if (*header)
1040  {
1041  if (!strstr(header, ":full") && (fputs(header, fp) == EOF))
1042  {
1043  mutt_file_fclose(&fp);
1044  return -1;
1045  }
1046  header = strchr(header, '\0') + 1;
1047  }
1048 
1049  field = strchr(field, '\t');
1050  if (field)
1051  *field++ = '\0';
1052  if ((fputs(b, fp) == EOF) || (fputc('\n', fp) == EOF))
1053  {
1054  mutt_file_fclose(&fp);
1055  return -1;
1056  }
1057  }
1058  rewind(fp);
1059 
1060  /* allocate memory for headers */
1061  if (m->msg_count >= m->email_max)
1062  mx_alloc_memory(m);
1063 
1064  /* parse header */
1065  m->emails[m->msg_count] = email_new();
1066  e = m->emails[m->msg_count];
1067  e->env = mutt_rfc822_read_header(fp, e, false, false);
1068  e->env->newsgroups = mutt_str_dup(mdata->group);
1069  e->received = e->date_sent;
1070  mutt_file_fclose(&fp);
1071 
1072 #ifdef USE_HCACHE
1073  if (fc->hc)
1074  {
1075  char buf[16];
1076 
1077  /* try to replace with header from cache */
1078  snprintf(buf, sizeof(buf), "%u", anum);
1079  struct HCacheEntry hce = mutt_hcache_fetch(fc->hc, buf, strlen(buf), 0);
1080  if (hce.email)
1081  {
1082  mutt_debug(LL_DEBUG2, "mutt_hcache_fetch %s\n", buf);
1083  email_free(&e);
1084  e = hce.email;
1085  m->emails[m->msg_count] = e;
1086  e->edata = NULL;
1087  e->read = false;
1088  e->old = false;
1089 
1090  /* skip header marked as deleted in cache */
1091  if (e->deleted && !fc->restore)
1092  {
1093  if (mdata->bcache)
1094  {
1095  mutt_debug(LL_DEBUG2, "mutt_bcache_del %s\n", buf);
1096  mutt_bcache_del(mdata->bcache, buf);
1097  }
1098  save = false;
1099  }
1100  }
1101 
1102  /* not cached yet, store header */
1103  else
1104  {
1105  mutt_debug(LL_DEBUG2, "mutt_hcache_store %s\n", buf);
1106  mutt_hcache_store(fc->hc, buf, strlen(buf), e, 0);
1107  }
1108  }
1109 #endif
1110 
1111  if (save)
1112  {
1113  e->index = m->msg_count++;
1114  e->read = false;
1115  e->old = false;
1116  e->deleted = false;
1117  e->edata = nntp_edata_new();
1119  nntp_edata_get(e)->article_num = anum;
1120  if (fc->restore)
1121  e->changed = true;
1122  else
1123  {
1124  nntp_article_status(m, e, NULL, anum);
1125  if (!e->read)
1126  nntp_parse_xref(m, e);
1127  }
1128  if (anum > mdata->last_loaded)
1129  mdata->last_loaded = anum;
1130  }
1131  else
1132  email_free(&e);
1133 
1134  /* progress */
1135  if (m->verbose)
1136  mutt_progress_update(&fc->progress, anum - fc->first + 1, -1);
1137  return 0;
1138 }
1139 
1150 static int nntp_fetch_headers(struct Mailbox *m, void *hc, anum_t first, anum_t last, bool restore)
1151 {
1152  if (!m)
1153  return -1;
1154 
1155  struct NntpMboxData *mdata = m->mdata;
1156  struct FetchCtx fc;
1157  struct Email *e = NULL;
1158  char buf[8192];
1159  int rc = 0;
1160  anum_t current;
1161  anum_t first_over = first;
1162 
1163  /* if empty group or nothing to do */
1164  if (!last || (first > last))
1165  return 0;
1166 
1167  /* init fetch context */
1168  fc.mailbox = m;
1169  fc.first = first;
1170  fc.last = last;
1171  fc.restore = restore;
1172  fc.messages = mutt_mem_calloc(last - first + 1, sizeof(unsigned char));
1173  if (!fc.messages)
1174  return -1;
1175  fc.hc = hc;
1176 
1177  /* fetch list of articles */
1178  if (C_NntpListgroup && 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  mutt_progress_init(&fc.progress, _("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  mutt_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_NntpListgroup && 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_NntpListgroup || !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  if (rc != 0)
1369  return -1;
1370  mutt_clear_error();
1371  return 0;
1372 }
1373 
1382 static int nntp_group_poll(struct NntpMboxData *mdata, bool update_stat)
1383 {
1384  char buf[1024] = { 0 };
1385  anum_t count, first, last;
1386 
1387  /* use GROUP command to poll newsgroup */
1388  if (nntp_query(mdata, buf, sizeof(buf)) < 0)
1389  return -1;
1390  if (sscanf(buf, "211 " ANUM " " ANUM " " ANUM, &count, &first, &last) != 3)
1391  return 0;
1392  if ((first == mdata->first_message) && (last == mdata->last_message))
1393  return 0;
1394 
1395  /* articles have been renumbered */
1396  if (last < mdata->last_message)
1397  {
1398  mdata->last_cached = 0;
1399  if (mdata->newsrc_len)
1400  {
1401  mutt_mem_realloc(&mdata->newsrc_ent, sizeof(struct NewsrcEntry));
1402  mdata->newsrc_len = 1;
1403  mdata->newsrc_ent[0].first = 1;
1404  mdata->newsrc_ent[0].last = 0;
1405  }
1406  }
1407  mdata->first_message = first;
1408  mdata->last_message = last;
1409  if (!update_stat)
1410  return 1;
1411 
1412  /* update counters */
1413  else if (!last || (!mdata->newsrc_ent && !mdata->last_cached))
1414  mdata->unread = count;
1415  else
1416  nntp_group_unread_stat(mdata);
1417  return 1;
1418 }
1419 
1427 static enum MxStatus check_mailbox(struct Mailbox *m)
1428 {
1429  if (!m)
1430  return MX_STATUS_ERROR;
1431 
1432  struct NntpMboxData *mdata = m->mdata;
1433  struct NntpAccountData *adata = mdata->adata;
1434  time_t now = mutt_date_epoch();
1435  enum MxStatus rc = MX_STATUS_OK;
1436  void *hc = NULL;
1437 
1438  if (adata->check_time + C_NntpPoll > now)
1439  return MX_STATUS_OK;
1440 
1441  mutt_message(_("Checking for new messages..."));
1442  if (nntp_newsrc_parse(adata) < 0)
1443  return MX_STATUS_ERROR;
1444 
1445  adata->check_time = now;
1446  int rc2 = nntp_group_poll(mdata, false);
1447  if (rc2 < 0)
1448  {
1450  return -1;
1451  }
1452  if (rc2 != 0)
1454 
1455  /* articles have been renumbered, remove all headers */
1456  if (mdata->last_message < mdata->last_loaded)
1457  {
1458  for (int i = 0; i < m->msg_count; i++)
1459  email_free(&m->emails[i]);
1460  m->msg_count = 0;
1461  m->msg_tagged = 0;
1462 
1463  if (mdata->last_message < mdata->last_loaded)
1464  {
1465  mdata->last_loaded = mdata->first_message - 1;
1466  if (C_NntpContext && (mdata->last_message - mdata->last_loaded > C_NntpContext))
1467  mdata->last_loaded = mdata->last_message - C_NntpContext;
1468  }
1469  rc = MX_STATUS_REOPENED;
1470  }
1471 
1472  /* .newsrc has been externally modified */
1473  if (adata->newsrc_modified)
1474  {
1475 #ifdef USE_HCACHE
1476  unsigned char *messages = NULL;
1477  char buf[16];
1478  struct Email *e = NULL;
1479  anum_t first = mdata->first_message;
1480 
1481  if (C_NntpContext && (mdata->last_message - first + 1 > C_NntpContext))
1482  first = mdata->last_message - C_NntpContext + 1;
1483  messages = mutt_mem_calloc(mdata->last_loaded - first + 1, sizeof(unsigned char));
1484  hc = nntp_hcache_open(mdata);
1485  nntp_hcache_update(mdata, hc);
1486 #endif
1487 
1488  /* update flags according to .newsrc */
1489  int j = 0;
1490  for (int i = 0; i < m->msg_count; i++)
1491  {
1492  if (!m->emails[i])
1493  continue;
1494  bool flagged = false;
1495  anum_t anum = nntp_edata_get(m->emails[i])->article_num;
1496 
1497 #ifdef USE_HCACHE
1498  /* check hcache for flagged and deleted flags */
1499  if (hc)
1500  {
1501  if ((anum >= first) && (anum <= mdata->last_loaded))
1502  messages[anum - first] = 1;
1503 
1504  snprintf(buf, sizeof(buf), "%u", anum);
1505  struct HCacheEntry hce = mutt_hcache_fetch(hc, buf, strlen(buf), 0);
1506  if (hce.email)
1507  {
1508  bool deleted;
1509 
1510  mutt_debug(LL_DEBUG2, "#1 mutt_hcache_fetch %s\n", buf);
1511  e = hce.email;
1512  e->edata = NULL;
1513  deleted = e->deleted;
1514  flagged = e->flagged;
1515  email_free(&e);
1516 
1517  /* header marked as deleted, removing from context */
1518  if (deleted)
1519  {
1520  mutt_set_flag(m, m->emails[i], MUTT_TAG, false);
1521  email_free(&m->emails[i]);
1522  continue;
1523  }
1524  }
1525  }
1526 #endif
1527 
1528  if (!m->emails[i]->changed)
1529  {
1530  m->emails[i]->flagged = flagged;
1531  m->emails[i]->read = false;
1532  m->emails[i]->old = false;
1533  nntp_article_status(m, m->emails[i], NULL, anum);
1534  if (!m->emails[i]->read)
1535  nntp_parse_xref(m, m->emails[i]);
1536  }
1537  m->emails[j++] = m->emails[i];
1538  }
1539 
1540 #ifdef USE_HCACHE
1541  m->msg_count = j;
1542 
1543  /* restore headers without "deleted" flag */
1544  for (anum_t anum = first; anum <= mdata->last_loaded; anum++)
1545  {
1546  if (messages[anum - first])
1547  continue;
1548 
1549  snprintf(buf, sizeof(buf), "%u", anum);
1550  struct HCacheEntry hce = mutt_hcache_fetch(hc, buf, strlen(buf), 0);
1551  if (hce.email)
1552  {
1553  mutt_debug(LL_DEBUG2, "#2 mutt_hcache_fetch %s\n", buf);
1554  if (m->msg_count >= m->email_max)
1555  mx_alloc_memory(m);
1556 
1557  e = hce.email;
1558  m->emails[m->msg_count] = e;
1559  e->edata = NULL;
1560  if (e->deleted)
1561  {
1562  email_free(&e);
1563  if (mdata->bcache)
1564  {
1565  mutt_debug(LL_DEBUG2, "mutt_bcache_del %s\n", buf);
1566  mutt_bcache_del(mdata->bcache, buf);
1567  }
1568  continue;
1569  }
1570 
1571  m->msg_count++;
1572  e->read = false;
1573  e->old = false;
1574  e->edata = nntp_edata_new();
1576  nntp_edata_get(e)->article_num = anum;
1577  nntp_article_status(m, e, NULL, anum);
1578  if (!e->read)
1579  nntp_parse_xref(m, e);
1580  }
1581  }
1582  FREE(&messages);
1583 #endif
1584 
1585  adata->newsrc_modified = false;
1586  rc = MX_STATUS_REOPENED;
1587  }
1588 
1589  /* some headers were removed, context must be updated */
1590  if (rc == MX_STATUS_REOPENED)
1592 
1593  /* fetch headers of new articles */
1594  if (mdata->last_message > mdata->last_loaded)
1595  {
1596  int oldmsgcount = m->msg_count;
1597  bool verbose = m->verbose;
1598  m->verbose = false;
1599 #ifdef USE_HCACHE
1600  if (!hc)
1601  {
1602  hc = nntp_hcache_open(mdata);
1603  nntp_hcache_update(mdata, hc);
1604  }
1605 #endif
1606  int old_msg_count = m->msg_count;
1607  rc2 = nntp_fetch_headers(m, hc, mdata->last_loaded + 1, mdata->last_message, false);
1608  m->verbose = verbose;
1609  if (rc2 == 0)
1610  {
1611  if (m->msg_count > old_msg_count)
1613  mdata->last_loaded = mdata->last_message;
1614  }
1615  if ((rc == MX_STATUS_OK) && (m->msg_count > oldmsgcount))
1616  rc = MX_STATUS_NEW_MAIL;
1617  }
1618 
1619 #ifdef USE_HCACHE
1620  mutt_hcache_close(hc);
1621 #endif
1622  if (rc != MX_STATUS_OK)
1623  nntp_newsrc_close(adata);
1624  mutt_clear_error();
1625  return rc;
1626 }
1627 
1635 static int nntp_date(struct NntpAccountData *adata, time_t *now)
1636 {
1637  if (adata->hasDATE)
1638  {
1639  struct NntpMboxData mdata = { 0 };
1640  char buf[1024];
1641  struct tm tm = { 0 };
1642 
1643  mdata.adata = adata;
1644  mdata.group = NULL;
1645  mutt_str_copy(buf, "DATE\r\n", sizeof(buf));
1646  if (nntp_query(&mdata, buf, sizeof(buf)) < 0)
1647  return -1;
1648 
1649  if (sscanf(buf, "111 %4d%2d%2d%2d%2d%2d%*s", &tm.tm_year, &tm.tm_mon,
1650  &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) == 6)
1651  {
1652  tm.tm_year -= 1900;
1653  tm.tm_mon--;
1654  *now = timegm(&tm);
1655  if (*now >= 0)
1656  {
1657  mutt_debug(LL_DEBUG1, "server time is %lu\n", *now);
1658  return 0;
1659  }
1660  }
1661  }
1662  *now = mutt_date_epoch();
1663  return 0;
1664 }
1665 
1672 static int fetch_children(char *line, void *data)
1673 {
1674  struct ChildCtx *cc = data;
1675  anum_t anum;
1676 
1677  if (!line || (sscanf(line, ANUM, &anum) != 1))
1678  return 0;
1679  for (unsigned int i = 0; i < cc->mailbox->msg_count; i++)
1680  {
1681  struct Email *e = cc->mailbox->emails[i];
1682  if (!e)
1683  break;
1684  if (nntp_edata_get(e)->article_num == anum)
1685  return 0;
1686  }
1687  if (cc->num >= cc->max)
1688  {
1689  cc->max *= 2;
1690  mutt_mem_realloc(&cc->child, sizeof(anum_t) * cc->max);
1691  }
1692  cc->child[cc->num++] = anum;
1693  return 0;
1694 }
1695 
1703 {
1704  struct Connection *conn = adata->conn;
1705  char buf[256];
1706  int cap;
1707  bool posting = false, auth = true;
1708 
1709  if (adata->status == NNTP_OK)
1710  return 0;
1711  if (adata->status == NNTP_BYE)
1712  return -1;
1713  adata->status = NNTP_NONE;
1714 
1715  if (mutt_socket_open(conn) < 0)
1716  return -1;
1717 
1718  if (mutt_socket_readln(buf, sizeof(buf), conn) < 0)
1719  return nntp_connect_error(adata);
1720 
1721  if (mutt_str_startswith(buf, "200"))
1722  posting = true;
1723  else if (!mutt_str_startswith(buf, "201"))
1724  {
1725  mutt_socket_close(conn);
1727  mutt_error("%s", buf);
1728  return -1;
1729  }
1730 
1731  /* get initial capabilities */
1732  cap = nntp_capabilities(adata);
1733  if (cap < 0)
1734  return -1;
1735 
1736  /* tell news server to switch to mode reader if it isn't so */
1737  if (cap > 0)
1738  {
1739  if ((mutt_socket_send(conn, "MODE READER\r\n") < 0) ||
1740  (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
1741  {
1742  return nntp_connect_error(adata);
1743  }
1744 
1745  if (mutt_str_startswith(buf, "200"))
1746  posting = true;
1747  else if (mutt_str_startswith(buf, "201"))
1748  posting = false;
1749  /* error if has capabilities, ignore result if no capabilities */
1750  else if (adata->hasCAPABILITIES)
1751  {
1752  mutt_socket_close(conn);
1753  mutt_error(_("Could not switch to reader mode"));
1754  return -1;
1755  }
1756 
1757  /* recheck capabilities after MODE READER */
1758  if (adata->hasCAPABILITIES)
1759  {
1760  cap = nntp_capabilities(adata);
1761  if (cap < 0)
1762  return -1;
1763  }
1764  }
1765 
1766  mutt_message(_("Connected to %s. %s"), conn->account.host,
1767  posting ? _("Posting is ok") : _("Posting is NOT ok"));
1768  mutt_sleep(1);
1769 
1770 #ifdef USE_SSL
1771  /* Attempt STARTTLS if available and desired. */
1772  if ((adata->use_tls != 1) && (adata->hasSTARTTLS || C_SslForceTls))
1773  {
1774  if (adata->use_tls == 0)
1775  {
1776  adata->use_tls =
1777  C_SslForceTls ||
1779  _("Secure connection with TLS?")) == MUTT_YES) ?
1780  2 :
1781  1;
1782  }
1783  if (adata->use_tls == 2)
1784  {
1785  if ((mutt_socket_send(conn, "STARTTLS\r\n") < 0) ||
1786  (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
1787  {
1788  return nntp_connect_error(adata);
1789  }
1790  // Clear any data after the STARTTLS acknowledgement
1791  mutt_socket_empty(conn);
1792  if (!mutt_str_startswith(buf, "382"))
1793  {
1794  adata->use_tls = 0;
1795  mutt_error("STARTTLS: %s", buf);
1796  }
1797  else if (mutt_ssl_starttls(conn))
1798  {
1799  adata->use_tls = 0;
1800  adata->status = NNTP_NONE;
1801  mutt_socket_close(adata->conn);
1802  mutt_error(_("Could not negotiate TLS connection"));
1803  return -1;
1804  }
1805  else
1806  {
1807  /* recheck capabilities after STARTTLS */
1808  cap = nntp_capabilities(adata);
1809  if (cap < 0)
1810  return -1;
1811  }
1812  }
1813  }
1814 #endif
1815 
1816  /* authentication required? */
1817  if (conn->account.flags & MUTT_ACCT_USER)
1818  {
1819  if (!conn->account.user[0])
1820  auth = false;
1821  }
1822  else
1823  {
1824  if ((mutt_socket_send(conn, "STAT\r\n") < 0) ||
1825  (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
1826  {
1827  return nntp_connect_error(adata);
1828  }
1829  if (!mutt_str_startswith(buf, "480"))
1830  auth = false;
1831  }
1832 
1833  /* authenticate */
1834  if (auth && (nntp_auth(adata) < 0))
1835  return -1;
1836 
1837  /* get final capabilities after authentication */
1838  if (adata->hasCAPABILITIES && (auth || (cap > 0)))
1839  {
1840  cap = nntp_capabilities(adata);
1841  if (cap < 0)
1842  return -1;
1843  if (cap > 0)
1844  {
1845  mutt_socket_close(conn);
1846  mutt_error(_("Could not switch to reader mode"));
1847  return -1;
1848  }
1849  }
1850 
1851  /* attempt features */
1852  if (nntp_attempt_features(adata) < 0)
1853  return -1;
1854 
1855  adata->status = NNTP_OK;
1856  return 0;
1857 }
1858 
1866 int nntp_post(struct Mailbox *m, const char *msg)
1867 {
1868  struct NntpMboxData *mdata = NULL;
1869  struct NntpMboxData tmp_mdata = { 0 };
1870  char buf[1024];
1871 
1872  if (m && (m->type == MUTT_NNTP))
1873  mdata = m->mdata;
1874  else
1875  {
1877  if (!CurrentNewsSrv)
1878  return -1;
1879 
1880  mdata = &tmp_mdata;
1881  mdata->adata = CurrentNewsSrv;
1882  mdata->group = NULL;
1883  }
1884 
1885  FILE *fp = mutt_file_fopen(msg, "r");
1886  if (!fp)
1887  {
1888  mutt_perror(msg);
1889  return -1;
1890  }
1891 
1892  mutt_str_copy(buf, "POST\r\n", sizeof(buf));
1893  if (nntp_query(mdata, buf, sizeof(buf)) < 0)
1894  {
1895  mutt_file_fclose(&fp);
1896  return -1;
1897  }
1898  if (buf[0] != '3')
1899  {
1900  mutt_error(_("Can't post article: %s"), buf);
1901  mutt_file_fclose(&fp);
1902  return -1;
1903  }
1904 
1905  buf[0] = '.';
1906  buf[1] = '\0';
1907  while (fgets(buf + 1, sizeof(buf) - 2, fp))
1908  {
1909  size_t len = strlen(buf);
1910  if (buf[len - 1] == '\n')
1911  {
1912  buf[len - 1] = '\r';
1913  buf[len] = '\n';
1914  len++;
1915  buf[len] = '\0';
1916  }
1917  if (mutt_socket_send_d(mdata->adata->conn, (buf[1] == '.') ? buf : buf + 1,
1918  MUTT_SOCK_LOG_FULL) < 0)
1919  {
1920  mutt_file_fclose(&fp);
1921  return nntp_connect_error(mdata->adata);
1922  }
1923  }
1924  mutt_file_fclose(&fp);
1925 
1926  if (((buf[strlen(buf) - 1] != '\n') &&
1927  (mutt_socket_send_d(mdata->adata->conn, "\r\n", MUTT_SOCK_LOG_FULL) < 0)) ||
1928  (mutt_socket_send_d(mdata->adata->conn, ".\r\n", MUTT_SOCK_LOG_FULL) < 0) ||
1929  (mutt_socket_readln(buf, sizeof(buf), mdata->adata->conn) < 0))
1930  {
1931  return nntp_connect_error(mdata->adata);
1932  }
1933  if (buf[0] != '2')
1934  {
1935  mutt_error(_("Can't post article: %s"), buf);
1936  return -1;
1937  }
1938  return 0;
1939 }
1940 
1948 int nntp_active_fetch(struct NntpAccountData *adata, bool mark_new)
1949 {
1950  struct NntpMboxData tmp_mdata = { 0 };
1951  char msg[256];
1952  char buf[1024];
1953  unsigned int i;
1954  int rc;
1955 
1956  snprintf(msg, sizeof(msg), _("Loading list of groups from server %s..."),
1957  adata->conn->account.host);
1958  mutt_message(msg);
1959  if (nntp_date(adata, &adata->newgroups_time) < 0)
1960  return -1;
1961 
1962  tmp_mdata.adata = adata;
1963  tmp_mdata.group = NULL;
1964  i = adata->groups_num;
1965  mutt_str_copy(buf, "LIST\r\n", sizeof(buf));
1966  rc = nntp_fetch_lines(&tmp_mdata, buf, sizeof(buf), msg, nntp_add_group, adata);
1967  if (rc)
1968  {
1969  if (rc > 0)
1970  {
1971  mutt_error("LIST: %s", buf);
1972  }
1973  return -1;
1974  }
1975 
1976  if (mark_new)
1977  {
1978  for (; i < adata->groups_num; i++)
1979  {
1980  struct NntpMboxData *mdata = adata->groups_list[i];
1981  mdata->has_new_mail = true;
1982  }
1983  }
1984 
1985  for (i = 0; i < adata->groups_num; i++)
1986  {
1987  struct NntpMboxData *mdata = adata->groups_list[i];
1988 
1989  if (mdata && mdata->deleted && !mdata->newsrc_ent)
1990  {
1992  mutt_hash_delete(adata->groups_hash, mdata->group, NULL);
1993  adata->groups_list[i] = NULL;
1994  }
1995  }
1996 
1998  rc = get_description(&tmp_mdata, "*", _("Loading descriptions..."));
1999 
2001  if (rc < 0)
2002  return -1;
2003  mutt_clear_error();
2004  return 0;
2005 }
2006 
2016 {
2017  struct NntpMboxData tmp_mdata = { 0 };
2018  time_t now;
2019  char buf[1024];
2020  char *msg = _("Checking for new newsgroups...");
2021  unsigned int i;
2022  int rc, update_active = false;
2023 
2024  if (!adata || !adata->newgroups_time)
2025  return -1;
2026 
2027  /* check subscribed newsgroups for new articles */
2028  if (C_ShowNewNews)
2029  {
2030  mutt_message(_("Checking for new messages..."));
2031  for (i = 0; i < adata->groups_num; i++)
2032  {
2033  struct NntpMboxData *mdata = adata->groups_list[i];
2034 
2035  if (mdata && mdata->subscribed)
2036  {
2037  rc = nntp_group_poll(mdata, true);
2038  if (rc < 0)
2039  return -1;
2040  if (rc > 0)
2041  update_active = true;
2042  }
2043  }
2044  }
2045  else if (adata->newgroups_time)
2046  return 0;
2047 
2048  /* get list of new groups */
2049  mutt_message(msg);
2050  if (nntp_date(adata, &now) < 0)
2051  return -1;
2052  tmp_mdata.adata = adata;
2053  if (m && m->mdata)
2054  tmp_mdata.group = ((struct NntpMboxData *) m->mdata)->group;
2055  else
2056  tmp_mdata.group = NULL;
2057  i = adata->groups_num;
2058  struct tm tm = mutt_date_gmtime(adata->newgroups_time);
2059  snprintf(buf, sizeof(buf), "NEWGROUPS %02d%02d%02d %02d%02d%02d GMT\r\n",
2060  tm.tm_year % 100, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
2061  rc = nntp_fetch_lines(&tmp_mdata, buf, sizeof(buf), msg, nntp_add_group, adata);
2062  if (rc)
2063  {
2064  if (rc > 0)
2065  {
2066  mutt_error("NEWGROUPS: %s", buf);
2067  }
2068  return -1;
2069  }
2070 
2071  /* new groups found */
2072  rc = 0;
2073  if (adata->groups_num != i)
2074  {
2075  int groups_num = i;
2076 
2077  adata->newgroups_time = now;
2078  for (; i < adata->groups_num; i++)
2079  {
2080  struct NntpMboxData *mdata = adata->groups_list[i];
2081  mdata->has_new_mail = true;
2082  }
2083 
2084  /* loading descriptions */
2086  {
2087  unsigned int count = 0;
2088  struct Progress progress;
2089 
2090  mutt_progress_init(&progress, _("Loading descriptions..."),
2091  MUTT_PROGRESS_READ, adata->groups_num - i);
2092  for (i = groups_num; i < adata->groups_num; i++)
2093  {
2094  struct NntpMboxData *mdata = adata->groups_list[i];
2095 
2096  if (get_description(mdata, NULL, NULL) < 0)
2097  return -1;
2098  mutt_progress_update(&progress, ++count, -1);
2099  }
2100  }
2101  update_active = true;
2102  rc = 1;
2103  }
2104  if (update_active)
2106  mutt_clear_error();
2107  return rc;
2108 }
2109 
2118 int nntp_check_msgid(struct Mailbox *m, const char *msgid)
2119 {
2120  if (!m)
2121  return -1;
2122 
2123  struct NntpMboxData *mdata = m->mdata;
2124  char buf[1024];
2125 
2126  FILE *fp = mutt_file_mkstemp();
2127  if (!fp)
2128  {
2129  mutt_perror(_("Can't create temporary file"));
2130  return -1;
2131  }
2132 
2133  snprintf(buf, sizeof(buf), "HEAD %s\r\n", msgid);
2134  int rc = nntp_fetch_lines(mdata, buf, sizeof(buf), NULL, fetch_tempfile, fp);
2135  if (rc)
2136  {
2137  mutt_file_fclose(&fp);
2138  if (rc < 0)
2139  return -1;
2140  if (mutt_str_startswith(buf, "430"))
2141  return 1;
2142  mutt_error("HEAD: %s", buf);
2143  return -1;
2144  }
2145 
2146  /* parse header */
2147  if (m->msg_count == m->email_max)
2148  mx_alloc_memory(m);
2149  m->emails[m->msg_count] = email_new();
2150  struct Email *e = m->emails[m->msg_count];
2151  e->edata = nntp_edata_new();
2153  e->env = mutt_rfc822_read_header(fp, e, false, false);
2154  mutt_file_fclose(&fp);
2155 
2156  /* get article number */
2157  if (e->env->xref)
2158  nntp_parse_xref(m, e);
2159  else
2160  {
2161  snprintf(buf, sizeof(buf), "STAT %s\r\n", msgid);
2162  if (nntp_query(mdata, buf, sizeof(buf)) < 0)
2163  {
2164  email_free(&e);
2165  return -1;
2166  }
2167  sscanf(buf + 4, ANUM, &nntp_edata_get(e)->article_num);
2168  }
2169 
2170  /* reset flags */
2171  e->read = false;
2172  e->old = false;
2173  e->deleted = false;
2174  e->changed = true;
2175  e->received = e->date_sent;
2176  e->index = m->msg_count++;
2178  return 0;
2179 }
2180 
2188 int nntp_check_children(struct Mailbox *m, const char *msgid)
2189 {
2190  if (!m)
2191  return -1;
2192 
2193  struct NntpMboxData *mdata = m->mdata;
2194  struct ChildCtx cc;
2195  char buf[256];
2196  int rc;
2197  void *hc = NULL;
2198 
2199  if (!mdata || !mdata->adata)
2200  return -1;
2201  if (mdata->first_message > mdata->last_loaded)
2202  return 0;
2203 
2204  /* init context */
2205  cc.mailbox = m;
2206  cc.num = 0;
2207  cc.max = 10;
2208  cc.child = mutt_mem_malloc(sizeof(anum_t) * cc.max);
2209 
2210  /* fetch numbers of child messages */
2211  snprintf(buf, sizeof(buf), "XPAT References %u-%u *%s*\r\n",
2212  mdata->first_message, mdata->last_loaded, msgid);
2213  rc = nntp_fetch_lines(mdata, buf, sizeof(buf), NULL, fetch_children, &cc);
2214  if (rc)
2215  {
2216  FREE(&cc.child);
2217  if (rc > 0)
2218  {
2219  if (!mutt_str_startswith(buf, "500"))
2220  mutt_error("XPAT: %s", buf);
2221  else
2222  {
2223  mutt_error(_("Unable to find child articles because server does not "
2224  "support XPAT command"));
2225  }
2226  }
2227  return -1;
2228  }
2229 
2230  /* fetch all found messages */
2231  bool verbose = m->verbose;
2232  m->verbose = false;
2233 #ifdef USE_HCACHE
2234  hc = nntp_hcache_open(mdata);
2235 #endif
2236  int old_msg_count = m->msg_count;
2237  for (int i = 0; i < cc.num; i++)
2238  {
2239  rc = nntp_fetch_headers(m, hc, cc.child[i], cc.child[i], true);
2240  if (rc < 0)
2241  break;
2242  }
2243  if (m->msg_count > old_msg_count)
2245 
2246 #ifdef USE_HCACHE
2247  mutt_hcache_close(hc);
2248 #endif
2249  m->verbose = verbose;
2250  FREE(&cc.child);
2251  return (rc < 0) ? -1 : 0;
2252 }
2253 
2257 int nntp_compare_order(const void *a, const void *b)
2258 {
2259  const struct Email *ea = *(struct Email const *const *) a;
2260  const struct Email *eb = *(struct Email const *const *) b;
2261 
2262  anum_t na = nntp_edata_get((struct Email *) ea)->article_num;
2263  anum_t nb = nntp_edata_get((struct Email *) eb)->article_num;
2264  int result = (na == nb) ? 0 : (na > nb) ? 1 : -1;
2265  result = perform_auxsort(result, a, b);
2266  return SORT_CODE(result);
2267 }
2268 
2272 static bool nntp_ac_owns_path(struct Account *a, const char *path)
2273 {
2274  return true;
2275 }
2276 
2280 static bool nntp_ac_add(struct Account *a, struct Mailbox *m)
2281 {
2282  return true;
2283 }
2284 
2288 static enum MxOpenReturns nntp_mbox_open(struct Mailbox *m)
2289 {
2290  if (!m->account)
2291  return MX_OPEN_ERROR;
2292 
2293  char buf[8192];
2294  char server[1024];
2295  char *group = NULL;
2296  int rc;
2297  void *hc = NULL;
2298  anum_t first, last, count = 0;
2299 
2300  struct Url *url = url_parse(mailbox_path(m));
2301  if (!url || !url->host || !url->path ||
2302  !((url->scheme == U_NNTP) || (url->scheme == U_NNTPS)))
2303  {
2304  url_free(&url);
2305  mutt_error(_("%s is an invalid newsgroup specification"), mailbox_path(m));
2306  return MX_OPEN_ERROR;
2307  }
2308 
2309  group = url->path;
2310  if (group[0] == '/') /* Skip a leading '/' */
2311  group++;
2312 
2313  url->path = strchr(url->path, '\0');
2314  url_tostring(url, server, sizeof(server), U_NO_FLAGS);
2315 
2317  struct NntpAccountData *adata = m->account->adata;
2318  if (!adata)
2319  {
2320  adata = nntp_select_server(m, server, true);
2321  m->account->adata = adata;
2323  }
2324 
2325  if (!adata)
2326  {
2327  url_free(&url);
2328  return MX_OPEN_ERROR;
2329  }
2331 
2332  m->msg_count = 0;
2333  m->msg_unread = 0;
2334  m->vcount = 0;
2335 
2336  if (group[0] == '/')
2337  group++;
2338 
2339  /* find news group data structure */
2341  if (!mdata)
2342  {
2344  mutt_error(_("Newsgroup %s not found on the server"), group);
2345  url_free(&url);
2346  return MX_OPEN_ERROR;
2347  }
2348 
2349  m->rights &= ~MUTT_ACL_INSERT; // Clear the flag
2350  if (!mdata->newsrc_ent && !mdata->subscribed && !C_SaveUnsubscribed)
2351  m->readonly = true;
2352 
2353  /* select newsgroup */
2354  mutt_message(_("Selecting %s..."), group);
2355  url_free(&url);
2356  buf[0] = '\0';
2357  if (nntp_query(mdata, buf, sizeof(buf)) < 0)
2358  {
2360  return MX_OPEN_ERROR;
2361  }
2362 
2363  /* newsgroup not found, remove it */
2364  if (mutt_str_startswith(buf, "411"))
2365  {
2366  mutt_error(_("Newsgroup %s has been removed from the server"), mdata->group);
2367  if (!mdata->deleted)
2368  {
2369  mdata->deleted = true;
2371  }
2372  if (mdata->newsrc_ent && !mdata->subscribed && !C_SaveUnsubscribed)
2373  {
2374  FREE(&mdata->newsrc_ent);
2375  mdata->newsrc_len = 0;
2378  }
2379  }
2380 
2381  /* parse newsgroup info */
2382  else
2383  {
2384  if (sscanf(buf, "211 " ANUM " " ANUM " " ANUM, &count, &first, &last) != 3)
2385  {
2387  mutt_error("GROUP: %s", buf);
2388  return MX_OPEN_ERROR;
2389  }
2390  mdata->first_message = first;
2391  mdata->last_message = last;
2392  mdata->deleted = false;
2393 
2394  /* get description if empty */
2395  if (C_NntpLoadDescription && !mdata->desc)
2396  {
2397  if (get_description(mdata, NULL, NULL) < 0)
2398  {
2400  return MX_OPEN_ERROR;
2401  }
2402  if (mdata->desc)
2404  }
2405  }
2406 
2408  m->mdata = mdata;
2409  // Every known newsgroup has an mdata which is stored in adata->groups_list.
2410  // Currently we don't let the Mailbox free the mdata.
2411  // m->mdata_free = nntp_mdata_free;
2412  if (!mdata->bcache && (mdata->newsrc_ent || mdata->subscribed || C_SaveUnsubscribed))
2413  mdata->bcache = mutt_bcache_open(&adata->conn->account, mdata->group);
2414 
2415  /* strip off extra articles if adding context is greater than $nntp_context */
2416  first = mdata->first_message;
2417  if (C_NntpContext && (mdata->last_message - first + 1 > C_NntpContext))
2418  first = mdata->last_message - C_NntpContext + 1;
2419  mdata->last_loaded = first ? first - 1 : 0;
2420  count = mdata->first_message;
2421  mdata->first_message = first;
2423  mdata->first_message = count;
2424 #ifdef USE_HCACHE
2425  hc = nntp_hcache_open(mdata);
2427 #endif
2428  if (!hc)
2429  m->rights &= ~(MUTT_ACL_WRITE | MUTT_ACL_DELETE); // Clear the flags
2430 
2432  rc = nntp_fetch_headers(m, hc, first, mdata->last_message, false);
2433 #ifdef USE_HCACHE
2434  mutt_hcache_close(hc);
2435 #endif
2436  if (rc < 0)
2437  return MX_OPEN_ERROR;
2438  mdata->last_loaded = mdata->last_message;
2439  adata->newsrc_modified = false;
2440  return MX_OPEN_OK;
2441 }
2442 
2448 static enum MxStatus nntp_mbox_check(struct Mailbox *m)
2449 {
2450  enum MxStatus rc = check_mailbox(m);
2451  if (rc == MX_STATUS_OK)
2452  {
2453  struct NntpMboxData *mdata = m->mdata;
2454  struct NntpAccountData *adata = mdata->adata;
2456  }
2457  return rc;
2458 }
2459 
2465 static enum MxStatus nntp_mbox_sync(struct Mailbox *m)
2466 {
2467  struct NntpMboxData *mdata = m->mdata;
2468 
2469  /* check for new articles */
2470  mdata->adata->check_time = 0;
2471  enum MxStatus check = check_mailbox(m);
2472  if (check != MX_STATUS_OK)
2473  return check;
2474 
2475 #ifdef USE_HCACHE
2476  mdata->last_cached = 0;
2477  struct HeaderCache *hc = nntp_hcache_open(mdata);
2478 #endif
2479 
2480  for (int i = 0; i < m->msg_count; i++)
2481  {
2482  struct Email *e = m->emails[i];
2483  if (!e)
2484  break;
2485 
2486  char buf[16];
2487 
2488  snprintf(buf, sizeof(buf), ANUM, nntp_edata_get(e)->article_num);
2489  if (mdata->bcache && e->deleted)
2490  {
2491  mutt_debug(LL_DEBUG2, "mutt_bcache_del %s\n", buf);
2492  mutt_bcache_del(mdata->bcache, buf);
2493  }
2494 
2495 #ifdef USE_HCACHE
2496  if (hc && (e->changed || e->deleted))
2497  {
2498  if (e->deleted && !e->read)
2499  mdata->unread--;
2500  mutt_debug(LL_DEBUG2, "mutt_hcache_store %s\n", buf);
2501  mutt_hcache_store(hc, buf, strlen(buf), e, 0);
2502  }
2503 #endif
2504  }
2505 
2506 #ifdef USE_HCACHE
2507  if (hc)
2508  {
2509  mutt_hcache_close(hc);
2510  mdata->last_cached = mdata->last_loaded;
2511  }
2512 #endif
2513 
2514  /* save .newsrc entries */
2516  nntp_newsrc_update(mdata->adata);
2517  nntp_newsrc_close(mdata->adata);
2518  return MX_STATUS_OK;
2519 }
2520 
2525 static enum MxStatus nntp_mbox_close(struct Mailbox *m)
2526 {
2527  struct NntpMboxData *mdata = m->mdata;
2528  struct NntpMboxData *tmp_mdata = NULL;
2529  if (!mdata)
2530  return MX_STATUS_OK;
2531 
2532  mdata->unread = m->msg_unread;
2533 
2535  if (!mdata->adata || !mdata->adata->groups_hash || !mdata->group)
2536  return MX_STATUS_OK;
2537 
2538  tmp_mdata = mutt_hash_find(mdata->adata->groups_hash, mdata->group);
2539  if (!tmp_mdata || (tmp_mdata != mdata))
2540  nntp_mdata_free((void **) &mdata);
2541  return MX_STATUS_OK;
2542 }
2543 
2547 static bool nntp_msg_open(struct Mailbox *m, struct Message *msg, int msgno)
2548 {
2549  struct NntpMboxData *mdata = m->mdata;
2550  struct Email *e = m->emails[msgno];
2551  if (!e)
2552  return false;
2553 
2554  char article[16];
2555 
2556  /* try to get article from cache */
2557  struct NntpAcache *acache = &mdata->acache[e->index % NNTP_ACACHE_LEN];
2558  if (acache->path)
2559  {
2560  if (acache->index == e->index)
2561  {
2562  msg->fp = mutt_file_fopen(acache->path, "r");
2563  if (msg->fp)
2564  return true;
2565  }
2566  /* clear previous entry */
2567  else
2568  {
2569  unlink(acache->path);
2570  FREE(&acache->path);
2571  }
2572  }
2573  snprintf(article, sizeof(article), ANUM, nntp_edata_get(e)->article_num);
2574  msg->fp = mutt_bcache_get(mdata->bcache, article);
2575  if (msg->fp)
2576  {
2577  if (nntp_edata_get(e)->parsed)
2578  return true;
2579  }
2580  else
2581  {
2582  char buf[PATH_MAX];
2583  /* don't try to fetch article from removed newsgroup */
2584  if (mdata->deleted)
2585  return false;
2586 
2587  /* create new cache file */
2588  const char *fetch_msg = _("Fetching message...");
2589  mutt_message(fetch_msg);
2590  msg->fp = mutt_bcache_put(mdata->bcache, article);
2591  if (!msg->fp)
2592  {
2593  mutt_mktemp(buf, sizeof(buf));
2594  acache->path = mutt_str_dup(buf);
2595  acache->index = e->index;
2596  msg->fp = mutt_file_fopen(acache->path, "w+");
2597  if (!msg->fp)
2598  {
2599  mutt_perror(acache->path);
2600  unlink(acache->path);
2601  FREE(&acache->path);
2602  return false;
2603  }
2604  }
2605 
2606  /* fetch message to cache file */
2607  snprintf(buf, sizeof(buf), "ARTICLE %s\r\n",
2608  nntp_edata_get(e)->article_num ? article : e->env->message_id);
2609  const int rc =
2610  nntp_fetch_lines(mdata, buf, sizeof(buf), fetch_msg, fetch_tempfile, msg->fp);
2611  if (rc)
2612  {
2613  mutt_file_fclose(&msg->fp);
2614  if (acache->path)
2615  {
2616  unlink(acache->path);
2617  FREE(&acache->path);
2618  }
2619  if (rc > 0)
2620  {
2621  if (mutt_str_startswith(buf, nntp_edata_get(e)->article_num ? "423" : "430"))
2622  {
2623  mutt_error(_("Article %s not found on the server"),
2624  nntp_edata_get(e)->article_num ? article : e->env->message_id);
2625  }
2626  else
2627  mutt_error("ARTICLE: %s", buf);
2628  }
2629  return false;
2630  }
2631 
2632  if (!acache->path)
2633  mutt_bcache_commit(mdata->bcache, article);
2634  }
2635 
2636  /* replace envelope with new one
2637  * hash elements must be updated because pointers will be changed */
2638  if (m->id_hash && e->env->message_id)
2639  mutt_hash_delete(m->id_hash, e->env->message_id, e);
2640  if (m->subj_hash && e->env->real_subj)
2642 
2643  mutt_env_free(&e->env);
2644  e->env = mutt_rfc822_read_header(msg->fp, e, false, false);
2645 
2646  if (m->id_hash && e->env->message_id)
2647  mutt_hash_insert(m->id_hash, e->env->message_id, e);
2648  if (m->subj_hash && e->env->real_subj)
2650 
2651  /* fix content length */
2652  fseek(msg->fp, 0, SEEK_END);
2653  e->body->length = ftell(msg->fp) - e->body->offset;
2654 
2655  /* this is called in neomutt before the open which fetches the message,
2656  * which is probably wrong, but we just call it again here to handle
2657  * the problem instead of fixing it */
2658  nntp_edata_get(e)->parsed = true;
2660 
2661  /* these would normally be updated in ctx_update(), but the
2662  * full headers aren't parsed with overview, so the information wasn't
2663  * available then */
2664  if (WithCrypto)
2665  e->security = crypt_query(e->body);
2666 
2667  rewind(msg->fp);
2668  mutt_clear_error();
2669  return true;
2670 }
2671 
2677 static int nntp_msg_close(struct Mailbox *m, struct Message *msg)
2678 {
2679  return mutt_file_fclose(&msg->fp);
2680 }
2681 
2685 enum MailboxType nntp_path_probe(const char *path, const struct stat *st)
2686 {
2687  if (mutt_istr_startswith(path, "news://"))
2688  return MUTT_NNTP;
2689 
2690  if (mutt_istr_startswith(path, "snews://"))
2691  return MUTT_NNTP;
2692 
2693  return MUTT_UNKNOWN;
2694 }
2695 
2699 static int nntp_path_canon(char *buf, size_t buflen)
2700 {
2701  return 0;
2702 }
2703 
2707 static int nntp_path_pretty(char *buf, size_t buflen, const char *folder)
2708 {
2709  /* Succeed, but don't do anything, for now */
2710  return 0;
2711 }
2712 
2716 static int nntp_path_parent(char *buf, size_t buflen)
2717 {
2718  /* Succeed, but don't do anything, for now */
2719  return 0;
2720 }
2721 
2722 // clang-format off
2726 struct MxOps MxNntpOps = {
2727  .type = MUTT_NNTP,
2728  .name = "nntp",
2729  .is_local = false,
2730  .ac_owns_path = nntp_ac_owns_path,
2731  .ac_add = nntp_ac_add,
2732  .mbox_open = nntp_mbox_open,
2733  .mbox_open_append = NULL,
2734  .mbox_check = nntp_mbox_check,
2735  .mbox_check_stats = NULL,
2736  .mbox_sync = nntp_mbox_sync,
2737  .mbox_close = nntp_mbox_close,
2738  .msg_open = nntp_msg_open,
2739  .msg_open_new = NULL,
2740  .msg_commit = NULL,
2741  .msg_close = nntp_msg_close,
2742  .msg_padding_size = NULL,
2743  .msg_save_hcache = NULL,
2744  .tags_edit = NULL,
2745  .tags_commit = NULL,
2746  .path_probe = nntp_path_probe,
2747  .path_canon = nntp_path_canon,
2748  .path_pretty = nntp_path_pretty,
2749  .path_parent = nntp_path_parent,
2750 };
2751 // clang-format on
ChildCtx::mailbox
struct Mailbox * mailbox
Definition: nntp.c:109
nntp_check_new_groups
int nntp_check_new_groups(struct Mailbox *m, struct NntpAccountData *adata)
Check for new groups/articles in subscribed groups.
Definition: nntp.c:2015
nntp_check_children
int nntp_check_children(struct Mailbox *m, const char *msgid)
Fetch children of article with the Message-ID.
Definition: nntp.c:2188
C_SslForceTls
bool C_SslForceTls
Config: (ssl) Require TLS encryption for all connections.
Definition: config.c:46
nntp_post
int nntp_post(struct Mailbox *m, const char *msg)
Post article.
Definition: nntp.c:1866
Mailbox::subj_hash
struct HashTable * subj_hash
Hash Table by subject.
Definition: mailbox.h:128
NntpMboxData::unread
anum_t unread
Definition: mdata.h:39
Email::date_sent
time_t date_sent
Time when the message was sent (UTC)
Definition: email.h:82
NntpMboxData::group
char * group
Definition: mdata.h:33
Email::msgno
int msgno
Number displayed to the user.
Definition: email.h:87
nntp_bcache_update
void nntp_bcache_update(struct NntpMboxData *mdata)
Remove stale cached messages.
Definition: newsrc.c:800
Mailbox::rights
AclFlags rights
ACL bits, see AclFlags.
Definition: mailbox.h:121
C_NntpListgroup
bool C_NntpListgroup
Config: (nntp) Check all articles when opening a newsgroup.
Definition: config.c:46
Connection
An open network connection (socket)
Definition: connection.h:34
mutt_bcache_open
struct BodyCache * mutt_bcache_open(struct ConnAccount *account, const char *mailbox)
Open an Email-Body Cache.
Definition: bcache.c:144
mutt_sasl_client_new
int mutt_sasl_client_new(struct Connection *conn, sasl_conn_t **saslconn)
Wrapper for sasl_client_new()
Definition: sasl.c:603
MxStatus
MxStatus
Return values from mx_mbox_check(), mx_mbox_sync(), and mx_mbox_close()
Definition: mx.h:71
nntp_connect_error
static int nntp_connect_error(struct NntpAccountData *adata)
Signal a failed connection.
Definition: nntp.c:128
ConnAccount::host
char host[128]
Server to login to.
Definition: connaccount.h:53
mutt_mem_calloc
void * mutt_mem_calloc(size_t nmemb, size_t size)
Allocate zeroed memory on the heap.
Definition: memory.c:50
nntp_edata_free
void nntp_edata_free(void **ptr)
Free the private Email data - Implements Email::edata_free()
Definition: edata.c:37
MxOps::type
enum MailboxType type
Mailbox type, e.g. MUTT_IMAP.
Definition: mx.h:118
mutt_hash_delete
void mutt_hash_delete(struct HashTable *table, const char *strkey, const void *data)
Remove an element from a Hash Table.
Definition: hash.c:419
NntpAccountData::status
unsigned int status
Definition: adata.h:45
_
#define _(a)
Definition: message.h:28
MxNntpOps
struct MxOps MxNntpOps
NNTP Mailbox - Implements MxOps.
Definition: nntp.c:2726
Mailbox
A mailbox.
Definition: mailbox.h:81
NntpAccountData::authenticators
char * authenticators
Definition: adata.h:50
nntp_add_group
int nntp_add_group(char *line, void *data)
Parse newsgroup.
Definition: newsrc.c:576
Envelope::message_id
char * message_id
Message ID.
Definition: envelope.h:69
NntpAccountData::hasXGTITLE
bool hasXGTITLE
Definition: adata.h:39
U_NNTPS
@ U_NNTPS
Url is nntps://.
Definition: url.h:41
NntpAccountData::hasLISTGROUPrange
bool hasLISTGROUPrange
Definition: adata.h:41
nntp_msg_close
static int nntp_msg_close(struct Mailbox *m, struct Message *msg)
Close an email - Implements MxOps::msg_close()
Definition: nntp.c:2677
parse_overview_line
static int parse_overview_line(char *line, void *data)
Parse overview line.
Definition: nntp.c:992
restore
static struct Email * restore(const unsigned char *d)
Restore an Email from data retrieved from the cache.
Definition: hcache.c:146
Mailbox::emails
struct Email ** emails
Array of Emails.
Definition: mailbox.h:99
FetchCtx::restore
bool restore
Definition: nntp.c:98
nntp_hcache_update
void nntp_hcache_update(struct NntpMboxData *mdata, struct HeaderCache *hc)
Remove stale cached headers.
Definition: newsrc.c:732
mutt_bcache_put
FILE * mutt_bcache_put(struct BodyCache *bcache, const char *id)
Create a file in the Body Cache.
Definition: bcache.c:208
NT_MAILBOX_INVALID
@ NT_MAILBOX_INVALID
Email list was changed.
Definition: mailbox.h:173
NntpAccountData::groups_num
unsigned int groups_num
Definition: adata.h:56
nntp_open_connection
int nntp_open_connection(struct NntpAccountData *adata)
Connect to server, authenticate and get capabilities.
Definition: nntp.c:1702
edata.h
Connection::account
struct ConnAccount account
Account details: username, password, etc.
Definition: connection.h:36
Body::offset
LOFF_T offset
offset where the actual data begins
Definition: body.h:44
mutt_sasl_interact
int mutt_sasl_interact(sasl_interact_t *interaction)
Perform an SASL interaction with the user.
Definition: sasl.c:698
MUTT_ACL_INSERT
#define MUTT_ACL_INSERT
Add/copy into the mailbox (used when editing a message)
Definition: mailbox.h:69
FetchCtx::hc
struct HeaderCache * hc
Definition: nntp.c:101
mutt_file_fclose
int mutt_file_fclose(FILE **fp)
Close a FILE handle (and NULL the pointer)
Definition: file.c:153
mutt_socket_empty
void mutt_socket_empty(struct Connection *conn)
Clear out any queued data.
Definition: socket.c:312
MUTT_ACL_DELETE
#define MUTT_ACL_DELETE
Delete a message.
Definition: mailbox.h:66
ChildCtx::child
anum_t * child
Definition: nntp.c:112
nntp_path_parent
static int nntp_path_parent(char *buf, size_t buflen)
Find the parent of a Mailbox path - Implements MxOps::path_parent()
Definition: nntp.c:2716
nntp_capabilities
static int nntp_capabilities(struct NntpAccountData *adata)
Get capabilities.
Definition: nntp.c:142
C_SslStarttls
unsigned char C_SslStarttls
Config: (ssl) Use STARTTLS on servers advertising the capability.
Definition: config.c:47
mutt_socket_readln_d
int mutt_socket_readln_d(char *buf, size_t buflen, struct Connection *conn, int dbg)
Read a line from a socket.
Definition: socket.c:245
nntp_edata_get
struct NntpEmailData * nntp_edata_get(struct Email *e)
Get the private data for this Email.
Definition: edata.c:57
nntp_mbox_open
static enum MxOpenReturns nntp_mbox_open(struct Mailbox *m)
Open a Mailbox - Implements MxOps::mbox_open()
Definition: nntp.c:2288
NntpAccountData::check_time
time_t check_time
Definition: adata.h:55
MxOpenReturns
MxOpenReturns
Return values for mbox_open()
Definition: mx.h:84
NntpMboxData::bcache
struct BodyCache * bcache
Definition: mdata.h:48
ANUM
#define ANUM
Definition: lib.h:73
U_NO_FLAGS
#define U_NO_FLAGS
Definition: url.h:48
FetchCtx::messages
unsigned char * messages
Definition: nntp.c:99
MxOps
The Mailbox API.
Definition: mx.h:116
nntp_memchr
static bool nntp_memchr(char **haystack, char *sentinel, int needle)
look for a char in a binary buf, conveniently
Definition: nntp.c:387
nntp_active_fetch
int nntp_active_fetch(struct NntpAccountData *adata, bool mark_new)
Fetch list of all newsgroups from server.
Definition: nntp.c:1948
mutt_socket_close
int mutt_socket_close(struct Connection *conn)
Close a socket.
Definition: socket.c:96
Url::scheme
enum UrlScheme scheme
Scheme, e.g. U_SMTPS.
Definition: url.h:69
NntpAccountData::hasOVER
bool hasOVER
Definition: adata.h:42
mutt_parse.h
lib.h
MUTT_YES
@ MUTT_YES
User answered 'Yes', or assume 'Yes'.
Definition: quad.h:40
NntpMboxData::desc
char * desc
Definition: mdata.h:34
mutt_str_dup
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:370
mutt_globals.h
mutt_file_fopen
FILE * mutt_file_fopen(const char *path, const char *mode)
Call fopen() safely.
Definition: file.c:589
LL_DEBUG1
@ LL_DEBUG1
Log at debug level 1.
Definition: logging.h:40
NntpAccountData::groups_list
void ** groups_list
Definition: adata.h:58
FREE
#define FREE(x)
Definition: memory.h:40
C_ShowNewNews
bool C_ShowNewNews
Config: (nntp) Check for new newsgroups when entering the browser.
Definition: config.c:53
mutt_perror
#define mutt_perror(...)
Definition: logging.h:85
MX_OPEN_OK
@ MX_OPEN_OK
Open succeeded.
Definition: mx.h:86
FetchCtx::progress
struct Progress progress
Definition: nntp.c:100
get_description
static int get_description(struct NntpMboxData *mdata, const char *wildmat, const char *msg)
Fetch newsgroups descriptions.
Definition: nntp.c:880
mutt_socket_readln
#define mutt_socket_readln(buf, buflen, conn)
Definition: mutt_socket.h:36
Email::path
char * path
Path of Email (for local Mailboxes)
Definition: email.h:92
Email::edata_free
void(* edata_free)(void **ptr)
Free the private data attached to the Email.
Definition: email.h:117
C_NntpPoll
short C_NntpPoll
Config: (nntp) Interval between checks for new posts.
Definition: config.c:49
mutt_bcache_commit
int mutt_bcache_commit(struct BodyCache *bcache, const char *id)
Move a temporary file into the Body Cache.
Definition: bcache.c:248
NntpMboxData::first_message
anum_t first_message
Definition: mdata.h:35
PATH_MAX
#define PATH_MAX
Definition: mutt.h:44
mutt_sleep
void mutt_sleep(short s)
Sleep for a while.
Definition: muttlib.c:1448
nntp_group_poll
static int nntp_group_poll(struct NntpMboxData *mdata, bool update_stat)
Check newsgroup for new articles.
Definition: nntp.c:1382
Envelope::real_subj
char * real_subj
Offset of the real subject.
Definition: envelope.h:67
email_new
struct Email * email_new(void)
Create a new Email.
Definition: email.c:72
lib.h
url_parse
struct Url * url_parse(const char *src)
Fill in Url.
Definition: url.c:234
Mailbox::vcount
int vcount
The number of virtual messages.
Definition: mailbox.h:102
MX_STATUS_REOPENED
@ MX_STATUS_REOPENED
Mailbox was reopened.
Definition: mx.h:77
NntpAccountData::conn
struct Connection * conn
Definition: adata.h:60
nntp_parse_xref
static void nntp_parse_xref(struct Mailbox *m, struct Email *e)
Parse cross-reference.
Definition: nntp.c:912
NntpMboxData::adata
struct NntpAccountData * adata
Definition: mdata.h:46
Account::adata_free
void(* adata_free)(void **ptr)
Free the private data attached to the Account.
Definition: account.h:49
nntp_ac_owns_path
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()
Definition: nntp.c:2272
mutt_parse_mime_message
void mutt_parse_mime_message(struct Mailbox *m, struct Email *e)
Parse a MIME email.
Definition: mutt_parse.c:49
mutt_hash_find
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:354
CurrentNewsSrv
struct NntpAccountData * CurrentNewsSrv
Current NNTP news server.
Definition: nntp.c:79
mutt_str_equal
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:871
MX_STATUS_NEW_MAIL
@ MX_STATUS_NEW_MAIL
New mail received in Mailbox.
Definition: mx.h:75
lib.h
NewsrcEntry::last
anum_t last
Definition: lib.h:90
Connection::fd
int fd
Socket file descriptor.
Definition: connection.h:40
Email::old
bool old
Email is seen, but unread.
Definition: email.h:50
mutt_socket.h
Email::received
time_t received
Time when the message was placed in the mailbox.
Definition: email.h:83
protos.h
mutt_progress_init
void mutt_progress_init(struct Progress *progress, const char *msg, enum ProgressType type, size_t size)
Set up a progress bar.
Definition: progress.c:153
mutt_sasl_setup_conn
void mutt_sasl_setup_conn(struct Connection *conn, sasl_conn_t *saslconn)
Set up an SASL connection.
Definition: sasl.c:734
ChildCtx
Keep track of the children of an article.
Definition: nntp.c:107
mutt_bcache_get
FILE * mutt_bcache_get(struct BodyCache *bcache, const char *id)
Open a file in the Body Cache.
Definition: bcache.c:180
query_quadoption
enum QuadOption query_quadoption(enum QuadOption opt, const char *prompt)
Ask the user a quad-question.
Definition: curs_lib.c:518
NntpAccountData::hasDATE
bool hasDATE
Definition: adata.h:37
mx_alloc_memory
void mx_alloc_memory(struct Mailbox *m)
Create storage for the emails.
Definition: mx.c:1230
Envelope::xref
char * xref
List of cross-references.
Definition: envelope.h:76
NNTP_NONE
@ NNTP_NONE
No connection to server.
Definition: private.h:42
nntp_path_pretty
static int nntp_path_pretty(char *buf, size_t buflen, const char *folder)
Abbreviate a Mailbox path - Implements MxOps::path_pretty()
Definition: nntp.c:2707
FetchCtx::first
anum_t first
Definition: nntp.c:96
mutt_env_free
void mutt_env_free(struct Envelope **ptr)
Free an Envelope.
Definition: envelope.c:96
NntpAccountData::hasCAPABILITIES
bool hasCAPABILITIES
Definition: adata.h:35
MUTT_NNTP
@ MUTT_NNTP
'NNTP' (Usenet) Mailbox type
Definition: mailbox.h:52
ChildCtx::num
unsigned int num
Definition: nntp.c:110
crypt_query
SecurityFlags crypt_query(struct Body *m)
Check out the type of encryption used.
Definition: crypt.c:687
Mailbox::type
enum MailboxType type
Mailbox type.
Definition: mailbox.h:105
Mailbox::mdata
void * mdata
Driver specific data.
Definition: mailbox.h:136
url_tostring
int url_tostring(struct Url *url, char *dest, size_t len, uint8_t flags)
Output the URL string for a given Url object.
Definition: url.c:418
perform_auxsort
int perform_auxsort(int retval, const void *a, const void *b)
Compare two emails using the auxiliary sort method.
Definition: sort.c:66
init.h
fetch_tempfile
static int fetch_tempfile(char *line, void *data)
Write line to temporary file.
Definition: nntp.c:953
nntp_delete_group_cache
void nntp_delete_group_cache(struct NntpMboxData *mdata)
Remove hcache and bcache of newsgroup.
Definition: newsrc.c:809
lib.h
Account
A group of associated Mailboxes.
Definition: account.h:36
muttlib.h
nntp_hashelem_free
void nntp_hashelem_free(int type, void *obj, intptr_t data)
Free our hash table data - Implements hash_hdata_free_t.
Definition: nntp.c:118
nntp_newsrc_parse
int nntp_newsrc_parse(struct NntpAccountData *adata)
Parse .newsrc file.
Definition: newsrc.c:164
C_NntpContext
short C_NntpContext
Config: (nntp) Maximum number of articles to list (0 for all articles)
Definition: config.c:45
NntpAccountData::use_tls
unsigned int use_tls
Definition: adata.h:44
Progress
A progress bar.
Definition: progress.h:50
mutt_account_hook
void mutt_account_hook(const char *url)
Perform an account hook.
Definition: hook.c:756
NntpAccountData::hasXOVER
bool hasXOVER
Definition: adata.h:43
Body::length
LOFF_T length
length (in bytes) of attachment
Definition: body.h:45
Url
A parsed URL proto://user:password@host:port/path?a=1&b=2
Definition: url.h:67
NntpMboxData::newsrc_len
unsigned int newsrc_len
Definition: mdata.h:44
Mailbox::account
struct Account * account
Account that owns this Mailbox.
Definition: mailbox.h:131
lib.h
Connection::inbuf
char inbuf[1024]
Buffer for incoming traffic.
Definition: connection.h:38
url_free
void url_free(struct Url **ptr)
Free the contents of a URL.
Definition: url.c:123
Mailbox::msg_count
int msg_count
Total number of messages.
Definition: mailbox.h:91
mutt_clear_error
void mutt_clear_error(void)
Clear the message line (bottom line of screen)
Definition: mutt_logging.c:113
nntp_attempt_features
static int nntp_attempt_features(struct NntpAccountData *adata)
Detect supported commands.
Definition: nntp.c:245
MUTT_UNKNOWN
@ MUTT_UNKNOWN
Mailbox wasn't recognised.
Definition: mailbox.h:47
NntpMboxData::last_message
anum_t last_message
Definition: mdata.h:36
nntp_ac_add
static bool nntp_ac_add(struct Account *a, struct Mailbox *m)
Add a Mailbox to an Account - Implements MxOps::ac_add()
Definition: nntp.c:2280
mutt_hash_insert
struct HashElem * mutt_hash_insert(struct HashTable *table, const char *strkey, void *data)
Add a new element to the Hash Table (with string keys)
Definition: hash.c:327
mdata.h
MUTT_SOCK_LOG_HDR
#define MUTT_SOCK_LOG_HDR
Definition: mutt_socket.h:30
C_SaveUnsubscribed
bool C_SaveUnsubscribed
Config: (nntp) Save a list of unsubscribed newsgroups to the 'newsrc'.
Definition: config.c:52
NntpMboxData::newsrc_ent
struct NewsrcEntry * newsrc_ent
Definition: mdata.h:45
MUTT_PROGRESS_READ
@ MUTT_PROGRESS_READ
Progress tracks elements, according to $read_inc
Definition: progress.h:42
mutt_socket_send
#define mutt_socket_send(conn, buf)
Definition: mutt_socket.h:37
nntp_fetch_lines
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:766
nntp_mbox_sync
static enum MxStatus nntp_mbox_sync(struct Mailbox *m)
Save changes to the Mailbox - Implements MxOps::mbox_sync()
Definition: nntp.c:2465
HCacheEntry::email
struct Email * email
Retrieved email.
Definition: lib.h:100
mutt_mem_realloc
void mutt_mem_realloc(void *ptr, size_t size)
Resize a block of memory on the heap.
Definition: memory.c:114
Email::env
struct Envelope * env
Envelope information.
Definition: email.h:90
mutt_debug
#define mutt_debug(LEVEL,...)
Definition: logging.h:81
nntp_newsrc_close
void nntp_newsrc_close(struct NntpAccountData *adata)
Unlock and close .newsrc file.
Definition: newsrc.c:120
Mailbox::email_max
int email_max
Number of pointers in emails.
Definition: mailbox.h:100
anum_t
#define anum_t
Definition: lib.h:72
nntp_acache_free
void nntp_acache_free(struct NntpMboxData *mdata)
Remove all temporarily cache files.
Definition: newsrc.c:104
NntpEmailData::parsed
bool parsed
Definition: edata.h:34
nntp_msg_open
static bool nntp_msg_open(struct Mailbox *m, struct Message *msg, int msgno)
Open an email message in a Mailbox - Implements MxOps::msg_open()
Definition: nntp.c:2547
HCacheEntry
Wrapper for Email retrieved from the header cache.
Definition: lib.h:96
Mailbox::realpath
char * realpath
Used for duplicate detection, context comparison, and the sidebar.
Definition: mailbox.h:84
Message::fp
FILE * fp
pointer to the message data
Definition: mx.h:96
MX_STATUS_ERROR
@ MX_STATUS_ERROR
An error occurred.
Definition: mx.h:73
Email::flagged
bool flagged
Marked important?
Definition: email.h:43
NewsrcEntry
An entry in a .newsrc (subscribed newsgroups)
Definition: lib.h:87
nntp_select_server
struct NntpAccountData * nntp_select_server(struct Mailbox *m, const char *server, bool leave_lock)
Open a connection to an NNTP server.
Definition: newsrc.c:1012
MUTT_ACCT_USER
#define MUTT_ACCT_USER
User field has been set.
Definition: connaccount.h:43
nntp_mdata_free
void nntp_mdata_free(void **ptr)
Free the private Mailbox data - Implements Mailbox::mdata_free()
Definition: mdata.c:37
Mailbox::verbose
bool verbose
Display status messages?
Definition: mailbox.h:118
ConnAccount::flags
MuttAccountFlags flags
Which fields are initialised, e.g. MUTT_ACCT_USER.
Definition: connaccount.h:59
Email::security
SecurityFlags security
bit 0-10: flags, bit 11,12: application, bit 13: traditional pgp See: ncrypt/lib.h pgplib....
Definition: email.h:39
nntp_path_probe
enum MailboxType nntp_path_probe(const char *path, const struct stat *st)
Is this an NNTP Mailbox? - Implements MxOps::path_probe()
Definition: nntp.c:2685
NntpMboxData::last_loaded
anum_t last_loaded
Definition: mdata.h:37
mutt_date_epoch
time_t mutt_date_epoch(void)
Return the number of seconds since the Unix epoch.
Definition: date.c:416
MUTT_ACL_WRITE
#define MUTT_ACL_WRITE
Write to a message (for flagging or linking threads)
Definition: mailbox.h:74
nntp_group_unread_stat
void nntp_group_unread_stat(struct NntpMboxData *mdata)
Count number of unread articles using .newsrc data.
Definition: newsrc.c:134
mutt_str_replace
char * mutt_str_replace(char **p, const char *s)
Replace one string with another.
Definition: string.c:446
nntp_newsrc_gen_entries
void nntp_newsrc_gen_entries(struct Mailbox *m)
Generate array of .newsrc entries.
Definition: newsrc.c:298
OverviewFmt
const char * OverviewFmt
Definition: nntp.c:81
WithCrypto
#define WithCrypto
Definition: lib.h:123
mutt_logging.h
nntp_newsrc_update
int nntp_newsrc_update(struct NntpAccountData *adata)
Update .newsrc file.
Definition: newsrc.c:442
NNTP_BYE
@ NNTP_BYE
Disconnected from server.
Definition: private.h:44
Envelope::newsgroups
char * newsgroups
List of newsgroups.
Definition: envelope.h:75
lib.h
lib.h
NntpMboxData::acache
struct NntpAcache acache[NNTP_ACACHE_LEN]
Definition: mdata.h:47
mutt_hcache_store
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:527
Email::deleted
bool deleted
Email is deleted.
Definition: email.h:45
nntp_mbox_close
static enum MxStatus nntp_mbox_close(struct Mailbox *m)
Close a Mailbox - Implements MxOps::mbox_close()
Definition: nntp.c:2525
FetchCtx
Keep track when getting data from a server.
Definition: nntp.c:93
mutt_bcache_del
int mutt_bcache_del(struct BodyCache *bcache, const char *id)
Delete a file from the Body Cache.
Definition: bcache.c:265
nntp_article_status
void nntp_article_status(struct Mailbox *m, struct Email *e, char *group, anum_t anum)
Get status of articles from .newsrc.
Definition: newsrc.c:1211
Mailbox::readonly
bool readonly
Don't allow changes to the mailbox.
Definition: mailbox.h:119
mutt_mktemp
#define mutt_mktemp(buf, buflen)
Definition: muttlib.h:74
NntpAccountData::newsrc_modified
bool newsrc_modified
Definition: adata.h:47
mutt_str_cat
char * mutt_str_cat(char *buf, size_t buflen, const char *s)
Concatenate two strings.
Definition: string.c:385
HeaderCache
header cache structure
Definition: lib.h:85
NewsrcEntry::first
anum_t first
Definition: lib.h:89
NntpAccountData
NNTP-specific Account data -.
Definition: adata.h:33
mutt_socket_open
int mutt_socket_open(struct Connection *conn)
Simple wrapper.
Definition: socket.c:75
C_DebugLevel
short C_DebugLevel
Config: Logging level for debug logs.
Definition: mutt_logging.c:48
Progress::msg
char msg[1024]
Definition: progress.h:52
sort.h
C_NewsServer
char * C_NewsServer
Config: (nntp) Url of the news server.
Definition: config.c:41
FetchCtx::mailbox
struct Mailbox * mailbox
Definition: nntp.c:95
adata.h
Email::edata
void * edata
Driver-specific data.
Definition: email.h:111
mutt_mem_malloc
void * mutt_mem_malloc(size_t size)
Allocate memory on the heap.
Definition: memory.c:90
private.h
nntp_hcache_open
struct HeaderCache * nntp_hcache_open(struct NntpMboxData *mdata)
Open newsgroup hcache.
Definition: newsrc.c:710
MUTT_TAG
@ MUTT_TAG
Tagged messages.
Definition: mutt.h:103
progress.h
C_NntpLoadDescription
bool C_NntpLoadDescription
Config: (nntp) Load descriptions for newsgroups when adding to the list.
Definition: config.c:47
mutt_str_remove_trailing_ws
void mutt_str_remove_trailing_ws(char *s)
Trim trailing whitespace from a string.
Definition: string.c:700
mutt.h
mutt_hcache_delete_record
int mutt_hcache_delete_record(struct HeaderCache *hc, const char *key, size_t keylen)
Multiplexor for StoreOps::delete_record.
Definition: hcache.c:604
U_NNTP
@ U_NNTP
Url is nntp://.
Definition: url.h:40
mutt_istr_startswith
size_t mutt_istr_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix, ignoring case.
Definition: string.c:172
mutt_date_gmtime
struct tm mutt_date_gmtime(time_t t)
Converts calendar time to a broken-down time structure expressed in UTC timezone.
Definition: date.c:661
NntpAccountData::overview_fmt
char * overview_fmt
Definition: adata.h:51
Url::host
char * host
Host.
Definition: url.h:72
mailbox_changed
void mailbox_changed(struct Mailbox *m, enum NotifyMailbox action)
Notify observers of a change to a Mailbox.
Definition: mailbox.c:186
check_mailbox
static enum MxStatus check_mailbox(struct Mailbox *m)
Check current newsgroup for new articles.
Definition: nntp.c:1427
ConnAccount::pass
char pass[256]
Password.
Definition: connaccount.h:56
Account::adata
void * adata
Private data (for Mailbox backends)
Definition: account.h:43
NntpMboxData::deleted
bool deleted
Definition: mdata.h:43
MailboxType
MailboxType
Supported mailbox formats.
Definition: mailbox.h:43
nntp_compare_order
int nntp_compare_order(const void *a, const void *b)
Sort to mailbox order - Implements sort_t.
Definition: nntp.c:2257
NntpMboxData::last_cached
anum_t last_cached
Definition: mdata.h:38
Email::index
int index
The absolute (unsorted) message number.
Definition: email.h:86
NntpEmailData::article_num
anum_t article_num
Definition: edata.h:33
mutt_rfc822_read_header
struct Envelope * mutt_rfc822_read_header(FILE *fp, struct Email *e, bool user_hdrs, bool weed)
parses an RFC822 header
Definition: parse.c:1112
MUTT_SOCK_LOG_CMD
#define MUTT_SOCK_LOG_CMD
Definition: mutt_socket.h:29
email_free
void email_free(struct Email **ptr)
Free an Email.
Definition: email.c:43
mutt_progress_update
void mutt_progress_update(struct Progress *progress, size_t pos, int percent)
Update the state of the progress bar.
Definition: progress.c:212
plen
static size_t plen
Length of cached packet.
Definition: pgppacket.c:39
MUTT_SOCK_LOG_FULL
#define MUTT_SOCK_LOG_FULL
Definition: mutt_socket.h:31
Mailbox::msg_unread
int msg_unread
Number of unread messages.
Definition: mailbox.h:92
Mailbox::msg_tagged
int msg_tagged
How many messages are tagged?
Definition: mailbox.h:97
C_NntpAuthenticators
char * C_NntpAuthenticators
Config: (nntp) Allowed authentication methods.
Definition: config.c:44
Url::path
char * path
Path.
Definition: url.h:74
mailbox_path
static const char * mailbox_path(const struct Mailbox *m)
Get the Mailbox's path string.
Definition: mailbox.h:206
mx.h
NntpAccountData::hasLIST_NEWSGROUPS
bool hasLIST_NEWSGROUPS
Definition: adata.h:38
nntp_check_msgid
int nntp_check_msgid(struct Mailbox *m, const char *msgid)
Fetch article by Message-ID.
Definition: nntp.c:2118
fetch_children
static int fetch_children(char *line, void *data)
Parse XPAT line.
Definition: nntp.c:1672
nntp_fetch_headers
static int nntp_fetch_headers(struct Mailbox *m, void *hc, anum_t first, anum_t last, bool restore)
Fetch headers.
Definition: nntp.c:1150
lib.h
mutt_hcache_close
void mutt_hcache_close(struct HeaderCache *hc)
Multiplexor for StoreOps::close.
Definition: hcache.c:419
nntp_mbox_check
static enum MxStatus nntp_mbox_check(struct Mailbox *m)
Check for new mail - Implements MxOps::mbox_check()
Definition: nntp.c:2448
mutt_yesorno
enum QuadOption mutt_yesorno(const char *msg, enum QuadOption def)
Ask the user a Yes/No question.
Definition: curs_lib.c:380
Email
The envelope/body of an email.
Definition: email.h:37
mutt_ssl_starttls
int mutt_ssl_starttls(struct Connection *conn)
Negotiate TLS over an already opened connection.
Definition: gnutls.c:1136
mutt_account_getuser
int mutt_account_getuser(struct ConnAccount *cac)
Retrieve username into ConnAccount, if necessary.
Definition: connaccount.c:48
ChildCtx::max
unsigned int max
Definition: nntp.c:111
lib.h
NntpAcache::index
unsigned int index
Definition: lib.h:80
FetchCtx::last
anum_t last
Definition: nntp.c:97
mutt_str_startswith
size_t mutt_str_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix.
Definition: string.c:160
nntp_edata_new
struct NntpEmailData * nntp_edata_new(void)
Create a new NntpEmailData for an Email.
Definition: edata.c:47
nntp_query
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:689
mutt_message
#define mutt_message(...)
Definition: logging.h:83
mutt_set_flag
#define mutt_set_flag(m, e, flag, bf)
Definition: protos.h:67
fetch_description
static int fetch_description(char *line, void *data)
Parse newsgroup description.
Definition: nntp.c:845
fetch_numbers
static int fetch_numbers(char *line, void *data)
Parse article number.
Definition: nntp.c:970
NNTP_OK
@ NNTP_OK
Connected to server.
Definition: private.h:43
MX_OPEN_ERROR
@ MX_OPEN_ERROR
Open failed with an error.
Definition: mx.h:87
NNTP_ACACHE_LEN
#define NNTP_ACACHE_LEN
Definition: lib.h:94
NntpAccountData::hasSTARTTLS
bool hasSTARTTLS
Definition: adata.h:36
mutt_file_mkstemp
#define mutt_file_mkstemp()
Definition: file.h:109
nntp_path_canon
static int nntp_path_canon(char *buf, size_t buflen)
Canonicalise a Mailbox path - Implements MxOps::path_canon()
Definition: nntp.c:2699
NntpAccountData::hasLISTGROUP
bool hasLISTGROUP
Definition: adata.h:40
nntp_date
static int nntp_date(struct NntpAccountData *adata, time_t *now)
Get date and time from server.
Definition: nntp.c:1635
NntpAcache
NNTP article cache.
Definition: lib.h:78
nntp_active_save_cache
int nntp_active_save_cache(struct NntpAccountData *adata)
Save list of all newsgroups to cache.
Definition: newsrc.c:651
nntp_auth
static int nntp_auth(struct NntpAccountData *adata)
Get login, password and authenticate.
Definition: nntp.c:427
Email::read
bool read
Email is read.
Definition: email.h:51
hook.h
NntpAccountData::newgroups_time
time_t newgroups_time
Definition: adata.h:54
Mailbox::id_hash
struct HashTable * id_hash
Hash Table by msg id.
Definition: mailbox.h:127
ConnAccount::user
char user[128]
Username.
Definition: connaccount.h:55
NntpAccountData::groups_hash
struct HashTable * groups_hash
Definition: adata.h:59
LL_DEBUG2
@ LL_DEBUG2
Log at debug level 2.
Definition: logging.h:41
mutt_account_getpass
int mutt_account_getpass(struct ConnAccount *cac)
Fetch password into ConnAccount, if necessary.
Definition: connaccount.c:111
mutt_socket_send_d
#define mutt_socket_send_d(conn, buf, dbg)
Definition: mutt_socket.h:38
Email::changed
bool changed
Email has been edited.
Definition: email.h:48
NntpMboxData
NNTP-specific Mailbox data -.
Definition: mdata.h:31
Message
A local copy of an email.
Definition: mx.h:94
nntp_adata_free
void nntp_adata_free(void **ptr)
Free the private Account data - Implements Account::adata_free()
Definition: adata.c:41
MX_STATUS_OK
@ MX_STATUS_OK
No changes.
Definition: mx.h:74
Email::body
struct Body * body
List of MIME parts.
Definition: email.h:91
NntpAcache::path
char * path
Definition: lib.h:81
mutt_error
#define mutt_error(...)
Definition: logging.h:84
SORT_CODE
#define SORT_CODE(x)
Definition: sort.h:40
nntp_log_binbuf
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:405
mutt_hcache_fetch
struct HCacheEntry mutt_hcache_fetch(struct HeaderCache *hc, const char *key, size_t keylen, uint32_t uidvalidity)
Multiplexor for StoreOps::fetch.
Definition: hcache.c:438
mutt_str_copy
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:716