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