Skip to content

Commit ae9c606

Browse files
Hitoshi Mitakegitster
authored andcommitted
imap-send: support CRAM-MD5 authentication
CRAM-MD5 authentication ought to be independent from SSL, but NO_OPENSSL build will not support this because the base64 and md5 code are used from the OpenSSL library in this implementation. Signed-off-by: Hitoshi Mitake <mitake@dcl.info.waseda.ac.jp> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent 7e5eb8f commit ae9c606

File tree

2 files changed

+135
-15
lines changed

2 files changed

+135
-15
lines changed

Documentation/git-imap-send.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ imap.preformattedHTML::
7171
option causes Thunderbird to send the patch as a plain/text,
7272
format=fixed email. Default is `false`.
7373

74+
imap.authMethod::
75+
Specify authenticate method for authentication with IMAP server.
76+
Current supported method is 'CRAM-MD5' only.
77+
7478
Examples
7579
~~~~~~~~
7680

imap-send.c

Lines changed: 131 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727
#include "run-command.h"
2828
#ifdef NO_OPENSSL
2929
typedef void *SSL;
30+
#else
31+
#include <openssl/evp.h>
32+
#include <openssl/hmac.h>
3033
#endif
3134

3235
struct store_conf {
@@ -140,6 +143,20 @@ struct imap_server_conf {
140143
int use_ssl;
141144
int ssl_verify;
142145
int use_html;
146+
char *auth_method;
147+
};
148+
149+
static struct imap_server_conf server = {
150+
NULL, /* name */
151+
NULL, /* tunnel */
152+
NULL, /* host */
153+
0, /* port */
154+
NULL, /* user */
155+
NULL, /* pass */
156+
0, /* use_ssl */
157+
1, /* ssl_verify */
158+
0, /* use_html */
159+
NULL, /* auth_method */
143160
};
144161

145162
struct imap_store_conf {
@@ -214,6 +231,7 @@ enum CAPABILITY {
214231
LITERALPLUS,
215232
NAMESPACE,
216233
STARTTLS,
234+
AUTH_CRAM_MD5,
217235
};
218236

219237
static const char *cap_list[] = {
@@ -222,6 +240,7 @@ static const char *cap_list[] = {
222240
"LITERAL+",
223241
"NAMESPACE",
224242
"STARTTLS",
243+
"AUTH=CRAM-MD5",
225244
};
226245

227246
#define RESP_OK 0
@@ -949,6 +968,87 @@ static void imap_close_store(struct store *ctx)
949968
free(ctx);
950969
}
951970

971+
#ifndef NO_OPENSSL
972+
973+
/*
974+
* hexchar() and cram() functions are based on the code from the isync
975+
* project (http://isync.sf.net/).
976+
*/
977+
static char hexchar(unsigned int b)
978+
{
979+
return b < 10 ? '0' + b : 'a' + (b - 10);
980+
}
981+
982+
#define ENCODED_SIZE(n) (4*((n+2)/3))
983+
static char *cram(const char *challenge_64, const char *user, const char *pass)
984+
{
985+
int i, resp_len, encoded_len, decoded_len;
986+
HMAC_CTX hmac;
987+
unsigned char hash[16];
988+
char hex[33];
989+
char *response, *response_64, *challenge;
990+
991+
/*
992+
* length of challenge_64 (i.e. base-64 encoded string) is a good
993+
* enough upper bound for challenge (decoded result).
994+
*/
995+
encoded_len = strlen(challenge_64);
996+
challenge = xmalloc(encoded_len);
997+
decoded_len = EVP_DecodeBlock((unsigned char *)challenge,
998+
(unsigned char *)challenge_64, encoded_len);
999+
if (decoded_len < 0)
1000+
die("invalid challenge %s", challenge_64);
1001+
HMAC_Init(&hmac, (unsigned char *)pass, strlen(pass), EVP_md5());
1002+
HMAC_Update(&hmac, (unsigned char *)challenge, decoded_len);
1003+
HMAC_Final(&hmac, hash, NULL);
1004+
HMAC_CTX_cleanup(&hmac);
1005+
1006+
hex[32] = 0;
1007+
for (i = 0; i < 16; i++) {
1008+
hex[2 * i] = hexchar((hash[i] >> 4) & 0xf);
1009+
hex[2 * i + 1] = hexchar(hash[i] & 0xf);
1010+
}
1011+
1012+
/* response: "<user> <digest in hex>" */
1013+
resp_len = strlen(user) + 1 + strlen(hex) + 1;
1014+
response = xmalloc(resp_len);
1015+
sprintf(response, "%s %s", user, hex);
1016+
1017+
response_64 = xmalloc(ENCODED_SIZE(resp_len) + 1);
1018+
encoded_len = EVP_EncodeBlock((unsigned char *)response_64,
1019+
(unsigned char *)response, resp_len);
1020+
if (encoded_len < 0)
1021+
die("EVP_EncodeBlock error");
1022+
response_64[encoded_len] = '\0';
1023+
return (char *)response_64;
1024+
}
1025+
1026+
#else
1027+
1028+
static char *cram(const char *challenge_64, const char *user, const char *pass)
1029+
{
1030+
die("If you want to use CRAM-MD5 authenticate method, "
1031+
"you have to build git-imap-send with OpenSSL library.");
1032+
}
1033+
1034+
#endif
1035+
1036+
static int auth_cram_md5(struct imap_store *ctx, struct imap_cmd *cmd, const char *prompt)
1037+
{
1038+
int ret;
1039+
char *response;
1040+
1041+
response = cram(prompt, server.user, server.pass);
1042+
1043+
ret = socket_write(&ctx->imap->buf.sock, response, strlen(response));
1044+
if (ret != strlen(response))
1045+
return error("IMAP error: sending response failed\n");
1046+
1047+
free(response);
1048+
1049+
return 0;
1050+
}
1051+
9521052
static struct store *imap_open_store(struct imap_server_conf *srvc)
9531053
{
9541054
struct imap_store *ctx;
@@ -1130,9 +1230,34 @@ static struct store *imap_open_store(struct imap_server_conf *srvc)
11301230
if (!imap->buf.sock.ssl)
11311231
imap_warn("*** IMAP Warning *** Password is being "
11321232
"sent in the clear\n");
1133-
if (imap_exec(ctx, NULL, "LOGIN \"%s\" \"%s\"", srvc->user, srvc->pass) != RESP_OK) {
1134-
fprintf(stderr, "IMAP error: LOGIN failed\n");
1135-
goto bail;
1233+
1234+
if (srvc->auth_method) {
1235+
struct imap_cmd_cb cb;
1236+
1237+
if (!strcmp(srvc->auth_method, "CRAM-MD5")) {
1238+
if (!CAP(AUTH_CRAM_MD5)) {
1239+
fprintf(stderr, "You specified"
1240+
"CRAM-MD5 as authentication method, "
1241+
"but %s doesn't support it.\n", srvc->host);
1242+
goto bail;
1243+
}
1244+
/* CRAM-MD5 */
1245+
1246+
memset(&cb, 0, sizeof(cb));
1247+
cb.cont = auth_cram_md5;
1248+
if (imap_exec(ctx, &cb, "AUTHENTICATE CRAM-MD5") != RESP_OK) {
1249+
fprintf(stderr, "IMAP error: AUTHENTICATE CRAM-MD5 failed\n");
1250+
goto bail;
1251+
}
1252+
} else {
1253+
fprintf(stderr, "Unknown authentication method:%s\n", srvc->host);
1254+
goto bail;
1255+
}
1256+
} else {
1257+
if (imap_exec(ctx, NULL, "LOGIN \"%s\" \"%s\"", srvc->user, srvc->pass) != RESP_OK) {
1258+
fprintf(stderr, "IMAP error: LOGIN failed\n");
1259+
goto bail;
1260+
}
11361261
}
11371262
} /* !preauth */
11381263

@@ -1310,18 +1435,6 @@ static int split_msg(struct msg_data *all_msgs, struct msg_data *msg, int *ofs)
13101435
return 1;
13111436
}
13121437

1313-
static struct imap_server_conf server = {
1314-
NULL, /* name */
1315-
NULL, /* tunnel */
1316-
NULL, /* host */
1317-
0, /* port */
1318-
NULL, /* user */
1319-
NULL, /* pass */
1320-
0, /* use_ssl */
1321-
1, /* ssl_verify */
1322-
0, /* use_html */
1323-
};
1324-
13251438
static char *imap_folder;
13261439

13271440
static int git_imap_config(const char *key, const char *val, void *cb)
@@ -1361,6 +1474,9 @@ static int git_imap_config(const char *key, const char *val, void *cb)
13611474
server.port = git_config_int(key, val);
13621475
else if (!strcmp("tunnel", key))
13631476
server.tunnel = xstrdup(val);
1477+
else if (!strcmp("authmethod", key))
1478+
server.auth_method = xstrdup(val);
1479+
13641480
return 0;
13651481
}
13661482

0 commit comments

Comments
 (0)