replace: add --graft option
[gitweb.git] / imap-send.c
index 21dc20b57d392c5789043968d28a3b958b2b50c7..83a6ed2ac338d7fc82f3bed6fcb93ffe8dae5739 100644 (file)
  */
 
 #include "cache.h"
+#include "credential.h"
 #include "exec_cmd.h"
 #include "run-command.h"
-#include "prompt.h"
 #ifdef NO_OPENSSL
 typedef void *SSL;
-#else
-#include <openssl/evp.h>
-#include <openssl/hmac.h>
 #endif
 
 static const char imap_send_usage[] = "git imap-send < <mbox>";
@@ -200,12 +197,64 @@ static void socket_perror(const char *func, struct imap_socket *sock, int ret)
        }
 }
 
+#ifdef NO_OPENSSL
 static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int verify)
 {
-#ifdef NO_OPENSSL
        fprintf(stderr, "SSL requested but SSL support not compiled in\n");
        return -1;
+}
+
 #else
+
+static int host_matches(const char *host, const char *pattern)
+{
+       if (pattern[0] == '*' && pattern[1] == '.') {
+               pattern += 2;
+               if (!(host = strchr(host, '.')))
+                       return 0;
+               host++;
+       }
+
+       return *host && *pattern && !strcasecmp(host, pattern);
+}
+
+static int verify_hostname(X509 *cert, const char *hostname)
+{
+       int len;
+       X509_NAME *subj;
+       char cname[1000];
+       int i, found;
+       STACK_OF(GENERAL_NAME) *subj_alt_names;
+
+       /* try the DNS subjectAltNames */
+       found = 0;
+       if ((subj_alt_names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL))) {
+               int num_subj_alt_names = sk_GENERAL_NAME_num(subj_alt_names);
+               for (i = 0; !found && i < num_subj_alt_names; i++) {
+                       GENERAL_NAME *subj_alt_name = sk_GENERAL_NAME_value(subj_alt_names, i);
+                       if (subj_alt_name->type == GEN_DNS &&
+                           strlen((const char *)subj_alt_name->d.ia5->data) == (size_t)subj_alt_name->d.ia5->length &&
+                           host_matches(hostname, (const char *)(subj_alt_name->d.ia5->data)))
+                               found = 1;
+               }
+               sk_GENERAL_NAME_pop_free(subj_alt_names, GENERAL_NAME_free);
+       }
+       if (found)
+               return 0;
+
+       /* try the common name */
+       if (!(subj = X509_get_subject_name(cert)))
+               return error("cannot get certificate subject");
+       if ((len = X509_NAME_get_text_by_NID(subj, NID_commonName, cname, sizeof(cname))) < 0)
+               return error("cannot get certificate common name");
+       if (strlen(cname) == (size_t)len && host_matches(hostname, cname))
+               return 0;
+       return error("certificate owner '%s' does not match hostname '%s'",
+                    cname, hostname);
+}
+
+static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int verify)
+{
 #if (OPENSSL_VERSION_NUMBER >= 0x10000000L)
        const SSL_METHOD *meth;
 #else
@@ -213,6 +262,7 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve
 #endif
        SSL_CTX *ctx;
        int ret;
+       X509 *cert;
 
        SSL_library_init();
        SSL_load_error_strings();
@@ -250,15 +300,35 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve
                return -1;
        }
 
+#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME
+       /*
+        * SNI (RFC4366)
+        * OpenSSL does not document this function, but the implementation
+        * returns 1 on success, 0 on failure after calling SSLerr().
+        */
+       ret = SSL_set_tlsext_host_name(sock->ssl, server.host);
+       if (ret != 1)
+               warning("SSL_set_tlsext_host_name(%s) failed.", server.host);
+#endif
+
        ret = SSL_connect(sock->ssl);
        if (ret <= 0) {
                socket_perror("SSL_connect", sock, ret);
                return -1;
        }
 
+       if (verify) {
+               /* make sure the hostname matches that of the certificate */
+               cert = SSL_get_peer_certificate(sock->ssl);
+               if (!cert)
+                       return error("unable to get peer certificate.");
+               if (verify_hostname(cert, server.host) < 0)
+                       return -1;
+       }
+
        return 0;
-#endif
 }
