NeoMutt  2024-04-16-36-g75b6fb
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
nntp.c File Reference

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

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

Go to the source code of this file.

Data Structures

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

Functions

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

Variables

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

Detailed Description

Usenet network mailbox type; talk to an NNTP server.

Authors
  • Richard Russon
  • Pietro Cerutti
  • Ian Zimmerman
  • Dennis Schön

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

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

Definition in file nntp.c.

Function Documentation

◆ nntp_connect_error()

static int nntp_connect_error ( struct NntpAccountData adata)
static

Signal a failed connection.

Parameters
adataNNTP server
Return values
-1Always

Definition at line 127 of file nntp.c.

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

◆ nntp_capabilities()

static int nntp_capabilities ( struct NntpAccountData adata)
static

Get capabilities.

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

Definition at line 141 of file nntp.c.

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

◆ nntp_attempt_features()

static int nntp_attempt_features ( struct NntpAccountData adata)
static

Detect supported commands.

Parameters
adataNNTP server
Return values
0Success
-1Failure

Definition at line 260 of file nntp.c.

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

◆ nntp_auth()

static int nntp_auth ( struct NntpAccountData adata)
static

Get login, password and authenticate.

Parameters
adataNNTP server
Return values
0Success
-1Failure

Definition at line 445 of file nntp.c.

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

◆ nntp_query()

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

Send data from buffer and receive answer to same buffer.

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

Definition at line 723 of file nntp.c.

724{
725 struct NntpAccountData *adata = mdata->adata;
726 char buf[1024] = { 0 };
727
728 if (adata->status == NNTP_BYE)
729 return -1;
730
731 while (true)
732 {
733 if (adata->status == NNTP_OK)
734 {
735 int rc = 0;
736
737 if (*line)
738 {
739 rc = mutt_socket_send(adata->conn, line);
740 }
741 else if (mdata->group)
742 {
743 snprintf(buf, sizeof(buf), "GROUP %s\r\n", mdata->group);
744 rc = mutt_socket_send(adata->conn, buf);
745 }
746 if (rc >= 0)
747 rc = mutt_socket_readln(buf, sizeof(buf), adata->conn);
748 if (rc >= 0)
749 break;
750 }
751
752 /* reconnect */
753 while (true)
754 {
755 adata->status = NNTP_NONE;
756 if (nntp_open_connection(adata) == 0)
757 break;
758
759 snprintf(buf, sizeof(buf), _("Connection to %s lost. Reconnect?"),
760 adata->conn->account.host);
761 if (query_yesorno(buf, MUTT_YES) != MUTT_YES)
762 {
763 adata->status = NNTP_BYE;
764 return -1;
765 }
766 }
767
768 /* select newsgroup after reconnection */
769 if (mdata->group)
770 {
771 snprintf(buf, sizeof(buf), "GROUP %s\r\n", mdata->group);
772 if ((mutt_socket_send(adata->conn, buf) < 0) ||
773 (mutt_socket_readln(buf, sizeof(buf), adata->conn) < 0))
774 {
776 }
777 }
778 if (*line == '\0')
779 break;
780 }
781
782 mutt_str_copy(line, buf, linelen);
783 return 0;
784}
@ 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:1754
@ MUTT_YES
User answered 'Yes', or assume 'Yes'.
Definition: quad.h:39
enum QuadOption query_yesorno(const char *prompt, enum QuadOption def)
Ask the user a Yes/No question.
Definition: question.c:327
void * adata
Private data (for Mailbox backends)
Definition: account.h:42
NNTP-specific Account data -.
Definition: adata.h:36
char * group
Name of newsgroup.
Definition: mdata.h:35
struct NntpAccountData * adata
Definition: mdata.h:48
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ nntp_fetch_lines()

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

Read lines, calling a callback function for each.

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

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

Definition at line 802 of file nntp.c.

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

◆ fetch_description()

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

Parse newsgroup description.

Parameters
lineString to parse
dataNNTP Server
Return values
0Always

Definition at line 887 of file nntp.c.

