git-remote-gcrypt 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916
  1. #!/bin/sh
  2. #
  3. # git-remote-gcrypt
  4. #
  5. # This program is free software: you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation, either version 3 of the License, or
  8. # (at your option) version 2 or any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. #
  18. # See README.rst for usage instructions
  19. set -e # errexit
  20. set -f # noglob
  21. set -C # noclobber
  22. export GITCEPTION="${GITCEPTION:-}+" # Reuse $Gref except when stacked
  23. Gref="refs/gcrypt/gitception$GITCEPTION"
  24. Gref_rbranch="refs/heads/master"
  25. Packkey_bytes=63 # nbr random bytes for packfile keys, any >= 256 bit is ok
  26. Hashtype=SHA256 # SHA512 SHA384 SHA256 SHA224 supported.
  27. Manifestfile=91bd0c092128cf2e60e1a608c31e92caf1f9c1595f83f2890ef17c0e4881aa0a
  28. Hex40="[a-f0-9]"
  29. Hex40=$Hex40$Hex40$Hex40$Hex40$Hex40$Hex40$Hex40$Hex40
  30. Hex40=$Hex40$Hex40$Hex40$Hex40$Hex40 # Match SHA-1 hexdigest
  31. Did_find_repo= # yes for connected, no for no repo
  32. Localdir="${GIT_DIR:=.git}/remote-gcrypt"
  33. Tempdir=
  34. Repoid=
  35. Refslist=
  36. Packlist=
  37. Keeplist=
  38. Extnlist=
  39. Repack_limit=25
  40. Recipients=
  41. # compat/utility functions
  42. # xfeed: The most basic output function puts $1 into the stdin of $2..$#
  43. xfeed()
  44. {
  45. local input_=
  46. input_=$1; shift
  47. "$@" <<EOF
  48. $input_
  49. EOF
  50. }
  51. xecho() { xfeed "$*" cat; }
  52. xecho_n() { xecho "$@" | tr -d \\n ; } # kill newlines
  53. echo_git() { xecho "$@" ; } # Code clarity
  54. echo_info() { xecho "gcrypt:" "$@" >&2; }
  55. echo_die() { echo_info "$@" ; exit 1; }
  56. isnull() { case "$1" in "") return 0;; *) return 1;; esac; }
  57. isnonnull() { ! isnull "$1"; }
  58. iseq() { case "$1" in "$2") return 0;; *) return 1;; esac; }
  59. isnoteq() { ! iseq "$1" "$2"; }
  60. negate() { ! "$@"; }
  61. # Execute $@ or die
  62. pipefail()
  63. {
  64. "$@" || { echo_info "'$1' failed!"; kill $$; exit 1; }
  65. }
  66. isurl() { isnull "${2%%$1://*}"; }
  67. islocalrepo() { isnull "${1##/*}" && [ ! -e "$1/HEAD" ]; }
  68. xgrep() { command grep "$@" || : ; }
  69. # setvar is used for named return variables
  70. # $1 *must* be a valid variable name, $2 is any value
  71. #
  72. # Conventions
  73. # return variable names are passed with a @ prefix
  74. # return variable functions use f_ prefix local vars
  75. # return var consumers use r_ prefix vars (or Titlecase globals)
  76. setvar()
  77. {
  78. isnull "${1##@*}" || echo_die "Missing @ for return variable: $1"
  79. eval ${1#@}=\$2
  80. }
  81. Newline="
  82. "
  83. # $1 is return var, $2 is value appended with newline separator
  84. append_to()
  85. {
  86. local f_append_tmp_=
  87. eval f_append_tmp_=\$${1#@}
  88. isnull "$f_append_tmp_" || f_append_tmp_=$f_append_tmp_$Newline
  89. setvar "$1" "$f_append_tmp_$2"
  90. }
  91. # Pick words from each line
  92. # $1 return variable name
  93. # $2 input value
  94. pick_fields_1_2()
  95. {
  96. local f_ret= f_one= f_two=
  97. while read f_one f_two _ # from << here-document
  98. do
  99. f_ret="$f_ret$f_one $f_two$Newline"
  100. done <<EOF
  101. $2
  102. EOF
  103. setvar "$1" "${f_ret#$Newline}"
  104. }
  105. # Take all lines matching $2 (full line)
  106. # $1 return variable name
  107. # $2 filter word
  108. # $3 input value
  109. # if $1 is a literal `!', the match is reversed (and arguments shift)
  110. # we instead remove all lines matching
  111. filter_to()
  112. {
  113. local f_neg= f_line= f_ret= IFS=
  114. isnoteq "$1" "!" || { f_neg=negate; shift; }
  115. IFS=$Newline
  116. for f_line in $3
  117. do
  118. $f_neg isnonnull "${f_line##$2}" || f_ret=$f_ret$f_line$Newline
  119. done
  120. setvar "$1" "${f_ret%$Newline}"
  121. }
  122. # Output the number of lines in $1
  123. line_count()
  124. {
  125. local IFS=
  126. IFS=$Newline
  127. set -- $1
  128. xecho "$#"
  129. }
  130. ## gitception part
  131. # Fetch giturl $1, file $2
  132. gitception_get()
  133. {
  134. # Take care to preserve FETCH_HEAD
  135. local ret_=: obj_id= fet_head="$GIT_DIR/FETCH_HEAD"
  136. [ -e "$fet_head" ] && command mv -f "$fet_head" "$fet_head.$$~" || :
  137. git fetch -q -f "$1" "$Gref_rbranch:$Gref" >/dev/null &&
  138. obj_id="$(git ls-tree "$Gref" | xgrep -E '\b'"$2"'$' | awk '{print $3}')" &&
  139. isnonnull "$obj_id" && git cat-file blob "$obj_id" && ret_=: ||
  140. { ret_=false && : ; }
  141. [ -e "$fet_head.$$~" ] && command mv -f "$fet_head.$$~" "$fet_head" || :
  142. $ret_
  143. }
  144. anon_commit()
  145. {
  146. GIT_AUTHOR_NAME="root" GIT_AUTHOR_EMAIL="root@localhost" \
  147. GIT_AUTHOR_DATE="1356994801 -0400" GIT_COMMITTER_NAME="root" \
  148. GIT_COMMITTER_EMAIL="root@localhost" \
  149. GIT_COMMITTER_DATE="1356994801 -0400" \
  150. git commit-tree "$@" <<EOF
  151. Initial commit
  152. EOF
  153. }
  154. # Get 'tree' from $1, change file $2 to obj id $3
  155. update_tree()
  156. {
  157. local tab_=" "
  158. # $2 is a filename from the repo format
  159. (set +e;
  160. git ls-tree "$1" | xgrep -v -E '\b'"$2"'$';
  161. xecho "100644 blob $3$tab_$2"
  162. ) | git mktree
  163. }
  164. # Put giturl $1, file $2
  165. # depends on previous GET to set $Gref and depends on PUT_FINAL later
  166. gitception_put()
  167. {
  168. local obj_id= tree_id= commit_id=
  169. obj_id=$(git hash-object -w --stdin) &&
  170. tree_id=$(update_tree "$Gref" "$2" "$obj_id") &&
  171. commit_id=$(anon_commit "$tree_id") &&
  172. git update-ref "$Gref" "$commit_id"
  173. }
  174. # Remove giturl $1, file $2
  175. # depends on previous GET like put
  176. gitception_remove()
  177. {
  178. local tree_id= commit_id= tab_=" "
  179. # $2 is a filename from the repo format
  180. tree_id=$(git ls-tree "$Gref" | xgrep -v -E '\b'"$2"'$' | git mktree) &&
  181. commit_id=$(anon_commit "$tree_id") &&
  182. git update-ref "$Gref" "$commit_id"
  183. }
  184. gitception_new_repo()
  185. {
  186. local commit_id= empty_tree=4b825dc642cb6eb9a060e54bf8d69288fbee4904
  187. # get any file to update Gref, and if it's not updated we create empty
  188. git update-ref -d "$Gref" || :
  189. gitception_get "$1" "x" 2>/dev/null >&2 || :
  190. git rev-parse -q --verify "$Gref" >/dev/null && return 0 ||
  191. commit_id=$(anon_commit "$empty_tree") &&
  192. git update-ref "$Gref" "$commit_id"
  193. }
  194. ## end gitception
  195. # Fetch repo $1, file $2, tmpfile in $3
  196. GET()
  197. {
  198. if isurl sftp "$1"
  199. then
  200. (exec 0>&-; curl -s -S -k "$1/$2") > "$3"
  201. elif isurl rsync "$1"
  202. then
  203. (exec 0>&-; rsync -I -W "${1#rsync://}"/"$2" "$3" >&2)
  204. elif islocalrepo "$1"
  205. then
  206. cat "$1/$2" > "$3"
  207. else
  208. gitception_get "${1#gitception://}" "$2" > "$3"
  209. fi
  210. }
  211. # Put repo $1, file $2 or fail, tmpfile in $3
  212. PUT()
  213. {
  214. if isurl sftp "$1"
  215. then
  216. curl -s -S -k --ftp-create-dirs -T "$3" "$1/$2"
  217. elif isurl rsync "$1"
  218. then
  219. rsync -I -W "$3" "${1#rsync://}"/"$2" >&2
  220. elif islocalrepo "$1"
  221. then
  222. cat >| "$1/$2" < "$3"
  223. else
  224. gitception_put "${1#gitception://}" "$2" < "$3"
  225. fi
  226. }
  227. # Put all PUT changes for repo $1 at once
  228. PUT_FINAL()
  229. {
  230. if isurl sftp "$1" || islocalrepo "$1" || isurl rsync "$1"
  231. then
  232. :
  233. else
  234. git push --quiet -f "${1#gitception://}" "$Gref:$Gref_rbranch"
  235. fi
  236. }
  237. # Put directory for repo $1
  238. PUTREPO()
  239. {
  240. if isurl sftp "$1"
  241. then
  242. :
  243. elif isurl rsync "$1"
  244. then
  245. rsync -q -r --exclude='*' "$Localdir/" "${1#rsync://}" >&2
  246. elif islocalrepo "$1"
  247. then
  248. mkdir -p "$1"
  249. else
  250. gitception_new_repo "${1#gitception://}"
  251. fi
  252. }
  253. # For repo $1, delete all newline-separated files in $2
  254. REMOVE()
  255. {
  256. local fn_=
  257. if isurl sftp "$1"
  258. then
  259. # FIXME
  260. echo_info "sftp: Ignore remove request $1/$2"
  261. elif isurl rsync "$1"
  262. then
  263. xfeed "$2" rsync -I -W -v -r --delete --include-from=- \
  264. --exclude='*' "$Localdir"/ "${1#rsync://}/" >&2
  265. elif islocalrepo "$1"
  266. then
  267. for fn_ in $2; do
  268. rm -f "$1"/"$fn_"
  269. done
  270. else
  271. for fn_ in $2; do
  272. gitception_remove "${1#gitception://}" "$fn_"
  273. done
  274. fi
  275. }
  276. CLEAN_FINAL()
  277. {
  278. if isurl sftp "$1" || islocalrepo "$1" || isurl rsync "$1"
  279. then
  280. :
  281. else
  282. git update-ref -d "$Gref" || :
  283. fi
  284. }
  285. ENCRYPT()
  286. {
  287. rungpg --batch --force-mdc --compress-algo none --trust-model=always --passphrase-fd 3 -c 3<<EOF
  288. $1
  289. EOF
  290. }
  291. DECRYPT()
  292. {
  293. rungpg -q --batch --no-default-keyring --secret-keyring /dev/null \
  294. --keyring /dev/null --passphrase-fd 3 -d 3<<EOF
  295. $1
  296. EOF
  297. }
  298. # Encrypt to recipients $1
  299. PRIVENCRYPT()
  300. {
  301. set -- $1
  302. if isnonnull "$Conf_signkey"; then
  303. set -- "$@" -u "$Conf_signkey"
  304. fi
  305. rungpg --compress-algo none --trust-model=always -se "$@"
  306. }
  307. GET_RECIPIENTS()
  308. {
  309. keys_id=$(rungpg --list-only --no-default-keyring --secret-keyring /dev/null \
  310. $1 2>&1 | awk -c '/gpg/{ gsub(",","",$8 ); print $8 }')
  311. for k in $keys_id; do
  312. fp=$(rungpg --with-colons --fingerprint $k | awk -F: -c '/^fpr/{print $10}')
  313. fingerprints="$fingerprints $fp"
  314. echo -n "$fp "
  315. done
  316. }
  317. # $1 is the match for good signature, $2 is the textual signers list
  318. PRIVDECRYPT()
  319. {
  320. local status_=
  321. exec 4>&1 &&
  322. status_=$(rungpg --status-fd 3 -q -d 3>&1 1>&4) &&
  323. xfeed "$status_" grep "^\[GNUPG:\] ENC_TO " >/dev/null &&
  324. (xfeed "$status_" grep -e "$1" >/dev/null || {
  325. echo_info "Failed to verify manifest signature!" &&
  326. echo_info "Only accepting signatories: ${2:-(none)}" &&
  327. return 1
  328. })
  329. }
  330. # Generate $1 random bytes
  331. genkey()
  332. {
  333. rungpg --armor --gen-rand 1 "$1"
  334. }
  335. gpg_hash()
  336. {
  337. local hash_=
  338. hash_=$(rungpg --with-colons --print-md "$1" | tr A-F a-f)
  339. hash_=${hash_#:*:}
  340. xecho "${hash_%:}"
  341. }
  342. rungpg()
  343. {
  344. # gpg will fail to run when there is no controlling tty,
  345. # due to trying to print messages to it, even if a gpg agent is set
  346. # up. --no-tty fixes this.
  347. if [ "x$GPG_AGENT_INFO" != "x" ]; then
  348. gpg --no-tty "$@"
  349. else
  350. gpg "$@"
  351. fi
  352. }
  353. # Pass the branch/ref by pipe to git
  354. safe_git_rev_parse()
  355. {
  356. git cat-file --batch-check 2>/dev/null |
  357. xgrep -v "missing" | cut -f 1 -d ' '
  358. }
  359. make_new_repo()
  360. {
  361. echo_info "Setting up new repository"
  362. PUTREPO "$URL"
  363. # Needed assumption: the same user should have no duplicate Repoid
  364. Repoid=":id:$(genkey 15)"
  365. iseq "${NAME#gcrypt::}" "$URL" ||
  366. git config "remote.$NAME.gcrypt-id" "$Repoid"
  367. echo_info "Remote ID is $Repoid"
  368. Extnlist="extn comment"
  369. }
  370. # $1 return var for goodsig match, $2 return var for signers text
  371. read_config()
  372. {
  373. local recp_= r_keyinfo= r_keyfpr= gpg_list= cap_= conf_part= good_sig= signers_=
  374. Conf_signkey=$(git config --get "remote.$NAME.gcrypt-signingkey" '.+' ||
  375. git config --path user.signingkey || :)
  376. conf_part=$(git config --get "remote.$NAME.gcrypt-participants" '.+' ||
  377. git config --get gcrypt.participants '.+' || :)
  378. Conf_pubish_participants=$(git config --get --bool "remote.$NAME.gcrypt-publish-participants" '.+' ||
  379. git config --get --bool gcrypt.publish-participants || :)
  380. # Figure out which keys we should encrypt to or accept signatures from
  381. if isnull "$conf_part" || iseq "$conf_part" simple
  382. then
  383. signers_="(default keyring)"
  384. Recipients="--throw-keyids --default-recipient-self"
  385. good_sig="^\[GNUPG:\] GOODSIG "
  386. setvar "$1" "$good_sig"
  387. setvar "$2" "$signers_"
  388. return 0
  389. fi
  390. for recp_ in $conf_part
  391. do
  392. gpg_list=$(rungpg --with-colons --fingerprint -k "$recp_")
  393. filter_to @r_keyinfo "pub*" "$gpg_list"
  394. filter_to @r_keyfpr "fpr*" "$gpg_list"
  395. isnull "$r_keyinfo" || isnonnull "${r_keyinfo##*"$Newline"*}" ||
  396. echo_info "WARNING: '$recp_' matches multiple keys, using one"
  397. isnull "$r_keyfpr" || isnonnull "${r_keyfpr##*"$Newline"*}" ||
  398. echo_info "WARNING: '$recp_' matches multiple fingerprints, using one"
  399. r_keyinfo=${r_keyinfo%%"$Newline"*}
  400. r_keyfpr=${r_keyfpr%%"$Newline"*}
  401. keyid_=$(xfeed "$r_keyinfo" cut -f 5 -d :)
  402. fprid_=$(xfeed "$r_keyfpr" cut -f 10 -d :)
  403. isnonnull "$fprid_" &&
  404. signers_="$signers_ $keyid_" &&
  405. append_to @good_sig "^\[GNUPG:\] VALIDSIG .*$fprid_$" || {
  406. echo_info "WARNING: Skipping missing key $recp_"
  407. continue
  408. }
  409. # Check 'E'ncrypt capability
  410. cap_=$(xfeed "$r_keyinfo" cut -f 12 -d :)
  411. if ! iseq "${cap_#*E}" "$cap_"; then
  412. if [ "$Conf_pubish_participants" = true ]; then
  413. Recipients="$Recipients -r $keyid_"
  414. else
  415. Recipients="$Recipients -R $keyid_"
  416. fi
  417. fi
  418. done
  419. if isnull "$Recipients"
  420. then
  421. echo_info "You have not configured any keys you can encrypt to" \
  422. "for this repository"
  423. echo_info "Use ::"
  424. echo_info " git config gcrypt.participants YOURKEYID"
  425. exit 1
  426. fi
  427. setvar "$1" "$good_sig"
  428. setvar "$2" "$signers_"
  429. }
  430. ensure_connected()
  431. {
  432. local manifest_= r_repoid= r_name= url_frag= r_sigmatch= r_signers= \
  433. tmp_manifest= r_participants=
  434. if isnonnull "$Did_find_repo"
  435. then
  436. return
  437. fi
  438. Did_find_repo=no
  439. read_config @r_sigmatch @r_signers
  440. iseq "${NAME#gcrypt::}" "$URL" || r_name=$NAME
  441. if isurl gitception "$URL" && isnonnull "$r_name"; then
  442. git config "remote.$r_name.url" "gcrypt::${URL#gitception://}"
  443. echo_info "Updated URL for $r_name, gitception:// -> ()"
  444. fi
  445. # Find the URL fragment
  446. url_frag=${URL##*"#"}
  447. isnoteq "$url_frag" "$URL" || url_frag=
  448. URL=${URL%"#$url_frag"}
  449. # manifestfile -- sha224 hash if we can, else the default location
  450. if isurl sftp "$URL" || islocalrepo "$URL" || isurl rsync "$URL"
  451. then
  452. # not for gitception
  453. isnull "$url_frag" ||
  454. Manifestfile=$(xecho_n "$url_frag" | gpg_hash SHA224)
  455. else
  456. isnull "$url_frag" || Gref_rbranch="refs/heads/$url_frag"
  457. fi
  458. Repoid=
  459. isnull "$r_name" ||
  460. Repoid=$(git config "remote.$r_name.gcrypt-id" || :)
  461. tmp_manifest="$Tempdir/maniF"
  462. GET "$URL" "$Manifestfile" "$tmp_manifest" 2>/dev/null || {
  463. echo_info "Repository not found: $URL"
  464. return 0
  465. }
  466. Did_find_repo=yes
  467. echo_info "Decrypting manifest"
  468. manifest_=$(PRIVDECRYPT "$r_sigmatch" "$r_signers" < "$tmp_manifest") &&
  469. isnonnull "$manifest_" ||
  470. echo_die "Failed to decrypt manifest!"
  471. # Getting repository participants parsing GPG file recipients
  472. r_participants=$(GET_RECIPIENTS "$tmp_manifest")
  473. echo_info $r_participants
  474. rm -f "$tmp_manifest"
  475. filter_to @Refslist "$Hex40 *" "$manifest_"
  476. filter_to @Packlist "pack :*:* *" "$manifest_"
  477. filter_to @Keeplist "keep :*:*" "$manifest_"
  478. filter_to @Extnlist "extn *" "$manifest_"
  479. filter_to @r_repoid "repo *" "$manifest_"
  480. r_repoid=${r_repoid#repo }
  481. r_repoid=${r_repoid% *}
  482. if isnull "$Repoid"
  483. then
  484. echo_info "Remote ID is $r_repoid"
  485. Repoid=$r_repoid
  486. elif isnoteq "$r_repoid" "$Repoid"
  487. then
  488. echo_info "WARNING:"
  489. echo_info "WARNING: Remote ID has changed!"
  490. echo_info "WARNING: from $Repoid"
  491. echo_info "WARNING: to $r_repoid"
  492. echo_info "WARNING:"
  493. Repoid=$r_repoid
  494. else
  495. return 0
  496. fi
  497. isnull "$r_name" || git config "remote.$r_name.gcrypt-id" "$r_repoid"
  498. isnull "$r_participants" || (git config \
  499. "remote.$r_name.gcrypt-participants" \
  500. "$r_participants" && \
  501. git config "remote.$r_name.gcrypt-publish-participants" true)
  502. }
  503. # $1 is the hash type (SHA256 etc)
  504. # $2 the pack id
  505. # $3 the key
  506. get_verify_decrypt_pack()
  507. {
  508. local rcv_id= tmp_encrypted=
  509. tmp_encrypted="$Tempdir/packF"
  510. GET "$URL" "$2" "$tmp_encrypted" &&
  511. rcv_id=$(gpg_hash "$1" < "$tmp_encrypted") &&
  512. iseq "$rcv_id" "$2" || echo_die "Packfile $2 does not match digest!"
  513. DECRYPT "$3" < "$tmp_encrypted"
  514. rm -f "$tmp_encrypted"
  515. }
  516. # download all packlines (pack :SHA256:a32abc1231) from stdin (or die)
  517. # $1 destdir (when repack, else "")
  518. get_pack_files()
  519. {
  520. local pack_id= r_pack_key_line= htype_= pack_= key_=
  521. while IFS=': ' read -r _ htype_ pack_ # <<here-document
  522. do
  523. isnonnull "$pack_" || continue
  524. # Get the Packlist line with the key
  525. pack_id=":${htype_}:$pack_"
  526. filter_to @r_pack_key_line "pack $pack_id *" "$Packlist"
  527. key_=${r_pack_key_line#pack $pack_id }
  528. if isnonnull "${pack_##$Hex40*}" ||
  529. isnoteq "$htype_" SHA256 && isnoteq "$htype_" SHA224 &&
  530. isnoteq "$htype_" SHA384 && isnoteq "$htype_" SHA512
  531. then
  532. echo_die "Packline malformed: $pack_id"
  533. fi
  534. get_verify_decrypt_pack "$htype_" "$pack_" "$key_" | \
  535. if isnull "${1:-}"
  536. then
  537. # add to local pack list
  538. git index-pack -v --stdin >/dev/null
  539. xecho "pack $pack_id" >> "$Localdir/have_packs$GITCEPTION"
  540. else
  541. git index-pack -v --stdin "$1/${pack_}.pack" >/dev/null
  542. fi
  543. done
  544. }
  545. # Download and unpack remote packfiles
  546. # $1 return var for list of packfiles to delete
  547. repack_if_needed()
  548. {
  549. local n_= m_= kline_= r_line= r_keep_packlist= r_del_list=
  550. isnonnull "$Packlist" || return 0
  551. if isnonnull "${GCRYPT_FULL_REPACK:-}"
  552. then
  553. Keeplist=
  554. Repack_limit=0
  555. fi
  556. pick_fields_1_2 @r_del_list "$Packlist"
  557. n_=$(line_count "$Packlist")
  558. m_=$(line_count "$Keeplist")
  559. if iseq 0 "$(( $Repack_limit < ($n_ - $m_) ))"; then
  560. return
  561. fi
  562. echo_info "Repacking remote $NAME, ..."
  563. mkdir "$Tempdir/pack"
  564. # Split packages to keep and to repack
  565. if isnonnull "$Keeplist"; then
  566. while read -r _ kline_ _ # <<here-document
  567. do
  568. isnonnull "$kline_" || continue
  569. filter_to @r_line "pack $kline_ *" "$Packlist"
  570. append_to @r_keep_packlist "$r_line"
  571. filter_to ! @r_del_list "pack $kline_" "$r_del_list"
  572. done <<EOF
  573. $Keeplist
  574. EOF
  575. fi
  576. xfeed "$r_del_list" get_pack_files "$Tempdir/pack/"
  577. (set +f; pipefail git verify-pack -v "$Tempdir"/pack/*.idx) |
  578. grep -E '^[0-9a-f]{40}' | cut -f 1 -d ' '
  579. Packlist=$r_keep_packlist
  580. setvar "$1" "$r_del_list"
  581. }
  582. do_capabilities()
  583. {
  584. echo_git fetch
  585. echo_git push
  586. echo_git
  587. }
  588. do_list()
  589. {
  590. local obj_id= ref_name= line_=
  591. ensure_connected
  592. xecho "$Refslist" | while read line_
  593. do
  594. isnonnull "$line_" || break
  595. obj_id=${line_%% *}
  596. ref_name=${line_##* }
  597. echo_git "$obj_id" "$ref_name"
  598. if iseq "$ref_name" "refs/heads/master"
  599. then
  600. echo_git "@refs/heads/master HEAD"
  601. fi
  602. done
  603. # end with blank line
  604. echo_git
  605. }
  606. do_fetch()
  607. {
  608. # Download packs in the manifest that don't appear in have_packs
  609. local pneed_= premote_=
  610. ensure_connected
  611. # The `+` for $GITCEPTION is pointless but we will be safe for stacking
  612. pick_fields_1_2 @premote_ "$Packlist"
  613. if [ -s "$Localdir/have_packs+" ]
  614. then
  615. pneed_=$(xfeed "$premote_" xgrep -v -x -f "$Localdir/have_packs+")
  616. else
  617. pneed_=$premote_
  618. fi
  619. xfeed "$pneed_" get_pack_files
  620. echo_git # end with blank line
  621. }
  622. # do_push PUSHARGS (multiple lines like +src:dst, with both + and src opt.)
  623. do_push()
  624. {
  625. # Security protocol:
  626. # Each git packfile is encrypted and then named for the encrypted
  627. # file's hash. The manifest is updated with the pack id.
  628. # The manifest is encrypted.
  629. local r_revlist= pack_id= key_= obj_= src_= dst_= \
  630. r_pack_delete= tmp_encrypted= tmp_objlist= tmp_manifest=
  631. ensure_connected
  632. if iseq "$Did_find_repo" "no"
  633. then
  634. make_new_repo
  635. fi
  636. if isnonnull "$Refslist"
  637. then
  638. # mark all remote refs with ^<sha-1> (if sha-1 exists locally)
  639. r_revlist=$(xfeed "$Refslist" cut -f 1 -d ' ' |
  640. safe_git_rev_parse | sed -e 's/^\(.\)/^&/')
  641. fi
  642. while IFS=: read -r src_ dst_ # << +src:dst
  643. do
  644. src_=${src_#+}
  645. filter_to ! @Refslist "$Hex40 $dst_" "$Refslist"
  646. if isnonnull "$src_"
  647. then
  648. append_to @r_revlist "$src_"
  649. obj_=$(xfeed "$src_" safe_git_rev_parse)
  650. append_to @Refslist "$obj_ $dst_"
  651. fi
  652. done <<EOF
  653. $1
  654. EOF
  655. tmp_encrypted="$Tempdir/packP"
  656. tmp_objlist="$Tempdir/objlP"
  657. {
  658. xfeed "$r_revlist" git rev-list --objects --stdin --
  659. repack_if_needed @r_pack_delete
  660. } > "$tmp_objlist"
  661. # Only send pack if we have any objects to send
  662. if [ -s "$tmp_objlist" ]
  663. then
  664. key_=$(genkey "$Packkey_bytes")
  665. pack_id=$(export GIT_ALTERNATE_OBJECT_DIRECTORIES=$Tempdir;
  666. pipefail git pack-objects --stdout < "$tmp_objlist" |
  667. pipefail ENCRYPT "$key_" |
  668. tee "$tmp_encrypted" | gpg_hash "$Hashtype")
  669. append_to @Packlist "pack :${Hashtype}:$pack_id $key_"
  670. if isnonnull "$r_pack_delete"
  671. then
  672. append_to @Keeplist "keep :${Hashtype}:$pack_id 1"
  673. fi
  674. fi
  675. # Generate manifest
  676. echo_info "Encrypting to: $Recipients"
  677. echo_info "Requesting manifest signature"
  678. tmp_manifest="$Tempdir/maniP"
  679. PRIVENCRYPT "$Recipients" > "$tmp_manifest" <<EOF
  680. $Refslist
  681. $Packlist
  682. $Keeplist
  683. repo $Repoid
  684. $Extnlist
  685. EOF
  686. # Upload pack
  687. if [ -s "$tmp_objlist" ]
  688. then
  689. PUT "$URL" "$pack_id" "$tmp_encrypted"
  690. fi
  691. # Upload manifest
  692. PUT "$URL" "$Manifestfile" "$tmp_manifest"
  693. rm -f "$tmp_encrypted"
  694. rm -f "$tmp_objlist"
  695. rm -f "$tmp_manifest"
  696. # Delete packs
  697. if isnonnull "$r_pack_delete"; then
  698. REMOVE "$URL" "$(xecho "$r_pack_delete" | \
  699. while IFS=': ' read -r _ _ pack_
  700. do
  701. isnonnull "$pack_" || continue
  702. xecho "$pack_"
  703. done)"
  704. fi
  705. PUT_FINAL "$URL"
  706. # ok all updates
  707. while IFS=: read -r src_ dst_ # << +src:dst
  708. do
  709. echo_git "ok $dst_"
  710. done <<EOF
  711. $1
  712. EOF
  713. echo_git
  714. }
  715. cleanup_tmpfiles()
  716. {
  717. if isnonnull "${Tempdir%%*."$$"}"; then
  718. echo_die "Unexpected Tempdir value: $Tempdir"
  719. fi
  720. rm -r -f -- "${Tempdir}" >&2
  721. }
  722. setup()
  723. {
  724. mkdir -p "$Localdir"
  725. # Set up a subdirectory in /tmp
  726. temp_key=$(genkey 9 | tr '/' _)
  727. Tempdir="${TMPDIR:-/tmp}/git-remote-gcrypt-${temp_key}.$$"
  728. mkdir -m 700 "${Tempdir}"
  729. trap cleanup_tmpfiles EXIT
  730. trap 'exit 1' 1 2 3 15
  731. echo_info "Development version -- Repository format MAY CHANGE"
  732. }
  733. # handle git-remote-helpers protocol
  734. gcrypt_main_loop()
  735. {
  736. local input_= input_inner= r_args= temp_key=
  737. NAME=$1 # Remote name
  738. URL=$2 # Remote URL
  739. setup
  740. while read input_
  741. do
  742. case "$input_" in
  743. capabilities)
  744. do_capabilities
  745. ;;
  746. list|list\ for-push)
  747. do_list
  748. ;;
  749. fetch\ *)
  750. r_args=${input_##fetch }
  751. while read input_inner
  752. do
  753. case "$input_inner" in
  754. fetch*)
  755. r_args= #ignored
  756. ;;
  757. *)
  758. break
  759. ;;
  760. esac
  761. done
  762. do_fetch "$r_args"
  763. ;;
  764. push\ *)
  765. r_args=${input_##push }
  766. while read input_inner
  767. do
  768. case "$input_inner" in
  769. push\ *)
  770. append_to @r_args "${input_inner#push }"
  771. ;;
  772. *)
  773. break
  774. ;;
  775. esac
  776. done
  777. do_push "$r_args"
  778. ;;
  779. ?*)
  780. echo_die "Unknown input!"
  781. ;;
  782. *)
  783. CLEAN_FINAL "$URL"
  784. exit 0
  785. ;;
  786. esac
  787. done
  788. }
  789. if [ "x$1" = x--check ]
  790. then
  791. NAME=dummy-gcrypt-check
  792. URL=$2
  793. setup
  794. ensure_connected
  795. git remote remove $NAME 2>/dev/null || true
  796. if iseq "$Did_find_repo" "no"
  797. then
  798. exit 100
  799. fi
  800. else
  801. gcrypt_main_loop "$@"
  802. fi