Skip to content

Commit 0896a22

Browse files
committed
add user-agent header
1 parent b9bc46b commit 0896a22

3 files changed

Lines changed: 186 additions & 0 deletions

File tree

pkg/config/sysinfo.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package config
2+
3+
import (
4+
"fmt"
5+
"os/exec"
6+
"runtime"
7+
"strings"
8+
"sync"
9+
)
10+
11+
var (
12+
cachedUserAgent string
13+
userAgentOnce sync.Once
14+
)
15+
16+
// GetKernelVersion returns the Linux kernel version using uname -r
17+
func GetKernelVersion() string {
18+
cmd := exec.Command("uname", "-r")
19+
output, err := cmd.Output()
20+
if err != nil {
21+
return "unknown"
22+
}
23+
return strings.TrimSpace(string(output))
24+
}
25+
26+
// GetDistroName returns the Linux distribution name
27+
func GetDistroName() string {
28+
// Try to get distro name from lsb_release
29+
cmd := exec.Command("lsb_release", "-ds")
30+
output, err := cmd.Output()
31+
if err == nil {
32+
distro := strings.TrimSpace(string(output))
33+
// Remove quotes if present
34+
distro = strings.Trim(distro, "\"")
35+
return distro
36+
}
37+
38+
// Fallback to uname -o
39+
cmd = exec.Command("uname", "-o")
40+
output, err = cmd.Output()
41+
if err != nil {
42+
return "Linux"
43+
}
44+
return strings.TrimSpace(string(output))
45+
}
46+
47+
// GetArchitecture returns the CPU architecture
48+
func GetArchitecture() string {
49+
return runtime.GOARCH
50+
}
51+
52+
// GetUserAgent constructs the user agent string according to the template:
53+
// Malwarebytes Privacy/$VERSION_NAME (github.com/Malwarebytes/mbvpn-linux; Build: $VERSION_NAME; $LINUX_DISTRO_NAME $KERNEL_VERSION $ARCH)
54+
func GetUserAgent(productVersion string) string {
55+
userAgentOnce.Do(func() {
56+
distro := GetDistroName()
57+
kernel := GetKernelVersion()
58+
arch := GetArchitecture()
59+
60+
cachedUserAgent = fmt.Sprintf(
61+
"Malwarebytes Privacy/%s (github.com/Malwarebytes/mbvpn-linux; Build: %s; %s %s %s)",
62+
productVersion,
63+
productVersion,
64+
distro,
65+
kernel,
66+
arch,
67+
)
68+
})
69+
70+
return cachedUserAgent
71+
}

pkg/config/sysinfo_test.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package config
2+
3+
import (
4+
"strings"
5+
"testing"
6+
)
7+
8+
func TestGetKernelVersion(t *testing.T) {
9+
version := GetKernelVersion()
10+
if version == "" {
11+
t.Error("Expected non-empty kernel version")
12+
}
13+
if version == "unknown" {
14+
t.Log("Warning: Could not determine kernel version")
15+
}
16+
t.Logf("Kernel version: %s", version)
17+
}
18+
19+
func TestGetDistroName(t *testing.T) {
20+
distro := GetDistroName()
21+
if distro == "" {
22+
t.Error("Expected non-empty distro name")
23+
}
24+
t.Logf("Distro name: %s", distro)
25+
}
26+
27+
func TestGetArchitecture(t *testing.T) {
28+
arch := GetArchitecture()
29+
if arch == "" {
30+
t.Error("Expected non-empty architecture")
31+
}
32+
// Common architectures
33+
validArchs := []string{"amd64", "386", "arm64", "arm", "ppc64le", "s390x"}
34+
isValid := false
35+
for _, valid := range validArchs {
36+
if arch == valid {
37+
isValid = true
38+
break
39+
}
40+
}
41+
if !isValid {
42+
t.Logf("Warning: Unusual architecture: %s", arch)
43+
}
44+
t.Logf("Architecture: %s", arch)
45+
}
46+
47+
func TestGetUserAgent(t *testing.T) {
48+
productVersion := "5.14.0"
49+
userAgent := GetUserAgent(productVersion)
50+
51+
if userAgent == "" {
52+
t.Fatal("Expected non-empty user agent")
53+
}
54+
55+
// Verify format: Malwarebytes Privacy/VERSION (github.com/Malwarebytes/mbvpn-linux; Build: VERSION; DISTRO KERNEL ARCH)
56+
if !strings.HasPrefix(userAgent, "Malwarebytes Privacy/"+productVersion) {
57+
t.Errorf("Expected user agent to start with 'Malwarebytes Privacy/%s', got: %s", productVersion, userAgent)
58+
}
59+
60+
if !strings.Contains(userAgent, "github.com/Malwarebytes/mbvpn-linux") {
61+
t.Error("Expected user agent to contain 'github.com/Malwarebytes/mbvpn-linux'")
62+
}
63+
64+
if !strings.Contains(userAgent, "Build: "+productVersion) {
65+
t.Errorf("Expected user agent to contain 'Build: %s'", productVersion)
66+
}
67+
68+
t.Logf("User agent: %s", userAgent)
69+
70+
// Test caching - should return same value
71+
userAgent2 := GetUserAgent(productVersion)
72+
if userAgent != userAgent2 {
73+
t.Error("Expected cached user agent to match first call")
74+
}
75+
}
76+
77+
func TestGetUserAgentFormat(t *testing.T) {
78+
productVersion := "5.14.0"
79+
userAgent := GetUserAgent(productVersion)
80+
81+
// Verify structure
82+
parts := strings.Split(userAgent, " (")
83+
if len(parts) != 2 {
84+
t.Fatalf("Expected user agent to have format 'PREFIX (DETAILS)', got: %s", userAgent)
85+
}
86+
87+
prefix := parts[0]
88+
if prefix != "Malwarebytes Privacy/"+productVersion {
89+
t.Errorf("Expected prefix 'Malwarebytes Privacy/%s', got: %s", productVersion, prefix)
90+
}
91+
92+
details := strings.TrimSuffix(parts[1], ")")
93+
detailParts := strings.Split(details, "; ")
94+
if len(detailParts) != 3 {
95+
t.Errorf("Expected 3 detail parts separated by '; ', got %d parts: %v", len(detailParts), detailParts)
96+
}
97+
98+
// Check first detail part
99+
if detailParts[0] != "github.com/Malwarebytes/mbvpn-linux" {
100+
t.Errorf("Expected first detail 'github.com/Malwarebytes/mbvpn-linux', got: %s", detailParts[0])
101+
}
102+
103+
// Check second detail part
104+
if !strings.HasPrefix(detailParts[1], "Build: ") {
105+
t.Errorf("Expected second detail to start with 'Build: ', got: %s", detailParts[1])
106+
}
107+
108+
// Check third detail part has distro, kernel, and arch
109+
systemInfo := detailParts[2]
110+
systemParts := strings.Fields(systemInfo)
111+
if len(systemParts) < 2 {
112+
t.Errorf("Expected system info to have at least distro and kernel, got: %s", systemInfo)
113+
}
114+
}

pkg/remote/holocron.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,7 @@ func (api *DefaultHolocron) doRequest(installationToken string, body *map[string
358358
}
359359

360360
request.Header.Set("X-Device-Bearer", fmt.Sprintf("%s|%s", installationToken, machineId))
361+
request.Header.Set("User-Agent", config.GetUserAgent(productVersion))
361362

362363
reqDump, err := httputil.DumpRequest(request, true)
363364
if err != nil {

0 commit comments

Comments
 (0)