1
+ #!/usr/bin/env node
2
+ const { spawn, spawnSync } = require ( 'child_process' )
3
+ var crypto = require ( 'crypto' )
4
+ var fs = require ( 'fs' )
5
+ var path = require ( 'path' )
6
+ var inspect = require ( 'util' ) . inspect
7
+
8
+ var buffersEqual = require ( 'buffer-equal-constant-time' )
9
+ var fixturez = require ( 'fixturez' )
10
+ var ssh2 = require ( 'ssh2' )
11
+
12
+ var config = {
13
+ root : path . resolve ( process . cwd ( ) , process . env . GIT_SSH_MOCK_SERVER_ROOT || '.' ) ,
14
+ glob : '*' ,
15
+ route : process . env . GIT_SSH_MOCK_SERVER_ROUTE || '/'
16
+ }
17
+
18
+ new Promise ( ( resolve , reject ) => {
19
+ try {
20
+ let key = fs . readFileSync ( path . join ( __dirname , 'id_rsa' ) )
21
+ let pubKey = fs . readFileSync ( path . join ( __dirname , 'id_rsa.pub' ) )
22
+ return resolve ( { key, pubKey} )
23
+ } catch ( err ) {
24
+ let proc = spawnSync ( 'ssh-keygen' , [ '-C' , '"git-ssh-mock-server@localhost"' , '-N' , '""' , '-f' , 'id_rsa' ] , {
25
+ cwd : __dirname ,
26
+ shell : true
27
+ } )
28
+ console . log ( proc . stdout . toString ( 'utf8' ) )
29
+ let key = fs . readFileSync ( path . join ( __dirname , 'id_rsa' ) )
30
+ let pubKey = fs . readFileSync ( path . join ( __dirname , 'id_rsa.pub' ) )
31
+ return resolve ( { key, pubKey} )
32
+ }
33
+ } )
34
+ . then ( keypair => {
35
+ var pubKey = ssh2 . utils . genPublicKey ( ssh2 . utils . parseKey ( keypair . pubKey ) )
36
+ var f = fixturez ( config . root , { root : process . cwd ( ) , glob : config . glob } )
37
+
38
+ new ssh2 . Server ( { hostKeys : [ keypair . key ] } , function ( client ) {
39
+ console . log ( 'client connected' )
40
+
41
+ client
42
+ . on ( 'authentication' , function ( ctx ) {
43
+ if (
44
+ ctx . method === 'password' &&
45
+ // Note: Don't do this in production code, see
46
+ // https://www.brendanlong.com/timing-attacks-and-usernames.html
47
+ // In node v6.0.0+, you can use `crypto.timingSafeEqual()` to safely
48
+ // compare two values.
49
+ ctx . username === 'foo' &&
50
+ ctx . password === 'bar'
51
+ ) { ctx . accept ( ) } else if (
52
+ ctx . method === 'publickey' &&
53
+ ctx . key . algo === pubKey . fulltype &&
54
+ buffersEqual ( ctx . key . data , pubKey . public )
55
+ ) {
56
+ if ( ctx . signature ) {
57
+ var verifier = crypto . createVerify ( ctx . sigAlgo )
58
+ verifier . update ( ctx . blob )
59
+ if ( verifier . verify ( pubKey . publicOrig , ctx . signature ) ) ctx . accept ( )
60
+ else ctx . reject ( )
61
+ } else {
62
+ // if no signature present, that means the client is just checking
63
+ // the validity of the given public key
64
+ ctx . accept ( )
65
+ }
66
+ } else ctx . reject ( )
67
+ } )
68
+ . on ( 'ready' , function ( ) {
69
+ console . log ( 'client authenticated' )
70
+
71
+ client . on ( 'session' , function ( accept , reject ) {
72
+ var session = accept ( )
73
+ session . once ( 'exec' , function ( accept , reject , info ) {
74
+ console . log ( info . command )
75
+ let [ _ , command , gitdir ] = info . command . match ( / ^ ( [ a - z - ] + ) ' ( .* ) ' / )
76
+ // Only allow these two commands to be executed
77
+ if ( command !== 'git-upload-pack' && command !== 'git-receive-pack' ) {
78
+ return reject ( )
79
+ }
80
+ if ( gitdir !== path . posix . normalize ( gitdir ) ) {
81
+ // something fishy about this filepath
82
+ console . log ( 'suspicious file path:' , gitdir )
83
+ return reject ( )
84
+ }
85
+
86
+ // Do copy-on-write trick for git push
87
+ let fixtureName = path . posix . basename ( gitdir )
88
+ let fulldir
89
+ if ( command === 'git-upload-pack' ) {
90
+ fulldir = f . find ( fixtureName )
91
+ } else if ( command === 'git-receive-pack' ) {
92
+ fulldir = f . copy ( fixtureName )
93
+ }
94
+
95
+ try {
96
+ fs . accessSync ( fulldir )
97
+ } catch ( err ) {
98
+ console . log ( fulldir + ' does not exist.' )
99
+ return reject ( )
100
+ }
101
+
102
+ var stream = accept ( )
103
+ console . log ( 'exec:' , command , gitdir )
104
+ console . log ( 'actual:' , command , fulldir )
105
+ let proc = spawn ( command , [ fulldir ] )
106
+ stream . exit ( 0 ) // always set a successful exit code
107
+ stream . pipe ( proc . stdin )
108
+ proc . stdout . pipe ( stream )
109
+ proc . stderr . pipe ( stream . stderr )
110
+ } )
111
+ } )
112
+ } )
113
+ . on ( 'end' , function ( ) {
114
+ console . log ( 'Client disconnected' )
115
+ } )
116
+ }
117
+ ) . listen ( process . env . GIT_SSH_MOCK_SERVER_PORT || 2222 , '127.0.0.1' , function ( ) {
118
+ console . log ( 'Listening on port ' + this . address ( ) . port )
119
+ } )
120
+
121
+ } )
0 commit comments