+#endif
 
 static int socket_read(struct imap_socket *sock, char *buf, int len)
 {
@@ -876,12 +946,13 @@ static int auth_cram_md5(struct imap_store *ctx, struct imap_cmd *cmd, const cha
 
 static struct imap_store *imap_open_store(struct imap_server_conf *srvc)
 {
+       struct credential cred = CREDENTIAL_INIT;
        struct imap_store *ctx;
        struct imap *imap;
        char *arg, *rsp;
        int s = -1, preauth;
 
-       ctx = xcalloc(sizeof(*ctx), 1);
+       ctx = xcalloc(1, sizeof(*ctx));
 
        ctx->imap = imap = xcalloc(sizeof(*imap), 1);
        imap->buf.sock.fd[0] = imap->buf.sock.fd[1] = -1;
@@ -1026,25 +1097,23 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc)
                }
 #endif
                imap_info("Logging in...\n");
-               if (!srvc->user) {
-                       fprintf(stderr, "Skipping server %s, no user\n", srvc->host);
-                       goto bail;
-               }
-               if (!srvc->pass) {
-                       struct strbuf prompt = STRBUF_INIT;
-                       strbuf_addf(&prompt, "Password (%s@%s): ", srvc->user, srvc->host);
-                       arg = git_getpass(prompt.buf);
-                       strbuf_release(&prompt);
-                       if (!*arg) {
-                               fprintf(stderr, "Skipping account %s@%s, no password\n", srvc->user, srvc->host);
-                               goto bail;
-                       }
-                       /*
-                        * getpass() returns a pointer to a static buffer.  make a copy
-                        * for long term storage.
-                        */
-                       srvc->pass = xstrdup(arg);
+               if (!srvc->user || !srvc->pass) {
+                       cred.protocol = xstrdup(srvc->use_ssl ? "imaps" : "imap");
+                       cred.host = xstrdup(srvc->host);
+
+                       if (srvc->user)
+                               cred.username = xstrdup(srvc->user);
+                       if (srvc->pass)
+                               cred.password = xstrdup(srvc->pass);
+
+                       credential_fill(&cred);
+
+                       if (!srvc->user)
+                               srvc->user = xstrdup(cred.username);
+                       if (!srvc->pass)
+                               srvc->pass = xstrdup(cred.password);
                }
+
                if (CAP(NOLOGIN)) {
                        fprintf(stderr, "Skipping account %s@%s, server forbids LOGIN\n", srvc->user, srvc->host);
                        goto bail;
@@ -1083,10 +1152,18 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc)
                }
        } /* !preauth */
 
+       if (cred.username)
+               credential_approve(&cred);
+       credential_clear(&cred);
+
        ctx->prefix = "";
        return ctx;
 
 bail:
+       if (cred.username)
+               credential_reject(&cred);
+       credential_clear(&cred);
+
        imap_close_store(ctx);
        return NULL;
 }
@@ -1193,7 +1270,7 @@ static int count_messages(struct strbuf *all_msgs)
        char *p = all_msgs->buf;
 
        while (1) {
-               if (!prefixcmp(p, "From ")) {
+               if (starts_with(p, "From ")) {
                        p = strstr(p+5, "\nFrom: ");
                        if (!p) break;
                        p = strstr(p+7, "\nDate: ");
@@ -1227,7 +1304,7 @@ static int split_msg(struct strbuf *all_msgs, struct strbuf *msg, int *ofs)
        data = &all_msgs->buf[*ofs];
        len = all_msgs->len - *ofs;
 
-       if (len < 5 || prefixcmp(data, "From "))
+       if (len < 5 || !starts_with(data, "From "))
                return 0;
 
        p = strchr(data, '\n');
@@ -1269,13 +1346,13 @@ static int git_imap_config(const char *key, const char *val, void *cb)
        if (!strcmp("folder", key)) {
                imap_folder = xstrdup(val);
        } else if (!strcmp("host", key)) {
-               if (!prefixcmp(val, "imap:"))
+               if (starts_with(val, "imap:"))
                        val += 5;
-               else if (!prefixcmp(val, "imaps:")) {
+               else if (starts_with(val, "imaps:")) {
                        val += 6;
                        server.use_ssl = 1;
                }
-               if (!prefixcmp(val, "//"))
+               if (starts_with(val, "//"))
                        val += 2;
                server.host = xstrdup(val);
        } else if (!strcmp("user", key))