diff --git a/service/mount.go b/service/mount.go new file mode 100644 index 0000000..e2bcef0 --- /dev/null +++ b/service/mount.go @@ -0,0 +1,185 @@ +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 mount with the specified name and description. +func M (name, description string) Mount { + return Mount { + Name: name, + Description: description, + } +} + +// 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 +} + +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" + } + 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 +} diff --git a/service/service.go b/service/service.go index c7b7f59..7bffa30 100644 --- a/service/service.go +++ b/service/service.go @@ -1,188 +1,2 @@ // Package service provides a toolkit for creating Hnakra services. 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 mount with the specified name and description. -func M (name, description string) Mount { - return Mount { - Name: name, - Description: description, - } -} - -// 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 -} - -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" - } - 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 -} - -// TODO: other protocols like gemini, websocket, ftp, etc