NeoMutt  2023-05-17-33-gce4425
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, struct Email *e)
 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 = NULL
 Current news server. More...
 
static const char * OverviewFmt
 Fields to get from server, if it supports the LIST OVERVIEW.FMT feature. More...
 
const 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 128 of file nntp.c.

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

143{
144 struct Connection *conn = adata->conn;
145 bool mode_reader = false;
146 char buf[1024] = { 0 };
147 char authinfo[1024] = { 0 };
148
149 adata->hasCAPABILITIES = false;
150 adata->hasSTARTTLS = false;
151 adata->hasDATE = false;
152 adata->hasLIST_NEWSGROUPS = false;
153 adata->hasLISTGROUP = false;
154 adata->hasLISTGROUPrange = false;
155 adata->hasOVER = false;
156 FREE(&adata->authenticators);
157
158 if ((mutt_socket_send(conn, "CAPABILITIES\r\n") < 0) ||
159 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
160 {
161 return nntp_connect_error(adata);
162 }
163
164 /* no capabilities */
165 if (!mutt_str_startswith(buf, "101"))
166 return 1;
167 adata->hasCAPABILITIES = true;
168
169 /* parse capabilities */
170 do
171 {
172 size_t plen = 0;
173 if (mutt_socket_readln(buf, sizeof(buf), conn) < 0)
174 return nntp_connect_error(adata);
175 if (mutt_str_equal("STARTTLS", buf))
176 {
177 adata->hasSTARTTLS = true;
178 }
179 else if (mutt_str_equal("MODE-READER", buf))
180 {
181 mode_reader = true;
182 }
183 else if (mutt_str_equal("READER", buf))
184 {
185 adata->hasDATE = true;
186 adata->hasLISTGROUP = true;
187 adata->hasLISTGROUPrange = true;
188 }
189 else if ((plen = mutt_str_startswith(buf, "AUTHINFO ")))
190 {
191 mutt_str_cat(buf, sizeof(buf), " ");
192 mutt_str_copy(authinfo, buf + plen - 1, sizeof(authinfo));
193 }
194#ifdef USE_SASL_CYRUS
195 else if ((plen = mutt_str_startswith(buf, "SASL ")))
196 {
197 char *p = buf + plen;
198 while (*p == ' ')
199 p++;
200 adata->authenticators = mutt_str_dup(p);
201 }
202#endif
203 else if (mutt_str_equal("OVER", buf))
204 {
205 adata->hasOVER = true;
206 }
207 else if (mutt_str_startswith(buf, "LIST "))
208 {
209 char *p = strstr(buf, " NEWSGROUPS");
210 if (p)
211 {
212 p += 11;
213 if ((*p == '\0') || (*p == ' '))
214 adata->hasLIST_NEWSGROUPS = true;
215 }
216 }
217 } while (!mutt_str_equal(".", buf));
218 *buf = '\0';
219#ifdef USE_SASL_CYRUS
220 if (adata->authenticators && mutt_istr_find(authinfo, " SASL "))
221 mutt_str_copy(buf, adata->authenticators, sizeof(buf));
222#endif
223 if (mutt_istr_find(authinfo, " USER "))
224 {
225 if (*buf != '\0')
226 mutt_str_cat(buf, sizeof(buf), " ");
227 mutt_str_cat(buf, sizeof(buf), "USER");
228 }
229 mutt_str_replace(&adata->authenticators, buf);
230
231 /* current mode is reader */
232 if (adata->hasDATE)
233 return 0;
234
235 /* server is mode-switching, need to switch to reader mode */
236 if (mode_reader)
237 return 1;
238
239 mutt_socket_close(conn);
240 adata->status = NNTP_BYE;
241 mutt_error(_("Server doesn't support reader mode"));
242 return -1;
243}
#define FREE(x)
Definition: memory.h:43
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:251
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:798
const char * mutt_istr_find(const char *haystack, const char *needle)
Find first occurrence of string (ignoring case)
Definition: string.c:593
size_t mutt_str_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix.
Definition: string.c:228
size_t mutt_str_copy(char *dest, const char *src, size_t dsize)
Copy a string into a buffer (guaranteeing NUL-termination)
Definition: string.c:653
char * mutt_str_replace(char **p, const char *s)
Replace one string with another.
Definition: string.c:327
char * mutt_str_cat(char *buf, size_t buflen, const char *s)
Concatenate two strings.
Definition: string.c:266
@ NNTP_BYE
Disconnected from server.
Definition: private.h:46
static int nntp_connect_error(struct NntpAccountData *adata)
Signal a failed connection.
Definition: nntp.c:128
int mutt_socket_close(struct Connection *conn)
Close a socket.
Definition: socket.c:101
#define mutt_socket_readln(buf, buflen, conn)
Definition: socket.h:58
#define mutt_socket_send(conn, buf)
Definition: socket.h:59
struct Connection * conn
Connection to NNTP Server.
Definition: adata.h:62
char * authenticators
Definition: adata.h:52
bool hasCAPABILITIES
Server supports CAPABILITIES command.
Definition: adata.h:37
bool hasSTARTTLS
Server supports STARTTLS command.
Definition: adata.h:38
bool hasLISTGROUPrange
Server supports LISTGROUPrange command.
Definition: adata.h:43
bool hasLISTGROUP
Server supports LISTGROUP command.
Definition: adata.h:42
bool hasOVER
Server supports OVER command.
Definition: adata.h:44
bool hasDATE
Server supports DATE command.
Definition: adata.h:39
bool hasLIST_NEWSGROUPS
Server supports LIST_NEWSGROUPS command.
Definition: adata.h:40
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ nntp_attempt_features()

static int nntp_attempt_features ( struct NntpAccountData adata)
static

Detect supported commands.

Parameters
adataNNTP server
Return values
0Success
-1Failure

Definition at line 251 of file nntp.c.

252{
253 struct Connection *conn = adata->conn;
254 char buf[1024] = { 0 };
255
256 /* no CAPABILITIES, trying DATE, LISTGROUP, LIST NEWSGROUPS */
257 if (!adata->hasCAPABILITIES)
258 {
259 if ((mutt_socket_send(conn, "DATE\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->hasDATE = true;
266
267 if ((mutt_socket_send(conn, "LISTGROUP\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->hasLISTGROUP = true;
274
275 if ((mutt_socket_send(conn, "LIST NEWSGROUPS +\r\n") < 0) ||
276 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
277 {
278 return nntp_connect_error(adata);
279 }
280 if (!mutt_str_startswith(buf, "500"))
281 adata->hasLIST_NEWSGROUPS = true;
282 if (mutt_str_startswith(buf, "215"))
283 {
284 do
285 {
286 if (mutt_socket_readln(buf, sizeof(buf), conn) < 0)
287 return nntp_connect_error(adata);
288 } while (!mutt_str_equal(".", buf));
289 }
290 }
291
292 /* no LIST NEWSGROUPS, trying XGTITLE */
293 if (!adata->hasLIST_NEWSGROUPS)
294 {
295 if ((mutt_socket_send(conn, "XGTITLE\r\n") < 0) ||
296 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
297 {
298 return nntp_connect_error(adata);
299 }
300 if (!mutt_str_startswith(buf, "500"))
301 adata->hasXGTITLE = true;
302 }
303
304 /* no OVER, trying XOVER */
305 if (!adata->hasOVER)
306 {
307 if ((mutt_socket_send(conn, "XOVER\r\n") < 0) ||
308 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
309 {
310 return nntp_connect_error(adata);
311 }
312 if (!mutt_str_startswith(buf, "500"))
313 adata->hasXOVER = true;
314 }
315
316 /* trying LIST OVERVIEW.FMT */
317 if (adata->hasOVER || adata->hasXOVER)
318 {
319 if ((mutt_socket_send(conn, "LIST OVERVIEW.FMT\r\n") < 0) ||
320 (mutt_socket_readln(buf, sizeof(buf), conn) < 0))
321 {
322 return nntp_connect_error(adata);
323 }
324 if (!mutt_str_startswith(buf, "215"))
325 {
327 }
328 else
329 {
330 bool cont = false;
331 size_t buflen = 2048, off = 0, b = 0;
332
333 FREE(&adata->overview_fmt);
334 adata->overview_fmt = mutt_mem_malloc(buflen);
335
336 while (true)
337 {
338 if ((buflen - off) < 1024)
339 {
340 buflen *= 2;
341 mutt_mem_realloc(&adata->overview_fmt, buflen);
342 }
343
344 const int chunk = mutt_socket_readln_d(adata->overview_fmt + off,
345 buflen - off, conn, MUTT_SOCK_LOG_HDR);
346 if (chunk < 0)
347 {
348 FREE(&adata->overview_fmt);
349 return nntp_connect_error(adata);
350 }
351
352 if (!cont && mutt_str_equal(".", adata->overview_fmt + off))
353 break;
354
355 cont = (chunk >= (buflen - off));
356 off += strlen(adata->overview_fmt + off);
357 if (!cont)
358 {
359 if (adata->overview_fmt[b] == ':')
360 {
361 memmove(adata->overview_fmt + b, adata->overview_fmt + b + 1, off - b - 1);
362 adata->overview_fmt[off - 1] = ':';
363 }
364 char *colon = strchr(adata->overview_fmt + b, ':');
365 if (!colon)
366 adata->overview_fmt[off++] = ':';
367 else if (!mutt_str_equal(colon + 1, "full"))
368 off = colon + 1 - adata->overview_fmt;
369 if (strcasecmp(adata->overview_fmt + b, "Bytes:") == 0)
370 {
371 size_t len = strlen(adata->overview_fmt + b);
372 mutt_str_copy(adata->overview_fmt + b, "Content-Length:", len + 1);
373 off = b + len;
374 }
375 adata->overview_fmt[off++] = '\0';
376 b = off;
377 }
378 }
379 adata->overview_fmt[off++] = '\0';
380 mutt_mem_realloc(&adata->overview_fmt, off);
381 }
382 }
383 return 0;
384}
void * mutt_mem_malloc(size_t size)
Allocate memory on the heap.
Definition: memory.c:90
void mutt_mem_realloc(void *ptr, size_t size)
Resize a block of memory on the heap.
Definition: memory.c:114
static const char * OverviewFmt
Fields to get from server, if it supports the LIST OVERVIEW.FMT feature.
Definition: nntp.c:81
int mutt_socket_readln_d(char *buf, size_t buflen, struct Connection *conn, int dbg)
Read a line from a socket.
Definition: socket.c:252
#define MUTT_SOCK_LOG_HDR
Definition: socket.h:55
bool hasXOVER
Server supports XOVER command.
Definition: adata.h:45
char * overview_fmt
Definition: adata.h:53
bool hasXGTITLE
Server supports XGTITLE command.
Definition: adata.h:41
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ nntp_auth()

static int nntp_auth ( struct NntpAccountData adata)
static

Get login, password and authenticate.

Parameters
adataNNTP server
Return values
0Success
-1Failure

Definition at line 436 of file nntp.c.

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

705{
706 struct NntpAccountData *adata = mdata->adata;
707 char buf[1024] = { 0 };
708
709 if (adata->status == NNTP_BYE)
710 return -1;
711
712 while (true)
713 {
714 if (adata->status == NNTP_OK)
715 {
716 int rc = 0;
717
718 if (*line)
719 {
720 rc = mutt_socket_send(adata->conn, line);
721 }
722 else if (mdata->group)
723 {
724 snprintf(buf, sizeof(buf), "GROUP %s\r\n", mdata->group);
725 rc = mutt_socket_send(adata->conn, buf);
726 }
727 if (rc >= 0)
728 rc = mutt_socket_readln(buf, sizeof(buf), adata->conn);
729 if (rc >= 0)
730 break;
731 }
732
733 /* reconnect */
734 while (true)
735 {
736 adata->status = NNTP_NONE;
737 if (nntp_open_connection(adata) == 0)
738 break;
739
740 snprintf(buf, sizeof(buf), _("Connection to %s lost. Reconnect?"),
741 adata->conn->account.host);
742 if (mutt_yesorno(buf, MUTT_YES) != MUTT_YES)
743 {
744 adata->status = NNTP_BYE;
745 return -1;
746 }
747 }
748
749 /* select newsgroup after reconnection */
750 if (mdata->group)
751 {
752 snprintf(buf, sizeof(buf), "GROUP %s\r\n", mdata->group);
753 if ((mutt_socket_send(adata->conn, buf) < 0) ||
754 (mutt_socket_readln(buf, sizeof(buf), adata->conn) < 0))
755 {
757 }
758 }
759 if (*line == '\0')
760 break;
761 }
762
763 mutt_str_copy(line, buf, linelen);
764 return 0;
765}
@ 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:1741
@ 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:36
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 783 of file nntp.c.

785{
786 bool done = false;
787 int rc;
788
789 while (!done)
790 {
791 char buf[1024] = { 0 };
792 char *line = NULL;
793 unsigned int lines = 0;
794 size_t off = 0;
795 struct Progress *progress = NULL;
796
797 mutt_str_copy(buf, query, sizeof(buf));
798 if (nntp_query(mdata, buf, sizeof(buf)) < 0)
799 return -1;
800 if (buf[0] != '2')
801 {
802 mutt_str_copy(query, buf, qlen);
803 return 1;
804 }
805
806 line = mutt_mem_malloc(sizeof(buf));
807 rc = 0;
808
809 if (msg)
810 progress = progress_new(msg, MUTT_PROGRESS_READ, 0);
811
812 while (true)
813 {
814 char *p = NULL;
815 int chunk = mutt_socket_readln_d(buf, sizeof(buf), mdata->adata->conn, MUTT_SOCK_LOG_FULL);
816 if (chunk < 0)
817 {
818 mdata->adata->status = NNTP_NONE;
819 break;
820 }
821
822 p = buf;
823 if (!off && (buf[0] == '.'))
824 {
825 if (buf[1] == '\0')
826 {
827 done = true;
828 break;
829 }
830 if (buf[1] == '.')
831 p++;
832 }
833
834 mutt_str_copy(line + off, p, sizeof(buf));
835
836 if (chunk >= sizeof(buf))
837 {
838 off += strlen(p);
839 }
840 else
841 {
842 if (msg)
843 progress_update(progress, ++lines, -1);
844
845 if ((rc == 0) && (func(line, data) < 0))
846 rc = -2;
847 off = 0;
848 }
849
850 mutt_mem_realloc(&line, off + sizeof(buf));
851 }
852 FREE(&line);
853 func(NULL, data);
854 progress_free(&progress);
855 }
856
857 return rc;
858}
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:704
@ 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:89
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:121
+ 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 866 of file nntp.c.

867{
868 if (!line)
869 return 0;
870
871 struct NntpAccountData *adata = data;
872
873 char *desc = strpbrk(line, " \t");
874 if (desc)
875 {
876 *desc++ = '\0';
877 desc += strspn(desc, " \t");
878 }
879 else
880 {
881 desc = strchr(line, '\0');
882 }
883
885 if (mdata && !mutt_str_equal(desc, mdata->desc))
886 {
887 mutt_str_replace(&mdata->desc, desc);
888 mutt_debug(LL_DEBUG2, "group: %s, desc: %s\n", line, desc);
889 }
890 return 0;
891}
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:41
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: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 903 of file nntp.c.

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

936{
937 struct NntpMboxData *mdata = m->mdata;
938
939 char *buf = mutt_str_dup(e->env->xref);
940 char *p = buf;
941 while (p)
942 {
943 anum_t anum;
944
945 /* skip to next word */
946 p += strspn(p, " \t");
947 char *grp = p;
948
949 /* skip to end of word */
950 p = strpbrk(p, " \t");
951 if (p)
952 *p++ = '\0';
953
954 /* find colon */
955 char *colon = strchr(grp, ':');
956 if (!colon)
957 continue;
958 *colon++ = '\0';
959 if (sscanf(colon, ANUM, &anum) != 1)
960 continue;
961
962 nntp_article_status(m, e, grp, anum);
963 if (!nntp_edata_get(e)->article_num && mutt_str_equal(mdata->group, grp))
964 nntp_edata_get(e)->article_num = anum;
965 }
966 FREE(&buf);
967}
void nntp_article_status(struct Mailbox *m, struct Email *e, char *group, anum_t anum)
Get status of articles from .newsrc.
Definition: newsrc.c:1236
struct NntpEmailData * nntp_edata_get(struct Email *e)
Get the private data for this Email.
Definition: edata.c:57
#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 976 of file nntp.c.

977{
978 FILE *fp = data;
979
980 if (!line)
981 rewind(fp);
982 else if ((fputs(line, fp) == EOF) || (fputc('\n', fp) == EOF))
983 return -1;
984 return 0;
985}
+ 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 993 of file nntp.c.

994{
995 struct FetchCtx *fc = data;
996 anum_t anum;
997
998 if (!line)
999 return 0;
1000 if (sscanf(line, ANUM, &anum) != 1)
1001 return 0;
1002 if ((anum < fc->first) || (anum > fc->last))
1003 return 0;
1004 fc->messages[anum - fc->first] = 1;
1005 return 0;
1006}
Keep track when getting data from a server.
Definition: nntp.c:94
anum_t first
Definition: nntp.c:96
anum_t last
Definition: nntp.c:97
unsigned char * messages
Definition: nntp.c:99
+ 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 1015 of file nntp.c.

1016{
1017 if (!line || !data)
1018 return 0;
1019
1020 struct FetchCtx *fc = data;
1021 struct Mailbox *m = fc->mailbox;
1022 if (!m)
1023 return -1;
1024
1025 struct NntpMboxData *mdata = m->mdata;
1026 struct Email *e = NULL;
1027 char *header = NULL, *field = NULL;
1028 bool save = true;
1029 anum_t anum;
1030
1031 /* parse article number */
1032 field = strchr(line, '\t');
1033 if (field)
1034 *field++ = '\0';
1035 if (sscanf(line, ANUM, &anum) != 1)
1036 return 0;
1037 mutt_debug(LL_DEBUG2, "" ANUM "\n", anum);
1038
1039 /* out of bounds */
1040 if ((anum < fc->first) || (anum > fc->last))
1041 return 0;
1042
1043 /* not in LISTGROUP */
1044 if (!fc->messages[anum - fc->first])
1045 {
1046 /* progress */
1047 if (m->verbose)
1048 progress_update(fc->progress, anum - fc->first + 1, -1);
1049 return 0;
1050 }
1051
1052 /* convert overview line to header */
1053 FILE *fp = mutt_file_mkstemp();
1054 if (!fp)
1055 return -1;
1056
1057 header = mdata->adata->overview_fmt;
1058 while (field)
1059 {
1060 char *b = field;
1061
1062 if (*header)
1063 {
1064 if (!strstr(header, ":full") && (fputs(header, fp) == EOF))
1065 {
1066 mutt_file_fclose(&fp);
1067 return -1;
1068 }
1069 header = strchr(header, '\0') + 1;
1070 }
1071
1072 field = strchr(field, '\t');
1073 if (field)
1074 *field++ = '\0';
1075 if ((fputs(b, fp) == EOF) || (fputc('\n', fp) == EOF))
1076 {
1077 mutt_file_fclose(&fp);
1078 return -1;
1079 }
1080 }
1081 rewind(fp);
1082
1083 /* allocate memory for headers */
1084 if (m->msg_count >= m->email_max)
1085 mx_alloc_memory(m);
1086
1087 /* parse header */
1088 m->emails[m->msg_count] = email_new();
1089 e = m->emails[m->msg_count];
1090 e->env = mutt_rfc822_read_header(fp, e, false, false);
1091 e->env->newsgroups = mutt_str_dup(mdata->group);
1092 e->received = e->date_sent;
1093 mutt_file_fclose(&fp);
1094
1095#ifdef USE_HCACHE
1096 if (fc->hc)
1097 {
1098 char buf[16] = { 0 };
1099
1100 /* try to replace with header from cache */
1101 snprintf(buf, sizeof(buf), ANUM, anum);
1102 struct HCacheEntry hce = mutt_hcache_fetch(fc->hc, buf, strlen(buf), 0);
1103 if (hce.email)
1104 {
1105 mutt_debug(LL_DEBUG2, "mutt_hcache_fetch %s\n", buf);
1106 email_free(&e);
1107 e = hce.email;
1108 m->emails[m->msg_count] = e;
1109 e->edata = NULL;
1110 e->read = false;
1111 e->old = false;
1112
1113 /* skip header marked as deleted in cache */
1114 if (e->deleted && !fc->restore)
1115 {
1116 if (mdata->bcache)
1117 {
1118 mutt_debug(LL_DEBUG2, "mutt_bcache_del %s\n", buf);
1119 mutt_bcache_del(mdata->bcache, buf);
1120 }
1121 save = false;
1122 }
1123 }
1124 else
1125 {
1126 /* not cached yet, store header */
1127 mutt_debug(LL_DEBUG2, "mutt_hcache_store %s\n", buf);
1128 mutt_hcache_store(fc->hc, buf, strlen(buf), e, 0);
1129 }
1130 }
1131#endif
1132
1133 if (save)
1134 {
1135 e->index = m->msg_count++;
1136 e->read = false;
1137 e->old = false;
1138 e->deleted = false;
1139 e->edata = nntp_edata_new();
1141 nntp_edata_get(e)->article_num = anum;
1142 if (fc->restore)
1143 {
1144 e->changed = true;
1145 }
1146 else
1147 {
1148 nntp_article_status(m, e, NULL, anum);
1149 if (!e->read)
1150 nntp_parse_xref(m, e);
1151 }
1152 if (anum > mdata->last_loaded)
1153 mdata->last_loaded = anum;
1154 }
1155 else
1156 {
1157 email_free(&e);
1158 }
1159
1160 /* progress */
1161 if (m->verbose)
1162 progress_update(fc->progress, anum - fc->first + 1, -1);
1163 return 0;
1164}
int mutt_bcache_del(struct BodyCache *bcache, const char *id)
Delete a file from the Body Cache.
Definition: bcache.c:263
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:150
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:616
struct HCacheEntry mutt_hcache_fetch(struct HeaderCache *hc, const char *key, size_t keylen, uint32_t uidvalidity)
Multiplexor for StoreOps::fetch.
Definition: hcache.c:513
void mx_alloc_memory(struct Mailbox *m)
Create storage for the emails.
Definition: mx.c:1236
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:47
static void nntp_parse_xref(struct Mailbox *m, struct Email *e)
Parse cross-reference.
Definition: nntp.c:935
struct Envelope * mutt_rfc822_read_header(FILE *fp, struct Email *e, bool user_hdrs, bool weed)
Parses an RFC822 header.
Definition: parse.c:1169
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:86
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:109
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:101
struct Progress * progress
Definition: nntp.c:100
struct Mailbox * mailbox
Definition: nntp.c:95
bool restore
Definition: nntp.c:98
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
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
#define mutt_file_mkstemp()
Definition: tmp.h:40
+ 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 1176 of file nntp.c.

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

1416{
1417 char buf[1024] = { 0 };
1418 anum_t count, first, last;
1419
1420 /* use GROUP command to poll newsgroup */
1421 if (nntp_query(mdata, buf, sizeof(buf)) < 0)
1422 return -1;
1423 if (sscanf(buf, "211 " ANUM " " ANUM " " ANUM, &count, &first, &last) != 3)
1424 return 0;
1425 if ((first == mdata->first_message) && (last == mdata->last_message))
1426 return 0;
1427
1428 /* articles have been renumbered */
1429 if (last < mdata->last_message)
1430 {
1431 mdata->last_cached = 0;
1432 if (mdata->newsrc_len)
1433 {
1434 mutt_mem_realloc(&mdata->newsrc_ent, sizeof(struct NewsrcEntry));
1435 mdata->newsrc_len = 1;
1436 mdata->newsrc_ent[0].first = 1;
1437 mdata->newsrc_ent[0].last = 0;
1438 }
1439 }
1440 mdata->first_message = first;
1441 mdata->last_message = last;
1442 if (!update_stat)
1443 {
1444 return 1;
1445 }
1446 else if (!last || (!mdata->newsrc_ent && !mdata->last_cached))
1447 {
1448 /* update counters */
1449 mdata->unread = count;
1450 }
1451 else
1452 {
1454 }
1455 return 1;
1456}
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 1465 of file nntp.c.

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

1675{
1676 if (adata->hasDATE)
1677 {
1678 struct NntpMboxData mdata = { 0 };
1679 char buf[1024] = { 0 };
1680 struct tm tm = { 0 };
1681
1682 mdata.adata = adata;
1683 mdata.group = NULL;
1684 mutt_str_copy(buf, "DATE\r\n", sizeof(buf));
1685 if (nntp_query(&mdata, buf, sizeof(buf)) < 0)
1686 return -1;
1687
1688 if (sscanf(buf, "111 %4d%2d%2d%2d%2d%2d%*s", &tm.tm_year, &tm.tm_mon,
1689 &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) == 6)
1690 {
1691 tm.tm_year -= 1900;
1692 tm.tm_mon--;
1693 *now = timegm(&tm);
1694 if (*now >= 0)
1695 {
1696 mutt_debug(LL_DEBUG1, "server time is %lu\n", *now);
1697 return 0;
1698 }
1699 }
1700 }
1701 *now = mutt_date_now();
1702 return 0;
1703}
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 1711 of file nntp.c.

1712{
1713 struct ChildCtx *cc = data;
1714 anum_t anum;
1715
1716 if (!line || (sscanf(line, ANUM, &anum) != 1))
1717 return 0;
1718 for (unsigned int i = 0; i < cc->mailbox->msg_count; i++)
1719 {
1720 struct Email *e = cc->mailbox->emails[i];
1721 if (!e)
1722 break;
1723 if (nntp_edata_get(e)->article_num == anum)
1724 return 0;
1725 }
1726 if (cc->num >= cc->max)
1727 {
1728 cc->max *= 2;
1729 mutt_mem_realloc(&cc->child, sizeof(anum_t) * cc->max);
1730 }
1731 cc->child[cc->num++] = anum;
1732 return 0;
1733}
Keep track of the children of an article.
Definition: nntp.c:108
anum_t * child
Definition: nntp.c:112
struct Mailbox * mailbox
Definition: nntp.c:109
unsigned int max
Definition: nntp.c:111
unsigned int num
Definition: nntp.c:110
+ 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 1741 of file nntp.c.

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

◆ nntp_post()

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

Post article.

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

Definition at line 1912 of file nntp.c.

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

1998{
1999 struct NntpMboxData tmp_mdata = { 0 };
2000 char msg[256] = { 0 };
2001 char buf[1024] = { 0 };
2002 unsigned int i;
2003 int rc;
2004
2005 snprintf(msg, sizeof(msg), _("Loading list of groups from server %s..."),
2007 mutt_message(msg);
2008 if (nntp_date(adata, &adata->newgroups_time) < 0)
2009 return -1;
2010
2011 tmp_mdata.adata = adata;
2012 tmp_mdata.group = NULL;
2013 i = adata->groups_num;
2014 mutt_str_copy(buf, "LIST\r\n", sizeof(buf));
2015 rc = nntp_fetch_lines(&tmp_mdata, buf, sizeof(buf), msg, nntp_add_group, adata);
2016 if (rc)
2017 {
2018 if (rc > 0)
2019 {
2020 mutt_error("LIST: %s", buf);
2021 }
2022 return -1;
2023 }
2024
2025 if (mark_new)
2026 {
2027 for (; i < adata->groups_num; i++)
2028 {
2029 struct NntpMboxData *mdata = adata->groups_list[i];
2030 mdata->has_new_mail = true;
2031 }
2032 }
2033
2034 for (i = 0; i < adata->groups_num; i++)
2035 {
2036 struct NntpMboxData *mdata = adata->groups_list[i];
2037
2038 if (mdata && mdata->deleted && !mdata->newsrc_ent)
2039 {
2041 mutt_hash_delete(adata->groups_hash, mdata->group, NULL);
2042 adata->groups_list[i] = NULL;
2043 }
2044 }
2045
2046 const bool c_nntp_load_description = cs_subset_bool(NeoMutt->sub, "nntp_load_description");
2047 if (c_nntp_load_description)
2048 rc = get_description(&tmp_mdata, "*", _("Loading descriptions..."));
2049
2051 if (rc < 0)
2052 return -1;
2054 return 0;
2055}
void mutt_hash_delete(struct HashTable *table, const char *strkey, const void *data)
Remove an element from a Hash Table.
Definition: hash.c:427
void nntp_delete_group_cache(struct NntpMboxData *mdata)
Remove hcache and bcache of newsgroup.
Definition: newsrc.c:814
int nntp_add_group(char *line, void *data)
Parse newsgroup.
Definition: newsrc.c:580
static int nntp_date(struct NntpAccountData *adata, time_t *now)
Get date and time from server.
Definition: nntp.c:1674
static int get_description(struct NntpMboxData *mdata, const char *wildmat, const char *msg)
Fetch newsgroups descriptions.
Definition: nntp.c:903
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 2065 of file nntp.c.

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

◆ nntp_check_msgid()

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

Fetch article by Message-ID.

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

Definition at line 2175 of file nntp.c.

2176{
2177 if (!m)
2178 return -1;
2179
2180 struct NntpMboxData *mdata = m->mdata;
2181 char buf[1024] = { 0 };
2182
2183 FILE *fp = mutt_file_mkstemp();
2184 if (!fp)
2185 {
2186 mutt_perror(_("Can't create temporary file"));
2187 return -1;
2188 }
2189
2190 snprintf(buf, sizeof(buf), "HEAD %s\r\n", msgid);
2191 int rc = nntp_fetch_lines(mdata, buf, sizeof(buf), NULL, fetch_tempfile, fp);
2192 if (rc)
2193 {
2194 mutt_file_fclose(&fp);
2195 if (rc < 0)
2196 return -1;
2197 if (mutt_str_startswith(buf, "430"))
2198 return 1;
2199 mutt_error("HEAD: %s", buf);
2200 return -1;
2201 }
2202
2203 /* parse header */
2204 if (m->msg_count == m->email_max)
2205 mx_alloc_memory(m);
2206 m->emails[m->msg_count] = email_new();
2207 struct Email *e = m->emails[m->msg_count];
2208 e->edata = nntp_edata_new();
2210 e->env = mutt_rfc822_read_header(fp, e, false, false);
2211 mutt_file_fclose(&fp);
2212
2213 /* get article number */
2214 if (e->env->xref)
2215 {
2216 nntp_parse_xref(m, e);
2217 }
2218 else
2219 {
2220 snprintf(buf, sizeof(buf), "STAT %s\r\n", msgid);
2221 if (nntp_query(mdata, buf, sizeof(buf)) < 0)
2222 {
2223 email_free(&e);
2224 return -1;
2225 }
2226 sscanf(buf + 4, ANUM, &nntp_edata_get(e)->article_num);
2227 }
2228
2229 /* reset flags */
2230 e->read = false;
2231 e->old = false;
2232 e->deleted = false;
2233 e->changed = true;
2234 e->received = e->date_sent;
2235 e->index = m->msg_count++;
2237 return 0;
2238}
+ 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 2247 of file nntp.c.

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