Initial commit

This commit is contained in:
NI
2019-08-07 15:56:51 +08:00
commit 02f14eb14f
206 changed files with 38863 additions and 0 deletions

View File

@@ -0,0 +1,164 @@
// Sshwifty - A Web SSH client
//
// Copyright (C) 2019 Rui NI <nirui@gmx.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package configuration
import (
"errors"
"fmt"
"net"
"time"
"github.com/niruix/sshwifty/application/network"
)
// Server contains configuration of a HTTP server
type Server struct {
ListenInterface string
ListenPort uint16
InitialTimeout time.Duration
ReadTimeout time.Duration
WriteTimeout time.Duration
HeartbeatTimeout time.Duration
ReadDelay time.Duration
WriteDelay time.Duration
TLSCertificateFile string
TLSCertificateKeyFile string
}
func (s Server) defaultListenInterface() string {
if len(s.ListenInterface) > 0 {
return s.ListenInterface
}
return net.IPv4(127, 0, 0, 1).String()
}
func (s Server) defaultListenPort() uint16 {
if s.ListenPort > 0 {
return s.ListenPort
}
return 80
}
func (s Server) maxDur(cur, def time.Duration) time.Duration {
if cur > def {
return cur
}
return def
}
// WithDefault build the configuration and fill the blank with default values
func (s Server) WithDefault() Server {
initialTimeout := s.maxDur(s.InitialTimeout, 1*time.Second)
readTimeout := s.maxDur(initialTimeout, 3*time.Second)
readTimeout = s.maxDur(s.ReadTimeout, readTimeout)
return Server{
ListenInterface: s.defaultListenInterface(),
ListenPort: s.defaultListenPort(),
InitialTimeout: initialTimeout,
ReadTimeout: readTimeout,
WriteTimeout: s.maxDur(s.WriteTimeout, 3*time.Second),
HeartbeatTimeout: s.maxDur(s.ReadTimeout, readTimeout/2),
ReadDelay: 0,
WriteDelay: 0,
TLSCertificateFile: "",
TLSCertificateKeyFile: "",
}
}
// IsTLS returns whether or not TLS should be used
func (s Server) IsTLS() bool {
return len(s.TLSCertificateFile) > 0 && len(s.TLSCertificateKeyFile) > 0
}
// Verify verifies current configuration
func (s Server) Verify() error {
if net.ParseIP(s.ListenInterface) == nil {
return fmt.Errorf("Invalid IP address \"%s\"", s.ListenInterface)
}
if (len(s.TLSCertificateFile) > 0 && len(s.TLSCertificateKeyFile) <= 0) ||
(len(s.TLSCertificateFile) <= 0 && len(s.TLSCertificateKeyFile) > 0) {
return errors.New("TLSCertificateFile and TLSCertificateKeyFile must " +
"both be specified in order to enable TLS")
}
return nil
}
// Configuration contains configuration of the application
type Configuration struct {
HostName string
SharedKey string
Dialer network.Dial
Servers []Server
}
// Common settings shared by mulitple servers
type Common struct {
HostName string
SharedKey string
Dialer network.Dial
}
// Verify verifies current setting
func (c Configuration) Verify() error {
if len(c.Servers) <= 0 {
return errors.New("Must specify at least one server")
}
for i, c := range c.Servers {
vErr := c.Verify()
if vErr == nil {
continue
}
return fmt.Errorf("Invalid setting for server %d: %s", i, vErr)
}
return nil
}
// Common returns common settings
func (c Configuration) Common() Common {
return Common{
HostName: c.HostName,
SharedKey: c.SharedKey,
Dialer: c.Dialer,
}
}
// WithDefault build the configuration and fill the blank with default values
func (c Common) WithDefault() Common {
dialer := c.Dialer
if dialer == nil {
dialer = network.TCPDial()
}
return Common{
HostName: c.HostName,
SharedKey: c.SharedKey,
Dialer: dialer,
}
}

View File

@@ -0,0 +1,25 @@
// Sshwifty - A Web SSH client
//
// Copyright (C) 2019 Rui NI <nirui@gmx.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package configuration
import (
"github.com/niruix/sshwifty/application/log"
)
// Loader Configuration loader
type Loader func(log log.Logger) (name string, cfg Configuration, err error)

View File

