From: Junio C Hamano Date: Wed, 8 Oct 2014 20:05:15 +0000 (-0700) Subject: Merge branch 'jc/push-cert' X-Git-Tag: v2.2.0-rc0~64 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/fb06b5280ea05d75515fa780cf08d4ec9d6fe101?ds=inline;hp=-c Merge branch 'jc/push-cert' Allow "git push" request to be signed, so that it can be verified and audited, using the GPG signature of the person who pushed, that the tips of branches at a public repository really point the commits the pusher wanted to, without having to "trust" the server. * jc/push-cert: (24 commits) receive-pack::hmac_sha1(): copy the entire SHA-1 hash out signed push: allow stale nonce in stateless mode signed push: teach smart-HTTP to pass "git push --signed" around signed push: fortify against replay attacks signed push: add "pushee" header to push certificate signed push: remove duplicated protocol info send-pack: send feature request on push-cert packet receive-pack: GPG-validate push certificates push: the beginning of "git push --signed" pack-protocol doc: typofix for PKT-LINE gpg-interface: move parse_signature() to where it should be gpg-interface: move parse_gpg_output() to where it should be send-pack: clarify that cmds_sent is a boolean send-pack: refactor inspecting and resetting status and sending commands send-pack: rename "new_refs" to "need_pack_data" receive-pack: factor out capability string generation send-pack: factor out capability string generation send-pack: always send capabilities send-pack: refactor decision to send update per ref send-pack: move REF_STATUS_REJECT_NODELETE logic a bit higher ... --- fb06b5280ea05d75515fa780cf08d4ec9d6fe101 diff --combined Documentation/config.txt index 3b5b24aeb7,d73366f6b8..04a1e2f37e --- a/Documentation/config.txt +++ b/Documentation/config.txt @@@ -499,8 -499,7 +499,8 @@@ core.bigFileThreshold: Files larger than this size are stored deflated, without attempting delta compression. Storing large files without delta compression avoids excessive memory usage, at the - slight expense of increased disk usage. + slight expense of increased disk usage. Additionally files + larger than this size are always treated as binary. + Default is 512 MiB on all platforms. This should be reasonable for most projects as source code and other text files can still @@@ -2044,6 -2043,25 +2044,25 @@@ receive.autogc: receiving data from git-push and updating refs. You can stop it by setting this variable to false. + receive.certnonceseed:: + By setting this variable to a string, `git receive-pack` + will accept a `git push --signed` and verifies it by using + a "nonce" protected by HMAC using this string as a secret + key. + + receive.certnonceslop:: + When a `git push --signed` sent a push certificate with a + "nonce" that was issued by a receive-pack serving the same + repository within this many seconds, export the "nonce" + found in the certificate to `GIT_PUSH_CERT_NONCE` to the + hooks (instead of what the receive-pack asked the sending + side to include). This may allow writing checks in + `pre-receive` and `post-receive` a bit easier. Instead of + checking `GIT_PUSH_CERT_NONCE_SLOP` environment variable + that records by how many seconds the nonce is stale to + decide if they want to accept the certificate, they only + can check `GIT_PUSH_CERT_NONCE_STATUS` is `OK`. + receive.fsckObjects:: If it is set to true, git-receive-pack will check all received objects. It will abort in the case of a malformed object or a diff --combined Documentation/git-push.txt index c0d7403b9a,21b3f29c3b..b17283ab7a --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@@ -10,7 -10,8 +10,8 @@@ SYNOPSI -------- [verse] 'git push' [--all | --mirror | --tags] [--follow-tags] [-n | --dry-run] [--receive-pack=] - [--repo=] [-f | --force] [--prune] [-v | --verbose] [-u | --set-upstream] + [--repo=] [-f | --force] [--prune] [-v | --verbose] + [-u | --set-upstream] [--signed] [--force-with-lease[=[:]]] [--no-verify] [ [...]] @@@ -33,7 -34,7 +34,7 @@@ When the command line does not specify arguments or `--all`, `--mirror`, `--tags` options, the command finds the default `` by consulting `remote.*.push` configuration, and if it is not found, honors `push.default` configuration to decide -what to push (See gitlink:git-config[1] for the meaning of `push.default`). +what to push (See linkgit:git-config[1] for the meaning of `push.default`). OPTIONS[[OPTIONS]] @@@ -129,6 -130,12 +130,12 @@@ already exists on the remote side from the remote but are pointing at commit-ish that are reachable from the refs being pushed. + --signed:: + GPG-sign the push request to update refs on the receiving + side, to allow it to be checked by the hooks and/or be + logged. See linkgit:git-receive-pack[1] for the details + on the receiving end. + --receive-pack=:: --exec=:: Path to the 'git-receive-pack' program on the remote diff --combined Documentation/technical/pack-protocol.txt index 569c48a352,dda120631e..462e20645f --- a/Documentation/technical/pack-protocol.txt +++ b/Documentation/technical/pack-protocol.txt @@@ -212,9 -212,9 +212,9 @@@ out of what the server said it could d want-list = first-want *additional-want - shallow-line = PKT_LINE("shallow" SP obj-id) + shallow-line = PKT-LINE("shallow" SP obj-id) - depth-request = PKT_LINE("deepen" SP depth) + depth-request = PKT-LINE("deepen" SP depth) first-want = PKT-LINE("want" SP obj-id SP capability-list LF) additional-want = PKT-LINE("want" SP obj-id LF) @@@ -465,9 -465,9 +465,9 @@@ contain all the objects that the serve references. ---- - update-request = *shallow command-list [pack-file] + update-request = *shallow ( command-list | push-cert ) [pack-file] - shallow = PKT-LINE("shallow" SP obj-id) + shallow = PKT-LINE("shallow" SP obj-id LF) command-list = PKT-LINE(command NUL capability-list LF) *PKT-LINE(command LF) @@@ -481,12 -481,27 +481,27 @@@ old-id = obj-id new-id = obj-id + push-cert = PKT-LINE("push-cert" NUL capability-list LF) + PKT-LINE("certificate version 0.1" LF) + PKT-LINE("pusher" SP ident LF) + PKT-LINE("pushee" SP url LF) + PKT-LINE("nonce" SP nonce LF) + PKT-LINE(LF) + *PKT-LINE(command LF) + *PKT-LINE(gpg-signature-lines LF) + PKT-LINE("push-cert-end" LF) + pack-file = "PACK" 28*(OCTET) ---- If the receiving end does not support delete-refs, the sending end MUST NOT ask for delete command. + If the receiving end does not support push-cert, the sending end + MUST NOT send a push-cert command. When a push-cert command is + sent, command-list MUST NOT be sent; the commands recorded in the + push certificate is used instead. + The pack-file MUST NOT be sent if the only command used is 'delete'. A pack-file MUST be sent if either create or update command is used, @@@ -501,6 -516,34 +516,34 @@@ was being processed (the obj-id is stil it will run any update hooks to make sure that the update is acceptable. If all of that is fine, the server will then update the references. + Push Certificate + ---------------- + + A push certificate begins with a set of header lines. After the + header and an empty line, the protocol commands follow, one per + line. + + Currently, the following header fields are defined: + + `pusher` ident:: + Identify the GPG key in "Human Readable Name " + format. + + `pushee` url:: + The repository URL (anonymized, if the URL contains + authentication material) the user who ran `git push` + intended to push into. + + `nonce` nonce:: + The 'nonce' string the receiving repository asked the + pushing user to include in the certificate, to prevent + replay attacks. + + The GPG signature lines are a detached signature for the contents + recorded in the push certificate before the signature block begins. + The detached signature is used to certify that the commands were + given by the pusher, who must be the signer. + Report Status ------------- diff --combined builtin/receive-pack.c index daf0600ca3,42f25a5103..a01ac2096a --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@@ -15,7 -15,8 +15,9 @@@ #include "connected.h" #include "argv-array.h" #include "version.h" + #include "tag.h" + #include "gpg-interface.h" +#include "sigchain.h" static const char receive_pack_usage[] = "git receive-pack "; @@@ -42,11 -43,27 +44,27 @@@ static int prefer_ofs_delta = 1 static int auto_update_server_info; static int auto_gc = 1; static int fix_thin = 1; + static int stateless_rpc; + static const char *service_dir; static const char *head_name; static void *head_name_to_free; static int sent_capabilities; static int shallow_update; static const char *alt_shallow_file; + static struct strbuf push_cert = STRBUF_INIT; + static unsigned char push_cert_sha1[20]; + static struct signature_check sigcheck; + static const char *push_cert_nonce; + static const char *cert_nonce_seed; + + static const char *NONCE_UNSOLICITED = "UNSOLICITED"; + static const char *NONCE_BAD = "BAD"; + static const char *NONCE_MISSING = "MISSING"; + static const char *NONCE_OK = "OK"; + static const char *NONCE_SLOP = "SLOP"; + static const char *nonce_status; + static long nonce_stamp_slop; + static unsigned long nonce_stamp_slop_limit; static enum deny_action parse_deny_action(const char *var, const char *value) { @@@ -130,6 -147,14 +148,14 @@@ static int receive_pack_config(const ch return 0; } + if (strcmp(var, "receive.certnonceseed") == 0) + return git_config_string(&cert_nonce_seed, var, value); + + if (strcmp(var, "receive.certnonceslop") == 0) { + nonce_stamp_slop_limit = git_config_ulong(var, value); + return 0; + } + return git_default_config(var, value, cb); } @@@ -138,15 -163,23 +164,23 @@@ static void show_ref(const char *path, if (ref_is_hidden(path)) return; - if (sent_capabilities) + if (sent_capabilities) { packet_write(1, "%s %s\n", sha1_to_hex(sha1), path); - else - packet_write(1, "%s %s%c%s%s agent=%s\n", - sha1_to_hex(sha1), path, 0, - " report-status delete-refs side-band-64k quiet", - prefer_ofs_delta ? " ofs-delta" : "", - git_user_agent_sanitized()); - sent_capabilities = 1; + } else { + struct strbuf cap = STRBUF_INIT; + + strbuf_addstr(&cap, + "report-status delete-refs side-band-64k quiet"); + if (prefer_ofs_delta) + strbuf_addstr(&cap, " ofs-delta"); + if (push_cert_nonce) + strbuf_addf(&cap, " push-cert=%s", push_cert_nonce); + strbuf_addf(&cap, " agent=%s", git_user_agent_sanitized()); + packet_write(1, "%s %s%c%s\n", + sha1_to_hex(sha1), path, 0, cap.buf); + strbuf_release(&cap); + sent_capabilities = 1; + } } static int show_ref_cb(const char *path, const unsigned char *sha1, int flag, void *unused) @@@ -253,10 -286,226 +287,226 @@@ static int copy_to_sideband(int in, in return 0; } + #define HMAC_BLOCK_SIZE 64 + + static void hmac_sha1(unsigned char *out, + const char *key_in, size_t key_len, + const char *text, size_t text_len) + { + unsigned char key[HMAC_BLOCK_SIZE]; + unsigned char k_ipad[HMAC_BLOCK_SIZE]; + unsigned char k_opad[HMAC_BLOCK_SIZE]; + int i; + git_SHA_CTX ctx; + + /* RFC 2104 2. (1) */ + memset(key, '\0', HMAC_BLOCK_SIZE); + if (HMAC_BLOCK_SIZE < key_len) { + git_SHA1_Init(&ctx); + git_SHA1_Update(&ctx, key_in, key_len); + git_SHA1_Final(key, &ctx); + } else { + memcpy(key, key_in, key_len); + } + + /* RFC 2104 2. (2) & (5) */ + for (i = 0; i < sizeof(key); i++) { + k_ipad[i] = key[i] ^ 0x36; + k_opad[i] = key[i] ^ 0x5c; + } + + /* RFC 2104 2. (3) & (4) */ + git_SHA1_Init(&ctx); + git_SHA1_Update(&ctx, k_ipad, sizeof(k_ipad)); + git_SHA1_Update(&ctx, text, text_len); + git_SHA1_Final(out, &ctx); + + /* RFC 2104 2. (6) & (7) */ + git_SHA1_Init(&ctx); + git_SHA1_Update(&ctx, k_opad, sizeof(k_opad)); + git_SHA1_Update(&ctx, out, 20); + git_SHA1_Final(out, &ctx); + } + + static char *prepare_push_cert_nonce(const char *path, unsigned long stamp) + { + struct strbuf buf = STRBUF_INIT; + unsigned char sha1[20]; + + strbuf_addf(&buf, "%s:%lu", path, stamp); + hmac_sha1(sha1, buf.buf, buf.len, cert_nonce_seed, strlen(cert_nonce_seed));; + strbuf_release(&buf); + + /* RFC 2104 5. HMAC-SHA1-80 */ + strbuf_addf(&buf, "%lu-%.*s", stamp, 20, sha1_to_hex(sha1)); + return strbuf_detach(&buf, NULL); + } + + /* + * NEEDSWORK: reuse find_commit_header() from jk/commit-author-parsing + * after dropping "_commit" from its name and possibly moving it out + * of commit.c + */ + static char *find_header(const char *msg, size_t len, const char *key) + { + int key_len = strlen(key); + const char *line = msg; + + while (line && line < msg + len) { + const char *eol = strchrnul(line, '\n'); + + if ((msg + len <= eol) || line == eol) + return NULL; + if (line + key_len < eol && + !memcmp(line, key, key_len) && line[key_len] == ' ') { + int offset = key_len + 1; + return xmemdupz(line + offset, (eol - line) - offset); + } + line = *eol ? eol + 1 : NULL; + } + return NULL; + } + + static const char *check_nonce(const char *buf, size_t len) + { + char *nonce = find_header(buf, len, "nonce"); + unsigned long stamp, ostamp; + char *bohmac, *expect = NULL; + const char *retval = NONCE_BAD; + + if (!nonce) { + retval = NONCE_MISSING; + goto leave; + } else if (!push_cert_nonce) { + retval = NONCE_UNSOLICITED; + goto leave; + } else if (!strcmp(push_cert_nonce, nonce)) { + retval = NONCE_OK; + goto leave; + } + + if (!stateless_rpc) { + /* returned nonce MUST match what we gave out earlier */ + retval = NONCE_BAD; + goto leave; + } + + /* + * In stateless mode, we may be receiving a nonce issued by + * another instance of the server that serving the same + * repository, and the timestamps may not match, but the + * nonce-seed and dir should match, so we can recompute and + * report the time slop. + * + * In addition, when a nonce issued by another instance has + * timestamp within receive.certnonceslop seconds, we pretend + * as if we issued that nonce when reporting to the hook. + */ + + /* nonce is concat(, "-", ) */ + if (*nonce <= '0' || '9' < *nonce) { + retval = NONCE_BAD; + goto leave; + } + stamp = strtoul(nonce, &bohmac, 10); + if (bohmac == nonce || bohmac[0] != '-') { + retval = NONCE_BAD; + goto leave; + } + + expect = prepare_push_cert_nonce(service_dir, stamp); + if (strcmp(expect, nonce)) { + /* Not what we would have signed earlier */ + retval = NONCE_BAD; + goto leave; + } + + /* + * By how many seconds is this nonce stale? Negative value + * would mean it was issued by another server with its clock + * skewed in the future. + */ + ostamp = strtoul(push_cert_nonce, NULL, 10); + nonce_stamp_slop = (long)ostamp - (long)stamp; + + if (nonce_stamp_slop_limit && + abs(nonce_stamp_slop) <= nonce_stamp_slop_limit) { + /* + * Pretend as if the received nonce (which passes the + * HMAC check, so it is not a forged by third-party) + * is what we issued. + */ + free((void *)push_cert_nonce); + push_cert_nonce = xstrdup(nonce); + retval = NONCE_OK; + } else { + retval = NONCE_SLOP; + } + + leave: + free(nonce); + free(expect); + return retval; + } + + static void prepare_push_cert_sha1(struct child_process *proc) + { + static int already_done; + struct argv_array env = ARGV_ARRAY_INIT; + + if (!push_cert.len) + return; + + if (!already_done) { + struct strbuf gpg_output = STRBUF_INIT; + struct strbuf gpg_status = STRBUF_INIT; + int bogs /* beginning_of_gpg_sig */; + + already_done = 1; + if (write_sha1_file(push_cert.buf, push_cert.len, "blob", push_cert_sha1)) + hashclr(push_cert_sha1); + + memset(&sigcheck, '\0', sizeof(sigcheck)); + sigcheck.result = 'N'; + + bogs = parse_signature(push_cert.buf, push_cert.len); + if (verify_signed_buffer(push_cert.buf, bogs, + push_cert.buf + bogs, push_cert.len - bogs, + &gpg_output, &gpg_status) < 0) { + ; /* error running gpg */ + } else { + sigcheck.payload = push_cert.buf; + sigcheck.gpg_output = gpg_output.buf; + sigcheck.gpg_status = gpg_status.buf; + parse_gpg_output(&sigcheck); + } + + strbuf_release(&gpg_output); + strbuf_release(&gpg_status); + nonce_status = check_nonce(push_cert.buf, bogs); + } + if (!is_null_sha1(push_cert_sha1)) { + argv_array_pushf(&env, "GIT_PUSH_CERT=%s", sha1_to_hex(push_cert_sha1)); + argv_array_pushf(&env, "GIT_PUSH_CERT_SIGNER=%s", + sigcheck.signer ? sigcheck.signer : ""); + argv_array_pushf(&env, "GIT_PUSH_CERT_KEY=%s", + sigcheck.key ? sigcheck.key : ""); + argv_array_pushf(&env, "GIT_PUSH_CERT_STATUS=%c", sigcheck.result); + if (push_cert_nonce) { + argv_array_pushf(&env, "GIT_PUSH_CERT_NONCE=%s", push_cert_nonce); + argv_array_pushf(&env, "GIT_PUSH_CERT_NONCE_STATUS=%s", nonce_status); + if (nonce_status == NONCE_SLOP) + argv_array_pushf(&env, "GIT_PUSH_CERT_NONCE_SLOP=%ld", + nonce_stamp_slop); + } + proc->env = env.argv; + } + } + typedef int (*feed_fn)(void *, const char **, size_t *); static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_state) { - struct child_process proc; + struct child_process proc = CHILD_PROCESS_INIT; struct async muxer; const char *argv[2]; int code; @@@ -267,10 -516,13 +517,12 @@@ argv[1] = NULL; - memset(&proc, 0, sizeof(proc)); proc.argv = argv; proc.in = -1; proc.stdout_to_stderr = 1; + prepare_push_cert_sha1(&proc); + if (use_sideband) { memset(&muxer, 0, sizeof(muxer)); muxer.proc = copy_to_sideband; @@@ -288,8 -540,6 +540,8 @@@ return code; } + sigchain_push(SIGPIPE, SIG_IGN); + while (1) { const char *buf; size_t n; @@@ -301,9 -551,6 +553,9 @@@ close(proc.in); if (use_sideband) finish_async(&muxer); + + sigchain_pop(SIGPIPE); + return finish_command(&proc); } @@@ -355,7 -602,7 +607,7 @@@ static int run_receive_hook(struct comm static int run_update_hook(struct command *cmd) { const char *argv[5]; - struct child_process proc; + struct child_process proc = CHILD_PROCESS_INIT; int code; argv[0] = find_hook("update"); @@@ -367,6 -614,7 +619,6 @@@ argv[3] = sha1_to_hex(cmd->new_sha1); argv[4] = NULL; - memset(&proc, 0, sizeof(proc)); proc.no_stdin = 1; proc.stdout_to_stderr = 1; proc.err = use_sideband ? -1 : 0; @@@ -479,6 -727,7 +731,6 @@@ static const char *update(struct comman const char *namespaced_name; unsigned char *old_sha1 = cmd->old_sha1; unsigned char *new_sha1 = cmd->new_sha1; - struct ref_lock *lock; /* only refs/... are allowed */ if (!starts_with(name, "refs/") || check_refname_format(name + 5, 0)) { @@@ -579,27 -828,19 +831,27 @@@ return NULL; /* good */ } else { + struct strbuf err = STRBUF_INIT; + struct ref_transaction *transaction; + if (shallow_update && si->shallow_ref[cmd->index] && update_shallow_ref(cmd, si)) return "shallow error"; - lock = lock_any_ref_for_update(namespaced_name, old_sha1, - 0, NULL); - if (!lock) { - rp_error("failed to lock %s", name); - return "failed to lock"; - } - if (write_ref_sha1(lock, new_sha1, "push")) { - return "failed to write"; /* error() already called */ + transaction = ref_transaction_begin(&err); + if (!transaction || + ref_transaction_update(transaction, namespaced_name, + new_sha1, old_sha1, 0, 1, &err) || + ref_transaction_commit(transaction, "push", &err)) { + ref_transaction_free(transaction); + + rp_error("%s", err.buf); + strbuf_release(&err); + return "failed to update ref"; } + + ref_transaction_free(transaction); + strbuf_release(&err); return NULL; /* good */ } } @@@ -609,7 -850,7 +861,7 @@@ static void run_update_post_hook(struc struct command *cmd; int argc; const char **argv; - struct child_process proc; + struct child_process proc = CHILD_PROCESS_INIT; char *hook; hook = find_hook("post-update"); @@@ -632,6 -873,7 +884,6 @@@ } argv[argc] = NULL; - memset(&proc, 0, sizeof(proc)); proc.no_stdin = 1; proc.stdout_to_stderr = 1; proc.err = use_sideband ? -1 : 0; @@@ -841,40 -1083,79 +1093,79 @@@ static void execute_commands(struct com "the reported refs above"); } + static struct command **queue_command(struct command **tail, + const char *line, + int linelen) + { + unsigned char old_sha1[20], new_sha1[20]; + struct command *cmd; + const char *refname; + int reflen; + + if (linelen < 83 || + line[40] != ' ' || + line[81] != ' ' || + get_sha1_hex(line, old_sha1) || + get_sha1_hex(line + 41, new_sha1)) + die("protocol error: expected old/new/ref, got '%s'", line); + + refname = line + 82; + reflen = linelen - 82; + cmd = xcalloc(1, sizeof(struct command) + reflen + 1); + hashcpy(cmd->old_sha1, old_sha1); + hashcpy(cmd->new_sha1, new_sha1); + memcpy(cmd->ref_name, refname, reflen); + cmd->ref_name[reflen] = '\0'; + *tail = cmd; + return &cmd->next; + } + + static void queue_commands_from_cert(struct command **tail, + struct strbuf *push_cert) + { + const char *boc, *eoc; + + if (*tail) + die("protocol error: got both push certificate and unsigned commands"); + + boc = strstr(push_cert->buf, "\n\n"); + if (!boc) + die("malformed push certificate %.*s", 100, push_cert->buf); + else + boc += 2; + eoc = push_cert->buf + parse_signature(push_cert->buf, push_cert->len); + + while (boc < eoc) { + const char *eol = memchr(boc, '\n', eoc - boc); + tail = queue_command(tail, boc, eol ? eol - boc : eoc - eol); + boc = eol ? eol + 1 : eoc; + } + } + static struct command *read_head_info(struct sha1_array *shallow) { struct command *commands = NULL; struct command **p = &commands; for (;;) { char *line; - unsigned char old_sha1[20], new_sha1[20]; - struct command *cmd; - char *refname; - int len, reflen; + int len, linelen; line = packet_read_line(0, &len); if (!line) break; if (len == 48 && starts_with(line, "shallow ")) { - if (get_sha1_hex(line + 8, old_sha1)) - die("protocol error: expected shallow sha, got '%s'", line + 8); - sha1_array_append(shallow, old_sha1); + unsigned char sha1[20]; + if (get_sha1_hex(line + 8, sha1)) + die("protocol error: expected shallow sha, got '%s'", + line + 8); + sha1_array_append(shallow, sha1); continue; } - if (len < 83 || - line[40] != ' ' || - line[81] != ' ' || - get_sha1_hex(line, old_sha1) || - get_sha1_hex(line + 41, new_sha1)) - die("protocol error: expected old/new/ref, got '%s'", - line); - - refname = line + 82; - reflen = strlen(refname); - if (reflen + 82 < len) { - const char *feature_list = refname + reflen + 1; + linelen = strlen(line); + if (linelen < len) { + const char *feature_list = line + linelen + 1; if (parse_feature_request(feature_list, "report-status")) report_status = 1; if (parse_feature_request(feature_list, "side-band-64k")) @@@ -882,13 -1163,34 +1173,34 @@@ if (parse_feature_request(feature_list, "quiet")) quiet = 1; } - cmd = xcalloc(1, sizeof(struct command) + len - 80); - hashcpy(cmd->old_sha1, old_sha1); - hashcpy(cmd->new_sha1, new_sha1); - memcpy(cmd->ref_name, line + 82, len - 81); - *p = cmd; - p = &cmd->next; + + if (!strcmp(line, "push-cert")) { + int true_flush = 0; + char certbuf[1024]; + + for (;;) { + len = packet_read(0, NULL, NULL, + certbuf, sizeof(certbuf), 0); + if (!len) { + true_flush = 1; + break; + } + if (!strcmp(certbuf, "push-cert-end\n")) + break; /* end of cert */ + strbuf_addstr(&push_cert, certbuf); + } + + if (true_flush) + break; + continue; + } + + p = queue_command(p, line, linelen); } + + if (push_cert.len) + queue_commands_from_cert(p, &push_cert); + return commands; } @@@ -921,7 -1223,7 +1233,7 @@@ static const char *unpack(int err_fd, s const char *hdr_err; int status; char hdr_arg[38]; - struct child_process child; + struct child_process child = CHILD_PROCESS_INIT; int fsck_objects = (receive_fsck_objects >= 0 ? receive_fsck_objects : transfer_fsck_objects >= 0 @@@ -943,6 -1245,7 +1255,6 @@@ argv_array_pushl(&av, "--shallow-file", alt_shallow_file, NULL); } - memset(&child, 0, sizeof(child)); if (ntohl(hdr.hdr_entries) < unpack_limit) { argv_array_pushl(&av, "unpack-objects", hdr_arg, NULL); if (quiet) @@@ -1129,9 -1432,7 +1441,7 @@@ static int delete_only(struct command * int cmd_receive_pack(int argc, const char **argv, const char *prefix) { int advertise_refs = 0; - int stateless_rpc = 0; int i; - const char *dir = NULL; struct command *commands; struct sha1_array shallow = SHA1_ARRAY_INIT; struct sha1_array ref = SHA1_ARRAY_INIT; @@@ -1164,19 -1465,21 +1474,21 @@@ usage(receive_pack_usage); } - if (dir) + if (service_dir) usage(receive_pack_usage); - dir = arg; + service_dir = arg; } - if (!dir) + if (!service_dir) usage(receive_pack_usage); setup_path(); - if (!enter_repo(dir, 0)) - die("'%s' does not appear to be a git repository", dir); + if (!enter_repo(service_dir, 0)) + die("'%s' does not appear to be a git repository", service_dir); git_config(receive_pack_config, NULL); + if (cert_nonce_seed) + push_cert_nonce = prepare_push_cert_nonce(service_dir, time(NULL)); if (0 <= transfer_unpack_limit) unpack_limit = transfer_unpack_limit; @@@ -1221,5 -1524,6 +1533,6 @@@ packet_flush(1); sha1_array_clear(&shallow); sha1_array_clear(&ref); + free((void *)push_cert_nonce); return 0; } diff --combined builtin/send-pack.c index 4b1bc0fef7,ca28d8d248..b564a77845 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@@ -110,7 -110,6 +110,7 @@@ int cmd_send_pack(int argc, const char int flags; unsigned int reject_reasons; int progress = -1; + int from_stdin = 0; struct push_cas_option cas = {0}; argv++; @@@ -154,6 -153,10 +154,10 @@@ args.verbose = 1; continue; } + if (!strcmp(arg, "--signed")) { + args.push_cert = 1; + continue; + } if (!strcmp(arg, "--progress")) { progress = 1; continue; @@@ -170,10 -173,6 +174,10 @@@ args.stateless_rpc = 1; continue; } + if (!strcmp(arg, "--stdin")) { + from_stdin = 1; + continue; + } if (!strcmp(arg, "--helper-status")) { helper_status = 1; continue; @@@ -206,28 -205,6 +210,28 @@@ } if (!dest) usage(send_pack_usage); + + if (from_stdin) { + struct argv_array all_refspecs = ARGV_ARRAY_INIT; + + for (i = 0; i < nr_refspecs; i++) + argv_array_push(&all_refspecs, refspecs[i]); + + if (args.stateless_rpc) { + const char *buf; + while ((buf = packet_read_line(0, NULL))) + argv_array_push(&all_refspecs, buf); + } else { + struct strbuf line = STRBUF_INIT; + while (strbuf_getline(&line, stdin, '\n') != EOF) + argv_array_push(&all_refspecs, line.buf); + strbuf_release(&line); + } + + refspecs = all_refspecs.argv; + nr_refspecs = all_refspecs.argc; + } + /* * --all and --mirror are incompatible; neither makes sense * with any refspecs. diff --combined commit.c index 9c4439fed6,01cdad2626..19cf8f9c67 --- a/commit.c +++ b/commit.c @@@ -584,19 -584,25 +584,19 @@@ define_commit_slab(author_date_slab, un static void record_author_date(struct author_date_slab *author_date, struct commit *commit) { - const char *buf, *line_end, *ident_line; const char *buffer = get_commit_buffer(commit, NULL); struct ident_split ident; + const char *ident_line; + size_t ident_len; char *date_end; unsigned long date; - for (buf = buffer; buf; buf = line_end + 1) { - line_end = strchrnul(buf, '\n'); - if (!skip_prefix(buf, "author ", &ident_line)) { - if (!line_end[0] || line_end[1] == '\n') - return; /* end of header */ - continue; - } - if (split_ident_line(&ident, - ident_line, line_end - ident_line) || - !ident.date_begin || !ident.date_end) - goto fail_exit; /* malformed "author" line */ - break; - } + ident_line = find_commit_header(buffer, "author", &ident_len); + if (!ident_line) + goto fail_exit; /* no author line */ + if (split_ident_line(&ident, ident_line, ident_len) || + !ident.date_begin || !ident.date_end) + goto fail_exit; /* malformed "author" line */ date = strtoul(ident.date_begin, &date_end, 10); if (date_end != ident.date_end) @@@ -1214,43 -1220,7 +1214,7 @@@ free_return free(buf); } - static struct { - char result; - const char *check; - } sigcheck_gpg_status[] = { - { 'G', "\n[GNUPG:] GOODSIG " }, - { 'B', "\n[GNUPG:] BADSIG " }, - { 'U', "\n[GNUPG:] TRUST_NEVER" }, - { 'U', "\n[GNUPG:] TRUST_UNDEFINED" }, - }; - - static void parse_gpg_output(struct signature_check *sigc) - { - const char *buf = sigc->gpg_status; - int i; - - /* Iterate over all search strings */ - for (i = 0; i < ARRAY_SIZE(sigcheck_gpg_status); i++) { - const char *found, *next; - - if (!skip_prefix(buf, sigcheck_gpg_status[i].check + 1, &found)) { - found = strstr(buf, sigcheck_gpg_status[i].check); - if (!found) - continue; - found += strlen(sigcheck_gpg_status[i].check); - } - sigc->result = sigcheck_gpg_status[i].result; - /* The trust messages are not followed by key/signer information */ - if (sigc->result != 'U') { - sigc->key = xmemdupz(found, 16); - found += 17; - next = strchrnul(found, '\n'); - sigc->signer = xmemdupz(found, next - found); - } - } - } - -void check_commit_signature(const struct commit* commit, struct signature_check *sigc) +void check_commit_signature(const struct commit *commit, struct signature_check *sigc) { struct strbuf payload = STRBUF_INIT; struct strbuf signature = STRBUF_INIT; @@@ -1654,25 -1624,3 +1618,25 @@@ void print_commit_list(struct commit_li printf(format, sha1_to_hex(list->item->object.sha1)); } } + +const char *find_commit_header(const char *msg, const char *key, size_t *out_len) +{ + int key_len = strlen(key); + const char *line = msg; + + while (line) { + const char *eol = strchrnul(line, '\n'); + + if (line == eol) + return NULL; + + if (eol - line > key_len && + !strncmp(line, key, key_len) && + line[key_len] == ' ') { + *out_len = eol - line - key_len - 1; + return line + key_len + 1; + } + line = *eol ? eol + 1 : NULL; + } + return NULL; +} diff --combined gpg-interface.c index 1ef73fb7df,0dd11eadb2..68b0c814f7 --- a/gpg-interface.c +++ b/gpg-interface.c @@@ -7,6 -7,9 +7,9 @@@ static char *configured_signing_key; static const char *gpg_program = "gpg"; + #define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----" + #define PGP_MESSAGE "-----BEGIN PGP MESSAGE-----" + void signature_check_clear(struct signature_check *sigc) { free(sigc->payload); @@@ -21,6 -24,60 +24,60 @@@ sigc->key = NULL; } + static struct { + char result; + const char *check; + } sigcheck_gpg_status[] = { + { 'G', "\n[GNUPG:] GOODSIG " }, + { 'B', "\n[GNUPG:] BADSIG " }, + { 'U', "\n[GNUPG:] TRUST_NEVER" }, + { 'U', "\n[GNUPG:] TRUST_UNDEFINED" }, + }; + + void parse_gpg_output(struct signature_check *sigc) + { + const char *buf = sigc->gpg_status; + int i; + + /* Iterate over all search strings */ + for (i = 0; i < ARRAY_SIZE(sigcheck_gpg_status); i++) { + const char *found, *next; + + if (!skip_prefix(buf, sigcheck_gpg_status[i].check + 1, &found)) { + found = strstr(buf, sigcheck_gpg_status[i].check); + if (!found) + continue; + found += strlen(sigcheck_gpg_status[i].check); + } + sigc->result = sigcheck_gpg_status[i].result; + /* The trust messages are not followed by key/signer information */ + if (sigc->result != 'U') { + sigc->key = xmemdupz(found, 16); + found += 17; + next = strchrnul(found, '\n'); + sigc->signer = xmemdupz(found, next - found); + } + } + } + + /* + * Look at GPG signed content (e.g. a signed tag object), whose + * payload is followed by a detached signature on it. Return the + * offset where the embedded detached signature begins, or the end of + * the data when there is no such signature. + */ + size_t parse_signature(const char *buf, unsigned long size) + { + char *eol; + size_t len = 0; + while (len < size && !starts_with(buf + len, PGP_SIGNATURE) && + !starts_with(buf + len, PGP_MESSAGE)) { + eol = memchr(buf + len, '\n', size - len); + len += eol ? eol - (buf + len) + 1 : size - len; + } + return len; + } + void set_signing_key(const char *key) { free(configured_signing_key); @@@ -55,11 -112,12 +112,11 @@@ const char *get_signing_key(void */ int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key) { - struct child_process gpg; + struct child_process gpg = CHILD_PROCESS_INIT; const char *args[4]; ssize_t len; size_t i, j, bottom; - memset(&gpg, 0, sizeof(gpg)); gpg.argv = args; gpg.in = -1; gpg.out = -1; @@@ -115,7 -173,7 +172,7 @@@ int verify_signed_buffer(const char *pa const char *signature, size_t signature_size, struct strbuf *gpg_output, struct strbuf *gpg_status) { - struct child_process gpg; + struct child_process gpg = CHILD_PROCESS_INIT; const char *args_gpg[] = {NULL, "--status-fd=1", "--verify", "FILE", "-", NULL}; char path[PATH_MAX]; int fd, ret; @@@ -132,6 -190,7 +189,6 @@@ path, strerror(errno)); close(fd); - memset(&gpg, 0, sizeof(gpg)); gpg.argv = args_gpg; gpg.in = -1; gpg.out = -1; diff --combined remote-curl.c index cd626d15e5,1ea4e95d9a..dd63bc27ab --- a/remote-curl.c +++ b/remote-curl.c @@@ -25,7 -25,8 +25,8 @@@ struct options update_shallow : 1, followtags : 1, dry_run : 1, - thin : 1; + thin : 1, + push_cert : 1; }; static struct options options; static struct string_list cas_options = STRING_LIST_INIT_DUP; @@@ -106,6 -107,14 +107,14 @@@ static int set_option(const char *name else return -1; return 0; + } else if (!strcmp(name, "pushcert")) { + if (!strcmp(value, "true")) + options.push_cert = 1; + else if (!strcmp(value, "false")) + options.push_cert = 0; + else + return -1; + return 0; } else { return 1 /* unsupported */; } @@@ -221,7 -230,7 +230,7 @@@ static int show_http_message(struct str return 0; } -static struct discovery* discover_refs(const char *service, int for_push) +static struct discovery *discover_refs(const char *service, int for_push) { struct strbuf exp = STRBUF_INIT; struct strbuf type = STRBUF_INIT; @@@ -623,9 -632,10 +632,9 @@@ static int rpc_service(struct rpc_stat const char *svc = rpc->service_name; struct strbuf buf = STRBUF_INIT; struct strbuf *preamble = rpc->stdin_preamble; - struct child_process client; + struct child_process client = CHILD_PROCESS_INIT; int err = 0; - memset(&client, 0, sizeof(client)); client.in = -1; client.out = -1; client.git_cmd = 1; @@@ -862,7 -872,6 +871,7 @@@ static int push_git(struct discovery *h int i, err; struct argv_array args; struct string_list_item *cas_option; + struct strbuf preamble = STRBUF_INIT; argv_array_init(&args); argv_array_pushl(&args, "send-pack", "--stateless-rpc", "--helper-status", @@@ -872,6 -881,8 +881,8 @@@ argv_array_push(&args, "--thin"); if (options.dry_run) argv_array_push(&args, "--dry-run"); + if (options.push_cert) + argv_array_push(&args, "--signed"); if (options.verbosity == 0) argv_array_push(&args, "--quiet"); else if (options.verbosity > 1) @@@ -880,22 -891,17 +891,22 @@@ for_each_string_list_item(cas_option, &cas_options) argv_array_push(&args, cas_option->string); argv_array_push(&args, url.buf); + + argv_array_push(&args, "--stdin"); for (i = 0; i < nr_spec; i++) - argv_array_push(&args, specs[i]); + packet_buf_write(&preamble, "%s\n", specs[i]); + packet_buf_flush(&preamble); memset(&rpc, 0, sizeof(rpc)); rpc.service_name = "git-receive-pack", rpc.argv = args.argv; + rpc.stdin_preamble = &preamble; err = rpc_service(&rpc, heads); if (rpc.result.len) write_or_die(1, rpc.result.buf, rpc.result.len); strbuf_release(&rpc.result); + strbuf_release(&preamble); argv_array_clear(&args); return err; } diff --combined send-pack.c index 8b4cbf049c,7ad1a5968b..949cb61aa0 --- a/send-pack.c +++ b/send-pack.c @@@ -11,6 -11,7 +11,7 @@@ #include "transport.h" #include "version.h" #include "sha1-array.h" + #include "gpg-interface.h" static int feed_object(const unsigned char *sha1, int fd, int negative) { @@@ -47,7 -48,7 +48,7 @@@ static int pack_objects(int fd, struct NULL, NULL, }; - struct child_process po; + struct child_process po = CHILD_PROCESS_INIT; int i; i = 4; @@@ -59,6 -60,7 +60,6 @@@ argv[i++] = "-q"; if (args->progress) argv[i++] = "--progress"; - memset(&po, 0, sizeof(po)); po.argv = argv; po.in = -1; po.out = args->stateless_rpc ? -1 : fd; @@@ -189,6 -191,94 +190,94 @@@ static void advertise_shallow_grafts_bu for_each_commit_graft(advertise_shallow_grafts_cb, sb); } + static int ref_update_to_be_sent(const struct ref *ref, const struct send_pack_args *args) + { + if (!ref->peer_ref && !args->send_mirror) + return 0; + + /* Check for statuses set by set_ref_status_for_push() */ + switch (ref->status) { + case REF_STATUS_REJECT_NONFASTFORWARD: + case REF_STATUS_REJECT_ALREADY_EXISTS: + case REF_STATUS_REJECT_FETCH_FIRST: + case REF_STATUS_REJECT_NEEDS_FORCE: + case REF_STATUS_REJECT_STALE: + case REF_STATUS_REJECT_NODELETE: + case REF_STATUS_UPTODATE: + return 0; + default: + return 1; + } + } + + /* + * the beginning of the next line, or the end of buffer. + * + * NEEDSWORK: perhaps move this to git-compat-util.h or somewhere and + * convert many similar uses found by "git grep -A4 memchr". + */ + static const char *next_line(const char *line, size_t len) + { + const char *nl = memchr(line, '\n', len); + if (!nl) + return line + len; /* incomplete line */ + return nl + 1; + } + + static int generate_push_cert(struct strbuf *req_buf, + const struct ref *remote_refs, + struct send_pack_args *args, + const char *cap_string, + const char *push_cert_nonce) + { + const struct ref *ref; - char stamp[60]; + char *signing_key = xstrdup(get_signing_key()); + const char *cp, *np; + struct strbuf cert = STRBUF_INIT; + int update_seen = 0; + - datestamp(stamp, sizeof(stamp)); + strbuf_addf(&cert, "certificate version 0.1\n"); - strbuf_addf(&cert, "pusher %s %s\n", signing_key, stamp); ++ strbuf_addf(&cert, "pusher %s ", signing_key); ++ datestamp(&cert); ++ strbuf_addch(&cert, '\n'); + if (args->url && *args->url) { + char *anon_url = transport_anonymize_url(args->url); + strbuf_addf(&cert, "pushee %s\n", anon_url); + free(anon_url); + } + if (push_cert_nonce[0]) + strbuf_addf(&cert, "nonce %s\n", push_cert_nonce); + strbuf_addstr(&cert, "\n"); + + for (ref = remote_refs; ref; ref = ref->next) { + if (!ref_update_to_be_sent(ref, args)) + continue; + update_seen = 1; + strbuf_addf(&cert, "%s %s %s\n", + sha1_to_hex(ref->old_sha1), + sha1_to_hex(ref->new_sha1), + ref->name); + } + if (!update_seen) + goto free_return; + + if (sign_buffer(&cert, &cert, signing_key)) + die(_("failed to sign the push certificate")); + + packet_buf_write(req_buf, "push-cert%c%s", 0, cap_string); + for (cp = cert.buf; cp < cert.buf + cert.len; cp = np) { + np = next_line(cp, cert.buf + cert.len - cp); + packet_buf_write(req_buf, + "%.*s", (int)(np - cp), cp); + } + packet_buf_write(req_buf, "push-cert-end\n"); + + free_return: + free(signing_key); + strbuf_release(&cert); + return update_seen; + } + int send_pack(struct send_pack_args *args, int fd[], struct child_process *conn, struct ref *remote_refs, @@@ -197,8 -287,9 +286,9 @@@ int in = fd[0]; int out = fd[1]; struct strbuf req_buf = STRBUF_INIT; + struct strbuf cap_buf = STRBUF_INIT; struct ref *ref; - int new_refs; + int need_pack_data = 0; int allow_deleting_refs = 0; int status_report = 0; int use_sideband = 0; @@@ -207,6 -298,7 +297,7 @@@ unsigned cmds_sent = 0; int ret; struct async demux; + const char *push_cert_nonce = NULL; /* Does the other end support the reporting? */ if (server_supports("report-status")) @@@ -223,6 -315,14 +314,14 @@@ agent_supported = 1; if (server_supports("no-thin")) args->use_thin_pack = 0; + if (args->push_cert) { + int len; + + push_cert_nonce = server_feature_value("push-cert", &len); + if (!push_cert_nonce) + die(_("the receiving end does not support --signed push")); + push_cert_nonce = xmemdupz(push_cert_nonce, len); + } if (!remote_refs) { fprintf(stderr, "No refs in common and none specified; doing nothing.\n" @@@ -230,64 -330,71 +329,71 @@@ return 0; } + if (status_report) + strbuf_addstr(&cap_buf, " report-status"); + if (use_sideband) + strbuf_addstr(&cap_buf, " side-band-64k"); + if (quiet_supported && (args->quiet || !args->progress)) + strbuf_addstr(&cap_buf, " quiet"); + if (agent_supported) + strbuf_addf(&cap_buf, " agent=%s", git_user_agent_sanitized()); + + /* + * NEEDSWORK: why does delete-refs have to be so specific to + * send-pack machinery that set_ref_status_for_push() cannot + * set this bit for us??? + */ + for (ref = remote_refs; ref; ref = ref->next) + if (ref->deletion && !allow_deleting_refs) + ref->status = REF_STATUS_REJECT_NODELETE; + if (!args->dry_run) advertise_shallow_grafts_buf(&req_buf); + if (!args->dry_run && args->push_cert) + cmds_sent = generate_push_cert(&req_buf, remote_refs, args, + cap_buf.buf, push_cert_nonce); + /* - * Finally, tell the other end! + * Clear the status for each ref and see if we need to send + * the pack data. */ - new_refs = 0; for (ref = remote_refs; ref; ref = ref->next) { - if (!ref->peer_ref && !args->send_mirror) + if (!ref_update_to_be_sent(ref, args)) continue; - /* Check for statuses set by set_ref_status_for_push() */ - switch (ref->status) { - case REF_STATUS_REJECT_NONFASTFORWARD: - case REF_STATUS_REJECT_ALREADY_EXISTS: - case REF_STATUS_REJECT_FETCH_FIRST: - case REF_STATUS_REJECT_NEEDS_FORCE: - case REF_STATUS_REJECT_STALE: - case REF_STATUS_UPTODATE: - continue; - default: - ; /* do nothing */ - } + if (!ref->deletion) + need_pack_data = 1; - if (ref->deletion && !allow_deleting_refs) { - ref->status = REF_STATUS_REJECT_NODELETE; + if (args->dry_run || !status_report) + ref->status = REF_STATUS_OK; + else + ref->status = REF_STATUS_EXPECTING_REPORT; + } + + /* + * Finally, tell the other end! + */ + for (ref = remote_refs; ref; ref = ref->next) { + char *old_hex, *new_hex; + + if (args->dry_run || args->push_cert) continue; - } - if (!ref->deletion) - new_refs++; + if (!ref_update_to_be_sent(ref, args)) + continue; - if (args->dry_run) { - ref->status = REF_STATUS_OK; + old_hex = sha1_to_hex(ref->old_sha1); + new_hex = sha1_to_hex(ref->new_sha1); + if (!cmds_sent) { + packet_buf_write(&req_buf, + "%s %s %s%c%s", + old_hex, new_hex, ref->name, 0, + cap_buf.buf); + cmds_sent = 1; } else { - char *old_hex = sha1_to_hex(ref->old_sha1); - char *new_hex = sha1_to_hex(ref->new_sha1); - int quiet = quiet_supported && (args->quiet || !args->progress); - - if (!cmds_sent && (status_report || use_sideband || - quiet || agent_supported)) { - packet_buf_write(&req_buf, - "%s %s %s%c%s%s%s%s%s", - old_hex, new_hex, ref->name, 0, - status_report ? " report-status" : "", - use_sideband ? " side-band-64k" : "", - quiet ? " quiet" : "", - agent_supported ? " agent=" : "", - agent_supported ? git_user_agent_sanitized() : "" - ); - } - else - packet_buf_write(&req_buf, "%s %s %s", - old_hex, new_hex, ref->name); - ref->status = status_report ? - REF_STATUS_EXPECTING_REPORT : - REF_STATUS_OK; - cmds_sent++; + packet_buf_write(&req_buf, "%s %s %s", + old_hex, new_hex, ref->name); } } @@@ -301,6 -408,7 +407,7 @@@ packet_flush(out); } strbuf_release(&req_buf); + strbuf_release(&cap_buf); if (use_sideband && cmds_sent) { memset(&demux, 0, sizeof(demux)); @@@ -312,7 -420,7 +419,7 @@@ in = demux.out; } - if (new_refs && cmds_sent) { + if (need_pack_data && cmds_sent) { if (pack_objects(out, remote_refs, extra_have, args) < 0) { for (ref = remote_refs; ref; ref = ref->next) ref->status = REF_STATUS_NONE; diff --combined t/t5541-http-push-smart.sh index db1998873c,ffb3af4498..d2c681ebfd --- a/t/t5541-http-push-smart.sh +++ b/t/t5541-http-push-smart.sh @@@ -12,6 -12,7 +12,7 @@@ if test -n "$NO_CURL"; the fi ROOT_PATH="$PWD" + . "$TEST_DIRECTORY"/lib-gpg.sh . "$TEST_DIRECTORY"/lib-httpd.sh . "$TEST_DIRECTORY"/lib-terminal.sh start_httpd @@@ -323,20 -324,45 +324,60 @@@ test_expect_success 'push into half-aut test_cmp expect actual ' +run_with_limited_cmdline () { + (ulimit -s 128 && "$@") +} + +test_lazy_prereq CMDLINE_LIMIT 'run_with_limited_cmdline true' + +test_expect_success CMDLINE_LIMIT 'push 2000 tags over http' ' + sha1=$(git rev-parse HEAD) && + test_seq 2000 | + sort | + sed "s|.*|$sha1 refs/tags/really-long-tag-name-&|" \ + >.git/packed-refs && + run_with_limited_cmdline git push --mirror +' + + test_expect_success GPG 'push with post-receive to inspect certificate' ' + ( + cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git && + mkdir -p hooks && + write_script hooks/post-receive <<-\EOF && + # discard the update list + cat >/dev/null + # record the push certificate + if test -n "${GIT_PUSH_CERT-}" + then + git cat-file blob $GIT_PUSH_CERT >../push-cert + fi && + cat >../push-cert-status < + KEY=13B6F51ECDDE430D + STATUS=G + NONCE_STATUS=OK + EOF + sed -n -e "s/^nonce /NONCE=/p" -e "/^$/q" push-cert + ) >expect && + test_cmp expect "$HTTPD_DOCUMENT_ROOT_PATH/push-cert-status" + ' + stop_httpd test_done diff --combined t/test-lib.sh index 82095e34ee,d5939b70f3..0f4a67bfc6 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@@ -813,7 -813,8 +813,8 @@@ rm -fr "$TRASH_DIRECTORY" || } HOME="$TRASH_DIRECTORY" - export HOME + GNUPGHOME="$HOME/gnupg-home-not-used" + export HOME GNUPGHOME if test -z "$TEST_NO_CREATE_REPO" then @@@ -870,7 -871,7 +871,7 @@@ case $(uname -s) i # backslashes in pathspec are converted to '/' # exec does not inherit the PID test_set_prereq MINGW - test_set_prereq NOT_CYGWIN + test_set_prereq NATIVE_CRLF test_set_prereq SED_STRIPS_CR test_set_prereq GREP_STRIPS_CR GIT_TEST_CMP=mingw_test_cmp @@@ -878,6 -879,7 +879,6 @@@ *CYGWIN*) test_set_prereq POSIXPERM test_set_prereq EXECKEEPSPID - test_set_prereq NOT_MINGW test_set_prereq CYGWIN test_set_prereq SED_STRIPS_CR test_set_prereq GREP_STRIPS_CR @@@ -886,6 -888,8 +887,6 @@@ test_set_prereq POSIXPERM test_set_prereq BSLASHPSPEC test_set_prereq EXECKEEPSPID - test_set_prereq NOT_MINGW - test_set_prereq NOT_CYGWIN ;; esac diff --combined transport-helper.c index 080a7a6ae2,4b1a26143a..2b24d51a24 --- a/transport-helper.c +++ b/transport-helper.c @@@ -118,8 -118,7 +118,8 @@@ static struct child_process *get_helper if (data->helper) return data->helper; - helper = xcalloc(1, sizeof(*helper)); + helper = xmalloc(sizeof(*helper)); + child_process_init(helper); helper->in = -1; helper->out = -1; helper->err = 0; @@@ -260,7 -259,8 +260,8 @@@ static const char *unsupported_options[ static const char *boolean_options[] = { TRANS_OPT_THIN, TRANS_OPT_KEEP, - TRANS_OPT_FOLLOWTAGS + TRANS_OPT_FOLLOWTAGS, + TRANS_OPT_PUSH_CERT }; static int set_helper_option(struct transport *transport, @@@ -396,7 -396,7 +397,7 @@@ static int get_importer(struct transpor struct child_process *helper = get_helper(transport); struct helper_data *data = transport->data; int cat_blob_fd, code; - memset(fastimport, 0, sizeof(*fastimport)); + child_process_init(fastimport); fastimport->in = helper->out; argv_array_push(&fastimport->args, "fast-import"); argv_array_push(&fastimport->args, debug ? "--stats" : "--quiet"); @@@ -836,6 -836,9 +837,9 @@@ static int push_refs_with_push(struct t if (flags & TRANSPORT_PUSH_DRY_RUN) { if (set_helper_option(transport, "dry-run", "true") != 0) die("helper %s does not support dry-run", data->name); + } else if (flags & TRANSPORT_PUSH_CERT) { + if (set_helper_option(transport, TRANS_OPT_PUSH_CERT, "true") != 0) + die("helper %s does not support --signed", data->name); } strbuf_addch(&buf, '\n'); @@@ -860,6 -863,9 +864,9 @@@ static int push_refs_with_export(struc if (flags & TRANSPORT_PUSH_DRY_RUN) { if (set_helper_option(transport, "dry-run", "true") != 0) die("helper %s does not support dry-run", data->name); + } else if (flags & TRANSPORT_PUSH_CERT) { + if (set_helper_option(transport, TRANS_OPT_PUSH_CERT, "true") != 0) + die("helper %s does not support dry-run", data->name); } if (flags & TRANSPORT_PUSH_FORCE) { diff --combined transport.c index 7388bb87da,1df13753a6..055d2a27d9 --- a/transport.c +++ b/transport.c @@@ -201,7 -201,7 +201,7 @@@ static struct ref *get_refs_via_rsync(s { struct strbuf buf = STRBUF_INIT, temp_dir = STRBUF_INIT; struct ref dummy = {NULL}, *tail = &dummy; - struct child_process rsync; + struct child_process rsync = CHILD_PROCESS_INIT; const char *args[5]; int temp_dir_len; @@@ -218,6 -218,7 +218,6 @@@ strbuf_addstr(&buf, rsync_url(transport->url)); strbuf_addstr(&buf, "/refs"); - memset(&rsync, 0, sizeof(rsync)); rsync.argv = args; rsync.stdout_to_stderr = 1; args[0] = "rsync"; @@@ -262,8 -263,9 +262,8 @@@ static int fetch_objs_via_rsync(struct transport *transport, int nr_objs, struct ref **to_fetch) { - struct child_process rsync; + struct child_process rsync = CHILD_PROCESS_INIT; - memset(&rsync, 0, sizeof(rsync)); rsync.stdout_to_stderr = 1; argv_array_push(&rsync.args, "rsync"); argv_array_push(&rsync.args, (transport->verbose > 1) ? "-rv" : "-r"); @@@ -325,7 -327,7 +325,7 @@@ static int rsync_transport_push(struct { struct strbuf buf = STRBUF_INIT, temp_dir = STRBUF_INIT; int result = 0, i; - struct child_process rsync; + struct child_process rsync = CHILD_PROCESS_INIT; const char *args[10]; if (flags & TRANSPORT_PUSH_MIRROR) @@@ -336,6 -338,7 +336,6 @@@ strbuf_addstr(&buf, rsync_url(transport->url)); strbuf_addch(&buf, '/'); - memset(&rsync, 0, sizeof(rsync)); rsync.argv = args; rsync.stdout_to_stderr = 1; i = 0; @@@ -477,6 -480,9 +477,9 @@@ static int set_git_option(struct git_tr die("transport: invalid depth option '%s'", value); } return 0; + } else if (!strcmp(name, TRANS_OPT_PUSH_CERT)) { + opts->push_cert = !!value; + return 0; } return 1; } @@@ -820,6 -826,8 +823,8 @@@ static int git_transport_push(struct tr args.progress = transport->progress; args.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN); args.porcelain = !!(flags & TRANSPORT_PUSH_PORCELAIN); + args.push_cert = !!(flags & TRANSPORT_PUSH_CERT); + args.url = transport->url; ret = send_pack(&args, data->fd, data->conn, remote_refs, &data->extra_have); @@@ -1053,7 -1061,7 +1058,7 @@@ static int run_pre_push_hook(struct tra { int ret = 0, x; struct ref *r; - struct child_process proc; + struct child_process proc = CHILD_PROCESS_INIT; struct strbuf buf; const char *argv[4]; @@@ -1064,6 -1072,7 +1069,6 @@@ argv[2] = transport->url; argv[3] = NULL; - memset(&proc, 0, sizeof(proc)); proc.argv = argv; proc.in = -1;