Git fork
1/*
2 * git-imap-send - drops patches into an imap Drafts folder
3 * derived from isync/mbsync - mailbox synchronizer
4 *
5 * Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org>
6 * Copyright (C) 2002-2004 Oswald Buddenhagen <ossi@users.sf.net>
7 * Copyright (C) 2004 Theodore Y. Ts'o <tytso@mit.edu>
8 * Copyright (C) 2006 Mike McCormack
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, see <https://www.gnu.org/licenses/>.
22 */
23
24#define USE_THE_REPOSITORY_VARIABLE
25#define DISABLE_SIGN_COMPARE_WARNINGS
26
27#include "git-compat-util.h"
28#include "advice.h"
29#include "config.h"
30#include "credential.h"
31#include "environment.h"
32#include "gettext.h"
33#include "run-command.h"
34#include "parse-options.h"
35#include "setup.h"
36#include "strbuf.h"
37#ifdef USE_CURL_FOR_IMAP_SEND
38#include "http.h"
39#endif
40
41#if defined(USE_CURL_FOR_IMAP_SEND)
42/* Always default to curl if it's available. */
43#define USE_CURL_DEFAULT 1
44#else
45/* We don't have curl, so continue to use the historical implementation */
46#define USE_CURL_DEFAULT 0
47#endif
48
49static int verbosity;
50static int list_folders;
51static int use_curl = USE_CURL_DEFAULT;
52static char *opt_folder;
53
54static char const * const imap_send_usage[] = {
55 N_("git imap-send [-v] [-q] [--[no-]curl] [(--folder|-f) <folder>] < <mbox>"),
56 "git imap-send --list",
57 NULL
58};
59
60static struct option imap_send_options[] = {
61 OPT__VERBOSITY(&verbosity),
62 OPT_BOOL(0, "curl", &use_curl, "use libcurl to communicate with the IMAP server"),
63 OPT_STRING('f', "folder", &opt_folder, "folder", "specify the IMAP folder"),
64 OPT_BOOL(0, "list", &list_folders, "list all folders on the IMAP server"),
65 OPT_END()
66};
67
68#undef DRV_OK
69#define DRV_OK 0
70#define DRV_MSG_BAD -1
71#define DRV_BOX_BAD -2
72#define DRV_STORE_BAD -3
73
74__attribute__((format (printf, 1, 2)))
75static void imap_info(const char *, ...);
76__attribute__((format (printf, 1, 2)))
77static void imap_warn(const char *, ...);
78
79static char *next_arg(char **);
80
81struct imap_server_conf {
82 char *tunnel;
83 char *host;
84 int port;
85 char *folder;
86 char *user;
87 char *pass;
88 int use_ssl;
89 int ssl_verify;
90 int use_html;
91 char *auth_method;
92};
93
94struct imap_socket {
95 int fd[2];
96#if defined(NO_OPENSSL) && !defined(HAVE_OPENSSL_CSPRNG)
97 void *ssl;
98#else
99 SSL *ssl;
100#endif
101};
102
103struct imap_buffer {
104 struct imap_socket sock;
105 int bytes;
106 int offset;
107 char buf[1024];
108};
109
110struct imap_cmd;
111
112struct imap {
113 int uidnext; /* from SELECT responses */
114 unsigned caps, rcaps; /* CAPABILITY results */
115 /* command queue */
116 int nexttag, num_in_progress, literal_pending;
117 struct imap_cmd *in_progress, **in_progress_append;
118 struct imap_buffer buf; /* this is BIG, so put it last */
119};
120
121struct imap_store {
122 const struct imap_server_conf *cfg;
123 /* currently open mailbox */
124 const char *name; /* foreign! maybe preset? */
125 int uidvalidity;
126 struct imap *imap;
127 const char *prefix;
128};
129
130struct imap_cmd_cb {
131 int (*cont)(struct imap_store *ctx, const char *prompt);
132 void *ctx;
133 char *data;
134 int dlen;
135};
136
137struct imap_cmd {
138 struct imap_cmd *next;
139 struct imap_cmd_cb cb;
140 char *cmd;
141 int tag;
142};
143
144#define CAP(cap) (imap->caps & (1 << (cap)))
145
146enum CAPABILITY {
147 NOLOGIN = 0,
148 UIDPLUS,
149 LITERALPLUS,
150 NAMESPACE,
151 STARTTLS,
152 AUTH_PLAIN,
153 AUTH_CRAM_MD5,
154 AUTH_OAUTHBEARER,
155 AUTH_XOAUTH2,
156};
157
158static const char *cap_list[] = {
159 "LOGINDISABLED",
160 "UIDPLUS",
161 "LITERAL+",
162 "NAMESPACE",
163 "STARTTLS",
164 "AUTH=PLAIN",
165 "AUTH=CRAM-MD5",
166 "AUTH=OAUTHBEARER",
167 "AUTH=XOAUTH2",
168};
169
170#define RESP_OK 0
171#define RESP_NO 1
172#define RESP_BAD 2
173
174static int get_cmd_result(struct imap_store *ctx, struct imap_cmd *tcmd);
175
176
177#ifndef NO_OPENSSL
178static void ssl_socket_perror(const char *func)
179{
180 fprintf(stderr, "%s: %s\n", func, ERR_error_string(ERR_get_error(), NULL));
181}
182#endif
183
184static void socket_perror(const char *func, struct imap_socket *sock, int ret)
185{
186#ifndef NO_OPENSSL
187 if (sock->ssl) {
188 int sslerr = SSL_get_error(sock->ssl, ret);
189 switch (sslerr) {
190 case SSL_ERROR_NONE:
191 break;
192 case SSL_ERROR_SYSCALL:
193 perror("SSL_connect");
194 break;
195 default:
196 ssl_socket_perror("SSL_connect");
197 break;
198 }
199 } else
200#endif
201 {
202 if (ret < 0)
203 perror(func);
204 else
205 fprintf(stderr, "%s: unexpected EOF\n", func);
206 }
207 /* mark as used to appease -Wunused-parameter with NO_OPENSSL */
208 (void)sock;
209}
210
211#ifdef NO_OPENSSL
212static int ssl_socket_connect(struct imap_socket *sock UNUSED,
213 const struct imap_server_conf *cfg UNUSED,
214 int use_tls_only UNUSED)
215{
216 fprintf(stderr, "SSL requested, but SSL support is not compiled in\n");
217 return -1;
218}
219
220#else
221
222static int host_matches(const char *host, const char *pattern)
223{
224 if (pattern[0] == '*' && pattern[1] == '.') {
225 pattern += 2;
226 if (!(host = strchr(host, '.')))
227 return 0;
228 host++;
229 }
230
231 return *host && *pattern && !strcasecmp(host, pattern);
232}
233
234static int verify_hostname(X509 *cert, const char *hostname)
235{
236 int len;
237 X509_NAME *subj;
238 char cname[1000];
239 int i, found;
240 STACK_OF(GENERAL_NAME) *subj_alt_names;
241
242 /* try the DNS subjectAltNames */
243 found = 0;
244 if ((subj_alt_names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL))) {
245 int num_subj_alt_names = sk_GENERAL_NAME_num(subj_alt_names);
246 for (i = 0; !found && i < num_subj_alt_names; i++) {
247 GENERAL_NAME *subj_alt_name = sk_GENERAL_NAME_value(subj_alt_names, i);
248 if (subj_alt_name->type == GEN_DNS &&
249 strlen((const char *)subj_alt_name->d.ia5->data) == (size_t)subj_alt_name->d.ia5->length &&
250 host_matches(hostname, (const char *)(subj_alt_name->d.ia5->data)))
251 found = 1;
252 }
253 sk_GENERAL_NAME_pop_free(subj_alt_names, GENERAL_NAME_free);
254 }
255 if (found)
256 return 0;
257
258 /* try the common name */
259 if (!(subj = X509_get_subject_name(cert)))
260 return error("cannot get certificate subject");
261 if ((len = X509_NAME_get_text_by_NID(subj, NID_commonName, cname, sizeof(cname))) < 0)
262 return error("cannot get certificate common name");
263 if (strlen(cname) == (size_t)len && host_matches(hostname, cname))
264 return 0;
265 return error("certificate owner '%s' does not match hostname '%s'",
266 cname, hostname);
267}
268
269static int ssl_socket_connect(struct imap_socket *sock,
270 const struct imap_server_conf *cfg,
271 int use_tls_only)
272{
273#if (OPENSSL_VERSION_NUMBER >= 0x10000000L)
274 const SSL_METHOD *meth;
275#else
276 SSL_METHOD *meth;
277#endif
278 SSL_CTX *ctx;
279 int ret;
280 X509 *cert;
281
282 SSL_library_init();
283 SSL_load_error_strings();
284
285 meth = SSLv23_method();
286 if (!meth) {
287 ssl_socket_perror("SSLv23_method");
288 return -1;
289 }
290
291 ctx = SSL_CTX_new(meth);
292 if (!ctx) {
293 ssl_socket_perror("SSL_CTX_new");
294 return -1;
295 }
296
297 if (use_tls_only)
298 SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
299
300 if (cfg->ssl_verify)
301 SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
302
303 if (!SSL_CTX_set_default_verify_paths(ctx)) {
304 ssl_socket_perror("SSL_CTX_set_default_verify_paths");
305 return -1;
306 }
307 sock->ssl = SSL_new(ctx);
308 if (!sock->ssl) {
309 ssl_socket_perror("SSL_new");
310 return -1;
311 }
312 if (!SSL_set_rfd(sock->ssl, sock->fd[0])) {
313 ssl_socket_perror("SSL_set_rfd");
314 return -1;
315 }
316 if (!SSL_set_wfd(sock->ssl, sock->fd[1])) {
317 ssl_socket_perror("SSL_set_wfd");
318 return -1;
319 }
320
321#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
322 /*
323 * SNI (RFC4366)
324 * OpenSSL does not document this function, but the implementation
325 * returns 1 on success, 0 on failure after calling SSLerr().
326 */
327 ret = SSL_set_tlsext_host_name(sock->ssl, cfg->host);
328 if (ret != 1)
329 warning("SSL_set_tlsext_host_name(%s) failed.", cfg->host);
330#endif
331
332 ret = SSL_connect(sock->ssl);
333 if (ret <= 0) {
334 socket_perror("SSL_connect", sock, ret);
335 return -1;
336 }
337
338 if (cfg->ssl_verify) {
339 /* make sure the hostname matches that of the certificate */
340 cert = SSL_get_peer_certificate(sock->ssl);
341 if (!cert)
342 return error("unable to get peer certificate.");
343 if (SSL_get_verify_result(sock->ssl) != X509_V_OK)
344 return error("unable to verify peer certificate");
345 if (verify_hostname(cert, cfg->host) < 0)
346 return -1;
347 }
348
349 return 0;
350}
351#endif
352
353static int socket_read(struct imap_socket *sock, char *buf, int len)
354{
355 ssize_t n;
356#ifndef NO_OPENSSL
357 if (sock->ssl)
358 n = SSL_read(sock->ssl, buf, len);
359 else
360#endif
361 n = xread(sock->fd[0], buf, len);
362 if (n <= 0) {
363 socket_perror("read", sock, n);
364 close(sock->fd[0]);
365 close(sock->fd[1]);
366 sock->fd[0] = sock->fd[1] = -1;
367 }
368 return n;
369}
370
371static int socket_write(struct imap_socket *sock, const char *buf, int len)
372{
373 int n;
374#ifndef NO_OPENSSL
375 if (sock->ssl)
376 n = SSL_write(sock->ssl, buf, len);
377 else
378#endif
379 n = write_in_full(sock->fd[1], buf, len);
380 if (n != len) {
381 socket_perror("write", sock, n);
382 close(sock->fd[0]);
383 close(sock->fd[1]);
384 sock->fd[0] = sock->fd[1] = -1;
385 }
386 return n;
387}
388
389static void socket_shutdown(struct imap_socket *sock)
390{
391#ifndef NO_OPENSSL
392 if (sock->ssl) {
393 SSL_shutdown(sock->ssl);
394 SSL_free(sock->ssl);
395 }
396#endif
397 close(sock->fd[0]);
398 close(sock->fd[1]);
399}
400
401/* simple line buffering */
402static int buffer_gets(struct imap_buffer *b, char **s)
403{
404 int n;
405 int start = b->offset;
406
407 *s = b->buf + start;
408
409 for (;;) {
410 /* make sure we have enough data to read the \r\n sequence */
411 if (b->offset + 1 >= b->bytes) {
412 if (start) {
413 /* shift down used bytes */
414 *s = b->buf;
415
416 assert(start <= b->bytes);
417 n = b->bytes - start;
418
419 if (n)
420 memmove(b->buf, b->buf + start, n);
421 b->offset -= start;
422 b->bytes = n;
423 start = 0;
424 }
425
426 n = socket_read(&b->sock, b->buf + b->bytes,
427 sizeof(b->buf) - b->bytes);
428
429 if (n <= 0)
430 return -1;
431
432 b->bytes += n;
433 }
434
435 if (b->buf[b->offset] == '\r') {
436 assert(b->offset + 1 < b->bytes);
437 if (b->buf[b->offset + 1] == '\n') {
438 b->buf[b->offset] = 0; /* terminate the string */
439 b->offset += 2; /* next line */
440 if ((0 < verbosity) || (list_folders && strstr(*s, "* LIST")))
441 puts(*s);
442 return 0;
443 }
444 }
445
446 b->offset++;
447 }
448 /* not reached */
449}
450
451__attribute__((format (printf, 1, 2)))
452static void imap_info(const char *msg, ...)
453{
454 va_list va;
455
456 if (0 <= verbosity) {
457 va_start(va, msg);
458 vprintf(msg, va);
459 va_end(va);
460 fflush(stdout);
461 }
462}
463
464__attribute__((format (printf, 1, 2)))
465static void imap_warn(const char *msg, ...)
466{
467 va_list va;
468
469 if (-2 < verbosity) {
470 va_start(va, msg);
471 vfprintf(stderr, msg, va);
472 va_end(va);
473 }
474}
475
476static char *next_arg(char **s)
477{
478 char *ret;
479
480 if (!s || !*s)
481 return NULL;
482 while (isspace((unsigned char) **s))
483 (*s)++;
484 if (!**s) {
485 *s = NULL;
486 return NULL;
487 }
488 if (**s == '"') {
489 ++*s;
490 ret = *s;
491 *s = strchr(*s, '"');
492 } else {
493 ret = *s;
494 while (**s && !isspace((unsigned char) **s))
495 (*s)++;
496 }
497 if (*s) {
498 if (**s)
499 *(*s)++ = 0;
500 if (!**s)
501 *s = NULL;
502 }
503 return ret;
504}
505
506static struct imap_cmd *issue_imap_cmd(struct imap_store *ctx,
507 struct imap_cmd_cb *cb,
508 const char *fmt, va_list ap)
509{
510 struct imap *imap = ctx->imap;
511 struct imap_cmd *cmd;
512 int n;
513 struct strbuf buf = STRBUF_INIT;
514
515 cmd = xmalloc(sizeof(struct imap_cmd));
516 cmd->cmd = xstrvfmt(fmt, ap);
517 cmd->tag = ++imap->nexttag;
518
519 if (cb)
520 cmd->cb = *cb;
521 else
522 memset(&cmd->cb, 0, sizeof(cmd->cb));
523
524 while (imap->literal_pending)
525 get_cmd_result(ctx, NULL);
526
527 if (!cmd->cb.data)
528 strbuf_addf(&buf, "%d %s\r\n", cmd->tag, cmd->cmd);
529 else
530 strbuf_addf(&buf, "%d %s{%d%s}\r\n", cmd->tag, cmd->cmd,
531 cmd->cb.dlen, CAP(LITERALPLUS) ? "+" : "");
532 if (buf.len > INT_MAX)
533 die("imap command overflow!");
534
535 if (0 < verbosity) {
536 if (imap->num_in_progress)
537 printf("(%d in progress) ", imap->num_in_progress);
538 if (!starts_with(cmd->cmd, "LOGIN"))
539 printf(">>> %s", buf.buf);
540 else
541 printf(">>> %d LOGIN <user> <pass>\n", cmd->tag);
542 }
543 if (socket_write(&imap->buf.sock, buf.buf, buf.len) != buf.len) {
544 free(cmd->cmd);
545 free(cmd);
546 if (cb)
547 free(cb->data);
548 strbuf_release(&buf);
549 return NULL;
550 }
551 strbuf_release(&buf);
552 if (cmd->cb.data) {
553 if (CAP(LITERALPLUS)) {
554 n = socket_write(&imap->buf.sock, cmd->cb.data, cmd->cb.dlen);
555 free(cmd->cb.data);
556 if (n != cmd->cb.dlen ||
557 socket_write(&imap->buf.sock, "\r\n", 2) != 2) {
558 free(cmd->cmd);
559 free(cmd);
560 return NULL;
561 }
562 cmd->cb.data = NULL;
563 } else
564 imap->literal_pending = 1;
565 } else if (cmd->cb.cont)
566 imap->literal_pending = 1;
567 cmd->next = NULL;
568 *imap->in_progress_append = cmd;
569 imap->in_progress_append = &cmd->next;
570 imap->num_in_progress++;
571 return cmd;
572}
573
574__attribute__((format (printf, 3, 4)))
575static int imap_exec(struct imap_store *ctx, struct imap_cmd_cb *cb,
576 const char *fmt, ...)
577{
578 va_list ap;
579 struct imap_cmd *cmdp;
580
581 va_start(ap, fmt);
582 cmdp = issue_imap_cmd(ctx, cb, fmt, ap);
583 va_end(ap);
584 if (!cmdp)
585 return RESP_BAD;
586
587 return get_cmd_result(ctx, cmdp);
588}
589
590__attribute__((format (printf, 3, 4)))
591static int imap_exec_m(struct imap_store *ctx, struct imap_cmd_cb *cb,
592 const char *fmt, ...)
593{
594 va_list ap;
595 struct imap_cmd *cmdp;
596
597 va_start(ap, fmt);
598 cmdp = issue_imap_cmd(ctx, cb, fmt, ap);
599 va_end(ap);
600 if (!cmdp)
601 return DRV_STORE_BAD;
602
603 switch (get_cmd_result(ctx, cmdp)) {
604 case RESP_BAD: return DRV_STORE_BAD;
605 case RESP_NO: return DRV_MSG_BAD;
606 default: return DRV_OK;
607 }
608}
609
610static int skip_imap_list_l(char **sp, int level)
611{
612 char *s = *sp;
613
614 for (;;) {
615 while (isspace((unsigned char)*s))
616 s++;
617 if (level && *s == ')') {
618 s++;
619 break;
620 }
621 if (*s == '(') {
622 /* sublist */
623 s++;
624 if (skip_imap_list_l(&s, level + 1))
625 goto bail;
626 } else if (*s == '"') {
627 /* quoted string */
628 s++;
629 for (; *s != '"'; s++)
630 if (!*s)
631 goto bail;
632 s++;
633 } else {
634 /* atom */
635 for (; *s && !isspace((unsigned char)*s); s++)
636 if (level && *s == ')')
637 break;
638 }
639
640 if (!level)
641 break;
642 if (!*s)
643 goto bail;
644 }
645 *sp = s;
646 return 0;
647
648bail:
649 return -1;
650}
651
652static void skip_list(char **sp)
653{
654 skip_imap_list_l(sp, 0);
655}
656
657static void parse_capability(struct imap *imap, char *cmd)
658{
659 char *arg;
660 unsigned i;
661
662 imap->caps = 0x80000000;
663 while ((arg = next_arg(&cmd)))
664 for (i = 0; i < ARRAY_SIZE(cap_list); i++)
665 if (!strcmp(cap_list[i], arg))
666 imap->caps |= 1 << i;
667 imap->rcaps = imap->caps;
668}
669
670static int parse_response_code(struct imap_store *ctx, struct imap_cmd_cb *cb,
671 char *s)
672{
673 struct imap *imap = ctx->imap;
674 char *arg, *p;
675
676 if (!s || *s != '[')
677 return RESP_OK; /* no response code */
678 s++;
679 if (!(p = strchr(s, ']'))) {
680 fprintf(stderr, "IMAP error: malformed response code\n");
681 return RESP_BAD;
682 }
683 *p++ = 0;
684 arg = next_arg(&s);
685 if (!arg) {
686 fprintf(stderr, "IMAP error: empty response code\n");
687 return RESP_BAD;
688 }
689 if (!strcmp("UIDVALIDITY", arg)) {
690 if (!(arg = next_arg(&s)) || strtol_i(arg, 10, &ctx->uidvalidity) || !ctx->uidvalidity) {
691 fprintf(stderr, "IMAP error: malformed UIDVALIDITY status\n");
692 return RESP_BAD;
693 }
694 } else if (!strcmp("UIDNEXT", arg)) {
695 if (!(arg = next_arg(&s)) || strtol_i(arg, 10, &imap->uidnext) || !imap->uidnext) {
696 fprintf(stderr, "IMAP error: malformed NEXTUID status\n");
697 return RESP_BAD;
698 }
699 } else if (!strcmp("CAPABILITY", arg)) {
700 parse_capability(imap, s);
701 } else if (!strcmp("ALERT", arg)) {
702 /* RFC2060 says that these messages MUST be displayed
703 * to the user
704 */
705 for (; isspace((unsigned char)*p); p++);
706 fprintf(stderr, "*** IMAP ALERT *** %s\n", p);
707 } else if (cb && cb->ctx && !strcmp("APPENDUID", arg)) {
708 if (!(arg = next_arg(&s)) || strtol_i(arg, 10, &ctx->uidvalidity) || !ctx->uidvalidity ||
709 !(arg = next_arg(&s)) || strtol_i(arg, 10, (int *)cb->ctx) || !cb->ctx) {
710 fprintf(stderr, "IMAP error: malformed APPENDUID status\n");
711 return RESP_BAD;
712 }
713 }
714 return RESP_OK;
715}
716
717static int get_cmd_result(struct imap_store *ctx, struct imap_cmd *tcmd)
718{
719 struct imap *imap = ctx->imap;
720 struct imap_cmd *cmdp, **pcmdp;
721 char *cmd;
722 const char *arg, *arg1;
723 int n, resp, resp2, tag;
724
725 for (;;) {
726 if (buffer_gets(&imap->buf, &cmd))
727 return RESP_BAD;
728
729 arg = next_arg(&cmd);
730 if (!arg) {
731 fprintf(stderr, "IMAP error: empty response\n");
732 return RESP_BAD;
733 }
734 if (*arg == '*') {
735 arg = next_arg(&cmd);
736 if (!arg) {
737 fprintf(stderr, "IMAP error: unable to parse untagged response\n");
738 return RESP_BAD;
739 }
740
741 if (!strcmp("NAMESPACE", arg)) {
742 /* rfc2342 NAMESPACE response. */
743 skip_list(&cmd); /* Personal mailboxes */
744 skip_list(&cmd); /* Others' mailboxes */
745 skip_list(&cmd); /* Shared mailboxes */
746 } else if (!strcmp("OK", arg) || !strcmp("BAD", arg) ||
747 !strcmp("NO", arg) || !strcmp("BYE", arg)) {
748 if ((resp = parse_response_code(ctx, NULL, cmd)) != RESP_OK)
749 return resp;
750 } else if (!strcmp("CAPABILITY", arg)) {
751 parse_capability(imap, cmd);
752 } else if ((arg1 = next_arg(&cmd))) {
753 ; /*
754 * Unhandled response-data with at least two words.
755 * Ignore it.
756 *
757 * NEEDSWORK: Previously this case handled '<num> EXISTS'
758 * and '<num> RECENT' but as a probably-unintended side
759 * effect it ignores other unrecognized two-word
760 * responses. imap-send doesn't ever try to read
761 * messages or mailboxes these days, so consider
762 * eliminating this case.
763 */
764 } else {
765 fprintf(stderr, "IMAP error: unable to parse untagged response\n");
766 return RESP_BAD;
767 }
768 } else if (!imap->in_progress) {
769 fprintf(stderr, "IMAP error: unexpected reply: %s %s\n", arg, cmd ? cmd : "");
770 return RESP_BAD;
771 } else if (*arg == '+') {
772 /* This can happen only with the last command underway, as
773 it enforces a round-trip. */
774 cmdp = (struct imap_cmd *)((char *)imap->in_progress_append -
775 offsetof(struct imap_cmd, next));
776 if (cmdp->cb.data) {
777 n = socket_write(&imap->buf.sock, cmdp->cb.data, cmdp->cb.dlen);
778 FREE_AND_NULL(cmdp->cb.data);
779 if (n != (int)cmdp->cb.dlen)
780 return RESP_BAD;
781 } else if (cmdp->cb.cont) {
782 if (cmdp->cb.cont(ctx, cmd))
783 return RESP_BAD;
784 } else {
785 fprintf(stderr, "IMAP error: unexpected command continuation request\n");
786 return RESP_BAD;
787 }
788 if (socket_write(&imap->buf.sock, "\r\n", 2) != 2)
789 return RESP_BAD;
790 if (!cmdp->cb.cont)
791 imap->literal_pending = 0;
792 if (!tcmd)
793 return DRV_OK;
794 } else {
795 if (strtol_i(arg, 10, &tag)) {
796 fprintf(stderr, "IMAP error: malformed tag %s\n", arg);
797 return RESP_BAD;
798 }
799 for (pcmdp = &imap->in_progress; (cmdp = *pcmdp); pcmdp = &cmdp->next)
800 if (cmdp->tag == tag)
801 goto gottag;
802 fprintf(stderr, "IMAP error: unexpected tag %s\n", arg);
803 return RESP_BAD;
804 gottag:
805 if (!(*pcmdp = cmdp->next))
806 imap->in_progress_append = pcmdp;
807 imap->num_in_progress--;
808 if (cmdp->cb.cont || cmdp->cb.data)
809 imap->literal_pending = 0;
810 arg = next_arg(&cmd);
811 if (!arg)
812 arg = "";
813 if (!strcmp("OK", arg))
814 resp = DRV_OK;
815 else {
816 if (!strcmp("NO", arg))
817 resp = RESP_NO;
818 else /*if (!strcmp("BAD", arg))*/
819 resp = RESP_BAD;
820 fprintf(stderr, "IMAP command '%s' returned response (%s) - %s\n",
821 !starts_with(cmdp->cmd, "LOGIN") ?
822 cmdp->cmd : "LOGIN <user> <pass>",
823 arg, cmd ? cmd : "");
824 }
825 if ((resp2 = parse_response_code(ctx, &cmdp->cb, cmd)) > resp)
826 resp = resp2;
827 free(cmdp->cb.data);
828 free(cmdp->cmd);
829 free(cmdp);
830 if (!tcmd || tcmd == cmdp)
831 return resp;
832 }
833 }
834 /* not reached */
835}
836
837static void imap_close_server(struct imap_store *ictx)
838{
839 struct imap *imap = ictx->imap;
840
841 if (imap->buf.sock.fd[0] != -1) {
842 imap_exec(ictx, NULL, "LOGOUT");
843 socket_shutdown(&imap->buf.sock);
844 }
845 free(imap);
846}
847
848static void imap_close_store(struct imap_store *ctx)
849{
850 imap_close_server(ctx);
851 free(ctx);
852}
853
854#ifndef NO_OPENSSL
855
856/*
857 * hexchar() and cram() functions are based on the code from the isync
858 * project (https://isync.sourceforge.io/).
859 */
860static char hexchar(unsigned int b)
861{
862 return b < 10 ? '0' + b : 'a' + (b - 10);
863}
864
865#define ENCODED_SIZE(n) (4 * DIV_ROUND_UP((n), 3))
866static char *plain_base64(const char *user, const char *pass)
867{
868 struct strbuf raw = STRBUF_INIT;
869 int b64_len;
870 char *b64;
871
872 /*
873 * Compose the PLAIN string
874 *
875 * The username and password are combined to one string and base64 encoded.
876 * "\0user\0pass"
877 *
878 * The method has been described in RFC4616.
879 *
880 * https://datatracker.ietf.org/doc/html/rfc4616
881 */
882 strbuf_addch(&raw, '\0');
883 strbuf_addstr(&raw, user);
884 strbuf_addch(&raw, '\0');
885 strbuf_addstr(&raw, pass);
886
887 b64 = xmallocz(ENCODED_SIZE(raw.len));
888 b64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw.buf, raw.len);
889 strbuf_release(&raw);
890
891 if (b64_len < 0) {
892 free(b64);
893 return NULL;
894 }
895 return b64;
896}
897
898static char *cram(const char *challenge_64, const char *user, const char *pass)
899{
900 int i, resp_len, encoded_len, decoded_len;
901 unsigned char hash[16];
902 char hex[33];
903 char *response, *response_64, *challenge;
904
905 /*
906 * length of challenge_64 (i.e. base-64 encoded string) is a good
907 * enough upper bound for challenge (decoded result).
908 */
909 encoded_len = strlen(challenge_64);
910 challenge = xmalloc(encoded_len);
911 decoded_len = EVP_DecodeBlock((unsigned char *)challenge,
912 (unsigned char *)challenge_64, encoded_len);
913 if (decoded_len < 0)
914 die("invalid challenge %s", challenge_64);
915 if (!HMAC(EVP_md5(), pass, strlen(pass), (unsigned char *)challenge, decoded_len, hash, NULL))
916 die("HMAC error");
917
918 hex[32] = 0;
919 for (i = 0; i < 16; i++) {
920 hex[2 * i] = hexchar((hash[i] >> 4) & 0xf);
921 hex[2 * i + 1] = hexchar(hash[i] & 0xf);
922 }
923
924 /* response: "<user> <digest in hex>" */
925 response = xstrfmt("%s %s", user, hex);
926 resp_len = strlen(response);
927
928 response_64 = xmallocz(ENCODED_SIZE(resp_len));
929 encoded_len = EVP_EncodeBlock((unsigned char *)response_64,
930 (unsigned char *)response, resp_len);
931 if (encoded_len < 0)
932 die("EVP_EncodeBlock error");
933 return (char *)response_64;
934}
935
936static char *oauthbearer_base64(const char *user, const char *access_token)
937{
938 int b64_len;
939 char *raw, *b64;
940
941 /*
942 * Compose the OAUTHBEARER string
943 *
944 * "n,a=" {User} ",^Ahost=" {Host} "^Aport=" {Port} "^Aauth=Bearer " {Access Token} "^A^A
945 *
946 * The first part `n,a=" {User} ",` is the gs2 header described in RFC5801.
947 * * gs2-cb-flag `n` -> client does not support CB
948 * * gs2-authzid `a=" {User} "`
949 *
950 * The second part are key value pairs containing host, port and auth as
951 * described in RFC7628.
952 *
953 * https://datatracker.ietf.org/doc/html/rfc5801
954 * https://datatracker.ietf.org/doc/html/rfc7628
955 */
956 raw = xstrfmt("n,a=%s,\001auth=Bearer %s\001\001", user, access_token);
957
958 /* Base64 encode */
959 b64 = xmallocz(ENCODED_SIZE(strlen(raw)));
960 b64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));
961 free(raw);
962
963 if (b64_len < 0) {
964 free(b64);
965 return NULL;
966 }
967 return b64;
968}
969
970static char *xoauth2_base64(const char *user, const char *access_token)
971{
972 int b64_len;
973 char *raw, *b64;
974
975 /*
976 * Compose the XOAUTH2 string
977 * "user=" {User} "^Aauth=Bearer " {Access Token} "^A^A"
978 * https://developers.google.com/workspace/gmail/imap/xoauth2-protocol#initial_client_response
979 */
980 raw = xstrfmt("user=%s\001auth=Bearer %s\001\001", user, access_token);
981
982 /* Base64 encode */
983 b64 = xmallocz(ENCODED_SIZE(strlen(raw)));
984 b64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw, strlen(raw));
985 free(raw);
986
987 if (b64_len < 0) {
988 free(b64);
989 return NULL;
990 }
991 return b64;
992}
993
994static int auth_plain(struct imap_store *ctx, const char *prompt UNUSED)
995{
996 int ret;
997 char *b64;
998
999 b64 = plain_base64(ctx->cfg->user, ctx->cfg->pass);
1000 if (!b64)
1001 return error("PLAIN: base64 encoding failed");
1002
1003 /* Send the base64-encoded response */
1004 ret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));
1005 if (ret != (int)strlen(b64)) {
1006 free(b64);
1007 return error("IMAP error: sending PLAIN response failed");
1008 }
1009
1010 free(b64);
1011 return 0;
1012}
1013
1014static int auth_cram_md5(struct imap_store *ctx, const char *prompt)
1015{
1016 int ret;
1017 char *response;
1018
1019 response = cram(prompt, ctx->cfg->user, ctx->cfg->pass);
1020
1021 ret = socket_write(&ctx->imap->buf.sock, response, strlen(response));
1022 if (ret != strlen(response)) {
1023 free(response);
1024 return error("IMAP error: sending CRAM-MD5 response failed");
1025 }
1026
1027 free(response);
1028
1029 return 0;
1030}
1031
1032static int auth_oauthbearer(struct imap_store *ctx, const char *prompt UNUSED)
1033{
1034 int ret;
1035 char *b64;
1036
1037 b64 = oauthbearer_base64(ctx->cfg->user, ctx->cfg->pass);
1038 if (!b64)
1039 return error("OAUTHBEARER: base64 encoding failed");
1040
1041 /* Send the base64-encoded response */
1042 ret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));
1043 if (ret != (int)strlen(b64)) {
1044 free(b64);
1045 return error("IMAP error: sending OAUTHBEARER response failed");
1046 }
1047
1048 free(b64);
1049 return 0;
1050}
1051
1052static int auth_xoauth2(struct imap_store *ctx, const char *prompt UNUSED)
1053{
1054 int ret;
1055 char *b64;
1056
1057 b64 = xoauth2_base64(ctx->cfg->user, ctx->cfg->pass);
1058 if (!b64)
1059 return error("XOAUTH2: base64 encoding failed");
1060
1061 /* Send the base64-encoded response */
1062 ret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));
1063 if (ret != (int)strlen(b64)) {
1064 free(b64);
1065 return error("IMAP error: sending XOAUTH2 response failed");
1066 }
1067
1068 free(b64);
1069 return 0;
1070}
1071
1072#else
1073
1074#define auth_plain NULL
1075#define auth_cram_md5 NULL
1076#define auth_oauthbearer NULL
1077#define auth_xoauth2 NULL
1078
1079#endif
1080
1081static void server_fill_credential(struct imap_server_conf *srvc, struct credential *cred)
1082{
1083 if (srvc->user && srvc->pass)
1084 return;
1085
1086 cred->protocol = xstrdup(srvc->use_ssl ? "imaps" : "imap");
1087 cred->host = xstrfmt("%s:%d", srvc->host, srvc->port);
1088
1089 cred->username = xstrdup_or_null(srvc->user);
1090 cred->password = xstrdup_or_null(srvc->pass);
1091
1092 credential_fill(the_repository, cred, 1);
1093
1094 if (!srvc->user)
1095 srvc->user = xstrdup(cred->username);
1096 if (!srvc->pass)
1097 srvc->pass = xstrdup(cred->password);
1098}
1099
1100static int try_auth_method(struct imap_server_conf *srvc,
1101 struct imap_store *ctx,
1102 struct imap *imap,
1103 const char *auth_method,
1104 enum CAPABILITY cap,
1105 int (*fn)(struct imap_store *, const char *))
1106{
1107 struct imap_cmd_cb cb = {0};
1108
1109 if (!CAP(cap)) {
1110 fprintf(stderr, "You specified "
1111 "%s as authentication method, "
1112 "but %s doesn't support it.\n",
1113 auth_method, srvc->host);
1114 return -1;
1115 }
1116 cb.cont = fn;
1117
1118 if (NOT_CONSTANT(!cb.cont)) {
1119 fprintf(stderr, "If you want to use %s authentication mechanism, "
1120 "you have to build git-imap-send with OpenSSL library.",
1121 auth_method);
1122 return -1;
1123 }
1124 if (imap_exec(ctx, &cb, "AUTHENTICATE %s", auth_method) != RESP_OK) {
1125 fprintf(stderr, "IMAP error: AUTHENTICATE %s failed\n",
1126 auth_method);
1127 return -1;
1128 }
1129 return 0;
1130}
1131
1132static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const char *folder)
1133{
1134 struct credential cred = CREDENTIAL_INIT;
1135 struct imap_store *ctx;
1136 struct imap *imap;
1137 char *arg, *rsp;
1138 int s = -1, preauth;
1139
1140 CALLOC_ARRAY(ctx, 1);
1141
1142 ctx->cfg = srvc;
1143 ctx->imap = CALLOC_ARRAY(imap, 1);
1144 imap->buf.sock.fd[0] = imap->buf.sock.fd[1] = -1;
1145 imap->in_progress_append = &imap->in_progress;
1146
1147 /* open connection to IMAP server */
1148
1149 if (srvc->tunnel) {
1150 struct child_process tunnel = CHILD_PROCESS_INIT;
1151
1152 imap_info("Starting tunnel '%s'... ", srvc->tunnel);
1153
1154 strvec_push(&tunnel.args, srvc->tunnel);
1155 tunnel.use_shell = 1;
1156 tunnel.in = -1;
1157 tunnel.out = -1;
1158 if (start_command(&tunnel))
1159 die("cannot start proxy %s", srvc->tunnel);
1160
1161 imap->buf.sock.fd[0] = tunnel.out;
1162 imap->buf.sock.fd[1] = tunnel.in;
1163
1164 imap_info("OK\n");
1165 } else {
1166#ifndef NO_IPV6
1167 struct addrinfo hints, *ai0, *ai;
1168 int gai;
1169 char portstr[6];
1170
1171 xsnprintf(portstr, sizeof(portstr), "%d", srvc->port);
1172
1173 memset(&hints, 0, sizeof(hints));
1174 hints.ai_socktype = SOCK_STREAM;
1175 hints.ai_protocol = IPPROTO_TCP;
1176
1177 imap_info("Resolving %s... ", srvc->host);
1178 gai = getaddrinfo(srvc->host, portstr, &hints, &ai);
1179 if (gai) {
1180 fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(gai));
1181 goto bail;
1182 }
1183 imap_info("OK\n");
1184
1185 for (ai0 = ai; ai; ai = ai->ai_next) {
1186 char addr[NI_MAXHOST];
1187
1188 s = socket(ai->ai_family, ai->ai_socktype,
1189 ai->ai_protocol);
1190 if (s < 0)
1191 continue;
1192
1193 getnameinfo(ai->ai_addr, ai->ai_addrlen, addr,
1194 sizeof(addr), NULL, 0, NI_NUMERICHOST);
1195 imap_info("Connecting to [%s]:%s... ", addr, portstr);
1196
1197 if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0) {
1198 close(s);
1199 s = -1;
1200 perror("connect");
1201 continue;
1202 }
1203
1204 break;
1205 }
1206 freeaddrinfo(ai0);
1207#else /* NO_IPV6 */
1208 struct hostent *he;
1209 struct sockaddr_in addr;
1210
1211 memset(&addr, 0, sizeof(addr));
1212 addr.sin_port = htons(srvc->port);
1213 addr.sin_family = AF_INET;
1214
1215 imap_info("Resolving %s... ", srvc->host);
1216 he = gethostbyname(srvc->host);
1217 if (!he) {
1218 perror("gethostbyname");
1219 goto bail;
1220 }
1221 imap_info("OK\n");
1222
1223 addr.sin_addr.s_addr = *((int *) he->h_addr_list[0]);
1224
1225 s = socket(PF_INET, SOCK_STREAM, 0);
1226
1227 imap_info("Connecting to %s:%hu... ", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
1228 if (connect(s, (struct sockaddr *)&addr, sizeof(addr))) {
1229 close(s);
1230 s = -1;
1231 perror("connect");
1232 }
1233#endif
1234 if (s < 0) {
1235 fputs("error: unable to connect to server\n", stderr);
1236 goto bail;
1237 }
1238
1239 imap->buf.sock.fd[0] = s;
1240 imap->buf.sock.fd[1] = dup(s);
1241
1242 if (srvc->use_ssl &&
1243 ssl_socket_connect(&imap->buf.sock, srvc, 0)) {
1244 close(s);
1245 goto bail;
1246 }
1247 imap_info("OK\n");
1248 }
1249
1250 /* read the greeting string */
1251 if (buffer_gets(&imap->buf, &rsp)) {
1252 fprintf(stderr, "IMAP error: no greeting response\n");
1253 goto bail;
1254 }
1255 arg = next_arg(&rsp);
1256 if (!arg || *arg != '*' || (arg = next_arg(&rsp)) == NULL) {
1257 fprintf(stderr, "IMAP error: invalid greeting response\n");
1258 goto bail;
1259 }
1260 preauth = 0;
1261 if (!strcmp("PREAUTH", arg))
1262 preauth = 1;
1263 else if (strcmp("OK", arg) != 0) {
1264 fprintf(stderr, "IMAP error: unknown greeting response\n");
1265 goto bail;
1266 }
1267 parse_response_code(ctx, NULL, rsp);
1268 if (!imap->caps && imap_exec(ctx, NULL, "CAPABILITY") != RESP_OK)
1269 goto bail;
1270
1271 if (!preauth) {
1272#ifndef NO_OPENSSL
1273 if (!srvc->use_ssl && CAP(STARTTLS)) {
1274 if (imap_exec(ctx, NULL, "STARTTLS") != RESP_OK)
1275 goto bail;
1276 if (ssl_socket_connect(&imap->buf.sock, srvc, 1))
1277 goto bail;
1278 /* capabilities may have changed, so get the new capabilities */
1279 if (imap_exec(ctx, NULL, "CAPABILITY") != RESP_OK)
1280 goto bail;
1281 }
1282#endif
1283 imap_info("Logging in...\n");
1284 server_fill_credential(srvc, &cred);
1285
1286 if (srvc->auth_method) {
1287 if (!strcmp(srvc->auth_method, "PLAIN")) {
1288 if (try_auth_method(srvc, ctx, imap, "PLAIN", AUTH_PLAIN, auth_plain))
1289 goto bail;
1290 } else if (!strcmp(srvc->auth_method, "CRAM-MD5")) {
1291 if (try_auth_method(srvc, ctx, imap, "CRAM-MD5", AUTH_CRAM_MD5, auth_cram_md5))
1292 goto bail;
1293 } else if (!strcmp(srvc->auth_method, "OAUTHBEARER")) {
1294 if (try_auth_method(srvc, ctx, imap, "OAUTHBEARER", AUTH_OAUTHBEARER, auth_oauthbearer))
1295 goto bail;
1296 } else if (!strcmp(srvc->auth_method, "XOAUTH2")) {
1297 if (try_auth_method(srvc, ctx, imap, "XOAUTH2", AUTH_XOAUTH2, auth_xoauth2))
1298 goto bail;
1299 } else {
1300 fprintf(stderr, "unknown authentication mechanism: %s\n", srvc->auth_method);
1301 goto bail;
1302 }
1303 } else {
1304 if (CAP(NOLOGIN)) {
1305 fprintf(stderr, "skipping account %s@%s, server forbids LOGIN\n",
1306 srvc->user, srvc->host);
1307 goto bail;
1308 }
1309 if (!imap->buf.sock.ssl)
1310 imap_warn("*** IMAP Warning *** Password is being "
1311 "sent in the clear\n");
1312 if (imap_exec(ctx, NULL, "LOGIN \"%s\" \"%s\"", srvc->user, srvc->pass) != RESP_OK) {
1313 fprintf(stderr, "IMAP error: LOGIN failed\n");
1314 goto bail;
1315 }
1316 }
1317 } /* !preauth */
1318
1319 if (cred.username)
1320 credential_approve(the_repository, &cred);
1321 credential_clear(&cred);
1322
1323 /* check the target mailbox exists */
1324 ctx->name = folder;
1325 switch (imap_exec(ctx, NULL, "EXAMINE \"%s\"", ctx->name)) {
1326 case RESP_OK:
1327 /* ok */
1328 break;
1329 case RESP_BAD:
1330 fprintf(stderr, "IMAP error: could not check mailbox\n");
1331 goto out;
1332 case RESP_NO:
1333 if (imap_exec(ctx, NULL, "CREATE \"%s\"", ctx->name) == RESP_OK) {
1334 imap_info("Created missing mailbox\n");
1335 } else {
1336 fprintf(stderr, "IMAP error: could not create missing mailbox\n");
1337 goto out;
1338 }
1339 break;
1340 }
1341
1342 ctx->prefix = "";
1343 return ctx;
1344
1345bail:
1346 if (cred.username)
1347 credential_reject(the_repository, &cred);
1348 credential_clear(&cred);
1349
1350 out:
1351 imap_close_store(ctx);
1352 return NULL;
1353}
1354
1355/*
1356 * Insert CR characters as necessary in *msg to ensure that every LF
1357 * character in *msg is preceded by a CR.
1358 */
1359static void lf_to_crlf(struct strbuf *msg)
1360{
1361 char *new_msg;
1362 size_t i, j;
1363 char lastc;
1364
1365 /* First pass: tally, in j, the size of the new_msg string: */
1366 for (i = j = 0, lastc = '\0'; i < msg->len; i++) {
1367 if (msg->buf[i] == '\n' && lastc != '\r')
1368 j++; /* a CR will need to be added here */
1369 lastc = msg->buf[i];
1370 j++;
1371 }
1372
1373 new_msg = xmallocz(j);
1374
1375 /*
1376 * Second pass: write the new_msg string. Note that this loop is
1377 * otherwise identical to the first pass.
1378 */
1379 for (i = j = 0, lastc = '\0'; i < msg->len; i++) {
1380 if (msg->buf[i] == '\n' && lastc != '\r')
1381 new_msg[j++] = '\r';
1382 lastc = new_msg[j++] = msg->buf[i];
1383 }
1384 strbuf_attach(msg, new_msg, j, j + 1);
1385}
1386
1387/*
1388 * Store msg to IMAP. Also detach and free the data from msg->data,
1389 * leaving msg->data empty.
1390 */
1391static int imap_store_msg(struct imap_store *ctx, struct strbuf *msg)
1392{
1393 struct imap *imap = ctx->imap;
1394 struct imap_cmd_cb cb;
1395 const char *prefix, *box;
1396 int ret;
1397
1398 lf_to_crlf(msg);
1399 memset(&cb, 0, sizeof(cb));
1400
1401 cb.dlen = msg->len;
1402 cb.data = strbuf_detach(msg, NULL);
1403
1404 box = ctx->name;
1405 prefix = !strcmp(box, "INBOX") ? "" : ctx->prefix;
1406 ret = imap_exec_m(ctx, &cb, "APPEND \"%s%s\" ", prefix, box);
1407 imap->caps = imap->rcaps;
1408 if (ret != DRV_OK)
1409 return ret;
1410
1411 return DRV_OK;
1412}
1413
1414static void wrap_in_html(struct strbuf *msg)
1415{
1416 struct strbuf buf = STRBUF_INIT;
1417 static const char *content_type = "Content-Type: text/html;\n";
1418 static const char *pre_open = "<pre>\n";
1419 static const char *pre_close = "</pre>\n";
1420 const char *body = strstr(msg->buf, "\n\n");
1421
1422 if (!body)
1423 return; /* Headers but no body; no wrapping needed */
1424
1425 body += 2;
1426
1427 strbuf_add(&buf, msg->buf, body - msg->buf - 1);
1428 strbuf_addstr(&buf, content_type);
1429 strbuf_addch(&buf, '\n');
1430 strbuf_addstr(&buf, pre_open);
1431 strbuf_addstr_xml_quoted(&buf, body);
1432 strbuf_addstr(&buf, pre_close);
1433
1434 strbuf_release(msg);
1435 *msg = buf;
1436}
1437
1438static int count_messages(struct strbuf *all_msgs)
1439{
1440 int count = 0;
1441 char *p = all_msgs->buf;
1442
1443 while (1) {
1444 if (starts_with(p, "From ")) {
1445 if (starts_with(p, "From git-send-email")) {
1446 p = strstr(p+5, "\nFrom: ");
1447 if (!p) break;
1448 p += 7;
1449 p = strstr(p, "\nTo: ");
1450 if (!p) break;
1451 p += 5;
1452 count++;
1453 } else {
1454 p = strstr(p+5, "\nFrom: ");
1455 if (!p) break;
1456 p = strstr(p+7, "\nDate: ");
1457 if (!p) break;
1458 p = strstr(p+7, "\nSubject: ");
1459 if (!p) break;
1460 p += 10;
1461 count++;
1462 }
1463 }
1464 p = strstr(p+5, "\nFrom ");
1465 if (!p)
1466 break;
1467 p++;
1468 }
1469 return count;
1470}
1471
1472/*
1473 * Copy the next message from all_msgs, starting at offset *ofs, to
1474 * msg. Update *ofs to the start of the following message. Return
1475 * true iff a message was successfully copied.
1476 */
1477static int split_msg(struct strbuf *all_msgs, struct strbuf *msg, int *ofs)
1478{
1479 char *p, *data;
1480 size_t len;
1481
1482 if (*ofs >= all_msgs->len)
1483 return 0;
1484
1485 data = &all_msgs->buf[*ofs];
1486 len = all_msgs->len - *ofs;
1487
1488 if (len < 5 || !starts_with(data, "From "))
1489 return 0;
1490
1491 p = strchr(data, '\n');
1492 if (p) {
1493 p++;
1494 len -= p - data;
1495 *ofs += p - data;
1496 data = p;
1497 }
1498
1499 p = strstr(data, "\nFrom ");
1500 if (p)
1501 len = &p[1] - data;
1502
1503 strbuf_add(msg, data, len);
1504 *ofs += len;
1505 return 1;
1506}
1507
1508static int git_imap_config(const char *var, const char *val,
1509 const struct config_context *ctx, void *cb)
1510{
1511 struct imap_server_conf *cfg = cb;
1512
1513 if (!strcmp("imap.sslverify", var)) {
1514 cfg->ssl_verify = git_config_bool(var, val);
1515 } else if (!strcmp("imap.preformattedhtml", var)) {
1516 cfg->use_html = git_config_bool(var, val);
1517 } else if (!strcmp("imap.folder", var)) {
1518 FREE_AND_NULL(cfg->folder);
1519 return git_config_string(&cfg->folder, var, val);
1520 } else if (!strcmp("imap.user", var)) {
1521 FREE_AND_NULL(cfg->user);
1522 return git_config_string(&cfg->user, var, val);
1523 } else if (!strcmp("imap.pass", var)) {
1524 FREE_AND_NULL(cfg->pass);
1525 return git_config_string(&cfg->pass, var, val);
1526 } else if (!strcmp("imap.tunnel", var)) {
1527 FREE_AND_NULL(cfg->tunnel);
1528 return git_config_string(&cfg->tunnel, var, val);
1529 } else if (!strcmp("imap.authmethod", var)) {
1530 FREE_AND_NULL(cfg->auth_method);
1531 return git_config_string(&cfg->auth_method, var, val);
1532 } else if (!strcmp("imap.port", var)) {
1533 cfg->port = git_config_int(var, val, ctx->kvi);
1534 } else if (!strcmp("imap.host", var)) {
1535 if (!val) {
1536 return config_error_nonbool(var);
1537 } else {
1538 if (starts_with(val, "imap:"))
1539 val += 5;
1540 else if (starts_with(val, "imaps:")) {
1541 val += 6;
1542 cfg->use_ssl = 1;
1543 }
1544 if (starts_with(val, "//"))
1545 val += 2;
1546 cfg->host = xstrdup(val);
1547 }
1548 } else {
1549 return git_default_config(var, val, ctx, cb);
1550 }
1551
1552 return 0;
1553}
1554
1555static int append_msgs_to_imap(struct imap_server_conf *server,
1556 struct strbuf* all_msgs, int total)
1557{
1558 struct strbuf msg = STRBUF_INIT;
1559 struct imap_store *ctx = NULL;
1560 int ofs = 0;
1561 int r;
1562 int n = 0;
1563
1564 ctx = imap_open_store(server, server->folder);
1565 if (!ctx) {
1566 fprintf(stderr, "failed to open store\n");
1567 return 1;
1568 }
1569 ctx->name = server->folder;
1570
1571 fprintf(stderr, "Sending %d message%s to %s folder...\n",
1572 total, (total != 1) ? "s" : "", server->folder);
1573 while (1) {
1574 unsigned percent = n * 100 / total;
1575
1576 fprintf(stderr, "%4u%% (%d/%d) done\r", percent, n, total);
1577
1578 if (!split_msg(all_msgs, &msg, &ofs))
1579 break;
1580 if (server->use_html)
1581 wrap_in_html(&msg);
1582 r = imap_store_msg(ctx, &msg);
1583 if (r != DRV_OK)
1584 break;
1585 n++;
1586 }
1587 fprintf(stderr, "\n");
1588
1589 imap_close_store(ctx);
1590
1591 return 0;
1592}
1593
1594static int list_imap_folders(struct imap_server_conf *server)
1595{
1596 struct imap_store *ctx = imap_open_store(server, "INBOX");
1597 if (!ctx) {
1598 fprintf(stderr, "failed to connect to IMAP server\n");
1599 return 1;
1600 }
1601
1602 fprintf(stderr, "Fetching the list of available folders...\n");
1603 /* Issue the LIST command and print the results */
1604 if (imap_exec(ctx, NULL, "LIST \"\" \"*\"") != RESP_OK) {
1605 fprintf(stderr, "failed to list folders\n");
1606 imap_close_store(ctx);
1607 return 1;
1608 }
1609
1610 imap_close_store(ctx);
1611 return 0;
1612}
1613
1614#ifdef USE_CURL_FOR_IMAP_SEND
1615static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred)
1616{
1617 CURL *curl;
1618 struct strbuf path = STRBUF_INIT;
1619 char *uri_encoded_folder;
1620
1621 if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK)
1622 die("curl_global_init failed");
1623
1624 curl = curl_easy_init();
1625
1626 if (!curl)
1627 die("curl_easy_init failed");
1628
1629 server_fill_credential(srvc, cred);
1630 curl_easy_setopt(curl, CURLOPT_USERNAME, srvc->user);
1631
1632 /*
1633 * Use CURLOPT_PASSWORD irrespective of whether there is
1634 * an auth method specified or not, unless it's OAuth2.0,
1635 * where we use CURLOPT_XOAUTH2_BEARER.
1636 */
1637 if (!srvc->auth_method ||
1638 (strcmp(srvc->auth_method, "XOAUTH2") &&
1639 strcmp(srvc->auth_method, "OAUTHBEARER")))
1640 curl_easy_setopt(curl, CURLOPT_PASSWORD, srvc->pass);
1641
1642 strbuf_addstr(&path, srvc->use_ssl ? "imaps://" : "imap://");
1643 strbuf_addstr(&path, srvc->host);
1644 if (!path.len || path.buf[path.len - 1] != '/')
1645 strbuf_addch(&path, '/');
1646
1647 if (!list_folders) {
1648 uri_encoded_folder = curl_easy_escape(curl, srvc->folder, 0);
1649 if (!uri_encoded_folder)
1650 die("failed to encode server folder");
1651 strbuf_addstr(&path, uri_encoded_folder);
1652 curl_free(uri_encoded_folder);
1653 }
1654
1655 curl_easy_setopt(curl, CURLOPT_URL, path.buf);
1656 strbuf_release(&path);
1657 curl_easy_setopt(curl, CURLOPT_PORT, (long)srvc->port);
1658
1659 if (srvc->auth_method) {
1660 if (!strcmp(srvc->auth_method, "XOAUTH2") ||
1661 !strcmp(srvc->auth_method, "OAUTHBEARER")) {
1662
1663 /*
1664 * While CURLOPT_XOAUTH2_BEARER looks as if it only supports XOAUTH2,
1665 * upon debugging, it has been found that it is capable of detecting
1666 * the best option out of OAUTHBEARER and XOAUTH2.
1667 */
1668 curl_easy_setopt(curl, CURLOPT_XOAUTH2_BEARER, srvc->pass);
1669 } else {
1670 struct strbuf auth = STRBUF_INIT;
1671 strbuf_addstr(&auth, "AUTH=");
1672 strbuf_addstr(&auth, srvc->auth_method);
1673 curl_easy_setopt(curl, CURLOPT_LOGIN_OPTIONS, auth.buf);
1674 strbuf_release(&auth);
1675 }
1676 }
1677
1678 if (!srvc->use_ssl)
1679 curl_easy_setopt(curl, CURLOPT_USE_SSL, (long)CURLUSESSL_TRY);
1680
1681 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, (long)srvc->ssl_verify);
1682 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, (long)srvc->ssl_verify);
1683
1684 if (0 < verbosity || getenv("GIT_CURL_VERBOSE"))
1685 http_trace_curl_no_data();
1686 setup_curl_trace(curl);
1687
1688 return curl;
1689}
1690
1691static int curl_append_msgs_to_imap(struct imap_server_conf *server,
1692 struct strbuf* all_msgs, int total)
1693{
1694 int ofs = 0;
1695 int n = 0;
1696 struct buffer msgbuf = { STRBUF_INIT, 0 };
1697 CURL *curl;
1698 CURLcode res = CURLE_OK;
1699 struct credential cred = CREDENTIAL_INIT;
1700
1701 curl = setup_curl(server, &cred);
1702
1703 curl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer);
1704 curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
1705
1706 curl_easy_setopt(curl, CURLOPT_READDATA, &msgbuf);
1707
1708 fprintf(stderr, "Sending %d message%s to %s folder...\n",
1709 total, (total != 1) ? "s" : "", server->folder);
1710 while (1) {
1711 unsigned percent = n * 100 / total;
1712 int prev_len;
1713
1714 fprintf(stderr, "%4u%% (%d/%d) done\r", percent, n, total);
1715
1716 prev_len = msgbuf.buf.len;
1717 if (!split_msg(all_msgs, &msgbuf.buf, &ofs))
1718 break;
1719 if (server->use_html)
1720 wrap_in_html(&msgbuf.buf);
1721 lf_to_crlf(&msgbuf.buf);
1722
1723 curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE,
1724 cast_size_t_to_curl_off_t(msgbuf.buf.len-prev_len));
1725
1726 res = curl_easy_perform(curl);
1727
1728 if(res != CURLE_OK) {
1729 fprintf(stderr, "curl_easy_perform() failed: %s\n",
1730 curl_easy_strerror(res));
1731 break;
1732 }
1733
1734 n++;
1735 }
1736 fprintf(stderr, "\n");
1737
1738 curl_easy_cleanup(curl);
1739 curl_global_cleanup();
1740
1741 if (cred.username) {
1742 if (res == CURLE_OK)
1743 credential_approve(the_repository, &cred);
1744 else if (res == CURLE_LOGIN_DENIED)
1745 credential_reject(the_repository, &cred);
1746 }
1747
1748 credential_clear(&cred);
1749
1750 return res != CURLE_OK;
1751}
1752
1753static int curl_list_imap_folders(struct imap_server_conf *server)
1754{
1755 CURL *curl;
1756 CURLcode res = CURLE_OK;
1757 struct credential cred = CREDENTIAL_INIT;
1758
1759 fprintf(stderr, "Fetching the list of available folders...\n");
1760 curl = setup_curl(server, &cred);
1761 res = curl_easy_perform(curl);
1762
1763 curl_easy_cleanup(curl);
1764 curl_global_cleanup();
1765
1766 if (cred.username) {
1767 if (res == CURLE_OK)
1768 credential_approve(the_repository, &cred);
1769 else if (res == CURLE_LOGIN_DENIED)
1770 credential_reject(the_repository, &cred);
1771 }
1772
1773 credential_clear(&cred);
1774
1775 return res != CURLE_OK;
1776}
1777#endif
1778
1779int cmd_main(int argc, const char **argv)
1780{
1781 struct imap_server_conf server = {
1782 .ssl_verify = 1,
1783 };
1784 struct strbuf all_msgs = STRBUF_INIT;
1785 int total;
1786 int nongit_ok;
1787 int ret;
1788
1789 setup_git_directory_gently(&nongit_ok);
1790 repo_config(the_repository, git_imap_config, &server);
1791
1792 argc = parse_options(argc, (const char **)argv, "", imap_send_options, imap_send_usage, 0);
1793
1794 if (opt_folder) {
1795 free(server.folder);
1796 server.folder = xstrdup(opt_folder);
1797 }
1798
1799 if (argc)
1800 usage_with_options(imap_send_usage, imap_send_options);
1801
1802#ifndef USE_CURL_FOR_IMAP_SEND
1803 if (use_curl) {
1804 warning("--curl not supported in this build");
1805 use_curl = 0;
1806 }
1807#elif defined(NO_OPENSSL)
1808 if (!use_curl) {
1809 warning("--no-curl not supported in this build");
1810 use_curl = 1;
1811 }
1812#endif
1813
1814 if (!server.port)
1815 server.port = server.use_ssl ? 993 : 143;
1816
1817 if (!server.host) {
1818 if (!server.tunnel) {
1819 error(_("no IMAP host specified"));
1820 advise(_("set the IMAP host with 'git config imap.host <host>'.\n"
1821 "(e.g., 'git config imap.host imaps://imap.example.com')"));
1822 ret = 1;
1823 goto out;
1824 }
1825 server.host = xstrdup("tunnel");
1826 }
1827
1828 if (list_folders) {
1829 if (server.tunnel)
1830 ret = list_imap_folders(&server);
1831#ifdef USE_CURL_FOR_IMAP_SEND
1832 else if (use_curl)
1833 ret = curl_list_imap_folders(&server);
1834#endif
1835 else
1836 ret = list_imap_folders(&server);
1837 goto out;
1838 }
1839
1840 if (!server.folder) {
1841 error(_("no IMAP folder specified"));
1842 advise(_("set the target folder with 'git config imap.folder <folder>'.\n"
1843 "(e.g., 'git config imap.folder Drafts')"));
1844 ret = 1;
1845 goto out;
1846 }
1847
1848 /* read the messages */
1849 if (strbuf_read(&all_msgs, 0, 0) < 0) {
1850 error_errno(_("could not read from stdin"));
1851 ret = 1;
1852 goto out;
1853 }
1854
1855 if (all_msgs.len == 0) {
1856 fprintf(stderr, "nothing to send\n");
1857 ret = 1;
1858 goto out;
1859 }
1860
1861 total = count_messages(&all_msgs);
1862 if (!total) {
1863 fprintf(stderr, "no messages found to send\n");
1864 ret = 1;
1865 goto out;
1866 }
1867
1868 /* write it to the imap server */
1869
1870 if (server.tunnel)
1871 ret = append_msgs_to_imap(&server, &all_msgs, total);
1872#ifdef USE_CURL_FOR_IMAP_SEND
1873 else if (use_curl)
1874 ret = curl_append_msgs_to_imap(&server, &all_msgs, total);
1875#endif
1876 else
1877 ret = append_msgs_to_imap(&server, &all_msgs, total);
1878
1879out:
1880 free(server.tunnel);
1881 free(server.host);
1882 free(server.folder);
1883 free(server.user);
1884 free(server.pass);
1885 free(server.auth_method);
1886 strbuf_release(&all_msgs);
1887 return ret;
1888}