NeoMutt  2025-09-05-7-geaa2bd
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
command.c File Reference

Send/receive commands to/from an IMAP server. More...

#include "config.h"
#include <errno.h>
#include <limits.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.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 "adata.h"
#include "commands.h"
#include "edata.h"
#include "mdata.h"
#include "msn.h"
#include "mutt_logging.h"
#include "mx.h"
+ Include dependency graph for command.c:

Go to the source code of this file.

Macros

#define IMAP_CMD_BUFSIZE   512
 

Functions

static bool cmd_queue_full (struct ImapAccountData *adata)
 Is the IMAP command queue full?
 
static struct ImapCommandcmd_new (struct ImapAccountData *adata)
 Create and queue a new command control block.
 
static int cmd_queue (struct ImapAccountData *adata, const char *cmdstr, ImapCmdFlags flags)
 Add a IMAP command to the queue.
 
static void cmd_handle_fatal (struct ImapAccountData *adata)
 When ImapAccountData is in fatal state, do what we can.
 
static int cmd_start (struct ImapAccountData *adata, const char *cmdstr, ImapCmdFlags flags)
 Start a new IMAP command.
 
static int cmd_status (const char *s)
 Parse response line for tagged OK/NO/BAD.
 
static void cmd_parse_expunge (struct ImapAccountData *adata, const char *s)
 Parse expunge command.
 
static void cmd_parse_vanished (struct ImapAccountData *adata, char *s)
 Parse vanished command.
 
static void cmd_parse_fetch (struct ImapAccountData *adata, char *s)
 Load fetch response into ImapAccountData.
 
static void cmd_parse_capability (struct ImapAccountData *adata, char *s)
 Set capability bits according to CAPABILITY response.
 
static void cmd_parse_list (struct ImapAccountData *adata, char *s)
 Parse a server LIST command (list mailboxes)
 
static void cmd_parse_lsub (struct ImapAccountData *adata, char *s)
 Parse a server LSUB (list subscribed mailboxes)
 
static void cmd_parse_myrights (struct ImapAccountData *adata, const char *s)
 Set rights bits according to MYRIGHTS response.
 
static struct Mailboxfind_mailbox (struct ImapAccountData *adata, const char *name)
 Find a Mailbox by its name.
 
static void cmd_parse_status (struct ImapAccountData *adata, char *s)
 Parse status from server.
 
static void cmd_parse_enabled (struct ImapAccountData *adata, const char *s)
 Record what the server has enabled.
 
static void cmd_parse_exists (struct ImapAccountData *adata, const char *pn)
 Parse EXISTS message from serer.
 
static int cmd_handle_untagged (struct ImapAccountData *adata)
 Fallback parser for otherwise unhandled messages.
 
int imap_cmd_start (struct ImapAccountData *adata, const char *cmdstr)
 Given an IMAP command, send it to the server.
 
int imap_cmd_step (struct ImapAccountData *adata)
 Reads server responses from an IMAP command.
 
bool imap_code (const char *s)
 Was the command successful.
 
const char * imap_cmd_trailer (struct ImapAccountData *adata)
 Extra information after tagged command response if any.
 
int imap_exec (struct ImapAccountData *adata, const char *cmdstr, ImapCmdFlags flags)
 Execute a command and wait for the response from the server.
 
void imap_cmd_finish (struct ImapAccountData *adata)
 Attempt to perform cleanup.
 
int imap_cmd_idle (struct ImapAccountData *adata)
 Enter the IDLE state.
 

Variables

static const char *const Capabilities []
 Server capabilities strings that we understand.
 

Detailed Description

Send/receive commands to/from an IMAP server.

Authors
  • Michael R. Elkins
  • Brandon Long
  • Brendan Cully
  • Richard Russon
  • Mehdi Abaakouk
  • Pietro Cerutti
  • Fabian Groffen
  • Federico Kircheis
  • Ian Zimmerman

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 command.c.

Macro Definition Documentation

◆ IMAP_CMD_BUFSIZE

#define IMAP_CMD_BUFSIZE   512

Definition at line 59 of file command.c.

Function Documentation

◆ cmd_queue_full()

static bool cmd_queue_full ( struct ImapAccountData adata)
static

Is the IMAP command queue full?

Parameters
adataImap Account data
Return values
trueQueue is full

Definition at line 96 of file command.c.

97{
98 if (((adata->nextcmd + 1) % adata->cmdslots) == adata->lastcmd)
99 return true;
100
101 return false;
102}
int lastcmd
Last command in the queue.
Definition: adata.h:72
int nextcmd
Next command to be sent.
Definition: adata.h:71
int cmdslots
Size of the command queue.
Definition: adata.h:70
+ Here is the caller graph for this function:

◆ cmd_new()

static struct ImapCommand * cmd_new ( struct ImapAccountData adata)
static

Create and queue a new command control block.

Parameters
adataImap Account data
Return values
NULLThe pipeline is full
ptrNew command

Definition at line 110 of file command.c.

111{
112 struct ImapCommand *cmd = NULL;
113
114 if (cmd_queue_full(adata))
115 {
116 mutt_debug(LL_DEBUG3, "IMAP command queue full\n");
117 return NULL;
118 }
119
120 cmd = adata->cmds + adata->nextcmd;
121 adata->nextcmd = (adata->nextcmd + 1) % adata->cmdslots;
122
123 snprintf(cmd->seq, sizeof(cmd->seq), "%c%04u", adata->seqid, adata->seqno++);
124 if (adata->seqno > 9999)
125 adata->seqno = 0;
126
127 cmd->state = IMAP_RES_NEW;
128
129 return cmd;
130}
#define mutt_debug(LEVEL,...)
Definition: logging2.h:90
static bool cmd_queue_full(struct ImapAccountData *adata)
Is the IMAP command queue full?
Definition: command.c:96
#define IMAP_RES_NEW
ImapCommand.state additions.
Definition: private.h:58
@ LL_DEBUG3
Log at debug level 3.
Definition: logging2.h:46
unsigned char seqid
tag sequence prefix
Definition: adata.h:56
struct ImapCommand * cmds
Queue of commands for the server.
Definition: adata.h:69
unsigned int seqno
tag sequence number, e.g. '{seqid}0001'
Definition: adata.h:57
IMAP command structure.
Definition: private.h:160
int state
Command state, e.g. IMAP_RES_NEW.
Definition: private.h:162
char seq[SEQ_LEN+1]
Command tag, e.g. 'a0001'.
Definition: private.h:161
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ cmd_queue()

static int cmd_queue ( struct ImapAccountData adata,
const char *  cmdstr,
ImapCmdFlags  flags 
)
static

Add a IMAP command to the queue.

Parameters
adataImap Account data
cmdstrCommand string
flagsServer flags, see ImapCmdFlags
Return values
0Success
<0Failure, e.g. IMAP_RES_BAD

If the queue is full, attempts to drain it.

Definition at line 142 of file command.c.

143{
144 if (cmd_queue_full(adata))
145 {
146 mutt_debug(LL_DEBUG3, "Draining IMAP command pipeline\n");
147
148 const int rc = imap_exec(adata, NULL, flags & IMAP_CMD_POLL);
149
150 if (rc == IMAP_EXEC_ERROR)
151 return IMAP_RES_BAD;
152 }
153
154 struct ImapCommand *cmd = cmd_new(adata);
155 if (!cmd)
156 return IMAP_RES_BAD;
157
158 if (buf_add_printf(&adata->cmdbuf, "%s %s\r\n", cmd->seq, cmdstr) < 0)
159 return IMAP_RES_BAD;
160
161 return 0;
162}
int buf_add_printf(struct Buffer *buf, const char *fmt,...)
Format a string appending a Buffer.
Definition: buffer.c:204
int imap_exec(struct ImapAccountData *adata, const char *cmdstr, ImapCmdFlags flags)
Execute a command and wait for the response from the server.
Definition: command.c:1302
static struct ImapCommand * cmd_new(struct ImapAccountData *adata)
Create and queue a new command control block.
Definition: command.c:110
#define IMAP_CMD_POLL
Poll the tcp connection before running the imap command.
Definition: private.h:74
@ IMAP_EXEC_ERROR
Imap command failure.
Definition: private.h:83
#define IMAP_RES_BAD
<tag> BAD ...
Definition: private.h:54
struct Buffer cmdbuf
Definition: adata.h:73
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ cmd_handle_fatal()

static void cmd_handle_fatal ( struct ImapAccountData adata)
static

When ImapAccountData is in fatal state, do what we can.

Parameters
adataImap Account data

Definition at line 168 of file command.c.

