Skip to content

Commit b7e0b0f

Browse files
authored
Merge pull request #23 from fosrl/dev
0.7.0
2 parents 509b8de + a7eb5ff commit b7e0b0f

File tree

13 files changed

+326
-214
lines changed

13 files changed

+326
-214
lines changed

auth/manager.go

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -55,19 +55,19 @@ type AuthManager struct {
5555
secretManager *secrets.SecretManager
5656

5757
// State
58-
mu sync.RWMutex
59-
isAuthenticated bool
60-
currentUser *api.User
61-
currentOrg *api.Org
62-
organizations []api.Org
63-
isInitializing bool
64-
errorMessage *string
65-
deviceAuthCode *string
66-
deviceAuthLoginURL *string
67-
serverInfo *api.ServerInfo
68-
isServerDown bool
69-
sessionExpired bool
70-
isDeviceAuthInProgress bool
58+
mu sync.RWMutex
59+
isAuthenticated bool
60+
currentUser *api.User
61+
currentOrg *api.Org
62+
organizations []api.Org
63+
isInitializing bool
64+
errorMessage *string
65+
deviceAuthCode *string
66+
deviceAuthLoginURL *string
67+
serverInfo *api.ServerInfo
68+
isServerDown bool
69+
sessionExpired bool
70+
isDeviceAuthInProgress bool
7171
startDeviceAuthImmediately bool
7272
}
7373

