Writing an custom imap command

Aki Tuomi aki.tuomi at open-xchange.com
Wed May 5 18:45:48 EEST 2021


> On 05/05/2021 18:28 Ryan Beethe <ryan at splintermail.com> wrote:
> 
>  
> On Wed, May 05, 2021 at 10:53:30AM +0300, Aki Tuomi wrote:
> >
> > > On 04/05/2021 16:42 Ryan Beethe <ryan at splintermail.com> wrote:
> > >
> > >
> > > On Mon, May 03, 2021 at 09:14:13AM +0300, Aki Tuomi wrote:
> > > >
> > > > > On 01/05/2021 18:32 Ryan Beethe <ryan at splintermail.com> wrote:
> > > > >
> > > > > 1. Why does cmd-idle.c sometimes call client_command_free()?  But
> > > > > sometimes it doesn't?
> > > > >
> > > > >     For example, cmd_idle_continue() frees it in some branches but not
> > > > >     others.  That makes no sense to me; it seems like it should be based
> > > > >     on your entrypoint (mailbox notify callback vs input callback vs
> > > > >     timeout callback), not based on which branch of logic within that
> > > > >     entrypoint.
> > > > >
> > > > > 2. Why does cmd-idle.c ever call client_destroy()?  That seems like
> > > > > something that should be invoked only by the imap process, not by any
> > > > > command.
> > > > >
> > > > >     It calls it in cmd-idle.c:idle_callback (which is a mailbox notify
> > > > >     callback).  It invokes it after idle_sync_now() when it detects that
> > > > >     client->disconnected is set.  Maybe that happens in imap_sync_init()
> > > > >     or something?
> > > > >
> > > > > 3. Why does cmd-idle.c ever call client_disconnect()?  That also seems
> > > > > like the responsibility of the imap process, and not any command.
> > > > >
> > > > >     idle_client_input_more() detects when i_stream_read returns -1,
> > > > >     meaning that the client has *already disconnected*.  Then it calls
> > > > >     client_disconnect().
> > > > >
> > > > >     I think this is the crazy part... the istream is effectively unique
> > > > >     to the imap process, so it seems unreasonable that any command is
> > > > >     responsible for cleaning it up; it should just always happen at the
> > > > >     imap process level before exiting, right?
> > > > >
> > > >
> > > > IDLE cmd can be sometimes delegated to a separate worker called imap-hibernate, in which case the connection is moved to another process. This explains about all your questions.
> > >
> > > Wait, but then why does APPEND also make each of these calls?  APPEND
> > > can't be hibernated, as far as I can tell?
> > >
> >
> > Because APPEND might need to read quite a lot of data from the client.
> 
> So then I am back at my original questions.  Maybe I can guess at some
> answers and you can tell me if I'm understanding correctly:
> 
>     1. Why does cmd-idle.c sometimes call client_command_free()? But
>     sometimes it doesn't?
> 
>         Earlier I said I though cmd_idle_continue() freed it in some
>         branches but not others, but I think I was mistaken.  It looks
>         like the only path where client_command_free is called is inside
>         an io_add_istream callback.  That makes sense, and I can do the
>         same thing with my command.
> 
>     2. Why does cmd-idle.c ever call client_destroy()?  That seems like
>     something that should be invoked only by the imap process, not by
>     any command.
> 
>         This is only ever triggered by idle_callback, which is a
>         mailbox_notify_changes callback, which I don't have to interact
>         with, so maybe I can ignore this.
> 
>     3. Why does cmd-idle.c ever call client_disconnect()?  That also
>     seems like the responsibility of the imap process, and not any
>     command.
> 
>         While I'm still not sure why the imap process is not responsible
>         for calling this, it does seem like it only gets called when
>         i_stream_read() returns -1, and I can probably immitate that
>         without much risk.
> 
>         But wait, why does cmd-idle.c call client_disconnect() when
>         i_stream_read() returns -1, but cmd-append.c calls
>         client_command_free() and client_destroy() but not
>         client_disconnect()?
> 
> > > > You probably shold look some much more simple commands as
> > > > insipiration. Try looking e.g. how cmd_id is implemented instead.
> > >
> > > I implemented a simpler command as well, but because it was simple I
> > > didn't have any questions :)
> > >
> > > Unfortunately I do need a long-running command more like IDLE as well.
> >
> > What kind of "long running command" did you have in mind?
> 
> My email service offers a layer of encryption which is not transparent
> to IMAP, and where the keys are created and kept on each client device.
> Since IMAP synchronization is bidirectional, each client needs to
> encrypt uploaded messages to all known client devices.  Thus, clients
> need a way update their list of all known keys.
> 
> So the command is roughly:
> 
>     tag XKEYSYNC [known_fingerprint ...]
>     ...
>     DONE
> 
> And the responses are rougly:
> 
>     * XKEYSYNC DELETED fingerprint
> 
>     * XKEYSYNC CREATED public_key
> 
> The full source can be found at:
> 
> github.com/splintermail/splintermail-client/blob/dev/server/xkeysync.c
> 
> Ryan

1-3 . IDLE is sometimes implemented by *imap-hibernate* process, which is a different process, so the connection is *moved* there (removed totally from imap process, imap process dies, and connection continues to live in imap-hibernate process). IDLE command is not the best to look for ideas, as it's pretty complicated thing.

You might want to check https://github.com/freswa/dovecot-xaps-plugin for ideas.

Aki


More information about the dovecot mailing list