169{
170 adata->status = IMAP_FATAL;
171
172 if (!adata->mailbox)
173 return;
174
175 struct ImapMboxData *mdata = adata->mailbox->mdata;
176
177 if ((adata->state >= IMAP_SELECTED) && (mdata->reopen & IMAP_REOPEN_ALLOW))
178 {
179 mx_fastclose_mailbox(adata->mailbox, true);
180 mutt_error(_("Mailbox %s@%s closed"), adata->conn->account.user,
181 adata->conn->account.host);
182 }
183
185 if (!adata->recovering)
186 {
187 adata->recovering = true;
188 if (imap_login(adata))
190 adata->recovering = false;
191 }
192}
#define mutt_error(...)
Definition: logging2.h:93
@ IMAP_SELECTED
Mailbox is selected.
Definition: private.h:108
#define IMAP_REOPEN_ALLOW
Allow re-opening a folder upon expunge.
Definition: private.h:64
@ IMAP_FATAL
Unrecoverable error occurred.
Definition: private.h:95
void imap_close_connection(struct ImapAccountData *adata)
Close an IMAP connection.
Definition: imap.c:851
int imap_login(struct ImapAccountData *adata)
Open an IMAP connection.
Definition: imap.c:1781
#define _(a)
Definition: message.h:28
void mutt_clear_error(void)
Clear the message line (bottom line of screen)
Definition: mutt_logging.c:74
void mx_fastclose_mailbox(struct Mailbox *m, bool keep_account)
Free up memory associated with the Mailbox.
Definition: mx.c:414
char user[128]
Username.
Definition: connaccount.h:56
char host[128]
Server to login to.
Definition: connaccount.h:54
struct ConnAccount account
Account details: username, password, etc.
Definition: connection.h:49
unsigned char state
ImapState, e.g. IMAP_AUTHENTICATED.
Definition: adata.h:44
struct Mailbox * mailbox
Current selected mailbox.
Definition: adata.h:76
unsigned char status
ImapFlags, e.g. IMAP_FATAL.
Definition: adata.h:45
bool recovering
Definition: adata.h:42
struct Connection * conn
Connection to IMAP server.
Definition: adata.h:41
IMAP-specific Mailbox data -.
Definition: mdata.h:40
void * mdata
Driver specific data.
Definition: mailbox.h:132
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ cmd_start()

static int cmd_start ( struct ImapAccountData adata,
const char *  cmdstr,
ImapCmdFlags  flags 
)
static

Start a new IMAP command.

Parameters
adataImap Account data
cmdstrCommand string
flagsCommand flags, see ImapCmdFlags
Return values
0Success
<0Failure, e.g. IMAP_RES_BAD

Definition at line 202 of file command.c.

