NeoMutt  2023-11-03-107-g582dc1
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
nntp.c File Reference

Usenet network mailbox type; talk to an NNTP server. More...

#include "config.h"
#include <ctype.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <time.h>
#include <unistd.h>
#include "private.h"
#include "mutt/lib.h"
#include "config/lib.h"
#include "email/lib.h"
#include "core/lib.h"
#include "conn/lib.h"
#include "lib.h"
#include "attach/lib.h"
#include "bcache/lib.h"
#include "hcache/lib.h"
#include "ncrypt/lib.h"
#include "progress/lib.h"
#include "question/lib.h"
#include "adata.h"
#include "edata.h"
#include "hook.h"
#include "mdata.h"
#include "mutt_logging.h"
#include "muttlib.h"
#include "mx.h"
#include "protos.h"
#include "mutt.h"
+ Include dependency graph for nntp.c:

Go to the source code of this file.

Data Structures

struct  FetchCtx
 Keep track when getting data from a server. More...
 
struct  ChildCtx
 Keep track of the children of an article. More...
 

Functions

void nntp_hashelem_free (int type, void *obj, intptr_t data)
 Free our hash table data - Implements hash_hdata_free_t -.
 
static int nntp_connect_error (struct NntpAccountData *adata)
 Signal a failed connection.
 
static int nntp_capabilities (struct NntpAccountData *adata)
 Get capabilities.
 
static int nntp_attempt_features (struct NntpAccountData *adata)
 Detect supported commands.
 
static int nntp_auth (struct NntpAccountData *adata)
 Get login, password and authenticate.
 
static int nntp_query (struct NntpMboxData *mdata, char *line, size_t linelen)
 Send data from buffer and receive answer to same buffer.
 
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.
 
static int fetch_description (char *line, void *data)
 Parse newsgroup description.
 
static int get_description (struct NntpMboxData *mdata, const char *wildmat, const char *msg)
 Fetch newsgroups descriptions.
 
static void nntp_parse_xref (struct Mailbox *m, struct Email *e)
 Parse cross-reference.
 
static int fetch_tempfile (char *line, void *data)
 Write line to temporary file.
 
static int fetch_numbers (char *line, void *data)
 Parse article number.
 
static int parse_overview_line (char *line, void *data)
 Parse overview line.
 
static int nntp_fetch_headers (struct Mailbox *m, void *hc, anum_t first, anum_t last, bool restore)
 Fetch headers.
 
static int nntp_group_poll (struct NntpMboxData *mdata, bool update_stat)
 Check newsgroup for new articles.
 
static enum MxStatus check_mailbox (struct Mailbox *m)
 Check current newsgroup for new articles.
 
static int nntp_date (struct NntpAccountData *adata, time_t *now)
 Get date and time from server.
 
static int fetch_children (char *line, void *data)
 Parse XPAT line.
 
int nntp_open_connection (struct NntpAccountData *adata)
 Connect to server, authenticate and get capabilities.
 
int nntp_post (struct Mailbox *m, const char *msg)
 Post article.
 
int nntp_active_fetch (struct NntpAccountData *adata, bool mark_new)
 Fetch list of all newsgroups from server.
 
int nntp_check_new_groups (struct Mailbox *m, struct NntpAccountData *adata)
 Check for new groups/articles in subscribed groups.
 
int nntp_check_msgid (struct Mailbox *m, const char *msgid)
 Fetch article by Message-ID.
 
int nntp_check_children (struct Mailbox *m, const char *msgid)
 Fetch children of article with the Message-ID.
 
int nntp_compare_order (const struct Email *a, const struct Email *b, bool reverse)
 Restore the 'unsorted' order of emails - Implements sort_mail_t -.
 
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() -.
 
static bool nntp_ac_add (struct Account *a, struct Mailbox *m)
 Add a Mailbox to an Account - Implements MxOps::ac_add() -.
 
static enum MxOpenReturns nntp_mbox_open (struct Mailbox *m)
 Open a Mailbox - Implements MxOps::mbox_open() -.
 
static enum MxStatus nntp_mbox_check (struct Mailbox *m)
 Check for new mail - Implements MxOps::mbox_check() -.
 
static enum MxStatus nntp_mbox_sync (struct Mailbox *m)
 Save changes to the Mailbox - Implements MxOps::mbox_sync() -.
 
static enum MxStatus nntp_mbox_close (struct Mailbox *m)
 Close a Mailbox - Implements MxOps::mbox_close() -.
 
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() -.
 
static int nntp_msg_close (struct Mailbox *m, struct Message *msg)
 Close an email - Implements MxOps::msg_close() -.
 
enum MailboxType nntp_path_probe (const char *path, const struct stat *st)
 Is this an NNTP Mailbox? - Implements MxOps::path_probe() -.
 
static int nntp_path_canon (struct Buffer *path)
 Canonicalise a Mailbox path - Implements MxOps::path_canon() -.
 

Variables

struct NntpAccountDataCurrentNewsSrv = NULL
 Current news server.
 
static const char * OverviewFmt
 Fields to get from server, if it supports the LIST OVERVIEW.FMT feature.
 
const struct MxOps MxNntpOps
 NNTP Mailbox - Implements MxOps -.
 

Detailed Description

Usenet network mailbox type; talk to an NNTP server.

Authors
  • Brandon Long
  • Andrej Gritsenko
  • Vsevolod Volkov
  • Richard Russon

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/.

Definition in file nntp.c.

Function Documentation

◆ nntp_connect_error()

static int nntp_connect_error ( struct NntpAccountData adata)
static

Signal a failed connection.

Parameters
adataNNTP server
Return values
-1Always

Definition at line 127 of file nntp.c.

128{
129 adata->status = NNTP_NONE;
130 mutt_error(_("Server closed connection"));
131 return -1;
132}
#define mutt_error(...)
Definition: logging2.h:92
#define _(a)
Definition: message.h:28
@ NNTP_NONE
No connection to server.
Definition: private.h:44
unsigned int status
Definition: adata.h:47
+ Here is the caller graph for this function:

◆ nntp_capabilities()

static int nntp_capabilities ( struct NntpAccountData adata)
static

Get capabilities.

Parameters
adataNNTP server
Return values
-1Error, connection is closed
0Mode is reader, capabilities set up
1Need to switch to reader mode

Definition at line 141 of file nntp.c.

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}
#define FREE(x)
Definition: memory.h:45
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:763
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
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
@ NNTP_BYE
Disconnected from server.
Definition: private.h:46
static int nntp_connect_error(struct NntpAccountData *adata)
Signal a failed connection.
Definition: nntp.c:127
int mutt_socket_close(struct Connection *conn)
Close a socket.
Definition: socket.c:101
#define mutt_socket_readln(buf, buflen, conn)
Definition: socket.h:58
#define mutt_socket_send(conn, buf)
Definition: socket.h:59
struct Connection * conn
Connection to NNTP Server.
Definition: adata.h:62
char * authenticators
Definition: adata.h:52
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
bool hasLISTGROUP
Server supports LISTGROUP command.
Definition: adata.h:42
bool hasOVER
Server supports OVER command.
Definition: adata.h:44
bool hasDATE
Server supports DATE command.
Definition: adata.h:39
bool hasLIST_NEWSGROUPS
Server supports LIST_NEWSGROUPS command.
Definition: adata.h:40
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ nntp_attempt_features()

static int nntp_attempt_features ( struct NntpAccountData adata)
static

Detect supported commands.

Parameters
adataNNTP server
Return values
0Success
-1Failure

Definition at line 250 of file nntp.c.

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}
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
static const char * OverviewFmt
Fields to get from server, if it supports the LIST OVERVIEW.FMT feature.
Definition: nntp.c:80
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_HDR
Definition: socket.h:55
bool hasXOVER
Server supports XOVER command.
Definition: adata.h:45
char * overview_fmt
Definition: adata.h:53
bool hasXGTITLE
Server supports XGTITLE command.
Definition: adata.h:41
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ nntp_auth()

static int nntp_auth ( struct NntpAccountData adata)
static

Get login, password and authenticate.

Parameters
adataNNTP server
Return values
0Success
-1Failure

Definition at line 435 of file nntp.c.

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}
const char * cs_subset_string(const struct ConfigSubset *sub, const char *name)
Get a string config item by name.
Definition: helpers.c:292
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_message(...)
Definition: logging2.h:91
#define mutt_debug(LEVEL,...)
Definition: logging2.h:89
@ LL_DEBUG1
Log at debug level 1.
Definition: logging2.h:43
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
#define MUTT_SOCK_LOG_FULL
Definition: socket.h:56
#define MUTT_SOCK_LOG_CMD
Definition: socket.h:54
#define mutt_socket_send_d(conn, buf, dbg)
Definition: socket.h:60
char user[128]
Username.
Definition: connaccount.h:56
char pass[256]
Password.
Definition: connaccount.h:57
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
Container for Accounts, Notifications.
Definition: neomutt.h:41
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:45
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ nntp_query()

