NeoMutt  2025-09-05-7-geaa2bd
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
nntp.c
Go to the documentation of this file.
1
34#include "config.h"
35#include <stdbool.h>
36#include <stdint.h>
37#include <stdio.h>
38#include <string.h>
39#include <strings.h>
40#include <time.h>
41#include <unistd.h>
42#include "private.h"
43#include "mutt/lib.h"
44#include "config/lib.h"
45#include "email/lib.h"
46#include "core/lib.h"
47#include "conn/lib.h"
48#include "lib.h"
49#include "attach/lib.h"
50#include "bcache/lib.h"
51#include "hcache/lib.h"
52#include "ncrypt/lib.h"
53#include "progress/lib.h"
54#include "question/lib.h"
55#include "adata.h"
56#include "edata.h"
57#include "hook.h"
58#include "mdata.h"
59#include "mutt_logging.h"
60#include "muttlib.h"
61#include "mx.h"
62#ifdef USE_HCACHE
63#include "protos.h"
64#endif
65#ifdef USE_SASL_CYRUS
66#include <sasl/sasl.h>
67#include <sasl/saslutil.h>
68#endif
69#if defined(USE_SSL) || defined(USE_HCACHE)
70#include "mutt.h"
71#endif
72
73struct stat;
74
77
79static const char *OverviewFmt = "Subject:\0"
80 "From:\0"
81 "Date:\0"
82 "Message-ID:\0"
83 "References:\0"
84 "Content-Length:\0"
85 "Lines:\0"
86 "\0";
87
92{
96 bool restore;
97 unsigned char *messages;
98 struct Progress *progress;
99 struct HeaderCache *hc;
100};
101
106{
108 unsigned int num;
109 unsigned int max;
111};
112
116void nntp_hashelem_free(int type, void *obj, intptr_t data)
117{
118 nntp_mdata_free(&obj);
119}
120
126static int nntp_connect_error(struct NntpAccountData *adata)
127{
128 adata->status = NNTP_NONE;
129 mutt_error(_("Server closed connection"));
130 return -1;
131}
132
140static int nntp_capabilities(struct NntpAccountData *adata)
141{
142 struct Connection *conn = adata->conn;
143 bool mode_reader = false;
144 char authinfo[1024] = { 0 };
145
146 adata->hasCAPABILITIES = false;
147 adata->hasSTARTTLS = false;
148 adata->hasDATE = false;
149 adata->hasLIST_NEWSGROUPS = false;
150 adata->hasLISTGROUP = false;
151 adata->hasLISTGROUPrange = false;
152 adata->hasOVER = false;
153 FREE(&adata->authenticators);
154
155 struct Buffer *buf = buf_pool_get();
156
157 if ((mutt_socket_send(conn, "CAPABILITIES\r\n") < 0) ||
158 (mutt_socket_buffer_readln(buf, conn) < 0))
159 {
160 buf_pool_release(&buf);
161 return nntp_connect_error(adata);
162 }
163
164 /* no capabilities */
165 if (!mutt_str_startswith(buf_string(buf), "101"))
166 {
167 buf_pool_release(&buf);
168 return 1;
169 }
170 adata->hasCAPABILITIES = true;
171
172 /* parse capabilities */
173 do
174 {
175 size_t plen = 0;
176 if (mutt_socket_buffer_readln(buf, conn) < 0)
177 {
178 buf_pool_release(&buf);
179 return nntp_connect_error(adata);
180 }
181 if (mutt_str_equal("STARTTLS", buf_string(buf)))
182 {
183 adata->hasSTARTTLS = true;
184 }
185 else if (mutt_str_equal("MODE-READER", buf_string(buf)))
186 {
187 mode_reader = true;
188 }
189 else if (mutt_str_equal("READER", buf_string(buf)))
190 {
191 adata->hasDATE = true;
192 adata->hasLISTGROUP = true;
193 adata->hasLISTGROUPrange = true;
194 }
195 else if ((plen = mutt_str_startswith(buf_string(buf), "AUTHINFO ")))
196 {
197 buf_addch(buf, ' ');
198 mutt_str_copy(authinfo, buf->data + plen - 1, sizeof(authinfo));
199 }
200#ifdef USE_SASL_CYRUS
201 else if ((plen = mutt_str_startswith(buf_string(buf), "SASL ")))
202 {
203 char *p = buf->data + plen;
204 while (*p == ' ')
205 p++;
206 adata->authenticators = mutt_str_dup(p);
207 }
208#endif
209 else if (mutt_str_equal("OVER", buf_string(buf)))
210 {
211 adata->hasOVER = true;
212 }
213 else if (mutt_str_startswith(buf_string(buf), "LIST "))
214 {
215 const char *p = buf_find_string(buf, " NEWSGROUPS");
216 if (p)
217 {
218 p += 11;
219 if ((*p == '\0') || (*p == ' '))
220 adata->hasLIST_NEWSGROUPS = true;
221 }
222 }
223 } while (!mutt_str_equal(".", buf_string(buf)));
224 buf_reset(buf);
225
226#ifdef USE_SASL_CYRUS
227 if (adata->authenticators && mutt_istr_find(authinfo, " SASL "))
228 buf_strcpy(buf, adata->authenticators);
229#endif
230 if (mutt_istr_find(authinfo, " USER "))
231 {
232 if (!buf_is_empty(buf))
233 buf_addch(buf, ' ');
234 buf_addstr(buf, "USER");
235 }
237 buf_pool_release(&buf);
238
239 /* current mode is reader */
240 if (adata->hasDATE)
241 return 0;
242
243 /* server is mode-switching, need to switch to reader mode */
244 if (mode_reader)
245 return 1;
246
247 mutt_socket_close(conn);
248 adata->status = NNTP_BYE;
249 mutt_error(_("Server doesn't support reader mode"));
250 return -1;
251}
252
259static int nntp_attempt_features(struct NntpAccountData *adata)
260{
261 struct Connection *conn = adata->conn;
262 char buf[1024] = { 0 };
263 int rc = -1;
264
265 /* no CAPABILITIES, trying DATE, LISTGROUP, LIST NEWSGROUPS */
266 if (!adata->hasCAPABILITIES)
267 {
268 if ((mutt_socket_send(conn, "DATE\r\n") < 0) ||
269 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
270 {
271 goto fail;
272 }
273 if (!mutt_str_startswith(buf, "500"))
274 adata->hasDATE = true;
275
276 if ((mutt_socket_send(conn, "LISTGROUP\r\n") < 0) ||
277 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
278 {
279 goto fail;
280 }
281 if (!mutt_str_startswith(buf, "500"))
282 adata->hasLISTGROUP = true;
283
284 if ((mutt_socket_send(conn, "LIST NEWSGROUPS +\r\n") < 0) ||
285 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
286 {
287 goto fail;
288 }
289 if (!mutt_str_startswith(buf, "500"))
290 adata->hasLIST_NEWSGROUPS = true;
291 if (mutt_str_startswith(buf, "215"))
292 {
293 do
294 {
295 if (mutt_socket_readln(buf, sizeof(buf), conn) < 0)
296 goto fail;
297 } while (!mutt_str_equal(".", buf));
298 }
299 }
300
301 /* no LIST NEWSGROUPS, trying XGTITLE */
302 if (!adata->hasLIST_NEWSGROUPS)
303 {
304 if ((mutt_socket_send(conn, "XGTITLE\r\n") < 0) ||
305 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
306 {
307 goto fail;
308 }
309 if (!mutt_str_startswith(buf, "500"))
310 adata->hasXGTITLE = true;
311 }
312
313 /* no OVER, trying XOVER */
314 if (!adata->hasOVER)
315 {
316 if ((mutt_socket_send(conn, "XOVER\r\n") < 0) ||
317 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
318 {
319 goto fail;
320 }
321 if (!mutt_str_startswith(buf, "500"))
322 adata->hasXOVER = true;
323 }
324
325 /* trying LIST OVERVIEW.FMT */
326 if (adata->hasOVER || adata->hasXOVER)
327 {
328 if ((mutt_socket_send(conn, "LIST OVERVIEW.FMT\r\n") < 0) ||
329 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
330 {
331 goto fail;
332 }
333 if (!mutt_str_startswith(buf, "215"))
334 {
336 }
337 else
338 {
339 bool cont = false;
340 size_t buflen = 2048, off = 0, b = 0;
341
342 FREE(&adata->overview_fmt);
343 adata->overview_fmt = MUTT_MEM_MALLOC(buflen, char);
344
345 while (true)
346 {
347 if ((buflen - off) < 1024)
348 {
349 buflen *= 2;
350 MUTT_MEM_REALLOC(&adata->overview_fmt, buflen, char);
351 }
352
353 const int chunk = mutt_socket_readln_d(adata->overview_fmt + off,
354 buflen - off, conn, MUTT_SOCK_LOG_HDR);
355 if (chunk < 0)
356 {
357 FREE(&adata->overview_fmt);
358 goto fail;
359 }
360
361 if (!cont && mutt_str_equal(".", adata->overview_fmt + off))
362 break;
363
364 cont = (chunk >= (buflen - off));
365 off += strlen(adata->overview_fmt + off);
366 if (!cont)
367 {
368 if (adata->overview_fmt[b] == ':')
369 {
370 memmove(adata->overview_fmt + b, adata->overview_fmt + b + 1, off - b - 1);
371 adata->overview_fmt[off - 1] = ':';
372 }
373 char *colon = strchr(adata->overview_fmt + b, ':');
374 if (!colon)
375 adata->overview_fmt[off++] = ':';
376 else if (!mutt_str_equal(colon + 1, "full"))
377 off = colon + 1 - adata->overview_fmt;
378 if (strcasecmp(adata->overview_fmt + b, "Bytes:") == 0)
379 {
380 size_t len = strlen(adata->overview_fmt + b);
381 mutt_str_copy(adata->overview_fmt + b, "Content-Length:", len + 1);
382 off = b + len;
383 }
384 adata->overview_fmt[off++] = '\0';
385 b = off;
386 }
387 }
388 adata->overview_fmt[off++] = '\0';
389 MUTT_MEM_REALLOC(&adata->overview_fmt, off, char);
390 }
391 }
392 rc = 0; // Success
393
394fail:
395 if (rc < 0)
396 nntp_connect_error(adata);
397
398 return rc;
399}
400
401#ifdef USE_SASL_CYRUS
410static bool nntp_memchr(char **haystack, const char *sentinel, int needle)
411{
412 char *start = *haystack;
413 size_t max_offset = sentinel - start;
414 void *vp = memchr(start, max_offset, needle);
415 if (!vp)
416 return false;
417 *haystack = vp;
418 return true;
419}
420
428static void nntp_log_binbuf(const char *buf, size_t len, const char *pfx, int dbg)
429{
430 char tmp[1024] = { 0 };
431 char *p = tmp;
432 char *sentinel = tmp + len;
433
434 const short c_debug_level = cs_subset_number(NeoMutt->sub, "debug_level");
435 if (c_debug_level < dbg)
436 return;
437 memcpy(tmp, buf, len);
438 tmp[len] = '\0';
439 while (nntp_memchr(&p, sentinel, '\0'))
440 *p = '.';
441 mutt_debug(dbg, "%s> %s\n", pfx, tmp);
442}
443#endif
444
451static int nntp_auth(struct NntpAccountData *adata)
452{
453 struct Connection *conn = adata->conn;
454 char authenticators[1024] = "USER";
455 char *method = NULL, *a = NULL, *p = NULL;
456 unsigned char flags = conn->account.flags;
457 struct Buffer *buf = buf_pool_get();
458
459 const char *const c_nntp_authenticators = cs_subset_string(NeoMutt->sub, "nntp_authenticators");
460 while (true)
461 {
462 /* get login and password */
463 if ((mutt_account_getuser(&conn->account) < 0) || (conn->account.user[0] == '\0') ||
464 (mutt_account_getpass(&conn->account) < 0) || (conn->account.pass[0] == '\0'))
465 {
466 break;
467 }
468
469 /* get list of authenticators */
470 if (c_nntp_authenticators)
471 {
472 mutt_str_copy(authenticators, c_nntp_authenticators, sizeof(authenticators));
473 }
474 else if (adata->hasCAPABILITIES)
475 {
476 mutt_str_copy(authenticators, adata->authenticators, sizeof(authenticators));
477 p = authenticators;
478 while (*p)
479 {
480 if (*p == ' ')
481 *p = ':';
482 p++;
483 }
484 }
485 p = authenticators;
486 while (*p)
487 {
488 *p = mutt_toupper(*p);
489 p++;
490 }
491
492 mutt_debug(LL_DEBUG1, "available methods: %s\n", adata->authenticators);
493 a = authenticators;
494 while (true)
495 {
496 if (!a)
497 {
498 mutt_error(_("No authenticators available"));
499 break;
500 }
501
502 method = a;
503 a = strchr(a, ':');
504 if (a)
505 *a++ = '\0';
506
507 /* check authenticator */
508 if (adata->hasCAPABILITIES)
509 {
510 if (!adata->authenticators)
511 continue;
512 const char *m = mutt_istr_find(adata->authenticators, method);
513 if (!m)
514 continue;
515 if ((m > adata->authenticators) && (*(m - 1) != ' '))
516 continue;
517 m += strlen(method);
518 if ((*m != '\0') && (*m != ' '))
519 continue;
520 }
521 mutt_debug(LL_DEBUG1, "trying method %s\n", method);
522
523 /* AUTHINFO USER authentication */
524 if (mutt_str_equal(method, "USER"))
525 {
526 // L10N: (%s) is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
527 mutt_message(_("Authenticating (%s)..."), method);
528 buf_printf(buf, "AUTHINFO USER %s\r\n", conn->account.user);
529 if ((mutt_socket_send(conn, buf_string(buf)) < 0) ||
531 {
532 break;
533 }
534
535 /* authenticated, password is not required */
536 if (mutt_str_startswith(buf_string(buf), "281"))
537 {
538 buf_pool_release(&buf);
539 return 0;
540 }
541
542 /* username accepted, sending password */
543 if (mutt_str_startswith(buf_string(buf), "381"))
544 {
545 mutt_debug(MUTT_SOCK_LOG_FULL, "%d> AUTHINFO PASS *\n", conn->fd);
546 buf_printf(buf, "AUTHINFO PASS %s\r\n", conn->account.pass);
547 if ((mutt_socket_send_d(conn, buf_string(buf), MUTT_SOCK_LOG_FULL) < 0) ||
549 {
550 break;
551 }
552
553 /* authenticated */
554 if (mutt_str_startswith(buf_string(buf), "281"))
555 {
556 buf_pool_release(&buf);
557 return 0;
558 }
559 }
560
561 /* server doesn't support AUTHINFO USER, trying next method */
562 if (buf_at(buf, 0) == '5')
563 continue;
564 }
565 else
566 {
567#ifdef USE_SASL_CYRUS
568 sasl_conn_t *saslconn = NULL;
569 sasl_interact_t *interaction = NULL;
570 int rc;
571 char inbuf[1024] = { 0 };
572 const char *mech = NULL;
573 const char *client_out = NULL;
574 unsigned int client_len, len;
575
576 if (mutt_sasl_client_new(conn, &saslconn) < 0)
577 {
578 mutt_debug(LL_DEBUG1, "error allocating SASL connection\n");
579 continue;
580 }
581
582 while (true)
583 {
584 rc = sasl_client_start(saslconn, method, &interaction, &client_out,
585 &client_len, &mech);
586 if (rc != SASL_INTERACT)
587 break;
588 mutt_sasl_interact(interaction);
589 }
590 if ((rc != SASL_OK) && (rc != SASL_CONTINUE))
591 {
592 sasl_dispose(&saslconn);
593 mutt_debug(LL_DEBUG1, "error starting SASL authentication exchange\n");
594 continue;
595 }
596
597 // L10N: (%s) is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
598 mutt_message(_("Authenticating (%s)..."), method);
599 buf_printf(buf, "AUTHINFO SASL %s", method);
600
601 /* looping protocol */
602 while ((rc == SASL_CONTINUE) || ((rc == SASL_OK) && client_len))
603 {
604 /* send out client response */
605 if (client_len)
606 {
607 nntp_log_binbuf(client_out, client_len, "SASL", MUTT_SOCK_LOG_FULL);
608 if (!buf_is_empty(buf))
609 buf_addch(buf, ' ');
610 len = buf_len(buf);
611 if (sasl_encode64(client_out, client_len, buf->data + len,
612 buf->dsize - len, &len) != SASL_OK)
613 {
614 mutt_debug(LL_DEBUG1, "error base64-encoding client response\n");
615 break;
616 }
617 }
618
619 buf_addstr(buf, "\r\n");
620 if (buf_find_char(buf, ' '))
621 {
622 mutt_debug(MUTT_SOCK_LOG_CMD, "%d> AUTHINFO SASL %s%s\n", conn->fd,
623 method, client_len ? " sasl_data" : "");
624 }
625 else
626 {
627 mutt_debug(MUTT_SOCK_LOG_CMD, "%d> sasl_data\n", conn->fd);
628 }
629 client_len = 0;
630 if ((mutt_socket_send_d(conn, buf_string(buf), MUTT_SOCK_LOG_FULL) < 0) ||
631 (mutt_socket_readln_d(inbuf, sizeof(inbuf), conn, MUTT_SOCK_LOG_FULL) < 0))
632 {
633 break;
634 }
635 if (!mutt_str_startswith(inbuf, "283 ") && !mutt_str_startswith(inbuf, "383 "))
636 {
637 mutt_debug(MUTT_SOCK_LOG_FULL, "%d< %s\n", conn->fd, inbuf);
638 break;
639 }
640 inbuf[3] = '\0';
641 mutt_debug(MUTT_SOCK_LOG_FULL, "%d< %s sasl_data\n", conn->fd, inbuf);
642
643 if (mutt_str_equal("=", inbuf + 4))
644 len = 0;
645 else if (sasl_decode64(inbuf + 4, strlen(inbuf + 4), buf->data,
646 buf->dsize - 1, &len) != SASL_OK)
647 {
648 mutt_debug(LL_DEBUG1, "error base64-decoding server response\n");
649 break;
650 }
651 else
652 {
653 nntp_log_binbuf(buf_string(buf), len, "SASL", MUTT_SOCK_LOG_FULL);
654 }
655
656 while (true)
657 {
658 rc = sasl_client_step(saslconn, buf_string(buf), len, &interaction,
659 &client_out, &client_len);
660 if (rc != SASL_INTERACT)
661 break;
662 mutt_sasl_interact(interaction);
663 }
664 if (*inbuf != '3')
665 break;
666
667 buf_reset(buf);
668 } /* looping protocol */
669
670 if ((rc == SASL_OK) && (client_len == 0) && (*inbuf == '2'))
671 {
672 mutt_sasl_setup_conn(conn, saslconn);
673 buf_pool_release(&buf);
674 return 0;
675 }
676
677 /* terminate SASL session */
678 sasl_dispose(&saslconn);
679 if (conn->fd < 0)
680 break;
681 if (mutt_str_startswith(inbuf, "383 "))
682 {
683 if ((mutt_socket_send(conn, "*\r\n") < 0) ||
684 (mutt_socket_readln(inbuf, sizeof(inbuf), conn) < 0))
685 {
686 break;
687 }
688 }
689
690 /* server doesn't support AUTHINFO SASL, trying next method */
691 if (*inbuf == '5')
692 continue;
693#else
694 continue;
695#endif /* USE_SASL_CYRUS */
696 }
697
698 // L10N: %s is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
699 mutt_error(_("%s authentication failed"), method);
700 break;
701 }
702 break;
703 }
704
705 /* error */
706 adata->status = NNTP_BYE;
707 conn->account.flags = flags;
708 if (conn->fd < 0)
709 {
710 mutt_error(_("Server closed connection"));
711 }
712 else
713 {
714 mutt_socket_close(conn);
715 }
716
717 buf_pool_release(&buf);
718 return -1;
719}
720
729static int nntp_query(struct NntpMboxData *mdata, char *line, size_t linelen)
730{
731 struct NntpAccountData *adata = mdata->adata;
732 if (adata->status == NNTP_BYE)
733 return -1;
734
735 char buf[1024] = { 0 };
736 int rc = -1;
737
738 while (true)
739 {
740 if (adata->status == NNTP_OK)
741 {
742 int rc_send = 0;
743
744 if (*line)
745 {
746 rc_send = mutt_socket_send(adata->conn, line);
747 }
748 else if (mdata->group)
749 {
750 snprintf(buf, sizeof(buf), "GROUP %s\r\n", mdata->group);
751 rc_send = mutt_socket_send(adata->conn, buf);
752 }
753 if (rc_send >= 0)
754 rc_send = mutt_socket_readln(buf, sizeof(buf), adata->conn);
755 if (rc_send >= 0)
756 break;
757 }
758
759 /* reconnect */
760 while (true)
761 {
762 adata->status = NNTP_NONE;
763 if (nntp_open_connection(adata) == 0)
764 break;
765
766 snprintf(buf, sizeof(buf), _("Connection to %s lost. Reconnect?"),
767 adata->conn->account.host);
768 if (query_yesorno(buf, MUTT_YES) != MUTT_YES)
769 {
770 adata->status = NNTP_BYE;
771 goto done;
772 }
773 }
774
775 /* select newsgroup after reconnection */
776 if (mdata->group)
777 {
778 snprintf(buf, sizeof(buf), "GROUP %s\r\n", mdata->group);
779 if ((mutt_socket_send(adata->conn, buf) < 0) ||
780 (mutt_socket_readln(buf, sizeof(buf), adata->conn) < 0))
781 {
783 goto done;
784 }
785 }
786 if (*line == '\0')
787 break;
788 }
789
790 mutt_str_copy(line, buf, linelen);
791 rc = 0;
792
793done:
794 return rc;
795}
796
813static int nntp_fetch_lines(struct NntpMboxData *mdata, char *query, size_t qlen,
814 const char *msg, int (*func)(char *, void *), void *data)
815{
816 bool done = false;
817 int rc;
818
819 while (!done)
820 {
821 char buf[1024] = { 0 };
822 char *line = NULL;
823 unsigned int lines = 0;
824 size_t off = 0;
825 struct Progress *progress = NULL;
826
827 mutt_str_copy(buf, query, sizeof(buf));
828 if (nntp_query(mdata, buf, sizeof(buf)) < 0)
829 return -1;
830 if (buf[0] != '2')
831 {
832 mutt_str_copy(query, buf, qlen);
833 return 1;
834 }
835
836 line = MUTT_MEM_MALLOC(sizeof(buf), char);
837 rc = 0;
838
839 if (msg)
840 {
841 progress = progress_new(MUTT_PROGRESS_READ, 0);
842 progress_set_message(progress, "%s", msg);
843 }
844
845 while (true)
846 {
847 char *p = NULL;
848 int chunk = mutt_socket_readln_d(buf, sizeof(buf), mdata->adata->conn, MUTT_SOCK_LOG_FULL);
849 if (chunk < 0)
850 {
851 mdata->adata->status = NNTP_NONE;
852 break;
853 }
854
855 p = buf;
856 if (!off && (buf[0] == '.'))
857 {
858 if (buf[1] == '\0')
859 {
860 done = true;
861 break;
862 }
863 if (buf[1] == '.')
864 p++;
865 }
866
867 mutt_str_copy(line + off, p, sizeof(buf));
868
869 if (chunk >= sizeof(buf))
870 {
871 off += strlen(p);
872 }
873 else
874 {
875 progress_update(progress, ++lines, -1);
876
877 if ((rc == 0) && (func(line, data) < 0))
878 rc = -2;
879 off = 0;
880 }
881
882 MUTT_MEM_REALLOC(&line, off + sizeof(buf), char);
883 }
884 FREE(&line);
885 func(NULL, data);
886 progress_free(&progress);
887 }
888
889 return rc;
890}
891
898static int fetch_description(char *line, void *data)
899{
900 if (!line)
901 return 0;
902
903 struct NntpAccountData *adata = data;
904
905 char *desc = strpbrk(line, " \t");
906 if (desc)
907 {
908 *desc++ = '\0';
909 desc += strspn(desc, " \t");
910 }
911 else
912 {
913 desc = strchr(line, '\0');
914 }
915
917 if (mdata && !mutt_str_equal(desc, mdata->desc))
918 {
919 mutt_str_replace(&mdata->desc, desc);
920 mutt_debug(LL_DEBUG2, "group: %s, desc: %s\n", line, desc);
921 }
922 return 0;
923}
924
935static int get_description(struct NntpMboxData *mdata, const char *wildmat, const char *msg)
936{
937 char buf[256] = { 0 };
938 const char *cmd = NULL;
939
940 /* get newsgroup description, if possible */
941 struct NntpAccountData *adata = mdata->adata;
942 if (!wildmat)
943 wildmat = mdata->group;
944 if (adata->hasLIST_NEWSGROUPS)
945 cmd = "LIST NEWSGROUPS";
946 else if (adata->hasXGTITLE)
947 cmd = "XGTITLE";
948 else
949 return 0;
950
951 snprintf(buf, sizeof(buf), "%s %s\r\n", cmd, wildmat);
952 int rc = nntp_fetch_lines(mdata, buf, sizeof(buf), msg, fetch_description, adata);
953 if (rc > 0)
954 {
955 mutt_error("%s: %s", cmd, buf);
956 }
957 return rc;
958}
959
967static void nntp_parse_xref(struct Mailbox *m, struct Email *e)
968{
969 struct NntpMboxData *mdata = m->mdata;
970
971 char *buf = mutt_str_dup(e->env->xref);
972 char *p = buf;
973 while (p)
974 {
975 anum_t anum = 0;
976
977 /* skip to next word */
978 p += strspn(p, " \t");
979 char *grp = p;
980
981 /* skip to end of word */
982 p = strpbrk(p, " \t");
983 if (p)
984 *p++ = '\0';
985
986 /* find colon */
987 char *colon = strchr(grp, ':');
988 if (!colon)
989 continue;
990 *colon++ = '\0';
991 if (sscanf(colon, ANUM_FMT, &anum) != 1)
992 continue;
993
994 nntp_article_status(m, e, grp, anum);
995 if (!nntp_edata_get(e)->article_num && mutt_str_equal(mdata->group, grp))
996 nntp_edata_get(e)->article_num = anum;
997 }
998 FREE(&buf);
999}
1000
1008static int fetch_tempfile(char *line, void *data)
1009{
1010 FILE *fp = data;
1011
1012 if (!line)
1013 rewind(fp);
1014 else if ((fputs(line, fp) == EOF) || (fputc('\n', fp) == EOF))
1015 return -1;
1016 return 0;
1017}
1018
1025static int fetch_numbers(char *line, void *data)
1026{
1027 struct FetchCtx *fc = data;
1028 anum_t anum = 0;
1029
1030 if (!line)
1031 return 0;
1032 if (sscanf(line, ANUM_FMT, &anum) != 1)
1033 return 0;
1034 if ((anum < fc->first) || (anum > fc->last))
1035 return 0;
1036 fc->messages[anum - fc->first] = 1;
1037 return 0;
1038}
1039
1047static int parse_overview_line(char *line, void *data)
1048{
1049 if (!line || !data)
1050 return 0;
1051
1052 struct FetchCtx *fc = data;
1053 struct Mailbox *m = fc->mailbox;
1054 if (!m)
1055 return -1;
1056
1057 struct NntpMboxData *mdata = m->mdata;
1058 struct Email *e = NULL;
1059 char *header = NULL, *field = NULL;
1060 bool save = true;
1061 anum_t anum = 0;
1062
1063 /* parse article number */
1064 field = strchr(line, '\t');
1065 if (field)
1066 *field++ = '\0';
1067 if (sscanf(line, ANUM_FMT, &anum) != 1)
1068 return 0;
1069 mutt_debug(LL_DEBUG2, "" ANUM_FMT "\n", anum);
1070
1071 /* out of bounds */
1072 if ((anum < fc->first) || (anum > fc->last))
1073 return 0;
1074
1075 /* not in LISTGROUP */
1076 if (!fc->messages[anum - fc->first])
1077 {
1078 progress_update(fc->progress, anum - fc->first + 1, -1);
1079 return 0;
1080 }
1081
1082 /* convert overview line to header */
1083 FILE *fp = mutt_file_mkstemp();
1084 if (!fp)
1085 return -1;
1086
1087 header = mdata->adata->overview_fmt;
1088 while (field)
1089 {
1090 char *b = field;
1091
1092 if (*header)
1093 {
1094 if (!strstr(header, ":full") && (fputs(header, fp) == EOF))
1095 {
1096 mutt_file_fclose(&fp);
1097 return -1;
1098 }
1099 header = strchr(header, '\0') + 1;
1100 }
1101
1102 field = strchr(field, '\t');
1103 if (field)
1104 *field++ = '\0';
1105 if ((fputs(b, fp) == EOF) || (fputc('\n', fp) == EOF))
1106 {
1107 mutt_file_fclose(&fp);
1108 return -1;
1109 }
1110 }
1111 rewind(fp);
1112
1113 /* allocate memory for headers */
1115
1116 /* parse header */
1117 m->emails[m->msg_count] = email_new();
1118 e = m->emails[m->msg_count];
1119 e->env = mutt_rfc822_read_header(fp, e, false, false);
1120 e->env->newsgroups = mutt_str_dup(mdata->group);
1121 e->received = e->date_sent;
1122 mutt_file_fclose(&fp);
1123
1124#ifdef USE_HCACHE
1125 if (fc->hc)
1126 {
1127 char buf[16] = { 0 };
1128
1129 /* try to replace with header from cache */
1130 snprintf(buf, sizeof(buf), ANUM_FMT, anum);
1131 struct HCacheEntry hce = hcache_fetch_email(fc->hc, buf, strlen(buf), 0);
1132 if (hce.email)
1133 {
1134 mutt_debug(LL_DEBUG2, "hcache_fetch_email %s\n", buf);
1135 email_free(&e);
1136 e = hce.email;
1137 m->emails[m->msg_count] = e;
1138 e->edata = NULL;
1139 e->read = false;
1140 e->old = false;
1141
1142 /* skip header marked as deleted in cache */
1143 if (e->deleted && !fc->restore)
1144 {
1145 if (mdata->bcache)
1146 {
1147 mutt_debug(LL_DEBUG2, "mutt_bcache_del %s\n", buf);
1148 mutt_bcache_del(mdata->bcache, buf);
1149 }
1150 save = false;
1151 }
1152 }
1153 else
1154 {
1155 /* not cached yet, store header */
1156 mutt_debug(LL_DEBUG2, "hcache_store_email %s\n", buf);
1157 hcache_store_email(fc->hc, buf, strlen(buf), e, 0);
1158 }
1159 }
1160#endif
1161
1162 if (save)
1163 {
1164 e->index = m->msg_count++;
1165 e->read = false;
1166 e->old = false;
1167 e->deleted = false;
1168 e->edata = nntp_edata_new();
1170 nntp_edata_get(e)->article_num = anum;
1171 if (fc->restore)
1172 {
1173 e->changed = true;
1174 }
1175 else
1176 {
1177 nntp_article_status(m, e, NULL, anum);
1178 if (!e->read)
1179 nntp_parse_xref(m, e);
1180 }
1181 if (anum > mdata->last_loaded)
1182 mdata->last_loaded = anum;
1183 }
1184 else
1185 {
1186 email_free(&e);
1187 }
1188
1189 progress_update(fc->progress, anum - fc->first + 1, -1);
1190 return 0;
1191}
1192
1203static int nntp_fetch_headers(struct Mailbox *m, void *hc, anum_t first, anum_t last, bool restore)
1204{
1205 if (!m)
1206 return -1;
1207
1208 struct NntpMboxData *mdata = m->mdata;
1209 struct FetchCtx fc = { 0 };
1210 struct Email *e = NULL;
1211 char buf[8192] = { 0 };
1212 int rc = 0;
1213 anum_t current;
1214 anum_t first_over = first;
1215
1216 /* if empty group or nothing to do */
1217 if (!last || (first > last))
1218 return 0;
1219
1220 /* init fetch context */
1221 fc.mailbox = m;
1222 fc.first = first;
1223 fc.last = last;
1224 fc.restore = restore;
1225 fc.messages = MUTT_MEM_CALLOC(last - first + 1, unsigned char);
1226 if (!fc.messages)
1227 return -1;
1228 fc.hc = hc;
1229
1230 /* fetch list of articles */
1231 const bool c_nntp_listgroup = cs_subset_bool(NeoMutt->sub, "nntp_listgroup");
1232 if (c_nntp_listgroup && mdata->adata->hasLISTGROUP && !mdata->deleted)
1233 {
1234 if (m->verbose)
1235 mutt_message(_("Fetching list of articles..."));
1236 if (mdata->adata->hasLISTGROUPrange)
1237 {
1238 snprintf(buf, sizeof(buf), "LISTGROUP %s " ANUM_FMT "-" ANUM_FMT "\r\n",
1239 mdata->group, first, last);
1240 }
1241 else
1242 {
1243 snprintf(buf, sizeof(buf), "LISTGROUP %s\r\n", mdata->group);
1244 }
1245 rc = nntp_fetch_lines(mdata, buf, sizeof(buf), NULL, fetch_numbers, &fc);
1246 if (rc > 0)
1247 {
1248 mutt_error("LISTGROUP: %s", buf);
1249 }
1250 if (rc == 0)
1251 {
1252 for (current = first; (current <= last); current++)
1253 {
1254 if (fc.messages[current - first])
1255 continue;
1256
1257 snprintf(buf, sizeof(buf), ANUM_FMT, current);
1258 if (mdata->bcache)
1259 {
1260 mutt_debug(LL_DEBUG2, "#1 mutt_bcache_del %s\n", buf);
1261 mutt_bcache_del(mdata->bcache, buf);
1262 }
1263
1264#ifdef USE_HCACHE
1265 if (fc.hc)
1266 {
1267 mutt_debug(LL_DEBUG2, "hcache_delete_email %s\n", buf);
1268 hcache_delete_email(fc.hc, buf, strlen(buf));
1269 }
1270#endif
1271 }
1272 }
1273 }
1274 else
1275 {
1276 for (current = first; current <= last; current++)
1277 fc.messages[current - first] = 1;
1278 }
1279
1280 /* fetching header from cache or server, or fallback to fetch overview */
1281 if (m->verbose)
1282 {
1283 fc.progress = progress_new(MUTT_PROGRESS_READ, last - first + 1);
1284 progress_set_message(fc.progress, _("Fetching message headers..."));
1285 }
1286 for (current = first; (current <= last) && (rc == 0); current++)
1287 {
1288 progress_update(fc.progress, current - first + 1, -1);
1289
1290#ifdef USE_HCACHE
1291 snprintf(buf, sizeof(buf), ANUM_FMT, current);
1292#endif
1293
1294 /* delete header from cache that does not exist on server */
1295 if (!fc.messages[current - first])
1296 continue;
1297
1298 /* allocate memory for headers */
1300
1301#ifdef USE_HCACHE
1302 /* try to fetch header from cache */
1303 struct HCacheEntry hce = hcache_fetch_email(fc.hc, buf, strlen(buf), 0);
1304 if (hce.email)
1305 {
1306 mutt_debug(LL_DEBUG2, "hcache_fetch_email %s\n", buf);
1307 e = hce.email;
1308 m->emails[m->msg_count] = e;
1309 e->edata = NULL;
1310
1311 /* skip header marked as deleted in cache */
1312 if (e->deleted && !restore)
1313 {
1314 email_free(&e);
1315 if (mdata->bcache)
1316 {
1317 mutt_debug(LL_DEBUG2, "#2 mutt_bcache_del %s\n", buf);
1318 mutt_bcache_del(mdata->bcache, buf);
1319 }
1320 continue;
1321 }
1322
1323 e->read = false;
1324 e->old = false;
1325 }
1326 else
1327#endif
1328 if (mdata->deleted)
1329 {
1330 /* don't try to fetch header from removed newsgroup */
1331 continue;
1332 }
1333 else if (mdata->adata->hasOVER || mdata->adata->hasXOVER)
1334 {
1335 /* fallback to fetch overview */
1336 if (c_nntp_listgroup && mdata->adata->hasLISTGROUP)
1337 break;
1338 else
1339 continue;
1340 }
1341 else
1342 {
1343 /* fetch header from server */
1344 FILE *fp = mutt_file_mkstemp();
1345 if (!fp)
1346 {
1347 mutt_perror(_("Can't create temporary file"));
1348 rc = -1;
1349 break;
1350 }
1351
1352 snprintf(buf, sizeof(buf), "HEAD " ANUM_FMT "\r\n", current);
1353 rc = nntp_fetch_lines(mdata, buf, sizeof(buf), NULL, fetch_tempfile, fp);
1354 if (rc)
1355 {
1356 mutt_file_fclose(&fp);
1357 if (rc < 0)
1358 break;
1359
1360 /* invalid response */
1361 if (!mutt_str_startswith(buf, "423"))
1362 {
1363 mutt_error("HEAD: %s", buf);
1364 break;
1365 }
1366
1367 /* no such article */
1368 if (mdata->bcache)
1369 {
1370 snprintf(buf, sizeof(buf), ANUM_FMT, current);
1371 mutt_debug(LL_DEBUG2, "#3 mutt_bcache_del %s\n", buf);
1372 mutt_bcache_del(mdata->bcache, buf);
1373 }
1374 rc = 0;
1375 continue;
1376 }
1377
1378 /* parse header */
1379 m->emails[m->msg_count] = email_new();
1380 e = m->emails[m->msg_count];
1381 e->env = mutt_rfc822_read_header(fp, e, false, false);
1382 e->received = e->date_sent;
1383 mutt_file_fclose(&fp);
1384 }
1385
1386 /* save header in context */
1387 e->index = m->msg_count++;
1388 e->read = false;
1389 e->old = false;
1390 e->deleted = false;
1391 e->edata = nntp_edata_new();
1393 nntp_edata_get(e)->article_num = current;
1394 if (restore)
1395 {
1396 e->changed = true;
1397 }
1398 else
1399 {
1400 nntp_article_status(m, e, NULL, nntp_edata_get(e)->article_num);
1401 if (!e->read)
1402 nntp_parse_xref(m, e);
1403 }
1404 if (current > mdata->last_loaded)
1405 mdata->last_loaded = current;
1406 first_over = current + 1;
1407 }
1408
1409 if (!c_nntp_listgroup || !mdata->adata->hasLISTGROUP)
1410 current = first_over;
1411
1412 /* fetch overview information */
1413 if ((current <= last) && (rc == 0) && !mdata->deleted)
1414 {
1415 char *cmd = mdata->adata->hasOVER ? "OVER" : "XOVER";
1416 snprintf(buf, sizeof(buf), "%s " ANUM_FMT "-" ANUM_FMT "\r\n", cmd, current, last);
1417 rc = nntp_fetch_lines(mdata, buf, sizeof(buf), NULL, parse_overview_line, &fc);
1418 if (rc > 0)
1419 {
1420 mutt_error("%s: %s", cmd, buf);
1421 }
1422 }
1423
1424 FREE(&fc.messages);
1426 if (rc != 0)
1427 return -1;
1429 return 0;
1430}
1431
1440static int nntp_group_poll(struct NntpMboxData *mdata, bool update_stat)
1441{
1442 char buf[1024] = { 0 };
1443 anum_t count = 0, first = 0, last = 0;
1444
1445 /* use GROUP command to poll newsgroup */
1446 if (nntp_query(mdata, buf, sizeof(buf)) < 0)
1447 return -1;
1448 if (sscanf(buf, "211 " ANUM_FMT " " ANUM_FMT " " ANUM_FMT, &count, &first, &last) != 3)
1449 return 0;
1450 if ((first == mdata->first_message) && (last == mdata->last_message))
1451 return 0;
1452
1453 /* articles have been renumbered */
1454 if (last < mdata->last_message)
1455 {
1456 mdata->last_cached = 0;
1457 if (mdata->newsrc_len)
1458 {
1459 MUTT_MEM_REALLOC(&mdata->newsrc_ent, 1, struct NewsrcEntry);
1460 mdata->newsrc_len = 1;
1461 mdata->newsrc_ent[0].first = 1;
1462 mdata->newsrc_ent[0].last = 0;
1463 }
1464 }
1465 mdata->first_message = first;
1466 mdata->last_message = last;
1467 if (!update_stat)
1468 {
1469 return 1;
1470 }
1471 else if (!last || (!mdata->newsrc_ent && !mdata->last_cached))
1472 {
1473 /* update counters */
1474 mdata->unread = count;
1475 }
1476 else
1477 {
1479 }
1480 return 1;
1481}
1482
1490static enum MxStatus check_mailbox(struct Mailbox *m)
1491{
1492 if (!m || !m->mdata)
1493 return MX_STATUS_ERROR;
1494
1495 struct NntpMboxData *mdata = m->mdata;
1496 struct NntpAccountData *adata = mdata->adata;
1497 time_t now = mutt_date_now();
1498 enum MxStatus rc = MX_STATUS_OK;
1499 struct HeaderCache *hc = NULL;
1500
1501 const short c_nntp_poll = cs_subset_number(NeoMutt->sub, "nntp_poll");
1502 if (adata->check_time + c_nntp_poll > now)
1503 return MX_STATUS_OK;
1504
1505 mutt_message(_("Checking for new messages..."));
1506 if (nntp_newsrc_parse(adata) < 0)
1507 return MX_STATUS_ERROR;
1508
1509 adata->check_time = now;
1510 int rc2 = nntp_group_poll(mdata, false);
1511 if (rc2 < 0)
1512 {
1513 nntp_newsrc_close(adata);
1514 return -1;
1515 }
1516 if (rc2 != 0)
1518
1519 /* articles have been renumbered, remove all emails */
1520 if (mdata->last_message < mdata->last_loaded)
1521 {
1522 for (int i = 0; i < m->msg_count; i++)
1523 email_free(&m->emails[i]);
1524 m->msg_count = 0;
1525 m->msg_tagged = 0;
1526
1527 mdata->last_loaded = mdata->first_message - 1;
1528 const long c_nntp_context = cs_subset_long(NeoMutt->sub, "nntp_context");
1529 if (c_nntp_context && (mdata->last_message - mdata->last_loaded > c_nntp_context))
1530 mdata->last_loaded = mdata->last_message - c_nntp_context;
1531
1532 rc = MX_STATUS_REOPENED;
1533 }
1534
1535 /* .newsrc has been externally modified */
1536 if (adata->newsrc_modified)
1537 {
1538#ifdef USE_HCACHE
1539 unsigned char *messages = NULL;
1540 char buf[16] = { 0 };
1541 struct Email *e = NULL;
1542 anum_t first = mdata->first_message;
1543
1544 const long c_nntp_context = cs_subset_long(NeoMutt->sub, "nntp_context");
1545 if (c_nntp_context && ((mdata->last_message - first + 1) > c_nntp_context))
1546 first = mdata->last_message - c_nntp_context + 1;
1547 messages = MUTT_MEM_CALLOC(mdata->last_loaded - first + 1, unsigned char);
1548 hc = nntp_hcache_open(mdata);
1549 nntp_hcache_update(mdata, hc);
1550#endif
1551
1552 /* update flags according to .newsrc */
1553 int j = 0;
1554 for (int i = 0; i < m->msg_count; i++)
1555 {
1556 if (!m->emails[i])
1557 continue;
1558 bool flagged = false;
1559 anum_t anum = nntp_edata_get(m->emails[i])->article_num;
1560
1561#ifdef USE_HCACHE
1562 /* check hcache for flagged and deleted flags */
1563 if (hc)
1564 {
1565 if ((anum >= first) && (anum <= mdata->last_loaded))
1566 messages[anum - first] = 1;
1567
1568 snprintf(buf, sizeof(buf), ANUM_FMT, anum);
1569 struct HCacheEntry hce = hcache_fetch_email(hc, buf, strlen(buf), 0);
1570 if (hce.email)
1571 {
1572 bool deleted;
1573
1574 mutt_debug(LL_DEBUG2, "#1 hcache_fetch_email %s\n", buf);
1575 e = hce.email;
1576 e->edata = NULL;
1577 deleted = e->deleted;
1578 flagged = e->flagged;
1579 email_free(&e);
1580
1581 /* header marked as deleted, removing from context */
1582 if (deleted)
1583 {
1584 mutt_set_flag(m, m->emails[i], MUTT_TAG, false, true);
1585 email_free(&m->emails[i]);
1586 continue;
1587 }
1588 }
1589 }
1590#endif
1591
1592 if (!m->emails[i]->changed)
1593 {
1594 m->emails[i]->flagged = flagged;
1595 m->emails[i]->read = false;
1596 m->emails[i]->old = false;
1597 nntp_article_status(m, m->emails[i], NULL, anum);
1598 if (!m->emails[i]->read)
1599 nntp_parse_xref(m, m->emails[i]);
1600 }
1601 m->emails[j++] = m->emails[i];
1602 }
1603
1604#ifdef USE_HCACHE
1605 m->msg_count = j;
1606
1607 /* restore headers without "deleted" flag */
1608 for (anum_t anum = first; anum <= mdata->last_loaded; anum++)
1609 {
1610 if (messages[anum - first])
1611 continue;
1612
1613 snprintf(buf, sizeof(buf), ANUM_FMT, anum);
1614 struct HCacheEntry hce = hcache_fetch_email(hc, buf, strlen(buf), 0);
1615 if (hce.email)
1616 {
1617 mutt_debug(LL_DEBUG2, "#2 hcache_fetch_email %s\n", buf);
1619
1620 e = hce.email;
1621 m->emails[m->msg_count] = e;
1622 e->edata = NULL;
1623 if (e->deleted)
1624 {
1625 email_free(&e);
1626 if (mdata->bcache)
1627 {
1628 mutt_debug(LL_DEBUG2, "mutt_bcache_del %s\n", buf);
1629 mutt_bcache_del(mdata->bcache, buf);
1630 }
1631 continue;
1632 }
1633
1634 m->msg_count++;
1635 e->read = false;
1636 e->old = false;
1637 e->edata = nntp_edata_new();
1639 nntp_edata_get(e)->article_num = anum;
1640 nntp_article_status(m, e, NULL, anum);
1641 if (!e->read)
1642 nntp_parse_xref(m, e);
1643 }
1644 }
1645 FREE(&messages);
1646#endif
1647
1648 adata->newsrc_modified = false;
1649 rc = MX_STATUS_REOPENED;
1650 }
1651
1652 /* some emails were removed, mailboxview must be updated */
1653 if (rc == MX_STATUS_REOPENED)
1655
1656 /* fetch headers of new articles */
1657 if (mdata->last_message > mdata->last_loaded)
1658 {
1659 int oldmsgcount = m->msg_count;
1660 bool verbose = m->verbose;
1661 m->verbose = false;
1662#ifdef USE_HCACHE
1663 if (!hc)
1664 {
1665 hc = nntp_hcache_open(mdata);
1666 nntp_hcache_update(mdata, hc);
1667 }
1668#endif
1669 int old_msg_count = m->msg_count;
1670 rc2 = nntp_fetch_headers(m, hc, mdata->last_loaded + 1, mdata->last_message, false);
1671 m->verbose = verbose;
1672 if (rc2 == 0)
1673 {
1674 if (m->msg_count > old_msg_count)
1676 mdata->last_loaded = mdata->last_message;
1677 }
1678 if ((rc == MX_STATUS_OK) && (m->msg_count > oldmsgcount))
1679 rc = MX_STATUS_NEW_MAIL;
1680 }
1681
1682#ifdef USE_HCACHE
1683 hcache_close(&hc);
1684#endif
1685 if (rc != MX_STATUS_OK)
1686 nntp_newsrc_close(adata);
1688 return rc;
1689}
1690
1698static int nntp_date(struct NntpAccountData *adata, time_t *now)
1699{
1700 if (adata->hasDATE)
1701 {
1702 struct NntpMboxData mdata = { 0 };
1703 char buf[1024] = { 0 };
1704 struct tm tm = { 0 };
1705
1706 mdata.adata = adata;
1707 mdata.group = NULL;
1708 mutt_str_copy(buf, "DATE\r\n", sizeof(buf));
1709 if (nntp_query(&mdata, buf, sizeof(buf)) < 0)
1710 return -1;
1711
1712 if (sscanf(buf, "111 %4d%2d%2d%2d%2d%2d%*s", &tm.tm_year, &tm.tm_mon,
1713 &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) == 6)
1714 {
1715 tm.tm_year -= 1900;
1716 tm.tm_mon--;
1717 *now = timegm(&tm);
1718 if (*now >= 0)
1719 {
1720 mutt_debug(LL_DEBUG1, "server time is %llu\n", (unsigned long long) *now);
1721 return 0;
1722 }
1723 }
1724 }
1725 *now = mutt_date_now();
1726 return 0;
1727}
1728
1735static int fetch_children(char *line, void *data)
1736{
1737 struct ChildCtx *cc = data;
1738 anum_t anum = 0;
1739
1740 if (!line || (sscanf(line, ANUM_FMT, &anum) != 1))
1741 return 0;
1742 for (unsigned int i = 0; i < cc->mailbox->msg_count; i++)
1743 {
1744 struct Email *e = cc->mailbox->emails[i];
1745 if (!e)
1746 break;
1747 if (nntp_edata_get(e)->article_num == anum)
1748 return 0;
1749 }
1750 if (cc->num >= cc->max)
1751 {
1752 cc->max *= 2;
1753 MUTT_MEM_REALLOC(&cc->child, cc->max, anum_t);
1754 }
1755 cc->child[cc->num++] = anum;
1756 return 0;
1757}
1758
1766{
1767 if (adata->status == NNTP_OK)
1768 return 0;
1769 if (adata->status == NNTP_BYE)
1770 return -1;
1771 adata->status = NNTP_NONE;
1772
1773 struct Connection *conn = adata->conn;
1774 if (mutt_socket_open(conn) < 0)
1775 return -1;
1776
1777 char buf[256] = { 0 };
1778 int cap;
1779 bool posting = false, auth = true;
1780 int rc = -1;
1781
1782 if (mutt_socket_readln(buf, sizeof(buf), conn) < 0)
1783 {
1784 nntp_connect_error(adata);
1785 goto done;
1786 }
1787
1788 if (mutt_str_startswith(buf, "200"))
1789 {
1790 posting = true;
1791 }
1792 else if (!mutt_str_startswith(buf, "201"))
1793 {
1794 mutt_socket_close(conn);
1796 mutt_error("%s", buf);
1797 goto done;
1798 }
1799
1800 /* get initial capabilities */
1801 cap = nntp_capabilities(adata);
1802 if (cap < 0)
1803 goto done;
1804
1805 /* tell news server to switch to mode reader if it isn't so */
1806 if (cap > 0)
1807 {
1808 if ((mutt_socket_send(conn, "MODE READER\r\n") < 0) ||
1809 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
1810 {
1811 nntp_connect_error(adata);
1812 goto done;
1813 }
1814
1815 if (mutt_str_startswith(buf, "200"))
1816 {
1817 posting = true;
1818 }
1819 else if (mutt_str_startswith(buf, "201"))
1820 {
1821 posting = false;
1822 }
1823 else if (adata->hasCAPABILITIES)
1824 {
1825 /* error if has capabilities, ignore result if no capabilities */
1826 mutt_socket_close(conn);
1827 mutt_error(_("Could not switch to reader mode"));
1828 goto done;
1829 }
1830
1831 /* recheck capabilities after MODE READER */
1832 if (adata->hasCAPABILITIES)
1833 {
1834 cap = nntp_capabilities(adata);
1835 if (cap < 0)
1836 goto done;
1837 }
1838 }
1839
1840 mutt_message(_("Connected to %s. %s"), conn->account.host,
1841 posting ? _("Posting is ok") : _("Posting is NOT ok"));
1842 mutt_sleep(1);
1843
1844#ifdef USE_SSL
1845 /* Attempt STARTTLS if available and desired. */
1846 const bool c_ssl_force_tls = cs_subset_bool(NeoMutt->sub, "ssl_force_tls");
1847 if ((adata->use_tls != 1) && (adata->hasSTARTTLS || c_ssl_force_tls))
1848 {
1849 if (adata->use_tls == 0)
1850 {
1851 adata->use_tls = c_ssl_force_tls ||
1852 (query_quadoption(_("Secure connection with TLS?"),
1853 NeoMutt->sub, "ssl_starttls") == MUTT_YES) ?
1854 2 :
1855 1;
1856 }
1857 if (adata->use_tls == 2)
1858 {
1859 if ((mutt_socket_send(conn, "STARTTLS\r\n") < 0) ||
1860 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
1861 {
1862 nntp_connect_error(adata);
1863 goto done;
1864 }
1865 // Clear any data after the STARTTLS acknowledgement
1866 mutt_socket_empty(conn);
1867 if (!mutt_str_startswith(buf, "382"))
1868 {
1869 adata->use_tls = 0;
1870 mutt_error("STARTTLS: %s", buf);
1871 }
1872 else if (mutt_ssl_starttls(conn))
1873 {
1874 adata->use_tls = 0;
1875 adata->status = NNTP_NONE;
1876 mutt_socket_close(adata->conn);
1877 mutt_error(_("Could not negotiate TLS connection"));
1878 goto done;
1879 }
1880 else
1881 {
1882 /* recheck capabilities after STARTTLS */
1883 cap = nntp_capabilities(adata);
1884 if (cap < 0)
1885 goto done;
1886 }
1887 }
1888 }
1889#endif
1890
1891 /* authentication required? */
1892 if (conn->account.flags & MUTT_ACCT_USER)
1893 {
1894 if (!conn->account.user[0])
1895 auth = false;
1896 }
1897 else
1898 {
1899 if ((mutt_socket_send(conn, "STAT\r\n") < 0) ||
1900 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
1901 {
1902 nntp_connect_error(adata);
1903 goto done;
1904 }
1905 if (!mutt_str_startswith(buf, "480"))
1906 auth = false;
1907 }
1908
1909 /* authenticate */
1910 if (auth && (nntp_auth(adata) < 0))
1911 goto done;
1912
1913 /* get final capabilities after authentication */
1914 if (adata->hasCAPABILITIES && (auth || (cap > 0)))
1915 {
1916 cap = nntp_capabilities(adata);
1917 if (cap < 0)
1918 goto done;
1919 if (cap > 0)
1920 {
1921 mutt_socket_close(conn);
1922 mutt_error(_("Could not switch to reader mode"));
1923 goto done;
1924 }
1925 }
1926
1927 /* attempt features */
1928 if (nntp_attempt_features(adata) < 0)
1929 goto done;
1930
1931 rc = 0;
1932 adata->status = NNTP_OK;
1933
1934done:
1935 return rc;
1936}
1937
1945int nntp_post(struct Mailbox *m, const char *msg)
1946{
1947 struct NntpMboxData *mdata = NULL;
1948 struct NntpMboxData tmp_mdata = { 0 };
1949 char buf[1024] = { 0 };
1950 int rc = -1;
1951
1952 if (m && (m->type == MUTT_NNTP))
1953 {
1954 mdata = m->mdata;
1955 }
1956 else
1957 {
1958 const char *const c_news_server = cs_subset_string(NeoMutt->sub, "news_server");
1959 CurrentNewsSrv = nntp_select_server(m, c_news_server, false);
1960 if (!CurrentNewsSrv)
1961 goto done;
1962
1963 mdata = &tmp_mdata;
1964 mdata->adata = CurrentNewsSrv;
1965 mdata->group = NULL;
1966 }
1967
1968 FILE *fp = mutt_file_fopen(msg, "r");
1969 if (!fp)
1970 {
1971 mutt_perror("%s", msg);
1972 goto done;
1973 }
1974
1975 mutt_str_copy(buf, "POST\r\n", sizeof(buf));
1976 if (nntp_query(mdata, buf, sizeof(buf)) < 0)
1977 {
1978 mutt_file_fclose(&fp);
1979 goto done;
1980 }
1981 if (buf[0] != '3')
1982 {
1983 mutt_error(_("Can't post article: %s"), buf);
1984 mutt_file_fclose(&fp);
1985 goto done;
1986 }
1987
1988 buf[0] = '.';
1989 buf[1] = '\0';
1990 while (fgets(buf + 1, sizeof(buf) - 2, fp))
1991 {
1992 size_t len = strlen(buf);
1993 if (buf[len - 1] == '\n')
1994 {
1995 buf[len - 1] = '\r';
1996 buf[len] = '\n';
1997 len++;
1998 buf[len] = '\0';
1999 }
2000 if (mutt_socket_send_d(mdata->adata->conn, (buf[1] == '.') ? buf : buf + 1,
2001 MUTT_SOCK_LOG_FULL) < 0)
2002 {
2003 mutt_file_fclose(&fp);
2004 nntp_connect_error(mdata->adata);
2005 goto done;
2006 }
2007 }
2008 mutt_file_fclose(&fp);
2009
2010 if (((buf[strlen(buf) - 1] != '\n') &&
2011 (mutt_socket_send_d(mdata->adata->conn, "\r\n", MUTT_SOCK_LOG_FULL) < 0)) ||
2012 (mutt_socket_send_d(mdata->adata->conn, ".\r\n", MUTT_SOCK_LOG_FULL) < 0) ||
2013 (mutt_socket_readln(buf, sizeof(buf), mdata->adata->conn) < 0))
2014 {
2015 nntp_connect_error(mdata->adata);
2016 goto done;
2017 }
2018 if (buf[0] != '2')
2019 {
2020 mutt_error(_("Can't post article: %s"), buf);
2021 goto done;
2022 }
2023 rc = 0;
2024
2025done:
2026 return rc;
2027}
2028
2036int nntp_active_fetch(struct NntpAccountData *adata, bool mark_new)
2037{
2038 struct NntpMboxData tmp_mdata = { 0 };
2039 char msg[256] = { 0 };
2040 char buf[1024] = { 0 };
2041 unsigned int i;
2042 int rc;
2043
2044 snprintf(msg, sizeof(msg), _("Loading list of groups from server %s..."),
2046 mutt_message("%s", msg);
2047 if (nntp_date(adata, &adata->newgroups_time) < 0)
2048 return -1;
2049
2050 tmp_mdata.adata = adata;
2051 tmp_mdata.group = NULL;
2052 i = adata->groups_num;
2053 mutt_str_copy(buf, "LIST\r\n", sizeof(buf));
2054 rc = nntp_fetch_lines(&tmp_mdata, buf, sizeof(buf), msg, nntp_add_group, adata);
2055 if (rc)
2056 {
2057 if (rc > 0)
2058 {
2059 mutt_error("LIST: %s", buf);
2060 }
2061 return -1;
2062 }
2063
2064 if (mark_new)
2065 {
2066 for (; i < adata->groups_num; i++)
2067 {
2068 struct NntpMboxData *mdata = adata->groups_list[i];
2069 mdata->has_new_mail = true;
2070 }
2071 }
2072
2073 for (i = 0; i < adata->groups_num; i++)
2074 {
2075 struct NntpMboxData *mdata = adata->groups_list[i];
2076
2077 if (mdata && mdata->deleted && !mdata->newsrc_ent)
2078 {
2080 mutt_hash_delete(adata->groups_hash, mdata->group, NULL);
2081 adata->groups_list[i] = NULL;
2082 }
2083 }
2084
2085 const bool c_nntp_load_description = cs_subset_bool(NeoMutt->sub, "nntp_load_description");
2086 if (c_nntp_load_description)
2087 rc = get_description(&tmp_mdata, "*", _("Loading descriptions..."));
2088
2090 if (rc < 0)
2091 return -1;
2093 return 0;
2094}
2095
2105{
2106 struct NntpMboxData tmp_mdata = { 0 };
2107 time_t now = 0;
2108 char buf[1024] = { 0 };
2109 char *msg = _("Checking for new newsgroups...");
2110 unsigned int i;
2111 int rc, update_active = false;
2112
2113 if (!adata || !adata->newgroups_time)
2114 return -1;
2115
2116 /* check subscribed newsgroups for new articles */
2117 const bool c_show_new_news = cs_subset_bool(NeoMutt->sub, "show_new_news");
2118 if (c_show_new_news)
2119 {
2120 mutt_message(_("Checking for new messages..."));
2121 for (i = 0; i < adata->groups_num; i++)
2122 {
2123 struct NntpMboxData *mdata = adata->groups_list[i];
2124
2125 if (mdata && mdata->subscribed)
2126 {
2127 rc = nntp_group_poll(mdata, true);
2128 if (rc < 0)
2129 return -1;
2130 if (rc > 0)
2131 update_active = true;
2132 }
2133 }
2134 }
2135 else if (adata->newgroups_time)
2136 {
2137 return 0;
2138 }
2139
2140 /* get list of new groups */
2141 mutt_message("%s", msg);
2142 if (nntp_date(adata, &now) < 0)
2143 return -1;
2144 tmp_mdata.adata = adata;
2145 if (m && m->mdata)
2146 tmp_mdata.group = ((struct NntpMboxData *) m->mdata)->group;
2147 else
2148 tmp_mdata.group = NULL;
2149 i = adata->groups_num;
2150 struct tm tm = mutt_date_gmtime(adata->newgroups_time);
2151 snprintf(buf, sizeof(buf), "NEWGROUPS %02d%02d%02d %02d%02d%02d GMT\r\n",
2152 tm.tm_year % 100, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
2153 rc = nntp_fetch_lines(&tmp_mdata, buf, sizeof(buf), msg, nntp_add_group, adata);
2154 if (rc)
2155 {
2156 if (rc > 0)
2157 {
2158 mutt_error("NEWGROUPS: %s", buf);
2159 }
2160 return -1;
2161 }
2162
2163 /* new groups found */
2164 rc = 0;
2165 if (adata->groups_num != i)
2166 {
2167 int groups_num = i;
2168
2169 adata->newgroups_time = now;
2170 for (; i < adata->groups_num; i++)
2171 {
2172 struct NntpMboxData *mdata = adata->groups_list[i];
2173 mdata->has_new_mail = true;
2174 }
2175
2176 /* loading descriptions */
2177 const bool c_nntp_load_description = cs_subset_bool(NeoMutt->sub, "nntp_load_description");
2178 if (c_nntp_load_description)
2179 {
2180 unsigned int count = 0;
2181 struct Progress *progress = progress_new(MUTT_PROGRESS_READ, adata->groups_num - i);
2182 progress_set_message(progress, _("Loading descriptions..."));
2183
2184 for (i = groups_num; i < adata->groups_num; i++)
2185 {
2186 struct NntpMboxData *mdata = adata->groups_list[i];
2187
2188 if (get_description(mdata, NULL, NULL) < 0)
2189 {
2190 progress_free(&progress);
2191 return -1;
2192 }
2193 progress_update(progress, ++count, -1);
2194 }
2195 progress_free(&progress);
2196 }
2197 update_active = true;
2198 rc = 1;
2199 }
2200 if (update_active)
2203 return rc;
2204}
2205
2214int nntp_check_msgid(struct Mailbox *m, const char *msgid)
2215{
2216 if (!m)
2217 return -1;
2218
2219 struct NntpMboxData *mdata = m->mdata;
2220 char buf[1024] = { 0 };
2221
2222 FILE *fp = mutt_file_mkstemp();
2223 if (!fp)
2224 {
2225 mutt_perror(_("Can't create temporary file"));
2226 return -1;
2227 }
2228
2229 snprintf(buf, sizeof(buf), "HEAD %s\r\n", msgid);
2230 int rc = nntp_fetch_lines(mdata, buf, sizeof(buf), NULL, fetch_tempfile, fp);
2231 if (rc)
2232 {
2233 mutt_file_fclose(&fp);
2234 if (rc < 0)
2235 return -1;
2236 if (mutt_str_startswith(buf, "430"))
2237 return 1;
2238 mutt_error("HEAD: %s", buf);
2239 return -1;
2240 }
2241
2242 /* parse header */
2244 m->emails[m->msg_count] = email_new();
2245 struct Email *e = m->emails[m->msg_count];
2246 e->edata = nntp_edata_new();
2248 e->env = mutt_rfc822_read_header(fp, e, false, false);
2249 mutt_file_fclose(&fp);
2250
2251 /* get article number */
2252 if (e->env->xref)
2253 {
2254 nntp_parse_xref(m, e);
2255 }
2256 else
2257 {
2258 snprintf(buf, sizeof(buf), "STAT %s\r\n", msgid);
2259 if (nntp_query(mdata, buf, sizeof(buf)) < 0)
2260 {
2261 email_free(&e);
2262 return -1;
2263 }
2264 sscanf(buf + 4, ANUM_FMT, &nntp_edata_get(e)->article_num);
2265 }
2266
2267 /* reset flags */
2268 e->read = false;
2269 e->old = false;
2270 e->deleted = false;
2271 e->changed = true;
2272 e->received = e->date_sent;
2273 e->index = m->msg_count++;
2275 return 0;
2276}
2277
2285int nntp_check_children(struct Mailbox *m, const char *msgid)
2286{
2287 if (!m)
2288 return -1;
2289
2290 struct NntpMboxData *mdata = m->mdata;
2291 char buf[256] = { 0 };
2292 int rc;
2293 struct HeaderCache *hc = NULL;
2294
2295 if (!mdata || !mdata->adata)
2296 return -1;
2297 if (mdata->first_message > mdata->last_loaded)
2298 return 0;
2299
2300 /* init context */
2301 struct ChildCtx cc = { 0 };
2302 cc.mailbox = m;
2303 cc.num = 0;
2304 cc.max = 10;
2305 cc.child = MUTT_MEM_MALLOC(cc.max, anum_t);
2306
2307 /* fetch numbers of child messages */
2308 snprintf(buf, sizeof(buf), "XPAT References " ANUM_FMT "-" ANUM_FMT " *%s*\r\n",
2309 mdata->first_message, mdata->last_loaded, msgid);
2310 rc = nntp_fetch_lines(mdata, buf, sizeof(buf), NULL, fetch_children, &cc);
2311 if (rc)
2312 {
2313 FREE(&cc.child);
2314 if (rc > 0)
2315 {
2316 if (!mutt_str_startswith(buf, "500"))
2317 {
2318 mutt_error("XPAT: %s", buf);
2319 }
2320 else
2321 {
2322 mutt_error(_("Unable to find child articles because server does not support XPAT command"));
2323 }
2324 }
2325 return -1;
2326 }
2327
2328 /* fetch all found messages */
2329 bool verbose = m->verbose;
2330 m->verbose = false;
2331#ifdef USE_HCACHE
2332 hc = nntp_hcache_open(mdata);
2333#endif
2334 int old_msg_count = m->msg_count;
2335 for (int i = 0; i < cc.num; i++)
2336 {
2337 rc = nntp_fetch_headers(m, hc, cc.child[i], cc.child[i], true);
2338 if (rc < 0)
2339 break;
2340 }
2341 if (m->msg_count > old_msg_count)
2343
2344#ifdef USE_HCACHE
2345 hcache_close(&hc);
2346#endif
2347 m->verbose = verbose;
2348 FREE(&cc.child);
2349 return (rc < 0) ? -1 : 0;
2350}
2351
2355int nntp_sort_unsorted(const struct Email *a, const struct Email *b, bool reverse)
2356{
2357 anum_t na = nntp_edata_get((struct Email *) a)->article_num;
2358 anum_t nb = nntp_edata_get((struct Email *) b)->article_num;
2359 int result = (na == nb) ? 0 : (na > nb) ? 1 : -1;
2360 return reverse ? -result : result;
2361}
2362
2366static bool nntp_ac_owns_path(struct Account *a, const char *path)
2367{
2368 return true;
2369}
2370
2374static bool nntp_ac_add(struct Account *a, struct Mailbox *m)
2375{
2376 return true;
2377}
2378
2383{
2384 if (!m->account)
2385 return MX_OPEN_ERROR;
2386
2387 char buf[8192] = { 0 };
2388 char server[1024] = { 0 };
2389 char *group = NULL;
2390 int rc;
2391 struct HeaderCache *hc = NULL;
2392 anum_t first = 0, last = 0, count = 0;
2393
2394 struct Url *url = url_parse(mailbox_path(m));
2395 if (!url || !url->host || !url->path ||
2396 !((url->scheme == U_NNTP) || (url->scheme == U_NNTPS)))
2397 {
2398 url_free(&url);
2399 mutt_error(_("%s is an invalid newsgroup specification"), mailbox_path(m));
2400 return MX_OPEN_ERROR;
2401 }
2402
2403 group = url->path;
2404 if (group[0] == '/') /* Skip a leading '/' */
2405 group++;
2406
2407 url->path = strchr(url->path, '\0');
2408 url_tostring(url, server, sizeof(server), U_NO_FLAGS);
2409
2411 struct NntpAccountData *adata = m->account->adata;
2412 if (!adata)
2414 if (!adata)
2415 {
2416 adata = nntp_select_server(m, server, true);
2417 m->account->adata = adata;
2419 }
2420
2421 if (!adata)
2422 {
2423 url_free(&url);
2424 return MX_OPEN_ERROR;
2425 }
2427
2428 m->msg_count = 0;
2429 m->msg_unread = 0;
2430 m->vcount = 0;
2431
2432 if (group[0] == '/')
2433 group++;
2434
2435 /* find news group data structure */
2437 if (!mdata)
2438 {
2440 mutt_error(_("Newsgroup %s not found on the server"), group);
2441 url_free(&url);
2442 return MX_OPEN_ERROR;
2443 }
2444
2445 m->rights &= ~MUTT_ACL_INSERT; // Clear the flag
2446 const bool c_save_unsubscribed = cs_subset_bool(NeoMutt->sub, "save_unsubscribed");
2447 if (!mdata->newsrc_ent && !mdata->subscribed && !c_save_unsubscribed)
2448 m->readonly = true;
2449
2450 /* select newsgroup */
2451 mutt_message(_("Selecting %s..."), group);
2452 url_free(&url);
2453 buf[0] = '\0';
2454 if (nntp_query(mdata, buf, sizeof(buf)) < 0)
2455 {
2457 return MX_OPEN_ERROR;
2458 }
2459
2460 /* newsgroup not found, remove it */
2461 if (mutt_str_startswith(buf, "411"))
2462 {
2463 mutt_error(_("Newsgroup %s has been removed from the server"), mdata->group);
2464 if (!mdata->deleted)
2465 {
2466 mdata->deleted = true;
2468 }
2469 if (mdata->newsrc_ent && !mdata->subscribed && !c_save_unsubscribed)
2470 {
2471 FREE(&mdata->newsrc_ent);
2472 mdata->newsrc_len = 0;
2475 }
2476 }
2477 else
2478 {
2479 /* parse newsgroup info */
2480 if (sscanf(buf, "211 " ANUM_FMT " " ANUM_FMT " " ANUM_FMT, &count, &first, &last) != 3)
2481 {
2483 mutt_error("GROUP: %s", buf);
2484 return MX_OPEN_ERROR;
2485 }
2486 mdata->first_message = first;
2487 mdata->last_message = last;
2488 mdata->deleted = false;
2489
2490 /* get description if empty */
2491 const bool c_nntp_load_description = cs_subset_bool(NeoMutt->sub, "nntp_load_description");
2492 if (c_nntp_load_description && !mdata->desc)
2493 {
2494 if (get_description(mdata, NULL, NULL) < 0)
2495 {
2497 return MX_OPEN_ERROR;
2498 }
2499 if (mdata->desc)
2501 }
2502 }
2503
2505 m->mdata = mdata;
2506 // Every known newsgroup has an mdata which is stored in adata->groups_list.
2507 // Currently we don't let the Mailbox free the mdata.
2508 // m->mdata_free = nntp_mdata_free;
2509 if (!mdata->bcache && (mdata->newsrc_ent || mdata->subscribed || c_save_unsubscribed))
2510 mdata->bcache = mutt_bcache_open(&adata->conn->account, mdata->group);
2511
2512 /* strip off extra articles if adding context is greater than $nntp_context */
2513 first = mdata->first_message;
2514 const long c_nntp_context = cs_subset_long(NeoMutt->sub, "nntp_context");
2515 if (c_nntp_context && ((mdata->last_message - first + 1) > c_nntp_context))
2516 first = mdata->last_message - c_nntp_context + 1;
2517 mdata->last_loaded = first ? first - 1 : 0;
2518 count = mdata->first_message;
2519 mdata->first_message = first;
2521 mdata->first_message = count;
2522#ifdef USE_HCACHE
2523 hc = nntp_hcache_open(mdata);
2525#endif
2526 if (!hc)
2527 m->rights &= ~(MUTT_ACL_WRITE | MUTT_ACL_DELETE); // Clear the flags
2528
2530 rc = nntp_fetch_headers(m, hc, first, mdata->last_message, false);
2531#ifdef USE_HCACHE
2532 hcache_close(&hc);
2533#endif
2534 if (rc < 0)
2535 return MX_OPEN_ERROR;
2536 mdata->last_loaded = mdata->last_message;
2537 adata->newsrc_modified = false;
2538 return MX_OPEN_OK;
2539}
2540
2546static enum MxStatus nntp_mbox_check(struct Mailbox *m)
2547{
2548 enum MxStatus rc = check_mailbox(m);
2549 if (rc == MX_STATUS_OK)
2550 {
2551 struct NntpMboxData *mdata = m->mdata;
2552 struct NntpAccountData *adata = mdata->adata;
2554 }
2555 return rc;
2556}
2557
2563static enum MxStatus nntp_mbox_sync(struct Mailbox *m)
2564{
2565 struct NntpMboxData *mdata = m->mdata;
2566
2567 /* check for new articles */
2568 mdata->adata->check_time = 0;
2569 enum MxStatus check = check_mailbox(m);
2570 if (check != MX_STATUS_OK)
2571 return check;
2572
2573#ifdef USE_HCACHE
2574 mdata->last_cached = 0;
2575 struct HeaderCache *hc = nntp_hcache_open(mdata);
2576#endif
2577
2578 for (int i = 0; i < m->msg_count; i++)
2579 {
2580 struct Email *e = m->emails[i];
2581 if (!e)
2582 break;
2583
2584 char buf[16] = { 0 };
2585
2586 snprintf(buf, sizeof(buf), ANUM_FMT, nntp_edata_get(e)->article_num);
2587 if (mdata->bcache && e->deleted)
2588 {
2589 mutt_debug(LL_DEBUG2, "mutt_bcache_del %s\n", buf);
2590 mutt_bcache_del(mdata->bcache, buf);
2591 }
2592
2593#ifdef USE_HCACHE
2594 if (hc && (e->changed || e->deleted))
2595 {
2596 if (e->deleted && !e->read)
2597 mdata->unread--;
2598 mutt_debug(LL_DEBUG2, "hcache_store_email %s\n", buf);
2599 hcache_store_email(hc, buf, strlen(buf), e, 0);
2600 }
2601#endif
2602 }
2603
2604#ifdef USE_HCACHE
2605 if (hc)
2606 {
2607 hcache_close(&hc);
2608 mdata->last_cached = mdata->last_loaded;
2609 }
2610#endif
2611
2612 /* save .newsrc entries */
2614 nntp_newsrc_update(mdata->adata);
2615 nntp_newsrc_close(mdata->adata);
2616 return MX_STATUS_OK;
2617}
2618
2623static enum MxStatus nntp_mbox_close(struct Mailbox *m)
2624{
2625 struct NntpMboxData *mdata = m->mdata;
2626 struct NntpMboxData *tmp_mdata = NULL;
2627 if (!mdata)
2628 return MX_STATUS_OK;
2629
2630 mdata->unread = m->msg_unread;
2631
2633 if (!mdata->adata || !mdata->adata->groups_hash || !mdata->group)
2634 return MX_STATUS_OK;
2635
2636 tmp_mdata = mutt_hash_find(mdata->adata->groups_hash, mdata->group);
2637 if (!tmp_mdata || (tmp_mdata != mdata))
2638 nntp_mdata_free((void **) &mdata);
2639 return MX_STATUS_OK;
2640}
2641
2645static bool nntp_msg_open(struct Mailbox *m, struct Message *msg, struct Email *e)
2646{
2647 struct NntpMboxData *mdata = m->mdata;
2648 char article[16] = { 0 };
2649
2650 /* try to get article from cache */
2651 struct NntpAcache *acache = &mdata->acache[e->index % NNTP_ACACHE_LEN];
2652 if (acache->path)
2653 {
2654 if (acache->index == e->index)
2655 {
2656 msg->fp = mutt_file_fopen(acache->path, "r");
2657 if (msg->fp)
2658 return true;
2659 }
2660 else
2661 {
2662 /* clear previous entry */
2663 unlink(acache->path);
2664 FREE(&acache->path);
2665 }
2666 }
2667 snprintf(article, sizeof(article), ANUM_FMT, nntp_edata_get(e)->article_num);
2668 msg->fp = mutt_bcache_get(mdata->bcache, article);
2669 if (msg->fp)
2670 {
2671 if (nntp_edata_get(e)->parsed)
2672 return true;
2673 }
2674 else
2675 {
2676 /* don't try to fetch article from removed newsgroup */
2677 if (mdata->deleted)
2678 return false;
2679
2680 /* create new cache file */
2681 const char *fetch_msg = _("Fetching message...");
2682 mutt_message("%s", fetch_msg);
2683 msg->fp = mutt_bcache_put(mdata->bcache, article);
2684 if (!msg->fp)
2685 {
2686 struct Buffer *tempfile = buf_pool_get();
2687 buf_mktemp(tempfile);
2688 acache->path = buf_strdup(tempfile);
2689 buf_pool_release(&tempfile);
2690 acache->index = e->index;
2691 msg->fp = mutt_file_fopen(acache->path, "w+");
2692 if (!msg->fp)
2693 {
2694 mutt_perror("%s", acache->path);
2695 unlink(acache->path);
2696 FREE(&acache->path);
2697 return false;
2698 }
2699 }
2700
2701 /* fetch message to cache file */
2702 char buf[2048] = { 0 };
2703 snprintf(buf, sizeof(buf), "ARTICLE %s\r\n",
2704 nntp_edata_get(e)->article_num ? article : e->env->message_id);
2705 const int rc = nntp_fetch_lines(mdata, buf, sizeof(buf), NULL, fetch_tempfile, msg->fp);
2706 if (rc)
2707 {
2708 mutt_file_fclose(&msg->fp);
2709 if (acache->path)
2710 {
2711 unlink(acache->path);
2712 FREE(&acache->path);
2713 }
2714 if (rc > 0)
2715 {
2716 if (mutt_str_startswith(buf, nntp_edata_get(e)->article_num ? "423" : "430"))
2717 {
2718 mutt_error(_("Article %s not found on the server"),
2719 nntp_edata_get(e)->article_num ? article : e->env->message_id);
2720 }
2721 else
2722 {
2723 mutt_error("ARTICLE: %s", buf);
2724 }
2725 }
2726 return false;
2727 }
2728
2729 if (!acache->path)
2730 mutt_bcache_commit(mdata->bcache, article);
2731 }
2732
2733 /* replace envelope with new one
2734 * hash elements must be updated because pointers will be changed */
2735 if (m->id_hash && e->env->message_id)
2737 if (m->subj_hash && e->env->real_subj)
2739
2740 mutt_env_free(&e->env);
2741 e->env = mutt_rfc822_read_header(msg->fp, e, false, false);
2742
2743 if (m->id_hash && e->env->message_id)
2745 if (m->subj_hash && e->env->real_subj)
2747
2748 /* fix content length */
2749 if (!mutt_file_seek(msg->fp, 0, SEEK_END))
2750 {
2751 return false;
2752 }
2753 e->body->length = ftell(msg->fp) - e->body->offset;
2754
2755 /* this is called in neomutt before the open which fetches the message,
2756 * which is probably wrong, but we just call it again here to handle
2757 * the problem instead of fixing it */
2758 nntp_edata_get(e)->parsed = true;
2759 mutt_parse_mime_message(e, msg->fp);
2760
2761 /* these would normally be updated in mview_update(), but the
2762 * full headers aren't parsed with overview, so the information wasn't
2763 * available then */
2764 if (WithCrypto)
2765 e->security = crypt_query(e->body);
2766
2767 rewind(msg->fp);
2769 return true;
2770}
2771
2777static int nntp_msg_close(struct Mailbox *m, struct Message *msg)
2778{
2779 return mutt_file_fclose(&msg->fp);
2780}
2781
2785enum MailboxType nntp_path_probe(const char *path, const struct stat *st)
2786{
2787 if (mutt_istr_startswith(path, "news://"))
2788 return MUTT_NNTP;
2789
2790 if (mutt_istr_startswith(path, "snews://"))
2791 return MUTT_NNTP;
2792
2793 return MUTT_UNKNOWN;
2794}
2795
2799static int nntp_path_canon(struct Buffer *path)
2800{
2801 return 0;
2802}
2803
2807const struct MxOps MxNntpOps = {
2808 // clang-format off
2809 .type = MUTT_NNTP,
2810 .name = "nntp",
2811 .is_local = false,
2812 .ac_owns_path = nntp_ac_owns_path,
2813 .ac_add = nntp_ac_add,
2814 .mbox_open = nntp_mbox_open,
2815 .mbox_open_append = NULL,
2816 .mbox_check = nntp_mbox_check,
2817 .mbox_check_stats = NULL,
2818 .mbox_sync = nntp_mbox_sync,
2819 .mbox_close = nntp_mbox_close,
2820 .msg_open = nntp_msg_open,
2821 .msg_open_new = NULL,
2822 .msg_commit = NULL,
2823 .msg_close = nntp_msg_close,
2824 .msg_padding_size = NULL,
2825 .msg_save_hcache = NULL,
2826 .tags_edit = NULL,
2827 .tags_commit = NULL,
2828 .path_probe = nntp_path_probe,
2829 .path_canon = nntp_path_canon,
2830 // clang-format on
2831};
GUI display the mailboxes in a side panel.
void mutt_parse_mime_message(struct Email *e, FILE *fp)
Parse a MIME email.
Definition: attachments.c:596
Body Caching (local copies of email bodies)
int mutt_bcache_commit(struct BodyCache *bcache, const char *id)
Move a temporary file into the Body Cache.
Definition: bcache.c:252
struct BodyCache * mutt_bcache_open(struct ConnAccount *account, const char *mailbox)
Open an Email-Body Cache.
Definition: bcache.c:146
FILE * mutt_bcache_get(struct BodyCache *bcache, const char *id)
Open a file in the Body Cache.
Definition: bcache.c:185
int mutt_bcache_del(struct BodyCache *bcache, const char *id)
Delete a file from the Body Cache.
Definition: bcache.c:269
FILE * mutt_bcache_put(struct BodyCache *bcache, const char *id)
Create a file in the Body Cache.
Definition: bcache.c:212
int buf_printf(struct Buffer *buf, const char *fmt,...)
Format a string overwriting a Buffer.
Definition: buffer.c:161
size_t buf_len(const struct Buffer *buf)
Calculate the length of a Buffer.
Definition: buffer.c:491
const char * buf_find_string(const struct Buffer *buf, const char *s)
Return a pointer to a substring found in the buffer.
Definition: buffer.c:638
void buf_reset(struct Buffer *buf)
Reset an existing Buffer.
Definition: buffer.c:76
bool buf_is_empty(const struct Buffer *buf)
Is the Buffer empty?
Definition: buffer.c:291
char buf_at(const struct Buffer *buf, size_t offset)
Return the character at the given offset.
Definition: buffer.c:668
size_t buf_addch(struct Buffer *buf, char c)
Add a single character to a Buffer.
Definition: buffer.c:241
size_t buf_addstr(struct Buffer *buf, const char *s)
Add a string to a Buffer.
Definition: buffer.c:226
const char * buf_find_char(const struct Buffer *buf, const char c)
Return a pointer to a char found in the buffer.
Definition: buffer.c:653
size_t buf_strcpy(struct Buffer *buf, const char *s)
Copy a string into a Buffer.
Definition: buffer.c:395
char * buf_strdup(const struct Buffer *buf)
Copy a Buffer's string.
Definition: buffer.c:571
static const char * buf_string(const struct Buffer *buf)
Convert a buffer to a const char * "string".
Definition: buffer.h:96
const char * cs_subset_string(const struct ConfigSubset *sub, const char *name)
Get a string config item by name.
Definition: helpers.c:291
short cs_subset_number(const struct ConfigSubset *sub, const char *name)
Get a number config item by name.
Definition: helpers.c:143
long cs_subset_long(const struct ConfigSubset *sub, const char *name)
Get a long config item by name.
Definition: helpers.c:95
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition: helpers.c:47
Convenience wrapper for the config headers.
Connection Library.
int mutt_account_getpass(struct ConnAccount *cac)
Fetch password into ConnAccount, if necessary.
Definition: connaccount.c:131
int mutt_account_getuser(struct ConnAccount *cac)
Retrieve username into ConnAccount, if necessary.
Definition: connaccount.c:52
#define MUTT_ACCT_USER
User field has been set.
Definition: connaccount.h:44
Convenience wrapper for the core headers.
void mailbox_changed(struct Mailbox *m, enum NotifyMailbox action)
Notify observers of a change to a Mailbox.
Definition: mailbox.c:233
@ NT_MAILBOX_INVALID
Email list was changed.
Definition: mailbox.h:189
#define MUTT_ACL_DELETE
Delete a message.
Definition: mailbox.h:63
static const char * mailbox_path(const struct Mailbox *m)
Get the Mailbox's path string.
Definition: mailbox.h:223
#define MUTT_ACL_WRITE
Write to a message (for flagging or linking threads)
Definition: mailbox.h:71
MailboxType
Supported mailbox formats.
Definition: mailbox.h:41
@ MUTT_NNTP
'NNTP' (Usenet) Mailbox type
Definition: mailbox.h:49
@ MUTT_UNKNOWN
Mailbox wasn't recognised.
Definition: mailbox.h:44
SecurityFlags crypt_query(struct Body *b)
Check out the type of encryption used.
Definition: crypt.c:688
int mutt_toupper(int arg)
Wrapper for toupper(3)
Definition: ctype.c:139
struct Email * email_new(void)
Create a new Email.
Definition: email.c:77
void email_free(struct Email **ptr)
Free an Email.
Definition: email.c:46
Structs that make up an email.
struct Envelope * mutt_rfc822_read_header(FILE *fp, struct Email *e, bool user_hdrs, bool weed)
Parses an RFC822 header.
Definition: parse.c:1204
void mutt_env_free(struct Envelope **ptr)
Free an Envelope.
Definition: envelope.c:126
bool mutt_file_seek(FILE *fp, LOFF_T offset, int whence)
Wrapper for fseeko with error handling.
Definition: file.c:655
#define mutt_file_fclose(FP)
Definition: file.h:139
#define mutt_file_fopen(PATH, MODE)
Definition: file.h:138
void mutt_set_flag(struct Mailbox *m, struct Email *e, enum MessageType flag, bool bf, bool upd_mbox)
Set a flag on an email.
Definition: flags.c:57
int mutt_ssl_starttls(struct Connection *conn)
Negotiate TLS over an already opened connection.
Definition: gnutls.c:1149
void nntp_adata_free(void **ptr)
Free the private Account data - Implements Account::adata_free() -.
Definition: adata.c:43
void nntp_edata_free(void **ptr)
Free the private Email data - Implements Email::edata_free() -.
Definition: edata.c:38
void nntp_hashelem_free(int type, void *obj, intptr_t data)
Free our hash table data - Implements hash_hdata_free_t -.
Definition: nntp.c:116
#define mutt_error(...)
Definition: logging2.h:93
#define mutt_message(...)
Definition: logging2.h:92
#define mutt_debug(LEVEL,...)
Definition: logging2.h:90
#define mutt_perror(...)
Definition: logging2.h:94
void nntp_mdata_free(void **ptr)
Free the private Mailbox data - Implements Mailbox::mdata_free() -.
Definition: mdata.c:38
static bool nntp_ac_add(struct Account *a, struct Mailbox *m)
Add a Mailbox to an Account - Implements MxOps::ac_add() -.
Definition: nntp.c:2374
static bool nntp_ac_owns_path(struct Account *a, const char *path)
Check whether an Account owns a Mailbox path - Implements MxOps::ac_owns_path() -.
Definition: nntp.c:2366
const struct MxOps MxNntpOps
NNTP Mailbox - Implements MxOps -.
Definition: nntp.c:2807
static enum MxStatus nntp_mbox_check(struct Mailbox *m)
Check for new mail - Implements MxOps::mbox_check() -.
Definition: nntp.c:2546
static enum MxStatus nntp_mbox_close(struct Mailbox *m)
Close a Mailbox - Implements MxOps::mbox_close() -.
Definition: nntp.c:2623
static enum MxOpenReturns nntp_mbox_open(struct Mailbox *m)
Open a Mailbox - Implements MxOps::mbox_open() -.
Definition: nntp.c:2382
static enum MxStatus nntp_mbox_sync(struct Mailbox *m)
Save changes to the Mailbox - Implements MxOps::mbox_sync() -.
Definition: nntp.c:2563
static int nntp_msg_close(struct Mailbox *m, struct Message *msg)
Close an email - Implements MxOps::msg_close() -.
Definition: nntp.c:2777
static bool nntp_msg_open(struct Mailbox *m, struct Message *msg, struct Email *e)
Open an email message in a Mailbox - Implements MxOps::msg_open() -.
Definition: nntp.c:2645
static int nntp_path_canon(struct Buffer *path)
Canonicalise a Mailbox path - Implements MxOps::path_canon() -.
Definition: nntp.c:2799
enum MailboxType nntp_path_probe(const char *path, const struct stat *st)
Is this an NNTP Mailbox? - Implements MxOps::path_probe() -.
Definition: nntp.c:2785
int nntp_sort_unsorted(const struct Email *a, const struct Email *b, bool reverse)
Restore the 'unsorted' order of emails - Implements sort_email_t -.
Definition: nntp.c:2355
struct HashElem * mutt_hash_insert(struct HashTable *table, const char *strkey, void *data)
Add a new element to the Hash Table (with string keys)
Definition: hash.c:335
void mutt_hash_delete(struct HashTable *table, const char *strkey, const void *data)
Remove an element from a Hash Table.
Definition: hash.c:427
void * mutt_hash_find(const struct HashTable *table, const char *strkey)
Find the HashElem data in a Hash Table element using a key.
Definition: hash.c:362
int hcache_delete_email(struct HeaderCache *hc, const char *key, size_t keylen)
Multiplexor for StoreOps::delete_record.
Definition: hcache.c:739
void hcache_close(struct HeaderCache **ptr)
Multiplexor for StoreOps::close.
Definition: hcache.c:542
struct HCacheEntry hcache_fetch_email(struct HeaderCache *hc, const char *key, size_t keylen, uint32_t uidvalidity)
Multiplexor for StoreOps::fetch.
Definition: hcache.c:562
int hcache_store_email(struct HeaderCache *hc, const char *key, size_t keylen, struct Email *e, uint32_t uidvalidity)
Multiplexor for StoreOps::store.
Definition: hcache.c:670
Header cache multiplexor.
void mutt_account_hook(const char *url)
Perform an account hook.
Definition: hook.c:888
Parse and execute user-defined hooks.
@ LL_DEBUG2
Log at debug level 2.
Definition: logging2.h:45
@ LL_DEBUG1
Log at debug level 1.
Definition: logging2.h:44
#define FREE(x)
Definition: memory.h:62
#define MUTT_MEM_CALLOC(n, type)
Definition: memory.h:47
#define MUTT_MEM_REALLOC(pptr, n, type)
Definition: memory.h:50
#define MUTT_MEM_MALLOC(n, type)
Definition: memory.h:48
struct tm mutt_date_gmtime(time_t t)
Converts calendar time to a broken-down time structure expressed in UTC timezone.
Definition: date.c:927
time_t mutt_date_now(void)
Return the number of seconds since the Unix epoch.
Definition: date.c:456
Convenience wrapper for the library headers.
#define _(a)
Definition: message.h:28
void mutt_str_remove_trailing_ws(char *s)
Trim trailing whitespace from a string.
Definition: string.c:564
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:254
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:659
const char * mutt_istr_find(const char *haystack, const char *needle)
Find first occurrence of string (ignoring case)
Definition: string.c:522
size_t mutt_str_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix.
Definition: string.c:231
size_t mutt_str_copy(char *dest, const char *src, size_t dsize)
Copy a string into a buffer (guaranteeing NUL-termination)
Definition: string.c:580
size_t mutt_istr_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix, ignoring case.
Definition: string.c:243
char * mutt_str_replace(char **p, const char *s)
Replace one string with another.
Definition: string.c:281
Many unsorted constants and some structs.
@ MUTT_TAG
Tagged messages.
Definition: mutt.h:80
void mutt_clear_error(void)
Clear the message line (bottom line of screen)
Definition: mutt_logging.c:74
NeoMutt Logging.
void mutt_sleep(short s)
Sleep for a while.
Definition: muttlib.c:841
Some miscellaneous functions.
void mx_alloc_memory(struct Mailbox *m, int req_size)
Create storage for the emails.
Definition: mx.c:1211
API for mailboxes.
MxOpenReturns
Return values for mbox_open()
Definition: mxapi.h:73
@ MX_OPEN_ERROR
Open failed with an error.
Definition: mxapi.h:75
@ MX_OPEN_OK
Open succeeded.
Definition: mxapi.h:74
MxStatus
Return values from mbox_check(), mbox_check_stats(), mbox_sync(), and mbox_close()
Definition: mxapi.h:60
@ MX_STATUS_ERROR
An error occurred.
Definition: mxapi.h:61
@ MX_STATUS_OK
No changes.
Definition: mxapi.h:62
@ MX_STATUS_REOPENED
Mailbox was reopened.
Definition: mxapi.h:65
@ MX_STATUS_NEW_MAIL
New mail received in Mailbox.
Definition: mxapi.h:63
API for encryption/signing of emails.
#define WithCrypto
Definition: lib.h:122
struct HeaderCache * nntp_hcache_open(struct NntpMboxData *mdata)
Open newsgroup hcache.
Definition: newsrc.c:707
void nntp_delete_group_cache(struct NntpMboxData *mdata)
Remove hcache and bcache of newsgroup.
Definition: newsrc.c:808
void nntp_newsrc_gen_entries(struct Mailbox *m)
Generate array of .newsrc entries.
Definition: newsrc.c:301
void nntp_hcache_update(struct NntpMboxData *mdata, struct HeaderCache *hc)
Remove stale cached headers.
Definition: newsrc.c:731
void nntp_article_status(struct Mailbox *m, struct Email *e, char *group, anum_t anum)
Get status of articles from .newsrc.
Definition: newsrc.c:1138
int nntp_add_group(char *line, void *data)
Parse newsgroup.
Definition: newsrc.c:572
int nntp_active_save_cache(struct NntpAccountData *adata)
Save list of all newsgroups to cache.
Definition: newsrc.c:647
void nntp_bcache_update(struct NntpMboxData *mdata)
Remove stale cached messages.
Definition: newsrc.c:799
void nntp_group_unread_stat(struct NntpMboxData *mdata)
Count number of unread articles using .newsrc data.
Definition: newsrc.c:134
void nntp_acache_free(struct NntpMboxData *mdata)
Remove all temporarily cache files.
Definition: newsrc.c:104
struct NntpEmailData * nntp_edata_get(struct Email *e)
Get the private data for this Email.
Definition: edata.c:60
struct NntpEmailData * nntp_edata_new(void)
Create a new NntpEmailData for an Email.
Definition: edata.c:50
#define NNTP_ACACHE_LEN
Definition: lib.h:83
int nntp_newsrc_parse(struct NntpAccountData *adata)
Parse .newsrc file.
Definition: newsrc.c:164
void nntp_newsrc_close(struct NntpAccountData *adata)
Unlock and close .newsrc file.
Definition: newsrc.c:120
int nntp_newsrc_update(struct NntpAccountData *adata)
Update .newsrc file.
Definition: newsrc.c:443
#define ANUM_FMT
Definition: lib.h:62
struct NntpAccountData * nntp_select_server(struct Mailbox *m, const char *server, bool leave_lock)
Open a connection to an NNTP server.
Definition: newsrc.c:945
#define anum_t
Definition: lib.h:61
@ NNTP_NONE
No connection to server.
Definition: private.h:44
@ NNTP_BYE
Disconnected from server.
Definition: private.h:46
@ NNTP_OK
Connected to server.
Definition: private.h:45
int nntp_check_msgid(struct Mailbox *m, const char *msgid)
Fetch article by Message-ID.
Definition: nntp.c:2214
int nntp_check_children(struct Mailbox *m, const char *msgid)
Fetch children of article with the Message-ID.
Definition: nntp.c:2285
int nntp_active_fetch(struct NntpAccountData *adata, bool mark_new)
Fetch list of all newsgroups from server.
Definition: nntp.c:2036
static int fetch_children(char *line, void *data)
Parse XPAT line.
Definition: nntp.c:1735
static int nntp_auth(struct NntpAccountData *adata)
Get login, password and authenticate.
Definition: nntp.c:451
static int nntp_date(struct NntpAccountData *adata, time_t *now)
Get date and time from server.
Definition: nntp.c:1698
int nntp_check_new_groups(struct Mailbox *m, struct NntpAccountData *adata)
Check for new groups/articles in subscribed groups.
Definition: nntp.c:2104
static const char * OverviewFmt
Fields to get from server, if it supports the LIST OVERVIEW.FMT feature.
Definition: nntp.c:79
static int nntp_group_poll(struct NntpMboxData *mdata, bool update_stat)
Check newsgroup for new articles.
Definition: nntp.c:1440
int nntp_post(struct Mailbox *m, const char *msg)
Post article.
Definition: nntp.c:1945
static int nntp_capabilities(struct NntpAccountData *adata)
Get capabilities.
Definition: nntp.c:140
static int parse_overview_line(char *line, void *data)
Parse overview line.
Definition: nntp.c:1047
static enum MxStatus check_mailbox(struct Mailbox *m)
Check current newsgroup for new articles.
Definition: nntp.c:1490
static int nntp_connect_error(struct NntpAccountData *adata)
Signal a failed connection.
Definition: nntp.c:126
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:729
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:813
static int get_description(struct NntpMboxData *mdata, const char *wildmat, const char *msg)
Fetch newsgroups descriptions.
Definition: nntp.c:935
static int fetch_tempfile(char *line, void *data)
Write line to temporary file.
Definition: nntp.c:1008
static void nntp_parse_xref(struct Mailbox *m, struct Email *e)
Parse cross-reference.
Definition: nntp.c:967
int nntp_open_connection(struct NntpAccountData *adata)
Connect to server, authenticate and get capabilities.
Definition: nntp.c:1765
static int nntp_attempt_features(struct NntpAccountData *adata)
Detect supported commands.
Definition: nntp.c:259
static int fetch_numbers(char *line, void *data)
Parse article number.
Definition: nntp.c:1025
struct NntpAccountData * CurrentNewsSrv
Current news server.
Definition: nntp.c:76
static int fetch_description(char *line, void *data)
Parse newsgroup description.
Definition: nntp.c:898
static int nntp_fetch_headers(struct Mailbox *m, void *hc, anum_t first, anum_t last, bool restore)
Fetch headers.
Definition: nntp.c:1203
Notmuch-specific Mailbox data.
struct Buffer * buf_pool_get(void)
Get a Buffer from the pool.
Definition: pool.c:82
void buf_pool_release(struct Buffer **ptr)
Return a Buffer to the pool.
Definition: pool.c:96
Pop-specific Account data.
Pop-specific Email data.
Progress Bar.
@ MUTT_PROGRESS_READ
Progress tracks elements, according to $read_inc
Definition: lib.h:83
struct Progress * progress_new(enum ProgressType type, size_t size)
Create a new Progress Bar.
Definition: progress.c:139
void progress_free(struct Progress **ptr)
Free a Progress Bar.
Definition: progress.c:110
void progress_set_message(struct Progress *progress, const char *fmt,...) __attribute__((__format__(__printf__
bool progress_update(struct Progress *progress, size_t pos, int percent)
Update the state of the progress bar.
Definition: progress.c:80
Prototypes for many functions.
@ MUTT_YES
User answered 'Yes', or assume 'Yes'.
Definition: quad.h:39
Ask the user a question.
enum QuadOption query_quadoption(const char *prompt, struct ConfigSubset *sub, const char *name)
Ask the user a quad-question.
Definition: question.c:378
enum QuadOption query_yesorno(const char *prompt, enum QuadOption def)
Ask the user a Yes/No question.
Definition: question.c:326
int mutt_sasl_interact(sasl_interact_t *interaction)
Perform an SASL interaction with the user.
Definition: sasl.c:704
int mutt_sasl_client_new(struct Connection *conn, sasl_conn_t **saslconn)
Wrapper for sasl_client_new()
Definition: sasl.c:606
void mutt_sasl_setup_conn(struct Connection *conn, sasl_conn_t *saslconn)
Set up an SASL connection.
Definition: sasl.c:741
GUI display the mailboxes in a side panel.
int mutt_socket_close(struct Connection *conn)
Close a socket.
Definition: socket.c:100
int mutt_socket_buffer_readln_d(struct Buffer *buf, struct Connection *conn, int dbg)
Read a line from a socket into a Buffer.
Definition: socket.c:328
void mutt_socket_empty(struct Connection *conn)
Clear out any queued data.
Definition: socket.c:306
int mutt_socket_open(struct Connection *conn)
Simple wrapper.
Definition: socket.c:76
int mutt_socket_readln_d(char *buf, size_t buflen, struct Connection *conn, int dbg)
Read a line from a socket.
Definition: socket.c:238
#define MUTT_SOCK_LOG_FULL
Definition: socket.h:54
#define MUTT_SOCK_LOG_HDR
Definition: socket.h:53
#define mutt_socket_readln(buf, buflen, conn)
Definition: socket.h:56
#define mutt_socket_send(conn, buf)
Definition: socket.h:57
#define mutt_socket_buffer_readln(buf, conn)
Definition: socket.h:61
#define MUTT_SOCK_LOG_CMD
Definition: socket.h:52
#define mutt_socket_send_d(conn, buf, dbg)
Definition: socket.h:58
Key value store.
A group of associated Mailboxes.
Definition: account.h:36
void(* adata_free)(void **ptr)
Definition: account.h:53
void * adata
Private data (for Mailbox backends)
Definition: account.h:42
LOFF_T offset
offset where the actual data begins
Definition: body.h:52
LOFF_T length
length (in bytes) of attachment
Definition: body.h:53
String manipulation buffer.
Definition: buffer.h:36
size_t dsize
Length of data.
Definition: buffer.h:39
char * data
Pointer to data.
Definition: buffer.h:37
Keep track of the children of an article.
Definition: nntp.c:106
anum_t * child
Definition: nntp.c:110
struct Mailbox * mailbox
Definition: nntp.c:107
unsigned int max
Definition: nntp.c:109
unsigned int num
Definition: nntp.c:108
char user[128]
Username.
Definition: connaccount.h:56
char pass[256]
Password.
Definition: connaccount.h:57
char host[128]
Server to login to.
Definition: connaccount.h:54
MuttAccountFlags flags
Which fields are initialised, e.g. MUTT_ACCT_USER.
Definition: connaccount.h:60
struct ConnAccount account
Account details: username, password, etc.
Definition: connection.h:49
int fd
Socket file descriptor.
Definition: connection.h:53
The envelope/body of an email.
Definition: email.h:39
bool read
Email is read.
Definition: email.h:50
struct Envelope * env
Envelope information.
Definition: email.h:68
void * edata
Driver-specific data.
Definition: email.h:74
SecurityFlags security
bit 0-10: flags, bit 11,12: application, bit 13: traditional pgp See: ncrypt/lib.h pgplib....
Definition: email.h:43
struct Body * body
List of MIME parts.
Definition: email.h:69
bool old
Email is seen, but unread.
Definition: email.h:49
void(* edata_free)(void **ptr)
Definition: email.h:90
bool changed
Email has been edited.
Definition: email.h:77
bool flagged
Marked important?
Definition: email.h:47
time_t date_sent
Time when the message was sent (UTC)
Definition: email.h:60
bool deleted
Email is deleted.
Definition: email.h:78
int index
The absolute (unsorted) message number.
Definition: email.h:110
time_t received
Time when the message was placed in the mailbox.
Definition: email.h:61
char * message_id
Message ID.
Definition: envelope.h:73
char * newsgroups
List of newsgroups.
Definition: envelope.h:78
char * xref
List of cross-references.
Definition: envelope.h:79
char *const real_subj
Offset of the real subject.
Definition: envelope.h:71
Keep track when getting data from a server.
Definition: nntp.c:92
struct HeaderCache * hc
Definition: nntp.c:99
struct Progress * progress
Definition: nntp.c:98
anum_t first
Definition: nntp.c:94
struct Mailbox * mailbox
Definition: nntp.c:93
bool restore
Definition: nntp.c:96
anum_t last
Definition: nntp.c:95
unsigned char * messages
Definition: nntp.c:97
Wrapper for Email retrieved from the header cache.
Definition: lib.h:99
struct Email * email
Retrieved email.
Definition: lib.h:102
Header Cache.
Definition: lib.h:86
A mailbox.
Definition: mailbox.h:79
int vcount
The number of virtual messages.
Definition: mailbox.h:99
char * realpath
Used for duplicate detection, context comparison, and the sidebar.
Definition: mailbox.h:81
int msg_count
Total number of messages.
Definition: mailbox.h:88
AclFlags rights
ACL bits, see AclFlags.
Definition: mailbox.h:119
enum MailboxType type
Mailbox type.
Definition: mailbox.h:102
void * mdata
Driver specific data.
Definition: mailbox.h:132
struct HashTable * subj_hash
Hash Table: "subject" -> Email.
Definition: mailbox.h:124
struct Email ** emails
Array of Emails.
Definition: mailbox.h:96
struct HashTable * id_hash
Hash Table: "message-id" -> Email.
Definition: mailbox.h:123
struct Account * account
Account that owns this Mailbox.
Definition: mailbox.h:127
bool readonly
Don't allow changes to the mailbox.
Definition: mailbox.h:116
int msg_tagged
How many messages are tagged?
Definition: mailbox.h:94
bool verbose
Display status messages?
Definition: mailbox.h:117
int msg_unread
Number of unread messages.
Definition: mailbox.h:89
A local copy of an email.
Definition: message.h:34
FILE * fp
pointer to the message data
Definition: message.h:35
Definition: mxapi.h:88
enum MailboxType type
Mailbox type, e.g. MUTT_IMAP.
Definition: mxapi.h:89
Container for Accounts, Notifications.
Definition: neomutt.h:43
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:47
An entry in a .newsrc (subscribed newsgroups)
Definition: lib.h:77
anum_t last
Last article number in run.
Definition: lib.h:79
anum_t first
First article number in run.
Definition: lib.h:78
NNTP article cache.
Definition: lib.h:68
char * path
Cache path.
Definition: lib.h:70
unsigned int index
Index number.
Definition: lib.h:69
NNTP-specific Account data -.
Definition: adata.h:36
time_t newgroups_time
Definition: adata.h:56
bool newsrc_modified
Definition: adata.h:49
struct HashTable * groups_hash
Hash Table: "newsgroup" -> NntpMboxData.
Definition: adata.h:61
bool hasXOVER
Server supports XOVER command.
Definition: adata.h:45
struct NntpMboxData ** groups_list
Definition: adata.h:60
struct Connection * conn
Connection to NNTP Server.
Definition: adata.h:62
unsigned int status
Definition: adata.h:47
char * authenticators
Definition: adata.h:52
char * overview_fmt
Definition: adata.h:53
bool hasXGTITLE
Server supports XGTITLE command.
Definition: adata.h:41
unsigned int groups_num
Definition: adata.h:58
bool hasCAPABILITIES
Server supports CAPABILITIES command.
Definition: adata.h:37
bool hasSTARTTLS
Server supports STARTTLS command.
Definition: adata.h:38
bool hasLISTGROUPrange
Server supports LISTGROUPrange command.
Definition: adata.h:43
time_t check_time
Definition: adata.h:57
unsigned int use_tls
Definition: adata.h:46
bool hasLISTGROUP
Server supports LISTGROUP command.
Definition: adata.h:42
bool hasOVER
Server supports OVER command.
Definition: adata.h:44
bool hasDATE
Server supports DATE command.
Definition: adata.h:39
bool hasLIST_NEWSGROUPS
Server supports LIST_NEWSGROUPS command.
Definition: adata.h:40
anum_t article_num
NNTP article number.
Definition: edata.h:36
bool parsed
Email has been parse.
Definition: edata.h:37
NNTP-specific Mailbox data -.
Definition: mdata.h:34
anum_t last_cached
Definition: mdata.h:40
bool deleted
Definition: mdata.h:45
anum_t last_message
Definition: mdata.h:38
struct BodyCache * bcache
Definition: mdata.h:50
char * group
Name of newsgroup.
Definition: mdata.h:35
struct NntpAccountData * adata
Definition: mdata.h:48
char * desc
Description of newsgroup.
Definition: mdata.h:36
struct NewsrcEntry * newsrc_ent
Definition: mdata.h:47
anum_t unread
Definition: mdata.h:41
anum_t last_loaded
Definition: mdata.h:39
unsigned int newsrc_len
Definition: mdata.h:46
struct NntpAcache acache[NNTP_ACACHE_LEN]
Definition: mdata.h:49
anum_t first_message
Definition: mdata.h:37
A parsed URL proto://user:password@host:port/path?a=1&b=2
Definition: url.h:69
char * host
Host.
Definition: url.h:73
char * path
Path.
Definition: url.h:75
enum UrlScheme scheme
Scheme, e.g. U_SMTPS.
Definition: url.h:70
time_t timegm(struct tm *tm)
Convert struct tm to time_t seconds since epoch.
Definition: timegm.c:70
#define buf_mktemp(buf)
Definition: tmp.h:33
#define mutt_file_mkstemp()
Definition: tmp.h:36
struct Url * url_parse(const char *src)
Fill in Url.
Definition: url.c:238
void url_free(struct Url **ptr)
Free the contents of a URL.
Definition: url.c:123
int url_tostring(const struct Url *url, char *dest, size_t len, uint8_t flags)
Output the URL string for a given Url object.
Definition: url.c:422
#define U_NO_FLAGS
Definition: url.h:49
@ U_NNTPS
Url is nntps://.
Definition: url.h:42
@ U_NNTP
Url is nntp://.
Definition: url.h:41