@@ -0,0 +1,107 @@
// Sshwifty - A Web SSH client
//
// Copyright (C) 2019 Rui NI <nirui@gmx.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package configuration
import (
"fmt"
"os"
"strconv"
"github.com/niruix/sshwifty/application/log"
"github.com/niruix/sshwifty/application/network"
)
const (
enviroTypeName = "Environment Variable"
)
// Enviro creates an environment variable based configuration loader
func Enviro() Loader {
return func(log log.Logger) (string, Configuration, error) {
log.Info("Loading configuration from environment variables ...")
cfg := fileCfgCommon{
HostName: os.Getenv("SSHWIFTY_HOSTNAME"),
SharedKey: os.Getenv("SSHWIFTY_SHAREDKEY"),
Socks5: os.Getenv("SSHWIFTY_SOCKS5"),
Socks5User: os.Getenv("SSHWIFTY_SOCKS5_USER"),
Socks5Password: os.Getenv("SSHWIFTY_SOCKS5_PASSWORD"),
}
listenPort, listenPortErr := strconv.ParseUint(
os.Getenv("SSHWIFTY_LISTENPORT"), 10, 16)
if listenPortErr != nil {
return enviroTypeName, Configuration{}, fmt.Errorf(
"Invalid \"SSHWIFTY_LISTENPORT\": %s", listenPortErr)
}
initialTimeout, _ := strconv.ParseUint(
os.Getenv("SSHWIFTY_INITIALTIMEOUT"), 10, 32)
readTimeout, _ := strconv.ParseUint(
os.Getenv("SSHWIFTY_READTIMEOUT"), 10, 32)
writeTimeout, _ := strconv.ParseUint(
os.Getenv("SSHWIFTY_WRITETIMEOUT"), 10, 32)
heartbeatTimeout, _ := strconv.ParseUint(
os.Getenv("SSHWIFTY_HEARTBEATTIMEOUT"), 10, 32)
readDelay, _ := strconv.ParseUint(
os.Getenv("SSHWIFTY_READDELAY"), 10, 32)
writeDelay, _ := strconv.ParseUint(
os.Getenv("SSHWIFTY_WRITEELAY"), 10, 32)
cfgSer := fileCfgServer{
ListenInterface: os.Getenv("SSHWIFTY_LISTENINTERFACE"),
ListenPort: uint16(listenPort),
InitialTimeout: int(initialTimeout),
ReadTimeout: int(readTimeout),
WriteTimeout: int(writeTimeout),
HeartbeatTimeout: int(heartbeatTimeout),
ReadDelay: int(readDelay),
WriteDelay: int(writeDelay),
TLSCertificateFile: os.Getenv("SSHWIFTY_TLSCERTIFICATEFILE"),
TLSCertificateKeyFile: os.Getenv("SSHWIFTY_TLSCERTIFICATEKEYFILE"),
}
var dialer network.Dial
if len(cfg.Socks5) <= 0 {
dialer = network.TCPDial()
} else {
sDial, sDialErr := network.BuildSocks5Dial(
cfg.Socks5, cfg.Socks5User, cfg.Socks5Password)
if sDialErr != nil {
return enviroTypeName, Configuration{}, sDialErr
}
dialer = sDial
}
return enviroTypeName, Configuration{
HostName: cfg.HostName,
SharedKey: cfg.SharedKey,
Dialer: dialer,
Servers: []Server{cfgSer.build()},
}, nil
}
}

View File