203{
204 int rc;
205
206 if (adata->status == IMAP_FATAL)
207 {
208 cmd_handle_fatal(adata);
209 return -1;
210 }
211
212 if (cmdstr && ((rc = cmd_queue(adata, cmdstr, flags)) < 0))
213 return rc;
214
215 if (flags & IMAP_CMD_QUEUE)
216 return 0;
217
218 if (buf_is_empty(&adata->cmdbuf))
219 return IMAP_RES_BAD;
220
221 rc = mutt_socket_send_d(adata->conn, adata->cmdbuf.data,
223 buf_reset(&adata->cmdbuf);
224
225 /* unidle when command queue is flushed */
226 if (adata->state == IMAP_IDLE)
227 adata->state = IMAP_SELECTED;
228
229 return (rc < 0) ? IMAP_RES_BAD : 0;
230}
void buf_reset(struct Buffer *buf)
Reset an existing Buffer.
Definition: buffer.c:76
bool buf_is_empty(const struct Buffer *buf)
Is the Buffer empty?
Definition: buffer.c:291
static int cmd_queue(struct ImapAccountData *adata, const char *cmdstr, ImapCmdFlags flags)
Add a IMAP command to the queue.
Definition: command.c:142
static void cmd_handle_fatal(struct ImapAccountData *adata)
When ImapAccountData is in fatal state, do what we can.
Definition: command.c:168
@ IMAP_IDLE
Connection is idle.
Definition: private.h:111
#define IMAP_CMD_PASS
Command contains a password. Suppress logging.
Definition: private.h:72
#define IMAP_LOG_PASS
Definition: private.h:50
#define IMAP_LOG_CMD
Definition: private.h:48
#define IMAP_CMD_QUEUE
Queue a command, do not execute.
Definition: private.h:73
#define mutt_socket_send_d(conn, buf, dbg)
Definition: socket.h:58
char * data
Pointer to data.
Definition: buffer.h:37
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ cmd_status()

static int cmd_status ( const char *  s)
static

Parse response line for tagged OK/NO/BAD.

Parameters
sStatus string from server
Return values
0Success
<0Failure, e.g. IMAP_RES_BAD

Definition at line 238 of file command.c.

239{
240 s = imap_next_word((char *) s);
241
242 if (mutt_istr_startswith(s, "OK"))
243 return IMAP_RES_OK;
244 if (mutt_istr_startswith(s, "NO"))
245 return IMAP_RES_NO;
246
247 return IMAP_RES_BAD;
248}
#define IMAP_RES_OK
<tag> OK ...
Definition: private.h:55
#define IMAP_RES_NO
<tag> NO ...
Definition: private.h:53
char * imap_next_word(char *s)
Find where the next IMAP word begins.
Definition: util.c:823
size_t mutt_istr_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix, ignoring case.
Definition: string.c:243
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ cmd_parse_expunge()

static void cmd_parse_expunge ( struct ImapAccountData adata,
const char *  s 
)
static

Parse expunge command.

Parameters
adataImap Account data
sString containing MSN of message to expunge

cmd_parse_expunge: mark headers with new sequence ID and mark adata to be reopened at our earliest convenience

Definition at line 258 of file command.c.

259{
260 unsigned int exp_msn;
261 struct Email *e = NULL;
262
263 mutt_debug(LL_DEBUG2, "Handling EXPUNGE\n");
264
265 struct ImapMboxData *mdata = adata->mailbox->mdata;
266
267 if (!mutt_str_atoui(s, &exp_msn) || (exp_msn < 1) ||
268 (exp_msn > imap_msn_highest(&mdata->msn)))
269 {
270 return;
271 }
272
273 e = imap_msn_get(&mdata->msn, exp_msn - 1);
274 if (e)
275 {
276 /* imap_expunge_mailbox() will rewrite e->index.
277 * It needs to resort using EMAIL_SORT_UNSORTED anyway, so setting to INT_MAX
278 * makes the code simpler and possibly more efficient. */
279 e->index = INT_MAX;
280 imap_edata_get(e)->msn = 0;
281 }
282
283 /* decrement seqno of those above. */
284 const size_t max_msn = imap_msn_highest(&mdata->msn);
285 for (unsigned int cur = exp_msn; cur < max_msn; cur++)
286 {
287 e = imap_msn_get(&mdata->msn, cur);
288 if (e)
289 imap_edata_get(e)->msn--;
290 imap_msn_set(&mdata->msn, cur - 1, e);
291 }
292 imap_msn_shrink(&mdata->msn, 1);
293
294 mdata->reopen |= IMAP_EXPUNGE_PENDING;
295}
const char * mutt_str_atoui(const char *str, unsigned int *dst)
Convert ASCII string to an unsigned integer.
Definition: atoi.c:218
struct ImapEmailData * imap_edata_get(struct Email *e)
Get the private data for this Email.
Definition: edata.c:67
#define IMAP_EXPUNGE_PENDING
Messages on the server have been expunged.
Definition: private.h:66
@ LL_DEBUG2
Log at debug level 2.
Definition: logging2.h:45
size_t imap_msn_shrink(struct MSNArray *msn, size_t num)
Remove a number of entries from the end of the cache.
Definition: msn.c:106
struct Email * imap_msn_get(const struct MSNArray *msn, int idx)
Return the Email associated with an msn.
Definition: msn.c:83
size_t imap_msn_highest(const struct MSNArray *msn)
Return the highest MSN in use.
Definition: msn.c:72
void imap_msn_set(struct MSNArray *msn, size_t idx, struct Email *e)
Cache an Email into a given position.
Definition: msn.c:95
The envelope/body of an email.
Definition: email.h:39
int index
The absolute (unsorted) message number.
Definition: email.h:110
unsigned int msn
Message Sequence Number.
Definition: edata.h:46
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ cmd_parse_vanished()

static void cmd_parse_vanished ( struct ImapAccountData adata,
char *  s 
)
static

Parse vanished command.

Parameters
adataImap Account data
sString containing MSN of message to expunge

Handle VANISHED (RFC7162), which is like expunge, but passes a seqset of UIDs. An optional (EARLIER) argument specifies not to decrement subsequent MSNs.

Definition at line 305 of file command.c.

306{
307 bool earlier = false;
308 int rc;
309 unsigned int uid = 0;
310
311 struct ImapMboxData *mdata = adata->mailbox->mdata;
312
313 mutt_debug(LL_DEBUG2, "Handling VANISHED\n");
314
315 if (mutt_istr_startswith(s, "(EARLIER)"))
316 {
317 /* The RFC says we should not decrement msns with the VANISHED EARLIER tag.
318 * My experimentation says that's crap. */
319 earlier = true;
320 s = imap_next_word(s);
321 }
322
323 char *end_of_seqset = s;
324 while (*end_of_seqset)
325 {
326 if (!strchr("0123456789:,", *end_of_seqset))
327 *end_of_seqset = '\0';
328 else
329 end_of_seqset++;
330 }
331
333 if (!iter)
334 {
335 mutt_debug(LL_DEBUG2, "VANISHED: empty seqset [%s]?\n", s);
336 return;
337 }
338
339 while ((rc = mutt_seqset_iterator_next(iter, &uid)) == 0)
340 {
341 struct Email *e = mutt_hash_int_find(mdata->uid_hash, uid);
342 if (!e)
343 continue;
344
345 unsigned int exp_msn = imap_edata_get(e)->msn;
346
347 /* imap_expunge_mailbox() will rewrite e->index.
348 * It needs to resort using EMAIL_SORT_UNSORTED anyway, so setting to INT_MAX
349 * makes the code simpler and possibly more efficient. */
350 e->index = INT_MAX;
351 imap_edata_get(e)->msn = 0;
352
353 if ((exp_msn < 1) || (exp_msn > imap_msn_highest(&mdata->msn)))
354 {
355 mutt_debug(LL_DEBUG1, "VANISHED: msn for UID %u is incorrect\n", uid);
356 continue;
357 }
358 if (imap_msn_get(&mdata->msn, exp_msn - 1) != e)
359 {
360 mutt_debug(LL_DEBUG1, "VANISHED: msn_index for UID %u is incorrect\n", uid);
361 continue;
362 }
363
364 imap_msn_remove(&mdata->msn, exp_msn - 1);
365
366 if (!earlier)
367 {
368 /* decrement seqno of those above. */
369 const size_t max_msn = imap_msn_highest(&mdata->msn);
370 for (unsigned int cur = exp_msn; cur < max_msn; cur++)
371 {
372 e = imap_msn_get(&mdata->msn, cur);
373 if (e)
374 imap_edata_get(e)->msn--;
375 imap_msn_set(&mdata->msn, cur - 1, e);
376 }
377
378 imap_msn_shrink(&mdata->msn, 1);
379 }
380 }
381
382 if (rc < 0)
383 mutt_debug(LL_DEBUG1, "VANISHED: illegal seqset %s\n", s);
384
386
388}
void * mutt_hash_int_find(const struct HashTable *table, unsigned int intkey)
Find the HashElem data in a Hash Table element using a key.
Definition: hash.c:392
struct SeqsetIterator * mutt_seqset_iterator_new(const char *seqset)
Create a new Sequence Set Iterator.
Definition: util.c:1124
int mutt_seqset_iterator_next(struct SeqsetIterator *iter, unsigned int *next)
Get the next UID from a Sequence Set.
Definition: util.c:1145
void mutt_seqset_iterator_free(struct SeqsetIterator **ptr)
Free a Sequence Set Iterator.
Definition: util.c:1204
@ LL_DEBUG1
Log at debug level 1.
Definition: logging2.h:44
void imap_msn_remove(struct MSNArray *msn, int idx)
Remove an entry from the cache.
Definition: msn.c:116
ImapOpenFlags reopen
Flags, e.g. IMAP_REOPEN_ALLOW.
Definition: mdata.h:45
struct HashTable * uid_hash
Hash Table: "uid" -> Email.
Definition: mdata.h:59
UID Sequence Set Iterator.
Definition: private.h:169
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ cmd_parse_fetch()

static void cmd_parse_fetch ( struct ImapAccountData adata,
char *  s 
)
static

Load fetch response into ImapAccountData.

Parameters
adataImap Account data
sString containing MSN of message to fetch

Currently only handles unanticipated FETCH responses, and only FLAGS data. We get these if another client has changed flags for a mailbox we've selected. Of course, a lot of code here duplicates code in message.c.

Definition at line 399 of file command.c.

400{
401 unsigned int msn, uid;
402 struct Email *e = NULL;
403 char *flags = NULL;
404 int uid_checked = 0;
405 bool server_changes = false;
406
407 struct ImapMboxData *mdata = imap_mdata_get(adata->mailbox);
408
409 mutt_debug(LL_DEBUG3, "Handling FETCH\n");
410
411 if (!mutt_str_atoui(s, &msn))
412 {
413 mutt_debug(LL_DEBUG3, "Skipping FETCH response - illegal MSN\n");
414 return;
415 }
416
417 if ((msn < 1) || (msn > imap_msn_highest(&mdata->msn)))
418 {
419 mutt_debug(LL_DEBUG3, "Skipping FETCH response - MSN %u out of range\n", msn);
420 return;
421 }
422
423 e = imap_msn_get(&mdata->msn, msn - 1);
424 if (!e || !e->active)
425 {
426 mutt_debug(LL_DEBUG3, "Skipping FETCH response - MSN %u not in msn_index\n", msn);
427 return;
428 }
429
430 mutt_debug(LL_DEBUG2, "Message UID %u updated\n", imap_edata_get(e)->uid);
431 /* skip FETCH */
432 s = imap_next_word(s);
433 s = imap_next_word(s);
434
435 if (*s != '(')
436 {
437 mutt_debug(LL_DEBUG1, "Malformed FETCH response\n");
438 return;
439 }
440 s++;
441
442 while (*s)
443 {
444 SKIPWS(s);
445 size_t plen = mutt_istr_startswith(s, "FLAGS");
446 if (plen != 0)
447 {
448 flags = s;
449 if (uid_checked)
450 break;
451
452 s += plen;
453 SKIPWS(s);
454 if (*s != '(')
455 {
456 mutt_debug(LL_DEBUG1, "bogus FLAGS response: %s\n", s);
457 return;
458 }
459 s++;
460 while (*s && (*s != ')'))
461 s++;
462 if (*s == ')')
463 {
464 s++;
465 }
466 else
467 {
468 mutt_debug(LL_DEBUG1, "Unterminated FLAGS response: %s\n", s);
469 return;
470 }
471 }
472 else if ((plen = mutt_istr_startswith(s, "UID")))
473 {
474 s += plen;
475 SKIPWS(s);
476 if (!mutt_str_atoui(s, &uid))
477 {
478 mutt_debug(LL_DEBUG1, "Illegal UID. Skipping update\n");
479 return;
480 }
481 if (uid != imap_edata_get(e)->uid)
482 {
483 mutt_debug(LL_DEBUG1, "UID vs MSN mismatch. Skipping update\n");
484 return;
485 }
486 uid_checked = 1;
487 if (flags)
488 break;
489 s = imap_next_word(s);
490 }
491 else if ((plen = mutt_istr_startswith(s, "MODSEQ")))
492 {
493 s += plen;
494 SKIPWS(s);
495 if (*s != '(')
496 {
497 mutt_debug(LL_DEBUG1, "bogus MODSEQ response: %s\n", s);
498 return;
499 }
500 s++;
501 while (*s && (*s != ')'))
502 s++;
503 if (*s == ')')
504 {
505 s++;
506 }
507 else
508 {
509 mutt_debug(LL_DEBUG1, "Unterminated MODSEQ response: %s\n", s);
510 return;
511 }
512 }
513 else if (*s == ')')
514 {
515 break; /* end of request */
516 }
517 else if (*s)
518 {
519 mutt_debug(LL_DEBUG2, "Only handle FLAGS updates\n");
520 break;
521 }
522 }
523
524 if (flags)
525 {
526 imap_set_flags(adata->mailbox, e, flags, &server_changes);
527 if (server_changes)
528 {
529 /* If server flags could conflict with NeoMutt's flags, reopen the mailbox. */
530 if (e->changed)
531 mdata->reopen |= IMAP_EXPUNGE_PENDING;
532 else
533 mdata->check_status |= IMAP_FLAGS_PENDING;
534 }
535 }
536}
struct ImapMboxData * imap_mdata_get(struct Mailbox *m)
Get the Mailbox data for this mailbox.
Definition: mdata.c:61
char * imap_set_flags(struct Mailbox *m, struct Email *e, char *s, bool *server_changes)
Fill the message header according to the server flags.
Definition: message.c:1919
#define IMAP_FLAGS_PENDING
Flags have changed on the server.
Definition: private.h:68
#define SKIPWS(ch)
Definition: string2.h:44
bool active
Message is not to be removed.
Definition: email.h:76
bool changed
Email has been edited.
Definition: email.h:77
struct ListHead flags
Definition: mdata.h:50
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ cmd_parse_capability()

static void cmd_parse_capability ( struct ImapAccountData adata,
char *  s 
)
static

Set capability bits according to CAPABILITY response.

Parameters
adataImap Account data
sCommand string with capabilities

Definition at line 543 of file command.c.

544{
545 mutt_debug(LL_DEBUG3, "Handling CAPABILITY\n");
546
547 s = imap_next_word(s);
548 char *bracket = strchr(s, ']');
549 if (bracket)
550 *bracket = '\0';
551 FREE(&adata->capstr);
552 adata->capstr = mutt_str_dup(s);
553 adata->capabilities = 0;
554
555 while (*s)
556 {
557 for (size_t i = 0; Capabilities[i]; i++)
558 {
559 size_t len = mutt_istr_startswith(s, Capabilities[i]);
560 if (len != 0 && ((s[len] == '\0') || mutt_isspace(s[len])))
561 {
562 adata->capabilities |= (1 << i);
563 mutt_debug(LL_DEBUG3, " Found capability \"%s\": %zu\n", Capabilities[i], i);
564 break;
565 }
566 }
567 s = imap_next_word(s);
568 }
569}
bool mutt_isspace(int arg)
Wrapper for isspace(3)
Definition: ctype.c:95
static const char *const Capabilities[]
Server capabilities strings that we understand.
Definition: command.c:66
#define FREE(x)
Definition: memory.h:62
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:254
ImapCapFlags capabilities
Capability flags.
Definition: adata.h:55
char * capstr
Capability string from the server.
Definition: adata.h:54
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ cmd_parse_list()

static void cmd_parse_list ( struct ImapAccountData adata,
char *  s 
)
static

Parse a server LIST command (list mailboxes)

Parameters
adataImap Account data
sCommand string with folder list

Definition at line 576 of file command.c.

577{
578 struct ImapList *list = NULL;
579 struct ImapList lb = { 0 };
580 unsigned int litlen;
581
582 if (adata->cmdresult)
583 list = adata->cmdresult;
584 else
585 list = &lb;
586
587 memset(list, 0, sizeof(struct ImapList));
588
589 /* flags */
590 s = imap_next_word(s);
591 if (*s != '(')
592 {
593 mutt_debug(LL_DEBUG1, "Bad LIST response\n");
594 return;
595 }
596 s++;
597 while (*s)
598 {
599 if (mutt_istr_startswith(s, "\\NoSelect"))
600 list->noselect = true;
601 else if (mutt_istr_startswith(s, "\\NonExistent")) /* rfc5258 */
602 list->noselect = true;
603 else if (mutt_istr_startswith(s, "\\NoInferiors"))
604 list->noinferiors = true;
605 else if (mutt_istr_startswith(s, "\\HasNoChildren")) /* rfc5258*/
606 list->noinferiors = true;
607
608 s = imap_next_word(s);
609 if (*(s - 2) == ')')
610 break;
611 }
612
613 /* Delimiter */
614 if (!mutt_istr_startswith(s, "NIL"))
615 {
616 char delimbuf[5] = { 0 }; // worst case: "\\"\0
617 snprintf(delimbuf, sizeof(delimbuf), "%s", s);
618 imap_unquote_string(delimbuf);
619 list->delim = delimbuf[0];
620 }
621
622 /* Name */
623 s = imap_next_word(s);
624 /* Notes often responds with literals here. We need a real tokenizer. */
625 if (imap_get_literal_count(s, &litlen) == 0)
626 {
627 if (imap_cmd_step(adata) != IMAP_RES_CONTINUE)
628 {
629 adata->status = IMAP_FATAL;
630 return;
631 }
632
633 if (strlen(adata->buf) < litlen)
634 {
635 mutt_debug(LL_DEBUG1, "Error parsing LIST mailbox\n");
636 return;
637 }
638
639 list->name = adata->buf;
640 s = list->name + litlen;
641 if (s[0] != '\0')
642 {
643 s[0] = '\0';
644 s++;
645 SKIPWS(s);
646 }
647 }
648 else
649 {
650 list->name = s;
651 /* Exclude rfc5258 RECURSIVEMATCH CHILDINFO suffix */
652 s = imap_next_word(s);
653 if (s[0] != '\0')
654 s[-1] = '\0';
655 imap_unmunge_mbox_name(adata->unicode, list->name);
656 }
657
658 if (list->name[0] == '\0')
659 {
660 adata->delim = list->delim;
661 mutt_debug(LL_DEBUG3, "Root delimiter: %c\n", adata->delim);
662 }
663}
int imap_cmd_step(struct ImapAccountData *adata)
Reads server responses from an IMAP command.
Definition: command.c:1127
void imap_unmunge_mbox_name(bool unicode, char *s)
Remove quoting from a mailbox name.
Definition: util.c:976
int imap_get_literal_count(const char *buf, unsigned int *bytes)
Write number of bytes in an IMAP literal into bytes.
Definition: util.c:779
void imap_unquote_string(char *s)
Equally stupid unquoting routine.
Definition: util.c:922
#define IMAP_RES_CONTINUE
* ...
Definition: private.h:56
char delim
Path delimiter.
Definition: adata.h:75
struct ImapList * cmdresult
Definition: adata.h:66
bool unicode
If true, we can send UTF-8, and the server will use UTF8 rather than mUTF7.
Definition: adata.h:62
char * buf
Definition: adata.h:59
Items in an IMAP browser.
Definition: private.h:149
bool noselect
Definition: private.h:152
bool noinferiors
Definition: private.h:153
char * name
Definition: private.h:150
char delim
Definition: private.h:151
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ cmd_parse_lsub()

static void cmd_parse_lsub ( struct ImapAccountData adata,
char *  s 
)
static

Parse a server LSUB (list subscribed mailboxes)

Parameters
adataImap Account data
sCommand string with folder list

Definition at line 670 of file command.c.

671{
672 if (adata->cmdresult)
673 {
674 /* caller will handle response itself */
675 cmd_parse_list(adata, s);
676 return;
677 }
678
679 const bool c_imap_check_subscribed = cs_subset_bool(NeoMutt->sub, "imap_check_subscribed");
680 if (!c_imap_check_subscribed)
681 return;
682
683 struct ImapList list = { 0 };
684
685 adata->cmdresult = &list;
686 cmd_parse_list(adata, s);
687 adata->cmdresult = NULL;
688 /* noselect is for a gmail quirk */
689 if (!list.name || list.noselect)
690 return;
691
692 mutt_debug(LL_DEBUG3, "Subscribing to %s\n", list.name);
693
694 struct Buffer *buf = buf_pool_get();
695 struct Buffer *err = buf_pool_get();
696 struct Url url = { 0 };
697
698 account_to_url(&adata->conn->account, &url);
699 url.path = list.name;
700
701 const char *const c_imap_user = cs_subset_string(NeoMutt->sub, "imap_user");
702 if (mutt_str_equal(url.user, c_imap_user))
703 url.user = NULL;
704 url_tobuffer(&url, buf, U_NO_FLAGS);
705
706 if (!mailbox_add_simple(buf_string(buf), err))
707 mutt_debug(LL_DEBUG1, "Error adding subscribed mailbox: %s\n", buf_string(err));
708
709 buf_pool_release(&buf);
710 buf_pool_release(&err);
711}
static const char * buf_string(const struct Buffer *buf)
Convert a buffer to a const char * "string".
Definition: buffer.h:96
bool mailbox_add_simple(const char *mailbox, struct Buffer *err)
Add a new Mailbox.
Definition: commands.c:741
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
static void cmd_parse_list(struct ImapAccountData *adata, char *s)
Parse a server LIST command (list mailboxes)
Definition: command.c:576
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:659
void account_to_url(struct ConnAccount *cac, struct Url *url)
Fill URL with info from account.
Definition: mutt_account.c:80
struct Buffer * buf_pool_get(void)
Get a Buffer from the pool.
Definition: pool.c:82
void buf_pool_release(struct Buffer **ptr)
Return a Buffer to the pool.
Definition: pool.c:96
String manipulation buffer.
Definition: buffer.h:36
Container for Accounts, Notifications.
Definition: neomutt.h:43
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:47
A parsed URL proto://user:password@host:port/path?a=1&b=2
Definition: url.h:69
char * user
Username.
Definition: url.h:71
char * path
Path.
Definition: url.h:75
int url_tobuffer(const struct Url *url, struct Buffer *buf, uint8_t flags)
Output the URL string for a given Url object.
Definition: url.c:357
#define U_NO_FLAGS
Definition: url.h:49
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ cmd_parse_myrights()

static void cmd_parse_myrights ( struct ImapAccountData adata,
const char *  s 
)
static

Set rights bits according to MYRIGHTS response.

Parameters
adataImap Account data
sCommand string with rights info

Definition at line 718 of file command.c.

719{
720 mutt_debug(LL_DEBUG2, "Handling MYRIGHTS\n");
721
722 s = imap_next_word((char *) s);
723 s = imap_next_word((char *) s);
724
725 /* zero out current rights set */
726 adata->mailbox->rights = 0;
727
728 while (*s && !mutt_isspace(*s))
729 {
730 switch (*s)
731 {
732 case 'a':
733 adata->mailbox->rights |= MUTT_ACL_ADMIN;
734 break;
735 case 'e':
737 break;
738 case 'i':
739 adata->mailbox->rights |= MUTT_ACL_INSERT;
740 break;
741 case 'k':
742 adata->mailbox->rights |= MUTT_ACL_CREATE;
743 break;
744 case 'l':
745 adata->mailbox->rights |= MUTT_ACL_LOOKUP;
746 break;
747 case 'p':
748 adata->mailbox->rights |= MUTT_ACL_POST;
749 break;
750 case 'r':
751 adata->mailbox->rights |= MUTT_ACL_READ;
752 break;
753 case 's':
754 adata->mailbox->rights |= MUTT_ACL_SEEN;
755 break;
756 case 't':
757 adata->mailbox->rights |= MUTT_ACL_DELETE;
758 break;
759 case 'w':
760 adata->mailbox->rights |= MUTT_ACL_WRITE;
761 break;
762 case 'x':
763 adata->mailbox->rights |= MUTT_ACL_DELMX;
764 break;
765
766 /* obsolete rights */
767 case 'c':
769 break;
770 case 'd':
772 break;
773 default:
774 mutt_debug(LL_DEBUG1, "Unknown right: %c\n", *s);
775 }
776 s++;
777 }
778}
#define MUTT_ACL_CREATE
Create a mailbox.
Definition: mailbox.h:62
#define MUTT_ACL_DELMX
Delete a mailbox.
Definition: mailbox.h:64
#define MUTT_ACL_POST
Post (submit messages to the server)
Definition: mailbox.h:68
#define MUTT_ACL_LOOKUP
Lookup mailbox (visible to 'list')
Definition: mailbox.h:67
#define MUTT_ACL_INSERT
Add/copy into the mailbox (used when editing a message)
Definition: mailbox.h:66
#define MUTT_ACL_DELETE
Delete a message.
Definition: mailbox.h:63
#define MUTT_ACL_EXPUNGE
Expunge messages.
Definition: mailbox.h:65
#define MUTT_ACL_WRITE
Write to a message (for flagging or linking threads)
Definition: mailbox.h:71
#define MUTT_ACL_SEEN
Change the 'seen' status of a message.
Definition: mailbox.h:70
#define MUTT_ACL_ADMIN
Administer the account (get/set permissions)
Definition: mailbox.h:61
#define MUTT_ACL_READ
Read the mailbox.
Definition: mailbox.h:69
AclFlags rights
ACL bits, see AclFlags.
Definition: mailbox.h:119
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ find_mailbox()

static struct Mailbox * find_mailbox ( struct ImapAccountData adata,
const char *  name 
)
static

Find a Mailbox by its name.

Parameters
adataImap Account data
nameMailbox to find
Return values
ptrMailbox

Definition at line 786 of file command.c.

787{
788 if (!adata || !adata->account || !name)
789 return NULL;
790
791 struct MailboxNode *np = NULL;
792 STAILQ_FOREACH(np, &adata->account->mailboxes, entries)
793 {
795 if (mutt_str_equal(name, mdata->name))
796 return np->mailbox;
797 }
798
799 return NULL;
800}
#define STAILQ_FOREACH(var, head, field)
Definition: queue.h:390
struct MailboxList mailboxes
List of Mailboxes.
Definition: account.h:40
struct Account * account
Parent Account.
Definition: adata.h:78
char * name
Mailbox name.
Definition: mdata.h:41
List of Mailboxes.
Definition: mailbox.h:166
struct Mailbox * mailbox
Mailbox in the list.
Definition: mailbox.h:167
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ cmd_parse_status()

static void cmd_parse_status ( struct ImapAccountData adata,
char *  s 
)
static

Parse status from server.

Parameters
adataImap Account data
sCommand string with status info

first cut: just do mailbox update. Later we may wish to cache all mailbox information, even that not desired by mailbox

Definition at line 810 of file command.c.

811{
812 unsigned int litlen = 0;
813
814 char *mailbox = imap_next_word(s);
815
816 /* We need a real tokenizer. */
817 if (imap_get_literal_count(mailbox, &litlen) == 0)
818 {
819 if (imap_cmd_step(adata) != IMAP_RES_CONTINUE)
820 {
821 adata->status = IMAP_FATAL;
822 return;
823 }
824
825 if (strlen(adata->buf) < litlen)
826 {
827 mutt_debug(LL_DEBUG1, "Error parsing STATUS mailbox\n");
828 return;
829 }
830
831 mailbox = adata->buf;
832 s = mailbox + litlen;
833 s[0] = '\0';
834 s++;
835 SKIPWS(s);
836 }
837 else
838 {
839 s = imap_next_word(mailbox);
840 s[-1] = '\0';
841 imap_unmunge_mbox_name(adata->unicode, mailbox);
842 }
843
844 struct Mailbox *m = find_mailbox(adata, mailbox);
845 struct ImapMboxData *mdata = imap_mdata_get(m);
846 if (!mdata)
847 {
848 mutt_debug(LL_DEBUG3, "Received status for an unexpected mailbox: %s\n", mailbox);
849 return;
850 }
851 uint32_t olduv = mdata->uidvalidity;
852 unsigned int oldun = mdata->uid_next;
853
854 if (*s++ != '(')
855 {
856 mutt_debug(LL_DEBUG1, "Error parsing STATUS\n");
857 return;
858 }
859 while ((s[0] != '\0') && (s[0] != ')'))
860 {
861 char *value = imap_next_word(s);
862
863 errno = 0;
864 const unsigned long ulcount = strtoul(value, &value, 10);
865 const bool truncated = ((errno == ERANGE) && (ulcount == ULONG_MAX)) ||
866 ((unsigned int) ulcount != ulcount);
867 const unsigned int count = (unsigned int) ulcount;
868
869 // we accept truncating a larger value only for UIDVALIDITY, to accommodate
870 // IMAP servers that use 64-bits for it. This seems to be what Thunderbird
871 // is also doing, see #3830
872 if (mutt_str_startswith(s, "UIDVALIDITY"))
873 {
874 if (truncated)
875 {
877 "UIDVALIDITY [%lu] exceeds 32 bits, "
878 "truncated to [%u]\n",
879 ulcount, count);
880 }
881 mdata->uidvalidity = count;
882 }
883 else
884 {
885 if (truncated)
886 {
887 mutt_debug(LL_DEBUG1, "Number in [%s] exceeds 32 bits\n", s);
888 return;
889 }
890 else
891 {
892 if (mutt_str_startswith(s, "MESSAGES"))
893 mdata->messages = count;
894 else if (mutt_str_startswith(s, "RECENT"))
895 mdata->recent = count;
896 else if (mutt_str_startswith(s, "UIDNEXT"))
897 mdata->uid_next = count;
898 else if (mutt_str_startswith(s, "UNSEEN"))
899 mdata->unseen = count;
900 }
901 }
902
903 s = value;
904 if ((s[0] != '\0') && (*s != ')'))
905 s = imap_next_word(s);
906 }
907 mutt_debug(LL_DEBUG3, "%s (UIDVALIDITY: %u, UIDNEXT: %u) %d messages, %d recent, %d unseen\n",
908 mdata->name, mdata->uidvalidity, mdata->uid_next, mdata->messages,
909 mdata->recent, mdata->unseen);
910
911 mutt_debug(LL_DEBUG3, "Running default STATUS handler\n");
912
913 mutt_debug(LL_DEBUG3, "Found %s in mailbox list (OV: %u ON: %u U: %d)\n",
914 mailbox, olduv, oldun, mdata->unseen);
915
916 bool new_mail = false;
917 const bool c_mail_check_recent = cs_subset_bool(NeoMutt->sub, "mail_check_recent");
918 if (c_mail_check_recent)
919 {
920 if ((olduv != 0) && (olduv == mdata->uidvalidity))
921 {
922 if (oldun < mdata->uid_next)
923 new_mail = (mdata->unseen > 0);
924 }
925 else if ((olduv == 0) && (oldun == 0))
926 {
927 /* first check per session, use recent. might need a flag for this. */
928 new_mail = (mdata->recent > 0);
929 }
930 else
931 {
932 new_mail = (mdata->unseen > 0);
933 }
934 }
935 else
936 {
937 new_mail = (mdata->unseen > 0);
938 }
939
940 m->has_new = new_mail;
941 m->msg_count = mdata->messages;
942 m->msg_unread = mdata->unseen;
943
944 // force back to keep detecting new mail until the mailbox is opened
945 if (m->has_new)
946 mdata->uid_next = oldun;
947
948 struct EventMailbox ev_m = { m };
950}
@ NT_MAILBOX_CHANGE
Mailbox has been changed.
Definition: mailbox.h:185
static struct Mailbox * find_mailbox(struct ImapAccountData *adata, const char *name)
Find a Mailbox by its name.
Definition: command.c:786
bool notify_send(struct Notify *notify, enum NotifyType event_type, int event_subtype, void *event_data)
Send out a notification message.
Definition: notify.c:173
size_t mutt_str_startswith(const char *str, const char *prefix)
Check whether a string starts with a prefix.
Definition: string.c:231
@ NT_MAILBOX
Mailbox has changed, NotifyMailbox, EventMailbox.
Definition: notify_type.h:49
An Event that happened to a Mailbox.
Definition: mailbox.h:199
unsigned int uid_next
Definition: mdata.h:52
A mailbox.
Definition: mailbox.h:79
bool has_new
Mailbox has new mail.
Definition: mailbox.h:85
int msg_count
Total number of messages.
Definition: mailbox.h:88
struct Notify * notify
Notifications: NotifyMailbox, EventMailbox.
Definition: mailbox.h:145
int msg_unread
Number of unread messages.
Definition: mailbox.h:89
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ cmd_parse_enabled()

