diff --git a/cmd/rdpgw/config/configuration.go b/cmd/rdpgw/config/configuration.go index 04cb810..c0fb9cc 100644 --- a/cmd/rdpgw/config/configuration.go +++ b/cmd/rdpgw/config/configuration.go @@ -89,12 +89,11 @@ type SecurityConfig struct { } type ClientConfig struct { - NetworkAutoDetect int `koanf:"networkautodetect"` - BandwidthAutoDetect int `koanf:"bandwidthautodetect"` - ConnectionType int `koanf:"connectiontype"` - UsernameTemplate string `koanf:"usernametemplate"` - SplitUserDomain bool `koanf:"splituserdomain"` - DefaultDomain string `koanf:"defaultdomain"` + Defaults string `koanf:"defaults"` + // kept for backwards compatibility + UsernameTemplate string `koanf:"usernametemplate"` + SplitUserDomain bool `koanf:"splituserdomain"` + DefaultDomain string `koanf:"defaultdomain"` } func ToCamel(s string) string { diff --git a/cmd/rdpgw/config/parsers/rdp.go b/cmd/rdpgw/config/parsers/rdp.go new file mode 100644 index 0000000..5f6d947 --- /dev/null +++ b/cmd/rdpgw/config/parsers/rdp.go @@ -0,0 +1,83 @@ +package parsers + +import ( + "bufio" + "bytes" + "fmt" + "sort" + "strconv" + "strings" +) + +type RDP struct{} + +func Parser() *RDP { + return &RDP{} +} + +func (p *RDP) Unmarshal(b []byte) (map[string]interface{}, error) { + r := bytes.NewReader(b) + scanner := bufio.NewScanner(r) + mp := make(map[string]interface{}) + + c := 0 + for scanner.Scan() { + c++ + line := strings.TrimSpace(scanner.Text()) + if line == "" || strings.HasPrefix(line, "#") { + continue + } + fields := strings.SplitN(line, ":", 3) + if len(fields) != 3 { + return nil, fmt.Errorf("malformed line %d: %q", c, line) + } + + key := strings.TrimSpace(fields[0]) + t := strings.TrimSpace(fields[1]) + val := strings.TrimSpace(fields[2]) + + switch t { + case "i": + intValue, err := strconv.Atoi(val) + if err != nil { + return nil, fmt.Errorf("cannot parse integer at line %d: %s", c, line) + } + mp[key] = intValue + case "s": + mp[key] = val + default: + return nil, fmt.Errorf("malformed line %d: %s", c, line) + } + } + return mp, nil +} + +func (p *RDP) Marshal(o map[string]interface{}) ([]byte, error) { + var b bytes.Buffer + + keys := make([]string, 0, len(o)) + for k := range o { + keys = append(keys, k) + } + sort.Strings(keys) + + for _, key := range keys { + v := o[key] + switch v.(type) { + case bool: + if v == true { + fmt.Fprintf(&b, "%s:i:1", key) + } else { + fmt.Fprintf(&b, "%s:i:0", key) + } + case int: + fmt.Fprintf(&b, "%s:i:%d", key, v) + case string: + fmt.Fprintf(&b, "%s:s:%s", key, v) + default: + return nil, fmt.Errorf("error marshalling") + } + fmt.Fprint(&b, "\r\n") + } + return b.Bytes(), nil +} diff --git a/cmd/rdpgw/config/parsers/rdp_test.go b/cmd/rdpgw/config/parsers/rdp_test.go new file mode 100644 index 0000000..73d3fe0 --- /dev/null +++ b/cmd/rdpgw/config/parsers/rdp_test.go @@ -0,0 +1,85 @@ +package parsers + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestUnmarshalRDPFile(t *testing.T) { + rdp := Parser() + + testCases := []struct { + name string + cfg []byte + expOutput map[string]interface{} + err error + }{ + { + name: "empty", + expOutput: map[string]interface{}{}, + }, + { + name: "string", + cfg: []byte(`username:s:user1`), + expOutput: map[string]interface{}{ + "username": "user1", + }, + }, + { + name: "integer", + cfg: []byte(`session bpp:i:32`), + expOutput: map[string]interface{}{ + "session bpp": 32, + }, + }, + { + name: "multi", + cfg: []byte("compression:i:1\r\nusername:s:user2\r\n"), + expOutput: map[string]interface{}{ + "compression": 1, + "username": "user2", + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + outMap, err := rdp.Unmarshal(tc.cfg) + assert.Equal(t, tc.err, err) + assert.Equal(t, tc.expOutput, outMap) + }) + } +} + +func TestRDP_Marshal(t *testing.T) { + testCases := []struct { + name string + input map[string]interface{} + output []byte + err error + }{ + { + name: "Empty RDP", + input: map[string]interface{}{}, + output: []byte(nil), + }, + { + name: "Valid RDP all types", + input: map[string]interface{}{ + "compression": 1, + "session bpp": 32, + "username": "user1", + }, + output: []byte("compression:i:1\r\nsession bpp:i:32\r\nusername:s:user1\r\n"), + }, + } + + rdp := Parser() + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + out, err := rdp.Marshal(tc.input) + assert.Equal(t, tc.output, out) + assert.Equal(t, tc.err, err) + }) + } +} diff --git a/cmd/rdpgw/main.go b/cmd/rdpgw/main.go index 66e8c6e..6aed44c 100644 --- a/cmd/rdpgw/main.go +++ b/cmd/rdpgw/main.go @@ -108,14 +108,12 @@ func main() { Hosts: conf.Server.Hosts, HostSelection: conf.Server.HostSelection, RdpOpts: web.RdpOpts{ - UsernameTemplate: conf.Client.UsernameTemplate, - SplitUserDomain: conf.Client.SplitUserDomain, - DefaultDomain: conf.Client.DefaultDomain, - NetworkAutoDetect: conf.Client.NetworkAutoDetect, - BandwidthAutoDetect: conf.Client.BandwidthAutoDetect, - ConnectionType: conf.Client.ConnectionType, + UsernameTemplate: conf.Client.UsernameTemplate, + SplitUserDomain: conf.Client.SplitUserDomain, + DefaultDomain: conf.Client.DefaultDomain, }, GatewayAddress: url, + TemplateFile: conf.Client.Defaults, } if conf.Caps.TokenAuth { diff --git a/cmd/rdpgw/rdp/rdp.go b/cmd/rdpgw/rdp/rdp.go new file mode 100644 index 0000000..fe0f29d --- /dev/null +++ b/cmd/rdpgw/rdp/rdp.go @@ -0,0 +1,204 @@ +package rdp + +import ( + "errors" + "fmt" + "github.com/fatih/structs" + "log" + "reflect" + "strconv" + "strings" +) + +const ( + CRLF = "\r\n" +) + +const ( + SourceNTLM int = iota + SourceSmartCard + SourceCurrent + SourceBasic + SourceUserSelect + SourceCookie +) + +type RdpSettings struct { + GatewayHostname string `rdp:"gatewayhostname"` + FullAddress string `rdp:"full address"` + AlternateFullAddress string `rdp:"alternate full address"` + Username string `rdp:"username"` + Domain string `rdp:"domain"` + GatewayCredentialsSource int `rdp:"gatewaycredentialssource" default:"0"` + GatewayCredentialMethod int `rdp:"gatewayprofileusagemethod" default:"0"` + GatewayUsageMethod int `rdp:"gatewayusagemethod" default:"0"` + GatewayAccessToken string `rdp:"gatewayaccesstoken"` + PromptCredentialsOnce bool `rdp:"promptcredentialonce" default:"true"` + AuthenticationLevel int `rdp:"authentication level" default:"3"` + EnableCredSSPSupport bool `rdp:"enablecredsspsupport" default:"true"` + EnableRdsAasAuth bool `rdp:"enablerdsaadauth" default:"false"` + DisableConnectionSharing bool `rdp:"disableconnectionsharing" default:"false"` + AlternateShell string `rdp:"alternate shell"` + AutoReconnectionEnabled bool `rdp:"autoreconnectionenabled" default:"true"` + BandwidthAutodetect bool `rdp:"bandwidthautodetect" default:"true"` + NetworkAutodetect bool `rdp:"networkautodetect" default:"true"` + Compression bool `rdp:"compression" default:"true"` + VideoPlaybackMode bool `rdp:"videoplaybackmode" default:"true"` + ConnectionType int `rdp:"connection type" default:"2"` + AudioCaptureMode bool `rdp:"audiocapturemode" default:"false"` + EncodeRedirectedVideoCapture bool `rdp:"encode redirected video capture" default:"true"` + RedirectedVideoCaptureEncodingQuality int `rdp:"redirected video capture encoding quality" default:"0"` + AudioMode int `rdp:"audiomode" default:"0"` + CameraStoreRedirect string `rdp:"camerastoredirect" default:"false"` + DeviceStoreRedirect string `rdp:"devicestoredirect" default:"false"` + DriveStoreRedirect string `rdp:"drivestoredirect" default:"false"` + KeyboardHook int `rdp:"keyboardhook" default:"2"` + RedirectClipboard bool `rdp:"redirectclipboard" default:"true"` + RedirectComPorts bool `rdp:"redirectcomports" default:"false"` + RedirectLocation bool `rdp:"redirectlocation" default:"false"` + RedirectPrinters bool `rdp:"redirectprinters" default:"true"` + RedirectSmartcards bool `rdp:"redirectsmartcards" default:"true"` + RedirectWebAuthn bool `rdp:"redirectwebauthn" default:"true"` + UsbDeviceStoRedirect string `rdp:"usbdevicestoredirect"` + UseMultimon bool `rdp:"use multimon" default:"false"` + SelectedMonitors string `rdp:"selectedmonitors"` + MaximizeToCurrentDisplays bool `rdp:"maximizetocurrentdisplays" default:"false"` + SingleMonInWindowedMode bool `rdp:"singlemoninwindowedmode" default:"0"` + ScreenModeId int `rdp:"screen mode id" default:"2"` + SmartSizing bool `rdp:"smart sizing" default:"false"` + DynamicResolution bool `rdp:"dynamic resolution" default:"true"` + DesktopSizeId int `rdp:"desktop size id"` + DesktopHeight int `rdp:"desktopheight"` + DesktopWidth int `rdp:"desktopwidth"` + DesktopScaleFactor int `rdp:"desktopscalefactor"` + BitmapCacheSize int `rdp:"bitmapcachesize" default:"1500"` + RemoteApplicationCmdLine string `rdp:"remoteapplicationcmdline"` + RemoteAppExpandWorkingDir bool `rdp:"remoteapplicationexpandworkingdir" default:"true"` + RemoteApplicationFile string `rdp:"remoteapplicationfile" default:"true"` + RemoteApplicationIcon string `rdp:"remoteapplicationicon"` + RemoteApplicationMode bool `rdp:"remoteapplicationmode" default:"true"` + RemoteApplicationName string `rdp:"remoteapplicationname"` + RemoteApplicationProgram string `rdp:"remoteapplicationprogram"` +} + +type RdpBuilder struct { + Settings RdpSettings +} + +func NewRdp() *RdpBuilder { + c := RdpSettings{} + + initStruct(&c) + + return &RdpBuilder{ + Settings: c, + } +} + +func (rb *RdpBuilder) String() string { + var sb strings.Builder + + addStructToString(rb.Settings, &sb) + + return sb.String() +} + +func addStructToString(st interface{}, sb *strings.Builder) { + s := structs.New(st) + for _, f := range s.Fields() { + if isZero(f) { + continue + } + sb.WriteString(f.Tag("rdp")) + sb.WriteString(":") + + switch f.Kind() { + case reflect.String: + sb.WriteString("s:") + sb.WriteString(f.Value().(string)) + case reflect.Int: + sb.WriteString("i:") + fmt.Fprintf(sb, "%d", f.Value()) + case reflect.Bool: + sb.WriteString("i:") + if f.Value().(bool) { + sb.WriteString("1") + } else { + sb.WriteString("0") + + } + } + sb.WriteString(CRLF) + } +} + +func isZero(f *structs.Field) bool { + t := f.Tag("default") + if t == "" { + return f.IsZero() + } + + switch f.Kind() { + case reflect.String: + if f.Value().(string) != t { + return false + } + return true + case reflect.Int: + i, err := strconv.Atoi(t) + if err != nil { + log.Fatalf("runtime error: default %s is not an integer", t) + } + if f.Value().(int) != i { + return false + } + return true + case reflect.Bool: + b := false + if t == "true" || t == "1" { + b = true + } + if f.Value().(bool) != b { + return false + } + return true + } + + return f.IsZero() +} + +func initStruct(st interface{}) { + s := structs.New(st) + for _, f := range s.Fields() { + t := f.Tag("default") + if t == "" { + continue + } + + err := setVariable(f, t) + if err != nil { + log.Fatalf("cannot init rdp struct: %s", err) + } + } +} + +func setVariable(f *structs.Field, v string) error { + switch f.Kind() { + case reflect.String: + return f.Set(v) + case reflect.Int: + i, err := strconv.Atoi(v) + if err != nil { + return err + } + return f.Set(i) + case reflect.Bool: + b := false + if v == "true" || v == "1" { + b = true + } + return f.Set(b) + default: + return errors.New("invalid field type") + } +} diff --git a/cmd/rdpgw/web/rdp_test.go b/cmd/rdpgw/rdp/rdp_test.go similarity index 67% rename from cmd/rdpgw/web/rdp_test.go rename to cmd/rdpgw/rdp/rdp_test.go index 5eff8b7..fc29bcb 100644 --- a/cmd/rdpgw/web/rdp_test.go +++ b/cmd/rdpgw/rdp/rdp_test.go @@ -1,4 +1,4 @@ -package web +package rdp import ( "log" @@ -12,18 +12,18 @@ const ( func TestRdpBuilder(t *testing.T) { builder := NewRdp() - builder.Connection.GatewayHostname = "my.yahoo.com" - builder.Session.AutoReconnectionEnabled = true - builder.Display.SmartSizing = true + builder.Settings.GatewayHostname = "my.yahoo.com" + builder.Settings.AutoReconnectionEnabled = true + builder.Settings.SmartSizing = true s := builder.String() - if !strings.Contains(s, "gatewayhostname:s:"+GatewayHostName+crlf) { + if !strings.Contains(s, "gatewayhostname:s:"+GatewayHostName+CRLF) { t.Fatalf("%s does not contain `gatewayhostname:s:%s", s, GatewayHostName) } if strings.Contains(s, "autoreconnectionenabled") { t.Fatalf("autoreconnectionenabled is in %s, but is default value", s) } - if !strings.Contains(s, "smart sizing:i:1"+crlf) { + if !strings.Contains(s, "smart sizing:i:1"+CRLF) { t.Fatalf("%s does not contain smart sizing:i:1", s) } @@ -31,7 +31,7 @@ func TestRdpBuilder(t *testing.T) { } func TestInitStruct(t *testing.T) { - conn := RdpConnection{} + conn := RdpSettings{} initStruct(&conn) if conn.PromptCredentialsOnce != true { diff --git a/cmd/rdpgw/web/rdp.go b/cmd/rdpgw/web/rdp.go deleted file mode 100644 index 7b553c7..0000000 --- a/cmd/rdpgw/web/rdp.go +++ /dev/null @@ -1,229 +0,0 @@ -package web - -import ( - "fmt" - "github.com/fatih/structs" - "log" - "reflect" - "strconv" - "strings" -) - -const ( - crlf = "\r\n" -) - -const ( - SourceNTLM int = iota - SourceSmartCard - SourceCurrent - SourceBasic - SourceUserSelect - SourceCookie -) - -type RdpConnection struct { - GatewayHostname string `rdp:"gatewayhostname"` - FullAddress string `rdp:"full address"` - AlternateFullAddress string `rdp:"alternate full address"` - Username string `rdp:"username"` - Domain string `rdp:"domain"` - GatewayCredentialsSource int `rdp:"gatewaycredentialssource" default:"0"` - GatewayCredentialMethod int `rdp:"gatewayprofileusagemethod" default:"0"` - GatewayUsageMethod int `rdp:"gatewayusagemethod" default:"0"` - GatewayAccessToken string `rdp:"gatewayaccesstoken"` - PromptCredentialsOnce bool `rdp:"promptcredentialonce" default:"true"` - AuthenticationLevel int `rdp:"authentication level" default:"3"` - EnableCredSSPSupport bool `rdp:"enablecredsspsupport" default:"true"` - EnableRdsAasAuth bool `rdp:"enablerdsaadauth" default:"false"` - DisableConnectionSharing bool `rdp:"disableconnectionsharing" default:"false"` - AlternateShell string `rdp:"alternate shell"` -} - -type RdpSession struct { - AutoReconnectionEnabled bool `rdp:"autoreconnectionenabled" default:"true"` - BandwidthAutodetect bool `rdp:"bandwidthautodetect" default:"true"` - NetworkAutodetect bool `rdp:"networkautodetect" default:"true"` - Compression bool `rdp:"compression" default:"true"` - VideoPlaybackMode bool `rdp:"videoplaybackmode" default:"true"` - ConnectionType int `rdp:"connection type" default:"2"` -} - -type RdpDeviceRedirect struct { - AudioCaptureMode bool `rdp:"audiocapturemode" default:"false"` - EncodeRedirectedVideoCapture bool `rdp:"encode redirected video capture" default:"true"` - RedirectedVideoCaptureEncodingQuality int `rdp:"redirected video capture encoding quality" default:"0"` - AudioMode int `rdp:"audiomode" default:"0"` - CameraStoreRedirect string `rdp:"camerastoredirect" default:"false"` - DeviceStoreRedirect string `rdp:"devicestoredirect" default:"false"` - DriveStoreRedirect string `rdp:"drivestoredirect" default:"false"` - KeyboardHook int `rdp:"keyboardhook" default:"2"` - RedirectClipboard bool `rdp:"redirectclipboard" default:"true"` - RedirectComPorts bool `rdp:"redirectcomports" default:"false"` - RedirectLocation bool `rdp:"redirectlocation" default:"false"` - RedirectPrinters bool `rdp:"redirectprinters" default:"true"` - RedirectSmartcards bool `rdp:"redirectsmartcards" default:"true"` - RedirectWebAuthn bool `rdp:"redirectwebauthn" default:"true"` - UsbDeviceStoRedirect string `rdp:"usbdevicestoredirect"` -} - -type RdpDisplay struct { - UseMultimon bool `rdp:"use multimon" default:"false"` - SelectedMonitors string `rdp:"selectedmonitors"` - MaximizeToCurrentDisplays bool `rdp:"maximizetocurrentdisplays" default:"false"` - SingleMonInWindowedMode bool `rdp:"singlemoninwindowedmode" default:"0"` - ScreenModeId int `rdp:"screen mode id" default:"2"` - SmartSizing bool `rdp:"smart sizing" default:"false"` - DynamicResolution bool `rdp:"dynamic resolution" default:"true"` - DesktopSizeId int `rdp:"desktop size id"` - DesktopHeight int `rdp:"desktopheight"` - DesktopWidth int `rdp:"desktopwidth"` - DesktopScaleFactor int `rdp:"desktopscalefactor"` - BitmapCacheSize int `rdp:"bitmapcachesize" default:"1500"` -} - -type RdpRemoteApp struct { - RemoteApplicationCmdLine string `rdp:"remoteapplicationcmdline"` - RemoteAppExpandWorkingDir bool `rdp:"remoteapplicationexpandworkingdir" default:"true"` - RemoteApplicationFile string `rdp:"remoteapplicationfile" default:"true"` - RemoteApplicationIcon string `rdp:"remoteapplicationicon"` - RemoteApplicationMode bool `rdp:"remoteapplicationmode" default:"true"` - RemoteApplicationName string `rdp:"remoteapplicationname"` - RemoteApplicationProgram string `rdp:"remoteapplicationprogram"` -} - -type RdpBuilder struct { - Connection RdpConnection - Session RdpSession - DeviceRedirect RdpDeviceRedirect - Display RdpDisplay - RemoteApp RdpRemoteApp -} - -func NewRdp() *RdpBuilder { - c := RdpConnection{} - s := RdpSession{} - dr := RdpDeviceRedirect{} - disp := RdpDisplay{} - ra := RdpRemoteApp{} - - initStruct(&c) - initStruct(&s) - initStruct(&dr) - initStruct(&disp) - initStruct(&ra) - - return &RdpBuilder{ - Connection: c, - Session: s, - DeviceRedirect: dr, - Display: disp, - RemoteApp: ra, - } -} - -func (rb *RdpBuilder) String() string { - var sb strings.Builder - - addStructToString(rb.Connection, &sb) - addStructToString(rb.Session, &sb) - addStructToString(rb.DeviceRedirect, &sb) - addStructToString(rb.Display, &sb) - addStructToString(rb.RemoteApp, &sb) - - return sb.String() -} - -func addStructToString(st interface{}, sb *strings.Builder) { - s := structs.New(st) - for _, f := range s.Fields() { - if isZero(f) { - continue - } - sb.WriteString(f.Tag("rdp")) - sb.WriteString(":") - - switch f.Kind() { - case reflect.String: - sb.WriteString("s:") - sb.WriteString(f.Value().(string)) - case reflect.Int: - sb.WriteString("i:") - fmt.Fprintf(sb, "%d", f.Value()) - case reflect.Bool: - sb.WriteString("i:") - if f.Value().(bool) { - sb.WriteString("1") - } else { - sb.WriteString("0") - - } - } - sb.WriteString(crlf) - } -} - -func isZero(f *structs.Field) bool { - t := f.Tag("default") - if t == "" { - return f.IsZero() - } - - switch f.Kind() { - case reflect.String: - if f.Value().(string) != t { - return false - } - return true - case reflect.Int: - i, err := strconv.Atoi(t) - if err != nil { - log.Fatalf("runtime error: default %s is not an integer", t) - } - if f.Value().(int) != i { - return false - } - return true - case reflect.Bool: - b := false - if t == "true" || t == "1" { - b = true - } - if f.Value().(bool) != b { - return false - } - return true - } - - return f.IsZero() -} - -func initStruct(st interface{}) { - s := structs.New(st) - for _, f := range s.Fields() { - t := f.Tag("default") - if t == "" { - continue - } - - switch f.Kind() { - case reflect.String: - f.Set(t) - case reflect.Int: - i, err := strconv.Atoi(t) - if err != nil { - log.Fatalf("runtime error: default %s is not an integer", t) - } - f.Set(i) - case reflect.Bool: - b := false - if t == "true" || t == "1" { - b = true - } - err := f.Set(b) - if err != nil { - log.Fatalf("Cannot set bool field") - } - } - } -} diff --git a/cmd/rdpgw/web/web.go b/cmd/rdpgw/web/web.go index 0c87206..4b8fe78 100644 --- a/cmd/rdpgw/web/web.go +++ b/cmd/rdpgw/web/web.go @@ -5,7 +5,12 @@ import ( "encoding/hex" "errors" "fmt" + "github.com/bolkedebruin/rdpgw/cmd/rdpgw/config/parsers" "github.com/bolkedebruin/rdpgw/cmd/rdpgw/identity" + "github.com/bolkedebruin/rdpgw/cmd/rdpgw/rdp" + "github.com/knadh/koanf/providers/file" + "github.com/knadh/koanf/v2" + "hash/maphash" "log" "math/rand" "net/http" @@ -28,6 +33,7 @@ type Config struct { HostSelection string GatewayAddress *url.URL RdpOpts RdpOpts + TemplateFile string } type RdpOpts struct { @@ -49,12 +55,14 @@ type Handler struct { hosts []string hostSelection string rdpOpts RdpOpts + rdpDefaults string } func (c *Config) NewHandler() *Handler { if len(c.Hosts) < 1 { log.Fatal("Not enough hosts to connect to specified") } + return &Handler{ paaTokenGenerator: c.PAATokenGenerator, enableUserToken: c.EnableUserToken, @@ -65,12 +73,13 @@ func (c *Config) NewHandler() *Handler { hosts: c.Hosts, hostSelection: c.HostSelection, rdpOpts: c.RdpOpts, + rdpDefaults: c.TemplateFile, } } func (h *Handler) selectRandomHost() string { - rand.Seed(time.Now().Unix()) - host := h.hosts[rand.Intn(len(h.hosts))] + r := rand.New(rand.NewSource(int64(new(maphash.Hash).Sum64()))) + host := h.hosts[r.Intn(len(h.hosts))] return host } @@ -188,20 +197,25 @@ func (h *Handler) HandleDownload(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Disposition", "attachment; filename="+fn) w.Header().Set("Content-Type", "application/x-rdp") - rdp := NewRdp() - rdp.Connection.Username = render - rdp.Connection.Domain = domain - rdp.Connection.FullAddress = host - rdp.Connection.GatewayHostname = h.gatewayAddress.Host - rdp.Connection.GatewayCredentialsSource = SourceCookie - rdp.Connection.GatewayAccessToken = token - rdp.Connection.GatewayCredentialMethod = 1 - rdp.Connection.GatewayUsageMethod = 1 - rdp.Session.NetworkAutodetect = opts.NetworkAutoDetect != 0 - rdp.Session.BandwidthAutodetect = opts.BandwidthAutoDetect != 0 - rdp.Session.ConnectionType = opts.ConnectionType - rdp.Display.SmartSizing = true - rdp.Display.BitmapCacheSize = 32000 + d := rdp.NewRdp() - http.ServeContent(w, r, fn, time.Now(), strings.NewReader(rdp.String())) + if h.rdpDefaults != "" { + var k = koanf.New(".") + if err := k.Load(file.Provider(h.rdpDefaults), parsers.Parser()); err != nil { + log.Fatalf("cannot load rdp template file from %s", h.rdpDefaults) + } + tag := koanf.UnmarshalConf{Tag: "rdp"} + k.UnmarshalWithConf("", &d.Settings, tag) + } + + d.Settings.Username = render + d.Settings.Domain = domain + d.Settings.FullAddress = host + d.Settings.GatewayHostname = h.gatewayAddress.Host + d.Settings.GatewayCredentialsSource = rdp.SourceCookie + d.Settings.GatewayAccessToken = token + d.Settings.GatewayCredentialMethod = 1 + d.Settings.GatewayUsageMethod = 1 + + http.ServeContent(w, r, fn, time.Now(), strings.NewReader(d.String())) } diff --git a/cmd/rdpgw/web/web_test.go b/cmd/rdpgw/web/web_test.go index 02aae98..f57cebf 100644 --- a/cmd/rdpgw/web/web_test.go +++ b/cmd/rdpgw/web/web_test.go @@ -3,6 +3,7 @@ package web import ( "context" "github.com/bolkedebruin/rdpgw/cmd/rdpgw/identity" + "github.com/bolkedebruin/rdpgw/cmd/rdpgw/rdp" "github.com/bolkedebruin/rdpgw/cmd/rdpgw/security" "net/http" "net/http/httptest" @@ -149,7 +150,7 @@ func TestHandler_HandleDownload(t *testing.T) { t.Errorf("content disposition is nil") } - data := rdpToMap(strings.Split(rr.Body.String(), crlf)) + data := rdpToMap(strings.Split(rr.Body.String(), rdp.CRLF)) if data["username"] != testuser { t.Errorf("username key in rdp does not match: got %v want %v", data["username"], testuser) } diff --git a/go.mod b/go.mod index 9961b59..5b34797 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,7 @@ require ( github.com/msteinert/pam v1.0.0 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/prometheus/client_golang v1.15.0 + github.com/stretchr/testify v1.8.1 github.com/thought-machine/go-flags v1.6.2 golang.org/x/crypto v0.8.0 golang.org/x/oauth2 v0.7.0 @@ -31,6 +32,7 @@ require ( require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/gorilla/securecookie v1.1.1 // indirect @@ -43,6 +45,7 @@ require ( github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect