NeoMutt  2025-09-05-2-g4bf191
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 <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_sort_unsorted (const struct Email *a, const struct Email *b, bool reverse)
 Restore the 'unsorted' order of emails - Implements sort_email_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
  • Richard Russon
  • Pietro Cerutti
  • Ian Zimmerman
  • Dennis Schön

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 126 of file nntp.c.

127{
128 adata->status = NNTP_NONE;
129 mutt_error(_("Server closed connection"));
130 return -1;
131}
#define mutt_error(...)
Definition: logging2.h:93
#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 140 of file nntp.c.

141{
142 struct Connection *conn = adata->conn;
143 bool mode_reader = false;
144 char authinfo[1024] = { 0 };
145
146 adata->hasCAPABILITIES = false;
147 adata->hasSTARTTLS = false;
148 adata->hasDATE = false;
149 adata->hasLIST_NEWSGROUPS = false;
150 adata->hasLISTGROUP = false;
151 adata->hasLISTGROUPrange = false;
152 adata->hasOVER = false;
153 FREE(&adata->authenticators);
154
155 struct Buffer *buf = buf_pool_get();
156
157 if ((mutt_socket_send(conn, "CAPABILITIES\r\n") < 0) ||
158 (mutt_socket_buffer_readln(buf, conn) < 0))
159 {
160 buf_pool_release(&buf);
161 return nntp_connect_error(adata);
162 }
163
164 /* no capabilities */
165 if (!mutt_str_startswith(buf_string(buf), "101"))
166 {
167 buf_pool_release(&buf);
168 return 1;
169 }
170 adata->hasCAPABILITIES = true;
171
172 /* parse capabilities */
173 do
174 {
175 size_t plen = 0;
176 if (mutt_socket_buffer_readln(buf, conn) < 0)
177 {
178 buf_pool_release(&buf);
179 return nntp_connect_error(adata);
180 }
181 if (mutt_str_equal("STARTTLS", buf_string(buf)))
182 {
183 adata->hasSTARTTLS = true;
184 }
185 else if (mutt_str_equal("MODE-READER", buf_string(buf)))
186 {
187 mode_reader = true;
188 }
189 else if (mutt_str_equal("READER", buf_string(buf)))
190 {
191 adata->hasDATE = true;
192 adata->hasLISTGROUP = true;
193 adata->hasLISTGROUPrange = true;
194 }
195 else if ((plen = mutt_str_startswith(buf_string(buf), "AUTHINFO ")))
196 {
197 buf_addch(buf, ' ');
198 mutt_str_copy(authinfo, buf->data + plen - 1, sizeof(authinfo));
199 }
200#ifdef USE_SASL_CYRUS
201 else if ((plen = mutt_str_startswith(buf_string(buf), "SASL ")))
202 {
203 char *p = buf->data + plen;
204 while (*p == ' ')
205 p++;
206 adata->authenticators = mutt_str_dup(p);
207 }
208#endif
209 else if (mutt_str_equal("OVER", buf_string(buf)))
210 {
211 adata->hasOVER = true;
212 }
213 else if (mutt_str_startswith(buf_string(buf), "LIST "))
214 {
215 const char *p = buf_find_string(buf, " NEWSGROUPS");
216 if (p)
217 {
218 p += 11;
219 if ((*p == '\0') || (*p == ' '))
220 adata->hasLIST_NEWSGROUPS = true;
221 }
222 }
223 } while (!mutt_str_equal(".", buf_string(buf)));
224 buf_reset(buf);
225
226#ifdef USE_SASL_CYRUS
227 if (adata->authenticators && mutt_istr_find(authinfo, " SASL "))
228 buf_strcpy(buf, adata->authenticators);
229#endif
230 if (mutt_istr_find(authinfo, " USER "))
231 {
232 if (!buf_is_empty(buf))
233 buf_addch(buf, ' ');
234 buf_addstr(buf, "USER");
235 }
237 buf_pool_release(&buf);
238
239 /* current mode is reader */
240 if (adata->hasDATE)
241 return 0;
242
243 /* server is mode-switching, need to switch to reader mode */
244 if (mode_reader)
245 return 1;
246
247 mutt_socket_close(conn);
248 adata->status = NNTP_BYE;
249 mutt_error(_("Server doesn't support reader mode"));
250 return -1;
251}
const char * buf_find_string(const struct Buffer *buf, const char *s)
Return a pointer to a substring found in the buffer.
Definition: buffer.c:638
void buf_reset(struct Buffer *buf)
Reset an existing Buffer.
Definition: buffer.c:76
bool buf_is_empty(const struct Buffer *buf)
Is the Buffer empty?
Definition: buffer.c:291
size_t buf_addch(struct Buffer *buf, char c)
Add a single character to a Buffer.
Definition: buffer.c:241
size_t buf_addstr(struct Buffer *buf, const char *s)
Add a string to a Buffer.
Definition: buffer.c:226
size_t buf_strcpy(struct Buffer *buf, const char *s)
Copy a string into a Buffer.
Definition: buffer.c:395
static const char * buf_string(const struct Buffer *buf)
Convert a buffer to a const char * "string".
Definition: buffer.h:96
#define FREE(x)
Definition: memory.h:62
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:254
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:659
const char * mutt_istr_find(const char *haystack, const char *needle)
Find first occurrence of string (ignoring case)
Definition: string.c:522
size_t mutt_str_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix.
Definition: string.c:231
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:580
char * mutt_str_replace(char **p, const char *s)
Replace one string with another.
Definition: string.c:281
@ NNTP_BYE
Disconnected from server.
Definition: private.h:46
static int nntp_connect_error(struct NntpAccountData *adata)
Signal a failed connection.
Definition: nntp.c:126
struct Buffer * buf_pool_get(void)
Get a Buffer from the pool.
Definition: pool.c:82
void buf_pool_release(struct Buffer **ptr)
Return a Buffer to the pool.
Definition: pool.c:96
int mutt_socket_close(struct Connection *conn)
Close a socket.
Definition: socket.c:100
#define mutt_socket_send(conn, buf)
Definition: socket.h:57
#define mutt_socket_buffer_readln(buf, conn)
Definition: socket.h:61
String manipulation buffer.
Definition: buffer.h:36
char * data
Pointer to data.
Definition: buffer.h:37
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 259 of file nntp.c.

