NeoMutt  2022-04-29-215-gc12b98
Teaching an old dog new tricks
DOXYGEN
nntp.c File Reference

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

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

Variables

struct NntpAccountDataCurrentNewsSrv
 Current NNTP news server. More...
 
const char * OverviewFmt
 
struct MxOps MxNntpOps
 NNTP Mailbox - Implements MxOps -. More...
 

Detailed Description

Usenet network mailbox type; talk to an NNTP server.

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

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

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

Definition in file nntp.c.

Function Documentation

◆ nntp_connect_error()

static int nntp_connect_error ( struct NntpAccountData adata)
static

Signal a failed connection.

Parameters
adataNNTP server
Return values
-1Always

Definition at line 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: logging.h:87
#define _(a)
Definition: message.h:28
@ NNTP_NONE
No connection to server.
Definition: private.h:44
unsigned int status
Definition: adata.h:48
+ 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 buf[1024] = { 0 };
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 if ((mutt_socket_send(conn, "CAPABILITIES\r\n") < 0) ||
157 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
158 {
159 return nntp_connect_error(adata);
160 }
161
162 /* no capabilities */
163 if (!mutt_str_startswith(buf, "101"))
164 return 1;
165 adata->hasCAPABILITIES = true;
166
167 /* parse capabilities */
168 do
169 {
170 size_t plen = 0;
171 if (mutt_socket_readln(buf, sizeof(buf), conn) < 0)
172 return nntp_connect_error(adata);
173 if (mutt_str_equal("STARTTLS", buf))
174 adata->hasSTARTTLS = true;
175 else if (mutt_str_equal("MODE-READER", buf))
176 mode_reader = true;
177 else if (mutt_str_equal("READER", buf))
178 {
179 adata->hasDATE = true;
180 adata->hasLISTGROUP = true;
181 adata->hasLISTGROUPrange = true;
182 }
183 else if ((plen = mutt_str_startswith(buf, "AUTHINFO ")))
184 {
185 mutt_str_cat(buf, sizeof(buf), " ");
186 mutt_str_copy(authinfo, buf + plen - 1, sizeof(authinfo));
187 }
188#ifdef USE_SASL_CYRUS
189 else if ((plen = mutt_str_startswith(buf, "SASL ")))
190 {
191 char *p = buf + plen;
192 while (*p == ' ')
193 p++;
194 adata->authenticators = mutt_str_dup(p);
195 }
196#endif
197 else if (mutt_str_equal("OVER", buf))
198 adata->hasOVER = true;
199 else if (mutt_str_startswith(buf, "LIST "))
200 {
201 char *p = strstr(buf, " NEWSGROUPS");
202 if (p)
203 {
204 p += 11;
205 if ((*p == '\0') || (*p == ' '))
206 adata->hasLIST_NEWSGROUPS = true;
207 }
208 }
209 } while (!mutt_str_equal(".", buf));
210 *buf = '\0';
211#ifdef USE_SASL_CYRUS
212 if (adata->authenticators && mutt_istr_find(authinfo, " SASL "))
213 mutt_str_copy(buf, adata->authenticators, sizeof(buf));
214#endif
215 if (mutt_istr_find(authinfo, " USER "))
216 {
217 if (*buf != '\0')
218 mutt_str_cat(buf, sizeof(buf), " ");
219 mutt_str_cat(buf, sizeof(buf), "USER");
220 }
221 mutt_str_replace(&adata->authenticators, buf);
222
223 /* current mode is reader */
224 if (adata->hasDATE)
225 return 0;
226
227 /* server is mode-switching, need to switch to reader mode */
228 if (mode_reader)
229 return 1;
230
231 mutt_socket_close(conn);
232 adata->status = NNTP_BYE;
233 mutt_error(_("Server doesn't support reader mode"));
234 return -1;
235}
#define FREE(x)
Definition: memory.h:43
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:250
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:807
const char * mutt_istr_find(const char *haystack, const char *needle)
Find first occurrence of string (ignoring case)
Definition: string.c:592
size_t mutt_str_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix.
Definition: string.c:227
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:652
char * mutt_str_replace(char **p, const char *s)
Replace one string with another.
Definition: string.c:326
char * mutt_str_cat(char *buf, size_t buflen, const char *s)
Concatenate two strings.
Definition: string.c:265
@ 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
static size_t plen
Length of cached packet.
Definition: pgppacket.c:39
int mutt_socket_close(struct Connection *conn)
Close a socket.
Definition: socket.c:98
#define mutt_socket_readln(buf, buflen, conn)
Definition: socket.h:58
#define mutt_socket_send(conn, buf)
Definition: socket.h:59
struct Connection * conn
Connection to NNTP Server.
Definition: adata.h:63
char * authenticators
Definition: adata.h:53
bool hasCAPABILITIES
Server supports CAPABILITIES command.
Definition: adata.h:38
bool hasSTARTTLS
Server supports STARTTLS command.
Definition: adata.h:39
bool hasLISTGROUPrange
Server supports LISTGROUPrange command.
Definition: adata.h:44
bool hasLISTGROUP
Server supports LISTGROUP command.
Definition: adata.h:43
bool hasOVER
Server supports OVER command.
Definition: adata.h:45
bool hasDATE
Server supports DATE command.
Definition: adata.h:40
bool hasLIST_NEWSGROUPS
Server supports LIST_NEWSGROUPS command.
Definition: adata.h:41
+ 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 243 of file nntp.c.

