NeoMutt  2024-04-25-76-g20fe7b
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
  • 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 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 authinfo[1024] = { 0 };
146
147 adata->hasCAPABILITIES = false;
148 adata->hasSTARTTLS = false;
149 adata->hasDATE = false;
150 adata->hasLIST_NEWSGROUPS = false;
151 adata->hasLISTGROUP = false;
152 adata->hasLISTGROUPrange = false;
153 adata->hasOVER = false;
154 FREE(&adata->authenticators);
155
156 struct Buffer *buf = buf_pool_get();
157
158 if ((mutt_socket_send(conn, "CAPABILITIES\r\n") < 0) ||
159 (mutt_socket_buffer_readln(buf, conn) < 0))
160 {
161 buf_pool_release(&buf);
162 return nntp_connect_error(adata);
163 }
164
165 /* no capabilities */
166 if (!mutt_str_startswith(buf_string(buf), "101"))
167 {
168 buf_pool_release(&buf);
169 return 1;
170 }
171 adata->hasCAPABILITIES = true;
172
173 /* parse capabilities */
174 do
175 {
176 size_t plen = 0;
177 if (mutt_socket_buffer_readln(buf, conn) < 0)
178 {
179 buf_pool_release(&buf);
180 return nntp_connect_error(adata);
181 }
182 if (mutt_str_equal("STARTTLS", buf_string(buf)))
183 {
184 adata->hasSTARTTLS = true;
185 }
186 else if (mutt_str_equal("MODE-READER", buf_string(buf)))
187 {
188 mode_reader = true;
189 }
190 else if (mutt_str_equal("READER", buf_string(buf)))
191 {
192 adata->hasDATE = true;
193 adata->hasLISTGROUP = true;
194 adata->hasLISTGROUPrange = true;
195 }
196 else if ((plen = mutt_str_startswith(buf_string(buf), "AUTHINFO ")))
197 {
198 buf_addch(buf, ' ');
199 mutt_str_copy(authinfo, buf->data + plen - 1, sizeof(authinfo));
200 }
201#ifdef USE_SASL_CYRUS
202 else if ((plen = mutt_str_startswith(buf_string(buf), "SASL ")))
203 {
204 char *p = buf->data + plen;
205 while (*p == ' ')
206 p++;
207 adata->authenticators = mutt_str_dup(p);
208 }
209#endif
210 else if (mutt_str_equal("OVER", buf_string(buf)))
211 {
212 adata->hasOVER = true;
213 }
214 else if (mutt_str_startswith(buf_string(buf), "LIST "))
215 {
216 const char *p = buf_find_string(buf, " NEWSGROUPS");
217 if (p)
218 {
219 p += 11;
220 if ((*p == '\0') || (*p == ' '))
221 adata->hasLIST_NEWSGROUPS = true;
222 }
223 }
224 } while (!mutt_str_equal(".", buf_string(buf)));
225 buf_reset(buf);
226
227#ifdef USE_SASL_CYRUS
228 if (adata->authenticators && mutt_istr_find(authinfo, " SASL "))
229 buf_strcpy(buf, adata->authenticators);
230#endif
231 if (mutt_istr_find(authinfo, " USER "))
232 {
233 if (!buf_is_empty(buf))
234 buf_addch(buf, ' ');
235 buf_addstr(buf, "USER");
236 }
238 buf_pool_release(&buf);
239
240 /* current mode is reader */
241 if (adata->hasDATE)
242 return 0;
243
244 /* server is mode-switching, need to switch to reader mode */
245 if (mode_reader)
246 return 1;
247
248 mutt_socket_close(conn);
249 adata->status = NNTP_BYE;
250 mutt_error(_("Server doesn't support reader mode"));
251 return -1;
252}
const char * buf_find_string(const struct Buffer *buf, const char *s)
Return a pointer to a substring found in the buffer.
Definition: buffer.c:640
void buf_reset(struct Buffer *buf)
Reset an existing Buffer.
Definition: buffer.c:76
bool buf_is_empty(const struct Buffer *buf)
Is the Buffer empty?
Definition: buffer.c:291
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:45
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:253
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:660
const char * mutt_istr_find(const char *haystack, const char *needle)
Find first occurrence of string (ignoring case)
Definition: string.c:521
size_t mutt_str_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix.
Definition: string.c:230
size_t mutt_str_copy(char *dest, const char *src, size_t dsize)
Copy a string into a buffer (guaranteeing NUL-termination)
Definition: string.c:581
char * mutt_str_replace(char **p, const char *s)
Replace one string with another.
Definition: string.c:280
@ 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
struct Buffer * buf_pool_get(void)
Get a Buffer from the pool.
Definition: pool.c:81
void buf_pool_release(struct Buffer **ptr)
Return a Buffer to the pool.
Definition: pool.c:94
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 260 of file nntp.c.

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

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

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

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

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

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

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

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