static int nntp_query ( struct NntpMboxData mdata,
char *  line,
size_t  linelen 
)
static

Send data from buffer and receive answer to same buffer.

Parameters
mdataNNTP Mailbox data
lineBuffer containing data
linelenLength of buffer
Return values
0Success
-1Failure

Definition at line 703 of file nntp.c.

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}
@ NNTP_OK
Connected to server.
Definition: private.h:45
int nntp_open_connection(struct NntpAccountData *adata)
Connect to server, authenticate and get capabilities.
Definition: nntp.c:1734
@ MUTT_YES
User answered 'Yes', or assume 'Yes'.
Definition: quad.h:39
enum QuadOption query_yesorno(const char *prompt, enum QuadOption def)
Ask the user a Yes/No question.
Definition: question.c:330
void * adata
Private data (for Mailbox backends)
Definition: account.h:43
NNTP-specific Account data -.
Definition: adata.h:36
char * group
Name of newsgroup.
Definition: mdata.h:35
struct NntpAccountData * adata
Definition: mdata.h:48
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ nntp_fetch_lines()

static int nntp_fetch_lines ( struct NntpMboxData mdata,
char *  query,
size_t  qlen,
const char *  msg,
int(*)(char *, void *)  func,
void *  data 
)
static

Read lines, calling a callback function for each.

Parameters
mdataNNTP Mailbox data
queryQuery to match
qlenLength of query
msgProgress message (OPTIONAL)
funcCallback function
dataData for callback function
Return values
0Success
1Bad response (answer in query buffer)
-1Connection lost
-2Error in func(*line, *data)

This function calls func(*line, *data) for each received line, func(NULL, *data) if rewind(*data) needs, exits when fail or done:

Definition at line 782 of file nntp.c.

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 {
810 progress = progress_new(MUTT_PROGRESS_READ, 0);
811 progress_set_message(progress, "%s", msg);
812 }
813
814 while (true)
815 {
816 char *p = NULL;
817 int chunk = mutt_socket_readln_d(buf, sizeof(buf), mdata->adata->conn, MUTT_SOCK_LOG_FULL);
818 if (chunk < 0)
819 {
820 mdata->adata->status = NNTP_NONE;
821 break;
822 }
823
824 p = buf;
825 if (!off && (buf[0] == '.'))
826 {
827 if (buf[1] == '\0')
828 {
829 done = true;
830 break;
831 }
832 if (buf[1] == '.')
833 p++;
834 }
835
836 mutt_str_copy(line + off, p, sizeof(buf));
837
838 if (chunk >= sizeof(buf))
839 {
840 off += strlen(p);
841 }
842 else
843 {
844 progress_update(progress, ++lines, -1);
845
846 if ((rc == 0) && (func(line, data) < 0))
847 rc = -2;
848 off = 0;
849 }
850
851 mutt_mem_realloc(&line, off + sizeof(buf));
852 }
853 FREE(&line);
854 func(NULL, data);
855 progress_free(&progress);
856 }
857
858 return rc;
859}
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
@ 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:140
void progress_free(struct Progress **ptr)
Free a Progress Bar.
Definition: progress.c:111
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:81
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ fetch_description()

static int fetch_description ( char *  line,
void *  data 
)
static

Parse newsgroup description.

Parameters
lineString to parse
dataNNTP Server
Return values
0Always

Definition at line 867 of file nntp.c.

868{
869 if (!line)
870 return 0;
871
872 struct NntpAccountData *adata = data;
873
874 char *desc = strpbrk(line, " \t");
875 if (desc)
876 {
877 *desc++ = '\0';
878 desc += strspn(desc, " \t");
879 }
880 else
881 {
882 desc = strchr(line, '\0');
883 }
884
886 if (mdata && !mutt_str_equal(desc, mdata->desc))
887 {
888 mutt_str_replace(&mdata->desc, desc);
889 mutt_debug(LL_DEBUG2, "group: %s, desc: %s\n", line, desc);
890 }
891 return 0;
892}
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
@ LL_DEBUG2
Log at debug level 2.
Definition: logging2.h:44
void * mdata
Driver specific data.
Definition: mailbox.h:131
struct HashTable * groups_hash
Hash Table: "newsgroup" -> NntpMboxData.
Definition: adata.h:61
NNTP-specific Mailbox data -.
Definition: mdata.h:34
char * desc
Description of newsgroup.
Definition: mdata.h:36
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ get_description()

static int get_description ( struct NntpMboxData mdata,
const char *  wildmat,
const char *  msg 
)
static

Fetch newsgroups descriptions.

Parameters
mdataNNTP Mailbox data
wildmatString to match
msgProgress message
Return values
0Success
1Bad response (answer in query buffer)
-1Connection lost
-2Error

Definition at line 904 of file nntp.c.

905{
906 char buf[256] = { 0 };
907 const char *cmd = NULL;
908
909 /* get newsgroup description, if possible */
910 struct NntpAccountData *adata = mdata->adata;
911 if (!wildmat)
912 wildmat = mdata->group;
913 if (adata->hasLIST_NEWSGROUPS)
914 cmd = "LIST NEWSGROUPS";
915 else if (adata->hasXGTITLE)
916 cmd = "XGTITLE";
917 else
918 return 0;
919
920 snprintf(buf, sizeof(buf), "%s %s\r\n", cmd, wildmat);
921 int rc = nntp_fetch_lines(mdata, buf, sizeof(buf), msg, fetch_description, adata);
922 if (rc > 0)
923 {
924 mutt_error("%s: %s", cmd, buf);
925 }
926 return rc;
927}
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 fetch_description(char *line, void *data)
Parse newsgroup description.
Definition: nntp.c:867
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ nntp_parse_xref()

static void nntp_parse_xref ( struct Mailbox m,
struct Email e 
)
static

Parse cross-reference.

Parameters
mMailbox
eEmail

Update read flag and set article number if empty

Definition at line 936 of file nntp.c.

937{
938 struct NntpMboxData *mdata = m->mdata;
939
940 char *buf = mutt_str_dup(e->env->xref);
941 char *p = buf;
942 while (p)
943 {
944 anum_t anum;
945
946 /* skip to next word */
947 p += strspn(p, " \t");
948 char *grp = p;
949
950 /* skip to end of word */
951 p = strpbrk(p, " \t");
952 if (p)
953 *p++ = '\0';
954
955 /* find colon */
956 char *colon = strchr(grp, ':');
957 if (!colon)
958 continue;
959 *colon++ = '\0';
960 if (sscanf(colon, ANUM, &anum) != 1)
961 continue;
962
963 nntp_article_status(m, e, grp, anum);
964 if (!nntp_edata_get(e)->article_num && mutt_str_equal(mdata->group, grp))
965 nntp_edata_get(e)->article_num = anum;
966 }
967 FREE(&buf);
968}
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
struct NntpEmailData * nntp_edata_get(struct Email *e)
Get the private data for this Email.
Definition: edata.c:60
#define ANUM
Definition: lib.h:62
#define anum_t
Definition: lib.h:61
struct Envelope * env
Envelope information.
Definition: email.h:66
char * xref
List of cross-references.
Definition: envelope.h:79
anum_t article_num
NNTP article number.
Definition: edata.h:36
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ fetch_tempfile()

static int fetch_tempfile ( char *  line,
void *  data 
)
static

Write line to temporary file.

Parameters
lineText to write
dataFILE pointer
Return values
0Success
-1Failure

Definition at line 977 of file nntp.c.

978{
979 FILE *fp = data;
980
981 if (!line)
982 rewind(fp);
983 else if ((fputs(line, fp) == EOF) || (fputc('\n', fp) == EOF))
984 return -1;
985 return 0;
986}
+ Here is the caller graph for this function:

◆ fetch_numbers()

static int fetch_numbers ( char *  line,
void *  data 
)
static

Parse article number.

Parameters
lineArticle number
dataFetchCtx
Return values
0Always

Definition at line 994 of file nntp.c.