888{
889 if (!line)
890 return 0;
891
892 struct NntpAccountData *adata = data;
893
894 char *desc = strpbrk(line, " \t");
895 if (desc)
896 {
897 *desc++ = '\0';
898 desc += strspn(desc, " \t");
899 }
900 else
901 {
902 desc = strchr(line, '\0');
903 }
904
906 if (mdata && !mutt_str_equal(desc, mdata->desc))
907 {
908 mutt_str_replace(&mdata->desc, desc);
909 mutt_debug(LL_DEBUG2, "group: %s, desc: %s\n", line, desc);
910 }
911 return 0;
912}
void * mutt_hash_find(const struct HashTable *table, const char *strkey)
Find the HashElem data in a Hash Table element using a key.
Definition: hash.c:362
@ LL_DEBUG2
Log at debug level 2.
Definition: logging2.h:44
void * mdata
Driver specific data.
Definition: mailbox.h:132
struct HashTable * groups_hash
Hash Table: "newsgroup" -> NntpMboxData.
Definition: adata.h:61
NNTP-specific Mailbox data -.
Definition: mdata.h:34
char * desc
Description of newsgroup.
Definition: mdata.h:36
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ get_description()

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

Fetch newsgroups descriptions.

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

Definition at line 924 of file nntp.c.

925{
926 char buf[256] = { 0 };
927 const char *cmd = NULL;
928
929 /* get newsgroup description, if possible */
930 struct NntpAccountData *adata = mdata->adata;
931 if (!wildmat)
932 wildmat = mdata->group;
933 if (adata->hasLIST_NEWSGROUPS)
934 cmd = "LIST NEWSGROUPS";
935 else if (adata->hasXGTITLE)
936 cmd = "XGTITLE";
937 else
938 return 0;
939
940 snprintf(buf, sizeof(buf), "%s %s\r\n", cmd, wildmat);
941 int rc = nntp_fetch_lines(mdata, buf, sizeof(buf), msg, fetch_description, adata);
942 if (rc > 0)
943 {
944 mutt_error("%s: %s", cmd, buf);
945 }
946 return rc;
947}
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:802
static int fetch_description(char *line, void *data)
Parse newsgroup description.
Definition: nntp.c:887
+ 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 956 of file nntp.c.

957{
958 struct NntpMboxData *mdata = m->mdata;
959
960 char *buf = mutt_str_dup(e->env->xref);
961 char *p = buf;
962 while (p)
963 {
964 anum_t anum;
965
966 /* skip to next word */
967 p += strspn(p, " \t");
968 char *grp = p;
969
970 /* skip to end of word */
971 p = strpbrk(p, " \t");
972 if (p)
973 *p++ = '\0';
974
975 /* find colon */
976 char *colon = strchr(grp, ':');
977 if (!colon)
978 continue;
979 *colon++ = '\0';
980 if (sscanf(colon, ANUM_FMT, &anum) != 1)
981 continue;
982
983 nntp_article_status(m, e, grp, anum);
984 if (!nntp_edata_get(e)->article_num && mutt_str_equal(mdata->group, grp))
985 nntp_edata_get(e)->article_num = anum;
986 }
987 FREE(&buf);
988}
void nntp_article_status(struct Mailbox *m, struct Email *e, char *group, anum_t anum)
Get status of articles from .newsrc.
Definition: newsrc.c:1256
struct NntpEmailData * nntp_edata_get(struct Email *e)
Get the private data for this Email.
Definition: edata.c:60
#define ANUM_FMT
Definition: lib.h:61
#define anum_t
Definition: lib.h:60
struct Envelope * env
Envelope information.
Definition: email.h:68
char * xref
List of cross-references.
Definition: envelope.h:79
anum_t article_num
NNTP article number.
Definition: edata.h:36
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ fetch_tempfile()

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

Write line to temporary file.

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

Definition at line 997 of file nntp.c.

998{
999 FILE *fp = data;
1000
1001 if (!line)
1002 rewind(fp);
1003 else if ((fputs(line, fp) == EOF) || (fputc('\n', fp) == EOF))
1004 return -1;
1005 return 0;
1006}
+ 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 1014 of file nntp.c.

1015{
1016 struct FetchCtx *fc = data;
1017 anum_t anum;
1018
1019 if (!line)
1020 return 0;
1021 if (sscanf(line, ANUM_FMT, &anum) != 1)
1022 return 0;
1023 if ((anum < fc->first) || (anum > fc->last))
1024 return 0;
1025 fc->messages[anum - fc->first] = 1;
1026 return 0;
1027}
Keep track when getting data from a server.
Definition: nntp.c:93
anum_t first
Definition: nntp.c:95
anum_t last
Definition: nntp.c:96
unsigned char * messages
Definition: nntp.c:98
+ Here is the caller graph for this function:

◆ parse_overview_line()

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

Parse overview line.

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

Definition at line 1036 of file nntp.c.

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

◆ nntp_fetch_headers()

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

Fetch headers.

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

Definition at line 1192 of file nntp.c.

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

◆ nntp_group_poll()

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

Check newsgroup for new articles.

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

Definition at line 1429 of file nntp.c.

