Permalink
Browse files

Play with git servers.

This commit contains a private key, but it's okay, because it's used
for local testing only.
  • Loading branch information...
dmitshur committed Sep 14, 2017
1 parent 55cedbb commit 9d46e01e4536bad4544359f90d86588318d260bb
Showing with 521 additions and 0 deletions.
  1. +99 −0 231/gopkgtest_rsa
  2. +1 −0 231/gopkgtest_rsa.pub
  3. +48 −0 231/main.go
  4. +223 −0 231/sshgit/sshgit.go
  5. +150 −0 232/main.go
View
@@ -0,0 +1,99 @@
-----BEGIN RSA PRIVATE KEY-----
MIISKQIBAAKCBAEA386eyr/vaWRfOotYqCSBRzdUlCKQU3hVxOw7f8r6OlWO/+g8
FITPGUgmWGyfaKy580p1iRcVWgPNPWRrNr7ZUoVto2hq8YpmuKGL+sEleD0r1T3m
4cMF0GqPA6I06i9EReogz5CrqxT7QyP3uA/vW4WJ938IrV7X0mBW0RsnpQJojjtH
x6/xspTh7enRVU9i9tNvFxcJ75BMWwyFqpSPla2taqBl+IPsQj7HoYawPF0ZM2Ay
tAoZqXj4KA/nhTiB7TyAmPehvI7/lf8I8uEkE28KBxPBoN1+mmVM48chst2a0THD
w7mmIOalqKCVQ8Qoy4CUMH+XpXbJ7k9/9BIrEvqSjZkA+DSAA3DdySWiqjRAiGCd
EIXUFx9Z/NscgC+ZKH8BogL2MAxFCoxzXvByykBRQ0GWi1fPImh8gkK6MjKNwWAT
ycqp5PGfvuL+GNJdHoV7XzibKzXeKef3nVG5GVHVgDVX3VzD0715znviM4yVFkga
IRMH3W28VLLsGArNFKFwjPGNjQ0bk93txRaYMhuC9UI/lMGArMqHfy/LtaKjLKsj
+/FdTHTOgs3sdh4NCJoS2/63ublXcTVSULEatt+Q0MGAzFJcFcRBHgS6fIFC5fQW
amUOmtMk1hmSDSa5JPEDvfqId7Eav5lwlut0WLZJ5lDRtVtX7cFKbO4dfrPXIVRQ
rMAYo8TZd1XvPS5qdAvecrhPsxDnEtFnmxVRgKhrfrmVc2OklMJ18z45qf/BN+HU
S0Od2VKQiA6pl87cvqJNov2RNYfDtGs+o9kuzHCYk01YJTcihi8AVaiDo3qMeVDm
6OIOW/KfQL0PY8X6INi5AW2yOE7QO8AxyF+xaWHfTlSmW3AhD9SHKRQGQf5iHtS4
zN0+b0yprXJCMdqy2qrNJ7mSlxiv6NvGv0yj1l0HU1fPN9dz7o/57IDjkIXfEAvt
BWlJkTwDshXZri50J4IHGTJUJ8BX3VD0tKI2PjOsGXs0EekgfqcDr+5Fi0D6ugwN
w1ahNjn4at0lGqdVh4isZEGCrtAt3R+qvX64kuRWma0sLUNPiyas/9NPL2Kvg4Jk
WyeQK857oKSD/vdp+fgA/6q7o1l61cDSUXV2Qxh0aOxvMr8E9/CCmdY2Fq4y/1k6
fOJKziFVVFh5iDBOhrFbN0Xr2chMKHrYx+b3XdTH0QPis5UDNUoI7ogQxNeEDWqF
tDbxx+TJIegFRvYrVPLxRNb+HvjrjTTarN8PQ3jdYLJUEVtDxDhT3BUoQ3Jz4gYE
AA6D+g0MwlRtluH0SHug89VJzeM41Ec6wNAzuA1IHhcyCYbR1lQlUuAJaN1xqvBf
js0nTcU1SyVrMU1oxVd5WQZmGRowi2vksS1RIQIDAQABAoIEACAm58sKzdMfN0sK
z77AF85qVX0+hetn4zJ6GYYfW9lbh0KeU1tDRRlFszSKzk/UWJZqAP6j0U5wM2D5
dymTO1dOkdSi4JI4ps8pbhmk+8QNTanBzdRrFv+8/zSvbdii17UPkI0hJHhceEhJ
94F60JEeWxayKkX11R2E95PsycI72Vdtjr9SOG/GUbrDF26hC/+XmmL/zU6cNUvz
PZSm6QXAwKQLzPppwVw29qju14ykd0VZMY+Jz+v30/a1ajZKSugRBAx1rseO54rd
66Qw0eT008T2gp+gklqN6QDkD6RJj78z3gLVPdeGKxBRmX/1w0KRAzkAa4G60gGp
bCFdJtEALGOpMml16VoVMdd+z6KSBR9Sed3wy+pSd7QKqkqEDAW0UUC8GjAG4zhD
uyNNecAygzodRwpXoAs6ZYC1I1ioyJKaQd2Afu3EBIYAyC46smP7GyEv2aLYh0UP
7ROttOC9T+WuRCQp+nwFwWT3iSSMsrJTw2Uy/bNC24ZzedVuX83x72BgJ0y3MFoK
zOi+eTjhmOuxC79MGTc5lUOhUyuR5M06/N1p7FRah3xlJu5FHOw6bjEqo6xLwxMh
bOTovIS/E2faFK3FuzLpUc2FMmFGlJzCdyHryLBXWvhjDgdTo895zGbTgh1n1Qbn
fTRBOSKxdWZf//CE4dnnJdEGWDhmSteklY6doMo8tJ0Y+T00tl7IgLC3U3iqqWbN
Wa0vtoSZXAVUPSJa33CgViyWLf7LdkX+jeR2NpGBqDyOUGMPFqgPfnNllfJnBaPo
8EIt5oJzg2NNrWckMsKLqa2lvlIprFkiVphCzsy5BncsSvL2tU4vSKoKmk1CyjWb
CwhwO6Kv22tcFUR2oW2dRQ8A6O4bv+umwZt4AZSJ+zrb/dUQzpdbWfWxvxc6bTji
xvsrYvLkKXMZU+VRj5dAsdmtBPvDz+YFY+EFkjTkn3+84qP0/ZgnPNimAD3lXMIU
Jx38uw7sw2vDyW/Y8PTb9Ls4Sa306sK4ElNzucmAemw/XjNrfCmZKrxDWlIjOFZ1
rKHIclYpAeQgr2AHnc29CoV1GZiWwYeoTJB9yukRZvHb5DjN0fiJSPNSqOkbtO1I
xV+kyFBjY3VVVAefaMdcBpl/jAtPhIbMesrlvVa8qrKVJ/JQo6dWqZW0R96t1n82
pvQPWOnou9vdlZn+y9/4JUq19WulLjjCzjvcqLCuzCIqOLvwOvCHEkcKlU0Gp5n5
2MxZ6yyKQ2LZCFI4W3ghmeT+8Sv+sxMBE4D9eB+cTYKTDqHaxibbJScFVCbAH3GL
NWa2xnY6qktuinbiutzyieD4+7W2UOcH0pEGb3W47End/t0rwXWKVbf04enNhbNW
vY6PHY0CggIBAPcDgu4FZggJ4QZo69tkgqPNk7/l7DFKKxiCht+ml0YvY0KelKzR
ymKjBDxwUlOrA52Ucj757KpjKyDHz7xGKcaIRyZVSC70Yjji4cOIekLZ70G8ruc1
6M/YnRxVYlaQBTssCVM8cHEPWMV0tKuvE/gYJN7ahCUVEx3MTU+V8H0dpYVKqGkD
45caNKSlzdBSLTLg9rgCiTKhlxWPrzzzsQO45DeMg0Th+l6XFqheg7S5YcEH7VrW
gk4M4c+4yxJk3ljwGGNp1jZzNl/+dkA/TNKtCSle0dMrYab18ejFEMfQnPk1uKdV
0hlUXLy4FlWVi6XKz/EW/XgJDL1BMNIqds8kqbo0kwF7J4rMWVHFH2ZEw53rbbOf
2N6E/BoTk02IPeBlJ2RLuWIquI2ww5s6gWsvTubi0H5Ol6rnYIQh+g1pWALWdTJi
GE9ndswbMVuZf2Hduzm4rXCAbYLAbB130MFwwczvWGB01It82QinY5lQM6A1JRZh
tEpBQoYbew2YBrEccShpZU6iWxVY/+UNNugUR4htb3LgCdtWuyVHmJVY0ghWUI29
14zjsYzJihqPD+DhdIy7vys4FPiZySmVKjs1grCCpaalSrrYA/g70LAL3VFbhmqI
AWP5NguJwkCshsK0Vvv8y486XG2bXjW2b2ESKOS4f0pLWwYj1zVZo6yLAoICAQDn
8vslD7tQlZuHTvnoLF16ecxlwaDadeQlesNMh2STGV6X4FPTwoa/D+dQrhA8i2mG
fcEamxri1Bd0lclGTcVXtedAyL7ITSN5f/n9wTq9R0/wK+MUDnM5Lh2k/eycZafg
2LjHZrBXdY4LfwIa6NRCxyE+h/LQYkI1wWT/vXrr46np2NV4hWwhbpOobRZpTKeb
91frP81sgYF80XV0NNGtYyTDo4pbDw0RCD8ZfDXUUqcjLzKvzSiH084qWuU/MJTa
ZKd87PiZLogJARRDn6vjFeYCaUxwrJpwNIpwBVMi5t0j7x64d50VT0yLkLxsUr0P
xXl65tx1P+5XVvp8I0oRfcfaI1WvJAJoV6kWgPnes4MtaA2d5JbZLox0BU2Lwz/V
VYC1J911ypZL0bqDi37PqRmvbV/nCMUFD9spfnd30/fcw+d4f/bwHLP01CCcTs66
xaGMhCF8kHuGrpL4NQKkOby9EOzEfu1bp+AvDrHyGXCZOOlV3JEHux5mAZgrrDb7
nr+xdNTUrb/z38qnacRL2fhdSEbTAzlwnRuikwSGTZYqlE8JJ2uF66w37LK/UND+
W+XFMYWdvPtIsa2hB0DmTV6vuU9PTOo40kox6HvhLl9srulCsfg8JAwNBPCFM9BK
YCkoKbPsomv2BnRI47mM3yej0Zcui+769fBm3ubSgwKCAgEAucM0RdhdgRIc3nst
VNLjxFgoExrQY8kJW+ddkvhvr2KX3VPxL36eG0vJNcKqxy1HDMzElkPSDdmCZ6ac
vVRuulVmXJeqL1WuQGWvryMF/xxyaBeD2XGbYiPfE53K1Jp+tJ9LbAN59Cy8b9Jy
xOJnvB48LDRdSJ3uwGwSgBWcxBr1jaY1vLQmBk645FGk1FPYlfjE0mFl2GPZgWeL
O+VwbAIx7uyHE9h2N4ici4Z4ugLoVwilIblMSXI6p8KrRWwCIxR84taHOG+wpsRx
HaP9HDrxNuGOHqbzS2KOyKTbF8n1Ix2Zfo518IKXM32lnjtWyBocA41slUcJA5Lx
KjyxGNrToatEGssJU8KWMX5zPK/eBPc2iu1dZQrRpLKwBpC9J/Iqi6y+/+ps3HHg
yO5YwcjUJ6LPdQ2Z1fcniHcc29UrUQcDIB7RdXWzy49vsRlgyLK9yIzwMjkDIl+f
bxHaNn+b34joB7IU3cnIVk+8AWJlUMhm2cwkj+tAIiashe6cqblgCCoq+9Xxdb52
w0UK08Hcgeb694MxJni1STXss/QWEBsJfW8VmWjq75vpdku8fzxOMEBJZjFI5CST
Oehp2BlH7YJzHT4VUyp84cPqD1r81+Bi197sXMjgMhEpuwWEWwzQlbTpSQ90zyEW
2oIWhQPpKQjvAWpmS9nMUNzW0D0CggIAIv/OF6SNDjbU9hDbssXnAREZ34zdGbnK
SEGC8KxJZ8zfMimeXIiwzmm27LynJIugy7N0DPC0mN+J/oSPj3Uyvu5qy6K+7vh8
ztuZvEmI9ICrNkFZDXC0wD8OKBRMvLi2IT5ye1wqZbgOS/vJNIhHZyAnCwl2gS0i
cnwRImnT/34l6GxYocv4Kf9GdpanWBJCBE3JTW0eMS5FfLEaeQNzVCdup7qoqN7X
iOdmphDRCo3CPJPloRNi90PliwyWJPfOrjmb8ETNqkeULU+lYCLay/RE9b4v05yn
3SCAdzDGf7C4U7DEQO+Dm3CQy0p2tB2z+4o/r2TghSy1aSZtu4eTqJoDKw5D4p6b
YmU/1W52xbzuoU5fJ6EThgf5F5SpQ9qdajJxrmQqjZiZHy2xaCIHa4xHnOnzG6h+
GipvVo6HtmQctYo2Bj/A4jBikyHjHdLibNpfaxWQ+/vNN+BluuA82sz/hsPkBi5k
4HyvXkpoXXCrReOJifQzSihZCOlardRZo5Qrtro7VzA0k7cEHEjgXtWwHVtHnJWS
+EQspFZsy1svupMQbgAtwFJ0dSfZPPcT4h25yQyU7d1TSX/OBEtU5qtsCzX6Gqwv
d417tpYvAyZ9Lagi+HZkT6bczqYdKOxNlLt32mCYjq9ffQsyRsFeATy2/YgluNdS
ozmzIcKmr8MCggIBAKmpvuXgVBrD4wwpPAYEKeabCWnkkDdxAJRJqy4gdEXPRBFS
+VyZdp5w9iEuWSqS5zx8XzGrFGNu3JNX0IGi2cPtkJIyPKn+MyFKGuDr86gJuQyX
ckEVgt1S3hdR0/k7+Y1R4XBW5slhxoLIIybj7+gzgPbSLRBnX1MUtrIsb8JqQtOS
SjhrbTPk6U+vZH1oYVqv/mZiPBpd1JzJSGi7frwjvz7qFfX5EK2pKkXKhEs27VXD
iZmbtP7P6/4MAbJjhoyCE6P6WQCSBtDMkSDFGpsXXMRrhFlneOFhWXmxQ+1ux9Qq
zYaUCM1lRJ5FLP2yrSBYKzXdFm7KyOInt7+olTkKircIgy+cB43UqRD8VF/tjy5n
vhkn4L8CpSPqj+SAnMKq0Y2zfzByWQ34iqa2uGld3FBwC4EoMO6TnFj3TzFPHMly
PsRt2seK9N8/o75fzSvUWA9U5lXUFTaGhazZgYeSFkDnKUkLwXklYW9OpBDv+mOU
h8IhhRXo9Rl3oquzoCNYsnP0rhAwSD4Zw6fiRujC04ZvIW+kGJhLAnJO8ZCACak2
uoigGMtL0IUWRnyaoJadishtAm+D3E8NN55NqniAZbdJS4Og0/IzegJVk03cvewK
DPnJvTmhmcM5Vsm872ldDLBE5DZrzVXwlcTeB68PMFDroqxUa2CfcPJCCR0E
-----END RSA PRIVATE KEY-----
View
@@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAEAQDfzp7Kv+9pZF86i1ioJIFHN1SUIpBTeFXE7Dt/yvo6VY7/6DwUhM8ZSCZYbJ9orLnzSnWJFxVaA809ZGs2vtlShW2jaGrxima4oYv6wSV4PSvVPebhwwXQao8DojTqL0RF6iDPkKurFPtDI/e4D+9bhYn3fwitXtfSYFbRGyelAmiOO0fHr/GylOHt6dFVT2L2028XFwnvkExbDIWqlI+Vra1qoGX4g+xCPsehhrA8XRkzYDK0ChmpePgoD+eFOIHtPICY96G8jv+V/wjy4SQTbwoHE8Gg3X6aZUzjxyGy3ZrRMcPDuaYg5qWooJVDxCjLgJQwf5eldsnuT3/0EisS+pKNmQD4NIADcN3JJaKqNECIYJ0QhdQXH1n82xyAL5kofwGiAvYwDEUKjHNe8HLKQFFDQZaLV88iaHyCQroyMo3BYBPJyqnk8Z++4v4Y0l0ehXtfOJsrNd4p5/edUbkZUdWANVfdXMPTvXnOe+IzjJUWSBohEwfdbbxUsuwYCs0UoXCM8Y2NDRuT3e3FFpgyG4L1Qj+UwYCsyod/L8u1oqMsqyP78V1MdM6Czex2Hg0ImhLb/re5uVdxNVJQsRq235DQwYDMUlwVxEEeBLp8gULl9BZqZQ6a0yTWGZINJrkk8QO9+oh3sRq/mXCW63RYtknmUNG1W1ftwUps7h1+s9chVFCswBijxNl3Ve89Lmp0C95yuE+zEOcS0WebFVGAqGt+uZVzY6SUwnXzPjmp/8E34dRLQ53ZUpCIDqmXzty+ok2i/ZE1h8O0az6j2S7McJiTTVglNyKGLwBVqIOjeox5UObo4g5b8p9AvQ9jxfog2LkBbbI4TtA7wDHIX7FpYd9OVKZbcCEP1IcpFAZB/mIe1LjM3T5vTKmtckIx2rLaqs0nuZKXGK/o28a/TKPWXQdTV88313Puj/nsgOOQhd8QC+0FaUmRPAOyFdmuLnQnggcZMlQnwFfdUPS0ojY+M6wZezQR6SB+pwOv7kWLQPq6DA3DVqE2Ofhq3SUap1WHiKxkQYKu0C3dH6q9friS5FaZrSwtQ0+LJqz/008vYq+DgmRbJ5ArznugpIP+92n5+AD/qrujWXrVwNJRdXZDGHRo7G8yvwT38IKZ1jYWrjL/WTp84krOIVVUWHmIME6GsVs3RevZyEwoetjH5vdd1MfRA+KzlQM1SgjuiBDE14QNaoW0NvHH5Mkh6AVG9itU8vFE1v4e+OuNNNqs3w9DeN1gslQRW0PEOFPcFShDcnPiBgQADoP6DQzCVG2W4fRIe6Dz1UnN4zjURzrA0DO4DUgeFzIJhtHWVCVS4Alo3XGq8F+OzSdNxTVLJWsxTWjFV3lZBmYZGjCLa+SxLVEh Dmitri@ShurBookPro.local
View
@@ -0,0 +1,48 @@
// Play with git server over SSH protocol.
package main
import (
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"github.com/shurcooL/play/231/sshgit"
"golang.org/x/crypto/ssh"
)
var (
keyFlag = flag.String("key", "gopkgtest_rsa", "Path to private key file to use. (Required.)")
)
func main() {
flag.Parse()
if *keyFlag == "" {
flag.Usage()
os.Exit(2)
}
err := run(*keyFlag)
if err != nil {
log.Fatalln(err)
}
}
func run(keyFile string) error {
privateBytes, err := ioutil.ReadFile(keyFile)
if err != nil {
return fmt.Errorf("failed to load private key: %v", err)
}
private, err := ssh.ParsePrivateKey(privateBytes)
if err != nil {
return fmt.Errorf("failed to parse private key: %v", err)
}
const addr = ":2022"
fmt.Printf("listening on %q\n", addr)
//server := sshgit.New(private, "/Users/Dmitri/Desktop/trygit")
server := sshgit.New(private, filepath.Join(os.Getenv("HOME"), "Dropbox", "Store", "repositories"))
return server.ListenAndServe(addr)
}
View
@@ -0,0 +1,223 @@
// Package sshgit provides functionality for a git server over SSH.
// It's hacky and incomplete, a proof of concept.
package sshgit
import (
"context"
"fmt"
"log"
"net"
"os/exec"
"path"
"path/filepath"
"strings"
"syscall"
"time"
"github.com/flynn/go-shlex"
"golang.org/x/crypto/ssh"
)
// Server is an SSH git server.
type Server struct {
config *ssh.ServerConfig
dir string // Path to root directory containing repositories.
}
// New creates a new SSH git server, using private as the SSH signer,
// and dir as root directory for git repositories.
func New(private ssh.Signer, dir string) *Server {
config := &ssh.ServerConfig{
PublicKeyCallback: func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
if c.User() != "git" {
return nil, fmt.Errorf("unsupported SSH user %q", c.User())
}
// TODO: Authentication. Lookup user via SSH public key, get user details and auththorizations, etc.
return &ssh.Permissions{
Extensions: map[string]string{
userLoginKey: "unknown", // TODO.
},
}, nil
},
}
config.AddHostKey(private)
return &Server{
config: config,
dir: dir,
}
}
// ListenAndServe listens on the TCP network address addr and starts the server.
func (s *Server) ListenAndServe(addr string) error {
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
// TODO: Wrap in tcpKeepAliveListener that enables HTTP keep-alives during Accept, if needed.
return s.serve(ln)
}
func (s *Server) serve(l net.Listener) error {
defer l.Close()
for {
tcpConn, err := l.Accept()
if err != nil {
log.Printf("failed to accept incoming connection: %v\n", err)
continue
}
tcpConn.SetDeadline(time.Now().Add(gitTransactionTimeout))
go s.handleConn(tcpConn)
}
}
func (s *Server) handleConn(tcpConn net.Conn) {
defer tcpConn.Close()
sshConn, chans, reqs, err := ssh.NewServerConn(tcpConn, s.config)
if err != nil {
log.Printf("failed to SSH handshake: %v\n", err)
return
}
go ssh.DiscardRequests(reqs)
for newChannel := range chans {
s.handleChannel(sshConn, newChannel)
}
}
func (s *Server) handleChannel(sshConn *ssh.ServerConn, newChannel ssh.NewChannel) {
if t := newChannel.ChannelType(); t != "session" {
newChannel.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %v", t))
return
}
ch, reqs, err := newChannel.Accept()
if err != nil {
return
}
defer ch.Close()
for req := range reqs {
fmt.Println("handleChannel: req.Type:", req.Type)
switch req.Type {
case "exec":
if req.WantReply {
req.Reply(true, nil)
}
err := s.handleExec(sshConn, ch, req)
if err != nil {
log.Println(err)
}
return
case "shell":
if req.WantReply {
req.Reply(true, nil)
}
err := s.handleShell(sshConn, ch, req)
if err != nil {
log.Println(err)
}
return
default:
if req.WantReply {
req.Reply(false, nil)
}
}
}
}
func (s *Server) handleShell(sshConn *ssh.ServerConn, ch ssh.Channel, req *ssh.Request) error {
_, err := fmt.Fprintf(ch, "Hello %v. You've successfully authenticated, but we don't provide shell access.\n",
sshConn.Permissions.Extensions[userLoginKey])
if err != nil {
return err
}
_, err = ch.SendRequest("exit-status", false, ssh.Marshal(statusMsg{Status: 0}))
if err != nil {
return fmt.Errorf("ch.SendRequest: %v", err)
}
return nil
}
func (s *Server) handleExec(sshConn *ssh.ServerConn, ch ssh.Channel, req *ssh.Request) error {
if len(req.Payload) < 4 {
return fmt.Errorf("invalid git transport protocol payload (less than 4 bytes): %q", req.Payload)
}
command := string(req.Payload[4:]) // E.g., "git-upload-pack '/user/repo'".
args, err := shlex.Split(command) // E.g., []string{"git-upload-pack", "/user/repo"}.
if err != nil || len(args) != 2 {
return fmt.Errorf("command %q is not a valid git command", command)
}
op := args[0] // E.g., "git-upload-pack".
repo := path.Clean("/" + args[1]) // E.g., "/user/repo".
repoDir := filepath.Join(s.dir, filepath.FromSlash(repo))
if repo == "" || !strings.HasPrefix(repoDir, s.dir) {
fmt.Fprintf(ch.Stderr(), "Specified repo %q lies outside of root.\n\n", repo)
return fmt.Errorf("specified repo %q lies outside of root", repo)
}
userLogin := sshConn.Permissions.Extensions[userLoginKey]
args = nil
switch op {
case "git-upload-pack":
args = []string{"--strict", "."}
// git-upload-pack uploads packs back to client. It happens when the client does
// git fetch or similar.
// TODO: Check for read access.
if false {
fmt.Fprintf(ch.Stderr(), "User %q doesn't have read permissions.\n\n", userLogin)
return fmt.Errorf("user %q doesn't have read permissions", userLogin)
}
case "git-receive-pack":
args = []string{"."}
// git-receive-pack receives packs and applies them to the repository. It happens
// when the client does git push or similar.
// TODO: Check for write access.
if true {
fmt.Fprintf(ch.Stderr(), "User %q doesn't have write permissions.\n\n", userLogin)
return fmt.Errorf("user %q doesn't have write permissions", userLogin)
}
default:
return fmt.Errorf("%q is not a supported git operation", op)
}
// Execute the git operation.
ctx, cancel := context.WithTimeout(context.Background(), gitTransactionTimeout)
defer cancel()
cmd := exec.CommandContext(ctx, op, args...) //"--timeout", "300",
cmd.Dir = repoDir
cmd.Stdin = ch //io.TeeReader(ch, os.Stdout)
cmd.Stdout = ch //io.MultiWriter(ch, os.Stdout)
cmd.Stderr = ch //io.MultiWriter(ch, os.Stderr)
err = cmd.Start()
if err != nil {
return fmt.Errorf("could not start command: %v", err)
}
commandError := cmd.Wait()
if commandError != nil {
log.Printf("command failed: %v\n", commandError)
}
_, err = ch.SendRequest("exit-status", false, ssh.Marshal(statusMsg{Status: exitStatus(commandError)}))
if err != nil {
return fmt.Errorf("ch.SendRequest: %v", err)
}
return nil
}
func exitStatus(err error) uint32 {
switch err := err.(type) {
case nil:
return 0
case *exec.ExitError:
return uint32(err.Sys().(syscall.WaitStatus).ExitStatus())
default:
return 1
}
}
type statusMsg struct {
Status uint32
}
const userLoginKey = "userLogin"
// gitTransactionTimeout is a timeout for a single git transaction.
const gitTransactionTimeout = 5 * time.Minute
Oops, something went wrong.

0 comments on commit 9d46e01

Please sign in to comment.