imap-send: add PLAIN authentication method to OpenSSL

The current implementation for PLAIN in imap-send works just fine if using curl, but if attempted to use for OpenSSL, it is treated as an invalid mechanism. The default implementation for OpenSSL is IMAP LOGIN command rather than AUTH PLAIN. Since AUTH PLAIN is still used today by many email providers in form of app passwords, lets add an implementation that can use AUTH PLAIN if specified. Signed-off-by: Aditya Garg <gargaditya08@live.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Aditya Garg committed Jun 20, 2025 at 12:10 UTC ea8681e3a4ca579f03d8dfb7a425a2a9da4b3493
2 files changed +61 -3
Documentation/config/imap.adoc
+2 -2
@@ -40,6 +40,6 @@ imap.authMethod::
40 Specify the authentication method for authenticating with the IMAP server.
41 If Git was built with the NO_CURL option, or if your curl version is older
42 than 7.34.0, or if you're running git-imap-send with the `--no-curl`
43 - option, the only supported methods are `CRAM-MD5`, `OAUTHBEARER` and
44 - `XOAUTH2`. If this is not set then `git imap-send` uses the basic IMAP
43 + option, the only supported methods are `PLAIN`, `CRAM-MD5`, `OAUTHBEARER`
44 + and `XOAUTH2`. If this is not set then `git imap-send` uses the basic IMAP
45 plaintext `LOGIN` command.
imap-send.c
+59 -1
@@ -139,6 +139,7 @@ enum CAPABILITY {
139 LITERALPLUS,
140 NAMESPACE,
141 STARTTLS,
142 + AUTH_PLAIN,
143 AUTH_CRAM_MD5,
144 AUTH_OAUTHBEARER,
145 AUTH_XOAUTH2,
@@ -150,6 +151,7 @@ static const char *cap_list[] = {
151 "LITERAL+",
152 "NAMESPACE",
153 "STARTTLS",
154 + "AUTH=PLAIN",
155 "AUTH=CRAM-MD5",
156 "AUTH=OAUTHBEARER",
157 "AUTH=XOAUTH2",
@@ -851,6 +853,38 @@ static char hexchar(unsigned int b)
853 }
854
855 #define ENCODED_SIZE(n) (4 * DIV_ROUND_UP((n), 3))
856 +static char *plain_base64(const char *user, const char *pass)
857 +{
858 + struct strbuf raw = STRBUF_INIT;
859 + int b64_len;
860 + char *b64;
861 +
862 + /*
863 + * Compose the PLAIN string
864 + *
865 + * The username and password are combined to one string and base64 encoded.
866 + * "\0user\0pass"
867 + *
868 + * The method has been described in RFC4616.
869 + *
870 + * https://datatracker.ietf.org/doc/html/rfc4616
871 + */
872 + strbuf_addch(&raw, '\0');
873 + strbuf_addstr(&raw, user);
874 + strbuf_addch(&raw, '\0');
875 + strbuf_addstr(&raw, pass);
876 +
877 + b64 = xmallocz(ENCODED_SIZE(raw.len));
878 + b64_len = EVP_EncodeBlock((unsigned char *)b64, (unsigned char *)raw.buf, raw.len);
879 + strbuf_release(&raw);
880 +
881 + if (b64_len < 0) {
882 + free(b64);
883 + return NULL;
884 + }
885 + return b64;
886 +}
887 +
888 static char *cram(const char *challenge_64, const char *user, const char *pass)
889 {
890 int i, resp_len, encoded_len, decoded_len;
@@ -947,6 +981,26 @@ static char *xoauth2_base64(const char *user, const char *access_token)
981 return b64;
982 }
983
984 +static int auth_plain(struct imap_store *ctx, const char *prompt UNUSED)
985 +{
986 + int ret;
987 + char *b64;
988 +
989 + b64 = plain_base64(ctx->cfg->user, ctx->cfg->pass);
990 + if (!b64)
991 + return error("PLAIN: base64 encoding failed");
992 +
993 + /* Send the base64-encoded response */
994 + ret = socket_write(&ctx->imap->buf.sock, b64, strlen(b64));
995 + if (ret != (int)strlen(b64)) {
996 + free(b64);
997 + return error("IMAP error: sending PLAIN response failed");
998 + }
999 +
1000 + free(b64);
1001 + return 0;
1002 +}
1003 +
1004 static int auth_cram_md5(struct imap_store *ctx, const char *prompt)
1005 {
1006 int ret;
@@ -1007,6 +1061,7 @@ static int auth_xoauth2(struct imap_store *ctx, const char *prompt UNUSED)
1061
1062 #else
1063
1064 +#define auth_plain NULL
1065 #define auth_cram_md5 NULL
1066 #define auth_oauthbearer NULL
1067 #define auth_xoauth2 NULL
@@ -1219,7 +1274,10 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c
1274 server_fill_credential(srvc, &cred);
1275
1276 if (srvc->auth_method) {
1222 - if (!strcmp(srvc->auth_method, "CRAM-MD5")) {
1277 + if (!strcmp(srvc->auth_method, "PLAIN")) {
1278 + if (try_auth_method(srvc, ctx, imap, "PLAIN", AUTH_PLAIN, auth_plain))
1279 + goto bail;
1280 + } else if (!strcmp(srvc->auth_method, "CRAM-MD5")) {
1281 if (try_auth_method(srvc, ctx, imap, "CRAM-MD5", AUTH_CRAM_MD5, auth_cram_md5))
1282 goto bail;
1283 } else if (!strcmp(srvc->auth_method, "OAUTHBEARER")) {