NeoMutt  2020-11-20
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 "hook.h"
54 #include "init.h"
55 #include "mutt_globals.h"
56 #include "mutt_logging.h"
57 #include "mutt_parse.h"
58 #include "mutt_socket.h"
59 #include "muttlib.h"
60 #include "mx.h"
61 #include "progress.h"
62 #include "sort.h"
63 #ifdef USE_HCACHE
64 #include "protos.h"
65 #endif
66 #ifdef USE_SASL
67 #include <sasl/sasl.h>
68 #include <sasl/saslutil.h>
69 #endif
70 #if defined(USE_SSL) || defined(USE_HCACHE)
71 #include "mutt.h"
72 #endif
73 
74 struct stat;
75 
77 
78 const char *OverviewFmt = "Subject:\0"
79  "From:\0"
80  "Date:\0"
81  "Message-ID:\0"
82  "References:\0"
83  "Content-Length:\0"
84  "Lines:\0"
85  "\0";
86 
90 struct FetchCtx
91 {
92  struct Mailbox *mailbox;
95  bool restore;
96  unsigned char *messages;
98  struct HeaderCache *hc;
99 };
100 
104 struct ChildCtx
105 {
106  struct Mailbox *mailbox;
107  unsigned int num;
108  unsigned int max;
110 };
111 
119 static void nntp_adata_free(void **ptr)
120 {
121  if (!ptr || !*ptr)
122  return;
123 
124  struct NntpAccountData *adata = *ptr;
125 
126  mutt_file_fclose(&adata->fp_newsrc);
127  FREE(&adata->newsrc_file);
128  FREE(&adata->authenticators);
129  FREE(&adata->overview_fmt);
130  FREE(&adata->conn);
131  FREE(&adata->groups_list);
132  mutt_hash_free(&adata->groups_hash);
133  FREE(ptr);
134 }
135 
139 static void nntp_hashelem_free(int type, void *obj, intptr_t data)
140 {
141  nntp_mdata_free(&obj);
142 }
143 
150 {
151  struct NntpAccountData *adata = mutt_mem_calloc(1, sizeof(struct NntpAccountData));
152  adata->conn = conn;
155  adata->groups_max = 16;
156  adata->groups_list =
157  mutt_mem_malloc(adata->groups_max * sizeof(struct NntpMboxData *));
158  return adata;
159 }
160 
161 #if 0
162 
166 struct NntpAccountData *nntp_adata_get(struct Mailbox *m)
167 {
168  if (!m || (m->type != MUTT_NNTP))
169  return NULL;
170  struct Account *a = m->account;
171  if (!a)
172  return NULL;
173  return a->adata;
174 }
175 #endif
176 
180 void nntp_mdata_free(void **ptr)
181 {
182  if (!ptr || !*ptr)
183  return;
184 
185  struct NntpMboxData *mdata = *ptr;
186 
187  nntp_acache_free(mdata);
188  mutt_bcache_close(&mdata->bcache);
189  FREE(&mdata->newsrc_ent);
190  FREE(&mdata->desc);
191  FREE(ptr);
192 }
193 
197 static void nntp_edata_free(void **ptr)
198 {
199  // struct NntpEmailData *edata = *ptr;
200  FREE(ptr);
201 }
202 
207 static struct NntpEmailData *nntp_edata_new(void)
208 {
209  return mutt_mem_calloc(1, sizeof(struct NntpEmailData));
210 }
211 
218 {
219  if (!e)
220  return NULL;
221  return e->edata;
222 }
223 
229 static int nntp_connect_error(struct NntpAccountData *adata)
230 {
231  adata->status = NNTP_NONE;
232  mutt_error(_("Server closed connection"));
233  return -1;
234 }
235 
243 static int nntp_capabilities(struct NntpAccountData *adata)
244 {
245  struct Connection *conn = adata->conn;
246  bool mode_reader = false;
247  char buf[1024];
248  char authinfo[1024] = { 0 };
249 
250  adata->hasCAPABILITIES = false;
251  adata->hasSTARTTLS = false;
252  adata->hasDATE = false;
253  adata->hasLIST_NEWSGROUPS = false;
254  adata->hasLISTGROUP = false;
255  adata->hasLISTGROUPrange = false;
256  adata->hasOVER = false;
257  FREE(&adata->authenticators);
258 
259  if ((mutt_socket_send(conn, "CAPABILITIES\r\n") < 0) ||
260  (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
261  {
262  return nntp_connect_error(adata);
263  }
264 
265  /* no capabilities */
266  if (!mutt_str_startswith(buf, "101"))
267  return 1;
268  adata->hasCAPABILITIES = true;
269 
270  /* parse capabilities */
271  do
272  {
273  size_t plen = 0;
274  if (mutt_socket_readln(buf, sizeof(buf), conn) < 0)
275  return nntp_connect_error(adata);
276  if (mutt_str_equal("STARTTLS", buf))
277  adata->hasSTARTTLS = true;
278  else if (mutt_str_equal("MODE-READER", buf))
279  mode_reader = true;
280  else if (mutt_str_equal("READER", buf))
281  {
282  adata->hasDATE = true;
283  adata->hasLISTGROUP = true;
284  adata->hasLISTGROUPrange = true;
285  }
286  else if ((plen = mutt_str_startswith(buf, "AUTHINFO ")))
287  {
288  mutt_str_cat(buf, sizeof(buf), " ");
289  mutt_str_copy(authinfo, buf + plen - 1, sizeof(authinfo));
290  }
291 #ifdef USE_SASL
292  else if ((plen = mutt_str_startswith(buf, "SASL ")))
293  {
294  char *p = buf + plen;
295  while (*p == ' ')
296  p++;
297  adata->authenticators = mutt_str_dup(p);
298  }
299 #endif
300  else if (mutt_str_equal("OVER", buf))
301  adata->hasOVER = true;
302  else if (mutt_str_startswith(buf, "LIST "))
303  {
304  char *p = strstr(buf, " NEWSGROUPS");
305  if (p)
306  {
307  p += 11;
308  if ((*p == '\0') || (*p == ' '))
309  adata->hasLIST_NEWSGROUPS = true;
310  }
311  }
312  } while (!mutt_str_equal(".", buf));
313  *buf = '\0';
314 #ifdef USE_SASL
315  if (adata->authenticators && strcasestr(authinfo, " SASL "))
316  mutt_str_copy(buf, adata->authenticators, sizeof(buf));
317 #endif
318  if (strcasestr(authinfo, " USER "))
319  {
320  if (*buf != '\0')
321  mutt_str_cat(buf, sizeof(buf), " ");
322  mutt_str_cat(buf, sizeof(buf), "USER");
323  }
324  mutt_str_replace(&adata->authenticators, buf);
325 
326  /* current mode is reader */
327  if (adata->hasDATE)
328  return 0;
329 
330  /* server is mode-switching, need to switch to reader mode */
331  if (mode_reader)
332  return 1;
333 
334  mutt_socket_close(conn);
335  adata->status = NNTP_BYE;
336  mutt_error(_("Server doesn't support reader mode"));
337  return -1;
338 }
339 
346 static int nntp_attempt_features(struct NntpAccountData *adata)
347 {
348  struct Connection *conn = adata->conn;
349  char buf[1024];
350 
351  /* no CAPABILITIES, trying DATE, LISTGROUP, LIST NEWSGROUPS */
352  if (!adata->hasCAPABILITIES)
353  {
354  if ((mutt_socket_send(conn, "DATE\r\n") < 0) ||
355  (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
356  {
357  return nntp_connect_error(adata);
358  }
359  if (!mutt_str_startswith(buf, "500"))
360  adata->hasDATE = true;
361 
362  if ((mutt_socket_send(conn, "LISTGROUP\r\n") < 0) ||
363  (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
364  {
365  return nntp_connect_error(adata);
366  }
367  if (!mutt_str_startswith(buf, "500"))
368  adata->hasLISTGROUP = true;
369 
370  if ((mutt_socket_send(conn, "LIST NEWSGROUPS +\r\n") < 0) ||
371  (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
372  {
373  return nntp_connect_error(adata);
374  }
375  if (!mutt_str_startswith(buf, "500"))
376  adata->hasLIST_NEWSGROUPS = true;
377  if (mutt_str_startswith(buf, "215"))
378  {
379  do
380  {
381  if (mutt_socket_readln(buf, sizeof(buf), conn) < 0)
382  return nntp_connect_error(adata);
383  } while (!mutt_str_equal(".", buf));
384  }
385  }
386 
387  /* no LIST NEWSGROUPS, trying XGTITLE */
388  if (!adata->hasLIST_NEWSGROUPS)
389  {
390  if ((mutt_socket_send(conn, "XGTITLE\r\n") < 0) ||
391  (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
392  {
393  return nntp_connect_error(adata);
394  }
395  if (!mutt_str_startswith(buf, "500"))
396  adata->hasXGTITLE = true;
397  }
398 
399  /* no OVER, trying XOVER */
400  if (!adata->hasOVER)
401  {
402  if ((mutt_socket_send(conn, "XOVER\r\n") < 0) ||
403  (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
404  {
405  return nntp_connect_error(adata);
406  }
407  if (!mutt_str_startswith(buf, "500"))
408  adata->hasXOVER = true;
409  }
410 
411  /* trying LIST OVERVIEW.FMT */
412  if (adata->hasOVER || adata->hasXOVER)
413  {
414  if ((mutt_socket_send(conn, "LIST OVERVIEW.FMT\r\n") < 0) ||
415  (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
416  {
417  return nntp_connect_error(adata);
418  }
419  if (!mutt_str_startswith(buf, "215"))
420  adata->overview_fmt = mutt_str_dup(OverviewFmt);
421  else
422  {
423  bool cont = false;
424  size_t buflen = 2048, off = 0, b = 0;
425 
426  FREE(&adata->overview_fmt);
427  adata->overview_fmt = mutt_mem_malloc(buflen);
428 
429  while (true)
430  {
431  if ((buflen - off) < 1024)
432  {
433  buflen *= 2;
434  mutt_mem_realloc(&adata->overview_fmt, buflen);
435  }
436 
437  const int chunk = mutt_socket_readln_d(adata->overview_fmt + off,
438  buflen - off, conn, MUTT_SOCK_LOG_HDR);
439  if (chunk < 0)
440  {
441  FREE(&adata->overview_fmt);
442  return nntp_connect_error(adata);
443  }
444 
445  if (!cont && mutt_str_equal(".", adata->overview_fmt + off))
446  break;
447 
448  cont = (chunk >= (buflen - off));
449  off += strlen(adata->overview_fmt + off);
450  if (!cont)
451  {
452  if (adata->overview_fmt[b] == ':')
453  {
454  memmove(adata->overview_fmt + b, adata->overview_fmt + b + 1, off - b - 1);
455  adata->overview_fmt[off - 1] = ':';
456  }
457  char *colon = strchr(adata->overview_fmt + b, ':');
458  if (!colon)
459  adata->overview_fmt[off++] = ':';
460  else if (strcmp(colon + 1, "full") != 0)
461  off = colon + 1 - adata->overview_fmt;
462  if (strcasecmp(adata->overview_fmt + b, "Bytes:") == 0)
463  {
464  size_t len = strlen(adata->overview_fmt + b);
465  mutt_str_copy(adata->overview_fmt + b, "Content-Length:", len + 1);
466  off = b + len;
467  }
468  adata->overview_fmt[off++] = '\0';
469  b = off;
470  }
471  }
472  adata->overview_fmt[off++] = '\0';
473  mutt_mem_realloc(&adata->overview_fmt, off);
474  }
475  }
476  return 0;
477 }
478 
479 #ifdef USE_SASL
480 
488 static bool nntp_memchr(char **haystack, char *sentinel, int needle)
489 {
490  char *start = *haystack;
491  size_t max_offset = sentinel - start;
492  void *vp = memchr(start, max_offset, needle);
493  if (!vp)
494  return false;
495  *haystack = vp;
496  return true;
497 }
498 
506 static void nntp_log_binbuf(const char *buf, size_t len, const char *pfx, int dbg)
507 {
508  char tmp[1024];
509  char *p = tmp;
510  char *sentinel = tmp + len;
511 
512  if (C_DebugLevel < dbg)
513  return;
514  memcpy(tmp, buf, len);
515  tmp[len] = '\0';
516  while (nntp_memchr(&p, sentinel, '\0'))
517  *p = '.';
518  mutt_debug(dbg, "%s> %s\n", pfx, tmp);
519 }
520 #endif
521 
528 static int nntp_auth(struct NntpAccountData *adata)
529 {
530  struct Connection *conn = adata->conn;
531  char buf[1024];
532  char authenticators[1024] = "USER";
533  char *method = NULL, *a = NULL, *p = NULL;
534  unsigned char flags = conn->account.flags;
535 
536  while (true)
537  {
538  /* get login and password */
539  if ((mutt_account_getuser(&conn->account) < 0) || (conn->account.user[0] == '\0') ||
540  (mutt_account_getpass(&conn->account) < 0) || (conn->account.pass[0] == '\0'))
541  {
542  break;
543  }
544 
545  /* get list of authenticators */
547  mutt_str_copy(authenticators, C_NntpAuthenticators, sizeof(authenticators));
548  else if (adata->hasCAPABILITIES)
549  {
550  mutt_str_copy(authenticators, adata->authenticators, sizeof(authenticators));
551  p = authenticators;
552  while (*p)
553  {
554  if (*p == ' ')
555  *p = ':';
556  p++;
557  }
558  }
559  p = authenticators;
560  while (*p)
561  {
562  *p = toupper(*p);
563  p++;
564  }
565 
566  mutt_debug(LL_DEBUG1, "available methods: %s\n", adata->authenticators);
567  a = authenticators;
568  while (true)
569  {
570  if (!a)
571  {
572  mutt_error(_("No authenticators available"));
573  break;
574  }
575 
576  method = a;
577  a = strchr(a, ':');
578  if (a)
579  *a++ = '\0';
580 
581  /* check authenticator */
582  if (adata->hasCAPABILITIES)
583  {
584  char *m = NULL;
585 
586  if (!adata->authenticators)
587  continue;
588  m = strcasestr(adata->authenticators, method);
589  if (!m)
590  continue;
591  if ((m > adata->authenticators) && (*(m - 1) != ' '))
592  continue;
593  m += strlen(method);
594  if ((*m != '\0') && (*m != ' '))
595  continue;
596  }
597  mutt_debug(LL_DEBUG1, "trying method %s\n", method);
598 
599  /* AUTHINFO USER authentication */
600  if (strcmp(method, "USER") == 0)
601  {
602  // L10N: (%s) is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
603  mutt_message(_("Authenticating (%s)..."), method);
604  snprintf(buf, sizeof(buf), "AUTHINFO USER %s\r\n", conn->account.user);
605  if ((mutt_socket_send(conn, buf) < 0) ||
606  (mutt_socket_readln_d(buf, sizeof(buf), conn, MUTT_SOCK_LOG_FULL) < 0))
607  {
608  break;
609  }
610 
611  /* authenticated, password is not required */
612  if (mutt_str_startswith(buf, "281"))
613  return 0;
614 
615  /* username accepted, sending password */
616  if (mutt_str_startswith(buf, "381"))
617  {
618  mutt_debug(MUTT_SOCK_LOG_FULL, "%d> AUTHINFO PASS *\n", conn->fd);
619  snprintf(buf, sizeof(buf), "AUTHINFO PASS %s\r\n", conn->account.pass);
620  if ((mutt_socket_send_d(conn, buf, MUTT_SOCK_LOG_FULL) < 0) ||
621  (mutt_socket_readln_d(buf, sizeof(buf), conn, MUTT_SOCK_LOG_FULL) < 0))
622  {
623  break;
624  }
625 
626  /* authenticated */
627  if (mutt_str_startswith(buf, "281"))
628  return 0;
629  }
630 
631  /* server doesn't support AUTHINFO USER, trying next method */
632  if (*buf == '5')
633  continue;
634  }
635  else
636  {
637 #ifdef USE_SASL
638  sasl_conn_t *saslconn = NULL;
639  sasl_interact_t *interaction = NULL;
640  int rc;
641  char inbuf[1024] = { 0 };
642  const char *mech = NULL;
643  const char *client_out = NULL;
644  unsigned int client_len, len;
645 
646  if (mutt_sasl_client_new(conn, &saslconn) < 0)
647  {
648  mutt_debug(LL_DEBUG1, "error allocating SASL connection\n");
649  continue;
650  }
651 
652  while (true)
653  {
654  rc = sasl_client_start(saslconn, method, &interaction, &client_out,
655  &client_len, &mech);
656  if (rc != SASL_INTERACT)
657  break;
658  mutt_sasl_interact(interaction);
659  }
660  if ((rc != SASL_OK) && (rc != SASL_CONTINUE))
661  {
662  sasl_dispose(&saslconn);
664  "error starting SASL authentication exchange\n");
665  continue;
666  }
667 
668  // L10N: (%s) is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
669  mutt_message(_("Authenticating (%s)..."), method);
670  snprintf(buf, sizeof(buf), "AUTHINFO SASL %s", method);
671 
672  /* looping protocol */
673  while ((rc == SASL_CONTINUE) || ((rc == SASL_OK) && client_len))
674  {
675  /* send out client response */
676  if (client_len)
677  {
678  nntp_log_binbuf(client_out, client_len, "SASL", MUTT_SOCK_LOG_FULL);
679  if (*buf != '\0')
680  mutt_str_cat(buf, sizeof(buf), " ");
681  len = strlen(buf);
682  if (sasl_encode64(client_out, client_len, buf + len,
683  sizeof(buf) - len, &len) != SASL_OK)
684  {
685  mutt_debug(LL_DEBUG1, "error base64-encoding client response\n");
686  break;
687  }
688  }
689 
690  mutt_str_cat(buf, sizeof(buf), "\r\n");
691  if (strchr(buf, ' '))
692  {
693  mutt_debug(MUTT_SOCK_LOG_CMD, "%d> AUTHINFO SASL %s%s\n", conn->fd,
694  method, client_len ? " sasl_data" : "");
695  }
696  else
697  mutt_debug(MUTT_SOCK_LOG_CMD, "%d> sasl_data\n", conn->fd);
698  client_len = 0;
699  if ((mutt_socket_send_d(conn, buf, MUTT_SOCK_LOG_FULL) < 0) ||
700  (mutt_socket_readln_d(inbuf, sizeof(inbuf), conn, MUTT_SOCK_LOG_FULL) < 0))
701  {
702  break;
703  }
704  if (!mutt_str_startswith(inbuf, "283 ") && !mutt_str_startswith(inbuf, "383 "))
705  {
706  mutt_debug(MUTT_SOCK_LOG_FULL, "%d< %s\n", conn->fd, inbuf);
707  break;
708  }
709  inbuf[3] = '\0';
710  mutt_debug(MUTT_SOCK_LOG_FULL, "%d< %s sasl_data\n", conn->fd, inbuf);
711 
712  if (strcmp("=", inbuf + 4) == 0)
713  len = 0;
714  else if (sasl_decode64(inbuf + 4, strlen(inbuf + 4), buf,
715  sizeof(buf) - 1, &len) != SASL_OK)
716  {
717  mutt_debug(LL_DEBUG1, "error base64-decoding server response\n");
718  break;
719  }
720  else
721  nntp_log_binbuf(buf, len, "SASL", MUTT_SOCK_LOG_FULL);
722 
723  while (true)
724  {
725  rc = sasl_client_step(saslconn, buf, len, &interaction, &client_out, &client_len);
726  if (rc != SASL_INTERACT)
727  break;
728  mutt_sasl_interact(interaction);
729  }
730  if (*inbuf != '3')
731  break;
732 
733  *buf = '\0';
734  } /* looping protocol */
735 
736  if ((rc == SASL_OK) && (client_len == 0) && (*inbuf == '2'))
737  {
738  mutt_sasl_setup_conn(conn, saslconn);
739  return 0;
740  }
741 
742  /* terminate SASL session */
743  sasl_dispose(&saslconn);
744  if (conn->fd < 0)
745  break;
746  if (mutt_str_startswith(inbuf, "383 "))
747  {
748  if ((mutt_socket_send(conn, "*\r\n") < 0) ||
749  (mutt_socket_readln(inbuf, sizeof(inbuf), conn) < 0))
750  {
751  break;
752  }
753  }
754 
755  /* server doesn't support AUTHINFO SASL, trying next method */
756  if (*inbuf == '5')
757  continue;
758 #else
759  continue;
760 #endif /* USE_SASL */
761  }
762 
763  // L10N: %s is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
764  mutt_error(_("%s authentication failed"), method);
765  break;
766  }
767  break;
768  }
769 
770  /* error */
771  adata->status = NNTP_BYE;
772  conn->account.flags = flags;
773  if (conn->fd < 0)
774  {
775  mutt_error(_("Server closed connection"));
776  }
777  else
778  mutt_socket_close(conn);
779  return -1;
780 }
781 
790 static int nntp_query(struct NntpMboxData *mdata, char *line, size_t linelen)
791 {
792  struct NntpAccountData *adata = mdata->adata;
793  char buf[1024] = { 0 };
794 
795  if (adata->status == NNTP_BYE)
796  return -1;
797 
798  while (true)
799  {
800  if (adata->status == NNTP_OK)
801  {
802  int rc = 0;
803 
804  if (*line)
805  rc = mutt_socket_send(adata->conn, line);
806  else if (mdata->group)
807  {
808  snprintf(buf, sizeof(buf), "GROUP %s\r\n", mdata->group);
809  rc = mutt_socket_send(adata->conn, buf);
810  }
811  if (rc >= 0)
812  rc = mutt_socket_readln(buf, sizeof(buf), adata->conn);
813  if (rc >= 0)
814  break;
815  }
816 
817  /* reconnect */
818  while (true)
819  {
820  adata->status = NNTP_NONE;
821  if (nntp_open_connection(adata) == 0)
822  break;
823 
824  snprintf(buf, sizeof(buf), _("Connection to %s lost. Reconnect?"),
825  adata->conn->account.host);
826  if (mutt_yesorno(buf, MUTT_YES) != MUTT_YES)
827  {
828  adata->status = NNTP_BYE;
829  return -1;
830  }
831  }
832 
833  /* select newsgroup after reconnection */
834  if (mdata->group)
835  {
836  snprintf(buf, sizeof(buf), "GROUP %s\r\n", mdata->group);
837  if ((mutt_socket_send(adata->conn, buf) < 0) ||
838  (mutt_socket_readln(buf, sizeof(buf), adata->conn) < 0))
839  {
840  return nntp_connect_error(adata);
841  }
842  }
843  if (*line == '\0')
844  break;
845  }
846 
847  mutt_str_copy(line, buf, linelen);
848  return 0;
849 }
850 
867 static int nntp_fetch_lines(struct NntpMboxData *mdata, char *query, size_t qlen,
868  const char *msg, int (*func)(char *, void *), void *data)
869 {
870  bool done = false;
871  int rc;
872 
873  while (!done)
874  {
875  char buf[1024];
876  char *line = NULL;
877  unsigned int lines = 0;
878  size_t off = 0;
879  struct Progress progress;
880 
881  if (msg)
882  mutt_progress_init(&progress, msg, MUTT_PROGRESS_READ, 0);
883 
884  mutt_str_copy(buf, query, sizeof(buf));
885  if (nntp_query(mdata, buf, sizeof(buf)) < 0)
886  return -1;
887  if (buf[0] != '2')
888  {
889  mutt_str_copy(query, buf, qlen);
890  return 1;
891  }
892 
893  line = mutt_mem_malloc(sizeof(buf));
894  rc = 0;
895 
896  while (true)
897  {
898  char *p = NULL;
899  int chunk = mutt_socket_readln_d(buf, sizeof(buf), mdata->adata->conn, MUTT_SOCK_LOG_FULL);
900  if (chunk < 0)
901  {
902  mdata->adata->status = NNTP_NONE;
903  break;
904  }
905 
906  p = buf;
907  if (!off && (buf[0] == '.'))
908  {
909  if (buf[1] == '\0')
910  {
911  done = true;
912  break;
913  }
914  if (buf[1] == '.')
915  p++;
916  }
917 
918  mutt_str_copy(line + off, p, sizeof(buf));
919 
920  if (chunk >= sizeof(buf))
921  off += strlen(p);
922  else
923  {
924  if (msg)
925  mutt_progress_update(&progress, ++lines, -1);
926 
927  if ((rc == 0) && (func(line, data) < 0))
928  rc = -2;
929  off = 0;
930  }
931 
932  mutt_mem_realloc(&line, off + sizeof(buf));
933  }
934  FREE(&line);
935  func(NULL, data);
936  }
937  return rc;
938 }
939 
946 static int fetch_description(char *line, void *data)
947 {
948  if (!line)
949  return 0;
950 
951  struct NntpAccountData *adata = data;
952 
953  char *desc = strpbrk(line, " \t");
954  if (desc)
955  {
956  *desc++ = '\0';
957  desc += strspn(desc, " \t");
958  }
959  else
960  desc = strchr(line, '\0');
961 
962  struct NntpMboxData *mdata = mutt_hash_find(adata->groups_hash, line);
963  if (mdata && !mutt_str_equal(desc, mdata->desc))
964  {
965  mutt_str_replace(&mdata->desc, desc);
966  mutt_debug(LL_DEBUG2, "group: %s, desc: %s\n", line, desc);
967  }
968  return 0;
969 }
970 
981 static int get_description(struct NntpMboxData *mdata, const char *wildmat, const char *msg)
982 {
983  char buf[256];
984  const char *cmd = NULL;
985 
986  /* get newsgroup description, if possible */
987  struct NntpAccountData *adata = mdata->adata;
988  if (!wildmat)
989  wildmat = mdata->group;
990  if (adata->hasLIST_NEWSGROUPS)
991  cmd = "LIST NEWSGROUPS";
992  else if (adata->hasXGTITLE)
993  cmd = "XGTITLE";
994  else
995  return 0;
996 
997  snprintf(buf, sizeof(buf), "%s %s\r\n", cmd, wildmat);
998  int rc = nntp_fetch_lines(mdata, buf, sizeof(buf), msg, fetch_description, adata);
999  if (rc > 0)
1000  {
1001  mutt_error("%s: %s", cmd, buf);
1002  }
1003  return rc;
1004 }
1005 
1013 static void nntp_parse_xref(struct Mailbox *m, struct Email *e)
1014 {
1015  struct NntpMboxData *mdata = m->mdata;
1016 
1017  char *buf = mutt_str_dup(e->env->xref);
1018  char *p = buf;
1019  while (p)
1020  {
1021  anum_t anum;
1022 
1023  /* skip to next word */
1024  p += strspn(p, " \t");
1025  char *grp = p;
1026 
1027  /* skip to end of word */
1028  p = strpbrk(p, " \t");
1029  if (p)
1030  *p++ = '\0';
1031 
1032  /* find colon */
1033  char *colon = strchr(grp, ':');
1034  if (!colon)
1035  continue;
1036  *colon++ = '\0';
1037  if (sscanf(colon, ANUM, &anum) != 1)
1038  continue;
1039 
1040  nntp_article_status(m, e, grp, anum);
1041  if (!nntp_edata_get(e)->article_num && mutt_str_equal(mdata->group, grp))
1042  nntp_edata_get(e)->article_num = anum;
1043  }
1044  FREE(&buf);
1045 }
1046 
1054 static int fetch_tempfile(char *line, void *data)
1055 {
1056  FILE *fp = data;
1057 
1058  if (!line)
1059  rewind(fp);
1060  else if ((fputs(line, fp) == EOF) || (fputc('\n', fp) == EOF))
1061  return -1;
1062  return 0;
1063 }
1064 
1071 static int fetch_numbers(char *line, void *data)
1072 {
1073  struct FetchCtx *fc = data;
1074  anum_t anum;
1075 
1076  if (!line)
1077  return 0;
1078  if (sscanf(line, ANUM, &anum) != 1)
1079  return 0;
1080  if ((anum < fc->first) || (anum > fc->last))
1081  return 0;
1082  fc->messages[anum - fc->first] = 1;
1083  return 0;
1084 }
1085 
1093 static int parse_overview_line(char *line, void *data)
1094 {
1095  if (!line || !data)
1096  return 0;
1097 
1098  struct FetchCtx *fc = data;
1099  struct Mailbox *m = fc->mailbox;
1100  if (!m)
1101  return -1;
1102 
1103  struct NntpMboxData *mdata = m->mdata;
1104  struct Email *e = NULL;
1105  char *header = NULL, *field = NULL;
1106  bool save = true;
1107  anum_t anum;
1108 
1109  /* parse article number */
1110  field = strchr(line, '\t');
1111  if (field)
1112  *field++ = '\0';
1113  if (sscanf(line, ANUM, &anum) != 1)
1114  return 0;
1115  mutt_debug(LL_DEBUG2, "" ANUM "\n", anum);
1116 
1117  /* out of bounds */
1118  if ((anum < fc->first) || (anum > fc->last))
1119  return 0;
1120 
1121  /* not in LISTGROUP */
1122  if (!fc->messages[anum - fc->first])
1123  {
1124  /* progress */
1125  if (m->verbose)
1126  mutt_progress_update(&fc->progress, anum - fc->first + 1, -1);
1127  return 0;
1128  }
1129 
1130  /* convert overview line to header */
1131  FILE *fp = mutt_file_mkstemp();
1132  if (!fp)
1133  return -1;
1134 
1135  header = mdata->adata->overview_fmt;
1136  while (field)
1137  {
1138  char *b = field;
1139 
1140  if (*header)
1141  {
1142  if (!strstr(header, ":full") && (fputs(header, fp) == EOF))
1143  {
1144  mutt_file_fclose(&fp);
1145  return -1;
1146  }
1147  header = strchr(header, '\0') + 1;
1148  }
1149 
1150  field = strchr(field, '\t');
1151  if (field)
1152  *field++ = '\0';
1153  if ((fputs(b, fp) == EOF) || (fputc('\n', fp) == EOF))
1154  {
1155  mutt_file_fclose(&fp);
1156  return -1;
1157  }
1158  }
1159  rewind(fp);
1160 
1161  /* allocate memory for headers */
1162  if (m->msg_count >= m->email_max)
1163  mx_alloc_memory(m);
1164 
1165  /* parse header */
1166  m->emails[m->msg_count] = email_new();
1167  e = m->emails[m->msg_count];
1168  e->env = mutt_rfc822_read_header(fp, e, false, false);
1169  e->env->newsgroups = mutt_str_dup(mdata->group);
1170  e->received = e->date_sent;
1171  mutt_file_fclose(&fp);
1172 
1173 #ifdef USE_HCACHE
1174  if (fc->hc)
1175  {
1176  char buf[16];
1177 
1178  /* try to replace with header from cache */
1179  snprintf(buf, sizeof(buf), "%u", anum);
1180  struct HCacheEntry hce = mutt_hcache_fetch(fc->hc, buf, strlen(buf), 0);
1181  if (hce.email)
1182  {
1183  mutt_debug(LL_DEBUG2, "mutt_hcache_fetch %s\n", buf);
1184  email_free(&e);
1185  e = hce.email;
1186  m->emails[m->msg_count] = e;
1187  e->edata = NULL;
1188  e->read = false;
1189  e->old = false;
1190 
1191  /* skip header marked as deleted in cache */
1192  if (e->deleted && !fc->restore)
1193  {
1194  if (mdata->bcache)
1195  {
1196  mutt_debug(LL_DEBUG2, "mutt_bcache_del %s\n", buf);
1197  mutt_bcache_del(mdata->bcache, buf);
1198  }
1199  save = false;
1200  }
1201  }
1202 
1203  /* not cached yet, store header */
1204  else
1205  {
1206  mutt_debug(LL_DEBUG2, "mutt_hcache_store %s\n", buf);
1207  mutt_hcache_store(fc->hc, buf, strlen(buf), e, 0);
1208  }
1209  }
1210 #endif
1211 
1212  if (save)
1213  {
1214  e->index = m->msg_count++;
1215  e->read = false;
1216  e->old = false;
1217  e->deleted = false;
1218  e->edata = nntp_edata_new();
1220  nntp_edata_get(e)->article_num = anum;
1221  if (fc->restore)
1222  e->changed = true;
1223  else
1224  {
1225  nntp_article_status(m, e, NULL, anum);
1226  if (!e->read)
1227  nntp_parse_xref(m, e);
1228  }
1229  if (anum > mdata->last_loaded)
1230  mdata->last_loaded = anum;
1231  }
1232  else
1233  email_free(&e);
1234 
1235  /* progress */
1236  if (m->verbose)
1237  mutt_progress_update(&fc->progress, anum - fc->first + 1, -1);
1238  return 0;
1239 }
1240 
1251 static int nntp_fetch_headers(struct Mailbox *m, void *hc, anum_t first, anum_t last, bool restore)
1252 {
1253  if (!m)
1254  return -1;
1255 
1256  struct NntpMboxData *mdata = m->mdata;
1257  struct FetchCtx fc;
1258  struct Email *e = NULL;
1259  char buf[8192];
1260  int rc = 0;
1261  anum_t current;
1262  anum_t first_over = first;
1263 
1264  /* if empty group or nothing to do */
1265  if (!last || (first > last))
1266  return 0;
1267 
1268  /* init fetch context */
1269  fc.mailbox = m;
1270  fc.first = first;
1271  fc.last = last;
1272  fc.restore = restore;
1273  fc.messages = mutt_mem_calloc(last - first + 1, sizeof(unsigned char));
1274  if (!fc.messages)
1275  return -1;
1276  fc.hc = hc;
1277 
1278  /* fetch list of articles */
1279  if (C_NntpListgroup && mdata->adata->hasLISTGROUP && !mdata->deleted)
1280  {
1281  if (m->verbose)
1282  mutt_message(_("Fetching list of articles..."));
1283  if (mdata->adata->hasLISTGROUPrange)
1284  snprintf(buf, sizeof(buf), "LISTGROUP %s %u-%u\r\n", mdata->group, first, last);
1285  else
1286  snprintf(buf, sizeof(buf), "LISTGROUP %s\r\n", mdata->group);
1287  rc = nntp_fetch_lines(mdata, buf, sizeof(buf), NULL, fetch_numbers, &fc);
1288  if (rc > 0)
1289  {
1290  mutt_error("LISTGROUP: %s", buf);
1291  }
1292  if (rc == 0)
1293  {
1294  for (current = first; (current <= last) && (rc == 0); current++)
1295  {
1296  if (fc.messages[current - first])
1297  continue;
1298 
1299  snprintf(buf, sizeof(buf), "%u", current);
1300  if (mdata->bcache)
1301  {
1302  mutt_debug(LL_DEBUG2, "#1 mutt_bcache_del %s\n", buf);
1303  mutt_bcache_del(mdata->bcache, buf);
1304  }
1305 
1306 #ifdef USE_HCACHE
1307  if (fc.hc)
1308  {
1309  mutt_debug(LL_DEBUG2, "mutt_hcache_delete_record %s\n", buf);
1310  mutt_hcache_delete_record(fc.hc, buf, strlen(buf));
1311  }
1312 #endif
1313  }
1314  }
1315  }
1316  else
1317  {
1318  for (current = first; current <= last; current++)
1319  fc.messages[current - first] = 1;
1320  }
1321 
1322  /* fetching header from cache or server, or fallback to fetch overview */
1323  if (m->verbose)
1324  {
1325  mutt_progress_init(&fc.progress, _("Fetching message headers..."),
1326  MUTT_PROGRESS_READ, last - first + 1);
1327  }
1328  for (current = first; (current <= last) && (rc == 0); current++)
1329  {
1330  if (m->verbose)
1331  mutt_progress_update(&fc.progress, current - first + 1, -1);
1332 
1333 #ifdef USE_HCACHE
1334  snprintf(buf, sizeof(buf), "%u", current);
1335 #endif
1336 
1337  /* delete header from cache that does not exist on server */
1338  if (!fc.messages[current - first])
1339  continue;
1340 
1341  /* allocate memory for headers */
1342  if (m->msg_count >= m->email_max)
1343  mx_alloc_memory(m);
1344 
1345 #ifdef USE_HCACHE
1346  /* try to fetch header from cache */
1347  struct HCacheEntry hce = mutt_hcache_fetch(fc.hc, buf, strlen(buf), 0);
1348  if (hce.email)
1349  {
1350  mutt_debug(LL_DEBUG2, "mutt_hcache_fetch %s\n", buf);
1351  e = hce.email;
1352  m->emails[m->msg_count] = e;
1353  e->edata = NULL;
1354 
1355  /* skip header marked as deleted in cache */
1356  if (e->deleted && !restore)
1357  {
1358  email_free(&e);
1359  if (mdata->bcache)
1360  {
1361  mutt_debug(LL_DEBUG2, "#2 mutt_bcache_del %s\n", buf);
1362  mutt_bcache_del(mdata->bcache, buf);
1363  }
1364  continue;
1365  }
1366 
1367  e->read = false;
1368  e->old = false;
1369  }
1370  else
1371 #endif
1372  if (mdata->deleted)
1373  {
1374  /* don't try to fetch header from removed newsgroup */
1375  continue;
1376  }
1377 
1378  /* fallback to fetch overview */
1379  else if (mdata->adata->hasOVER || mdata->adata->hasXOVER)
1380  {
1381  if (C_NntpListgroup && mdata->adata->hasLISTGROUP)
1382  break;
1383  else
1384  continue;
1385  }
1386 
1387  /* fetch header from server */
1388  else
1389  {
1390  FILE *fp = mutt_file_mkstemp();
1391  if (!fp)
1392  {
1393  mutt_perror(_("Can't create temporary file"));
1394  rc = -1;
1395  break;
1396  }
1397 
1398  snprintf(buf, sizeof(buf), "HEAD %u\r\n", current);
1399  rc = nntp_fetch_lines(mdata, buf, sizeof(buf), NULL, fetch_tempfile, fp);
1400  if (rc)
1401  {
1402  mutt_file_fclose(&fp);
1403  if (rc < 0)
1404  break;
1405 
1406  /* invalid response */
1407  if (!mutt_str_startswith(buf, "423"))
1408  {
1409  mutt_error("HEAD: %s", buf);
1410  break;
1411  }
1412 
1413  /* no such article */
1414  if (mdata->bcache)
1415  {
1416  snprintf(buf, sizeof(buf), "%u", current);
1417  mutt_debug(LL_DEBUG2, "#3 mutt_bcache_del %s\n", buf);
1418  mutt_bcache_del(mdata->bcache, buf);
1419  }
1420  rc = 0;
1421  continue;
1422  }
1423 
1424  /* parse header */
1425  m->emails[m->msg_count] = email_new();
1426  e = m->emails[m->msg_count];
1427  e->env = mutt_rfc822_read_header(fp, e, false, false);
1428  e->received = e->date_sent;
1429  mutt_file_fclose(&fp);
1430  }
1431 
1432  /* save header in context */
1433  e->index = m->msg_count++;
1434  e->read = false;
1435  e->old = false;
1436  e->deleted = false;
1437  e->edata = nntp_edata_new();
1439  nntp_edata_get(e)->article_num = current;
1440  if (restore)
1441  e->changed = true;
1442  else
1443  {
1444  nntp_article_status(m, e, NULL, nntp_edata_get(e)->article_num);
1445  if (!e->read)
1446  nntp_parse_xref(m, e);
1447  }
1448  if (current > mdata->last_loaded)
1449  mdata->last_loaded = current;
1450  first_over = current + 1;
1451  }
1452 
1453  if (!C_NntpListgroup || !mdata->adata->hasLISTGROUP)
1454  current = first_over;
1455 
1456  /* fetch overview information */
1457  if ((current <= last) && (rc == 0) && !mdata->deleted)
1458  {
1459  char *cmd = mdata->adata->hasOVER ? "OVER" : "XOVER";
1460  snprintf(buf, sizeof(buf), "%s %u-%u\r\n", cmd, current, last);
1461  rc = nntp_fetch_lines(mdata, buf, sizeof(buf), NULL, parse_overview_line, &fc);
1462  if (rc > 0)
1463  {
1464  mutt_error("%s: %s", cmd, buf);
1465  }
1466  }
1467 
1468  FREE(&fc.messages);
1469  if (rc != 0)
1470  return -1;
1471  mutt_clear_error();
1472  return 0;
1473 }
1474 
1483 static int nntp_group_poll(struct NntpMboxData *mdata, bool update_stat)
1484 {
1485  char buf[1024] = { 0 };
1486  anum_t count, first, last;
1487 
1488  /* use GROUP command to poll newsgroup */
1489  if (nntp_query(mdata, buf, sizeof(buf)) < 0)
1490  return -1;
1491  if (sscanf(buf, "211 " ANUM " " ANUM " " ANUM, &count, &first, &last) != 3)
1492  return 0;
1493  if ((first == mdata->first_message) && (last == mdata->last_message))
1494  return 0;
1495 
1496  /* articles have been renumbered */
1497  if (last < mdata->last_message)
1498  {
1499  mdata->last_cached = 0;
1500  if (mdata->newsrc_len)
1501  {
1502  mutt_mem_realloc(&mdata->newsrc_ent, sizeof(struct NewsrcEntry));
1503  mdata->newsrc_len = 1;
1504  mdata->newsrc_ent[0].first = 1;
1505  mdata->newsrc_ent[0].last = 0;
1506  }
1507  }
1508  mdata->first_message = first;
1509  mdata->last_message = last;
1510  if (!update_stat)
1511  return 1;
1512 
1513  /* update counters */
1514  else if (!last || (!mdata->newsrc_ent && !mdata->last_cached))
1515  mdata->unread = count;
1516  else
1517  nntp_group_unread_stat(mdata);
1518  return 1;
1519 }
1520 
1531 static int check_mailbox(struct Mailbox *m)
1532 {
1533  if (!m)
1534  return -1;
1535 
1536  struct NntpMboxData *mdata = m->mdata;
1537  struct NntpAccountData *adata = mdata->adata;
1538  time_t now = mutt_date_epoch();
1539  int rc = 0;
1540  void *hc = NULL;
1541 
1542  if (adata->check_time + C_NntpPoll > now)
1543  return 0;
1544 
1545  mutt_message(_("Checking for new messages..."));
1546  if (nntp_newsrc_parse(adata) < 0)
1547  return -1;
1548 
1549  adata->check_time = now;
1550  int rc2 = nntp_group_poll(mdata, false);
1551  if (rc2 < 0)
1552  {
1553  nntp_newsrc_close(adata);
1554  return -1;
1555  }
1556  if (rc2 != 0)
1557  nntp_active_save_cache(adata);
1558 
1559  /* articles have been renumbered, remove all headers */
1560  if (mdata->last_message < mdata->last_loaded)
1561  {
1562  for (int i = 0; i < m->msg_count; i++)
1563  email_free(&m->emails[i]);
1564  m->msg_count = 0;
1565  m->msg_tagged = 0;
1566 
1567  if (mdata->last_message < mdata->last_loaded)
1568  {
1569  mdata->last_loaded = mdata->first_message - 1;
1570  if (C_NntpContext && (mdata->last_message - mdata->last_loaded > C_NntpContext))
1571  mdata->last_loaded = mdata->last_message - C_NntpContext;
1572  }
1573  rc = MUTT_REOPENED;
1574  }
1575 
1576  /* .newsrc has been externally modified */
1577  if (adata->newsrc_modified)
1578  {
1579 #ifdef USE_HCACHE
1580  unsigned char *messages = NULL;
1581  char buf[16];
1582  struct Email *e = NULL;
1583  anum_t first = mdata->first_message;
1584 
1585  if (C_NntpContext && (mdata->last_message - first + 1 > C_NntpContext))
1586  first = mdata->last_message - C_NntpContext + 1;
1587  messages = mutt_mem_calloc(mdata->last_loaded - first + 1, sizeof(unsigned char));
1588  hc = nntp_hcache_open(mdata);
1589  nntp_hcache_update(mdata, hc);
1590 #endif
1591 
1592  /* update flags according to .newsrc */
1593  int j = 0;
1594  for (int i = 0; i < m->msg_count; i++)
1595  {
1596  if (!m->emails[i])
1597  continue;
1598  bool flagged = false;
1599  anum_t anum = nntp_edata_get(m->emails[i])->article_num;
1600 
1601 #ifdef USE_HCACHE
1602  /* check hcache for flagged and deleted flags */
1603  if (hc)
1604  {
1605  if ((anum >= first) && (anum <= mdata->last_loaded))
1606  messages[anum - first] = 1;
1607 
1608  snprintf(buf, sizeof(buf), "%u", anum);
1609  struct HCacheEntry hce = mutt_hcache_fetch(hc, buf, strlen(buf), 0);
1610  if (hce.email)
1611  {
1612  bool deleted;
1613 
1614  mutt_debug(LL_DEBUG2, "#1 mutt_hcache_fetch %s\n", buf);
1615  e = hce.email;
1616  e->edata = NULL;
1617  deleted = e->deleted;
1618  flagged = e->flagged;
1619  email_free(&e);
1620 
1621  /* header marked as deleted, removing from context */
1622  if (deleted)
1623  {
1624  mutt_set_flag(m, m->emails[i], MUTT_TAG, false);
1625  email_free(&m->emails[i]);
1626  continue;
1627  }
1628  }
1629  }
1630 #endif
1631 
1632  if (!m->emails[i]->changed)
1633  {
1634  m->emails[i]->flagged = flagged;
1635  m->emails[i]->read = false;
1636  m->emails[i]->old = false;
1637  nntp_article_status(m, m->emails[i], NULL, anum);
1638  if (!m->emails[i]->read)
1639  nntp_parse_xref(m, m->emails[i]);
1640  }
1641  m->emails[j++] = m->emails[i];
1642  }
1643 
1644 #ifdef USE_HCACHE
1645  m->msg_count = j;
1646 
1647  /* restore headers without "deleted" flag */
1648  for (anum_t anum = first; anum <= mdata->last_loaded; anum++)
1649  {
1650  if (messages[anum - first])
1651  continue;
1652 
1653  snprintf(buf, sizeof(buf), "%u", anum);
1654  struct HCacheEntry hce = mutt_hcache_fetch(hc, buf, strlen(buf), 0);
1655  if (hce.email)
1656  {
1657  mutt_debug(LL_DEBUG2, "#2 mutt_hcache_fetch %s\n", buf);
1658  if (m->msg_count >= m->email_max)
1659  mx_alloc_memory(m);
1660 
1661  e = hce.email;
1662  m->emails[m->msg_count] = e;
1663  e->edata = NULL;
1664  if (e->deleted)
1665  {
1666  email_free(&e);
1667  if (mdata->bcache)
1668  {
1669  mutt_debug(LL_DEBUG2, "mutt_bcache_del %s\n", buf);
1670  mutt_bcache_del(mdata->bcache, buf);
1671  }
1672  continue;
1673  }
1674 
1675  m->msg_count++;
1676  e->read = false;
1677  e->old = false;
1678  e->edata = nntp_edata_new();
1680  nntp_edata_get(e)->article_num = anum;
1681  nntp_article_status(m, e, NULL, anum);
1682  if (!e->read)
1683  nntp_parse_xref(m, e);
1684  }
1685  }
1686  FREE(&messages);
1687 #endif
1688 
1689  adata->newsrc_modified = false;
1690  rc = MUTT_REOPENED;
1691  }
1692 
1693  /* some headers were removed, context must be updated */
1694  if (rc == MUTT_REOPENED)
1696 
1697  /* fetch headers of new articles */
1698  if (mdata->last_message > mdata->last_loaded)
1699  {
1700  int oldmsgcount = m->msg_count;
1701  bool verbose = m->verbose;
1702  m->verbose = false;
1703 #ifdef USE_HCACHE
1704  if (!hc)
1705  {
1706  hc = nntp_hcache_open(mdata);
1707  nntp_hcache_update(mdata, hc);
1708  }
1709 #endif
1710  int old_msg_count = m->msg_count;
1711  rc2 = nntp_fetch_headers(m, hc, mdata->last_loaded + 1, mdata->last_message, false);
1712  m->verbose = verbose;
1713  if (rc2 == 0)
1714  {
1715  if (m->msg_count > old_msg_count)
1717  mdata->last_loaded = mdata->last_message;
1718  }
1719  if ((rc == 0) && (m->msg_count > oldmsgcount))
1720  rc = MUTT_NEW_MAIL;
1721  }
1722 
1723 #ifdef USE_HCACHE
1724  mutt_hcache_close(hc);
1725 #endif
1726  if (rc)
1727  nntp_newsrc_close(adata);
1728  mutt_clear_error();
1729  return rc;
1730 }
1731 
1739 static int nntp_date(struct NntpAccountData *adata, time_t *now)
1740 {
1741  if (adata->hasDATE)
1742  {
1743  struct NntpMboxData mdata = { 0 };
1744  char buf[1024];
1745  struct tm tm = { 0 };
1746 
1747  mdata.adata = adata;
1748  mdata.group = NULL;
1749  mutt_str_copy(buf, "DATE\r\n", sizeof(buf));
1750  if (nntp_query(&mdata, buf, sizeof(buf)) < 0)
1751  return -1;
1752 
1753  if (sscanf(buf, "111 %4d%2d%2d%2d%2d%2d%*s", &tm.tm_year, &tm.tm_mon,
1754  &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) == 6)
1755  {
1756  tm.tm_year -= 1900;
1757  tm.tm_mon--;
1758  *now = timegm(&tm);
1759  if (*now >= 0)
1760  {
1761  mutt_debug(LL_DEBUG1, "server time is %lu\n", *now);
1762  return 0;
1763  }
1764  }
1765  }
1766  *now = mutt_date_epoch();
1767  return 0;
1768 }
1769 
1776 static int fetch_children(char *line, void *data)
1777 {
1778  struct ChildCtx *cc = data;
1779  anum_t anum;
1780 
1781  if (!line || (sscanf(line, ANUM, &anum) != 1))
1782  return 0;
1783  for (unsigned int i = 0; i < cc->mailbox->msg_count; i++)
1784  {
1785  struct Email *e = cc->mailbox->emails[i];
1786  if (!e)
1787  break;
1788  if (nntp_edata_get(e)->article_num == anum)
1789  return 0;
1790  }
1791  if (cc->num >= cc->max)
1792  {
1793  cc->max *= 2;
1794  mutt_mem_realloc(&cc->child, sizeof(anum_t) * cc->max);
1795  }
1796  cc->child[cc->num++] = anum;
1797  return 0;
1798 }
1799 
1807 {
1808  struct Connection *conn = adata->conn;
1809  char buf[256];
1810  int cap;
1811  bool posting = false, auth = true;
1812 
1813  if (adata->status == NNTP_OK)
1814  return 0;
1815  if (adata->status == NNTP_BYE)
1816  return -1;
1817  adata->status = NNTP_NONE;
1818 
1819  if (mutt_socket_open(conn) < 0)
1820  return -1;
1821 
1822  if (mutt_socket_readln(buf, sizeof(buf), conn) < 0)
1823  return nntp_connect_error(adata);
1824 
1825  if (mutt_str_startswith(buf, "200"))
1826  posting = true;
1827  else if (!mutt_str_startswith(buf, "201"))
1828  {
1829  mutt_socket_close(conn);
1831  mutt_error("%s", buf);
1832  return -1;
1833  }
1834 
1835  /* get initial capabilities */
1836  cap = nntp_capabilities(adata);
1837  if (cap < 0)
1838  return -1;
1839 
1840  /* tell news server to switch to mode reader if it isn't so */
1841  if (cap > 0)
1842  {
1843  if ((mutt_socket_send(conn, "MODE READER\r\n") < 0) ||
1844  (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
1845  {
1846  return nntp_connect_error(adata);
1847  }
1848 
1849  if (mutt_str_startswith(buf, "200"))
1850  posting = true;
1851  else if (mutt_str_startswith(buf, "201"))
1852  posting = false;
1853  /* error if has capabilities, ignore result if no capabilities */
1854  else if (adata->hasCAPABILITIES)
1855  {
1856  mutt_socket_close(conn);
1857  mutt_error(_("Could not switch to reader mode"));
1858  return -1;
1859  }
1860 
1861  /* recheck capabilities after MODE READER */
1862  if (adata->hasCAPABILITIES)
1863  {
1864  cap = nntp_capabilities(adata);
1865  if (cap < 0)
1866  return -1;
1867  }
1868  }
1869 
1870  mutt_message(_("Connected to %s. %s"), conn->account.host,
1871  posting ? _("Posting is ok") : _("Posting is NOT ok"));
1872  mutt_sleep(1);
1873 
1874 #ifdef USE_SSL
1875  /* Attempt STARTTLS if available and desired. */
1876  if ((adata->use_tls != 1) && (adata->hasSTARTTLS || C_SslForceTls))
1877  {
1878  if (adata->use_tls == 0)
1879  {
1880  adata->use_tls =
1881  C_SslForceTls ||
1883  _("Secure connection with TLS?")) == MUTT_YES) ?
1884  2 :
1885  1;
1886  }
1887  if (adata->use_tls == 2)
1888  {
1889  if ((mutt_socket_send(conn, "STARTTLS\r\n") < 0) ||
1890  (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
1891  {
1892  return nntp_connect_error(adata);
1893  }
1894  // Clear any data after the STARTTLS acknowledgement
1895  mutt_socket_empty(conn);
1896  if (!mutt_str_startswith(buf, "382"))
1897  {
1898  adata->use_tls = 0;
1899  mutt_error("STARTTLS: %s", buf);
1900  }
1901  else if (mutt_ssl_starttls(conn))
1902  {
1903  adata->use_tls = 0;
1904  adata->status = NNTP_NONE;
1905  mutt_socket_close(adata->conn);
1906  mutt_error(_("Could not negotiate TLS connection"));
1907  return -1;
1908  }
1909  else
1910  {
1911  /* recheck capabilities after STARTTLS */
1912  cap = nntp_capabilities(adata);
1913  if (cap < 0)
1914  return -1;
1915  }
1916  }
1917  }
1918 #endif
1919 
1920  /* authentication required? */
1921  if (conn->account.flags & MUTT_ACCT_USER)
1922  {
1923  if (!conn->account.user[0])
1924  auth = false;
1925  }
1926  else
1927  {
1928  if ((mutt_socket_send(conn, "STAT\r\n") < 0) ||
1929  (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
1930  {
1931  return nntp_connect_error(adata);
1932  }
1933  if (!mutt_str_startswith(buf, "480"))
1934  auth = false;
1935  }
1936 
1937  /* authenticate */
1938  if (auth && (nntp_auth(adata) < 0))
1939  return -1;
1940 
1941  /* get final capabilities after authentication */
1942  if (adata->hasCAPABILITIES && (auth || (cap > 0)))
1943  {
1944  cap = nntp_capabilities(adata);
1945  if (cap < 0)
1946  return -1;
1947  if (cap > 0)
1948  {
1949  mutt_socket_close(conn);
1950  mutt_error(_("Could not switch to reader mode"));
1951  return -1;
1952  }
1953  }
1954 
1955  /* attempt features */
1956  if (nntp_attempt_features(adata) < 0)
1957  return -1;
1958 
1959  adata->status = NNTP_OK;
1960  return 0;
1961 }
1962 
1970 int nntp_post(struct Mailbox *m, const char *msg)
1971 {
1972  struct NntpMboxData *mdata = NULL;
1973  struct NntpMboxData tmp_mdata = { 0 };
1974  char buf[1024];
1975 
1976  if (m && (m->type == MUTT_NNTP))
1977  mdata = m->mdata;
1978  else
1979  {
1980  CurrentNewsSrv = nntp_select_server(m, C_NewsServer, false);
1981  if (!CurrentNewsSrv)
1982  return -1;
1983 
1984  mdata = &tmp_mdata;
1985  mdata->adata = CurrentNewsSrv;
1986  mdata->group = NULL;
1987  }
1988 
1989  FILE *fp = mutt_file_fopen(msg, "r");
1990  if (!fp)
1991  {
1992  mutt_perror(msg);
1993  return -1;
1994  }
1995 
1996  mutt_str_copy(buf, "POST\r\n", sizeof(buf));
1997  if (nntp_query(mdata, buf, sizeof(buf)) < 0)
1998  {
1999  mutt_file_fclose(&fp);
2000  return -1;
2001  }
2002  if (buf[0] != '3')
2003  {
2004  mutt_error(_("Can't post article: %s"), buf);
2005  mutt_file_fclose(&fp);
2006  return -1;
2007  }
2008 
2009  buf[0] = '.';
2010  buf[1] = '\0';
2011  while (fgets(buf + 1, sizeof(buf) - 2, fp))
2012  {
2013  size_t len = strlen(buf);
2014  if (buf[len - 1] == '\n')
2015  {
2016  buf[len - 1] = '\r';
2017  buf[len] = '\n';
2018  len++;
2019  buf[len] = '\0';
2020  }
2021  if (mutt_socket_send_d(mdata->adata->conn, (buf[1] == '.') ? buf : buf + 1,
2022  MUTT_SOCK_LOG_FULL) < 0)
2023  {
2024  mutt_file_fclose(&fp);
2025  return nntp_connect_error(mdata->adata);
2026  }
2027  }
2028  mutt_file_fclose(&fp);
2029 
2030  if (((buf[strlen(buf) - 1] != '\n') &&
2031  (mutt_socket_send_d(mdata->adata->conn, "\r\n", MUTT_SOCK_LOG_FULL) < 0)) ||
2032  (mutt_socket_send_d(mdata->adata->conn, ".\r\n", MUTT_SOCK_LOG_FULL) < 0) ||
2033  (mutt_socket_readln(buf, sizeof(buf), mdata->adata->conn) < 0))
2034  {
2035  return nntp_connect_error(mdata->adata);
2036  }
2037  if (buf[0] != '2')
2038  {
2039  mutt_error(_("Can't post article: %s"), buf);
2040  return -1;
2041  }
2042  return 0;
2043 }
2044 
2052 int nntp_active_fetch(struct NntpAccountData *adata, bool mark_new)
2053 {
2054  struct NntpMboxData tmp_mdata = { 0 };
2055  char msg[256];
2056  char buf[1024];
2057  unsigned int i;
2058  int rc;
2059 
2060  snprintf(msg, sizeof(msg), _("Loading list of groups from server %s..."),
2061  adata->conn->account.host);
2062  mutt_message(msg);
2063  if (nntp_date(adata, &adata->newgroups_time) < 0)
2064  return -1;
2065 
2066  tmp_mdata.adata = adata;
2067  tmp_mdata.group = NULL;
2068  i = adata->groups_num;
2069  mutt_str_copy(buf, "LIST\r\n", sizeof(buf));
2070  rc = nntp_fetch_lines(&tmp_mdata, buf, sizeof(buf), msg, nntp_add_group, adata);
2071  if (rc)
2072  {
2073  if (rc > 0)
2074  {
2075  mutt_error("LIST: %s", buf);
2076  }
2077  return -1;
2078  }
2079 
2080  if (mark_new)
2081  {
2082  for (; i < adata->groups_num; i++)
2083  {
2084  struct NntpMboxData *mdata = adata->groups_list[i];
2085  mdata->has_new_mail = true;
2086  }
2087  }
2088 
2089  for (i = 0; i < adata->groups_num; i++)
2090  {
2091  struct NntpMboxData *mdata = adata->groups_list[i];
2092 
2093  if (mdata && mdata->deleted && !mdata->newsrc_ent)
2094  {
2095  nntp_delete_group_cache(mdata);
2096  mutt_hash_delete(adata->groups_hash, mdata->group, NULL);
2097  adata->groups_list[i] = NULL;
2098  }
2099  }
2100 
2102  rc = get_description(&tmp_mdata, "*", _("Loading descriptions..."));
2103 
2104  nntp_active_save_cache(adata);
2105  if (rc < 0)
2106  return -1;
2107  mutt_clear_error();
2108  return 0;
2109 }
2110 
2120 {
2121  struct NntpMboxData tmp_mdata = { 0 };
2122  time_t now;
2123  char buf[1024];
2124  char *msg = _("Checking for new newsgroups...");
2125  unsigned int i;
2126  int rc, update_active = false;
2127 
2128  if (!adata || !adata->newgroups_time)
2129  return -1;
2130 
2131  /* check subscribed newsgroups for new articles */
2132  if (C_ShowNewNews)
2133  {
2134  mutt_message(_("Checking for new messages..."));
2135  for (i = 0; i < adata->groups_num; i++)
2136  {
2137  struct NntpMboxData *mdata = adata->groups_list[i];
2138 
2139  if (mdata && mdata->subscribed)
2140  {
2141  rc = nntp_group_poll(mdata, true);
2142  if (rc < 0)
2143  return -1;
2144  if (rc > 0)
2145  update_active = true;
2146  }
2147  }
2148  }
2149  else if (adata->newgroups_time)
2150  return 0;
2151 
2152  /* get list of new groups */
2153  mutt_message(msg);
2154  if (nntp_date(adata, &now) < 0)
2155  return -1;
2156  tmp_mdata.adata = adata;
2157  if (m && m->mdata)
2158  tmp_mdata.group = ((struct NntpMboxData *) m->mdata)->group;
2159  else
2160  tmp_mdata.group = NULL;
2161  i = adata->groups_num;
2162  struct tm tm = mutt_date_gmtime(adata->newgroups_time);
2163  snprintf(buf, sizeof(buf), "NEWGROUPS %02d%02d%02d %02d%02d%02d GMT\r\n",
2164  tm.tm_year % 100, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
2165  rc = nntp_fetch_lines(&tmp_mdata, buf, sizeof(buf), msg, nntp_add_group, adata);
2166  if (rc)
2167  {
2168  if (rc > 0)
2169  {
2170  mutt_error("NEWGROUPS: %s", buf);
2171  }
2172  return -1;
2173  }
2174 
2175  /* new groups found */
2176  rc = 0;
2177  if (adata->groups_num != i)
2178  {
2179  int groups_num = i;
2180 
2181  adata->newgroups_time = now;
2182  for (; i < adata->groups_num; i++)
2183  {
2184  struct NntpMboxData *mdata = adata->groups_list[i];
2185  mdata->has_new_mail = true;
2186  }
2187 
2188  /* loading descriptions */
2190  {
2191  unsigned int count = 0;
2192  struct Progress progress;
2193 
2194  mutt_progress_init(&progress, _("Loading descriptions..."),
2195  MUTT_PROGRESS_READ, adata->groups_num - i);
2196  for (i = groups_num; i < adata->groups_num; i++)
2197  {
2198  struct NntpMboxData *mdata = adata->groups_list[i];
2199 
2200  if (get_description(mdata, NULL, NULL) < 0)
2201  return -1;
2202  mutt_progress_update(&progress, ++count, -1);
2203  }
2204  }
2205  update_active = true;
2206  rc = 1;
2207  }
2208  if (update_active)
2209  nntp_active_save_cache(adata);
2210  mutt_clear_error();
2211  return rc;
2212 }
2213 
2222 int nntp_check_msgid(struct Mailbox *m, const char *msgid)
2223 {
2224  if (!m)
2225  return -1;
2226 
2227  struct NntpMboxData *mdata = m->mdata;
2228  char buf[1024];
2229 
2230  FILE *fp = mutt_file_mkstemp();
2231  if (!fp)
2232  {
2233  mutt_perror(_("Can't create temporary file"));
2234  return -1;
2235  }
2236 
2237  snprintf(buf, sizeof(buf), "HEAD %s\r\n", msgid);
2238  int rc = nntp_fetch_lines(mdata, buf, sizeof(buf), NULL, fetch_tempfile, fp);
2239  if (rc)
2240  {
2241  mutt_file_fclose(&fp);
2242  if (rc < 0)
2243  return -1;
2244  if (mutt_str_startswith(buf, "430"))
2245  return 1;
2246  mutt_error("HEAD: %s", buf);
2247  return -1;
2248  }
2249 
2250  /* parse header */
2251  if (m->msg_count == m->email_max)
2252  mx_alloc_memory(m);
2253  m->emails[m->msg_count] = email_new();
2254  struct Email *e = m->emails[m->msg_count];
2255  e->edata = nntp_edata_new();
2257  e->env = mutt_rfc822_read_header(fp, e, false, false);
2258  mutt_file_fclose(&fp);
2259 
2260  /* get article number */
2261  if (e->env->xref)
2262  nntp_parse_xref(m, e);
2263  else
2264  {
2265  snprintf(buf, sizeof(buf), "STAT %s\r\n", msgid);
2266  if (nntp_query(mdata, buf, sizeof(buf)) < 0)
2267  {
2268  email_free(&e);
2269  return -1;
2270  }
2271  sscanf(buf + 4, ANUM, &nntp_edata_get(e)->article_num);
2272  }
2273 
2274  /* reset flags */
2275  e->read = false;
2276  e->old = false;
2277  e->deleted = false;
2278  e->changed = true;
2279  e->received = e->date_sent;
2280  e->index = m->msg_count++;
2282  return 0;
2283 }
2284 
2292 int nntp_check_children(struct Mailbox *m, const char *msgid)
2293 {
2294  if (!m)
2295  return -1;
2296 
2297  struct NntpMboxData *mdata = m->mdata;
2298  struct ChildCtx cc;
2299  char buf[256];
2300  int rc;
2301  void *hc = NULL;
2302 
2303  if (!mdata || !mdata->adata)
2304  return -1;
2305  if (mdata->first_message > mdata->last_loaded)
2306  return 0;
2307 
2308  /* init context */
2309  cc.mailbox = m;
2310  cc.num = 0;
2311  cc.max = 10;
2312  cc.child = mutt_mem_malloc(sizeof(anum_t) * cc.max);
2313 
2314  /* fetch numbers of child messages */
2315  snprintf(buf, sizeof(buf), "XPAT References %u-%u *%s*\r\n",
2316  mdata->first_message, mdata->last_loaded, msgid);
2317  rc = nntp_fetch_lines(mdata, buf, sizeof(buf), NULL, fetch_children, &cc);
2318  if (rc)
2319  {
2320  FREE(&cc.child);
2321  if (rc > 0)
2322  {
2323  if (!mutt_str_startswith(buf, "500"))
2324  mutt_error("XPAT: %s", buf);
2325  else
2326  {
2327  mutt_error(_("Unable to find child articles because server does not "
2328  "support XPAT command"));
2329  }
2330  }
2331  return -1;
2332  }
2333 
2334  /* fetch all found messages */
2335  bool verbose = m->verbose;
2336  m->verbose = false;
2337 #ifdef USE_HCACHE
2338  hc = nntp_hcache_open(mdata);
2339 #endif
2340  int old_msg_count = m->msg_count;
2341  for (int i = 0; i < cc.num; i++)
2342  {
2343  rc = nntp_fetch_headers(m, hc, cc.child[i], cc.child[i], true);
2344  if (rc < 0)
2345  break;
2346  }
2347  if (m->msg_count > old_msg_count)
2349 
2350 #ifdef USE_HCACHE
2351  mutt_hcache_close(hc);
2352 #endif
2353  m->verbose = verbose;
2354  FREE(&cc.child);
2355  return (rc < 0) ? -1 : 0;
2356 }
2357 
2361 int nntp_compare_order(const void *a, const void *b)
2362 {
2363  const struct Email *ea = *(struct Email const *const *) a;
2364  const struct Email *eb = *(struct Email const *const *) b;
2365 
2366  anum_t na = nntp_edata_get((struct Email *) ea)->article_num;
2367  anum_t nb = nntp_edata_get((struct Email *) eb)->article_num;
2368  int result = (na == nb) ? 0 : (na > nb) ? 1 : -1;
2369  result = perform_auxsort(result, a, b);
2370  return SORT_CODE(result);
2371 }
2372 
2376 static struct Account *nntp_ac_find(struct Account *a, const char *path)
2377 {
2378  return a;
2379 }
2380 
2384 static int nntp_ac_add(struct Account *a, struct Mailbox *m)
2385 {
2386  return 0;
2387 }
2388 
2392 static int nntp_mbox_open(struct Mailbox *m)
2393 {
2394  if (!m->account)
2395  return -1;
2396 
2397  char buf[8192];
2398  char server[1024];
2399  char *group = NULL;
2400  int rc;
2401  void *hc = NULL;
2402  anum_t first, last, count = 0;
2403 
2404  struct Url *url = url_parse(mailbox_path(m));
2405  if (!url || !url->host || !url->path ||
2406  !((url->scheme == U_NNTP) || (url->scheme == U_NNTPS)))
2407  {
2408  url_free(&url);
2409  mutt_error(_("%s is an invalid newsgroup specification"), mailbox_path(m));
2410  return -1;
2411  }
2412 
2413  group = url->path;
2414  if (group[0] == '/') /* Skip a leading '/' */
2415  group++;
2416 
2417  url->path = strchr(url->path, '\0');
2418  url_tostring(url, server, sizeof(server), 0);
2419 
2421  struct NntpAccountData *adata = m->account->adata;
2422  if (!adata)
2423  {
2424  adata = nntp_select_server(m, server, true);
2425  m->account->adata = adata;
2427  }
2428 
2429  if (!adata)
2430  {
2431  url_free(&url);
2432  return -1;
2433  }
2434  CurrentNewsSrv = adata;
2435 
2436  m->msg_count = 0;
2437  m->msg_unread = 0;
2438  m->vcount = 0;
2439 
2440  if (group[0] == '/')
2441  group++;
2442 
2443  /* find news group data structure */
2444  struct NntpMboxData *mdata = mutt_hash_find(adata->groups_hash, group);
2445  if (!mdata)
2446  {
2447  nntp_newsrc_close(adata);
2448  mutt_error(_("Newsgroup %s not found on the server"), group);
2449  url_free(&url);
2450  return -1;
2451  }
2452 
2453  m->rights &= ~MUTT_ACL_INSERT; // Clear the flag
2454  if (!mdata->newsrc_ent && !mdata->subscribed && !C_SaveUnsubscribed)
2455  m->readonly = true;
2456 
2457  /* select newsgroup */
2458  mutt_message(_("Selecting %s..."), group);
2459  url_free(&url);
2460  buf[0] = '\0';
2461  if (nntp_query(mdata, buf, sizeof(buf)) < 0)
2462  {
2463  nntp_newsrc_close(adata);
2464  return -1;
2465  }
2466 
2467  /* newsgroup not found, remove it */
2468  if (mutt_str_startswith(buf, "411"))
2469  {
2470  mutt_error(_("Newsgroup %s has been removed from the server"), mdata->group);
2471  if (!mdata->deleted)
2472  {
2473  mdata->deleted = true;
2474  nntp_active_save_cache(adata);
2475  }
2476  if (mdata->newsrc_ent && !mdata->subscribed && !C_SaveUnsubscribed)
2477  {
2478  FREE(&mdata->newsrc_ent);
2479  mdata->newsrc_len = 0;
2480  nntp_delete_group_cache(mdata);
2481  nntp_newsrc_update(adata);
2482  }
2483  }
2484 
2485  /* parse newsgroup info */
2486  else
2487  {
2488  if (sscanf(buf, "211 " ANUM " " ANUM " " ANUM, &count, &first, &last) != 3)
2489  {
2490  nntp_newsrc_close(adata);
2491  mutt_error("GROUP: %s", buf);
2492  return -1;
2493  }
2494  mdata->first_message = first;
2495  mdata->last_message = last;
2496  mdata->deleted = false;
2497 
2498  /* get description if empty */
2499  if (C_NntpLoadDescription && !mdata->desc)
2500  {
2501  if (get_description(mdata, NULL, NULL) < 0)
2502  {
2503  nntp_newsrc_close(adata);
2504  return -1;
2505  }
2506  if (mdata->desc)
2507  nntp_active_save_cache(adata);
2508  }
2509  }
2510 
2511  adata->check_time = mutt_date_epoch();
2512  m->mdata = mdata;
2513  // Every known newsgroup has an mdata which is stored in adata->groups_list.
2514  // Currently we don't let the Mailbox free the mdata.
2515  // m->mdata_free = nntp_mdata_free;
2516  if (!mdata->bcache && (mdata->newsrc_ent || mdata->subscribed || C_SaveUnsubscribed))
2517  mdata->bcache = mutt_bcache_open(&adata->conn->account, mdata->group);
2518 
2519  /* strip off extra articles if adding context is greater than $nntp_context */
2520  first = mdata->first_message;
2521  if (C_NntpContext && (mdata->last_message - first + 1 > C_NntpContext))
2522  first = mdata->last_message - C_NntpContext + 1;
2523  mdata->last_loaded = first ? first - 1 : 0;
2524  count = mdata->first_message;
2525  mdata->first_message = first;
2526  nntp_bcache_update(mdata);
2527  mdata->first_message = count;
2528 #ifdef USE_HCACHE
2529  hc = nntp_hcache_open(mdata);
2530  nntp_hcache_update(mdata, hc);
2531 #endif
2532  if (!hc)
2533  m->rights &= ~(MUTT_ACL_WRITE | MUTT_ACL_DELETE); // Clear the flags
2534 
2535  nntp_newsrc_close(adata);
2536  rc = nntp_fetch_headers(m, hc, first, mdata->last_message, false);
2537 #ifdef USE_HCACHE
2538  mutt_hcache_close(hc);
2539 #endif
2540  if (rc < 0)
2541  return -1;
2542  mdata->last_loaded = mdata->last_message;
2543  adata->newsrc_modified = false;
2544  return 0;
2545 }
2546 
2555 static int nntp_mbox_check(struct Mailbox *m)
2556 {
2557  int rc = check_mailbox(m);
2558  if (rc == 0)
2559  {
2560  struct NntpMboxData *mdata = m->mdata;
2561  struct NntpAccountData *adata = mdata->adata;
2562  nntp_newsrc_close(adata);
2563  }
2564  return rc;
2565 }
2566 
2572 static int nntp_mbox_sync(struct Mailbox *m)
2573 {
2574  struct NntpMboxData *mdata = m->mdata;
2575  int rc;
2576 
2577  /* check for new articles */
2578  mdata->adata->check_time = 0;
2579  rc = check_mailbox(m);
2580  if (rc)
2581  return rc;
2582 
2583 #ifdef USE_HCACHE
2584  mdata->last_cached = 0;
2585  struct HeaderCache *hc = nntp_hcache_open(mdata);
2586 #endif
2587 
2588  for (int i = 0; i < m->msg_count; i++)
2589  {
2590  struct Email *e = m->emails[i];
2591  if (!e)
2592  break;
2593 
2594  char buf[16];
2595 
2596  snprintf(buf, sizeof(buf), ANUM, nntp_edata_get(e)->article_num);
2597  if (mdata->bcache && e->deleted)
2598  {
2599  mutt_debug(LL_DEBUG2, "mutt_bcache_del %s\n", buf);
2600  mutt_bcache_del(mdata->bcache, buf);
2601  }
2602 
2603 #ifdef USE_HCACHE
2604  if (hc && (e->changed || e->deleted))
2605  {
2606  if (e->deleted && !e->read)
2607  mdata->unread--;
2608  mutt_debug(LL_DEBUG2, "mutt_hcache_store %s\n", buf);
2609  mutt_hcache_store(hc, buf, strlen(buf), e, 0);
2610  }
2611 #endif
2612  }
2613 
2614 #ifdef USE_HCACHE
2615  if (hc)
2616  {
2617  mutt_hcache_close(hc);
2618  mdata->last_cached = mdata->last_loaded;
2619  }
2620 #endif
2621 
2622  /* save .newsrc entries */
2624  nntp_newsrc_update(mdata->adata);
2625  nntp_newsrc_close(mdata->adata);
2626  return 0;
2627 }
2628 
2633 static int nntp_mbox_close(struct Mailbox *m)
2634 {
2635  struct NntpMboxData *mdata = m->mdata;
2636  struct NntpMboxData *tmp_mdata = NULL;
2637  if (!mdata)
2638  return 0;
2639 
2640  mdata->unread = m->msg_unread;
2641 
2642  nntp_acache_free(mdata);
2643  if (!mdata->adata || !mdata->adata->groups_hash || !mdata->group)
2644  return 0;
2645 
2646  tmp_mdata = mutt_hash_find(mdata->adata->groups_hash, mdata->group);
2647  if (!tmp_mdata || (tmp_mdata != mdata))
2648  nntp_mdata_free((void **) &mdata);
2649  return 0;
2650 }
2651 
2655 static int nntp_msg_open(struct Mailbox *m, struct Message *msg, int msgno)
2656 {
2657  struct NntpMboxData *mdata = m->mdata;
2658  struct Email *e = m->emails[msgno];
2659  if (!e)
2660  return -1;
2661 
2662  char article[16];
2663 
2664  /* try to get article from cache */
2665  struct NntpAcache *acache = &mdata->acache[e->index % NNTP_ACACHE_LEN];
2666  if (acache->path)
2667  {
2668  if (acache->index == e->index)
2669  {
2670  msg->fp = mutt_file_fopen(acache->path, "r");
2671  if (msg->fp)
2672  return 0;
2673  }
2674  /* clear previous entry */
2675  else
2676  {
2677  unlink(acache->path);
2678  FREE(&acache->path);
2679  }
2680  }
2681  snprintf(article, sizeof(article), ANUM, nntp_edata_get(e)->article_num);
2682  msg->fp = mutt_bcache_get(mdata->bcache, article);
2683  if (msg->fp)
2684  {
2685  if (nntp_edata_get(e)->parsed)
2686  return 0;
2687  }
2688  else
2689  {
2690  char buf[PATH_MAX];
2691  /* don't try to fetch article from removed newsgroup */
2692  if (mdata->deleted)
2693  return -1;
2694 
2695  /* create new cache file */
2696  const char *fetch_msg = _("Fetching message...");
2697  mutt_message(fetch_msg);
2698  msg->fp = mutt_bcache_put(mdata->bcache, article);
2699  if (!msg->fp)
2700  {
2701  mutt_mktemp(buf, sizeof(buf));
2702  acache->path = mutt_str_dup(buf);
2703  acache->index = e->index;
2704  msg->fp = mutt_file_fopen(acache->path, "w+");
2705  if (!msg->fp)
2706  {
2707  mutt_perror(acache->path);
2708  unlink(acache->path);
2709  FREE(&acache->path);
2710  return -1;
2711  }
2712  }
2713 
2714  /* fetch message to cache file */
2715  snprintf(buf, sizeof(buf), "ARTICLE %s\r\n",
2716  nntp_edata_get(e)->article_num ? article : e->env->message_id);
2717  const int rc =
2718  nntp_fetch_lines(mdata, buf, sizeof(buf), fetch_msg, fetch_tempfile, msg->fp);
2719  if (rc)
2720  {
2721  mutt_file_fclose(&msg->fp);
2722  if (acache->path)
2723  {
2724  unlink(acache->path);
2725  FREE(&acache->path);
2726  }
2727  if (rc > 0)
2728  {
2729  if (mutt_str_startswith(buf, nntp_edata_get(e)->article_num ? "423" : "430"))
2730  {
2731  mutt_error(_("Article %s not found on the server"),
2732  nntp_edata_get(e)->article_num ? article : e->env->message_id);
2733  }
2734  else
2735  mutt_error("ARTICLE: %s", buf);
2736  }
2737  return -1;
2738  }
2739 
2740  if (!acache->path)
2741  mutt_bcache_commit(mdata->bcache, article);
2742  }
2743 
2744  /* replace envelope with new one
2745  * hash elements must be updated because pointers will be changed */
2746  if (m->id_hash && e->env->message_id)
2747  mutt_hash_delete(m->id_hash, e->env->message_id, e);
2748  if (m->subj_hash && e->env->real_subj)
2750 
2751  mutt_env_free(&e->env);
2752  e->env = mutt_rfc822_read_header(msg->fp, e, false, false);
2753 
2754  if (m->id_hash && e->env->message_id)
2755  mutt_hash_insert(m->id_hash, e->env->message_id, e);
2756  if (m->subj_hash && e->env->real_subj)
2758 
2759  /* fix content length */
2760  fseek(msg->fp, 0, SEEK_END);
2761  e->body->length = ftell(msg->fp) - e->body->offset;
2762 
2763  /* this is called in neomutt before the open which fetches the message,
2764  * which is probably wrong, but we just call it again here to handle
2765  * the problem instead of fixing it */
2766  nntp_edata_get(e)->parsed = true;
2768 
2769  /* these would normally be updated in ctx_update(), but the
2770  * full headers aren't parsed with overview, so the information wasn't
2771  * available then */
2772  if (WithCrypto)
2773  e->security = crypt_query(e->body);
2774 
2775  rewind(msg->fp);
2776  mutt_clear_error();
2777  return 0;
2778 }
2779 
2785 static int nntp_msg_close(struct Mailbox *m, struct Message *msg)
2786 {
2787  return mutt_file_fclose(&msg->fp);
2788 }
2789 
2793 enum MailboxType nntp_path_probe(const char *path, const struct stat *st)
2794 {
2795  if (mutt_istr_startswith(path, "news://"))
2796  return MUTT_NNTP;
2797 
2798  if (mutt_istr_startswith(path, "snews://"))
2799  return MUTT_NNTP;
2800 
2801  return MUTT_UNKNOWN;
2802 }
2803 
2807 static int nntp_path_canon(char *buf, size_t buflen)
2808 {
2809  return 0;
2810 }
2811 
2815 static int nntp_path_pretty(char *buf, size_t buflen, const char *folder)
2816 {
2817  /* Succeed, but don't do anything, for now */
2818  return 0;
2819 }
2820 
2824 static int nntp_path_parent(char *buf, size_t buflen)
2825 {
2826  /* Succeed, but don't do anything, for now */
2827  return 0;
2828 }
2829 
2830 // clang-format off
2834 struct MxOps MxNntpOps = {
2835  .type = MUTT_NNTP,
2836  .name = "nntp",
2837  .is_local = false,
2838  .ac_find = nntp_ac_find,
2839  .ac_add = nntp_ac_add,
2840  .mbox_open = nntp_mbox_open,
2841  .mbox_open_append = NULL,
2842  .mbox_check = nntp_mbox_check,
2843  .mbox_check_stats = NULL,
2844  .mbox_sync = nntp_mbox_sync,
2845  .mbox_close = nntp_mbox_close,
2846  .msg_open = nntp_msg_open,
2847  .msg_open_new = NULL,
2848  .msg_commit = NULL,
2849  .msg_close = nntp_msg_close,
2850  .msg_padding_size = NULL,
2851  .msg_save_hcache = NULL,
2852  .tags_edit = NULL,
2853  .tags_commit = NULL,
2854  .path_probe = nntp_path_probe,
2855  .path_canon = nntp_path_canon,
2856  .path_pretty = nntp_path_pretty,
2857  .path_parent = nntp_path_parent,
2858 };
2859 // clang-format on
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
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:871
struct Email ** emails
Array of Emails.
Definition: mailbox.h:99
Convenience wrapper for the gui headers.
time_t mutt_date_epoch(void)
Return the number of seconds since the Unix epoch.
Definition: date.c:416
bool has_new_mail
Definition: lib.h:148
static const char * mailbox_path(const struct Mailbox *m)
Get the Mailbox&#39;s path string.
Definition: mailbox.h:203
char * path
Definition: lib.h:120
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
struct NntpAcache acache[NNTP_ACACHE_LEN]
Definition: lib.h:154
enum MailboxType type
Mailbox type.
Definition: mailbox.h:105
char * newsrc_file
Definition: lib.h:91
void * mutt_mem_calloc(size_t nmemb, size_t size)
Allocate zeroed memory on the heap.
Definition: memory.c:50
struct HeaderCache * hc
Definition: nntp.c:98
int mutt_account_getuser(struct ConnAccount *cac)
Retrieve username into ConnAccount, if necessary.
Definition: connaccount.c:48
Miscellaneous email parsing routines.
int msg_count
Total number of messages.
Definition: mailbox.h:91
#define mutt_set_flag(m, e, flag, bf)
Definition: protos.h:67
#define WithCrypto
Definition: lib.h:123
The envelope/body of an email.
Definition: email.h:37
int nntp_active_fetch(struct NntpAccountData *adata, bool mark_new)
Fetch list of all newsgroups from server.
Definition: nntp.c:2052
#define mutt_perror(...)
Definition: logging.h:85
bool C_SaveUnsubscribed
Config: (nntp) Save a list of unsubscribed newsgroups to the &#39;newsrc&#39;.
Definition: config.c:52
struct NntpEmailData * nntp_edata_get(struct Email *e)
Get the private data for this Email.
Definition: nntp.c:217
static void nntp_hashelem_free(int type, void *obj, intptr_t data)
Free our hash table data - Implements hash_hdata_free_t.
Definition: nntp.c:139
void nntp_newsrc_gen_entries(struct Mailbox *m)
Generate array of .newsrc entries.
Definition: newsrc.c:295
Config/command parsing.
Wrapper for Email retrieved from the header cache.
Definition: lib.h:96
static int nntp_auth(struct NntpAccountData *adata)
Get login, password and authenticate.
Definition: nntp.c:528
struct ConnAccount account
Account details: username, password, etc.
Definition: connection.h:36
struct Body * body
List of MIME parts.
Definition: email.h:91
#define mutt_socket_send(conn, buf)
Definition: mutt_socket.h:37
int msg_unread
Number of unread messages.
Definition: mailbox.h:92
&#39;NNTP&#39; (Usenet) Mailbox type
Definition: mailbox.h:52
Structs that make up an email.
static void nntp_parse_xref(struct Mailbox *m, struct Email *e)
Parse cross-reference.
Definition: nntp.c:1013
struct BodyCache * mutt_bcache_open(struct ConnAccount *account, const char *mailbox)
Open an Email-Body Cache.
Definition: bcache.c:143
static int nntp_attempt_features(struct NntpAccountData *adata)
Detect supported commands.
Definition: nntp.c:346
void mutt_hash_free(struct HashTable **ptr)
Free a hash table.
Definition: hash.c:447
#define mutt_message(...)
Definition: logging.h:83
static int nntp_fetch_headers(struct Mailbox *m, void *hc, anum_t first, anum_t last, bool restore)
Fetch headers.
Definition: nntp.c:1251
Disconnected from server.
Definition: private.h:44
A parsed URL proto://user:password@host:port/path?a=1&b=2
Definition: url.h:66
unsigned int use_tls
Definition: lib.h:86
char * xref
List of cross-references.
Definition: envelope.h:76
bool newsrc_modified
Definition: lib.h:89
unsigned int num
Definition: nntp.c:107
struct NntpAccountData * adata
Definition: lib.h:153
enum UrlScheme scheme
Scheme, e.g. U_SMTPS.
Definition: url.h:68
static size_t plen
Length of cached packet.
Definition: pgppacket.c:39
static int fetch_tempfile(char *line, void *data)
Write line to temporary file.
Definition: nntp.c:1054
static int nntp_path_pretty(char *buf, size_t buflen, const char *folder)
Abbreviate a Mailbox path - Implements MxOps::path_pretty()
Definition: nntp.c:2815
int mutt_bcache_commit(struct BodyCache *bcache, const char *id)
Move a temporary file into the Body Cache.
Definition: bcache.c:246
char * realpath
Used for duplicate detection, context comparison, and the sidebar.
Definition: mailbox.h:84
NeoMutt Logging.
int nntp_post(struct Mailbox *m, const char *msg)
Post article.
Definition: nntp.c:1970
static int nntp_date(struct NntpAccountData *adata, time_t *now)
Get date and time from server.
Definition: nntp.c:1739
struct Mailbox * mailbox
Definition: nntp.c:92
A group of associated Mailboxes.
Definition: account.h:36
char * C_NntpAuthenticators
Config: (nntp) Allowed authentication methods.
Definition: config.c:44
header cache structure
Definition: lib.h:85
bool parsed
Definition: lib.h:111
static int fetch_description(char *line, void *data)
Parse newsgroup description.
Definition: nntp.c:946
An open network connection (socket)
Definition: connection.h:34
#define MUTT_ACL_INSERT
Add/copy into the mailbox (used when editing a message)
Definition: mailbox.h:69
char user[128]
Username.
Definition: connaccount.h:55
short C_NntpContext
Config: (nntp) Maximum number of articles to list (0 for all articles)
Definition: config.c:45
void mutt_parse_mime_message(struct Mailbox *m, struct Email *e)
Parse a MIME email.
Definition: mutt_parse.c:49
void mx_alloc_memory(struct Mailbox *m)
Create storage for the emails.
Definition: mx.c:1230
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:370
LOFF_T offset
offset where the actual data begins
Definition: body.h:44
#define _(a)
Definition: message.h:28
Mailbox wasn&#39;t recognised.
Definition: mailbox.h:47
anum_t article_num
Definition: lib.h:110
char * real_subj
Offset of the real subject.
Definition: envelope.h:67
bool changed
Email has been edited.
Definition: email.h:48
#define NNTP_ACACHE_LEN
Definition: lib.h:133
int mutt_sasl_client_new(struct Connection *conn, sasl_conn_t **saslconn)
Wrapper for sasl_client_new()
Definition: sasl.c:603
void nntp_mdata_free(void **ptr)
Free the private Mailbox data - Implements Mailbox::mdata_free()
Definition: nntp.c:180
static int nntp_connect_error(struct NntpAccountData *adata)
Signal a failed connection.
Definition: nntp.c:229
Keep track of the children of an article.
Definition: nntp.c:104
void mutt_sasl_setup_conn(struct Connection *conn, sasl_conn_t *saslconn)
Set up an SASL connection.
Definition: sasl.c:731
char * C_NewsServer
Config: (nntp) Url of the news server.
Definition: config.c:41
void nntp_newsrc_close(struct NntpAccountData *adata)
Unlock and close .newsrc file.
Definition: newsrc.c:117
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:506
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
bool hasSTARTTLS
Definition: lib.h:78
Email list was changed.
Definition: mailbox.h:173
void url_free(struct Url **ptr)
Free the contents of a URL.
Definition: url.c:123
enum MailboxType type
Type of Mailboxes this Account contains.
Definition: account.h:38
#define MUTT_SOCK_LOG_CMD
Definition: mutt_socket.h:29
int nntp_active_save_cache(struct NntpAccountData *adata)
Save list of all newsgroups to cache.
Definition: newsrc.c:648
unsigned char * messages
Definition: nntp.c:96
void nntp_delete_group_cache(struct NntpMboxData *mdata)
Remove hcache and bcache of newsgroup.
Definition: newsrc.c:806
void nntp_hcache_update(struct NntpMboxData *mdata, struct HeaderCache *hc)
Remove stale cached headers.
Definition: newsrc.c:729
NNTP-specific Email data -.
Definition: lib.h:108
#define MUTT_SOCK_LOG_FULL
Definition: mutt_socket.h:31
A progress bar.
Definition: progress.h:50
#define MUTT_ACCT_USER
User field has been set.
Definition: connaccount.h:43
struct HashTable * groups_hash
Definition: lib.h:101
NNTP-specific Account data -.
Definition: lib.h:75
anum_t last
Definition: lib.h:129
#define MUTT_ACL_DELETE
Delete a message.
Definition: mailbox.h:66
Connected to server.
Definition: private.h:43
bool subscribed
Definition: lib.h:147
int vcount
The number of virtual messages.
Definition: mailbox.h:102
Convenience wrapper for the config headers.
anum_t last_cached
Definition: lib.h:145
int mutt_socket_open(struct Connection *conn)
Simple wrapper.
Definition: socket.c:75
Assorted sorting methods.
char host[128]
Server to login to.
Definition: connaccount.h:53
Some miscellaneous functions.
char inbuf[1024]
Buffer for incoming traffic.
Definition: connection.h:38
bool read
Email is read.
Definition: email.h:51
void mutt_progress_update(struct Progress *progress, size_t pos, int percent)
Update the state of the progress bar.
Definition: progress.c:212
void mutt_sleep(short s)
Sleep for a while.
Definition: muttlib.c:1446
char * message_id
Message ID.
Definition: envelope.h:69
enum QuadOption mutt_yesorno(const char *msg, enum QuadOption def)
Ask the user a Yes/No question.
Definition: curs_lib.c:379
Header cache multiplexor.
struct HashTable * id_hash
Hash Table by msg id.
Definition: mailbox.h:127
Parse and execute user-defined hooks.
const char * OverviewFmt
Definition: nntp.c:78
Many unsorted constants and some structs.
Log at debug level 2.
Definition: logging.h:41
FILE * mutt_bcache_get(struct BodyCache *bcache, const char *id)
Open a file in the Body Cache.
Definition: bcache.c:179
API for mailboxes.
bool old
Email is seen, but unread.
Definition: email.h:50
time_t newgroups_time
Definition: lib.h:96
bool readonly
Don&#39;t allow changes to the mailbox.
Definition: mailbox.h:119
An entry in a .newsrc (subscribed newsgroups)
Definition: lib.h:126
int mutt_file_fclose(FILE **fp)
Close a FILE handle (and NULL the pointer)
Definition: file.c:153
anum_t last_loaded
Definition: lib.h:144
anum_t last
Definition: nntp.c:94
struct Envelope * env
Envelope information.
Definition: email.h:90
bool hasLIST_NEWSGROUPS
Definition: lib.h:80
char * overview_fmt
Definition: lib.h:93
Convenience wrapper for the core headers.
char pass[256]
Password.
Definition: connaccount.h:56
#define mutt_socket_send_d(conn, buf, dbg)
Definition: mutt_socket.h:38
char * group
Definition: lib.h:140
Progress tracks elements, according to $read_inc
Definition: progress.h:42
static struct Account * nntp_ac_find(struct Account *a, const char *path)
Find an Account that matches a Mailbox path - Implements MxOps::ac_find()
Definition: nntp.c:2376
void mutt_clear_error(void)
Clear the message line (bottom line of screen)
Definition: mutt_logging.c:113
Progress bar.
void(* adata_free)(void **ptr)
Free the private data attached to the Account.
Definition: account.h:49
void * mdata
Driver specific data.
Definition: mailbox.h:136
struct NntpAccountData * nntp_select_server(struct Mailbox *m, const char *server, bool leave_lock)
Open a connection to an NNTP server.
Definition: newsrc.c:1008
static int nntp_path_parent(char *buf, size_t buflen)
Find the parent of a Mailbox path - Implements MxOps::path_parent()
Definition: nntp.c:2824
#define MUTT_ACL_WRITE
Write to a message (for flagging or linking threads)
Definition: mailbox.h:74
Url is nntps://.
Definition: url.h:41
struct HashTable * subj_hash
Hash Table by subject.
Definition: mailbox.h:128
enum MailboxType type
Mailbox type, e.g. MUTT_IMAP.
Definition: mx.h:106
static int nntp_group_poll(struct NntpMboxData *mdata, bool update_stat)
Check newsgroup for new articles.
Definition: nntp.c:1483
Body Caching - local copies of email bodies.
FILE * mutt_bcache_put(struct BodyCache *bcache, const char *id)
Create a file in the Body Cache.
Definition: bcache.c:206
void nntp_bcache_update(struct NntpMboxData *mdata)
Remove stale cached messages.
Definition: newsrc.c:797
void mutt_mem_realloc(void *ptr, size_t size)
Resize a block of memory on the heap.
Definition: memory.c:114
time_t date_sent
Time when the message was sent (UTC)
Definition: email.h:82
void mutt_str_remove_trailing_ws(char *s)
Trim trailing whitespace from a string.
Definition: string.c:700
void mutt_hash_set_destructor(struct HashTable *table, hash_hdata_free_t fn, intptr_t fn_data)
Set the destructor for a Hash Table.
Definition: hash.c:293
Prototypes for many functions.
int nntp_compare_order(const void *a, const void *b)
Sort to mailbox order - Implements sort_t.
Definition: nntp.c:2361
#define ANUM
Definition: lib.h:70
#define mutt_mktemp(buf, buflen)
Definition: muttlib.h:74
static int nntp_mbox_open(struct Mailbox *m)
Open a Mailbox - Implements MxOps::mbox_open()
Definition: nntp.c:2392
bool C_ShowNewNews
Config: (nntp) Check for new newsgroups when entering the browser.
Definition: config.c:53
short C_DebugLevel
Config: Logging level for debug logs.
Definition: mutt_logging.c:48
size_t mutt_str_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix.
Definition: string.c:160
A local copy of an email.
Definition: mx.h:82
int email_max
Number of pointers in emails.
Definition: mailbox.h:100
struct BodyCache * bcache
Definition: lib.h:155
unsigned char C_SslStarttls
Config: (ssl) Use STARTTLS on servers advertising the capability.
Definition: config.c:47
void mutt_account_hook(const char *url)
Perform an account hook.
Definition: hook.c:755
LOFF_T length
length (in bytes) of attachment
Definition: body.h:45
unsigned int status
Definition: lib.h:87
A mailbox.
Definition: mailbox.h:81
bool hasXGTITLE
Definition: lib.h:81
#define PATH_MAX
Definition: mutt.h:44
short C_NntpPoll
Config: (nntp) Interval between checks for new posts.
Definition: config.c:49
unsigned int groups_max
Definition: lib.h:99
int fd
Socket file descriptor.
Definition: connection.h:40
void * mutt_mem_malloc(size_t size)
Allocate memory on the heap.
Definition: memory.c:90
void * adata
Private data (for Mailbox backends)
Definition: account.h:43
struct HeaderCache * nntp_hcache_open(struct NntpMboxData *mdata)
Open newsgroup hcache.
Definition: newsrc.c:707
bool hasLISTGROUP
Definition: lib.h:82
int nntp_check_msgid(struct Mailbox *m, const char *msgid)
Fetch article by Message-ID.
Definition: nntp.c:2222
void mutt_env_free(struct Envelope **ptr)
Free an Envelope.
Definition: envelope.c:96
Tagged messages.
Definition: mutt.h:103
void(* edata_free)(void **ptr)
Free the private data attached to the Email.
Definition: email.h:117
No connection to server.
Definition: private.h:42
unsigned int groups_num
Definition: lib.h:98
int mutt_hcache_delete_record(struct HeaderCache *hc, const char *key, size_t keylen)
Multiplexor for StoreOps::delete_record.
Definition: hcache.c:602
static int nntp_mbox_check(struct Mailbox *m)
Check for new mail - Implements MxOps::mbox_check()
Definition: nntp.c:2555
static int 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:2655
#define SORT_CODE(x)
Definition: sort.h:40
int nntp_newsrc_parse(struct NntpAccountData *adata)
Parse .newsrc file.
Definition: newsrc.c:161
#define mutt_file_mkstemp()
Definition: file.h:106
API for encryption/signing of emails.
static int nntp_path_canon(char *buf, size_t buflen)
Canonicalise a Mailbox path - Implements MxOps::path_canon()
Definition: nntp.c:2807
struct Progress progress
Definition: nntp.c:97
bool verbose
Display status messages?
Definition: mailbox.h:118
char * host
Host.
Definition: url.h:71
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:525
int nntp_add_group(char *line, void *data)
Parse newsgroup.
Definition: newsrc.c:573
int msg_tagged
How many messages are tagged?
Definition: mailbox.h:97
int mutt_bcache_del(struct BodyCache *bcache, const char *id)
Delete a file from the Body Cache.
Definition: bcache.c:263
struct Mailbox * mailbox
Definition: nntp.c:106
static int fetch_numbers(char *line, void *data)
Parse article number.
Definition: nntp.c:1071
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
anum_t * child
Definition: nntp.c:109
AclFlags rights
ACL bits, see AclFlags.
Definition: mailbox.h:121
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
static int get_description(struct NntpMboxData *mdata, const char *wildmat, const char *msg)
Fetch newsgroups descriptions.
Definition: nntp.c:981
bool C_SslForceTls
Config: (ssl) Require TLS encryption for all connections.
Definition: config.c:46
SecurityFlags security
bit 0-10: flags, bit 11,12: application, bit 13: traditional pgp See: ncrypt/lib.h pgplib...
Definition: email.h:39
struct NewsrcEntry * newsrc_ent
Definition: lib.h:152
bool hasOVER
Definition: lib.h:84
int mutt_socket_close(struct Connection *conn)
Close a socket.
Definition: socket.c:96
NNTP-specific Mailbox data -.
Definition: lib.h:138
static int nntp_mbox_sync(struct Mailbox *m)
Save changes to the Mailbox - Implements MxOps::mbox_sync()
Definition: nntp.c:2572
unsigned int newsrc_len
Definition: lib.h:151
char * path
Path.
Definition: url.h:73
char * mutt_str_cat(char *buf, size_t buflen, const char *s)
Concatenate two strings.
Definition: string.c:385
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:867
static int fetch_children(char *line, void *data)
Parse XPAT line.
Definition: nntp.c:1776
void mutt_hcache_close(struct HeaderCache *hc)
Multiplexor for StoreOps::close.
Definition: hcache.c:417
unsigned int index
Definition: lib.h:119
Keep track when getting data from a server.
Definition: nntp.c:90
#define mutt_socket_readln(buf, buflen, conn)
Definition: mutt_socket.h:36
struct Connection * conn
Definition: lib.h:102
void mutt_socket_empty(struct Connection *conn)
Clear out any queued data.
Definition: socket.c:312
enum QuadOption query_quadoption(enum QuadOption opt, const char *prompt)
Ask the user a quad-question.
Definition: curs_lib.c:517
char * authenticators
Definition: lib.h:92
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:790
time_t check_time
Definition: lib.h:97
MailboxType
Supported mailbox formats.
Definition: mailbox.h:43
struct Account * account
Account that owns this Mailbox.
Definition: mailbox.h:131
Log at debug level 1.
Definition: logging.h:40
bool deleted
Definition: lib.h:150
struct NntpAccountData * nntp_adata_new(struct Connection *conn)
Allocate and initialise a new NntpAccountData structure.
Definition: nntp.c:149
bool flagged
Marked important?
Definition: email.h:43
char * newsgroups
List of newsgroups.
Definition: envelope.h:75
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
static struct NntpEmailData * nntp_edata_new(void)
Create a new NntpEmailData for an Email.
Definition: nntp.c:207
bool hasLISTGROUPrange
Definition: lib.h:83
int nntp_check_children(struct Mailbox *m, const char *msgid)
Fetch children of article with the Message-ID.
Definition: nntp.c:2292
char * mutt_str_replace(char **p, const char *s)
Replace one string with another.
Definition: string.c:446
anum_t first_message
Definition: lib.h:142
bool deleted
Email is deleted.
Definition: email.h:45
void * edata
Driver-specific data.
Definition: email.h:111
NNTP article cache.
Definition: lib.h:117
anum_t unread
Definition: lib.h:146
Url is nntp://.
Definition: url.h:40
#define mutt_error(...)
Definition: logging.h:84
Connection Library.
void ** groups_list
Definition: lib.h:100
void nntp_group_unread_stat(struct NntpMboxData *mdata)
Count number of unread articles using .newsrc data.
Definition: newsrc.c:131
FILE * fp
pointer to the message data
Definition: mx.h:84
FILE * fp_newsrc
Definition: lib.h:90
int index
The absolute (unsorted) message number.
Definition: email.h:86
#define FREE(x)
Definition: memory.h:40
static int nntp_msg_close(struct Mailbox *m, struct Message *msg)
Close an email - Implements MxOps::msg_close()
Definition: nntp.c:2785
int url_tostring(struct Url *url, char *dest, size_t len, int flags)
Output the URL string for a given Url object.
Definition: url.c:418
static bool nntp_memchr(char **haystack, char *sentinel, int needle)
look for a char in a binary buf, conveniently
Definition: nntp.c:488
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
bool restore
Definition: nntp.c:95
void mutt_hash_delete(struct HashTable *table, const char *strkey, const void *data)
Remove an element from a Hash Table.
Definition: hash.c:419
struct HCacheEntry mutt_hcache_fetch(struct HeaderCache *hc, const char *key, size_t keylen, uint32_t uidvalidity)
Multiplexor for StoreOps::fetch.
Definition: hcache.c:436
int perform_auxsort(int retval, const void *a, const void *b)
Compare two emails using the auxiliary sort method.
Definition: sort.c:65
static void nntp_edata_free(void **ptr)
Free the private Email data - Implements Email::edata_free()
Definition: nntp.c:197
bool hasXOVER
Definition: lib.h:85
struct Email * email_new(void)
Create a new Email.
Definition: email.c:72
int nntp_check_new_groups(struct Mailbox *m, struct NntpAccountData *adata)
Check for new groups/articles in subscribed groups.
Definition: nntp.c:2119
Hundreds of global variables to back the user variables.
#define mutt_debug(LEVEL,...)
Definition: logging.h:81
New mail received in Mailbox.
Definition: mx.h:73
struct Email * email
Retrieved email.
Definition: lib.h:100
enum MailboxType nntp_path_probe(const char *path, const struct stat *st)
Is this an NNTP Mailbox? - Implements MxOps::path_probe()
Definition: nntp.c:2793
bool hasDATE
Definition: lib.h:79
NeoMutt connections.
int nntp_newsrc_update(struct NntpAccountData *adata)
Update .newsrc file.
Definition: newsrc.c:439
unsigned int max
Definition: nntp.c:108
bool hasCAPABILITIES
Definition: lib.h:77
Usenet network mailbox type; talk to an NNTP server.
bool C_NntpListgroup
Config: (nntp) Check all articles when opening a newsgroup.
Definition: config.c:46
Convenience wrapper for the library headers.
FILE * mutt_file_fopen(const char *path, const char *mode)
Call fopen() safely.
Definition: file.c:588
#define anum_t
Definition: lib.h:69
void email_free(struct Email **ptr)
Free an Email.
Definition: email.c:43
anum_t first
Definition: nntp.c:93
bool C_NntpLoadDescription
Config: (nntp) Load descriptions for newsgroups when adding to the list.
Definition: config.c:47
Mailbox was reopened.
Definition: mx.h:75
struct Envelope * mutt_rfc822_read_header(FILE *fp, struct Email *e, bool user_hdrs, bool weed)
parses an RFC822 header
Definition: parse.c:1111
int mutt_ssl_starttls(struct Connection *conn)
Negotiate TLS over an already opened connection.
Definition: gnutls.c:1136
struct HashTable * mutt_hash_new(size_t num_elems, HashFlags flags)
Create a new Hash Table (with string keys)
Definition: hash.c:251
static int nntp_capabilities(struct NntpAccountData *adata)
Get capabilities.
Definition: nntp.c:243
#define MUTT_HASH_NO_FLAGS
No flags are set.
Definition: hash.h:97
SecurityFlags crypt_query(struct Body *m)
Check out the type of encryption used.
Definition: crypt.c:685
void mailbox_changed(struct Mailbox *m, enum NotifyMailbox action)
Notify observers of a change to a Mailbox.
Definition: mailbox.c:177
void nntp_acache_free(struct NntpMboxData *mdata)
Remove all temporarily cache files.
Definition: newsrc.c:101
void mutt_bcache_close(struct BodyCache **bcache)
Close an Email-Body Cache.
Definition: bcache.c:164
anum_t first
Definition: lib.h:128
void nntp_article_status(struct Mailbox *m, struct Email *e, char *group, anum_t anum)
Get status of articles from .newsrc.
Definition: newsrc.c:1207
static int parse_overview_line(char *line, void *data)
Parse overview line.
Definition: nntp.c:1093
static int nntp_mbox_close(struct Mailbox *m)
Close a Mailbox - Implements MxOps::mbox_close()
Definition: nntp.c:2633
time_t received
Time when the message was placed in the mailbox.
Definition: email.h:83
The Mailbox API.
Definition: mx.h:104
int msgno
Number displayed to the user.
Definition: email.h:87
static void nntp_adata_free(void **ptr)
Free the private Account data - Implements Account::adata_free()
Definition: nntp.c:119
struct NntpAccountData * CurrentNewsSrv
Current NNTP news server.
Definition: nntp.c:76
static int nntp_ac_add(struct Account *a, struct Mailbox *m)
Add a Mailbox to an Account - Implements MxOps::ac_add()
Definition: nntp.c:2384
User answered &#39;Yes&#39;, or assume &#39;Yes&#39;.
Definition: quad.h:40
int nntp_open_connection(struct NntpAccountData *adata)
Connect to server, authenticate and get capabilities.
Definition: nntp.c:1806
MuttAccountFlags flags
Which fields are initialised, e.g. MUTT_ACCT_USER.
Definition: connaccount.h:59
int mutt_account_getpass(struct ConnAccount *cac)
Fetch password into ConnAccount, if necessary.
Definition: connaccount.c:111
static int check_mailbox(struct Mailbox *m)
Check current newsgroup for new articles.
Definition: nntp.c:1531
int mutt_sasl_interact(sasl_interact_t *interaction)
Perform an SASL interaction with the user.
Definition: sasl.c:698
char * desc
Definition: lib.h:141
#define MUTT_SOCK_LOG_HDR
Definition: mutt_socket.h:30
struct Url * url_parse(const char *src)
Fill in Url.
Definition: url.c:234
anum_t last_message
Definition: lib.h:143