995{
996 struct FetchCtx *fc = data;
997 anum_t anum;
998
999 if (!line)
1000 return 0;
1001 if (sscanf(line, ANUM, &anum) != 1)
1002 return 0;
1003 if ((anum < fc->first) || (anum > fc->last))
1004 return 0;
1005 fc->messages[anum - fc->first] = 1;
1006 return 0;
1007}
Keep track when getting data from a server.
Definition: nntp.c:93
anum_t first
Definition: nntp.c:95
anum_t last
Definition: nntp.c:96
unsigned char * messages
Definition: nntp.c:98
+ Here is the caller graph for this function:

◆ parse_overview_line()

static int parse_overview_line ( char *  line,
void *  data 
)
static

Parse overview line.

Parameters
lineString to parse
dataFetchCtx
Return values
0Success
-1Failure

Definition at line 1016 of file nntp.c.

1017{
1018 if (!line || !data)
1019 return 0;
1020
1021 struct FetchCtx *fc = data;
1022 struct Mailbox *m = fc->mailbox;
1023 if (!m)
1024 return -1;
1025
1026 struct NntpMboxData *mdata = m->mdata;
1027 struct Email *e = NULL;
1028 char *header = NULL, *field = NULL;
1029 bool save = true;
1030 anum_t anum;
1031
1032 /* parse article number */
1033 field = strchr(line, '\t');
1034 if (field)
1035 *field++ = '\0';
1036 if (sscanf(line, ANUM, &anum) != 1)
1037 return 0;
1038 mutt_debug(LL_DEBUG2, "" ANUM "\n", anum);
1039
1040 /* out of bounds */
1041 if ((anum < fc->first) || (anum > fc->last))
1042 return 0;
1043
1044 /* not in LISTGROUP */
1045 if (!fc->messages[anum - fc->first])
1046 {
1047 progress_update(fc->progress, anum - fc->first + 1, -1);
1048 return 0;
1049 }
1050
1051 /* convert overview line to header */
1052 FILE *fp = mutt_file_mkstemp();
1053 if (!fp)
1054 return -1;
1055
1056 header = mdata->adata->overview_fmt;
1057 while (field)
1058 {
1059 char *b = field;
1060
1061 if (*header)
1062 {
1063 if (!strstr(header, ":full") && (fputs(header, fp) == EOF))
1064 {
1065 mutt_file_fclose(&fp);
1066 return -1;
1067 }
1068 header = strchr(header, '\0') + 1;
1069 }
1070
1071 field = strchr(field, '\t');
1072 if (field)
1073 *field++ = '\0';
1074 if ((fputs(b, fp) == EOF) || (fputc('\n', fp) == EOF))
1075 {
1076 mutt_file_fclose(&fp);
1077 return -1;
1078 }
1079 }
1080 rewind(fp);
1081
1082 /* allocate memory for headers */
1084
1085 /* parse header */
1086 m->emails[m->msg_count] = email_new();
1087 e = m->emails[m->msg_count];
1088 e->env = mutt_rfc822_read_header(fp, e, false, false);
1089 e->env->newsgroups = mutt_str_dup(mdata->group);
1090 e->received = e->date_sent;
1091 mutt_file_fclose(&fp);
1092
1093#ifdef USE_HCACHE
1094 if (fc->hc)
1095 {
1096 char buf[16] = { 0 };
1097
1098 /* try to replace with header from cache */
1099 snprintf(buf, sizeof(buf), ANUM, anum);
1100 struct HCacheEntry hce = hcache_fetch_email(fc->hc, buf, strlen(buf), 0);
1101 if (hce.email)
1102 {
1103 mutt_debug(LL_DEBUG2, "hcache_fetch_email %s\n", buf);
1104 email_free(&e);
1105 e = hce.email;
1106 m->emails[m->msg_count] = e;
1107 e->edata = NULL;
1108 e->read = false;
1109 e->old = false;
1110
1111 /* skip header marked as deleted in cache */
1112 if (e->deleted && !fc->restore)
1113 {
1114 if (mdata->bcache)
1115 {
1116 mutt_debug(LL_DEBUG2, "mutt_bcache_del %s\n", buf);
1117 mutt_bcache_del(mdata->bcache, buf);
1118 }
1119 save = false;
1120 }
1121 }
1122 else
1123 {
1124 /* not cached yet, store header */
1125 mutt_debug(LL_DEBUG2, "hcache_store_email %s\n", buf);
1126 hcache_store_email(fc->hc, buf, strlen(buf), e, 0);
1127 }
1128 }
1129#endif
1130
1131 if (save)
1132 {
1133 e->index = m->msg_count++;
1134 e->read = false;
1135 e->old = false;
1136 e->deleted = false;
1137 e->edata = nntp_edata_new();
1139 nntp_edata_get(e)->article_num = anum;
1140 if (fc->restore)
1141 {
1142 e->changed = true;
1143 }
1144 else
1145 {
1146 nntp_article_status(m, e, NULL, anum);
1147 if (!e->read)
1148 nntp_parse_xref(m, e);
1149 }
1150 if (anum > mdata->last_loaded)
1151 mdata->last_loaded = anum;
1152 }
1153 else
1154 {
1155 email_free(&e);
1156 }
1157
1158 progress_update(fc->progress, anum - fc->first + 1, -1);
1159 return 0;
1160}
int mutt_bcache_del(struct BodyCache *bcache, const char *id)
Delete a file from the Body Cache.
Definition: bcache.c:266
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
struct Envelope * mutt_rfc822_read_header(FILE *fp, struct Email *e, bool user_hdrs, bool weed)
Parses an RFC822 header.
Definition: parse.c:1156
int mutt_file_fclose(FILE **fp)
Close a FILE handle (and NULL the pointer)
Definition: file.c:152
void nntp_edata_free(void **ptr)
Free the private Email data - Implements Email::edata_free() -.
Definition: edata.c:38
struct HCacheEntry hcache_fetch_email(struct HeaderCache *hc, const char *key, size_t keylen, uint32_t uidvalidity)
Multiplexor for StoreOps::fetch.
Definition: hcache.c:562
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:670
void mx_alloc_memory(struct Mailbox *m, int req_size)
Create storage for the emails.
Definition: mx.c:1211
struct NntpEmailData * nntp_edata_new(void)
Create a new NntpEmailData for an Email.
Definition: edata.c:50
static void nntp_parse_xref(struct Mailbox *m, struct Email *e)
Parse cross-reference.
Definition: nntp.c:936
The envelope/body of an email.
Definition: email.h:37
bool read
Email is read.
Definition: email.h:48
void * edata
Driver-specific data.
Definition: email.h:72
bool old
Email is seen, but unread.
Definition: email.h:47
void(* edata_free)(void **ptr)
Definition: email.h:88
bool changed
Email has been edited.
Definition: email.h:75
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:111
time_t received
Time when the message was placed in the mailbox.
Definition: email.h:59
char * newsgroups
List of newsgroups.
Definition: envelope.h:78
struct HeaderCache * hc
Definition: nntp.c:100
struct Progress * progress
Definition: nntp.c:99
struct Mailbox * mailbox
Definition: nntp.c:94
bool restore
Definition: nntp.c:97
Wrapper for Email retrieved from the header cache.
Definition: lib.h:101
struct Email * email
Retrieved email.
Definition: lib.h:104
A mailbox.
Definition: mailbox.h:79
int msg_count
Total number of messages.
Definition: mailbox.h:88
struct Email ** emails
Array of Emails.
Definition: mailbox.h:96
struct BodyCache * bcache
Definition: mdata.h:50
anum_t last_loaded
Definition: mdata.h:39
#define mutt_file_mkstemp()
Definition: tmp.h:36
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ nntp_fetch_headers()

static int nntp_fetch_headers ( struct Mailbox m,
void *  hc,
anum_t  first,
anum_t  last,
bool  restore 
)
static

Fetch headers.

Parameters
mMailbox
hcHeader cache
firstNumber of first header to fetch
lastNumber of last header to fetch
restoreRestore message listed as deleted
Return values
0Success
-1Failure

Definition at line 1172 of file nntp.c.

