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