Compare commits
224 Commits
Author | SHA1 | Date |
---|---|---|
|
a929fdc211 | |
|
fc66a0557a | |
|
bd3f90d308 | |
|
8c1f90aaff | |
|
efa01ee0e1 | |
|
4aea95fa3c | |
|
8e96e4fb32 | |
|
f07aa457b3 | |
|
b12c8fe562 | |
|
e5b6422d70 | |
|
12409319e7 | |
|
bfcd097a14 | |
|
136f37e209 | |
|
ec9b4fe2f4 | |
|
aa33a3b8a0 | |
|
7e4aeba93a | |
|
91bb0778b2 | |
|
952279a108 | |
|
dbaa8b5b62 | |
|
77c9b8654f | |
|
e42645a2b3 | |
|
057a3c01c7 | |
|
540cb8ff3a | |
|
ae67ee6201 | |
|
8827d67cc6 | |
|
17d7bc01ef | |
|
89ad0e0998 | |
|
713f44086a | |
|
08cccb6929 | |
|
6212119621 | |
|
faf8769ac4 | |
|
32b669192b | |
|
e82d968381 | |
|
032baf63d6 | |
|
c569a5a3c9 | |
|
36799ba9e7 | |
|
f0dc681a4c | |
|
871f9a200c | |
|
908a1bcda2 | |
|
39a8bd2f03 | |
|
9244cc9785 | |
|
d0f8751b2b | |
|
a3d8543816 | |
|
bcea6d713f | |
|
580053adbd | |
|
74be6173b6 | |
|
119c039b91 | |
|
3cd6f8b7e6 | |
|
623d622d58 | |
|
06124d7584 | |
|
7b6a9d1350 | |
|
5ee09de99a | |
|
9ca5ccae32 | |
|
bd0b48d98f | |
|
3325bb3a4e | |
|
0913311351 | |
|
b2e43f4bad | |
|
667328a91c | |
|
0f28d2c023 | |
|
5c826f7a5f | |
|
1d13e6a3bd | |
|
b5f9333b3a | |
|
9a0dd8270a | |
|
0b70cdbac0 | |
|
5b6bec0287 | |
|
254e9fb7d6 | |
|
653e732445 | |
|
610781aadf | |
|
1f9f03dfe3 | |
|
677172dc1f | |
|
7a4560762d | |
|
f24d4d8fdb | |
|
42ad33065b | |
|
41769660cc | |
|
73eadf7534 | |
|
819b359306 | |
|
ee19787b5e | |
|
0765c8bea6 | |
|
a4af720280 | |
|
4f9a064940 | |
|
ccebf6f4b3 | |
|
08b8bd37d5 | |
|
8e8fff415c | |
|
81e3728c98 | |
|
3f6ee1e005 | |
|
4168b5038b | |
|
02e379e50d | |
|
9c844938a9 | |
|
20113653f7 | |
|
ca4335df9e | |
|
6965ecf78e | |
|
d837bd2583 | |
|
12b7e525bf | |
|
0184e34284 | |
|
cfc9ab8590 | |
|
129dce4b08 | |
|
232ee0e2de | |
|
7002f87477 | |
|
db8697aa35 | |
|
6e06c81941 | |
|
0e453f15d0 | |
|
aae7b14e7c | |
|
6904268797 | |
|
b016e3cd77 | |
|
e4b69eab2d | |
|
9ba07719cd | |
|
83cd38c651 | |
|
71f2bdfe8d | |
|
c1ab12dc1b | |
|
435e2b6380 | |
|
e8986d3464 | |
|
c511af6c13 | |
|
bcc07a8fac | |
|
242d39192c | |
|
3fc74bae70 | |
|
287f9d1035 | |
|
f2929120b6 | |
|
4e8e064c74 | |
|
c23edc6874 | |
|
3193ede825 | |
|
640e59be4f | |
|
5f42894ac2 | |
|
d0f76fd3b4 | |
|
035df99069 | |
|
0f22f35e7c | |
|
5aca04e1d5 | |
|
569230a2af | |
|
01aa67d1f7 | |
|
7901cd8809 | |
|
611b6853b2 | |
|
61b7a80e00 | |
|
b3a359a84e | |
|
1943b2314a | |
|
499eaa665b | |
|
eb373ff37b | |
|
1b01ed14f2 | |
|
09055dffe6 | |
|
f480c8cf78 | |
|
69a3fb5d08 | |
|
3522976ef7 | |
|
f0a2bb0295 | |
|
1addd18806 | |
|
57b71e7925 | |
|
58c87f7e0e | |
|
769df46fb1 | |
|
51488fab6d | |
|
0962c0b811 | |
|
3bea3e6ebb | |
|
1d30b8582d | |
|
47a3c6f1b8 | |
|
6bb4b90c87 | |
|
531c846899 | |
|
cba36b66f1 | |
|
5448dbc7b9 | |
|
09c2408c06 | |
|
4686361ece | |
|
e803d3da09 | |
|
0c1d5e036e | |
|
3b0ddba7f2 | |
|
b3ebd0db17 | |
|
d1076c9bd9 | |
|
2e16c9a692 | |
|
9d674315f9 | |
|
64a314df11 | |
|
f6910162b4 | |
|
6ac6f02b3b | |
|
f3bf6d9041 | |
|
138f855a82 | |
|
e9aa0072a5 | |
|
8de76520e4 | |
|
e84f432033 | |
|
654de563dc | |
|
733fc46d86 | |
|
d18396f535 | |
|
e320725e07 | |
|
d4a3521437 | |
|
c2c43c1c50 | |
|
9ffeab2456 | |
|
0eb785a9dc | |
|
58652ce935 | |
|
1b964a4066 | |
|
3eee573231 | |
|
d3d7ac9aae | |
|
6c049dde08 | |
|
862c0c3d7f | |
|
15a1a39d81 | |
|
977380e61f | |
|
bebfd1cb1f | |
|
399babd62a | |
|
0303b394c8 | |
|
0933aa4ea5 | |
|
d3ebcab1d4 | |
|
89b6e8bce7 | |
|
eb9ce0e0e2 | |
|
d75b419c7a | |
|
f5be3578a8 | |
|
e55b560230 | |
|
d6cd51c79f | |
|
caac02a77b | |
|
7fe915450b | |
|
3be1243bf9 | |
|
7ce725c7d2 | |
|
4e0ddf282a | |
|
9f956cff62 | |
|
f8f4bcbe77 | |
|
faf4d5c50a | |
|
1f84bc95ff | |
|
d4f50bfdc0 | |
|
9b90c0558e | |
|
8dbb10f324 | |
|
b19687c80b | |
|
423410bb40 | |
|
a990c5e1f3 | |
|
3b73c6d731 | |
|
ba52356e6c | |
|
6483c8cc33 | |
|
ce1adf7a07 | |
|
dc3f6f9bd5 | |
|
c354036952 | |
|
c95794da1f | |
|
d7dbcd8fdf | |
|
c9d478ff30 | |
|
35e23b20ce | |
|
795adf6aa0 |
|
@ -0,0 +1,160 @@
|
|||
linters-settings:
|
||||
depguard:
|
||||
list-type: blacklist
|
||||
packages:
|
||||
# logging is allowed only by logutils.Log, logrus
|
||||
# is allowed to use only in logutils package
|
||||
- github.com/sirupsen/logrus
|
||||
packages-with-error-message:
|
||||
- github.com/sirupsen/logrus: "logging is allowed only by logutils.Log"
|
||||
dupl:
|
||||
threshold: 125
|
||||
funlen:
|
||||
lines: 125
|
||||
statements: 50
|
||||
gci:
|
||||
local-prefixes: github.com/golangci/golangci-lint
|
||||
goconst:
|
||||
min-len: 2
|
||||
min-occurrences: 2
|
||||
gocritic:
|
||||
enabled-tags:
|
||||
- diagnostic
|
||||
- experimental
|
||||
- performance
|
||||
#- style
|
||||
#- opinionated
|
||||
disabled-checks:
|
||||
- commentFormatting
|
||||
- commentedOutCode
|
||||
- dupImport # https://github.com/go-critic/go-critic/issues/845
|
||||
- ifElseChain
|
||||
- octalLiteral
|
||||
- whyNoLint
|
||||
- wrapperFunc
|
||||
gocyclo:
|
||||
min-complexity: 15
|
||||
goimports:
|
||||
local-prefixes: github.com/golangci/golangci-lint
|
||||
gomnd:
|
||||
settings:
|
||||
mnd:
|
||||
# don't include the "operation" and "assign"
|
||||
checks: argument,case,condition,return
|
||||
govet:
|
||||
check-shadowing: true
|
||||
settings:
|
||||
printf:
|
||||
funcs:
|
||||
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof
|
||||
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf
|
||||
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf
|
||||
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf
|
||||
lll:
|
||||
line-length: 140
|
||||
maligned:
|
||||
suggest-new: true
|
||||
misspell:
|
||||
locale: en_CA
|
||||
nolintlint:
|
||||
allow-leading-space: true # don't require machine-readable nolint directives (i.e. with no leading space)
|
||||
allow-unused: false # report any unused nolint directives
|
||||
require-explanation: false # don't require an explanation for nolint directives
|
||||
require-specific: false # don't require nolint directives to be specific about which linter is being skipped
|
||||
|
||||
linters:
|
||||
# please, do not use `enable-all`: it's deprecated and will be removed soon.
|
||||
# inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint
|
||||
disable-all: true
|
||||
enable:
|
||||
- bodyclose
|
||||
- depguard
|
||||
- dogsled
|
||||
- dupl
|
||||
- errcheck
|
||||
- exhaustive
|
||||
- exportloopref
|
||||
- funlen
|
||||
- gochecknoinits
|
||||
- goconst
|
||||
- gocritic
|
||||
- gocyclo
|
||||
- gofmt
|
||||
- goimports
|
||||
#- golint
|
||||
- gomnd
|
||||
- goprintffuncname
|
||||
- gosec
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
#- interfacer
|
||||
- lll
|
||||
- misspell
|
||||
#- nakedret
|
||||
- noctx
|
||||
- nolintlint
|
||||
- rowserrcheck
|
||||
#- scopelint
|
||||
- staticcheck
|
||||
#- structcheck
|
||||
- stylecheck
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- unused
|
||||
#- varcheck
|
||||
- whitespace
|
||||
|
||||
# don't enable:
|
||||
# - asciicheck
|
||||
# - deadcode
|
||||
# - gochecknoglobals
|
||||
# - gocognit
|
||||
# - godot
|
||||
# - godox
|
||||
# - goerr113
|
||||
# - golint
|
||||
# - interfacer
|
||||
# - maligned
|
||||
# - nestif
|
||||
# - prealloc
|
||||
## - rowserrcheck
|
||||
# - scopelint
|
||||
# - structcheck
|
||||
# - testpackage
|
||||
# - varcheck
|
||||
# - wsl
|
||||
|
||||
issues:
|
||||
# Excluding configuration per-path, per-linter, per-text and per-source
|
||||
exclude-rules:
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- gomnd
|
||||
|
||||
# https://github.com/go-critic/go-critic/issues/926
|
||||
- linters:
|
||||
- gocritic
|
||||
text: "unnecessaryDefer:"
|
||||
|
||||
# TODO temporary rule, must be removed
|
||||
# seems related to v0.34.1, but I was not able to reproduce locally,
|
||||
# I was also not able to reproduce in the CI of a fork,
|
||||
# only the golangci-lint CI seems to be affected by this invalid analysis.
|
||||
- path: pkg/golinters/scopelint.go
|
||||
text: 'directive `//nolint:interfacer` is unused for linter interfacer'
|
||||
|
||||
run:
|
||||
skip-dirs:
|
||||
- test/testdata_etc
|
||||
- internal/cache
|
||||
- internal/renameio
|
||||
- internal/robustio
|
||||
|
||||
# golangci.com configuration
|
||||
# https://github.com/golangci/golangci/wiki/Configuration
|
||||
service:
|
||||
golangci-lint-version: 1.23.x # use the fixed version to not introduce new linters unexpectedly
|
||||
prepare:
|
||||
- echo "here I can run custom commands, but no preparation needed for this repo"
|
|
@ -55,5 +55,7 @@ to the project. Any intent to deviate the project from its original purpose
|
|||
of existence will constitute grounds for remedial action which may include
|
||||
expulsion from the project.
|
||||
|
||||
This document is based upon the Code of Merit version 1.0 (Dec 4 2018).
|
||||
This document is based upon the original Code of Merit version 1.0 (Dec 4 2018).
|
||||
(https://web.archive.org/web/20181204203029/http://code-of-merit.org/)
|
||||
|
||||
Updated version (Mar 29 2020): https://codeofmerit.org/code/
|
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2017 - 2018 Russell Magee (hkexsh/hkexshd/hkexnet/hkexpasswd)
|
||||
Copyright (c) 2017 - 2021 Russell Magee (xs/xsd/xsnet/xspasswd)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
116
Makefile
|
@ -1,24 +1,57 @@
|
|||
.PHONY: lint vis clean common client server passwd subpkgs install uninstall reinstall
|
||||
VERSION := 0.9.13
|
||||
.PHONY: lint vis clean common client server passwd\
|
||||
subpkgs install uninstall reinstall scc
|
||||
|
||||
## Tag version of binaries with build info wrt.
|
||||
## GO111MODULE(=on) and vendor/ setup vs. $GOPATH pkg builds
|
||||
############################################################
|
||||
ifeq ($(shell go env GOMOD),)
|
||||
MTAG=
|
||||
else
|
||||
MTAG="-m"
|
||||
endif
|
||||
|
||||
#ifeq ($(MAKEOPTS),)
|
||||
MAKEOPTS = $(MAKEOPTS)
|
||||
#endif
|
||||
# If available, one may build 'garbled' binaries
|
||||
# See https://github.com/burrowers/garble.git
|
||||
ifeq ($(GARBLE),y)
|
||||
GO=garble -literals -tiny -debugdir=garbled
|
||||
else
|
||||
GO=go
|
||||
endif
|
||||
|
||||
ifneq ($(VENDOR),)
|
||||
GOBUILDOPTS :=-v -mod vendor
|
||||
VTAG = "-v"
|
||||
else
|
||||
GOBUILDOPTS=
|
||||
VTAG =
|
||||
endif
|
||||
############################################################
|
||||
|
||||
GIT_COMMIT := $(shell git rev-list -1 HEAD)
|
||||
VERSION := 0.8.5
|
||||
|
||||
#ifeq ($(BUILDOPTS),)
|
||||
BUILDOPTS :=$(BUILDOPTS)" -ldflags \"-X main.version=$(VERSION) -X main.gitCommit=$(GIT_COMMIT)\""
|
||||
BUILDOPTS :=$(BUILDOPTS)"$(GOBUILDOPTS) -ldflags \"-X main.version=$(VERSION)$(MTAG)$(VTAG) -X main.gitCommit=$(GIT_COMMIT)\""
|
||||
#endif
|
||||
|
||||
SUBPKGS = logger spinsult hkexnet
|
||||
TOOLS = hkexpasswd hkexsh hkexshd
|
||||
SUBPKGS = logger spinsult xsnet
|
||||
TOOLS = xs xsd
|
||||
SUBDIRS = $(LIBS) $(TOOLS)
|
||||
|
||||
INSTPREFIX = /usr/local
|
||||
ifeq ($(GOOS),)
|
||||
GOOS=$(shell go env GOOS)
|
||||
endif
|
||||
|
||||
all: common client server passwd
|
||||
ifeq ($(GOOS),windows)
|
||||
INSTPREFIX = /usr
|
||||
else
|
||||
INSTPREFIX = /usr/local
|
||||
endif
|
||||
|
||||
all: common client server
|
||||
|
||||
clean:
|
||||
@echo "Make: $(MAKE)"
|
||||
go clean .
|
||||
for d in $(SUBDIRS); do\
|
||||
$(MAKE) -C $$d clean;\
|
||||
|
@ -36,69 +69,52 @@ tools:
|
|||
|
||||
|
||||
common:
|
||||
go build .
|
||||
go install .
|
||||
$(GO) build .
|
||||
go install -a .
|
||||
|
||||
|
||||
client: common
|
||||
$(MAKE) BUILDOPTS=$(BUILDOPTS) -C hkexsh
|
||||
$(MAKE) BUILDOPTS=$(BUILDOPTS) -C xs
|
||||
|
||||
|
||||
ifeq ($(MSYSTEM),)
|
||||
ifneq ($(GOOS),windows)
|
||||
server: common
|
||||
$(MAKE) BUILDOPTS=$(BUILDOPTS) -C hkexshd
|
||||
ifeq ($(GOOS),windows)
|
||||
echo "Build of xsd server for Windows not yet supported"
|
||||
else
|
||||
echo "Cross-build of hkexshd server for Windows not yet supported"
|
||||
endif
|
||||
else
|
||||
server: common
|
||||
echo "hkexshd server not (yet) supported on Windows"
|
||||
$(MAKE) BUILDOPTS=$(BUILDOPTS) -C xsd
|
||||
endif
|
||||
|
||||
|
||||
passwd: common
|
||||
$(MAKE) BUILDOPTS=$(BUILDOPTS) -C hkexpasswd
|
||||
scc:
|
||||
@scc --exclude-dir=bacillus,vendor,garbled --cocomo-project-type=rlabs,3,1.12,1,1
|
||||
|
||||
vis:
|
||||
@which go-callvis >/dev/null 2>&1; \
|
||||
stat=$$?; if [ $$stat -ne "0" ]; then \
|
||||
/bin/echo "go-callvis not found. Run go get github.com/Russtopia/go-callvis to install."; \
|
||||
/bin/echo "go-callvis not found. Run go get https://github.com/TrueFurby/go-callvis to install."; \
|
||||
else \
|
||||
make -C hkexsh vis;\
|
||||
make -C hkexshd vis;\
|
||||
make -C hkexpasswd vis; \
|
||||
$(MAKE) -C xs vis;\
|
||||
$(MAKE) -C xsd vis;\
|
||||
fi
|
||||
|
||||
lint:
|
||||
make -C hkexpasswd lint
|
||||
make -C hkexshd lint
|
||||
make -C hkexsh lint
|
||||
$(MAKE) -C xsd lint
|
||||
$(MAKE) -C xs lint
|
||||
|
||||
reinstall: uninstall install
|
||||
|
||||
install:
|
||||
cp hkexsh/hkexsh $(INSTPREFIX)/bin
|
||||
ifeq ($(MSYSTEM),)
|
||||
ifneq ($(GOOS),windows)
|
||||
cp hkexshd/hkexshd hkexpasswd/hkexpasswd $(INSTPREFIX)/sbin
|
||||
ifeq ($(GOOS),windows)
|
||||
cp xs/xs $(INSTPREFIX)/bin/xs
|
||||
cp xs/xs $(INSTPREFIX)/bin/xc
|
||||
@echo "Install of xsd server for Windows not yet supported"
|
||||
else
|
||||
mv $(INSTPREFIX)/bin/hkexsh $(INSTPREFIX)/bin/_hkexsh
|
||||
cp hkexsh/mintty_wrapper.sh $(INSTPREFIX)/bin/hkexsh
|
||||
echo "Cross-build of hkexshd server for Windows not yet supported"
|
||||
cp xs/xs $(INSTPREFIX)/bin
|
||||
cd $(INSTPREFIX)/bin && ln -s xs xc && cd -
|
||||
cp xsd/xsd $(INSTPREFIX)/sbin
|
||||
endif
|
||||
else
|
||||
echo "Cross-build of hkexshd server for Windows not yet supported"
|
||||
endif
|
||||
cd $(INSTPREFIX)/bin && ln -s hkexsh hkexcp && cd -
|
||||
|
||||
|
||||
uninstall:
|
||||
rm -f $(INSTPREFIX)/bin/hkexsh $(INSTPREFIX)/bin/hkexcp $(INSTPREFIX)/bin/_hkexsh
|
||||
ifeq ($(MSYSTEM),)
|
||||
ifneq ($(GOOS),windows)
|
||||
rm -f $(INSTPREFIX)/sbin/hkexshd $(INSTPREFIX)/sbin/hkexpasswd
|
||||
else
|
||||
endif
|
||||
else
|
||||
rm -f $(INSTPREFIX)/bin/xs $(INSTPREFIX)/bin/xc
|
||||
ifndef $(WIN_MSYS)
|
||||
rm -f $(INSTPREFIX)/sbin/xsd
|
||||
endif
|
||||
|
|
193
README.md
|
@ -1,19 +1,27 @@
|
|||
[](https://godoc.org/blitter.com/go/hkexsh)
|
||||
[](https://godoc.org/blitter.com/go/xs)
|
||||
|
||||
# XS
|
||||
|
||||
# HKExSh
|
||||

|
||||
--
|
||||
|
||||
HKExSh (**H**erradura**K**yber**Ex** **Sh**ell) is a golang implementation of a simple
|
||||
remote shell client and server, similar in role to ssh, offering
|
||||
encrypted interactive and non-interactive sessions, file copying and tunnels with traffic obfuscation ('chaffing').
|
||||
XS (**X**perimental **S**hell) is a simple alternative to ssh (<5% total SLOCC) written from scratch in Go.
|
||||
A testbed for candidate PQC (Post-Quantum Cryptography) KEMs (Key-Encapsulation Mechanisms) and symmetric
|
||||
session encryption algorithms.
|
||||
|
||||
xs also features integrated traffic chaffing to obscure interactive session and file copy activity.
|
||||
Supports encrypted interactive and non-interactive sessions (remote commands), remote file copying and tunnels.
|
||||
|
||||
Runs on Linux, FreeBSD, Windows (client only, MSYS) and Android (within Termux). https://gogs.blitter.com/RLabs/xs
|
||||
|
||||
It is stable to the point that I use it for day-to-day remote access in place of, and in preference to, ssh.
|
||||
|
||||
***
|
||||
**NOTE: Due to the experimental nature of the KEX/KEM algorithms used, and the novelty of the overall codebase, this package SHOULD BE CONSIDERED EXTREMELY EXPERIMENTAL and USED WITH CAUTION. It DEFINITELY SHOULD NOT be used for any sensitive applications. USE AT YOUR OWN RISK. NEITHER WARRANTY NOR CLAIM OF FITNESS FOR PURPOSE IS EXPRESSED OR IMPLIED.**
|
||||
|
||||
***
|
||||
|
||||
The client and server programs (hkexsh and hkexshd) use a mostly drop-in
|
||||
The client and server programs (xs and xsd) use a mostly drop-in
|
||||
replacement for golang's standard golang/pkg/net facilities (net.Dial(), net.Listen(), net.Accept()
|
||||
and the net.Conn type), which automatically negotiate keying material for
|
||||
secure sockets using one of a selectable set of experimental key exchange (KEX) or
|
||||
|
@ -24,9 +32,9 @@ Currently supported exchanges are:
|
|||
|
||||
* The HerraduraKEx key exchange algorithm first released at
|
||||
[Omar Elejandro Herrera Reyna's HerraduraKEx project](http://github.com/Caume/HerraduraKEx);
|
||||
* The KYBER IND-CCA-2 secure key encapsulation mechanism, [pq-crystals Kyber](https://pq-crystals.org/kyber/) :: [Yawning/kyber golang implementation](https://git.schwanenlied.me/yawning/kyber)
|
||||
* The NEWHOPE algorithm [newhopecrypto.org](https://www.newhopecrypto.org/) :: [Yawning/go-newhope golang implementation](https://git.schwanenlied.me/yawning/newhope)
|
||||
|
||||
* The KYBER IND-CCA-2 secure key encapsulation mechanism, [pq-crystals Kyber](https://pq-crystals.org/kyber/) :: [Yawning/kyber golang implementation](https://gitlab.com/yawning/kyber)
|
||||
* The NEWHOPE algorithm [newhopecrypto.org](https://www.newhopecrypto.org/) :: [Yawning/newhope golang implementation](https://gitlab.com/yawning/newhope)
|
||||
* The FrodoKEM algorithm [frodokem.org](https://frodokem.org/) :: Go version by [Eduardo E. S. Riccardi](https://github.com/kuking/go-frodokem)
|
||||
|
||||
Currently supported session algorithms:
|
||||
|
||||
|
@ -35,17 +43,34 @@ Currently supported session algorithms:
|
|||
* Twofish-128
|
||||
* Blowfish-64
|
||||
* CryptMTv1 (64bit) (https://eprint.iacr.org/2005/165.pdf)
|
||||
* ChaCha20 (https://github.com/aead/chacha20)
|
||||
* HOPSCOTCH (https://gogs.blitter.com/Russtopia/hopscotch)
|
||||
|
||||
[HMAC]
|
||||
* HMAC-SHA256
|
||||
* HMAC-SHA512
|
||||
* WHIRLPOOL
|
||||
|
||||
***
|
||||
**A Note on 'cryptographic agility'**
|
||||
|
||||
It has been suggested recently to me that offering multiple cryptographic primitives is considered bad in 2021.
|
||||
|
||||
An interesting question. See [this write-up for a discussion](https://paragonie.com/blog/2019/10/against-agility-in-cryptography-protocols).
|
||||
|
||||
xs operates via the philosophy that **it is the server admin's prerogitive to configure local policy wrt. allowed cryptographic primitives**. The connection protocol makes no allowance for any sort of 'downgrades' or algo substitution during negotiation; there is no 'fallback' mode or two-way negotiation of what primitives to use, which would open the possibility of downgrade attacks. Unlike `ssh`, the server does not offer to clients a list of supported algorithms; the client can only offer a single configuration to the server, which it simply accepts or rejects without comment to the client.
|
||||
|
||||
In all releases prior to v0.9.3, absent a specific whitelist of algs to allow, the server allows 'all' combinations of the above cryptographic primitives to be proposed by clients (but again, **only one** combination is proposed by the client in a single connect attempt). If the admin wishes to restrict the accepted algorithms now or at any future time, they may use the `-aK`, `-aC` and `-aH` options when launching the server to define a whitelist which excludes certain primitives.
|
||||
|
||||
As of release v0.9.3, the default when supplying no explicit KEX, cipher or HMAC algorithms to `xsd` results in *no* algs being accepted; so the admin must decide on a specific whitelist of algorithms.
|
||||
***
|
||||
|
||||
|
||||
### Conn
|
||||
Calls to hkexnet.Dial() and hkexnet.Listen()/Accept() are generally the same as calls to the equivalents within the _net_ package; however upon connection a key exchange automatically occurs whereby client and server independently derive the same keying material, and all following traffic is secured by a symmetric encryption algorithm.
|
||||
Calls to xsnet.Dial() and xsnet.Listen()/Accept() are generally the same as calls to the equivalents within the _net_ package; however upon connection a key exchange automatically occurs whereby client and server independently derive the same keying material, and all following traffic is secured by a symmetric encryption algorithm.
|
||||
|
||||
### Session Negotiation
|
||||
Above the hkexnet.Conn layer, the server and client apps in this repository (hkexshd/ and hkexsh/ respectively) negotiate session settings (cipher/hmac algorithms, interactive/non-interactive mode, tunnel specifiers, etc.) to be used for communication.
|
||||
Above the xsnet.Conn layer, the server and client apps in this repository (xsd/ and xs/ respectively) negotiate session settings (cipher/hmac algorithms, interactive/non-interactive mode, tunnel specifiers, etc.) to be used for communication.
|
||||
|
||||
### Padding and Chaffing
|
||||
Packets are subject to padding (random size, randomly applied as prefix or postfix), and optionally the client and server channels can both send _chaff_ packets at random defineable intervals to help thwart analysis of session activity (applicable to interactive and non-interactive command sessions, file copies and tunnels).
|
||||
|
@ -54,8 +79,7 @@ Packets are subject to padding (random size, randomly applied as prefix or postf
|
|||
Chaffing and tunnels, if specified, are set up during initial client->server connection. Packets from the client local port(s) are sent through the main secured connection to the server's remote port(s), and vice versa, tagged with a chaff or tunnel specifier so that they can be discarded as chaff or de-multiplexed and delivered to the proper tunnel endpoints, respectively.
|
||||
|
||||
### Accounts and Passwords
|
||||
Within the hkexpasswd/ directory is a password-setting utility. HKExSh uses its own password file distinct from the system /etc/passwd to authenticate clients, using standard bcrypt+salt storage. This is currently done to allow alternate login credentials via hkexsh vs. console/ssh login, due to the experimental nature of the program. At some point in the future an option to use the system's /etc/passwd and /etc/shadow may be implemented, making the use of the auxilliary hkexpasswd utility optional or obsolete.
|
||||
|
||||
Within the ```xspasswd/``` directory is a password-setting utility, ```xspasswd```, used if one wishes ```xs``` access to use separate credentials from those of the default (likely ssh) login method. In this mode, ```xsd``` uses its own password file distinct from the system /etc/passwd to authenticate clients, using standard bcrypt+salt storage. Activate this mode by invoking ```xsd``` with ```-s false```.
|
||||
|
||||
HERRADURA KEX
|
||||
|
||||
|
@ -70,77 +94,114 @@ KYBER IND-CCA-2 KEM
|
|||
|
||||
As of this time (Oct 2018) Kyber is one of the candidate algorithms submitted to the [NIST post-quantum cryptography project](https://csrc.nist.gov/Projects/Post-Quantum-Cryptography). The authors recommend using it in "... so-called hybrid mode in combination with established "pre-quantum" security; for example in combination with elliptic-curve Diffie-Hellman." THIS PROJECT DOES NOT DO THIS (in case you didn't notice yet, THIS PROJECT IS EXPERIMENTAL.)
|
||||
|
||||
### Dependencies:
|
||||
|
||||
* Recent version of go (tested, at various times, with go-1.9 to go-1.12.4)
|
||||
* [github.com/mattn/go-isatty](http://github.com/mattn/go-isatty) //terminal tty detection
|
||||
* [github.com/kr/pty](http://github.com/kr/pty) //unix pty control (server pty connections)
|
||||
* [github.com/jameskeane/bcrypt](http://github.com/jameskeane/bcrypt) //password storage/auth
|
||||
* [blitter.com/go/goutmp](https://gogs.blitter.com/RLabs/goutmp) // wtmp/lastlog C bindings for user accounting
|
||||
* [https://git.schwanenlied.me/yawning/kyber](https://git.schwanenlied.me/yawning/kyber) // golang Kyber KEM
|
||||
* [https://git.schwanenlied.me/yawning/newhope](https://git.schwanenlied.me/yawning/newhope) // golang NEWHOPE,NEWHOPE-SIMPLE KEX
|
||||
* [blitter.com/go/mtwist](https://gogs.blitter.com/RLabs/mtwist) // 64-bit Mersenne Twister PRNG
|
||||
* [blitter.com/go/cryptmt](https://gogs.blitter.com/RLabs/cryptmt) // CryptMTv1 stream cipher
|
||||
### Installing
|
||||
|
||||
As of Go 1.8, one can directly use `go install` to get the client `xs` and server `xsd` binaries; however it is not recommended, as `xsd` requires root and for general use should be in one of the system directories, akin to other daemons. If one insists, the following will work to place them in $HOME/go/bin:
|
||||
|
||||
```
|
||||
$ go install blitter.com/go/xs/xs@latest
|
||||
$ go install blitter.com/go/xs/xsd@latest
|
||||
```
|
||||
|
||||
(NOTE the `-v` (version) option for binaries obtained in this manner will be blank; another reason to build them yourself locally using the steps below.)
|
||||
|
||||
|
||||
### Get source code
|
||||
|
||||
* $ go get -u blitter.com/go/hkexsh
|
||||
* $ cd $GOPATH/src/blitter.com/go/hkexsh
|
||||
* $ go build ./... # install all dependent go pkgs
|
||||
```
|
||||
$ git clone https://gogs.blitter.com/RLabs/xs
|
||||
```
|
||||
|
||||
|
||||
### To build
|
||||
|
||||
* $ cd $GOPATH/src/blitter.com/go/hkexsh
|
||||
* $ make clean all
|
||||
```
|
||||
$ cd xs
|
||||
$ make clean && make
|
||||
```
|
||||
|
||||
### To install, uninstall, re-install
|
||||
### To install, uninstall, re-install (xsd server)
|
||||
|
||||
* $ sudo make [install | uninstall | reinstall]
|
||||
```
|
||||
$ sudo make [install | uninstall | reinstall]
|
||||
```
|
||||
|
||||
### To manage service (assuming openrc init)
|
||||
### To manage service (openrc init)
|
||||
|
||||
* $ sudo rc-config [start | restart | stop] hkexshd
|
||||
An example init script (xsd.initrc) is provided. Consult your Linux distribution documentation for proper service/daemon installation. For openrc,
|
||||
|
||||
```
|
||||
$ sudo cp xsd.initrc /etc/init.d/xsd
|
||||
$ sudo rc-config add xsd default
|
||||
```
|
||||
|
||||
An example init script (hkexshd.initrc) is provided. Consult your Linux distribution documentation for proper service/daemon installation. Default assumes installation in /usr/local/sbin (hkexshd, hkexpasswd) and /usr/local/bin (hkexsh/hkexcp symlink).
|
||||
### To manage service (sysV init)
|
||||
|
||||
An example init script (xsd.sysvrc) is provided. Consult your Linux distribution documentation for proper service/daemon installation. For sysV init,
|
||||
|
||||
### To set accounts & passwords:
|
||||
```
|
||||
$ sudo cp xsd.sysvrc /etc/init.d/xsd
|
||||
$ sudo sysv-rc-conf --level 2345 xsd on
|
||||
```
|
||||
|
||||
* $ sudo touch /etc/hkexsh.passwd
|
||||
* $ sudo hkexpasswd/hkexpasswd -u joebloggs
|
||||
* $ <enter a password, enter again to confirm>
|
||||
The make system assumes installation in /usr/local/sbin (xsd, xspasswd) and /usr/local/bin (xs/xc symlink).
|
||||
|
||||
```
|
||||
$ sudo rc-config [start | restart | stop] xsd
|
||||
# .. or sudo /etc/init.d/xsd [start | restart stop]
|
||||
```
|
||||
|
||||
### To set accounts & passwords (DEPRECATED: `-s` is now true by default)
|
||||
|
||||
```
|
||||
$ sudo touch /etc/xs.passwd
|
||||
$ sudo xspasswd/xspasswd -u joebloggs
|
||||
$ <enter a password, enter again to confirm>
|
||||
```
|
||||
|
||||
### Testing Client and Server from $GOPATH dev tree (w/o 'make install')
|
||||
|
||||
In separate shells A and B:
|
||||
* [A]$ cd hkexshd && sudo ./hkexshd & # add -d for debugging
|
||||
```
|
||||
[A]$ cd xsd && sudo ./xsd & # add -d for debugging
|
||||
```
|
||||
|
||||
Interactive shell
|
||||
* [B]$ cd hkexsh && ./hkexsh joebloggs@host-or-ip # add -d for debugging
|
||||
```
|
||||
[B]$ cd xs && ./xs joebloggs@host-or-ip # add -d for debugging
|
||||
```
|
||||
|
||||
One-shot command
|
||||
* [B]$ cd hkexsh && ./hkexsh -x "ls /tmp" joebloggs@host-or-ip
|
||||
```
|
||||
[B]$ cd xs && ./xs -x "ls /tmp" joebloggs@host-or-ip
|
||||
```
|
||||
|
||||
WARNING WARNING WARNING: the -d debug flag will echo passwords to the log/console!
|
||||
Logging on Linux usually goes to /var/log/syslog and/or /var/log/debug, /var/log/daemon.log.
|
||||
|
||||
NOTE if running client (hkexsh) with -d, one will likely need to run 'reset' afterwards
|
||||
NOTE if running client (xs) with -d, one will likely need to run 'reset' afterwards
|
||||
to fix up the shell tty afterwards, as stty echo may not be restored if client crashes
|
||||
or is interrupted.
|
||||
|
||||
### Setting up an 'authtoken' for scripted (password-free) logins
|
||||
|
||||
Use the -g option of hkexsh to request a token from the remote server, which will return a
|
||||
hostname:token string. Place this string into $HOME/.hkexsh_id to allow logins without
|
||||
entering a password (obviously, $HOME/.hkexsh_id on both server and client for the user
|
||||
Use the -g option of xs to request a token from the remote server, which will return a
|
||||
hostname:token string. Place this string into $HOME/.config/xs/.xs_id to allow logins without
|
||||
entering a password (obviously, $HOME/.config/xs/.xs_id on both server and client for the user
|
||||
should *not* be world-readable.)
|
||||
|
||||
### File Copying using hkexcp
|
||||
```
|
||||
$ xs -g user@host.net >>~/.config/xs/.xs_id
|
||||
```
|
||||
[enter password blindly, authtoken entry will be stored in ~/.config/xs/.xs_id]
|
||||
|
||||
hkexcp is a symlink to hkexsh, and the binary checks its own filename to determine whether
|
||||
NOTE you may need to remove older entries for the same host if this is not the first time you have added
|
||||
it to your .xs_id file.
|
||||
|
||||
### File Copying using xc
|
||||
|
||||
xc is a symlink to xs, and the binary checks its own filename to determine whether
|
||||
it is being invoked in 'shell' or 'copy' mode. Refer to the '-h' output for differences in
|
||||
accepted options.
|
||||
|
||||
|
@ -150,25 +211,47 @@ remote user. File operations are all performed as the remote user, so account pe
|
|||
as expected.
|
||||
|
||||
Local (client) to remote (server) copy:
|
||||
* hkexcp fileA /some/where/fileB /some/where/else/dirC joebloggs@host-or-ip:remoteDir
|
||||
```
|
||||
$ xc fileA /some/where/fileB /some/where/else/dirC joebloggs@host-or-ip:remoteDir
|
||||
```
|
||||
|
||||
Remote (server) to local (client) copy:
|
||||
* hkexcp joebloggs@host-or-ip:/remoteDirOrFile /some/where/local/Dir
|
||||
```
|
||||
$ xc joebloggs@host-or-ip:/remoteDirOrFile /some/where/local/Dir
|
||||
```
|
||||
|
||||
hkexcp uses a 'tarpipe' to send file data over the encrypted channel. Use the -d flag on client or server to see the generated tar commands if you're curious.
|
||||
xc uses a 'tarpipe' to send file data over the encrypted channel. Use the -d flag on client or server to see the generated tar commands if you're curious.
|
||||
|
||||
NOTE: Renaming while copying (eg., 'cp /foo/bar/fileA ./fileB') is NOT supported. Put another way, the destination (whether local or remote) must ALWAYS be a directory.
|
||||
|
||||
If the 'pv' pipeview utility is available (http://www.ivarch.com/programs/pv.shtml) file transfer progress and bandwidth control will be available (suppress the former with the -q option, set the latter with -L <bytes_per_second>).
|
||||
|
||||
Special care should be taken when doing client → server copies: since the tarpipe (should) always succeed at least sending data to the remote side, a destination with no write permission will not return a nonzero status and the client closes its end after sending all data, giving the server no opportunity to send an error code to the client.
|
||||
It is recommended to test beforehand if the server-side destination is writable (and optionally if the destination already exists, if one does not want to clobber an existing path) by:
|
||||
|
||||
```
|
||||
$ xs -x "test -w /dest/path" me@myserver ## If clobbering /dest/path is OK, or
|
||||
$ xs -x "test -w /dest/path -o ! -e /dest/path" me@myserver ## To prevent clobbering
|
||||
```
|
||||
|
||||
Perhaps in future a more complex handshake will be devised to allow the client to half-close the tarpipe, allowing the server to complete its side of the operation and send back its success or failure code, but the current connection protocol does not allow this. If this is a deal-breaking feature, please contact the maintainer.
|
||||
|
||||
### Tunnels
|
||||
|
||||
Simple tunnels (client -> server, no reverse tunnels for now) are supported.
|
||||
Simple tunnels (client → server, no reverse tunnels for now) are supported.
|
||||
|
||||
Syntax: hkexsh -T=<tunspec>{,<tunspec>...}
|
||||
Syntax: xs -T=<tunspec>{,<tunspec>...}
|
||||
.. where <tunspec> is <localport:remoteport>
|
||||
|
||||
Example, tunnelling ssh through hkexsh
|
||||
Example, tunnelling ssh through xs
|
||||
|
||||
* [server side] $ sudo /usr/sbin/sshd -p 7002
|
||||
* [client side, term A] $ hkexsh -T=6002:7002 user@server
|
||||
* [client side, term B] $ ssh user@localhost -p 6002
|
||||
* [server side] ```$ sudo /usr/sbin/sshd -p 7002```
|
||||
* [client side, term A] ```$ xs -T=6002:7002 user@server```
|
||||
* [client side, term B] ```$ ssh user@localhost -p 6002```
|
||||
|
||||
|
||||
### Building for FreeBSD
|
||||
|
||||
The Makefile(s) to build require GNU make (gmake).
|
||||
Please install and invoke build via:
|
||||
```$ gmake clean all```
|
||||
|
|
1
TODO.txt
|
@ -18,6 +18,7 @@ Architecture
|
|||
(parts split out into hkexnet/*, hkexsession.go)
|
||||
(DONE) - Make KEx fully-pluggable: isolate all code to do with Herradura into a
|
||||
KEx-neutral pkg so it can be swapped out for other methods (eg., DH etc.)
|
||||
(DONE - test branch) - Use system password db (/etc/{passwd,shadow})
|
||||
|
||||
Features
|
||||
(DONE) - Support for hkcp (hkex-cp) - secure file copy protocol
|
||||
|
|
|
@ -0,0 +1,235 @@
|
|||
package xs
|
||||
|
||||
// Package xs - a secure terminal client/server written from scratch in Go
|
||||
//
|
||||
// Copyright (c) 2017-2020 Russell Magee
|
||||
// Licensed under the terms of the MIT license (see LICENSE.mit in this
|
||||
// distribution)
|
||||
//
|
||||
// golang implementation by Russ Magee (rmagee_at_gmail.com)
|
||||
|
||||
// Authentication routines for the HKExSh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/csv"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/user"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"blitter.com/go/xs/xsnet"
|
||||
"github.com/jameskeane/bcrypt"
|
||||
passlib "gopkg.in/hlandau/passlib.v1"
|
||||
)
|
||||
|
||||
type AuthCtx struct {
|
||||
reader func(string) ([]byte, error) // eg. ioutil.ReadFile()
|
||||
userlookup func(string) (*user.User, error) // eg. os/user.Lookup()
|
||||
}
|
||||
|
||||
func NewAuthCtx( /*reader func(string) ([]byte, error), userlookup func(string) (*user.User, error)*/ ) (ret *AuthCtx) {
|
||||
ret = &AuthCtx{ioutil.ReadFile, user.Lookup}
|
||||
return
|
||||
}
|
||||
|
||||
// --------- System passwd/shadow auth routine(s) --------------
|
||||
|
||||
// VerifyPass verifies a password against system standard shadow file
|
||||
// Note auxilliary fields for expiry policy are *not* inspected.
|
||||
func VerifyPass(ctx *AuthCtx, user, password string) (bool, error) {
|
||||
if ctx.reader == nil {
|
||||
ctx.reader = ioutil.ReadFile // dependency injection hides that this is required
|
||||
}
|
||||
passlib.UseDefaults(passlib.Defaults20180601)
|
||||
var pwFileName string
|
||||
if runtime.GOOS == "linux" {
|
||||
pwFileName = "/etc/shadow"
|
||||
} else if runtime.GOOS == "freebsd" {
|
||||
pwFileName = "/etc/master.passwd"
|
||||
} else {
|
||||
return false, errors.New("Unsupported platform")
|
||||
}
|
||||
pwFileData, e := ctx.reader(pwFileName)
|
||||
if e != nil {
|
||||
return false, e
|
||||
}
|
||||
pwLines := strings.Split(string(pwFileData), "\n")
|
||||
if len(pwLines) < 1 {
|
||||
return false, errors.New("Empty shadow file!")
|
||||
} else {
|
||||
var line string
|
||||
var hash string
|
||||
var idx int
|
||||
for idx = range pwLines {
|
||||
line = pwLines[idx]
|
||||
lFields := strings.Split(line, ":")
|
||||
if lFields[0] == user {
|
||||
hash = lFields[1]
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(hash) == 0 {
|
||||
return false, errors.New("nil hash!")
|
||||
} else {
|
||||
pe := passlib.VerifyNoUpgrade(password, hash)
|
||||
if pe != nil {
|
||||
return false, pe
|
||||
}
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// --------- End System passwd/shadow auth routine(s) ----------
|
||||
|
||||
// ------------- xs-local passwd auth routine(s) ---------------
|
||||
|
||||
// AuthUserByPasswd checks user login information using a password.
|
||||
// This checks /etc/xs.passwd for auth info, and system /etc/passwd
|
||||
// to cross-check the user actually exists.
|
||||
// nolint: gocyclo
|
||||
func AuthUserByPasswd(ctx *AuthCtx, username string, auth string, fname string) (valid bool, allowedCmds string) {
|
||||
if ctx.reader == nil {
|
||||
ctx.reader = ioutil.ReadFile // dependency injection hides that this is required
|
||||
}
|
||||
if ctx.userlookup == nil {
|
||||
ctx.userlookup = user.Lookup // again for dependency injection as dep is now hidden
|
||||
}
|
||||
b, e := ctx.reader(fname) // nolint: gosec
|
||||
if e != nil {
|
||||
valid = false
|
||||
log.Printf("ERROR: Cannot read %s!\n", fname)
|
||||
}
|
||||
r := csv.NewReader(bytes.NewReader(b))
|
||||
|
||||
r.Comma = ':'
|
||||
r.Comment = '#'
|
||||
r.FieldsPerRecord = 3 // username:salt:authCookie [TODO:disallowedCmdList (a,b,...)]
|
||||
for {
|
||||
record, err := r.Read()
|
||||
if err == io.EOF {
|
||||
// Use dummy entry if user not found
|
||||
// (prevent user enumeration attack via obvious timing diff;
|
||||
// ie., not attempting any auth at all)
|
||||
record = []string{"$nosuchuser$",
|
||||
"$2a$12$l0coBlRDNEJeQVl6GdEPbU",
|
||||
"$2a$12$l0coBlRDNEJeQVl6GdEPbUC/xmuOANvqgmrMVum6S4i.EXPgnTXy6"}
|
||||
username = "$nosuchuser$"
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if username == record[0] {
|
||||
tmp, err := bcrypt.Hash(auth, record[1])
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if tmp == record[2] && username != "$nosuchuser$" {
|
||||
valid = true
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
// Security scrub
|
||||
for i := range b {
|
||||
b[i] = 0
|
||||
}
|
||||
r = nil
|
||||
runtime.GC()
|
||||
|
||||
_, userErr := ctx.userlookup(username)
|
||||
if userErr != nil {
|
||||
valid = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ------------- End xs-local passwd auth routine(s) -----------
|
||||
|
||||
// AuthUserByToken checks user login information against an auth token.
|
||||
// Auth tokens are stored in each user's $HOME/.config/xs/.xs_id and are requested
|
||||
// via the -g option.
|
||||
// The function also check system /etc/passwd to cross-check the user
|
||||
// actually exists.
|
||||
func AuthUserByToken(ctx *AuthCtx, username string, connhostname string, auth string) (valid bool) {
|
||||
if ctx.reader == nil {
|
||||
ctx.reader = ioutil.ReadFile // dependency injection hides that this is required
|
||||
}
|
||||
if ctx.userlookup == nil {
|
||||
ctx.userlookup = user.Lookup // again for dependency injection as dep is now hidden
|
||||
}
|
||||
|
||||
auth = strings.TrimSpace(auth)
|
||||
u, ue := ctx.userlookup(username)
|
||||
if ue != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
b, e := ctx.reader(fmt.Sprintf("%s/%s", u.HomeDir, xsnet.XS_ID_AUTHTOKFILE))
|
||||
if e != nil {
|
||||
log.Printf("INFO: Cannot read %s/%s\n", u.HomeDir, xsnet.XS_ID_AUTHTOKFILE)
|
||||
return false
|
||||
}
|
||||
|
||||
r := csv.NewReader(bytes.NewReader(b))
|
||||
|
||||
r.Comma = ':'
|
||||
r.Comment = '#'
|
||||
r.FieldsPerRecord = 3 // connhost:username:authtoken
|
||||
for {
|
||||
record, err := r.Read()
|
||||
if err == io.EOF {
|
||||
return false
|
||||
}
|
||||
if len(record) < 3 ||
|
||||
len(record[0]) < 1 ||
|
||||
len(record[1]) < 1 ||
|
||||
len(record[2]) < 1 {
|
||||
return false
|
||||
}
|
||||
record[0] = strings.TrimSpace(record[0])
|
||||
record[1] = strings.TrimSpace(record[1])
|
||||
record[2] = strings.TrimSpace(record[2])
|
||||
//fmt.Println("auth:", auth, "record:",
|
||||
// strings.Join([]string{record[0], record[1], record[2]}, ":"))
|
||||
|
||||
if (connhostname == record[0]) &&
|
||||
username == record[1] &&
|
||||
(auth == strings.Join([]string{record[0], record[1], record[2]}, ":")) {
|
||||
valid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
_, userErr := ctx.userlookup(username)
|
||||
if userErr != nil {
|
||||
valid = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func GetTool(tool string) (ret string) {
|
||||
ret = "/bin/" + tool
|
||||
_, err := os.Stat(ret)
|
||||
if err == nil {
|
||||
return ret
|
||||
}
|
||||
ret = "/usr/bin/" + tool
|
||||
_, err = os.Stat(ret)
|
||||
if err == nil {
|
||||
return ret
|
||||
}
|
||||
ret = "/usr/local/bin/" + tool
|
||||
_, err = os.Stat(ret)
|
||||
if err == nil {
|
||||
return ret
|
||||
}
|
||||
return ""
|
||||
}
|
|
@ -0,0 +1,212 @@
|
|||
package xs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/user"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type userVerifs struct {
|
||||
user string
|
||||
passwd string
|
||||
good bool
|
||||
}
|
||||
|
||||
var (
|
||||
dummyShadowA = `johndoe:$6$EeQlTtn/KXdSh6CW$UHbFuEw3UA0Jg9/GoPHxgWk6Ws31x3IjqsP22a9pVMOte0yQwX1.K34oI4FACu8GRg9DArJ5RyWUE9m98qwzZ1:18310:0:99999:7:::
|
||||
joebloggs:$6$F.0IXOrb0w0VJHG1$3O4PYyng7F3hlh42mbroEdQZvslybY5etPPiLMQJ1xosjABY.Q4xqAfyIfe03Du61ZjGQIt3nL0j12P9k1fsK/:18310:0:99999:7:::
|
||||
disableduser:!:18310::::::`
|
||||
|
||||
dummyAuthTokenFile = "hostA:johndoe:abcdefg\nhostB:imposter:wxyz\n"
|
||||
|
||||
dummyXsPasswdFile = `#username:salt:authCookie
|
||||
bobdobbs:$2a$12$9vqGkFqikspe/2dTARqu1O:$2a$12$9vqGkFqikspe/2dTARqu1OuDKCQ/RYWsnaFjmi.HtmECRkxcZ.kBK
|
||||
notbob:$2a$12$cZpiYaq5U998cOkXzRKdyu:$2a$12$cZpiYaq5U998cOkXzRKdyuJ2FoEQyVLa3QkYdPQk74VXMoAzhvuP6
|
||||
`
|
||||
|
||||
testGoodUsers = []userVerifs{
|
||||
{"johndoe", "testpass", true},
|
||||
{"joebloggs", "testpass2", true},
|
||||
{"johndoe", "badpass", false},
|
||||
}
|
||||
|
||||
testXsPasswdUsers = []userVerifs{
|
||||
{"bobdobbs", "praisebob", true},
|
||||
{"notbob", "imposter", false},
|
||||
}
|
||||
|
||||
userlookup_arg_u string
|
||||
readfile_arg_f string
|
||||
)
|
||||
|
||||
func newMockAuthCtx(reader func(string) ([]byte, error), userlookup func(string) (*user.User, error)) (ret *AuthCtx) {
|
||||
ret = &AuthCtx{reader, userlookup}
|
||||
return
|
||||
}
|
||||
|
||||
func _mock_user_Lookup(username string) (*user.User, error) {
|
||||
username = userlookup_arg_u
|
||||
if username == "baduser" {
|
||||
return &user.User{}, errors.New("bad user")
|
||||
}
|
||||
urec := &user.User{Uid: "1000", Gid: "1000", Username: username, Name: "Full Name", HomeDir: "/home/user"}
|
||||
fmt.Printf(" [mock user rec:%v]\n", urec)
|
||||
return urec, nil
|
||||
}
|
||||
|
||||
func _mock_ioutil_ReadFile(f string) ([]byte, error) {
|
||||
f = readfile_arg_f
|
||||
if f == "/etc/shadow" {
|
||||
fmt.Println(" [mocking ReadFile(\"/etc/shadow\")]")
|
||||
return []byte(dummyShadowA), nil
|
||||
}
|
||||
if f == "/etc/xs.passwd" {
|
||||
fmt.Println(" [mocking ReadFile(\"/etc/xs.passwd\")]")
|
||||
return []byte(dummyXsPasswdFile), nil
|
||||
}
|
||||
if strings.Contains(f, "/.xs_id") {
|
||||
fmt.Println(" [mocking ReadFile(\".xs_id\")]")
|
||||
return []byte(dummyAuthTokenFile), nil
|
||||
}
|
||||
return []byte{}, errors.New("no readfile_arg_f supplied")
|
||||
}
|
||||
|
||||
func _mock_ioutil_ReadFileEmpty(f string) ([]byte, error) {
|
||||
return []byte{}, nil
|
||||
}
|
||||
|
||||
func _mock_ioutil_ReadFileHasError(f string) ([]byte, error) {
|
||||
return []byte{}, errors.New("IO Error")
|
||||
}
|
||||
|
||||
func TestVerifyPass(t *testing.T) {
|
||||
readfile_arg_f = "/etc/shadow"
|
||||
ctx := newMockAuthCtx(_mock_ioutil_ReadFile, nil)
|
||||
for idx, rec := range testGoodUsers {
|
||||
stat, e := VerifyPass(ctx, rec.user, rec.passwd)
|
||||
if rec.good && (!stat || e != nil) {
|
||||
t.Fatalf("failed %d\n", idx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyPassFailsOnEmptyFile(t *testing.T) {
|
||||
ctx := newMockAuthCtx(_mock_ioutil_ReadFileEmpty, nil)
|
||||
stat, e := VerifyPass(ctx, "johndoe", "somepass")
|
||||
if stat || (e == nil) {
|
||||
t.Fatal("failed to fail w/empty file")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyPassFailsOnFileError(t *testing.T) {
|
||||
ctx := newMockAuthCtx(_mock_ioutil_ReadFileEmpty, nil)
|
||||
stat, e := VerifyPass(ctx, "johndoe", "somepass")
|
||||
if stat || (e == nil) {
|
||||
t.Fatal("failed to fail on ioutil.ReadFile error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyPassFailsOnDisabledEntry(t *testing.T) {
|
||||
ctx := newMockAuthCtx(_mock_ioutil_ReadFileEmpty, nil)
|
||||
stat, e := VerifyPass(ctx, "disableduser", "!")
|
||||
if stat || (e == nil) {
|
||||
t.Fatal("failed to fail on disabled user entry")
|
||||
}
|
||||
}
|
||||
|
||||
////
|
||||
|
||||
func TestAuthUserByTokenFailsOnMissingEntryForHost(t *testing.T) {
|
||||
ctx := newMockAuthCtx(_mock_ioutil_ReadFile, _mock_user_Lookup)
|
||||
stat := AuthUserByToken(ctx, "johndoe", "hostZ", "abcdefg")
|
||||
if stat {
|
||||
t.Fatal("failed to fail on missing/mismatched host entry")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthUserByTokenFailsOnMissingEntryForUser(t *testing.T) {
|
||||
ctx := newMockAuthCtx(_mock_ioutil_ReadFile, _mock_user_Lookup)
|
||||
stat := AuthUserByToken(ctx, "unkuser", "hostA", "abcdefg")
|
||||
if stat {
|
||||
t.Fatal("failed to fail on wrong user")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthUserByTokenFailsOnUserLookupFailure(t *testing.T) {
|
||||
ctx := newMockAuthCtx(_mock_ioutil_ReadFile, _mock_user_Lookup)
|
||||
userlookup_arg_u = "baduser"
|
||||
stat := AuthUserByToken(ctx, "johndoe", "hostA", "abcdefg")
|
||||
if stat {
|
||||
t.Fatal("failed to fail with bad return from user.Lookup()")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthUserByTokenFailsOnMismatchedTokenForUser(t *testing.T) {
|
||||
ctx := newMockAuthCtx(_mock_ioutil_ReadFile, _mock_user_Lookup)
|
||||
stat := AuthUserByToken(ctx, "johndoe", "hostA", "badtoken")
|
||||
if stat {
|
||||
t.Fatal("failed to fail with valid user, bad token")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthUserByTokenSucceedsWithMatchedUserAndToken(t *testing.T) {
|
||||
ctx := newMockAuthCtx(_mock_ioutil_ReadFile, _mock_user_Lookup)
|
||||
userlookup_arg_u = "johndoe"
|
||||
readfile_arg_f = "/.xs_id"
|
||||
stat := AuthUserByToken(ctx, userlookup_arg_u, "hostA", "hostA:johndoe:abcdefg")
|
||||
if !stat {
|
||||
t.Fatal("failed with valid user and token")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthUserByPasswdFailsOnEmptyFile(t *testing.T) {
|
||||
ctx := newMockAuthCtx(_mock_ioutil_ReadFileEmpty, _mock_user_Lookup)
|
||||
userlookup_arg_u = "bobdobbs"
|
||||
readfile_arg_f = "/etc/xs.passwd"
|
||||
stat, _ := AuthUserByPasswd(ctx, userlookup_arg_u, "praisebob", readfile_arg_f)
|
||||
if stat {
|
||||
t.Fatal("failed to fail with missing xs.passwd file")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthUserByPasswdFailsOnBadAuth(t *testing.T) {
|
||||
ctx := newMockAuthCtx(_mock_ioutil_ReadFile, _mock_user_Lookup)
|
||||
userlookup_arg_u = "bobdobbs"
|
||||
readfile_arg_f = "/etc/xs.passwd"
|
||||
stat, _ := AuthUserByPasswd(ctx, userlookup_arg_u, "wrongpass", readfile_arg_f)
|
||||
if stat {
|
||||
t.Fatal("failed to fail with valid user, incorrect passwd in xs.passwd file")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthUserByPasswdFailsOnBadUser(t *testing.T) {
|
||||
ctx := newMockAuthCtx(_mock_ioutil_ReadFile, _mock_user_Lookup)
|
||||
userlookup_arg_u = "bobdobbs"
|
||||
readfile_arg_f = "/etc/xs.passwd"
|
||||
stat, _ := AuthUserByPasswd(ctx, userlookup_arg_u, "theotherbob", readfile_arg_f)
|
||||
if stat {
|
||||
t.Fatal("failed to fail on invalid user vs. xs.passwd file")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthUserByPasswdPassesOnGoodAuth(t *testing.T) {
|
||||
ctx := newMockAuthCtx(_mock_ioutil_ReadFile, _mock_user_Lookup)
|
||||
userlookup_arg_u = "bobdobbs"
|
||||
readfile_arg_f = "/etc/xs.passwd"
|
||||
stat, _ := AuthUserByPasswd(ctx, userlookup_arg_u, "praisebob", readfile_arg_f)
|
||||
if !stat {
|
||||
t.Fatal("failed on valid user w/correct passwd in xs.passwd file")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthUserByPasswdPassesOnOtherGoodAuth(t *testing.T) {
|
||||
ctx := newMockAuthCtx(_mock_ioutil_ReadFile, _mock_user_Lookup)
|
||||
userlookup_arg_u = "notbob"
|
||||
readfile_arg_f = "/etc/xs.passwd"
|
||||
stat, _ := AuthUserByPasswd(ctx, userlookup_arg_u, "imposter", readfile_arg_f)
|
||||
if !stat {
|
||||
t.Fatal("failed on valid user 2nd entry w/correct passwd in xs.passwd file")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
## bacillus (https://gogs.blitter.com/Russtopia/bacillus) build/test CI script
|
||||
|
||||
export GOPATH="${HOME}/go"
|
||||
export PATH=/usr/local/bin:/usr/bin:/usr/lib/ccache/bin:/bin:$GOPATH/bin
|
||||
unset GO111MODULE
|
||||
#export GOPROXY="direct"
|
||||
#!# GOCACHE will be phased out in v1.12. [github.com/golang/go/issues/26809]
|
||||
#!export GOCACHE="${HOME}/.cache/go-build"
|
||||
|
||||
echo "workdir: ${BACILLUS_WORKDIR}"
|
||||
mkdir -p "${BACILLUS_ARTFDIR}"
|
||||
|
||||
echo "---"
|
||||
go env
|
||||
echo "---"
|
||||
echo "passed env:"
|
||||
env
|
||||
echo "---"
|
||||
|
||||
cd ${REPO}
|
||||
branch=$(git for-each-ref --sort=-committerdate --format='%(refname)' | head -n 1)
|
||||
echo "Building most recent push on branch $branch"
|
||||
git checkout "$branch"
|
||||
ls
|
||||
|
||||
go mod init
|
||||
go mod tidy
|
||||
|
||||
############
|
||||
stage "Build"
|
||||
############
|
||||
echo "Invoking 'make clean' ..."
|
||||
make clean
|
||||
echo "Invoking 'make all' ..."
|
||||
make all
|
||||
|
||||
############
|
||||
stage "Lint"
|
||||
############
|
||||
make lint
|
||||
|
||||
############
|
||||
stage "UnitTests"
|
||||
############
|
||||
go test -v .
|
||||
|
||||
############
|
||||
stage "Test(Authtoken)"
|
||||
############
|
||||
if [ -f ~/.config/xs/.xs_id ]; then
|
||||
echo "Clearing test user $USER .xs_id file ..."
|
||||
mv ~/.config/xs/.xs_id ~/.config/xs/.xs_id.bak
|
||||
fi
|
||||
echo "Setting dummy authtoken in .xs_id ..."
|
||||
echo "localhost:${USER}:asdfasdfasdf" >~/.config/xs/.xs_id
|
||||
echo "Performing remote command on @localhost via authtoken login ..."
|
||||
tokentest=$(timeout 10 xs -x "echo -n FOO" @localhost)
|
||||
if [ "${tokentest}" != "FOO" ]; then
|
||||
echo "AUTHTOKEN LOGIN FAILED"
|
||||
exit 1
|
||||
else
|
||||
echo "client cmd performed OK."
|
||||
unset tokentest
|
||||
fi
|
||||
|
||||
############
|
||||
stage "Test(xc S->C)"
|
||||
############
|
||||
echo "Testing secure copy from server -> client ..."
|
||||
./xc_testfiles.sh
|
||||
|
||||
tmpdir=$$
|
||||
mkdir -p /tmp/$tmpdir
|
||||
cd /tmp/$tmpdir
|
||||
xc @localhost:${BACILLUS_WORKDIR}/build/xs/cptest .
|
||||
echo -n "Integrity check on copied files (sha1sum) ..."
|
||||
sha1sum $(find cptest -type f | sort) >sc.sha1sum
|
||||
diff sc.sha1sum ${BACILLUS_WORKDIR}/build/xs/cptest.sha1sum
|
||||
stat=$?
|
||||
cd -
|
||||
|
||||
rm -rf /tmp/$tmpdir
|
||||
if [ $stat -eq "0" ]; then
|
||||
echo "OK."
|
||||
else
|
||||
echo "FAILED!"
|
||||
exit $stat
|
||||
fi
|
||||
|
||||
############
|
||||
stage "Test(xc C->S)"
|
||||
############
|
||||
echo "TODO ..."
|
||||
|
||||
if [ -f ~/.config/xs/.xs_id.bak ]; then
|
||||
echo "Restoring test user $USER .xs_id file ..."
|
||||
mv ~/.config/xs/.xs_id.bak ~/.config/xs/.xs_id
|
||||
fi
|
||||
|
||||
############
|
||||
stage "Artifacts"
|
||||
############
|
||||
echo -n "Creating tarfile ..."
|
||||
tar -cz --exclude=.git --exclude=cptest -f ${BACILLUS_ARTFDIR}/xs.tgz .
|
||||
|
||||
############
|
||||
stage "Cleanup"
|
||||
############
|
||||
rm -rf cptest
|
||||
|
||||
echo
|
||||
echo "--Done--"
|
|
@ -1,10 +1,10 @@
|
|||
// Package hkexsh - a secure terminal client/server written from scratch in Go
|
||||
// Package xs - a secure terminal client/server written from scratch in Go
|
||||
//
|
||||
// Copyright (c) 2017-2018 Russell Magee
|
||||
// Copyright (c) 2017-2020 Russell Magee
|
||||
// Licensed under the terms of the MIT license (see LICENSE.mit in this
|
||||
// distribution)
|
||||
//
|
||||
// golang implementation by Russ Magee (rmagee_at_gmail.com)
|
||||
package hkexsh
|
||||
package xs
|
||||
|
||||
// common constants for the HKExSh
|
||||
// common constants for the XS (Xperimental Shell)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
306637b5c621892078ebadd9454a78820a000598 cptest/file16KB
|
||||
1a118dfff291352eb4aec02c34f4f957669460fc cptest/file1KB
|
||||
f474d5da45890b7cb5b0ae84c8ade5abcb3b4474 cptest/file32KB
|
||||
03939175ceac92b9c6464d037a0243e22563c423 cptest/file6B
|
||||
da67c7698b25d94c0cc20284ba9d4008cdee201b cptest/subdir/file32MB
|
||||
9da9888265371375b48c224b94a0b3132b7ddc41 cptest/subdir/file64MB
|
||||
6010a446cdcf8c1203c2d08998cc69a8c88f77d5 cptest/file16KB
|
||||
f086ea96f3718efd78e6791178f967585acb3701 cptest/file1KB
|
||||
6061e16c3d3840712d0b8e5268c49b5c2f8137ac cptest/file32KB
|
||||
490af32035bbe737480f06439a02d91a171ac407 cptest/file6B
|
||||
e4a8a4ad9678b7265a28c5f0cb5b078e1049ea23 cptest/subdir/file32MB
|
||||
82b50550c7d0e0d6ac18d9d0796b6814c3e038bf cptest/subdir/file64MB
|
||||
|
|
18
go.mod
|
@ -1,18 +0,0 @@
|
|||
module blitter.com/go/hkexsh
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
blitter.com/go/cryptmt v1.0.0
|
||||
blitter.com/go/goutmp v1.0.1
|
||||
blitter.com/go/herradurakex v1.0.0
|
||||
blitter.com/go/mtwist v1.0.0 // indirect
|
||||
git.schwanenlied.me/yawning/chacha20.git v0.0.0-20170904085104-e3b1f968fc63 // indirect
|
||||
git.schwanenlied.me/yawning/kyber.git v0.0.0-20180530164001-a270899bd22c
|
||||
git.schwanenlied.me/yawning/newhope.git v0.0.0-20170622154529-9598792ba8f2
|
||||
github.com/jameskeane/bcrypt v0.0.0-20120420032655-c3cd44c1e20f
|
||||
github.com/kr/pty v1.1.4
|
||||
github.com/mattn/go-isatty v0.0.7
|
||||
golang.org/x/crypto v0.0.0-20190417174047-f416ebab96af
|
||||
golang.org/x/sys v0.0.0-20190416152802-12500544f89f
|
||||
)
|
14
go.sum
|
@ -1,14 +0,0 @@
|
|||
blitter.com/go/cryptmt v1.0.0/go.mod h1:tdME2J3O4agaDAYIYNQzzuB28yVGnPSMmV3a/ucSU84=
|
||||
blitter.com/go/goutmp v1.0.1/go.mod h1:gtlbjC8xGzMk/Cf0BpnVltSa3awOqJ+B5WAxVptTMxk=
|
||||
blitter.com/go/herradurakex v1.0.0/go.mod h1:m3+vYZX+2dDjdo+n/HDnXEYJX9pwmNeQLgAfJM8mtxw=
|
||||
blitter.com/go/mtwist v1.0.0/go.mod h1:aU82Nx8+b1v8oZRNqImfEDzDTPim81rY0ACKAIclV18=
|
||||
git.schwanenlied.me/yawning/chacha20.git v0.0.0-20170904085104-e3b1f968fc63/go.mod h1:NYi4Ifd1g/YbhIDgDfw6t7QdsW4tofQWMX/+FiDtJWs=
|
||||
git.schwanenlied.me/yawning/kyber.git v0.0.0-20180530164001-a270899bd22c/go.mod h1:QrbgzU5EL/1jaMD5pD4Tiikj3R5elPMa+RMwFUTGwQU=
|
||||
git.schwanenlied.me/yawning/newhope.git v0.0.0-20170622154529-9598792ba8f2/go.mod h1:weMqACFGzJs4Ni+K9shsRd02N4LkDrtGlkRxISK+II0=
|
||||
github.com/jameskeane/bcrypt v0.0.0-20120420032655-c3cd44c1e20f/go.mod h1:u+9Snq0w+ZdYKi8BBoaxnEwWu0fY4Kvu9ByFpM51t1s=
|
||||
github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
golang.org/x/crypto v0.0.0-20190417174047-f416ebab96af/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190416152802-12500544f89f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
@ -1,5 +1,5 @@
|
|||
env:
|
||||
project: hkexsh
|
||||
project: xs
|
||||
version: 0.8.0
|
||||
buildDir: build/
|
||||
docDir: doc/
|
||||
|
@ -38,7 +38,7 @@ commands:
|
|||
|
||||
app:
|
||||
aliases: [ build ]
|
||||
help: build the hkexsh tools
|
||||
help: build the xs tools
|
||||
exec: |
|
||||
make clean
|
||||
make all
|
||||
|
|
130
hkexauth.go
|
@ -1,130 +0,0 @@
|
|||
package hkexsh
|
||||
|
||||
// Package hkexsh - a secure terminal client/server written from scratch in Go
|
||||
//
|
||||
// Copyright (c) 2017-2018 Russell Magee
|
||||
// Licensed under the terms of the MIT license (see LICENSE.mit in this
|
||||
// distribution)
|
||||
//
|
||||
// golang implementation by Russ Magee (rmagee_at_gmail.com)
|
||||
|
||||
// Authentication routines for the HKExSh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os/user"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/jameskeane/bcrypt"
|
||||
)
|
||||
|
||||
func userExistsOnSystem(who string) bool {
|
||||
_, userErr := user.Lookup(who)
|
||||
return userErr == nil
|
||||
}
|
||||
|
||||
// AuthUserByPasswd checks user login information using a password.
|
||||
// This checks /etc/hkexsh.passwd for auth info, and system /etc/passwd
|
||||
// to cross-check the user actually exists.
|
||||
// nolint: gocyclo
|
||||
func AuthUserByPasswd(username string, auth string, fname string) (valid bool, allowedCmds string) {
|
||||
b, e := ioutil.ReadFile(fname) // nolint: gosec
|
||||
if e != nil {
|
||||
valid = false
|
||||
log.Printf("ERROR: Cannot read %s!\n", fname)
|
||||
}
|
||||
r := csv.NewReader(bytes.NewReader(b))
|
||||
|
||||
r.Comma = ':'
|
||||
r.Comment = '#'
|
||||
r.FieldsPerRecord = 3 // username:salt:authCookie [TODO:disallowedCmdList (a,b,...)]
|
||||
for {
|
||||
record, err := r.Read()
|
||||
if err == io.EOF {
|
||||
// Use dummy entry if user not found
|
||||
// (prevent user enumeration attack via obvious timing diff;
|
||||
// ie., not attempting any auth at all)
|
||||
record = []string{"$nosuchuser$",
|
||||
"$2a$12$l0coBlRDNEJeQVl6GdEPbU",
|
||||
"$2a$12$l0coBlRDNEJeQVl6GdEPbUC/xmuOANvqgmrMVum6S4i.EXPgnTXy6"}
|
||||
username = "$nosuchuser$"
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if username == record[0] {
|
||||
tmp, err := bcrypt.Hash(auth, record[1])
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if tmp == record[2] && username != "$nosuchuser$" {
|
||||
valid = true
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
// Security scrub
|
||||
for i := range b {
|
||||
b[i] = 0
|
||||
}
|
||||
r = nil
|
||||
runtime.GC()
|
||||
|
||||
if !userExistsOnSystem(username) {
|
||||
valid = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// AuthUserByToken checks user login information against an auth token.
|
||||
// Auth tokens are stored in each user's $HOME/.hkexsh_id and are requested
|
||||
// via the -g option.
|
||||
// The function also check system /etc/passwd to cross-check the user
|
||||
// actually exists.
|
||||
func AuthUserByToken(username string, connhostname string, auth string) (valid bool) {
|
||||
auth = strings.TrimSpace(auth)
|
||||
u, ue := user.Lookup(username)
|
||||
if ue != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
b, e := ioutil.ReadFile(fmt.Sprintf("%s/.hkexsh_id", u.HomeDir))
|
||||
if e != nil {
|
||||
log.Printf("INFO: Cannot read %s/.hkexsh_id\n", u.HomeDir)
|
||||
return false
|
||||
}
|
||||
|
||||
r := csv.NewReader(bytes.NewReader(b))
|
||||
|
||||
r.Comma = ':'
|
||||
r.Comment = '#'
|
||||
r.FieldsPerRecord = 2 // connhost:authtoken
|
||||
for {
|
||||
record, err := r.Read()
|
||||
if err == io.EOF {
|
||||
return false
|
||||
}
|
||||
record[0] = strings.TrimSpace(record[0])
|
||||
record[1] = strings.TrimSpace(record[1])
|
||||
//fmt.Println("auth:", auth, "record:",
|
||||
// strings.Join([]string{record[0], record[1]}, ":"))
|
||||
|
||||
if (connhostname == record[0]) &&
|
||||
(auth == strings.Join([]string{record[0], record[1]}, ":")) {
|
||||
valid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !userExistsOnSystem(username) {
|
||||
valid = false
|
||||
}
|
||||
return
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
digraph gocallvis {
|
||||
label="blitter.com/go/hkexsh/hkexpasswd";
|
||||
labeljust="l";
|
||||
fontname="Arial";
|
||||
fontsize="14";
|
||||
rankdir="LR";
|
||||
bgcolor="lightgray";
|
||||
style="solid";
|
||||
penwidth="0.5";
|
||||
pad="0.0";
|
||||
nodesep="0.35";
|
||||
|
||||
node [shape="ellipse" style="filled" fillcolor="honeydew" fontname="Verdana" penwidth="1.0" margin="0.05,0.0"];
|
||||
edge [minlen="2"]
|
||||
|
||||
subgraph "cluster_focus" {
|
||||
label="main";
|
||||
labelloc="t";
|
||||
labeljust="c";
|
||||
fontsize="18";
|
||||
bgcolor="#e6ecfa";
|
||||
|
||||
"blitter.com/go/hkexsh/hkexpasswd.main" [ fillcolor="lightblue" label="main" penwidth="0.5" ]
|
||||
|
||||
subgraph "cluster_blitter.com/go/hkexsh" {
|
||||
label="[hkexsh]";
|
||||
penwidth="0.8";
|
||||
style="filled";
|
||||
rank="sink";
|
||||
URL="/?f=blitter.com/go/hkexsh";
|
||||
tooltip="package: blitter.com/go/hkexsh";
|
||||
fontsize="16";
|
||||
fillcolor="lightyellow";
|
||||
fontname="bold";
|
||||
|
||||
"blitter.com/go/hkexsh.ReadPassword" [ fillcolor="moccasin" label="ReadPassword" penwidth="1.5" ]
|
||||
|
||||
}
|
||||
|
||||
subgraph "cluster_github.com/jameskeane/bcrypt" {
|
||||
fontsize="16";
|
||||
rank="sink";
|
||||
label="[bcrypt]";
|
||||
URL="/?f=github.com/jameskeane/bcrypt";
|
||||
tooltip="package: github.com/jameskeane/bcrypt";
|
||||
penwidth="0.8";
|
||||
style="filled";
|
||||
fillcolor="lightyellow";
|
||||
fontname="bold";
|
||||
|
||||
"github.com/jameskeane/bcrypt.Salt" [ fillcolor="moccasin" label="Salt" penwidth="1.5" ]
|
||||
"github.com/jameskeane/bcrypt.Hash" [ fillcolor="moccasin" label="Hash" penwidth="1.5" ]
|
||||
"github.com/jameskeane/bcrypt.Match" [ fillcolor="moccasin" label="Match" penwidth="1.5" ]
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
"blitter.com/go/hkexsh/hkexpasswd.main" -> "blitter.com/go/hkexsh.ReadPassword" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexpasswd.main" -> "github.com/jameskeane/bcrypt.Salt" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexpasswd.main" -> "github.com/jameskeane/bcrypt.Hash" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexpasswd.main" -> "github.com/jameskeane/bcrypt.Match" [ color="saddlebrown" ]
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
.PHONY: clean all vis lint
|
||||
|
||||
EXTPKGS = bytes,errors,flag,fmt,internal,io,log,net,os,path,runtime,time,strings,sync,syscall,binary,encoding
|
||||
EXE = $(notdir $(shell pwd))
|
||||
|
||||
all:
|
||||
echo "BUILDOPTS:" $(BUILDOPTS)
|
||||
go build $(BUILDOPTS) .
|
||||
|
||||
clean:
|
||||
$(RM) $(EXE) $(EXE).exe
|
||||
|
||||
vis:
|
||||
go-callvis -file hkexsh-vis -format png -ignore $(EXTPKGS) -group pkg,type .
|
||||
../fixup-gv.sh hkexsh.go && cat hkexsh-vis.gv | dot -Tpng -ohkexsh-vis-fixedup.png
|
||||
|
||||
lint:
|
||||
-gometalinter --deadline=60s | sort
|
|
@ -1 +0,0 @@
|
|||
hkexsh
|
Before Width: | Height: | Size: 681 KiB |
|
@ -1,263 +0,0 @@
|
|||
digraph gocallvis {
|
||||
label="blitter.com/go/hkexsh/hkexsh";
|
||||
labeljust="l";
|
||||
fontname="Arial";
|
||||
fontsize="14";
|
||||
rankdir="LR";
|
||||
bgcolor="lightgray";
|
||||
style="solid";
|
||||
penwidth="0.5";
|
||||
pad="0.0";
|
||||
nodesep="0.35";
|
||||
|
||||
node [shape="ellipse" style="filled" fillcolor="honeydew" fontname="Verdana" penwidth="1.0" margin="0.05,0.0"];
|
||||
edge [minlen="2"]
|
||||
|
||||
subgraph "cluster_focus" {
|
||||
bgcolor="#e6ecfa";
|
||||
label="main";
|
||||
labelloc="t";
|
||||
labeljust="c";
|
||||
fontsize="18";
|
||||
|
||||
"blitter.com/go/hkexsh/hkexsh.reqTunnel" [ penwidth="0.5" fillcolor="lightblue" label="reqTunnel" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.doShellMode$1" [ fillcolor="lightblue" label="shellRemoteToStdin" style="dotted,filled" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.doShellMode$1$1" [ fillcolor="lightblue" label="doShellMode$1$1" style="dotted,filled" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.doShellMode" [ fillcolor="lightblue" label="doShellMode" penwidth="0.5" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.handleTermResizes$1" [ fillcolor="lightblue" label="handleTermResizes$1" style="dotted,filled" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.GetSize" [ label="GetSize" penwidth="1.5" fillcolor="lightblue" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.handleTermResizes" [ penwidth="0.5" fillcolor="lightblue" label="handleTermResizes" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.copyBuffer" [ fillcolor="lightblue" label="copyBuffer" penwidth="0.5" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.copyBuffer$1" [ fillcolor="lightblue" label="copyBuffer$1" style="dotted,filled" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.copyBuffer$2" [ fillcolor="lightblue" label="copyBuffer$2" style="dotted,filled" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.copyBuffer$3" [ style="dotted,filled" fillcolor="lightblue" label="copyBuffer$3" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.Copy" [ fillcolor="lightblue" label="Copy" penwidth="1.5" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.doShellMode$2$1" [ fillcolor="lightblue" label="doShellMode$2$1" style="dotted,filled" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.doShellMode$2" [ fillcolor="lightblue" label="shellStdinToRemote" style="dotted,filled" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.main$2" [ fillcolor="lightblue" label="deferCloseChaff" style="dotted,filled" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.sendSessionParams" [ fillcolor="lightblue" label="sendSessionParams" penwidth="0.5" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.main$1" [ label="deferRestore" style="dotted,filled" fillcolor="lightblue" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.launchTuns" [ penwidth="0.5" fillcolor="lightblue" label="launchTuns" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.rejectUserMsg" [ label="rejectUserMsg" penwidth="0.5" fillcolor="lightblue" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.main" [ fillcolor="lightblue" label="main" penwidth="0.5" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.parseNonSwitchArgs" [ fillcolor="lightblue" label="parseNonSwitchArgs" penwidth="0.5" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.doCopyMode" [ fillcolor="lightblue" label="doCopyMode" penwidth="0.5" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.usageShell" [ fillcolor="lightblue" label="usageShell" penwidth="0.5" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.usageCp" [ fillcolor="lightblue" label="usageCp" penwidth="0.5" ]
|
||||
|
||||
subgraph "cluster_blitter.com/go/hkexsh" {
|
||||
style="filled";
|
||||
fillcolor="lightyellow";
|
||||
rank="sink";
|
||||
label="[hkexsh]";
|
||||
tooltip="package: blitter.com/go/hkexsh";
|
||||
penwidth="0.8";
|
||||
fontsize="16";
|
||||
fontname="bold";
|
||||
URL="/?f=blitter.com/go/hkexsh";
|
||||
|
||||
"blitter.com/go/hkexsh.Restore" [ fillcolor="moccasin" label="Restore" penwidth="1.5" ]
|
||||
"blitter.com/go/hkexsh.MakeRaw" [ fillcolor="moccasin" label="MakeRaw" penwidth="1.5" ]
|
||||
"blitter.com/go/hkexsh.ReadPassword" [ penwidth="1.5" fillcolor="moccasin" label="ReadPassword" ]
|
||||
"blitter.com/go/hkexsh.NewSession" [ label="NewSession" penwidth="1.5" fillcolor="moccasin" ]
|
||||
|
||||
subgraph "cluster_*blitter.com/go/hkexsh.Session" {
|
||||
style="rounded,filled";
|
||||
fillcolor="wheat2";
|
||||
label="(*Session)";
|
||||
tooltip="type: *blitter.com/go/hkexsh.Session";
|
||||
penwidth="0.5";
|
||||
fontsize="15";
|
||||
fontcolor="#222222";
|
||||
labelloc="b";
|
||||
|
||||
"(*blitter.com/go/hkexsh.Session).SetStatus" [ label="SetStatus" penwidth="1.5" fillcolor="moccasin" ]
|
||||
|
||||
}
|
||||
|
||||
subgraph "cluster_blitter.com/go/hkexsh.Session" {
|
||||
fillcolor="wheat2";
|
||||
label="(Session)";
|
||||
tooltip="type: blitter.com/go/hkexsh.Session";
|
||||
penwidth="0.5";
|
||||
fontsize="15";
|
||||
fontcolor="#222222";
|
||||
labelloc="b";
|
||||
style="rounded,filled";
|
||||
|
||||
"(blitter.com/go/hkexsh.Session).Status" [ label="Status" penwidth="1.5" fillcolor="moccasin" ]
|
||||
"(blitter.com/go/hkexsh.Session).Op" [ fillcolor="moccasin" label="Op" penwidth="1.5" ]
|
||||
"(blitter.com/go/hkexsh.Session).Who" [ fillcolor="moccasin" label="Who" penwidth="1.5" ]
|
||||
"(blitter.com/go/hkexsh.Session).ConnHost" [ fillcolor="moccasin" label="ConnHost" penwidth="1.5" ]
|
||||
"(blitter.com/go/hkexsh.Session).TermType" [ penwidth="1.5" fillcolor="moccasin" label="TermType" ]
|
||||
"(blitter.com/go/hkexsh.Session).Cmd" [ label="Cmd" penwidth="1.5" fillcolor="moccasin" ]
|
||||
"(blitter.com/go/hkexsh.Session).AuthCookie" [ fillcolor="moccasin" label="AuthCookie" penwidth="1.5" ]
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
subgraph "cluster_blitter.com/go/hkexsh/hkexnet" {
|
||||
fontname="bold";
|
||||
label="[hkexnet]";
|
||||
URL="/?f=blitter.com/go/hkexsh/hkexnet";
|
||||
tooltip="package: blitter.com/go/hkexsh/hkexnet";
|
||||
fontsize="16";
|
||||
fillcolor="lightyellow";
|
||||
rank="sink";
|
||||
penwidth="0.8";
|
||||
style="filled";
|
||||
|
||||
"blitter.com/go/hkexsh/hkexnet.Init" [ fillcolor="moccasin" label="Init" penwidth="1.5" ]
|
||||
"blitter.com/go/hkexsh/hkexnet.Dial" [ penwidth="1.5" fillcolor="moccasin" label="Dial" ]
|
||||
|
||||
subgraph "cluster_*blitter.com/go/hkexsh/hkexnet.Conn" {
|
||||
penwidth="0.5";
|
||||
fontsize="15";
|
||||
fontcolor="#222222";
|
||||
labelloc="b";
|
||||
style="rounded,filled";
|
||||
fillcolor="wheat2";
|
||||
label="(*Conn)";
|
||||
tooltip="type: *blitter.com/go/hkexsh/hkexnet.Conn";
|
||||
|
||||
"(*blitter.com/go/hkexsh/hkexnet.Conn).WritePacket" [ fillcolor="moccasin" label="WritePacket" penwidth="1.5" ]
|
||||
"(*blitter.com/go/hkexsh/hkexnet.Conn).Close" [ fillcolor="moccasin" label="Close" penwidth="1.5" ]
|
||||
"(*blitter.com/go/hkexsh/hkexnet.Conn).SetupChaff" [ fillcolor="moccasin" label="SetupChaff" penwidth="1.5" ]
|
||||
"(*blitter.com/go/hkexsh/hkexnet.Conn).EnableChaff" [ fillcolor="moccasin" label="EnableChaff" penwidth="1.5" ]
|
||||
"(*blitter.com/go/hkexsh/hkexnet.Conn).DisableChaff" [ fillcolor="moccasin" label="DisableChaff" penwidth="1.5" ]
|
||||
"(*blitter.com/go/hkexsh/hkexnet.Conn).ShutdownChaff" [ label="ShutdownChaff" penwidth="1.5" fillcolor="moccasin" ]
|
||||
"(*blitter.com/go/hkexsh/hkexnet.Conn).SetStatus" [ penwidth="1.5" fillcolor="moccasin" label="SetStatus" ]
|
||||
|
||||
}
|
||||
|
||||
subgraph "cluster_blitter.com/go/hkexsh/hkexnet.Conn" {
|
||||
labelloc="b";
|
||||
style="rounded,filled";
|
||||
fillcolor="wheat2";
|
||||
label="(Conn)";
|
||||
tooltip="type: blitter.com/go/hkexsh/hkexnet.Conn";
|
||||
penwidth="0.5";
|
||||
fontsize="15";
|
||||
fontcolor="#222222";
|
||||
|
||||
"(blitter.com/go/hkexsh/hkexnet.Conn).GetStatus" [ fillcolor="moccasin" label="GetStatus" penwidth="1.5" ]
|
||||
"(blitter.com/go/hkexsh/hkexnet.Conn).Write" [ fillcolor="moccasin" label="Write" penwidth="1.5" ]
|
||||
"(blitter.com/go/hkexsh/hkexnet.Conn).Read" [ fillcolor="moccasin" label="Read" penwidth="1.5" ]
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
subgraph "cluster_blitter.com/go/hkexsh/logger" {
|
||||
fillcolor="lightyellow";
|
||||
fontname="bold";
|
||||
label="[logger]";
|
||||
tooltip="package: blitter.com/go/hkexsh/logger";
|
||||
penwidth="0.8";
|
||||
style="filled";
|
||||
rank="sink";
|
||||
URL="/?f=blitter.com/go/hkexsh/logger";
|
||||
fontsize="16";
|
||||
|
||||
"blitter.com/go/hkexsh/logger.LogDebug" [ fillcolor="moccasin" label="LogDebug" penwidth="1.5" ]
|
||||
"blitter.com/go/hkexsh/logger.New" [ fillcolor="moccasin" label="New" penwidth="1.5" ]
|
||||
|
||||
}
|
||||
|
||||
subgraph "cluster_blitter.com/go/hkexsh/spinsult" {
|
||||
penwidth="0.8";
|
||||
style="filled";
|
||||
rank="sink";
|
||||
tooltip="package: blitter.com/go/hkexsh/spinsult";
|
||||
fontsize="16";
|
||||
fillcolor="lightyellow";
|
||||
fontname="bold";
|
||||
label="[spinsult]";
|
||||
URL="/?f=blitter.com/go/hkexsh/spinsult";
|
||||
|
||||
"blitter.com/go/hkexsh/spinsult.GetSentence" [ fillcolor="moccasin" label="GetSentence" penwidth="1.5" ]
|
||||
|
||||
}
|
||||
|
||||
subgraph "cluster_github.com/mattn/go-isatty" {
|
||||
tooltip="package: github.com/mattn/go-isatty";
|
||||
penwidth="0.8";
|
||||
fillcolor="lightyellow";
|
||||
rank="sink";
|
||||
label="[isatty]";
|
||||
URL="/?f=github.com/mattn/go-isatty";
|
||||
fontsize="16";
|
||||
style="filled";
|
||||
fontname="bold";
|
||||
|
||||
"github.com/mattn/go-isatty.IsTerminal" [ fillcolor="moccasin" label="IsTerminal" penwidth="1.5" ]
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
"blitter.com/go/hkexsh/hkexsh.reqTunnel" -> "blitter.com/go/hkexsh/logger.LogDebug" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.reqTunnel" -> "(*blitter.com/go/hkexsh/hkexnet.Conn).WritePacket" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.doShellMode$1" -> "blitter.com/go/hkexsh/hkexsh.doShellMode$1$1" [ arrowhead="normalnoneodiamond" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.doShellMode$1" -> "blitter.com/go/hkexsh.Restore" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.doShellMode$1" -> "(blitter.com/go/hkexsh/hkexnet.Conn).GetStatus" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.doShellMode$1" -> "(*blitter.com/go/hkexsh.Session).SetStatus" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.doShellMode$1" -> "(blitter.com/go/hkexsh.Session).Status" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.doShellMode" -> "blitter.com/go/hkexsh/hkexsh.doShellMode$1" [ arrowhead="normalnoneodot" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.handleTermResizes$1" -> "blitter.com/go/hkexsh/hkexsh.GetSize" [ ]
|
||||
"blitter.com/go/hkexsh/hkexsh.handleTermResizes$1" -> "(*blitter.com/go/hkexsh/hkexnet.Conn).WritePacket" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.handleTermResizes" -> "blitter.com/go/hkexsh/hkexsh.handleTermResizes$1" [ arrowhead="normalnoneodot" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.doShellMode" -> "blitter.com/go/hkexsh/hkexsh.handleTermResizes" [ ]
|
||||
"blitter.com/go/hkexsh/hkexsh.copyBuffer" -> "(blitter.com/go/hkexsh/hkexnet.Conn).Write" [ style="dashed" color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.copyBuffer" -> "blitter.com/go/hkexsh/hkexsh.copyBuffer$1" [ style="dashed" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.copyBuffer" -> "blitter.com/go/hkexsh/hkexsh.copyBuffer$2" [ style="dashed" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.copyBuffer" -> "blitter.com/go/hkexsh/hkexsh.copyBuffer$3" [ style="dashed" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.Copy" -> "blitter.com/go/hkexsh/hkexsh.copyBuffer" [ ]
|
||||
"blitter.com/go/hkexsh/hkexsh.doShellMode$2$1" -> "blitter.com/go/hkexsh/hkexsh.Copy" [ ]
|
||||
"blitter.com/go/hkexsh/hkexsh.doShellMode$2" -> "blitter.com/go/hkexsh/hkexsh.doShellMode$2$1" [ ]
|
||||
"blitter.com/go/hkexsh/hkexsh.doShellMode$2" -> "blitter.com/go/hkexsh.Restore" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.doShellMode" -> "blitter.com/go/hkexsh/hkexsh.doShellMode$2" [ arrowhead="normalnoneodot" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.main$2" -> "(*blitter.com/go/hkexsh/hkexnet.Conn).WritePacket" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.sendSessionParams" -> "(blitter.com/go/hkexsh.Session).Op" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.sendSessionParams" -> "(blitter.com/go/hkexsh.Session).Who" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.sendSessionParams" -> "(blitter.com/go/hkexsh.Session).ConnHost" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.sendSessionParams" -> "(blitter.com/go/hkexsh.Session).TermType" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.sendSessionParams" -> "(blitter.com/go/hkexsh.Session).Cmd" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.sendSessionParams" -> "(blitter.com/go/hkexsh.Session).AuthCookie" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.sendSessionParams" -> "(blitter.com/go/hkexsh/hkexnet.Conn).Write" [ style="dashed" color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.main$1" -> "blitter.com/go/hkexsh.Restore" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.launchTuns" -> "blitter.com/go/hkexsh/hkexsh.reqTunnel" [ ]
|
||||
"blitter.com/go/hkexsh/hkexsh.rejectUserMsg" -> "blitter.com/go/hkexsh/spinsult.GetSentence" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.main" -> "blitter.com/go/hkexsh/hkexsh.parseNonSwitchArgs" [ ]
|
||||
"blitter.com/go/hkexsh/hkexsh.main" -> "blitter.com/go/hkexsh/logger.New" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.main" -> "blitter.com/go/hkexsh/hkexnet.Init" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.main" -> "blitter.com/go/hkexsh/hkexnet.Dial" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.main" -> "(*blitter.com/go/hkexsh/hkexnet.Conn).Close" [ color="saddlebrown" arrowhead="normalnoneodiamond" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.main" -> "github.com/mattn/go-isatty.IsTerminal" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.main" -> "blitter.com/go/hkexsh.MakeRaw" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.main" -> "blitter.com/go/hkexsh/hkexsh.main$1" [ arrowhead="normalnoneodiamond" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.main" -> "blitter.com/go/hkexsh.ReadPassword" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.main" -> "blitter.com/go/hkexsh.NewSession" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.main" -> "blitter.com/go/hkexsh/hkexsh.sendSessionParams" [ ]
|
||||
"blitter.com/go/hkexsh/hkexsh.main" -> "(blitter.com/go/hkexsh/hkexnet.Conn).Read" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.main" -> "(*blitter.com/go/hkexsh.Session).SetStatus" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.main" -> "blitter.com/go/hkexsh/hkexsh.rejectUserMsg" [ ]
|
||||
"blitter.com/go/hkexsh/hkexsh.main" -> "(*blitter.com/go/hkexsh/hkexnet.Conn).SetupChaff" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.main" -> "(*blitter.com/go/hkexsh/hkexnet.Conn).EnableChaff" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.main" -> "(*blitter.com/go/hkexsh/hkexnet.Conn).DisableChaff" [ arrowhead="normalnoneodiamond" color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.main" -> "(*blitter.com/go/hkexsh/hkexnet.Conn).ShutdownChaff" [ arrowhead="normalnoneodiamond" color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.main" -> "blitter.com/go/hkexsh/hkexsh.main$2" [ arrowhead="normalnoneodot" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.main" -> "blitter.com/go/hkexsh/hkexsh.launchTuns" [ ]
|
||||
"blitter.com/go/hkexsh/hkexsh.main" -> "blitter.com/go/hkexsh/hkexsh.doShellMode" [ ]
|
||||
"blitter.com/go/hkexsh/hkexsh.main" -> "(blitter.com/go/hkexsh.Session).Status" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.doCopyMode" -> "(blitter.com/go/hkexsh.Session).Cmd" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.doCopyMode" -> "(*blitter.com/go/hkexsh/hkexnet.Conn).WritePacket" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.doCopyMode" -> "(blitter.com/go/hkexsh/hkexnet.Conn).Read" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.doCopyMode" -> "(*blitter.com/go/hkexsh/hkexnet.Conn).SetStatus" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.doCopyMode" -> "(blitter.com/go/hkexsh/hkexnet.Conn).GetStatus" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.main" -> "blitter.com/go/hkexsh/hkexsh.doCopyMode" [ ]
|
||||
"blitter.com/go/hkexsh/hkexsh.main" -> "blitter.com/go/hkexsh.Restore" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.main" -> "blitter.com/go/hkexsh/hkexsh.usageShell" [ style="dashed" ]
|
||||
"blitter.com/go/hkexsh/hkexsh.main" -> "blitter.com/go/hkexsh/hkexsh.usageCp" [ style="dashed" ]
|
||||
}
|
Before Width: | Height: | Size: 68 KiB |
|
@ -1,37 +0,0 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
## This wrapper may be used within the MSYS/mintty Windows
|
||||
## shell environment to have a functioning hkexsh client with
|
||||
## working 'raw' mode and hidden password entry.
|
||||
##
|
||||
## mintty uses named pipes and ptys to get a more POSIX-like
|
||||
## terminal (incl. VT/ANSI codes) rather than the dumb Windows
|
||||
## console interface; however Go on Windows does not have functioning
|
||||
## MSYS/mintty code to set raw, echo etc. modes.
|
||||
##
|
||||
## Someday it would be preferable to put native Windows term mode
|
||||
## code into the client build, but this is 'good enough' for now
|
||||
## (with the exception of tty rows/cols not being set based on
|
||||
## info from the server).
|
||||
##
|
||||
## INSTALLATION
|
||||
## --
|
||||
## Build the client, put it somewhere in your $PATH with this
|
||||
## wrapper and edit the name of the client binary
|
||||
## eg.,
|
||||
## $ cp hkexsh.exe /usr/bin/.hkexsh.exe
|
||||
## $ cp mintty_wrapper.sh /usr/bin/hkexsh
|
||||
####
|
||||
trap cleanup EXIT ERR
|
||||
|
||||
cleanup() {
|
||||
stty sane
|
||||
}
|
||||
|
||||
if [ ${1}x == "-hx" ]; then
|
||||
./hkexsh -h
|
||||
else
|
||||
stty -echo raw icrnl
|
||||
./hkexsh $@
|
||||
fi
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
.PHONY: clean all vis lint
|
||||
|
||||
EXTPKGS = binary,bytes,crypto,encoding,errors,flag,fmt,internal,io,log,net,os,path,runtime,time,strings,sync,syscall
|
||||
EXE = $(notdir $(shell pwd))
|
||||
|
||||
all:
|
||||
go build $(BUILDOPTS) .
|
||||
|
||||
clean:
|
||||
$(RM) $(EXE) $(EXE).exe
|
||||
|
||||
vis:
|
||||
go-callvis -file hkexshd-vis -format png -ignore $(EXTPKGS) -group pkg,type .
|
||||
../fixup-gv.sh hkexshd.go && cat hkexshd-vis.gv | dot -Tpng -ohkexshd-vis-fixedup.png
|
||||
|
||||
lint:
|
||||
-gometalinter --deadline=60s | sort
|
||||
|
Before Width: | Height: | Size: 586 KiB |
|
@ -1,281 +0,0 @@
|
|||
digraph gocallvis {
|
||||
label="blitter.com/go/hkexsh/hkexshd";
|
||||
labeljust="l";
|
||||
fontname="Arial";
|
||||
fontsize="14";
|
||||
rankdir="LR";
|
||||
bgcolor="lightgray";
|
||||
style="solid";
|
||||
penwidth="0.5";
|
||||
pad="0.0";
|
||||
nodesep="0.35";
|
||||
|
||||
node [shape="ellipse" style="filled" fillcolor="honeydew" fontname="Verdana" penwidth="1.0" margin="0.05,0.0"];
|
||||
edge [minlen="2"]
|
||||
|
||||
subgraph "cluster_focus" {
|
||||
bgcolor="#e6ecfa";
|
||||
label="main";
|
||||
labelloc="t";
|
||||
labeljust="c";
|
||||
fontsize="18";
|
||||
|
||||
"blitter.com/go/hkexsh/hkexshd.main$1" [ fillcolor="lightblue" label="main$1" style="dotted,filled" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main$2$1" [ fillcolor="lightblue" label="main$2$1" style="dotted,filled" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.runShellAs$4" [ fillcolor="lightblue" label="deferChaffShutdown" style="dotted,filled" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main" [ fillcolor="lightblue" label="main" penwidth="0.5" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main$2" [ fillcolor="lightblue" label="main$2" style="dotted,filled" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.GenAuthToken" [ fillcolor="lightblue" label="GenAuthToken" penwidth="1.5" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.runShellAs" [ fillcolor="lightblue" label="runShellAs" penwidth="0.5" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.runShellAs$1" [ label="deferPtmxClose" style="dotted,filled" fillcolor="lightblue" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.runShellAs$2" [ style="dotted,filled" fillcolor="lightblue" label="termResizeWatcher" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.runShellAs$3" [ fillcolor="lightblue" label="stdinToPtyWorker" style="dotted,filled" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.runShellAs$5" [ fillcolor="lightblue" label="ptyToStdoutWorker" style="dotted,filled" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.runClientToServerCopyAs" [ fillcolor="lightblue" label="runClientToServerCopyAs" penwidth="0.5" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.runServerToClientCopyAs" [ fillcolor="lightblue" label="runServerToClientCopyAs" penwidth="0.5" ]
|
||||
|
||||
subgraph "cluster_blitter.com/go/goutmp" {
|
||||
fontname="bold";
|
||||
rank="sink";
|
||||
tooltip="package: blitter.com/go/goutmp";
|
||||
penwidth="0.8";
|
||||
fillcolor="lightyellow";
|
||||
label="[goutmp]";
|
||||
URL="/?f=blitter.com/go/goutmp";
|
||||
fontsize="16";
|
||||
style="filled";
|
||||
|
||||
"blitter.com/go/goutmp.Unput_utmp" [ label="Unput_utmp" penwidth="1.5" fillcolor="moccasin" ]
|
||||
"blitter.com/go/goutmp.GetHost" [ fillcolor="moccasin" label="GetHost" penwidth="1.5" ]
|
||||
"blitter.com/go/goutmp.Put_utmp" [ label="Put_utmp" penwidth="1.5" fillcolor="moccasin" ]
|
||||
"blitter.com/go/goutmp.Put_lastlog_entry" [ fillcolor="moccasin" label="Put_lastlog_entry" penwidth="1.5" ]
|
||||
|
||||
}
|
||||
|
||||
subgraph "cluster_blitter.com/go/hkexsh" {
|
||||
fillcolor="lightyellow";
|
||||
label="[hkexsh]";
|
||||
tooltip="package: blitter.com/go/hkexsh";
|
||||
penwidth="0.8";
|
||||
style="filled";
|
||||
fontname="bold";
|
||||
rank="sink";
|
||||
URL="/?f=blitter.com/go/hkexsh";
|
||||
fontsize="16";
|
||||
|
||||
"blitter.com/go/hkexsh.AuthUserByToken" [ label="AuthUserByToken" penwidth="1.5" fillcolor="moccasin" ]
|
||||
"blitter.com/go/hkexsh.AuthUserByPasswd" [ fillcolor="moccasin" label="AuthUserByPasswd" penwidth="1.5" ]
|
||||
|
||||
subgraph "cluster_*blitter.com/go/hkexsh.Session" {
|
||||
fontsize="15";
|
||||
fontcolor="#222222";
|
||||
labelloc="b";
|
||||
style="rounded,filled";
|
||||
fillcolor="wheat2";
|
||||
label="(*Session)";
|
||||
tooltip="type: *blitter.com/go/hkexsh.Session";
|
||||
penwidth="0.5";
|
||||
|
||||
"(*blitter.com/go/hkexsh.Session).SetOp" [ fillcolor="moccasin" label="SetOp" penwidth="1.5" ]
|
||||
"(*blitter.com/go/hkexsh.Session).SetWho" [ fillcolor="moccasin" label="SetWho" penwidth="1.5" ]
|
||||
"(*blitter.com/go/hkexsh.Session).SetConnHost" [ penwidth="1.5" fillcolor="moccasin" label="SetConnHost" ]
|
||||
"(*blitter.com/go/hkexsh.Session).SetTermType" [ label="SetTermType" penwidth="1.5" fillcolor="moccasin" ]
|
||||
"(*blitter.com/go/hkexsh.Session).SetCmd" [ fillcolor="moccasin" label="SetCmd" penwidth="1.5" ]
|
||||
"(*blitter.com/go/hkexsh.Session).SetAuthCookie" [ fillcolor="moccasin" label="SetAuthCookie" penwidth="1.5" ]
|
||||
"(*blitter.com/go/hkexsh.Session).ClearAuthCookie" [ label="ClearAuthCookie" penwidth="1.5" fillcolor="moccasin" ]
|
||||
|
||||
}
|
||||
|
||||
subgraph "cluster_blitter.com/go/hkexsh.Session" {
|
||||
fillcolor="wheat2";
|
||||
label="(Session)";
|
||||
tooltip="type: blitter.com/go/hkexsh.Session";
|
||||
penwidth="0.5";
|
||||
fontsize="15";
|
||||
fontcolor="#222222";
|
||||
labelloc="b";
|
||||
style="rounded,filled";
|
||||
|
||||
"(blitter.com/go/hkexsh.Session).Op" [ fillcolor="moccasin" label="Op" penwidth="1.5" ]
|
||||
"(blitter.com/go/hkexsh.Session).Who" [ fillcolor="moccasin" label="Who" penwidth="1.5" ]
|
||||
"(blitter.com/go/hkexsh.Session).ConnHost" [ fillcolor="moccasin" label="ConnHost" penwidth="1.5" ]
|
||||
"(blitter.com/go/hkexsh.Session).Cmd" [ penwidth="1.5" fillcolor="moccasin" label="Cmd" ]
|
||||
"(blitter.com/go/hkexsh.Session).AuthCookie" [ fillcolor="moccasin" label="AuthCookie" penwidth="1.5" ]
|
||||
"(blitter.com/go/hkexsh.Session).TermType" [ fillcolor="moccasin" label="TermType" penwidth="1.5" ]
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
subgraph "cluster_blitter.com/go/hkexsh/hkexnet" {
|
||||
fontsize="16";
|
||||
URL="/?f=blitter.com/go/hkexsh/hkexnet";
|
||||
rank="sink";
|
||||
label="[hkexnet]";
|
||||
tooltip="package: blitter.com/go/hkexsh/hkexnet";
|
||||
penwidth="0.8";
|
||||
style="filled";
|
||||
fillcolor="lightyellow";
|
||||
fontname="bold";
|
||||
|
||||
"blitter.com/go/hkexsh/hkexnet.Init" [ fillcolor="moccasin" label="Init" penwidth="1.5" ]
|
||||
"blitter.com/go/hkexsh/hkexnet.Listen" [ fillcolor="moccasin" label="Listen" penwidth="1.5" ]
|
||||
|
||||
subgraph "cluster_*blitter.com/go/hkexsh/hkexnet.Conn" {
|
||||
tooltip="type: *blitter.com/go/hkexsh/hkexnet.Conn";
|
||||
penwidth="0.5";
|
||||
fontsize="15";
|
||||
fontcolor="#222222";
|
||||
labelloc="b";
|
||||
style="rounded,filled";
|
||||
fillcolor="wheat2";
|
||||
label="(*Conn)";
|
||||
|
||||
"(*blitter.com/go/hkexsh/hkexnet.Conn).DisableChaff" [ fillcolor="moccasin" label="DisableChaff" penwidth="1.5" ]
|
||||
"(*blitter.com/go/hkexsh/hkexnet.Conn).ShutdownChaff" [ fillcolor="moccasin" label="ShutdownChaff" penwidth="1.5" ]
|
||||
"(*blitter.com/go/hkexsh/hkexnet.Conn).SetupChaff" [ fillcolor="moccasin" label="SetupChaff" penwidth="1.5" ]
|
||||
"(*blitter.com/go/hkexsh/hkexnet.Conn).Close" [ fillcolor="moccasin" label="Close" penwidth="1.5" ]
|
||||
"(*blitter.com/go/hkexsh/hkexnet.Conn).RemoteAddr" [ label="RemoteAddr" penwidth="1.5" fillcolor="moccasin" ]
|
||||
"(*blitter.com/go/hkexsh/hkexnet.Conn).EnableChaff" [ fillcolor="moccasin" label="EnableChaff" penwidth="1.5" ]
|
||||
"(*blitter.com/go/hkexsh/hkexnet.Conn).SetStatus" [ fillcolor="moccasin" label="SetStatus" penwidth="1.5" ]
|
||||
"(*blitter.com/go/hkexsh/hkexnet.Conn).WritePacket" [ label="WritePacket" penwidth="1.5" fillcolor="moccasin" ]
|
||||
|
||||
}
|
||||
|
||||
subgraph "cluster_*blitter.com/go/hkexsh/hkexnet.HKExListener" {
|
||||
tooltip="type: *blitter.com/go/hkexsh/hkexnet.HKExListener";
|
||||
penwidth="0.5";
|
||||
fontsize="15";
|
||||
fontcolor="#222222";
|
||||
labelloc="b";
|
||||
style="rounded,filled";
|
||||
fillcolor="wheat2";
|
||||
label="(*HKExListener)";
|
||||
|
||||
"(*blitter.com/go/hkexsh/hkexnet.HKExListener).Accept" [ fillcolor="moccasin" label="Accept" penwidth="1.5" ]
|
||||
|
||||
}
|
||||
|
||||
subgraph "cluster_blitter.com/go/hkexsh/hkexnet.Conn" {
|
||||
fontcolor="#222222";
|
||||
labelloc="b";
|
||||
style="rounded,filled";
|
||||
fillcolor="wheat2";
|
||||
label="(Conn)";
|
||||
tooltip="type: blitter.com/go/hkexsh/hkexnet.Conn";
|
||||
penwidth="0.5";
|
||||
fontsize="15";
|
||||
|
||||
"(blitter.com/go/hkexsh/hkexnet.Conn).Write" [ label="Write" penwidth="1.5" fillcolor="moccasin" ]
|
||||
|
||||
}
|
||||
|
||||
subgraph "cluster_blitter.com/go/hkexsh/hkexnet.HKExListener" {
|
||||
fillcolor="wheat2";
|
||||
label="(HKExListener)";
|
||||
tooltip="type: blitter.com/go/hkexsh/hkexnet.HKExListener";
|
||||
penwidth="0.5";
|
||||
fontsize="15";
|
||||
fontcolor="#222222";
|
||||
labelloc="b";
|
||||
style="rounded,filled";
|
||||
|
||||
"(blitter.com/go/hkexsh/hkexnet.HKExListener).Close" [ fillcolor="moccasin" label="Close" penwidth="1.5" ]
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
subgraph "cluster_blitter.com/go/hkexsh/logger" {
|
||||
penwidth="0.8";
|
||||
fontsize="16";
|
||||
style="filled";
|
||||
rank="sink";
|
||||
fillcolor="lightyellow";
|
||||
fontname="bold";
|
||||
label="[logger]";
|
||||
URL="/?f=blitter.com/go/hkexsh/logger";
|
||||
tooltip="package: blitter.com/go/hkexsh/logger";
|
||||
|
||||
"blitter.com/go/hkexsh/logger.LogNotice" [ fillcolor="moccasin" label="LogNotice" penwidth="1.5" ]
|
||||
"blitter.com/go/hkexsh/logger.New" [ fillcolor="moccasin" label="New" penwidth="1.5" ]
|
||||
"blitter.com/go/hkexsh/logger.LogErr" [ fillcolor="moccasin" label="LogErr" penwidth="1.5" ]
|
||||
|
||||
}
|
||||
|
||||
subgraph "cluster_github.com/kr/pty" {
|
||||
penwidth="0.8";
|
||||
fillcolor="lightyellow";
|
||||
tooltip="package: github.com/kr/pty";
|
||||
label="[pty]";
|
||||
URL="/?f=github.com/kr/pty";
|
||||
fontsize="16";
|
||||
style="filled";
|
||||
fontname="bold";
|
||||
rank="sink";
|
||||
|
||||
"github.com/kr/pty.Start" [ label="Start" penwidth="1.5" fillcolor="moccasin" ]
|
||||
"github.com/kr/pty.Setsize" [ fillcolor="moccasin" label="Setsize" penwidth="1.5" ]
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
"blitter.com/go/hkexsh/hkexshd.main$1" -> "blitter.com/go/hkexsh/logger.LogNotice" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main$2$1" -> "blitter.com/go/goutmp.Unput_utmp" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.runShellAs$4" -> "(*blitter.com/go/hkexsh/hkexnet.Conn).DisableChaff" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.runShellAs$4" -> "(*blitter.com/go/hkexsh/hkexnet.Conn).ShutdownChaff" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main" -> "blitter.com/go/hkexsh/logger.New" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main" -> "blitter.com/go/hkexsh/hkexnet.Init" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main" -> "blitter.com/go/hkexsh/hkexshd.main$1" [ arrowhead="normalnoneodot" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main" -> "blitter.com/go/hkexsh/hkexnet.Listen" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main" -> "(blitter.com/go/hkexsh/hkexnet.HKExListener).Close" [ arrowhead="normalnoneodiamond" color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main" -> "(*blitter.com/go/hkexsh/hkexnet.HKExListener).Accept" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main" -> "(*blitter.com/go/hkexsh/hkexnet.Conn).SetupChaff" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main$2" -> "(*blitter.com/go/hkexsh/hkexnet.Conn).Close" [ arrowhead="normalnoneodiamond" color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main$2" -> "(*blitter.com/go/hkexsh.Session).SetOp" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main$2" -> "(*blitter.com/go/hkexsh.Session).SetWho" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main$2" -> "(*blitter.com/go/hkexsh.Session).SetConnHost" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main$2" -> "(*blitter.com/go/hkexsh.Session).SetTermType" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main$2" -> "(*blitter.com/go/hkexsh.Session).SetCmd" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main$2" -> "(*blitter.com/go/hkexsh.Session).SetAuthCookie" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main$2" -> "(blitter.com/go/hkexsh.Session).Op" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main$2" -> "(blitter.com/go/hkexsh.Session).Who" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main$2" -> "(blitter.com/go/hkexsh.Session).ConnHost" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main$2" -> "(blitter.com/go/hkexsh.Session).Cmd" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main$2" -> "(blitter.com/go/hkexsh.Session).AuthCookie" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main$2" -> "blitter.com/go/hkexsh.AuthUserByToken" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main$2" -> "(*blitter.com/go/hkexsh.Session).ClearAuthCookie" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main$2" -> "blitter.com/go/hkexsh.AuthUserByPasswd" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main$2" -> "(blitter.com/go/hkexsh/hkexnet.Conn).Write" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main$2" -> "blitter.com/go/hkexsh/logger.LogNotice" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main$2" -> "(*blitter.com/go/hkexsh/hkexnet.Conn).RemoteAddr" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main$2" -> "blitter.com/go/goutmp.GetHost" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main$2" -> "blitter.com/go/hkexsh/hkexshd.GenAuthToken" [ ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main$2" -> "(blitter.com/go/hkexsh.Session).TermType" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.runShellAs" -> "github.com/kr/pty.Start" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.runShellAs" -> "blitter.com/go/hkexsh/hkexshd.runShellAs$1" [ arrowhead="normalnoneodiamond" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.runShellAs$2" -> "github.com/kr/pty.Setsize" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.runShellAs" -> "blitter.com/go/hkexsh/hkexshd.runShellAs$2" [ arrowhead="normalnoneodot" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.runShellAs" -> "blitter.com/go/hkexsh/hkexshd.runShellAs$3" [ arrowhead="normalnoneodot" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.runShellAs" -> "(*blitter.com/go/hkexsh/hkexnet.Conn).EnableChaff" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.runShellAs" -> "blitter.com/go/hkexsh/hkexshd.runShellAs$4" [ arrowhead="normalnoneodiamond" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.runShellAs" -> "blitter.com/go/hkexsh/hkexshd.runShellAs$5" [ arrowhead="normalnoneodot" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.runShellAs" -> "(*blitter.com/go/hkexsh/hkexnet.Conn).SetStatus" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main$2" -> "blitter.com/go/hkexsh/hkexshd.runShellAs" [ ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main$2" -> "blitter.com/go/hkexsh/logger.LogErr" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main$2" -> "(*blitter.com/go/hkexsh/hkexnet.Conn).SetStatus" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main$2" -> "blitter.com/go/goutmp.Put_utmp" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main$2" -> "blitter.com/go/hkexsh/hkexshd.main$2$1" [ arrowhead="normalnoneodiamond" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main$2" -> "blitter.com/go/goutmp.Put_lastlog_entry" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.runClientToServerCopyAs" -> "(*blitter.com/go/hkexsh/hkexnet.Conn).EnableChaff" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.runClientToServerCopyAs" -> "(*blitter.com/go/hkexsh/hkexnet.Conn).DisableChaff" [ arrowhead="normalnoneodiamond" color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.runClientToServerCopyAs" -> "(*blitter.com/go/hkexsh/hkexnet.Conn).ShutdownChaff" [ color="saddlebrown" arrowhead="normalnoneodiamond" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main$2" -> "blitter.com/go/hkexsh/hkexshd.runClientToServerCopyAs" [ ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main$2" -> "(*blitter.com/go/hkexsh/hkexnet.Conn).WritePacket" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.runServerToClientCopyAs" -> "(*blitter.com/go/hkexsh/hkexnet.Conn).EnableChaff" [ color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.runServerToClientCopyAs" -> "(*blitter.com/go/hkexsh/hkexnet.Conn).DisableChaff" [ arrowhead="normalnoneodiamond" color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.runServerToClientCopyAs" -> "(*blitter.com/go/hkexsh/hkexnet.Conn).ShutdownChaff" [ arrowhead="normalnoneodiamond" color="saddlebrown" ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main$2" -> "blitter.com/go/hkexsh/hkexshd.runServerToClientCopyAs" [ ]
|
||||
"blitter.com/go/hkexsh/hkexshd.main" -> "blitter.com/go/hkexsh/hkexshd.main$2" [ arrowhead="normalnoneodot" ]
|
||||
}
|
|
@ -1,703 +0,0 @@
|
|||
// hkexshd server
|
||||
//
|
||||
// Copyright (c) 2017-2018 Russell Magee
|
||||
// Licensed under the terms of the MIT license (see LICENSE.mit in this
|
||||
// distribution)
|
||||
//
|
||||
// golang implementation by Russ Magee (rmagee_at_gmail.com)
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"os/user"
|
||||
"path"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"blitter.com/go/goutmp"
|
||||
hkexsh "blitter.com/go/hkexsh"
|
||||
"blitter.com/go/hkexsh/hkexnet"
|
||||
"blitter.com/go/hkexsh/logger"
|
||||
"github.com/kr/pty"
|
||||
)
|
||||
|
||||
var (
|
||||
version string
|
||||
gitCommit string // set in -ldflags by build
|
||||
|
||||
// Log - syslog output (with no -d)
|
||||
Log *logger.Writer
|
||||
)
|
||||
|
||||
func ioctl(fd, request, argp uintptr) error {
|
||||
if _, _, e := syscall.Syscall6(syscall.SYS_IOCTL, fd, request, argp, 0, 0, 0); e != 0 {
|
||||
return e
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ptsName(fd uintptr) (string, error) {
|
||||
var n uintptr
|
||||
err := ioctl(fd, syscall.TIOCGPTN, uintptr(unsafe.Pointer(&n)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("/dev/pts/%d", n), nil
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------- */
|
||||
// Perform a client->server copy
|
||||
func runClientToServerCopyAs(who, ttype string, conn *hkexnet.Conn, fpath string, chaffing bool) (exitStatus uint32, err error) {
|
||||
u, _ := user.Lookup(who) // nolint: gosec
|
||||
var uid, gid uint32
|
||||
fmt.Sscanf(u.Uid, "%d", &uid) // nolint: gosec,errcheck
|
||||
fmt.Sscanf(u.Gid, "%d", &gid) // nolint: gosec,errcheck
|
||||
log.Println("uid:", uid, "gid:", gid)
|
||||
|
||||
// Need to clear server's env and set key vars of the
|
||||
// target user. This isn't perfect (TERM doesn't seem to
|
||||
// work 100%; ANSI/xterm colour isn't working even
|
||||
// if we set "xterm" or "ansi" here; and line count
|
||||
// reported by 'stty -a' defaults to 24 regardless
|
||||
// of client shell window used to run client.
|
||||
// Investigate -- rlm 2018-01-26)
|
||||
os.Clearenv()
|
||||
os.Setenv("HOME", u.HomeDir) // nolint: gosec,errcheck
|
||||
os.Setenv("TERM", ttype) // nolint: gosec,errcheck
|
||||
os.Setenv("HKEXSH", "1") // nolint: gosec,errcheck
|
||||
|
||||
var c *exec.Cmd
|
||||
cmdName := "/bin/tar"
|
||||
|
||||
var destDir string
|
||||
if path.IsAbs(fpath) {
|
||||
destDir = fpath
|
||||
} else {
|
||||
destDir = path.Join(u.HomeDir, fpath)
|
||||
}
|
||||
|
||||
cmdArgs := []string{"-xz", "-C", destDir}
|
||||
|
||||
// NOTE the lack of quotes around --xform option's sed expression.
|
||||
// When args are passed in exec() format, no quoting is required
|
||||
// (as this isn't input from a shell) (right? -rlm 20180823)
|
||||
//cmdArgs := []string{"-x", "-C", destDir, `--xform=s#.*/\(.*\)#\1#`}
|
||||
c = exec.Command(cmdName, cmdArgs...) // nolint: gosec
|
||||
|
||||
c.Dir = destDir
|
||||
|
||||
//If os.Clearenv() isn't called by server above these will be seen in the
|
||||
//client's session env.
|
||||
//c.Env = []string{"HOME=" + u.HomeDir, "SUDO_GID=", "SUDO_UID=", "SUDO_USER=", "SUDO_COMMAND=", "MAIL=", "LOGNAME="+who}
|
||||
//c.Dir = u.HomeDir
|
||||
c.SysProcAttr = &syscall.SysProcAttr{}
|
||||
c.SysProcAttr.Credential = &syscall.Credential{Uid: uid, Gid: gid}
|
||||
c.Stdin = conn
|
||||
c.Stdout = os.Stdout
|
||||
c.Stderr = os.Stderr
|
||||
|
||||
if chaffing {
|
||||
conn.EnableChaff()
|
||||
}
|
||||
defer conn.DisableChaff()
|
||||
defer conn.ShutdownChaff()
|
||||
|
||||
// Start the command (no pty)
|
||||
log.Printf("[%v %v]\n", cmdName, cmdArgs)
|
||||
err = c.Start() // returns immediately
|
||||
/////////////
|
||||
// NOTE: There is, apparently, a bug in Go stdlib here. Start()
|
||||
// can actually return immediately, on a command which *does*
|
||||
// start but exits quickly, with c.Wait() error
|
||||
// "c.Wait status: exec: not started".
|
||||
// As in this example, attempting a client->server copy to
|
||||
// a nonexistent remote dir (it's tar exiting right away, exitStatus
|
||||
// 2, stderr
|
||||
// /bin/tar -xz -C /home/someuser/nosuchdir
|
||||
// stderr: fork/exec /bin/tar: no such file or directory
|
||||
//
|
||||
// In this case, c.Wait() won't give us the real
|
||||
// exit status (is it lost?).
|
||||
/////////////
|
||||
if err != nil {
|
||||
log.Println("cmd exited immediately. Cannot get cmd.Wait().ExitStatus()")
|
||||
err = errors.New("cmd exited prematurely")
|
||||
//exitStatus = uint32(254)
|
||||
exitStatus = hkexnet.CSEExecFail
|
||||
} else {
|
||||
if err := c.Wait(); err != nil {
|
||||
//fmt.Println("*** c.Wait() done ***")
|
||||
if exiterr, ok := err.(*exec.ExitError); ok {
|
||||
// The program has exited with an exit code != 0
|
||||
|
||||
// This works on both Unix and Windows. Although package
|
||||
// syscall is generally platform dependent, WaitStatus is
|
||||
// defined for both Unix and Windows and in both cases has
|
||||
// an ExitStatus() method with the same signature.
|
||||
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
|
||||
exitStatus = uint32(status.ExitStatus())
|
||||
//err = errors.New("cmd returned nonzero status")
|
||||
log.Printf("Exit Status: %d\n", exitStatus)
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Println("*** client->server cp finished ***")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Perform a server->client copy
|
||||
func runServerToClientCopyAs(who, ttype string, conn *hkexnet.Conn, srcPath string, chaffing bool) (exitStatus uint32, err error) {
|
||||
u, err := user.Lookup(who)
|
||||
if err != nil {
|
||||
exitStatus = 1
|
||||
return
|
||||
}
|
||||
var uid, gid uint32
|
||||
_, _ = fmt.Sscanf(u.Uid, "%d", &uid) // nolint: gosec
|
||||
_, _ = fmt.Sscanf(u.Gid, "%d", &gid) // nolint: gosec
|
||||
log.Println("uid:", uid, "gid:", gid)
|
||||
|
||||
// Need to clear server's env and set key vars of the
|
||||
// target user. This isn't perfect (TERM doesn't seem to
|
||||
// work 100%; ANSI/xterm colour isn't working even
|
||||
// if we set "xterm" or "ansi" here; and line count
|
||||
// reported by 'stty -a' defaults to 24 regardless
|
||||
// of client shell window used to run client.
|
||||
// Investigate -- rlm 2018-01-26)
|
||||
os.Clearenv()
|
||||
_ = os.Setenv("HOME", u.HomeDir) // nolint: gosec
|
||||
_ = os.Setenv("TERM", ttype) // nolint: gosec
|
||||
_ = os.Setenv("HKEXSH", "1") // nolint: gosec
|
||||
|
||||
var c *exec.Cmd
|
||||
cmdName := "/bin/tar"
|
||||
if !path.IsAbs(srcPath) {
|
||||
srcPath = fmt.Sprintf("%s%c%s", u.HomeDir, os.PathSeparator, srcPath)
|
||||
}
|
||||
|
||||
srcDir, srcBase := path.Split(srcPath)
|
||||
cmdArgs := []string{"-cz", "-C", srcDir, "-f", "-", srcBase}
|
||||
|
||||
c = exec.Command(cmdName, cmdArgs...) // nolint: gosec
|
||||
|
||||
//If os.Clearenv() isn't called by server above these will be seen in the
|
||||
//client's session env.
|
||||
//c.Env = []string{"HOME=" + u.HomeDir, "SUDO_GID=", "SUDO_UID=", "SUDO_USER=", "SUDO_COMMAND=", "MAIL=", "LOGNAME="+who}
|
||||
c.Dir = u.HomeDir
|
||||
c.SysProcAttr = &syscall.SysProcAttr{}
|
||||
c.SysProcAttr.Credential = &syscall.Credential{Uid: uid, Gid: gid}
|
||||
c.Stdout = conn
|
||||
// Stderr sinkholing (or buffering to something other than stdout)
|
||||
// is important. Any extraneous output to tarpipe messes up remote
|
||||
// side as it's expecting pure tar data.
|
||||
// (For example, if user specifies abs paths, tar outputs
|
||||
// "Removing leading '/' from path names")
|
||||
stdErrBuffer := new(bytes.Buffer)
|
||||
c.Stderr = stdErrBuffer
|
||||
//c.Stderr = nil
|
||||
|
||||
if chaffing {
|
||||
conn.EnableChaff()
|
||||
}
|
||||
//defer conn.Close()
|
||||
defer conn.DisableChaff()
|
||||
defer conn.ShutdownChaff()
|
||||
|
||||
// Start the command (no pty)
|
||||
log.Printf("[%v %v]\n", cmdName, cmdArgs)
|
||||
err = c.Start() // returns immediately
|
||||
if err != nil {
|
||||
log.Printf("Command finished with error: %v", err)
|
||||
return hkexnet.CSEExecFail, err // !?
|
||||
}
|
||||
if err := c.Wait(); err != nil {
|
||||
//fmt.Println("*** c.Wait() done ***")
|
||||
if exiterr, ok := err.(*exec.ExitError); ok {
|
||||
// The program has exited with an exit code != 0
|
||||
|
||||
// This works on both Unix and Windows. Although package
|
||||
// syscall is generally platform dependent, WaitStatus is
|
||||
// defined for both Unix and Windows and in both cases has
|
||||
// an ExitStatus() method with the same signature.
|
||||
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
|
||||
exitStatus = uint32(status.ExitStatus())
|
||||
if len(stdErrBuffer.Bytes()) > 0 {
|
||||
log.Print(stdErrBuffer)
|
||||
}
|
||||
log.Printf("Exit Status: %d", exitStatus)
|
||||
}
|
||||
}
|
||||
}
|
||||
//fmt.Println("*** server->client cp finished ***")
|
||||
return
|
||||
}
|
||||
|
||||
// Run a command (via default shell) as a specific user
|
||||
//
|
||||
// Uses ptys to support commands which expect a terminal.
|
||||
// nolint: gocyclo
|
||||
func runShellAs(who, hname, ttype, cmd string, interactive bool, conn *hkexnet.Conn, chaffing bool) (exitStatus uint32, err error) {
|
||||
var wg sync.WaitGroup
|
||||
u, err := user.Lookup(who)
|
||||
if err != nil {
|
||||
exitStatus = 1
|
||||
return
|
||||
}
|
||||
var uid, gid uint32
|
||||
_, _ = fmt.Sscanf(u.Uid, "%d", &uid) // nolint: gosec
|
||||
_, _ = fmt.Sscanf(u.Gid, "%d", &gid) // nolint: gosec
|
||||
log.Println("uid:", uid, "gid:", gid)
|
||||
|
||||
// Need to clear server's env and set key vars of the
|
||||
// target user. This isn't perfect (TERM doesn't seem to
|
||||
// work 100%; ANSI/xterm colour isn't working even
|
||||
// if we set "xterm" or "ansi" here; and line count
|
||||
// reported by 'stty -a' defaults to 24 regardless
|
||||
// of client shell window used to run client.
|
||||
// Investigate -- rlm 2018-01-26)
|
||||
os.Clearenv()
|
||||
_ = os.Setenv("HOME", u.HomeDir) // nolint: gosec
|
||||
_ = os.Setenv("TERM", ttype) // nolint: gosec
|
||||
_ = os.Setenv("HKEXSH", "1") // nolint: gosec
|
||||
|
||||
var c *exec.Cmd
|
||||
if interactive {
|
||||
//c = exec.Command("/bin/login", "-f", "-p", who) // nolint: gosec
|
||||
c = exec.Command("/bin/bash", "-i", "-l") // nolint: gosec
|
||||
} else {
|
||||
c = exec.Command("/bin/bash", "-c", cmd) // nolint: gosec
|
||||
}
|
||||
//If os.Clearenv() isn't called by server above these will be seen in the
|
||||
//client's session env.
|
||||
//c.Env = []string{"HOME=" + u.HomeDir, "SUDO_GID=", "SUDO_UID=", "SUDO_USER=", "SUDO_COMMAND=", "MAIL=", "LOGNAME="+who}
|
||||
c.Dir = u.HomeDir
|
||||
c.SysProcAttr = &syscall.SysProcAttr{}
|
||||
//c.SysProcAttr.Credential = &syscall.Credential{}
|
||||
c.SysProcAttr.Credential = &syscall.Credential{Uid: uid, Gid: gid}
|
||||
c.Stdin = conn
|
||||
c.Stdout = conn
|
||||
c.Stderr = conn
|
||||
|
||||
// Start the command with a pty.
|
||||
ptmx, err := pty.Start(c) // returns immediately with ptmx file
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return hkexnet.CSEPtyExecFail, err
|
||||
}
|
||||
// Make sure to close the pty at the end.
|
||||
// #gv:s/label=\"runShellAs\$1\"/label=\"deferPtmxClose\"/
|
||||
defer func() {
|
||||
//logger.LogDebug(fmt.Sprintf("[Exited process was %d]", c.Process.Pid))
|
||||
_ = ptmx.Close()
|
||||
}() // nolint: gosec
|
||||
|
||||
// get pty info for system accounting (who, lastlog)
|
||||
pts, pe := ptsName(ptmx.Fd())
|
||||
if pe != nil {
|
||||
return hkexnet.CSEPtyGetNameFail, err
|
||||
}
|
||||
utmpx := goutmp.Put_utmp(who, pts, hname)
|
||||
defer func() { goutmp.Unput_utmp(utmpx) }()
|
||||
goutmp.Put_lastlog_entry("hkexsh", who, pts, hname)
|
||||
|
||||
log.Printf("[%s]\n", cmd)
|
||||
if err != nil {
|
||||
log.Printf("Command finished with error: %v", err)
|
||||
} else {
|
||||
// Watch for term resizes
|
||||
// #gv:s/label=\"runShellAs\$2\"/label=\"termResizeWatcher\"/
|
||||
go func() {
|
||||
for sz := range conn.WinCh {
|
||||
log.Printf("[Setting term size to: %v %v]\n", sz.Rows, sz.Cols)
|
||||
pty.Setsize(ptmx, &pty.Winsize{Rows: sz.Rows, Cols: sz.Cols}) // nolint: gosec,errcheck
|
||||
}
|
||||
log.Println("*** WinCh goroutine done ***")
|
||||
}()
|
||||
|
||||
// Copy stdin to the pty.. (bgnd goroutine)
|
||||
// #gv:s/label=\"runShellAs\$3\"/label=\"stdinToPtyWorker\"/
|
||||
go func() {
|
||||
_, e := io.Copy(ptmx, conn)
|
||||
if e != nil {
|
||||
log.Println("** stdin->pty ended **:", e.Error())
|
||||
} else {
|
||||
log.Println("*** stdin->pty goroutine done ***")
|
||||
}
|
||||
}()
|
||||
|
||||
if chaffing {
|
||||
conn.EnableChaff()
|
||||
}
|
||||
// #gv:s/label=\"runShellAs\$4\"/label=\"deferChaffShutdown\"/
|
||||
defer func() {
|
||||
conn.DisableChaff()
|
||||
conn.ShutdownChaff()
|
||||
}()
|
||||
|
||||
// ..and the pty to stdout.
|
||||
// This may take some time exceeding that of the
|
||||
// actual command's lifetime, so the c.Wait() below
|
||||
// must synchronize with the completion of this goroutine
|
||||
// to ensure all stdout data gets to the client before
|
||||
// connection is closed.
|
||||
wg.Add(1)
|
||||
// #gv:s/label=\"runShellAs\$5\"/label=\"ptyToStdoutWorker\"/
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
_, e := io.Copy(conn, ptmx)
|
||||
if e != nil {
|
||||
log.Println("** pty->stdout ended **:", e.Error())
|
||||
} else {
|
||||
// The above io.Copy() will exit when the command attached
|
||||
// to the pty exits
|
||||
log.Println("*** pty->stdout goroutine done ***")
|
||||
}
|
||||
}()
|
||||
|
||||
if err := c.Wait(); err != nil {
|
||||
//fmt.Println("*** c.Wait() done ***")
|
||||
if exiterr, ok := err.(*exec.ExitError); ok {
|
||||
// The program has exited with an exit code != 0
|
||||
|
||||
// This works on both Unix and Windows. Although package
|
||||
// syscall is generally platform dependent, WaitStatus is
|
||||
// defined for both Unix and Windows and in both cases has
|
||||
// an ExitStatus() method with the same signature.
|
||||
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
|
||||
exitStatus = uint32(status.ExitStatus())
|
||||
log.Printf("Exit Status: %d", exitStatus)
|
||||
}
|
||||
}
|
||||
conn.SetStatus(hkexnet.CSOType(exitStatus))
|
||||
} else {
|
||||
logger.LogDebug("*** Main proc has exited. ***")
|
||||
// Background jobs still may be running; close the
|
||||
// pty anyway, so the client can return before
|
||||
// wg.Wait() below completes (Issue #18)
|
||||
_ = ptmx.Close()
|
||||
}
|
||||
wg.Wait() // Wait on pty->stdout completion to client
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GenAuthToken generates a pseudorandom auth token for a specific
|
||||
// user from a specific host to allow non-interactive logins.
|
||||
func GenAuthToken(who string, connhost string) string {
|
||||
//tokenA, e := os.Hostname()
|
||||
//if e != nil {
|
||||
// tokenA = "badhost"
|
||||
//}
|
||||
tokenA := connhost
|
||||
|
||||
tokenB := make([]byte, 64)
|
||||
_, _ = rand.Read(tokenB) // nolint: gosec
|
||||
return fmt.Sprintf("%s:%s", tokenA, hex.EncodeToString(tokenB))
|
||||
}
|
||||
|
||||
// Demo of a simple server that listens and spawns goroutines for each
|
||||
// connecting client. Note this code is identical to standard tcp
|
||||
// server code, save for declaring 'hkex' rather than 'net'
|
||||
// Listener and Conns. The KEx and encrypt/decrypt is done within the type.
|
||||
// Compare to 'serverp.go' in this directory to see the equivalence.
|
||||
// TODO: reduce gocyclo
|
||||
func main() {
|
||||
var vopt bool
|
||||
var chaffEnabled bool
|
||||
var chaffFreqMin uint
|
||||
var chaffFreqMax uint
|
||||
var chaffBytesMax uint
|
||||
var dbg bool
|
||||
var laddr string
|
||||
|
||||
flag.BoolVar(&vopt, "v", false, "show version")
|
||||
flag.StringVar(&laddr, "l", ":2000", "interface[:port] to listen")
|
||||
flag.BoolVar(&chaffEnabled, "e", true, "enable chaff pkts")
|
||||
flag.UintVar(&chaffFreqMin, "f", 100, "chaff pkt freq min (msecs)")
|
||||
flag.UintVar(&chaffFreqMax, "F", 5000, "chaff pkt freq max (msecs)")
|
||||
flag.UintVar(&chaffBytesMax, "B", 64, "chaff pkt size max (bytes)")
|
||||
flag.BoolVar(&dbg, "d", false, "debug logging")
|
||||
flag.Parse()
|
||||
|
||||
if vopt {
|
||||
fmt.Printf("version %s (%s)\n", version, gitCommit)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
{
|
||||
me, e := user.Current()
|
||||
if e != nil || me.Uid != "0" {
|
||||
log.Fatal("Must run as root.")
|
||||
}
|
||||
}
|
||||
|
||||
// Enforce some sane min/max vals on chaff flags
|
||||
if chaffFreqMin < 2 {
|
||||
chaffFreqMin = 2
|
||||
}
|
||||
if chaffFreqMax == 0 {
|
||||
chaffFreqMax = chaffFreqMin + 1
|
||||
}
|
||||
if chaffBytesMax == 0 || chaffBytesMax > 4096 {
|
||||
chaffBytesMax = 64
|
||||
}
|
||||
|
||||
Log, _ = logger.New(logger.LOG_DAEMON|logger.LOG_DEBUG|logger.LOG_NOTICE|logger.LOG_ERR, "hkexshd") // nolint: gosec
|
||||
hkexnet.Init(dbg, "hkexshd", logger.LOG_DAEMON|logger.LOG_DEBUG|logger.LOG_NOTICE|logger.LOG_ERR)
|
||||
if dbg {
|
||||
log.SetOutput(Log)
|
||||
} else {
|
||||
log.SetOutput(ioutil.Discard)
|
||||
}
|
||||
|
||||
// Set up handler for daemon signalling
|
||||
exitCh := make(chan os.Signal, 1)
|
||||
signal.Notify(exitCh, os.Signal(syscall.SIGTERM), os.Signal(syscall.SIGINT), os.Signal(syscall.SIGHUP), os.Signal(syscall.SIGUSR1), os.Signal(syscall.SIGUSR2))
|
||||
go func() {
|
||||
for {
|
||||
sig := <-exitCh
|
||||
switch sig.String() {
|
||||
case "terminated":
|
||||
logger.LogNotice(fmt.Sprintf("[Got signal: %s]", sig)) // nolint: gosec,errcheck
|
||||
signal.Reset()
|
||||
syscall.Kill(0, syscall.SIGTERM) // nolint: gosec,errcheck
|
||||
case "interrupt":
|
||||
logger.LogNotice(fmt.Sprintf("[Got signal: %s]", sig)) // nolint: gosec,errcheck
|
||||
signal.Reset()
|
||||
syscall.Kill(0, syscall.SIGINT) // nolint: gosec,errcheck
|
||||
case "hangup":
|
||||
logger.LogNotice(fmt.Sprintf("[Got signal: %s - nop]", sig)) // nolint:gosec,errcheck
|
||||
default:
|
||||
logger.LogNotice(fmt.Sprintf("[Got signal: %s - ignored]", sig)) // nolint: gosec,errcheck
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Listen on TCP port 2000 on all available unicast and
|
||||
// anycast IP addresses of the local system.
|
||||
l, err := hkexnet.Listen("tcp", laddr)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer l.Close() // nolint: errcheck
|
||||
|
||||
log.Println("Serving on", laddr)
|
||||
for {
|
||||
// Wait for a connection.
|
||||
conn, err := l.Accept()
|
||||
if err != nil {
|
||||
log.Printf("Accept() got error(%v), hanging up.\n", err)
|
||||
} else {
|
||||
log.Println("Accepted client")
|
||||
|
||||
// Set up chaffing to client
|
||||
// Will only start when runShellAs() is called
|
||||
// after stdin/stdout are hooked up
|
||||
conn.SetupChaff(chaffFreqMin, chaffFreqMax, chaffBytesMax) // configure server->client chaffing
|
||||
|
||||
// Handle the connection in a new goroutine.
|
||||
// The loop then returns to accepting, so that
|
||||
// multiple connections may be served concurrently.
|
||||
go func(hc *hkexnet.Conn) (e error) {
|
||||
defer hc.Close() // nolint: errcheck
|
||||
|
||||
//We use io.ReadFull() here to guarantee we consume
|
||||
//just the data we want for the hkexsh.Session, and no more.
|
||||
//Otherwise data will be sitting in the channel that isn't
|
||||
//passed down to the command handlers.
|
||||
var rec hkexsh.Session
|
||||
var len1, len2, len3, len4, len5, len6 uint32
|
||||
|
||||
n, err := fmt.Fscanf(hc, "%d %d %d %d %d %d\n", &len1, &len2, &len3, &len4, &len5, &len6)
|
||||
log.Printf("hkexsh.Session read:%d %d %d %d %d %d\n", len1, len2, len3, len4, len5, len6)
|
||||
|
||||
if err != nil || n < 6 {
|
||||
log.Println("[Bad hkexsh.Session fmt]")
|
||||
return err
|
||||
}
|
||||
|
||||
tmp := make([]byte, len1)
|
||||
_, err = io.ReadFull(hc, tmp)
|
||||
if err != nil {
|
||||
log.Println("[Bad hkexsh.Session.Op]")
|
||||
return err
|
||||
}
|
||||
rec.SetOp(tmp)
|
||||
|
||||
tmp = make([]byte, len2)
|
||||
_, err = io.ReadFull(hc, tmp)
|
||||
if err != nil {
|
||||
log.Println("[Bad hkexsh.Session.Who]")
|
||||
return err
|
||||
}
|
||||
rec.SetWho(tmp)
|
||||
|
||||
tmp = make([]byte, len3)
|
||||
_, err = io.ReadFull(hc, tmp)
|
||||
if err != nil {
|
||||
log.Println("[Bad hkexsh.Session.ConnHost]")
|
||||
return err
|
||||
}
|
||||
rec.SetConnHost(tmp)
|
||||
|
||||
tmp = make([]byte, len4)
|
||||
_, err = io.ReadFull(hc, tmp)
|
||||
if err != nil {
|
||||
log.Println("[Bad hkexsh.Session.TermType]")
|
||||
return err
|
||||
}
|
||||
rec.SetTermType(tmp)
|
||||
|
||||
tmp = make([]byte, len5)
|
||||
_, err = io.ReadFull(hc, tmp)
|
||||
if err != nil {
|
||||
log.Println("[Bad hkexsh.Session.Cmd]")
|
||||
return err
|
||||
}
|
||||
rec.SetCmd(tmp)
|
||||
|
||||
tmp = make([]byte, len6)
|
||||
_, err = io.ReadFull(hc, tmp)
|
||||
if err != nil {
|
||||
log.Println("[Bad hkexsh.Session.AuthCookie]")
|
||||
return err
|
||||
}
|
||||
rec.SetAuthCookie(tmp)
|
||||
|
||||
log.Printf("[hkexsh.Session: op:%c who:%s connhost:%s cmd:%s auth:****]\n",
|
||||
rec.Op()[0], string(rec.Who()), string(rec.ConnHost()), string(rec.Cmd()))
|
||||
|
||||
var valid bool
|
||||
var allowedCmds string // Currently unused
|
||||
if hkexsh.AuthUserByToken(string(rec.Who()), string(rec.ConnHost()), string(rec.AuthCookie(true))) {
|
||||
valid = true
|
||||
} else {
|
||||
valid, allowedCmds = hkexsh.AuthUserByPasswd(string(rec.Who()), string(rec.AuthCookie(true)), "/etc/hkexsh.passwd")
|
||||
}
|
||||
|
||||
// Security scrub
|
||||
rec.ClearAuthCookie()
|
||||
|
||||
// Tell client if auth was valid
|
||||
if valid {
|
||||
hc.Write([]byte{1}) // nolint: gosec,errcheck
|
||||
} else {
|
||||
logger.LogNotice(fmt.Sprintln("Invalid user", string(rec.Who()))) // nolint: errcheck,gosec
|
||||
hc.Write([]byte{0}) // nolint: gosec,errcheck
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("[allowedCmds:%s]\n", allowedCmds)
|
||||
|
||||
if rec.Op()[0] == 'A' {
|
||||
// Generate automated login token
|
||||
addr := hc.RemoteAddr()
|
||||
hname := goutmp.GetHost(addr.String())
|
||||
logger.LogNotice(fmt.Sprintf("[Generating autologin token for [%s@%s]]\n", rec.Who(), hname)) // nolint: gosec,errcheck
|
||||
token := GenAuthToken(string(rec.Who()), string(rec.ConnHost()))
|
||||
tokenCmd := fmt.Sprintf("echo \"%s\" | tee -a ~/.hkexsh_id", token)
|
||||
cmdStatus, runErr := runShellAs(string(rec.Who()), hname, string(rec.TermType()), tokenCmd, false, hc, chaffEnabled)
|
||||
// Returned hopefully via an EOF or exit/logout;
|
||||
// Clear current op so user can enter next, or EOF
|
||||
rec.SetOp([]byte{0})
|
||||
if runErr != nil {
|
||||
logger.LogErr(fmt.Sprintf("[Error generating autologin token for %s@%s]\n", rec.Who(), hname)) // nolint: gosec,errcheck
|
||||
} else {
|
||||
log.Printf("[Autologin token generation completed for %s@%s, status %d]\n", rec.Who(), hname, cmdStatus)
|
||||
hc.SetStatus(hkexnet.CSOType(cmdStatus))
|
||||
}
|
||||
} else if rec.Op()[0] == 'c' {
|
||||
// Non-interactive command
|
||||
addr := hc.RemoteAddr()
|
||||
hname := goutmp.GetHost(addr.String())
|
||||
logger.LogNotice(fmt.Sprintf("[Running command for [%s@%s]]\n", rec.Who(), hname)) // nolint: gosec,errcheck
|
||||
cmdStatus, runErr := runShellAs(string(rec.Who()), hname, string(rec.TermType()), string(rec.Cmd()), false, hc, chaffEnabled)
|
||||
// Returned hopefully via an EOF or exit/logout;
|
||||
// Clear current op so user can enter next, or EOF
|
||||
rec.SetOp([]byte{0})
|
||||
if runErr != nil {
|
||||
logger.LogErr(fmt.Sprintf("[Error spawning cmd for %s@%s]\n", rec.Who(), hname)) // nolint: gosec,errcheck
|
||||
} else {
|
||||
logger.LogNotice(fmt.Sprintf("[Command completed for %s@%s, status %d]\n", rec.Who(), hname, cmdStatus)) // nolint: gosec,errcheck
|
||||
hc.SetStatus(hkexnet.CSOType(cmdStatus))
|
||||
}
|
||||
} else if rec.Op()[0] == 's' {
|
||||
// Interactive session
|
||||
addr := hc.RemoteAddr()
|
||||
hname := goutmp.GetHost(addr.String())
|
||||
logger.LogNotice(fmt.Sprintf("[Running shell for [%s@%s]]\n", rec.Who(), hname)) // nolint: gosec,errcheck
|
||||
|
||||
cmdStatus, runErr := runShellAs(string(rec.Who()), hname, string(rec.TermType()), string(rec.Cmd()), true, hc, chaffEnabled)
|
||||
// Returned hopefully via an EOF or exit/logout;
|
||||
// Clear current op so user can enter next, or EOF
|
||||
rec.SetOp([]byte{0})
|
||||
if runErr != nil {
|
||||
Log.Err(fmt.Sprintf("[Error spawning shell for %s@%s]\n", rec.Who(), hname)) // nolint: gosec,errcheck
|
||||
} else {
|
||||
logger.LogNotice(fmt.Sprintf("[Shell completed for %s@%s, status %d]\n", rec.Who(), hname, cmdStatus)) // nolint: gosec,errcheck
|
||||
hc.SetStatus(hkexnet.CSOType(cmdStatus))
|
||||
}
|
||||
} else if rec.Op()[0] == 'D' {
|
||||
// File copy (destination) operation - client copy to server
|
||||
log.Printf("[Client->Server copy]\n")
|
||||
addr := hc.RemoteAddr()
|
||||
hname := goutmp.GetHost(addr.String())
|
||||
logger.LogNotice(fmt.Sprintf("[Running copy for [%s@%s]]\n", rec.Who(), hname)) // nolint: gosec,errcheck
|
||||
cmdStatus, runErr := runClientToServerCopyAs(string(rec.Who()), string(rec.TermType()), hc, string(rec.Cmd()), chaffEnabled)
|
||||
// Returned hopefully via an EOF or exit/logout;
|
||||
// Clear current op so user can enter next, or EOF
|
||||
rec.SetOp([]byte{0})
|
||||
if runErr != nil {
|
||||
logger.LogErr(fmt.Sprintf("[Error running cp for %s@%s]\n", rec.Who(), hname)) // nolint: gosec,errcheck
|
||||
} else {
|
||||
logger.LogNotice(fmt.Sprintf("[Command completed for %s@%s, status %d]\n", rec.Who(), hname, cmdStatus)) // nolint: gosec,errcheck
|
||||
}
|
||||
hc.SetStatus(hkexnet.CSOType(cmdStatus))
|
||||
|
||||
// Send CSOExitStatus *before* client closes channel
|
||||
s := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(s, cmdStatus)
|
||||
log.Printf("** cp writing closeStat %d at Close()\n", cmdStatus)
|
||||
hc.WritePacket(s, hkexnet.CSOExitStatus) // nolint: gosec,errcheck
|
||||
} else if rec.Op()[0] == 'S' {
|
||||
// File copy (src) operation - server copy to client
|
||||
log.Printf("[Server->Client copy]\n")
|
||||
addr := hc.RemoteAddr()
|
||||
hname := goutmp.GetHost(addr.String())
|
||||
logger.LogNotice(fmt.Sprintf("[Running copy for [%s@%s]]\n", rec.Who(), hname)) // nolint: gosec,errcheck
|
||||
cmdStatus, runErr := runServerToClientCopyAs(string(rec.Who()), string(rec.TermType()), hc, string(rec.Cmd()), chaffEnabled)
|
||||
if runErr != nil {
|
||||
logger.LogErr(fmt.Sprintf("[Error spawning cp for %s@%s]\n", rec.Who(), hname)) // nolint: gosec,errcheck
|
||||
} else {
|
||||
// Returned hopefully via an EOF or exit/logout;
|
||||
logger.LogNotice(fmt.Sprintf("[Command completed for %s@%s, status %d]\n", rec.Who(), hname, cmdStatus)) // nolint: gosec,errcheck
|
||||
}
|
||||
// Clear current op so user can enter next, or EOF
|
||||
rec.SetOp([]byte{0})
|
||||
hc.SetStatus(hkexnet.CSOType(cmdStatus))
|
||||
//fmt.Println("Waiting for EOF from other end.")
|
||||
//_, _ = hc.Read(nil /*ackByte*/)
|
||||
//fmt.Println("Got remote end ack.")
|
||||
} else {
|
||||
logger.LogErr(fmt.Sprintln("[Bad hkexsh.Session]")) // nolint: gosec,errcheck
|
||||
}
|
||||
return
|
||||
}(&conn) // nolint: errcheck
|
||||
} // Accept() success
|
||||
} //endfor
|
||||
//logger.LogNotice(fmt.Sprintln("[Exiting]")) // nolint: gosec,errcheck
|
||||
}
|
|
@ -2,8 +2,14 @@
|
|||
|
||||
EXE = $(notdir $(shell pwd))
|
||||
|
||||
ifeq ($(GARBLE),y)
|
||||
GO=garble -literals -tiny -debugdir=garbled
|
||||
else
|
||||
GO = go
|
||||
endif
|
||||
|
||||
all:
|
||||
go build .
|
||||
$(GO) build .
|
||||
|
||||
clean:
|
||||
$(RM) $(EXE) $(EXE).exe
|
||||
|
|
|
@ -0,0 +1,156 @@
|
|||
// +build freebsd
|
||||
|
||||
// Package logger is a wrapper around UNIX syslog, so that it also may
|
||||
// be wrapped with something else for Windows (Sadly, the stdlib log/syslog
|
||||
// is frozen, and there is no Windows implementation.)
|
||||
package logger
|
||||
|
||||
import (
|
||||
sl "log/syslog"
|
||||
)
|
||||
|
||||
// Priority is the logger priority
|
||||
type Priority = sl.Priority
|
||||
|
||||
// Writer is a syslog Writer
|
||||
type Writer = sl.Writer
|
||||
|
||||
// nolint: golint
|
||||
const (
|
||||
// Severity.
|
||||
|
||||
// From /usr/include/sys/syslog.h.
|
||||
// These are the same on Linux, BSD, and OS X.
|
||||
LOG_EMERG Priority = iota
|
||||
LOG_ALERT
|
||||
LOG_CRIT
|
||||
LOG_ERR
|
||||
LOG_WARNING
|
||||
LOG_NOTICE
|
||||
LOG_INFO
|
||||
LOG_DEBUG
|
||||
)
|
||||
|
||||
// nolint: golint
|
||||
const (
|
||||
// Facility.
|
||||
|
||||
// From /usr/include/sys/syslog.h.
|
||||
// These are the same up to LOG_FTP on Linux, BSD, and OS X.
|
||||
LOG_KERN Priority = iota << 3
|
||||
LOG_USER
|
||||
LOG_MAIL
|
||||
LOG_DAEMON
|
||||
LOG_AUTH
|
||||
LOG_SYSLOG
|
||||
LOG_LPR
|
||||
LOG_NEWS
|
||||
LOG_UUCP
|
||||
LOG_CRON
|
||||
LOG_AUTHPRIV
|
||||
LOG_FTP
|
||||
_ // unused
|
||||
_ // unused
|
||||
_ // unused
|
||||
_ // unused
|
||||
LOG_LOCAL0
|
||||
LOG_LOCAL1
|
||||
LOG_LOCAL2
|
||||
LOG_LOCAL3
|
||||
LOG_LOCAL4
|
||||
LOG_LOCAL5
|
||||
LOG_LOCAL6
|
||||
LOG_LOCAL7
|
||||
)
|
||||
|
||||
var (
|
||||
l *sl.Writer
|
||||
)
|
||||
|
||||
// New returns a new log Writer.
|
||||
func New(flags Priority, tag string) (w *Writer, e error) {
|
||||
w, e = sl.New(flags, tag)
|
||||
l = w
|
||||
return w, e
|
||||
}
|
||||
|
||||
// Alert returns a log Alert error
|
||||
func Alert(s string) error {
|
||||
if l != nil {
|
||||
return l.Alert(s)
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// LogClose closes the log Writer.
|
||||
func LogClose() error {
|
||||
if l != nil {
|
||||
return l.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LogCrit returns a log Alert error
|
||||
func LogCrit(s string) error {
|
||||
if l != nil {
|
||||
return l.Crit(s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LogDebug returns a log Debug error
|
||||
func LogDebug(s string) error {
|
||||
if l != nil {
|
||||
return l.Debug(s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LogEmerg returns a log Emerg error
|
||||
func LogEmerg(s string) error {
|
||||
if l != nil {
|
||||
return l.Emerg(s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LogErr returns a log Err error
|
||||
func LogErr(s string) error {
|
||||
if l != nil {
|
||||
return l.Err(s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LogInfo returns a log Info error
|
||||
func LogInfo(s string) error {
|
||||
if l != nil {
|
||||
return l.Info(s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LogNotice returns a log Notice error
|
||||
func LogNotice(s string) error {
|
||||
if l != nil {
|
||||
return l.Notice(s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LogWarning returns a log Warning error
|
||||
func LogWarning(s string) error {
|
||||
if l != nil {
|
||||
return l.Warning(s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LogWrite writes to the logger at default level
|
||||
func LogWrite(b []byte) (int, error) {
|
||||
if l != nil {
|
||||
return l.Write(b)
|
||||
}
|
||||
return len(b),nil
|
||||
}
|
|
@ -11,6 +11,7 @@ import (
|
|||
|
||||
// Priority is the logger priority
|
||||
type Priority = sl.Priority
|
||||
|
||||
// Writer is a syslog Writer
|
||||
type Writer = sl.Writer
|
||||
|
||||
|
@ -75,50 +76,81 @@ func New(flags Priority, tag string) (w *Writer, e error) {
|
|||
|
||||
// Alert returns a log Alert error
|
||||
func Alert(s string) error {
|
||||
if l != nil {
|
||||
return l.Alert(s)
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// LogClose closes the log Writer.
|
||||
func LogClose() error {
|
||||
if l != nil {
|
||||
return l.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LogCrit returns a log Alert error
|
||||
func LogCrit(s string) error {
|
||||
if l != nil {
|
||||
return l.Crit(s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LogDebug returns a log Debug error
|
||||
func LogDebug(s string) error {
|
||||
if l != nil {
|
||||
return l.Debug(s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LogEmerg returns a log Emerg error
|
||||
func LogEmerg(s string) error {
|
||||
if l != nil {
|
||||
return l.Emerg(s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LogErr returns a log Err error
|
||||
func LogErr(s string) error {
|
||||
if l != nil {
|
||||
return l.Err(s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LogInfo returns a log Info error
|
||||
func LogInfo(s string) error {
|
||||
if l != nil {
|
||||
return l.Info(s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LogNotice returns a log Notice error
|
||||
func LogNotice(s string) error {
|
||||
if l != nil {
|
||||
return l.Notice(s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LogWarning returns a log Warning error
|
||||
func LogWarning(s string) error {
|
||||
if l != nil {
|
||||
return l.Warning(s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LogWrite writes to the logger at default level
|
||||
func LogWrite(b []byte) (int, error) {
|
||||
if l != nil {
|
||||
return l.Write(b)
|
||||
}
|
||||
return len(b),nil
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// +build windows
|
||||
//
|
||||
|
||||
// Wrapper around UNIX syslog, so that it also may be wrapped
|
||||
// with something else for Windows.
|
||||
package logger
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package hkexsh
|
||||
package xs
|
||||
|
||||
// Package hkexsh - a secure terminal client/server written from scratch in Go
|
||||
// Package xs - a secure terminal client/server written from scratch in Go
|
||||
//
|
||||
// Copyright (c) 2017-2018 Russell Magee
|
||||
// Copyright (c) 2017-2020 Russell Magee
|
||||
// Licensed under the terms of the MIT license (see LICENSE.mit in this
|
||||
// distribution)
|
||||
//
|
||||
|
@ -28,7 +28,7 @@ type Session struct {
|
|||
|
||||
// Output Session record as a string. Implements Stringer interface.
|
||||
func (h *Session) String() string {
|
||||
return fmt.Sprintf("hkexsh.Session:\nOp:%v\nWho:%v\nCmd:%v\nAuthCookie:%v\nStatus:%v",
|
||||
return fmt.Sprintf("xs.Session:\nOp:%v\nWho:%v\nCmd:%v\nAuthCookie:%v\nStatus:%v",
|
||||
h.op, h.who, h.cmd, h.AuthCookie(false), h.status)
|
||||
}
|
||||
|
||||
|
@ -96,7 +96,7 @@ func (h Session) AuthCookie(reallyShow bool) []byte {
|
|||
return []byte("**REDACTED**")
|
||||
}
|
||||
|
||||
// SetAuthCookie stores the authcookie (essential the password) used to
|
||||
// SetAuthCookie stores the authcookie (essentially the password) used to
|
||||
// authenticate the Session.
|
||||
func (h *Session) SetAuthCookie(a []byte) {
|
||||
h.authCookie = a
|
|
@ -0,0 +1,30 @@
|
|||
package xs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func _newMockSession() (s *Session) {
|
||||
s = &Session{op: []byte("A"),
|
||||
who: []byte("johndoe"),
|
||||
connhost: []byte("host"),
|
||||
termtype: []byte("vt100"),
|
||||
cmd: []byte("/bin/false"),
|
||||
authCookie: []byte("authcookie"),
|
||||
status: 0}
|
||||
return s
|
||||
}
|
||||
|
||||
func TestSessionAuthCookieShowTrue(t *testing.T) {
|
||||
sess := _newMockSession()
|
||||
if string(sess.AuthCookie(true)) != string(sess.authCookie) {
|
||||
t.Fatal("Failed to return unredacted authcookie on request")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionAuthCookieShowFalse(t *testing.T) {
|
||||
sess := _newMockSession()
|
||||
if string(sess.AuthCookie(false)) != string("**REDACTED**") {
|
||||
t.Fatal("Failed to return redacted authcookie on request")
|
||||
}
|
||||
}
|
|
@ -1,11 +1,18 @@
|
|||
.PHONY: info clean lib
|
||||
|
||||
ifeq ($(GARBLE),y)
|
||||
GO = garble -literals -tiny -debugdir=garbled
|
||||
else
|
||||
GO = go
|
||||
endif
|
||||
|
||||
all: lib
|
||||
|
||||
clean:
|
||||
go clean .
|
||||
|
||||
lib: info
|
||||
$(GO) build .
|
||||
go install .
|
||||
|
||||
ifneq ($(MSYSTEM),)
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
// +build freebsd
|
||||
|
||||
package xs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"unsafe"
|
||||
|
||||
unix "golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
/* -------------
|
||||
* minimal terminal APIs brought in from ssh/terminal
|
||||
* (they have no real business being there as they aren't specific to
|
||||
* ssh, but as of Go v1.10, late 2019, core go stdlib hasn't yet done
|
||||
* the planned terminal lib reorgs.)
|
||||
* ------------- */
|
||||
|
||||
// From github.com/golang/crypto/blob/master/ssh/terminal/util_linux.go
|
||||
const getTermios = unix.TIOCGETA
|
||||
const setTermios = unix.TIOCSETA
|
||||
|
||||
// From github.com/golang/crypto/blob/master/ssh/terminal/util.go
|
||||
|
||||
// State contains the state of a terminal.
|
||||
type State struct {
|
||||
termios unix.Termios
|
||||
}
|
||||
|
||||
// MakeRaw put the terminal connected to the given file descriptor into raw
|
||||
// mode and returns the previous state of the terminal so that it can be
|
||||
// restored.
|
||||
func MakeRaw(fd uintptr) (*State, error) {
|
||||
var oldState State
|
||||
if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, getTermios, uintptr(unsafe.Pointer(&oldState.termios))); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newState := oldState.termios
|
||||
newState.Iflag &^= (unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON)
|
||||
newState.Oflag &^= unix.OPOST
|
||||
newState.Lflag &^= (unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN)
|
||||
newState.Cflag &^= (unix.CSIZE | unix.PARENB)
|
||||
newState.Cflag |= unix.CS8
|
||||
newState.Cc[unix.VMIN] = 1
|
||||
newState.Cc[unix.VTIME] = 0
|
||||
|
||||
if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, setTermios, uintptr(unsafe.Pointer(&newState))); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &oldState, nil
|
||||
}
|
||||
|
||||
// Restore restores the terminal connected to the given file descriptor to a
|
||||
// previous state.
|
||||
func Restore(fd uintptr, state *State) error {
|
||||
if state != nil {
|
||||
if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, setTermios, uintptr(unsafe.Pointer(state))); err != 0 {
|
||||
return err
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
return errors.New("nil State")
|
||||
}
|
||||
}
|
||||
|
||||
// ReadPassword reads a line of input from a terminal without local echo. This
|
||||
// is commonly used for inputting passwords and other sensitive data. The slice
|
||||
// returned does not include the \n.
|
||||
func ReadPassword(fd uintptr) ([]byte, error) {
|
||||
var oldState State
|
||||
if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, getTermios, uintptr(unsafe.Pointer(&oldState.termios))); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newState := oldState.termios
|
||||
newState.Lflag &^= unix.ECHO
|
||||
newState.Lflag |= unix.ICANON | unix.ISIG
|
||||
newState.Iflag |= unix.ICRNL
|
||||
if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, setTermios, uintptr(unsafe.Pointer(&newState))); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
unix.Syscall(unix.SYS_IOCTL, fd, setTermios, uintptr(unsafe.Pointer(&oldState.termios)))
|
||||
}()
|
||||
|
||||
return readPasswordLine(passwordReader(fd))
|
||||
}
|
||||
|
||||
// passwordReader is an io.Reader that reads from a specific file descriptor.
|
||||
type passwordReader int
|
||||
|
||||
func (r passwordReader) Read(buf []byte) (int, error) {
|
||||
return unix.Read(int(r), buf)
|
||||
}
|
||||
|
||||
// readPasswordLine reads from reader until it finds \n or io.EOF.
|
||||
// The slice returned does not include the \n.
|
||||
// readPasswordLine also ignores any \r it finds.
|
||||
func readPasswordLine(reader io.Reader) ([]byte, error) {
|
||||
var buf [1]byte
|
||||
var ret []byte
|
||||
|
||||
for {
|
||||
n, err := reader.Read(buf[:])
|
||||
if n > 0 {
|
||||
switch buf[0] {
|
||||
case '\n':
|
||||
return ret, nil
|
||||
case '\r':
|
||||
// remove \r from passwords on Windows
|
||||
default:
|
||||
ret = append(ret, buf[0])
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
if err == io.EOF && len(ret) > 0 {
|
||||
return ret, nil
|
||||
}
|
||||
return ret, err
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +1,11 @@
|
|||
// +build linux
|
||||
|
||||
package hkexsh
|
||||
package xs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
unix "golang.org/x/sys/unix"
|
||||
)
|
||||
|
@ -12,7 +13,7 @@ import (
|
|||
/* -------------
|
||||
* minimal terminal APIs brought in from ssh/terminal
|
||||
* (they have no real business being there as they aren't specific to
|
||||
* ssh, but as of Go v1.10, early 2018, core go stdlib hasn't yet done
|
||||
* ssh, but as of Go v1.10, late 2019, core go stdlib hasn't yet done
|
||||
* the planned terminal lib reorgs.)
|
||||
* ------------- */
|
||||
|
||||
|
@ -30,8 +31,9 @@ type State struct {
|
|||
// MakeRaw put the terminal connected to the given file descriptor into raw
|
||||
// mode and returns the previous state of the terminal so that it can be
|
||||
// restored.
|
||||
func MakeRaw(fd int) (*State, error) {
|
||||
termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
||||
func MakeRaw(f *os.File) (*State, error) {
|
||||
fd := f.Fd()
|
||||
termios, err := unix.IoctlGetTermios(int(fd), ioctlReadTermios)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -47,7 +49,7 @@ func MakeRaw(fd int) (*State, error) {
|
|||
termios.Cflag |= unix.CS8
|
||||
termios.Cc[unix.VMIN] = 1
|
||||
termios.Cc[unix.VTIME] = 0
|
||||
if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, termios); err != nil {
|
||||
if err := unix.IoctlSetTermios(int(fd), ioctlWriteTermios, termios); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -56,8 +58,8 @@ func MakeRaw(fd int) (*State, error) {
|
|||
|
||||
// GetState returns the current state of a terminal which may be useful to
|
||||
// restore the terminal after a signal.
|
||||
func GetState(fd int) (*State, error) {
|
||||
termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
||||
func GetState(f *os.File) (*State, error) {
|
||||
termios, err := unix.IoctlGetTermios(int(f.Fd()), ioctlReadTermios)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -67,9 +69,9 @@ func GetState(fd int) (*State, error) {
|
|||
|
||||
// Restore restores the terminal connected to the given file descriptor to a
|
||||
// previous state.
|
||||
func Restore(fd int, state *State) error {
|
||||
func Restore(f *os.File, state *State) error {
|
||||
if state != nil {
|
||||
return unix.IoctlSetTermios(fd, ioctlWriteTermios, &state.termios)
|
||||
return unix.IoctlSetTermios(int(f.Fd()), ioctlWriteTermios, &state.termios)
|
||||
} else {
|
||||
return errors.New("nil State")
|
||||
}
|
||||
|
@ -78,8 +80,9 @@ func Restore(fd int, state *State) error {
|
|||
// ReadPassword reads a line of input from a terminal without local echo. This
|
||||
// is commonly used for inputting passwords and other sensitive data. The slice
|
||||
// returned does not include the \n.
|
||||
func ReadPassword(fd int) ([]byte, error) {
|
||||
termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
||||
func ReadPassword(f *os.File) ([]byte, error) {
|
||||
fd := f.Fd()
|
||||
termios, err := unix.IoctlGetTermios(int(fd), ioctlReadTermios)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -88,12 +91,12 @@ func ReadPassword(fd int) ([]byte, error) {
|
|||
newState.Lflag &^= unix.ECHO
|
||||
newState.Lflag |= unix.ICANON | unix.ISIG
|
||||
newState.Iflag |= unix.ICRNL
|
||||
if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, &newState); err != nil {
|
||||
if err := unix.IoctlSetTermios(int(fd), ioctlWriteTermios, &newState); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = unix.IoctlSetTermios(fd, ioctlWriteTermios, termios) // nolint: gosec
|
||||
_ = unix.IoctlSetTermios(int(fd), ioctlWriteTermios, termios) // nolint: gosec
|
||||
}()
|
||||
|
||||
return readPasswordLine(passwordReader(fd))
|
|
@ -1,7 +1,8 @@
|
|||
//go:build windows
|
||||
// +build windows
|
||||
//
|
||||
|
||||
// Note the terminal manipulation functions herein are mostly stubs. They
|
||||
// don't really do anything and the hkexsh demo client depends on a wrapper
|
||||
// don't really do anything and the xs demo client depends on a wrapper
|
||||
// script using the 'stty' tool to actually set the proper mode for
|
||||
// password login and raw mode required, then restoring it upon logout/exit.
|
||||
//
|
||||
|
@ -12,13 +13,15 @@
|
|||
// here; the wrapper does the bare minimum to make the client workable
|
||||
// under MSYS+mintty which is what I use.
|
||||
|
||||
package hkexsh
|
||||
package xs
|
||||
|
||||
import (
|
||||
"io"
|
||||
"bufio"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
"os/signal"
|
||||
)
|
||||
|
||||
type State struct {
|
||||
|
@ -27,67 +30,84 @@ type State struct {
|
|||
// MakeRaw put the terminal connected to the given file descriptor into raw
|
||||
// mode and returns the previous state of the terminal so that it can be
|
||||
// restored.
|
||||
func MakeRaw(fd int) (*State, error) {
|
||||
// This doesn't really work. The exec.Command() runs a sub-shell
|
||||
// so the stty mods don't affect the client process.
|
||||
cmd := exec.Command("stty", "-echo raw")
|
||||
cmd.Run()
|
||||
func MakeRaw(f *os.File) (*State, error) {
|
||||
cmd := exec.Command("stty", "-echo", "raw")
|
||||
cmd.Stdin = f
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return &State{}, err
|
||||
}
|
||||
|
||||
// MSYS2/CYGWIN: wintty needs CTRL-C caught
|
||||
// ----------------------------------------
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt, os.Kill)
|
||||
go func() {
|
||||
for sig := range c {
|
||||
_ = sig
|
||||
//fmt.Println(sig)
|
||||
}
|
||||
}()
|
||||
// ----------------------------------------
|
||||
|
||||
return &State{}, nil
|
||||
}
|
||||
|
||||
// GetState returns the current state of a terminal which may be useful to
|
||||
// restore the terminal after a signal.
|
||||
func GetState(fd int) (*State, error) {
|
||||
func GetState(f *os.File) (*State, error) {
|
||||
return &State{}, nil
|
||||
}
|
||||
|
||||
// Restore restores the terminal connected to the given file descriptor to a
|
||||
// previous state.
|
||||
func Restore(fd int, state *State) error {
|
||||
cmd := exec.Command("stty", "echo cooked")
|
||||
cmd.Run()
|
||||
func Restore(f *os.File, state *State) error {
|
||||
cmd := exec.Command("stty", "sane")
|
||||
cmd.Stdin = f
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadPassword reads a line of input from a terminal without local echo. This
|
||||
// is commonly used for inputting passwords and other sensitive data. The slice
|
||||
// returned does not include the \n.
|
||||
func ReadPassword(fd int) ([]byte, error) {
|
||||
return readPasswordLine(passwordReader(fd))
|
||||
}
|
||||
|
||||
// passwordReader is an io.Reader that reads from a specific file descriptor.
|
||||
type passwordReader windows.Handle
|
||||
|
||||
func (r passwordReader) Read(buf []byte) (int, error) {
|
||||
return windows.Read(windows.Handle(r), buf)
|
||||
}
|
||||
|
||||
// readPasswordLine reads from reader until it finds \n or io.EOF.
|
||||
// The slice returned does not include the \n.
|
||||
// readPasswordLine also ignores any \r it finds.
|
||||
func readPasswordLine(reader io.Reader) ([]byte, error) {
|
||||
var buf [1]byte
|
||||
var ret []byte
|
||||
|
||||
for {
|
||||
n, err := reader.Read(buf[:])
|
||||
if n > 0 {
|
||||
switch buf[0] {
|
||||
case '\n':
|
||||
return ret, nil
|
||||
case '\r':
|
||||
// remove \r from passwords on Windows
|
||||
default:
|
||||
ret = append(ret, buf[0])
|
||||
}
|
||||
continue
|
||||
}
|
||||
func ReadPassword(f *os.File) (pw []byte, err error) {
|
||||
sttycmd, err := exec.LookPath("stty")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
//fmt.Printf("stty found at: %v\n", sttycmd)
|
||||
cmdOff := exec.Command(sttycmd, "-echo")
|
||||
cmdOff.Stdin = f //os.Stdin
|
||||
cmdOff.Stdout = nil //os.Stdout
|
||||
cmdOff.Stderr = nil //os.Stderr
|
||||
err = cmdOff.Run()
|
||||
if err != nil {
|
||||
if err == io.EOF && len(ret) > 0 {
|
||||
return ret, nil
|
||||
}
|
||||
return ret, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//fmt.Printf("Enter password:")
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
scanner.Scan()
|
||||
err = scanner.Err()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pw = scanner.Bytes()
|
||||
fmt.Println()
|
||||
cmdOn := exec.Command(sttycmd, "echo")
|
||||
cmdOn.Stdin = f //os.Stdin
|
||||
cmdOn.Stdout = nil //os.Stdout
|
||||
cmdOn.Stderr = nil //os.Stderr
|
||||
err = cmdOn.Run()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 675 KiB After Width: | Height: | Size: 675 KiB |
|
@ -0,0 +1,14 @@
|
|||
#!/bin/bash
|
||||
|
||||
## setup.sh - create some files for xc copy testing
|
||||
dir=cptest
|
||||
|
||||
mkdir -p ${dir}/subdir
|
||||
dd bs=1024 count=16 if=/dev/urandom of=${dir}/file16KB
|
||||
dd bs=1024 count=16 if=/dev/urandom of=${dir}/file1KB
|
||||
dd bs=1024 count=16 if=/dev/urandom of=${dir}/file32KB
|
||||
dd bs=1024 count=16 if=/dev/urandom of=${dir}/file6B
|
||||
dd bs=1048576 count=32 if=/dev/urandom of=${dir}/subdir/file32MB
|
||||
dd bs=1048576 count=64 if=/dev/urandom of=${dir}/subdir/file64MB
|
||||
|
||||
sha1sum $(find ${dir} -type f | sort) >${dir}.sha1sum
|
|
@ -0,0 +1,24 @@
|
|||
.PHONY: clean all vis lint
|
||||
|
||||
ifeq ($(GARBLE),y)
|
||||
GO = garble -literals -tiny -debugdir=garbled
|
||||
else
|
||||
GO = go
|
||||
endif
|
||||
|
||||
EXTPKGS = bytes,errors,flag,fmt,internal,io,log,net,os,path,runtime,time,strings,sync,syscall,binary,encoding
|
||||
EXE = $(notdir $(shell pwd))
|
||||
|
||||
all:
|
||||
echo "BUILDOPTS:" $(BUILDOPTS)
|
||||
$(GO) build $(BUILDOPTS) .
|
||||
|
||||
clean:
|
||||
$(RM) $(EXE) $(EXE).exe
|
||||
|
||||
vis:
|
||||
go-callvis -file xs-vis -format png -ignore $(EXTPKGS) -group pkg,type .
|
||||
../fixup-gv.sh xs.go && cat xs-vis.gv | dot -Tpng -oxs-vis-fixedup.png
|
||||
|
||||
lint:
|
||||
-golangci-lint run
|
|
@ -1,4 +1,5 @@
|
|||
// +build linux
|
||||
//go:build linux || freebsd
|
||||
// +build linux freebsd
|
||||
|
||||
package main
|
||||
|
||||
|
@ -9,11 +10,11 @@ import (
|
|||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"blitter.com/go/hkexsh/hkexnet"
|
||||
"blitter.com/go/xs/xsnet"
|
||||
)
|
||||
|
||||
// Handle pty resizes (notify server side)
|
||||
func handleTermResizes(conn *hkexnet.Conn) {
|
||||
func handleTermResizes(conn *xsnet.Conn) {
|
||||
ch := make(chan os.Signal, 1)
|
||||
signal.Notify(ch, syscall.SIGWINCH)
|
||||
wg.Add(1)
|
||||
|
@ -30,7 +31,7 @@ func handleTermResizes(conn *hkexnet.Conn) {
|
|||
log.Println(err)
|
||||
}
|
||||
termSzPacket := fmt.Sprintf("%d %d", rows, cols)
|
||||
conn.WritePacket([]byte(termSzPacket), hkexnet.CSOTermSize) // nolint: errcheck,gosec
|
||||
conn.WritePacket([]byte(termSzPacket), xsnet.CSOTermSize) //nolint:errcheck
|
||||
}
|
||||
}()
|
||||
ch <- syscall.SIGWINCH // Initial resize.
|
|
@ -6,11 +6,11 @@ import (
|
|||
"log"
|
||||
"time"
|
||||
|
||||
"blitter.com/go/hkexsh/hkexnet"
|
||||
"blitter.com/go/xs/xsnet"
|
||||
)
|
||||
|
||||
// Handle pty resizes (notify server side)
|
||||
func handleTermResizes(conn *hkexnet.Conn) {
|
||||
func handleTermResizes(conn *xsnet.Conn) {
|
||||
var hasStty bool
|
||||
curCols, curRows := 0, 0
|
||||
_, _, err := GetSize()
|
||||
|
@ -57,7 +57,7 @@ func handleTermResizes(conn *hkexnet.Conn) {
|
|||
log.Println(err)
|
||||
}
|
||||
termSzPacket := fmt.Sprintf("%d %d", curRows, curCols)
|
||||
conn.WritePacket([]byte(termSzPacket), hkexnet.CSOTermSize)
|
||||
conn.WritePacket([]byte(termSzPacket), xsnet.CSOTermSize)
|
||||
}
|
||||
}
|
||||
}()
|
After Width: | Height: | Size: 1.2 MiB |
|
@ -0,0 +1,622 @@
|
|||
digraph gocallvis {
|
||||
label="blitter.com/go/xs/xs";
|
||||
labeljust="l";
|
||||
fontname="Arial";
|
||||
fontsize="14";
|
||||
rankdir="LR";
|
||||
bgcolor="lightgray";
|
||||
style="solid";
|
||||
penwidth="0.5";
|
||||
pad="0.0";
|
||||
nodesep="0.35";
|
||||
|
||||
node [shape="box" style="filled,rounded" fillcolor="honeydew" fontname="Verdana" penwidth="1.0" margin="0.05,0.0"];
|
||||
edge [minlen="2"]
|
||||
|
||||
subgraph "cluster_focus" {
|
||||
bgcolor="#e6ecfa";
|
||||
label="main";
|
||||
labelloc="t";
|
||||
labeljust="c";
|
||||
fontsize="18";
|
||||
|
||||
"blitter.com/go/xs/xs.restoreTermState" [ fillcolor="lightblue" label="restoreTermState" penwidth="0.5" tooltip="blitter.com/go/xs/xs.restoreTermState | defined in xs.go:1117\nat xs.go:1118: calling [blitter.com/go/xs.Restore]" ]
|
||||
"blitter.com/go/xs/xs.main$2" [ fillcolor="lightblue" label="deferCloseChaff" style="dotted,filled" tooltip="blitter.com/go/xs/xs.main$2 | defined in xs.go:996\nat xs.go:999: calling [blitter.com/go/xs/xs.exitWithStatus]\nat xs.go:997: calling [blitter.com/go/xs/xs.restoreTermState]" ]
|
||||
"blitter.com/go/xs/xs.exitWithStatus" [ penwidth="0.5" tooltip="blitter.com/go/xs/xs.exitWithStatus | defined in xs.go:1122" fillcolor="lightblue" label="exitWithStatus" ]
|
||||
"blitter.com/go/xs/xs.doCopyMode" [ fillcolor="lightblue" label="doCopyMode" penwidth="0.5" tooltip="blitter.com/go/xs/xs.doCopyMode | defined in xs.go:354\nat xs.go:435: calling [(*blitter.com/go/xs/xsnet.Conn).GetStatus]\nat xs.go:438: calling [(*blitter.com/go/xs/xsnet.Conn).GetStatus]\nat xs.go:473: calling [(*blitter.com/go/xs/xsnet.Conn).GetStatus]\nat xs.go:475: calling [(*blitter.com/go/xs/xsnet.Conn).GetStatus]\nat xs.go:430: calling [(*blitter.com/go/xs/xsnet.Conn).SetStatus]\nat xs.go:444: calling [blitter.com/go/xs/xs.buildCmdRemoteToLocal]\nat xs.go:424: calling [(blitter.com/go/xs/xsnet.Conn).Read]\nat xs.go:356: calling [(blitter.com/go/xs.Session).Cmd]\nat xs.go:441: calling [(blitter.com/go/xs.Session).Cmd]\nat xs.go:417: calling [(*blitter.com/go/xs/xsnet.Conn).WritePacket]\nat xs.go:364: calling [blitter.com/go/xs/xs.buildCmdLocalToRemote]" ]
|
||||
"blitter.com/go/xs/xs.buildCmdLocalToRemote" [ tooltip="blitter.com/go/xs/xs.buildCmdLocalToRemote | defined in xs.go:270\nat xs.go:283: calling [blitter.com/go/xs.GetTool]\nat xs.go:311: calling [blitter.com/go/xs.GetTool]\nat xs.go:312: calling [blitter.com/go/xs.GetTool]\nat xs.go:335: calling [blitter.com/go/xs/xs.getTreeSizeSubCmd]" fillcolor="lightblue" label="buildCmdLocalToRemote" penwidth="0.5" ]
|
||||
"blitter.com/go/xs/xs.getTreeSizeSubCmd" [ fillcolor="lightblue" label="getTreeSizeSubCmd" penwidth="0.5" tooltip="blitter.com/go/xs/xs.getTreeSizeSubCmd | defined in xs.go:342" ]
|
||||
"blitter.com/go/xs/xs.buildCmdRemoteToLocal" [ fillcolor="lightblue" label="buildCmdRemoteToLocal" penwidth="0.5" tooltip="blitter.com/go/xs/xs.buildCmdRemoteToLocal | defined in xs.go:244\nat xs.go:256: calling [blitter.com/go/xs.GetTool]\nat xs.go:263: calling [blitter.com/go/xs.GetTool]" ]
|
||||
"blitter.com/go/xs/xs.copyBuffer" [ fillcolor="lightblue" label="copyBuffer" penwidth="0.5" tooltip="blitter.com/go/xs/xs.copyBuffer | defined in xs.go:128\nat xs.go:186: calling [blitter.com/go/xs/xs.copyBuffer$2]\nat xs.go:186: calling [blitter.com/go/xs/xs.copyBuffer$3]\nat xs.go:193: calling [(blitter.com/go/xs/xsnet.Conn).Write]\nat xs.go:186: calling [blitter.com/go/xs/xs.copyBuffer$1]" ]
|
||||
"blitter.com/go/xs/xs.copyBuffer$1" [ fillcolor="lightblue" label="copyBuffer$1" style="dotted,filled" tooltip="blitter.com/go/xs/xs.copyBuffer$1 | defined in xs.go:144" ]
|
||||
"blitter.com/go/xs/xs.copyBuffer$2" [ fillcolor="lightblue" label="copyBuffer$2" style="dotted,filled" tooltip="blitter.com/go/xs/xs.copyBuffer$2 | defined in xs.go:145" ]
|
||||
"blitter.com/go/xs/xs.copyBuffer$3" [ fillcolor="lightblue" label="copyBuffer$3" style="dotted,filled" tooltip="blitter.com/go/xs/xs.copyBuffer$3 | defined in xs.go:146" ]
|
||||
"blitter.com/go/xs/xs.reqTunnel" [ tooltip="blitter.com/go/xs/xs.reqTunnel | defined in xs.go:584\nat xs.go:594: calling [(*blitter.com/go/xs/xsnet.Conn).WritePacket]\nat xs.go:593: calling [blitter.com/go/xs/logger.LogDebug]" fillcolor="lightblue" label="reqTunnel" penwidth="0.5" ]
|
||||
"blitter.com/go/xs/xs.main" [ fillcolor="lightblue" label="main" penwidth="0.5" tooltip="blitter.com/go/xs/xs.main | defined in xs.go:680\nat xs.go:1056: calling [(*blitter.com/go/xs/xsnet.Conn).ShutdownChaff]\nat xs.go:1027: calling [(blitter.com/go/xs.Session).Status]\nat xs.go:1089: calling [(blitter.com/go/xs.Session).Status]\nat xs.go:1091: calling [(blitter.com/go/xs.Session).Status]\nat xs.go:1101: calling [(blitter.com/go/xs.Session).Status]\nat xs.go:864: calling [blitter.com/go/xs/logger.New]\nat xs.go:978: calling [blitter.com/go/xs.MakeRaw]\nat xs.go:1075: calling [blitter.com/go/xs/xs.main$3]\nat xs.go:1039: calling [(blitter.com/go/xs/xsnet.Conn).Read]\nat xs.go:960: calling [blitter.com/go/xs/xsnet.Dial]\nat xs.go:1081: calling [blitter.com/go/xs/xs.launchTuns]\nat xs.go:977: calling [github.com/mattn/go-isatty.IsTerminal]\nat xs.go:772: calling [blitter.com/go/xs/xs.exitWithStatus]\nat xs.go:853: calling [blitter.com/go/xs/xs.exitWithStatus]\nat xs.go:963: calling [blitter.com/go/xs/xs.exitWithStatus]\nat xs.go:1027: calling [blitter.com/go/xs/xs.exitWithStatus]\nat xs.go:1101: calling [blitter.com/go/xs/xs.exitWithStatus]\nat xs.go:1082: calling [blitter.com/go/xs/xs.doShellMode]\nat xs.go:1050: calling [(*blitter.com/go/xs/xsnet.Conn).SetupChaff]\nat xs.go:1022: calling [blitter.com/go/xs/xs.sendSessionParams]\nat xs.go:796: calling [blitter.com/go/xs/xs.parseNonSwitchArgs]\nat xs.go:1024: calling [blitter.com/go/xs/xs.restoreTermState]\nat xs.go:1090: calling [blitter.com/go/xs/xs.restoreTermState]\nat xs.go:1096: calling [blitter.com/go/xs/xs.restoreTermState]\nat xs.go:1055: calling [(*blitter.com/go/xs/xsnet.Conn).DisableChaff]\nat xs.go:1025: calling [(*blitter.com/go/xs.Session).SetStatus]\nat xs.go:1043: calling [(*blitter.com/go/xs.Session).SetStatus]\nat xs.go:1047: calling [(*blitter.com/go/xs.Session).SetStatus]\nat xs.go:1086: calling [(*blitter.com/go/xs.Session).SetStatus]\nat xs.go:852: calling [blitter.com/go/xs/xs.usageShell]\nat xs.go:984: calling [blitter.com/go/xs/xs.restoreTermState]\nat xs.go:1005: calling [blitter.com/go/xs.ReadPassword]\nat xs.go:1054: calling [(*blitter.com/go/xs/xsnet.Conn).EnableChaff]\nat xs.go:1085: calling [blitter.com/go/xs/xs.doCopyMode]\nat xs.go:972: calling [(*blitter.com/go/xs/xsnet.Conn).Close]\nat xs.go:1046: calling [blitter.com/go/xs/xs.rejectUserMsg]\nat xs.go:852: calling [blitter.com/go/xs/xs.usageCp]\nat xs.go:790: calling [blitter.com/go/xs/xs.main$1]\nat xs.go:803: calling [blitter.com/go/xs/xs.localUserName]\nat xs.go:865: calling [blitter.com/go/xs/xsnet.Init]\nat xs.go:1021: calling [blitter.com/go/xs.NewSession]" ]
|
||||
"blitter.com/go/xs/xs.parseNonSwitchArgs" [ label="parseNonSwitchArgs" penwidth="0.5" tooltip="blitter.com/go/xs/xs.parseNonSwitchArgs | defined in xs.go:599" fillcolor="lightblue" ]
|
||||
"blitter.com/go/xs/xs.main$1" [ fillcolor="lightblue" label="deferRestore" style="dotted,filled" tooltip="blitter.com/go/xs/xs.main$1 | defined in xs.go:790" ]
|
||||
"blitter.com/go/xs/xs.localUserName" [ fillcolor="lightblue" label="localUserName" penwidth="0.5" tooltip="blitter.com/go/xs/xs.localUserName | defined in xs.go:1106" ]
|
||||
"blitter.com/go/xs/xs.sendSessionParams" [ fillcolor="lightblue" label="sendSessionParams" penwidth="0.5" tooltip="blitter.com/go/xs/xs.sendSessionParams | defined in xs.go:649\nat xs.go:651: calling [(blitter.com/go/xs.Session).Cmd]\nat xs.go:671: calling [(blitter.com/go/xs.Session).Cmd]\nat xs.go:651: calling [(blitter.com/go/xs.Session).AuthCookie]\nat xs.go:675: calling [(blitter.com/go/xs.Session).AuthCookie]\nat xs.go:651: calling [(blitter.com/go/xs.Session).Who]\nat xs.go:659: calling [(blitter.com/go/xs.Session).Who]\nat xs.go:651: calling [(blitter.com/go/xs.Session).Op]\nat xs.go:655: calling [(blitter.com/go/xs.Session).Op]\nat xs.go:651: calling [(blitter.com/go/xs.Session).TermType]\nat xs.go:667: calling [(blitter.com/go/xs.Session).TermType]\nat xs.go:651: calling [(blitter.com/go/xs.Session).ConnHost]\nat xs.go:663: calling [(blitter.com/go/xs.Session).ConnHost]\nat xs.go:675: calling [(blitter.com/go/xs/xsnet.Conn).Write]\nat xs.go:671: calling [(blitter.com/go/xs/xsnet.Conn).Write]\nat xs.go:667: calling [(blitter.com/go/xs/xsnet.Conn).Write]\nat xs.go:663: calling [(blitter.com/go/xs/xsnet.Conn).Write]\nat xs.go:659: calling [(blitter.com/go/xs/xsnet.Conn).Write]\nat xs.go:655: calling [(blitter.com/go/xs/xsnet.Conn).Write]" ]
|
||||
"blitter.com/go/xs/xs.rejectUserMsg" [ fillcolor="lightblue" label="rejectUserMsg" penwidth="0.5" tooltip="blitter.com/go/xs/xs.rejectUserMsg | defined in xs.go:576\nat xs.go:577: calling [blitter.com/go/xs/spinsult.GetSentence]" ]
|
||||
"blitter.com/go/xs/xs.main$3" [ fillcolor="lightblue" label="main$3" style="dotted,filled" tooltip="blitter.com/go/xs/xs.main$3 | defined in xs.go:1065\nat xs.go:1068: calling [math/rand.Intn]\nat xs.go:1072: calling [(*blitter.com/go/xs/xsnet.Conn).WritePacket]" ]
|
||||
"blitter.com/go/xs/xs.launchTuns" [ fillcolor="lightblue" label="launchTuns" penwidth="0.5" tooltip="blitter.com/go/xs/xs.launchTuns | defined in xs.go:634\nat xs.go:645: calling [blitter.com/go/xs/xs.reqTunnel]" ]
|
||||
"blitter.com/go/xs/xs.doShellMode$1" [ tooltip="blitter.com/go/xs/xs.doShellMode$1 | defined in xs.go:490\nat xs.go:509: calling [blitter.com/go/xs/xs.exitWithStatus]\nat xs.go:519: calling [blitter.com/go/xs/xs.exitWithStatus]\nat xs.go:507: calling [(crypto/x509.SystemRootsError).Error]\nat xs.go:513: calling [(*blitter.com/go/xs.Session).SetStatus]\nat xs.go:507: calling [(crypto/x509.CertificateInvalidError).Error]\nat xs.go:507: calling [(vendor/golang.org/x/net/idna.labelError).Error]\nat xs.go:507: calling [(crypto/tls.RecordHeaderError).Error]\nat xs.go:507: calling [(crypto/x509.UnknownAuthorityError).Error]\nat xs.go:513: calling [(*blitter.com/go/xs/xsnet.Conn).GetStatus]\nat xs.go:507: calling [(crypto/tls.alert).Error]\nat xs.go:507: calling [(context.deadlineExceededError).Error]\nat xs.go:507: calling [(crypto/aes.KeySizeError).Error]\nat xs.go:507: calling [(crypto/x509.UnhandledCriticalExtension).Error]\nat xs.go:507: calling [(compress/flate.CorruptInputError).Error]\nat xs.go:507: calling [(*github.com/pkg/errors.fundamental).Error]\nat xs.go:491: calling [blitter.com/go/xs/xs.doShellMode$1$1]\nat xs.go:507: calling [(crypto/x509.HostnameError).Error]\nat xs.go:507: calling [(*crypto/tls.permanentError).Error]\nat xs.go:507: calling [(vendor/golang.org/x/net/idna.runeError).Error]\nat xs.go:514: calling [(blitter.com/go/xs.Session).Status]\nat xs.go:519: calling [(blitter.com/go/xs.Session).Status]\nat xs.go:503: calling [blitter.com/go/xs/xs.restoreTermState]\nat xs.go:518: calling [blitter.com/go/xs/xs.restoreTermState]\nat xs.go:507: calling [(compress/flate.InternalError).Error]" fillcolor="lightblue" label="shellRemoteToStdin" style="dotted,filled" ]
|
||||
"blitter.com/go/xs/xs.doShellMode$1$1" [ fillcolor="lightblue" label="doShellMode$1$1" style="dotted,filled" tooltip="blitter.com/go/xs/xs.doShellMode$1$1 | defined in xs.go:491" ]
|
||||
"blitter.com/go/xs/xs.doShellMode" [ fillcolor="lightblue" label="doShellMode" penwidth="0.5" tooltip="blitter.com/go/xs/xs.doShellMode | defined in xs.go:483\nat xs.go:522: calling [blitter.com/go/xs/xs.doShellMode$1]\nat xs.go:551: calling [blitter.com/go/xs/xs.doShellMode$2]\nat xs.go:527: calling [blitter.com/go/xs/xs.handleTermResizes]" ]
|
||||
"blitter.com/go/xs/xs.handleTermResizes$1" [ tooltip="blitter.com/go/xs/xs.handleTermResizes$1 | defined in termsize_unix.go:21\nat termsize_unix.go:33: calling [(*blitter.com/go/xs/xsnet.Conn).WritePacket]\nat termsize_unix.go:27: calling [blitter.com/go/xs/xs.GetSize]" fillcolor="lightblue" label="handleTermResizes$1" style="dotted,filled" ]
|
||||
"blitter.com/go/xs/xs.GetSize" [ penwidth="1.5" tooltip="blitter.com/go/xs/xs.GetSize | defined in xs.go:221" fillcolor="lightblue" label="GetSize" ]
|
||||
"blitter.com/go/xs/xs.handleTermResizes" [ fillcolor="lightblue" label="handleTermResizes" penwidth="0.5" tooltip="blitter.com/go/xs/xs.handleTermResizes | defined in termsize_unix.go:16\nat termsize_unix.go:21: calling [blitter.com/go/xs/xs.handleTermResizes$1]" ]
|
||||
"blitter.com/go/xs/xs.Copy" [ tooltip="blitter.com/go/xs/xs.Copy | defined in xs.go:115\nat xs.go:116: calling [blitter.com/go/xs/xs.copyBuffer]" fillcolor="lightblue" label="Copy" penwidth="1.5" ]
|
||||
"blitter.com/go/xs/xs.doShellMode$2$1" [ fillcolor="lightblue" label="doShellMode$2$1" style="dotted,filled" tooltip="blitter.com/go/xs/xs.doShellMode$2$1 | defined in xs.go:536\nat xs.go:539: calling [blitter.com/go/xs/xs.Copy]" ]
|
||||
"blitter.com/go/xs/xs.doShellMode$2" [ style="dotted,filled" tooltip="blitter.com/go/xs/xs.doShellMode$2 | defined in xs.go:534\nat xs.go:546: calling [blitter.com/go/xs/xs.restoreTermState]\nat xs.go:548: calling [blitter.com/go/xs/xs.exitWithStatus]\nat xs.go:541: calling [blitter.com/go/xs/xs.doShellMode$2$1]" fillcolor="lightblue" label="shellStdinToRemote" ]
|
||||
"blitter.com/go/xs/xs.usageShell" [ fillcolor="lightblue" label="usageShell" penwidth="0.5" tooltip="blitter.com/go/xs/xs.usageShell | defined in xs.go:559" ]
|
||||
"blitter.com/go/xs/xs.usageCp" [ label="usageCp" penwidth="0.5" tooltip="blitter.com/go/xs/xs.usageCp | defined in xs.go:565" fillcolor="lightblue" ]
|
||||
|
||||
subgraph "cluster_blitter.com/go/xs" {
|
||||
style="filled";
|
||||
rank="sink";
|
||||
tooltip="package: blitter.com/go/xs";
|
||||
label="xs";
|
||||
URL="/?f=blitter.com/go/xs";
|
||||
penwidth="0.8";
|
||||
fontsize="16";
|
||||
fillcolor="lightyellow";
|
||||
fontname="Tahoma bold";
|
||||
|
||||
"blitter.com/go/xs.Restore" [ tooltip="blitter.com/go/xs.Restore | defined in termmode_linux.go:70" fillcolor="moccasin" label="Restore" penwidth="1.5" ]
|
||||
"blitter.com/go/xs.GetTool" [ fillcolor="moccasin" label="GetTool" penwidth="1.5" tooltip="blitter.com/go/xs.GetTool | defined in auth.go:217" ]
|
||||
"blitter.com/go/xs.MakeRaw" [ fillcolor="moccasin" label="MakeRaw" penwidth="1.5" tooltip="blitter.com/go/xs.MakeRaw | defined in termmode_linux.go:33" ]
|
||||
"blitter.com/go/xs.ReadPassword" [ tooltip="blitter.com/go/xs.ReadPassword | defined in termmode_linux.go:81" fillcolor="moccasin" label="ReadPassword" penwidth="1.5" ]
|
||||
"blitter.com/go/xs.NewSession" [ fillcolor="moccasin" label="NewSession" penwidth="1.5" tooltip="blitter.com/go/xs.NewSession | defined in session.go:130" ]
|
||||
|
||||
subgraph "cluster_*blitter.com/go/xs.Session" {
|
||||
fillcolor="wheat2";
|
||||
label="(*Session)";
|
||||
tooltip="type: *blitter.com/go/xs.Session";
|
||||
penwidth="0.5";
|
||||
fontsize="15";
|
||||
fontcolor="#222222";
|
||||
labelloc="b";
|
||||
style="rounded,filled";
|
||||
|
||||
"(*blitter.com/go/xs.Session).SetStatus" [ fillcolor="moccasin" label="SetStatus" penwidth="1.5" tooltip="(*blitter.com/go/xs.Session).SetStatus | defined in session.go:125" ]
|
||||
|
||||
}
|
||||
|
||||
subgraph "cluster_blitter.com/go/xs.Session" {
|
||||
penwidth="0.5";
|
||||
fontsize="15";
|
||||
fontcolor="#222222";
|
||||
labelloc="b";
|
||||
style="rounded,filled";
|
||||
fillcolor="wheat2";
|
||||
label="(Session)";
|
||||
tooltip="type: blitter.com/go/xs.Session";
|
||||
|
||||
"(blitter.com/go/xs.Session).Cmd" [ fillcolor="moccasin" label="Cmd" penwidth="1.5" tooltip="(blitter.com/go/xs.Session).Cmd | defined in session.go:79" ]
|
||||
"(blitter.com/go/xs.Session).Op" [ fillcolor="moccasin" label="Op" penwidth="1.5" tooltip="(blitter.com/go/xs.Session).Op | defined in session.go:36" ]
|
||||
"(blitter.com/go/xs.Session).Who" [ fillcolor="moccasin" label="Who" penwidth="1.5" tooltip="(blitter.com/go/xs.Session).Who | defined in session.go:46" ]
|
||||
"(blitter.com/go/xs.Session).ConnHost" [ label="ConnHost" penwidth="1.5" tooltip="(blitter.com/go/xs.Session).ConnHost | defined in session.go:56" fillcolor="moccasin" ]
|
||||
"(blitter.com/go/xs.Session).TermType" [ fillcolor="moccasin" label="TermType" penwidth="1.5" tooltip="(blitter.com/go/xs.Session).TermType | defined in session.go:67" ]
|
||||
"(blitter.com/go/xs.Session).AuthCookie" [ fillcolor="moccasin" label="AuthCookie" penwidth="1.5" tooltip="(blitter.com/go/xs.Session).AuthCookie | defined in session.go:92" ]
|
||||
"(blitter.com/go/xs.Session).Status" [ penwidth="1.5" tooltip="(blitter.com/go/xs.Session).Status | defined in session.go:120" fillcolor="moccasin" label="Status" ]
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
subgraph "cluster_blitter.com/go/xs/logger" {
|
||||
penwidth="0.8";
|
||||
fontsize="16";
|
||||
fontname="Tahoma bold";
|
||||
URL="/?f=blitter.com/go/xs/logger";
|
||||
tooltip="package: blitter.com/go/xs/logger";
|
||||
style="filled";
|
||||
fillcolor="lightyellow";
|
||||
rank="sink";
|
||||
label="logger";
|
||||
|
||||
"blitter.com/go/xs/logger.LogDebug" [ fillcolor="moccasin" label="LogDebug" penwidth="1.5" tooltip="blitter.com/go/xs/logger.LogDebug | defined in logger_linux.go:103" ]
|
||||
"blitter.com/go/xs/logger.New" [ fillcolor="moccasin" label="New" penwidth="1.5" tooltip="blitter.com/go/xs/logger.New | defined in logger_linux.go:71" ]
|
||||
|
||||
}
|
||||
|
||||
subgraph "cluster_blitter.com/go/xs/spinsult" {
|
||||
penwidth="0.8";
|
||||
style="filled";
|
||||
fontname="Tahoma bold";
|
||||
rank="sink";
|
||||
fontsize="16";
|
||||
fillcolor="lightyellow";
|
||||
label="spinsult";
|
||||
URL="/?f=blitter.com/go/xs/spinsult";
|
||||
tooltip="package: blitter.com/go/xs/spinsult";
|
||||
|
||||
"blitter.com/go/xs/spinsult.GetSentence" [ label="GetSentence" penwidth="1.5" tooltip="blitter.com/go/xs/spinsult.GetSentence | defined in spinsult.go:43" fillcolor="moccasin" ]
|
||||
|
||||
}
|
||||
|
||||
subgraph "cluster_blitter.com/go/xs/xsnet" {
|
||||
fontname="Tahoma bold";
|
||||
rank="sink";
|
||||
label="xsnet";
|
||||
tooltip="package: blitter.com/go/xs/xsnet";
|
||||
penwidth="0.8";
|
||||
fontsize="16";
|
||||
style="filled";
|
||||
fillcolor="lightyellow";
|
||||
URL="/?f=blitter.com/go/xs/xsnet";
|
||||
|
||||
"blitter.com/go/xs/xsnet.Init" [ fillcolor="moccasin" label="Init" penwidth="1.5" tooltip="blitter.com/go/xs/xsnet.Init | defined in net.go:198" ]
|
||||
"blitter.com/go/xs/xsnet.Dial" [ fillcolor="moccasin" label="Dial" penwidth="1.5" tooltip="blitter.com/go/xs/xsnet.Dial | defined in net.go:899" ]
|
||||
|
||||
subgraph "cluster_*blitter.com/go/xs/xsnet.Conn" {
|
||||
fontcolor="#222222";
|
||||
labelloc="b";
|
||||
style="rounded,filled";
|
||||
fillcolor="wheat2";
|
||||
label="(*Conn)";
|
||||
tooltip="type: *blitter.com/go/xs/xsnet.Conn";
|
||||
penwidth="0.5";
|
||||
fontsize="15";
|
||||
|
||||
"(*blitter.com/go/xs/xsnet.Conn).WritePacket" [ fillcolor="moccasin" label="WritePacket" penwidth="1.5" tooltip="(*blitter.com/go/xs/xsnet.Conn).WritePacket | defined in net.go:1437" ]
|
||||
"(*blitter.com/go/xs/xsnet.Conn).SetStatus" [ label="SetStatus" penwidth="1.5" tooltip="(*blitter.com/go/xs/xsnet.Conn).SetStatus | defined in net.go:218" fillcolor="moccasin" ]
|
||||
"(*blitter.com/go/xs/xsnet.Conn).GetStatus" [ fillcolor="moccasin" label="GetStatus" penwidth="1.5" tooltip="(*blitter.com/go/xs/xsnet.Conn).GetStatus | defined in net.go:214" ]
|
||||
"(*blitter.com/go/xs/xsnet.Conn).Close" [ fillcolor="moccasin" label="Close" penwidth="1.5" tooltip="(*blitter.com/go/xs/xsnet.Conn).Close | defined in net.go:979" ]
|
||||
"(*blitter.com/go/xs/xsnet.Conn).SetupChaff" [ penwidth="1.5" tooltip="(*blitter.com/go/xs/xsnet.Conn).SetupChaff | defined in net.go:1563" fillcolor="moccasin" label="SetupChaff" ]
|
||||
"(*blitter.com/go/xs/xsnet.Conn).EnableChaff" [ fillcolor="moccasin" label="EnableChaff" penwidth="1.5" tooltip="(*blitter.com/go/xs/xsnet.Conn).EnableChaff | defined in net.go:1546" ]
|
||||
"(*blitter.com/go/xs/xsnet.Conn).DisableChaff" [ fillcolor="moccasin" label="DisableChaff" penwidth="1.5" tooltip="(*blitter.com/go/xs/xsnet.Conn).DisableChaff | defined in net.go:1553" ]
|
||||
"(*blitter.com/go/xs/xsnet.Conn).ShutdownChaff" [ tooltip="(*blitter.com/go/xs/xsnet.Conn).ShutdownChaff | defined in net.go:1558" fillcolor="moccasin" label="ShutdownChaff" penwidth="1.5" ]
|
||||
|
||||
}
|
||||
|
||||
subgraph "cluster_blitter.com/go/xs/xsnet.Conn" {
|
||||
label="(Conn)";
|
||||
tooltip="type: blitter.com/go/xs/xsnet.Conn";
|
||||
penwidth="0.5";
|
||||
fontsize="15";
|
||||
fontcolor="#222222";
|
||||
labelloc="b";
|
||||
style="rounded,filled";
|
||||
fillcolor="wheat2";
|
||||
|
||||
"(blitter.com/go/xs/xsnet.Conn).Read" [ penwidth="1.5" tooltip="(blitter.com/go/xs/xsnet.Conn).Read | defined in net.go:1192" fillcolor="moccasin" label="Read" ]
|
||||
"(blitter.com/go/xs/xsnet.Conn).Write" [ fillcolor="moccasin" label="Write" penwidth="1.5" tooltip="(blitter.com/go/xs/xsnet.Conn).Write | defined in net.go:1431" ]
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
subgraph "cluster_compress/flate" {
|
||||
fontsize="16";
|
||||
style="filled";
|
||||
fillcolor="lightyellow";
|
||||
label="flate";
|
||||
URL="/?f=compress/flate";
|
||||
penwidth="0.8";
|
||||
fontname="Tahoma bold";
|
||||
rank="sink";
|
||||
tooltip="package: compress/flate";
|
||||
|
||||
|
||||
subgraph "cluster_compress/flate.CorruptInputError" {
|
||||
fontcolor="#222222";
|
||||
labelloc="b";
|
||||
style="rounded,filled";
|
||||
fillcolor="wheat2";
|
||||
label="(CorruptInputError)";
|
||||
tooltip="type: compress/flate.CorruptInputError";
|
||||
penwidth="0.5";
|
||||
fontsize="15";
|
||||
|
||||
"(compress/flate.CorruptInputError).Error" [ penwidth="1.5" tooltip="(compress/flate.CorruptInputError).Error | defined in inflate.go:35" fillcolor="moccasin" label="Error" ]
|
||||
|
||||
}
|
||||
|
||||
subgraph "cluster_compress/flate.InternalError" {
|
||||
tooltip="type: compress/flate.InternalError";
|
||||
penwidth="0.5";
|
||||
fontsize="15";
|
||||
fontcolor="#222222";
|
||||
labelloc="b";
|
||||
style="rounded,filled";
|
||||
fillcolor="wheat2";
|
||||
label="(InternalError)";
|
||||
|
||||
"(compress/flate.InternalError).Error" [ tooltip="(compress/flate.InternalError).Error | defined in inflate.go:42" fillcolor="moccasin" label="Error" penwidth="1.5" ]
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
subgraph "cluster_context" {
|
||||
fontsize="16";
|
||||
style="filled";
|
||||
label="context";
|
||||
penwidth="0.8";
|
||||
fillcolor="lightyellow";
|
||||
fontname="Tahoma bold";
|
||||
rank="sink";
|
||||
URL="/?f=context";
|
||||
tooltip="package: context";
|
||||
|
||||
|
||||
subgraph "cluster_context.deadlineExceededError" {
|
||||
style="rounded,filled";
|
||||
fillcolor="wheat2";
|
||||
label="(deadlineExceededError)";
|
||||
tooltip="type: context.deadlineExceededError";
|
||||
penwidth="0.5";
|
||||
fontsize="15";
|
||||
fontcolor="#222222";
|
||||
labelloc="b";
|
||||
|
||||
"(context.deadlineExceededError).Error" [ fillcolor="moccasin" label="Error" penwidth="1.5" tooltip="(context.deadlineExceededError).Error | defined in context.go:165" ]
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
subgraph "cluster_crypto/aes" {
|
||||
penwidth="0.8";
|
||||
style="filled";
|
||||
fontname="Tahoma bold";
|
||||
rank="sink";
|
||||
label="aes";
|
||||
URL="/?f=crypto/aes";
|
||||
tooltip="package: crypto/aes";
|
||||
fontsize="16";
|
||||
fillcolor="lightyellow";
|
||||
|
||||
|
||||
subgraph "cluster_crypto/aes.KeySizeError" {
|
||||
style="rounded,filled";
|
||||
fillcolor="wheat2";
|
||||
label="(KeySizeError)";
|
||||
tooltip="type: crypto/aes.KeySizeError";
|
||||
penwidth="0.5";
|
||||
fontsize="15";
|
||||
fontcolor="#222222";
|
||||
labelloc="b";
|
||||
|
||||
"(crypto/aes.KeySizeError).Error" [ fillcolor="moccasin" label="Error" penwidth="1.5" tooltip="(crypto/aes.KeySizeError).Error | defined in cipher.go:24" ]
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
subgraph "cluster_crypto/tls" {
|
||||
penwidth="0.8";
|
||||
fontsize="16";
|
||||
fontname="Tahoma bold";
|
||||
URL="/?f=crypto/tls";
|
||||
tooltip="package: crypto/tls";
|
||||
style="filled";
|
||||
fillcolor="lightyellow";
|
||||
rank="sink";
|
||||
label="tls";
|
||||
|
||||
|
||||
subgraph "cluster_*crypto/tls.permanentError" {
|
||||
label="(*permanentError)";
|
||||
tooltip="type: *crypto/tls.permanentError";
|
||||
penwidth="0.5";
|
||||
fontsize="15";
|
||||
fontcolor="#222222";
|
||||
labelloc="b";
|
||||
style="rounded,filled";
|
||||
fillcolor="wheat2";
|
||||
|
||||
"(*crypto/tls.permanentError).Error" [ tooltip="(*crypto/tls.permanentError).Error | defined in conn.go:184" fillcolor="moccasin" label="Error" penwidth="1.5" ]
|
||||
|
||||
}
|
||||
|
||||
subgraph "cluster_crypto/tls.RecordHeaderError" {
|
||||
fillcolor="wheat2";
|
||||
label="(RecordHeaderError)";
|
||||
tooltip="type: crypto/tls.RecordHeaderError";
|
||||
penwidth="0.5";
|
||||
fontsize="15";
|
||||
fontcolor="#222222";
|
||||
labelloc="b";
|
||||
style="rounded,filled";
|
||||
|
||||
"(crypto/tls.RecordHeaderError).Error" [ tooltip="(crypto/tls.RecordHeaderError).Error | defined in conn.go:571" fillcolor="moccasin" label="Error" penwidth="1.5" ]
|
||||
|
||||
}
|
||||
|
||||
subgraph "cluster_crypto/tls.alert" {
|
||||
fontcolor="#222222";
|
||||
labelloc="b";
|
||||
style="rounded,filled";
|
||||
fillcolor="wheat2";
|
||||
label="(alert)";
|
||||
tooltip="type: crypto/tls.alert";
|
||||
penwidth="0.5";
|
||||
fontsize="15";
|
||||
|
||||
"(crypto/tls.alert).Error" [ fillcolor="moccasin" label="Error" penwidth="1.5" tooltip="(crypto/tls.alert).Error | defined in alert.go:97" ]
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
subgraph "cluster_crypto/x509" {
|
||||
URL="/?f=crypto/x509";
|
||||
style="filled";
|
||||
fillcolor="lightyellow";
|
||||
fontname="Tahoma bold";
|
||||
rank="sink";
|
||||
label="x509";
|
||||
tooltip="package: crypto/x509";
|
||||
penwidth="0.8";
|
||||
fontsize="16";
|
||||
|
||||
|
||||
subgraph "cluster_crypto/x509.CertificateInvalidError" {
|
||||
label="(CertificateInvalidError)";
|
||||
tooltip="type: crypto/x509.CertificateInvalidError";
|
||||
penwidth="0.5";
|
||||
fontsize="15";
|
||||
fontcolor="#222222";
|
||||
labelloc="b";
|
||||
style="rounded,filled";
|
||||
fillcolor="wheat2";
|
||||
|
||||
"(crypto/x509.CertificateInvalidError).Error" [ fillcolor="moccasin" label="Error" penwidth="1.5" tooltip="(crypto/x509.CertificateInvalidError).Error | defined in verify.go:67" ]
|
||||
|
||||
}
|
||||
|
||||
subgraph "cluster_crypto/x509.HostnameError" {
|
||||
tooltip="type: crypto/x509.HostnameError";
|
||||
penwidth="0.5";
|
||||
fontsize="15";
|
||||
fontcolor="#222222";
|
||||
labelloc="b";
|
||||
style="rounded,filled";
|
||||
fillcolor="wheat2";
|
||||
label="(HostnameError)";
|
||||
|
||||
"(crypto/x509.HostnameError).Error" [ fillcolor="moccasin" label="Error" penwidth="1.5" tooltip="(crypto/x509.HostnameError).Error | defined in verify.go:98" ]
|
||||
|
||||
}
|
||||
|
||||
subgraph "cluster_crypto/x509.SystemRootsError" {
|
||||
tooltip="type: crypto/x509.SystemRootsError";
|
||||
penwidth="0.5";
|
||||
fontsize="15";
|
||||
fontcolor="#222222";
|
||||
labelloc="b";
|
||||
style="rounded,filled";
|
||||
fillcolor="wheat2";
|
||||
label="(SystemRootsError)";
|
||||
|
||||
"(crypto/x509.SystemRootsError).Error" [ label="Error" penwidth="1.5" tooltip="(crypto/x509.SystemRootsError).Error | defined in verify.go:159" fillcolor="moccasin" ]
|
||||
|
||||
}
|
||||
|
||||
subgraph "cluster_crypto/x509.UnhandledCriticalExtension" {
|
||||
label="(UnhandledCriticalExtension)";
|
||||
tooltip="type: crypto/x509.UnhandledCriticalExtension";
|
||||
penwidth="0.5";
|
||||
fontsize="15";
|
||||
fontcolor="#222222";
|
||||
labelloc="b";
|
||||
style="rounded,filled";
|
||||
fillcolor="wheat2";
|
||||
|
||||
"(crypto/x509.UnhandledCriticalExtension).Error" [ label="Error" penwidth="1.5" tooltip="(crypto/x509.UnhandledCriticalExtension).Error | defined in x509.go:893" fillcolor="moccasin" ]
|
||||
|
||||
}
|
||||
|
||||
subgraph "cluster_crypto/x509.UnknownAuthorityError" {
|
||||
fontsize="15";
|
||||
fontcolor="#222222";
|
||||
labelloc="b";
|
||||
style="rounded,filled";
|
||||
fillcolor="wheat2";
|
||||
label="(UnknownAuthorityError)";
|
||||
tooltip="type: crypto/x509.UnknownAuthorityError";
|
||||
penwidth="0.5";
|
||||
|
||||
"(crypto/x509.UnknownAuthorityError).Error" [ tooltip="(crypto/x509.UnknownAuthorityError).Error | defined in verify.go:138" fillcolor="moccasin" label="Error" penwidth="1.5" ]
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
subgraph "cluster_github.com/mattn/go-isatty" {
|
||||
penwidth="0.8";
|
||||
fontsize="16";
|
||||
fontname="Tahoma bold";
|
||||
rank="sink";
|
||||
style="filled";
|
||||
fillcolor="lightyellow";
|
||||
label="isatty";
|
||||
URL="/?f=github.com/mattn/go-isatty";
|
||||
tooltip="package: github.com/mattn/go-isatty";
|
||||
|
||||
"github.com/mattn/go-isatty.IsTerminal" [ penwidth="1.5" tooltip="github.com/mattn/go-isatty.IsTerminal | defined in isatty_tcgets.go:10" fillcolor="moccasin" label="IsTerminal" ]
|
||||
|
||||
}
|
||||
|
||||
subgraph "cluster_github.com/pkg/errors" {
|
||||
fontname="Tahoma bold";
|
||||
label="errors";
|
||||
URL="/?f=github.com/pkg/errors";
|
||||
tooltip="package: github.com/pkg/errors";
|
||||
rank="sink";
|
||||
penwidth="0.8";
|
||||
fontsize="16";
|
||||
style="filled";
|
||||
fillcolor="lightyellow";
|
||||
|
||||
|
||||
subgraph "cluster_*github.com/pkg/errors.fundamental" {
|
||||
label="(*fundamental)";
|
||||
tooltip="type: *github.com/pkg/errors.fundamental";
|
||||
penwidth="0.5";
|
||||
fontsize="15";
|
||||
fontcolor="#222222";
|
||||
labelloc="b";
|
||||
style="rounded,filled";
|
||||
fillcolor="wheat2";
|
||||
|
||||
"(*github.com/pkg/errors.fundamental).Error" [ fillcolor="moccasin" label="Error" penwidth="1.5" tooltip="(*github.com/pkg/errors.fundamental).Error | defined in errors.go:125" ]
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
subgraph "cluster_math/rand" {
|
||||
style="filled";
|
||||
fillcolor="lightyellow";
|
||||
rank="sink";
|
||||
URL="/?f=math/rand";
|
||||
tooltip="package: math/rand";
|
||||
penwidth="0.8";
|
||||
fontsize="16";
|
||||
fontname="Tahoma bold";
|
||||
label="rand";
|
||||
|
||||
"math/rand.Intn" [ fillcolor="moccasin" label="Intn" penwidth="1.5" tooltip="math/rand.Intn | defined in rand.go:337" ]
|
||||
|
||||
}
|
||||
|
||||
subgraph "cluster_vendor/golang.org/x/net/idna" {
|
||||
URL="/?f=vendor/golang.org/x/net/idna";
|
||||
penwidth="0.8";
|
||||
fontname="Tahoma bold";
|
||||
fillcolor="lightyellow";
|
||||
rank="sink";
|
||||
label="idna";
|
||||
tooltip="package: vendor/golang.org/x/net/idna";
|
||||
fontsize="16";
|
||||
style="filled";
|
||||
|
||||
|
||||
subgraph "cluster_vendor/golang.org/x/net/idna.labelError" {
|
||||
style="rounded,filled";
|
||||
fillcolor="wheat2";
|
||||
label="(labelError)";
|
||||
tooltip="type: vendor/golang.org/x/net/idna.labelError";
|
||||
penwidth="0.5";
|
||||
fontsize="15";
|
||||
fontcolor="#222222";
|
||||
labelloc="b";
|
||||
|
||||
"(vendor/golang.org/x/net/idna.labelError).Error" [ fillcolor="moccasin" label="Error" penwidth="1.5" tooltip="(vendor/golang.org/x/net/idna.labelError).Error | defined in idna10.0.0.go:324" ]
|
||||
|
||||
}
|
||||
|
||||
subgraph "cluster_vendor/golang.org/x/net/idna.runeError" {
|
||||
style="rounded,filled";
|
||||
fillcolor="wheat2";
|
||||
label="(runeError)";
|
||||
tooltip="type: vendor/golang.org/x/net/idna.runeError";
|
||||
penwidth="0.5";
|
||||
fontsize="15";
|
||||
fontcolor="#222222";
|
||||
labelloc="b";
|
||||
|
||||
"(vendor/golang.org/x/net/idna.runeError).Error" [ tooltip="(vendor/golang.org/x/net/idna.runeError).Error | defined in idna10.0.0.go:331" fillcolor="moccasin" label="Error" penwidth="1.5" ]
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
"blitter.com/go/xs/xs.doShellMode$1" -> "blitter.com/go/xs/xs.exitWithStatus" [ tooltip="at xs.go:509: calling [blitter.com/go/xs/xs.exitWithStatus]\nat xs.go:519: calling [blitter.com/go/xs/xs.exitWithStatus]" ]
|
||||
"blitter.com/go/xs/xs.doShellMode$1" -> "(crypto/x509.SystemRootsError).Error" [ style="dashed" color="saddlebrown" tooltip="at xs.go:507: calling [(crypto/x509.SystemRootsError).Error]" ]
|
||||
"blitter.com/go/xs/xs.main$2" -> "blitter.com/go/xs/xs.exitWithStatus" [ tooltip="at xs.go:999: calling [blitter.com/go/xs/xs.exitWithStatus]" ]
|
||||
"blitter.com/go/xs/xs.doCopyMode" -> "(*blitter.com/go/xs/xsnet.Conn).GetStatus" [ color="saddlebrown" tooltip="at xs.go:435: calling [(*blitter.com/go/xs/xsnet.Conn).GetStatus]\nat xs.go:438: calling [(*blitter.com/go/xs/xsnet.Conn).GetStatus]\nat xs.go:473: calling [(*blitter.com/go/xs/xsnet.Conn).GetStatus]\nat xs.go:475: calling [(*blitter.com/go/xs/xsnet.Conn).GetStatus]" ]
|
||||
"blitter.com/go/xs/xs.main" -> "(*blitter.com/go/xs/xsnet.Conn).ShutdownChaff" [ arrowhead="normalnoneodiamond" color="saddlebrown" tooltip="at xs.go:1056: calling [(*blitter.com/go/xs/xsnet.Conn).ShutdownChaff]" ]
|
||||
"blitter.com/go/xs/xs.doShellMode$1" -> "(*blitter.com/go/xs.Session).SetStatus" [ color="saddlebrown" tooltip="at xs.go:513: calling [(*blitter.com/go/xs.Session).SetStatus]" ]
|
||||
"blitter.com/go/xs/xs.doShellMode" -> "blitter.com/go/xs/xs.doShellMode$1" [ arrowhead="normalnoneodot" tooltip="at xs.go:522: calling [blitter.com/go/xs/xs.doShellMode$1]" ]
|
||||
"blitter.com/go/xs/xs.main$2" -> "blitter.com/go/xs/xs.restoreTermState" [ tooltip="at xs.go:997: calling [blitter.com/go/xs/xs.restoreTermState]" ]
|
||||
"blitter.com/go/xs/xs.doCopyMode" -> "(*blitter.com/go/xs/xsnet.Conn).SetStatus" [ color="saddlebrown" tooltip="at xs.go:430: calling [(*blitter.com/go/xs/xsnet.Conn).SetStatus]" ]
|
||||
"blitter.com/go/xs/xs.sendSessionParams" -> "(blitter.com/go/xs.Session).Cmd" [ tooltip="at xs.go:651: calling [(blitter.com/go/xs.Session).Cmd]\nat xs.go:671: calling [(blitter.com/go/xs.Session).Cmd]" color="saddlebrown" ]
|
||||
"blitter.com/go/xs/xs.main" -> "(blitter.com/go/xs.Session).Status" [ color="saddlebrown" tooltip="at xs.go:1027: calling [(blitter.com/go/xs.Session).Status]\nat xs.go:1089: calling [(blitter.com/go/xs.Session).Status]\nat xs.go:1091: calling [(blitter.com/go/xs.Session).Status]\nat xs.go:1101: calling [(blitter.com/go/xs.Session).Status]" ]
|
||||
"blitter.com/go/xs/xs.main$3" -> "math/rand.Intn" [ tooltip="at xs.go:1068: calling [math/rand.Intn]" color="saddlebrown" ]
|
||||
"blitter.com/go/xs/xs.doShellMode$1" -> "(crypto/x509.CertificateInvalidError).Error" [ style="dashed" color="saddlebrown" tooltip="at xs.go:507: calling [(crypto/x509.CertificateInvalidError).Error]" ]
|
||||
"blitter.com/go/xs/xs.doShellMode$2" -> "blitter.com/go/xs/xs.restoreTermState" [ tooltip="at xs.go:546: calling [blitter.com/go/xs/xs.restoreTermState]" ]
|
||||
"blitter.com/go/xs/xs.doShellMode$2" -> "blitter.com/go/xs/xs.exitWithStatus" [ tooltip="at xs.go:548: calling [blitter.com/go/xs/xs.exitWithStatus]" ]
|
||||
"blitter.com/go/xs/xs.restoreTermState" -> "blitter.com/go/xs.Restore" [ color="saddlebrown" tooltip="at xs.go:1118: calling [blitter.com/go/xs.Restore]" ]
|
||||
"blitter.com/go/xs/xs.main" -> "blitter.com/go/xs/logger.New" [ color="saddlebrown" tooltip="at xs.go:864: calling [blitter.com/go/xs/logger.New]" ]
|
||||
"blitter.com/go/xs/xs.main" -> "blitter.com/go/xs.MakeRaw" [ color="saddlebrown" tooltip="at xs.go:978: calling [blitter.com/go/xs.MakeRaw]" ]
|
||||
"blitter.com/go/xs/xs.sendSessionParams" -> "(blitter.com/go/xs.Session).AuthCookie" [ color="saddlebrown" tooltip="at xs.go:651: calling [(blitter.com/go/xs.Session).AuthCookie]\nat xs.go:675: calling [(blitter.com/go/xs.Session).AuthCookie]" ]
|
||||
"blitter.com/go/xs/xs.main" -> "blitter.com/go/xs/xs.main$3" [ arrowhead="normalnoneodot" tooltip="at xs.go:1075: calling [blitter.com/go/xs/xs.main$3]" ]
|
||||
"blitter.com/go/xs/xs.doCopyMode" -> "blitter.com/go/xs/xs.buildCmdRemoteToLocal" [ tooltip="at xs.go:444: calling [blitter.com/go/xs/xs.buildCmdRemoteToLocal]" ]
|
||||
"blitter.com/go/xs/xs.doCopyMode" -> "(blitter.com/go/xs/xsnet.Conn).Read" [ color="saddlebrown" tooltip="at xs.go:424: calling [(blitter.com/go/xs/xsnet.Conn).Read]" ]
|
||||
"blitter.com/go/xs/xs.sendSessionParams" -> "(blitter.com/go/xs.Session).Who" [ tooltip="at xs.go:651: calling [(blitter.com/go/xs.Session).Who]\nat xs.go:659: calling [(blitter.com/go/xs.Session).Who]" color="saddlebrown" ]
|
||||
"blitter.com/go/xs/xs.main" -> "(blitter.com/go/xs/xsnet.Conn).Read" [ color="saddlebrown" tooltip="at xs.go:1039: calling [(blitter.com/go/xs/xsnet.Conn).Read]" ]
|
||||
"blitter.com/go/xs/xs.main" -> "blitter.com/go/xs/xsnet.Dial" [ color="saddlebrown" tooltip="at xs.go:960: calling [blitter.com/go/xs/xsnet.Dial]" ]
|
||||
"blitter.com/go/xs/xs.sendSessionParams" -> "(blitter.com/go/xs.Session).Op" [ color="saddlebrown" tooltip="at xs.go:651: calling [(blitter.com/go/xs.Session).Op]\nat xs.go:655: calling [(blitter.com/go/xs.Session).Op]" ]
|
||||
"blitter.com/go/xs/xs.rejectUserMsg" -> "blitter.com/go/xs/spinsult.GetSentence" [ color="saddlebrown" tooltip="at xs.go:577: calling [blitter.com/go/xs/spinsult.GetSentence]" ]
|
||||
"blitter.com/go/xs/xs.main" -> "blitter.com/go/xs/xs.launchTuns" [ tooltip="at xs.go:1081: calling [blitter.com/go/xs/xs.launchTuns]" ]
|
||||
"blitter.com/go/xs/xs.doShellMode$1" -> "(vendor/golang.org/x/net/idna.labelError).Error" [ style="dashed" color="saddlebrown" tooltip="at xs.go:507: calling [(vendor/golang.org/x/net/idna.labelError).Error]" ]
|
||||
"blitter.com/go/xs/xs.Copy" -> "blitter.com/go/xs/xs.copyBuffer" [ tooltip="at xs.go:116: calling [blitter.com/go/xs/xs.copyBuffer]" ]
|
||||
"blitter.com/go/xs/xs.main" -> "github.com/mattn/go-isatty.IsTerminal" [ color="saddlebrown" tooltip="at xs.go:977: calling [github.com/mattn/go-isatty.IsTerminal]" ]
|
||||
"blitter.com/go/xs/xs.sendSessionParams" -> "(blitter.com/go/xs.Session).TermType" [ color="saddlebrown" tooltip="at xs.go:651: calling [(blitter.com/go/xs.Session).TermType]\nat xs.go:667: calling [(blitter.com/go/xs.Session).TermType]" ]
|
||||
"blitter.com/go/xs/xs.doShellMode$1" -> "(crypto/tls.RecordHeaderError).Error" [ style="dashed" color="saddlebrown" tooltip="at xs.go:507: calling [(crypto/tls.RecordHeaderError).Error]" ]
|
||||
"blitter.com/go/xs/xs.doShellMode$1" -> "(crypto/x509.UnknownAuthorityError).Error" [ style="dashed" color="saddlebrown" tooltip="at xs.go:507: calling [(crypto/x509.UnknownAuthorityError).Error]" ]
|
||||
"blitter.com/go/xs/xs.main" -> "blitter.com/go/xs/xs.exitWithStatus" [ tooltip="at xs.go:772: calling [blitter.com/go/xs/xs.exitWithStatus]\nat xs.go:853: calling [blitter.com/go/xs/xs.exitWithStatus]\nat xs.go:963: calling [blitter.com/go/xs/xs.exitWithStatus]\nat xs.go:1027: calling [blitter.com/go/xs/xs.exitWithStatus]\nat xs.go:1101: calling [blitter.com/go/xs/xs.exitWithStatus]" ]
|
||||
"blitter.com/go/xs/xs.doShellMode$2" -> "blitter.com/go/xs/xs.doShellMode$2$1" [ tooltip="at xs.go:541: calling [blitter.com/go/xs/xs.doShellMode$2$1]" ]
|
||||
"blitter.com/go/xs/xs.main" -> "blitter.com/go/xs/xs.doShellMode" [ tooltip="at xs.go:1082: calling [blitter.com/go/xs/xs.doShellMode]" ]
|
||||
"blitter.com/go/xs/xs.doShellMode" -> "blitter.com/go/xs/xs.doShellMode$2" [ arrowhead="normalnoneodot" tooltip="at xs.go:551: calling [blitter.com/go/xs/xs.doShellMode$2]" ]
|
||||
"blitter.com/go/xs/xs.buildCmdLocalToRemote" -> "blitter.com/go/xs.GetTool" [ color="saddlebrown" tooltip="at xs.go:283: calling [blitter.com/go/xs.GetTool]\nat xs.go:311: calling [blitter.com/go/xs.GetTool]\nat xs.go:312: calling [blitter.com/go/xs.GetTool]" ]
|
||||
"blitter.com/go/xs/xs.doShellMode$1" -> "(*blitter.com/go/xs/xsnet.Conn).GetStatus" [ color="saddlebrown" tooltip="at xs.go:513: calling [(*blitter.com/go/xs/xsnet.Conn).GetStatus]" ]
|
||||
"blitter.com/go/xs/xs.doShellMode$1" -> "(crypto/tls.alert).Error" [ style="dashed" color="saddlebrown" tooltip="at xs.go:507: calling [(crypto/tls.alert).Error]" ]
|
||||
"blitter.com/go/xs/xs.doShellMode" -> "blitter.com/go/xs/xs.handleTermResizes" [ tooltip="at xs.go:527: calling [blitter.com/go/xs/xs.handleTermResizes]" ]
|
||||
"blitter.com/go/xs/xs.main" -> "(*blitter.com/go/xs/xsnet.Conn).SetupChaff" [ color="saddlebrown" tooltip="at xs.go:1050: calling [(*blitter.com/go/xs/xsnet.Conn).SetupChaff]" ]
|
||||
"blitter.com/go/xs/xs.doShellMode$1" -> "(context.deadlineExceededError).Error" [ style="dashed" color="saddlebrown" tooltip="at xs.go:507: calling [(context.deadlineExceededError).Error]" ]
|
||||
"blitter.com/go/xs/xs.doShellMode$1" -> "(crypto/aes.KeySizeError).Error" [ style="dashed" color="saddlebrown" tooltip="at xs.go:507: calling [(crypto/aes.KeySizeError).Error]" ]
|
||||
"blitter.com/go/xs/xs.doShellMode$2$1" -> "blitter.com/go/xs/xs.Copy" [ tooltip="at xs.go:539: calling [blitter.com/go/xs/xs.Copy]" ]
|
||||
"blitter.com/go/xs/xs.doCopyMode" -> "(blitter.com/go/xs.Session).Cmd" [ color="saddlebrown" tooltip="at xs.go:356: calling [(blitter.com/go/xs.Session).Cmd]\nat xs.go:441: calling [(blitter.com/go/xs.Session).Cmd]" ]
|
||||
"blitter.com/go/xs/xs.doCopyMode" -> "(*blitter.com/go/xs/xsnet.Conn).WritePacket" [ tooltip="at xs.go:417: calling [(*blitter.com/go/xs/xsnet.Conn).WritePacket]" color="saddlebrown" ]
|
||||
"blitter.com/go/xs/xs.sendSessionParams" -> "(blitter.com/go/xs.Session).ConnHost" [ color="saddlebrown" tooltip="at xs.go:651: calling [(blitter.com/go/xs.Session).ConnHost]\nat xs.go:663: calling [(blitter.com/go/xs.Session).ConnHost]" ]
|
||||
"blitter.com/go/xs/xs.main" -> "blitter.com/go/xs/xs.sendSessionParams" [ tooltip="at xs.go:1022: calling [blitter.com/go/xs/xs.sendSessionParams]" ]
|
||||
"blitter.com/go/xs/xs.doCopyMode" -> "blitter.com/go/xs/xs.buildCmdLocalToRemote" [ tooltip="at xs.go:364: calling [blitter.com/go/xs/xs.buildCmdLocalToRemote]" ]
|
||||
"blitter.com/go/xs/xs.main" -> "blitter.com/go/xs/xs.parseNonSwitchArgs" [ tooltip="at xs.go:796: calling [blitter.com/go/xs/xs.parseNonSwitchArgs]" ]
|
||||
"blitter.com/go/xs/xs.main" -> "blitter.com/go/xs/xs.restoreTermState" [ tooltip="at xs.go:1024: calling [blitter.com/go/xs/xs.restoreTermState]\nat xs.go:1090: calling [blitter.com/go/xs/xs.restoreTermState]\nat xs.go:1096: calling [blitter.com/go/xs/xs.restoreTermState]" ]
|
||||
"blitter.com/go/xs/xs.main" -> "(*blitter.com/go/xs/xsnet.Conn).DisableChaff" [ color="saddlebrown" tooltip="at xs.go:1055: calling [(*blitter.com/go/xs/xsnet.Conn).DisableChaff]" arrowhead="normalnoneodiamond" ]
|
||||
"blitter.com/go/xs/xs.launchTuns" -> "blitter.com/go/xs/xs.reqTunnel" [ tooltip="at xs.go:645: calling [blitter.com/go/xs/xs.reqTunnel]" ]
|
||||
"blitter.com/go/xs/xs.doShellMode$1" -> "(crypto/x509.UnhandledCriticalExtension).Error" [ style="dashed" color="saddlebrown" tooltip="at xs.go:507: calling [(crypto/x509.UnhandledCriticalExtension).Error]" ]
|
||||
"blitter.com/go/xs/xs.doShellMode$1" -> "(compress/flate.CorruptInputError).Error" [ style="dashed" color="saddlebrown" tooltip="at xs.go:507: calling [(compress/flate.CorruptInputError).Error]" ]
|
||||
"blitter.com/go/xs/xs.buildCmdLocalToRemote" -> "blitter.com/go/xs/xs.getTreeSizeSubCmd" [ tooltip="at xs.go:335: calling [blitter.com/go/xs/xs.getTreeSizeSubCmd]" ]
|
||||
"blitter.com/go/xs/xs.buildCmdRemoteToLocal" -> "blitter.com/go/xs.GetTool" [ color="saddlebrown" tooltip="at xs.go:256: calling [blitter.com/go/xs.GetTool]\nat xs.go:263: calling [blitter.com/go/xs.GetTool]" ]
|
||||
"blitter.com/go/xs/xs.sendSessionParams" -> "(blitter.com/go/xs/xsnet.Conn).Write" [ style="dashed" color="saddlebrown" tooltip="at xs.go:675: calling [(blitter.com/go/xs/xsnet.Conn).Write]\nat xs.go:671: calling [(blitter.com/go/xs/xsnet.Conn).Write]\nat xs.go:667: calling [(blitter.com/go/xs/xsnet.Conn).Write]\nat xs.go:663: calling [(blitter.com/go/xs/xsnet.Conn).Write]\nat xs.go:659: calling [(blitter.com/go/xs/xsnet.Conn).Write]\nat xs.go:655: calling [(blitter.com/go/xs/xsnet.Conn).Write]" ]
|
||||
"blitter.com/go/xs/xs.main" -> "(*blitter.com/go/xs.Session).SetStatus" [ color="saddlebrown" tooltip="at xs.go:1025: calling [(*blitter.com/go/xs.Session).SetStatus]\nat xs.go:1043: calling [(*blitter.com/go/xs.Session).SetStatus]\nat xs.go:1047: calling [(*blitter.com/go/xs.Session).SetStatus]\nat xs.go:1086: calling [(*blitter.com/go/xs.Session).SetStatus]" ]
|
||||
"blitter.com/go/xs/xs.doShellMode$1" -> "(*github.com/pkg/errors.fundamental).Error" [ style="dashed" color="saddlebrown" tooltip="at xs.go:507: calling [(*github.com/pkg/errors.fundamental).Error]" ]
|
||||
"blitter.com/go/xs/xs.handleTermResizes" -> "blitter.com/go/xs/xs.handleTermResizes$1" [ arrowhead="normalnoneodot" tooltip="at termsize_unix.go:21: calling [blitter.com/go/xs/xs.handleTermResizes$1]" ]
|
||||
"blitter.com/go/xs/xs.main" -> "blitter.com/go/xs/xs.usageShell" [ style="dashed" tooltip="at xs.go:852: calling [blitter.com/go/xs/xs.usageShell]" ]
|
||||
"blitter.com/go/xs/xs.main" -> "blitter.com/go/xs/xs.restoreTermState" [ arrowhead="normalnoneodiamond" tooltip="at xs.go:984: calling [blitter.com/go/xs/xs.restoreTermState]" ]
|
||||
"blitter.com/go/xs/xs.main" -> "blitter.com/go/xs.ReadPassword" [ color="saddlebrown" tooltip="at xs.go:1005: calling [blitter.com/go/xs.ReadPassword]" ]
|
||||
"blitter.com/go/xs/xs.doShellMode$1" -> "blitter.com/go/xs/xs.doShellMode$1$1" [ arrowhead="normalnoneodiamond" tooltip="at xs.go:491: calling [blitter.com/go/xs/xs.doShellMode$1$1]" ]
|
||||
"blitter.com/go/xs/xs.handleTermResizes$1" -> "(*blitter.com/go/xs/xsnet.Conn).WritePacket" [ color="saddlebrown" tooltip="at termsize_unix.go:33: calling [(*blitter.com/go/xs/xsnet.Conn).WritePacket]" ]
|
||||
"blitter.com/go/xs/xs.main" -> "(*blitter.com/go/xs/xsnet.Conn).EnableChaff" [ color="saddlebrown" tooltip="at xs.go:1054: calling [(*blitter.com/go/xs/xsnet.Conn).EnableChaff]" ]
|
||||
"blitter.com/go/xs/xs.doShellMode$1" -> "(crypto/x509.HostnameError).Error" [ style="dashed" color="saddlebrown" tooltip="at xs.go:507: calling [(crypto/x509.HostnameError).Error]" ]
|
||||
"blitter.com/go/xs/xs.doShellMode$1" -> "(*crypto/tls.permanentError).Error" [ color="saddlebrown" tooltip="at xs.go:507: calling [(*crypto/tls.permanentError).Error]" style="dashed" ]
|
||||
"blitter.com/go/xs/xs.main" -> "blitter.com/go/xs/xs.doCopyMode" [ tooltip="at xs.go:1085: calling [blitter.com/go/xs/xs.doCopyMode]" ]
|
||||
"blitter.com/go/xs/xs.copyBuffer" -> "blitter.com/go/xs/xs.copyBuffer$2" [ style="dashed" tooltip="at xs.go:186: calling [blitter.com/go/xs/xs.copyBuffer$2]" ]
|
||||
"blitter.com/go/xs/xs.copyBuffer" -> "blitter.com/go/xs/xs.copyBuffer$3" [ style="dashed" tooltip="at xs.go:186: calling [blitter.com/go/xs/xs.copyBuffer$3]" ]
|
||||
"blitter.com/go/xs/xs.main" -> "(*blitter.com/go/xs/xsnet.Conn).Close" [ arrowhead="normalnoneodiamond" color="saddlebrown" tooltip="at xs.go:972: calling [(*blitter.com/go/xs/xsnet.Conn).Close]" ]
|
||||
"blitter.com/go/xs/xs.main" -> "blitter.com/go/xs/xs.rejectUserMsg" [ tooltip="at xs.go:1046: calling [blitter.com/go/xs/xs.rejectUserMsg]" ]
|
||||
"blitter.com/go/xs/xs.main" -> "blitter.com/go/xs/xs.usageCp" [ tooltip="at xs.go:852: calling [blitter.com/go/xs/xs.usageCp]" style="dashed" ]
|
||||
"blitter.com/go/xs/xs.doShellMode$1" -> "(vendor/golang.org/x/net/idna.runeError).Error" [ color="saddlebrown" tooltip="at xs.go:507: calling [(vendor/golang.org/x/net/idna.runeError).Error]" style="dashed" ]
|
||||
"blitter.com/go/xs/xs.copyBuffer" -> "(blitter.com/go/xs/xsnet.Conn).Write" [ tooltip="at xs.go:193: calling [(blitter.com/go/xs/xsnet.Conn).Write]" style="dashed" color="saddlebrown" ]
|
||||
"blitter.com/go/xs/xs.copyBuffer" -> "blitter.com/go/xs/xs.copyBuffer$1" [ style="dashed" tooltip="at xs.go:186: calling [blitter.com/go/xs/xs.copyBuffer$1]" ]
|
||||
"blitter.com/go/xs/xs.main" -> "blitter.com/go/xs/xs.main$1" [ tooltip="at xs.go:790: calling [blitter.com/go/xs/xs.main$1]" arrowhead="normalnoneodot" ]
|
||||
"blitter.com/go/xs/xs.main" -> "blitter.com/go/xs/xs.localUserName" [ tooltip="at xs.go:803: calling [blitter.com/go/xs/xs.localUserName]" ]
|
||||
"blitter.com/go/xs/xs.reqTunnel" -> "(*blitter.com/go/xs/xsnet.Conn).WritePacket" [ color="saddlebrown" tooltip="at xs.go:594: calling [(*blitter.com/go/xs/xsnet.Conn).WritePacket]" ]
|
||||
"blitter.com/go/xs/xs.main" -> "blitter.com/go/xs/xsnet.Init" [ color="saddlebrown" tooltip="at xs.go:865: calling [blitter.com/go/xs/xsnet.Init]" ]
|
||||
"blitter.com/go/xs/xs.main" -> "blitter.com/go/xs.NewSession" [ tooltip="at xs.go:1021: calling [blitter.com/go/xs.NewSession]" color="saddlebrown" ]
|
||||
"blitter.com/go/xs/xs.doShellMode$1" -> "(blitter.com/go/xs.Session).Status" [ tooltip="at xs.go:514: calling [(blitter.com/go/xs.Session).Status]\nat xs.go:519: calling [(blitter.com/go/xs.Session).Status]" color="saddlebrown" ]
|
||||
"blitter.com/go/xs/xs.handleTermResizes$1" -> "blitter.com/go/xs/xs.GetSize" [ tooltip="at termsize_unix.go:27: calling [blitter.com/go/xs/xs.GetSize]" ]
|
||||
"blitter.com/go/xs/xs.reqTunnel" -> "blitter.com/go/xs/logger.LogDebug" [ color="saddlebrown" tooltip="at xs.go:593: calling [blitter.com/go/xs/logger.LogDebug]" ]
|
||||
"blitter.com/go/xs/xs.main$3" -> "(*blitter.com/go/xs/xsnet.Conn).WritePacket" [ tooltip="at xs.go:1072: calling [(*blitter.com/go/xs/xsnet.Conn).WritePacket]" color="saddlebrown" ]
|
||||
"blitter.com/go/xs/xs.doShellMode$1" -> "blitter.com/go/xs/xs.restoreTermState" [ tooltip="at xs.go:503: calling [blitter.com/go/xs/xs.restoreTermState]\nat xs.go:518: calling [blitter.com/go/xs/xs.restoreTermState]" ]
|
||||
"blitter.com/go/xs/xs.doShellMode$1" -> "(compress/flate.InternalError).Error" [ tooltip="at xs.go:507: calling [(compress/flate.InternalError).Error]" style="dashed" color="saddlebrown" ]
|
||||
}
|
After Width: | Height: | Size: 67 KiB |
|
@ -1,12 +1,14 @@
|
|||
#!/sbin/openrc-run
|
||||
|
||||
SVCNAME=hkexshd
|
||||
HKEXSHD_PIDFILE=/var/run/hkexshd.pid
|
||||
HKEXSHD_USER=root
|
||||
HKEXSHD_HOME=/var/run
|
||||
SVCNAME=xsd
|
||||
XSD_PIDFILE=/var/run/xsd.pid
|
||||
XSD_USER=root
|
||||
XSD_HOME=/var/run
|
||||
INST_PREFIX=/usr/local
|
||||
COMMAND=$INST_PREFIX/sbin/hkexshd
|
||||
ARGS=""
|
||||
COMMAND=$INST_PREFIX/sbin/xsd
|
||||
#ARGS="-L -aK KEX_all -aC C_all -aH H_all"
|
||||
echo "SET ARGS in this script to define allow KEX, cipher and hmac algs"
|
||||
exit 1
|
||||
|
||||
depend() {
|
||||
need net
|
||||
|
@ -25,15 +27,15 @@ start() {
|
|||
|
||||
ebegin "Starting ${SVCNAME}"
|
||||
start-stop-daemon \
|
||||
-d ${HKEXSHD_HOME} \
|
||||
--make-pidfile --pidfile ${HKEXSHD_PIDFILE} \
|
||||
-d ${XSD_HOME} \
|
||||
--make-pidfile --pidfile ${XSD_PIDFILE} \
|
||||
--start --quiet --background \
|
||||
--exec "${COMMAND}" "${ARGS}"
|
||||
--exec "${COMMAND}" -- ${ARGS}
|
||||
eend $?
|
||||
}
|
||||
|
||||
stop() {
|
||||
ebegin "Stopping ${SVCNAME}"
|
||||
start-stop-daemon --stop --quiet --pidfile $HKEXSHD_PIDFILE
|
||||
start-stop-daemon --stop --quiet --pidfile $XSD_PIDFILE
|
||||
eend $?
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
#! /bin/sh
|
||||
|
||||
### BEGIN INIT INFO
|
||||
# Provides: xsd
|
||||
# Required-Start: $remote_fs $syslog
|
||||
# Required-Stop: $remote_fs $syslog
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop:
|
||||
# Short-Description: eXperimental Shell Daemon
|
||||
### END INIT INFO
|
||||
|
||||
set -e
|
||||
|
||||
echo "SET XSD_OPTS in this script to define allow KEX, cipher and hmac algs"
|
||||
#XSD_OPTS="-L -aK KEX_all -aC C_all -aH H_all"
|
||||
exit 1
|
||||
|
||||
# /etc/init.d/xsd: start and stop the eXperimental "secure" Shell Daemon
|
||||
|
||||
test -x /usr/local/sbin/xsd || exit 0
|
||||
( /usr/local/sbin/xsd -h 2>&1 | grep -q chaff ) 2>/dev/null || exit 0
|
||||
|
||||
umask 022
|
||||
|
||||
#if test -f /etc/default/ssh; then
|
||||
# . /etc/default/ssh
|
||||
#fi
|
||||
|
||||
. /lib/lsb/init-functions
|
||||
|
||||
if [ -n "$2" ]; then
|
||||
XSD_OPTS="$XSD_OPTS $2"
|
||||
fi
|
||||
|
||||
# Are we running from init?
|
||||
run_by_init() {
|
||||
([ "$previous" ] && [ "$runlevel" ]) || [ "$runlevel" = S ]
|
||||
}
|
||||
|
||||
check_for_no_start() {
|
||||
# forget it if we're trying to start, and /etc/xsd_not_to_be_run exists
|
||||
if [ -e /etc/xsd_not_to_be_run ]; then
|
||||
if [ "$1" = log_end_msg ]; then
|
||||
log_end_msg 0 || true
|
||||
fi
|
||||
if ! run_by_init; then
|
||||
log_action_msg "eXperimental Shell Daemon not in use (/etc/xsd_not_to_be_run)" || true
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
}
|
||||
|
||||
check_dev_null() {
|
||||
if [ ! -c /dev/null ]; then
|
||||
if [ "$1" = log_end_msg ]; then
|
||||
log_end_msg 1 || true
|
||||
fi
|
||||
if ! run_by_init; then
|
||||
log_action_msg "/dev/null is not a character device!" || true
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
#check_privsep_dir() {
|
||||
# # Create the PrivSep empty dir if necessary
|
||||
# if [ ! -d /run/sshd ]; then
|
||||
# mkdir /run/sshd
|
||||
# chmod 0755 /run/sshd
|
||||
# fi
|
||||
#}
|
||||
|
||||
#check_config() {
|
||||
# if [ ! -e /etc/xsd_not_to_be_run ]; then
|
||||
# /usr/local/sbin/xsd $XSD_OPTS -t || exit 1
|
||||
# fi
|
||||
#}
|
||||
|
||||
export PATH="${PATH:+$PATH:}/usr/local/sbin:/usr/sbin:/sbin"
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
#check_privsep_dir
|
||||
check_for_no_start
|
||||
check_dev_null
|
||||
log_daemon_msg "Starting eXperimental Shell Daemon" "xsd" || true
|
||||
if start-stop-daemon --start -b --quiet --oknodo --chuid 0:0 --exec /usr/local/sbin/xsd -- $XSD_OPTS; then
|
||||
log_end_msg 0 || true
|
||||
else
|
||||
log_end_msg 1 || true
|
||||
fi
|
||||
;;
|
||||
stop)
|
||||
log_daemon_msg "Stopping eXperimental Shell Daemon" "xsd" || true
|
||||
if start-stop-daemon --stop --quiet --oknodo --exec /usr/local/sbin/xsd; then
|
||||
log_end_msg 0 || true
|
||||
else
|
||||
log_end_msg 1 || true
|
||||
fi
|
||||
;;
|
||||
|
||||
reload|force-reload)
|
||||
check_for_no_start
|
||||
#check_config
|
||||
log_daemon_msg "Reloading eXperimental Shell Daemon's configuration" "xsd" || true
|
||||
if start-stop-daemon --stop --signal 1 --quiet --oknodo --exec /usr/local/sbin/xsd; then
|
||||
log_end_msg 0 || true
|
||||
else
|
||||
log_end_msg 1 || true
|
||||
fi
|
||||
;;
|
||||
|
||||
restart)
|
||||
#check_privsep_dir
|
||||
#check_config
|
||||
log_daemon_msg "Restarting eXperimental Shell Daemon" "xsd" || true
|
||||
start-stop-daemon --stop --quiet --oknodo --retry 30 --exec /usr/local/sbin/xsd
|
||||
check_for_no_start log_end_msg
|
||||
check_dev_null log_end_msg
|
||||
if start-stop-daemon --start -b --quiet --oknodo --chuid 0:0 --exec /usr/local/sbin/xsd -- $XSD_OPTS; then
|
||||
log_end_msg 0 || true
|
||||
else
|
||||
log_end_msg 1 || true
|
||||
fi
|
||||
;;
|
||||
|
||||
try-restart)
|
||||
#check_privsep_dir
|
||||
#check_config
|
||||
log_daemon_msg "Restarting eXperimental Shell Daemon" "xsd" || true
|
||||
RET=0
|
||||
start-stop-daemon --stop --quiet --retry 30 --exec /usr/local/sbin/xsd || RET="$?"
|
||||
case $RET in
|
||||
0)
|
||||
# old daemon stopped
|
||||
check_for_no_start log_end_msg
|
||||
check_dev_null log_end_msg
|
||||
if start-stop-daemon --start -b --quiet --oknodo --chuid 0:0 --exec /usr/local/sbin/xsd -- $XSD_OPTS; then
|
||||
log_end_msg 0 || true
|
||||
else
|
||||
log_end_msg 1 || true
|
||||
fi
|
||||
;;
|
||||
1)
|
||||
# daemon not running
|
||||
log_progress_msg "(not running)" || true
|
||||
log_end_msg 0 || true
|
||||
;;
|
||||
*)
|
||||
# failed to stop
|
||||
log_progress_msg "(failed to stop)" || true
|
||||
log_end_msg 1 || true
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
|
||||
status)
|
||||
status_of_proc -p /run/xsd.pid /usr/local/sbin/xsd xsd && exit 0 || exit $?
|
||||
;;
|
||||
|
||||
*)
|
||||
log_action_msg "Usage: /etc/init.d/xsd {start|stop|reload|force-reload|restart|try-restart|status}" || true
|
||||
exit 1
|
||||
esac
|
||||
|
||||
exit 0
|
|
@ -0,0 +1,24 @@
|
|||
.PHONY: clean all vis lint
|
||||
|
||||
ifeq ($(GARBLE),y)
|
||||
GO = garble -literals -tiny -debugdir=garbled
|
||||
else
|
||||
GO = go
|
||||
endif
|
||||
|
||||
EXTPKGS = binary,bytes,crypto,encoding,errors,flag,fmt,internal,io,log,net,os,path,runtime,time,strings,sync,syscall
|
||||
EXE = $(notdir $(shell pwd))
|
||||
|
||||
all:
|
||||
$(GO) build $(BUILDOPTS) .
|
||||
|
||||
clean:
|
||||
$(RM) $(EXE) $(EXE).exe
|
||||
|
||||
vis:
|
||||
go-callvis -file xsd-vis -format png -ignore $(EXTPKGS) -group pkg,type .
|
||||
../fixup-gv.sh xsd.go && cat xsd-vis.gv | dot -Tpng -oxsd-vis-fixedup.png
|
||||
|
||||
lint:
|
||||
-golangci-lint run
|
||||
|
Before Width: | Height: | Size: 581 KiB After Width: | Height: | Size: 581 KiB |
After Width: | Height: | Size: 1.1 MiB |
|
@ -0,0 +1,952 @@
|
|||
// xsd server
|
||||
//
|
||||
// Copyright (c) 2017-2020 Russell Magee
|
||||
// Licensed under the terms of the MIT license (see LICENSE.mit in this
|
||||
// distribution)
|
||||
//
|
||||
// golang implementation by Russ Magee (rmagee_at_gmail.com)
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"os/user"
|
||||
"path"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"blitter.com/go/goutmp"
|
||||
xs "blitter.com/go/xs"
|
||||
"blitter.com/go/xs/logger"
|
||||
"blitter.com/go/xs/xsnet"
|
||||
"github.com/creack/pty"
|
||||
)
|
||||
|
||||
var (
|
||||
version string
|
||||
gitCommit string // set in -ldflags by build
|
||||
|
||||
useSysLogin bool
|
||||
kcpMode string // set to a valid KCP BlockCrypt alg tag to use rather than TCP
|
||||
|
||||
// Log - syslog output (with no -d)
|
||||
Log *logger.Writer
|
||||
|
||||
cpuprofile string
|
||||
memprofile string
|
||||
)
|
||||
|
||||
const (
|
||||
AuthTokenLen = 64
|
||||
LoginTimeoutSecs = 30
|
||||
)
|
||||
|
||||
func ioctl(fd, request, argp uintptr) error {
|
||||
if _, _, e := syscall.Syscall6(syscall.SYS_IOCTL, fd, request, argp, 0, 0, 0); e != 0 {
|
||||
return e
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ptsName(fd uintptr) (string, error) {
|
||||
var n uintptr
|
||||
err := ioctl(fd, syscall.TIOCGPTN, uintptr(unsafe.Pointer(&n)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("/dev/pts/%d", n), nil
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------- */
|
||||
// Perform a client->server copy
|
||||
func runClientToServerCopyAs(who, ttype string, conn *xsnet.Conn, fpath string, chaffing bool) (exitStatus uint32, err error) {
|
||||
u, _ := user.Lookup(who)
|
||||
var uid, gid uint32
|
||||
fmt.Sscanf(u.Uid, "%d", &uid)
|
||||
fmt.Sscanf(u.Gid, "%d", &gid)
|
||||
log.Println("uid:", uid, "gid:", gid)
|
||||
|
||||
// Need to clear server's env and set key vars of the
|
||||
// target user.
|
||||
os.Clearenv()
|
||||
os.Setenv("HOME", u.HomeDir)
|
||||
os.Setenv("TERM", ttype)
|
||||
os.Setenv("XS_SESSION", "1")
|
||||
|
||||
var c *exec.Cmd
|
||||
cmdName := xs.GetTool("tar")
|
||||
|
||||
var destDir string
|
||||
if path.IsAbs(fpath) {
|
||||
destDir = fpath
|
||||
} else {
|
||||
destDir = path.Join(u.HomeDir, fpath)
|
||||
}
|
||||
|
||||
cmdArgs := []string{"-xz", "-C", destDir}
|
||||
|
||||
// NOTE the lack of quotes around --xform option's sed expression.
|
||||
// When args are passed in exec() format, no quoting is required
|
||||
// (as this isn't input from a shell) (right? -rlm 20180823)
|
||||
//cmdArgs := []string{"-x", "-C", destDir, `--xform=s#.*/\(.*\)#\1#`}
|
||||
fmt.Println(cmdName, cmdArgs)
|
||||
c = exec.Command(cmdName, cmdArgs...)
|
||||
|
||||
c.Dir = destDir
|
||||
|
||||
//If os.Clearenv() isn't called by server above these will be seen
|
||||
//in the client's session env.
|
||||
//c.Env = []string{"HOME=" + u.HomeDir, "SUDO_GID=", "SUDO_UID=",
|
||||
// "SUDO_USER=", "SUDO_COMMAND=", "MAIL=", "LOGNAME="+who}
|
||||
//c.Dir = u.HomeDir
|
||||
c.SysProcAttr = &syscall.SysProcAttr{}
|
||||
c.SysProcAttr.Credential = &syscall.Credential{Uid: uid, Gid: gid}
|
||||
c.Stdin = conn
|
||||
c.Stdout = os.Stdout
|
||||
c.Stderr = os.Stderr
|
||||
|
||||
if chaffing {
|
||||
conn.StartupChaff()
|
||||
}
|
||||
defer conn.ShutdownChaff()
|
||||
|
||||
// Start the command (no pty)
|
||||
log.Printf("[%v %v]\n", cmdName, cmdArgs)
|
||||
err = c.Start() // returns immediately
|
||||
/////////////
|
||||
// NOTE: There is, apparently, a bug in Go stdlib here. Start()
|
||||
// can actually return immediately, on a command which *does*
|
||||
// start but exits quickly, with c.Wait() error
|
||||
// "c.Wait status: exec: not started".
|
||||
// As in this example, attempting a client->server copy to
|
||||
// a nonexistent remote dir (it's tar exiting right away, exitStatus
|
||||
// 2, stderr
|
||||
// /bin/tar -xz -C /home/someuser/nosuchdir
|
||||
// stderr: fork/exec /bin/tar: no such file or directory
|
||||
//
|
||||
// In this case, c.Wait() won't give us the real
|
||||
// exit status (is it lost?).
|
||||
/////////////
|
||||
if err != nil {
|
||||
log.Println("cmd exited immediately. Cannot get cmd.Wait().ExitStatus()")
|
||||
err = errors.New("cmd exited prematurely")
|
||||
//exitStatus = uint32(254)
|
||||
exitStatus = xsnet.CSEExecFail
|
||||
} else {
|
||||
if err := c.Wait(); err != nil {
|
||||
//fmt.Println("*** c.Wait() done ***")
|
||||
if exiterr, ok := err.(*exec.ExitError); ok {
|
||||
// The program has exited with an exit code != 0
|
||||
|
||||
// This works on both Unix and Windows. Although package
|
||||
// syscall is generally platform dependent, WaitStatus is
|
||||
// defined for both Unix and Windows and in both cases has
|
||||
// an ExitStatus() method with the same signature.
|
||||
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
|
||||
exitStatus = uint32(status.ExitStatus())
|
||||
//err = errors.New("cmd returned nonzero status")
|
||||
log.Printf("Exit Status: %d\n", exitStatus)
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Println("*** client->server cp finished ***")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Perform a server->client copy
|
||||
func runServerToClientCopyAs(who, ttype string, conn *xsnet.Conn, srcPath string, chaffing bool) (exitStatus uint32, err error) {
|
||||
u, err := user.Lookup(who)
|
||||
if err != nil {
|
||||
exitStatus = 1
|
||||
return
|
||||
}
|
||||
var uid, gid uint32
|
||||
_, _ = fmt.Sscanf(u.Uid, "%d", &uid)
|
||||
_, _ = fmt.Sscanf(u.Gid, "%d", &gid)
|
||||
log.Println("uid:", uid, "gid:", gid)
|
||||
|
||||
// Need to clear server's env and set key vars of the
|
||||
// target user.
|
||||
os.Clearenv()
|
||||
_ = os.Setenv("HOME", u.HomeDir)
|
||||
_ = os.Setenv("TERM", ttype)
|
||||
_ = os.Setenv("XS_SESSION", "1")
|
||||
|
||||
var c *exec.Cmd
|
||||
cmdName := xs.GetTool("tar")
|
||||
if !path.IsAbs(srcPath) {
|
||||
srcPath = fmt.Sprintf("%s%c%s", u.HomeDir, os.PathSeparator, srcPath)
|
||||
}
|
||||
|
||||
srcDir, srcBase := path.Split(srcPath)
|
||||
cmdArgs := []string{"-cz", "-C", srcDir, "-f", "-", srcBase}
|
||||
|
||||
c = exec.Command(cmdName, cmdArgs...)
|
||||
|
||||
//If os.Clearenv() isn't called by server above these will be seen
|
||||
//in the client's session env.
|
||||
//c.Env = []string{"HOME=" + u.HomeDir, "SUDO_GID=", "SUDO_UID=", "SUDO_USER=",
|
||||
// "SUDO_COMMAND=", "MAIL=", "LOGNAME="+who}
|
||||
c.Dir = u.HomeDir
|
||||
c.SysProcAttr = &syscall.SysProcAttr{}
|
||||
c.SysProcAttr.Credential = &syscall.Credential{Uid: uid, Gid: gid}
|
||||
c.Stdout = conn
|
||||
// Stderr sinkholing (or buffering to something other than stdout)
|
||||
// is important. Any extraneous output to tarpipe messes up remote
|
||||
// side as it's expecting pure tar data.
|
||||
// (For example, if user specifies abs paths, tar outputs
|
||||
// "Removing leading '/' from path names")
|
||||
stdErrBuffer := new(bytes.Buffer)
|
||||
c.Stderr = stdErrBuffer
|
||||
//c.Stderr = nil
|
||||
|
||||
if chaffing {
|
||||
conn.StartupChaff()
|
||||
}
|
||||
//defer conn.Close()
|
||||
defer conn.ShutdownChaff()
|
||||
|
||||
// Start the command (no pty)
|
||||
log.Printf("[%v %v]\n", cmdName, cmdArgs)
|
||||
err = c.Start() // returns immediately
|
||||
if err != nil {
|
||||
log.Printf("Command finished with error: %v", err)
|
||||
return xsnet.CSEExecFail, err // !?
|
||||
}
|
||||
if err := c.Wait(); err != nil {
|
||||
//fmt.Println("*** c.Wait() done ***")
|
||||
if exiterr, ok := err.(*exec.ExitError); ok {
|
||||
// The program has exited with an exit code != 0
|
||||
|
||||
// This works on both Unix and Windows. Although package
|
||||
// syscall is generally platform dependent, WaitStatus is
|
||||
// defined for both Unix and Windows and in both cases has
|
||||
// an ExitStatus() method with the same signature.
|
||||
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
|
||||
exitStatus = uint32(status.ExitStatus())
|
||||
if len(stdErrBuffer.Bytes()) > 0 {
|
||||
log.Print(stdErrBuffer)
|
||||
}
|
||||
log.Printf("Exit Status: %d", exitStatus)
|
||||
}
|
||||
}
|
||||
}
|
||||
//fmt.Println("*** server->client cp finished ***")
|
||||
return
|
||||
}
|
||||
|
||||
// Run a command (via default shell) as a specific user. Uses
|
||||
// ptys to support commands which expect a terminal. //nolint:gofmt
|
||||
func runShellAs(who, hname, ttype, cmd string, interactive bool, //nolint:funlen
|
||||
conn *xsnet.Conn, chaffing bool) (exitStatus uint32, err error) {
|
||||
var wg sync.WaitGroup
|
||||
u, err := user.Lookup(who)
|
||||
if err != nil {
|
||||
exitStatus = 1
|
||||
return
|
||||
}
|
||||
var uid, gid uint32
|
||||
_, _ = fmt.Sscanf(u.Uid, "%d", &uid)
|
||||
_, _ = fmt.Sscanf(u.Gid, "%d", &gid)
|
||||
log.Println("uid:", uid, "gid:", gid)
|
||||
|
||||
// Need to clear server's env and set key vars of the
|
||||
// target user.
|
||||
os.Clearenv()
|
||||
_ = os.Setenv("HOME", u.HomeDir)
|
||||
_ = os.Setenv("TERM", ttype)
|
||||
_ = os.Setenv("XS_SESSION", "1")
|
||||
|
||||
var c *exec.Cmd
|
||||
|
||||
if interactive {
|
||||
if useSysLogin {
|
||||
// Use the server's login binary (post-auth)
|
||||
//
|
||||
// Things UNIX login does, like print the 'motd',
|
||||
// and use the shell specified by /etc/passwd, will be done
|
||||
// automagically, at the cost of another external tool
|
||||
// dependency.
|
||||
//
|
||||
// One drawback of using 'login' is that the remote side
|
||||
// cannot give us back the shell's exit code, since it
|
||||
// exits back to 'login', which usually returns its own
|
||||
// 0 status back to us.
|
||||
//
|
||||
// Note login will drop privs to the intended user for us.
|
||||
//
|
||||
c = exec.Command(xs.GetTool("login"), "-f", "-p", who) //nolint:gosec
|
||||
} else {
|
||||
// Run shell directly (which allows nonzero exit codes back to
|
||||
// the local system upon shell exit, whereas 'login' does not.)
|
||||
//
|
||||
// Note we must drop privs ourselves for the user shell since
|
||||
// we aren't using 'login' on the remote end which would do it
|
||||
// for us.
|
||||
//
|
||||
c = exec.Command(xs.GetTool("bash"), "-i", "-l") //nolint:gosec
|
||||
c.SysProcAttr = &syscall.SysProcAttr{}
|
||||
c.SysProcAttr.Credential = &syscall.Credential{Uid: uid, Gid: gid}
|
||||
}
|
||||
} else {
|
||||
c = exec.Command(xs.GetTool("bash"), "-c", cmd) //nolint:gosec
|
||||
c.SysProcAttr = &syscall.SysProcAttr{}
|
||||
c.SysProcAttr.Credential = &syscall.Credential{Uid: uid, Gid: gid}
|
||||
}
|
||||
//If os.Clearenv() isn't called by server above these will be seen
|
||||
//in the client's session env.
|
||||
//c.Env = []string{"HOME=" + u.HomeDir, "SUDO_GID=", "SUDO_UID=",
|
||||
// "SUDO_USER=", "SUDO_COMMAND=", "MAIL=", "LOGNAME="+who}
|
||||
c.Dir = u.HomeDir
|
||||
|
||||
// Start the command with a pty.
|
||||
ptmx, err := pty.Start(c) // returns immediately with ptmx file
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return xsnet.CSEPtyExecFail, err
|
||||
}
|
||||
// Make sure to close the pty at the end.
|
||||
// #gv:s/label=\"runShellAs\$1\"/label=\"deferPtmxClose\"/
|
||||
defer func() {
|
||||
//logger.LogDebug(fmt.Sprintf("[Exited process was %d]", c.Process.Pid))
|
||||
_ = ptmx.Close()
|
||||
}()
|
||||
|
||||
// get pty info for system accounting (who, lastlog)
|
||||
pts, pe := ptsName(ptmx.Fd())
|
||||
if pe != nil {
|
||||
return xsnet.CSEPtyGetNameFail, err
|
||||
}
|
||||
utmpx := goutmp.Put_utmp(who, pts, hname)
|
||||
defer func() { goutmp.Unput_utmp(utmpx) }()
|
||||
goutmp.Put_lastlog_entry("xs", who, pts, hname)
|
||||
|
||||
conn.Pproc = c.Process.Pid
|
||||
//fmt.Printf("[process %d started]\n", c.Process.Pid)
|
||||
|
||||
log.Printf("[%s]\n", cmd)
|
||||
if err != nil {
|
||||
log.Printf("Command finished with error: %v", err)
|
||||
} else {
|
||||
// Watch for term resizes
|
||||
// #gv:s/label=\"runShellAs\$2\"/label=\"termResizeWatcher\"/
|
||||
go func() {
|
||||
for sz := range conn.WinCh {
|
||||
log.Printf("[Setting term size to: %v %v]\n", sz.Rows, sz.Cols)
|
||||
pty.Setsize(ptmx, &pty.Winsize{Rows: sz.Rows, Cols: sz.Cols}) //nolint:errcheck
|
||||
}
|
||||
log.Println("*** WinCh goroutine done ***")
|
||||
}()
|
||||
|
||||
// Copy stdin to the pty.. (bgnd goroutine)
|
||||
// #gv:s/label=\"runShellAs\$3\"/label=\"stdinToPtyWorker\"/
|
||||
go func() {
|
||||
_, e := io.Copy(ptmx, conn)
|
||||
if e != nil {
|
||||
log.Println("** stdin->pty ended **:", e.Error())
|
||||
} else {
|
||||
log.Println("*** stdin->pty goroutine done ***")
|
||||
}
|
||||
}()
|
||||
|
||||
// === Set up connection keepalive to client
|
||||
conn.StartupKeepAlive() // goroutine, returns immediately
|
||||
defer conn.ShutdownKeepAlive()
|
||||
|
||||
if chaffing {
|
||||
conn.StartupChaff()
|
||||
// #gv:s/label=\"runShellAs\$4\"/label=\"deferChaffShutdown\"/
|
||||
defer func() {
|
||||
conn.ShutdownChaff()
|
||||
}()
|
||||
}
|
||||
|
||||
// ..and the pty to stdout.
|
||||
// This may take some time exceeding that of the
|
||||
// actual command's lifetime, so the c.Wait() below
|
||||
// must synchronize with the completion of this goroutine
|
||||
// to ensure all stdout data gets to the client before
|
||||
// connection is closed.
|
||||
wg.Add(1)
|
||||
// #gv:s/label=\"runShellAs\$5\"/label=\"ptyToStdoutWorker\"/
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
_, e := io.Copy(conn, ptmx)
|
||||
if e != nil {
|
||||
log.Println("** pty->stdout ended **:", e.Error())
|
||||
} else {
|
||||
// The above io.Copy() will exit when the command attached
|
||||
// to the pty exits
|
||||
log.Println("*** pty->stdout goroutine done ***")
|
||||
}
|
||||
}()
|
||||
|
||||
if err := c.Wait(); err != nil {
|
||||
//fmt.Println("*** c.Wait() done ***")
|
||||
if exiterr, ok := err.(*exec.ExitError); ok {
|
||||
// The program has exited with an exit code != 0
|
||||
|
||||
// This works on both Unix and Windows. Although package
|
||||
// syscall is generally platform dependent, WaitStatus is
|
||||
// defined for both Unix and Windows and in both cases has
|
||||
// an ExitStatus() method with the same signature.
|
||||
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
|
||||
exitStatus = uint32(status.ExitStatus())
|
||||
log.Printf("Exit Status: %d", exitStatus)
|
||||
}
|
||||
}
|
||||
conn.SetStatus(xsnet.CSOType(exitStatus))
|
||||
} else {
|
||||
logger.LogDebug(fmt.Sprintf("*** Main proc has exited (%d) ***", c.ProcessState.ExitCode())) //nolint:errcheck
|
||||
// Background jobs still may be running; close the
|
||||
// pty anyway, so the client can return before
|
||||
// wg.Wait() below completes (Issue #18)
|
||||
if interactive {
|
||||
_ = ptmx.Close()
|
||||
}
|
||||
}
|
||||
wg.Wait() // Wait on pty->stdout completion to client
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GenAuthToken generates a pseudorandom auth token for a specific
|
||||
// user from a specific host to allow non-interactive logins.
|
||||
func GenAuthToken(who string, connhost string) string {
|
||||
//hname, e := os.Hostname()
|
||||
//if e != nil {
|
||||
// hname = "#badhost#"
|
||||
//}
|
||||
hname := connhost
|
||||
|
||||
token := make([]byte, AuthTokenLen)
|
||||
_, _ = rand.Read(token)
|
||||
return fmt.Sprintf("%s:%s:%s", hname, who, hex.EncodeToString(token))
|
||||
}
|
||||
|
||||
var (
|
||||
aKEXAlgs allowedKEXAlgs
|
||||
aCipherAlgs allowedCipherAlgs
|
||||
aHMACAlgs allowedHMACAlgs
|
||||
)
|
||||
|
||||
type allowedKEXAlgs []string
|
||||
type allowedCipherAlgs []string
|
||||
type allowedHMACAlgs []string
|
||||
|
||||
func (a allowedKEXAlgs) allowed(k xsnet.KEXAlg) bool {
|
||||
for i := 0; i < len(a); i++ {
|
||||
if a[i] == "KEX_all" || a[i] == k.String() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (a *allowedKEXAlgs) String() string {
|
||||
return fmt.Sprintf("allowedKEXAlgs: %v", *a)
|
||||
}
|
||||
|
||||
func (a *allowedKEXAlgs) Set(value string) error {
|
||||
*a = append(*a, strings.TrimSpace(value))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a allowedCipherAlgs) allowed(c xsnet.CSCipherAlg) bool {
|
||||
for i := 0; i < len(a); i++ {
|
||||
if a[i] == "C_all" || a[i] == c.String() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (a *allowedCipherAlgs) String() string {
|
||||
return fmt.Sprintf("allowedCipherAlgs: %v", *a)
|
||||
}
|
||||
|
||||
func (a *allowedCipherAlgs) Set(value string) error {
|
||||
*a = append(*a, strings.TrimSpace(value))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a allowedHMACAlgs) allowed(h xsnet.CSHmacAlg) bool {
|
||||
for i := 0; i < len(a); i++ {
|
||||
if a[i] == "H_all" || a[i] == h.String() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (a *allowedHMACAlgs) String() string {
|
||||
return fmt.Sprintf("allowedHMACAlgs: %v", *a)
|
||||
}
|
||||
|
||||
func (a *allowedHMACAlgs) Set(value string) error {
|
||||
*a = append(*a, strings.TrimSpace(value))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Main server that listens and spawns goroutines for each
|
||||
// connecting client to serve interactive or file copy sessions
|
||||
// and any requested tunnels.
|
||||
// Note that this server does not do UNIX forks of itself to give
|
||||
// each client its own separate manager process, so if the main
|
||||
// daemon dies, all clients will be rudely disconnected.
|
||||
// Consider this when planning to restart or upgrade in-place an installation.
|
||||
// TODO: reduce gocyclo
|
||||
func main() { //nolint:funlen,gocyclo
|
||||
var vopt bool
|
||||
var chaffEnabled bool
|
||||
var chaffFreqMin uint
|
||||
var chaffFreqMax uint
|
||||
var chaffBytesMax uint
|
||||
var dbg bool
|
||||
var laddr string
|
||||
var rekeySecs uint
|
||||
var remodSupported bool // true: when rekeying, switch to random cipher/hmac alg
|
||||
|
||||
var useSystemPasswd bool
|
||||
|
||||
flag.BoolVar(&vopt, "v", false, "show version")
|
||||
flag.UintVar(&rekeySecs, "r", 300, "rekey interval in `secs`")
|
||||
flag.BoolVar(&remodSupported, "R", false, "Borg Countermeasures (remodulate cipher/hmac alg on each rekey)")
|
||||
flag.StringVar(&laddr, "l", ":2000", "interface[:port] to listen") //nolint:gomnd,lll
|
||||
flag.StringVar(&kcpMode, "K", "unused", `set to one of ["KCP_NONE","KCP_AES", "KCP_BLOWFISH", "KCP_CAST5", "KCP_SM4", "KCP_SALSA20", "KCP_SIMPLEXOR", "KCP_TEA", "KCP_3DES", "KCP_TWOFISH", "KCP_XTEA"] to use KCP (github.com/xtaci/kcp-go) reliable UDP instead of TCP`) //nolint:lll
|
||||
flag.BoolVar(&useSysLogin, "L", false, "use system login")
|
||||
flag.BoolVar(&chaffEnabled, "e", true, "enable chaff pkts")
|
||||
flag.UintVar(&chaffFreqMin, "f", 100, "chaff pkt freq min (msecs)") //nolint:gomnd
|
||||
flag.UintVar(&chaffFreqMax, "F", 5000, "chaff pkt freq max (msecs)") //nolint:gomnd
|
||||
flag.UintVar(&chaffBytesMax, "B", 64, "chaff pkt size max (bytes)") //nolint:gomnd
|
||||
flag.BoolVar(&useSystemPasswd, "s", true, "use system shadow passwds")
|
||||
flag.BoolVar(&dbg, "d", false, "debug logging")
|
||||
flag.Var(&aKEXAlgs, "aK", "Allowed KEX `alg`s (eg. '-aK KEXAlgA -aK KEXAlgB ...')"+`
|
||||
KEX_all
|
||||
KEX_HERRADURA256
|
||||
KEX_HERRADURA512
|
||||
KEX_HERRADURA1024
|
||||
KEX_HERRADURA2048
|
||||
KEX_KYBER512
|
||||
KEX_KYBER768
|
||||
KEX_KYBER1024
|
||||
KEX_NEWHOPE
|
||||
KEX_NEWHOPE_SIMPLE
|
||||
KEX_FRODOKEM_1344AES
|
||||
KEX_FRODOKEM_1344SHAKE
|
||||
KEX_FRODOKEM_976AES
|
||||
KEX_FRODOKEM_976SHAKE`)
|
||||
flag.Var(&aCipherAlgs, "aC", "Allowed `cipher`s (eg. '-aC CAlgA -aC CAlgB ...')"+`
|
||||
C_all
|
||||
C_AES_256
|
||||
C_TWOFISH_128
|
||||
C_BLOWFISH_64
|
||||
C_CRYPTMT1
|
||||
C_HOPSCOTCH
|
||||
C_CHACHA20_12`)
|
||||
flag.Var(&aHMACAlgs, "aH", "Allowed `HMAC`s (eg. '-aH HMACAlgA -aH HMACAlgB ...')"+`
|
||||
H_all
|
||||
H_SHA256
|
||||
H_SHA512
|
||||
H_WHIRLPOOL`)
|
||||
|
||||
flag.StringVar(&cpuprofile, "cpuprofile", "", "write cpu profile to <`file`>")
|
||||
flag.StringVar(&memprofile, "memprofile", "", "write memory profile to <`file`>")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if vopt {
|
||||
fmt.Printf("version %s (%s)\n", version, gitCommit)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
{
|
||||
me, e := user.Current()
|
||||
if e != nil || me.Uid != "0" {
|
||||
log.Fatal("Must run as root.")
|
||||
}
|
||||
}
|
||||
|
||||
// === Profiling instrumentation
|
||||
|
||||
if cpuprofile != "" {
|
||||
f, err := os.Create(cpuprofile)
|
||||
if err != nil {
|
||||
log.Fatal("could not create CPU profile: ", err)
|
||||
}
|
||||
defer f.Close()
|
||||
fmt.Println("StartCPUProfile()")
|
||||
if err := pprof.StartCPUProfile(f); err != nil {
|
||||
log.Fatal("could not start CPU profile: ", err) //nolint:gocritic
|
||||
} else {
|
||||
defer pprof.StopCPUProfile()
|
||||
}
|
||||
|
||||
go func() { http.ListenAndServe("localhost:6060", nil) }() //nolint:errcheck,gosec
|
||||
}
|
||||
|
||||
// Enforce some sane min/max vals on chaff flags
|
||||
if chaffFreqMin < 2 { //nolint:gomnd
|
||||
chaffFreqMin = 2
|
||||
}
|
||||
if chaffFreqMax == 0 {
|
||||
chaffFreqMax = chaffFreqMin + 1
|
||||
}
|
||||
if chaffBytesMax == 0 || chaffBytesMax > 4096 {
|
||||
chaffBytesMax = 64
|
||||
}
|
||||
|
||||
Log, _ = logger.New(logger.LOG_DAEMON|logger.LOG_DEBUG|logger.LOG_NOTICE|logger.LOG_ERR, "xsd")
|
||||
xsnet.Init(dbg, "xsd", logger.LOG_DAEMON|logger.LOG_DEBUG|logger.LOG_NOTICE|logger.LOG_ERR)
|
||||
if dbg {
|
||||
log.SetOutput(Log)
|
||||
} else {
|
||||
log.SetOutput(io.Discard)
|
||||
}
|
||||
|
||||
// Set up allowed algs, if specified (default allow all)
|
||||
if len(aKEXAlgs) == 0 {
|
||||
aKEXAlgs = []string{"none"}
|
||||
}
|
||||
logger.LogNotice(fmt.Sprintf("Allowed KEXAlgs: %v\n", aKEXAlgs)) //nolint:errcheck
|
||||
|
||||
if len(aCipherAlgs) == 0 {
|
||||
aCipherAlgs = []string{"none"}
|
||||
}
|
||||
logger.LogNotice(fmt.Sprintf("Allowed CipherAlgs: %v\n", aCipherAlgs)) //nolint:errcheck
|
||||
|
||||
if len(aHMACAlgs) == 0 {
|
||||
aHMACAlgs = []string{"none"}
|
||||
}
|
||||
logger.LogNotice(fmt.Sprintf("Allowed HMACAlgs: %v\n", aHMACAlgs)) //nolint:errcheck
|
||||
|
||||
// Set up handler for daemon signalling
|
||||
exitCh := make(chan os.Signal, 1)
|
||||
signal.Notify(exitCh, os.Signal(syscall.SIGTERM), os.Signal(syscall.SIGINT), os.Signal(syscall.SIGHUP), os.Signal(syscall.SIGUSR1), os.Signal(syscall.SIGUSR2)) //nolint:lll
|
||||
go func() {
|
||||
for {
|
||||
sig := <-exitCh
|
||||
switch sig {
|
||||
case syscall.SIGTERM: //"terminated":
|
||||
logger.LogNotice(fmt.Sprintf("[Got signal: %s]", sig.String())) //nolint:errcheck
|
||||
signal.Reset()
|
||||
syscall.Kill(0, syscall.SIGTERM) //nolint:errcheck
|
||||
case syscall.SIGINT: //"interrupt":
|
||||
logger.LogNotice(fmt.Sprintf("[Got signal: %s]", sig.String())) //nolint:errcheck
|
||||
signal.Reset()
|
||||
syscall.Kill(0, syscall.SIGINT) //nolint:errcheck
|
||||
case syscall.SIGHUP: //"hangup":
|
||||
logger.LogNotice(fmt.Sprintf("[Got signal: %s - nop]", sig.String())) //nolint:errcheck
|
||||
if cpuprofile != "" || memprofile != "" {
|
||||
dumpProf()
|
||||
}
|
||||
default:
|
||||
logger.LogNotice(fmt.Sprintf("[Got signal: %s - ignored]", sig.String())) //nolint:errcheck
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
proto := "tcp"
|
||||
if kcpMode != "unused" {
|
||||
proto = "kcp"
|
||||
}
|
||||
l, err := xsnet.Listen(proto, laddr, kcpMode)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
log.Println("Serving on", laddr)
|
||||
for {
|
||||
// Wait for a connection.
|
||||
// Then check if client-proposed algs are allowed
|
||||
conn, err := l.Accept()
|
||||
//logger.LogDebug(fmt.Sprintf("l.Accept()\n"))
|
||||
if err != nil {
|
||||
log.Printf("Accept() got error(%v), hanging up.\n", err)
|
||||
} else {
|
||||
if !aKEXAlgs.allowed(conn.KEX()) {
|
||||
log.Printf("Accept() rejected for banned KEX alg %d, hanging up.\n", conn.KEX())
|
||||
conn.SetStatus(xsnet.CSEKEXAlgDenied)
|
||||
conn.Close()
|
||||
} else if !aCipherAlgs.allowed(conn.CAlg()) {
|
||||
log.Printf("Accept() rejected for banned Cipher alg %d, hanging up.\n", conn.CAlg())
|
||||
conn.SetStatus(xsnet.CSECipherAlgDenied)
|
||||
conn.Close()
|
||||
} else if !aHMACAlgs.allowed(conn.HAlg()) {
|
||||
log.Printf("Accept() rejected for banned HMAC alg %d, hanging up.\n", conn.HAlg())
|
||||
conn.SetStatus(xsnet.CSEHMACAlgDenied)
|
||||
conn.Close()
|
||||
} else {
|
||||
log.Println("Accepted client")
|
||||
|
||||
// Only enable cipher alg changes on re-key if we were told
|
||||
// to support it (launching xsd with -R), *and* the client
|
||||
// proposes to use it.
|
||||
if !remodSupported {
|
||||
if (conn.Opts() & xsnet.CORemodulateShields) != 0 {
|
||||
logger.LogDebug("[client proposed cipher/hmac remod, but we don't support it.]")
|
||||
conn.Close()
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
if conn.Opts()&xsnet.CORemodulateShields != 0 {
|
||||
logger.LogDebug("[cipher/hmac remodulation active]")
|
||||
} else {
|
||||
logger.LogDebug("[cipher/hmac remodulation inactive]")
|
||||
}
|
||||
}
|
||||
conn.RekeyHelper(rekeySecs)
|
||||
|
||||
// Set up chaffing to client
|
||||
// Will only start when runShellAs() is called
|
||||
// after stdin/stdout are hooked up
|
||||
conn.SetupChaff(chaffFreqMin, chaffFreqMax, chaffBytesMax) // configure server->client chaffing
|
||||
|
||||
// Handle the connection in a new goroutine.
|
||||
// The loop then returns to accepting, so that
|
||||
// multiple connections may be served concurrently.
|
||||
go func(hc *xsnet.Conn) (e error) {
|
||||
defer hc.ShutdownRekey()
|
||||
defer hc.Close()
|
||||
|
||||
// Start login timeout here and disconnect if user/pass phase stalls
|
||||
loginTimeout := time.AfterFunc(LoginTimeoutSecs*time.Second, func() {
|
||||
logger.LogNotice(fmt.Sprintln("Login timed out")) //nolint:errcheck
|
||||
hc.Write([]byte{0}) //nolint:errcheck
|
||||
hc.Close()
|
||||
})
|
||||
|
||||
//We use io.ReadFull() here to guarantee we consume
|
||||
//just the data we want for the xs.Session, and no more.
|
||||
//Otherwise data will be sitting in the channel that isn't
|
||||
//passed down to the command handlers.
|
||||
var rec xs.Session
|
||||
var len1, len2, len3, len4, len5, len6 uint32
|
||||
|
||||
n, err := fmt.Fscanf(hc, "%d %d %d %d %d %d\n", &len1, &len2, &len3, &len4, &len5, &len6)
|
||||
log.Printf("xs.Session read:%d %d %d %d %d %d\n", len1, len2, len3, len4, len5, len6)
|
||||
|
||||
if err != nil || n < 6 {
|
||||
log.Println("[Bad xs.Session fmt]")
|
||||
return err
|
||||
}
|
||||
|
||||
tmp := make([]byte, len1)
|
||||
_, err = io.ReadFull(hc, tmp)
|
||||
if err != nil {
|
||||
log.Println("[Bad xs.Session.Op]")
|
||||
return err
|
||||
}
|
||||
rec.SetOp(tmp)
|
||||
|
||||
tmp = make([]byte, len2)
|
||||
_, err = io.ReadFull(hc, tmp)
|
||||
if err != nil {
|
||||
log.Println("[Bad xs.Session.Who]")
|
||||
return err
|
||||
}
|
||||
rec.SetWho(tmp)
|
||||
|
||||
tmp = make([]byte, len3)
|
||||
_, err = io.ReadFull(hc, tmp)
|
||||
if err != nil {
|
||||
log.Println("[Bad xs.Session.ConnHost]")
|
||||
return err
|
||||
}
|
||||
rec.SetConnHost(tmp)
|
||||
|
||||
tmp = make([]byte, len4)
|
||||
_, err = io.ReadFull(hc, tmp)
|
||||
if err != nil {
|
||||
log.Println("[Bad xs.Session.TermType]")
|
||||
return err
|
||||
}
|
||||
rec.SetTermType(tmp)
|
||||
|
||||
tmp = make([]byte, len5)
|
||||
_, err = io.ReadFull(hc, tmp)
|
||||
if err != nil {
|
||||
log.Println("[Bad xs.Session.Cmd]")
|
||||
return err
|
||||
}
|
||||
rec.SetCmd(tmp)
|
||||
|
||||
tmp = make([]byte, len6)
|
||||
_, err = io.ReadFull(hc, tmp)
|
||||
if err != nil {
|
||||
log.Println("[Bad xs.Session.AuthCookie]")
|
||||
return err
|
||||
}
|
||||
rec.SetAuthCookie(tmp)
|
||||
|
||||
log.Printf("[xs.Session: op:%c who:%s connhost:%s cmd:%s auth:****]\n",
|
||||
rec.Op()[0], string(rec.Who()), string(rec.ConnHost()), string(rec.Cmd()))
|
||||
|
||||
var valid bool
|
||||
var allowedCmds string // Currently unused
|
||||
if xs.AuthUserByToken(xs.NewAuthCtx(), string(rec.Who()), string(rec.ConnHost()), string(rec.AuthCookie(true))) {
|
||||
valid = true
|
||||
} else {
|
||||
if useSystemPasswd {
|
||||
//var passErr error
|
||||
valid, _ /*passErr*/ = xs.VerifyPass(xs.NewAuthCtx(), string(rec.Who()), string(rec.AuthCookie(true)))
|
||||
} else {
|
||||
valid, allowedCmds = xs.AuthUserByPasswd(xs.NewAuthCtx(), string(rec.Who()), string(rec.AuthCookie(true)), "/etc/xs.passwd")
|
||||
}
|
||||
}
|
||||
|
||||
_ = loginTimeout.Stop()
|
||||
// Security scrub
|
||||
rec.ClearAuthCookie()
|
||||
|
||||
// Tell client if auth was valid
|
||||
if valid {
|
||||
hc.Write([]byte{1}) //nolint:errcheck
|
||||
} else {
|
||||
logger.LogNotice(fmt.Sprintln("Invalid user", string(rec.Who()))) //nolint:errcheck
|
||||
hc.Write([]byte{0}) //nolint:errcheck
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("[allowedCmds:%s]\n", allowedCmds)
|
||||
|
||||
if rec.Op()[0] == 'A' {
|
||||
// Generate automated login token
|
||||
addr := hc.RemoteAddr()
|
||||
hname := goutmp.GetHost(addr.String())
|
||||
logger.LogNotice(fmt.Sprintf("[Generating autologin token for [%s@%s]]\n", rec.Who(), hname)) //nolint:errcheck
|
||||
token := GenAuthToken(string(rec.Who()), string(rec.ConnHost()))
|
||||
tokenCmd := fmt.Sprintf("echo %q | tee -a ~/%s", token, xsnet.XS_ID_AUTHTOKFILE)
|
||||
cmdStatus, runErr := runShellAs(string(rec.Who()), hname, string(rec.TermType()), tokenCmd, false, hc, chaffEnabled)
|
||||
// Returned hopefully via an EOF or exit/logout;
|
||||
// Clear current op so user can enter next, or EOF
|
||||
rec.SetOp([]byte{0})
|
||||
if runErr != nil {
|
||||
logger.LogErr(fmt.Sprintf("[Error generating autologin token for %s@%s]\n", rec.Who(), hname)) //nolint:errcheck
|
||||
} else {
|
||||
log.Printf("[Autologin token generation completed for %s@%s, status %d]\n", rec.Who(), hname, cmdStatus)
|
||||
hc.SetStatus(xsnet.CSOType(cmdStatus))
|
||||
}
|
||||
} else if rec.Op()[0] == 'c' {
|
||||
// Non-interactive command
|
||||
addr := hc.RemoteAddr()
|
||||
hname := goutmp.GetHost(addr.String())
|
||||
logger.LogNotice(fmt.Sprintf("[Running command for [%s@%s]]\n", rec.Who(), hname)) //nolint:errcheck
|
||||
cmdStatus, runErr := runShellAs(string(rec.Who()), hname, string(rec.TermType()), string(rec.Cmd()), false, hc, chaffEnabled)
|
||||
// Returned hopefully via an EOF or exit/logout;
|
||||
// Clear current op so user can enter next, or EOF
|
||||
rec.SetOp([]byte{0})
|
||||
if runErr != nil {
|
||||
logger.LogErr(fmt.Sprintf("[Error spawning cmd for %s@%s]\n", rec.Who(), hname)) //nolint:errcheck
|
||||
} else {
|
||||
logger.LogNotice(fmt.Sprintf("[Command completed for %s@%s, status %d]\n", rec.Who(), hname, cmdStatus)) //nolint:errcheck
|
||||
hc.SetStatus(xsnet.CSOType(cmdStatus))
|
||||
}
|
||||
} else if rec.Op()[0] == 's' {
|
||||
// Interactive session
|
||||
addr := hc.RemoteAddr()
|
||||
hname := goutmp.GetHost(addr.String())
|
||||
logger.LogNotice(fmt.Sprintf("[Running shell for [%s@%s]]\n", rec.Who(), hname)) //nolint:errcheck
|
||||
|
||||
cmdStatus, runErr := runShellAs(string(rec.Who()), hname, string(rec.TermType()), string(rec.Cmd()), true, hc, chaffEnabled)
|
||||
// Returned hopefully via an EOF or exit/logout;
|
||||
// Clear current op so user can enter next, or EOF
|
||||
rec.SetOp([]byte{0})
|
||||
if runErr != nil {
|
||||
Log.Err(fmt.Sprintf("[Error spawning shell for %s@%s]\n", rec.Who(), hname)) //nolint:errcheck
|
||||
} else {
|
||||
logger.LogNotice(fmt.Sprintf("[Shell completed for %s@%s, status %d]\n", rec.Who(), hname, cmdStatus)) //nolint:errcheck
|
||||
hc.SetStatus(xsnet.CSOType(cmdStatus))
|
||||
}
|
||||
} else if rec.Op()[0] == 'D' {
|
||||
// File copy (destination) operation - client copy to server
|
||||
log.Printf("[Client->Server copy]\n")
|
||||
addr := hc.RemoteAddr()
|
||||
hname := goutmp.GetHost(addr.String())
|
||||
logger.LogNotice(fmt.Sprintf("[c->s copy for %s@%s]\n", rec.Who(), hname)) //nolint:errcheck
|
||||
cmdStatus, runErr := runClientToServerCopyAs(string(rec.Who()), string(rec.TermType()), hc, string(rec.Cmd()), chaffEnabled)
|
||||
// Returned hopefully via an EOF or exit/logout;
|
||||
// Clear current op so user can enter next, or EOF
|
||||
rec.SetOp([]byte{0})
|
||||
if runErr != nil {
|
||||
logger.LogErr(fmt.Sprintf("[c->s copy error for %s@%s]\n", rec.Who(), hname)) //nolint:errcheck
|
||||
} else {
|
||||
logger.LogNotice(fmt.Sprintf("[c->s copy completed for %s@%s, status %d]\n", rec.Who(), hname, cmdStatus)) //nolint:errcheck
|
||||
}
|
||||
// TODO: Test this with huge files.. see Bug #22 - do we need to
|
||||
// sync w/sender (client) that we've gotten all data?
|
||||
hc.SetStatus(xsnet.CSOType(cmdStatus))
|
||||
|
||||
// Send CSOExitStatus *before* client closes channel
|
||||
s := make([]byte, 4) //nolint:gomnd
|
||||
binary.BigEndian.PutUint32(s, cmdStatus)
|
||||
log.Printf("** cp writing closeStat %d at Close()\n", cmdStatus)
|
||||
hc.WritePacket(s, xsnet.CSOExitStatus) //nolint:errcheck
|
||||
} else if rec.Op()[0] == 'S' {
|
||||
// File copy (src) operation - server copy to client
|
||||
log.Printf("[Server->Client copy]\n")
|
||||
addr := hc.RemoteAddr()
|
||||
hname := goutmp.GetHost(addr.String())
|
||||
logger.LogNotice(fmt.Sprintf("[s->c copy for %s@%s]\n", rec.Who(), hname)) //nolint:errcheck
|
||||
cmdStatus, runErr := runServerToClientCopyAs(string(rec.Who()), string(rec.TermType()), hc, string(rec.Cmd()), chaffEnabled)
|
||||
if runErr != nil {
|
||||
logger.LogErr(fmt.Sprintf("[s->c copy error for %s@%s]\n", rec.Who(), hname)) //nolint:errcheck
|
||||
} else {
|
||||
// Returned hopefully via an EOF or exit/logout;
|
||||
logger.LogNotice(fmt.Sprintf("[s->c copy completed for %s@%s, status %d]\n", rec.Who(), hname, cmdStatus)) //nolint:errcheck
|
||||
}
|
||||
|
||||
// Clear current op so user can enter next, or EOF
|
||||
rec.SetOp([]byte{0})
|
||||
hc.SetStatus(xsnet.CSOType(cmdStatus))
|
||||
//fmt.Println("Waiting for EOF from other end.")
|
||||
//_, _ = hc.Read(nil /*ackByte*/)
|
||||
//fmt.Println("Got remote end ack.")
|
||||
} else {
|
||||
logger.LogErr(fmt.Sprintln("[Bad xs.Session]")) //nolint:errcheck
|
||||
}
|
||||
return
|
||||
}(&conn) //nolint:errcheck
|
||||
} // algs valid and not blacklisted
|
||||
} // Accept() success
|
||||
} //endfor
|
||||
//logger.LogNotice(fmt.Sprintln("[Exiting]")) //nolint:errcheck
|
||||
}
|
||||
|
||||
func dumpProf() {
|
||||
if cpuprofile != "" {
|
||||
pprof.StopCPUProfile()
|
||||
}
|
||||
|
||||
if memprofile != "" {
|
||||
f, err := os.Create(memprofile)
|
||||
if err != nil {
|
||||
log.Fatal("could not create memory profile: ", err)
|
||||
}
|
||||
defer f.Close()
|
||||
runtime.GC() // get up-to-date statistics
|
||||
if err := pprof.WriteHeapProfile(f); err != nil {
|
||||
log.Fatal("could not write memory profile: ", err) //nolint:gocritic
|
||||
}
|
||||
}
|
||||
|
||||
//os.Exit(status)
|
||||
}
|
|
@ -1,12 +1,18 @@
|
|||
.PHONY: info clean lib
|
||||
|
||||
ifeq ($(GARBLE),y)
|
||||
GO = garble -tiny -literals -debugdir=garbled
|
||||
else
|
||||
GO = go
|
||||
endif
|
||||
|
||||
all: lib
|
||||
|
||||
clean:
|
||||
go clean .
|
||||
|
||||
lib: info
|
||||
go install .
|
||||
$(GO) install .
|
||||
|
||||
ifneq ($(MSYSTEM),)
|
||||
info:
|
|
@ -1,6 +1,6 @@
|
|||
package hkexnet
|
||||
package xsnet
|
||||
|
||||
// Copyright (c) 2017-2018 Russell Magee
|
||||
// Copyright (c) 2017-2020 Russell Magee
|
||||
// Licensed under the terms of the MIT license (see LICENSE.mit in this
|
||||
// distribution)
|
||||
//
|
||||
|
@ -20,9 +20,13 @@ import (
|
|||
"hash"
|
||||
"log"
|
||||
|
||||
"blitter.com/go/cryptmt"
|
||||
"blitter.com/go/hopscotch"
|
||||
"blitter.com/go/xs/logger"
|
||||
"github.com/aead/chacha20/chacha"
|
||||
"golang.org/x/crypto/blowfish"
|
||||
"golang.org/x/crypto/twofish"
|
||||
"blitter.com/go/cryptmt"
|
||||
whirlpool "github.com/jzelinskie/whirlpool"
|
||||
// hash algos must be manually imported thusly:
|
||||
// (Would be nice if the golang pkg docs were more clear
|
||||
// on this...)
|
||||
|
@ -54,10 +58,20 @@ func expandKeyMat(keymat []byte, blocksize int) []byte {
|
|||
return keymat
|
||||
}
|
||||
|
||||
/* Support functionality to set up encryption after a channel has
|
||||
been negotiated via hkexnet.go
|
||||
*/
|
||||
func (hc Conn) getStream(keymat []byte) (rc cipher.Stream, mc hash.Hash, err error) {
|
||||
// Choose a cipher and hmac alg from supported sets, given two uint8 values
|
||||
func getNewStreamAlgs(cb uint8, hb uint8) (config uint32) {
|
||||
// Get new cipher and hash algs (clamped to valid values) based on
|
||||
// the input rekeying data
|
||||
c := (cb % CAlgNoneDisallowed)
|
||||
h := (hb % HmacNoneDisallowed)
|
||||
config = uint32(h<<8) | uint32(c)
|
||||
logger.LogDebug(fmt.Sprintf("[Chose new algs [%d:%d]", h, c))
|
||||
return
|
||||
}
|
||||
|
||||
// (Re-)initialize the keystream and hmac state for an xsnet.Conn, returning
|
||||
// a cipherStream and hash
|
||||
func (hc *Conn) getStream(keymat []byte) (rc cipher.Stream, mc hash.Hash, err error) {
|
||||
var key []byte
|
||||
var block cipher.Block
|
||||
var iv []byte
|
||||
|
@ -75,7 +89,6 @@ func (hc Conn) getStream(keymat []byte) (rc cipher.Stream, mc hash.Hash, err err
|
|||
iv = keymat[aes.BlockSize : aes.BlockSize+ivlen]
|
||||
rc = cipher.NewOFB(block, iv)
|
||||
log.Printf("[cipher AES_256 (%d)]\n", copts)
|
||||
break
|
||||
case CAlgTwofish128:
|
||||
keymat = expandKeyMat(keymat, twofish.BlockSize)
|
||||
key = keymat[0:twofish.BlockSize]
|
||||
|
@ -84,7 +97,6 @@ func (hc Conn) getStream(keymat []byte) (rc cipher.Stream, mc hash.Hash, err err
|
|||
iv = keymat[twofish.BlockSize : twofish.BlockSize+ivlen]
|
||||
rc = cipher.NewOFB(block, iv)
|
||||
log.Printf("[cipher TWOFISH_128 (%d)]\n", copts)
|
||||
break
|
||||
case CAlgBlowfish64:
|
||||
keymat = expandKeyMat(keymat, blowfish.BlockSize)
|
||||
key = keymat[0:blowfish.BlockSize]
|
||||
|
@ -102,11 +114,26 @@ func (hc Conn) getStream(keymat []byte) (rc cipher.Stream, mc hash.Hash, err err
|
|||
iv = keymat[blowfish.BlockSize : blowfish.BlockSize+ivlen]
|
||||
rc = cipher.NewOFB(block, iv)
|
||||
log.Printf("[cipher BLOWFISH_64 (%d)]\n", copts)
|
||||
break
|
||||
case CAlgCryptMT1:
|
||||
rc = cryptmt.NewCipher(keymat)
|
||||
rc = cryptmt.New(nil, nil, keymat)
|
||||
//NOTE: this alg is not based on block cipher, no IV
|
||||
log.Printf("[cipher CRYPTMT1 (%d)]\n", copts)
|
||||
break
|
||||
case CAlgHopscotch:
|
||||
rc = hopscotch.New(nil, nil, 4, keymat)
|
||||
//NOTE: this alg is not based on block cipher, no IV
|
||||
log.Printf("[cipher HOPSCOTCH (%d)]\n", copts)
|
||||
case CAlgChaCha20_12:
|
||||
keymat = expandKeyMat(keymat, chacha.KeySize)
|
||||
key = keymat[0:chacha.KeySize]
|
||||
ivlen = chacha.INonceSize
|
||||
iv = keymat[chacha.KeySize : chacha.KeySize+ivlen]
|
||||
rc, err = chacha.NewCipher(iv, key, chacha.INonceSize)
|
||||
if err != nil {
|
||||
log.Printf("[ChaCha20 config error]\n")
|
||||
fmt.Printf("[ChaCha20 config error]\n")
|
||||
}
|
||||
// TODO: SetCounter() to something derived from key or nonce or extra keymat?
|
||||
log.Printf("[cipher CHACHA20_12 (%d)]\n", copts)
|
||||
default:
|
||||
log.Printf("[invalid cipher (%d)]\n", copts)
|
||||
fmt.Printf("DOOFUS SET A VALID CIPHER ALG (%d)\n", copts)
|
||||
|
@ -123,7 +150,6 @@ func (hc Conn) getStream(keymat []byte) (rc cipher.Stream, mc hash.Hash, err err
|
|||
if !halg.Available() {
|
||||
log.Fatal("hash not available!")
|
||||
}
|
||||
break
|
||||
case HmacSHA512:
|
||||
log.Printf("[hash HmacSHA512 (%d)]\n", hopts)
|
||||
halg := crypto.SHA512
|
||||
|
@ -131,7 +157,9 @@ func (hc Conn) getStream(keymat []byte) (rc cipher.Stream, mc hash.Hash, err err
|
|||
if !halg.Available() {
|
||||
log.Fatal("hash not available!")
|
||||
}
|
||||
break
|
||||
case HmacWHIRLPOOL:
|
||||
log.Printf("[hash HmacWHIRLPOOL (%d)]\n", hopts)
|
||||
mc = whirlpool.New()
|
||||
default:
|
||||
log.Printf("[invalid hmac (%d)]\n", hopts)
|
||||
fmt.Printf("DOOFUS SET A VALID HMAC ALG (%d)\n", hopts)
|
||||
|
@ -140,7 +168,7 @@ func (hc Conn) getStream(keymat []byte) (rc cipher.Stream, mc hash.Hash, err err
|
|||
//os.Exit(1)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if err == nil && ivlen > 0 {
|
||||
// Feed the IV into the hmac: all traffic in the connection must
|
||||
// feed its data into the hmac afterwards, so both ends can xor
|
||||
// that with the stream to detect corruption.
|
|
@ -1,16 +1,17 @@
|
|||
// consts.go - consts for hkexnet
|
||||
// consts.go - consts for xsnet
|
||||
|
||||
// Copyright (c) 2017-2018 Russell Magee
|
||||
// Copyright (c) 2017-2020 Russell Magee
|
||||
// Licensed under the terms of the MIT license (see LICENSE.mit in this
|
||||
// distribution)
|
||||
//
|
||||
// golang implementation by Russ Magee (rmagee_at_gmail.com)
|
||||
package hkexnet
|
||||
package xsnet
|
||||
|
||||
// KEX algorithm values
|
||||
//
|
||||
// Specified (in string form) as the extensions parameter
|
||||
// to hkexnet.Dial()
|
||||
// to xsnet.Dial()
|
||||
// Alg is sent in a uint8 so there are up to 256 possible
|
||||
const (
|
||||
KEX_HERRADURA256 = iota // this MUST be first for default if omitted in ctor
|
||||
KEX_HERRADURA512
|
||||
|
@ -28,21 +29,31 @@ const (
|
|||
KEX_NEWHOPE_SIMPLE // 'NewHopeLP-Simple' - https://eprint.iacr.org/2016/1157
|
||||
KEX_resvd14
|
||||
KEX_resvd15
|
||||
KEX_FRODOKEM_1344AES
|
||||
KEX_FRODOKEM_1344SHAKE
|
||||
KEX_FRODOKEM_976AES
|
||||
KEX_FRODOKEM_976SHAKE
|
||||
KEX_invalid = 255
|
||||
)
|
||||
|
||||
// Sent from client to server in order to specify which
|
||||
// algo shall be used (see hkexnet.KEX_HERRADURA256, ...)
|
||||
// algo shall be used (see xsnet.KEX_HERRADURA256, ...)
|
||||
type KEXAlg uint8
|
||||
|
||||
// Extended exit status codes - indicate comm/pty issues
|
||||
// rather than remote end normal UNIX exit codes
|
||||
const (
|
||||
CSENone = 1024 + iota
|
||||
CSETruncCSO // No CSOExitStatus in payload
|
||||
CSEStillOpen // Channel closed unexpectedly
|
||||
CSEExecFail // cmd.Start() (exec) failed
|
||||
CSEPtyExecFail // pty.Start() (exec w/pty) failed
|
||||
CSEPtyGetNameFail // failed to obtain pty name
|
||||
CSENone = 1024 + iota
|
||||
CSETruncCSO // No CSOExitStatus in payload
|
||||
CSEStillOpen // Channel closed unexpectedly
|
||||
CSEExecFail // cmd.Start() (exec) failed
|
||||
CSEPtyExecFail // pty.Start() (exec w/pty) failed
|
||||
CSEPtyGetNameFail // failed to obtain pty name
|
||||
CSEKEXAlgDenied // server rejected proposed KEX alg
|
||||
CSECipherAlgDenied // server rejected proposed Cipher alg
|
||||
CSEHMACAlgDenied // server rejected proposed HMAC alg
|
||||
CSEConnDead // connection keepalives expired
|
||||
CSELoginTimeout
|
||||
)
|
||||
|
||||
// Extended (>255 UNIX exit status) codes
|
||||
|
@ -66,6 +77,8 @@ const (
|
|||
CSOTunKeepAlive // client tunnel heartbeat
|
||||
CSOTunDisconn // server -> client: tunnel rport disconnected
|
||||
CSOTunHangup // client -> server: tunnel lport hung up
|
||||
CSOKeepAlive // bidir keepalive packet to monitor main connection
|
||||
CSORekey // TODO: rekey/re-select session cipher/hash algs
|
||||
)
|
||||
|
||||
// TunEndpoint.tunCtl control values - used to control workers for client
|
||||
|
@ -85,8 +98,8 @@ const (
|
|||
// Channel status Op byte type (see CSONone, ... and CSENone, ...)
|
||||
type CSOType uint32
|
||||
|
||||
//TODO: this should be small (max unfragmented packet size?)
|
||||
const MAX_PAYLOAD_LEN = 4*1024*1024*1024 - 1
|
||||
// TODO: this should be small (max unfragmented packet size?)
|
||||
const MAX_PAYLOAD_LEN = 2*1024*1024*1024 - 1
|
||||
|
||||
// Session symmetric crypto algs
|
||||
const (
|
||||
|
@ -94,6 +107,8 @@ const (
|
|||
CAlgTwofish128 // golang.org/x/crypto/twofish
|
||||
CAlgBlowfish64 // golang.org/x/crypto/blowfish
|
||||
CAlgCryptMT1 //cryptmt using mtwist64
|
||||
CAlgChaCha20_12
|
||||
CAlgHopscotch
|
||||
CAlgNoneDisallowed
|
||||
)
|
||||
|
||||
|
@ -104,8 +119,27 @@ type CSCipherAlg uint32
|
|||
const (
|
||||
HmacSHA256 = iota
|
||||
HmacSHA512
|
||||
HmacWHIRLPOOL
|
||||
HmacNoneDisallowed
|
||||
)
|
||||
|
||||
// Conn opts outside of basic kex/cipher/hmac connect config
|
||||
const (
|
||||
CONone = iota
|
||||
CORemodulateShields // if set, rekeying also reselects random cipher/hmac alg
|
||||
)
|
||||
|
||||
type COValue uint32
|
||||
|
||||
// Available HMACs for hkex.Conn
|
||||
type CSHmacAlg uint32
|
||||
|
||||
// Some bounds-checking consts
|
||||
const (
|
||||
REKEY_SECS_MIN = 1
|
||||
REKEY_SECS_MAX = 28800 // 8 hours
|
||||
CHAFF_FREQ_MSECS_MIN = 1
|
||||
CHAFF_FREQ_MSECS_MAX = 300000 // 5 minutes
|
||||
)
|
||||
|
||||
const XS_ID_AUTHTOKFILE = ".config/xs/.xs_id"
|
|
@ -0,0 +1,129 @@
|
|||
package xsnet
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"blitter.com/go/xs/logger"
|
||||
kcp "github.com/xtaci/kcp-go"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
|
||||
const (
|
||||
KCP_NONE = iota
|
||||
KCP_AES
|
||||
KCP_BLOWFISH
|
||||
KCP_CAST5
|
||||
KCP_SM4
|
||||
KCP_SALSA20
|
||||
KCP_SIMPLEXOR
|
||||
KCP_TEA
|
||||
KCP_3DES
|
||||
KCP_TWOFISH
|
||||
KCP_XTEA
|
||||
)
|
||||
|
||||
// for github.com/xtaci/kcp-go BlockCrypt alg selection
|
||||
type KCPAlg uint8
|
||||
|
||||
var (
|
||||
kcpKeyBytes []byte = []byte("SET THIS") // symmetric crypto key for KCP (github.com/xtaci/kcp-go) if used
|
||||
kcpSaltBytes []byte = []byte("ALSO SET THIS")
|
||||
)
|
||||
|
||||
func getKCPalgnum(extensions []string) (k KCPAlg) {
|
||||
k = KCP_AES // default
|
||||
var s string
|
||||
for _, s = range extensions {
|
||||
switch s {
|
||||
case "KCP_NONE":
|
||||
k = KCP_NONE
|
||||
break //golint:ignore SA4011 out of for
|
||||
case "KCP_AES":
|
||||
k = KCP_AES
|
||||
break //out of for
|
||||
case "KCP_BLOWFISH":
|
||||
k = KCP_BLOWFISH
|
||||
break //out of for
|
||||
case "KCP_CAST5":
|
||||
k = KCP_CAST5
|
||||
break //out of for
|
||||
case "KCP_SM4":
|
||||
k = KCP_SM4
|
||||
break //out of for
|
||||
case "KCP_SALSA20":
|
||||
k = KCP_SALSA20
|
||||
break //out of for
|
||||
case "KCP_SIMPLEXOR":
|
||||
k = KCP_SIMPLEXOR
|
||||
break //out of for
|
||||
case "KCP_TEA":
|
||||
k = KCP_TEA
|
||||
break //out of for
|
||||
case "KCP_3DES":
|
||||
k = KCP_3DES
|
||||
break //out of for
|
||||
case "KCP_TWOFISH":
|
||||
k = KCP_TWOFISH
|
||||
break //out of for
|
||||
case "KCP_XTEA":
|
||||
k = KCP_XTEA
|
||||
break //out of for
|
||||
}
|
||||
}
|
||||
logger.LogDebug(fmt.Sprintf("[KCP BlockCrypt '%s' activated]", s))
|
||||
return
|
||||
}
|
||||
|
||||
func SetKCPKeyAndSalt(key []byte, salt []byte) {
|
||||
kcpKeyBytes = key
|
||||
kcpSaltBytes = salt
|
||||
}
|
||||
|
||||
func _newKCPBlockCrypt(key []byte, extensions []string) (b kcp.BlockCrypt, e error) {
|
||||
switch getKCPalgnum(extensions) {
|
||||
case KCP_NONE:
|
||||
return kcp.NewNoneBlockCrypt(key)
|
||||
case KCP_AES:
|
||||
return kcp.NewAESBlockCrypt(key)
|
||||
case KCP_BLOWFISH:
|
||||
return kcp.NewBlowfishBlockCrypt(key)
|
||||
case KCP_CAST5:
|
||||
return kcp.NewCast5BlockCrypt(key)
|
||||
case KCP_SM4:
|
||||
return kcp.NewSM4BlockCrypt(key)
|
||||
case KCP_SALSA20:
|
||||
return kcp.NewSalsa20BlockCrypt(key)
|
||||
case KCP_SIMPLEXOR:
|
||||
return kcp.NewSimpleXORBlockCrypt(key)
|
||||
case KCP_TEA:
|
||||
return kcp.NewTEABlockCrypt(key)
|
||||
case KCP_3DES:
|
||||
return kcp.NewTripleDESBlockCrypt(key)
|
||||
case KCP_TWOFISH:
|
||||
return kcp.NewTwofishBlockCrypt(key)
|
||||
case KCP_XTEA:
|
||||
return kcp.NewXTEABlockCrypt(key)
|
||||
}
|
||||
return nil, errors.New("Invalid KCP BlockCrypto specified")
|
||||
}
|
||||
|
||||
func kcpDial(ipport string, extensions []string) (c net.Conn, err error) {
|
||||
kcpKey := pbkdf2.Key(kcpKeyBytes, kcpSaltBytes, 1024, 32, sha1.New)
|
||||
block, be := _newKCPBlockCrypt([]byte(kcpKey), extensions)
|
||||
_ = be
|
||||
return kcp.DialWithOptions(ipport, block, 10, 3)
|
||||
}
|
||||
|
||||
func kcpListen(ipport string, extensions []string) (l net.Listener, err error) {
|
||||
kcpKey := pbkdf2.Key(kcpKeyBytes, kcpSaltBytes, 1024, 32, sha1.New)
|
||||
block, be := _newKCPBlockCrypt([]byte(kcpKey), extensions)
|
||||
_ = be
|
||||
return kcp.ListenWithOptions(ipport, block, 10, 3)
|
||||
}
|
||||
|
||||
func (hl *HKExListener) AcceptKCP() (c net.Conn, e error) {
|
||||
return hl.l.(*kcp.Listener).AcceptKCP()
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package xsnet
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func (hc *Conn) kill() {
|
||||
syscall.Kill(hc.Pproc, syscall.SIGABRT) //nolint:errcheck
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package xsnet
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func (hc *Conn) kill() {
|
||||
exec.Command("taskkill", "/f", "/pid", strconv.Itoa(hc.Pproc)).Run()
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
// hkextun.go - Tunnel setup using an established hkexnet.Conn
|
||||
// hkextun.go - Tunnel setup using an established xsnet.Conn
|
||||
|
||||
// Copyright (c) 2017-2018 Russell Magee
|
||||
// Copyright (c) 2017-2020 Russell Magee
|
||||
// Licensed under the terms of the MIT license (see LICENSE.mit in this
|
||||
// distribution)
|
||||
//
|
||||
// golang implementation by Russ Magee (rmagee_at_gmail.com)
|
||||
|
||||
package hkexnet
|
||||
package xsnet
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
@ -18,7 +18,7 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"blitter.com/go/hkexsh/logger"
|
||||
"blitter.com/go/xs/logger"
|
||||
)
|
||||
|
||||
type (
|
||||
|
@ -37,6 +37,8 @@ type (
|
|||
// client starts worker to receive/send data using lport
|
||||
// ... client disconnects: sends remhost [CSOTunClose:rport]
|
||||
// ... or server disconnects: sends client [CSOTunClose:lport]
|
||||
// ... or server disconnects: due to client failing to send TunKeepAlive
|
||||
// events for too long
|
||||
// server at any time sends [CSOTunRefused:rport] if daemon died
|
||||
// --
|
||||
|
|
@ -1,16 +1,22 @@
|
|||
.PHONY: clean all vis lint
|
||||
|
||||
ifeq ($(GARBLE),y)
|
||||
GO = garble -tiny -literals -debugdir=garbled
|
||||
else
|
||||
GO = go
|
||||
endif
|
||||
|
||||
EXTPKGS = bytes,errors,flag,fmt,internal,io,log,net,os,path,runtime,time,strings,sync,syscall,binary,encoding
|
||||
EXE = $(notdir $(shell pwd))
|
||||
|
||||
all:
|
||||
go build $(BUILDOPTS) .
|
||||
$(GO) build $(BUILDOPTS) .
|
||||
|
||||
clean:
|
||||
$(RM) $(EXE) $(EXE).exe
|
||||
|
||||
vis:
|
||||
go-callvis -format png -file hkexpasswd-vis -ignore $(EXTPKGS) -group pkg,type .
|
||||
go-callvis -format png -file xspasswd-vis -ignore $(EXTPKGS) -group pkg,type .
|
||||
|
||||
lint:
|
||||
-gometalinter --deadline=60s | sort
|
||||
-golangci-lint run
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
@ -1,7 +1,7 @@
|
|||
// Util to generate/store passwords for users in a file akin to /etc/passwd
|
||||
// suitable for the demo hkexsh server, using bcrypt.
|
||||
// suitable for the xs server, using bcrypt.
|
||||
//
|
||||
// Copyright (c) 2017-2018 Russell Magee
|
||||
// Copyright (c) 2017-2020 Russell Magee
|
||||
// Licensed under the terms of the MIT license (see LICENSE.mit in this
|
||||
// distribution)
|
||||
//
|
||||
|
@ -17,7 +17,7 @@ import (
|
|||
"log"
|
||||
"os"
|
||||
|
||||
hkexsh "blitter.com/go/hkexsh"
|
||||
xs "blitter.com/go/xs"
|
||||
"github.com/jameskeane/bcrypt"
|
||||
)
|
||||
|
||||
|
@ -36,7 +36,7 @@ func main() {
|
|||
|
||||
flag.BoolVar(&vopt, "v", false, "show version")
|
||||
flag.StringVar(&userName, "u", "", "username")
|
||||
flag.StringVar(&pfName, "f", "/etc/hkexsh.passwd", "passwd file")
|
||||
flag.StringVar(&pfName, "f", "/etc/xs.passwd", "passwd file")
|
||||
flag.Parse()
|
||||
|
||||
if vopt {
|
||||
|
@ -59,7 +59,7 @@ func main() {
|
|||
uname = userName
|
||||
|
||||
fmt.Printf("New Password:")
|
||||
ab, err := hkexsh.ReadPassword(int(os.Stdin.Fd()))
|
||||
ab, err := xs.ReadPassword(os.Stdin.Fd())
|
||||
fmt.Printf("\r\n")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
@ -68,7 +68,7 @@ func main() {
|
|||
newpw = string(ab)
|
||||
|
||||
fmt.Printf("Confirm:")
|
||||
ab, err = hkexsh.ReadPassword(int(os.Stdin.Fd()))
|
||||
ab, err = xs.ReadPassword(os.Stdin.Fd())
|
||||
fmt.Printf("\r\n")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
@ -130,7 +130,7 @@ func main() {
|
|||
records = append(records, newRec)
|
||||
}
|
||||
|
||||
outFile, err := ioutil.TempFile("", "hkexsh-passwd")
|
||||
outFile, err := ioutil.TempFile("", "xs-passwd")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|