1173{
1174 if (!m)
1175 return -1;
1176
1177 struct NntpMboxData *mdata = m->mdata;
1178 struct FetchCtx fc = { 0 };
1179 struct Email *e = NULL;
1180 char buf[8192] = { 0 };
1181 int rc = 0;
1182 anum_t current;
1183 anum_t first_over = first;
1184
1185 /* if empty group or nothing to do */
1186 if (!last || (first > last))
1187 return 0;
1188
1189 /* init fetch context */
1190 fc.mailbox = m;
1191 fc.first = first;
1192 fc.last = last;
1193 fc.restore = restore;
1194 fc.messages = mutt_mem_calloc(last - first + 1, sizeof(unsigned char));
1195 if (!fc.messages)
1196 return -1;
1197 fc.hc = hc;
1198
1199 /* fetch list of articles */
1200 const bool c_nntp_listgroup = cs_subset_bool(NeoMutt->sub, "nntp_listgroup");
1201 if (c_nntp_listgroup && mdata->adata->hasLISTGROUP && !mdata->deleted)
1202 {
1203 if (m->verbose)
1204 mutt_message(_("Fetching list of articles..."));
1205 if (mdata->adata->hasLISTGROUPrange)
1206 {
1207 snprintf(buf, sizeof(buf), "LISTGROUP %s " ANUM "-" ANUM "\r\n",
1208 mdata->group, first, last);
1209 }
1210 else
1211 {
1212 snprintf(buf, sizeof(buf), "LISTGROUP %s\r\n", mdata->group);
1213 }
1214 rc = nntp_fetch_lines(mdata, buf, sizeof(buf), NULL, fetch_numbers, &fc);
1215 if (rc > 0)
1216 {
1217 mutt_error("LISTGROUP: %s", buf);
1218 }
1219 if (rc == 0)
1220 {
1221 for (current = first; (current <= last); current++)
1222 {
1223 if (fc.messages[current - first])
1224 continue;
1225
1226 snprintf(buf, sizeof(buf), ANUM, current);
1227 if (mdata->bcache)
1228 {
1229 mutt_debug(LL_DEBUG2, "#1 mutt_bcache_del %s\n", buf);
1230 mutt_bcache_del(mdata->bcache, buf);
1231 }
1232
1233#ifdef USE_HCACHE
1234 if (fc.hc)
1235 {
1236 mutt_debug(LL_DEBUG2, "hcache_delete_email %s\n", buf);
1237 hcache_delete_email(fc.hc, buf, strlen(buf));
1238 }
1239#endif
1240 }
1241 }
1242 }
1243 else
1244 {
1245 for (current = first; current <= last; current++)
1246 fc.messages[current - first] = 1;
1247 }
1248
1249 /* fetching header from cache or server, or fallback to fetch overview */
1250 if (m->verbose)
1251 {
1252 fc.progress = progress_new(MUTT_PROGRESS_READ, last - first + 1);
1253 progress_set_message(fc.progress, _("Fetching message headers..."));
1254 }
1255 for (current = first; (current <= last) && (rc == 0); current++)
1256 {
1257 progress_update(fc.progress, current - first + 1, -1);
1258
1259#ifdef USE_HCACHE
1260 snprintf(buf, sizeof(buf), ANUM, current);
1261#endif
1262
1263 /* delete header from cache that does not exist on server */
1264 if (!fc.messages[current - first])
1265 continue;
1266
1267 /* allocate memory for headers */
1269
1270#ifdef USE_HCACHE
1271 /* try to fetch header from cache */
1272 struct HCacheEntry hce = hcache_fetch_email(fc.hc, buf, strlen(buf), 0);
1273 if (hce.email)
1274 {
1275 mutt_debug(LL_DEBUG2, "hcache_fetch_email %s\n", buf);
1276 e = hce.email;
1277 m->emails[m->msg_count] = e;
1278 e->edata = NULL;
1279
1280 /* skip header marked as deleted in cache */
1281 if (e->deleted && !restore)
1282 {
1283 email_free(&e);
1284 if (mdata->bcache)
1285 {
1286 mutt_debug(LL_DEBUG2, "#2 mutt_bcache_del %s\n", buf);
1287 mutt_bcache_del(mdata->bcache, buf);
1288 }
1289 continue;
1290 }
1291
1292 e->read = false;
1293 e->old = false;
1294 }
1295 else
1296#endif
1297 if (mdata->deleted)
1298 {
1299 /* don't try to fetch header from removed newsgroup */
1300 continue;
1301 }
1302 else if (mdata->adata->hasOVER || mdata->adata->hasXOVER)
1303 {
1304 /* fallback to fetch overview */
1305 if (c_nntp_listgroup && mdata->adata->hasLISTGROUP)
1306 break;
1307 else
1308 continue;
1309 }
1310 else
1311 {
1312 /* fetch header from server */
1313 FILE *fp = mutt_file_mkstemp();
1314 if (!fp)
1315 {
1316 mutt_perror(_("Can't create temporary file"));
1317 rc = -1;
1318 break;
1319 }
1320
1321 snprintf(buf, sizeof(buf), "HEAD " ANUM "\r\n", current);
1322 rc = nntp_fetch_lines(mdata, buf, sizeof(buf), NULL, fetch_tempfile, fp);
1323 if (rc)
1324 {
1325 mutt_file_fclose(&fp);
1326 if (rc < 0)
1327 break;
1328
1329 /* invalid response */
1330 if (!mutt_str_startswith(buf, "423"))
1331 {
1332 mutt_error("HEAD: %s", buf);
1333 break;
1334 }
1335
1336 /* no such article */
1337 if (mdata->bcache)
1338 {
1339 snprintf(buf, sizeof(buf), ANUM, current);
1340 mutt_debug(LL_DEBUG2, "#3 mutt_bcache_del %s\n", buf);
1341 mutt_bcache_del(mdata->bcache, buf);
1342 }
1343 rc = 0;
1344 continue;
1345 }
1346
1347 /* parse header */
1348 m->emails[m->msg_count] = email_new();
1349 e = m->emails[m->msg_count];
1350 e->env = mutt_rfc822_read_header(fp, e, false, false);
1351 e->received = e->date_sent;
1352 mutt_file_fclose(&fp);
1353 }
1354
1355 /* save header in context */
1356 e->index = m->msg_count++;
1357 e->read = false;
1358 e->old = false;
1359 e->deleted = false;
1360 e->edata = nntp_edata_new();
1362 nntp_edata_get(e)->article_num = current;
1363 if (restore)
1364 {
1365 e->changed = true;
1366 }
1367 else
1368 {
1369 nntp_article_status(m, e, NULL, nntp_edata_get(e)->article_num);
1370 if (!e->read)
1371 nntp_parse_xref(m, e);
1372 }
1373 if (current > mdata->last_loaded)
1374 mdata->last_loaded = current;
1375 first_over = current + 1;
1376 }
1377
1378 if (!c_nntp_listgroup || !mdata->adata->hasLISTGROUP)
1379 current = first_over;
1380
1381 /* fetch overview information */
1382 if ((current <= last) && (rc == 0) && !mdata->deleted)
1383 {
1384 char *cmd = mdata->adata->hasOVER ? "OVER" : "XOVER";
1385 snprintf(buf, sizeof(buf), "%s " ANUM "-" ANUM "\r\n", cmd, current, last);
1386 rc = nntp_fetch_lines(mdata, buf, sizeof(buf), NULL, parse_overview_line, &fc);
1387 if (rc > 0)
1388 {
1389 mutt_error("%s: %s", cmd, buf);
1390 }
1391 }
1392
1393 FREE(&fc.messages);
1395 if (rc != 0)
1396 return -1;
1398 return 0;
1399}
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition: helpers.c:48
#define mutt_perror(...)
Definition: logging2.h:93
int hcache_delete_email(struct HeaderCache *hc, const char *key, size_t keylen)
Multiplexor for StoreOps::delete_record.
Definition: hcache.c:739
void * mutt_mem_calloc(size_t nmemb, size_t size)
Allocate zeroed memory on the heap.
Definition: memory.c:50
void mutt_clear_error(void)
Clear the message line (bottom line of screen)
Definition: mutt_logging.c:73
static int parse_overview_line(char *line, void *data)
Parse overview line.
Definition: nntp.c:1016
static int fetch_tempfile(char *line, void *data)
Write line to temporary file.
Definition: nntp.c:977
static int fetch_numbers(char *line, void *data)
Parse article number.
Definition: nntp.c:994
bool verbose
Display status messages?
Definition: mailbox.h:116
bool deleted
Definition: mdata.h:45
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ nntp_group_poll()

static int nntp_group_poll ( struct NntpMboxData mdata,
bool  update_stat 
)
static

Check newsgroup for new articles.

Parameters
mdataNNTP Mailbox data
update_statUpdate the stats?
Return values
1New articles found
0No change
-1Lost connection

Definition at line 1409 of file nntp.c.