260{
261 struct Connection *conn = adata->conn;
262 char buf[1024] = { 0 };
263 int rc = -1;
264
265 /* no CAPABILITIES, trying DATE, LISTGROUP, LIST NEWSGROUPS */
266 if (!adata->hasCAPABILITIES)
267 {
268 if ((mutt_socket_send(conn, "DATE\r\n") < 0) ||
269 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
270 {
271 goto fail;
272 }
273 if (!mutt_str_startswith(buf, "500"))
274 adata->hasDATE = true;
275
276 if ((mutt_socket_send(conn, "LISTGROUP\r\n") < 0) ||
277 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
278 {
279 goto fail;
280 }
281 if (!mutt_str_startswith(buf, "500"))
282 adata->hasLISTGROUP = true;
283
284 if ((mutt_socket_send(conn, "LIST NEWSGROUPS +\r\n") < 0) ||
285 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
286 {
287 goto fail;
288 }
289 if (!mutt_str_startswith(buf, "500"))
290 adata->hasLIST_NEWSGROUPS = true;
291 if (mutt_str_startswith(buf, "215"))
292 {
293 do
294 {
295 if (mutt_socket_readln(buf, sizeof(buf), conn) < 0)
296 goto fail;
297 } while (!mutt_str_equal(".", buf));
298 }
299 }
300
301 /* no LIST NEWSGROUPS, trying XGTITLE */
302 if (!adata->hasLIST_NEWSGROUPS)
303 {
304 if ((mutt_socket_send(conn, "XGTITLE\r\n") < 0) ||
305 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
306 {
307 goto fail;
308 }
309 if (!mutt_str_startswith(buf, "500"))
310 adata->hasXGTITLE = true;
311 }
312
313 /* no OVER, trying XOVER */
314 if (!adata->hasOVER)
315 {
316 if ((mutt_socket_send(conn, "XOVER\r\n") < 0) ||
317 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
318 {
319 goto fail;
320 }
321 if (!mutt_str_startswith(buf, "500"))
322 adata->hasXOVER = true;
323 }
324
325 /* trying LIST OVERVIEW.FMT */
326 if (adata->hasOVER || adata->hasXOVER)
327 {
328 if ((mutt_socket_send(conn, "LIST OVERVIEW.FMT\r\n") < 0) ||
329 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
330 {
331 goto fail;
332 }
333 if (!mutt_str_startswith(buf, "215"))
334 {
336 }
337 else
338 {
339 bool cont = false;
340 size_t buflen = 2048, off = 0, b = 0;
341
342 FREE(&adata->overview_fmt);
343 adata->overview_fmt = MUTT_MEM_MALLOC(buflen, char);
344
345 while (true)
346 {
347 if ((buflen - off) < 1024)
348 {
349 buflen *= 2;
350 MUTT_MEM_REALLOC(&adata->overview_fmt, buflen, char);
351 }
352
353 const int chunk = mutt_socket_readln_d(adata->overview_fmt + off,
354 buflen - off, conn, MUTT_SOCK_LOG_HDR);
355 if (chunk < 0)
356 {
357 FREE(&adata->overview_fmt);
358 goto fail;
359 }
360
361 if (!cont && mutt_str_equal(".", adata->overview_fmt + off))
362 break;
363
364 cont = (chunk >= (buflen - off));
365 off += strlen(adata->overview_fmt + off);
366 if (!cont)
367 {
368 if (adata->overview_fmt[b] == ':')
369 {
370 memmove(adata->overview_fmt + b, adata->overview_fmt + b + 1, off - b - 1);
371 adata->overview_fmt[off - 1] = ':';
372 }
373 char *colon = strchr(adata->overview_fmt + b, ':');
374 if (!colon)
375 adata->overview_fmt[off++] = ':';
376 else if (!mutt_str_equal(colon + 1, "full"))
377 off = colon + 1 - adata->overview_fmt;
378 if (strcasecmp(adata->overview_fmt + b, "Bytes:") == 0)
379 {
380 size_t len = strlen(adata->overview_fmt + b);
381 mutt_str_copy(adata->overview_fmt + b, "Content-Length:", len + 1);
382 off = b + len;
383 }
384 adata->overview_fmt[off++] = '\0';
385 b = off;
386 }
387 }
388 adata->overview_fmt[off++] = '\0';
389 MUTT_MEM_REALLOC(&adata->overview_fmt, off, char);
390 }
391 }
392 rc = 0; // Success
393
394fail:
395 if (rc < 0)
396 nntp_connect_error(adata);
397
398 return rc;
399}
#define MUTT_MEM_REALLOC(pptr, n, type)
Definition: memory.h:50
#define MUTT_MEM_MALLOC(n, type)
Definition: memory.h:48
static const char * OverviewFmt
Fields to get from server, if it supports the LIST OVERVIEW.FMT feature.
Definition: nntp.c:79
int mutt_socket_readln_d(char *buf, size_t buflen, struct Connection *conn, int dbg)
Read a line from a socket.
Definition: socket.c:238
#define MUTT_SOCK_LOG_HDR
Definition: socket.h:53
#define mutt_socket_readln(buf, buflen, conn)
Definition: socket.h:56
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 451 of file nntp.c.

452{
453 struct Connection *conn = adata->conn;
454 char authenticators[1024] = "USER";
455 char *method = NULL, *a = NULL, *p = NULL;
456 unsigned char flags = conn->account.flags;
457 struct Buffer *buf = buf_pool_get();
458
459 const char *const c_nntp_authenticators = cs_subset_string(NeoMutt->sub, "nntp_authenticators");
460 while (true)
461 {
462 /* get login and password */
463 if ((mutt_account_getuser(&conn->account) < 0) || (conn->account.user[0] == '\0') ||
464 (mutt_account_getpass(&conn->account) < 0) || (conn->account.pass[0] == '\0'))
465 {
466 break;
467 }
468
469 /* get list of authenticators */
470 if (c_nntp_authenticators)
471 {
472 mutt_str_copy(authenticators, c_nntp_authenticators, sizeof(authenticators));
473 }
474 else if (adata->hasCAPABILITIES)
475 {
476 mutt_str_copy(authenticators, adata->authenticators, sizeof(authenticators));
477 p = authenticators;
478 while (*p)
479 {
480 if (*p == ' ')
481 *p = ':';
482 p++;
483 }
484 }
485 p = authenticators;
486 while (*p)
487 {
488 *p = mutt_toupper(*p);
489 p++;
490 }
491
492 mutt_debug(LL_DEBUG1, "available methods: %s\n", adata->authenticators);
493 a = authenticators;
494 while (true)
495 {
496 if (!a)
497 {
498 mutt_error(_("No authenticators available"));
499 break;
500 }
501
502 method = a;
503 a = strchr(a, ':');
504 if (a)
505 *a++ = '\0';
506
507 /* check authenticator */
508 if (adata->hasCAPABILITIES)
509 {
510 if (!adata->authenticators)
511 continue;
512 const char *m = mutt_istr_find(adata->authenticators, method);
513 if (!m)
514 continue;
515 if ((m > adata->authenticators) && (*(m - 1) != ' '))
516 continue;
517 m += strlen(method);
518 if ((*m != '\0') && (*m != ' '))
519 continue;
520 }
521 mutt_debug(LL_DEBUG1, "trying method %s\n", method);
522
523 /* AUTHINFO USER authentication */
524 if (mutt_str_equal(method, "USER"))
525 {
526 // L10N: (%s) is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
527 mutt_message(_("Authenticating (%s)..."), method);
528 buf_printf(buf, "AUTHINFO USER %s\r\n", conn->account.user);
529 if ((mutt_socket_send(conn, buf_string(buf)) < 0) ||
531 {
532 break;
533 }
534
535 /* authenticated, password is not required */
536 if (mutt_str_startswith(buf_string(buf), "281"))
537 {
538 buf_pool_release(&buf);
539 return 0;
540 }
541
542 /* username accepted, sending password */
543 if (mutt_str_startswith(buf_string(buf), "381"))
544 {
545 mutt_debug(MUTT_SOCK_LOG_FULL, "%d> AUTHINFO PASS *\n", conn->fd);
546 buf_printf(buf, "AUTHINFO PASS %s\r\n", conn->account.pass);
547 if ((mutt_socket_send_d(conn, buf_string(buf), MUTT_SOCK_LOG_FULL) < 0) ||
549 {
550 break;
551 }
552
553 /* authenticated */
554 if (mutt_str_startswith(buf_string(buf), "281"))
555 {
556 buf_pool_release(&buf);
557 return 0;
558 }
559 }
560
561 /* server doesn't support AUTHINFO USER, trying next method */
562 if (buf_at(buf, 0) == '5')
563 continue;
564 }
565 else
566 {
567#ifdef USE_SASL_CYRUS
568 sasl_conn_t *saslconn = NULL;
569 sasl_interact_t *interaction = NULL;
570 int rc;
571 char inbuf[1024] = { 0 };
572 const char *mech = NULL;
573 const char *client_out = NULL;
574 unsigned int client_len, len;
575
576 if (mutt_sasl_client_new(conn, &saslconn) < 0)
577 {
578 mutt_debug(LL_DEBUG1, "error allocating SASL connection\n");
579 continue;
580 }
581
582 while (true)
583 {
584 rc = sasl_client_start(saslconn, method, &interaction, &client_out,
585 &client_len, &mech);
586 if (rc != SASL_INTERACT)
587 break;
588 mutt_sasl_interact(interaction);
589 }
590 if ((rc != SASL_OK) && (rc != SASL_CONTINUE))
591 {
592 sasl_dispose(&saslconn);
593 mutt_debug(LL_DEBUG1, "error starting SASL authentication exchange\n");
594 continue;
595 }
596
597 // L10N: (%s) is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
598 mutt_message(_("Authenticating (%s)..."), method);
599 buf_printf(buf, "AUTHINFO SASL %s", method);
600
601 /* looping protocol */
602 while ((rc == SASL_CONTINUE) || ((rc == SASL_OK) && client_len))
603 {
604 /* send out client response */
605 if (client_len)
606 {
607 nntp_log_binbuf(client_out, client_len, "SASL", MUTT_SOCK_LOG_FULL);
608 if (!buf_is_empty(buf))
609 buf_addch(buf, ' ');
610 len = buf_len(buf);
611 if (sasl_encode64(client_out, client_len, buf->data + len,
612 buf->dsize - len, &len) != SASL_OK)
613 {
614 mutt_debug(LL_DEBUG1, "error base64-encoding client response\n");
615 break;
616 }
617 }
618
619 buf_addstr(buf, "\r\n");
620 if (buf_find_char(buf, ' '))
621 {
622 mutt_debug(MUTT_SOCK_LOG_CMD, "%d> AUTHINFO SASL %s%s\n", conn->fd,
623 method, client_len ? " sasl_data" : "");
624 }
625 else
626 {
627 mutt_debug(MUTT_SOCK_LOG_CMD, "%d> sasl_data\n", conn->fd);
628 }
629 client_len = 0;
630 if ((mutt_socket_send_d(conn, buf_string(buf), MUTT_SOCK_LOG_FULL) < 0) ||
631 (mutt_socket_readln_d(inbuf, sizeof(inbuf), conn, MUTT_SOCK_LOG_FULL) < 0))
632 {
633 break;
634 }
635 if (!mutt_str_startswith(inbuf, "283 ") && !mutt_str_startswith(inbuf, "383 "))
636 {
637 mutt_debug(MUTT_SOCK_LOG_FULL, "%d< %s\n", conn->fd, inbuf);
638 break;
639 }
640 inbuf[3] = '\0';
641 mutt_debug(MUTT_SOCK_LOG_FULL, "%d< %s sasl_data\n", conn->fd, inbuf);
642
643 if (mutt_str_equal("=", inbuf + 4))
644 len = 0;
645 else if (sasl_decode64(inbuf + 4, strlen(inbuf + 4), buf->data,
646 buf->dsize - 1, &len) != SASL_OK)
647 {
648 mutt_debug(LL_DEBUG1, "error base64-decoding server response\n");
649 break;
650 }
651 else
652 {
653 nntp_log_binbuf(buf_string(buf), len, "SASL", MUTT_SOCK_LOG_FULL);
654 }
655
656 while (true)
657 {
658 rc = sasl_client_step(saslconn, buf_string(buf), len, &interaction,
659 &client_out, &client_len);
660 if (rc != SASL_INTERACT)
661 break;
662 mutt_sasl_interact(interaction);
663 }
664 if (*inbuf != '3')
665 break;
666
667 buf_reset(buf);
668 } /* looping protocol */
669
670 if ((rc == SASL_OK) && (client_len == 0) && (*inbuf == '2'))
671 {
672 mutt_sasl_setup_conn(conn, saslconn);
673 buf_pool_release(&buf);
674 return 0;
675 }
676
677 /* terminate SASL session */
678 sasl_dispose(&saslconn);
679 if (conn->fd < 0)
680 break;
681 if (mutt_str_startswith(inbuf, "383 "))
682 {
683 if ((mutt_socket_send(conn, "*\r\n") < 0) ||
684 (mutt_socket_readln(inbuf, sizeof(inbuf), conn) < 0))
685 {
686 break;
687 }
688 }
689
690 /* server doesn't support AUTHINFO SASL, trying next method */
691 if (*inbuf == '5')
692 continue;
693#else
694 continue;
695#endif /* USE_SASL_CYRUS */
696 }
697
698 // L10N: %s is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
699 mutt_error(_("%s authentication failed"), method);
700 break;
701 }
702 break;
703 }
704
705 /* error */
706 adata->status = NNTP_BYE;
707 conn->account.flags = flags;
708 if (conn->fd < 0)
709 {
710 mutt_error(_("Server closed connection"));
711 }
712 else
713 {
714 mutt_socket_close(conn);
715 }
716
717 buf_pool_release(&buf);
718 return -1;
719}
int buf_printf(struct Buffer *buf, const char *fmt,...)
Format a string overwriting a Buffer.
Definition: buffer.c:161
size_t buf_len(const struct Buffer *buf)
Calculate the length of a Buffer.
Definition: buffer.c:491
char buf_at(const struct Buffer *buf, size_t offset)
Return the character at the given offset.
Definition: buffer.c:668
const char * buf_find_char(const struct Buffer *buf, const char c)
Return a pointer to a char found in the buffer.
Definition: buffer.c:653
const char * cs_subset_string(const struct ConfigSubset *sub, const char *name)
Get a string config item by name.
Definition: helpers.c:291
int mutt_account_getpass(struct ConnAccount *cac)
Fetch password into ConnAccount, if necessary.
Definition: connaccount.c:131
int mutt_account_getuser(struct ConnAccount *cac)
Retrieve username into ConnAccount, if necessary.
Definition: connaccount.c:52
int mutt_toupper(int arg)
Wrapper for toupper(3)
Definition: ctype.c:139
#define mutt_message(...)
Definition: logging2.h:92
#define mutt_debug(LEVEL,...)
Definition: logging2.h:90
@ LL_DEBUG1
Log at debug level 1.
Definition: logging2.h:44
int mutt_sasl_interact(sasl_interact_t *interaction)
Perform an SASL interaction with the user.
Definition: sasl.c:704
int mutt_sasl_client_new(struct Connection *conn, sasl_conn_t **saslconn)
Wrapper for sasl_client_new()
Definition: sasl.c:606
void mutt_sasl_setup_conn(struct Connection *conn, sasl_conn_t *saslconn)
Set up an SASL connection.
Definition: sasl.c:741
int mutt_socket_buffer_readln_d(struct Buffer *buf, struct Connection *conn, int dbg)
Read a line from a socket into a Buffer.
Definition: socket.c:328
#define MUTT_SOCK_LOG_FULL
Definition: socket.h:54
#define MUTT_SOCK_LOG_CMD
Definition: socket.h:52
#define mutt_socket_send_d(conn, buf, dbg)
Definition: socket.h:58
size_t dsize
Length of data.
Definition: buffer.h:39
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
struct ConnAccount account
Account details: username, password, etc.
Definition: connection.h:49
int fd
Socket file descriptor.
Definition: connection.h:53
Container for Accounts, Notifications.
Definition: neomutt.h:43
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:47
+ 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 729 of file nntp.c.

730{
731 struct NntpAccountData *adata = mdata->adata;
732 if (adata->status == NNTP_BYE)
733 return -1;
734
735 char buf[1024] = { 0 };
736 int rc = -1;
737
738 while (true)
739 {
740 if (adata->status == NNTP_OK)
741 {
742 int rc_send = 0;
743
744 if (*line)
745 {
746 rc_send = mutt_socket_send(adata->conn, line);
747 }
748 else if (mdata->group)
749 {
750 snprintf(buf, sizeof(buf), "GROUP %s\r\n", mdata->group);
751 rc_send = mutt_socket_send(adata->conn, buf);
752 }
753 if (rc_send >= 0)
754 rc_send = mutt_socket_readln(buf, sizeof(buf), adata->conn);
755 if (rc_send >= 0)
756 break;
757 }
758
759 /* reconnect */
760 while (true)
761 {
762 adata->status = NNTP_NONE;
763 if (nntp_open_connection(adata) == 0)
764 break;
765
766 snprintf(buf, sizeof(buf), _("Connection to %s lost. Reconnect?"),
767 adata->conn->account.host);
768 if (query_yesorno(buf, MUTT_YES) != MUTT_YES)
769 {
770 adata->status = NNTP_BYE;
771 goto done;
772 }
773 }
774
775 /* select newsgroup after reconnection */
776 if (mdata->group)
777 {
778 snprintf(buf, sizeof(buf), "GROUP %s\r\n", mdata->group);
779 if ((mutt_socket_send(adata->conn, buf) < 0) ||
780 (mutt_socket_readln(buf, sizeof(buf), adata->conn) < 0))
781 {
783 goto done;
784 }
785 }
786 if (*line == '\0')
787 break;
788 }
789
790 mutt_str_copy(line, buf, linelen);
791 rc = 0;
792
793done:
794 return rc;
795}
@ 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:1765
@ 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:326
void * adata
Private data (for Mailbox backends)
Definition: account.h:42
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 813 of file nntp.c.

815{
816 bool done = false;
817 int rc;
818
819 while (!done)
820 {
821 char buf[1024] = { 0 };
822 char *line = NULL;
823 unsigned int lines = 0;
824 size_t off = 0;
825 struct Progress *progress = NULL;
826
827 mutt_str_copy(buf, query, sizeof(buf));
828 if (nntp_query(mdata, buf, sizeof(buf)) < 0)
829 return -1;
830 if (buf[0] != '2')
831 {
832 mutt_str_copy(query, buf, qlen);
833 return 1;
834 }
835
836 line = MUTT_MEM_MALLOC(sizeof(buf), char);
837 rc = 0;
838
839 if (msg)
840 {
841 progress = progress_new(MUTT_PROGRESS_READ, 0);
842 progress_set_message(progress, "%s", msg);
843 }
844
845 while (true)
846 {
847 char *p = NULL;
848 int chunk = mutt_socket_readln_d(buf, sizeof(buf), mdata->adata->conn, MUTT_SOCK_LOG_FULL);
849 if (chunk < 0)
850 {
851 mdata->adata->status = NNTP_NONE;
852 break;
853 }
854
855 p = buf;
856 if (!off && (buf[0] == '.'))
857 {
858 if (buf[1] == '\0')
859 {
860 done = true;
861 break;
862 }
863 if (buf[1] == '.')
864 p++;
865 }
866
867 mutt_str_copy(line + off, p, sizeof(buf));
868
869 if (chunk >= sizeof(buf))
870 {
871 off += strlen(p);
872 }
873 else
874 {
875 progress_update(progress, ++lines, -1);
876
877 if ((rc == 0) && (func(line, data) < 0))
878 rc = -2;
879 off = 0;
880 }
881
882 MUTT_MEM_REALLOC(&line, off + sizeof(buf), char);
883 }
884 FREE(&line);
885 func(NULL, data);
886 progress_free(&progress);
887 }
888
889 return rc;
890}
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:729
@ MUTT_PROGRESS_READ
Progress tracks elements, according to $read_inc
Definition: lib.h:83
struct Progress * progress_new(enum ProgressType type, size_t size)
Create a new Progress Bar.
Definition: progress.c:139
void progress_free(struct Progress **ptr)
Free a Progress Bar.
Definition: progress.c:110
void progress_set_message(struct Progress *progress, const char *fmt,...) __attribute__((__format__(__printf__
bool progress_update(struct Progress *progress, size_t pos, int percent)
Update the state of the progress bar.
Definition: progress.c:80
+ 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 898 of file nntp.c.

899{
900 if (!line)
901 return 0;
902
903 struct NntpAccountData *adata = data;
904
905 char *desc = strpbrk(line, " \t");
906 if (desc)
907 {
908 *desc++ = '\0';
909 desc += strspn(desc, " \t");
910 }
911 else
912 {
913 desc = strchr(line, '\0');
914 }
915
917 if (mdata && !mutt_str_equal(desc, mdata->desc))
918 {
919 mutt_str_replace(&mdata->desc, desc);
920 mutt_debug(LL_DEBUG2, "group: %s, desc: %s\n", line, desc);
921 }
922 return 0;
923}
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:45
void * mdata
Driver specific data.
Definition: mailbox.h:132
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 935 of file nntp.c.

936{
937 char buf[256] = { 0 };
938 const char *cmd = NULL;
939
940 /* get newsgroup description, if possible */
941 struct NntpAccountData *adata = mdata->adata;
942 if (!wildmat)
943 wildmat = mdata->group;
944 if (adata->hasLIST_NEWSGROUPS)
945 cmd = "LIST NEWSGROUPS";
946 else if (adata->hasXGTITLE)
947 cmd = "XGTITLE";
948 else
949 return 0;
950
951 snprintf(buf, sizeof(buf), "%s %s\r\n", cmd, wildmat);
952 int rc = nntp_fetch_lines(mdata, buf, sizeof(buf), msg, fetch_description, adata);
953 if (rc > 0)
954 {
955 mutt_error("%s: %s", cmd, buf);
956 }
957 return rc;
958}
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:813
static int fetch_description(char *line, void *data)
Parse newsgroup description.
Definition: nntp.c:898
+ 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 967 of file nntp.c.

968{
969 struct NntpMboxData *mdata = m->mdata;
970
971 char *buf = mutt_str_dup(e->env->xref);
972 char *p = buf;
973 while (p)
974 {
975 anum_t anum = 0;
976
977 /* skip to next word */
978 p += strspn(p, " \t");
979 char *grp = p;
980
981 /* skip to end of word */
982 p = strpbrk(p, " \t");
983 if (p)
984 *p++ = '\0';
985
986 /* find colon */
987 char *colon = strchr(grp, ':');
988 if (!colon)
989 continue;
990 *colon++ = '\0';
991 if (sscanf(colon, ANUM_FMT, &anum) != 1)
992 continue;
993
994 nntp_article_status(m, e, grp, anum);
995 if (!nntp_edata_get(e)->article_num && mutt_str_equal(mdata->group, grp))
996 nntp_edata_get(e)->article_num = anum;
997 }
998 FREE(&buf);
999}
void nntp_article_status(struct Mailbox *m, struct Email *e, char *group, anum_t anum)
Get status of articles from .newsrc.
Definition: newsrc.c:1138
struct NntpEmailData * nntp_edata_get(struct Email *e)
Get the private data for this Email.
Definition: edata.c:60
#define ANUM_FMT
Definition: lib.h:62
#define anum_t
Definition: lib.h:61
struct Envelope * env
Envelope information.
Definition: email.h:68
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 1008 of file nntp.c.

1009{
1010 FILE *fp = data;
1011
1012 if (!line)
1013 rewind(fp);
1014 else if ((fputs(line, fp) == EOF) || (fputc('\n', fp) == EOF))
1015 return -1;
1016 return 0;
1017}
+ 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 1025 of file nntp.c.

1026{
1027 struct FetchCtx *fc = data;
1028 anum_t anum = 0;
1029
1030 if (!line)
1031 return 0;
1032 if (sscanf(line, ANUM_FMT, &anum) != 1)
1033 return 0;
1034 if ((anum < fc->first) || (anum > fc->last))
1035 return 0;
1036 fc->messages[anum - fc->first] = 1;
1037 return 0;
1038}
Keep track when getting data from a server.
Definition: nntp.c:92
anum_t first
Definition: nntp.c:94
anum_t last
Definition: nntp.c:95
unsigned char * messages
Definition: nntp.c:97
+ 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 1047 of file nntp.c.

1048{
1049 if (!line || !data)
1050 return 0;
1051
1052 struct FetchCtx *fc = data;
1053 struct Mailbox *m = fc->mailbox;
1054 if (!m)
1055 return -1;
1056
1057 struct NntpMboxData *mdata = m->mdata;
1058 struct Email *e = NULL;
1059 char *header = NULL, *field = NULL;
1060 bool save = true;
1061 anum_t anum = 0;
1062
1063 /* parse article number */
1064 field = strchr(line, '\t');
1065 if (field)
1066 *field++ = '\0';
1067 if (sscanf(line, ANUM_FMT, &anum) != 1)
1068 return 0;
1069 mutt_debug(LL_DEBUG2, "" ANUM_FMT "\n", anum);
1070
1071 /* out of bounds */
1072 if ((anum < fc->first) || (anum > fc->last))
1073 return 0;
1074
1075 /* not in LISTGROUP */
1076 if (!fc->messages[anum - fc->first])
1077 {
1078 progress_update(fc->progress, anum - fc->first + 1, -1);
1079 return 0;
1080 }
1081
1082 /* convert overview line to header */
1083 FILE *fp = mutt_file_mkstemp();
1084 if (!fp)
1085 return -1;
1086
1087 header = mdata->adata->overview_fmt;
1088 while (field)
1089 {
1090 char *b = field;
1091
1092 if (*header)
1093 {
1094 if (!strstr(header, ":full") && (fputs(header, fp) == EOF))
1095 {
1096 mutt_file_fclose(&fp);
1097 return -1;
1098 }
1099 header = strchr(header, '\0') + 1;
1100 }
1101
1102 field = strchr(field, '\t');
1103 if (field)
1104 *field++ = '\0';
1105 if ((fputs(b, fp) == EOF) || (fputc('\n', fp) == EOF))
1106 {
1107 mutt_file_fclose(&fp);
1108 return -1;
1109 }
1110 }
1111 rewind(fp);
1112
1113 /* allocate memory for headers */
1115
1116 /* parse header */
1117 m->emails[m->msg_count] = email_new();
1118 e = m->emails[m->msg_count];
1119 e->env = mutt_rfc822_read_header(fp, e, false, false);
1120 e->env->newsgroups = mutt_str_dup(mdata->group);
1121 e->received = e->date_sent;
1122 mutt_file_fclose(&fp);
1123
1124#ifdef USE_HCACHE
1125 if (fc->hc)
1126 {
1127 char buf[16] = { 0 };
1128
1129 /* try to replace with header from cache */
1130 snprintf(buf, sizeof(buf), ANUM_FMT, anum);
1131 struct HCacheEntry hce = hcache_fetch_email(fc->hc, buf, strlen(buf), 0);
1132 if (hce.email)
1133 {
1134 mutt_debug(LL_DEBUG2, "hcache_fetch_email %s\n", buf);
1135 email_free(&e);
1136 e = hce.email;
1137 m->emails[m->msg_count] = e;
1138 e->edata = NULL;
1139 e->read = false;
1140 e->old = false;
1141
1142 /* skip header marked as deleted in cache */
1143 if (e->deleted && !fc->restore)
1144 {
1145 if (mdata->bcache)
1146 {
1147 mutt_debug(LL_DEBUG2, "mutt_bcache_del %s\n", buf);
1148 mutt_bcache_del(mdata->bcache, buf);
1149 }
1150 save = false;
1151 }
1152 }
1153 else
1154 {
1155 /* not cached yet, store header */
1156 mutt_debug(LL_DEBUG2, "hcache_store_email %s\n", buf);
1157 hcache_store_email(fc->hc, buf, strlen(buf), e, 0);
1158 }
1159 }
1160#endif
1161
1162 if (save)
1163 {
1164 e->index = m->msg_count++;
1165 e->read = false;
1166 e->old = false;
1167 e->deleted = false;
1168 e->edata = nntp_edata_new();
1170 nntp_edata_get(e)->article_num = anum;
1171 if (fc->restore)
1172 {
1173 e->changed = true;
1174 }
1175 else
1176 {
1177 nntp_article_status(m, e, NULL, anum);
1178 if (!e->read)
1179 nntp_parse_xref(m, e);
1180 }
1181 if (anum > mdata->last_loaded)
1182 mdata->last_loaded = anum;
1183 }
1184 else
1185 {
1186 email_free(&e);
1187 }
1188
1189 progress_update(fc->progress, anum - fc->first + 1, -1);
1190 return 0;
1191}
int mutt_bcache_del(struct BodyCache *bcache, const char *id)
Delete a file from the Body Cache.
Definition: bcache.c:269
struct Email * email_new(void)
Create a new Email.
Definition: email.c:77
void email_free(struct Email **ptr)
Free an Email.
Definition: email.c:46
struct Envelope * mutt_rfc822_read_header(FILE *fp, struct Email *e, bool user_hdrs, bool weed)
Parses an RFC822 header.
Definition: parse.c:1204
#define mutt_file_fclose(FP)
Definition: file.h:139
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:967
The envelope/body of an email.
Definition: email.h:39
bool read
Email is read.
Definition: email.h:50
void * edata
Driver-specific data.
Definition: email.h:74
bool old
Email is seen, but unread.
Definition: email.h:49
void(* edata_free)(void **ptr)
Definition: email.h:90
bool changed
Email has been edited.
Definition: email.h:77
time_t date_sent
Time when the message was sent (UTC)
Definition: email.h:60
bool deleted
Email is deleted.
Definition: email.h:78
int index
The absolute (unsorted) message number.
Definition: email.h:110
time_t received
Time when the message was placed in the mailbox.
Definition: email.h:61
char * newsgroups
List of newsgroups.
Definition: envelope.h:78
struct HeaderCache * hc
Definition: nntp.c:99
struct Progress * progress
Definition: nntp.c:98
struct Mailbox * mailbox
Definition: nntp.c:93
bool restore
Definition: nntp.c:96
Wrapper for Email retrieved from the header cache.
Definition: lib.h:99
struct Email * email
Retrieved email.
Definition: lib.h:102
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 1203 of file nntp.c.

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

1441{
1442 char buf[1024] = { 0 };
1443 anum_t count = 0, first = 0, last = 0;
1444
1445 /* use GROUP command to poll newsgroup */
1446 if (nntp_query(mdata, buf, sizeof(buf)) < 0)
1447 return -1;
1448 if (sscanf(buf, "211 " ANUM_FMT " " ANUM_FMT " " ANUM_FMT, &count, &first, &last) != 3)
1449 return 0;
1450 if ((first == mdata->first_message) && (last == mdata->last_message))
1451 return 0;
1452
1453 /* articles have been renumbered */
1454 if (last < mdata->last_message)
1455 {
1456 mdata->last_cached = 0;
1457 if (mdata->newsrc_len)
1458 {
1459 MUTT_MEM_REALLOC(&mdata->newsrc_ent, 1, struct NewsrcEntry);
1460 mdata->newsrc_len = 1;
1461 mdata->newsrc_ent[0].first = 1;
1462 mdata->newsrc_ent[0].last = 0;
1463 }
1464 }
1465 mdata->first_message = first;
1466 mdata->last_message = last;
1467 if (!update_stat)
1468 {
1469 return 1;
1470 }
1471 else if (!last || (!mdata->newsrc_ent && !mdata->last_cached))
1472 {
1473 /* update counters */
1474 mdata->unread = count;
1475 }
1476 else
1477 {
1479 }
1480 return 1;
1481}
void nntp_group_unread_stat(struct NntpMboxData *mdata)
Count number of unread articles using .newsrc data.
Definition: newsrc.c:134
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 1490 of file nntp.c.

1491{
1492 if (!m || !m->mdata)
1493 return MX_STATUS_ERROR;
1494
1495 struct NntpMboxData *mdata = m->mdata;
1496 struct NntpAccountData *adata = mdata->adata;
1497 time_t now = mutt_date_now();
1498 enum MxStatus rc = MX_STATUS_OK;
1499 struct HeaderCache *hc = NULL;
1500
1501 const short c_nntp_poll = cs_subset_number(NeoMutt->sub, "nntp_poll");
1502 if (adata->check_time + c_nntp_poll > now)
1503 return MX_STATUS_OK;
1504
1505 mutt_message(_("Checking for new messages..."));
1506 if (nntp_newsrc_parse(adata) < 0)
1507 return MX_STATUS_ERROR;
1508
1509 adata->check_time = now;
1510 int rc2 = nntp_group_poll(mdata, false);
1511 if (rc2 < 0)
1512 {
1513 nntp_newsrc_close(adata);
1514 return -1;
1515 }
1516 if (rc2 != 0)
1518
1519 /* articles have been renumbered, remove all emails */
1520 if (mdata->last_message < mdata->last_loaded)
1521 {
1522 for (int i = 0; i < m->msg_count; i++)
1523 email_free(&m->emails[i]);
1524 m->msg_count = 0;
1525 m->msg_tagged = 0;
1526
1527 mdata->last_loaded = mdata->first_message - 1;
1528 const long c_nntp_context = cs_subset_long(NeoMutt->sub, "nntp_context");
1529 if (c_nntp_context && (mdata->last_message - mdata->last_loaded > c_nntp_context))
1530 mdata->last_loaded = mdata->last_message - c_nntp_context;
1531
1532 rc = MX_STATUS_REOPENED;
1533 }
1534
1535 /* .newsrc has been externally modified */
1536 if (adata->newsrc_modified)
1537 {
1538#ifdef USE_HCACHE
1539 unsigned char *messages = NULL;
1540 char buf[16] = { 0 };
1541 struct Email *e = NULL;
1542 anum_t first = mdata->first_message;
1543
1544 const long c_nntp_context = cs_subset_long(NeoMutt->sub, "nntp_context");
1545 if (c_nntp_context && ((mdata->last_message - first + 1) > c_nntp_context))
1546 first = mdata->last_message - c_nntp_context + 1;
1547 messages = MUTT_MEM_CALLOC(mdata->last_loaded - first + 1, unsigned char);
1548 hc = nntp_hcache_open(mdata);
1549 nntp_hcache_update(mdata, hc);
1550#endif
1551
1552 /* update flags according to .newsrc */
1553 int j = 0;
1554 for (int i = 0; i < m->msg_count; i++)
1555 {
1556 if (!m->emails[i])
1557 continue;
1558 bool flagged = false;
1559 anum_t anum = nntp_edata_get(m->emails[i])->article_num;
1560
1561#ifdef USE_HCACHE
1562 /* check hcache for flagged and deleted flags */
1563 if (hc)
1564 {
1565 if ((anum >= first) && (anum <= mdata->last_loaded))
1566 messages[anum - first] = 1;
1567
1568 snprintf(buf, sizeof(buf), ANUM_FMT, anum);
1569 struct HCacheEntry hce = hcache_fetch_email(hc, buf, strlen(buf), 0);
1570 if (hce.email)
1571 {
1572 bool deleted;
1573
1574 mutt_debug(LL_DEBUG2, "#1 hcache_fetch_email %s\n", buf);
1575 e = hce.email;
1576 e->edata = NULL;
1577 deleted = e->deleted;
1578 flagged = e->flagged;
1579 email_free(&e);
1580
1581 /* header marked as deleted, removing from context */
1582 if (deleted)
1583 {
1584 mutt_set_flag(m, m->emails[i], MUTT_TAG, false, true);
1585 email_free(&m->emails[i]);
1586 continue;
1587 }
1588 }
1589 }
1590#endif
1591
1592 if (!m->emails[i]->changed)
1593 {
1594 m->emails[i]->flagged = flagged;
1595 m->emails[i]->read = false;
1596 m->emails[i]->old = false;
1597 nntp_article_status(m, m->emails[i], NULL, anum);
1598 if (!m->emails[i]->read)
1599 nntp_parse_xref(m, m->emails[i]);
1600 }
1601 m->emails[j++] = m->emails[i];
1602 }
1603
1604#ifdef USE_HCACHE
1605 m->msg_count = j;
1606
1607 /* restore headers without "deleted" flag */
1608 for (anum_t anum = first; anum <= mdata->last_loaded; anum++)
1609 {
1610 if (messages[anum - first])
1611 continue;
1612
1613 snprintf(buf, sizeof(buf), ANUM_FMT, anum);
1614 struct HCacheEntry hce = hcache_fetch_email(hc, buf, strlen(buf), 0);
1615 if (hce.email)
1616 {
1617 mutt_debug(LL_DEBUG2, "#2 hcache_fetch_email %s\n", buf);
1619
1620 e = hce.email;
1621 m->emails[m->msg_count] = e;
1622 e->edata = NULL;
1623 if (e->deleted)
1624 {
1625 email_free(&e);
1626 if (mdata->bcache)
1627 {
1628 mutt_debug(LL_DEBUG2, "mutt_bcache_del %s\n", buf);
1629 mutt_bcache_del(mdata->bcache, buf);
1630 }
1631 continue;
1632 }
1633
1634 m->msg_count++;
1635 e->read = false;
1636 e->old = false;
1637 e->edata = nntp_edata_new();
1639 nntp_edata_get(e)->article_num = anum;
1640 nntp_article_status(m, e, NULL, anum);
1641 if (!e->read)
1642 nntp_parse_xref(m, e);
1643 }
1644 }
1645 FREE(&messages);
1646#endif
1647
1648 adata->newsrc_modified = false;
1649 rc = MX_STATUS_REOPENED;
1650 }
1651
1652 /* some emails were removed, mailboxview must be updated */
1653 if (rc == MX_STATUS_REOPENED)
1655
1656 /* fetch headers of new articles */
1657 if (mdata->last_message > mdata->last_loaded)
1658 {
1659 int oldmsgcount = m->msg_count;
1660 bool verbose = m->verbose;
1661 m->verbose = false;
1662#ifdef USE_HCACHE
1663 if (!hc)
1664 {
1665 hc = nntp_hcache_open(mdata);
1666 nntp_hcache_update(mdata, hc);
1667 }
1668#endif
1669 int old_msg_count = m->msg_count;
1670 rc2 = nntp_fetch_headers(m, hc, mdata->last_loaded + 1, mdata->last_message, false);
1671 m->verbose = verbose;
1672 if (rc2 == 0)
1673 {
1674 if (m->msg_count > old_msg_count)
1676 mdata->last_loaded = mdata->last_message;
1677 }
1678 if ((rc == MX_STATUS_OK) && (m->msg_count > oldmsgcount))
1679 rc = MX_STATUS_NEW_MAIL;
1680 }
1681
1682#ifdef USE_HCACHE
1683 hcache_close(&hc);
1684#endif
1685 if (rc != MX_STATUS_OK)
1686 nntp_newsrc_close(adata);
1688 return rc;
1689}
short cs_subset_number(const struct ConfigSubset *sub, const char *name)
Get a number config item by name.
Definition: helpers.c:143
long cs_subset_long(const struct ConfigSubset *sub, const char *name)
Get a long config item by name.
Definition: helpers.c:95
void mailbox_changed(struct Mailbox *m, enum NotifyMailbox action)
Notify observers of a change to a Mailbox.
Definition: mailbox.c:233
@ NT_MAILBOX_INVALID
Email list was changed.
Definition: mailbox.h:189
void mutt_set_flag(struct Mailbox *m, struct Email *e, enum MessageType flag, bool bf, bool upd_mbox)
Set a flag on an email.
Definition: flags.c:57
void hcache_close(struct HeaderCache **ptr)
Multiplexor for StoreOps::close.
Definition: hcache.c:542
time_t mutt_date_now(void)
Return the number of seconds since the Unix epoch.
Definition: date.c:456
@ MUTT_TAG
Tagged messages.
Definition: mutt.h:80
MxStatus
Return values from mbox_check(), mbox_check_stats(), mbox_sync(), and mbox_close()
Definition: mxapi.h:60
@ MX_STATUS_ERROR
An error occurred.
Definition: mxapi.h:61
@ MX_STATUS_OK
No changes.
Definition: mxapi.h:62
@ MX_STATUS_REOPENED
Mailbox was reopened.
Definition: mxapi.h:65
@ MX_STATUS_NEW_MAIL
New mail received in Mailbox.
Definition: mxapi.h:63
struct HeaderCache * nntp_hcache_open(struct NntpMboxData *mdata)
Open newsgroup hcache.
Definition: newsrc.c:707
void nntp_hcache_update(struct NntpMboxData *mdata, struct HeaderCache *hc)
Remove stale cached headers.
Definition: newsrc.c:731
int nntp_active_save_cache(struct NntpAccountData *adata)
Save list of all newsgroups to cache.
Definition: newsrc.c:647
int nntp_newsrc_parse(struct NntpAccountData *adata)
Parse .newsrc file.
Definition: newsrc.c:164
void nntp_newsrc_close(struct NntpAccountData *adata)
Unlock and close .newsrc file.
Definition: newsrc.c:120
static int nntp_group_poll(struct NntpMboxData *mdata, bool update_stat)
Check newsgroup for new articles.
Definition: nntp.c:1440
static int nntp_fetch_headers(struct Mailbox *m, void *hc, anum_t first, anum_t last, bool restore)
Fetch headers.
Definition: nntp.c:1203
bool flagged
Marked important?
Definition: email.h:47
Header Cache.
Definition: lib.h:86
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 1698 of file nntp.c.

1699{
1700 if (adata->hasDATE)
1701 {
1702 struct NntpMboxData mdata = { 0 };
1703 char buf[1024] = { 0 };
1704 struct tm tm = { 0 };
1705
1706 mdata.adata = adata;
1707 mdata.group = NULL;
1708 mutt_str_copy(buf, "DATE\r\n", sizeof(buf));
1709 if (nntp_query(&mdata, buf, sizeof(buf)) < 0)
1710 return -1;
1711
1712 if (sscanf(buf, "111 %4d%2d%2d%2d%2d%2d%*s", &tm.tm_year, &tm.tm_mon,
1713 &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) == 6)
1714 {
1715 tm.tm_year -= 1900;
1716 tm.tm_mon--;
1717 *now = timegm(&tm);
1718 if (*now >= 0)
1719 {
1720 mutt_debug(LL_DEBUG1, "server time is %llu\n", (unsigned long long) *now);
1721 return 0;
1722 }
1723 }
1724 }
1725 *now = mutt_date_now();
1726 return 0;
1727}
time_t timegm(struct tm *tm)
Convert struct tm to time_t seconds since epoch.
Definition: timegm.c:70
+ 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 1735 of file nntp.c.

1736{
1737 struct ChildCtx *cc = data;
1738 anum_t anum = 0;
1739
1740 if (!line || (sscanf(line, ANUM_FMT, &anum) != 1))
1741 return 0;
1742 for (unsigned int i = 0; i < cc->mailbox->msg_count; i++)
1743 {
1744 struct Email *e = cc->mailbox->emails[i];
1745 if (!e)
1746 break;
1747 if (nntp_edata_get(e)->article_num == anum)
1748 return 0;
1749 }
1750 if (cc->num >= cc->max)
1751 {
1752 cc->max *= 2;
1753 MUTT_MEM_REALLOC(&cc->child, cc->max, anum_t);
1754 }
1755 cc->child[cc->num++] = anum;
1756 return 0;
1757}
Keep track of the children of an article.
Definition: nntp.c:106
anum_t * child
Definition: nntp.c:110
struct Mailbox * mailbox
Definition: nntp.c:107
unsigned int max
Definition: nntp.c:109
unsigned int num
Definition: nntp.c:108
+ 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 1765 of file nntp.c.

1766{
1767 if (adata->status == NNTP_OK)
1768 return 0;
1769 if (adata->status == NNTP_BYE)
1770 return -1;
1771 adata->status = NNTP_NONE;
1772
1773 struct Connection *conn = adata->conn;
1774 if (mutt_socket_open(conn) < 0)
1775 return -1;
1776
1777 char buf[256] = { 0 };
1778 int cap;
1779 bool posting = false, auth = true;
1780 int rc = -1;
1781
1782 if (mutt_socket_readln(buf, sizeof(buf), conn) < 0)
1783 {
1784 nntp_connect_error(adata);
1785 goto done;
1786 }
1787
1788 if (mutt_str_startswith(buf, "200"))
1789 {
1790 posting = true;
1791 }
1792 else if (!mutt_str_startswith(buf, "201"))
1793 {
1794 mutt_socket_close(conn);
1796 mutt_error("%s", buf);
1797 goto done;
1798 }
1799
1800 /* get initial capabilities */
1801 cap = nntp_capabilities(adata);
1802 if (cap < 0)
1803 goto done;
1804
1805 /* tell news server to switch to mode reader if it isn't so */
1806 if (cap > 0)
1807 {
1808 if ((mutt_socket_send(conn, "MODE READER\r\n") < 0) ||
1809 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
1810 {
1811 nntp_connect_error(adata);
1812 goto done;
1813 }
1814
1815 if (mutt_str_startswith(buf, "200"))
1816 {
1817 posting = true;
1818 }
1819 else if (mutt_str_startswith(buf, "201"))
1820 {
1821 posting = false;
1822 }
1823 else if (adata->hasCAPABILITIES)
1824 {
1825 /* error if has capabilities, ignore result if no capabilities */
1826 mutt_socket_close(conn);
1827 mutt_error(_("Could not switch to reader mode"));
1828 goto done;
1829 }
1830
1831 /* recheck capabilities after MODE READER */
1832 if (adata->hasCAPABILITIES)
1833 {
1834 cap = nntp_capabilities(adata);
1835 if (cap < 0)
1836 goto done;
1837 }
1838 }
1839
1840 mutt_message(_("Connected to %s. %s"), conn->account.host,
1841 posting ? _("Posting is ok") : _("Posting is NOT ok"));
1842 mutt_sleep(1);
1843
1844#ifdef USE_SSL
1845 /* Attempt STARTTLS if available and desired. */
1846 const bool c_ssl_force_tls = cs_subset_bool(NeoMutt->sub, "ssl_force_tls");
1847 if ((adata->use_tls != 1) && (adata->hasSTARTTLS || c_ssl_force_tls))
1848 {
1849 if (adata->use_tls == 0)
1850 {
1851 adata->use_tls = c_ssl_force_tls ||
1852 (query_quadoption(_("Secure connection with TLS?"),
1853 NeoMutt->sub, "ssl_starttls") == MUTT_YES) ?
1854 2 :
1855 1;
1856 }
1857 if (adata->use_tls == 2)
1858 {
1859 if ((mutt_socket_send(conn, "STARTTLS\r\n") < 0) ||
1860 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
1861 {
1862 nntp_connect_error(adata);
1863 goto done;
1864 }
1865 // Clear any data after the STARTTLS acknowledgement
1866 mutt_socket_empty(conn);
1867 if (!mutt_str_startswith(buf, "382"))
1868 {
1869 adata->use_tls = 0;
1870 mutt_error("STARTTLS: %s", buf);
1871 }
1872 else if (mutt_ssl_starttls(conn))
1873 {
1874 adata->use_tls = 0;
1875 adata->status = NNTP_NONE;
1876 mutt_socket_close(adata->conn);
1877 mutt_error(_("Could not negotiate TLS connection"));
1878 goto done;
1879 }
1880 else
1881 {
1882 /* recheck capabilities after STARTTLS */
1883 cap = nntp_capabilities(adata);
1884 if (cap < 0)
1885 goto done;
1886 }
1887 }
1888 }
1889#endif
1890
1891 /* authentication required? */
1892 if (conn->account.flags & MUTT_ACCT_USER)
1893 {
1894 if (!conn->account.user[0])
1895 auth = false;
1896 }
1897 else
1898 {
1899 if ((mutt_socket_send(conn, "STAT\r\n") < 0) ||
1900 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
1901 {
1902 nntp_connect_error(adata);
1903 goto done;
1904 }
1905 if (!mutt_str_startswith(buf, "480"))
1906 auth = false;
1907 }
1908
1909 /* authenticate */
1910 if (auth && (nntp_auth(adata) < 0))
1911 goto done;
1912
1913 /* get final capabilities after authentication */
1914 if (adata->hasCAPABILITIES && (auth || (cap > 0)))
1915 {
1916 cap = nntp_capabilities(adata);
1917 if (cap < 0)
1918 goto done;
1919 if (cap > 0)
1920 {
1921 mutt_socket_close(conn);
1922 mutt_error(_("Could not switch to reader mode"));
1923 goto done;
1924 }
1925 }
1926
1927 /* attempt features */
1928 if (nntp_attempt_features(adata) < 0)
1929 goto done;
1930
1931 rc = 0;
1932 adata->status = NNTP_OK;
1933
1934done:
1935 return rc;
1936}
#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:1149
void mutt_str_remove_trailing_ws(char *s)
Trim trailing whitespace from a string.
Definition: string.c:564
void mutt_sleep(short s)
Sleep for a while.
Definition: muttlib.c:841
static int nntp_auth(struct NntpAccountData *adata)
Get login, password and authenticate.
Definition: nntp.c:451
static int nntp_capabilities(struct NntpAccountData *adata)
Get capabilities.
Definition: nntp.c:140
static int nntp_attempt_features(struct NntpAccountData *adata)
Detect supported commands.
Definition: nntp.c:259
enum QuadOption query_quadoption(const char *prompt, struct ConfigSubset *sub, const char *name)
Ask the user a quad-question.
Definition: question.c:378
void mutt_socket_empty(struct Connection *conn)
Clear out any queued data.
Definition: socket.c:306
int mutt_socket_open(struct Connection *conn)
Simple wrapper.
Definition: socket.c:76
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 1945 of file nntp.c.

1946{
1947 struct NntpMboxData *mdata = NULL;
1948 struct NntpMboxData tmp_mdata = { 0 };
1949 char buf[1024] = { 0 };
1950 int rc = -1;
1951
1952 if (m && (m->type == MUTT_NNTP))
1953 {
1954 mdata = m->mdata;
1955 }
1956 else
1957 {
1958 const char *const c_news_server = cs_subset_string(NeoMutt->sub, "news_server");
1959 CurrentNewsSrv = nntp_select_server(m, c_news_server, false);
1960 if (!CurrentNewsSrv)
1961 goto done;
1962
1963 mdata = &tmp_mdata;
1964 mdata->adata = CurrentNewsSrv;
1965 mdata->group = NULL;
1966 }
1967
1968 FILE *fp = mutt_file_fopen(msg, "r");
1969 if (!fp)
1970 {
1971 mutt_perror("%s", msg);
1972 goto done;
1973 }
1974
1975 mutt_str_copy(buf, "POST\r\n", sizeof(buf));
1976 if (nntp_query(mdata, buf, sizeof(buf)) < 0)
1977 {
1978 mutt_file_fclose(&fp);
1979 goto done;
1980 }
1981 if (buf[0] != '3')
1982 {
1983 mutt_error(_("Can't post article: %s"), buf);
1984 mutt_file_fclose(&fp);
1985 goto done;
1986 }
1987
1988 buf[0] = '.';
1989 buf[1] = '\0';
1990 while (fgets(buf + 1, sizeof(buf) - 2, fp))
1991 {
1992 size_t len = strlen(buf);
1993 if (buf[len - 1] == '\n')
1994 {
1995 buf[len - 1] = '\r';
1996 buf[len] = '\n';
1997 len++;
1998 buf[len] = '\0';
1999 }
2000 if (mutt_socket_send_d(mdata->adata->conn, (buf[1] == '.') ? buf : buf + 1,
2001 MUTT_SOCK_LOG_FULL) < 0)
2002 {
2003 mutt_file_fclose(&fp);
2004 nntp_connect_error(mdata->adata);
2005 goto done;
2006 }
2007 }
2008 mutt_file_fclose(&fp);
2009
2010 if (((buf[strlen(buf) - 1] != '\n') &&
2011 (mutt_socket_send_d(mdata->adata->conn, "\r\n", MUTT_SOCK_LOG_FULL) < 0)) ||
2012 (mutt_socket_send_d(mdata->adata->conn, ".\r\n", MUTT_SOCK_LOG_FULL) < 0) ||
2013 (mutt_socket_readln(buf, sizeof(buf), mdata->adata->conn) < 0))
2014 {
2015 nntp_connect_error(mdata->adata);
2016 goto done;
2017 }
2018 if (buf[0] != '2')
2019 {
2020 mutt_error(_("Can't post article: %s"), buf);
2021 goto done;
2022 }
2023 rc = 0;
2024
2025done:
2026 return rc;
2027}
@ MUTT_NNTP
'NNTP' (Usenet) Mailbox type
Definition: mailbox.h:49
#define mutt_file_fopen(PATH, MODE)
Definition: file.h:138
struct NntpAccountData * nntp_select_server(struct Mailbox *m, const char *server, bool leave_lock)
Open a connection to an NNTP server.
Definition: newsrc.c:945
struct NntpAccountData * CurrentNewsSrv
Current news server.
Definition: nntp.c:76
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 2036 of file nntp.c.

2037{
2038 struct NntpMboxData tmp_mdata = { 0 };
2039 char msg[256] = { 0 };
2040 char buf[1024] = { 0 };
2041 unsigned int i;
2042 int rc;
2043
2044 snprintf(msg, sizeof(msg), _("Loading list of groups from server %s..."),
2046 mutt_message("%s", msg);
2047 if (nntp_date(adata, &adata->newgroups_time) < 0)
2048 return -1;
2049
2050 tmp_mdata.adata = adata;
2051 tmp_mdata.group = NULL;
2052 i = adata->groups_num;
2053 mutt_str_copy(buf, "LIST\r\n", sizeof(buf));
2054 rc = nntp_fetch_lines(&tmp_mdata, buf, sizeof(buf), msg, nntp_add_group, adata);
2055 if (rc)
2056 {
2057 if (rc > 0)
2058 {
2059 mutt_error("LIST: %s", buf);
2060 }
2061 return -1;
2062 }
2063
2064 if (mark_new)
2065 {
2066 for (; i < adata->groups_num; i++)
2067 {
2068 struct NntpMboxData *mdata = adata->groups_list[i];
2069 mdata->has_new_mail = true;
2070 }
2071 }
2072
2073 for (i = 0; i < adata->groups_num; i++)
2074 {
2075 struct NntpMboxData *mdata = adata->groups_list[i];
2076
2077 if (mdata && mdata->deleted && !mdata->newsrc_ent)
2078 {
2080 mutt_hash_delete(adata->groups_hash, mdata->group, NULL);
2081 adata->groups_list[i] = NULL;
2082 }
2083 }
2084
2085 const bool c_nntp_load_description = cs_subset_bool(NeoMutt->sub, "nntp_load_description");
2086 if (c_nntp_load_description)
2087 rc = get_description(&tmp_mdata, "*", _("Loading descriptions..."));
2088
2090 if (rc < 0)
2091 return -1;
2093 return 0;
2094}
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:808
int nntp_add_group(char *line, void *data)
Parse newsgroup.
Definition: newsrc.c:572
static int nntp_date(struct NntpAccountData *adata, time_t *now)
Get date and time from server.
Definition: nntp.c:1698
static int get_description(struct NntpMboxData *mdata, const char *wildmat, const char *msg)
Fetch newsgroups descriptions.
Definition: nntp.c:935
time_t newgroups_time
Definition: adata.h:56
struct NntpMboxData ** groups_list
Definition: adata.h:60
unsigned int groups_num
Definition: adata.h:58
+ 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 2104 of file nntp.c.

2105{
2106 struct NntpMboxData tmp_mdata = { 0 };
2107 time_t now = 0;
2108 char buf[1024] = { 0 };
2109 char *msg = _("Checking for new newsgroups...");
2110 unsigned int i;
2111 int rc, update_active = false;
2112
2113 if (!adata || !adata->newgroups_time)
2114 return -1;
2115
2116 /* check subscribed newsgroups for new articles */
2117 const bool c_show_new_news = cs_subset_bool(NeoMutt->sub, "show_new_news");
2118 if (c_show_new_news)
2119 {
2120 mutt_message(_("Checking for new messages..."));
2121 for (i = 0; i < adata->groups_num; i++)
2122 {
2123 struct NntpMboxData *mdata = adata->groups_list[i];
2124
2125 if (mdata && mdata->subscribed)
2126 {
2127 rc = nntp_group_poll(mdata, true);
2128 if (rc < 0)
2129 return -1;
2130 if (rc > 0)
2131 update_active = true;
2132 }
2133 }
2134 }
2135 else if (adata->newgroups_time)
2136 {
2137 return 0;
2138 }
2139
2140 /* get list of new groups */
2141 mutt_message("%s", msg);
2142 if (nntp_date(adata, &now) < 0)
2143 return -1;
2144 tmp_mdata.adata = adata;
2145 if (m && m->mdata)
2146 tmp_mdata.group = ((struct NntpMboxData *) m->mdata)->group;
2147 else
2148 tmp_mdata.group = NULL;
2149 i = adata->groups_num;
2150 struct tm tm = mutt_date_gmtime(adata->newgroups_time);
2151 snprintf(buf, sizeof(buf), "NEWGROUPS %02d%02d%02d %02d%02d%02d GMT\r\n",
2152 tm.tm_year % 100, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
2153 rc = nntp_fetch_lines(&tmp_mdata, buf, sizeof(buf), msg, nntp_add_group, adata);
2154 if (rc)
2155 {
2156 if (rc > 0)
2157 {
2158 mutt_error("NEWGROUPS: %s", buf);
2159 }
2160 return -1;
2161 }
2162
2163 /* new groups found */
2164 rc = 0;
2165 if (adata->groups_num != i)
2166 {
2167 int groups_num = i;
2168
2169 adata->newgroups_time = now;
2170 for (; i < adata->groups_num; i++)
2171 {
2172 struct NntpMboxData *mdata = adata->groups_list[i];
2173 mdata->has_new_mail = true;
2174 }
2175
2176 /* loading descriptions */
2177 const bool c_nntp_load_description = cs_subset_bool(NeoMutt->sub, "nntp_load_description");
2178 if (c_nntp_load_description)
2179 {
2180 unsigned int count = 0;
2181 struct Progress *progress = progress_new(MUTT_PROGRESS_READ, adata->groups_num - i);
2182 progress_set_message(progress, _("Loading descriptions..."));
2183
2184 for (i = groups_num; i < adata->groups_num; i++)
2185 {
2186 struct NntpMboxData *mdata = adata->groups_list[i];
2187
2188 if (get_description(mdata, NULL, NULL) < 0)
2189 {
2190 progress_free(&progress);
2191 return -1;
2192 }
2193 progress_update(progress, ++count, -1);
2194 }
2195 progress_free(&progress);
2196 }
2197 update_active = true;
2198 rc = 1;
2199 }
2200 if (update_active)
2203 return rc;
2204}
struct tm mutt_date_gmtime(time_t t)
Converts calendar time to a broken-down time structure expressed in UTC timezone.
Definition: date.c:927
+ 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 2214 of file nntp.c.

2215{
2216 if (!m)
2217 return -1;
2218
2219 struct NntpMboxData *mdata = m->mdata;
2220 char buf[1024] = { 0 };
2221
2222 FILE *fp = mutt_file_mkstemp();
2223 if (!fp)
2224 {
2225 mutt_perror(_("Can't create temporary file"));
2226 return -1;
2227 }
2228
2229 snprintf(buf, sizeof(buf), "HEAD %s\r\n", msgid);
2230 int rc = nntp_fetch_lines(mdata, buf, sizeof(buf), NULL, fetch_tempfile, fp);
2231 if (rc)
2232 {
2233 mutt_file_fclose(&fp);
2234 if (rc < 0)
2235 return -1;
2236 if (mutt_str_startswith(buf, "430"))
2237 return 1;
2238 mutt_error("HEAD: %s", buf);
2239 return -1;
2240 }
2241
2242 /* parse header */
2244 m->emails[m->msg_count] = email_new();
2245 struct Email *e = m->emails[m->msg_count];
2246 e->edata = nntp_edata_new();
2248 e->env = mutt_rfc822_read_header(fp, e, false, false);
2249 mutt_file_fclose(&fp);
2250
2251 /* get article number */
2252 if (e->env->xref)
2253 {
2254 nntp_parse_xref(m, e);
2255 }
2256 else
2257 {
2258 snprintf(buf, sizeof(buf), "STAT %s\r\n", msgid);
2259 if (nntp_query(mdata, buf, sizeof(buf)) < 0)
2260 {
2261 email_free(&e);
2262 return -1;
2263 }
2264 sscanf(buf + 4, ANUM_FMT, &nntp_edata_get(e)->article_num);
2265 }
2266
2267 /* reset flags */
2268 e->read = false;
2269 e->old = false;
2270 e->deleted = false;
2271 e->changed = true;
2272 e->received = e->date_sent;
2273 e->index = m->msg_count++;
2275 return 0;
2276}
+ 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 2285 of file nntp.c.

2286{
2287 if (!m)
2288 return -1;
2289
2290 struct NntpMboxData *mdata = m->mdata;
2291 char buf[256] = { 0 };
2292 int rc;
2293 struct HeaderCache *hc = NULL;
2294
2295 if (!mdata || !mdata->adata)
2296 return -1;
2297 if (mdata->first_message > mdata->last_loaded)
2298 return 0;
2299
2300 /* init context */
2301 struct ChildCtx cc = { 0 };
2302 cc.mailbox = m;
2303 cc.num = 0;
2304 cc.max = 10;
2305 cc.child = MUTT_MEM_MALLOC(cc.max, anum_t);
2306
2307 /* fetch numbers of child messages */
2308 snprintf(buf, sizeof(buf), "XPAT References " ANUM_FMT "-" ANUM_FMT " *%s*\r\n",
2309 mdata->first_message, mdata->last_loaded, msgid);
2310 rc = nntp_fetch_lines(mdata, buf, sizeof(buf), NULL, fetch_children, &cc);
2311 if (rc)
2312 {
2313 FREE(&cc.child);
2314 if (rc > 0)
2315 {
2316 if (!mutt_str_startswith(buf, "500"))
2317 {
2318 mutt_error("XPAT: %s", buf);
2319 }
2320 else
2321 {
2322 mutt_error(_("Unable to find child articles because server does not support XPAT command"));
2323 }
2324 }
2325 return -1;
2326 }
2327
2328 /* fetch all found messages */
2329 bool verbose = m->verbose;
2330 m->verbose = false;
2331#ifdef USE_HCACHE
2332 hc = nntp_hcache_open(mdata);
2333#endif
2334 int old_msg_count = m->msg_count;
2335 for (int i = 0; i < cc.num; i++)
2336 {
2337 rc = nntp_fetch_headers(m, hc, cc.child[i], cc.child[i], true);
2338 if (rc < 0)
2339 break;
2340 }
2341 if (m->msg_count > old_msg_count)
2343
2344#ifdef USE_HCACHE
2345 hcache_close(&hc);
2346#endif
2347 m->verbose = verbose;
2348 FREE(&cc.child);
2349 return (rc < 0) ? -1 : 0;
2350}
static int fetch_children(char *line, void *data)
Parse XPAT line.
Definition: nntp.c:1735
+ 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 76 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 79 of file nntp.c.