static void cmd_parse_enabled ( struct ImapAccountData adata,
const char *  s 
)
static

Record what the server has enabled.

Parameters
adataImap Account data
sCommand string containing acceptable encodings

Definition at line 957 of file command.c.

958{
959 mutt_debug(LL_DEBUG2, "Handling ENABLED\n");
960
961 while ((s = imap_next_word((char *) s)) && (*s != '\0'))
962 {
963 if (mutt_istr_startswith(s, "UTF8=ACCEPT") || mutt_istr_startswith(s, "UTF8=ONLY"))
964 {
965 adata->unicode = true;
966 }
967 if (mutt_istr_startswith(s, "QRESYNC"))
968 adata->qresync = true;
969 }
970}
bool qresync
true, if QRESYNC is successfully ENABLE'd
Definition: adata.h:63
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ cmd_parse_exists()

static void cmd_parse_exists ( struct ImapAccountData adata,
const char *  pn 
)
static

Parse EXISTS message from serer.

Parameters
adataImap Account data
pnString containing the total number of messages for the selected mailbox

Definition at line 977 of file command.c.

978{
979 unsigned int count = 0;
980 mutt_debug(LL_DEBUG2, "Handling EXISTS\n");
981
982 if (!mutt_str_atoui(pn, &count))
983 {
984 mutt_debug(LL_DEBUG1, "Malformed EXISTS: '%s'\n", pn);
985 return;
986 }
987
988 struct ImapMboxData *mdata = adata->mailbox->mdata;
989
990 /* new mail arrived */
991 if (count < imap_msn_highest(&mdata->msn))
992 {
993 /* Notes 6.0.3 has a tendency to report fewer messages exist than
994 * it should. */
995 mutt_debug(LL_DEBUG1, "Message count is out of sync\n");
996 }
997 else if (count == imap_msn_highest(&mdata->msn))
998 {
999 /* at least the InterChange server sends EXISTS messages freely,
1000 * even when there is no new mail */
1001 mutt_debug(LL_DEBUG3, "superfluous EXISTS message\n");
1002 }
1003 else
1004 {
1005 mutt_debug(LL_DEBUG2, "New mail in %s - %d messages total\n", mdata->name, count);
1006 mdata->reopen |= IMAP_NEWMAIL_PENDING;
1007 mdata->new_mail_count = count;
1008 }
1009}
#define IMAP_NEWMAIL_PENDING
New mail is waiting on the server.
Definition: private.h:67
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ cmd_handle_untagged()