1430{
1431 char buf[1024] = { 0 };
1432 anum_t count, first, last;
1433
1434 /* use GROUP command to poll newsgroup */
1435 if (nntp_query(mdata, buf, sizeof(buf)) < 0)
1436 return -1;
1437 if (sscanf(buf, "211 " ANUM_FMT " " ANUM_FMT " " ANUM_FMT, &count, &first, &last) != 3)
1438 return 0;
1439 if ((first == mdata->first_message) && (last == mdata->last_message))
1440 return 0;
1441
1442 /* articles have been renumbered */
1443 if (last < mdata->last_message)
1444 {
1445 mdata->last_cached = 0;
1446 if (mdata->newsrc_len)
1447 {
1448 mutt_mem_realloc(&mdata->newsrc_ent, sizeof(struct NewsrcEntry));
1449 mdata->newsrc_len = 1;
1450 mdata->newsrc_ent[0].first = 1;
1451 mdata->newsrc_ent[0].last = 0;
1452 }
1453 }
1454 mdata->first_message = first;
1455 mdata->last_message = last;
1456 if (!update_stat)
1457 {
1458 return 1;
1459 }
1460 else if (!last || (!mdata->newsrc_ent && !mdata->last_cached))
1461 {
1462 /* update counters */
1463 mdata->unread = count;
1464 }
1465 else
1466 {
1468 }
1469 return 1;
1470}
void nntp_group_unread_stat(struct NntpMboxData *mdata)
Count number of unread articles using .newsrc data.
Definition: newsrc.c:135
An entry in a .newsrc (subscribed newsgroups)
Definition: lib.h:76
anum_t last
Last article number in run.
Definition: lib.h:78
anum_t first
First article number in run.
Definition: lib.h:77
anum_t last_cached
Definition: mdata.h:40
anum_t last_message
Definition: mdata.h:38
struct NewsrcEntry * newsrc_ent
Definition: mdata.h:47
anum_t unread
Definition: mdata.h:41
unsigned int newsrc_len
Definition: mdata.h:46
anum_t first_message
Definition: mdata.h:37
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ check_mailbox()

static enum MxStatus check_mailbox ( struct Mailbox m)
static

Check current newsgroup for new articles.

Parameters
mMailbox
Return values
enumMxStatus

Leave newsrc locked

Definition at line 1479 of file nntp.c.

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

◆ nntp_date()

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

Get date and time from server.

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

Definition at line 1687 of file nntp.c.

1688{
1689 if (adata->hasDATE)
1690 {
1691 struct NntpMboxData mdata = { 0 };
1692 char buf[1024] = { 0 };
1693 struct tm tm = { 0 };
1694
1695 mdata.adata = adata;
1696 mdata.group = NULL;
1697 mutt_str_copy(buf, "DATE\r\n", sizeof(buf));
1698 if (nntp_query(&mdata, buf, sizeof(buf)) < 0)
1699 return -1;
1700
1701 if (sscanf(buf, "111 %4d%2d%2d%2d%2d%2d%*s", &tm.tm_year, &tm.tm_mon,
1702 &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec) == 6)
1703 {
1704 tm.tm_year -= 1900;
1705 tm.tm_mon--;
1706 *now = timegm(&tm);
1707 if (*now >= 0)
1708 {
1709 mutt_debug(LL_DEBUG1, "server time is %llu\n", (unsigned long long) *now);
1710 return 0;
1711 }
1712 }
1713 }
1714 *now = mutt_date_now();
1715 return 0;
1716}
time_t timegm(struct tm *tm)
Convert struct tm to time_t seconds since epoch.
Definition: timegm.c:70
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ fetch_children()

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

Parse XPAT line.

Parameters
lineString to parse
dataChildCtx
Return values
0Always

Definition at line 1724 of file nntp.c.

1725{
1726 struct ChildCtx *cc = data;
1727 anum_t anum;
1728
1729 if (!line || (sscanf(line, ANUM_FMT, &anum) != 1))
1730 return 0;
1731 for (unsigned int i = 0; i < cc->mailbox->msg_count; i++)
1732 {
1733 struct Email *e = cc->mailbox->emails[i];
1734 if (!e)
1735 break;
1736 if (nntp_edata_get(e)->article_num == anum)
1737 return 0;
1738 }
1739 if (cc->num >= cc->max)
1740 {
1741 cc->max *= 2;
1742 mutt_mem_realloc(&cc->child, sizeof(anum_t) * cc->max);
1743 }
1744 cc->child[cc->num++] = anum;
1745 return 0;
1746}
Keep track of the children of an article.
Definition: nntp.c:107
anum_t * child
Definition: nntp.c:111
struct Mailbox * mailbox
Definition: nntp.c:108
unsigned int max
Definition: nntp.c:110
unsigned int num
Definition: nntp.c:109
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ nntp_open_connection()

