NeoMutt  2024-12-12-19-ge4b57e
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
lib.c
Go to the documentation of this file.
1
31#include "config.h"
32#include <arpa/inet.h>
33#include <errno.h>
34#include <netdb.h>
35#include <stdbool.h>
36#include <stdio.h>
37#include <stdlib.h>
38#include <string.h>
39#include "private.h"
40#include "mutt/lib.h"
41#include "config/lib.h"
42#include "email/lib.h"
43#include "core/lib.h"
44#include "conn/lib.h"
45#include "question/lib.h"
46#include "progress/lib.h"
47#include "adata.h"
48#include "edata.h"
49#include "mutt_logging.h"
50
51struct Progress;
52
56const char *pop_get_field(enum ConnAccountField field, void *gf_data)
57{
58 switch (field)
59 {
60 case MUTT_CA_LOGIN:
61 case MUTT_CA_USER:
62 return cs_subset_string(NeoMutt->sub, "pop_user");
63 case MUTT_CA_PASS:
64 return cs_subset_string(NeoMutt->sub, "pop_pass");
66 return cs_subset_string(NeoMutt->sub, "pop_oauth_refresh_command");
67 case MUTT_CA_HOST:
68 default:
69 return NULL;
70 }
71}
72
82int pop_parse_path(const char *path, struct ConnAccount *cac)
83{
84 /* Defaults */
85 cac->flags = 0;
87 cac->port = 0;
88 cac->service = "pop";
90
91 struct Url *url = url_parse(path);
92
93 if (!url || ((url->scheme != U_POP) && (url->scheme != U_POPS)) ||
94 !url->host || (account_from_url(cac, url) < 0))
95 {
96 url_free(&url);
97 mutt_error(_("Invalid POP URL: %s"), path);
98 return -1;
99 }
100
101 if (url->scheme == U_POPS)
102 cac->flags |= MUTT_ACCT_SSL;
103
104 struct servent *service = getservbyname((url->scheme == U_POP) ? "pop3" : "pop3s", "tcp");
105 if (cac->port == 0)
106 {
107 if (service)
108 cac->port = ntohs(service->s_port);
109 else
110 cac->port = (url->scheme == U_POP) ? POP_PORT : POP_SSL_PORT;
111 }
112
113 url_free(&url);
114 return 0;
115}
116
122static void pop_error(struct PopAccountData *adata, char *msg)
123{
124 char *t = strchr(adata->err_msg, '\0');
125 char *c = msg;
126
127 size_t plen = mutt_str_startswith(msg, "-ERR ");
128 if (plen != 0)
129 {
130 char *c2 = mutt_str_skip_email_wsp(msg + plen);
131
132 if (*c2)
133 c = c2;
134 }
135
136 mutt_str_copy(t, c, sizeof(adata->err_msg) - strlen(adata->err_msg));
138}
139
146static int fetch_capa(const char *line, void *data)
147{
148 struct PopAccountData *adata = data;
149
150 if (mutt_istr_startswith(line, "SASL"))
151 {
152 const char *c = mutt_str_skip_email_wsp(line + 4);
153 buf_strcpy(&adata->auth_list, c);
154 }
155 else if (mutt_istr_startswith(line, "STLS"))
156 {
157 adata->cmd_stls = true;
158 }
159 else if (mutt_istr_startswith(line, "USER"))
160 {
161 adata->cmd_user = 1;
162 }
163 else if (mutt_istr_startswith(line, "UIDL"))
164 {
165 adata->cmd_uidl = 1;
166 }
167 else if (mutt_istr_startswith(line, "TOP"))
168 {
169 adata->cmd_top = 1;
170 }
171
172 return 0;
173}
174
181static int fetch_auth(const char *line, void *data)
182{
183 struct PopAccountData *adata = data;
184
185 if (!buf_is_empty(&adata->auth_list))
186 {
187 buf_addstr(&adata->auth_list, " ");
188 }
189 buf_addstr(&adata->auth_list, line);
190
191 return 0;
192}
193
202static int pop_capabilities(struct PopAccountData *adata, int mode)
203{
204 char buf[1024] = { 0 };
205
206 /* don't check capabilities on reconnect */
207 if (adata->capabilities)
208 return 0;
209
210 /* init capabilities */
211 if (mode == 0)
212 {
213 adata->cmd_capa = false;
214 adata->cmd_stls = false;
215 adata->cmd_user = 0;
216 adata->cmd_uidl = 0;
217 adata->cmd_top = 0;
218 adata->resp_codes = false;
219 adata->expire = true;
220 adata->login_delay = 0;
221 buf_init(&adata->auth_list);
222 }
223
224 /* Execute CAPA command */
225 if ((mode == 0) || adata->cmd_capa)
226 {
227 mutt_str_copy(buf, "CAPA\r\n", sizeof(buf));
228 switch (pop_fetch_data(adata, buf, NULL, fetch_capa, adata))
229 {
230 case 0:
231 {
232 adata->cmd_capa = true;
233 break;
234 }
235 case -1:
236 return -1;
237 }
238 }
239
240 /* CAPA not supported, use defaults */
241 if ((mode == 0) && !adata->cmd_capa)
242 {
243 adata->cmd_user = 2;
244 adata->cmd_uidl = 2;
245 adata->cmd_top = 2;
246
247 mutt_str_copy(buf, "AUTH\r\n", sizeof(buf));
248 if (pop_fetch_data(adata, buf, NULL, fetch_auth, adata) == -1)
249 return -1;
250 }
251
252 /* Check capabilities */
253 if (mode == 2)
254 {
255 char *msg = NULL;
256
257 if (!adata->expire)
258 msg = _("Unable to leave messages on server");
259 if (adata->cmd_top == 0)
260 msg = _("Command TOP is not supported by server");
261 if (adata->cmd_uidl == 0)
262 msg = _("Command UIDL is not supported by server");
263 if (msg && adata->cmd_capa)
264 {
265 mutt_error("%s", msg);
266 return -2;
267 }
268 adata->capabilities = true;
269 }
270
271 return 0;
272}
273
282{
283 char buf[1024] = { 0 };
284
285 adata->status = POP_NONE;
286 if ((mutt_socket_open(adata->conn) < 0) ||
287 (mutt_socket_readln(buf, sizeof(buf), adata->conn) < 0))
288 {
289 mutt_error(_("Error connecting to server: %s"), adata->conn->account.host);
290 return -1;
291 }
292
293 adata->status = POP_CONNECTED;
294
295 if (!mutt_str_startswith(buf, "+OK"))
296 {
297 *adata->err_msg = '\0';
298 pop_error(adata, buf);
299 mutt_error("%s", adata->err_msg);
300 return -2;
301 }
302
304
305 return 0;
306}
307
317{
318 char buf[1024] = { 0 };
319
320 int rc = pop_connect(adata);
321 if (rc < 0)
322 return rc;
323
324 rc = pop_capabilities(adata, 0);
325 if (rc == -1)
326 goto err_conn;
327 if (rc == -2)
328 return -2;
329
330#ifdef USE_SSL
331 /* Attempt STLS if available and desired. */
332 const bool c_ssl_force_tls = cs_subset_bool(NeoMutt->sub, "ssl_force_tls");
333 if ((adata->conn->ssf == 0) && (adata->cmd_stls || c_ssl_force_tls))
334 {
335 if (c_ssl_force_tls)
336 adata->use_stls = 2;
337 if (adata->use_stls == 0)
338 {
339 enum QuadOption ans = query_quadoption(_("Secure connection with TLS?"),
340 NeoMutt->sub, "ssl_starttls");
341 if (ans == MUTT_ABORT)
342 return -2;
343 adata->use_stls = 1;
344 if (ans == MUTT_YES)
345 adata->use_stls = 2;
346 }
347 if (adata->use_stls == 2)
348 {
349 mutt_str_copy(buf, "STLS\r\n", sizeof(buf));
350 rc = pop_query(adata, buf, sizeof(buf));
351 // Clear any data after the STLS acknowledgement
353 if (rc == -1)
354 goto err_conn;
355 if (rc != 0)
356 {
357 mutt_error("%s", adata->err_msg);
358 }
359 else if (mutt_ssl_starttls(adata->conn))
360 {
361 mutt_error(_("Could not negotiate TLS connection"));
362 return -2;
363 }
364 else
365 {
366 /* recheck capabilities after STLS completes */
367 rc = pop_capabilities(adata, 1);
368 if (rc == -1)
369 goto err_conn;
370 if (rc == -2)
371 return -2;
372 }
373 }
374 }
375
376 if (c_ssl_force_tls && (adata->conn->ssf == 0))
377 {
378 mutt_error(_("Encrypted connection unavailable"));
379 return -2;
380 }
381#endif
382
384 if (rc == -1)
385 goto err_conn;
386 if (rc == -3)
388 if (rc != 0)
389 return rc;
390
391 /* recheck capabilities after authentication */
392 rc = pop_capabilities(adata, 2);
393 if (rc == -1)
394 goto err_conn;
395 if (rc == -2)
396 return -2;
397
398 /* get total size of mailbox */
399 mutt_str_copy(buf, "STAT\r\n", sizeof(buf));
400 rc = pop_query(adata, buf, sizeof(buf));
401 if (rc == -1)
402 goto err_conn;
403 if (rc == -2)
404 {
405 mutt_error("%s", adata->err_msg);
406 return rc;
407 }
408
409 unsigned int n = 0, size = 0;
410 sscanf(buf, "+OK %u %u", &n, &size);
411 adata->size = size;
412 return 0;
413
414err_conn:
415 adata->status = POP_DISCONNECTED;
416 mutt_error(_("Server closed connection"));
417 return -1;
418}
419
424void pop_logout(struct Mailbox *m)
425{
427
428 if (adata->status == POP_CONNECTED)
429 {
430 int rc = 0;
431 char buf[1024] = { 0 };
432 mutt_message(_("Closing connection to POP server..."));
433
434 if (m->readonly)
435 {
436 mutt_str_copy(buf, "RSET\r\n", sizeof(buf));
437 rc = pop_query(adata, buf, sizeof(buf));
438 }
439
440 if (rc != -1)
441 {
442 mutt_str_copy(buf, "QUIT\r\n", sizeof(buf));
443 rc = pop_query(adata, buf, sizeof(buf));
444 }
445
446 if (rc < 0)
447 mutt_debug(LL_DEBUG1, "Error closing POP connection\n");
448
450 }
451
452 adata->status = POP_DISCONNECTED;
453}
454
465int pop_query_d(struct PopAccountData *adata, char *buf, size_t buflen, char *msg)
466{
467 if (adata->status != POP_CONNECTED)
468 return -1;
469
470 /* print msg instead of real command */
471 if (msg)
472 {
473 mutt_debug(MUTT_SOCK_LOG_CMD, "> %s", msg);
474 }
475
477
478 char *c = strpbrk(buf, " \r\n");
479 if (c)
480 *c = '\0';
481 snprintf(adata->err_msg, sizeof(adata->err_msg), "%s: ", buf);
482
483 if (mutt_socket_readln_d(buf, buflen, adata->conn, MUTT_SOCK_LOG_FULL) < 0)
484 {
485 adata->status = POP_DISCONNECTED;
486 return -1;
487 }
488 if (mutt_str_startswith(buf, "+OK"))
489 return 0;
490
491 pop_error(adata, buf);
492 return -2;
493}
494
510int pop_fetch_data(struct PopAccountData *adata, const char *query,
511 struct Progress *progress, pop_fetch_t callback, void *data)
512{
513 char buf[1024] = { 0 };
514 long pos = 0;
515 size_t lenbuf = 0;
516
517 mutt_str_copy(buf, query, sizeof(buf));
518 int rc = pop_query(adata, buf, sizeof(buf));
519 if (rc < 0)
520 return rc;
521
522 char *inbuf = MUTT_MEM_MALLOC(sizeof(buf), char);
523
524 while (true)
525 {
526 const int chunk = mutt_socket_readln_d(buf, sizeof(buf), adata->conn, MUTT_SOCK_LOG_FULL);
527 if (chunk < 0)
528 {
529 adata->status = POP_DISCONNECTED;
530 rc = -1;
531 break;
532 }
533
534 char *p = buf;
535 if (!lenbuf && (buf[0] == '.'))
536 {
537 if (buf[1] != '.')
538 break;
539 p++;
540 }
541
542 mutt_str_copy(inbuf + lenbuf, p, sizeof(buf));
543 pos += chunk;
544
545 /* cast is safe since we break out of the loop when chunk<=0 */
546 if ((size_t) chunk >= sizeof(buf))
547 {
548 lenbuf += strlen(p);
549 }
550 else
551 {
552 progress_update(progress, pos, -1);
553 if ((rc == 0) && (callback(inbuf, data) < 0))
554 rc = -3;
555 lenbuf = 0;
556 }
557
558 MUTT_MEM_REALLOC(&inbuf, lenbuf + sizeof(buf), char);
559 }
560
561 FREE(&inbuf);
562 return rc;
563}
564
574static int check_uidl(const char *line, void *data)
575{
576 if (!line || !data)
577 return -1;
578
579 char *endp = NULL;
580
581 errno = 0;
582 unsigned int index = strtoul(line, &endp, 10);
583 if (errno != 0)
584 return -1;
585 while (*endp == ' ')
586 endp++;
587
588 struct Mailbox *m = data;
589 for (int i = 0; i < m->msg_count; i++)
590 {
591 struct PopEmailData *edata = pop_edata_get(m->emails[i]);
592 if (mutt_str_equal(edata->uid, endp))
593 {
594 edata->refno = index;
595 break;
596 }
597 }
598
599 return 0;
600}
601
608int pop_reconnect(struct Mailbox *m)
609{
611
612 if (adata->status == POP_CONNECTED)
613 return 0;
614
615 while (true)
616 {
618
619 int rc = pop_open_connection(adata);
620 if (rc == 0)
621 {
622 struct Progress *progress = progress_new(MUTT_PROGRESS_NET, 0);
623 progress_set_message(progress, _("Verifying message indexes..."));
624
625 for (int i = 0; i < m->msg_count; i++)
626 {
627 struct PopEmailData *edata = pop_edata_get(m->emails[i]);
628 edata->refno = -1;
629 }
630
631 rc = pop_fetch_data(adata, "UIDL\r\n", progress, check_uidl, m);
632 progress_free(&progress);
633 if (rc == -2)
634 {
635 mutt_error("%s", adata->err_msg);
636 }
637 }
638
639 if (rc == 0)
640 return 0;
641
642 pop_logout(m);
643
644 if (rc < -1)
645 return -1;
646
647 if (query_quadoption(_("Connection lost. Reconnect to POP server?"),
648 NeoMutt->sub, "pop_reconnect") != MUTT_YES)
649 {
650 return -1;
651 }
652 }
653}
bool buf_is_empty(const struct Buffer *buf)
Is the Buffer empty?
Definition: buffer.c:291
struct Buffer * buf_init(struct Buffer *buf)
Initialise a new Buffer.
Definition: buffer.c:61
size_t buf_addstr(struct Buffer *buf, const char *s)
Add a string to a Buffer.
Definition: buffer.c:226
size_t buf_strcpy(struct Buffer *buf, const char *s)
Copy a string into a Buffer.
Definition: buffer.c:395
const char * cs_subset_string(const struct ConfigSubset *sub, const char *name)
Get a string config item by name.
Definition: helpers.c:291
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition: helpers.c:47
Convenience wrapper for the config headers.
Connection Library.
ConnAccountField
Login credentials.
Definition: connaccount.h:33
@ MUTT_CA_OAUTH_CMD
OAuth refresh command.
Definition: connaccount.h:38
@ MUTT_CA_USER
User name.
Definition: connaccount.h:36
@ MUTT_CA_LOGIN
Login name.
Definition: connaccount.h:35
@ MUTT_CA_HOST
Server name.
Definition: connaccount.h:34
@ MUTT_CA_PASS
Password.
Definition: connaccount.h:37
#define MUTT_ACCT_SSL
Account uses SSL/TLS.
Definition: connaccount.h:47
Convenience wrapper for the core headers.
Structs that make up an email.
int mutt_ssl_starttls(struct Connection *conn)
Negotiate TLS over an already opened connection.
Definition: gnutls.c:1146
const char * pop_get_field(enum ConnAccountField field, void *gf_data)
Get connection login credentials - Implements ConnAccount::get_field() -.
Definition: lib.c:56
#define mutt_error(...)
Definition: logging2.h:92
#define mutt_message(...)
Definition: logging2.h:91
#define mutt_debug(LEVEL,...)
Definition: logging2.h:89
static int fetch_capa(const char *line, void *data)
Parse CAPA response - Implements pop_fetch_t -.
Definition: lib.c:146
static int fetch_auth(const char *line, void *data)
Parse AUTH response - Implements pop_fetch_t -.
Definition: lib.c:181
static int check_uidl(const char *line, void *data)
Parse UIDL response - Implements pop_fetch_t -.
Definition: lib.c:574
@ LL_DEBUG1
Log at debug level 1.
Definition: logging2.h:43
#define FREE(x)
Definition: memory.h:55
#define MUTT_MEM_REALLOC(pptr, n, type)
Definition: memory.h:43
#define MUTT_MEM_MALLOC(n, type)
Definition: memory.h:41
Convenience wrapper for the library headers.
#define _(a)
Definition: message.h:28
void mutt_str_remove_trailing_ws(char *s)
Trim trailing whitespace from a string.
Definition: string.c:565
char * mutt_str_skip_email_wsp(const char *s)
Skip over whitespace as defined by RFC5322.
Definition: string.c:608
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:660
size_t mutt_str_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix.
Definition: string.c:230
size_t mutt_str_copy(char *dest, const char *src, size_t dsize)
Copy a string into a buffer (guaranteeing NUL-termination)
Definition: string.c:581
size_t mutt_istr_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix, ignoring case.
Definition: string.c:242
int account_from_url(struct ConnAccount *cac, const struct Url *url)
Fill ConnAccount with information from url.
Definition: mutt_account.c:44
@ MUTT_ACCT_TYPE_POP
Pop Account.
Definition: mutt_account.h:37
void mutt_clear_error(void)
Clear the message line (bottom line of screen)
Definition: mutt_logging.c:74
NeoMutt Logging.
struct PopAccountData * pop_adata_get(struct Mailbox *m)
Get the Account data for this mailbox.
Definition: adata.c:73
Pop-specific Account data.
void pop_apop_timestamp(struct PopAccountData *adata, char *buf)
Get the server timestamp for APOP authentication.
Definition: auth.c:301
int pop_authenticate(struct PopAccountData *adata)
Authenticate with a POP server.
Definition: auth.c:523
struct PopEmailData * pop_edata_get(struct Email *e)
Get the private data for this Email.
Definition: edata.c:68
Pop-specific Email data.
int pop_connect(struct PopAccountData *adata)
Open connection.
Definition: lib.c:281
int pop_open_connection(struct PopAccountData *adata)
Open connection and authenticate.
Definition: lib.c:316
int pop_parse_path(const char *path, struct ConnAccount *cac)
Parse a POP mailbox name.
Definition: lib.c:82
static int pop_capabilities(struct PopAccountData *adata, int mode)
Get capabilities from a POP server.
Definition: lib.c:202
static void pop_error(struct PopAccountData *adata, char *msg)
Copy error message to err_msg buffer.
Definition: lib.c:122
int pop_query_d(struct PopAccountData *adata, char *buf, size_t buflen, char *msg)
Send data from buffer and receive answer to the same buffer.
Definition: lib.c:465
int pop_fetch_data(struct PopAccountData *adata, const char *query, struct Progress *progress, pop_fetch_t callback, void *data)
Read Headers with callback function.
Definition: lib.c:510
void pop_logout(struct Mailbox *m)
Logout from a POP server.
Definition: lib.c:424
int pop_reconnect(struct Mailbox *m)
Reconnect and verify indexes if connection was lost.
Definition: lib.c:608
#define POP_SSL_PORT
Definition: private.h:36
#define pop_query(adata, buf, buflen)
Definition: private.h:111
int(* pop_fetch_t)(const char *str, void *data)
Definition: private.h:108
@ POP_DISCONNECTED
Disconnected from server.
Definition: private.h:51
@ POP_CONNECTED
Connected to server.
Definition: private.h:50
@ POP_NONE
No connected to server.
Definition: private.h:49
#define POP_PORT
Definition: private.h:35
Progress Bar.
@ MUTT_PROGRESS_NET
Progress tracks bytes, according to $net_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
QuadOption
Possible values for a quad-option.
Definition: quad.h:36
@ MUTT_ABORT
User aborted the question (with Ctrl-G)
Definition: quad.h:37
@ MUTT_YES
User answered 'Yes', or assume 'Yes'.
Definition: quad.h:39
Ask the user a question.
enum QuadOption query_quadoption(const char *prompt, struct ConfigSubset *sub, const char *name)
Ask the user a quad-question.
Definition: question.c:379
GUI display the mailboxes in a side panel.
int mutt_socket_close(struct Connection *conn)
Close a socket.
Definition: socket.c:100
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
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_FULL
Definition: socket.h:54
#define mutt_socket_readln(buf, buflen, conn)
Definition: socket.h:56
#define MUTT_SOCK_LOG_CMD
Definition: socket.h:52
#define mutt_socket_send_d(conn, buf, dbg)
Definition: socket.h:58
void * adata
Private data (for Mailbox backends)
Definition: account.h:42
Login details for a remote server.
Definition: connaccount.h:53
const char * service
Name of the service, e.g. "imap".
Definition: connaccount.h:61
const char *(* get_field)(enum ConnAccountField field, void *gf_data)
Definition: connaccount.h:70
unsigned char type
Connection type, e.g. MUTT_ACCT_TYPE_IMAP.
Definition: connaccount.h:59
MuttAccountFlags flags
Which fields are initialised, e.g. MUTT_ACCT_USER.
Definition: connaccount.h:60
unsigned short port
Port to connect to.
Definition: connaccount.h:58
void * edata
Driver-specific data.
Definition: email.h:74
int index
The absolute (unsorted) message number.
Definition: email.h:110
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
bool readonly
Don't allow changes to the mailbox.
Definition: mailbox.h:116
Container for Accounts, Notifications.
Definition: neomutt.h:42
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:46
POP-specific Account data -.
Definition: adata.h:37
size_t size
Definition: adata.h:50
char err_msg[POP_CMD_RESPONSE]
Definition: adata.h:56
POP-specific Email data -.
Definition: edata.h:32
A parsed URL proto://user:password@host:port/path?a=1&b=2
Definition: url.h:69
char * host
Host.
Definition: url.h:73
char * path
Path.
Definition: url.h:75
enum UrlScheme scheme
Scheme, e.g. U_SMTPS.
Definition: url.h:70
struct Url * url_parse(const char *src)
Fill in Url.
Definition: url.c:239
void url_free(struct Url **ptr)
Free the contents of a URL.
Definition: url.c:124
@ U_POPS
Url is pops://.
Definition: url.h:38
@ U_POP
Url is pop://.
Definition: url.h:37