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