NeoMutt  2024-10-02-37-gfa9146
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_account.h"
50#include "mutt_logging.h"
51
52struct Progress;
53
57const char *pop_get_field(enum ConnAccountField field, void *gf_data)
58{
59 switch (field)
60 {
61 case MUTT_CA_LOGIN:
62 case MUTT_CA_USER:
63 return cs_subset_string(NeoMutt->sub, "pop_user");
64 case MUTT_CA_PASS:
65 return cs_subset_string(NeoMutt->sub, "pop_pass");
67 return cs_subset_string(NeoMutt->sub, "pop_oauth_refresh_command");
68 case MUTT_CA_HOST:
69 default:
70 return NULL;
71 }
72}
73
83int pop_parse_path(const char *path, struct ConnAccount *cac)
84{
85 /* Defaults */
86 cac->flags = 0;
88 cac->port = 0;
89 cac->service = "pop";
91
92 struct Url *url = url_parse(path);
93
94 if (!url || ((url->scheme != U_POP) && (url->scheme != U_POPS)) ||
95 !url->host || (mutt_account_fromurl(cac, url) < 0))
96 {
97 url_free(&url);
98 mutt_error(_("Invalid POP URL: %s"), path);
99 return -1;
100 }
101
102 if (url->scheme == U_POPS)
103 cac->flags |= MUTT_ACCT_SSL;
104
105 struct servent *service = getservbyname((url->scheme == U_POP) ? "pop3" : "pop3s", "tcp");
106 if (cac->port == 0)
107 {
108 if (service)
109 cac->port = ntohs(service->s_port);
110 else
111 cac->port = (url->scheme == U_POP) ? POP_PORT : POP_SSL_PORT;
112 }
113
114 url_free(&url);
115 return 0;
116}
117
123static void pop_error(struct PopAccountData *adata, char *msg)
124{
125 char *t = strchr(adata->err_msg, '\0');
126 char *c = msg;
127
128 size_t plen = mutt_str_startswith(msg, "-ERR ");
129 if (plen != 0)
130 {
131 char *c2 = mutt_str_skip_email_wsp(msg + plen);
132
133 if (*c2)
134 c = c2;
135 }
136
137 mutt_str_copy(t, c, sizeof(adata->err_msg) - strlen(adata->err_msg));
139}
140
147static int fetch_capa(const char *line, void *data)
148{
149 struct PopAccountData *adata = data;
150
151 if (mutt_istr_startswith(line, "SASL"))
152 {
153 const char *c = mutt_str_skip_email_wsp(line + 4);
154 buf_strcpy(&adata->auth_list, c);
155 }
156 else if (mutt_istr_startswith(line, "STLS"))
157 {
158 adata->cmd_stls = true;
159 }
160 else if (mutt_istr_startswith(line, "USER"))
161 {
162 adata->cmd_user = 1;
163 }
164 else if (mutt_istr_startswith(line, "UIDL"))
165 {
166 adata->cmd_uidl = 1;
167 }
168 else if (mutt_istr_startswith(line, "TOP"))
169 {
170 adata->cmd_top = 1;
171 }
172
173 return 0;
174}
175
182static int fetch_auth(const char *line, void *data)
183{
184 struct PopAccountData *adata = data;
185
186 if (!buf_is_empty(&adata->auth_list))
187 {
188 buf_addstr(&adata->auth_list, " ");
189 }
190 buf_addstr(&adata->auth_list, line);
191
192 return 0;
193}
194
203static int pop_capabilities(struct PopAccountData *adata, int mode)
204{
205 char buf[1024] = { 0 };
206
207 /* don't check capabilities on reconnect */
208 if (adata->capabilities)
209 return 0;
210
211 /* init capabilities */
212 if (mode == 0)
213 {
214 adata->cmd_capa = false;
215 adata->cmd_stls = false;
216 adata->cmd_user = 0;
217 adata->cmd_uidl = 0;
218 adata->cmd_top = 0;
219 adata->resp_codes = false;
220 adata->expire = true;
221 adata->login_delay = 0;
222 buf_init(&adata->auth_list);
223 }
224
225 /* Execute CAPA command */
226 if ((mode == 0) || adata->cmd_capa)
227 {
228 mutt_str_copy(buf, "CAPA\r\n", sizeof(buf));
229 switch (pop_fetch_data(adata, buf, NULL, fetch_capa, adata))
230 {
231 case 0:
232 {
233 adata->cmd_capa = true;
234 break;
235 }
236 case -1:
237 return -1;
238 }
239 }
240
241 /* CAPA not supported, use defaults */
242 if ((mode == 0) && !adata->cmd_capa)
243 {
244 adata->cmd_user = 2;
245 adata->cmd_uidl = 2;
246 adata->cmd_top = 2;
247
248 mutt_str_copy(buf, "AUTH\r\n", sizeof(buf));
249 if (pop_fetch_data(adata, buf, NULL, fetch_auth, adata) == -1)
250 return -1;
251 }
252
253 /* Check capabilities */
254 if (mode == 2)
255 {
256 char *msg = NULL;
257
258 if (!adata->expire)
259 msg = _("Unable to leave messages on server");
260 if (adata->cmd_top == 0)
261 msg = _("Command TOP is not supported by server");
262 if (adata->cmd_uidl == 0)
263 msg = _("Command UIDL is not supported by server");
264 if (msg && adata->cmd_capa)
265 {
266 mutt_error("%s", msg);
267 return -2;
268 }
269 adata->capabilities = true;
270 }
271
272 return 0;
273}
274
283{
284 char buf[1024] = { 0 };
285
286 adata->status = POP_NONE;
287 if ((mutt_socket_open(adata->conn) < 0) ||
288 (mutt_socket_readln(buf, sizeof(buf), adata->conn) < 0))
289 {
290 mutt_error(_("Error connecting to server: %s"), adata->conn->account.host);
291 return -1;
292 }
293
294 adata->status = POP_CONNECTED;
295
296 if (!mutt_str_startswith(buf, "+OK"))
297 {
298 *adata->err_msg = '\0';
299 pop_error(adata, buf);
300 mutt_error("%s", adata->err_msg);
301 return -2;
302 }
303
305
306 return 0;
307}
308
318{
319 char buf[1024] = { 0 };
320
321 int rc = pop_connect(adata);
322 if (rc < 0)
323 return rc;
324
325 rc = pop_capabilities(adata, 0);
326 if (rc == -1)
327 goto err_conn;
328 if (rc == -2)
329 return -2;
330
331#ifdef USE_SSL
332 /* Attempt STLS if available and desired. */
333 const bool c_ssl_force_tls = cs_subset_bool(NeoMutt->sub, "ssl_force_tls");
334 if ((adata->conn->ssf == 0) && (adata->cmd_stls || c_ssl_force_tls))
335 {
336 if (c_ssl_force_tls)
337 adata->use_stls = 2;
338 if (adata->use_stls == 0)
339 {
340 enum QuadOption ans = query_quadoption(_("Secure connection with TLS?"),
341 NeoMutt->sub, "ssl_starttls");
342 if (ans == MUTT_ABORT)
343 return -2;
344 adata->use_stls = 1;
345 if (ans == MUTT_YES)
346 adata->use_stls = 2;
347 }
348 if (adata->use_stls == 2)
349 {
350 mutt_str_copy(buf, "STLS\r\n", sizeof(buf));
351 rc = pop_query(adata, buf, sizeof(buf));
352 // Clear any data after the STLS acknowledgement
354 if (rc == -1)
355 goto err_conn;
356 if (rc != 0)
357 {
358 mutt_error("%s", adata->err_msg);
359 }
360 else if (mutt_ssl_starttls(adata->conn))
361 {
362 mutt_error(_("Could not negotiate TLS connection"));
363 return -2;
364 }
365 else
366 {
367 /* recheck capabilities after STLS completes */
368 rc = pop_capabilities(adata, 1);
369 if (rc == -1)
370 goto err_conn;
371 if (rc == -2)
372 return -2;
373 }
374 }
375 }
376
377 if (c_ssl_force_tls && (adata->conn->ssf == 0))
378 {
379 mutt_error(_("Encrypted connection unavailable"));
380 return -2;
381 }
382#endif
383
385 if (rc == -1)
386 goto err_conn;
387 if (rc == -3)
389 if (rc != 0)
390 return rc;
391
392 /* recheck capabilities after authentication */
393 rc = pop_capabilities(adata, 2);
394 if (rc == -1)
395 goto err_conn;
396 if (rc == -2)
397 return -2;
398
399 /* get total size of mailbox */
400 mutt_str_copy(buf, "STAT\r\n", sizeof(buf));
401 rc = pop_query(adata, buf, sizeof(buf));
402 if (rc == -1)
403 goto err_conn;
404 if (rc == -2)
405 {
406 mutt_error("%s", adata->err_msg);
407 return rc;
408 }
409
410 unsigned int n = 0, size = 0;
411 sscanf(buf, "+OK %u %u", &n, &size);
412 adata->size = size;
413 return 0;
414
415err_conn:
416 adata->status = POP_DISCONNECTED;
417 mutt_error(_("Server closed connection"));
418 return -1;
419}
420
425void pop_logout(struct Mailbox *m)
426{
428
429 if (adata->status == POP_CONNECTED)
430 {
431 int rc = 0;
432 char buf[1024] = { 0 };
433 mutt_message(_("Closing connection to POP server..."));
434
435 if (m->readonly)
436 {
437 mutt_str_copy(buf, "RSET\r\n", sizeof(buf));
438 rc = pop_query(adata, buf, sizeof(buf));
439 }
440
441 if (rc != -1)
442 {
443 mutt_str_copy(buf, "QUIT\r\n", sizeof(buf));
444 rc = pop_query(adata, buf, sizeof(buf));
445 }
446
447 if (rc < 0)
448 mutt_debug(LL_DEBUG1, "Error closing POP connection\n");
449
451 }
452
453 adata->status = POP_DISCONNECTED;
454}
455
466int pop_query_d(struct PopAccountData *adata, char *buf, size_t buflen, char *msg)
467{
468 if (adata->status != POP_CONNECTED)
469 return -1;
470
471 /* print msg instead of real command */
472 if (msg)
473 {
474 mutt_debug(MUTT_SOCK_LOG_CMD, "> %s", msg);
475 }
476
478
479 char *c = strpbrk(buf, " \r\n");
480 if (c)
481 *c = '\0';
482 snprintf(adata->err_msg, sizeof(adata->err_msg), "%s: ", buf);
483
484 if (mutt_socket_readln_d(buf, buflen, adata->conn, MUTT_SOCK_LOG_FULL) < 0)
485 {
486 adata->status = POP_DISCONNECTED;
487 return -1;
488 }
489 if (mutt_str_startswith(buf, "+OK"))
490 return 0;
491
492 pop_error(adata, buf);
493 return -2;
494}
495
511int pop_fetch_data(struct PopAccountData *adata, const char *query,
512 struct Progress *progress, pop_fetch_t callback, void *data)
513{
514 char buf[1024] = { 0 };
515 long pos = 0;
516 size_t lenbuf = 0;
517
518 mutt_str_copy(buf, query, sizeof(buf));
519 int rc = pop_query(adata, buf, sizeof(buf));
520 if (rc < 0)
521 return rc;
522
523 char *inbuf = mutt_mem_malloc(sizeof(buf));
524
525 while (true)
526 {
527 const int chunk = mutt_socket_readln_d(buf, sizeof(buf), adata->conn, MUTT_SOCK_LOG_FULL);
528 if (chunk < 0)
529 {
530 adata->status = POP_DISCONNECTED;
531 rc = -1;
532 break;
533 }
534
535 char *p = buf;
536 if (!lenbuf && (buf[0] == '.'))
537 {
538 if (buf[1] != '.')
539 break;
540 p++;
541 }
542
543 mutt_str_copy(inbuf + lenbuf, p, sizeof(buf));
544 pos += chunk;
545
546 /* cast is safe since we break out of the loop when chunk<=0 */
547 if ((size_t) chunk >= sizeof(buf))
548 {
549 lenbuf += strlen(p);
550 }
551 else
552 {
553 progress_update(progress, pos, -1);
554 if ((rc == 0) && (callback(inbuf, data) < 0))
555 rc = -3;
556 lenbuf = 0;
557 }
558
559 mutt_mem_realloc(&inbuf, lenbuf + sizeof(buf));
560 }
561
562 FREE(&inbuf);
563 return rc;
564}
565
575static int check_uidl(const char *line, void *data)
576{
577 if (!line || !data)
578 return -1;
579
580 char *endp = NULL;
581
582 errno = 0;
583 unsigned int index = strtoul(line, &endp, 10);
584 if (errno != 0)
585 return -1;
586 while (*endp == ' ')
587 endp++;
588
589 struct Mailbox *m = data;
590 for (int i = 0; i < m->msg_count; i++)
591 {
592 struct PopEmailData *edata = pop_edata_get(m->emails[i]);
593 if (mutt_str_equal(edata->uid, endp))
594 {
595 edata->refno = index;
596 break;
597 }
598 }
599
600 return 0;
601}
602
609int pop_reconnect(struct Mailbox *m)
610{
612
613 if (adata->status == POP_CONNECTED)
614 return 0;
615
616 while (true)
617 {
619
620 int rc = pop_open_connection(adata);
621 if (rc == 0)
622 {
623 struct Progress *progress = progress_new(MUTT_PROGRESS_NET, 0);
624 progress_set_message(progress, _("Verifying message indexes..."));
625
626 for (int i = 0; i < m->msg_count; i++)
627 {
628 struct PopEmailData *edata = pop_edata_get(m->emails[i]);
629 edata->refno = -1;
630 }
631
632 rc = pop_fetch_data(adata, "UIDL\r\n", progress, check_uidl, m);
633 progress_free(&progress);
634 if (rc == -2)
635 {
636 mutt_error("%s", adata->err_msg);
637 }
638 }
639
640 if (rc == 0)
641 return 0;
642
643 pop_logout(m);
644
645 if (rc < -1)
646 return -1;
647
648 if (query_quadoption(_("Connection lost. Reconnect to POP server?"),
649 NeoMutt->sub, "pop_reconnect") != MUTT_YES)
650 {
651 return -1;
652 }
653 }
654}
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:1145
const char * pop_get_field(enum ConnAccountField field, void *gf_data)
Get connection login credentials - Implements ConnAccount::get_field() -.
Definition: lib.c:57
#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:147
static int fetch_auth(const char *line, void *data)
Parse AUTH response - Implements pop_fetch_t -.
Definition: lib.c:182
static int check_uidl(const char *line, void *data)
Parse UIDL response - Implements pop_fetch_t -.
Definition: lib.c:575
@ LL_DEBUG1
Log at debug level 1.
Definition: logging2.h:43
void * mutt_mem_malloc(size_t size)
Allocate memory on the heap.
Definition: memory.c:91
void mutt_mem_realloc(void *ptr, size_t size)
Resize a block of memory on the heap.
Definition: memory.c:115
#define FREE(x)
Definition: memory.h:45
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 mutt_account_fromurl(struct ConnAccount *cac, const struct Url *url)
Fill ConnAccount with information from url.
Definition: mutt_account.c:44
ConnAccount object used by POP and IMAP.
@ MUTT_ACCT_TYPE_POP
Pop Account.
Definition: mutt_account.h:39
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:282
int pop_open_connection(struct PopAccountData *adata)
Open connection and authenticate.
Definition: lib.c:317
int pop_parse_path(const char *path, struct ConnAccount *cac)
Parse a POP mailbox name.
Definition: lib.c:83
static int pop_capabilities(struct PopAccountData *adata, int mode)
Get capabilities from a POP server.
Definition: lib.c:203
static void pop_error(struct PopAccountData *adata, char *msg)
Copy error message to err_msg buffer.
Definition: lib.c:123
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:466
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:511
void pop_logout(struct Mailbox *m)
Logout from a POP server.
Definition: lib.c:425
int pop_reconnect(struct Mailbox *m)
Reconnect and verify indexes if connection was lost.
Definition: lib.c:609
#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:81
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