├── .gitignore ├── .project ├── .pydevproject ├── Jenkinsfile ├── Makefile ├── README.md ├── bin ├── git-local-qubes └── git-remote-qubes ├── build.parameters ├── etc ├── qubes-rpc │ └── ruddo.Git.in └── qubes │ └── policy.d │ └── 80-git-remote-qubes.policy ├── git-remote-qubes.spec └── src └── gitremotequbes ├── __init__.py ├── client.py ├── copier.py └── server.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *~ 3 | *.tar.gz 4 | *.rpm 5 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | git-remote-qubes 4 | 5 | 6 | 7 | 8 | 9 | org.python.pydev.PyDevBuilder 10 | 11 | 12 | 13 | 14 | 15 | org.python.pydev.pythonNature 16 | 17 | 18 | -------------------------------------------------------------------------------- /.pydevproject: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /${PROJECT_DIR_NAME}/src 5 | 6 | python 3.8 7 | Default 8 | 9 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | // https://github.com/Rudd-O/shared-jenkins-libraries 2 | @Library('shared-jenkins-libraries@master') _ 3 | 4 | genericFedoraRPMPipeline() 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BINDIR=/usr/bin 2 | SYSCONFDIR=/etc 3 | LIBEXECDIR=/usr/libexec 4 | GITEXECPATH=`git --exec-path` 5 | DESTDIR= 6 | PROGNAME=git-remote-qubes 7 | SITELIBDIR=`python3 -c 'import distutils.sysconfig; print (distutils.sysconfig.get_python_lib())'` 8 | 9 | OBJLIST=$(shell find src/gitremotequbes -name '*.py' | sed 's/.py$$/.pyc/') 10 | SUBSTLIST=etc/qubes-rpc/ruddo.Git 11 | 12 | all: $(OBJLIST) $(SUBSTLIST) bin/git-*-qubes 13 | 14 | src/gitremotequbes/%.pyc: src/gitremotequbes/%.py 15 | @if [ -z "$(SITELIBDIR)" ] ; then echo Error: you need Python 3 on your system >&2 ; exit 1 ; fi 16 | python3 -m compileall src/gitremotequbes/ 17 | 18 | etc/%: etc/%.in 19 | cat $< | sed 's|@BINDIR@|$(BINDIR)|g' | sed 's|@LIBEXECDIR@|$(LIBEXECDIR)|g' > $@ 20 | 21 | clean: 22 | rm -rfv $(OBJLIST) $(SUBSTLIST) 23 | find -name '*~' -print0 | xargs -0 rm -fv 24 | rm -fv *.tar.gz *.rpm 25 | 26 | dist: clean 27 | excludefrom= ; test -f .gitignore && excludefrom=--exclude-from=.gitignore ; DIR=$(PROGNAME)-`awk '/^Version:/ {print $$2}' $(PROGNAME).spec` && FILENAME=$$DIR.tar.gz && tar cvzf "$$FILENAME" --exclude="$$FILENAME" --exclude=.git --exclude=.gitignore $$excludefrom --transform="s|^|$$DIR/|" --show-transformed * 28 | 29 | rpm: dist 30 | T=`mktemp -d` && rpmbuild --define "_topdir $$T" -ta $(PROGNAME)-`awk '/^Version:/ {print $$2}' $(PROGNAME).spec`.tar.gz || { rm -rf "$$T"; exit 1; } && mv "$$T"/RPMS/*/* "$$T"/SRPMS/* . || { rm -rf "$$T"; exit 1; } && rm -rf "$$T" 31 | 32 | srpm: dist 33 | T=`mktemp -d` && rpmbuild --define "_topdir $$T" -ts $(PROGNAME)-`awk '/^Version:/ {print $$2}' $(PROGNAME).spec`.tar.gz || { rm -rf "$$T"; exit 1; } && mv "$$T"/SRPMS/* . || { rm -rf "$$T"; exit 1; } && rm -rf "$$T" 34 | 35 | install-vm: all 36 | install -Dm 644 src/gitremotequbes/*.py src/gitremotequbes/__pycache__/*.pyc -t $(DESTDIR)/$(SITELIBDIR)/gitremotequbes/ 37 | install -Dm 755 bin/git-local-qubes -t $(DESTDIR)/$(LIBEXECDIR)/ 38 | install -Dm 755 bin/git-remote-qubes -t $(DESTDIR)/$(GITEXECPATH)/ 39 | install -Dm 755 etc/qubes-rpc/ruddo.Git -t $(DESTDIR)/$(SYSCONFDIR)/qubes-rpc/ 40 | 41 | install-dom0: all 42 | install -Dm 664 etc/qubes/policy.d/*.policy -t $(DESTDIR)/$(SYSCONFDIR)/qubes/policy.d/ 43 | getent group qubes && chgrp qubes $(DESTDIR)/$(SYSCONFDIR)/qubes/policy.d/*.policy || true 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Inter-VM Git for Qubes OS 2 | 3 | This is a very simple Git protocol bridge between Qubes OS VMs. With it, 4 | you can `git pull` and `git push` between VMs without having to grant 5 | any of the VMs any special policy privileges other than access to Git. 6 | 7 | The advantages of this solution over Git-over-SSH or other networking 8 | protocols are clear: 9 | 10 | 1. Better security: you do not need to network the VM that stores your 11 | Git repos, or take care of firewall rules. 12 | 2. More resource efficiency: you do not need to run any network-facing 13 | daemons, effectively saving RAM for other uses. 14 | 15 | ## Using the software 16 | 17 | These instructions assume you have installed the software. See the 18 | *Installing the software* heading below for more information. 19 | 20 | ### Creating a repository 21 | 22 | We'll assume for illustration purposes that you want to access a repository 23 | stored in `/home/user/xyz` on your VM `servervm`. 24 | 25 | Run the following commands on `servervm`: 26 | 27 | ``` 28 | cd /home/user 29 | mkdir -p xyz 30 | cd xyz 31 | git --bare init 32 | ``` 33 | 34 | That's it. Your new and empty repository is ready to use. 35 | 36 | ### Adding a remote to a local repository 37 | 38 | For illustration purposes, you'll be pushing and pulling `servervm`'s `xyz` 39 | repo on your vm `clientvm`. Run the following commands on `clientvm`: 40 | 41 | ``` 42 | cd /home/user 43 | git clone qubes://clientvm/home/user/xyz 44 | ``` 45 | 46 | You will get a permission dialog from dom0 asking for `ruddo.Git` access. 47 | Accept it. Note that accepting the permission dialog implicitly gives 48 | Git access to all Git repos stored in `servervm`, but only for that one 49 | execution (unless you say *Yes to all*, in which case the permission 50 | is remembered within the policy file that you installed earlier with the 51 | `dom0` package). 52 | 53 | This should have cloned `xyz` from `servervm` into your `/home/user/xyz` 54 | directory in `clientvm`. 55 | 56 | From this point on, you can push and pull in `clientvm` from 57 | `servervm:/home/user/xyz` to your heart's content. 58 | 59 | If, instead of cloning, you have an existing repo, you can add a new remote 60 | just as easily: 61 | 62 | ``` 63 | cd /home/user/existingxyz 64 | git remote add qubesremotevm qubes://servervm/home/user/xyz 65 | ``` 66 | 67 | That addition will enable to push and pull from the remote `qubesremotevm` 68 | which represents the repo `/home/user/xyz` in the remote VM `servervm`. 69 | 70 | ## Installing the software 71 | 72 | There are two components for this software: 73 | 74 | * Component 1 is the VM side of things, which implements the Git protocol 75 | across VMs. 76 | * Component 2 is the dom0 side of things, which is a simple text file declaring 77 | the initial Git access policy for your VMs. 78 | 79 | First, build the software, After cloning this repository on a suitable VM, 80 | run the command: 81 | 82 | ``` 83 | make rpm 84 | ``` 85 | 86 | This will generate two installable packages on the local directory: 87 | 88 | * `git-remote-qubes-.noarch.rpm`, which contains the Git 89 | protocol implementation. 90 | * `git-remote-qubes-dom0-.noarch.rpm`, which contains the 91 | default policy. 92 | 93 | Copy the `git-remote-qubes-.noarch.rpm` file to the template VM 94 | or standalone VM where you plan to pull or push to / from a Git repo 95 | stored in another Qubes VM. Install the RPM with 96 | `dnf install `. At this point, this VM, as well as 97 | any VMs using this as a template, have gained the ability to push and pull 98 | from Git repos stored in other VMs, as well as the ability to listen on 99 | push / pull requests from different VMs in the same system. 100 | 101 | Now copy the `git-remote-qubes-dom0-.noarch.rpm` file to 102 | your dom0. At this point, the default policy (`deny`) is active on 103 | your Qubes OS system, and you can begin pushing and pulling. 104 | 105 | Those clever among you will have discovered that there is a `Makefile` 106 | included, and that you can use the `Makefile` to install the software on 107 | other non-RPM templates. I welcome pull requests to add support for 108 | other distro packages and Qubes OS templates. 109 | 110 | ## Troubleshooting and debugging 111 | 112 | If you are experiencing problems communicating with a Git repo in a VM, 113 | export the variable `QUBES_DEBUG` on the side of your client (where your 114 | local Git repo is), and look at the debugging output that appears. 115 | 116 | As always, you can file new issues on the repo of this project for help 117 | with fixing bugs that the programs may have. Pull requests also welcome. 118 | -------------------------------------------------------------------------------- /bin/git-local-qubes: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 -u 2 | 3 | import sys 4 | 5 | import gitremotequbes.server 6 | 7 | 8 | sys.exit(gitremotequbes.server.main()) 9 | -------------------------------------------------------------------------------- /bin/git-remote-qubes: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 -u 2 | 3 | import sys 4 | 5 | import gitremotequbes.client 6 | 7 | 8 | sys.exit(gitremotequbes.client.main()) 9 | -------------------------------------------------------------------------------- /build.parameters: -------------------------------------------------------------------------------- 1 | ["RELEASE": "q4.2 38 39"] 2 | -------------------------------------------------------------------------------- /etc/qubes-rpc/ruddo.Git.in: -------------------------------------------------------------------------------- 1 | @LIBEXECDIR@/git-local-qubes 2 | -------------------------------------------------------------------------------- /etc/qubes/policy.d/80-git-remote-qubes.policy: -------------------------------------------------------------------------------- 1 | ## Default git-remote-qubes policy. 2 | 3 | ruddo.Git * @anyvm @anyvm ask 4 | -------------------------------------------------------------------------------- /git-remote-qubes.spec: -------------------------------------------------------------------------------- 1 | %define debug_package %{nil} 2 | 3 | %define mybuildnumber %{?build_number}%{?!build_number:1} 4 | 5 | %{!?python3_sitelib: %define python3_sitelib %(python3 -c "from distutils.sysconfig import get_python_lib ; print(get_python_lib(1))")} 6 | 7 | Name: git-remote-qubes 8 | Version: 0.1.0 9 | Release: %{mybuildnumber}%{?dist} 10 | Summary: Inter-VM git push and pull for Qubes OS AppVMs and StandaloneVMs 11 | BuildArch: noarch 12 | 13 | License: GPLv3+ 14 | URL: https://github.com/Rudd-O/git-remote-qubes 15 | Source0: https://github.com/Rudd-O/%{name}/archive/{%version}.tar.gz#/%{name}-%{version}.tar.gz 16 | 17 | BuildRequires: make 18 | BuildRequires: sed 19 | BuildRequires: python3 20 | BuildRequires: git 21 | 22 | Requires: python3 23 | Requires: git-core 24 | # systemd is required because of systemd-escape. 25 | Requires: systemd 26 | 27 | %package dom0 28 | Summary: Policy package for Qubes OS dom0s that arbitrates %{name} 29 | Requires: qubes-core-dom0 >= 4.1 30 | 31 | Requires: systemd qubes-core-dom0-linux 32 | 33 | %description 34 | This package lets you setup Git servers on your Qubes OS VMs. 35 | You are meant to install this package on TemplateVMs that are the templates 36 | for AppVMs where you want to either serve git repos from, or push/pull git 37 | repos from, as well as StandaloneVMs where you wish to do the same things. 38 | 39 | %description dom0 40 | This package contains the Qubes OS execution policy for the %{name} package. 41 | You are meant to install this package on the dom0 of the machine where you 42 | have VMs that have the %{name} package installed. 43 | 44 | %prep 45 | %setup -q 46 | 47 | %build 48 | # variables must be kept in sync with install 49 | make DESTDIR=$RPM_BUILD_ROOT BINDIR=%{_bindir} SYSCONFDIR=%{_sysconfdir} SITELIBDIR=%{python3_sitelib} LIBEXECDIR=%{_libexecdir} 50 | 51 | %install 52 | rm -rf $RPM_BUILD_ROOT 53 | # variables must be kept in sync with build 54 | for target in install-vm install-dom0; do 55 | make $target DESTDIR=$RPM_BUILD_ROOT BINDIR=%{_bindir} SYSCONFDIR=%{_sysconfdir} SITELIBDIR=%{python3_sitelib} LIBEXECDIR=%{_libexecdir} 56 | done 57 | 58 | %check 59 | if grep -r --exclude='*.pyc' --exclude='*.pyo' --exclude='*.policy' '@.*@' $RPM_BUILD_ROOT ; then 60 | echo "Check failed: files with AT identifiers appeared" >&2 61 | exit 1 62 | fi 63 | 64 | %files 65 | %attr(0755, root, root) %{_libexecdir}/git-local-qubes 66 | %attr(0755, root, root) %{_libexecdir}/git-core/git-remote-qubes 67 | %attr(0644, root, root) %{python3_sitelib}/gitremotequbes/*.py 68 | %attr(0644, root, root) %{python3_sitelib}/gitremotequbes/*.pyc 69 | %attr(0644, root, root) %{python3_sitelib}/gitremotequbes/__pycache__/*.pyc 70 | %attr(0755, root, root) %{_sysconfdir}/qubes-rpc/ruddo.Git 71 | %doc README.md 72 | 73 | %files dom0 74 | %config(noreplace) %attr(0664, root, qubes) %{_sysconfdir}/qubes/policy.d/80-git-remote-qubes.policy 75 | %doc README.md 76 | 77 | %changelog 78 | * Mon Oct 24 2016 Manuel Amador (Rudd-O) 79 | - Initial release. 80 | -------------------------------------------------------------------------------- /src/gitremotequbes/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Rudd-O/git-remote-qubes/89bf26f3c56fe99899596a4c135bad43a66b3b6b/src/gitremotequbes/__init__.py -------------------------------------------------------------------------------- /src/gitremotequbes/client.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import logging 3 | import os 4 | import pipes 5 | import subprocess 6 | import sys 7 | import urllib.parse 8 | 9 | import gitremotequbes.copier 10 | 11 | 12 | def get_main_parser(): 13 | p = argparse.ArgumentParser() 14 | p.add_argument("name", metavar="NAME") 15 | p.add_argument("url", metavar="URL") 16 | return p 17 | 18 | 19 | def main(): 20 | logging.basicConfig( 21 | format="local:" + logging.BASIC_FORMAT, 22 | level=logging.DEBUG if os.getenv("QUBES_DEBUG") else logging.INFO, 23 | ) 24 | 25 | p = get_main_parser() 26 | args = p.parse_args() 27 | url = urllib.parse.urlparse(args.url) 28 | assert url.scheme == "qubes" 29 | 30 | l = logging.getLogger() 31 | 32 | rpcarg = subprocess.check_output([ 33 | "systemd-escape", "--", url.path 34 | ], universal_newlines=True)[:-1] 35 | if len(rpcarg) > 64 or "\\" in rpcarg: 36 | # Path is too long! We must do without rpcarg. 37 | rpcarg = None 38 | 39 | vm = subprocess.Popen( 40 | ["/usr/lib/qubes/qrexec-client-vm", 41 | url.netloc, 42 | "ruddo.Git" + ("+%s" % rpcarg if rpcarg else "")], 43 | stdin=subprocess.PIPE, 44 | stdout=subprocess.PIPE, 45 | bufsize=0, 46 | ) 47 | 48 | cmd = sys.stdin.readline() 49 | assert cmd == "capabilities\n" 50 | sys.stdout.write("connect\n\n") 51 | sys.stdout.flush() 52 | 53 | remoteargs = [args.name, url.path] 54 | if os.getenv("QUBES_DEBUG"): 55 | remoteargs = ["-d"] + remoteargs 56 | quotedargs = " ".join(pipes.quote(x) for x in remoteargs) 57 | quotedlen = len(quotedargs) 58 | vm.stdin.write(("%s\n" % quotedlen + quotedargs).encode("utf-8")) 59 | vm.stdin.flush() 60 | 61 | line = vm.stdout.readline() 62 | if line != b"confirmed\n": 63 | l.debug("the request appears to have been refused or it malfunctioned") 64 | return 128 65 | 66 | ret = 0 67 | while ret == 0: 68 | for f in sys.stdin.buffer, vm.stdin, sys.stdout.buffer, vm.stdout: 69 | gitremotequbes.copier.b(f) 70 | cmd = sys.stdin.readline() 71 | 72 | if not cmd: 73 | l.debug("no more commands, exiting") 74 | break 75 | elif cmd.startswith("connect "): 76 | l.debug("asked to run %s", cmd) 77 | vm.stdin.write(cmd.encode("utf-8")) 78 | reply = vm.stdout.readline().decode("utf-8") 79 | assert reply == "\n", "local: wrong reply %r" % reply 80 | sys.stdout.write(reply) 81 | sys.stdout.flush() 82 | 83 | ret = gitremotequbes.copier.call( 84 | vm, 85 | sys.stdin.buffer, 86 | sys.stdout.buffer, 87 | ) 88 | if ret != 0: 89 | l.debug("remote side exited with %s", ret) 90 | else: 91 | l.debug("remote side exited normally") 92 | break 93 | elif cmd == "\n": 94 | l.debug("git sent us an empty line as command") 95 | else: 96 | l.error("invalid command %r", cmd) 97 | ret = 127 98 | 99 | return ret 100 | -------------------------------------------------------------------------------- /src/gitremotequbes/copier.py: -------------------------------------------------------------------------------- 1 | import fcntl 2 | import logging 3 | import os 4 | import select 5 | import subprocess 6 | import threading 7 | 8 | 9 | def nb(f): 10 | fd = f.fileno() 11 | fl = fcntl.fcntl(fd, fcntl.F_GETFL) 12 | fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) 13 | 14 | 15 | def b(f): 16 | fd = f.fileno() 17 | fl = fcntl.fcntl(fd, fcntl.F_GETFL) 18 | fcntl.fcntl(fd, fcntl.F_SETFL, fl & (~os.O_NONBLOCK)) 19 | 20 | 21 | class Copy(threading.Thread): 22 | """Copy from the keys of the dictionary allfds to the values of the 23 | allfds dictionary. 24 | 25 | Side effects: fds passed will be set to nonblocking. 26 | """ 27 | 28 | def __init__(self, allfds): 29 | threading.Thread.__init__(self) 30 | self.l = logging.getLogger("copy") 31 | self.setDaemon(True) 32 | self.allfds = allfds 33 | for readable in allfds: 34 | nb(readable) 35 | self.enders = {} 36 | for r in self.allfds: 37 | pr, pw = os.pipe() 38 | pr = os.fdopen(pr, "rb") 39 | pw = os.fdopen(pw, "ab") 40 | nb(pr) 41 | self.enders[r] = [pr, pw] 42 | 43 | def fdname(self, f): 44 | return "[%s %r]" % (f.name, f.mode) 45 | 46 | def run(self): 47 | fdname = self.fdname 48 | l = self.l 49 | 50 | def copier(readable, writable): 51 | l.debug("beginning to copy from %s to %s", 52 | fdname(readable), fdname(writable)) 53 | stop = False 54 | readables = [readable, self.enders[readable][0]] 55 | while True: 56 | r, _, _ = select.select( 57 | readables, 58 | [], 59 | [], 60 | 0 if stop else None 61 | ) 62 | if self.enders[readable][0] in r: 63 | l.debug("signaled to stop copying from %s to %s", 64 | fdname(readable), fdname(writable)) 65 | self.enders[readable][0].close() 66 | readables.remove(self.enders[readable][0]) 67 | stop = True 68 | continue 69 | elif stop and not r: 70 | break 71 | chunk = r[0].read() 72 | if type(chunk) is str: 73 | chunk = str.encode("utf-8") 74 | if chunk == '' or chunk == b"": 75 | l.debug("%s closed", fdname(readable)) 76 | readable.close() 77 | l.debug("closing write end %s", 78 | fdname(self.allfds[readable])) 79 | self.allfds[readable].close() 80 | self.enders[readable][0].close() 81 | break 82 | l.debug("copying from %s to %s: %r", 83 | fdname(readable), fdname(writable), chunk) 84 | writable.write(chunk) 85 | writable.flush() 86 | l.debug("done copying data from %s to %s", 87 | fdname(readable), fdname(writable)) 88 | 89 | self.l.debug("beginning to copy") 90 | 91 | threads = [] 92 | for readable, writable in list(self.allfds.items()): 93 | threads.append(threading.Thread(target=copier, 94 | args=(readable, writable))) 95 | threads[-1].setDaemon(True) 96 | threads[-1].start() 97 | 98 | for t in threads: 99 | t.join() 100 | 101 | self.l.debug("done copying") 102 | 103 | def end(self): 104 | for readable, (_, wp) in list(self.enders.items()): 105 | self.l.debug("ending copy of data from %s", 106 | self.fdname(readable)) 107 | wp.close() 108 | 109 | 110 | def call(cmd, stdin, stdout, env=None): 111 | """call() runs (or adopts) a subprocess, copying data from stdin into 112 | the process' stdin, and stdout from the process into stdout. 113 | """ 114 | if env is None: 115 | env = os.environ 116 | 117 | l = logging.getLogger("call") 118 | 119 | if isinstance(cmd, subprocess.Popen): 120 | p = cmd 121 | l.debug("adopting command %s", cmd) 122 | else: 123 | l.debug("running command %s", cmd) 124 | p = subprocess.Popen(list(cmd), 125 | env=env, 126 | stdin=subprocess.PIPE, 127 | stdout=subprocess.PIPE) 128 | 129 | copier = Copy({p.stdout: stdout, stdin: p.stdin}) 130 | copier.start() 131 | 132 | l.debug("waiting for %s to end", cmd) 133 | ret = p.wait() 134 | l.debug("%s ended, signaling copier to end", cmd) 135 | copier.end() 136 | l.debug("copier signaled, waiting for readers and writers") 137 | copier.join() 138 | l.debug("readers and writers done") 139 | 140 | return ret 141 | -------------------------------------------------------------------------------- /src/gitremotequbes/server.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import shlex 4 | import signal 5 | import subprocess 6 | import sys 7 | 8 | import gitremotequbes.copier 9 | 10 | 11 | def main(): 12 | quotedlen = sys.stdin.readline() 13 | if not quotedlen: 14 | logging.basicConfig(format="remote:" + logging.BASIC_FORMAT, level=logging.INFO) 15 | l = logging.getLogger() 16 | logging.error("Peer disconnected early.") 17 | return 8 18 | quotedlen = int(quotedlen, 10) 19 | if quotedlen > 65535 or quotedlen < 1: 20 | assert 0, "invalid len" 21 | args = sys.stdin.read(quotedlen) 22 | if len(args) != quotedlen: 23 | assert 0, "invalid argument list" 24 | try: 25 | args = shlex.split(args) 26 | except Exception as e: 27 | assert 0, "invalid argument list: %s" % e 28 | if args[0] == "-d": 29 | args = args[1:] 30 | level = logging.DEBUG 31 | else: 32 | level = logging.INFO 33 | git_dir = args[1] 34 | 35 | logging.basicConfig(format="remote:" + logging.BASIC_FORMAT, level=level) 36 | l = logging.getLogger() 37 | 38 | trustedarg = os.getenv("QREXEC_SERVICE_ARGUMENT") 39 | if trustedarg: 40 | # Qubes OS subsystem has sent us an argument, and that argument 41 | # is trusted, so trust that over whatever the remote process said. 42 | l.debug("trustworthy argument %r sent by Qubes OS", trustedarg) 43 | git_dir = subprocess.check_output([ 44 | "systemd-escape", "--unescape", "--", trustedarg 45 | ], universal_newlines=True)[:-1] 46 | 47 | sys.stdout.write("confirmed\n") 48 | 49 | while True: 50 | for f in sys.stdin, sys.stdout: 51 | gitremotequbes.copier.b(f) 52 | cmd = sys.stdin.readline() 53 | 54 | if not cmd: 55 | l.debug("no more commands, exiting") 56 | break 57 | if cmd.startswith("connect "): 58 | cmd = cmd[8:-1] 59 | assert cmd in ("git-upload-pack", "git-receive-pack"), \ 60 | "remote: bad command %r" % cmd 61 | sys.stdout.write("\n") 62 | # And here we go. We no longer are in control. Child is. 63 | os.execvp("git", ["git", cmd[4:], git_dir]) 64 | else: 65 | assert 0, "invalid command %r" % cmd 66 | --------------------------------------------------------------------------------