@@ -830,7 +830,7 @@ func (am *AuthManager) Logout() error {
830830

831831
_ = am.secretManager.DeleteSessionToken(userID)
832832
// for backward compatibility with old servers so we dont recreate the olm
833-
// we are keeping this commented out for now so we dont remove the olm
833+
// we are keeping this commented out for now so we dont remove the olm
834834
// _ = am.secretManager.DeleteOlmCredentials(userID)
835835

836836
_ = am.accountManager.RemoveAccount(userID)

main.go

Lines changed: 45 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import (
66
"fmt"
77
"os"
88
"strconv"
9+
"syscall"
910
"time"
11+
"unsafe"
1012

1113
"github.com/fosrl/windows/api"
1214
"github.com/fosrl/windows/auth"
@@ -24,6 +26,20 @@ import (
2426
"golang.org/x/sys/windows/svc/mgr"
2527
)
2628

29+
var (
30+
user32 = syscall.NewLazyDLL("user32.dll")
31+
procMessageBoxW = user32.NewProc("MessageBoxW")
32+
)
33+
34+
const mbOK = 0x00000000
35+
36+
// showMessageBox displays a message box (used when run without UI, e.g. RequestUILaunch failed).
37+
func showMessageBox(text, caption string) {
38+
textPtr, _ := windows.UTF16PtrFromString(text)
39+
captionPtr, _ := windows.UTF16PtrFromString(caption)
40+
procMessageBoxW.Call(0, uintptr(unsafe.Pointer(textPtr)), uintptr(unsafe.Pointer(captionPtr)), mbOK)
41+
}
42+
2743
func execElevatedManagerServiceInstaller() error {
2844
path, err := os.Executable()
2945
if err != nil {
@@ -79,15 +95,23 @@ func main() {
7995
if err != nil {
8096
if err == managers.ErrManagerAlreadyRunning {
8197
logger.Info("Manager service is already running")
82-
// Wait a bit for UI to appear
83-
time.Sleep(5 * time.Second)
98+
// Request UI launch so user gets the UI from this run (e.g. first launch raced with another)
99+
time.Sleep(2 * time.Second)
100+
managers.RequestUILaunch()
84101
return
85102
}
86103
logger.Fatal("Failed to install manager service: %v", err)
87104
}
88105
logger.Info("Manager service installed successfully")
89-
// Wait a bit for service to start and UI to appear
90-
time.Sleep(5 * time.Second)
106+
// Wait for service to start and listen on the UI launch pipe, then request UI so first launch shows UI without a second run
107+
time.Sleep(2 * time.Second)
108+
if managers.RequestUILaunch() {
109+
logger.Info("UI launch requested successfully")
110+
} else {
111+
// Retry once in case the pipe wasn't ready yet
112+
time.Sleep(2 * time.Second)
113+
managers.RequestUILaunch()
114+
}
91115
return
92116
}
93117

@@ -113,21 +137,19 @@ func main() {
113137
logger.Info("Connected to manager service via IPC")
114138
// Fall through to run UI
115139
} else {
116-
// No arguments - check if manager service is running, install/start if needed
117-
// This is the normal entry point when user double-clicks the .exe
118-
serviceName := config.AppName + "Manager"
140+
// No arguments - normal entry when user double-clicks the .exe.
141+
// Try the named pipe first so standard users never need SCM or UAC when the manager is running.
142+
if managers.RequestUILaunch() {
143+
return
144+
}
119145

120-
// Try to connect to service manager
121-
// Note: Standard users should be able to connect to query services,
122-
// but may need elevation to install/start services
146+
// Pipe connect failed (manager not running or not installed). Use SCM to install/start; may require UAC.
147+
serviceName := config.AppName + "Manager"
123148
m, err := mgr.Connect()
124149
if err != nil {
125-
// If we can't connect to service manager, we can't check service status
126-
// This is unusual for standard users - they should be able to connect to query services
127150
if err == windows.ERROR_ACCESS_DENIED {
128151
logger.Info("Cannot access service manager without admin privileges")
129152
logger.Info("Attempting to install/start manager service (will show UAC prompt)...")
130-
// Try to elevate to install/start the service
131153
err = execElevatedManagerServiceInstaller()
132154
if err != nil {
133155
logger.Fatal("Failed to install/start manager service: %v\nPlease run as administrator to install the service.", err)
@@ -140,7 +162,6 @@ func main() {
140162

141163
service, err := m.OpenService(serviceName)
142164
if err != nil {
143-
// Service doesn't exist, need to install it (requires elevation)
144165
logger.Info("Manager service not found, installing...")
145166
err = execElevatedManagerServiceInstaller()
146167
if err != nil {
@@ -155,23 +176,22 @@ func main() {
155176
logger.Fatal("Failed to query service status: %v", err)
156177
}
157178

158-
// If service is already running, exit - the manager service will automatically
159-
// launch the UI for logged-in users. No UAC prompt needed.
160179
if status.State == svc.Running || status.State == svc.StartPending {
161-
logger.Info("Manager service is already running")
180+
// Service is running but pipe failed earlier; try UI launch once more
181+
if managers.RequestUILaunch() {
182+
return
183+
}
184+
logger.Error("Could not start Pangolin. Please try again or contact your administrator.")
185+
showMessageBox("Could not start Pangolin. Please try again or contact your administrator.", "Pangolin")
162186
return
163187
}
164188

165189
if status.State == svc.Stopped {
166-
// Service exists but is stopped, try to start it
167190
logger.Info("Manager service is stopped, starting...")
168191
err = service.Start()
169192
if err != nil {
170-
// If we don't have permission to start the service, try to elevate
171193
if err == windows.ERROR_ACCESS_DENIED {
172194
logger.Info("Need admin privileges to start service, requesting elevation...")
173-
// Use cmd.exe to run net start, which can be elevated to start the service
174-
// This will show a UAC prompt if needed
175195
err = elevate.ShellExecute("cmd.exe", fmt.Sprintf("/c net start \"%s\"", serviceName), "", windows.SW_HIDE)
176196
if err != nil && err != windows.ERROR_CANCELLED {
177197
logger.Fatal("Failed to start manager service (access denied): %v\nPlease start the service manually or run as administrator.", err)
@@ -180,9 +200,7 @@ func main() {
180200
logger.Info("User cancelled elevation, cannot start service")
181201
return
182202
}
183-
// Wait a moment for service to start
184203
time.Sleep(2 * time.Second)
185-
// Verify it started
186204
status, err = service.Query()
187205
if err != nil {
188206
logger.Fatal("Failed to query service status after start: %v", err)
@@ -195,14 +213,15 @@ func main() {
195213
logger.Fatal("Failed to start manager service: %v", err)
196214
}
197215
} else {
198-
// Wait a moment for service to start and launch UI
199216
logger.Info("Manager service started, UI should appear shortly")
200217
time.Sleep(2 * time.Second)
201218
}
202219
}
203220

204-
// Exit - the manager service will handle launching the UI
205-
// The manager service automatically launches UI processes for logged-in users
221+
// After install/start, try UI launch again so user gets the UI without relaunching
222+
if managers.RequestUILaunch() {
223+
return
224+
}
206225
return
207226
}
208227

managers/install.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ func InstallManager() error {
8080

8181
svcConfig := mgr.Config{
8282
ServiceType: windows.SERVICE_WIN32_OWN_PROCESS,
83-
StartType: mgr.StartManual,
83+
StartType: mgr.StartAutomatic,
8484
ErrorControl: mgr.ErrorNormal,
8585
DisplayName: config.AppName + " Manager",
8686
}

managers/ipc_client.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const (
3232
UpdateMethodType
3333
StartTunnelMethodType
3434
StopTunnelMethodType
35+
StopAllTunnelsMethodType
3536
)
3637

3738
var (
@@ -248,6 +249,18 @@ func IPCClientStopTunnel() error {
248249
return err
249250
}
250251

252+
func IPCClientStopAllTunnels() error {
253+
rpcMutex.Lock()
254+
defer rpcMutex.Unlock()
255+
256+
err := rpcEncoder.Encode(StopAllTunnelsMethodType)
257+
if err != nil {
258+
return err
259+
}
260+
err = rpcDecodeError()
261+
return err
262+
}
263+
251264
func IPCClientRegisterTunnelStateChange(cb func(state TunnelState)) *TunnelStateChangeCallback {
252265
s := &TunnelStateChangeCallback{cb}
253266
tunnelStateChangeCallbacks[s] = true

managers/ipc_server.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,36 @@ func (s *ManagerService) StopTunnel() error {
152152
return nil
153153
}
154154

155+
func (s *ManagerService) StopAllTunnels() error {
156+
tunnel.SetStateChangeCallback(func(state TunnelState) {
157+
IPCServerNotifyTunnelStateChange(state)
158+
})
159+
tunnel.SetInstallTunnelCallback(InstallTunnel)
160+
tunnel.SetUninstallTunnelCallback(func(name string) error {
161+
return UninstallTunnel(name)
162+
})
163+
164+
activeTunnelsLock.Lock()
165+
tunnelNames := make([]string, 0, len(activeTunnels))
166+
for name := range activeTunnels {
167+
tunnelNames = append(tunnelNames, name)
168+
}
169+
activeTunnelsLock.Unlock()
170+
171+
for _, name := range tunnelNames {
172+
logger.Info("Stopping tunnel: %s", name)
173+
if err := UninstallTunnel(name); err != nil {
174+
logger.Error("Failed to stop tunnel %s: %v", name, err)
175+
// Continue stopping other tunnels even if one fails
176+
} else {
177+
activeTunnelsLock.Lock()
178+
delete(activeTunnels, name)
179+
activeTunnelsLock.Unlock()
180+
}
181+
}
182+
return nil
183+
}
184+
155185
func (s *ManagerService) ServeConn(reader io.Reader, writer io.Writer) {
156186
decoder := gob.NewDecoder(reader)
157187
encoder := gob.NewEncoder(writer)
@@ -202,6 +232,12 @@ func (s *ManagerService) ServeConn(reader io.Reader, writer io.Writer) {
202232
if err != nil {
203233
return
204234
}
235+
case StopAllTunnelsMethodType:
236+
retErr := s.StopAllTunnels()
237+
err = encoder.Encode(errToString(retErr))
238+
if err != nil {
239+
return
240+
}
205241
default:
206242
return
207243
}

0 commit comments

Comments
 (0)