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