Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
镜像
Coder
code-server
提交
3463d561
C
code-server
项目概览
镜像
/
Coder
/
code-server
2022-09-21 03:15:05同步失败
通知
15
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
C
code-server
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
未验证
提交
3463d561
编写于
3月 16, 2020
作者:
W
Will O'Beirne
提交者:
Asher
3月 16, 2020
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
SSH server & endpoint
上级
5f63d2b8
变更
10
隐藏空白更改
内联
并排
Showing
10 changed file
with
526 addition
and
8 deletion
+526
-8
package.json
package.json
+6
-0
src/browser/pages/home.html
src/browser/pages/home.html
+4
-1
src/node/cli.ts
src/node/cli.ts
+5
-0
src/node/entry.ts
src/node/entry.ts
+23
-1
src/node/http.ts
src/node/http.ts
+1
-1
src/node/ssh/server.ts
src/node/ssh/server.ts
+110
-0
src/node/ssh/sftp.ts
src/node/ssh/sftp.ts
+201
-0
src/node/ssh/ssh.ts
src/node/ssh/ssh.ts
+122
-0
src/node/util.ts
src/node/util.ts
+6
-0
yarn.lock
yarn.lock
+48
-5
未找到文件。
package.json
浏览文件 @
3463d561
...
@@ -24,6 +24,10 @@
...
@@ -24,6 +24,10 @@
"
@types/safe-compare
"
:
"
^1.1.0
"
,
"
@types/safe-compare
"
:
"
^1.1.0
"
,
"
@types/semver
"
:
"
^7.1.0
"
,
"
@types/semver
"
:
"
^7.1.0
"
,
"@types/tar-fs"
:
"
^1.16.2
"
,
"@types/tar-fs"
:
"
^1.16.2
"
,
"
@types/ssh2
"
:
"
0.5.39
"
,
"
@types/ssh2-streams
"
:
"
^0.1.6
"
,
"
@types/tar-fs
"
:
"
^1.16.1
"
,
"
@types/tar-stream
"
:
"
^1.6.1
"
,
"
@types/ws
"
:
"
^6.0.4
"
,
"
@types/ws
"
:
"
^6.0.4
"
,
"
@typescript-eslint/eslint-plugin
"
:
"
^2.0.0
"
,
"
@typescript-eslint/eslint-plugin
"
:
"
^2.0.0
"
,
"
@typescript-eslint/parser
"
:
"
^2.0.0
"
,
"
@typescript-eslint/parser
"
:
"
^2.0.0
"
,
...
@@ -50,10 +54,12 @@
...
@@ -50,10 +54,12 @@
"
adm-zip
"
:
"
^0.4.14
"
,
"
adm-zip
"
:
"
^0.4.14
"
,
"
fs-extra
"
:
"
^8.1.0
"
,
"
fs-extra
"
:
"
^8.1.0
"
,
"
httpolyglot
"
:
"
^0.1.2
"
,
"
httpolyglot
"
:
"
^0.1.2
"
,
"
node-pty
"
:
"
^0.9.0
"
,
"
pem
"
:
"
^1.14.2
"
,
"
pem
"
:
"
^1.14.2
"
,
"
safe-compare
"
:
"
^1.1.4
"
,
"
safe-compare
"
:
"
^1.1.4
"
,
"
semver
"
:
"
^7.1.3
"
,
"
semver
"
:
"
^7.1.3
"
,
"
tar
"
:
"
^6.0.1
"
,
"
tar
"
:
"
^6.0.1
"
,
"
ssh2
"
:
"
^0.8.7
"
,
"
tar-fs
"
:
"
^2.0.0
"
,
"
tar-fs
"
:
"
^2.0.0
"
,
"
ws
"
:
"
^7.2.0
"
"
ws
"
:
"
^7.2.0
"
}
}
...
...
src/browser/pages/home.html
浏览文件 @
3463d561
...
@@ -6,7 +6,10 @@
...
@@ -6,7 +6,10 @@
name=
"viewport"
name=
"viewport"
content=
"width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"
content=
"width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"
/>
/>
<meta
http-equiv=
"Content-Security-Policy"
content=
"style-src 'self' 'unsafe-inline'; manifest-src 'self'; img-src 'self' data:;"
/>
<meta
http-equiv=
"Content-Security-Policy"
content=
"style-src 'self' 'unsafe-inline'; manifest-src 'self'; img-src 'self' data:;"
/>
<title>
code-server
</title>
<title>
code-server
</title>
<link
rel=
"icon"
href=
"{{BASE}}/static/{{COMMIT}}/src/browser/media/favicon.ico"
type=
"image/x-icon"
/>
<link
rel=
"icon"
href=
"{{BASE}}/static/{{COMMIT}}/src/browser/media/favicon.ico"
type=
"image/x-icon"
/>
<link
<link
...
...
src/node/cli.ts
浏览文件 @
3463d561
...
@@ -31,6 +31,8 @@ export interface Args extends VsArgs {
...
@@ -31,6 +31,8 @@ export interface Args extends VsArgs {
readonly
open
?:
boolean
readonly
open
?:
boolean
readonly
port
?:
number
readonly
port
?:
number
readonly
socket
?:
string
readonly
socket
?:
string
readonly
"
ssh-host-key
"
?:
string
readonly
"
disable-ssh
"
?:
boolean
readonly
version
?:
boolean
readonly
version
?:
boolean
readonly
force
?:
boolean
readonly
force
?:
boolean
readonly
"
list-extensions
"
?:
boolean
readonly
"
list-extensions
"
?:
boolean
...
@@ -96,6 +98,9 @@ const options: Options<Required<Args>> = {
...
@@ -96,6 +98,9 @@ const options: Options<Required<Args>> = {
version
:
{
type
:
"
boolean
"
,
short
:
"
v
"
,
description
:
"
Display version information.
"
},
version
:
{
type
:
"
boolean
"
,
short
:
"
v
"
,
description
:
"
Display version information.
"
},
_
:
{
type
:
"
string[]
"
},
_
:
{
type
:
"
string[]
"
},
"
disable-ssh
"
:
{
type
:
"
boolean
"
},
"
ssh-host-key
"
:
{
type
:
"
string
"
,
path
:
true
},
"
user-data-dir
"
:
{
type
:
"
string
"
,
path
:
true
,
description
:
"
Path to the user data directory.
"
},
"
user-data-dir
"
:
{
type
:
"
string
"
,
path
:
true
,
description
:
"
Path to the user data directory.
"
},
"
extensions-dir
"
:
{
type
:
"
string
"
,
path
:
true
,
description
:
"
Path to the extensions directory.
"
},
"
extensions-dir
"
:
{
type
:
"
string
"
,
path
:
true
,
description
:
"
Path to the extensions directory.
"
},
"
builtin-extensions-dir
"
:
{
type
:
"
string
"
,
path
:
true
},
"
builtin-extensions-dir
"
:
{
type
:
"
string
"
,
path
:
true
},
...
...
src/node/entry.ts
浏览文件 @
3463d561
...
@@ -10,7 +10,8 @@ import { UpdateHttpProvider } from "./app/update"
...
@@ -10,7 +10,8 @@ import { UpdateHttpProvider } from "./app/update"
import
{
VscodeHttpProvider
}
from
"
./app/vscode
"
import
{
VscodeHttpProvider
}
from
"
./app/vscode
"
import
{
Args
,
optionDescriptions
,
parse
}
from
"
./cli
"
import
{
Args
,
optionDescriptions
,
parse
}
from
"
./cli
"
import
{
AuthType
,
HttpServer
}
from
"
./http
"
import
{
AuthType
,
HttpServer
}
from
"
./http
"
import
{
generateCertificate
,
generatePassword
,
hash
,
open
}
from
"
./util
"
import
{
SshProvider
}
from
"
./ssh/server
"
import
{
generateCertificate
,
generatePassword
,
generateSshHostKey
,
hash
,
open
}
from
"
./util
"
import
{
ipcMain
,
wrap
}
from
"
./wrapper
"
import
{
ipcMain
,
wrap
}
from
"
./wrapper
"
const
main
=
async
(
args
:
Args
):
Promise
<
void
>
=>
{
const
main
=
async
(
args
:
Args
):
Promise
<
void
>
=>
{
...
@@ -29,6 +30,7 @@ const main = async (args: Args): Promise<void> => {
...
@@ -29,6 +30,7 @@ const main = async (args: Args): Promise<void> => {
auth
,
auth
,
cert
:
args
.
cert
?
args
.
cert
.
value
:
undefined
,
cert
:
args
.
cert
?
args
.
cert
.
value
:
undefined
,
certKey
:
args
[
"
cert-key
"
],
certKey
:
args
[
"
cert-key
"
],
sshHostKey
:
args
[
"
ssh-host-key
"
],
commit
:
commit
||
"
development
"
,
commit
:
commit
||
"
development
"
,
host
:
args
.
host
||
(
args
.
auth
===
AuthType
.
Password
&&
typeof
args
.
cert
!==
"
undefined
"
?
"
0.0.0.0
"
:
"
localhost
"
),
host
:
args
.
host
||
(
args
.
auth
===
AuthType
.
Password
&&
typeof
args
.
cert
!==
"
undefined
"
?
"
0.0.0.0
"
:
"
localhost
"
),
password
:
originalPassword
?
hash
(
originalPassword
)
:
undefined
,
password
:
originalPassword
?
hash
(
originalPassword
)
:
undefined
,
...
@@ -43,6 +45,13 @@ const main = async (args: Args): Promise<void> => {
...
@@ -43,6 +45,13 @@ const main = async (args: Args): Promise<void> => {
}
else
if
(
args
.
cert
&&
!
args
[
"
cert-key
"
])
{
}
else
if
(
args
.
cert
&&
!
args
[
"
cert-key
"
])
{
throw
new
Error
(
"
--cert-key is missing
"
)
throw
new
Error
(
"
--cert-key is missing
"
)
}
}
if
(
!
args
[
"
disable-ssh
"
])
{
if
(
!
options
.
sshHostKey
&&
typeof
options
.
sshHostKey
!==
"
undefined
"
)
{
throw
new
Error
(
"
--ssh-host-key cannot be blank
"
)
}
else
if
(
!
options
.
sshHostKey
)
{
options
.
sshHostKey
=
await
generateSshHostKey
()
}
}
const
httpServer
=
new
HttpServer
(
options
)
const
httpServer
=
new
HttpServer
(
options
)
const
vscode
=
httpServer
.
registerHttpProvider
(
"
/
"
,
VscodeHttpProvider
,
args
)
const
vscode
=
httpServer
.
registerHttpProvider
(
"
/
"
,
VscodeHttpProvider
,
args
)
...
@@ -55,6 +64,13 @@ const main = async (args: Args): Promise<void> => {
...
@@ -55,6 +64,13 @@ const main = async (args: Args): Promise<void> => {
ipcMain
().
onDispose
(()
=>
httpServer
.
dispose
())
ipcMain
().
onDispose
(()
=>
httpServer
.
dispose
())
logger
.
info
(
`code-server
${
require
(
"
../../package.json
"
).
version
}
`
)
logger
.
info
(
`code-server
${
require
(
"
../../package.json
"
).
version
}
`
)
let
sshPort
=
""
if
(
!
args
[
"
disable-ssh
"
])
{
const
sshProvider
=
httpServer
.
registerHttpProvider
(
"
/ssh
"
,
SshProvider
,
options
.
sshHostKey
as
string
)
sshPort
=
await
sshProvider
.
listen
()
}
const
serverAddress
=
await
httpServer
.
listen
()
const
serverAddress
=
await
httpServer
.
listen
()
logger
.
info
(
`Server listening on
${
serverAddress
}
`
)
logger
.
info
(
`Server listening on
${
serverAddress
}
`
)
...
@@ -82,6 +98,12 @@ const main = async (args: Args): Promise<void> => {
...
@@ -82,6 +98,12 @@ const main = async (args: Args): Promise<void> => {
logger
.
info
(
` - Automatic updates are
${
update
.
enabled
?
"
enabled
"
:
"
disabled
"
}
`
)
logger
.
info
(
` - Automatic updates are
${
update
.
enabled
?
"
enabled
"
:
"
disabled
"
}
`
)
if
(
sshPort
)
{
logger
.
info
(
` - SSH Server - Listening :
${
sshPort
}
`
)
}
else
{
logger
.
info
(
"
- SSH Server - Disabled
"
)
}
if
(
serverAddress
&&
!
options
.
socket
&&
args
.
open
)
{
if
(
serverAddress
&&
!
options
.
socket
&&
args
.
open
)
{
// The web socket doesn't seem to work if browsing with 0.0.0.0.
// The web socket doesn't seem to work if browsing with 0.0.0.0.
const
openAddress
=
serverAddress
.
replace
(
/:
\/\/
0.0.0.0/
,
"
://localhost
"
)
const
openAddress
=
serverAddress
.
replace
(
/:
\/\/
0.0.0.0/
,
"
://localhost
"
)
...
...
src/node/http.ts
浏览文件 @
3463d561
...
@@ -525,7 +525,7 @@ export class HttpServer {
...
@@ -525,7 +525,7 @@ export class HttpServer {
"
Set-Cookie
"
:
[
"
Set-Cookie
"
:
[
`
${
payload
.
cookie
.
key
}
=
${
payload
.
cookie
.
value
}
`
,
`
${
payload
.
cookie
.
key
}
=
${
payload
.
cookie
.
value
}
`
,
`Path=
${
normalize
(
payload
.
cookie
.
path
||
"
/
"
,
true
)}
`
,
`Path=
${
normalize
(
payload
.
cookie
.
path
||
"
/
"
,
true
)}
`
,
"
HttpOnly
"
,
//
"HttpOnly",
"
SameSite=strict
"
,
"
SameSite=strict
"
,
].
join
(
"
;
"
),
].
join
(
"
;
"
),
}
}
...
...
src/node/ssh/server.ts
0 → 100644
浏览文件 @
3463d561
import
*
as
http
from
"
http
"
import
*
as
net
from
"
net
"
import
*
as
ssh
from
"
ssh2
"
import
*
as
ws
from
"
ws
"
import
*
as
fs
from
"
fs
"
import
{
logger
}
from
"
@coder/logger
"
import
safeCompare
from
"
safe-compare
"
import
{
HttpProvider
,
HttpResponse
,
HttpProviderOptions
,
Route
}
from
"
../http
"
import
{
HttpCode
}
from
"
../../common/http
"
import
{
forwardSshPort
,
fillSshSession
}
from
"
./ssh
"
import
{
hash
}
from
"
../util
"
export
class
SshProvider
extends
HttpProvider
{
private
readonly
wss
=
new
ws
.
Server
({
noServer
:
true
})
private
sshServer
:
ssh
.
Server
public
constructor
(
options
:
HttpProviderOptions
,
hostKeyPath
:
string
)
{
super
(
options
)
const
hostKey
=
fs
.
readFileSync
(
hostKeyPath
)
this
.
sshServer
=
new
ssh
.
Server
({
hostKeys
:
[
hostKey
]
},
this
.
handleSsh
)
this
.
sshServer
.
on
(
"
error
"
,
(
err
)
=>
{
logger
.
error
(
`SSH server error:
${
err
.
stack
}
`
)
})
}
public
async
listen
():
Promise
<
string
>
{
return
new
Promise
((
resolve
,
reject
)
=>
{
this
.
sshServer
.
once
(
"
error
"
,
reject
)
this
.
sshServer
.
listen
(()
=>
{
resolve
(
this
.
sshServer
.
address
().
port
.
toString
())
})
})
}
public
async
handleRequest
():
Promise
<
HttpResponse
>
{
// SSH has no HTTP endpoints
return
{
code
:
HttpCode
.
NotFound
}
}
public
handleWebSocket
(
_route
:
Route
,
request
:
http
.
IncomingMessage
,
socket
:
net
.
Socket
,
head
:
Buffer
,
):
Promise
<
void
>
{
// Create a fake websocket to the sshServer
const
sshSocket
=
net
.
connect
(
this
.
sshServer
.
address
().
port
,
"
localhost
"
)
return
new
Promise
((
resolve
)
=>
{
this
.
wss
.
handleUpgrade
(
request
,
socket
,
head
,
(
ws
)
=>
{
// Send SSH data to WS as compressed binary
sshSocket
.
on
(
"
data
"
,
(
data
)
=>
{
ws
.
send
(
data
,
{
binary
:
true
,
compress
:
true
,
fin
:
true
,
})
})
// Send WS data to SSH as buffer
ws
.
on
(
"
message
"
,
(
msg
)
=>
{
// Buffer.from is cool with all types, but casting as string keeps typing simple
sshSocket
.
write
(
Buffer
.
from
(
msg
as
string
))
})
ws
.
on
(
"
error
"
,
(
err
)
=>
{
logger
.
error
(
`SSH websocket error:
${
err
.
stack
}
`
)
})
resolve
()
})
})
}
/**
* Determine how to handle incoming SSH connections.
*/
private
handleSsh
=
(
client
:
ssh
.
Connection
,
info
:
ssh
.
ClientInfo
):
void
=>
{
logger
.
debug
(
`Incoming SSH connection from
${
info
.
ip
}
`
)
client
.
on
(
"
authentication
"
,
(
ctx
)
=>
{
// Allow any auth to go through if we have no password
if
(
!
this
.
options
.
password
)
{
return
ctx
.
accept
()
}
// Otherwise require the same password as code-server
if
(
ctx
.
method
===
"
password
"
)
{
if
(
safeCompare
(
this
.
options
.
password
,
hash
(
ctx
.
password
))
||
safeCompare
(
this
.
options
.
password
,
ctx
.
password
)
)
{
return
ctx
.
accept
()
}
}
// Reject, letting them know that password is the only method we allow
ctx
.
reject
([
"
password
"
])
})
client
.
on
(
"
tcpip
"
,
forwardSshPort
)
client
.
on
(
"
session
"
,
fillSshSession
)
client
.
on
(
"
error
"
,
(
err
)
=>
{
// Don't bother logging Keepalive errors, they probably just disconnected
if
(
err
.
message
===
"
Keepalive timeout
"
)
{
return
logger
.
debug
(
"
SSH client keepalive timeout
"
)
}
logger
.
error
(
`SSH client error:
${
err
.
stack
}
`
)
})
}
}
src/node/ssh/sftp.ts
0 → 100644
浏览文件 @
3463d561
/**
* Provides utilities for handling SSH connections
*/
import
*
as
fs
from
"
fs
"
import
*
as
path
from
"
path
"
import
*
as
ssh
from
"
ssh2
"
import
{
FileEntry
,
SFTPStream
}
from
"
ssh2-streams
"
/**
* Fills out all the functionality of SFTP using fs.
*/
export
function
fillSftpStream
(
accept
:
()
=>
SFTPStream
):
void
{
const
sftp
=
accept
()
let
oid
=
0
const
fds
:
{
[
key
:
number
]:
boolean
}
=
{}
const
ods
:
{
[
key
:
number
]:
{
path
:
string
read
:
boolean
}
}
=
{}
const
sftpStatus
=
(
reqID
:
number
,
err
?:
NodeJS
.
ErrnoException
|
null
):
boolean
=>
{
let
code
=
ssh
.
SFTP_STATUS_CODE
.
OK
if
(
err
)
{
if
(
err
.
code
===
"
EACCES
"
)
{
code
=
ssh
.
SFTP_STATUS_CODE
.
PERMISSION_DENIED
}
else
if
(
err
.
code
===
"
ENOENT
"
)
{
code
=
ssh
.
SFTP_STATUS_CODE
.
NO_SUCH_FILE
}
else
{
code
=
ssh
.
SFTP_STATUS_CODE
.
FAILURE
}
}
return
sftp
.
status
(
reqID
,
code
)
}
sftp
.
on
(
"
OPEN
"
,
(
reqID
,
filename
)
=>
{
fs
.
open
(
filename
,
"
w
"
,
(
err
,
fd
)
=>
{
if
(
err
)
{
return
sftpStatus
(
reqID
,
err
)
}
fds
[
fd
]
=
true
const
buf
=
Buffer
.
alloc
(
4
)
buf
.
writeUInt32BE
(
fd
,
0
)
return
sftp
.
handle
(
reqID
,
buf
)
})
})
sftp
.
on
(
"
OPENDIR
"
,
(
reqID
,
path
)
=>
{
const
buf
=
Buffer
.
alloc
(
4
)
const
id
=
oid
++
buf
.
writeUInt32BE
(
id
,
0
)
ods
[
id
]
=
{
path
,
read
:
false
,
}
sftp
.
handle
(
reqID
,
buf
)
})
sftp
.
on
(
"
READDIR
"
,
(
reqID
,
handle
)
=>
{
const
od
=
handle
.
readUInt32BE
(
0
)
if
(
!
ods
[
od
])
{
return
sftp
.
status
(
reqID
,
ssh
.
SFTP_STATUS_CODE
.
NO_SUCH_FILE
)
}
if
(
ods
[
od
].
read
)
{
sftp
.
status
(
reqID
,
ssh
.
SFTP_STATUS_CODE
.
EOF
)
return
}
return
fs
.
readdir
(
ods
[
od
].
path
,
(
err
,
files
)
=>
{
if
(
err
)
{
return
sftpStatus
(
reqID
,
err
)
}
return
Promise
.
all
(
files
.
map
((
f
)
=>
{
return
new
Promise
<
FileEntry
>
((
resolve
,
reject
)
=>
{
const
fullPath
=
path
.
join
(
ods
[
od
].
path
,
f
)
fs
.
stat
(
fullPath
,
(
err
,
stats
)
=>
{
if
(
err
)
{
return
reject
(
err
)
}
resolve
({
filename
:
f
,
longname
:
fullPath
,
attrs
:
{
atime
:
stats
.
atimeMs
,
gid
:
stats
.
gid
,
mode
:
stats
.
mode
,
size
:
stats
.
size
,
mtime
:
stats
.
mtimeMs
,
uid
:
stats
.
uid
,
},
})
})
})
}),
)
.
then
((
files
)
=>
{
sftp
.
name
(
reqID
,
files
)
ods
[
od
].
read
=
true
})
.
catch
(()
=>
{
sftp
.
status
(
reqID
,
ssh
.
SFTP_STATUS_CODE
.
FAILURE
)
})
})
})
sftp
.
on
(
"
WRITE
"
,
(
reqID
,
handle
,
offset
,
data
)
=>
{
const
fd
=
handle
.
readUInt32BE
(
0
)
if
(
!
fds
[
fd
])
{
return
sftp
.
status
(
reqID
,
ssh
.
SFTP_STATUS_CODE
.
NO_SUCH_FILE
)
}
return
fs
.
write
(
fd
,
data
,
offset
,
(
err
)
=>
sftpStatus
(
reqID
,
err
))
})
sftp
.
on
(
"
CLOSE
"
,
(
reqID
,
handle
)
=>
{
const
fd
=
handle
.
readUInt32BE
(
0
)
if
(
!
fds
[
fd
])
{
if
(
ods
[
fd
])
{
delete
ods
[
fd
]
return
sftp
.
status
(
reqID
,
ssh
.
SFTP_STATUS_CODE
.
OK
)
}
return
sftp
.
status
(
reqID
,
ssh
.
SFTP_STATUS_CODE
.
NO_SUCH_FILE
)
}
return
fs
.
close
(
fd
,
(
err
)
=>
sftpStatus
(
reqID
,
err
))
})
sftp
.
on
(
"
STAT
"
,
(
reqID
,
path
)
=>
{
fs
.
stat
(
path
,
(
err
,
stats
)
=>
{
if
(
err
)
{
return
sftpStatus
(
reqID
,
err
)
}
return
sftp
.
attrs
(
reqID
,
{
atime
:
stats
.
atime
.
getTime
(),
gid
:
stats
.
gid
,
mode
:
stats
.
mode
,
mtime
:
stats
.
mtime
.
getTime
(),
size
:
stats
.
size
,
uid
:
stats
.
uid
,
})
})
})
sftp
.
on
(
"
MKDIR
"
,
(
reqID
,
path
)
=>
{
fs
.
mkdir
(
path
,
(
err
)
=>
sftpStatus
(
reqID
,
err
))
})
sftp
.
on
(
"
LSTAT
"
,
(
reqID
,
path
)
=>
{
fs
.
lstat
(
path
,
(
err
,
stats
)
=>
{
if
(
err
)
{
return
sftpStatus
(
reqID
,
err
)
}
return
sftp
.
attrs
(
reqID
,
{
atime
:
stats
.
atimeMs
,
gid
:
stats
.
gid
,
mode
:
stats
.
mode
,
mtime
:
stats
.
mtimeMs
,
size
:
stats
.
size
,
uid
:
stats
.
uid
,
})
})
})
sftp
.
on
(
"
REMOVE
"
,
(
reqID
,
path
)
=>
{
fs
.
unlink
(
path
,
(
err
)
=>
sftpStatus
(
reqID
,
err
))
})
sftp
.
on
(
"
RMDIR
"
,
(
reqID
,
path
)
=>
{
fs
.
rmdir
(
path
,
(
err
)
=>
sftpStatus
(
reqID
,
err
))
})
sftp
.
on
(
"
REALPATH
"
,
(
reqID
,
path
)
=>
{
fs
.
realpath
(
path
,
(
pathErr
,
resolved
)
=>
{
if
(
pathErr
)
{
return
sftpStatus
(
reqID
,
pathErr
)
}
fs
.
stat
(
path
,
(
statErr
,
stat
)
=>
{
if
(
statErr
)
{
return
sftpStatus
(
reqID
,
statErr
)
}
sftp
.
name
(
reqID
,
[
{
filename
:
resolved
,
longname
:
resolved
,
attrs
:
{
mode
:
stat
.
mode
,
uid
:
stat
.
uid
,
gid
:
stat
.
gid
,
size
:
stat
.
size
,
atime
:
stat
.
atime
.
getTime
(),
mtime
:
stat
.
mtime
.
getTime
(),
},
},
])
return
})
return
})
})
}
src/node/ssh/ssh.ts
0 → 100644
浏览文件 @
3463d561
/**
* Provides utilities for handling SSH connections
*/
import
*
as
net
from
"
net
"
import
*
as
cp
from
"
child_process
"
import
*
as
ssh
from
"
ssh2
"
import
*
as
nodePty
from
"
node-pty
"
import
{
fillSftpStream
}
from
"
./sftp
"
/**
* Fills out all of the functionality of SSH using node equivalents.
*/
export
function
fillSshSession
(
accept
:
()
=>
ssh
.
Session
):
void
{
let
pty
:
nodePty
.
IPty
|
undefined
let
activeProcess
:
cp
.
ChildProcess
let
ptyInfo
:
ssh
.
PseudoTtyInfo
|
undefined
const
env
:
{
[
key
:
string
]:
string
}
=
{}
const
session
=
accept
()
// Run a command, stream back the data
const
cmd
=
(
command
:
string
,
channel
:
ssh
.
ServerChannel
):
void
=>
{
if
(
ptyInfo
)
{
// Remove undefined and project env vars
// keysToRemove taken from sanitizeProcessEnvironment
const
keysToRemove
=
[
/^ELECTRON_.+$/
,
/^GOOGLE_API_KEY$/
,
/^VSCODE_.+$/
,
/^SNAP
(
|_.*
)
$/
]
const
env
=
Object
.
keys
(
process
.
env
).
reduce
((
prev
,
k
)
=>
{
if
(
process
.
env
[
k
]
===
undefined
)
{
return
prev
}
const
val
=
process
.
env
[
k
]
as
string
if
(
keysToRemove
.
find
((
rx
)
=>
val
.
search
(
rx
)))
{
return
prev
}
prev
[
k
]
=
val
return
prev
},
{}
as
{
[
key
:
string
]:
string
})
pty
=
nodePty
.
spawn
(
command
,
[],
{
cols
:
ptyInfo
.
cols
,
rows
:
ptyInfo
.
rows
,
env
,
})
pty
.
onData
((
d
)
=>
channel
.
write
(
d
))
pty
.
on
(
"
exit
"
,
(
exitCode
)
=>
{
channel
.
exit
(
exitCode
)
channel
.
close
()
})
channel
.
on
(
"
data
"
,
(
d
:
string
)
=>
pty
&&
pty
.
write
(
d
))
return
}
const
proc
=
cp
.
spawn
(
command
,
{
shell
:
true
})
proc
.
stdout
.
on
(
"
data
"
,
(
d
)
=>
channel
.
stdout
.
write
(
d
))
proc
.
stderr
.
on
(
"
data
"
,
(
d
)
=>
channel
.
stderr
.
write
(
d
))
proc
.
on
(
"
exit
"
,
(
exitCode
)
=>
{
channel
.
exit
(
exitCode
||
0
)
channel
.
close
()
})
channel
.
stdin
.
on
(
"
data
"
,
(
d
:
unknown
)
=>
proc
.
stdin
.
write
(
d
))
channel
.
stdin
.
on
(
"
close
"
,
()
=>
proc
.
stdin
.
end
())
}
session
.
on
(
"
pty
"
,
(
accept
,
_
,
info
)
=>
{
ptyInfo
=
info
accept
&&
accept
()
})
session
.
on
(
"
shell
"
,
(
accept
)
=>
{
cmd
(
process
.
env
.
SHELL
||
"
/usr/bin/env bash
"
,
accept
())
})
session
.
on
(
"
exec
"
,
(
accept
,
_
,
info
)
=>
{
cmd
(
info
.
command
,
accept
())
})
session
.
on
(
"
sftp
"
,
fillSftpStream
)
session
.
on
(
"
signal
"
,
(
accept
,
_
,
info
)
=>
{
accept
&&
accept
()
process
.
kill
((
pty
||
activeProcess
).
pid
,
info
.
name
)
})
session
.
on
(
"
env
"
,
(
accept
,
_reject
,
info
)
=>
{
accept
&&
accept
()
env
[
info
.
key
]
=
info
.
value
})
session
.
on
(
"
auth-agent
"
,
(
accept
)
=>
{
accept
()
})
session
.
on
(
"
window-change
"
,
(
accept
,
reject
,
info
)
=>
{
if
(
pty
)
{
pty
.
resize
(
info
.
cols
,
info
.
rows
)
accept
&&
accept
()
}
else
{
reject
()
}
})
}
/**
* Pipes a requested port over SSH
*/
export
function
forwardSshPort
(
accept
:
()
=>
ssh
.
ServerChannel
,
reject
:
()
=>
boolean
,
info
:
ssh
.
TcpipRequestInfo
,
):
void
{
const
fwdSocket
=
net
.
createConnection
(
info
.
destPort
,
info
.
destIP
)
fwdSocket
.
on
(
"
error
"
,
()
=>
reject
())
fwdSocket
.
on
(
"
connect
"
,
()
=>
{
const
channel
=
accept
()
channel
.
pipe
(
fwdSocket
)
channel
.
on
(
"
close
"
,
()
=>
fwdSocket
.
end
())
fwdSocket
.
pipe
(
channel
)
fwdSocket
.
on
(
"
close
"
,
()
=>
channel
.
close
())
fwdSocket
.
on
(
"
error
"
,
()
=>
channel
.
end
())
fwdSocket
.
on
(
"
end
"
,
()
=>
channel
.
end
())
})
}
src/node/util.ts
浏览文件 @
3463d561
...
@@ -44,6 +44,12 @@ export const generateCertificate = async (): Promise<{ cert: string; certKey: st
...
@@ -44,6 +44,12 @@ export const generateCertificate = async (): Promise<{ cert: string; certKey: st
return
paths
return
paths
}
}
export
const
generateSshHostKey
=
async
():
Promise
<
string
>
=>
{
// Just reuse the SSL cert as the SSH host key
const
{
certKey
}
=
await
generateCertificate
()
return
certKey
}
export
const
generatePassword
=
async
(
length
=
24
):
Promise
<
string
>
=>
{
export
const
generatePassword
=
async
(
length
=
24
):
Promise
<
string
>
=>
{
const
buffer
=
Buffer
.
alloc
(
Math
.
ceil
(
length
/
2
))
const
buffer
=
Buffer
.
alloc
(
Math
.
ceil
(
length
/
2
))
await
util
.
promisify
(
crypto
.
randomFill
)(
buffer
)
await
util
.
promisify
(
crypto
.
randomFill
)(
buffer
)
...
...
yarn.lock
浏览文件 @
3463d561
...
@@ -937,7 +937,22 @@
...
@@ -937,7 +937,22 @@
dependencies:
dependencies:
"@types/node" "*"
"@types/node" "*"
"@types/tar-fs@^1.16.2":
"@types/ssh2-streams@*", "@types/ssh2-streams@^0.1.6":
version "0.1.6"
resolved "https://registry.yarnpkg.com/@types/ssh2-streams/-/ssh2-streams-0.1.6.tgz#1ff5b1fe50c15f282efe9fd092c68494f8702bc2"
integrity sha512-NB+SYftagfHTDPdgVyvSZCeg5MURyHECd/PycGIW9hwhnEbTFxIdDbFtf3jxUO3rRnfr0lfdtkZFiv28T1cAsg==
dependencies:
"@types/node" "*"
"@types/ssh2@0.5.39":
version "0.5.39"
resolved "https://registry.yarnpkg.com/@types/ssh2/-/ssh2-0.5.39.tgz#e39ab7e38fc01337f66237ed6c42237ef3e58e3b"
integrity sha512-MtX4tr6Jtdn/JPVsQytjB/NBeOd7JfCyf/l79KOdkUYL+r4GXUXcySX1n8W4O61fnQdljTBXEPJJ2dhnGhi2xg==
dependencies:
"@types/node" "*"
"@types/ssh2-streams" "*"
"@types/tar-fs@^1.16.1":
version "1.16.2"
version "1.16.2"
resolved "https://registry.yarnpkg.com/@types/tar-fs/-/tar-fs-1.16.2.tgz#6f5acea15d3b7777b8bf3f1c6d4e80ce71288f34"
resolved "https://registry.yarnpkg.com/@types/tar-fs/-/tar-fs-1.16.2.tgz#6f5acea15d3b7777b8bf3f1c6d4e80ce71288f34"
integrity sha512-eds/pbRf0Fe0EKmrHDbs8mRkfbjz2upAdoUfREw14dPboZaHqqZ1Y+uVeoakoPavpZMpj22nhUTAYkX5bz3DXA==
integrity sha512-eds/pbRf0Fe0EKmrHDbs8mRkfbjz2upAdoUfREw14dPboZaHqqZ1Y+uVeoakoPavpZMpj22nhUTAYkX5bz3DXA==
...
@@ -945,7 +960,7 @@
...
@@ -945,7 +960,7 @@
"@types/node" "*"
"@types/node" "*"
"@types/tar-stream" "*"
"@types/tar-stream" "*"
"@types/tar-stream@*":
"@types/tar-stream@*"
, "@types/tar-stream@^1.6.1"
:
version "1.6.1"
version "1.6.1"
resolved "https://registry.yarnpkg.com/@types/tar-stream/-/tar-stream-1.6.1.tgz#67d759068ff781d976cad978893bb7a334ec8809"
resolved "https://registry.yarnpkg.com/@types/tar-stream/-/tar-stream-1.6.1.tgz#67d759068ff781d976cad978893bb7a334ec8809"
integrity sha512-pYCDOPuRE+4tXFk1rSMYiuI+kSrXiJ4av1bboQbkcEBA2rqwEWfIn9kdMSH+5nYu58WksHuxwx+7kVbtg0Le7w==
integrity sha512-pYCDOPuRE+4tXFk1rSMYiuI+kSrXiJ4av1bboQbkcEBA2rqwEWfIn9kdMSH+5nYu58WksHuxwx+7kVbtg0Le7w==
...
@@ -1221,7 +1236,7 @@ asn1.js@^4.0.0:
...
@@ -1221,7 +1236,7 @@ asn1.js@^4.0.0:
inherits "^2.0.1"
inherits "^2.0.1"
minimalistic-assert "^1.0.0"
minimalistic-assert "^1.0.0"
asn1@~0.2.3:
asn1@~0.2.
0, asn1@~0.2.
3:
version "0.2.4"
version "0.2.4"
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==
integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==
...
@@ -1356,7 +1371,7 @@ base@^0.11.1:
...
@@ -1356,7 +1371,7 @@ base@^0.11.1:
mixin-deep "^1.2.0"
mixin-deep "^1.2.0"
pascalcase "^0.1.1"
pascalcase "^0.1.1"
bcrypt-pbkdf@^1.0.0:
bcrypt-pbkdf@^1.0.0
, bcrypt-pbkdf@^1.0.2
:
version "1.0.2"
version "1.0.2"
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=
integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=
...
@@ -4404,7 +4419,7 @@ mute-stream@0.0.8:
...
@@ -4404,7 +4419,7 @@ mute-stream@0.0.8:
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==
integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==
nan@^2.12.1:
nan@^2.12.1
, nan@^2.14.0
:
version "2.14.0"
version "2.14.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==
integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==
...
@@ -4483,6 +4498,13 @@ node-libs-browser@^2.0.0:
...
@@ -4483,6 +4498,13 @@ node-libs-browser@^2.0.0:
util "^0.11.0"
util "^0.11.0"
vm-browserify "^1.0.1"
vm-browserify "^1.0.1"
node-pty@^0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.9.0.tgz#8f9bcc0d1c5b970a3184ffd533d862c7eb6590a6"
integrity sha512-MBnCQl83FTYOu7B4xWw10AW77AAh7ThCE1VXEv+JeWj8mSpGo+0bwgsV+b23ljBFwEM9OmsOv3kM27iUPPm84g==
dependencies:
nan "^2.14.0"
node-releases@^1.1.49:
node-releases@^1.1.49:
version "1.1.49"
version "1.1.49"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.49.tgz#67ba5a3fac2319262675ef864ed56798bb33b93e"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.49.tgz#67ba5a3fac2319262675ef864ed56798bb33b93e"
...
@@ -6249,6 +6271,22 @@ sprintf-js@~1.0.2:
...
@@ -6249,6 +6271,22 @@ sprintf-js@~1.0.2:
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
ssh2-streams@~0.4.9:
version "0.4.9"
resolved "https://registry.yarnpkg.com/ssh2-streams/-/ssh2-streams-0.4.9.tgz#d3d92155410001437d27119d9c023b303cbe2309"
integrity sha512-glMQKeYKuA+rLaH16fJC3nZMV1BWklbxuYCR4C5/LlBSf2yaoNRpPU7Ul702xsi5nvYjIx9XDkKBJwrBjkDynw==
dependencies:
asn1 "~0.2.0"
bcrypt-pbkdf "^1.0.2"
streamsearch "~0.1.2"
ssh2@^0.8.7:
version "0.8.8"
resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-0.8.8.tgz#1d9815e287faef623ae2b7db32e674dadbef4664"
integrity sha512-egJVQkf3sbjECTY6rCeg8rgV/fab6S/7E5kpYqHT3Fe/YpfJbLYeA1qTcB2d+LRUUAjqKi7rlbfWkaP66YdpAQ==
dependencies:
ssh2-streams "~0.4.9"
sshpk@^1.7.0:
sshpk@^1.7.0:
version "1.16.1"
version "1.16.1"
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877"
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877"
...
@@ -6338,6 +6376,11 @@ stream-http@^2.7.2:
...
@@ -6338,6 +6376,11 @@ stream-http@^2.7.2:
to-arraybuffer "^1.0.0"
to-arraybuffer "^1.0.0"
xtend "^4.0.0"
xtend "^4.0.0"
streamsearch@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a"
integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=
"string-width@^1.0.2 || 2":
"string-width@^1.0.2 || 2":
version "2.1.1"
version "2.1.1"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录