1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429 |
- #include "git-compat-util.h"
- #include "cache.h"
- #include "config.h"
- #include "pkt-line.h"
- #include "quote.h"
- #include "refs.h"
- #include "run-command.h"
- #include "remote.h"
- #include "connect.h"
- #include "url.h"
- #include "string-list.h"
- #include "oid-array.h"
- #include "transport.h"
- #include "strbuf.h"
- #include "version.h"
- #include "protocol.h"
- #include "alias.h"
- static char *server_capabilities_v1;
- static struct strvec server_capabilities_v2 = STRVEC_INIT;
- static const char *next_server_feature_value(const char *feature, int *len, int *offset);
- static int check_ref(const char *name, unsigned int flags)
- {
- if (!flags)
- return 1;
- if (!skip_prefix(name, "refs/", &name))
- return 0;
- /* REF_NORMAL means that we don't want the magic fake tag refs */
- if ((flags & REF_NORMAL) && check_refname_format(name, 0))
- return 0;
- /* REF_HEADS means that we want regular branch heads */
- if ((flags & REF_HEADS) && starts_with(name, "heads/"))
- return 1;
- /* REF_TAGS means that we want tags */
- if ((flags & REF_TAGS) && starts_with(name, "tags/"))
- return 1;
- /* All type bits clear means that we are ok with anything */
- return !(flags & ~REF_NORMAL);
- }
- int check_ref_type(const struct ref *ref, int flags)
- {
- return check_ref(ref->name, flags);
- }
- static NORETURN void die_initial_contact(int unexpected)
- {
- /*
- * A hang-up after seeing some response from the other end
- * means that it is unexpected, as we know the other end is
- * willing to talk to us. A hang-up before seeing any
- * response does not necessarily mean an ACL problem, though.
- */
- if (unexpected)
- die(_("the remote end hung up upon initial contact"));
- else
- die(_("Could not read from remote repository.\n\n"
- "Please make sure you have the correct access rights\n"
- "and the repository exists."));
- }
- /* Checks if the server supports the capability 'c' */
- int server_supports_v2(const char *c, int die_on_error)
- {
- int i;
- for (i = 0; i < server_capabilities_v2.nr; i++) {
- const char *out;
- if (skip_prefix(server_capabilities_v2.v[i], c, &out) &&
- (!*out || *out == '='))
- return 1;
- }
- if (die_on_error)
- die(_("server doesn't support '%s'"), c);
- return 0;
- }
- int server_feature_v2(const char *c, const char **v)
- {
- int i;
- for (i = 0; i < server_capabilities_v2.nr; i++) {
- const char *out;
- if (skip_prefix(server_capabilities_v2.v[i], c, &out) &&
- (*out == '=')) {
- *v = out + 1;
- return 1;
- }
- }
- return 0;
- }
- int server_supports_feature(const char *c, const char *feature,
- int die_on_error)
- {
- int i;
- for (i = 0; i < server_capabilities_v2.nr; i++) {
- const char *out;
- if (skip_prefix(server_capabilities_v2.v[i], c, &out) &&
- (!*out || *(out++) == '=')) {
- if (parse_feature_request(out, feature))
- return 1;
- else
- break;
- }
- }
- if (die_on_error)
- die(_("server doesn't support feature '%s'"), feature);
- return 0;
- }
- static void process_capabilities_v2(struct packet_reader *reader)
- {
- while (packet_reader_read(reader) == PACKET_READ_NORMAL)
- strvec_push(&server_capabilities_v2, reader->line);
- if (reader->status != PACKET_READ_FLUSH)
- die(_("expected flush after capabilities"));
- }
- enum protocol_version discover_version(struct packet_reader *reader)
- {
- enum protocol_version version = protocol_unknown_version;
- /*
- * Peek the first line of the server's response to
- * determine the protocol version the server is speaking.
- */
- switch (packet_reader_peek(reader)) {
- case PACKET_READ_EOF:
- die_initial_contact(0);
- case PACKET_READ_FLUSH:
- case PACKET_READ_DELIM:
- case PACKET_READ_RESPONSE_END:
- version = protocol_v0;
- break;
- case PACKET_READ_NORMAL:
- version = determine_protocol_version_client(reader->line);
- break;
- }
- switch (version) {
- case protocol_v2:
- process_capabilities_v2(reader);
- break;
- case protocol_v1:
- /* Read the peeked version line */
- packet_reader_read(reader);
- break;
- case protocol_v0:
- break;
- case protocol_unknown_version:
- BUG("unknown protocol version");
- }
- return version;
- }
- static void parse_one_symref_info(struct string_list *symref, const char *val, int len)
- {
- char *sym, *target;
- struct string_list_item *item;
- if (!len)
- return; /* just "symref" */
- /* e.g. "symref=HEAD:refs/heads/master" */
- sym = xmemdupz(val, len);
- target = strchr(sym, ':');
- if (!target)
- /* just "symref=something" */
- goto reject;
- *(target++) = '\0';
- if (check_refname_format(sym, REFNAME_ALLOW_ONELEVEL) ||
- check_refname_format(target, REFNAME_ALLOW_ONELEVEL))
- /* "symref=bogus:pair */
- goto reject;
- item = string_list_append_nodup(symref, sym);
- item->util = target;
- return;
- reject:
- free(sym);
- return;
- }
- static void annotate_refs_with_symref_info(struct ref *ref)
- {
- struct string_list symref = STRING_LIST_INIT_DUP;
- int offset = 0;
- while (1) {
- int len;
- const char *val;
- val = next_server_feature_value("symref", &len, &offset);
- if (!val)
- break;
- parse_one_symref_info(&symref, val, len);
- }
- string_list_sort(&symref);
- for (; ref; ref = ref->next) {
- struct string_list_item *item;
- item = string_list_lookup(&symref, ref->name);
- if (!item)
- continue;
- ref->symref = xstrdup((char *)item->util);
- }
- string_list_clear(&symref, 0);
- }
- static void process_capabilities(struct packet_reader *reader, int *linelen)
- {
- const char *feat_val;
- int feat_len;
- const char *line = reader->line;
- int nul_location = strlen(line);
- if (nul_location == *linelen)
- return;
- server_capabilities_v1 = xstrdup(line + nul_location + 1);
- *linelen = nul_location;
- feat_val = server_feature_value("object-format", &feat_len);
- if (feat_val) {
- char *hash_name = xstrndup(feat_val, feat_len);
- int hash_algo = hash_algo_by_name(hash_name);
- if (hash_algo != GIT_HASH_UNKNOWN)
- reader->hash_algo = &hash_algos[hash_algo];
- free(hash_name);
- } else {
- reader->hash_algo = &hash_algos[GIT_HASH_SHA1];
- }
- }
- static int process_dummy_ref(const struct packet_reader *reader)
- {
- const char *line = reader->line;
- struct object_id oid;
- const char *name;
- if (parse_oid_hex_algop(line, &oid, &name, reader->hash_algo))
- return 0;
- if (*name != ' ')
- return 0;
- name++;
- return oideq(&null_oid, &oid) && !strcmp(name, "capabilities^{}");
- }
- static void check_no_capabilities(const char *line, int len)
- {
- if (strlen(line) != len)
- warning(_("ignoring capabilities after first line '%s'"),
- line + strlen(line));
- }
- static int process_ref(const struct packet_reader *reader, int len,
- struct ref ***list, unsigned int flags,
- struct oid_array *extra_have)
- {
- const char *line = reader->line;
- struct object_id old_oid;
- const char *name;
- if (parse_oid_hex_algop(line, &old_oid, &name, reader->hash_algo))
- return 0;
- if (*name != ' ')
- return 0;
- name++;
- if (extra_have && !strcmp(name, ".have")) {
- oid_array_append(extra_have, &old_oid);
- } else if (!strcmp(name, "capabilities^{}")) {
- die(_("protocol error: unexpected capabilities^{}"));
- } else if (check_ref(name, flags)) {
- struct ref *ref = alloc_ref(name);
- oidcpy(&ref->old_oid, &old_oid);
- **list = ref;
- *list = &ref->next;
- }
- check_no_capabilities(line, len);
- return 1;
- }
- static int process_shallow(const struct packet_reader *reader, int len,
- struct oid_array *shallow_points)
- {
- const char *line = reader->line;
- const char *arg;
- struct object_id old_oid;
- if (!skip_prefix(line, "shallow ", &arg))
- return 0;
- if (get_oid_hex_algop(arg, &old_oid, reader->hash_algo))
- die(_("protocol error: expected shallow sha-1, got '%s'"), arg);
- if (!shallow_points)
- die(_("repository on the other end cannot be shallow"));
- oid_array_append(shallow_points, &old_oid);
- check_no_capabilities(line, len);
- return 1;
- }
- enum get_remote_heads_state {
- EXPECTING_FIRST_REF = 0,
- EXPECTING_REF,
- EXPECTING_SHALLOW,
- EXPECTING_DONE,
- };
- /*
- * Read all the refs from the other end
- */
- struct ref **get_remote_heads(struct packet_reader *reader,
- struct ref **list, unsigned int flags,
- struct oid_array *extra_have,
- struct oid_array *shallow_points)
- {
- struct ref **orig_list = list;
- int len = 0;
- enum get_remote_heads_state state = EXPECTING_FIRST_REF;
- *list = NULL;
- while (state != EXPECTING_DONE) {
- switch (packet_reader_read(reader)) {
- case PACKET_READ_EOF:
- die_initial_contact(1);
- case PACKET_READ_NORMAL:
- len = reader->pktlen;
- break;
- case PACKET_READ_FLUSH:
- state = EXPECTING_DONE;
- break;
- case PACKET_READ_DELIM:
- case PACKET_READ_RESPONSE_END:
- die(_("invalid packet"));
- }
- switch (state) {
- case EXPECTING_FIRST_REF:
- process_capabilities(reader, &len);
- if (process_dummy_ref(reader)) {
- state = EXPECTING_SHALLOW;
- break;
- }
- state = EXPECTING_REF;
- /* fallthrough */
- case EXPECTING_REF:
- if (process_ref(reader, len, &list, flags, extra_have))
- break;
- state = EXPECTING_SHALLOW;
- /* fallthrough */
- case EXPECTING_SHALLOW:
- if (process_shallow(reader, len, shallow_points))
- break;
- die(_("protocol error: unexpected '%s'"), reader->line);
- case EXPECTING_DONE:
- break;
- }
- }
- annotate_refs_with_symref_info(*orig_list);
- return list;
- }
- /* Returns 1 when a valid ref has been added to `list`, 0 otherwise */
- static int process_ref_v2(struct packet_reader *reader, struct ref ***list)
- {
- int ret = 1;
- int i = 0;
- struct object_id old_oid;
- struct ref *ref;
- struct string_list line_sections = STRING_LIST_INIT_DUP;
- const char *end;
- const char *line = reader->line;
- /*
- * Ref lines have a number of fields which are space deliminated. The
- * first field is the OID of the ref. The second field is the ref
- * name. Subsequent fields (symref-target and peeled) are optional and
- * don't have a particular order.
- */
- if (string_list_split(&line_sections, line, ' ', -1) < 2) {
- ret = 0;
- goto out;
- }
- if (parse_oid_hex_algop(line_sections.items[i++].string, &old_oid, &end, reader->hash_algo) ||
- *end) {
- ret = 0;
- goto out;
- }
- ref = alloc_ref(line_sections.items[i++].string);
- memcpy(ref->old_oid.hash, old_oid.hash, reader->hash_algo->rawsz);
- **list = ref;
- *list = &ref->next;
- for (; i < line_sections.nr; i++) {
- const char *arg = line_sections.items[i].string;
- if (skip_prefix(arg, "symref-target:", &arg))
- ref->symref = xstrdup(arg);
- if (skip_prefix(arg, "peeled:", &arg)) {
- struct object_id peeled_oid;
- char *peeled_name;
- struct ref *peeled;
- if (parse_oid_hex_algop(arg, &peeled_oid, &end,
- reader->hash_algo) || *end) {
- ret = 0;
- goto out;
- }
- peeled_name = xstrfmt("%s^{}", ref->name);
- peeled = alloc_ref(peeled_name);
- memcpy(peeled->old_oid.hash, peeled_oid.hash,
- reader->hash_algo->rawsz);
- **list = peeled;
- *list = &peeled->next;
- free(peeled_name);
- }
- }
- out:
- string_list_clear(&line_sections, 0);
- return ret;
- }
- void check_stateless_delimiter(int stateless_rpc,
- struct packet_reader *reader,
- const char *error)
- {
- if (!stateless_rpc)
- return; /* not in stateless mode, no delimiter expected */
- if (packet_reader_read(reader) != PACKET_READ_RESPONSE_END)
- die("%s", error);
- }
- struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,
- struct ref **list, int for_push,
- const struct strvec *ref_prefixes,
- const struct string_list *server_options,
- int stateless_rpc)
- {
- int i;
- const char *hash_name;
- *list = NULL;
- if (server_supports_v2("ls-refs", 1))
- packet_write_fmt(fd_out, "command=ls-refs\n");
- if (server_supports_v2("agent", 0))
- packet_write_fmt(fd_out, "agent=%s", git_user_agent_sanitized());
- if (server_feature_v2("object-format", &hash_name)) {
- int hash_algo = hash_algo_by_name(hash_name);
- if (hash_algo == GIT_HASH_UNKNOWN)
- die(_("unknown object format '%s' specified by server"), hash_name);
- reader->hash_algo = &hash_algos[hash_algo];
- packet_write_fmt(fd_out, "object-format=%s", reader->hash_algo->name);
- } else {
- reader->hash_algo = &hash_algos[GIT_HASH_SHA1];
- }
- if (server_options && server_options->nr &&
- server_supports_v2("server-option", 1))
- for (i = 0; i < server_options->nr; i++)
- packet_write_fmt(fd_out, "server-option=%s",
- server_options->items[i].string);
- packet_delim(fd_out);
- /* When pushing we don't want to request the peeled tags */
- if (!for_push)
- packet_write_fmt(fd_out, "peel\n");
- packet_write_fmt(fd_out, "symrefs\n");
- for (i = 0; ref_prefixes && i < ref_prefixes->nr; i++) {
- packet_write_fmt(fd_out, "ref-prefix %s\n",
- ref_prefixes->v[i]);
- }
- packet_flush(fd_out);
- /* Process response from server */
- while (packet_reader_read(reader) == PACKET_READ_NORMAL) {
- if (!process_ref_v2(reader, &list))
- die(_("invalid ls-refs response: %s"), reader->line);
- }
- if (reader->status != PACKET_READ_FLUSH)
- die(_("expected flush after ref listing"));
- check_stateless_delimiter(stateless_rpc, reader,
- _("expected response end packet after ref listing"));
- return list;
- }
- const char *parse_feature_value(const char *feature_list, const char *feature, int *lenp, int *offset)
- {
- int len;
- if (!feature_list)
- return NULL;
- len = strlen(feature);
- if (offset)
- feature_list += *offset;
- while (*feature_list) {
- const char *found = strstr(feature_list, feature);
- if (!found)
- return NULL;
- if (feature_list == found || isspace(found[-1])) {
- const char *value = found + len;
- /* feature with no value (e.g., "thin-pack") */
- if (!*value || isspace(*value)) {
- if (lenp)
- *lenp = 0;
- return value;
- }
- /* feature with a value (e.g., "agent=git/1.2.3") */
- else if (*value == '=') {
- int end;
- value++;
- end = strcspn(value, " \t\n");
- if (lenp)
- *lenp = end;
- if (offset)
- *offset = value + end - feature_list;
- return value;
- }
- /*
- * otherwise we matched a substring of another feature;
- * keep looking
- */
- }
- feature_list = found + 1;
- }
- return NULL;
- }
- int server_supports_hash(const char *desired, int *feature_supported)
- {
- int offset = 0;
- int len;
- const char *hash;
- hash = next_server_feature_value("object-format", &len, &offset);
- if (feature_supported)
- *feature_supported = !!hash;
- if (!hash) {
- hash = hash_algos[GIT_HASH_SHA1].name;
- len = strlen(hash);
- }
- while (hash) {
- if (!xstrncmpz(desired, hash, len))
- return 1;
- hash = next_server_feature_value("object-format", &len, &offset);
- }
- return 0;
- }
- int parse_feature_request(const char *feature_list, const char *feature)
- {
- return !!parse_feature_value(feature_list, feature, NULL, NULL);
- }
- static const char *next_server_feature_value(const char *feature, int *len, int *offset)
- {
- return parse_feature_value(server_capabilities_v1, feature, len, offset);
- }
- const char *server_feature_value(const char *feature, int *len)
- {
- return parse_feature_value(server_capabilities_v1, feature, len, NULL);
- }
- int server_supports(const char *feature)
- {
- return !!server_feature_value(feature, NULL);
- }
- enum protocol {
- PROTO_LOCAL = 1,
- PROTO_FILE,
- PROTO_SSH,
- PROTO_GIT
- };
- int url_is_local_not_ssh(const char *url)
- {
- const char *colon = strchr(url, ':');
- const char *slash = strchr(url, '/');
- return !colon || (slash && slash < colon) ||
- (has_dos_drive_prefix(url) && is_valid_path(url));
- }
- static const char *prot_name(enum protocol protocol)
- {
- switch (protocol) {
- case PROTO_LOCAL:
- case PROTO_FILE:
- return "file";
- case PROTO_SSH:
- return "ssh";
- case PROTO_GIT:
- return "git";
- default:
- return "unknown protocol";
- }
- }
- static enum protocol get_protocol(const char *name)
- {
- if (!strcmp(name, "ssh"))
- return PROTO_SSH;
- if (!strcmp(name, "git"))
- return PROTO_GIT;
- if (!strcmp(name, "git+ssh")) /* deprecated - do not use */
- return PROTO_SSH;
- if (!strcmp(name, "ssh+git")) /* deprecated - do not use */
- return PROTO_SSH;
- if (!strcmp(name, "file"))
- return PROTO_FILE;
- die(_("protocol '%s' is not supported"), name);
- }
- static char *host_end(char **hoststart, int removebrackets)
- {
- char *host = *hoststart;
- char *end;
- char *start = strstr(host, "@[");
- if (start)
- start++; /* Jump over '@' */
- else
- start = host;
- if (start[0] == '[') {
- end = strchr(start + 1, ']');
- if (end) {
- if (removebrackets) {
- *end = 0;
- memmove(start, start + 1, end - start);
- end++;
- }
- } else
- end = host;
- } else
- end = host;
- return end;
- }
- #define STR_(s) # s
- #define STR(s) STR_(s)
- static void get_host_and_port(char **host, const char **port)
- {
- char *colon, *end;
- end = host_end(host, 1);
- colon = strchr(end, ':');
- if (colon) {
- long portnr = strtol(colon + 1, &end, 10);
- if (end != colon + 1 && *end == '\0' && 0 <= portnr && portnr < 65536) {
- *colon = 0;
- *port = colon + 1;
- } else if (!colon[1]) {
- *colon = 0;
- }
- }
- }
- static void enable_keepalive(int sockfd)
- {
- int ka = 1;
- if (setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &ka, sizeof(ka)) < 0)
- error_errno(_("unable to set SO_KEEPALIVE on socket"));
- }
- #ifndef NO_IPV6
- static const char *ai_name(const struct addrinfo *ai)
- {
- static char addr[NI_MAXHOST];
- if (getnameinfo(ai->ai_addr, ai->ai_addrlen, addr, sizeof(addr), NULL, 0,
- NI_NUMERICHOST) != 0)
- xsnprintf(addr, sizeof(addr), "(unknown)");
- return addr;
- }
- /*
- * Returns a connected socket() fd, or else die()s.
- */
- static int git_tcp_connect_sock(char *host, int flags)
- {
- struct strbuf error_message = STRBUF_INIT;
- int sockfd = -1;
- const char *port = STR(DEFAULT_GIT_PORT);
- struct addrinfo hints, *ai0, *ai;
- int gai;
- int cnt = 0;
- get_host_and_port(&host, &port);
- if (!*port)
- port = "<none>";
- memset(&hints, 0, sizeof(hints));
- if (flags & CONNECT_IPV4)
- hints.ai_family = AF_INET;
- else if (flags & CONNECT_IPV6)
- hints.ai_family = AF_INET6;
- hints.ai_socktype = SOCK_STREAM;
- hints.ai_protocol = IPPROTO_TCP;
- if (flags & CONNECT_VERBOSE)
- fprintf(stderr, _("Looking up %s ... "), host);
- gai = getaddrinfo(host, port, &hints, &ai);
- if (gai)
- die(_("unable to look up %s (port %s) (%s)"), host, port, gai_strerror(gai));
- if (flags & CONNECT_VERBOSE)
- /* TRANSLATORS: this is the end of "Looking up %s ... " */
- fprintf(stderr, _("done.\nConnecting to %s (port %s) ... "), host, port);
- for (ai0 = ai; ai; ai = ai->ai_next, cnt++) {
- sockfd = socket(ai->ai_family,
- ai->ai_socktype, ai->ai_protocol);
- if ((sockfd < 0) ||
- (connect(sockfd, ai->ai_addr, ai->ai_addrlen) < 0)) {
- strbuf_addf(&error_message, "%s[%d: %s]: errno=%s\n",
- host, cnt, ai_name(ai), strerror(errno));
- if (0 <= sockfd)
- close(sockfd);
- sockfd = -1;
- continue;
- }
- if (flags & CONNECT_VERBOSE)
- fprintf(stderr, "%s ", ai_name(ai));
- break;
- }
- freeaddrinfo(ai0);
- if (sockfd < 0)
- die(_("unable to connect to %s:\n%s"), host, error_message.buf);
- enable_keepalive(sockfd);
- if (flags & CONNECT_VERBOSE)
- /* TRANSLATORS: this is the end of "Connecting to %s (port %s) ... " */
- fprintf_ln(stderr, _("done."));
- strbuf_release(&error_message);
- return sockfd;
- }
- #else /* NO_IPV6 */
- /*
- * Returns a connected socket() fd, or else die()s.
- */
- static int git_tcp_connect_sock(char *host, int flags)
- {
- struct strbuf error_message = STRBUF_INIT;
- int sockfd = -1;
- const char *port = STR(DEFAULT_GIT_PORT);
- char *ep;
- struct hostent *he;
- struct sockaddr_in sa;
- char **ap;
- unsigned int nport;
- int cnt;
- get_host_and_port(&host, &port);
- if (flags & CONNECT_VERBOSE)
- fprintf(stderr, _("Looking up %s ... "), host);
- he = gethostbyname(host);
- if (!he)
- die(_("unable to look up %s (%s)"), host, hstrerror(h_errno));
- nport = strtoul(port, &ep, 10);
- if ( ep == port || *ep ) {
- /* Not numeric */
- struct servent *se = getservbyname(port,"tcp");
- if ( !se )
- die(_("unknown port %s"), port);
- nport = se->s_port;
- }
- if (flags & CONNECT_VERBOSE)
- /* TRANSLATORS: this is the end of "Looking up %s ... " */
- fprintf(stderr, _("done.\nConnecting to %s (port %s) ... "), host, port);
- for (cnt = 0, ap = he->h_addr_list; *ap; ap++, cnt++) {
- memset(&sa, 0, sizeof sa);
- sa.sin_family = he->h_addrtype;
- sa.sin_port = htons(nport);
- memcpy(&sa.sin_addr, *ap, he->h_length);
- sockfd = socket(he->h_addrtype, SOCK_STREAM, 0);
- if ((sockfd < 0) ||
- connect(sockfd, (struct sockaddr *)&sa, sizeof sa) < 0) {
- strbuf_addf(&error_message, "%s[%d: %s]: errno=%s\n",
- host,
- cnt,
- inet_ntoa(*(struct in_addr *)&sa.sin_addr),
- strerror(errno));
- if (0 <= sockfd)
- close(sockfd);
- sockfd = -1;
- continue;
- }
- if (flags & CONNECT_VERBOSE)
- fprintf(stderr, "%s ",
- inet_ntoa(*(struct in_addr *)&sa.sin_addr));
- break;
- }
- if (sockfd < 0)
- die(_("unable to connect to %s:\n%s"), host, error_message.buf);
- enable_keepalive(sockfd);
- if (flags & CONNECT_VERBOSE)
- /* TRANSLATORS: this is the end of "Connecting to %s (port %s) ... " */
- fprintf_ln(stderr, _("done."));
- return sockfd;
- }
- #endif /* NO_IPV6 */
- /*
- * Dummy child_process returned by git_connect() if the transport protocol
- * does not need fork(2).
- */
- static struct child_process no_fork = CHILD_PROCESS_INIT;
- int git_connection_is_socket(struct child_process *conn)
- {
- return conn == &no_fork;
- }
- static struct child_process *git_tcp_connect(int fd[2], char *host, int flags)
- {
- int sockfd = git_tcp_connect_sock(host, flags);
- fd[0] = sockfd;
- fd[1] = dup(sockfd);
- return &no_fork;
- }
- static char *git_proxy_command;
- static int git_proxy_command_options(const char *var, const char *value,
- void *cb)
- {
- if (!strcmp(var, "core.gitproxy")) {
- const char *for_pos;
- int matchlen = -1;
- int hostlen;
- const char *rhost_name = cb;
- int rhost_len = strlen(rhost_name);
- if (git_proxy_command)
- return 0;
- if (!value)
- return config_error_nonbool(var);
- /* [core]
- * ;# matches www.kernel.org as well
- * gitproxy = netcatter-1 for kernel.org
- * gitproxy = netcatter-2 for sample.xz
- * gitproxy = netcatter-default
- */
- for_pos = strstr(value, " for ");
- if (!for_pos)
- /* matches everybody */
- matchlen = strlen(value);
- else {
- hostlen = strlen(for_pos + 5);
- if (rhost_len < hostlen)
- matchlen = -1;
- else if (!strncmp(for_pos + 5,
- rhost_name + rhost_len - hostlen,
- hostlen) &&
- ((rhost_len == hostlen) ||
- rhost_name[rhost_len - hostlen -1] == '.'))
- matchlen = for_pos - value;
- else
- matchlen = -1;
- }
- if (0 <= matchlen) {
- /* core.gitproxy = none for kernel.org */
- if (matchlen == 4 &&
- !memcmp(value, "none", 4))
- matchlen = 0;
- git_proxy_command = xmemdupz(value, matchlen);
- }
- return 0;
- }
- return git_default_config(var, value, cb);
- }
- static int git_use_proxy(const char *host)
- {
- git_proxy_command = getenv("GIT_PROXY_COMMAND");
- git_config(git_proxy_command_options, (void*)host);
- return (git_proxy_command && *git_proxy_command);
- }
- static struct child_process *git_proxy_connect(int fd[2], char *host)
- {
- const char *port = STR(DEFAULT_GIT_PORT);
- struct child_process *proxy;
- get_host_and_port(&host, &port);
- if (looks_like_command_line_option(host))
- die(_("strange hostname '%s' blocked"), host);
- if (looks_like_command_line_option(port))
- die(_("strange port '%s' blocked"), port);
- proxy = xmalloc(sizeof(*proxy));
- child_process_init(proxy);
- strvec_push(&proxy->args, git_proxy_command);
- strvec_push(&proxy->args, host);
- strvec_push(&proxy->args, port);
- proxy->in = -1;
- proxy->out = -1;
- if (start_command(proxy))
- die(_("cannot start proxy %s"), git_proxy_command);
- fd[0] = proxy->out; /* read from proxy stdout */
- fd[1] = proxy->in; /* write to proxy stdin */
- return proxy;
- }
- static char *get_port(char *host)
- {
- char *end;
- char *p = strchr(host, ':');
- if (p) {
- long port = strtol(p + 1, &end, 10);
- if (end != p + 1 && *end == '\0' && 0 <= port && port < 65536) {
- *p = '\0';
- return p+1;
- }
- }
- return NULL;
- }
- /*
- * Extract protocol and relevant parts from the specified connection URL.
- * The caller must free() the returned strings.
- */
- static enum protocol parse_connect_url(const char *url_orig, char **ret_host,
- char **ret_path)
- {
- char *url;
- char *host, *path;
- char *end;
- int separator = '/';
- enum protocol protocol = PROTO_LOCAL;
- if (is_url(url_orig))
- url = url_decode(url_orig);
- else
- url = xstrdup(url_orig);
- host = strstr(url, "://");
- if (host) {
- *host = '\0';
- protocol = get_protocol(url);
- host += 3;
- } else {
- host = url;
- if (!url_is_local_not_ssh(url)) {
- protocol = PROTO_SSH;
- separator = ':';
- }
- }
- /*
- * Don't do destructive transforms as protocol code does
- * '[]' unwrapping in get_host_and_port()
- */
- end = host_end(&host, 0);
- if (protocol == PROTO_LOCAL)
- path = end;
- else if (protocol == PROTO_FILE && *host != '/' &&
- !has_dos_drive_prefix(host) &&
- offset_1st_component(host - 2) > 1)
- path = host - 2; /* include the leading "//" */
- else if (protocol == PROTO_FILE && has_dos_drive_prefix(end))
- path = end; /* "file://$(pwd)" may be "file://C:/projects/repo" */
- else
- path = strchr(end, separator);
- if (!path || !*path)
- die(_("no path specified; see 'git help pull' for valid url syntax"));
- /*
- * null-terminate hostname and point path to ~ for URL's like this:
- * ssh://host.xz/~user/repo
- */
- end = path; /* Need to \0 terminate host here */
- if (separator == ':')
- path++; /* path starts after ':' */
- if (protocol == PROTO_GIT || protocol == PROTO_SSH) {
- if (path[1] == '~')
- path++;
- }
- path = xstrdup(path);
- *end = '\0';
- *ret_host = xstrdup(host);
- *ret_path = path;
- free(url);
- return protocol;
- }
- static const char *get_ssh_command(void)
- {
- const char *ssh;
- if ((ssh = getenv("GIT_SSH_COMMAND")))
- return ssh;
- if (!git_config_get_string_tmp("core.sshcommand", &ssh))
- return ssh;
- return NULL;
- }
- enum ssh_variant {
- VARIANT_AUTO,
- VARIANT_SIMPLE,
- VARIANT_SSH,
- VARIANT_PLINK,
- VARIANT_PUTTY,
- VARIANT_TORTOISEPLINK,
- };
- static void override_ssh_variant(enum ssh_variant *ssh_variant)
- {
- const char *variant = getenv("GIT_SSH_VARIANT");
- if (!variant && git_config_get_string_tmp("ssh.variant", &variant))
- return;
- 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;
- else if (!strcmp(variant, "tortoiseplink"))
- *ssh_variant = VARIANT_TORTOISEPLINK;
- else if (!strcmp(variant, "simple"))
- *ssh_variant = VARIANT_SIMPLE;
- else
- *ssh_variant = VARIANT_SSH;
- }
- static enum ssh_variant determine_ssh_variant(const char *ssh_command,
- int is_cmdline)
- {
- enum ssh_variant ssh_variant = VARIANT_AUTO;
- const char *variant;
- char *p = NULL;
- override_ssh_variant(&ssh_variant);
- if (ssh_variant != VARIANT_AUTO)
- return ssh_variant;
- if (!is_cmdline) {
- p = xstrdup(ssh_command);
- variant = basename(p);
- } else {
- const char **ssh_argv;
- p = xstrdup(ssh_command);
- if (split_cmdline(p, &ssh_argv) > 0) {
- variant = basename((char *)ssh_argv[0]);
- /*
- * At this point, variant points into the buffer
- * referenced by p, hence we do not need ssh_argv
- * any longer.
- */
- free(ssh_argv);
- } else {
- free(p);
- return ssh_variant;
- }
- }
- if (!strcasecmp(variant, "ssh") ||
- !strcasecmp(variant, "ssh.exe"))
- ssh_variant = VARIANT_SSH;
- else if (!strcasecmp(variant, "plink") ||
- !strcasecmp(variant, "plink.exe"))
- ssh_variant = VARIANT_PLINK;
- else if (!strcasecmp(variant, "tortoiseplink") ||
- !strcasecmp(variant, "tortoiseplink.exe"))
- ssh_variant = VARIANT_TORTOISEPLINK;
- free(p);
- 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,
- enum protocol_version version,
- 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 (version > 0) {
- strbuf_addch(&request, '\0');
- strbuf_addf(&request, "version=%d%c",
- version, '\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 strvec *args, struct strvec *env,
- enum ssh_variant variant, const char *port,
- enum protocol_version version, int flags)
- {
- if (variant == VARIANT_SSH &&
- version > 0) {
- strvec_push(args, "-o");
- strvec_push(args, "SendEnv=" GIT_PROTOCOL_ENVIRONMENT);
- strvec_pushf(env, GIT_PROTOCOL_ENVIRONMENT "=version=%d",
- version);
- }
- 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:
- strvec_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:
- strvec_push(args, "-6");
- }
- }
- if (variant == VARIANT_TORTOISEPLINK)
- strvec_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:
- strvec_push(args, "-p");
- break;
- case VARIANT_PLINK:
- case VARIANT_PUTTY:
- case VARIANT_TORTOISEPLINK:
- strvec_push(args, "-P");
- }
- strvec_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, enum protocol_version version,
- 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;
- strvec_push(&detect.args, ssh);
- strvec_push(&detect.args, "-G");
- push_ssh_options(&detect.args, &detect.env_array,
- VARIANT_SSH, port, version, flags);
- strvec_push(&detect.args, ssh_host);
- variant = run_command(&detect) ? VARIANT_SIMPLE : VARIANT_SSH;
- }
- strvec_push(&conn->args, ssh);
- push_ssh_options(&conn->args, &conn->env_array, variant, port, version, flags);
- strvec_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
- * done, finish the connection with finish_connect() with the value returned
- * from this function (it is safe to call finish_connect() with NULL to
- * support the former case).
- *
- * If it returns, the connect is successful; it just dies on errors (this
- * will hopefully be changed in a libification effort, to return NULL when
- * the connection failed).
- */
- struct child_process *git_connect(int fd[2], const char *url,
- const char *prog, int flags)
- {
- char *hostandport, *path;
- struct child_process *conn;
- enum protocol protocol;
- enum protocol_version version = get_protocol_version_config();
- /*
- * NEEDSWORK: If we are trying to use protocol v2 and we are planning
- * to perform a push, then fallback to v0 since the client doesn't know
- * how to push yet using v2.
- */
- if (version == protocol_v2 && !strcmp("git-receive-pack", prog))
- version = protocol_v0;
- /* Without this we cannot rely on waitpid() to tell
- * what happened to our children.
- */
- signal(SIGCHLD, SIG_DFL);
- protocol = parse_connect_url(url, &hostandport, &path);
- if ((flags & CONNECT_DIAG_URL) && (protocol != PROTO_SSH)) {
- printf("Diag: url=%s\n", url ? url : "NULL");
- printf("Diag: protocol=%s\n", prot_name(protocol));
- printf("Diag: hostandport=%s\n", hostandport ? hostandport : "NULL");
- printf("Diag: path=%s\n", path ? path : "NULL");
- conn = NULL;
- } else if (protocol == PROTO_GIT) {
- conn = git_connect_git(fd, hostandport, path, prog, version, flags);
- conn->trace2_child_class = "transport/git";
- } else {
- struct strbuf cmd = STRBUF_INIT;
- const char *const *var;
- conn = xmalloc(sizeof(*conn));
- child_process_init(conn);
- if (looks_like_command_line_option(path))
- die(_("strange pathname '%s' blocked"), path);
- strbuf_addstr(&cmd, prog);
- strbuf_addch(&cmd, ' ');
- sq_quote_buf(&cmd, path);
- /* remove repo-local variables from the environment */
- for (var = local_repo_env; *var; var++)
- strvec_push(&conn->env_array, *var);
- conn->use_shell = 1;
- conn->in = conn->out = -1;
- if (protocol == PROTO_SSH) {
- char *ssh_host = hostandport;
- const char *port = NULL;
- transport_check_allowed("ssh");
- get_host_and_port(&ssh_host, &port);
- if (!port)
- port = get_port(ssh_host);
- if (flags & CONNECT_DIAG_URL) {
- printf("Diag: url=%s\n", url ? url : "NULL");
- printf("Diag: protocol=%s\n", prot_name(protocol));
- printf("Diag: userandhost=%s\n", ssh_host ? ssh_host : "NULL");
- printf("Diag: port=%s\n", port ? port : "NONE");
- printf("Diag: path=%s\n", path ? path : "NULL");
- free(hostandport);
- free(path);
- free(conn);
- strbuf_release(&cmd);
- return NULL;
- }
- conn->trace2_child_class = "transport/ssh";
- fill_ssh_args(conn, ssh_host, port, version, flags);
- } else {
- transport_check_allowed("file");
- conn->trace2_child_class = "transport/file";
- if (version > 0) {
- strvec_pushf(&conn->env_array,
- GIT_PROTOCOL_ENVIRONMENT "=version=%d",
- version);
- }
- }
- strvec_push(&conn->args, cmd.buf);
- if (start_command(conn))
- die(_("unable to fork"));
- fd[0] = conn->out; /* read from child's stdout */
- fd[1] = conn->in; /* write to child's stdin */
- strbuf_release(&cmd);
- }
- free(hostandport);
- free(path);
- return conn;
- }
- int finish_connect(struct child_process *conn)
- {
- int code;
- if (!conn || git_connection_is_socket(conn))
- return 0;
- code = finish_command(conn);
- free(conn);
- return code;
- }
|