Repository: jkroepke/openvpn-auth-oauth2 (go)

Issue:
Pass-Through : Send welcome message to client
### Problem Statement

When using the passthrough fonctionnality, the passthrough server don't transmit the welcome message send by the OpenVPN Management Interface.
For manual connection, it can be a bit confusing.
For script connection, it can be blocking because of the script waiting the welcome message, or the end of it, and never receive it.


### Proposed Solution

In internal/openvpn/passthrough.go
On connection to Management Interface, read input and send it to client

### Additional information

Example : 
Following script is used to send command to OpenVPN Management Interface (Component of https://github.com/d3vilh/openvpn-ui) : 
https://github.com/d3vilh/openvpn-server-config/blob/main/server/mi/client.go#L98

At line 98, it read the welcome message before sending command, but never receive it from the PassThrough 
`buf := bufio.NewReader(conn)
	buf.ReadString('\n') //read welcome message
	err = SendCommand(conn, cmd)` 


### Acceptance Criteria

_No response_

Pull request:
passthrough: Add welcome message
#### What this PR does / why we need it

#### Which issue this PR fixes

*(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close that issue when PR gets merged)*: fixes #

- fixes #219

#### Special notes for your reviewer

#### Particularly user-facing changes

#### Checklist

Complete these before marking the PR as `ready to review`:

<!-- [Place an '[x]' (no spaces) in all applicable fields.] -->

- [ ] [DCO](https://github.com/prometheus-community/helm-charts/blob/main/CONTRIBUTING.md#sign-off-your-work) signed
- [ ] The PR title has a summary of the changes
- [ ] The PR body has a summary to reflect any significant (and particularly user-facing) changes introduced by this PR


Patch:
diff --git a/internal/openvpn/passthrough.go b/internal/openvpn/passthrough.go
index 594fa69..9c2c021 100644
--- a/internal/openvpn/passthrough.go
+++ b/internal/openvpn/passthrough.go
@@ -72,7 +72,7 @@ func (c *Client) handlePassthrough() {
 				}
 
 				connMu.Lock()
-				if conn == nil {
+				if conn == nil || c.passthroughConnected.Load() == 0 {
 					continue
 				}
 
@@ -102,6 +102,8 @@ func (c *Client) handlePassthrough() {
 
 		c.handlePassthroughClient(conn)
 
+		c.passthroughConnected.Store(0)
+
 		connMu.Lock()
 		conn = nil
 		connMu.Unlock()
@@ -141,6 +143,9 @@ func (c *Client) handlePassthroughClient(conn net.Conn) {
 		return
 	}
 
+	c.writeToPassthroughClient(">INFO:OpenVPN Management Interface Version 5 -- type 'help' for more info")
+	c.passthroughConnected.CompareAndSwap(0, 1)
+
 	if err = c.handlePassthroughClientCommands(conn, logger, scanner); err != nil {
 		logger.Warn(err.Error())
 	}
@@ -156,7 +161,7 @@ func (c *Client) handlePassthroughClientCommands(conn net.Conn, logger *slog.Log
 	)
 
 	for scanner.Scan() {
-		line = scanner.Text()
+		line = strings.TrimSpace(scanner.Text())
 
 		logger.LogAttrs(c.ctx, slog.LevelDebug, "received command", slog.String("command", line))
 
@@ -166,11 +171,11 @@ func (c *Client) handlePassthroughClientCommands(conn net.Conn, logger *slog.Log
 			logger.Warn("pass-through: client send client-deny or client-auth message, ignoring...")
 
 			continue
-		case strings.HasPrefix(line, "hold"):
+		case line == "hold":
 			c.writeToPassthroughClient("SUCCESS: hold release succeeded")
 
 			continue
-		case strings.HasPrefix(line, "exit"), strings.HasPrefix(line, "quit"):
+		case line == "exit", line == "quit":
 			conn.Close()
 
 			return nil
diff --git a/internal/openvpn/types.go b/internal/openvpn/types.go
index 909a658..a5a7e05 100644
--- a/internal/openvpn/types.go
+++ b/internal/openvpn/types.go
@@ -38,4 +38,6 @@ type Client struct {
 	commandResponseCh chan string
 	commandsCh        chan string
 	passthroughCh     chan string
+
+	passthroughConnected atomic.Uint32
 }


Test patch:
diff --git a/internal/openvpn/bench_test.go b/internal/openvpn/bench_test.go
index aa27090..32d5db2 100644
--- a/internal/openvpn/bench_test.go
+++ b/internal/openvpn/bench_test.go
@@ -267,6 +267,8 @@ func BenchmarkOpenVPNPassthrough(b *testing.B) {
 		}
 	})
 
+	testutils.ExpectMessage(b, passThroughConn, passThroughReader, ">INFO:OpenVPN Management Interface Version 5 -- type 'help' for more info")
+
 	b.ResetTimer()
 	b.StartTimer()
 
diff --git a/internal/openvpn/main_test.go b/internal/openvpn/main_test.go
index 554ee1b..bf8763c 100644
--- a/internal/openvpn/main_test.go
+++ b/internal/openvpn/main_test.go
@@ -417,6 +417,8 @@ func TestClientInvalidVersion(t *testing.T) {
 
 	for _, tt := range versions {
 		t.Run(tt.name, func(t *testing.T) {
+			conf := conf
+
 			t.Parallel()
 
 			managementInterface, err := nettest.NewLocalListener("tcp")
diff --git a/internal/openvpn/passthrough_test.go b/internal/openvpn/passthrough_test.go
index 63d3b8c..a7d9f24 100644
--- a/internal/openvpn/passthrough_test.go
+++ b/internal/openvpn/passthrough_test.go
@@ -314,6 +314,8 @@ func TestPassthroughFull(t *testing.T) {
 					)
 				}
 
+				testutils.ExpectMessage(t, passThroughConn, passThroughReader, ">INFO:OpenVPN Management Interface Version 5 -- type 'help' for more info")
+
 				for range 10 {
 					testutils.SendMessage(t, passThroughConn, "")
 					testutils.SendMessage(t, passThroughConn, "\n")
@@ -373,7 +375,7 @@ func TestPassthroughFull(t *testing.T) {
 				}
 
 				if tt.scheme == openvpn.SchemeUnix {
-					testutils.SendMessage(t, passThroughConn, "exit")
+					testutils.SendMessage(t, passThroughConn, " exit ")
 
 					stat, err := os.Stat(passThroughInterface.Addr().String())
 					if !assert.NoError(t, err) {
@@ -386,7 +388,7 @@ func TestPassthroughFull(t *testing.T) {
 					assert.Equal(t, tt.conf.OpenVpn.Passthrough.SocketGroup, strconv.Itoa(int(gid.Gid)))
 					assert.Equal(t, os.FileMode(tt.conf.OpenVpn.Passthrough.SocketMode), stat.Mode().Perm())
 				} else {
-					testutils.SendMessage(t, passThroughConn, "quit")
+					testutils.SendMessage(t, passThroughConn, " quit ")
 				}
 			}()