static int cmd_handle_untagged ( struct ImapAccountData adata)
static

Fallback parser for otherwise unhandled messages.

Parameters
adataImap Account data
Return values
0Success
-1Failure

Definition at line 1017 of file command.c.

1018{
1019 char *s = imap_next_word(adata->buf);
1020 char *pn = imap_next_word(s);
1021
1022 const bool c_imap_server_noise = cs_subset_bool(NeoMutt->sub, "imap_server_noise");
1023 if ((adata->state >= IMAP_SELECTED) && mutt_isdigit(*s))
1024 {
1025 /* pn vs. s: need initial seqno */
1026 pn = s;
1027 s = imap_next_word(s);
1028
1029 /* EXISTS, EXPUNGE, FETCH are always related to the SELECTED mailbox */
1030 if (mutt_istr_startswith(s, "EXISTS"))
1031 cmd_parse_exists(adata, pn);
1032 else if (mutt_istr_startswith(s, "EXPUNGE"))
1033 cmd_parse_expunge(adata, pn);
1034 else if (mutt_istr_startswith(s, "FETCH"))
1035 cmd_parse_fetch(adata, pn);
1036 }
1037 else if ((adata->state >= IMAP_SELECTED) && mutt_istr_startswith(s, "VANISHED"))
1038 {
1039 cmd_parse_vanished(adata, pn);
1040 }
1041 else if (mutt_istr_startswith(s, "CAPABILITY"))
1042 {
1043 cmd_parse_capability(adata, s);
1044 }
1045 else if (mutt_istr_startswith(s, "OK [CAPABILITY"))
1046 {
1047 cmd_parse_capability(adata, pn);
1048 }
1049 else if (mutt_istr_startswith(pn, "OK [CAPABILITY"))
1050 {
1052 }
1053 else if (mutt_istr_startswith(s, "LIST"))
1054 {
1055 cmd_parse_list(adata, s);
1056 }
1057 else if (mutt_istr_startswith(s, "LSUB"))
1058 {
1059 cmd_parse_lsub(adata, s);
1060 }
1061 else if (mutt_istr_startswith(s, "MYRIGHTS"))
1062 {
1063 cmd_parse_myrights(adata, s);
1064 }
1065 else if (mutt_istr_startswith(s, "SEARCH"))
1066 {
1067 cmd_parse_search(adata, s);
1068 }
1069 else if (mutt_istr_startswith(s, "STATUS"))
1070 {
1071 cmd_parse_status(adata, s);
1072 }
1073 else if (mutt_istr_startswith(s, "ENABLED"))
1074 {
1075 cmd_parse_enabled(adata, s);
1076 }
1077 else if (mutt_istr_startswith(s, "BYE"))
1078 {
1079 mutt_debug(LL_DEBUG2, "Handling BYE\n");
1080
1081 /* check if we're logging out */
1082 if (adata->status == IMAP_BYE)
1083 return 0;
1084
1085 /* server shut down our connection */
1086 s += 3;
1087 SKIPWS(s);
1088 mutt_error("%s", s);
1089 cmd_handle_fatal(adata);
1090
1091 return -1;
1092 }
1093 else if (c_imap_server_noise && mutt_istr_startswith(s, "NO"))
1094 {
1095 mutt_debug(LL_DEBUG2, "Handling untagged NO\n");
1096
1097 /* Display the warning message from the server */
1098 mutt_error("%s", s + 2);
1099 }
1100
1101 return 0;
1102}
bool mutt_isdigit(int arg)
Wrapper for isdigit(3)
Definition: ctype.c:65
static void cmd_parse_capability(struct ImapAccountData *adata, char *s)
Set capability bits according to CAPABILITY response.
Definition: command.c:543
static void cmd_parse_lsub(struct ImapAccountData *adata, char *s)
Parse a server LSUB (list subscribed mailboxes)
Definition: command.c:670
static void cmd_parse_status(struct ImapAccountData *adata, char *s)
Parse status from server.
Definition: command.c:810
static void cmd_parse_exists(struct ImapAccountData *adata, const char *pn)
Parse EXISTS message from serer.
Definition: command.c:977
static void cmd_parse_myrights(struct ImapAccountData *adata, const char *s)
Set rights bits according to MYRIGHTS response.
Definition: command.c:718
static void cmd_parse_expunge(struct ImapAccountData *adata, const char *s)
Parse expunge command.
Definition: command.c:258
static void cmd_parse_enabled(struct ImapAccountData *adata, const char *s)
Record what the server has enabled.
Definition: command.c:957
static void cmd_parse_vanished(struct ImapAccountData *adata, char *s)
Parse vanished command.
Definition: command.c:305
static void cmd_parse_fetch(struct ImapAccountData *adata, char *s)
Load fetch response into ImapAccountData.
Definition: command.c:399
@ IMAP_BYE
Logged out from server.
Definition: private.h:96
void cmd_parse_search(struct ImapAccountData *adata, const char *s)
Store SEARCH response for later use.
Definition: search.c:256
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ imap_cmd_start()