1027{
1028 struct FetchCtx *fc = data;
1029 anum_t anum;
1030
1031 if (!line)
1032 return 0;
1033 if (sscanf(line, ANUM_FMT, &anum) != 1)
1034 return 0;
1035 if ((anum < fc->first) || (anum > fc->last))
1036 return 0;
1037 fc->messages[anum - fc->first] = 1;
1038 return 0;
1039}
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 1048 of file nntp.c.

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

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

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

1492{
1493 if (!m || !m->mdata)
1494 return MX_STATUS_ERROR;
1495
1496 struct NntpMboxData *mdata = m->mdata;
1497 struct NntpAccountData *adata = mdata->adata;
1498 time_t now = mutt_date_now();
1499 enum MxStatus rc = MX_STATUS_OK;
1500 struct HeaderCache *hc = NULL;
1501
1502 const short c_nntp_poll = cs_subset_number(NeoMutt->sub, "nntp_poll");
1503 if (adata->check_time + c_nntp_poll > now)
1504 return MX_STATUS_OK;
1505
1506 mutt_message(_("Checking for new messages..."));
1507 if (nntp_newsrc_parse(adata) < 0)
1508 return MX_STATUS_ERROR;
1509
1510 adata->check_time = now;
1511 int rc2 = nntp_group_poll(mdata, false);
1512 if (rc2 < 0)
1513 {
1514 nntp_newsrc_close(adata);
1515 return -1;
1516 }
1517 if (rc2 != 0)
1519
1520 /* articles have been renumbered, remove all emails */
1521 if (mdata->last_message < mdata->last_loaded)
1522 {
1523 for (int i = 0; i < m->msg_count; i++)
1524 email_free(&m->emails[i]);
1525 m->msg_count = 0;
1526 m->msg_tagged = 0;
1527
1528 mdata->last_loaded = mdata->first_message - 1;
1529 const long c_nntp_context = cs_subset_long(NeoMutt->sub, "nntp_context");
1530 if (c_nntp_context && (mdata->last_message - mdata->last_loaded > c_nntp_context))
1531 mdata->last_loaded = mdata->last_message - c_nntp_context;
1532
1533 rc = MX_STATUS_REOPENED;
1534 }
1535
1536 /* .newsrc has been externally modified */
1537 if (adata->newsrc_modified)
1538 {
1539#ifdef USE_HCACHE
1540 unsigned char *messages = NULL;
1541 char buf[16] = { 0 };
1542 struct Email *e = NULL;
1543 anum_t first = mdata->first_message;
1544
1545 const long c_nntp_context = cs_subset_long(NeoMutt->sub, "nntp_context");
1546 if (c_nntp_context && ((mdata->last_message - first + 1) > c_nntp_context))
1547 first = mdata->last_message - c_nntp_context + 1;
1548 messages = mutt_mem_calloc(mdata->last_loaded - first + 1, sizeof(unsigned char));
1549 hc = nntp_hcache_open(mdata);
1550 nntp_hcache_update(mdata, hc);
1551#endif
1552
1553 /* update flags according to .newsrc */
1554 int j = 0;
1555 for (int i = 0; i < m->msg_count; i++)
1556 {
1557 if (!m->emails[i])
1558 continue;
1559 bool flagged = false;
1560 anum_t anum = nntp_edata_get(m->emails[i])->article_num;
1561
1562#ifdef USE_HCACHE
1563 /* check hcache for flagged and deleted flags */
1564 if (hc)
1565 {
1566 if ((anum >= first) && (anum <= mdata->last_loaded))
1567 messages[anum - first] = 1;
1568
1569 snprintf(buf, sizeof(buf), ANUM_FMT, anum);
1570 struct HCacheEntry hce = hcache_fetch_email(hc, buf, strlen(buf), 0);
1571 if (hce.email)
1572 {
1573 bool deleted;
1574
1575 mutt_debug(LL_DEBUG2, "#1 hcache_fetch_email %s\n", buf);
1576 e = hce.email;
1577 e->edata = NULL;
1578 deleted = e->deleted;
1579 flagged = e->flagged;
1580 email_free(&e);
1581
1582 /* header marked as deleted, removing from context */
1583 if (deleted)
1584 {
1585 mutt_set_flag(m, m->emails[i], MUTT_TAG, false, true);
1586 email_free(&m->emails[i]);
1587 continue;
1588 }
1589 }
1590 }
1591#endif
1592
1593 if (!m->emails[i]->changed)
1594 {
1595 m->emails[i]->flagged = flagged;
1596 m->emails[i]->read = false;
1597 m->emails[i]->old = false;
1598 nntp_article_status(m, m->emails[i], NULL, anum);
1599 if (!m->emails[i]->read)
1600 nntp_parse_xref(m, m->emails[i]);
1601 }
1602 m->emails[j++] = m->emails[i];
1603 }
1604
1605#ifdef USE_HCACHE
1606 m->msg_count = j;
1607
1608 /* restore headers without "deleted" flag */
1609 for (anum_t anum = first; anum <= mdata->last_loaded; anum++)
1610 {
1611 if (messages[anum - first])
1612 continue;
1613
1614 snprintf(buf, sizeof(buf), ANUM_FMT, anum);
1615 struct HCacheEntry hce = hcache_fetch_email(hc, buf, strlen(buf), 0);
1616 if (hce.email)
1617 {
1618 mutt_debug(LL_DEBUG2, "#2 hcache_fetch_email %s\n", buf);
1620
1621 e = hce.email;
1622 m->emails[m->msg_count] = e;
1623 e->edata = NULL;
1624 if (e->deleted)
1625 {
1626 email_free(&e);
1627 if (mdata->bcache)
1628 {
1629 mutt_debug(LL_DEBUG2, "mutt_bcache_del %s\n", buf);
1630 mutt_bcache_del(mdata->bcache, buf);
1631 }
1632 continue;
1633 }
1634
1635 m->msg_count++;
1636 e->read = false;
1637 e->old = false;
1638 e->edata = nntp_edata_new();
1640 nntp_edata_get(e)->article_num = anum;
1641 nntp_article_status(m, e, NULL, anum);
1642 if (!e->read)
1643 nntp_parse_xref(m, e);
1644 }
1645 }
1646 FREE(&messages);
1647#endif
1648
1649 adata->newsrc_modified = false;
1650 rc = MX_STATUS_REOPENED;
1651 }
1652
1653 /* some emails were removed, mailboxview must be updated */
1654 if (rc == MX_STATUS_REOPENED)
1656
1657 /* fetch headers of new articles */
1658 if (mdata->last_message > mdata->last_loaded)
1659 {
1660 int oldmsgcount = m->msg_count;
1661 bool verbose = m->verbose;
1662 m->verbose = false;
1663#ifdef USE_HCACHE
1664 if (!hc)
1665 {
1666 hc = nntp_hcache_open(mdata);
1667 nntp_hcache_update(mdata, hc);
1668 }
1669#endif
1670 int old_msg_count = m->msg_count;
1671 rc2 = nntp_fetch_headers(m, hc, mdata->last_loaded + 1, mdata->last_message, false);
1672 m->verbose = verbose;
1673 if (rc2 == 0)
1674 {
1675 if (m->msg_count > old_msg_count)
1677 mdata->last_loaded = mdata->last_message;
1678 }
1679 if ((rc == MX_STATUS_OK) && (m->msg_count > oldmsgcount))
1680 rc = MX_STATUS_NEW_MAIL;
1681 }
1682
1683#ifdef USE_HCACHE
1684 hcache_close(&hc);
1685#endif
1686 if (rc != MX_STATUS_OK)
1687 nntp_newsrc_close(adata);
1689 return rc;
1690}
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:540
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: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:709
void nntp_hcache_update(struct NntpMboxData *mdata, struct HeaderCache *hc)
Remove stale cached headers.
Definition: newsrc.c:733
int nntp_active_save_cache(struct NntpAccountData *adata)
Save list of all newsgroups to cache.
Definition: newsrc.c:649
int nntp_newsrc_parse(struct NntpAccountData *adata)
Parse .newsrc file.
Definition: newsrc.c:166
void nntp_newsrc_close(struct NntpAccountData *adata)
Unlock and close .newsrc file.
Definition: newsrc.c:122
static int nntp_group_poll(struct NntpMboxData *mdata, bool update_stat)
Check newsgroup for new articles.
Definition: nntp.c:1441
static int nntp_fetch_headers(struct Mailbox *m, void *hc, anum_t first, anum_t last, bool restore)
Fetch headers.
Definition: nntp.c:1204
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 1699 of file nntp.c.

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

1737{
1738 struct ChildCtx *cc = data;
1739 anum_t anum;
1740
1741 if (!line || (sscanf(line, ANUM_FMT, &anum) != 1))
1742 return 0;
1743 for (unsigned int i = 0; i < cc->mailbox->msg_count; i++)
1744 {
1745 struct Email *e = cc->mailbox->emails[i];
1746 if (!e)
1747 break;
1748 if (nntp_edata_get(e)->article_num == anum)
1749 return 0;
1750 }
1751 if (cc->num >= cc->max)
1752 {
1753 cc->max *= 2;
1754 mutt_mem_realloc(&cc->child, sizeof(anum_t) * cc->max);
1755 }
1756 cc->child[cc->num++] = anum;
1757 return 0;
1758}
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 1766 of file nntp.c.

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

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

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

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

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

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