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