Merge branch 'ab/simplify-perl-makefile'
[gitweb.git] / connect.c
index aa994d1518354bcfdcbcb7f1d582bceca03beb8f..c3a014c5babf72ee4c0d135fec264afb37b040de 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -788,6 +788,7 @@ static const char *get_ssh_command(void)
 }
 
 enum ssh_variant {
+       VARIANT_AUTO,
        VARIANT_SIMPLE,
        VARIANT_SSH,
        VARIANT_PLINK,
@@ -795,14 +796,16 @@ enum ssh_variant {
        VARIANT_TORTOISEPLINK,
 };
 
-static int override_ssh_variant(enum ssh_variant *ssh_variant)
+static void override_ssh_variant(enum ssh_variant *ssh_variant)
 {
        const char *variant = getenv("GIT_SSH_VARIANT");
 
        if (!variant && git_config_get_string_const("ssh.variant", &variant))
-               return 0;
+               return;
 
-       if (!strcmp(variant, "plink"))
+       if (!strcmp(variant, "auto"))
+               *ssh_variant = VARIANT_AUTO;
+       else if (!strcmp(variant, "plink"))
                *ssh_variant = VARIANT_PLINK;
        else if (!strcmp(variant, "putty"))
                *ssh_variant = VARIANT_PUTTY;
@@ -812,18 +815,18 @@ static int override_ssh_variant(enum ssh_variant *ssh_variant)
                *ssh_variant = VARIANT_SIMPLE;
        else
                *ssh_variant = VARIANT_SSH;
-
-       return 1;
 }
 
 static enum ssh_variant determine_ssh_variant(const char *ssh_command,
                                              int is_cmdline)
 {
-       enum ssh_variant ssh_variant = VARIANT_SIMPLE;
+       enum ssh_variant ssh_variant = VARIANT_AUTO;
        const char *variant;
        char *p = NULL;
 
-       if (override_ssh_variant(&ssh_variant))
+       override_ssh_variant(&ssh_variant);
+
+       if (ssh_variant != VARIANT_AUTO)
                return ssh_variant;
 
        if (!is_cmdline) {
@@ -861,6 +864,176 @@ static enum ssh_variant determine_ssh_variant(const char *ssh_command,
        return ssh_variant;
 }
 
+/*
+ * Open a connection using Git's native protocol.
+ *
+ * The caller is responsible for freeing hostandport, but this function may
+ * modify it (for example, to truncate it to remove the port part).
+ */
+static struct child_process *git_connect_git(int fd[2], char *hostandport,
+                                            const char *path, const char *prog,
+                                            int flags)
+{
+       struct child_process *conn;
+       struct strbuf request = STRBUF_INIT;
+       /*
+        * Set up virtual host information based on where we will
+        * connect, unless the user has overridden us in
+        * the environment.
+        */
+       char *target_host = getenv("GIT_OVERRIDE_VIRTUAL_HOST");
+       if (target_host)
+               target_host = xstrdup(target_host);
+       else
+               target_host = xstrdup(hostandport);
+
+       transport_check_allowed("git");
+
+       /*
+        * These underlying connection commands die() if they
+        * cannot connect.
+        */
+       if (git_use_proxy(hostandport))
+               conn = git_proxy_connect(fd, hostandport);
+       else
+               conn = git_tcp_connect(fd, hostandport, flags);
+       /*
+        * Separate original protocol components prog and path
+        * from extended host header with a NUL byte.
+        *
+        * Note: Do not add any other headers here!  Doing so
+        * will cause older git-daemon servers to crash.
+        */
+       strbuf_addf(&request,
+                   "%s %s%chost=%s%c",
+                   prog, path, 0,
+                   target_host, 0);
+
+       /* If using a new version put that stuff here after a second null byte */
+       if (get_protocol_version_config() > 0) {
+               strbuf_addch(&request, '\0');
+               strbuf_addf(&request, "version=%d%c",
+                           get_protocol_version_config(), '\0');
+       }
+
+       packet_write(fd[1], request.buf, request.len);
+
+       free(target_host);
+       strbuf_release(&request);
+       return conn;
+}
+
+/*
+ * Append the appropriate environment variables to `env` and options to
+ * `args` for running ssh in Git's SSH-tunneled transport.
+ */
+static void push_ssh_options(struct argv_array *args, struct argv_array *env,
+                            enum ssh_variant variant, const char *port,
+                            int flags)
+{
+       if (variant == VARIANT_SSH &&
+           get_protocol_version_config() > 0) {
+               argv_array_push(args, "-o");
+               argv_array_push(args, "SendEnv=" GIT_PROTOCOL_ENVIRONMENT);
+               argv_array_pushf(env, GIT_PROTOCOL_ENVIRONMENT "=version=%d",
+                                get_protocol_version_config());
+       }
+
+       if (flags & CONNECT_IPV4) {
+               switch (variant) {
+               case VARIANT_AUTO:
+                       BUG("VARIANT_AUTO passed to push_ssh_options");
+               case VARIANT_SIMPLE:
+                       die("ssh variant 'simple' does not support -4");
+               case VARIANT_SSH:
+               case VARIANT_PLINK:
+               case VARIANT_PUTTY:
+               case VARIANT_TORTOISEPLINK:
+                       argv_array_push(args, "-4");
+               }
+       } else if (flags & CONNECT_IPV6) {
+               switch (variant) {
+               case VARIANT_AUTO:
+                       BUG("VARIANT_AUTO passed to push_ssh_options");
+               case VARIANT_SIMPLE:
+                       die("ssh variant 'simple' does not support -6");
+               case VARIANT_SSH:
+               case VARIANT_PLINK:
+               case VARIANT_PUTTY:
+               case VARIANT_TORTOISEPLINK:
+                       argv_array_push(args, "-6");
+               }
+       }
+
+       if (variant == VARIANT_TORTOISEPLINK)
+               argv_array_push(args, "-batch");
+
+       if (port) {
+               switch (variant) {
+               case VARIANT_AUTO:
+                       BUG("VARIANT_AUTO passed to push_ssh_options");
+               case VARIANT_SIMPLE:
+                       die("ssh variant 'simple' does not support setting port");
+               case VARIANT_SSH:
+                       argv_array_push(args, "-p");
+                       break;
+               case VARIANT_PLINK:
+               case VARIANT_PUTTY:
+               case VARIANT_TORTOISEPLINK:
+                       argv_array_push(args, "-P");
+               }
+
+               argv_array_push(args, port);
+       }
+}
+
+/* Prepare a child_process for use by Git's SSH-tunneled transport. */
+static void fill_ssh_args(struct child_process *conn, const char *ssh_host,
+                         const char *port, int flags)
+{
+       const char *ssh;
+       enum ssh_variant variant;
+
+       if (looks_like_command_line_option(ssh_host))
+               die("strange hostname '%s' blocked", ssh_host);
+
+       ssh = get_ssh_command();
+       if (ssh) {
+               variant = determine_ssh_variant(ssh, 1);
+       } else {
+               /*
+                * GIT_SSH is the no-shell version of
+                * GIT_SSH_COMMAND (and must remain so for
+                * historical compatibility).
+                */
+               conn->use_shell = 0;
+
+               ssh = getenv("GIT_SSH");
+               if (!ssh)
+                       ssh = "ssh";
+               variant = determine_ssh_variant(ssh, 0);
+       }
+
+       if (variant == VARIANT_AUTO) {
+               struct child_process detect = CHILD_PROCESS_INIT;
+
+               detect.use_shell = conn->use_shell;
+               detect.no_stdin = detect.no_stdout = detect.no_stderr = 1;
+
+               argv_array_push(&detect.args, ssh);
+               argv_array_push(&detect.args, "-G");
+               push_ssh_options(&detect.args, &detect.env_array,
+                                VARIANT_SSH, port, flags);
+               argv_array_push(&detect.args, ssh_host);
+
+               variant = run_command(&detect) ? VARIANT_SIMPLE : VARIANT_SSH;
+       }
+
+       argv_array_push(&conn->args, ssh);
+       push_ssh_options(&conn->args, &conn->env_array, variant, port, flags);
+       argv_array_push(&conn->args, ssh_host);
+}
+
 /*
  * This returns the dummy child_process `no_fork` if the transport protocol
  * does not need fork(2), or a struct child_process object if it does.  Once
@@ -892,50 +1065,7 @@ struct child_process *git_connect(int fd[2], const char *url,
                printf("Diag: path=%s\n", path ? path : "NULL");
                conn = NULL;
        } else if (protocol == PROTO_GIT) {
-               struct strbuf request = STRBUF_INIT;
-               /*
-                * Set up virtual host information based on where we will
-                * connect, unless the user has overridden us in
-                * the environment.
-                */
-               char *target_host = getenv("GIT_OVERRIDE_VIRTUAL_HOST");
-               if (target_host)
-                       target_host = xstrdup(target_host);
-               else
-                       target_host = xstrdup(hostandport);
-
-               transport_check_allowed("git");
-
-               /* These underlying connection commands die() if they
-                * cannot connect.
-                */
-               if (git_use_proxy(hostandport))
-                       conn = git_proxy_connect(fd, hostandport);
-               else
-                       conn = git_tcp_connect(fd, hostandport, flags);
-               /*
-                * Separate original protocol components prog and path
-                * from extended host header with a NUL byte.
-                *
-                * Note: Do not add any other headers here!  Doing so
-                * will cause older git-daemon servers to crash.
-                */
-               strbuf_addf(&request,
-                           "%s %s%chost=%s%c",
-                           prog, path, 0,
-                           target_host, 0);
-
-               /* If using a new version put that stuff here after a second null byte */
-               if (get_protocol_version_config() > 0) {
-                       strbuf_addch(&request, '\0');
-                       strbuf_addf(&request, "version=%d%c",
-                                   get_protocol_version_config(), '\0');
-               }
-
-               packet_write(fd[1], request.buf, request.len);
-
-               free(target_host);
-               strbuf_release(&request);
+               conn = git_connect_git(fd, hostandport, path, prog, flags);
        } else {
                struct strbuf cmd = STRBUF_INIT;
                const char *const *var;
@@ -957,8 +1087,6 @@ struct child_process *git_connect(int fd[2], const char *url,
                conn->use_shell = 1;
                conn->in = conn->out = -1;
                if (protocol == PROTO_SSH) {
-                       const char *ssh;
-                       enum ssh_variant variant;
                        char *ssh_host = hostandport;
                        const char *port = NULL;
                        transport_check_allowed("ssh");
@@ -980,57 +1108,7 @@ struct child_process *git_connect(int fd[2], const char *url,
                                strbuf_release(&cmd);
                                return NULL;
                        }
-
-                       if (looks_like_command_line_option(ssh_host))
-                               die("strange hostname '%s' blocked", ssh_host);
-
-                       ssh = get_ssh_command();
-                       if (ssh) {
-                               variant = determine_ssh_variant(ssh, 1);
-                       } else {
-                               /*
-                                * GIT_SSH is the no-shell version of
-                                * GIT_SSH_COMMAND (and must remain so for
-                                * historical compatibility).
-                                */
-                               conn->use_shell = 0;
-
-                               ssh = getenv("GIT_SSH");
-                               if (!ssh)
-                                       ssh = "ssh";
-                               variant = determine_ssh_variant(ssh, 0);
-                       }
-
-                       argv_array_push(&conn->args, ssh);
-
-                       if (variant == VARIANT_SSH &&
-                           get_protocol_version_config() > 0) {
-                               argv_array_push(&conn->args, "-o");
-                               argv_array_push(&conn->args, "SendEnv=" GIT_PROTOCOL_ENVIRONMENT);
-                               argv_array_pushf(&conn->env_array, GIT_PROTOCOL_ENVIRONMENT "=version=%d",
-                                                get_protocol_version_config());
-                       }
-
-                       if (variant != VARIANT_SIMPLE) {
-                               if (flags & CONNECT_IPV4)
-                                       argv_array_push(&conn->args, "-4");
-                               else if (flags & CONNECT_IPV6)
-                                       argv_array_push(&conn->args, "-6");
-                       }
-
-                       if (variant == VARIANT_TORTOISEPLINK)
-                               argv_array_push(&conn->args, "-batch");
-
-                       if (port && variant != VARIANT_SIMPLE) {
-                               if (variant == VARIANT_SSH)
-                                       argv_array_push(&conn->args, "-p");
-                               else
-                                       argv_array_push(&conn->args, "-P");
-
-                               argv_array_push(&conn->args, port);
-                       }
-
-                       argv_array_push(&conn->args, ssh_host);
+                       fill_ssh_args(conn, ssh_host, port, flags);
                } else {
                        transport_check_allowed("file");
                        if (get_protocol_version_config() > 0) {