1410{
1411 char buf[1024] = { 0 };
1412 anum_t count, first, last;
1413
1414 /* use GROUP command to poll newsgroup */
1415 if (nntp_query(mdata, buf, sizeof(buf)) < 0)
1416 return -1;
1417 if (sscanf(buf, "211 " ANUM " " ANUM " " ANUM, &count, &first, &last) != 3)
1418 return 0;
1419 if ((first == mdata->first_message) && (last == mdata->last_message))
1420 return 0;
1421
1422 /* articles have been renumbered */
1423 if (last < mdata->last_message)
1424 {
1425 mdata->last_cached = 0;
1426 if (mdata->newsrc_len)
1427 {
1428 mutt_mem_realloc(&mdata->newsrc_ent, sizeof(struct NewsrcEntry));
1429 mdata->newsrc_len = 1;
1430 mdata->newsrc_ent[0].first = 1;
1431 mdata->newsrc_ent[0].last = 0;
1432 }
1433 }
1434 mdata->first_message = first;
1435 mdata->last_message = last;
1436 if (!update_stat)
1437 {
1438 return 1;
1439 }
1440 else if (!last || (!mdata->newsrc_ent && !mdata->last_cached))
1441 {
1442 /* update counters */
1443 mdata->unread = count;
1444 }
1445 else
1446 {
1448 }
1449 return 1;
1450}
void nntp_group_unread_stat(struct NntpMboxData *mdata)
Count number of unread articles using .newsrc data.
Definition: newsrc.c:132
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
anum_t last_cached
Definition: mdata.h:40
anum_t last_message
Definition: mdata.h:38
struct NewsrcEntry * newsrc_ent
Definition: mdata.h:47
anum_t unread
Definition: mdata.h:41
unsigned int newsrc_len
Definition: mdata.h:46
anum_t first_message
Definition: mdata.h:37
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ check_mailbox()

static enum MxStatus check_mailbox ( struct Mailbox m)
static

Check current newsgroup for new articles.

Parameters
mMailbox
Return values
enumMxStatus

Leave newsrc locked

Definition at line 1459 of file nntp.c.

1460{
1461 if (!m)
1462 return MX_STATUS_ERROR;
1463
1464 struct NntpMboxData *mdata = m->mdata;
1465 struct NntpAccountData *adata = mdata->adata;
1466 time_t now = mutt_date_now();
1467 enum MxStatus rc = MX_STATUS_OK;
1468 struct HeaderCache *hc = NULL;
1469
1470 const short c_nntp_poll = cs_subset_number(NeoMutt->sub, "nntp_poll");
1471 if (adata->check_time + c_nntp_poll > now)
1472 return MX_STATUS_OK;
1473
1474 mutt_message(_("Checking for new messages..."));
1475 if (nntp_newsrc_parse(adata) < 0)
1476 return MX_STATUS_ERROR;
1477
1478 adata->check_time = now;
1479 int rc2 = nntp_group_poll(mdata, false);
1480 if (rc2 < 0)
1481 {
1482 nntp_newsrc_close(adata);
1483 return -1;
1484 }
1485 if (rc2 != 0)
1487
1488 /* articles have been renumbered, remove all emails */
1489 if (mdata->last_message < mdata->last_loaded)
1490 {
1491 for (int i = 0; i < m->msg_count; i++)
1492 email_free(&m->emails[i]);
1493 m->msg_count = 0;
1494 m->msg_tagged = 0;
1495
1496 mdata->last_loaded = mdata->first_message - 1;
1497 const long c_nntp_context = cs_subset_long(NeoMutt->sub, "nntp_context");
1498 if (c_nntp_context && (mdata->last_message - mdata->last_loaded > c_nntp_context))
1499 mdata->last_loaded = mdata->last_message - c_nntp_context;
1500
1501 rc = MX_STATUS_REOPENED;
1502 }
1503
1504 /* .newsrc has been externally modified */
1505 if (adata->newsrc_modified)
1506 {
1507#ifdef USE_HCACHE
1508 unsigned char *messages = NULL;
1509 char buf[16] = { 0 };
1510 struct Email *e = NULL;
1511 anum_t first = mdata->first_message;
1512
1513 const long c_nntp_context = cs_subset_long(NeoMutt->sub, "nntp_context");
1514 if (c_nntp_context && ((mdata->last_message - first + 1) > c_nntp_context))
1515 first = mdata->last_message - c_nntp_context + 1;
1516 messages = mutt_mem_calloc(mdata->last_loaded - first + 1, sizeof(unsigned char));
1517 hc = nntp_hcache_open(mdata);
1518 nntp_hcache_update(mdata, hc);
1519#endif
1520
1521 /* update flags according to .newsrc */
1522 int j = 0;
1523 for (int i = 0; i < m->msg_count; i++)
1524 {
1525 if (!m->emails[i])
1526 continue;
1527 bool flagged = false;
1528 anum_t anum = nntp_edata_get(m->emails[i])->article_num;
1529
1530#ifdef USE_HCACHE
1531 /* check hcache for flagged and deleted flags */
1532 if (hc)
1533 {
1534 if ((anum >= first) && (anum <= mdata->last_loaded))
1535 messages[anum - first] = 1;
1536
1537 snprintf(buf, sizeof(buf), ANUM, anum);
1538 struct HCacheEntry hce = hcache_fetch_email(hc, buf, strlen(buf), 0);
1539 if (hce.email)
1540 {
1541 bool deleted;
1542
1543 mutt_debug(LL_DEBUG2, "#1 hcache_fetch_email %s\n", buf);
1544 e = hce.email;
1545 e->edata = NULL;
1546 deleted = e->deleted;
1547 flagged = e->flagged;
1548 email_free(&e);
1549
1550 /* header marked as deleted, removing from context */
1551 if (deleted)
1552 {
1553 mutt_set_flag(m, m->emails[i], MUTT_TAG, false, true);
1554 email_free(&m->emails[i]);
1555 continue;
1556 }
1557 }
1558 }
1559#endif
1560
1561 if (!m->emails[i]->changed)
1562 {
1563 m->emails[i]->flagged = flagged;
1564 m->emails[i]->read = false;
1565 m->emails[i]->old = false;
1566 nntp_article_status(m, m->emails[i], NULL, anum);
1567 if (!m->emails[i]->read)
1568 nntp_parse_xref(m, m->emails[i]);
1569 }
1570 m->emails[j++] = m->emails[i];
1571 }
1572
1573#ifdef USE_HCACHE
1574 m->msg_count = j;
1575
1576 /* restore headers without "deleted" flag */
1577 for (anum_t anum = first; anum <= mdata->last_loaded; anum++)
1578 {
1579 if (messages[anum - first])
1580 continue;
1581
1582 snprintf(buf, sizeof(buf), ANUM, anum);
1583 struct HCacheEntry hce = hcache_fetch_email(hc, buf, strlen(buf), 0);
1584 if (hce.email)
1585 {
1586 mutt_debug(LL_DEBUG2, "#2 hcache_fetch_email %s\n", buf);
1588
1589 e = hce.email;
1590 m->emails[m->msg_count] = e;
1591 e->edata = NULL;
1592 if (e->deleted)
1593 {
1594 email_free(&e);
1595 if (mdata->bcache)
1596 {
1597 mutt_debug(LL_DEBUG2, "mutt_bcache_del %s\n", buf);
1598 mutt_bcache_del(mdata->bcache, buf);
1599 }
1600 continue;
1601 }
1602
1603 m->msg_count++;
1604 e->read = false;
1605 e->old = false;
1606 e->edata = nntp_edata_new();
1608 nntp_edata_get(e)->article_num = anum;
1609 nntp_article_status(m, e, NULL, anum);
1610 if (!e->read)
1611 nntp_parse_xref(m, e);
1612 }
1613 }
1614 FREE(&messages);
1615#endif
1616
1617 adata->newsrc_modified = false;
1618 rc = MX_STATUS_REOPENED;
1619 }
1620
1621 /* some emails were removed, mailboxview must be updated */
1622 if (rc == MX_STATUS_REOPENED)
1624
1625 /* fetch headers of new articles */
1626 if (mdata->last_message > mdata->last_loaded)
1627 {
1628 int oldmsgcount = m->msg_count;
1629 bool verbose = m->verbose;
1630 m->verbose = false;
1631#ifdef USE_HCACHE
1632 if (!hc)
1633 {
1634 hc = nntp_hcache_open(mdata);
1635 nntp_hcache_update(mdata, hc);
1636 }
1637#endif
1638 int old_msg_count = m->msg_count;
1639 rc2 = nntp_fetch_headers(m, hc, mdata->last_loaded + 1, mdata->last_message, false);
1640 m->verbose = verbose;
1641 if (rc2 == 0)
1642 {
1643 if (m->msg_count > old_msg_count)
1645 mdata->last_loaded = mdata->last_message;
1646 }
1647 if ((rc == MX_STATUS_OK) && (m->msg_count > oldmsgcount))
1648 rc = MX_STATUS_NEW_MAIL;
1649 }
1650
1651#ifdef USE_HCACHE
1652 hcache_close(&hc);
1653#endif
1654 if (rc != MX_STATUS_OK)
1655 nntp_newsrc_close(adata);
1657 return rc;
1658}
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
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
void hcache_close(struct HeaderCache **ptr)
Multiplexor for StoreOps::close.
Definition: hcache.c:542
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
time_t mutt_date_now(void)
Return the number of seconds since the Unix epoch.
Definition: date.c:446
@ MUTT_TAG
Tagged messages.
Definition: mutt.h:79
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
struct HeaderCache * nntp_hcache_open(struct NntpMboxData *mdata)
Open newsgroup hcache.
Definition: newsrc.c:714
void nntp_hcache_update(struct NntpMboxData *mdata, struct HeaderCache *hc)
Remove stale cached headers.
Definition: newsrc.c:738
int nntp_active_save_cache(struct NntpAccountData *adata)
Save list of all newsgroups to cache.
Definition: newsrc.c:655
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
static int nntp_group_poll(struct NntpMboxData *mdata, bool update_stat)
Check newsgroup for new articles.
Definition: nntp.c:1409
static int nntp_fetch_headers(struct Mailbox *m, void *hc, anum_t first, anum_t last, bool restore)
Fetch headers.
Definition: nntp.c:1172
bool flagged
Marked important?
Definition: email.h:45
Header Cache.
Definition: lib.h:88
int msg_tagged
How many messages are tagged?
Definition: mailbox.h:94
bool newsrc_modified
Definition: adata.h:49
time_t check_time
Definition: adata.h:57
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ nntp_date()