@@ -0,0 +1,190 @@
// Sshwifty - A Web SSH client
//
// Copyright (C) 2019 Rui NI <nirui@gmx.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package configuration
import (
"encoding/json"
"fmt"
"os"
"os/user"
"path"
"path/filepath"
"strings"
"time"
"github.com/niruix/sshwifty/application/network"
"github.com/niruix/sshwifty/application/log"
)
const (
fileTypeName = "File"
)
type fileCfgServer struct {
ListenInterface string // Interface to listen to
ListenPort uint16 // Port to listen
InitialTimeout int // Client initial request timeout, in second
ReadTimeout int // Read operation timeout, in second
WriteTimeout int // Write operation timeout, in second
HeartbeatTimeout int // Client heartbeat interval, in second
ReadDelay int // Read delay, in millisecond
WriteDelay int // Write delay, in millisecond
TLSCertificateFile string // Location of TLS certificate file
TLSCertificateKeyFile string // Location of TLS certificate key
}
func (f fileCfgServer) minDur(current, min int) int {
if current > min {
return current
}
return min
}
func (f *fileCfgServer) build() Server {
return Server{
ListenInterface: f.ListenInterface,
ListenPort: f.ListenPort,
InitialTimeout: time.Duration(
f.minDur(f.InitialTimeout, 5)) * time.Second,
ReadTimeout: time.Duration(
f.minDur(f.ReadTimeout, 30)) * time.Second,
WriteTimeout: time.Duration(
f.minDur(f.WriteTimeout, 30)) * time.Second,
HeartbeatTimeout: time.Duration(
f.minDur(f.HeartbeatTimeout, 10)) * time.Second,
ReadDelay: time.Duration(
f.minDur(f.ReadDelay, 0)) * time.Millisecond,
WriteDelay: time.Duration(
f.minDur(f.WriteDelay, 0)) * time.Millisecond,
TLSCertificateFile: f.TLSCertificateFile,
TLSCertificateKeyFile: f.TLSCertificateKeyFile,
}
}
type fileCfgCommon struct {
HostName string // Host name
SharedKey string // Shared key, empty to enable public access
Socks5 string // Socks5 server address, optional
Socks5User string // Login user for socks5 server, optional
Socks5Password string // Login pass for socks5 server, optional
Servers []*fileCfgServer // Servers
}
func loadFile(filePath string) (string, Configuration, error) {
f, fErr := os.Open(filePath)
if fErr != nil {
return fileTypeName, Configuration{}, fErr
}
defer f.Close()
cfg := fileCfgCommon{}
jDecoder := json.NewDecoder(f)
jDecodeErr := jDecoder.Decode(&cfg)
if jDecodeErr != nil {
return fileTypeName, Configuration{}, jDecodeErr
}
servers := make([]Server, len(cfg.Servers))
for i := range servers {
servers[i] = cfg.Servers[i].build()
}
var dialer network.Dial
if len(cfg.Socks5) <= 0 {
dialer = network.TCPDial()
} else {
sDial, sDialErr := network.BuildSocks5Dial(
cfg.Socks5, cfg.Socks5User, cfg.Socks5Password)
if sDialErr != nil {
return fileTypeName, Configuration{}, sDialErr
}
dialer = sDial
}
return fileTypeName, Configuration{
HostName: cfg.HostName,
SharedKey: cfg.SharedKey,
Dialer: dialer,
Servers: servers,
}, nil
}
// File creates a configuration file loader
func File(customPath string) Loader {
return func(log log.Logger) (string, Configuration, error) {
if len(customPath) > 0 {
log.Info("Loading configuration from: %s", customPath)
return loadFile(customPath)
}
fallbackFileSearchList := make([]string, 0, 3)
// ~/.config/sshwifty.conf.json
u, userErr := user.Current()
if userErr == nil {
fallbackFileSearchList = append(
fallbackFileSearchList,
path.Join(u.HomeDir, ".config", "sshwifty.conf.json"))
}
// /etc/sshwifty.conf.json
fallbackFileSearchList = append(
fallbackFileSearchList, "/etc/sshwifty.conf.json")
// sshwifty.conf.json located at the same directory as Sshwifty bin
ex, exErr := os.Executable()
if exErr == nil {
fallbackFileSearchList = append(
fallbackFileSearchList,
path.Join(filepath.Dir(ex), "sshwifty.conf.json"))
}
for f := range fallbackFileSearchList {
fInfo, fErr := os.Stat(fallbackFileSearchList[f])
if fErr != nil {
continue
}
if fInfo.IsDir() {
continue
}
log.Info("Loading configuration from: %s",
fallbackFileSearchList[f])
return loadFile(fallbackFileSearchList[f])
}
return "", Configuration{}, fmt.Errorf(
"Configuration file was not specified. Also tried fallback files "+
"\"%s\", but none of it was available",
strings.Join(fallbackFileSearchList, "\", \""))
}
}

View File

@@ -0,0 +1,52 @@
// Sshwifty - A Web SSH client
//
// Copyright (C) 2019 Rui NI <nirui@gmx.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package configuration
import (
"fmt"
"github.com/niruix/sshwifty/application/log"
)
const (
redundantTypeName = "Redundant"
)
// Redundant creates a group of loaders. They will be executed one by one until
// one of it successfully returned a configuration
func Redundant(loaders ...Loader) Loader {
return func(log log.Logger) (string, Configuration, error) {
ll := log.Context("Redundant")
for i := range loaders {
lLoaderName, lCfg, lErr := loaders[i](ll)
if lErr != nil {
ll.Warning("Unable to load configuration from \"%s\": %s",
lLoaderName, lErr)
continue
}
return lLoaderName, lCfg, nil
}
return redundantTypeName, Configuration{}, fmt.Errorf(
"All existing redundant loader has failed")
}
}