int nntp_open_connection ( struct NntpAccountData adata)

Connect to server, authenticate and get capabilities.

Parameters
adataNNTP server
Return values
0Success
-1Failure

Definition at line 1754 of file nntp.c.

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

◆ nntp_post()

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

Post article.

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

Definition at line 1924 of file nntp.c.

1925{
1926 struct NntpMboxData *mdata = NULL;
1927 struct NntpMboxData tmp_mdata = { 0 };
1928 char buf[1024] = { 0 };
1929
1930 if (m && (m->type == MUTT_NNTP))
1931 {
1932 mdata = m->mdata;
1933 }
1934 else
1935 {
1936 const char *const c_news_server = cs_subset_string(NeoMutt->sub, "news_server");
1937 CurrentNewsSrv = nntp_select_server(m, c_news_server, false);
1938 if (!CurrentNewsSrv)
1939 return -1;
1940
1941 mdata = &tmp_mdata;
1942 mdata->adata = CurrentNewsSrv;
1943 mdata->group = NULL;
1944 }
1945
1946 FILE *fp = mutt_file_fopen(msg, "r");
1947 if (!fp)
1948 {
1949 mutt_perror("%s", msg);
1950 return -1;
1951 }
1952
1953 mutt_str_copy(buf, "POST\r\n", sizeof(buf));
1954 if (nntp_query(mdata, buf, sizeof(buf)) < 0)
1955 {
1956 mutt_file_fclose(&fp);
1957 return -1;
1958 }
1959 if (buf[0] != '3')
1960 {
1961 mutt_error(_("Can't post article: %s"), buf);
1962 mutt_file_fclose(&fp);
1963 return -1;
1964 }
1965
1966 buf[0] = '.';
1967 buf[1] = '\0';
1968 while (fgets(buf + 1, sizeof(buf) - 2, fp))
1969 {
1970 size_t len = strlen(buf);
1971 if (buf[len - 1] == '\n')
1972 {
1973 buf[len - 1] = '\r';
1974 buf[len] = '\n';
1975 len++;
1976 buf[len] = '\0';
1977 }
1978 if (mutt_socket_send_d(mdata->adata->conn, (buf[1] == '.') ? buf : buf + 1,
1979 MUTT_SOCK_LOG_FULL) < 0)
1980 {
1981 mutt_file_fclose(&fp);
1982 return nntp_connect_error(mdata->adata);
1983 }
1984 }
1985 mutt_file_fclose(&fp);
1986
1987 if (((buf[strlen(buf) - 1] != '\n') &&
1988 (mutt_socket_send_d(mdata->adata->conn, "\r\n", MUTT_SOCK_LOG_FULL) < 0)) ||
1989 (mutt_socket_send_d(mdata->adata->conn, ".\r\n", MUTT_SOCK_LOG_FULL) < 0) ||
1990 (mutt_socket_readln(buf, sizeof(buf), mdata->adata->conn) < 0))
1991 {
1992 return nntp_connect_error(mdata->adata);
1993 }
1994 if (buf[0] != '2')
1995 {
1996 mutt_error(_("Can't post article: %s"), buf);
1997 return -1;
1998 }
1999 return 0;
2000}
@ MUTT_NNTP
'NNTP' (Usenet) Mailbox type
Definition: mailbox.h:49
#define mutt_file_fopen(PATH, MODE)
Definition: file.h:146
struct NntpAccountData * nntp_select_server(struct Mailbox *m, const char *server, bool leave_lock)
Open a connection to an NNTP server.
Definition: newsrc.c:1064
struct NntpAccountData * CurrentNewsSrv
Current news server.
Definition: nntp.c:77
enum MailboxType type
Mailbox type.
Definition: mailbox.h:102
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ nntp_active_fetch()

int nntp_active_fetch ( struct NntpAccountData adata,
bool  mark_new 
)

Fetch list of all newsgroups from server.

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

Definition at line 2009 of file nntp.c.