static int nntp_date ( struct NntpAccountData adata,
time_t *  now 
)
static

Get date and time from server.

Parameters
adataNNTP server
nowServer time
Return values
0Success
-1Failure

Definition at line 1667 of file nntp.c.

1668{
1669 if (adata->hasDATE)
1670 {
1671 struct NntpMboxData mdata = { 0 };
1672 char buf[1024] = { 0 };
1673 struct tm tm = { 0 };
1674
1675 mdata.adata = adata;
1676 mdata.group = NULL;
1677 mutt_str_copy(buf, "DATE\r\n", sizeof(buf));
1678 if (nntp_query(&mdata, buf, sizeof(buf)) < 0)
1679 return -1;
1680
1681 if (sscanf(buf, "111 %4d%2d%2d%2d%2d%2d%*s", &tm.tm_year, &tm.tm_mon,
1682 &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) == 6)
1683 {
1684 tm.tm_year -= 1900;
1685 tm.tm_mon--;
1686 *now = timegm(&tm);
1687 if (*now >= 0)
1688 {
1689 mutt_debug(LL_DEBUG1, "server time is %llu\n", (unsigned long long) *now);
1690 return 0;
1691 }
1692 }
1693 }
1694 *now = mutt_date_now();
1695 return 0;
1696}
time_t timegm(struct tm *tm)
Convert struct tm to time_t seconds since epoch.
Definition: timegm.c:69
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ fetch_children()

static int fetch_children ( char *  line,
void *  data 
)
static

Parse XPAT line.

Parameters
lineString to parse
dataChildCtx
Return values
0Always

Definition at line 1704 of file nntp.c.

1705{
1706 struct ChildCtx *cc = data;
1707 anum_t anum;
1708
1709 if (!line || (sscanf(line, ANUM, &anum) != 1))
1710 return 0;
1711 for (unsigned int i = 0; i < cc->mailbox->msg_count; i++)
1712 {
1713 struct Email *e = cc->mailbox->emails[i];
1714 if (!e)
1715 break;
1716 if (nntp_edata_get(e)->article_num == anum)
1717 return 0;
1718 }
1719 if (cc->num >= cc->max)
1720 {
1721 cc->max *= 2;
1722 mutt_mem_realloc(&cc->child, sizeof(anum_t) * cc->max);
1723 }
1724 cc->child[cc->num++] = anum;
1725 return 0;
1726}
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
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ nntp_open_connection()

int nntp_open_connection ( struct NntpAccountData adata)

Connect to server, authenticate and get capabilities.

Parameters
adataNNTP server
Return values
0Success
-1Failure

Definition at line 1734 of file nntp.c.