244{
245 struct Connection *conn = adata->conn;
246 char buf[1024] = { 0 };
247
248 /* no CAPABILITIES, trying DATE, LISTGROUP, LIST NEWSGROUPS */
249 if (!adata->hasCAPABILITIES)
250 {
251 if ((mutt_socket_send(conn, "DATE\r\n") < 0) ||
252 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
253 {
254 return nntp_connect_error(adata);
255 }
256 if (!mutt_str_startswith(buf, "500"))
257 adata->hasDATE = true;
258
259 if ((mutt_socket_send(conn, "LISTGROUP\r\n") < 0) ||
260 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
261 {
262 return nntp_connect_error(adata);
263 }
264 if (!mutt_str_startswith(buf, "500"))
265 adata->hasLISTGROUP = true;
266
267 if ((mutt_socket_send(conn, "LIST NEWSGROUPS +\r\n") < 0) ||
268 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
269 {
270 return nntp_connect_error(adata);
271 }
272 if (!mutt_str_startswith(buf, "500"))
273 adata->hasLIST_NEWSGROUPS = true;
274 if (mutt_str_startswith(buf, "215"))
275 {
276 do
277 {
278 if (mutt_socket_readln(buf, sizeof(buf), conn) < 0)
279 return nntp_connect_error(adata);
280 } while (!mutt_str_equal(".", buf));
281 }
282 }
283
284 /* no LIST NEWSGROUPS, trying XGTITLE */
285 if (!adata->hasLIST_NEWSGROUPS)
286 {
287 if ((mutt_socket_send(conn, "XGTITLE\r\n") < 0) ||
288 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
289 {
290 return nntp_connect_error(adata);
291 }
292 if (!mutt_str_startswith(buf, "500"))
293 adata->hasXGTITLE = true;
294 }
295
296 /* no OVER, trying XOVER */
297 if (!adata->hasOVER)
298 {
299 if ((mutt_socket_send(conn, "XOVER\r\n") < 0) ||
300 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
301 {
302 return nntp_connect_error(adata);
303 }
304 if (!mutt_str_startswith(buf, "500"))
305 adata->hasXOVER = true;
306 }
307
308 /* trying LIST OVERVIEW.FMT */
309 if (adata->hasOVER || adata->hasXOVER)
310 {
311 if ((mutt_socket_send(conn, "LIST OVERVIEW.FMT\r\n") < 0) ||
312 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
313 {
314 return nntp_connect_error(adata);
315 }
316 if (!mutt_str_startswith(buf, "215"))
318 else
319 {
320 bool cont = false;
321 size_t buflen = 2048, off = 0, b = 0;
322
323 FREE(&adata->overview_fmt);
324 adata->overview_fmt = mutt_mem_malloc(buflen);
325
326 while (true)
327 {
328 if ((buflen - off) < 1024)
329 {
330 buflen *= 2;
331 mutt_mem_realloc(&adata->overview_fmt, buflen);
332 }
333
334 const int chunk = mutt_socket_readln_d(adata->overview_fmt + off,
335 buflen - off, conn, MUTT_SOCK_LOG_HDR);
336 if (chunk < 0)
337 {
338 FREE(&adata->overview_fmt);
339 return nntp_connect_error(adata);
340 }
341
342 if (!cont && mutt_str_equal(".", adata->overview_fmt + off))
343 break;
344
345 cont = (chunk >= (buflen - off));
346 off += strlen(adata->overview_fmt + off);
347 if (!cont)
348 {
349 if (adata->overview_fmt[b] == ':')
350 {
351 memmove(adata->overview_fmt + b, adata->overview_fmt + b + 1, off - b - 1);
352 adata->overview_fmt[off - 1] = ':';
353 }
354 char *colon = strchr(adata->overview_fmt + b, ':');
355 if (!colon)
356 adata->overview_fmt[off++] = ':';
357 else if (strcmp(colon + 1, "full") != 0)
358 off = colon + 1 - adata->overview_fmt;
359 if (strcasecmp(adata->overview_fmt + b, "Bytes:") == 0)
360 {
361 size_t len = strlen(adata->overview_fmt + b);
362 mutt_str_copy(adata->overview_fmt + b, "Content-Length:", len + 1);
363 off = b + len;
364 }
365 adata->overview_fmt[off++] = '\0';
366 b = off;
367 }
368 }
369 adata->overview_fmt[off++] = '\0';
370 mutt_mem_realloc(&adata->overview_fmt, off);
371 }
372 }
373 return 0;
374}
void * mutt_mem_malloc(size_t size)
Allocate memory on the heap.
Definition: memory.c:90
void mutt_mem_realloc(void *ptr, size_t size)
Resize a block of memory on the heap.
Definition: memory.c:114
const char * OverviewFmt
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:247
#define MUTT_SOCK_LOG_HDR
Definition: socket.h:55
bool hasXOVER
Server supports XOVER command.
Definition: adata.h:46
char * overview_fmt
Definition: adata.h:54
bool hasXGTITLE
Server supports XGTITLE command.
Definition: adata.h:42
+ 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 426 of file nntp.c.

427{
428 struct Connection *conn = adata->conn;
429 char buf[1024] = { 0 };
430 char authenticators[1024] = "USER";
431 char *method = NULL, *a = NULL, *p = NULL;
432 unsigned char flags = conn->account.flags;
433
434 while (true)
435 {
436 /* get login and password */
437 if ((mutt_account_getuser(&conn->account) < 0) || (conn->account.user[0] == '\0') ||
438 (mutt_account_getpass(&conn->account) < 0) || (conn->account.pass[0] == '\0'))
439 {
440 break;
441 }
442
443 /* get list of authenticators */
444 const char *const c_nntp_authenticators = cs_subset_string(NeoMutt->sub, "nntp_authenticators");
445 if (c_nntp_authenticators)
446 mutt_str_copy(authenticators, c_nntp_authenticators, sizeof(authenticators));
447 else if (adata->hasCAPABILITIES)
448 {
449 mutt_str_copy(authenticators, adata->authenticators, sizeof(authenticators));
450 p = authenticators;
451 while (*p)
452 {
453 if (*p == ' ')
454 *p = ':';
455 p++;
456 }
457 }
458 p = authenticators;
459 while (*p)
460 {
461 *p = toupper(*p);
462 p++;
463 }
464
465 mutt_debug(LL_DEBUG1, "available methods: %s\n", adata->authenticators);
466 a = authenticators;
467 while (true)
468 {
469 if (!a)
470 {
471 mutt_error(_("No authenticators available"));
472 break;
473 }
474
475 method = a;
476 a = strchr(a, ':');
477 if (a)
478 *a++ = '\0';
479
480 /* check authenticator */
481 if (adata->hasCAPABILITIES)
482 {
483 if (!adata->authenticators)
484 continue;
485 const char *m = mutt_istr_find(adata->authenticators, method);
486 if (!m)
487 continue;
488 if ((m > adata->authenticators) && (*(m - 1) != ' '))
489 continue;
490 m += strlen(method);
491 if ((*m != '\0') && (*m != ' '))
492 continue;
493 }
494 mutt_debug(LL_DEBUG1, "trying method %s\n", method);
495
496 /* AUTHINFO USER authentication */
497 if (strcmp(method, "USER") == 0)
498 {
499 // L10N: (%s) is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
500 mutt_message(_("Authenticating (%s)..."), method);
501 snprintf(buf, sizeof(buf), "AUTHINFO USER %s\r\n", conn->account.user);
502 if ((mutt_socket_send(conn, buf) < 0) ||
503 (mutt_socket_readln_d(buf, sizeof(buf), conn, MUTT_SOCK_LOG_FULL) < 0))
504 {
505 break;
506 }
507
508 /* authenticated, password is not required */
509 if (mutt_str_startswith(buf, "281"))
510 return 0;
511
512 /* username accepted, sending password */
513 if (mutt_str_startswith(buf, "381"))
514 {
515 mutt_debug(MUTT_SOCK_LOG_FULL, "%d> AUTHINFO PASS *\n", conn->fd);
516 snprintf(buf, sizeof(buf), "AUTHINFO PASS %s\r\n", conn->account.pass);
517 if ((mutt_socket_send_d(conn, buf, MUTT_SOCK_LOG_FULL) < 0) ||
518 (mutt_socket_readln_d(buf, sizeof(buf), conn, MUTT_SOCK_LOG_FULL) < 0))
519 {
520 break;
521 }
522
523 /* authenticated */
524 if (mutt_str_startswith(buf, "281"))
525 return 0;
526 }
527
528 /* server doesn't support AUTHINFO USER, trying next method */
529 if (*buf == '5')
530 continue;
531 }
532 else
533 {
534#ifdef USE_SASL_CYRUS
535 sasl_conn_t *saslconn = NULL;
536 sasl_interact_t *interaction = NULL;
537 int rc;
538 char inbuf[1024] = { 0 };
539 const char *mech = NULL;
540 const char *client_out = NULL;
541 unsigned int client_len, len;
542
543 if (mutt_sasl_client_new(conn, &saslconn) < 0)
544 {
545 mutt_debug(LL_DEBUG1, "error allocating SASL connection\n");
546 continue;
547 }
548
549 while (true)
550 {
551 rc = sasl_client_start(saslconn, method, &interaction, &client_out,
552 &client_len, &mech);
553 if (rc != SASL_INTERACT)
554 break;
555 mutt_sasl_interact(interaction);
556 }
557 if ((rc != SASL_OK) && (rc != SASL_CONTINUE))
558 {
559 sasl_dispose(&saslconn);
560 mutt_debug(LL_DEBUG1, "error starting SASL authentication exchange\n");
561 continue;
562 }
563
564 // L10N: (%s) is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
565 mutt_message(_("Authenticating (%s)..."), method);
566 snprintf(buf, sizeof(buf), "AUTHINFO SASL %s", method);
567
568 /* looping protocol */
569 while ((rc == SASL_CONTINUE) || ((rc == SASL_OK) && client_len))
570 {
571 /* send out client response */
572 if (client_len)
573 {
574 nntp_log_binbuf(client_out, client_len, "SASL", MUTT_SOCK_LOG_FULL);
575 if (*buf != '\0')
576 mutt_str_cat(buf, sizeof(buf), " ");
577 len = strlen(buf);
578 if (sasl_encode64(client_out, client_len, buf + len,
579 sizeof(buf) - len, &len) != SASL_OK)
580 {
581 mutt_debug(LL_DEBUG1, "error base64-encoding client response\n");
582 break;
583 }
584 }
585
586 mutt_str_cat(buf, sizeof(buf), "\r\n");
587 if (strchr(buf, ' '))
588 {
589 mutt_debug(MUTT_SOCK_LOG_CMD, "%d> AUTHINFO SASL %s%s\n", conn->fd,
590 method, client_len ? " sasl_data" : "");
591 }
592 else
593 mutt_debug(MUTT_SOCK_LOG_CMD, "%d> sasl_data\n", conn->fd);
594 client_len = 0;
595 if ((mutt_socket_send_d(conn, buf, MUTT_SOCK_LOG_FULL) < 0) ||
596 (mutt_socket_readln_d(inbuf, sizeof(inbuf), conn, MUTT_SOCK_LOG_FULL) < 0))
597 {
598 break;
599 }
600 if (!mutt_str_startswith(inbuf, "283 ") && !mutt_str_startswith(inbuf, "383 "))
601 {
602 mutt_debug(MUTT_SOCK_LOG_FULL, "%d< %s\n", conn->fd, inbuf);
603 break;
604 }
605 inbuf[3] = '\0';
606 mutt_debug(MUTT_SOCK_LOG_FULL, "%d< %s sasl_data\n", conn->fd, inbuf);
607
608 if (strcmp("=", inbuf + 4) == 0)
609 len = 0;
610 else if (sasl_decode64(inbuf + 4, strlen(inbuf + 4), buf,
611 sizeof(buf) - 1, &len) != SASL_OK)
612 {
613 mutt_debug(LL_DEBUG1, "error base64-decoding server response\n");
614 break;
615 }
616 else
617 nntp_log_binbuf(buf, len, "SASL", MUTT_SOCK_LOG_FULL);
618
619 while (true)
620 {
621 rc = sasl_client_step(saslconn, buf, len, &interaction, &client_out, &client_len);
622 if (rc != SASL_INTERACT)
623 break;
624 mutt_sasl_interact(interaction);
625 }
626 if (*inbuf != '3')
627 break;
628
629 *buf = '\0';
630 } /* looping protocol */
631
632 if ((rc == SASL_OK) && (client_len == 0) && (*inbuf == '2'))
633 {
634 mutt_sasl_setup_conn(conn, saslconn);
635 return 0;
636 }
637
638 /* terminate SASL session */
639 sasl_dispose(&saslconn);
640 if (conn->fd < 0)
641 break;
642 if (mutt_str_startswith(inbuf, "383 "))
643 {
644 if ((mutt_socket_send(conn, "*\r\n") < 0) ||
645 (mutt_socket_readln(inbuf, sizeof(inbuf), conn) < 0))
646 {
647 break;
648 }
649 }
650
651 /* server doesn't support AUTHINFO SASL, trying next method */
652 if (*inbuf == '5')
653 continue;
654#else
655 continue;
656#endif /* USE_SASL_CYRUS */
657 }
658
659 // L10N: %s is the method name, e.g. Anonymous, CRAM-MD5, GSSAPI, SASL
660 mutt_error(_("%s authentication failed"), method);
661 break;
662 }
663 break;
664 }
665
666 /* error */
667 adata->status = NNTP_BYE;
668 conn->account.flags = flags;
669 if (conn->fd < 0)
670 {
671 mutt_error(_("Server closed connection"));
672 }
673 else
674 mutt_socket_close(conn);
675 return -1;
676}
const char * cs_subset_string(const struct ConfigSubset *sub, const char *name)
Get a string config item by name.
Definition: helpers.c:317
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:50
#define mutt_message(...)
Definition: logging.h:86
#define mutt_debug(LEVEL,...)
Definition: logging.h:84
@ LL_DEBUG1
Log at debug level 1.
Definition: logging.h:40
int mutt_sasl_interact(sasl_interact_t *interaction)
Perform an SASL interaction with the user.
Definition: sasl.c:694
int mutt_sasl_client_new(struct Connection *conn, sasl_conn_t **saslconn)
Wrapper for sasl_client_new()
Definition: sasl.c:599
void mutt_sasl_setup_conn(struct Connection *conn, sasl_conn_t *saslconn)
Set up an SASL connection.
Definition: sasl.c:731
#define MUTT_SOCK_LOG_FULL
Definition: socket.h:56
#define MUTT_SOCK_LOG_CMD
Definition: socket.h:54
#define mutt_socket_send_d(conn, buf, dbg)
Definition: socket.h:60
char user[128]
Username.
Definition: connaccount.h:56
char pass[256]
Password.
Definition: connaccount.h:57
MuttAccountFlags flags
Which fields are initialised, e.g. MUTT_ACCT_USER.
Definition: connaccount.h:60
char inbuf[1024]
Buffer for incoming traffic.
Definition: connection.h:52
struct ConnAccount account
Account details: username, password, etc.
Definition: connection.h:50
int fd
Socket file descriptor.
Definition: connection.h:54
Container for Accounts, Notifications.
Definition: neomutt.h:37
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:39
+ 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 686 of file nntp.c.

687{
688 struct NntpAccountData *adata = mdata->adata;
689 char buf[1024] = { 0 };
690
691 if (adata->status == NNTP_BYE)
692 return -1;
693
694 while (true)
695 {
696 if (adata->status == NNTP_OK)
697 {
698 int rc = 0;
699
700 if (*line)
701 rc = mutt_socket_send(adata->conn, line);
702 else if (mdata->group)
703 {
704 snprintf(buf, sizeof(buf), "GROUP %s\r\n", mdata->group);
705 rc = mutt_socket_send(adata->conn, buf);
706 }
707 if (rc >= 0)
708 rc = mutt_socket_readln(buf, sizeof(buf), adata->conn);
709 if (rc >= 0)
710 break;
711 }
712
713 /* reconnect */
714 while (true)
715 {
716 adata->status = NNTP_NONE;
717 if (nntp_open_connection(adata) == 0)
718 break;
719
720 snprintf(buf, sizeof(buf), _("Connection to %s lost. Reconnect?"),
721 adata->conn->account.host);
722 if (mutt_yesorno(buf, MUTT_YES) != MUTT_YES)
723 {
724 adata->status = NNTP_BYE;
725 return -1;
726 }
727 }
728
729 /* select newsgroup after reconnection */
730 if (mdata->group)
731 {
732 snprintf(buf, sizeof(buf), "GROUP %s\r\n", mdata->group);
733 if ((mutt_socket_send(adata->conn, buf) < 0) ||
734 (mutt_socket_readln(buf, sizeof(buf), adata->conn) < 0))
735 {
737 }
738 }
739 if (*line == '\0')
740 break;
741 }
742
743 mutt_str_copy(line, buf, linelen);
744 return 0;
745}
@ 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:1709
@ MUTT_YES
User answered 'Yes', or assume 'Yes'.
Definition: quad.h:39
enum QuadOption mutt_yesorno(const char *msg, enum QuadOption def)
Ask the user a Yes/No question.
Definition: question.c:194
void * adata
Private data (for Mailbox backends)
Definition: account.h:43
NNTP-specific Account data -.
Definition: adata.h:37
char * group
Name of newsgroup.
Definition: mdata.h:34
struct NntpAccountData * adata
Definition: mdata.h:47
+ 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 763 of file nntp.c.

765{
766 bool done = false;
767 int rc;
768
769 while (!done)
770 {
771 char buf[1024] = { 0 };
772 char *line = NULL;
773 unsigned int lines = 0;
774 size_t off = 0;
775 struct Progress *progress = NULL;
776
777 mutt_str_copy(buf, query, sizeof(buf));
778 if (nntp_query(mdata, buf, sizeof(buf)) < 0)
779 return -1;
780 if (buf[0] != '2')
781 {
782 mutt_str_copy(query, buf, qlen);
783 return 1;
784 }
785
786 line = mutt_mem_malloc(sizeof(buf));
787 rc = 0;
788
789 if (msg)
790 progress = progress_new(msg, MUTT_PROGRESS_READ, 0);
791
792 while (true)
793 {
794 char *p = NULL;
795 int chunk = mutt_socket_readln_d(buf, sizeof(buf), mdata->adata->conn, MUTT_SOCK_LOG_FULL);
796 if (chunk < 0)
797 {
798 mdata->adata->status = NNTP_NONE;
799 break;
800 }
801
802 p = buf;
803 if (!off && (buf[0] == '.'))
804 {
805 if (buf[1] == '\0')
806 {
807 done = true;
808 break;
809 }
810 if (buf[1] == '.')
811 p++;
812 }
813
814 mutt_str_copy(line + off, p, sizeof(buf));
815
816 if (chunk >= sizeof(buf))
817 off += strlen(p);
818 else
819 {
820 if (msg)
821 progress_update(progress, ++lines, -1);
822
823 if ((rc == 0) && (func(line, data) < 0))
824 rc = -2;
825 off = 0;
826 }
827
828 mutt_mem_realloc(&line, off + sizeof(buf));
829 }
830 FREE(&line);
831 func(NULL, data);
832 progress_free(&progress);
833 }
834
835 return rc;
836}
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:686
@ MUTT_PROGRESS_READ
Progress tracks elements, according to $read_inc
Definition: lib.h:49
void progress_free(struct Progress **ptr)
Free a Progress Bar.
Definition: progress.c:86
bool progress_update(struct Progress *progress, size_t pos, int percent)
Update the state of the progress bar.
Definition: progress.c:73
struct Progress * progress_new(const char *msg, enum ProgressType type, size_t size)
Create a new Progress Bar.
Definition: progress.c:118
+ 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 844 of file nntp.c.

845{
846 if (!line)
847 return 0;
848
849 struct NntpAccountData *adata = data;
850
851 char *desc = strpbrk(line, " \t");
852 if (desc)
853 {
854 *desc++ = '\0';
855 desc += strspn(desc, " \t");
856 }
857 else
858 desc = strchr(line, '\0');
859
861 if (mdata && !mutt_str_equal(desc, mdata->desc))
862 {
863 mutt_str_replace(&mdata->desc, desc);
864 mutt_debug(LL_DEBUG2, "group: %s, desc: %s\n", line, desc);
865 }
866 return 0;
867}
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: logging.h:41
void * mdata
Driver specific data.
Definition: mailbox.h:132
struct HashTable * groups_hash
Definition: adata.h:62
NNTP-specific Mailbox data -.
Definition: mdata.h:33
char * desc
Description of newsgroup.
Definition: mdata.h:35
+ 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 879 of file nntp.c.

880{
881 char buf[256] = { 0 };
882 const char *cmd = NULL;
883
884 /* get newsgroup description, if possible */
885 struct NntpAccountData *adata = mdata->adata;
886 if (!wildmat)
887 wildmat = mdata->group;
888 if (adata->hasLIST_NEWSGROUPS)
889 cmd = "LIST NEWSGROUPS";
890 else if (adata->hasXGTITLE)
891 cmd = "XGTITLE";
892 else
893 return 0;
894
895 snprintf(buf, sizeof(buf), "%s %s\r\n", cmd, wildmat);
896 int rc = nntp_fetch_lines(mdata, buf, sizeof(buf), msg, fetch_description, adata);
897 if (rc > 0)
898 {
899 mutt_error("%s: %s", cmd, buf);
900 }
901 return rc;
902}
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:763
static int fetch_description(char *line, void *data)
Parse newsgroup description.
Definition: nntp.c:844
+ 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 911 of file nntp.c.

912{
913 struct NntpMboxData *mdata = m->mdata;
914
915 char *buf = mutt_str_dup(e->env->xref);
916 char *p = buf;
917 while (p)
918 {
919 anum_t anum;
920
921 /* skip to next word */
922 p += strspn(p, " \t");
923 char *grp = p;
924
925 /* skip to end of word */
926 p = strpbrk(p, " \t");
927 if (p)
928 *p++ = '\0';
929
930 /* find colon */
931 char *colon = strchr(grp, ':');
932 if (!colon)
933 continue;
934 *colon++ = '\0';
935 if (sscanf(colon, ANUM, &anum) != 1)
936 continue;
937
938 nntp_article_status(m, e, grp, anum);
939 if (!nntp_edata_get(e)->article_num && mutt_str_equal(mdata->group, grp))
940 nntp_edata_get(e)->article_num = anum;
941 }
942 FREE(&buf);
943}
void nntp_article_status(struct Mailbox *m, struct Email *e, char *group, anum_t anum)
Get status of articles from .newsrc.
Definition: newsrc.c:1235
struct NntpEmailData * nntp_edata_get(struct Email *e)
Get the private data for this Email.
Definition: edata.c:58
#define ANUM
Definition: lib.h:61
#define anum_t
Definition: lib.h:60
struct Envelope * env
Envelope information.
Definition: email.h:66
char * xref
List of cross-references.
Definition: envelope.h:80
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 952 of file nntp.c.

953{
954 FILE *fp = data;
955
956 if (!line)
957 rewind(fp);
958 else if ((fputs(line, fp) == EOF) || (fputc('\n', fp) == EOF))
959 return -1;
960 return 0;
961}
+ 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 969 of file nntp.c.

970{
971 struct FetchCtx *fc = data;
972 anum_t anum;
973
974 if (!line)
975 return 0;
976 if (sscanf(line, ANUM, &anum) != 1)
977 return 0;
978 if ((anum < fc->first) || (anum > fc->last))
979 return 0;
980 fc->messages[anum - fc->first] = 1;
981 return 0;
982}
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 991 of file nntp.c.

992{
993 if (!line || !data)
994 return 0;
995
996 struct FetchCtx *fc = data;
997 struct Mailbox *m = fc->mailbox;
998 if (!m)
999 return -1;
1000
1001 struct NntpMboxData *mdata = m->mdata;
1002 struct Email *e = NULL;
1003 char *header = NULL, *field = NULL;
1004 bool save = true;
1005 anum_t anum;
1006
1007 /* parse article number */
1008 field = strchr(line, '\t');
1009 if (field)
1010 *field++ = '\0';
1011 if (sscanf(line, ANUM, &anum) != 1)
1012 return 0;
1013 mutt_debug(LL_DEBUG2, "" ANUM "\n", anum);
1014
1015 /* out of bounds */
1016 if ((anum < fc->first) || (anum > fc->last))
1017 return 0;
1018
1019 /* not in LISTGROUP */
1020 if (!fc->messages[anum - fc->first])
1021 {
1022 /* progress */
1023 if (m->verbose)
1024 progress_update(fc->progress, anum - fc->first + 1, -1);
1025 return 0;
1026 }
1027
1028 /* convert overview line to header */
1029 FILE *fp = mutt_file_mkstemp();
1030 if (!fp)
1031 return -1;
1032
1033 header = mdata->adata->overview_fmt;
1034 while (field)
1035 {
1036 char *b = field;
1037
1038 if (*header)
1039 {
1040 if (!strstr(header, ":full") && (fputs(header, fp) == EOF))
1041 {
1042 mutt_file_fclose(&fp);
1043 return -1;
1044 }
1045 header = strchr(header, '\0') + 1;
1046 }
1047
1048 field = strchr(field, '\t');
1049 if (field)
1050 *field++ = '\0';
1051 if ((fputs(b, fp) == EOF) || (fputc('\n', fp) == EOF))
1052 {
1053 mutt_file_fclose(&fp);
1054 return -1;
1055 }
1056 }
1057 rewind(fp);
1058
1059 /* allocate memory for headers */
1060 if (m->msg_count >= m->email_max)
1061 mx_alloc_memory(m);
1062
1063 /* parse header */
1064 m->emails[m->msg_count] = email_new();
1065 e = m->emails[m->msg_count];
1066 e->env = mutt_rfc822_read_header(fp, e, false, false);
1067 e->env->newsgroups = mutt_str_dup(mdata->group);
1068 e->received = e->date_sent;
1069 mutt_file_fclose(&fp);
1070
1071#ifdef USE_HCACHE
1072 if (fc->hc)
1073 {
1074 char buf[16] = { 0 };
1075
1076 /* try to replace with header from cache */
1077 snprintf(buf, sizeof(buf), ANUM, anum);
1078 struct HCacheEntry hce = mutt_hcache_fetch(fc->hc, buf, strlen(buf), 0);
1079 if (hce.email)
1080 {
1081 mutt_debug(LL_DEBUG2, "mutt_hcache_fetch %s\n", buf);
1082 email_free(&e);
1083 e = hce.email;
1084 m->emails[m->msg_count] = e;
1085 e->edata = NULL;
1086 e->read = false;
1087 e->old = false;
1088
1089 /* skip header marked as deleted in cache */
1090 if (e->deleted && !fc->restore)
1091 {
1092 if (mdata->bcache)
1093 {
1094 mutt_debug(LL_DEBUG2, "mutt_bcache_del %s\n", buf);
1095 mutt_bcache_del(mdata->bcache, buf);
1096 }
1097 save = false;
1098 }
1099 }
1100
1101 /* not cached yet, store header */
1102 else
1103 {
1104 mutt_debug(LL_DEBUG2, "mutt_hcache_store %s\n", buf);
1105 mutt_hcache_store(fc->hc, buf, strlen(buf), e, 0);
1106 }
1107 }
1108#endif
1109
1110 if (save)
1111 {
1112 e->index = m->msg_count++;
1113 e->read = false;
1114 e->old = false;
1115 e->deleted = false;
1116 e->edata = nntp_edata_new();
1118 nntp_edata_get(e)->article_num = anum;
1119 if (fc->restore)
1120 e->changed = true;
1121 else
1122 {
1123 nntp_article_status(m, e, NULL, anum);
1124 if (!e->read)
1125 nntp_parse_xref(m, e);
1126 }
1127 if (anum > mdata->last_loaded)
1128 mdata->last_loaded = anum;
1129 }
1130 else
1131 email_free(&e);
1132
1133 /* progress */
1134 if (m->verbose)
1135 progress_update(fc->progress, anum - fc->first + 1, -1);
1136 return 0;
1137}
int mutt_bcache_del(struct BodyCache *bcache, const char *id)
Delete a file from the Body Cache.
Definition: bcache.c:265
struct Email * email_new(void)
Create a new Email.
Definition: email.c:78
void email_free(struct Email **ptr)
Free an Email.
Definition: email.c:44
int mutt_file_fclose(FILE **fp)
Close a FILE handle (and NULL the pointer)
Definition: file.c:152
#define mutt_file_mkstemp()
Definition: file.h:112
int mutt_hcache_store(struct HeaderCache *hc, const char *key, size_t keylen, struct Email *e, uint32_t uidvalidity)
Multiplexor for StoreOps::store.
Definition: hcache.c:552
struct HCacheEntry mutt_hcache_fetch(struct HeaderCache *hc, const char *key, size_t keylen, uint32_t uidvalidity)
Multiplexor for StoreOps::fetch.
Definition: hcache.c:456
void mx_alloc_memory(struct Mailbox *m)
Create storage for the emails.
Definition: mx.c:1219
void nntp_edata_free(void **ptr)
Free the private Email data - Implements Email::edata_free()
Definition: edata.c:38
struct NntpEmailData * nntp_edata_new(void)
Create a new NntpEmailData for an Email.
Definition: edata.c:48
static void nntp_parse_xref(struct Mailbox *m, struct Email *e)
Parse cross-reference.
Definition: nntp.c:911
struct Envelope * mutt_rfc822_read_header(FILE *fp, struct Email *e, bool user_hdrs, bool weed)
Parses an RFC822 header.
Definition: parse.c:1158
The envelope/body of an email.
Definition: email.h:37
bool read
Email is read.
Definition: email.h:48
void * edata
Driver-specific data.
Definition: email.h:72
bool old
Email is seen, but unread.
Definition: email.h:47
void(* edata_free)(void **ptr)
Free the private data attached to the Email.
Definition: email.h:87
bool changed
Email has been edited.
Definition: email.h:75
time_t date_sent
Time when the message was sent (UTC)
Definition: email.h:58
bool deleted
Email is deleted.
Definition: email.h:76
int index
The absolute (unsorted) message number.
Definition: email.h:110
time_t received
Time when the message was placed in the mailbox.
Definition: email.h:59
char * newsgroups
List of newsgroups.
Definition: envelope.h:79
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:98
struct Email * email
Retrieved email.
Definition: lib.h:101
A mailbox.
Definition: mailbox.h:79
int msg_count
Total number of messages.
Definition: mailbox.h:88
int email_max
Number of pointers in emails.
Definition: mailbox.h:97
struct Email ** emails
Array of Emails.
Definition: mailbox.h:96
bool verbose
Display status messages?
Definition: mailbox.h:114
struct BodyCache * bcache
Definition: mdata.h:49
anum_t last_loaded
Definition: mdata.h:38
+ 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 1149 of file nntp.c.

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

1389{
1390 char buf[1024] = { 0 };
1391 anum_t count, first, last;
1392
1393 /* use GROUP command to poll newsgroup */
1394 if (nntp_query(mdata, buf, sizeof(buf)) < 0)
1395 return -1;
1396 if (sscanf(buf, "211 " ANUM " " ANUM " " ANUM, &count, &first, &last) != 3)
1397 return 0;
1398 if ((first == mdata->first_message) && (last == mdata->last_message))
1399 return 0;
1400
1401 /* articles have been renumbered */
1402 if (last < mdata->last_message)
1403 {
1404 mdata->last_cached = 0;
1405 if (mdata->newsrc_len)
1406 {
1407 mutt_mem_realloc(&mdata->newsrc_ent, sizeof(struct NewsrcEntry));
1408 mdata->newsrc_len = 1;
1409 mdata->newsrc_ent[0].first = 1;
1410 mdata->newsrc_ent[0].last = 0;
1411 }
1412 }
1413 mdata->first_message = first;
1414 mdata->last_message = last;
1415 if (!update_stat)
1416 return 1;
1417
1418 /* update counters */
1419 else if (!last || (!mdata->newsrc_ent && !mdata->last_cached))
1420 mdata->unread = count;
1421 else
1423 return 1;
1424}
void nntp_group_unread_stat(struct NntpMboxData *mdata)
Count number of unread articles using .newsrc data.
Definition: newsrc.c:132
An entry in a .newsrc (subscribed newsgroups)
Definition: lib.h: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:39
anum_t last_message
Definition: mdata.h:37
struct NewsrcEntry * newsrc_ent
Definition: mdata.h:46
anum_t unread
Definition: mdata.h:40
unsigned int newsrc_len
Definition: mdata.h:45
anum_t first_message
Definition: mdata.h:36
+ 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 1433 of file nntp.c.

1434{
1435 if (!m)
1436 return MX_STATUS_ERROR;
1437
1438 struct NntpMboxData *mdata = m->mdata;
1439 struct NntpAccountData *adata = mdata->adata;
1440 time_t now = mutt_date_epoch();
1441 enum MxStatus rc = MX_STATUS_OK;
1442 void *hc = NULL;
1443
1444 const short c_nntp_poll = cs_subset_number(NeoMutt->sub, "nntp_poll");
1445 if (adata->check_time + c_nntp_poll > now)
1446 return MX_STATUS_OK;
1447
1448 mutt_message(_("Checking for new messages..."));
1449 if (nntp_newsrc_parse(adata) < 0)
1450 return MX_STATUS_ERROR;
1451
1452 adata->check_time = now;
1453 int rc2 = nntp_group_poll(mdata, false);
1454 if (rc2 < 0)
1455 {
1457 return -1;
1458 }
1459 if (rc2 != 0)
1461
1462 /* articles have been renumbered, remove all headers */
1463 if (mdata->last_message < mdata->last_loaded)
1464 {
1465 for (int i = 0; i < m->msg_count; i++)
1466 email_free(&m->emails[i]);
1467 m->msg_count = 0;
1468 m->msg_tagged = 0;
1469
1470 mdata->last_loaded = mdata->first_message - 1;
1471 const long c_nntp_context = cs_subset_long(NeoMutt->sub, "nntp_context");
1472 if (c_nntp_context && (mdata->last_message - mdata->last_loaded > c_nntp_context))
1473 mdata->last_loaded = mdata->last_message - c_nntp_context;
1474
1475 rc = MX_STATUS_REOPENED;
1476 }
1477
1478 /* .newsrc has been externally modified */
1479 if (adata->newsrc_modified)
1480 {
1481#ifdef USE_HCACHE
1482 unsigned char *messages = NULL;
1483 char buf[16] = { 0 };
1484 struct Email *e = NULL;
1485 anum_t first = mdata->first_message;
1486
1487 const long c_nntp_context = cs_subset_long(NeoMutt->sub, "nntp_context");
1488 if (c_nntp_context && (mdata->last_message - first + 1 > c_nntp_context))
1489 first = mdata->last_message - c_nntp_context + 1;
1490 messages = mutt_mem_calloc(mdata->last_loaded - first + 1, sizeof(unsigned char));
1491 hc = nntp_hcache_open(mdata);
1492 nntp_hcache_update(mdata, hc);
1493#endif
1494
1495 /* update flags according to .newsrc */
1496 int j = 0;
1497 for (int i = 0; i < m->msg_count; i++)
1498 {
1499 if (!m->emails[i])
1500 continue;
1501 bool flagged = false;
1502 anum_t anum = nntp_edata_get(m->emails[i])->article_num;
1503
1504#ifdef USE_HCACHE
1505 /* check hcache for flagged and deleted flags */
1506 if (hc)
1507 {
1508 if ((anum >= first) && (anum <= mdata->last_loaded))
1509 messages[anum - first] = 1;
1510
1511 snprintf(buf, sizeof(buf), ANUM, anum);
1512 struct HCacheEntry hce = mutt_hcache_fetch(hc, buf, strlen(buf), 0);
1513 if (hce.email)
1514 {
1515 bool deleted;
1516
1517 mutt_debug(LL_DEBUG2, "#1 mutt_hcache_fetch %s\n", buf);
1518 e = hce.email;
1519 e->edata = NULL;
1520 deleted = e->deleted;
1521 flagged = e->flagged;
1522 email_free(&e);
1523
1524 /* header marked as deleted, removing from context */
1525 if (deleted)
1526 {
1527 mutt_set_flag(m, m->emails[i], MUTT_TAG, false);
1528 email_free(&m->emails[i]);
1529 continue;
1530 }
1531 }
1532 }
1533#endif
1534
1535 if (!m->emails[i]->changed)
1536 {
1537 m->emails[i]->flagged = flagged;
1538 m->emails[i]->read = false;
1539 m->emails[i]->old = false;
1540 nntp_article_status(m, m->emails[i], NULL, anum);
1541 if (!m->emails[i]->read)
1542 nntp_parse_xref(m, m->emails[i]);
1543 }
1544 m->emails[j++] = m->emails[i];
1545 }
1546
1547#ifdef USE_HCACHE
1548 m->msg_count = j;
1549
1550 /* restore headers without "deleted" flag */
1551 for (anum_t anum = first; anum <= mdata->last_loaded; anum++)
1552 {
1553 if (messages[anum - first])
1554 continue;
1555
1556 snprintf(buf, sizeof(buf), ANUM, anum);
1557 struct HCacheEntry hce = mutt_hcache_fetch(hc, buf, strlen(buf), 0);
1558 if (hce.email)
1559 {
1560 mutt_debug(LL_DEBUG2, "#2 mutt_hcache_fetch %s\n", buf);
1561 if (m->msg_count >= m->email_max)
1562 mx_alloc_memory(m);
1563
1564 e = hce.email;
1565 m->emails[m->msg_count] = e;
1566 e->edata = NULL;
1567 if (e->deleted)
1568 {
1569 email_free(&e);
1570 if (mdata->bcache)
1571 {
1572 mutt_debug(LL_DEBUG2, "mutt_bcache_del %s\n", buf);
1573 mutt_bcache_del(mdata->bcache, buf);
1574 }
1575 continue;
1576 }
1577
1578 m->msg_count++;
1579 e->read = false;
1580 e->old = false;
1581 e->edata = nntp_edata_new();
1583 nntp_edata_get(e)->article_num = anum;
1584 nntp_article_status(m, e, NULL, anum);
1585 if (!e->read)
1586 nntp_parse_xref(m, e);
1587 }
1588 }
1589 FREE(&messages);
1590#endif
1591
1592 adata->newsrc_modified = false;
1593 rc = MX_STATUS_REOPENED;
1594 }
1595
1596 /* some headers were removed, context must be updated */
1597 if (rc == MX_STATUS_REOPENED)
1599
1600 /* fetch headers of new articles */
1601 if (mdata->last_message > mdata->last_loaded)
1602 {
1603 int oldmsgcount = m->msg_count;
1604 bool verbose = m->verbose;
1605 m->verbose = false;
1606#ifdef USE_HCACHE
1607 if (!hc)
1608 {
1609 hc = nntp_hcache_open(mdata);
1610 nntp_hcache_update(mdata, hc);
1611 }
1612#endif
1613 int old_msg_count = m->msg_count;
1614 rc2 = nntp_fetch_headers(m, hc, mdata->last_loaded + 1, mdata->last_message, false);
1615 m->verbose = verbose;
1616 if (rc2 == 0)
1617 {
1618 if (m->msg_count > old_msg_count)
1620 mdata->last_loaded = mdata->last_message;
1621 }
1622 if ((rc == MX_STATUS_OK) && (m->msg_count > oldmsgcount))
1623 rc = MX_STATUS_NEW_MAIL;
1624 }
1625
1626#ifdef USE_HCACHE
1628#endif
1629 if (rc != MX_STATUS_OK)
1630 nntp_newsrc_close(adata);
1632 return rc;
1633}
short cs_subset_number(const struct ConfigSubset *sub, const char *name)
Get a number config item by name.
Definition: helpers.c:169
long cs_subset_long(const struct ConfigSubset *sub, const char *name)
Get a long config item by name.
Definition: helpers.c:121
time_t mutt_date_epoch(void)
Return the number of seconds since the Unix epoch.
Definition: date.c:428
void mutt_hcache_close(struct HeaderCache *hc)
Multiplexor for StoreOps::close.
Definition: hcache.c:432
void mailbox_changed(struct Mailbox *m, enum NotifyMailbox action)
Notify observers of a change to a Mailbox.
Definition: mailbox.c:222
@ NT_MAILBOX_INVALID
Email list was changed.
Definition: mailbox.h:176
@ MUTT_TAG
Tagged messages.
Definition: mutt.h:100
MxStatus
Return values from mbox_check(), mbox_check_stats(), mbox_snc(), and mbox_close()
Definition: mxapi.h:84
@ MX_STATUS_ERROR
An error occurred.
Definition: mxapi.h:85
@ MX_STATUS_OK
No changes.
Definition: mxapi.h:86
@ MX_STATUS_REOPENED
Mailbox was reopened.
Definition: mxapi.h:89
@ MX_STATUS_NEW_MAIL
New mail received in Mailbox.
Definition: mxapi.h:87
struct HeaderCache * nntp_hcache_open(struct NntpMboxData *mdata)
Open newsgroup hcache.
Definition: newsrc.c:711
void nntp_hcache_update(struct NntpMboxData *mdata, struct HeaderCache *hc)
Remove stale cached headers.
Definition: newsrc.c:735
int nntp_active_save_cache(struct NntpAccountData *adata)
Save list of all newsgroups to cache.
Definition: newsrc.c:652
int nntp_newsrc_parse(struct NntpAccountData *adata)
Parse .newsrc file.
Definition: newsrc.c:162
void nntp_newsrc_close(struct NntpAccountData *adata)
Unlock and close .newsrc file.
Definition: newsrc.c:118
static int nntp_group_poll(struct NntpMboxData *mdata, bool update_stat)
Check newsgroup for new articles.
Definition: nntp.c:1388
static int nntp_fetch_headers(struct Mailbox *m, void *hc, anum_t first, anum_t last, bool restore)
Fetch headers.
Definition: nntp.c:1149
#define mutt_set_flag(m, e, flag, bf)
Definition: protos.h:63
bool flagged
Marked important?
Definition: email.h:45
int msg_tagged
How many messages are tagged?
Definition: mailbox.h:94
bool newsrc_modified
Definition: adata.h:50
+ 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 1642 of file nntp.c.

1643{
1644 if (adata->hasDATE)
1645 {
1646 struct NntpMboxData mdata = { 0 };
1647 char buf[1024] = { 0 };
1648 struct tm tm = { 0 };
1649
1650 mdata.adata = adata;
1651 mdata.group = NULL;
1652 mutt_str_copy(buf, "DATE\r\n", sizeof(buf));
1653 if (nntp_query(&mdata, buf, sizeof(buf)) < 0)
1654 return -1;
1655
1656 if (sscanf(buf, "111 %4d%2d%2d%2d%2d%2d%*s", &tm.tm_year, &tm.tm_mon,
1657 &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) == 6)
1658 {
1659 tm.tm_year -= 1900;
1660 tm.tm_mon--;
1661 *now = timegm(&tm);
1662 if (*now >= 0)
1663 {
1664 mutt_debug(LL_DEBUG1, "server time is %lu\n", *now);
1665 return 0;
1666 }
1667 }
1668 }
1669 *now = mutt_date_epoch();
1670 return 0;
1671}
time_t timegm(struct tm *tm)
Convert struct tm to time_t seconds since epoch.
Definition: timegm.c:69
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ fetch_children()

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

Parse XPAT line.

Parameters
lineString to parse
dataChildCtx
Return values
0Always

Definition at line 1679 of file nntp.c.

1680{
1681 struct ChildCtx *cc = data;
1682 anum_t anum;
1683
1684 if (!line || (sscanf(line, ANUM, &anum) != 1))
1685 return 0;
1686 for (unsigned int i = 0; i < cc->mailbox->msg_count; i++)
1687 {
1688 struct Email *e = cc->mailbox->emails[i];
1689 if (!e)
1690 break;
1691 if (nntp_edata_get(e)->article_num == anum)
1692 return 0;
1693 }
1694 if (cc->num >= cc->max)
1695 {
1696 cc->max *= 2;
1697 mutt_mem_realloc(&cc->child, sizeof(anum_t) * cc->max);
1698 }
1699 cc->child[cc->num++] = anum;
1700 return 0;
1701}
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 1709 of file nntp.c.

1710{
1711 struct Connection *conn = adata->conn;
1712 char buf[256] = { 0 };
1713 int cap;
1714 bool posting = false, auth = true;
1715
1716 if (adata->status == NNTP_OK)
1717 return 0;
1718 if (adata->status == NNTP_BYE)
1719 return -1;
1720 adata->status = NNTP_NONE;
1721
1722 if (mutt_socket_open(conn) < 0)
1723 return -1;
1724
1725 if (mutt_socket_readln(buf, sizeof(buf), conn) < 0)
1726 return nntp_connect_error(adata);
1727
1728 if (mutt_str_startswith(buf, "200"))
1729 posting = true;
1730 else if (!mutt_str_startswith(buf, "201"))
1731 {
1732 mutt_socket_close(conn);
1734 mutt_error("%s", buf);
1735 return -1;
1736 }
1737
1738 /* get initial capabilities */
1739 cap = nntp_capabilities(adata);
1740 if (cap < 0)
1741 return -1;
1742
1743 /* tell news server to switch to mode reader if it isn't so */
1744 if (cap > 0)
1745 {
1746 if ((mutt_socket_send(conn, "MODE READER\r\n") < 0) ||
1747 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
1748 {
1749 return nntp_connect_error(adata);
1750 }
1751
1752 if (mutt_str_startswith(buf, "200"))
1753 posting = true;
1754 else if (mutt_str_startswith(buf, "201"))
1755 posting = false;
1756 /* error if has capabilities, ignore result if no capabilities */
1757 else if (adata->hasCAPABILITIES)
1758 {
1759 mutt_socket_close(conn);
1760 mutt_error(_("Could not switch to reader mode"));
1761 return -1;
1762 }
1763
1764 /* recheck capabilities after MODE READER */
1765 if (adata->hasCAPABILITIES)
1766 {
1767 cap = nntp_capabilities(adata);
1768 if (cap < 0)
1769 return -1;
1770 }
1771 }
1772
1773 mutt_message(_("Connected to %s. %s"), conn->account.host,
1774 posting ? _("Posting is ok") : _("Posting is NOT ok"));
1775 mutt_sleep(1);
1776
1777#ifdef USE_SSL
1778 /* Attempt STARTTLS if available and desired. */
1779 const bool c_ssl_force_tls = cs_subset_bool(NeoMutt->sub, "ssl_force_tls");
1780 if ((adata->use_tls != 1) && (adata->hasSTARTTLS || c_ssl_force_tls))
1781 {
1782 if (adata->use_tls == 0)
1783 {
1784 const enum QuadOption c_ssl_starttls = cs_subset_quad(NeoMutt->sub, "ssl_starttls");
1785 adata->use_tls = c_ssl_force_tls ||
1786 (query_quadoption(c_ssl_starttls,
1787 _("Secure connection with TLS?")) == MUTT_YES) ?
1788 2 :
1789 1;
1790 }
1791 if (adata->use_tls == 2)
1792 {
1793 if ((mutt_socket_send(conn, "STARTTLS\r\n") < 0) ||
1794 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
1795 {
1796 return nntp_connect_error(adata);
1797 }
1798 // Clear any data after the STARTTLS acknowledgement
1799 mutt_socket_empty(conn);
1800 if (!mutt_str_startswith(buf, "382"))
1801 {
1802 adata->use_tls = 0;
1803 mutt_error("STARTTLS: %s", buf);
1804 }
1805 else if (mutt_ssl_starttls(conn))
1806 {
1807 adata->use_tls = 0;
1808 adata->status = NNTP_NONE;
1809 mutt_socket_close(adata->conn);
1810 mutt_error(_("Could not negotiate TLS connection"));
1811 return -1;
1812 }
1813 else
1814 {
1815 /* recheck capabilities after STARTTLS */
1816 cap = nntp_capabilities(adata);
1817 if (cap < 0)
1818 return -1;
1819 }
1820 }
1821 }
1822#endif
1823
1824 /* authentication required? */
1825 if (conn->account.flags & MUTT_ACCT_USER)
1826 {
1827 if (!conn->account.user[0])
1828 auth = false;
1829 }
1830 else
1831 {
1832 if ((mutt_socket_send(conn, "STAT\r\n") < 0) ||
1833 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
1834 {
1835 return nntp_connect_error(adata);
1836 }
1837 if (!mutt_str_startswith(buf, "480"))
1838 auth = false;
1839 }
1840
1841 /* authenticate */
1842 if (auth && (nntp_auth(adata) < 0))
1843 return -1;
1844
1845 /* get final capabilities after authentication */
1846 if (adata->hasCAPABILITIES && (auth || (cap > 0)))
1847 {
1848 cap = nntp_capabilities(adata);
1849 if (cap < 0)
1850 return -1;
1851 if (cap > 0)
1852 {
1853 mutt_socket_close(conn);
1854 mutt_error(_("Could not switch to reader mode"));
1855 return -1;
1856 }
1857 }
1858
1859 /* attempt features */
1860 if (nntp_attempt_features(adata) < 0)
1861 return -1;
1862
1863 adata->status = NNTP_OK;
1864 return 0;
1865}
enum QuadOption cs_subset_quad(const struct ConfigSubset *sub, const char *name)
Get a quad-value config item by name.
Definition: helpers.c:218
#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:1143
void mutt_str_remove_trailing_ws(char *s)
Trim trailing whitespace from a string.
Definition: string.c:636
void mutt_sleep(short s)
Sleep for a while.
Definition: muttlib.c:1455
static int nntp_auth(struct NntpAccountData *adata)
Get login, password and authenticate.
Definition: nntp.c:426
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:243
QuadOption
Possible values for a quad-option.
Definition: quad.h:36
enum QuadOption query_quadoption(enum QuadOption opt, const char *prompt)
Ask the user a quad-question.
Definition: question.c:386
void mutt_socket_empty(struct Connection *conn)
Clear out any queued data.
Definition: socket.c:314
int mutt_socket_open(struct Connection *conn)
Simple wrapper.
Definition: socket.c:77
char host[128]
Server to login to.
Definition: connaccount.h:54
unsigned int use_tls
Definition: adata.h:47
+ 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 1874 of file nntp.c.

1875{
1876 struct NntpMboxData *mdata = NULL;
1877 struct NntpMboxData tmp_mdata = { 0 };
1878 char buf[1024] = { 0 };
1879
1880 if (m && (m->type == MUTT_NNTP))
1881 mdata = m->mdata;
1882 else
1883 {
1884 const char *const c_news_server = cs_subset_string(NeoMutt->sub, "news_server");
1885 CurrentNewsSrv = nntp_select_server(m, c_news_server, false);
1886 if (!CurrentNewsSrv)
1887 return -1;
1888
1889 mdata = &tmp_mdata;
1890 mdata->adata = CurrentNewsSrv;
1891 mdata->group = NULL;
1892 }
1893
1894 FILE *fp = mutt_file_fopen(msg, "r");
1895 if (!fp)
1896 {
1897 mutt_perror(msg);
1898 return -1;
1899 }
1900
1901 mutt_str_copy(buf, "POST\r\n", sizeof(buf));
1902 if (nntp_query(mdata, buf, sizeof(buf)) < 0)
1903 {
1904 mutt_file_fclose(&fp);
1905 return -1;
1906 }
1907 if (buf[0] != '3')
1908 {
1909 mutt_error(_("Can't post article: %s"), buf);
1910 mutt_file_fclose(&fp);
1911 return -1;
1912 }
1913
1914 buf[0] = '.';
1915 buf[1] = '\0';
1916 while (fgets(buf + 1, sizeof(buf) - 2, fp))
1917 {
1918 size_t len = strlen(buf);
1919 if (buf[len - 1] == '\n')
1920 {
1921 buf[len - 1] = '\r';
1922 buf[len] = '\n';
1923 len++;
1924 buf[len] = '\0';
1925 }
1926 if (mutt_socket_send_d(mdata->adata->conn, (buf[1] == '.') ? buf : buf + 1,
1927 MUTT_SOCK_LOG_FULL) < 0)
1928 {
1929 mutt_file_fclose(&fp);
1930 return nntp_connect_error(mdata->adata);
1931 }
1932 }
1933 mutt_file_fclose(&fp);
1934
1935 if (((buf[strlen(buf) - 1] != '\n') &&
1936 (mutt_socket_send_d(mdata->adata->conn, "\r\n", MUTT_SOCK_LOG_FULL) < 0)) ||
1937 (mutt_socket_send_d(mdata->adata->conn, ".\r\n", MUTT_SOCK_LOG_FULL) < 0) ||
1938 (mutt_socket_readln(buf, sizeof(buf), mdata->adata->conn) < 0))
1939 {
1940 return nntp_connect_error(mdata->adata);
1941 }
1942 if (buf[0] != '2')
1943 {
1944 mutt_error(_("Can't post article: %s"), buf);
1945 return -1;
1946 }
1947 return 0;
1948}
FILE * mutt_file_fopen(const char *path, const char *mode)
Call fopen() safely.
Definition: file.c:618
@ MUTT_NNTP
'NNTP' (Usenet) Mailbox type
Definition: mailbox.h:49
struct NntpAccountData * nntp_select_server(struct Mailbox *m, const char *server, bool leave_lock)
Open a connection to an NNTP server.
Definition: newsrc.c:1019
struct NntpAccountData * CurrentNewsSrv
Current NNTP 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 1957 of file nntp.c.

1958{
1959 struct NntpMboxData tmp_mdata = { 0 };
1960 char msg[256] = { 0 };
1961 char buf[1024] = { 0 };
1962 unsigned int i;
1963 int rc;
1964
1965 snprintf(msg, sizeof(msg), _("Loading list of groups from server %s..."),
1967 mutt_message(msg);
1968 if (nntp_date(adata, &adata->newgroups_time) < 0)
1969 return -1;
1970
1971 tmp_mdata.adata = adata;
1972 tmp_mdata.group = NULL;
1973 i = adata->groups_num;
1974 mutt_str_copy(buf, "LIST\r\n", sizeof(buf));
1975 rc = nntp_fetch_lines(&tmp_mdata, buf, sizeof(buf), msg, nntp_add_group, adata);
1976 if (rc)
1977 {
1978 if (rc > 0)
1979 {
1980 mutt_error("LIST: %s", buf);
1981 }
1982 return -1;
1983 }
1984
1985 if (mark_new)
1986 {
1987 for (; i < adata->groups_num; i++)
1988 {
1989 struct NntpMboxData *mdata = adata->groups_list[i];
1990 mdata->has_new_mail = true;
1991 }
1992 }
1993
1994 for (i = 0; i < adata->groups_num; i++)
1995 {
1996 struct NntpMboxData *mdata = adata->groups_list[i];
1997
1998 if (mdata && mdata->deleted && !mdata->newsrc_ent)
1999 {
2001 mutt_hash_delete(adata->groups_hash, mdata->group, NULL);
2002 adata->groups_list[i] = NULL;
2003 }
2004 }
2005
2006 const bool c_nntp_load_description = cs_subset_bool(NeoMutt->sub, "nntp_load_description");
2007 if (c_nntp_load_description)
2008 rc = get_description(&tmp_mdata, "*", _("Loading descriptions..."));
2009
2011 if (rc < 0)
2012 return -1;
2014 return 0;
2015}
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:813
int nntp_add_group(char *line, void *data)
Parse newsgroup.
Definition: newsrc.c:577
static int nntp_date(struct NntpAccountData *adata, time_t *now)
Get date and time from server.
Definition: nntp.c:1642
static int get_description(struct NntpMboxData *mdata, const char *wildmat, const char *msg)
Fetch newsgroups descriptions.
Definition: nntp.c:879
time_t newgroups_time
Definition: adata.h:57
unsigned int groups_num
Definition: adata.h:59
void ** groups_list
Definition: adata.h:61
+ 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 2025 of file nntp.c.

2026{
2027 struct NntpMboxData tmp_mdata = { 0 };
2028 time_t now = 0;
2029 char buf[1024] = { 0 };
2030 char *msg = _("Checking for new newsgroups...");
2031 unsigned int i;
2032 int rc, update_active = false;
2033
2034 if (!adata || !adata->newgroups_time)
2035 return -1;
2036
2037 /* check subscribed newsgroups for new articles */
2038 const bool c_show_new_news = cs_subset_bool(NeoMutt->sub, "show_new_news");
2039 if (c_show_new_news)
2040 {
2041 mutt_message(_("Checking for new messages..."));
2042 for (i = 0; i < adata->groups_num; i++)
2043 {
2044 struct NntpMboxData *mdata = adata->groups_list[i];
2045
2046 if (mdata && mdata->subscribed)
2047 {
2048 rc = nntp_group_poll(mdata, true);
2049 if (rc < 0)
2050 return -1;
2051 if (rc > 0)
2052 update_active = true;
2053 }
2054 }
2055 }
2056 else if (adata->newgroups_time)
2057 return 0;
2058
2059 /* get list of new groups */
2060 mutt_message(msg);
2061 if (nntp_date(adata, &now) < 0)
2062 return -1;
2063 tmp_mdata.adata = adata;
2064 if (m && m->mdata)
2065 tmp_mdata.group = ((struct NntpMboxData *) m->mdata)->group;
2066 else
2067 tmp_mdata.group = NULL;
2068 i = adata->groups_num;
2069 struct tm tm = mutt_date_gmtime(adata->newgroups_time);
2070 snprintf(buf, sizeof(buf), "NEWGROUPS %02d%02d%02d %02d%02d%02d GMT\r\n",
2071 tm.tm_year % 100, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
2072 rc = nntp_fetch_lines(&tmp_mdata, buf, sizeof(buf), msg, nntp_add_group, adata);
2073 if (rc)
2074 {
2075 if (rc > 0)
2076 {
2077 mutt_error("NEWGROUPS: %s", buf);
2078 }
2079 return -1;
2080 }
2081
2082 /* new groups found */
2083 rc = 0;
2084 if (adata->groups_num != i)
2085 {
2086 int groups_num = i;
2087
2088 adata->newgroups_time = now;
2089 for (; i < adata->groups_num; i++)
2090 {
2091 struct NntpMboxData *mdata = adata->groups_list[i];
2092 mdata->has_new_mail = true;
2093 }
2094
2095 /* loading descriptions */
2096 const bool c_nntp_load_description = cs_subset_bool(NeoMutt->sub, "nntp_load_description");
2097 if (c_nntp_load_description)
2098 {
2099 unsigned int count = 0;
2100 struct Progress *progress = progress_new(_("Loading descriptions..."), MUTT_PROGRESS_READ,
2101 adata->groups_num - i);
2102
2103 for (i = groups_num; i < adata->groups_num; i++)
2104 {
2105 struct NntpMboxData *mdata = adata->groups_list[i];
2106
2107 if (get_description(mdata, NULL, NULL) < 0)
2108 {
2109 progress_free(&progress);
2110 return -1;
2111 }
2112 progress_update(progress, ++count, -1);
2113 }
2114 progress_free(&progress);
2115 }
2116 update_active = true;
2117 rc = 1;
2118 }
2119 if (update_active)
2122 return rc;
2123}
struct tm mutt_date_gmtime(time_t t)
Converts calendar time to a broken-down time structure expressed in UTC timezone.
Definition: date.c:673
+ 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 2133 of file nntp.c.

2134{
2135 if (!m)
2136 return -1;
2137
2138 struct NntpMboxData *mdata = m->mdata;
2139 char buf[1024] = { 0 };
2140
2141 FILE *fp = mutt_file_mkstemp();
2142 if (!fp)
2143 {
2144 mutt_perror(_("Can't create temporary file"));
2145 return -1;
2146 }
2147
2148 snprintf(buf, sizeof(buf), "HEAD %s\r\n", msgid);
2149 int rc = nntp_fetch_lines(mdata, buf, sizeof(buf), NULL, fetch_tempfile, fp);
2150 if (rc)
2151 {
2152 mutt_file_fclose(&fp);
2153 if (rc < 0)
2154 return -1;
2155 if (mutt_str_startswith(buf, "430"))
2156 return 1;
2157 mutt_error("HEAD: %s", buf);
2158 return -1;
2159 }
2160
2161 /* parse header */
2162 if (m->msg_count == m->email_max)
2163 mx_alloc_memory(m);
2164 m->emails[m->msg_count] = email_new();
2165 struct Email *e = m->emails[m->msg_count];
2166 e->edata = nntp_edata_new();
2168 e->env = mutt_rfc822_read_header(fp, e, false, false);
2169 mutt_file_fclose(&fp);
2170
2171 /* get article number */
2172 if (e->env->xref)
2173 nntp_parse_xref(m, e);
2174 else
2175 {
2176 snprintf(buf, sizeof(buf), "STAT %s\r\n", msgid);
2177 if (nntp_query(mdata, buf, sizeof(buf)) < 0)
2178 {
2179 email_free(&e);
2180 return -1;
2181 }
2182 sscanf(buf + 4, ANUM, &nntp_edata_get(e)->article_num);
2183 }
2184
2185 /* reset flags */
2186 e->read = false;
2187 e->old = false;
2188 e->deleted = false;
2189 e->changed = true;
2190 e->received = e->date_sent;
2191 e->index = m->msg_count++;
2193 return 0;
2194}
+ 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 2203 of file nntp.c.

2204{
2205 if (!m)
2206 return -1;
2207
2208 struct NntpMboxData *mdata = m->mdata;
2209 struct ChildCtx cc;
2210 char buf[256] = { 0 };
2211 int rc;
2212 void *hc = NULL;
2213
2214 if (!mdata || !mdata->adata)
2215 return -1;
2216 if (mdata->first_message > mdata->last_loaded)
2217 return 0;
2218
2219 /* init context */
2220 cc.mailbox = m;
2221 cc.num = 0;
2222 cc.max = 10;
2223 cc.child = mutt_mem_malloc(sizeof(anum_t) * cc.max);
2224
2225 /* fetch numbers of child messages */
2226 snprintf(buf, sizeof(buf), "XPAT References " ANUM "-" ANUM " *%s*\r\n",
2227 mdata->first_message, mdata->last_loaded, msgid);
2228 rc = nntp_fetch_lines(mdata, buf, sizeof(buf), NULL, fetch_children, &cc);
2229 if (rc)
2230 {
2231 FREE(&cc.child);
2232 if (rc > 0)
2233 {
2234 if (!mutt_str_startswith(buf, "500"))
2235 mutt_error("XPAT: %s", buf);
2236 else
2237 {
2238 mutt_error(_("Unable to find child articles because server does not support XPAT command"));
2239 }
2240 }
2241 return -1;
2242 }
2243
2244 /* fetch all found messages */
2245 bool verbose = m->verbose;
2246 m->verbose = false;
2247#ifdef USE_HCACHE
2248 hc = nntp_hcache_open(mdata);
2249#endif
2250 int old_msg_count = m->msg_count;
2251 for (int i = 0; i < cc.num; i++)
2252 {
2253 rc = nntp_fetch_headers(m, hc, cc.child[i], cc.child[i], true);
2254 if (rc < 0)
2255 break;
2256 }
2257 if (m->msg_count > old_msg_count)
2259
2260#ifdef USE_HCACHE
2262#endif
2263 m->verbose = verbose;
2264 FREE(&cc.child);
2265 return (rc < 0) ? -1 : 0;
2266}
static int fetch_children(char *line, void *data)
Parse XPAT line.
Definition: nntp.c:1679
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

Variable Documentation

◆ CurrentNewsSrv

struct NntpAccountData* CurrentNewsSrv

Current NNTP news server.

Definition at line 77 of file nntp.c.

◆ OverviewFmt

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

Definition at line 79 of file nntp.c.