252 lines
6.1 KiB
Go
252 lines
6.1 KiB
Go
// Sshwifty - A Web SSH client
|
|
//
|
|
// Copyright (C) 2019-2022 Ni Rui <ranqus@gmail.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/nirui/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
|
|
}
|
|
|
|
func (s Server) minDur(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)
|
|
|
|
maxHeartBeatTimeout := time.Duration(float64(readTimeout) * 0.8)
|
|
heartBeatTimeout := s.minDur(s.HeartbeatTimeout, maxHeartBeatTimeout)
|
|
|
|
if heartBeatTimeout <= 0 {
|
|
heartBeatTimeout = maxHeartBeatTimeout
|
|
}
|
|
|
|
return Server{
|
|
ListenInterface: s.defaultListenInterface(),
|
|
ListenPort: s.defaultListenPort(),
|
|
InitialTimeout: initialTimeout,
|
|
ReadTimeout: readTimeout,
|
|
WriteTimeout: s.maxDur(s.WriteTimeout, 3*time.Second),
|
|
HeartbeatTimeout: heartBeatTimeout,
|
|
ReadDelay: s.ReadDelay,
|
|
WriteDelay: s.WriteDelay,
|
|
TLSCertificateFile: s.TLSCertificateFile,
|
|
TLSCertificateKeyFile: s.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
|
|
}
|
|
|
|
// Meta contains data of a Key -> Value map which can be use to store
|
|
// dynamically structured configuration options
|
|
type Meta map[string]String
|
|
|
|
// Concretize returns an concretized Meta as a `map[string]string`
|
|
func (m Meta) Concretize() (map[string]string, error) {
|
|
mm := make(map[string]string, len(m))
|
|
|
|
for k, v := range m {
|
|
result, err := v.Parse()
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to parse Meta \"%s\": %s", k, err)
|
|
}
|
|
|
|
mm[k] = result
|
|
}
|
|
|
|
return mm, nil
|
|
}
|
|
|
|
// Preset contains data of a static remote host
|
|
type Preset struct {
|
|
Title string
|
|
Type string
|
|
Host string
|
|
Meta map[string]string
|
|
}
|
|
|
|
// Configuration contains configuration of the application
|
|
type Configuration struct {
|
|
HostName string
|
|
SharedKey string
|
|
DialTimeout time.Duration
|
|
Socks5 string
|
|
Socks5User string
|
|
Socks5Password string
|
|
Servers []Server
|
|
Presets []Preset
|
|
OnlyAllowPresetRemotes bool
|
|
}
|
|
|
|
// Common settings shared by mulitple servers
|
|
type Common struct {
|
|
HostName string
|
|
SharedKey string
|
|
Dialer network.Dial
|
|
DialTimeout time.Duration
|
|
Presets []Preset
|
|
OnlyAllowPresetRemotes bool
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// Dialer builds a Dialer
|
|
func (c Configuration) Dialer() network.Dial {
|
|
dialTimeout := c.DialTimeout
|
|
|
|
if dialTimeout < 3 {
|
|
dialTimeout = 3
|
|
}
|
|
|
|
dialer := network.TCPDial()
|
|
|
|
if len(c.Socks5) > 0 {
|
|
sDial, sDialErr := network.BuildSocks5Dial(
|
|
c.Socks5, c.Socks5User, c.Socks5Password)
|
|
|
|
if sDialErr != nil {
|
|
panic("Unable to build Socks5 Dialer: " + sDialErr.Error())
|
|
}
|
|
|
|
dialer = sDial
|
|
}
|
|
|
|
if c.OnlyAllowPresetRemotes {
|
|
accessList := make(network.AllowedHosts, len(c.Presets))
|
|
|
|
for _, k := range c.Presets {
|
|
if len(k.Host) <= 0 {
|
|
continue
|
|
}
|
|
|
|
accessList[k.Host] = struct{}{}
|
|
}
|
|
|
|
dialer = network.AccessControlDial(accessList, dialer)
|
|
}
|
|
|
|
return dialer
|
|
}
|
|
|
|
// Common returns common settings
|
|
func (c Configuration) Common() Common {
|
|
return Common{
|
|
HostName: c.HostName,
|
|
SharedKey: c.SharedKey,
|
|
Dialer: c.Dialer(),
|
|
DialTimeout: c.DialTimeout,
|
|
Presets: c.Presets,
|
|
OnlyAllowPresetRemotes: c.OnlyAllowPresetRemotes,
|
|
}
|
|
}
|
|
|
|
// DecideDialTimeout will return a reasonable timeout for dialing
|
|
func (c Common) DecideDialTimeout(max time.Duration) time.Duration {
|
|
if c.DialTimeout > max {
|
|
return max
|
|
}
|
|
|
|
return c.DialTimeout
|
|
}
|