1735{
1736 struct Connection *conn = adata->conn;
1737 char buf[256] = { 0 };
1738 int cap;
1739 bool posting = false, auth = true;
1740
1741 if (adata->status == NNTP_OK)
1742 return 0;
1743 if (adata->status == NNTP_BYE)
1744 return -1;
1745 adata->status = NNTP_NONE;
1746
1747 if (mutt_socket_open(conn) < 0)
1748 return -1;
1749
1750 if (mutt_socket_readln(buf, sizeof(buf), conn) < 0)
1751 return nntp_connect_error(adata);
1752
1753 if (mutt_str_startswith(buf, "200"))
1754 {
1755 posting = true;
1756 }
1757 else if (!mutt_str_startswith(buf, "201"))
1758 {
1759 mutt_socket_close(conn);
1761 mutt_error("%s", buf);
1762 return -1;
1763 }
1764
1765 /* get initial capabilities */
1766 cap = nntp_capabilities(adata);
1767 if (cap < 0)
1768 return -1;
1769
1770 /* tell news server to switch to mode reader if it isn't so */
1771 if (cap > 0)
1772 {
1773 if ((mutt_socket_send(conn, "MODE READER\r\n") < 0) ||
1774 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
1775 {
1776 return nntp_connect_error(adata);
1777 }
1778
1779 if (mutt_str_startswith(buf, "200"))
1780 {
1781 posting = true;
1782 }
1783 else if (mutt_str_startswith(buf, "201"))
1784 {
1785 posting = false;
1786 }
1787 else if (adata->hasCAPABILITIES)
1788 {
1789 /* error if has capabilities, ignore result if no capabilities */
1790 mutt_socket_close(conn);
1791 mutt_error(_("Could not switch to reader mode"));
1792 return -1;
1793 }
1794
1795 /* recheck capabilities after MODE READER */
1796 if (adata->hasCAPABILITIES)
1797 {
1798 cap = nntp_capabilities(adata);
1799 if (cap < 0)
1800 return -1;
1801 }
1802 }
1803
1804 mutt_message(_("Connected to %s. %s"), conn->account.host,
1805 posting ? _("Posting is ok") : _("Posting is NOT ok"));
1806 mutt_sleep(1);
1807
1808#ifdef USE_SSL
1809 /* Attempt STARTTLS if available and desired. */
1810 const bool c_ssl_force_tls = cs_subset_bool(NeoMutt->sub, "ssl_force_tls");
1811 if ((adata->use_tls != 1) && (adata->hasSTARTTLS || c_ssl_force_tls))
1812 {
1813 if (adata->use_tls == 0)
1814 {
1815 adata->use_tls = c_ssl_force_tls ||
1816 (query_quadoption(_("Secure connection with TLS?"),
1817 NeoMutt->sub, "ssl_starttls") == MUTT_YES) ?
1818 2 :
1819 1;
1820 }
1821 if (adata->use_tls == 2)
1822 {
1823 if ((mutt_socket_send(conn, "STARTTLS\r\n") < 0) ||
1824 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
1825 {
1826 return nntp_connect_error(adata);
1827 }
1828 // Clear any data after the STARTTLS acknowledgement
1829 mutt_socket_empty(conn);
1830 if (!mutt_str_startswith(buf, "382"))
1831 {
1832 adata->use_tls = 0;
1833 mutt_error("STARTTLS: %s", buf);
1834 }
1835 else if (mutt_ssl_starttls(conn))
1836 {
1837 adata->use_tls = 0;
1838 adata->status = NNTP_NONE;
1839 mutt_socket_close(adata->conn);
1840 mutt_error(_("Could not negotiate TLS connection"));
1841 return -1;
1842 }
1843 else
1844 {
1845 /* recheck capabilities after STARTTLS */
1846 cap = nntp_capabilities(adata);
1847 if (cap < 0)
1848 return -1;
1849 }
1850 }
1851 }
1852#endif
1853
1854 /* authentication required? */
1855 if (conn->account.flags & MUTT_ACCT_USER)
1856 {
1857 if (!conn->account.user[0])
1858 auth = false;
1859 }
1860 else
1861 {
1862 if ((mutt_socket_send(conn, "STAT\r\n") < 0) ||
1863 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
1864 {
1865 return nntp_connect_error(adata);
1866 }
1867 if (!mutt_str_startswith(buf, "480"))
1868 auth = false;
1869 }
1870
1871 /* authenticate */
1872 if (auth && (nntp_auth(adata) < 0))
1873 return -1;
1874
1875 /* get final capabilities after authentication */
1876 if (adata->hasCAPABILITIES && (auth || (cap > 0)))
1877 {
1878 cap = nntp_capabilities(adata);
1879 if (cap < 0)
1880 return -1;
1881 if (cap > 0)
1882 {
1883 mutt_socket_close(conn);
1884 mutt_error(_("Could not switch to reader mode"));
1885 return -1;
1886 }
1887 }
1888
1889 /* attempt features */
1890 if (nntp_attempt_features(adata) < 0)
1891 return -1;
1892
1893 adata->status = NNTP_OK;
1894 return 0;
1895}
#define MUTT_ACCT_USER
User field has been set.
Definition: connaccount.h:44
int mutt_ssl_starttls(struct Connection *conn)
Negotiate TLS over an already opened connection.
Definition: gnutls.c:1144
void mutt_str_remove_trailing_ws(char *s)
Trim trailing whitespace from a string.
Definition: string.c:637
void mutt_sleep(short s)
Sleep for a while.
Definition: muttlib.c:1418
static int nntp_auth(struct NntpAccountData *adata)
Get login, password and authenticate.
Definition: nntp.c:435
static int nntp_capabilities(struct NntpAccountData *adata)
Get capabilities.
Definition: nntp.c:141
static int nntp_attempt_features(struct NntpAccountData *adata)
Detect supported commands.
Definition: nntp.c:250
enum QuadOption query_quadoption(const char *prompt, struct ConfigSubset *sub, const char *name)
Ask the user a quad-question.
Definition: question.c:369
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
char host[128]
Server to login to.
Definition: connaccount.h:54
unsigned int use_tls
Definition: adata.h:46
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ nntp_post()

int nntp_post ( struct Mailbox m,
const char *  msg 
)

Post article.

Parameters
mMailbox
msgMessage to post
Return values
0Success
-1Failure

Definition at line 1904 of file nntp.c.

1905{
1906 struct NntpMboxData *mdata = NULL;
1907 struct NntpMboxData tmp_mdata = { 0 };
1908 char buf[1024] = { 0 };
1909
1910 if (m && (m->type == MUTT_NNTP))
1911 {
1912 mdata = m->mdata;
1913 }
1914 else
1915 {
1916 const char *const c_news_server = cs_subset_string(NeoMutt->sub, "news_server");
1917 CurrentNewsSrv = nntp_select_server(m, c_news_server, false);
1918 if (!CurrentNewsSrv)
1919 return -1;
1920
1921 mdata = &tmp_mdata;
1922 mdata->adata = CurrentNewsSrv;
1923 mdata->group = NULL;
1924 }
1925
1926 FILE *fp = mutt_file_fopen(msg, "r");
1927 if (!fp)
1928 {
1929 mutt_perror("%s", msg);
1930 return -1;
1931 }
1932
1933 mutt_str_copy(buf, "POST\r\n", sizeof(buf));
1934 if (nntp_query(mdata, buf, sizeof(buf)) < 0)
1935 {
1936 mutt_file_fclose(&fp);
1937 return -1;
1938 }
1939 if (buf[0] != '3')
1940 {
1941 mutt_error(_("Can't post article: %s"), buf);
1942 mutt_file_fclose(&fp);
1943 return -1;
1944 }
1945
1946 buf[0] = '.';
1947 buf[1] = '\0';
1948 while (fgets(buf + 1, sizeof(buf) - 2, fp))
1949 {
1950 size_t len = strlen(buf);
1951 if (buf[len - 1] == '\n')
1952 {
1953 buf[len - 1] = '\r';
1954 buf[len] = '\n';
1955 len++;
1956 buf[len] = '\0';
1957 }
1958 if (mutt_socket_send_d(mdata->adata->conn, (buf[1] == '.') ? buf : buf + 1,
1959 MUTT_SOCK_LOG_FULL) < 0)
1960 {
1961 mutt_file_fclose(&fp);
1962 return nntp_connect_error(mdata->adata);
1963 }
1964 }
1965 mutt_file_fclose(&fp);
1966
1967 if (((buf[strlen(buf) - 1] != '\n') &&
1968 (mutt_socket_send_d(mdata->adata->conn, "\r\n", MUTT_SOCK_LOG_FULL) < 0)) ||
1969 (mutt_socket_send_d(mdata->adata->conn, ".\r\n", MUTT_SOCK_LOG_FULL) < 0) ||
1970 (mutt_socket_readln(buf, sizeof(buf), mdata->adata->conn) < 0))
1971 {
1972 return nntp_connect_error(mdata->adata);
1973 }
1974 if (buf[0] != '2')
1975 {
1976 mutt_error(_("Can't post article: %s"), buf);
1977 return -1;
1978 }
1979 return 0;
1980}
FILE * mutt_file_fopen(const char *path, const char *mode)
Call fopen() safely.
Definition: file.c:636
@ MUTT_NNTP
'NNTP' (Usenet) Mailbox type
Definition: mailbox.h:49
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
struct NntpAccountData * CurrentNewsSrv
Current news server.
Definition: nntp.c:77
enum MailboxType type
Mailbox type.
Definition: mailbox.h:102
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ nntp_active_fetch()

int nntp_active_fetch ( struct NntpAccountData adata,
bool  mark_new 
)

Fetch list of all newsgroups from server.

Parameters
adataNNTP server
mark_newMark the groups as new
Return values
0Success
-1Failure

Definition at line 1989 of file nntp.c.

1990{
1991 struct NntpMboxData tmp_mdata = { 0 };
1992 char msg[256] = { 0 };
1993 char buf[1024] = { 0 };
1994 unsigned int i;
1995 int rc;
1996
1997 snprintf(msg, sizeof(msg), _("Loading list of groups from server %s..."),
1999 mutt_message("%s", msg);
2000 if (nntp_date(adata, &adata->newgroups_time) < 0)
2001 return -1;
2002
2003 tmp_mdata.adata = adata;
2004 tmp_mdata.group = NULL;
2005 i = adata->groups_num;
2006 mutt_str_copy(buf, "LIST\r\n", sizeof(buf));
2007 rc = nntp_fetch_lines(&tmp_mdata, buf, sizeof(buf), msg, nntp_add_group, adata);
2008 if (rc)
2009 {
2010 if (rc > 0)
2011 {
2012 mutt_error("LIST: %s", buf);
2013 }
2014 return -1;
2015 }
2016
2017 if (mark_new)
2018 {
2019 for (; i < adata->groups_num; i++)
2020 {
2021 struct NntpMboxData *mdata = adata->groups_list[i];
2022 mdata->has_new_mail = true;
2023 }
2024 }
2025
2026 for (i = 0; i < adata->groups_num; i++)
2027 {
2028 struct NntpMboxData *mdata = adata->groups_list[i];
2029
2030 if (mdata && mdata->deleted && !mdata->newsrc_ent)
2031 {
2033 mutt_hash_delete(adata->groups_hash, mdata->group, NULL);
2034 adata->groups_list[i] = NULL;
2035 }
2036 }
2037
2038 const bool c_nntp_load_description = cs_subset_bool(NeoMutt->sub, "nntp_load_description");
2039 if (c_nntp_load_description)
2040 rc = get_description(&tmp_mdata, "*", _("Loading descriptions..."));
2041
2043 if (rc < 0)
2044 return -1;
2046 return 0;
2047}
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 nntp_delete_group_cache(struct NntpMboxData *mdata)
Remove hcache and bcache of newsgroup.
Definition: newsrc.c:814
int nntp_add_group(char *line, void *data)
Parse newsgroup.
Definition: newsrc.c:580
static int nntp_date(struct NntpAccountData *adata, time_t *now)
Get date and time from server.
Definition: nntp.c:1667
static int get_description(struct NntpMboxData *mdata, const char *wildmat, const char *msg)
Fetch newsgroups descriptions.
Definition: nntp.c:904
time_t newgroups_time
Definition: adata.h:56
unsigned int groups_num
Definition: adata.h:58
void ** groups_list
Definition: adata.h:60
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ nntp_check_new_groups()

int nntp_check_new_groups ( struct Mailbox m,
struct NntpAccountData adata 
)

Check for new groups/articles in subscribed groups.

Parameters
mMailbox
adataNNTP server
Return values
1New groups found
0No new groups
-1Error

Definition at line 2057 of file nntp.c.

2058{
2059 struct NntpMboxData tmp_mdata = { 0 };
2060 time_t now = 0;
2061 char buf[1024] = { 0 };
2062 char *msg = _("Checking for new newsgroups...");
2063 unsigned int i;
2064 int rc, update_active = false;
2065
2066 if (!adata || !adata->newgroups_time)
2067 return -1;
2068
2069 /* check subscribed newsgroups for new articles */
2070 const bool c_show_new_news = cs_subset_bool(NeoMutt->sub, "show_new_news");
2071 if (c_show_new_news)
2072 {
2073 mutt_message(_("Checking for new messages..."));
2074 for (i = 0; i < adata->groups_num; i++)
2075 {
2076 struct NntpMboxData *mdata = adata->groups_list[i];
2077
2078 if (mdata && mdata->subscribed)
2079 {
2080 rc = nntp_group_poll(mdata, true);
2081 if (rc < 0)
2082 return -1;
2083 if (rc > 0)
2084 update_active = true;
2085 }
2086 }
2087 }
2088 else if (adata->newgroups_time)
2089 {
2090 return 0;
2091 }
2092
2093 /* get list of new groups */
2094 mutt_message("%s", msg);
2095 if (nntp_date(adata, &now) < 0)
2096 return -1;
2097 tmp_mdata.adata = adata;
2098 if (m && m->mdata)
2099 tmp_mdata.group = ((struct NntpMboxData *) m->mdata)->group;
2100 else
2101 tmp_mdata.group = NULL;
2102 i = adata->groups_num;
2103 struct tm tm = mutt_date_gmtime(adata->newgroups_time);
2104 snprintf(buf, sizeof(buf), "NEWGROUPS %02d%02d%02d %02d%02d%02d GMT\r\n",
2105 tm.tm_year % 100, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
2106 rc = nntp_fetch_lines(&tmp_mdata, buf, sizeof(buf), msg, nntp_add_group, adata);
2107 if (rc)
2108 {
2109 if (rc > 0)
2110 {
2111 mutt_error("NEWGROUPS: %s", buf);
2112 }
2113 return -1;
2114 }
2115
2116 /* new groups found */
2117 rc = 0;
2118 if (adata->groups_num != i)
2119 {
2120 int groups_num = i;
2121
2122 adata->newgroups_time = now;
2123 for (; i < adata->groups_num; i++)
2124 {
2125 struct NntpMboxData *mdata = adata->groups_list[i];
2126 mdata->has_new_mail = true;
2127 }
2128
2129 /* loading descriptions */
2130 const bool c_nntp_load_description = cs_subset_bool(NeoMutt->sub, "nntp_load_description");
2131 if (c_nntp_load_description)
2132 {
2133 unsigned int count = 0;
2134 struct Progress *progress = progress_new(MUTT_PROGRESS_READ, adata->groups_num - i);
2135 progress_set_message(progress, _("Loading descriptions..."));
2136
2137 for (i = groups_num; i < adata->groups_num; i++)
2138 {
2139 struct NntpMboxData *mdata = adata->groups_list[i];
2140
2141 if (get_description(mdata, NULL, NULL) < 0)
2142 {
2143 progress_free(&progress);
2144 return -1;
2145 }
2146 progress_update(progress, ++count, -1);
2147 }
2148 progress_free(&progress);
2149 }
2150 update_active = true;
2151 rc = 1;
2152 }
2153 if (update_active)
2156 return rc;
2157}
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
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ nntp_check_msgid()

int nntp_check_msgid ( struct Mailbox m,
const char *  msgid 
)

Fetch article by Message-ID.

Parameters
mMailbox
msgidMessage ID
Return values
0Success
1No such article
-1Error

Definition at line 2167 of file nntp.c.

2168{
2169 if (!m)
2170 return -1;
2171
2172 struct NntpMboxData *mdata = m->mdata;
2173 char buf[1024] = { 0 };
2174
2175 FILE *fp = mutt_file_mkstemp();
2176 if (!fp)
2177 {
2178 mutt_perror(_("Can't create temporary file"));
2179 return -1;
2180 }
2181
2182 snprintf(buf, sizeof(buf), "HEAD %s\r\n", msgid);
2183 int rc = nntp_fetch_lines(mdata, buf, sizeof(buf), NULL, fetch_tempfile, fp);
2184 if (rc)
2185 {
2186 mutt_file_fclose(&fp);
2187 if (rc < 0)
2188 return -1;
2189 if (mutt_str_startswith(buf, "430"))
2190 return 1;
2191 mutt_error("HEAD: %s", buf);
2192 return -1;
2193 }
2194
2195 /* parse header */
2197 m->emails[m->msg_count] = email_new();
2198 struct Email *e = m->emails[m->msg_count];
2199 e->edata = nntp_edata_new();
2201 e->env = mutt_rfc822_read_header(fp, e, false, false);
2202 mutt_file_fclose(&fp);
2203
2204 /* get article number */
2205 if (e->env->xref)
2206 {
2207 nntp_parse_xref(m, e);
2208 }
2209 else
2210 {
2211 snprintf(buf, sizeof(buf), "STAT %s\r\n", msgid);
2212 if (nntp_query(mdata, buf, sizeof(buf)) < 0)
2213 {
2214 email_free(&e);
2215 return -1;
2216 }
2217 sscanf(buf + 4, ANUM, &nntp_edata_get(e)->article_num);
2218 }
2219
2220 /* reset flags */
2221 e->read = false;
2222 e->old = false;
2223 e->deleted = false;
2224 e->changed = true;
2225 e->received = e->date_sent;
2226 e->index = m->msg_count++;
2228 return 0;
2229}
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ nntp_check_children()

int nntp_check_children ( struct Mailbox m,
const char *  msgid 
)

Fetch children of article with the Message-ID.

Parameters
mMailbox
msgidMessage ID to find
Return values
0Success
-1Failure

Definition at line 2238 of file nntp.c.

2239{
2240 if (!m)
2241 return -1;
2242
2243 struct NntpMboxData *mdata = m->mdata;
2244 char buf[256] = { 0 };
2245 int rc;
2246 struct HeaderCache *hc = NULL;
2247
2248 if (!mdata || !mdata->adata)
2249 return -1;
2250 if (mdata->first_message > mdata->last_loaded)
2251 return 0;
2252
2253 /* init context */
2254 struct ChildCtx cc = { 0 };
2255 cc.mailbox = m;
2256 cc.num = 0;
2257 cc.max = 10;
2258 cc.child = mutt_mem_malloc(sizeof(anum_t) * cc.max);
2259
2260 /* fetch numbers of child messages */
2261 snprintf(buf, sizeof(buf), "XPAT References " ANUM "-" ANUM " *%s*\r\n",
2262 mdata->first_message, mdata->last_loaded, msgid);
2263 rc = nntp_fetch_lines(mdata, buf, sizeof(buf), NULL, fetch_children, &cc);
2264 if (rc)
2265 {
2266 FREE(&cc.child);
2267 if (rc > 0)
2268 {
2269 if (!mutt_str_startswith(buf, "500"))
2270 {
2271 mutt_error("XPAT: %s", buf);
2272 }
2273 else
2274 {
2275 mutt_error(_("Unable to find child articles because server does not support XPAT command"));
2276 }
2277 }
2278 return -1;
2279 }
2280
2281 /* fetch all found messages */
2282 bool verbose = m->verbose;
2283 m->verbose = false;
2284#ifdef USE_HCACHE
2285 hc = nntp_hcache_open(mdata);
2286#endif
2287 int old_msg_count = m->msg_count;
2288 for (int i = 0; i < cc.num; i++)
2289 {
2290 rc = nntp_fetch_headers(m, hc, cc.child[i], cc.child[i], true);
2291 if (rc < 0)
2292 break;
2293 }
2294 if (m->msg_count > old_msg_count)
2296
2297#ifdef USE_HCACHE
2298 hcache_close(&hc);
2299#endif
2300 m->verbose = verbose;
2301 FREE(&cc.child);
2302 return (rc < 0) ? -1 : 0;
2303}
static int fetch_children(char *line, void *data)
Parse XPAT line.
Definition: nntp.c:1704
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

Variable Documentation

◆ CurrentNewsSrv

struct NntpAccountData* CurrentNewsSrv = NULL

Current news server.

Current NNTP news server.

Definition at line 77 of file nntp.c.

◆ OverviewFmt

const char* OverviewFmt
static
Initial value:
= "Subject:\0"
"From:\0"
"Date:\0"
"Message-ID:\0"
"References:\0"
"Content-Length:\0"
"Lines:\0"
"\0"

Fields to get from server, if it supports the LIST OVERVIEW.FMT feature.

Definition at line 80 of file nntp.c.