]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'ob/imap-send-ssl-verify'
authorJunio C Hamano <gitster@pobox.com>
Thu, 21 Mar 2013 21:02:39 +0000 (14:02 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 21 Mar 2013 21:02:40 +0000 (14:02 -0700)
Correctly connect to SSL/TLS sites that serve multiple hostnames on
a single IP by including Server Name Indication in the client-hello.

* ob/imap-send-ssl-verify:
  imap-send: support Server Name Indication (RFC4366)

1  2 
imap-send.c

diff --combined imap-send.c
index 43ac4e0bdfdba8850eff176b53d6bd071e8f424c,91671d6f41895dbb8c93fc2d8c0ca5d449fefc53..d9bcfb44dc334d86363fd60b576fcc2e2121f971
@@@ -25,7 -25,6 +25,7 @@@
  #include "cache.h"
  #include "exec_cmd.h"
  #include "run-command.h"
 +#include "prompt.h"
  #ifdef NO_OPENSSL
  typedef void *SSL;
  #else
  #include <openssl/x509v3.h>
  #endif
  
 -struct store_conf {
 -      char *name;
 -      const char *path; /* should this be here? its interpretation is driver-specific */
 -      char *map_inbox;
 -      char *trash;
 -      unsigned max_size; /* off_t is overkill */
 -      unsigned trash_remote_new:1, trash_only_new:1;
 -};
 -
 -/* For message->status */
 -#define M_RECENT       (1<<0) /* unsyncable flag; maildir_* depend on this being 1<<0 */
 -#define M_DEAD         (1<<1) /* expunged */
 -#define M_FLAGS        (1<<2) /* flags fetched */
 -
 -struct message {
 -      struct message *next;
 -      size_t size; /* zero implies "not fetched" */
 -      int uid;
 -      unsigned char flags, status;
 -};
 -
 -struct store {
 -      struct store_conf *conf; /* foreign */
 -
 -      /* currently open mailbox */
 -      const char *name; /* foreign! maybe preset? */
 -      char *path; /* own */
 -      struct message *msgs; /* own */
 -      int uidvalidity;
 -      unsigned char opts; /* maybe preset? */
 -      /* note that the following do _not_ reflect stats from msgs, but mailbox totals */
 -      int count; /* # of messages */
 -      int recent; /* # of recent messages - don't trust this beyond the initial read */
 -};
 -
 -struct msg_data {
 -      char *data;
 -      int len;
 -      unsigned char flags;
 -};
 -
  static const char imap_send_usage[] = "git imap-send < <mbox>";
  
  #undef DRV_OK
@@@ -51,6 -91,8 +51,6 @@@ static void imap_warn(const char *, ...
  
  static char *next_arg(char **);
  
 -static void free_generic_messages(struct message *);
 -
  __attribute__((format (printf, 3, 4)))
  static int nfsnprintf(char *buf, int blen, const char *fmt, ...);
  
@@@ -94,6 -136,21 +94,6 @@@ static struct imap_server_conf server 
        NULL,   /* auth_method */
  };
  
 -struct imap_store_conf {
 -      struct store_conf gen;
 -      struct imap_server_conf *server;
 -      unsigned use_namespace:1;
 -};
 -
 -#define NIL   (void *)0x1
 -#define LIST  (void *)0x2
 -
 -struct imap_list {
 -      struct imap_list *next, *child;
 -      char *val;
 -      int len;
 -};
 -
  struct imap_socket {
        int fd[2];
        SSL *ssl;
@@@ -110,6 -167,7 +110,6 @@@ struct imap_cmd
  
  struct imap {
        int uidnext; /* from SELECT responses */
 -      struct imap_list *ns_personal, *ns_other, *ns_shared; /* NAMESPACE info */
        unsigned caps, rcaps; /* CAPABILITY results */
        /* command queue */
        int nexttag, num_in_progress, literal_pending;
  };
  
  struct imap_store {
 -      struct store gen;
 +      /* currently open mailbox */
 +      const char *name; /* foreign! maybe preset? */
        int uidvalidity;
        struct imap *imap;
        const char *prefix;
 -      unsigned /*currentnc:1,*/ trashnc:1;
  };
  
  struct imap_cmd_cb {
@@@ -169,6 -227,14 +169,6 @@@ static const char *cap_list[] = 
  static int get_cmd_result(struct imap_store *ctx, struct imap_cmd *tcmd);
  
  
 -static const char *Flags[] = {
 -      "Draft",
 -      "Flagged",
 -      "Answered",
 -      "Seen",
 -      "Deleted",
 -};
 -
  #ifndef NO_OPENSSL
  static void ssl_socket_perror(const char *func)
  {
@@@ -304,6 -370,17 +304,17 @@@ static int ssl_socket_connect(struct im
                return -1;
        }
  
+ #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
+       /*
+        * SNI (RFC4366)
+        * OpenSSL does not document this function, but the implementation
+        * returns 1 on success, 0 on failure after calling SSLerr().
+        */
+       ret = SSL_set_tlsext_host_name(sock->ssl, server.host);
+       if (ret != 1)
+               warning("SSL_set_tlsext_host_name(%s) failed.", server.host);
+ #endif
        ret = SSL_connect(sock->ssl);
        if (ret <= 0) {
                socket_perror("SSL_connect", sock, ret);
@@@ -474,6 -551,16 +485,6 @@@ static char *next_arg(char **s
        return ret;
  }
  
 -static void free_generic_messages(struct message *msgs)
 -{
 -      struct message *tmsg;
 -
 -      for (; msgs; msgs = tmsg) {
 -              tmsg = msgs->next;
 -              free(msgs);
 -      }
 -}
 -
  static int nfsnprintf(char *buf, int blen, const char *fmt, ...)
  {
        int ret;
@@@ -601,9 -688,35 +612,9 @@@ static int imap_exec_m(struct imap_stor
        }
  }
  
 -static int is_atom(struct imap_list *list)
 -{
 -      return list && list->val && list->val != NIL && list->val != LIST;
 -}
 -
 -static int is_list(struct imap_list *list)
 -{
 -      return list && list->val == LIST;
 -}
 -
 -static void free_list(struct imap_list *list)
 -{
 -      struct imap_list *tmp;
 -
 -      for (; list; list = tmp) {
 -              tmp = list->next;
 -              if (is_list(list))
 -                      free_list(list->child);
 -              else if (is_atom(list))
 -                      free(list->val);
 -              free(list);
 -      }
 -}
 -
 -static int parse_imap_list_l(struct imap *imap, char **sp, struct imap_list **curp, int level)
 +static int skip_imap_list_l(char **sp, int level)
  {
 -      struct imap_list *cur;
 -      char *s = *sp, *p;
 -      int n, bytes;
 +      char *s = *sp;
  
        for (;;) {
                while (isspace((unsigned char)*s))
                        s++;
                        break;
                }
 -              *curp = cur = xmalloc(sizeof(*cur));
 -              curp = &cur->next;
 -              cur->val = NULL; /* for clean bail */
                if (*s == '(') {
                        /* sublist */
                        s++;
 -                      cur->val = LIST;
 -                      if (parse_imap_list_l(imap, &s, &cur->child, level + 1))
 -                              goto bail;
 -              } else if (imap && *s == '{') {
 -                      /* literal */
 -                      bytes = cur->len = strtol(s + 1, &s, 10);
 -                      if (*s != '}')
 -                              goto bail;
 -
 -                      s = cur->val = xmalloc(cur->len);
 -
 -                      /* dump whats left over in the input buffer */
 -                      n = imap->buf.bytes - imap->buf.offset;
 -
 -                      if (n > bytes)
 -                              /* the entire message fit in the buffer */
 -                              n = bytes;
 -
 -                      memcpy(s, imap->buf.buf + imap->buf.offset, n);
 -                      s += n;
 -                      bytes -= n;
 -
 -                      /* mark that we used part of the buffer */
 -                      imap->buf.offset += n;
 -
 -                      /* now read the rest of the message */
 -                      while (bytes > 0) {
 -                              if ((n = socket_read(&imap->buf.sock, s, bytes)) <= 0)
 -                                      goto bail;
 -                              s += n;
 -                              bytes -= n;
 -                      }
 -
 -                      if (buffer_gets(&imap->buf, &s))
 +                      if (skip_imap_list_l(&s, level + 1))
                                goto bail;
                } else if (*s == '"') {
                        /* quoted string */
                        s++;
 -                      p = s;
                        for (; *s != '"'; s++)
                                if (!*s)
                                        goto bail;
 -                      cur->len = s - p;
                        s++;
 -                      cur->val = xmemdupz(p, cur->len);
                } else {
                        /* atom */
 -                      p = s;
                        for (; *s && !isspace((unsigned char)*s); s++)
                                if (level && *s == ')')
                                        break;
 -                      cur->len = s - p;
 -                      if (cur->len == 3 && !memcmp("NIL", p, 3))
 -                              cur->val = NIL;
 -                      else
 -                              cur->val = xmemdupz(p, cur->len);
                }
  
                if (!level)
                        goto bail;
        }
        *sp = s;
 -      *curp = NULL;
        return 0;
  
  bail:
 -      *curp = NULL;
        return -1;
  }
  
 -static struct imap_list *parse_imap_list(struct imap *imap, char **sp)
 -{
 -      struct imap_list *head;
 -
 -      if (!parse_imap_list_l(imap, sp, &head, 0))
 -              return head;
 -      free_list(head);
 -      return NULL;
 -}
 -
 -static struct imap_list *parse_list(char **sp)
 +static void skip_list(char **sp)
  {
 -      return parse_imap_list(NULL, sp);
 +      skip_imap_list_l(sp, 0);
  }
  
  static void parse_capability(struct imap *imap, char *cmd)
@@@ -677,7 -847,7 +688,7 @@@ static int parse_response_code(struct i
        *p++ = 0;
        arg = next_arg(&s);
        if (!strcmp("UIDVALIDITY", arg)) {
 -              if (!(arg = next_arg(&s)) || !(ctx->gen.uidvalidity = atoi(arg))) {
 +              if (!(arg = next_arg(&s)) || !(ctx->uidvalidity = atoi(arg))) {
                        fprintf(stderr, "IMAP error: malformed UIDVALIDITY status\n");
                        return RESP_BAD;
                }
                for (; isspace((unsigned char)*p); p++);
                fprintf(stderr, "*** IMAP ALERT *** %s\n", p);
        } else if (cb && cb->ctx && !strcmp("APPENDUID", arg)) {
 -              if (!(arg = next_arg(&s)) || !(ctx->gen.uidvalidity = atoi(arg)) ||
 +              if (!(arg = next_arg(&s)) || !(ctx->uidvalidity = atoi(arg)) ||
                    !(arg = next_arg(&s)) || !(*(int *)cb->ctx = atoi(arg))) {
                        fprintf(stderr, "IMAP error: malformed APPENDUID status\n");
                        return RESP_BAD;
@@@ -724,28 -894,20 +735,28 @@@ static int get_cmd_result(struct imap_s
                        }
  
                        if (!strcmp("NAMESPACE", arg)) {
 -                              imap->ns_personal = parse_list(&cmd);
 -                              imap->ns_other = parse_list(&cmd);
 -                              imap->ns_shared = parse_list(&cmd);
 +                              /* rfc2342 NAMESPACE response. */
 +                              skip_list(&cmd); /* Personal mailboxes */
 +                              skip_list(&cmd); /* Others' mailboxes */
 +                              skip_list(&cmd); /* Shared mailboxes */
                        } else if (!strcmp("OK", arg) || !strcmp("BAD", arg) ||
                                   !strcmp("NO", arg) || !strcmp("BYE", arg)) {
                                if ((resp = parse_response_code(ctx, NULL, cmd)) != RESP_OK)
                                        return resp;
 -                      } else if (!strcmp("CAPABILITY", arg))
 +                      } else if (!strcmp("CAPABILITY", arg)) {
                                parse_capability(imap, cmd);
 -                      else if ((arg1 = next_arg(&cmd))) {
 -                              if (!strcmp("EXISTS", arg1))
 -                                      ctx->gen.count = atoi(arg);
 -                              else if (!strcmp("RECENT", arg1))
 -                                      ctx->gen.recent = atoi(arg);
 +                      } else if ((arg1 = next_arg(&cmd))) {
 +                              ; /*
 +                                 * Unhandled response-data with at least two words.
 +                                 * Ignore it.
 +                                 *
 +                                 * NEEDSWORK: Previously this case handled '<num> EXISTS'
 +                                 * and '<num> RECENT' but as a probably-unintended side
 +                                 * effect it ignores other unrecognized two-word
 +                                 * responses.  imap-send doesn't ever try to read
 +                                 * messages or mailboxes these days, so consider
 +                                 * eliminating this case.
 +                                 */
                        } else {
                                fprintf(stderr, "IMAP error: unable to parse untagged response\n");
                                return RESP_BAD;
@@@ -847,12 -1009,16 +858,12 @@@ static void imap_close_server(struct im
                imap_exec(ictx, NULL, "LOGOUT");
                socket_shutdown(&imap->buf.sock);
        }
 -      free_list(imap->ns_personal);
 -      free_list(imap->ns_other);
 -      free_list(imap->ns_shared);
        free(imap);
  }
  
 -static void imap_close_store(struct store *ctx)
 +static void imap_close_store(struct imap_store *ctx)
  {
 -      imap_close_server((struct imap_store *)ctx);
 -      free_generic_messages(ctx->msgs);
 +      imap_close_server(ctx);
        free(ctx);
  }
  
@@@ -930,14 -1096,14 +941,14 @@@ static int auth_cram_md5(struct imap_st
  
        ret = socket_write(&ctx->imap->buf.sock, response, strlen(response));
        if (ret != strlen(response))
 -              return error("IMAP error: sending response failed\n");
 +              return error("IMAP error: sending response failed");
  
        free(response);
  
        return 0;
  }
  
 -static struct store *imap_open_store(struct imap_server_conf *srvc)
 +static struct imap_store *imap_open_store(struct imap_server_conf *srvc)
  {
        struct imap_store *ctx;
        struct imap *imap;
                        goto bail;
                }
                if (!srvc->pass) {
 -                      char prompt[80];
 -                      sprintf(prompt, "Password (%s@%s): ", srvc->user, srvc->host);
 -                      arg = git_getpass(prompt);
 -                      if (!arg) {
 -                              perror("getpass");
 -                              exit(1);
 -                      }
 +                      struct strbuf prompt = STRBUF_INIT;
 +                      strbuf_addf(&prompt, "Password (%s@%s): ", srvc->user, srvc->host);
 +                      arg = git_getpass(prompt.buf);
 +                      strbuf_release(&prompt);
                        if (!*arg) {
                                fprintf(stderr, "Skipping account %s@%s, no password\n", srvc->user, srvc->host);
                                goto bail;
        } /* !preauth */
  
        ctx->prefix = "";
 -      ctx->trashnc = 1;
 -      return (struct store *)ctx;
 +      return ctx;
  
  bail:
 -      imap_close_store(&ctx->gen);
 +      imap_close_store(ctx);
        return NULL;
  }
  
 -static int imap_make_flags(int flags, char *buf)
 -{
 -      const char *s;
 -      unsigned i, d;
 -
 -      for (i = d = 0; i < ARRAY_SIZE(Flags); i++)
 -              if (flags & (1 << i)) {
 -                      buf[d++] = ' ';
 -                      buf[d++] = '\\';
 -                      for (s = Flags[i]; *s; s++)
 -                              buf[d++] = *s;
 -              }
 -      buf[0] = '(';
 -      buf[d++] = ')';
 -      return d;
 -}
 -
 -static void lf_to_crlf(struct msg_data *msg)
 +/*
 + * Insert CR characters as necessary in *msg to ensure that every LF
 + * character in *msg is preceded by a CR.
 + */
 +static void lf_to_crlf(struct strbuf *msg)
  {
        char *new;
 -      int i, j, lfnum = 0;
 -
 -      if (msg->data[0] == '\n')
 -              lfnum++;
 -      for (i = 1; i < msg->len; i++) {
 -              if (msg->data[i - 1] != '\r' && msg->data[i] == '\n')
 -                      lfnum++;
 +      size_t i, j;
 +      char lastc;
 +
 +      /* First pass: tally, in j, the size of the new string: */
 +      for (i = j = 0, lastc = '\0'; i < msg->len; i++) {
 +              if (msg->buf[i] == '\n' && lastc != '\r')
 +                      j++; /* a CR will need to be added here */
 +              lastc = msg->buf[i];
 +              j++;
        }
  
 -      new = xmalloc(msg->len + lfnum);
 -      if (msg->data[0] == '\n') {
 -              new[0] = '\r';
 -              new[1] = '\n';
 -              i = 1;
 -              j = 2;
 -      } else {
 -              new[0] = msg->data[0];
 -              i = 1;
 -              j = 1;
 -      }
 -      for ( ; i < msg->len; i++) {
 -              if (msg->data[i] != '\n') {
 -                      new[j++] = msg->data[i];
 -                      continue;
 -              }
 -              if (msg->data[i - 1] != '\r')
 +      new = xmalloc(j + 1);
 +
 +      /*
 +       * Second pass: write the new string.  Note that this loop is
 +       * otherwise identical to the first pass.
 +       */
 +      for (i = j = 0, lastc = '\0'; i < msg->len; i++) {
 +              if (msg->buf[i] == '\n' && lastc != '\r')
                        new[j++] = '\r';
 -              /* otherwise it already had CR before */
 -              new[j++] = '\n';
 +              lastc = new[j++] = msg->buf[i];
        }
 -      msg->len += lfnum;
 -      free(msg->data);
 -      msg->data = new;
 +      strbuf_attach(msg, new, j, j + 1);
  }
  
 -static int imap_store_msg(struct store *gctx, struct msg_data *data)
 +/*
 + * Store msg to IMAP.  Also detach and free the data from msg->data,
 + * leaving msg->data empty.
 + */
 +static int imap_store_msg(struct imap_store *ctx, struct strbuf *msg)
  {
 -      struct imap_store *ctx = (struct imap_store *)gctx;
        struct imap *imap = ctx->imap;
        struct imap_cmd_cb cb;
        const char *prefix, *box;
 -      int ret, d;
 -      char flagstr[128];
 +      int ret;
  
 -      lf_to_crlf(data);
 +      lf_to_crlf(msg);
        memset(&cb, 0, sizeof(cb));
  
 -      cb.dlen = data->len;
 -      cb.data = xmalloc(cb.dlen);
 -      memcpy(cb.data, data->data, data->len);
 +      cb.dlen = msg->len;
 +      cb.data = strbuf_detach(msg, NULL);
  
 -      d = 0;
 -      if (data->flags) {
 -              d = imap_make_flags(data->flags, flagstr);
 -              flagstr[d++] = ' ';
 -      }
 -      flagstr[d] = 0;
 -
 -      box = gctx->name;
 +      box = ctx->name;
        prefix = !strcmp(box, "INBOX") ? "" : ctx->prefix;
        cb.create = 0;
 -      ret = imap_exec_m(ctx, &cb, "APPEND \"%s%s\" %s", prefix, box, flagstr);
 +      ret = imap_exec_m(ctx, &cb, "APPEND \"%s%s\" ", prefix, box);
        imap->caps = imap->rcaps;
        if (ret != DRV_OK)
                return ret;
 -      gctx->count++;
  
        return DRV_OK;
  }
  
 -static void encode_html_chars(struct strbuf *p)
 -{
 -      int i;
 -      for (i = 0; i < p->len; i++) {
 -              if (p->buf[i] == '&')
 -                      strbuf_splice(p, i, 1, "&amp;", 5);
 -              if (p->buf[i] == '<')
 -                      strbuf_splice(p, i, 1, "&lt;", 4);
 -              if (p->buf[i] == '>')
 -                      strbuf_splice(p, i, 1, "&gt;", 4);
 -              if (p->buf[i] == '"')
 -                      strbuf_splice(p, i, 1, "&quot;", 6);
 -      }
 -}
 -static void wrap_in_html(struct msg_data *msg)
 +static void wrap_in_html(struct strbuf *msg)
  {
        struct strbuf buf = STRBUF_INIT;
 -      struct strbuf **lines;
 -      struct strbuf **p;
        static char *content_type = "Content-Type: text/html;\n";
        static char *pre_open = "<pre>\n";
        static char *pre_close = "</pre>\n";
 -      int added_header = 0;
 -
 -      strbuf_attach(&buf, msg->data, msg->len, msg->len);
 -      lines = strbuf_split(&buf, '\n');
 -      strbuf_release(&buf);
 -      for (p = lines; *p; p++) {
 -              if (! added_header) {
 -                      if ((*p)->len == 1 && *((*p)->buf) == '\n') {
 -                              strbuf_addstr(&buf, content_type);
 -                              strbuf_addbuf(&buf, *p);
 -                              strbuf_addstr(&buf, pre_open);
 -                              added_header = 1;
 -                              continue;
 -                      }
 -              }
 -              else
 -                      encode_html_chars(*p);
 -              strbuf_addbuf(&buf, *p);
 -      }
 +      const char *body = strstr(msg->buf, "\n\n");
 +
 +      if (!body)
 +              return; /* Headers but no body; no wrapping needed */
 +
 +      body += 2;
 +
 +      strbuf_add(&buf, msg->buf, body - msg->buf - 1);
 +      strbuf_addstr(&buf, content_type);
 +      strbuf_addch(&buf, '\n');
 +      strbuf_addstr(&buf, pre_open);
 +      strbuf_addstr_xml_quoted(&buf, body);
        strbuf_addstr(&buf, pre_close);
 -      strbuf_list_free(lines);
 -      msg->len  = buf.len;
 -      msg->data = strbuf_detach(&buf, NULL);
 +
 +      strbuf_release(msg);
 +      *msg = buf;
  }
  
  #define CHUNKSIZE 0x1000
  
 -static int read_message(FILE *f, struct msg_data *msg)
 +static int read_message(FILE *f, struct strbuf *all_msgs)
  {
 -      struct strbuf buf = STRBUF_INIT;
 -
 -      memset(msg, 0, sizeof(*msg));
 -
        do {
 -              if (strbuf_fread(&buf, CHUNKSIZE, f) <= 0)
 +              if (strbuf_fread(all_msgs, CHUNKSIZE, f) <= 0)
                        break;
        } while (!feof(f));
  
 -      msg->len  = buf.len;
 -      msg->data = strbuf_detach(&buf, NULL);
 -      return msg->len;
 +      return ferror(f) ? -1 : 0;
  }
  
 -static int count_messages(struct msg_data *msg)
 +static int count_messages(struct strbuf *all_msgs)
  {
        int count = 0;
 -      char *p = msg->data;
 +      char *p = all_msgs->buf;
  
        while (1) {
                if (!prefixcmp(p, "From ")) {
        return count;
  }
  
 -static int split_msg(struct msg_data *all_msgs, struct msg_data *msg, int *ofs)
 +/*
 + * Copy the next message from all_msgs, starting at offset *ofs, to
 + * msg.  Update *ofs to the start of the following message.  Return
 + * true iff a message was successfully copied.
 + */
 +static int split_msg(struct strbuf *all_msgs, struct strbuf *msg, int *ofs)
  {
        char *p, *data;
 +      size_t len;
  
 -      memset(msg, 0, sizeof *msg);
        if (*ofs >= all_msgs->len)
                return 0;
  
 -      data = &all_msgs->data[*ofs];
 -      msg->len = all_msgs->len - *ofs;
 +      data = &all_msgs->buf[*ofs];
 +      len = all_msgs->len - *ofs;
  
 -      if (msg->len < 5 || prefixcmp(data, "From "))
 +      if (len < 5 || prefixcmp(data, "From "))
                return 0;
  
        p = strchr(data, '\n');
        if (p) {
 -              p = &p[1];
 -              msg->len -= p-data;
 -              *ofs += p-data;
 +              p++;
 +              len -= p - data;
 +              *ofs += p - data;
                data = p;
        }
  
        p = strstr(data, "\nFrom ");
        if (p)
 -              msg->len = &p[1] - data;
 +              len = &p[1] - data;
  
 -      msg->data = xmemdupz(data, msg->len);
 -      *ofs += msg->len;
 +      strbuf_add(msg, data, len);
 +      *ofs += len;
        return 1;
  }
  
@@@ -1357,9 -1581,8 +1368,9 @@@ static int git_imap_config(const char *
  
  int main(int argc, char **argv)
  {
 -      struct msg_data all_msgs, msg;
 -      struct store *ctx = NULL;
 +      struct strbuf all_msgs = STRBUF_INIT;
 +      struct strbuf msg = STRBUF_INIT;
 +      struct imap_store *ctx = NULL;
        int ofs = 0;
        int r;
        int total, n = 0;
  
        git_extract_argv0_path(argv[0]);
  
 +      git_setup_gettext();
 +
        if (argc != 1)
                usage(imap_send_usage);
  
        }
  
        /* read the messages */
 -      if (!read_message(stdin, &all_msgs)) {
 +      if (read_message(stdin, &all_msgs)) {
 +              fprintf(stderr, "error reading input\n");
 +              return 1;
 +      }
 +
 +      if (all_msgs.len == 0) {
                fprintf(stderr, "nothing to send\n");
                return 1;
        }
        ctx->name = imap_folder;
        while (1) {
                unsigned percent = n * 100 / total;
 +
                fprintf(stderr, "%4u%% (%d/%d) done\r", percent, n, total);
                if (!split_msg(&all_msgs, &msg, &ofs))
                        break;