summaryrefslogtreecommitdiff
path: root/mu/libexec/imap.c
diff options
context:
space:
mode:
Diffstat (limited to 'mu/libexec/imap.c')
-rw-r--r--mu/libexec/imap.c1310
1 files changed, 1310 insertions, 0 deletions
diff --git a/mu/libexec/imap.c b/mu/libexec/imap.c
new file mode 100644
index 000000000..a56af8c77
--- /dev/null
+++ b/mu/libexec/imap.c
@@ -0,0 +1,1310 @@
+/* GNU Mailutils -- a suite of utilities for electronic mail
+ Copyright (C) 2010-2012, 2014-2017 Free Software Foundation, Inc.
+
+ GNU Mailutils is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3, or (at your option)
+ any later version.
+
+ GNU Mailutils 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 GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. */
+
+#if defined(HAVE_CONFIG_H)
+# include <config.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <mailutils/mailutils.h>
+#include <mailutils/imap.h>
+#include <mailutils/imapio.h>
+#include <mailutils/imaputil.h>
+#include <mailutils/msgset.h>
+#include "mu.h"
+
+char imap_docstring[] = N_("IMAP4 client shell");
+
+static mu_imap_t imap;
+static int uid_mode;
+
+static enum mu_imap_session_state
+current_imap_state ()
+{
+ int state;
+ if (imap == NULL)
+ state = MU_IMAP_SESSION_INIT;
+ else
+ state = mu_imap_session_state (imap);
+ return state;
+}
+
+
+static void
+imap_set_verbose ()
+{
+ if (imap)
+ {
+ if (QRY_VERBOSE ())
+ mu_imap_trace (imap, MU_IMAP_TRACE_SET);
+ else
+ mu_imap_trace (imap, MU_IMAP_TRACE_CLR);
+ }
+}
+
+void
+imap_set_verbose_mask ()
+{
+ if (imap)
+ {
+ mu_imap_trace_mask (imap, QRY_VERBOSE_MASK (MU_XSCRIPT_SECURE)
+ ? MU_IMAP_TRACE_SET : MU_IMAP_TRACE_CLR,
+ MU_XSCRIPT_SECURE);
+ mu_imap_trace_mask (imap, QRY_VERBOSE_MASK (MU_XSCRIPT_PAYLOAD)
+ ? MU_IMAP_TRACE_SET : MU_IMAP_TRACE_CLR,
+ MU_XSCRIPT_PAYLOAD);
+ }
+}
+
+static int
+com_verbose (int argc, char **argv)
+{
+ return shell_verbose (argc, argv,
+ imap_set_verbose, imap_set_verbose_mask);
+
+}
+
+static mu_msgset_t
+parse_msgset (const char *arg)
+{
+ int status;
+ mu_msgset_t msgset;
+ char *p;
+
+ status = mu_msgset_create (&msgset, NULL, MU_MSGSET_NUM);
+ if (status)
+ {
+ mu_diag_funcall (MU_DIAG_ERROR, "mu_msgset_create", NULL, status);
+ return NULL;
+ }
+ status = mu_msgset_parse_imap (msgset, MU_MSGSET_NUM, arg, &p);
+ if (status)
+ {
+ mu_error (_("failed to parse message set near \"%s\": %s"),
+ p, mu_strerror (status));
+ mu_msgset_destroy (&msgset);
+ }
+ return msgset;
+}
+
+static void
+report_failure (const char *what, int status)
+{
+ const char *str;
+
+ mu_error (_("%s failed: %s"), what, mu_strerror (status));
+ if (mu_imap_strerror (imap, &str) == 0)
+ mu_error (_("server reply: %s"), str);
+}
+
+static int connect_argc;
+static char **connect_argv;
+#define host connect_argv[0]
+
+static char *username;
+
+static void
+imap_prompt_env (void)
+{
+ mu_assoc_t assoc = mutool_shell_prompt_assoc ();
+ enum mu_imap_session_state state = current_imap_state ();
+ const char *p;
+
+ if (state >= MU_IMAP_SESSION_AUTH && username)
+ mu_assoc_install (assoc, "user", username);
+
+ if (connect_argv)
+ mu_assoc_install (assoc, "host", host);
+
+ if (mu_imap_session_state_str (state, &p) == 0)
+ mu_assoc_install (assoc, "status", (void*) p);
+}
+
+/* Callbacks */
+static void
+imap_preauth_callback (void *data, int code, size_t sdat, void *pdat)
+{
+ const char *text = pdat;
+ if (text)
+ mu_diag_output (MU_DIAG_INFO, _("session authenticated: %s"), text);
+ else
+ mu_diag_output (MU_DIAG_INFO, _("session authenticated"));
+}
+
+static void
+imap_bye_callback (void *data, int code, size_t sdat, void *pdat)
+{
+ const char *text = pdat;
+ if (text)
+ mu_diag_output (MU_DIAG_INFO, _("server is closing connection: %s"), text);
+ else
+ mu_diag_output (MU_DIAG_INFO, _("server is closing connection"));
+}
+
+static void
+imap_bad_callback (void *data, int code, size_t sdat, void *pdat)
+{
+ const char *text = pdat;
+ mu_diag_output (MU_DIAG_CRIT, "SERVER ALERT: %s", text);
+}
+
+/* Fetch callback */
+static void
+format_email (mu_stream_t str, const char *name, mu_address_t addr)
+{
+ mu_stream_printf (str, " %s = ", name);
+ if (!addr)
+ mu_stream_printf (str, "NIL");
+ else
+ mu_stream_format_address (str, addr);
+ mu_stream_printf (str, "\n");
+}
+
+static void
+format_date (mu_stream_t str, char *name,
+ struct tm *tm, struct mu_timezone *tz)
+{
+ char date[128];
+
+ strftime (date, sizeof (date), "%a %b %e %H:%M:%S", tm);
+ mu_stream_printf (str, " %s = %s", name, date);
+ if (tz->tz_name)
+ mu_stream_printf (str, " %s", tz->tz_name);
+ else
+ {
+ int off = tz->utc_offset;
+ if (off < 0)
+ {
+ mu_stream_printf (str, " -");
+ off = - off;
+ }
+ else
+ mu_stream_printf (str, " +");
+ off /= 60;
+ mu_stream_printf (str, "%02d%02d", off / 60, off % 60);
+ }
+ mu_stream_printf (str, "\n");
+}
+
+#define S(str) ((str) ? (str) : "")
+
+static void
+print_param (mu_stream_t ostr, const char *prefix, mu_assoc_t assoc,
+ int indent)
+{
+ mu_iterator_t itr;
+ int i;
+
+ mu_stream_printf (ostr, "%*s%s:\n", indent, "", prefix);
+ indent += 4;
+ if (mu_assoc_get_iterator (assoc, &itr))
+ return;
+ for (i = 0, mu_iterator_first (itr);
+ !mu_iterator_is_done (itr);
+ i++, mu_iterator_next (itr))
+ {
+ const char *name;
+ struct mu_mime_param *p;
+
+ mu_iterator_current_kv (itr, (const void **)&name, (void**)&p);
+ mu_stream_printf (ostr, "%*s%d: %s=%s\n", indent, "", i, name, p->value);
+ }
+ mu_iterator_destroy (&itr);
+}
+
+struct print_data
+{
+ mu_stream_t ostr;
+ int num;
+ int level;
+};
+
+static void print_bs (mu_stream_t ostr,
+ struct mu_bodystructure *bs, int level);
+
+static int
+print_item (void *item, void *data)
+{
+ struct mu_bodystructure *bs = item;
+ struct print_data *pd = data;
+ mu_stream_printf (pd->ostr, "%*sPart #%d\n", (pd->level-1) << 2, "",
+ pd->num);
+ print_bs (pd->ostr, bs, pd->level);
+ ++pd->num;
+ return 0;
+}
+
+static void
+print_address (mu_stream_t ostr, const char *title, mu_address_t addr,
+ int indent)
+{
+ mu_stream_printf (ostr, "%*s%s: ", indent, "", title);
+ mu_stream_format_address (mu_strout, addr);
+ mu_stream_printf (ostr, "\n");
+}
+
+static void
+print_imapenvelope (mu_stream_t ostr, struct mu_imapenvelope *env, int level)
+{
+ int indent = (level << 2);
+
+ mu_stream_printf (ostr, "%*sEnvelope:\n", indent, "");
+ indent += 4;
+ mu_stream_printf (ostr, "%*sTime: ", indent, "");
+ mu_c_streamftime (mu_strout, "%c%n", &env->date, &env->tz);
+ mu_stream_printf (ostr, "%*sSubject: %s\n", indent, "", S(env->subject));
+ print_address (ostr, "From", env->from, indent);
+ print_address (ostr, "Sender", env->sender, indent);
+ print_address (ostr, "Reply-to", env->reply_to, indent);
+ print_address (ostr, "To", env->to, indent);
+ print_address (ostr, "Cc", env->cc, indent);
+ print_address (ostr, "Bcc", env->bcc, indent);
+ mu_stream_printf (ostr, "%*sIn-Reply-To: %s\n", indent, "",
+ S(env->in_reply_to));
+ mu_stream_printf (ostr, "%*sMessage-ID: %s\n", indent, "",
+ S(env->message_id));
+}
+
+static void
+print_bs (mu_stream_t ostr, struct mu_bodystructure *bs, int level)
+{
+ int indent = level << 2;
+ mu_stream_printf (ostr, "%*sbody_type=%s\n", indent, "", S(bs->body_type));
+ mu_stream_printf (ostr, "%*sbody_subtype=%s\n", indent, "",
+ S(bs->body_subtype));
+ print_param (ostr, "Parameters", bs->body_param, indent);
+ mu_stream_printf (ostr, "%*sbody_id=%s\n", indent, "", S(bs->body_id));
+ mu_stream_printf (ostr, "%*sbody_descr=%s\n", indent, "", S(bs->body_descr));
+ mu_stream_printf (ostr, "%*sbody_encoding=%s\n", indent, "",
+ S(bs->body_encoding));
+ mu_stream_printf (ostr, "%*sbody_size=%lu\n", indent, "",
+ (unsigned long) bs->body_size);
+ /* Optional */
+ mu_stream_printf (ostr, "%*sbody_md5=%s\n", indent, "", S(bs->body_md5));
+ mu_stream_printf (ostr, "%*sbody_disposition=%s\n", indent, "",
+ S(bs->body_disposition));
+ print_param (ostr, "Disposition Parameters", bs->body_disp_param, indent);
+ mu_stream_printf (ostr, "%*sbody_language=%s\n", indent, "",
+ S(bs->body_language));
+ mu_stream_printf (ostr, "%*sbody_location=%s\n", indent, "",
+ S(bs->body_location));
+
+ mu_stream_printf (ostr, "%*sType ", indent, "");
+ switch (bs->body_message_type)
+ {
+ case mu_message_other:
+ mu_stream_printf (ostr, "mu_message_other\n");
+ break;
+
+ case mu_message_text:
+ mu_stream_printf (ostr, "mu_message_text:\n%*sbody_lines=%lu\n",
+ indent + 4, "",
+ (unsigned long) bs->v.text.body_lines);
+ break;
+
+ case mu_message_rfc822:
+ mu_stream_printf (ostr, "mu_message_rfc822:\n%*sbody_lines=%lu\n",
+ indent + 4, "",
+ (unsigned long) bs->v.rfc822.body_lines);
+ print_imapenvelope (ostr, bs->v.rfc822.body_env, level + 1);
+ print_bs (ostr, bs->v.rfc822.body_struct, level + 1);
+ break;
+
+ case mu_message_multipart:
+ {
+ struct print_data pd;
+ pd.ostr = ostr;
+ pd.num = 0;
+ pd.level = level + 1;
+ mu_stream_printf (ostr, "mu_message_multipart:\n");
+ mu_list_foreach (bs->v.multipart.body_parts, print_item, &pd);
+ }
+ }
+}
+
+static int
+fetch_response_printer (void *item, void *data)
+{
+ union mu_imap_fetch_response *resp = item;
+ mu_stream_t str = data;
+
+ switch (resp->type)
+ {
+ case MU_IMAP_FETCH_BODY:
+ mu_stream_printf (str, "BODY [");
+ if (resp->body.partv)
+ {
+ size_t i;
+
+ for (i = 0; i < resp->body.partc; i++)
+ mu_stream_printf (str, "%lu.",
+ (unsigned long) resp->body.partv[i]);
+ }
+ if (resp->body.section)
+ mu_stream_printf (str, "%s", resp->body.section);
+ mu_stream_printf (str, "]");
+ mu_stream_printf (str, "\nBEGIN\n%s\nEND\n", resp->body.text);
+ break;
+
+ case MU_IMAP_FETCH_BODYSTRUCTURE:
+ /* FIXME */
+ mu_stream_printf (str, "BODYSTRUCTURE:\nBEGIN\n");
+ print_bs (str, resp->bodystructure.bs, 0);
+ mu_stream_printf (str, "END\n");
+ break;
+
+ case MU_IMAP_FETCH_ENVELOPE:
+ {
+ struct mu_imapenvelope *env = resp->envelope.imapenvelope;
+
+ mu_stream_printf (str, "ENVELOPE:\n");
+
+ format_date (str, "date", &env->date, &env->tz);
+ mu_stream_printf (str, " subject = %s\n",
+ env->subject ?
+ env->subject : "NIL");
+
+ format_email (str, "from", env->from);
+ format_email (str, "sender", env->sender);
+ format_email (str, "reply-to", env->reply_to);
+ format_email (str, "to", env->to);
+ format_email (str, "cc", env->cc);
+ format_email (str, "bcc", env->bcc);
+
+ mu_stream_printf (str, " in-reply-to = %s\n",
+ env->in_reply_to ?
+ env->in_reply_to : "NIL");
+ mu_stream_printf (str, " message-id = %s\n",
+ env->message_id ?
+ env->message_id : "NIL");
+ }
+ break;
+
+ case MU_IMAP_FETCH_FLAGS:
+ mu_stream_printf (str, " flags = ");
+ mu_imap_format_flags (str, resp->flags.flags, 1);
+ mu_stream_printf (str, "\n");
+ break;
+
+ case MU_IMAP_FETCH_INTERNALDATE:
+ format_date (str, "internaldate", &resp->internaldate.tm,
+ &resp->internaldate.tz);
+ break;
+
+ case MU_IMAP_FETCH_RFC822_SIZE:
+ mu_stream_printf (str, " size = %lu\n",
+ (unsigned long) resp->rfc822_size.size);
+ break;
+
+ case MU_IMAP_FETCH_UID:
+ mu_stream_printf (str, " UID = %lu\n",
+ (unsigned long) resp->uid.uid);
+ }
+ return 0;
+}
+
+static void
+imap_fetch_callback (void *data, int code, size_t sdat, void *pdat)
+{
+ mu_stream_t str = data;
+ mu_list_t list = pdat;
+
+ mu_stream_printf (str, "Message #%lu:\n", (unsigned long) sdat);
+ mu_list_foreach (list, fetch_response_printer, str);
+ mu_stream_printf (str, "\n\n");
+}
+
+static int
+com_uid (int argc, char **argv)
+{
+ int bv;
+
+ if (argc == 1)
+ mu_printf ("%s\n", uid_mode ? _("UID is on") : _("UID is off"));
+ else if (get_bool (argv[1], &bv) == 0)
+ uid_mode = bv;
+ else
+ mu_error (_("invalid boolean value"));
+ return 0;
+}
+
+static int
+com_disconnect (int argc MU_ARG_UNUSED, char **argv MU_ARG_UNUSED)
+{
+ if (imap)
+ {
+ mu_imap_disconnect (imap);
+ mu_imap_destroy (&imap);
+
+ mu_argcv_free (connect_argc, connect_argv);
+ connect_argc = 0;
+ connect_argv = NULL;
+ imap_prompt_env ();
+ }
+ return 0;
+}
+
+static int
+com_connect (int argc, char **argv)
+{
+ int status;
+ int tls = 0;
+ int i = 1;
+ enum mu_imap_session_state state;
+
+ for (i = 1; i < argc; i++)
+ {
+ if (strcmp (argv[i], "-tls") == 0)
+ {
+#ifdef WITH_TLS
+ tls = 1;
+#else
+ mu_error (_("TLS not supported"));
+ return 0;
+#endif
+ }
+ else
+ break;
+ }
+
+ argc -= i;
+ argv += i;
+
+ state = current_imap_state ();
+
+ if (state != MU_IMAP_SESSION_INIT)
+ com_disconnect (0, NULL);
+
+ status = mu_imap_create (&imap);
+ if (status == 0)
+ {
+ mu_stream_t tcp;
+ struct mu_sockaddr *sa;
+ struct mu_sockaddr_hints hints;
+
+ memset (&hints, 0, sizeof (hints));
+ hints.flags = MU_AH_DETECT_FAMILY;
+ hints.port = tls ? MU_IMAP_DEFAULT_SSL_PORT : MU_IMAP_DEFAULT_PORT;
+ hints.protocol = IPPROTO_TCP;
+ hints.socktype = SOCK_STREAM;
+ status = mu_sockaddr_from_node (&sa, argv[0], argv[1], &hints);
+ if (status == 0)
+ {
+ status = mu_tcp_stream_create_from_sa (&tcp, sa, NULL, 0);
+ if (status)
+ mu_sockaddr_free (sa);
+ }
+ if (status == 0)
+ {
+#ifdef WITH_TLS
+ if (tls)
+ {
+ mu_stream_t tlsstream;
+
+ status = mu_tls_client_stream_create (&tlsstream, tcp, tcp, 0);
+ mu_stream_unref (tcp);
+ if (status)
+ {
+ mu_error (_("cannot create TLS stream: %s"),
+ mu_strerror (status));
+ return 0;
+ }
+ tcp = tlsstream;
+ }
+#endif
+ mu_imap_set_carrier (imap, tcp);
+
+ if (QRY_VERBOSE ())
+ {
+ imap_set_verbose ();
+ imap_set_verbose_mask ();
+ }
+
+ /* Set callbacks */
+ mu_imap_register_callback_function (imap, MU_IMAP_CB_PREAUTH,
+ imap_preauth_callback,
+ NULL);
+ mu_imap_register_callback_function (imap, MU_IMAP_CB_BYE,
+ imap_bye_callback,
+ NULL);
+ mu_imap_register_callback_function (imap, MU_IMAP_CB_BAD,
+ imap_bad_callback,
+ NULL);
+ mu_imap_register_callback_function (imap, MU_IMAP_CB_FETCH,
+ imap_fetch_callback,
+ mu_strout);
+
+ status = mu_imap_connect (imap);
+ if (status)
+ {
+ const char *err;
+
+ mu_error ("Failed to connect: %s", mu_strerror (status));
+ if (mu_imap_strerror (imap, &err))
+ mu_error ("server response: %s", err);
+ mu_imap_destroy (&imap);
+ }
+ }
+ else
+ mu_imap_destroy (&imap);
+ }
+ else
+ mu_error (_("Failed to create imap connection: %s"), mu_strerror (status));
+
+ if (!status)
+ {
+ connect_argc = argc;
+ connect_argv = mu_calloc (argc + 1, sizeof (*connect_argv));
+ for (i = 0; i < argc; i++)
+ connect_argv[i] = mu_strdup (argv[i]);
+ connect_argv[i] = NULL;
+
+ imap_prompt_env ();
+ }
+
+ return status;
+}
+
+static int
+com_starttls (int argc MU_ARG_UNUSED, char **argv MU_ARG_UNUSED)
+{
+ int status = mu_imap_starttls (imap);
+ if (status)
+ report_failure ("starttls", status);
+ return 0;
+}
+
+static int
+com_logout (int argc MU_ARG_UNUSED, char **argv MU_ARG_UNUSED)
+{
+ int status = 0;
+ if (imap)
+ {
+ if (mu_imap_logout (imap) == 0)
+ {
+ status = com_disconnect (0, NULL);
+ }
+ else
+ {
+ mu_printf ("Try 'exit' to leave %s\n", mu_program_name);
+ }
+ }
+ else
+ mu_printf (_("Try 'exit' to leave %s\n"), mu_program_name);
+ return status;
+}
+
+static int
+com_capability (int argc, char **argv)
+{
+ mu_iterator_t iterator = NULL;
+ int status = 0;
+ int reread = 0;
+ int i = 1;
+
+ for (i = 1; i < argc; i++)
+ {
+ if (strcmp (argv[i], "-reread") == 0)
+ reread = 1;
+ else
+ break;
+ }
+
+ if (i < argc)
+ {
+ if (reread)
+ {
+ status = mu_imap_capability (imap, 1, NULL);
+ if (status)
+ return status;
+ }
+ for (; i < argc; i++)
+ {
+ const char *elt;
+ int rc = mu_imap_capability_test (imap, argv[i], &elt);
+ switch (rc)
+ {
+ case 0:
+ if (*elt)
+ mu_printf ("%s: %s\n", argv[i], elt);
+ else
+ mu_printf (_("%s is set\n"), argv[i]);
+ break;
+
+ case MU_ERR_NOENT:
+ mu_printf (_("%s is not set\n"), argv[i]);
+ break;
+
+ default:
+ return rc;
+ }
+ }
+ }
+ else
+ {
+ status = mu_imap_capability (imap, reread, &iterator);
+
+ if (status == 0)
+ {
+ for (mu_iterator_first (iterator);
+ !mu_iterator_is_done (iterator); mu_iterator_next (iterator))
+ {
+ char *capa = NULL;
+ mu_iterator_current (iterator, (void **) &capa);
+ mu_printf ("CAPA: %s\n", capa ? capa : "");
+ }
+ mu_iterator_destroy (&iterator);
+ }
+ }
+ return status;
+}
+
+static int
+com_login (int argc, char **argv)
+{
+ int status;
+ char *pwd, *passbuf = NULL;
+
+ if (!imap)
+ {
+ mu_error (_("you need to connect first"));
+ return 0;
+ }
+
+ if (argc == 2)
+ {
+ if (!mutool_shell_interactive)
+ {
+ mu_error (_("login: password required"));
+ return 1;
+ }
+ status = mu_getpass (mu_strin, mu_strout, _("Password:"), &passbuf);
+ if (status)
+ return status;
+ pwd = passbuf;
+ }
+ else
+ pwd = argv[2];
+
+ status = mu_imap_login (imap, argv[1], pwd);
+ memset (pwd, 0, strlen (pwd));
+ free (passbuf);
+ if (status == 0)
+ {
+ free (username);
+ username = mu_strdup (argv[1]);
+ imap_prompt_env ();
+ }
+ else
+ report_failure ("login", status);
+ return 0;
+}
+
+static int
+com_id (int argc, char **argv)
+{
+ mu_assoc_t assoc;
+ char *test = NULL;
+ int status;
+
+ argv++;
+ if (argv[0] && strcmp (argv[0], "-test") == 0)
+ {
+ argv++;
+ if (argv[0] == NULL)
+ {
+ mu_error (_("id -test requires an argument"));
+ return 0;
+ }
+ test = argv[0];
+ argv++;
+ }
+
+ status = mu_imap_id (imap, argv + 1, &assoc);
+ if (status == 0)
+ {
+ if (test)
+ {
+ char *val = mu_assoc_get (assoc, test);
+ if (val)
+ {
+ mu_printf ("%s: %s\n", test, val);
+ }
+ else
+ mu_printf (_("%s is not set\n"), test);
+ }
+ else
+ {
+ mu_iterator_t itr;
+
+ mu_assoc_get_iterator (assoc, &itr);
+ for (mu_iterator_first (itr);
+ !mu_iterator_is_done (itr); mu_iterator_next (itr))
+ {
+ char *key;
+ char *val;
+
+ mu_iterator_current_kv (itr, (const void**)&key, (void**)&val);
+ mu_printf ("ID: %s %s\n", key, val);
+ }
+ mu_iterator_destroy (&itr);
+ }
+ mu_assoc_destroy (&assoc);
+ }
+ return status;
+}
+
+static void
+print_imap_stats (struct mu_imap_stat *st)
+{
+ if (st->flags & MU_IMAP_STAT_DEFINED_FLAGS)
+ {
+ mu_printf (_("Flags defined: "));
+ mu_imap_format_flags (mu_strout, st->defined_flags, 0);
+ mu_printf ("\n");
+ }
+ if (st->flags & MU_IMAP_STAT_PERMANENT_FLAGS)
+ {
+ mu_printf (_("Flags permanent: "));
+ mu_imap_format_flags (mu_strout, st->permanent_flags, 0);
+ mu_printf ("\n");
+ }
+
+ if (st->flags & MU_IMAP_STAT_MESSAGE_COUNT)
+ mu_printf (_("Total messages: %lu\n"), (unsigned long) st->message_count);
+ if (st->flags & MU_IMAP_STAT_RECENT_COUNT)
+ mu_printf (_("Recent messages: %lu\n"), (unsigned long) st->recent_count);
+ if (st->flags & MU_IMAP_STAT_FIRST_UNSEEN)
+ mu_printf (_("First unseen message: %lu\n"),
+ (unsigned long) st->first_unseen);
+ if (st->flags & MU_IMAP_STAT_UIDNEXT)
+ mu_printf (_("Next UID: %lu\n"), (unsigned long) st->uidnext);
+ if (st->flags & MU_IMAP_STAT_UIDVALIDITY)
+ mu_printf (_("UID validity: %lu\n"), st->uidvalidity);
+}
+
+
+static int
+select_mbox (int argc, char **argv, int writable)
+{
+ int status;
+ struct mu_imap_stat st;
+
+ status = mu_imap_select (imap, argv[1], writable, &st);
+ if (status == 0)
+ {
+ print_imap_stats (&st);
+ imap_prompt_env ();
+ }
+ else
+ report_failure ("select", status);
+ return 0;
+}
+
+static int
+com_select (int argc, char **argv)
+{
+ return select_mbox (argc, argv, 1);
+}
+
+static int
+com_examine (int argc, char **argv)
+{
+ return select_mbox (argc, argv, 0);
+}
+
+static int
+com_status (int argc, char **argv)
+{
+ struct mu_imap_stat st;
+ int i, flag;
+ int status;
+
+ st.flags = 0;
+ for (i = 2; i < argc; i++)
+ {
+ if (mu_kwd_xlat_name_ci (_mu_imap_status_name_table, argv[i], &flag))
+ {
+ mu_error (_("unknown data item: %s"), argv[i]);
+ return 0;
+ }
+ st.flags |= flag;
+ }
+
+ status = mu_imap_status (imap, argv[1], &st);
+ if (status == 0)
+ {
+ print_imap_stats (&st);
+ }
+ else
+ report_failure ("status", status);
+ return 0;
+}
+
+static int
+com_noop (int argc MU_ARG_UNUSED, char **argv MU_ARG_UNUSED)
+{
+ int status = mu_imap_noop (imap);
+ if (status)
+ report_failure ("noop", status);
+ return 0;
+}
+
+static int
+com_check (int argc MU_ARG_UNUSED, char **argv MU_ARG_UNUSED)
+{
+ int status = mu_imap_check (imap);
+ if (status)
+ report_failure ("check", status);
+ return 0;
+}
+
+static int
+com_fetch (int argc, char **argv)
+{
+ mu_stream_t out = mutool_open_pager ();
+ mu_msgset_t msgset = parse_msgset (argv[1]);
+
+ if (msgset)
+ {
+ int status;
+
+ mu_imap_register_callback_function (imap, MU_IMAP_CB_FETCH,
+ imap_fetch_callback,
+ out);
+ status = mu_imap_fetch (imap, uid_mode, msgset, argv[2]);
+ mu_stream_destroy (&out);
+ mu_imap_register_callback_function (imap, MU_IMAP_CB_FETCH,
+ imap_fetch_callback,
+ mu_strout);
+ mu_msgset_free (msgset);
+ if (status)
+ report_failure ("fetch", status);
+ }
+ return 0;
+}
+
+static int
+com_store (int argc, char **argv)
+{
+ mu_msgset_t msgset = parse_msgset (argv[1]);
+
+ if (msgset)
+ {
+ int status = mu_imap_store (imap, uid_mode, msgset, argv[2]);
+ if (status)
+ report_failure ("store", status);
+ }
+ return 0;
+}
+
+static int
+com_close (int argc MU_ARG_UNUSED, char **argv MU_ARG_UNUSED)
+{
+ int status = mu_imap_close (imap);
+ if (status)
+ report_failure ("close", status);
+ return 0;
+}
+
+static int
+com_unselect (int argc MU_ARG_UNUSED, char **argv MU_ARG_UNUSED)
+{
+ int status = mu_imap_noop (imap);
+ if (status)
+ report_failure ("unselect", status);
+ return 0;
+}
+
+static int
+com_delete (int argc, char **argv)
+{
+ int status = mu_imap_delete (imap, argv[1]);
+ if (status)
+ report_failure ("delete", status);
+ return 0;
+}
+
+static int
+com_rename (int argc, char **argv)
+{
+ int status = mu_imap_rename (imap, argv[1], argv[2]);
+ if (status)
+ report_failure ("rename", status);
+ return 0;
+}
+
+static int
+com_expunge (int argc MU_ARG_UNUSED, char **argv MU_ARG_UNUSED)
+{
+ int status = mu_imap_expunge (imap);
+ if (status)
+ report_failure ("expunge", status);
+ return 0;
+}
+
+static int
+com_create (int argc, char **argv)
+{
+ int status = mu_imap_mailbox_create (imap, argv[1]);
+ if (status)
+ report_failure ("create", status);
+ return 0;
+}
+
+static int
+com_subscribe (int argc, char **argv)
+{
+ int status = mu_imap_subscribe (imap, argv[1]);
+ if (status)
+ report_failure ("subscribe", status);
+ return 0;
+}
+
+static int
+com_unsubscribe (int argc, char **argv)
+{
+ int status = mu_imap_unsubscribe (imap, argv[1]);
+ if (status)
+ report_failure ("unsubscribe", status);
+ return 0;
+}
+
+static int
+com_append (int argc, char **argv)
+{
+ struct tm tmbuf, *tm = NULL;
+ struct mu_timezone tzbuf, *tz = NULL;
+ int flags = 0;
+ mu_stream_t stream;
+ int rc, i;
+
+ for (i = 1; i < argc; i++)
+ {
+ if (strcmp (argv[i], "-time") == 0)
+ {
+ char *p;
+
+ if (++i == argc)
+ {
+ mu_error (_("-time requires argument"));
+ return 0;
+ }
+ rc = mu_scan_datetime (argv[i], MU_DATETIME_INTERNALDATE,
+ &tmbuf, &tzbuf, &p);
+ if (rc || *p)
+ {
+ mu_error (_("cannot parse time"));
+ return 0;
+ }
+ tm = &tmbuf;
+ tz = &tzbuf;
+ }
+ else if (strcmp (argv[i], "-flag") == 0)
+ {
+ if (++i == argc)
+ {
+ mu_error (_("-flag requires argument"));
+ return 0;
+ }
+ if (mu_imap_flag_to_attribute (argv[i], &flags))
+ {
+ mu_error (_("unrecognized flag: %s"), argv[i]);
+ return 0;
+ }
+ }
+ else if (strcmp (argv[i], "--") == 0)
+ {
+ i++;
+ break;
+ }
+ else if (argv[i][0] == '-')
+ {
+ mu_error (_("unrecognized option: %s"), argv[i]);
+ return 0;
+ }
+ else
+ break;
+ }
+
+ if (argc - i != 2)
+ {
+ mu_error (_("wrong number of arguments"));
+ return 0;
+ }
+
+ rc = mu_file_stream_create (&stream, argv[i + 1],
+ MU_STREAM_READ|MU_STREAM_SEEK);
+ if (rc)
+ {
+ mu_error (_("cannot open file %s: %s"), argv[i + 1], mu_strerror (rc));
+ return 0;
+ }
+
+ rc = mu_imap_append_stream (imap, argv[i], flags, tm, tz, stream);
+ mu_stream_unref (stream);
+ if (rc)
+ report_failure ("append", rc);
+ return 0;
+}
+
+static int
+com_copy (int argc, char **argv)
+{
+ mu_msgset_t msgset = parse_msgset (argv[1]);
+ if (msgset)
+ {
+ int status = mu_imap_copy (imap, uid_mode, msgset, argv[2]);
+ mu_msgset_free (msgset);
+ if (status)
+ report_failure ("copy", status);
+ }
+ return 0;
+}
+
+static int
+com_search (int argc, char **argv)
+{
+ mu_msgset_t mset;
+ size_t count;
+ int rc;
+
+ rc = mu_imap_search (imap, uid_mode, argv[1], &mset);
+ if (rc)
+ {
+ report_failure ("search", rc);
+ return 0;
+ }
+
+ rc = mu_msgset_count (mset, &count);
+ if (rc == EINVAL || count == 0)
+ {
+ mu_printf ("%s\n", _("no matches"));
+ return 0;
+ }
+ mu_printf ("%lu matches:", (unsigned long) count);
+ mu_msgset_print (mu_strout, mset);
+ mu_printf ("\n");
+ mu_msgset_free (mset);
+
+ return 0;
+}
+
+
+static int
+print_list_item (void *item, void *data)
+{
+ struct mu_list_response *resp = item;
+ mu_stream_t out = data;
+
+ mu_stream_printf (out,
+ "%c%c %c %4d %s\n",
+ (resp->type & MU_FOLDER_ATTRIBUTE_DIRECTORY) ? 'd' : '-',
+ (resp->type & MU_FOLDER_ATTRIBUTE_FILE) ? 'f' : '-',
+ resp->separator ?
+ resp->separator : MU_HIERARCHY_DELIMITER,
+ resp->level,
+ resp->name);
+ return 0;
+}
+
+static int
+com_list (int argc, char **argv)
+{
+ mu_list_t list;
+ int rc;
+ mu_stream_t out;
+
+ rc = mu_imap_list_new (imap, argv[1], argv[2], &list);
+ if (rc)
+ {
+ report_failure ("list", rc);
+ return 0;
+ }
+
+ out = mutool_open_pager ();
+ mu_list_foreach (list, print_list_item, out);
+ mu_stream_unref (out);
+ return 0;
+}
+
+static int
+com_lsub (int argc, char **argv)
+{
+ mu_list_t list;
+ int rc;
+ mu_stream_t out;
+
+ rc = mu_imap_lsub_new (imap, argv[1], argv[2], &list);
+ if (rc)
+ {
+ report_failure ("lsub", rc);
+ return 0;
+ }
+
+ out = mutool_open_pager ();
+ mu_list_foreach (list, print_list_item, out);
+ mu_stream_unref (out);
+ return 0;
+}
+
+struct mutool_command imap_comtab[] = {
+ { "capability", 1, -1, 0,
+ com_capability,
+ /* TRANSLATORS: -reread is a keyword; do not translate. */
+ N_("[-reread] [NAME...]"),
+ N_("list server capabilities") },
+ { "verbose", 1, 4, 0,
+ com_verbose,
+ "[on|off|mask|unmask] [secure [payload]]",
+ N_("control the protocol tracing") },
+ { "connect", 1, 4, 0,
+ com_connect,
+ /* TRANSLATORS: -tls is a keyword. */
+ N_("[-tls] HOSTNAME [PORT]"),
+ N_("open connection") },
+ { "disconnect", 1, 1, 0,
+ com_disconnect,
+ NULL,
+ N_("close connection") },
+ { "starttls", 1, 1, 0,
+ com_starttls,
+ NULL,
+ N_("Establish TLS encrypted channel") },
+ { "login", 2, 3, 0,
+ com_login,
+ N_("USER [PASS]"),
+ N_("login to the server") },
+ { "logout", 1, 1, 0,
+ com_logout,
+ NULL,
+ N_("quit imap session") },
+ { "id", 1, -1, 0,
+ com_id,
+ N_("[-test KW] [ARG [ARG...]]"),
+ N_("send ID command") },
+ { "noop", 1, 1, 0,
+ com_noop,
+ NULL,
+ N_("no operation (keepalive)") },
+ { "check", 1, 1, 0,
+ com_check,
+ NULL,
+ N_("request a server checkpoint") },
+ { "select", 1, 2, 0,
+ com_select,
+ N_("[MBOX]"),
+ N_("select a mailbox") },
+ { "examine", 1, 2, 0,
+ com_examine,
+ N_("[MBOX]"),
+ N_("examine a mailbox") },
+ { "status", 3, -1, 0,
+ com_status,
+ N_("MBOX KW [KW...]"),
+ N_("get mailbox status") },
+ { "fetch", 3, 3, CMD_COALESCE_EXTRA_ARGS,
+ com_fetch,
+ N_("MSGSET ITEMS"),
+ N_("fetch message data") },
+ { "store", 3, 3, CMD_COALESCE_EXTRA_ARGS,
+ com_store,
+ N_("MSGSET ITEMS"),
+ N_("alter mailbox data") },
+ { "close", 1, 1, 0,
+ com_close,
+ NULL,
+ N_("close the mailbox (with expunge)") },
+ { "unselect", 1, 1, 0,
+ com_unselect,
+ NULL,
+ N_("close the mailbox (without expunge)") },
+ { "delete", 2, 2, 0,
+ com_delete,
+ N_("MAILBOX"),
+ N_("delete the mailbox") },
+ { "rename", 3, 3, 0,
+ com_rename,
+ N_("OLD-NAME NEW-NAME"),
+ N_("rename existing mailbox") },
+ { "expunge", 1, 1, 0,
+ com_expunge,
+ NULL,
+ N_("permanently remove messages marked for deletion") },
+ { "create", 2, 2, 0,
+ com_create,
+ N_("MAILBOX"),
+ N_("create new mailbox") },
+ { "append", 3, -1, 0,
+ com_append,
+ N_("[-time DATETIME] [-flag FLAG] MAILBOX FILE"),
+ N_("append message text from FILE to MAILBOX") },
+ { "copy", 3, 3, 0,
+ com_copy,
+ N_("MSGSET MAILBOX"),
+ N_("Copy messages from MSGSET to MAILBOX") },
+ { "list", 3, 3, 0,
+ com_list,
+ N_("REF MBOX"),
+ N_("List matching mailboxes") },
+ { "lsub", 3, 3, 0,
+ com_lsub,
+ N_("REF MBOX"),
+ N_("List subscribed mailboxes") },
+ { "subscribe", 2, 2, 0,
+ com_subscribe,
+ N_("MBOX"),
+ N_("Subscribe to a m