int imap_cmd_start ( struct ImapAccountData adata,
const char *  cmdstr 
)

Given an IMAP command, send it to the server.

Parameters
adataImap Account data
cmdstrCommand string to send
Return values
0Success
<0Failure, e.g. IMAP_RES_BAD

If cmdstr is NULL, sends queued commands.

Definition at line 1113 of file command.c.

1114{
1115 return cmd_start(adata, cmdstr, IMAP_CMD_NO_FLAGS);
1116}
static int cmd_start(struct ImapAccountData *adata, const char *cmdstr, ImapCmdFlags flags)
Start a new IMAP command.
Definition: command.c:202
#define IMAP_CMD_NO_FLAGS
No flags are set.
Definition: private.h:71
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ imap_cmd_step()

int imap_cmd_step ( struct ImapAccountData adata)

Reads server responses from an IMAP command.

Parameters
adataImap Account data
Return values
0Success
<0Failure, e.g. IMAP_RES_BAD

detects tagged completion response, handles untagged messages, can read arbitrarily large strings (using malloc, so don't make it too large!).

Definition at line 1127 of file command.c.

1128{
1129 if (!adata)
1130 return -1;
1131
1132 size_t len = 0;
1133 int c;
1134 int rc;
1135 int stillrunning = 0;
1136 struct ImapCommand *cmd = NULL;
1137
1138 if (adata->status == IMAP_FATAL)
1139 {
1140 cmd_handle_fatal(adata);
1141 return IMAP_RES_BAD;
1142 }
1143
1144 /* read into buffer, expanding buffer as necessary until we have a full
1145 * line */
1146 do
1147 {
1148 if (len == adata->blen)
1149 {
1150 MUTT_MEM_REALLOC(&adata->buf, adata->blen + IMAP_CMD_BUFSIZE, char);
1151 adata->blen = adata->blen + IMAP_CMD_BUFSIZE;
1152 mutt_debug(LL_DEBUG3, "grew buffer to %zu bytes\n", adata->blen);
1153 }
1154
1155 /* back up over '\0' */
1156 if (len)
1157 len--;
1158 c = mutt_socket_readln_d(adata->buf + len, adata->blen - len, adata->conn, MUTT_SOCK_LOG_FULL);
1159 if (c <= 0)
1160 {
1161 mutt_debug(LL_DEBUG1, "Error reading server response\n");
1162 cmd_handle_fatal(adata);
1163 return IMAP_RES_BAD;
1164 }
1165
1166 len += c;
1167 }
1168 /* if we've read all the way to the end of the buffer, we haven't read a
1169 * full line (mutt_socket_readln strips the \r, so we always have at least
1170 * one character free when we've read a full line) */
1171 while (len == adata->blen);
1172
1173 /* don't let one large string make cmd->buf hog memory forever */
1174 if ((adata->blen > IMAP_CMD_BUFSIZE) && (len <= IMAP_CMD_BUFSIZE))
1175 {
1176 MUTT_MEM_REALLOC(&adata->buf, IMAP_CMD_BUFSIZE, char);
1177 adata->blen = IMAP_CMD_BUFSIZE;
1178 mutt_debug(LL_DEBUG3, "shrank buffer to %zu bytes\n", adata->blen);
1179 }
1180
1181 adata->lastread = mutt_date_now();
1182
1183 /* handle untagged messages. The caller still gets its shot afterwards. */
1184 if ((mutt_str_startswith(adata->buf, "* ") ||
1185 mutt_str_startswith(imap_next_word(adata->buf), "OK [")) &&
1186 cmd_handle_untagged(adata))
1187 {
1188 return IMAP_RES_BAD;
1189 }
1190
1191 /* server demands a continuation response from us */
1192 if (adata->buf[0] == '+')
1193 return IMAP_RES_RESPOND;
1194
1195 /* Look for tagged command completions.
1196 *
1197 * Some response handlers can end up recursively calling
1198 * imap_cmd_step() and end up handling all tagged command
1199 * completions.
1200 * (e.g. FETCH->set_flag->set_header_color->~h pattern match.)
1201 *
1202 * Other callers don't even create an adata->cmds entry.
1203 *
1204 * For both these cases, we default to returning OK */
1205 rc = IMAP_RES_OK;
1206 c = adata->lastcmd;
1207 do
1208 {
1209 cmd = &adata->cmds[c];
1210 if (cmd->state == IMAP_RES_NEW)
1211 {
1212 if (mutt_str_startswith(adata->buf, cmd->seq))
1213 {
1214 if (!stillrunning)
1215 {
1216 /* first command in queue has finished - move queue pointer up */
1217 adata->lastcmd = (adata->lastcmd + 1) % adata->cmdslots;
1218 }
1219 cmd->state = cmd_status(adata->buf);
1220 rc = cmd->state;
1221 if (cmd->state == IMAP_RES_NO || cmd->state == IMAP_RES_BAD)
1222 {
1223 mutt_message(_("IMAP command failed: %s"), adata->buf);
1224 }
1225 }
1226 else
1227 {
1228 stillrunning++;
1229 }
1230 }
1231
1232 c = (c + 1) % adata->cmdslots;
1233 } while (c != adata->nextcmd);
1234
1235 if (stillrunning)
1236 {
1237 rc = IMAP_RES_CONTINUE;
1238 }
1239 else
1240 {
1241 mutt_debug(LL_DEBUG3, "IMAP queue drained\n");
1242 imap_cmd_finish(adata);
1243 }
1244
1245 return rc;
1246}
#define mutt_message(...)
Definition: logging2.h:92
static int cmd_handle_untagged(struct ImapAccountData *adata)
Fallback parser for otherwise unhandled messages.
Definition: command.c:1017
static int cmd_status(const char *s)
Parse response line for tagged OK/NO/BAD.
Definition: command.c:238
#define IMAP_CMD_BUFSIZE
Definition: command.c:59
void imap_cmd_finish(struct ImapAccountData *adata)
Attempt to perform cleanup.
Definition: command.c:1367
#define IMAP_RES_RESPOND
+
Definition: private.h:57
#define MUTT_MEM_REALLOC(pptr, n, type)
Definition: memory.h:50
time_t mutt_date_now(void)
Return the number of seconds since the Unix epoch.
Definition: date.c:456
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
time_t lastread
last time we read a command for the server
Definition: adata.h:58
size_t blen
Definition: adata.h:60
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ imap_code()

bool imap_code ( const char *  s)

Was the command successful.

Parameters
sIMAP command status
Return values
1Command result was OK
0NO or BAD

Definition at line 1254 of file command.c.

1255{
1256 return cmd_status(s) == IMAP_RES_OK;
1257}
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ imap_cmd_trailer()

const char * imap_cmd_trailer ( struct ImapAccountData adata)

Extra information after tagged command response if any.

Parameters
adataImap Account data
Return values
ptrExtra command information (pointer into adata->buf)
""Error (static string)

Definition at line 1265 of file command.c.

1266{
1267 static const char *notrailer = "";
1268 const char *s = adata->buf;
1269
1270 if (!s)
1271 {
1272 mutt_debug(LL_DEBUG2, "not a tagged response\n");
1273 return notrailer;
1274 }
1275
1276 s = imap_next_word((char *) s);
1277 if (!s || (!mutt_istr_startswith(s, "OK") && !mutt_istr_startswith(s, "NO") &&
1278 !mutt_istr_startswith(s, "BAD")))
1279 {
1280 mutt_debug(LL_DEBUG2, "not a command completion: %s\n", adata->buf);
1281 return notrailer;
1282 }
1283
1284 s = imap_next_word((char *) s);
1285 if (!s)
1286 return notrailer;
1287
1288 return s;
1289}
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ imap_exec()

int imap_exec ( struct ImapAccountData adata,
const char *  cmdstr,
ImapCmdFlags  flags 
)

Execute a command and wait for the response from the server.

Parameters
adataImap Account data
cmdstrCommand to execute
flagsFlags, see ImapCmdFlags
Return values
IMAP_EXEC_SUCCESSCommand successful or queued
IMAP_EXEC_ERRORCommand returned an error
IMAP_EXEC_FATALImap connection failure

Also, handle untagged responses.

Definition at line 1302 of file command.c.

1303{
1304 int rc;
1305
1306 if (flags & IMAP_CMD_SINGLE)
1307 {
1308 // Process any existing commands
1309 if (adata->nextcmd != adata->lastcmd)
1310 imap_exec(adata, NULL, IMAP_CMD_POLL);
1311 }
1312
1313 rc = cmd_start(adata, cmdstr, flags);
1314 if (rc < 0)
1315 {
1316 cmd_handle_fatal(adata);
1317 return IMAP_EXEC_FATAL;
1318 }
1319
1320 if (flags & IMAP_CMD_QUEUE)
1321 return IMAP_EXEC_SUCCESS;
1322
1323 const short c_imap_poll_timeout = cs_subset_number(NeoMutt->sub, "imap_poll_timeout");
1324 if ((flags & IMAP_CMD_POLL) && (c_imap_poll_timeout > 0) &&
1325 ((mutt_socket_poll(adata->conn, c_imap_poll_timeout)) == 0))
1326 {
1327 mutt_error(_("Connection to %s timed out"), adata->conn->account.host);
1328 cmd_handle_fatal(adata);
1329 return IMAP_EXEC_FATAL;
1330 }
1331
1332 /* Allow interruptions, particularly useful if there are network problems. */
1334 do
1335 {
1336 rc = imap_cmd_step(adata);
1337 // The queue is empty, so the single command has been processed
1338 if ((flags & IMAP_CMD_SINGLE) && (adata->nextcmd == adata->lastcmd))
1339 break;
1340 } while (rc == IMAP_RES_CONTINUE);
1342
1343 if (rc == IMAP_RES_NO)
1344 return IMAP_EXEC_ERROR;
1345 if (rc != IMAP_RES_OK)
1346 {
1347 if (adata->status != IMAP_FATAL)
1348 return IMAP_EXEC_ERROR;
1349
1350 mutt_debug(LL_DEBUG1, "command failed: %s\n", adata->buf);
1351 return IMAP_EXEC_FATAL;
1352 }
1353
1354 return IMAP_EXEC_SUCCESS;
1355}
short cs_subset_number(const struct ConfigSubset *sub, const char *name)
Get a number config item by name.
Definition: helpers.c:143
@ IMAP_EXEC_SUCCESS
Imap command executed or queued successfully.
Definition: private.h:82
@ IMAP_EXEC_FATAL
Imap connection failure.
Definition: private.h:84
#define IMAP_CMD_SINGLE
Run a single command.
Definition: private.h:75
void mutt_sig_allow_interrupt(bool allow)
Allow/disallow Ctrl-C (SIGINT)
Definition: signal.c:316
int mutt_socket_poll(struct Connection *conn, time_t wait_secs)
Checks whether reads would block.
Definition: socket.c:182
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ imap_cmd_finish()

void imap_cmd_finish ( struct ImapAccountData adata)

Attempt to perform cleanup.

Parameters
adataImap Account data

If a reopen is allowed, it attempts to perform cleanup (eg fetch new mail if detected, do expunge). Called automatically by imap_cmd_step(), but may be called at any time.

mdata->check_status is set and will be used later by imap_check_mailbox().

Definition at line 1367 of file command.c.

1368{
1369 if (!adata)
1370 return;
1371
1372 if (adata->status == IMAP_FATAL)
1373 {
1374 adata->closing = false;
1375 cmd_handle_fatal(adata);
1376 return;
1377 }
1378
1379 if (!(adata->state >= IMAP_SELECTED) || (adata->mailbox && adata->closing))
1380 {
1381 adata->closing = false;
1382 return;
1383 }
1384
1385 adata->closing = false;
1386
1387 struct ImapMboxData *mdata = imap_mdata_get(adata->mailbox);
1388
1389 if (mdata && mdata->reopen & IMAP_REOPEN_ALLOW)
1390 {
1391 // First remove expunged emails from the msn_index
1392 if (mdata->reopen & IMAP_EXPUNGE_PENDING)
1393 {
1394 mutt_debug(LL_DEBUG2, "Expunging mailbox\n");
1395 imap_expunge_mailbox(adata->mailbox, true);
1396 /* Detect whether we've gotten unexpected EXPUNGE messages */
1397 if (!(mdata->reopen & IMAP_EXPUNGE_EXPECTED))
1398 mdata->check_status |= IMAP_EXPUNGE_PENDING;
1400 }
1401
1402 // Then add new emails to it
1403 if (mdata->reopen & IMAP_NEWMAIL_PENDING)
1404 {
1405 const size_t max_msn = imap_msn_highest(&mdata->msn);
1406 if (mdata->new_mail_count > max_msn)
1407 {
1408 if (!(mdata->reopen & IMAP_EXPUNGE_PENDING))
1409 mdata->check_status |= IMAP_NEWMAIL_PENDING;
1410
1411 mutt_debug(LL_DEBUG2, "Fetching new mails from %zd to %u\n",
1412 max_msn + 1, mdata->new_mail_count);
1413 imap_read_headers(adata->mailbox, max_msn + 1, mdata->new_mail_count, false);
1414 }
1415 }
1416
1417 // And to finish inform about MUTT_REOPEN if needed
1418 if (mdata->reopen & IMAP_EXPUNGE_PENDING && !(mdata->reopen & IMAP_EXPUNGE_EXPECTED))
1419 mdata->check_status |= IMAP_EXPUNGE_PENDING;
1420
1421 if (mdata->reopen & IMAP_EXPUNGE_PENDING)
1423 }
1424
1425 adata->status = 0;
1426}
int imap_read_headers(struct Mailbox *m, unsigned int msn_begin, unsigned int msn_end, bool initial_download)
Read headers from the server.
Definition: message.c:1339
#define IMAP_EXPUNGE_EXPECTED
Messages will be expunged from the server.
Definition: private.h:65
void imap_expunge_mailbox(struct Mailbox *m, bool resort)
Purge messages from the server.
Definition: imap.c:671
bool closing
If true, we are waiting for CLOSE completion.
Definition: adata.h:43
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ imap_cmd_idle()

int imap_cmd_idle ( struct ImapAccountData adata)

Enter the IDLE state.

Parameters
adataImap Account data
Return values
0Success
<0Failure, e.g. IMAP_RES_BAD

Definition at line 1434 of file command.c.

1435{
1436 int rc;
1437
1438 if (cmd_start(adata, "IDLE", IMAP_CMD_POLL) < 0)
1439 {
1440 cmd_handle_fatal(adata);
1441 return -1;
1442 }
1443
1444 const short c_imap_poll_timeout = cs_subset_number(NeoMutt->sub, "imap_poll_timeout");
1445 if ((c_imap_poll_timeout > 0) &&
1446 ((mutt_socket_poll(adata->conn, c_imap_poll_timeout)) == 0))
1447 {
1448 mutt_error(_("Connection to %s timed out"), adata->conn->account.host);
1449 cmd_handle_fatal(adata);
1450 return -1;
1451 }
1452
1453 do
1454 {
1455 rc = imap_cmd_step(adata);
1456 } while (rc == IMAP_RES_CONTINUE);
1457
1458 if (rc == IMAP_RES_RESPOND)
1459 {
1460 /* successfully entered IDLE state */
1461 adata->state = IMAP_IDLE;
1462 /* queue automatic exit when next command is issued */
1463 buf_addstr(&adata->cmdbuf, "DONE\r\n");
1464 rc = IMAP_RES_OK;
1465 }
1466 if (rc != IMAP_RES_OK)
1467 {
1468 mutt_debug(LL_DEBUG1, "error starting IDLE\n");
1469 return -1;
1470 }
1471
1472 return 0;
1473}
size_t buf_addstr(struct Buffer *buf, const char *s)
Add a string to a Buffer.
Definition: buffer.c:226
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

Variable Documentation

◆ Capabilities

const char* const Capabilities[]
static
Initial value:
= {
"IMAP4",
"IMAP4rev1",
"STATUS",
"ACL",
"NAMESPACE",
"AUTH=CRAM-MD5",
"AUTH=GSSAPI",
"AUTH=ANONYMOUS",
"AUTH=OAUTHBEARER",
"AUTH=XOAUTH2",
"STARTTLS",
"LOGINDISABLED",
"IDLE",
"SASL-IR",
"ENABLE",
"CONDSTORE",
"QRESYNC",
"LIST-EXTENDED",
"COMPRESS=DEFLATE",
"X-GM-EXT-1",
"ID",
NULL,
}

Server capabilities strings that we understand.

Note
This must be kept in the same order as ImapCaps.

Definition at line 66 of file command.c.