Git fork
at reftables-rust 1888 lines 46 kB view raw
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}