2010{
2011 struct NntpMboxData tmp_mdata = { 0 };
2012 char msg[256] = { 0 };
2013 char buf[1024] = { 0 };
2014 unsigned int i;
2015 int rc;
2016
2017 snprintf(msg, sizeof(msg), _("Loading list of groups from server %s..."),
2019 mutt_message("%s", msg);
2020 if (nntp_date(adata, &adata->newgroups_time) < 0)
2021 return -1;
2022
2023 tmp_mdata.adata = adata;
2024 tmp_mdata.group = NULL;
2025 i = adata->groups_num;
2026 mutt_str_copy(buf, "LIST\r\n", sizeof(buf));
2027 rc = nntp_fetch_lines(&tmp_mdata, buf, sizeof(buf), msg, nntp_add_group, adata);
2028 if (rc)
2029 {
2030 if (rc > 0)
2031 {
2032 mutt_error("LIST: %s", buf);
2033 }
2034 return -1;
2035 }
2036
2037 if (mark_new)
2038 {
2039 for (; i < adata->groups_num; i++)
2040 {
2041 struct NntpMboxData *mdata = adata->groups_list[i];
2042 mdata->has_new_mail = true;
2043 }
2044 }
2045
2046 for (i = 0; i < adata->groups_num; i++)
2047 {
2048 struct NntpMboxData *mdata = adata->groups_list[i];
2049
2050 if (mdata && mdata->deleted && !mdata->newsrc_ent)
2051 {
2053 mutt_hash_delete(adata->groups_hash, mdata->group, NULL);
2054 adata->groups_list[i] = NULL;
2055 }
2056 }
2057
2058 const bool c_nntp_load_description = cs_subset_bool(NeoMutt->sub, "nntp_load_description");
2059 if (c_nntp_load_description)
2060 rc = get_description(&tmp_mdata, "*", _("Loading descriptions..."));
2061
2063 if (rc < 0)
2064 return -1;
2066 return 0;
2067}
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:811
int nntp_add_group(char *line, void *data)
Parse newsgroup.
Definition: newsrc.c:575
static int nntp_date(struct NntpAccountData *adata, time_t *now)
Get date and time from server.
Definition: nntp.c:1687
static int get_description(struct NntpMboxData *mdata, const char *wildmat, const char *msg)
Fetch newsgroups descriptions.
Definition: nntp.c:924
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 2077 of file nntp.c.

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

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

2259{
2260 if (!m)
2261 return -1;
2262
2263 struct NntpMboxData *mdata = m->mdata;
2264 char buf[256] = { 0 };
2265 int rc;
2266 struct HeaderCache *hc = NULL;
2267
2268 if (!mdata || !mdata->adata)
2269 return -1;
2270 if (mdata->first_message > mdata->last_loaded)
2271 return 0;
2272
2273 /* init context */
2274 struct ChildCtx cc = { 0 };
2275 cc.mailbox = m;
2276 cc.num = 0;
2277 cc.max = 10;
2278 cc.child = mutt_mem_malloc(sizeof(anum_t) * cc.max);
2279
2280 /* fetch numbers of child messages */
2281 snprintf(buf, sizeof(buf), "XPAT References " ANUM_FMT "-" ANUM_FMT " *%s*\r\n",
2282 mdata->first_message, mdata->last_loaded, msgid);
2283 rc = nntp_fetch_lines(mdata, buf, sizeof(buf), NULL, fetch_children, &cc);
2284 if (rc)
2285 {
2286 FREE(&cc.child);
2287 if (rc > 0)
2288 {
2289 if (!mutt_str_startswith(buf, "500"))
2290 {
2291 mutt_error("XPAT: %s", buf);
2292 }
2293 else
2294 {
2295 mutt_error(_("Unable to find child articles because server does not support XPAT command"));
2296 }
2297 }
2298 return -1;
2299 }
2300
2301 /* fetch all found messages */
2302 bool verbose = m->verbose;
2303 m->verbose = false;
2304#ifdef USE_HCACHE
2305 hc = nntp_hcache_open(mdata);
2306#endif
2307 int old_msg_count = m->msg_count;
2308 for (int i = 0; i < cc.num; i++)
2309 {
2310 rc = nntp_fetch_headers(m, hc, cc.child[i], cc.child[i], true);
2311 if (rc < 0)
2312 break;
2313 }
2314 if (m->msg_count > old_msg_count)
2316
2317#ifdef USE_HCACHE
2318 hcache_close(&hc);
2319#endif
2320 m->verbose = verbose;
2321 FREE(&cc.child);
2322 return (rc < 0) ? -1 : 0;
2323}
static int fetch_children(char *line, void *data)
Parse XPAT line.
Definition: nntp.c:1724
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

Variable Documentation

◆ CurrentNewsSrv

struct NntpAccountData* CurrentNewsSrv = NULL

Current news server.

Current NNTP news server.

Definition at line 77 of file nntp.c.

◆ OverviewFmt

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

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

Definition at line 80 of file nntp.c.