NeoMutt  2024-04-16-36-g75b6fb
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
265 /* no CAPABILITIES, trying DATE, LISTGROUP, LIST NEWSGROUPS */
266 if (!adata->hasCAPABILITIES)
267 {
268 if ((mutt_socket_send(conn, "DATE\r\n") < 0) ||
269 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
270 {
271 return nntp_connect_error(adata);
272 }
273 if (!mutt_str_startswith(buf, "500"))
274 adata->hasDATE = true;
275
276 if ((mutt_socket_send(conn, "LISTGROUP\r\n") < 0) ||
277 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
278 {
279 return nntp_connect_error(adata);
280 }
281 if (!mutt_str_startswith(buf, "500"))
282 adata->hasLISTGROUP = true;
283
284 if ((mutt_socket_send(conn, "LIST NEWSGROUPS +\r\n") < 0) ||
285 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
286 {
287 return nntp_connect_error(adata);
288 }
289 if (!mutt_str_startswith(buf, "500"))
290 adata->hasLIST_NEWSGROUPS = true;
291 if (mutt_str_startswith(buf, "215"))
292 {
293 do
294 {
295 if (mutt_socket_readln(buf, sizeof(buf), conn) < 0)
296 return nntp_connect_error(adata);
297 } while (!mutt_str_equal(".", buf));
298 }
299 }
300
301 /* no LIST NEWSGROUPS, trying XGTITLE */
302 if (!adata->hasLIST_NEWSGROUPS)
303 {
304 if ((mutt_socket_send(conn, "XGTITLE\r\n") < 0) ||
305 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
306 {
307 return nntp_connect_error(adata);
308 }
309 if (!mutt_str_startswith(buf, "500"))
310 adata->hasXGTITLE = true;
311 }
312
313 /* no OVER, trying XOVER */
314 if (!adata->hasOVER)
315 {
316 if ((mutt_socket_send(conn, "XOVER\r\n") < 0) ||
317 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
318 {
319 return nntp_connect_error(adata);
320 }
321 if (!mutt_str_startswith(buf, "500"))
322 adata->hasXOVER = true;
323 }
324
325 /* trying LIST OVERVIEW.FMT */
326 if (adata->hasOVER || adata->hasXOVER)
327 {
328 if ((mutt_socket_send(conn, "LIST OVERVIEW.FMT\r\n") < 0) ||
329 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
330 {
331 return nntp_connect_error(adata);
332 }
333 if (!mutt_str_startswith(buf, "215"))
334 {
336 }
337 else
338 {
339 bool cont = false;
340 size_t buflen = 2048, off = 0, b = 0;
341
342 FREE(&adata->overview_fmt);
343 adata->overview_fmt = mutt_mem_malloc(buflen);
344
345 while (true)
346 {
347 if ((buflen - off) < 1024)
348 {
349 buflen *= 2;
350 mutt_mem_realloc(&adata->overview_fmt, buflen);
351 }
352
353 const int chunk = mutt_socket_readln_d(adata->overview_fmt + off,
354 buflen - off, conn, MUTT_SOCK_LOG_HDR);
355 if (chunk < 0)
356 {
357 FREE(&adata->overview_fmt);
358 return nntp_connect_error(adata);
359 }
360
361 if (!cont && mutt_str_equal(".", adata->overview_fmt + off))
362 break;
363
364 cont = (chunk >= (buflen - off));
365 off += strlen(adata->overview_fmt + off);
366 if (!cont)
367 {
368 if (adata->overview_fmt[b] == ':')
369 {
370 memmove(adata->overview_fmt + b, adata->overview_fmt + b + 1, off - b - 1);
371 adata->overview_fmt[off - 1] = ':';
372 }
373 char *colon = strchr(adata->overview_fmt + b, ':');
374 if (!colon)
375 adata->overview_fmt[off++] = ':';
376 else if (!mutt_str_equal(colon + 1, "full"))
377 off = colon + 1 - adata->overview_fmt;
378 if (strcasecmp(adata->overview_fmt + b, "Bytes:") == 0)
379 {
380 size_t len = strlen(adata->overview_fmt + b);
381 mutt_str_copy(adata->overview_fmt + b, "Content-Length:", len + 1);
382 off = b + len;
383 }
384 adata->overview_fmt[off++] = '\0';
385 b = off;
386 }
387 }
388 adata->overview_fmt[off++] = '\0';
389 mutt_mem_realloc(&adata->overview_fmt, off);
390 }
391 }
392 return 0;
393}
394
395#ifdef USE_SASL_CYRUS
404static bool nntp_memchr(char **haystack, const char *sentinel, int needle)
405{
406 char *start = *haystack;
407 size_t max_offset = sentinel - start;
408 void *vp = memchr(start, max_offset, needle);
409 if (!vp)
410 return false;
411 *haystack = vp;
412 return true;
413}
414
422static void nntp_log_binbuf(const char *buf, size_t len, const char *pfx, int dbg)
423{
424 char tmp[1024] = { 0 };
425 char *p = tmp;
426 char *sentinel = tmp + len;
427
428 const short c_debug_level = cs_subset_number(NeoMutt->sub, "debug_level");
429 if (c_debug_level < dbg)
430 return;
431 memcpy(tmp, buf, len);
432 tmp[len] = '\0';
433 while (nntp_memchr(&p, sentinel, '\0'))
434 *p = '.';
435 mutt_debug(dbg, "%s> %s\n", pfx, tmp);
436}
437#endif
438
445static int nntp_auth(struct NntpAccountData *adata)
446{
447 struct Connection *conn = adata->conn;
448 char authenticators[1024] = "USER";
449 char *method = NULL, *a = NULL, *p = NULL;
450 unsigned char flags = conn->account.flags;
451 struct Buffer *buf = buf_pool_get();
452
453 const char *const c_nntp_authenticators = cs_subset_string(NeoMutt->sub, "nntp_authenticators");
454 while (true)
455 {
456 /* get login and password */
457 if ((mutt_account_getuser(&conn->account) < 0) || (conn->account.user[0] == '\0') ||
458 (mutt_account_getpass(&conn->account) < 0) || (conn->account.pass[0] == '\0'))
459 {
460 break;
461 }
462
463 /* get list of authenticators */
464 if (c_nntp_authenticators)
465 {
466 mutt_str_copy(authenticators, c_nntp_authenticators, sizeof(authenticators));
467 }
468 else if (adata->hasCAPABILITIES)
469 {
470 mutt_str_copy(authenticators, adata->authenticators, sizeof(authenticators));
471 p = authenticators;
472 while (*p)
473 {
474 if (*p == ' ')
475 *p = ':';
476 p++;
477 }
478 }
479 p = authenticators;
480 while (*p)
481 {
482 *p = toupper(*p);
483 p++;
484 }
485
486 mutt_debug(LL_DEBUG1, "available methods: %s\n", adata->authenticators);
487 a = authenticators;
488 while (true)
489 {
490 if (!a)
491 {
492 mutt_error(_("No authenticators available"));
493 break;
494 }
495
496 method = a;
497 a = strchr(a, ':');
498 if (a)
499 *a++ = '\0';
500
501 /* check authenticator */
502 if (adata->hasCAPABILITIES)
503 {
504 if (!adata->authenticators)
505 continue;
506 const char *m = mutt_istr_find(adata->authenticators, method);
507 if (!m)
508 continue;
509 if ((m > adata->authenticators) && (*(m - 1) != ' '))
510 continue;
511 m += strlen(method);
512 if ((*m != '\0') && (*m != ' '))
513 continue;
514 }
515 mutt_debug(LL_DEBUG1, "trying method %s\n", method);
516
517 /* AUTHINFO USER authentication */
518 if (mutt_str_equal(method, "USER"))
519 {
520 // L10N: (%s) is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
521 mutt_message(_("Authenticating (%s)..."), method);
522 buf_printf(buf, "AUTHINFO USER %s\r\n", conn->account.user);
523 if ((mutt_socket_send(conn, buf_string(buf)) < 0) ||
525 {
526 break;
527 }
528
529 /* authenticated, password is not required */
530 if (mutt_str_startswith(buf_string(buf), "281"))
531 {
532 buf_pool_release(&buf);
533 return 0;
534 }
535
536 /* username accepted, sending password */
537 if (mutt_str_startswith(buf_string(buf), "381"))
538 {
539 mutt_debug(MUTT_SOCK_LOG_FULL, "%d> AUTHINFO PASS *\n", conn->fd);
540 buf_printf(buf, "AUTHINFO PASS %s\r\n", conn->account.pass);
541 if ((mutt_socket_send_d(conn, buf_string(buf), MUTT_SOCK_LOG_FULL) < 0) ||
543 {
544 break;
545 }
546
547 /* authenticated */
548 if (mutt_str_startswith(buf_string(buf), "281"))
549 {
550 buf_pool_release(&buf);
551 return 0;
552 }
553 }
554
555 /* server doesn't support AUTHINFO USER, trying next method */
556 if (buf_at(buf, 0) == '5')
557 continue;
558 }
559 else
560 {
561#ifdef USE_SASL_CYRUS
562 sasl_conn_t *saslconn = NULL;
563 sasl_interact_t *interaction = NULL;
564 int rc;
565 char inbuf[1024] = { 0 };
566 const char *mech = NULL;
567 const char *client_out = NULL;
568 unsigned int client_len, len;
569
570 if (mutt_sasl_client_new(conn, &saslconn) < 0)
571 {
572 mutt_debug(LL_DEBUG1, "error allocating SASL connection\n");
573 continue;
574 }
575
576 while (true)
577 {
578 rc = sasl_client_start(saslconn, method, &interaction, &client_out,
579 &client_len, &mech);
580 if (rc != SASL_INTERACT)
581 break;
582 mutt_sasl_interact(interaction);
583 }
584 if ((rc != SASL_OK) && (rc != SASL_CONTINUE))
585 {
586 sasl_dispose(&saslconn);
587 mutt_debug(LL_DEBUG1, "error starting SASL authentication exchange\n");
588 continue;
589 }
590
591 // L10N: (%s) is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
592 mutt_message(_("Authenticating (%s)..."), method);
593 buf_printf(buf, "AUTHINFO SASL %s", method);
594
595 /* looping protocol */
596 while ((rc == SASL_CONTINUE) || ((rc == SASL_OK) && client_len))
597 {
598 /* send out client response */
599 if (client_len)
600 {
601 nntp_log_binbuf(client_out, client_len, "SASL", MUTT_SOCK_LOG_FULL);
602 if (!buf_is_empty(buf))
603 buf_addch(buf, ' ');
604 len = buf_len(buf);
605 if (sasl_encode64(client_out, client_len, buf->data + len,
606 buf->dsize - len, &len) != SASL_OK)
607 {
608 mutt_debug(LL_DEBUG1, "error base64-encoding client response\n");
609 break;
610 }
611 }
612
613 buf_addstr(buf, "\r\n");
614 if (buf_find_char(buf, ' '))
615 {
616 mutt_debug(MUTT_SOCK_LOG_CMD, "%d> AUTHINFO SASL %s%s\n", conn->fd,
617 method, client_len ? " sasl_data" : "");
618 }
619 else
620 {
621 mutt_debug(MUTT_SOCK_LOG_CMD, "%d> sasl_data\n", conn->fd);
622 }
623 client_len = 0;
624 if ((mutt_socket_send_d(conn, buf_string(buf), MUTT_SOCK_LOG_FULL) < 0) ||
625 (mutt_socket_readln_d(inbuf, sizeof(inbuf), conn, MUTT_SOCK_LOG_FULL) < 0))
626 {
627 break;
628 }
629 if (!mutt_str_startswith(inbuf, "283 ") && !mutt_str_startswith(inbuf, "383 "))
630 {
631 mutt_debug(MUTT_SOCK_LOG_FULL, "%d< %s\n", conn->fd, inbuf);
632 break;
633 }
634 inbuf[3] = '\0';
635 mutt_debug(MUTT_SOCK_LOG_FULL, "%d< %s sasl_data\n", conn->fd, inbuf);
636
637 if (mutt_str_equal("=", inbuf + 4))
638 len = 0;
639 else if (sasl_decode64(inbuf + 4, strlen(inbuf + 4), buf->data,
640 buf->dsize - 1, &len) != SASL_OK)
641 {
642 mutt_debug(LL_DEBUG1, "error base64-decoding server response\n");
643 break;
644 }
645 else
646 {
647 nntp_log_binbuf(buf_string(buf), len, "SASL", MUTT_SOCK_LOG_FULL);
648 }
649
650 while (true)
651 {
652 rc = sasl_client_step(saslconn, buf_string(buf), len, &interaction,
653 &client_out, &client_len);
654 if (rc != SASL_INTERACT)
655 break;
656 mutt_sasl_interact(interaction);
657 }
658 if (*inbuf != '3')
659 break;
660
661 buf_reset(buf);
662 } /* looping protocol */
663
664 if ((rc == SASL_OK) && (client_len == 0) && (*inbuf == '2'))
665 {
666 mutt_sasl_setup_conn(conn, saslconn);
667 buf_pool_release(&buf);
668 return 0;
669 }
670
671 /* terminate SASL session */
672 sasl_dispose(&saslconn);
673 if (conn->fd < 0)
674 break;
675 if (mutt_str_startswith(inbuf, "383 "))
676 {
677 if ((mutt_socket_send(conn, "*\r\n") < 0) ||
678 (mutt_socket_readln(inbuf, sizeof(inbuf), conn) < 0))
679 {
680 break;
681 }
682 }
683
684 /* server doesn't support AUTHINFO SASL, trying next method */
685 if (*inbuf == '5')
686 continue;
687#else
688 continue;
689#endif /* USE_SASL_CYRUS */
690 }
691
692 // L10N: %s is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
693 mutt_error(_("%s authentication failed"), method);
694 break;
695 }
696 break;
697 }
698
699 /* error */
700 adata->status = NNTP_BYE;
701 conn->account.flags = flags;
702 if (conn->fd < 0)
703 {
704 mutt_error(_("Server closed connection"));
705 }
706 else
707 {
708 mutt_socket_close(conn);
709 }
710
711 buf_pool_release(&buf);
712 return -1;
713}
714
723static int nntp_query(struct NntpMboxData *mdata, char *line, size_t linelen)
724{
725 struct NntpAccountData *adata = mdata->adata;
726 char buf[1024] = { 0 };
727
728 if (adata->status == NNTP_BYE)
729 return -1;
730
731 while (true)
732 {
733 if (adata->status == NNTP_OK)
734 {
735 int rc = 0;
736
737 if (*line)
738 {
739 rc = mutt_socket_send(adata->conn, line);
740 }
741 else if (mdata->group)
742 {
743 snprintf(buf, sizeof(buf), "GROUP %s\r\n", mdata->group);
744 rc = mutt_socket_send(adata->conn, buf);
745 }
746 if (rc >= 0)
747 rc = mutt_socket_readln(buf, sizeof(buf), adata->conn);
748 if (rc >= 0)
749 break;
750 }
751
752 /* reconnect */
753 while (true)
754 {
755 adata->status = NNTP_NONE;
756 if (nntp_open_connection(adata) == 0)
757 break;
758
759 snprintf(buf, sizeof(buf), _("Connection to %s lost. Reconnect?"),
760 adata->conn->account.host);
761 if (query_yesorno(buf, MUTT_YES) != MUTT_YES)
762 {
763 adata->status = NNTP_BYE;
764 return -1;
765 }
766 }
767
768 /* select newsgroup after reconnection */
769 if (mdata->group)
770 {
771 snprintf(buf, sizeof(buf), "GROUP %s\r\n", mdata->group);
772 if ((mutt_socket_send(adata->conn, buf) < 0) ||
773 (mutt_socket_readln(buf, sizeof(buf), adata->conn) < 0))
774 {
776 }
777 }
778 if (*line == '\0')
779 break;
780 }
781
782 mutt_str_copy(line, buf, linelen);
783 return 0;
784}
785
802static int nntp_fetch_lines(struct NntpMboxData *mdata, char *query, size_t qlen,
803 const char *msg, int (*func)(char *, void *), void *data)
804{
805 bool done = false;
806 int rc;
807
808 while (!done)
809 {
810 char buf[1024] = { 0 };
811 char *line = NULL;
812 unsigned int lines = 0;
813 size_t off = 0;
814 struct Progress *progress = NULL;
815
816 mutt_str_copy(buf, query, sizeof(buf));
817 if (nntp_query(mdata, buf, sizeof(buf)) < 0)
818 return -1;
819 if (buf[0] != '2')
820 {
821 mutt_str_copy(query, buf, qlen);
822 return 1;
823 }
824
825 line = mutt_mem_malloc(sizeof(buf));
826 rc = 0;
827
828 if (msg)
829 {
830 progress = progress_new(MUTT_PROGRESS_READ, 0);
831 progress_set_message(progress, "%s", msg);
832 }
833
834 while (true)
835 {
836 char *p = NULL;
837 int chunk = mutt_socket_readln_d(buf, sizeof(buf), mdata->adata->conn, MUTT_SOCK_LOG_FULL);
838 if (chunk < 0)
839 {
840 mdata->adata->status = NNTP_NONE;
841 break;
842 }
843
844 p = buf;
845 if (!off && (buf[0] == '.'))
846 {
847 if (buf[1] == '\0')
848 {
849 done = true;
850 break;
851 }
852 if (buf[1] == '.')
853 p++;
854 }
855
856 mutt_str_copy(line + off, p, sizeof(buf));
857
858 if (chunk >= sizeof(buf))
859 {
860 off += strlen(p);
861 }
862 else
863 {
864 progress_update(progress, ++lines, -1);
865
866 if ((rc == 0) && (func(line, data) < 0))
867 rc = -2;
868 off = 0;
869 }
870
871 mutt_mem_realloc(&line, off + sizeof(buf));
872 }
873 FREE(&line);
874 func(NULL, data);
875 progress_free(&progress);
876 }
877
878 return rc;
879}
880
887static int fetch_description(char *line, void *data)
888{
889 if (!line)
890 return 0;
891
892 struct NntpAccountData *adata = data;
893
894 char *desc = strpbrk(line, " \t");
895 if (desc)
896 {
897 *desc++ = '\0';
898 desc += strspn(desc, " \t");
899 }
900 else
901 {
902 desc = strchr(line, '\0');
903 }
904
906 if (mdata && !mutt_str_equal(desc, mdata->desc))
907 {
908 mutt_str_replace(&mdata->desc, desc);
909 mutt_debug(LL_DEBUG2, "group: %s, desc: %s\n", line, desc);
910 }
911 return 0;
912}
913
924static int get_description(struct NntpMboxData *mdata, const char *wildmat, const char *msg)
925{
926 char buf[256] = { 0 };
927 const char *cmd = NULL;
928
929 /* get newsgroup description, if possible */
930 struct NntpAccountData *adata = mdata->adata;
931 if (!wildmat)
932 wildmat = mdata->group;
933 if (adata->hasLIST_NEWSGROUPS)
934 cmd = "LIST NEWSGROUPS";
935 else if (adata->hasXGTITLE)
936 cmd = "XGTITLE";
937 else
938 return 0;
939
940 snprintf(buf, sizeof(buf), "%s %s\r\n", cmd, wildmat);
941 int rc = nntp_fetch_lines(mdata, buf, sizeof(buf), msg, fetch_description, adata);
942 if (rc > 0)
943 {
944 mutt_error("%s: %s", cmd, buf);
945 }
946 return rc;
947}
948
956static void nntp_parse_xref(struct Mailbox *m, struct Email *e)
957{
958 struct NntpMboxData *mdata = m->mdata;
959
960 char *buf = mutt_str_dup(e->env->xref);
961 char *p = buf;
962 while (p)
963 {
964 anum_t anum;
965
966 /* skip to next word */
967 p += strspn(p, " \t");
968 char *grp = p;
969
970 /* skip to end of word */
971 p = strpbrk(p, " \t");
972 if (p)
973 *p++ = '\0';
974
975 /* find colon */
976 char *colon = strchr(grp, ':');
977 if (!colon)
978 continue;
979 *colon++ = '\0';
980 if (sscanf(colon, ANUM_FMT, &anum) != 1)
981 continue;
982
983 nntp_article_status(m, e, grp, anum);
984 if (!nntp_edata_get(e)->article_num && mutt_str_equal(mdata->group, grp))
985 nntp_edata_get(e)->article_num = anum;
986 }
987 FREE(&buf);
988}
989
997static int fetch_tempfile(char *line, void *data)
998{
999 FILE *fp = data;
1000
1001 if (!line)
1002 rewind(fp);
1003 else if ((fputs(line, fp) == EOF) || (fputc('\n', fp) == EOF))
1004 return -1;
1005 return 0;
1006}
1007
1014static int fetch_numbers(char *line, void *data)
1015{
1016 struct FetchCtx *fc = data;
1017 anum_t anum;
1018
1019 if (!line)
1020 return 0;
1021 if (sscanf(line, ANUM_FMT, &anum) != 1)
1022 return 0;
1023 if ((anum < fc->first) || (anum > fc->last))
1024 return 0;
1025 fc->messages[anum - fc->first] = 1;
1026 return 0;
1027}
1028
1036static int parse_overview_line(char *line, void *data)
1037{
1038 if (!line || !data)
1039 return 0;
1040
1041 struct FetchCtx *fc = data;
1042 struct Mailbox *m = fc->mailbox;
1043 if (!m)
1044 return -1;
1045
1046 struct NntpMboxData *mdata = m->mdata;
1047 struct Email *e = NULL;
1048 char *header = NULL, *field = NULL;
1049 bool save = true;
1050 anum_t anum;
1051
1052 /* parse article number */
1053 field = strchr(line, '\t');
1054 if (field)
1055 *field++ = '\0';
1056 if (sscanf(line, ANUM_FMT, &anum) != 1)
1057 return 0;
1058 mutt_debug(LL_DEBUG2, "" ANUM_FMT "\n", anum);
1059
1060 /* out of bounds */
1061 if ((anum < fc->first) || (anum > fc->last))
1062 return 0;
1063
1064 /* not in LISTGROUP */
1065 if (!fc->messages[anum - fc->first])
1066 {
1067 progress_update(fc->progress, anum - fc->first + 1, -1);
1068 return 0;
1069 }
1070
1071 /* convert overview line to header */
1072 FILE *fp = mutt_file_mkstemp();
1073 if (!fp)
1074 return -1;
1075
1076 header = mdata->adata->overview_fmt;
1077 while (field)
1078 {
1079 char *b = field;
1080
1081 if (*header)
1082 {
1083 if (!strstr(header, ":full") && (fputs(header, fp) == EOF))
1084 {
1085 mutt_file_fclose(&fp);
1086 return -1;
1087 }
1088 header = strchr(header, '\0') + 1;
1089 }
1090
1091 field = strchr(field, '\t');
1092 if (field)
1093 *field++ = '\0';
1094 if ((fputs(b, fp) == EOF) || (fputc('\n', fp) == EOF))
1095 {
1096 mutt_file_fclose(&fp);
1097 return -1;
1098 }
1099 }
1100 rewind(fp);
1101
1102 /* allocate memory for headers */
1104
1105 /* parse header */
1106 m->emails[m->msg_count] = email_new();
1107 e = m->emails[m->msg_count];
1108 e->env = mutt_rfc822_read_header(fp, e, false, false);
1109 e->env->newsgroups = mutt_str_dup(mdata->group);
1110 e->received = e->date_sent;
1111 mutt_file_fclose(&fp);
1112
1113#ifdef USE_HCACHE
1114 if (fc->hc)
1115 {
1116 char buf[16] = { 0 };
1117
1118 /* try to replace with header from cache */
1119 snprintf(buf, sizeof(buf), ANUM_FMT, anum);
1120 struct HCacheEntry hce = hcache_fetch_email(fc->hc, buf, strlen(buf), 0);
1121 if (hce.email)
1122 {
1123 mutt_debug(LL_DEBUG2, "hcache_fetch_email %s\n", buf);
1124 email_free(&e);
1125 e = hce.email;
1126 m->emails[m->msg_count] = e;
1127 e->edata = NULL;
1128 e->read = false;
1129 e->old = false;
1130
1131 /* skip header marked as deleted in cache */
1132 if (e->deleted && !fc->restore)
1133 {
1134 if (mdata->bcache)
1135 {
1136 mutt_debug(LL_DEBUG2, "mutt_bcache_del %s\n", buf);
1137 mutt_bcache_del(mdata->bcache, buf);
1138 }
1139 save = false;
1140 }
1141 }
1142 else
1143 {
1144 /* not cached yet, store header */
1145 mutt_debug(LL_DEBUG2, "hcache_store_email %s\n", buf);
1146 hcache_store_email(fc->hc, buf, strlen(buf), e, 0);
1147 }
1148 }
1149#endif
1150
1151 if (save)
1152 {
1153 e->index = m->msg_count++;
1154 e->read = false;
1155 e->old = false;
1156 e->deleted = false;
1157 e->edata = nntp_edata_new();
1159 nntp_edata_get(e)->article_num = anum;
1160 if (fc->restore)
1161 {
1162 e->changed = true;
1163 }
1164 else
1165 {
1166 nntp_article_status(m, e, NULL, anum);
1167 if (!e->read)
1168 nntp_parse_xref(m, e);
1169 }
1170 if (anum > mdata->last_loaded)
1171 mdata->last_loaded = anum;
1172 }
1173 else
1174 {
1175 email_free(&e);
1176 }
1177
1178 progress_update(fc->progress, anum - fc->first + 1, -1);
1179 return 0;
1180}
1181
1192static int nntp_fetch_headers(struct Mailbox *m, void *hc, anum_t first, anum_t last, bool restore)
1193{
1194 if (!m)
1195 return -1;
1196
1197 struct NntpMboxData *mdata = m->mdata;
1198 struct FetchCtx fc = { 0 };
1199 struct Email *e = NULL;
1200 char buf[8192] = { 0 };
1201 int rc = 0;
1202 anum_t current;
1203 anum_t first_over = first;
1204
1205 /* if empty group or nothing to do */
1206 if (!last || (first > last))
1207 return 0;
1208
1209 /* init fetch context */
1210 fc.mailbox = m;
1211 fc.first = first;
1212 fc.last = last;
1213 fc.restore = restore;
1214 fc.messages = mutt_mem_calloc(last - first + 1, sizeof(unsigned char));
1215 if (!fc.messages)
1216 return -1;
1217 fc.hc = hc;
1218
1219 /* fetch list of articles */
1220 const bool c_nntp_listgroup = cs_subset_bool(NeoMutt->sub, "nntp_listgroup");
1221 if (c_nntp_listgroup && mdata->adata->hasLISTGROUP && !mdata->deleted)
1222 {
1223 if (m->verbose)
1224 mutt_message(_("Fetching list of articles..."));
1225 if (mdata->adata->hasLISTGROUPrange)
1226 {
1227 snprintf(buf, sizeof(buf), "LISTGROUP %s " ANUM_FMT "-" ANUM_FMT "\r\n",
1228 mdata->group, first, last);
1229 }
1230 else
1231 {
1232 snprintf(buf, sizeof(buf), "LISTGROUP %s\r\n", mdata->group);
1233 }
1234 rc = nntp_fetch_lines(mdata, buf, sizeof(buf), NULL, fetch_numbers, &fc);
1235 if (rc > 0)
1236 {
1237 mutt_error("LISTGROUP: %s", buf);
1238 }
1239 if (rc == 0)
1240 {
1241 for (current = first; (current <= last); current++)
1242 {
1243 if (fc.messages[current - first])
1244 continue;
1245
1246 snprintf(buf, sizeof(buf), ANUM_FMT, current);
1247 if (mdata->bcache)
1248 {
1249 mutt_debug(LL_DEBUG2, "#1 mutt_bcache_del %s\n", buf);
1250 mutt_bcache_del(mdata->bcache, buf);
1251 }
1252
1253#ifdef USE_HCACHE
1254 if (fc.hc)
1255 {
1256 mutt_debug(LL_DEBUG2, "hcache_delete_email %s\n", buf);
1257 hcache_delete_email(fc.hc, buf, strlen(buf));
1258 }
1259#endif
1260 }
1261 }
1262 }
1263 else
1264 {
1265 for (current = first; current <= last; current++)
1266 fc.messages[current - first] = 1;
1267 }
1268
1269 /* fetching header from cache or server, or fallback to fetch overview */
1270 if (m->verbose)
1271 {
1272 fc.progress = progress_new(MUTT_PROGRESS_READ, last - first + 1);
1273 progress_set_message(fc.progress, _("Fetching message headers..."));
1274 }
1275 for (current = first; (current <= last) && (rc == 0); current++)
1276 {
1277 progress_update(fc.progress, current - first + 1, -1);
1278
1279#ifdef USE_HCACHE
1280 snprintf(buf, sizeof(buf), ANUM_FMT, current);
1281#endif
1282
1283 /* delete header from cache that does not exist on server */
1284 if (!fc.messages[current - first])
1285 continue;
1286
1287 /* allocate memory for headers */
1289
1290#ifdef USE_HCACHE
1291 /* try to fetch header from cache */
1292 struct HCacheEntry hce = hcache_fetch_email(fc.hc, buf, strlen(buf), 0);
1293 if (hce.email)
1294 {
1295 mutt_debug(LL_DEBUG2, "hcache_fetch_email %s\n", buf);
1296 e = hce.email;
1297 m->emails[m->msg_count] = e;
1298 e->edata = NULL;
1299
1300 /* skip header marked as deleted in cache */
1301 if (e->deleted && !restore)
1302 {
1303 email_free(&e);
1304 if (mdata->bcache)
1305 {
1306 mutt_debug(LL_DEBUG2, "#2 mutt_bcache_del %s\n", buf);
1307 mutt_bcache_del(mdata->bcache, buf);
1308 }
1309 continue;
1310 }
1311
1312 e->read = false;
1313 e->old = false;
1314 }
1315 else
1316#endif
1317 if (mdata->deleted)
1318 {
1319 /* don't try to fetch header from removed newsgroup */
1320 continue;
1321 }
1322 else if (mdata->adata->hasOVER || mdata->adata->hasXOVER)
1323 {
1324 /* fallback to fetch overview */
1325 if (c_nntp_listgroup && mdata->adata->hasLISTGROUP)
1326 break;
1327 else
1328 continue;
1329 }
1330 else
1331 {
1332 /* fetch header from server */
1333 FILE *fp = mutt_file_mkstemp();
1334 if (!fp)
1335 {
1336 mutt_perror(_("Can't create temporary file"));
1337 rc = -1;
1338 break;
1339 }
1340
1341 snprintf(buf, sizeof(buf), "HEAD " ANUM_FMT "\r\n", current);
1342 rc = nntp_fetch_lines(mdata, buf, sizeof(buf), NULL, fetch_tempfile, fp);
1343 if (rc)
1344 {
1345 mutt_file_fclose(&fp);
1346 if (rc < 0)
1347 break;
1348
1349 /* invalid response */
1350 if (!mutt_str_startswith(buf, "423"))
1351 {
1352 mutt_error("HEAD: %s", buf);
1353 break;
1354 }
1355
1356 /* no such article */
1357 if (mdata->bcache)
1358 {
1359 snprintf(buf, sizeof(buf), ANUM_FMT, current);
1360 mutt_debug(LL_DEBUG2, "#3 mutt_bcache_del %s\n", buf);
1361 mutt_bcache_del(mdata->bcache, buf);
1362 }
1363 rc = 0;
1364 continue;
1365 }
1366
1367 /* parse header */
1368 m->emails[m->msg_count] = email_new();
1369 e = m->emails[m->msg_count];
1370 e->env = mutt_rfc822_read_header(fp, e, false, false);
1371 e->received = e->date_sent;
1372 mutt_file_fclose(&fp);
1373 }
1374
1375 /* save header in context */
1376 e->index = m->msg_count++;
1377 e->read = false;
1378 e->old = false;
1379 e->deleted = false;
1380 e->edata = nntp_edata_new();
1382 nntp_edata_get(e)->article_num = current;
1383 if (restore)
1384 {
1385 e->changed = true;
1386 }
1387 else
1388 {
1389 nntp_article_status(m, e, NULL, nntp_edata_get(e)->article_num);
1390 if (!e->read)
1391 nntp_parse_xref(m, e);
1392 }
1393 if (current > mdata->last_loaded)
1394 mdata->last_loaded = current;
1395 first_over = current + 1;
1396 }
1397
1398 if (!c_nntp_listgroup || !mdata->adata->hasLISTGROUP)
1399 current = first_over;
1400
1401 /* fetch overview information */
1402 if ((current <= last) && (rc == 0) && !mdata->deleted)
1403 {
1404 char *cmd = mdata->adata->hasOVER ? "OVER" : "XOVER";
1405 snprintf(buf, sizeof(buf), "%s " ANUM_FMT "-" ANUM_FMT "\r\n", cmd, current, last);
1406 rc = nntp_fetch_lines(mdata, buf, sizeof(buf), NULL, parse_overview_line, &fc);
1407 if (rc > 0)
1408 {
1409 mutt_error("%s: %s", cmd, buf);
1410 }
1411 }
1412
1413 FREE(&fc.messages);
1415 if (rc != 0)
1416 return -1;
1418 return 0;
1419}
1420
1429static int nntp_group_poll(struct NntpMboxData *mdata, bool update_stat)
1430{
1431 char buf[1024] = { 0 };
1432 anum_t count, first, last;
1433
1434 /* use GROUP command to poll newsgroup */
1435 if (nntp_query(mdata, buf, sizeof(buf)) < 0)
1436 return -1;
1437 if (sscanf(buf, "211 " ANUM_FMT " " ANUM_FMT " " ANUM_FMT, &count, &first, &last) != 3)
1438 return 0;
1439 if ((first == mdata->first_message) && (last == mdata->last_message))
1440 return 0;
1441
1442 /* articles have been renumbered */
1443 if (last < mdata->last_message)
1444 {
1445 mdata->last_cached = 0;
1446 if (mdata->newsrc_len)
1447 {
1448 mutt_mem_realloc(&mdata->newsrc_ent, sizeof(struct NewsrcEntry));
1449 mdata->newsrc_len = 1;
1450 mdata->newsrc_ent[0].first = 1;
1451 mdata->newsrc_ent[0].last = 0;
1452 }
1453 }
1454 mdata->first_message = first;
1455 mdata->last_message = last;
1456 if (!update_stat)
1457 {
1458 return 1;
1459 }
1460 else if (!last || (!mdata->newsrc_ent && !mdata->last_cached))
1461 {
1462 /* update counters */
1463 mdata->unread = count;
1464 }
1465 else
1466 {
1468 }
1469 return 1;
1470}
1471
1479static enum MxStatus check_mailbox(struct Mailbox *m)
1480{
1481 if (!m || !m->mdata)
1482 return MX_STATUS_ERROR;
1483
1484 struct NntpMboxData *mdata = m->mdata;
1485 struct NntpAccountData *adata = mdata->adata;
1486 time_t now = mutt_date_now();
1487 enum MxStatus rc = MX_STATUS_OK;
1488 struct HeaderCache *hc = NULL;
1489
1490 const short c_nntp_poll = cs_subset_number(NeoMutt->sub, "nntp_poll");
1491 if (adata->check_time + c_nntp_poll > now)
1492 return MX_STATUS_OK;
1493
1494 mutt_message(_("Checking for new messages..."));
1495 if (nntp_newsrc_parse(adata) < 0)
1496 return MX_STATUS_ERROR;
1497
1498 adata->check_time = now;
1499 int rc2 = nntp_group_poll(mdata, false);
1500 if (rc2 < 0)
1501 {
1502 nntp_newsrc_close(adata);
1503 return -1;
1504 }
1505 if (rc2 != 0)
1507
1508 /* articles have been renumbered, remove all emails */
1509 if (mdata->last_message < mdata->last_loaded)
1510 {
1511 for (int i = 0; i < m->msg_count; i++)
1512 email_free(&m->emails[i]);
1513 m->msg_count = 0;
1514 m->msg_tagged = 0;
1515
1516 mdata->last_loaded = mdata->first_message - 1;
1517 const long c_nntp_context = cs_subset_long(NeoMutt->sub, "nntp_context");
1518 if (c_nntp_context && (mdata->last_message - mdata->last_loaded > c_nntp_context))
1519 mdata->last_loaded = mdata->last_message - c_nntp_context;
1520
1521 rc = MX_STATUS_REOPENED;
1522 }
1523
1524 /* .newsrc has been externally modified */
1525 if (adata->newsrc_modified)
1526 {
1527#ifdef USE_HCACHE
1528 unsigned char *messages = NULL;
1529 char buf[16] = { 0 };
1530 struct Email *e = NULL;
1531 anum_t first = mdata->first_message;
1532
1533 const long c_nntp_context = cs_subset_long(NeoMutt->sub, "nntp_context");
1534 if (c_nntp_context && ((mdata->last_message - first + 1) > c_nntp_context))
1535 first = mdata->last_message - c_nntp_context + 1;
1536 messages = mutt_mem_calloc(mdata->last_loaded - first + 1, sizeof(unsigned char));
1537 hc = nntp_hcache_open(mdata);
1538 nntp_hcache_update(mdata, hc);
1539#endif
1540
1541 /* update flags according to .newsrc */
1542 int j = 0;
1543 for (int i = 0; i < m->msg_count; i++)
1544 {
1545 if (!m->emails[i])
1546 continue;
1547 bool flagged = false;
1548 anum_t anum = nntp_edata_get(m->emails[i])->article_num;
1549
1550#ifdef USE_HCACHE
1551 /* check hcache for flagged and deleted flags */
1552 if (hc)
1553 {
1554 if ((anum >= first) && (anum <= mdata->last_loaded))
1555 messages[anum - first] = 1;
1556
1557 snprintf(buf, sizeof(buf), ANUM_FMT, anum);
1558 struct HCacheEntry hce = hcache_fetch_email(hc, buf, strlen(buf), 0);
1559 if (hce.email)
1560 {
1561 bool deleted;
1562
1563 mutt_debug(LL_DEBUG2, "#1 hcache_fetch_email %s\n", buf);
1564 e = hce.email;
1565 e->edata = NULL;
1566 deleted = e->deleted;
1567 flagged = e->flagged;
1568 email_free(&e);
1569
1570 /* header marked as deleted, removing from context */
1571 if (deleted)
1572 {
1573 mutt_set_flag(m, m->emails[i], MUTT_TAG, false, true);
1574 email_free(&m->emails[i]);
1575 continue;
1576 }
1577 }
1578 }
1579#endif
1580
1581 if (!m->emails[i]->changed)
1582 {
1583 m->emails[i]->flagged = flagged;
1584 m->emails[i]->read = false;
1585 m->emails[i]->old = false;
1586 nntp_article_status(m, m->emails[i], NULL, anum);
1587 if (!m->emails[i]->read)
1588 nntp_parse_xref(m, m->emails[i]);
1589 }
1590 m->emails[j++] = m->emails[i];
1591 }
1592
1593#ifdef USE_HCACHE
1594 m->msg_count = j;
1595
1596 /* restore headers without "deleted" flag */
1597 for (anum_t anum = first; anum <= mdata->last_loaded; anum++)
1598 {
1599 if (messages[anum - first])
1600 continue;
1601
1602 snprintf(buf, sizeof(buf), ANUM_FMT, anum);
1603 struct HCacheEntry hce = hcache_fetch_email(hc, buf, strlen(buf), 0);
1604 if (hce.email)
1605 {
1606 mutt_debug(LL_DEBUG2, "#2 hcache_fetch_email %s\n", buf);
1608
1609 e = hce.email;
1610 m->emails[m->msg_count] = e;
1611 e->edata = NULL;
1612 if (e->deleted)
1613 {
1614 email_free(&e);
1615 if (mdata->bcache)
1616 {
1617 mutt_debug(LL_DEBUG2, "mutt_bcache_del %s\n", buf);
1618 mutt_bcache_del(mdata->bcache, buf);
1619 }
1620 continue;
1621 }
1622
1623 m->msg_count++;
1624 e->read = false;
1625 e->old = false;
1626 e->edata = nntp_edata_new();
1628 nntp_edata_get(e)->article_num = anum;
1629 nntp_article_status(m, e, NULL, anum);
1630 if (!e->read)
1631 nntp_parse_xref(m, e);
1632 }
1633 }
1634 FREE(&messages);
1635#endif
1636
1637 adata->newsrc_modified = false;
1638 rc = MX_STATUS_REOPENED;
1639 }
1640
1641 /* some emails were removed, mailboxview must be updated */
1642 if (rc == MX_STATUS_REOPENED)
1644
1645 /* fetch headers of new articles */
1646 if (mdata->last_message > mdata->last_loaded)
1647 {
1648 int oldmsgcount = m->msg_count;
1649 bool verbose = m->verbose;
1650 m->verbose = false;
1651#ifdef USE_HCACHE
1652 if (!hc)
1653 {
1654 hc = nntp_hcache_open(mdata);
1655 nntp_hcache_update(mdata, hc);
1656 }
1657#endif
1658 int old_msg_count = m->msg_count;
1659 rc2 = nntp_fetch_headers(m, hc, mdata->last_loaded + 1, mdata->last_message, false);
1660 m->verbose = verbose;
1661 if (rc2 == 0)
1662 {
1663 if (m->msg_count > old_msg_count)
1665 mdata->last_loaded = mdata->last_message;
1666 }
1667 if ((rc == MX_STATUS_OK) && (m->msg_count > oldmsgcount))
1668 rc = MX_STATUS_NEW_MAIL;
1669 }
1670
1671#ifdef USE_HCACHE
1672 hcache_close(&hc);
1673#endif
1674 if (rc != MX_STATUS_OK)
1675 nntp_newsrc_close(adata);
1677 return rc;
1678}
1679
1687static int nntp_date(struct NntpAccountData *adata, time_t *now)
1688{
1689 if (adata->hasDATE)
1690 {
1691 struct NntpMboxData mdata = { 0 };
1692 char buf[1024] = { 0 };
1693 struct tm tm = { 0 };
1694
1695 mdata.adata = adata;
1696 mdata.group = NULL;
1697 mutt_str_copy(buf, "DATE\r\n", sizeof(buf));
1698 if (nntp_query(&mdata, buf, sizeof(buf)) < 0)
1699 return -1;
1700
1701 if (sscanf(buf, "111 %4d%2d%2d%2d%2d%2d%*s", &tm.tm_year, &tm.tm_mon,
1702 &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) == 6)
1703 {
1704 tm.tm_year -= 1900;
1705 tm.tm_mon--;
1706 *now = timegm(&tm);
1707 if (*now >= 0)
1708 {
1709 mutt_debug(LL_DEBUG1, "server time is %llu\n", (unsigned long long) *now);
1710 return 0;
1711 }
1712 }
1713 }
1714 *now = mutt_date_now();
1715 return 0;
1716}
1717
1724static int fetch_children(char *line, void *data)
1725{
1726 struct ChildCtx *cc = data;
1727 anum_t anum;
1728
1729 if (!line || (sscanf(line, ANUM_FMT, &anum) != 1))
1730 return 0;
1731 for (unsigned int i = 0; i < cc->mailbox->msg_count; i++)
1732 {
1733 struct Email *e = cc->mailbox->emails[i];
1734 if (!e)
1735 break;
1736 if (nntp_edata_get(e)->article_num == anum)
1737 return 0;
1738 }
1739 if (cc->num >= cc->max)
1740 {
1741 cc->max *= 2;
1742 mutt_mem_realloc(&cc->child, sizeof(anum_t) * cc->max);
1743 }
1744 cc->child[cc->num++] = anum;
1745 return 0;
1746}
1747
1755{
1756 struct Connection *conn = adata->conn;
1757 char buf[256] = { 0 };
1758 int cap;
1759 bool posting = false, auth = true;
1760
1761 if (adata->status == NNTP_OK)
1762 return 0;
1763 if (adata->status == NNTP_BYE)
1764 return -1;
1765 adata->status = NNTP_NONE;
1766
1767 if (mutt_socket_open(conn) < 0)
1768 return -1;
1769
1770 if (mutt_socket_readln(buf, sizeof(buf), conn) < 0)
1771 return nntp_connect_error(adata);
1772
1773 if (mutt_str_startswith(buf, "200"))
1774 {
1775 posting = true;
1776 }
1777 else if (!mutt_str_startswith(buf, "201"))
1778 {
1779 mutt_socket_close(conn);
1781 mutt_error("%s", buf);
1782 return -1;
1783 }
1784
1785 /* get initial capabilities */
1786 cap = nntp_capabilities(adata);
1787 if (cap < 0)
1788 return -1;
1789
1790 /* tell news server to switch to mode reader if it isn't so */
1791 if (cap > 0)
1792 {
1793 if ((mutt_socket_send(conn, "MODE READER\r\n") < 0) ||
1794 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
1795 {
1796 return nntp_connect_error(adata);
1797 }
1798
1799 if (mutt_str_startswith(buf, "200"))
1800 {
1801 posting = true;
1802 }
1803 else if (mutt_str_startswith(buf, "201"))
1804 {
1805 posting = false;
1806 }
1807 else if (adata->hasCAPABILITIES)
1808 {
1809 /* error if has capabilities, ignore result if no capabilities */
1810 mutt_socket_close(conn);
1811 mutt_error(_("Could not switch to reader mode"));
1812 return -1;
1813 }
1814
1815 /* recheck capabilities after MODE READER */
1816 if (adata->hasCAPABILITIES)
1817 {
1818 cap = nntp_capabilities(adata);
1819 if (cap < 0)
1820 return -1;
1821 }
1822 }
1823
1824 mutt_message(_("Connected to %s. %s"), conn->account.host,
1825 posting ? _("Posting is ok") : _("Posting is NOT ok"));
1826 mutt_sleep(1);
1827
1828#ifdef USE_SSL
1829 /* Attempt STARTTLS if available and desired. */
1830 const bool c_ssl_force_tls = cs_subset_bool(NeoMutt->sub, "ssl_force_tls");
1831 if ((adata->use_tls != 1) && (adata->hasSTARTTLS || c_ssl_force_tls))
1832 {
1833 if (adata->use_tls == 0)
1834 {
1835 adata->use_tls = c_ssl_force_tls ||
1836 (query_quadoption(_("Secure connection with TLS?"),
1837 NeoMutt->sub, "ssl_starttls") == MUTT_YES) ?
1838 2 :
1839 1;
1840 }
1841 if (adata->use_tls == 2)
1842 {
1843 if ((mutt_socket_send(conn, "STARTTLS\r\n") < 0) ||
1844 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
1845 {
1846 return nntp_connect_error(adata);
1847 }
1848 // Clear any data after the STARTTLS acknowledgement
1849 mutt_socket_empty(conn);
1850 if (!mutt_str_startswith(buf, "382"))
1851 {
1852 adata->use_tls = 0;
1853 mutt_error("STARTTLS: %s", buf);
1854 }
1855 else if (mutt_ssl_starttls(conn))
1856 {
1857 adata->use_tls = 0;
1858 adata->status = NNTP_NONE;
1859 mutt_socket_close(adata->conn);
1860 mutt_error(_("Could not negotiate TLS connection"));
1861 return -1;
1862 }
1863 else
1864 {
1865 /* recheck capabilities after STARTTLS */
1866 cap = nntp_capabilities(adata);
1867 if (cap < 0)
1868 return -1;
1869 }
1870 }
1871 }
1872#endif
1873
1874 /* authentication required? */
1875 if (conn->account.flags & MUTT_ACCT_USER)
1876 {
1877 if (!conn->account.user[0])
1878 auth = false;
1879 }
1880 else
1881 {
1882 if ((mutt_socket_send(conn, "STAT\r\n") < 0) ||
1883 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
1884 {
1885 return nntp_connect_error(adata);
1886 }
1887 if (!mutt_str_startswith(buf, "480"))
1888 auth = false;
1889 }
1890
1891 /* authenticate */
1892 if (auth && (nntp_auth(adata) < 0))
1893 return -1;
1894
1895 /* get final capabilities after authentication */
1896 if (adata->hasCAPABILITIES && (auth || (cap > 0)))
1897 {
1898 cap = nntp_capabilities(adata);
1899 if (cap < 0)
1900 return -1;
1901 if (cap > 0)
1902 {
1903 mutt_socket_close(conn);
1904 mutt_error(_("Could not switch to reader mode"));
1905 return -1;
1906 }
1907 }
1908
1909 /* attempt features */
1910 if (nntp_attempt_features(adata) < 0)
1911 return -1;
1912
1913 adata->status = NNTP_OK;
1914 return 0;
1915}
1916
1924int nntp_post(struct Mailbox *m, const char *msg)
1925{
1926 struct NntpMboxData *mdata = NULL;
1927 struct NntpMboxData tmp_mdata = { 0 };
1928 char buf[1024] = { 0 };
1929
1930 if (m && (m->type == MUTT_NNTP))
1931 {
1932 mdata = m->mdata;
1933 }
1934 else
1935 {
1936 const char *const c_news_server = cs_subset_string(NeoMutt->sub, "news_server");
1937 CurrentNewsSrv = nntp_select_server(m, c_news_server, false);
1938 if (!CurrentNewsSrv)
1939 return -1;
1940
1941 mdata = &tmp_mdata;
1942 mdata->adata = CurrentNewsSrv;
1943 mdata->group = NULL;
1944 }
1945
1946 FILE *fp = mutt_file_fopen(msg, "r");
1947 if (!fp)
1948 {
1949 mutt_perror("%s", msg);
1950 return -1;
1951 }
1952
1953 mutt_str_copy(buf, "POST\r\n", sizeof(buf));
1954 if (nntp_query(mdata, buf, sizeof(buf)) < 0)
1955 {
1956 mutt_file_fclose(&fp);
1957 return -1;
1958 }
1959 if (buf[0] != '3')
1960 {
1961 mutt_error(_("Can't post article: %s"), buf);
1962 mutt_file_fclose(&fp);
1963 return -1;
1964 }
1965
1966 buf[0] = '.';
1967 buf[1] = '\0';
1968 while (fgets(buf + 1, sizeof(buf) - 2, fp))
1969 {
1970 size_t len = strlen(buf);
1971 if (buf[len - 1] == '\n')
1972 {
1973 buf[len - 1] = '\r';
1974 buf[len] = '\n';
1975 len++;
1976 buf[len] = '\0';
1977 }
1978 if (mutt_socket_send_d(mdata->adata->conn, (buf[1] == '.') ? buf : buf + 1,
1979 MUTT_SOCK_LOG_FULL) < 0)
1980 {
1981 mutt_file_fclose(&fp);
1982 return nntp_connect_error(mdata->adata);
1983 }
1984 }
1985 mutt_file_fclose(&fp);
1986
1987 if (((buf[strlen(buf) - 1] != '\n') &&
1988 (mutt_socket_send_d(mdata->adata->conn, "\r\n", MUTT_SOCK_LOG_FULL) < 0)) ||
1989 (mutt_socket_send_d(mdata->adata->conn, ".\r\n", MUTT_SOCK_LOG_FULL) < 0) ||
1990 (mutt_socket_readln(buf, sizeof(buf), mdata->adata->conn) < 0))
1991 {
1992 return nntp_connect_error(mdata->adata);
1993 }
1994 if (buf[0] != '2')
1995 {
1996 mutt_error(_("Can't post article: %s"), buf);
1997 return -1;
1998 }
1999 return 0;
2000}
2001
2009int nntp_active_fetch(struct NntpAccountData *adata, bool mark_new)
2010{
2011 struct NntpMboxData tmp_mdata = { 0 };
2012 char msg[256] = { 0 };
2013 char buf[1024] = { 0 };
2014 unsigned int i;
2015 int rc;
2016
2017 snprintf(msg, sizeof(msg), _("Loading list of groups from server %s..."),
2019 mutt_message("%s", msg);
2020 if (nntp_date(adata, &adata->newgroups_time) < 0)
2021 return -1;
2022
2023 tmp_mdata.adata = adata;
2024 tmp_mdata.group = NULL;
2025 i = adata->groups_num;
2026 mutt_str_copy(buf, "LIST\r\n", sizeof(buf));
2027 rc = nntp_fetch_lines(&tmp_mdata, buf, sizeof(buf), msg, nntp_add_group, adata);
2028 if (rc)
2029 {
2030 if (rc > 0)
2031 {
2032 mutt_error("LIST: %s", buf);
2033 }
2034 return -1;
2035 }
2036
2037 if (mark_new)
2038 {
2039 for (; i < adata->groups_num; i++)
2040 {
2041 struct NntpMboxData *mdata = adata->groups_list[i];
2042 mdata->has_new_mail = true;
2043 }
2044 }
2045
2046 for (i = 0; i < adata->groups_num; i++)
2047 {
2048 struct NntpMboxData *mdata = adata->groups_list[i];
2049
2050 if (mdata && mdata->deleted && !mdata->newsrc_ent)
2051 {
2053 mutt_hash_delete(adata->groups_hash, mdata->group, NULL);
2054 adata->groups_list[i] = NULL;
2055 }
2056 }
2057
2058 const bool c_nntp_load_description = cs_subset_bool(NeoMutt->sub, "nntp_load_description");
2059 if (c_nntp_load_description)
2060 rc = get_description(&tmp_mdata, "*", _("Loading descriptions..."));
2061
2063 if (rc < 0)
2064 return -1;
2066 return 0;
2067}
2068
2078{
2079 struct NntpMboxData tmp_mdata = { 0 };
2080 time_t now = 0;
2081 char buf[1024] = { 0 };
2082 char *msg = _("Checking for new newsgroups...");
2083 unsigned int i;
2084 int rc, update_active = false;
2085
2086 if (!adata || !adata->newgroups_time)
2087 return -1;
2088
2089 /* check subscribed newsgroups for new articles */
2090 const bool c_show_new_news = cs_subset_bool(NeoMutt->sub, "show_new_news");
2091 if (c_show_new_news)
2092 {
2093 mutt_message(_("Checking for new messages..."));
2094 for (i = 0; i < adata->groups_num; i++)
2095 {
2096 struct NntpMboxData *mdata = adata->groups_list[i];
2097
2098 if (mdata && mdata->subscribed)
2099 {
2100 rc = nntp_group_poll(mdata, true);
2101 if (rc < 0)
2102 return -1;
2103 if (rc > 0)
2104 update_active = true;
2105 }
2106 }
2107 }
2108 else if (adata->newgroups_time)
2109 {
2110 return 0;
2111 }
2112
2113 /* get list of new groups */
2114 mutt_message("%s", msg);
2115 if (nntp_date(adata, &now) < 0)
2116 return -1;
2117 tmp_mdata.adata = adata;
2118 if (m && m->mdata)
2119 tmp_mdata.group = ((struct NntpMboxData *) m->mdata)->group;
2120 else
2121 tmp_mdata.group = NULL;
2122 i = adata->groups_num;
2123 struct tm tm = mutt_date_gmtime(adata->newgroups_time);
2124 snprintf(buf, sizeof(buf), "NEWGROUPS %02d%02d%02d %02d%02d%02d GMT\r\n",
2125 tm.tm_year % 100, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
2126 rc = nntp_fetch_lines(&tmp_mdata, buf, sizeof(buf), msg, nntp_add_group, adata);
2127 if (rc)
2128 {
2129 if (rc > 0)
2130 {
2131 mutt_error("NEWGROUPS: %s", buf);
2132 }
2133 return -1;
2134 }
2135
2136 /* new groups found */
2137 rc = 0;
2138 if (adata->groups_num != i)
2139 {
2140 int groups_num = i;
2141
2142 adata->newgroups_time = now;
2143 for (; i < adata->groups_num; i++)
2144 {
2145 struct NntpMboxData *mdata = adata->groups_list[i];
2146 mdata->has_new_mail = true;
2147 }
2148
2149 /* loading descriptions */
2150 const bool c_nntp_load_description = cs_subset_bool(NeoMutt->sub, "nntp_load_description");
2151 if (c_nntp_load_description)
2152 {
2153 unsigned int count = 0;
2154 struct Progress *progress = progress_new(MUTT_PROGRESS_READ, adata->groups_num - i);
2155 progress_set_message(progress, _("Loading descriptions..."));
2156
2157 for (i = groups_num; i < adata->groups_num; i++)
2158 {
2159 struct NntpMboxData *mdata = adata->groups_list[i];
2160
2161 if (get_description(mdata, NULL, NULL) < 0)
2162 {
2163 progress_free(&progress);
2164 return -1;
2165 }
2166 progress_update(progress, ++count, -1);
2167 }
2168 progress_free(&progress);
2169 }
2170 update_active = true;
2171 rc = 1;
2172 }
2173 if (update_active)
2176 return rc;
2177}
2178
2187int nntp_check_msgid(struct Mailbox *m, const char *msgid)
2188{
2189 if (!m)
2190 return -1;
2191
2192 struct NntpMboxData *mdata = m->mdata;
2193 char buf[1024] = { 0 };
2194
2195 FILE *fp = mutt_file_mkstemp();
2196 if (!fp)
2197 {
2198 mutt_perror(_("Can't create temporary file"));
2199 return -1;
2200 }
2201
2202 snprintf(buf, sizeof(buf), "HEAD %s\r\n", msgid);
2203 int rc = nntp_fetch_lines(mdata, buf, sizeof(buf), NULL, fetch_tempfile, fp);
2204 if (rc)
2205 {
2206 mutt_file_fclose(&fp);
2207 if (rc < 0)
2208 return -1;
2209 if (mutt_str_startswith(buf, "430"))
2210 return 1;
2211 mutt_error("HEAD: %s", buf);
2212 return -1;
2213 }
2214
2215 /* parse header */
2217 m->emails[m->msg_count] = email_new();
2218 struct Email *e = m->emails[m->msg_count];
2219 e->edata = nntp_edata_new();
2221 e->env = mutt_rfc822_read_header(fp, e, false, false);
2222 mutt_file_fclose(&fp);
2223
2224 /* get article number */
2225 if (e->env->xref)
2226 {
2227 nntp_parse_xref(m, e);
2228 }
2229 else
2230 {
2231 snprintf(buf, sizeof(buf), "STAT %s\r\n", msgid);
2232 if (nntp_query(mdata, buf, sizeof(buf)) < 0)
2233 {
2234 email_free(&e);
2235 return -1;
2236 }
2237 sscanf(buf + 4, ANUM_FMT, &nntp_edata_get(e)->article_num);
2238 }
2239
2240 /* reset flags */
2241 e->read = false;
2242 e->old = false;
2243 e->deleted = false;
2244 e->changed = true;
2245 e->received = e->date_sent;
2246 e->index = m->msg_count++;
2248 return 0;
2249}
2250
2258int nntp_check_children(struct Mailbox *m, const char *msgid)
2259{
2260 if (!m)
2261 return -1;
2262
2263 struct NntpMboxData *mdata = m->mdata;
2264 char buf[256] = { 0 };
2265 int rc;
2266 struct HeaderCache *hc = NULL;
2267
2268 if (!mdata || !mdata->adata)
2269 return -1;
2270 if (mdata->first_message > mdata->last_loaded)
2271 return 0;
2272
2273 /* init context */
2274 struct ChildCtx cc = { 0 };
2275 cc.mailbox = m;
2276 cc.num = 0;
2277 cc.max = 10;
2278 cc.child = mutt_mem_malloc(sizeof(anum_t) * cc.max);
2279
2280 /* fetch numbers of child messages */
2281 snprintf(buf, sizeof(buf), "XPAT References " ANUM_FMT "-" ANUM_FMT " *%s*\r\n",
2282 mdata->first_message, mdata->last_loaded, msgid);
2283 rc = nntp_fetch_lines(mdata, buf, sizeof(buf), NULL, fetch_children, &cc);
2284 if (rc)
2285 {
2286 FREE(&cc.child);
2287 if (rc > 0)
2288 {
2289 if (!mutt_str_startswith(buf, "500"))
2290 {
2291 mutt_error("XPAT: %s", buf);
2292 }
2293 else
2294 {
2295 mutt_error(_("Unable to find child articles because server does not support XPAT command"));
2296 }
2297 }
2298 return -1;
2299 }
2300
2301 /* fetch all found messages */
2302 bool verbose = m->verbose;
2303 m->verbose = false;
2304#ifdef USE_HCACHE
2305 hc = nntp_hcache_open(mdata);
2306#endif
2307 int old_msg_count = m->msg_count;
2308 for (int i = 0; i < cc.num; i++)
2309 {
2310 rc = nntp_fetch_headers(m, hc, cc.child[i], cc.child[i], true);
2311 if (rc < 0)
2312 break;
2313 }
2314 if (m->msg_count > old_msg_count)
2316
2317#ifdef USE_HCACHE
2318 hcache_close(&hc);
2319#endif
2320 m->verbose = verbose;
2321 FREE(&cc.child);
2322 return (rc < 0) ? -1 : 0;
2323}
2324
2328int nntp_compare_order(const struct Email *a, const struct Email *b, bool reverse)
2329{
2330 anum_t na = nntp_edata_get((struct Email *) a)->article_num;
2331 anum_t nb = nntp_edata_get((struct Email *) b)->article_num;
2332 int result = (na == nb) ? 0 : (na > nb) ? 1 : -1;
2333 return reverse ? -result : result;
2334}
2335
2339static bool nntp_ac_owns_path(struct Account *a, const char *path)
2340{
2341 return true;
2342}
2343
2347static bool nntp_ac_add(struct Account *a, struct Mailbox *m)
2348{
2349 return true;
2350}
2351
2356{
2357 if (!m->account)
2358 return MX_OPEN_ERROR;
2359
2360 char buf[8192] = { 0 };
2361 char server[1024] = { 0 };
2362 char *group = NULL;
2363 int rc;
2364 struct HeaderCache *hc = NULL;
2365 anum_t first, last, count = 0;
2366
2367 struct Url *url = url_parse(mailbox_path(m));
2368 if (!url || !url->host || !url->path ||
2369 !((url->scheme == U_NNTP) || (url->scheme == U_NNTPS)))
2370 {
2371 url_free(&url);
2372 mutt_error(_("%s is an invalid newsgroup specification"), mailbox_path(m));
2373 return MX_OPEN_ERROR;
2374 }
2375
2376 group = url->path;
2377 if (group[0] == '/') /* Skip a leading '/' */
2378 group++;
2379
2380 url->path = strchr(url->path, '\0');
2381 url_tostring(url, server, sizeof(server), U_NO_FLAGS);
2382
2384 struct NntpAccountData *adata = m->account->adata;
2385 if (!adata)
2387 if (!adata)
2388 {
2389 adata = nntp_select_server(m, server, true);
2390 m->account->adata = adata;
2392 }
2393
2394 if (!adata)
2395 {
2396 url_free(&url);
2397 return MX_OPEN_ERROR;
2398 }
2400
2401 m->msg_count = 0;
2402 m->msg_unread = 0;
2403 m->vcount = 0;
2404
2405 if (group[0] == '/')
2406 group++;
2407
2408 /* find news group data structure */
2410 if (!mdata)
2411 {
2413 mutt_error(_("Newsgroup %s not found on the server"), group);
2414 url_free(&url);
2415 return MX_OPEN_ERROR;
2416 }
2417
2418 m->rights &= ~MUTT_ACL_INSERT; // Clear the flag
2419 const bool c_save_unsubscribed = cs_subset_bool(NeoMutt->sub, "save_unsubscribed");
2420 if (!mdata->newsrc_ent && !mdata->subscribed && !c_save_unsubscribed)
2421 m->readonly = true;
2422
2423 /* select newsgroup */
2424 mutt_message(_("Selecting %s..."), group);
2425 url_free(&url);
2426 buf[0] = '\0';
2427 if (nntp_query(mdata, buf, sizeof(buf)) < 0)
2428 {
2430 return MX_OPEN_ERROR;
2431 }
2432
2433 /* newsgroup not found, remove it */
2434 if (mutt_str_startswith(buf, "411"))
2435 {
2436 mutt_error(_("Newsgroup %s has been removed from the server"), mdata->group);
2437 if (!mdata->deleted)
2438 {
2439 mdata->deleted = true;
2441 }
2442 if (mdata->newsrc_ent && !mdata->subscribed && !c_save_unsubscribed)
2443 {
2444 FREE(&mdata->newsrc_ent);
2445 mdata->newsrc_len = 0;
2448 }
2449 }
2450 else
2451 {
2452 /* parse newsgroup info */
2453 if (sscanf(buf, "211 " ANUM_FMT " " ANUM_FMT " " ANUM_FMT, &count, &first, &last) != 3)
2454 {
2456 mutt_error("GROUP: %s", buf);
2457 return MX_OPEN_ERROR;
2458 }
2459 mdata->first_message = first;
2460 mdata->last_message = last;
2461 mdata->deleted = false;
2462
2463 /* get description if empty */
2464 const bool c_nntp_load_description = cs_subset_bool(NeoMutt->sub, "nntp_load_description");
2465 if (c_nntp_load_description && !mdata->desc)
2466 {
2467 if (get_description(mdata, NULL, NULL) < 0)
2468 {
2470 return MX_OPEN_ERROR;
2471 }
2472 if (mdata->desc)
2474 }
2475 }
2476
2478 m->mdata = mdata;
2479 // Every known newsgroup has an mdata which is stored in adata->groups_list.
2480 // Currently we don't let the Mailbox free the mdata.
2481 // m->mdata_free = nntp_mdata_free;
2482 if (!mdata->bcache && (mdata->newsrc_ent || mdata->subscribed || c_save_unsubscribed))
2483 mdata->bcache = mutt_bcache_open(&adata->conn->account, mdata->group);
2484
2485 /* strip off extra articles if adding context is greater than $nntp_context */
2486 first = mdata->first_message;
2487 const long c_nntp_context = cs_subset_long(NeoMutt->sub, "nntp_context");
2488 if (c_nntp_context && ((mdata->last_message - first + 1) > c_nntp_context))
2489 first = mdata->last_message - c_nntp_context + 1;
2490 mdata->last_loaded = first ? first - 1 : 0;
2491 count = mdata->first_message;
2492 mdata->first_message = first;
2494 mdata->first_message = count;
2495#ifdef USE_HCACHE
2496 hc = nntp_hcache_open(mdata);
2498#endif
2499 if (!hc)
2500 m->rights &= ~(MUTT_ACL_WRITE | MUTT_ACL_DELETE); // Clear the flags
2501
2503 rc = nntp_fetch_headers(m, hc, first, mdata->last_message, false);
2504#ifdef USE_HCACHE
2505 hcache_close(&hc);
2506#endif
2507 if (rc < 0)
2508 return MX_OPEN_ERROR;
2509 mdata->last_loaded = mdata->last_message;
2510 adata->newsrc_modified = false;
2511 return MX_OPEN_OK;
2512}
2513
2519static enum MxStatus nntp_mbox_check(struct Mailbox *m)
2520{
2521 enum MxStatus rc = check_mailbox(m);
2522 if (rc == MX_STATUS_OK)
2523 {
2524 struct NntpMboxData *mdata = m->mdata;
2525 struct NntpAccountData *adata = mdata->adata;
2527 }
2528 return rc;
2529}
2530
2536static enum MxStatus nntp_mbox_sync(struct Mailbox *m)
2537{
2538 struct NntpMboxData *mdata = m->mdata;
2539
2540 /* check for new articles */
2541 mdata->adata->check_time = 0;
2542 enum MxStatus check = check_mailbox(m);
2543 if (check != MX_STATUS_OK)
2544 return check;
2545
2546#ifdef USE_HCACHE
2547 mdata->last_cached = 0;
2548 struct HeaderCache *hc = nntp_hcache_open(mdata);
2549#endif
2550
2551 for (int i = 0; i < m->msg_count; i++)
2552 {
2553 struct Email *e = m->emails[i];
2554 if (!e)
2555 break;
2556
2557 char buf[16] = { 0 };
2558
2559 snprintf(buf, sizeof(buf), ANUM_FMT, nntp_edata_get(e)->article_num);
2560 if (mdata->bcache && e->deleted)
2561 {
2562 mutt_debug(LL_DEBUG2, "mutt_bcache_del %s\n", buf);
2563 mutt_bcache_del(mdata->bcache, buf);
2564 }
2565
2566#ifdef USE_HCACHE
2567 if (hc && (e->changed || e->deleted))
2568 {
2569 if (e->deleted && !e->read)
2570 mdata->unread--;
2571 mutt_debug(LL_DEBUG2, "hcache_store_email %s\n", buf);
2572 hcache_store_email(hc, buf, strlen(buf), e, 0);
2573 }
2574#endif
2575 }
2576
2577#ifdef USE_HCACHE
2578 if (hc)
2579 {
2580 hcache_close(&hc);
2581 mdata->last_cached = mdata->last_loaded;
2582 }
2583#endif
2584
2585 /* save .newsrc entries */
2587 nntp_newsrc_update(mdata->adata);
2588 nntp_newsrc_close(mdata->adata);
2589 return MX_STATUS_OK;
2590}
2591
2596static enum MxStatus nntp_mbox_close(struct Mailbox *m)
2597{
2598 struct NntpMboxData *mdata = m->mdata;
2599 struct NntpMboxData *tmp_mdata = NULL;
2600 if (!mdata)
2601 return MX_STATUS_OK;
2602
2603 mdata->unread = m->msg_unread;
2604
2606 if (!mdata->adata || !mdata->adata->groups_hash || !mdata->group)
2607 return MX_STATUS_OK;
2608
2609 tmp_mdata = mutt_hash_find(mdata->adata->groups_hash, mdata->group);
2610 if (!tmp_mdata || (tmp_mdata != mdata))
2611 nntp_mdata_free((void **) &mdata);
2612 return MX_STATUS_OK;
2613}
2614
2618static bool nntp_msg_open(struct Mailbox *m, struct Message *msg, struct Email *e)
2619{
2620 struct NntpMboxData *mdata = m->mdata;
2621 char article[16] = { 0 };
2622
2623 /* try to get article from cache */
2624 struct NntpAcache *acache = &mdata->acache[e->index % NNTP_ACACHE_LEN];
2625 if (acache->path)
2626 {
2627 if (acache->index == e->index)
2628 {
2629 msg->fp = mutt_file_fopen(acache->path, "r");
2630 if (msg->fp)
2631 return true;
2632 }
2633 else
2634 {
2635 /* clear previous entry */
2636 unlink(acache->path);
2637 FREE(&acache->path);
2638 }
2639 }
2640 snprintf(article, sizeof(article), ANUM_FMT, nntp_edata_get(e)->article_num);
2641 msg->fp = mutt_bcache_get(mdata->bcache, article);
2642 if (msg->fp)
2643 {
2644 if (nntp_edata_get(e)->parsed)
2645 return true;
2646 }
2647 else
2648 {
2649 /* don't try to fetch article from removed newsgroup */
2650 if (mdata->deleted)
2651 return false;
2652
2653 /* create new cache file */
2654 const char *fetch_msg = _("Fetching message...");
2655 mutt_message("%s", fetch_msg);
2656 msg->fp = mutt_bcache_put(mdata->bcache, article);
2657 if (!msg->fp)
2658 {
2659 struct Buffer *tempfile = buf_pool_get();
2660 buf_mktemp(tempfile);
2661 acache->path = buf_strdup(tempfile);
2662 buf_pool_release(&tempfile);
2663 acache->index = e->index;
2664 msg->fp = mutt_file_fopen(acache->path, "w+");
2665 if (!msg->fp)
2666 {
2667 mutt_perror("%s", acache->path);
2668 unlink(acache->path);
2669 FREE(&acache->path);
2670 return false;
2671 }
2672 }
2673
2674 /* fetch message to cache file */
2675 char buf[2048] = { 0 };
2676 snprintf(buf, sizeof(buf), "ARTICLE %s\r\n",
2677 nntp_edata_get(e)->article_num ? article : e->env->message_id);
2678 const int rc = nntp_fetch_lines(mdata, buf, sizeof(buf), NULL, fetch_tempfile, msg->fp);
2679 if (rc)
2680 {
2681 mutt_file_fclose(&msg->fp);
2682 if (acache->path)
2683 {
2684 unlink(acache->path);
2685 FREE(&acache->path);
2686 }
2687 if (rc > 0)
2688 {
2689 if (mutt_str_startswith(buf, nntp_edata_get(e)->article_num ? "423" : "430"))
2690 {
2691 mutt_error(_("Article %s not found on the server"),
2692 nntp_edata_get(e)->article_num ? article : e->env->message_id);
2693 }
2694 else
2695 {
2696 mutt_error("ARTICLE: %s", buf);
2697 }
2698 }
2699 return false;
2700 }
2701
2702 if (!acache->path)
2703 mutt_bcache_commit(mdata->bcache, article);
2704 }
2705
2706 /* replace envelope with new one
2707 * hash elements must be updated because pointers will be changed */
2708 if (m->id_hash && e->env->message_id)
2710 if (m->subj_hash && e->env->real_subj)
2712
2713 mutt_env_free(&e->env);
2714 e->env = mutt_rfc822_read_header(msg->fp, e, false, false);
2715
2716 if (m->id_hash && e->env->message_id)
2718 if (m->subj_hash && e->env->real_subj)
2720
2721 /* fix content length */
2722 if (!mutt_file_seek(msg->fp, 0, SEEK_END))
2723 {
2724 return false;
2725 }
2726 e->body->length = ftell(msg->fp) - e->body->offset;
2727
2728 /* this is called in neomutt before the open which fetches the message,
2729 * which is probably wrong, but we just call it again here to handle
2730 * the problem instead of fixing it */
2731 nntp_edata_get(e)->parsed = true;
2732 mutt_parse_mime_message(e, msg->fp);
2733
2734 /* these would normally be updated in mview_update(), but the
2735 * full headers aren't parsed with overview, so the information wasn't
2736 * available then */
2737 if (WithCrypto)
2738 e->security = crypt_query(e->body);
2739
2740 rewind(msg->fp);
2742 return true;
2743}
2744
2750static int nntp_msg_close(struct Mailbox *m, struct Message *msg)
2751{
2752 return mutt_file_fclose(&msg->fp);
2753}
2754
2758enum MailboxType nntp_path_probe(const char *path, const struct stat *st)
2759{
2760 if (mutt_istr_startswith(path, "news://"))
2761 return MUTT_NNTP;
2762
2763 if (mutt_istr_startswith(path, "snews://"))
2764 return MUTT_NNTP;
2765
2766 return MUTT_UNKNOWN;
2767}
2768
2772static int nntp_path_canon(struct Buffer *path)
2773{
2774 return 0;
2775}
2776
2780const struct MxOps MxNntpOps = {
2781 // clang-format off
2782 .type = MUTT_NNTP,
2783 .name = "nntp",
2784 .is_local = false,
2785 .ac_owns_path = nntp_ac_owns_path,
2786 .ac_add = nntp_ac_add,
2787 .mbox_open = nntp_mbox_open,
2788 .mbox_open_append = NULL,
2789 .mbox_check = nntp_mbox_check,
2790 .mbox_check_stats = NULL,
2791 .mbox_sync = nntp_mbox_sync,
2792 .mbox_close = nntp_mbox_close,
2793 .msg_open = nntp_msg_open,
2794 .msg_open_new = NULL,
2795 .msg_commit = NULL,
2796 .msg_close = nntp_msg_close,
2797 .msg_padding_size = NULL,
2798 .msg_save_hcache = NULL,
2799 .tags_edit = NULL,
2800 .tags_commit = NULL,
2801 .path_probe = nntp_path_probe,
2802 .path_canon = nntp_path_canon,
2803 // clang-format on
2804};
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:249
struct BodyCache * mutt_bcache_open(struct ConnAccount *account, const char *mailbox)
Open an Email-Body Cache.
Definition: bcache.c:143
FILE * mutt_bcache_get(struct BodyCache *bcache, const char *id)
Open a file in the Body Cache.
Definition: bcache.c:182
int mutt_bcache_del(struct BodyCache *bcache, const char *id)
Delete a file from the Body Cache.
Definition: bcache.c:266
FILE * mutt_bcache_put(struct BodyCache *bcache, const char *id)
Create a file in the Body Cache.
Definition: bcache.c:209
int buf_printf(struct Buffer *buf, const char *fmt,...)
Format a string overwriting a Buffer.
Definition: buffer.c:160
size_t buf_len(const struct Buffer *buf)
Calculate the length of a Buffer.
Definition: buffer.c:490
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:639
void buf_reset(struct Buffer *buf)
Reset an existing Buffer.
Definition: buffer.c:75
bool buf_is_empty(const struct Buffer *buf)
Is the Buffer empty?
Definition: buffer.c:290
char buf_at(const struct Buffer *buf, size_t offset)
Return the character at the given offset.
Definition: buffer.c:669
size_t buf_addch(struct Buffer *buf, char c)
Add a single character to a Buffer.
Definition: buffer.c:240
size_t buf_addstr(struct Buffer *buf, const char *s)
Add a string to a Buffer.
Definition: buffer.c:225
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:654
size_t buf_strcpy(struct Buffer *buf, const char *s)
Copy a string into a Buffer.
Definition: buffer.c:394
char * buf_strdup(const struct Buffer *buf)
Copy a Buffer's string.
Definition: buffer.c:570
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:292
short cs_subset_number(const struct ConfigSubset *sub, const char *name)
Get a number config item by name.
Definition: helpers.c:144
long cs_subset_long(const struct ConfigSubset *sub, const char *name)
Get a long config item by name.
Definition: helpers.c:96
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition: helpers.c:48
Convenience wrapper for the config headers.
Connection Library.
int mutt_account_getpass(struct ConnAccount *cac)
Fetch password into ConnAccount, if necessary.
Definition: connaccount.c:131
int mutt_account_getuser(struct ConnAccount *cac)
Retrieve username into ConnAccount, if necessary.
Definition: connaccount.c:52
#define MUTT_ACCT_USER
User field has been set.
Definition: connaccount.h:44
Convenience wrapper for the core headers.
void mailbox_changed(struct Mailbox *m, enum NotifyMailbox action)
Notify observers of a change to a Mailbox.
Definition: mailbox.c:234
@ 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:673
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:775
#define mutt_file_fclose(FP)
Definition: file.h:147
#define mutt_file_fopen(PATH, MODE)
Definition: file.h:146
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:2347
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:2339
const struct MxOps MxNntpOps
NNTP Mailbox - Implements MxOps -.
Definition: nntp.c:2780
static enum MxStatus nntp_mbox_check(struct Mailbox *m)
Check for new mail - Implements MxOps::mbox_check() -.
Definition: nntp.c:2519
static enum MxStatus nntp_mbox_close(struct Mailbox *m)
Close a Mailbox - Implements MxOps::mbox_close() -.
Definition: nntp.c:2596
static enum MxOpenReturns nntp_mbox_open(struct Mailbox *m)
Open a Mailbox - Implements MxOps::mbox_open() -.
Definition: nntp.c:2355
static enum MxStatus nntp_mbox_sync(struct Mailbox *m)
Save changes to the Mailbox - Implements MxOps::mbox_sync() -.
Definition: nntp.c:2536
static int nntp_msg_close(struct Mailbox *m, struct Message *msg)
Close an email - Implements MxOps::msg_close() -.
Definition: nntp.c:2750
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:2618
static int nntp_path_canon(struct Buffer *path)
Canonicalise a Mailbox path - Implements MxOps::path_canon() -.
Definition: nntp.c:2772
enum MailboxType nntp_path_probe(const char *path, const struct stat *st)
Is this an NNTP Mailbox? - Implements MxOps::path_probe() -.
Definition: nntp.c:2758
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:2328
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:50
void * mutt_mem_malloc(size_t size)
Allocate memory on the heap.
Definition: memory.c:90
void mutt_mem_realloc(void *ptr, size_t size)
Resize a block of memory on the heap.
Definition: memory.c:114
#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:926
time_t mutt_date_now(void)
Return the number of seconds since the Unix epoch.
Definition: date.c:455
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:559
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:654
const char * mutt_istr_find(const char *haystack, const char *needle)
Find first occurrence of string (ignoring case)
Definition: string.c:515
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:575
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:274
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:1204
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:710
void nntp_delete_group_cache(struct NntpMboxData *mdata)
Remove hcache and bcache of newsgroup.
Definition: newsrc.c:811
void nntp_newsrc_gen_entries(struct Mailbox *m)
Generate array of .newsrc entries.
Definition: newsrc.c:302
void nntp_hcache_update(struct NntpMboxData *mdata, struct HeaderCache *hc)
Remove stale cached headers.
Definition: newsrc.c:734
void nntp_article_status(struct Mailbox *m, struct Email *e, char *group, anum_t anum)
Get status of articles from .newsrc.
Definition: newsrc.c:1256
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:650
void nntp_bcache_update(struct NntpMboxData *mdata)
Remove stale cached messages.
Definition: newsrc.c:802
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
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: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:444
#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:1064
#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:2187
int nntp_check_children(struct Mailbox *m, const char *msgid)
Fetch children of article with the Message-ID.
Definition: nntp.c:2258
int nntp_active_fetch(struct NntpAccountData *adata, bool mark_new)
Fetch list of all newsgroups from server.
Definition: nntp.c:2009
static int fetch_children(char *line, void *data)
Parse XPAT line.
Definition: nntp.c:1724
static int nntp_auth(struct NntpAccountData *adata)
Get login, password and authenticate.
Definition: nntp.c:445
static int nntp_date(struct NntpAccountData *adata, time_t *now)
Get date and time from server.
Definition: nntp.c:1687
int nntp_check_new_groups(struct Mailbox *m, struct NntpAccountData *adata)
Check for new groups/articles in subscribed groups.
Definition: nntp.c:2077
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:1429
int nntp_post(struct Mailbox *m, const char *msg)
Post article.
Definition: nntp.c:1924
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:1036
static enum MxStatus check_mailbox(struct Mailbox *m)
Check current newsgroup for new articles.
Definition: nntp.c:1479
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:723
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:802
static int get_description(struct NntpMboxData *mdata, const char *wildmat, const char *msg)
Fetch newsgroups descriptions.
Definition: nntp.c:924
static int fetch_tempfile(char *line, void *data)
Write line to temporary file.
Definition: nntp.c:997
static void nntp_parse_xref(struct Mailbox *m, struct Email *e)
Parse cross-reference.
Definition: nntp.c:956
int nntp_open_connection(struct NntpAccountData *adata)
Connect to server, authenticate and get capabilities.
Definition: nntp.c:1754
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:1014
struct NntpAccountData * CurrentNewsSrv
Current news server.
Definition: nntp.c:77
static int fetch_description(char *line, void *data)
Parse newsgroup description.
Definition: nntp.c:887
static int nntp_fetch_headers(struct Mailbox *m, void *hc, anum_t first, anum_t last, bool restore)
Fetch headers.
Definition: nntp.c:1192
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:366
enum QuadOption query_yesorno(const char *prompt, enum QuadOption def)
Ask the user a Yes/No question.
Definition: question.c:327
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:41
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:45
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