Compare commits

...

3 Commits

Author SHA1 Message Date
Sasha Koshka 3d0a69d5a3 Added a convenient Service type 2023-05-26 20:01:27 -04:00
Sasha Koshka 53e7e00f80 Mount is now MountConfig (and fully public) 2023-05-26 19:42:52 -04:00
Sasha Koshka 92911cdab4 Mount is defined in mount.go 2023-05-26 13:08:42 -04:00
3 changed files with 263 additions and 178 deletions

View File

@ -10,7 +10,8 @@ import "hnakra/protocol"
// HTTP is an https:// mount.
type HTTP struct {
Mount
// Mount specifies the mount config to use for connecting to the router.
Mount MountConfig
// AllowInsecure allows this mount to respond to plain-text HTTP
// requests. You can get a TLS cert for free nowadays so there are very
@ -25,6 +26,7 @@ type HTTP struct {
connLock sync.Mutex
connReadWriter *bufio.ReadWriter
requests requestManager
idFactory *protocol.IDFactory
}
// Close closes the mount abruptly, interrupting any active connections.
@ -45,10 +47,11 @@ func (mount *HTTP) Shutdown () error {
// will only return when the connection to the router has been closed.
func (mount *HTTP) Run () (err error) {
if mount.AllowInsecure {
mount.conn, err = mount.connect("http")
mount.Mount.Scheme = "http"
} else {
mount.conn, err = mount.connect("https")
mount.Mount.Scheme = "https"
}
mount.conn, mount.idFactory, err = mount.Mount.Connect()
if err != nil { return }
mount.connReadWriter = bufio.NewReadWriter (

209
service/mount.go Normal file
View File

@ -0,0 +1,209 @@
package service
import "os"
import "log"
import "fmt"
import "net"
import "errors"
import "strings"
import "crypto/tls"
import "encoding/base64"
import "hnakra/protocol"
// M creates a very basic MountConfig with the specified name and description.
func M (name, description string) MountConfig {
return MountConfig {
Name: name,
Description: description,
}
}
// Mount is an interface satisfied by all mount types.
type Mount interface {
Close () error
Shutdown () error
Run () error
}
// MountConfig contains generic information common to all mounts.
type MountConfig struct {
// Host specifies the host to mount on. If the host is left empty, it
// will default to @ (meaning default/any host). The port is entirely up
// to the router. Maximum length for host portion: 255 bytes
Host string
// Scheme specifies the protocol to mount on. This will be automatically
// set by specialized mount types, so setting it manually shouldn't be
// needed.
Scheme string
// Path specifies the path to mount on. If the path ends with a /, then
// all requests under the path will be sent to this service. If there is
// no trailing /, this service will only recieve requests that match the
// path exactly (when normalized).
// Maximum length: 2^16-1 bytes
Path string
// Router specifies the host:port of the router to connect to. This
// defaults to $HNAKRA_ROUTER_HOST:$HNAKRA_ROUTER_PORT if left empty.
// The default value of these environment variables (if not set) is
// localhost:2048. If you are giving this service to other people, it is
// a good idea to leave this empty.
Router string
// Name should be set to a human-readable name of the service.
// Maximum length: 255 bytes
Name string
// Description should be set to a human-readable description of what the
// service does.
// Maximum length: 255 bytes
Description string
// User is an optional string that specifies who is connecting to the
// router. This can be used by routers in conjunction with the key
// bytes in order to enforce rules about which service can do what. This
// defaults to $HNAKRA_USER if left empty. If you are giving this
// service to other people, it is a good idea to leave this empty.
// Maximum length: 255 bytes
User string
// Key is an optional byte slice that is sent to the router in order for
// it to authorize the connection. If nil, this defaults to the contents
// of $HNAKRA_KEY interpreted as base64. Do not embed a key in your
// code - you should only use this if your service reads the key from a
// safe source upon startup. In addition to this, If you choose to set
// the key via the aforementioned enviroment variable, ensure that it
// can only be read by the service.
// Maximum length: 255 bytes
Key []byte
// TLSConfig is an optional TLS configuration.
TLSConfig *tls.Config
}
// Connect creates a new connection to the router specified in the MountConfig.
func (mount *MountConfig) Connect () (
conn net.Conn,
idFactory *protocol.IDFactory,
err error,
) {
log.Println("(i) service", mount.Name)
idFactory = protocol.NewServiceIDFactory()
defaultRouterHost := os.Getenv("HNAKRA_ROUTER_HOST")
if defaultRouterHost == "" {
defaultRouterHost = "localhost"
}
defaultRouterPort := os.Getenv("HNAKRA_ROUTER_PORT")
if defaultRouterPort == "" {
defaultRouterPort = "2048"
}
// parse router host/port
routerHost, routerPort, _ := strings.Cut(mount.Router, ":")
if routerHost == "" {
routerHost = defaultRouterHost
}
if routerPort == "" {
routerPort = defaultRouterPort
}
// get mount point
scheme := mount.Scheme
host := mount.Host
if host == "" {
host = "@"
}
if len(host) > 255 {
return nil, nil, errors.New (
"mount point host cannot be longer than 255 bytes")
}
path := mount.Path
if path == "" {
path = "/"
}
if len(path) > int(protocol.MaxIntOfSize(2)) {
return nil, nil, errors.New(fmt.Sprint (
"mount point path cannot be longer than ",
protocol.MaxIntOfSize(2), " bytes"))
}
// get user
user := mount.User
if user == "" {
user = os.Getenv("HNAKRA_USER")
}
if len(user) > 255 {
return nil, nil, errors.New (
"user cannot be longer than 255 bytes")
}
// get key
key := mount.Key
if key == nil {
base64Key := os.Getenv("HNAKRA_KEY")
key, err = base64.StdEncoding.DecodeString(base64Key)
if err != nil { return nil, nil, err }
}
if len(key) > 255 {
return nil, nil, errors.New (
"key cannot be longer than 255 bytes")
}
// ensure name/description aren't too big
if len(mount.Name) > 255 {
return nil, nil, errors.New (
"service name cannot be longer than 255 bytes")
}
if len(mount.Description) > 255 {
return nil, nil, errors.New (
"service description cannot be longer than 255 bytes")
}
// connect to router
routerAddr := fmt.Sprint(routerHost, ":", routerPort)
log.Println("... dialing", routerAddr)
conn, err = tls.Dial("tcp", routerAddr, mount.TLSConfig)
if err != nil { return nil, nil, err }
// log in
log.Println (
"... logging in as", user,
"on", scheme + "://" + host + path)
err = protocol.MessageLogin {
ID: idFactory.Next(),
Version: protocol.Version { Major: 0, Minor: 0 },
User: user,
Key: key,
Name: mount.Name,
Description: mount.Description,
Scheme: scheme,
Host: host,
Path: path,
}.Send(conn)
if err != nil {
conn.Close()
return nil, nil, err
}
// read status
message, err := protocol.ReadMessage(conn)
if err != nil {
conn.Close()
return nil, nil, err
}
status, ok := message.(protocol.MessageStatus)
if !ok {
conn.Close()
return nil, nil, errors.New(fmt.Sprint (
"router sent unknown type, expecting",
protocol.TypeStatus))
}
if status.Status != protocol.StatusOk {
return nil, nil, status
}
log.Println(".// logged in")
return conn, idFactory, nil
}

View File

@ -3,186 +3,59 @@ package service
import "os"
import "log"
import "fmt"
import "net"
import "errors"
import "strings"
import "crypto/tls"
import "encoding/base64"
import "hnakra/protocol"
import "time"
import "hnakra/rotate"
import "hnakra/routines"
// M creates a very basic mount with the specified name and description.
func M (name, description string) Mount {
return Mount {
Name: name,
Description: description,
// Service is capable of managing multiple mounts. It also sets up logging
// automatically.
type Service []Mount
// Run runs the mounts within the service, and only exits when all of them have
// exited. It will automatically start logging to the directory specified by
// $HNAKRA_LOG_DIR. If that variable is unset, it will just log to stdout.
func (service Service) Run () error {
// set up logging
logDir := os.Getenv("HNAKRA_LOG_DIR")
if logDir != "" {
logger, err := rotate.New(logDir)
if err != nil { log.Fatal("cannot access log dir:", err) }
log.SetOutput(logger)
}
// set up routine manager
manager := routines.Manager { RestartDeadline: time.Second * 8 }
manager.Routines = make([]routines.Routine, len(service))
for index, mount := range manager.Routines {
manager.Routines[index] = mount
}
// send it
err := manager.Run()
if err != nil { log.Println("XXX", err) }
return err
}
// Mount contains generic information common to all mounts.
type Mount struct {
// Host specifies the host to mount on. If the host is left empty, it
// will default to @ (meaning default/any host). The port is entirely up
// to the router. Maximum length for host portion: 255 bytes
Host string
// Path specifies the path to mount on. If the path ends with a /, then
// all requests under the path will be sent to this service. If there is
// no trailing /, this service will only recieve requests that match the
// path exactly (when normalized).
// Maximum length: 2^16-1 bytes
Path string
// Router specifies the host:port of the router to connect to. This
// defaults to $HNAKRA_ROUTER_HOST:$HNAKRA_ROUTER_PORT if left empty.
// The default value of these environment variables (if not set) is
// localhost:2048. If you are giving this service to other people, it is
// a good idea to leave this empty.
Router string
// Name should be set to a human-readable name of the service.
// Maximum length: 255 bytes
Name string
// Description should be set to a human-readable description of what the
// service does.
// Maximum length: 255 bytes
Description string
// User is an optional string that specifies who is connecting to the
// router. This can be used by routers in conjunction with the key
// bytes in order to enforce rules about which service can do what. This
// defaults to $HNAKRA_USER if left empty. If you are giving this
// service to other people, it is a good idea to leave this empty.
// Maximum length: 255 bytes
User string
// Key is an optional byte slice that is sent to the router in order for
// it to authorize the connection. If nil, this defaults to the contents
// of $HNAKRA_KEY interpreted as base64. Do not embed a key in your
// code - you should only use this if your service reads the key from a
// safe source upon startup. In addition to this, If you choose to set
// the key via the aforementioned enviroment variable, ensure that it
// can only be read by the service.
// Maximum length: 255 bytes
Key []byte
// TLSConfig is an optional TLS configuration.
TLSConfig *tls.Config
idFactory *protocol.IDFactory
// Close abruptly closes all mounts in the service. This will cause Run() to
// exit.
func (service Service) Close () (err error) {
for _, mount := range service {
singleErr := mount.Close()
if singleErr != nil {
err = singleErr
}
}
return
}
func (mount *Mount) connect (scheme string) (conn net.Conn, err error) {
log.Println("(i) service", mount.Name)
mount.idFactory = protocol.NewServiceIDFactory()
defaultRouterHost := os.Getenv("HNAKRA_ROUTER_HOST")
if defaultRouterHost == "" {
defaultRouterHost = "localhost"
// Shutdown gracefully shuts down each mount in the service. This will cause
// Run() to exit.
func (service Service) Shutdown () (err error) {
for _, mount := range service {
singleErr := mount.Shutdown()
if singleErr != nil {
err = singleErr
}
}
defaultRouterPort := os.Getenv("HNAKRA_ROUTER_PORT")
if defaultRouterPort == "" {
defaultRouterPort = "2048"
}
// parse router host/port
routerHost, routerPort, _ := strings.Cut(mount.Router, ":")
if routerHost == "" {
routerHost = defaultRouterHost
}
if routerPort == "" {
routerPort = defaultRouterPort
}
// get mount point
host := mount.Host
if host == "" {
host = "@"
}
if len(host) > 255 {
return nil, errors.New("mount point host cannot be longer than 255 bytes")
}
path := mount.Path
if path == "" {
path = "/"
}
if len(path) > int(protocol.MaxIntOfSize(2)) {
return nil, errors.New(fmt.Sprint (
"mount point path cannot be longer than ",
protocol.MaxIntOfSize(2), " bytes"))
}
// get user
user := mount.User
if user == "" {
user = os.Getenv("HNAKRA_USER")
}
if len(user) > 255 {
return nil, errors.New("user cannot be longer than 255 bytes")
}
// get key
key := mount.Key
if key == nil {
key, err = base64.StdEncoding.DecodeString(os.Getenv("HNAKRA_KEY"))
if err != nil { return nil, err }
}
if len(key) > 255 {
return nil, errors.New("key cannot be longer than 255 bytes")
}
// ensure name/description aren't too big
if len(mount.Name) > 255 {
return nil, errors.New("service name cannot be longer than 255 bytes")
}
if len(mount.Description) > 255 {
return nil, errors.New("service description cannot be longer than 255 bytes")
}
// connect to router
routerAddr := fmt.Sprint(routerHost, ":", routerPort)
log.Println("... dialing", routerAddr)
conn, err = tls.Dial("tcp", routerAddr, mount.TLSConfig)
if err != nil { return nil, err }
// log in
log.Println (
"... logging in as", user,
"on", scheme + "://" + host + path)
err = protocol.MessageLogin {
ID: mount.idFactory.Next(),
Version: protocol.Version { Major: 0, Minor: 0 },
User: user,
Key: key,
Name: mount.Name,
Description: mount.Description,
Scheme: scheme,
Host: host,
Path: path,
}.Send(conn)
if err != nil {
conn.Close()
return nil, err
}
// read status
message, err := protocol.ReadMessage(conn)
if err != nil {
conn.Close()
return nil, err
}
status, ok := message.(protocol.MessageStatus)
if !ok {
conn.Close()
return nil, errors.New(fmt.Sprint("router sent unknown type, expecting", protocol.TypeStatus))
}
if status.Status != protocol.StatusOk {
return nil, status
}
log.Println(".// logged in")
return conn, nil
return
}
// TODO: other protocols like gemini, websocket, ftp, etc