mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-08-02 03:28:28 -04:00
@@ -22,13 +22,14 @@ type ConnectionStateChecker interface {
|
||||
}
|
||||
|
||||
type IWDAgent struct {
|
||||
conn *dbus.Conn
|
||||
objPath dbus.ObjectPath
|
||||
prompts PromptBroker
|
||||
onUserCanceled func()
|
||||
onPromptRetry func(ssid string)
|
||||
lastRequestSSID string
|
||||
stateChecker ConnectionStateChecker
|
||||
conn *dbus.Conn
|
||||
objPath dbus.ObjectPath
|
||||
prompts PromptBroker
|
||||
onUserCanceled func()
|
||||
onPromptRetry func(ssid string)
|
||||
takePendingSecret func(ssid string) (string, bool)
|
||||
lastRequestSSID string
|
||||
stateChecker ConnectionStateChecker
|
||||
}
|
||||
|
||||
const iwdAgentIntrospectXML = `
|
||||
@@ -119,6 +120,13 @@ func (a *IWDAgent) RequestPassphrase(network dbus.ObjectPath) (string, *dbus.Err
|
||||
return "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
|
||||
}
|
||||
|
||||
if a.takePendingSecret != nil {
|
||||
if psk, ok := a.takePendingSecret(ssid); ok {
|
||||
a.lastRequestSSID = ssid
|
||||
return psk, nil
|
||||
}
|
||||
}
|
||||
|
||||
if a.prompts == nil {
|
||||
if a.onUserCanceled != nil {
|
||||
a.onUserCanceled()
|
||||
@@ -126,19 +134,24 @@ func (a *IWDAgent) RequestPassphrase(network dbus.ObjectPath) (string, *dbus.Err
|
||||
return "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
|
||||
}
|
||||
|
||||
if a.lastRequestSSID == ssid {
|
||||
if a.onPromptRetry != nil {
|
||||
a.onPromptRetry(ssid)
|
||||
}
|
||||
retry := a.lastRequestSSID == ssid
|
||||
if retry && a.onPromptRetry != nil {
|
||||
a.onPromptRetry(ssid)
|
||||
}
|
||||
a.lastRequestSSID = ssid
|
||||
|
||||
reason := ""
|
||||
if retry {
|
||||
reason = "wrong-password"
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
token, err := a.prompts.Ask(ctx, PromptRequest{
|
||||
SSID: ssid,
|
||||
Fields: []string{"psk"},
|
||||
Reason: reason,
|
||||
})
|
||||
if err != nil {
|
||||
if a.onUserCanceled != nil {
|
||||
|
||||
@@ -24,6 +24,7 @@ const (
|
||||
type connectAttempt struct {
|
||||
ssid string
|
||||
netPath dbus.ObjectPath
|
||||
saved bool
|
||||
start time.Time
|
||||
deadline time.Time
|
||||
sawAuthish bool
|
||||
@@ -53,6 +54,37 @@ type IWDBackend struct {
|
||||
attemptMutex sync.RWMutex
|
||||
recentScans map[string]time.Time
|
||||
recentScansMu sync.Mutex
|
||||
pendingPSK *pendingReplacementPSK
|
||||
pendingPSKMu sync.Mutex
|
||||
}
|
||||
|
||||
type pendingReplacementPSK struct {
|
||||
ssid string
|
||||
psk string
|
||||
expires time.Time
|
||||
}
|
||||
|
||||
func (b *IWDBackend) storePendingPSK(ssid, psk string) {
|
||||
b.pendingPSKMu.Lock()
|
||||
b.pendingPSK = &pendingReplacementPSK{
|
||||
ssid: ssid,
|
||||
psk: psk,
|
||||
expires: time.Now().Add(30 * time.Second),
|
||||
}
|
||||
b.pendingPSKMu.Unlock()
|
||||
}
|
||||
|
||||
func (b *IWDBackend) takePendingPSK(ssid string) (string, bool) {
|
||||
b.pendingPSKMu.Lock()
|
||||
defer b.pendingPSKMu.Unlock()
|
||||
|
||||
pending := b.pendingPSK
|
||||
if pending == nil || pending.ssid != ssid || time.Now().After(pending.expires) {
|
||||
return "", false
|
||||
}
|
||||
|
||||
b.pendingPSK = nil
|
||||
return pending.psk, true
|
||||
}
|
||||
|
||||
func NewIWDBackend() (*IWDBackend, error) {
|
||||
|
||||
@@ -18,6 +18,7 @@ func (b *IWDBackend) StartMonitoring(onStateChange func()) error {
|
||||
}
|
||||
agent.onUserCanceled = b.OnUserCanceledPrompt
|
||||
agent.onPromptRetry = b.OnPromptRetry
|
||||
agent.takePendingSecret = b.takePendingPSK
|
||||
b.iwdAgent = agent
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -314,6 +315,133 @@ func TestConnectAttempt_Finalization(t *testing.T) {
|
||||
backend.stateMutex.RUnlock()
|
||||
}
|
||||
|
||||
func TestIWDBackend_PendingPSK(t *testing.T) {
|
||||
backend, _ := NewIWDBackend()
|
||||
|
||||
_, ok := backend.takePendingPSK("Home")
|
||||
assert.False(t, ok)
|
||||
|
||||
backend.storePendingPSK("Home", "newpass")
|
||||
|
||||
_, ok = backend.takePendingPSK("Other")
|
||||
assert.False(t, ok, "pending PSK should not match a different SSID")
|
||||
|
||||
psk, ok := backend.takePendingPSK("Home")
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, "newpass", psk)
|
||||
|
||||
_, ok = backend.takePendingPSK("Home")
|
||||
assert.False(t, ok, "pending PSK should be consumed on take")
|
||||
|
||||
backend.storePendingPSK("Home", "newpass")
|
||||
backend.pendingPSKMu.Lock()
|
||||
backend.pendingPSK.expires = time.Now().Add(-time.Second)
|
||||
backend.pendingPSKMu.Unlock()
|
||||
|
||||
_, ok = backend.takePendingPSK("Home")
|
||||
assert.False(t, ok, "expired pending PSK should not be returned")
|
||||
}
|
||||
|
||||
type fakePromptBroker struct {
|
||||
asked chan PromptRequest
|
||||
reply PromptReply
|
||||
replyErr error
|
||||
}
|
||||
|
||||
func (f *fakePromptBroker) Ask(ctx context.Context, req PromptRequest) (string, error) {
|
||||
f.asked <- req
|
||||
return "token", nil
|
||||
}
|
||||
|
||||
func (f *fakePromptBroker) Wait(ctx context.Context, token string) (PromptReply, error) {
|
||||
return f.reply, f.replyErr
|
||||
}
|
||||
|
||||
func (f *fakePromptBroker) Resolve(token string, reply PromptReply) error { return nil }
|
||||
|
||||
func (f *fakePromptBroker) Cancel(path string, setting string) error { return nil }
|
||||
|
||||
func TestIWDBackend_BadCredentialsSavedNetwork_PromptsReplacement(t *testing.T) {
|
||||
backend, _ := NewIWDBackend()
|
||||
backend.state = &BackendState{}
|
||||
broker := &fakePromptBroker{
|
||||
asked: make(chan PromptRequest, 1),
|
||||
reply: PromptReply{Cancel: true},
|
||||
}
|
||||
backend.promptBroker = broker
|
||||
|
||||
att := &connectAttempt{
|
||||
ssid: "Home",
|
||||
netPath: "/test",
|
||||
saved: true,
|
||||
start: time.Now(),
|
||||
deadline: time.Now().Add(15 * time.Second),
|
||||
}
|
||||
|
||||
backend.finalizeAttempt(att, "bad-credentials")
|
||||
|
||||
select {
|
||||
case req := <-broker.asked:
|
||||
assert.Equal(t, "Home", req.SSID)
|
||||
assert.Equal(t, "wrong-password", req.Reason)
|
||||
assert.Equal(t, []string{"psk"}, req.Fields)
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("expected replacement credentials prompt for saved network")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIWDBackend_BadCredentialsUnsavedNetwork_NoReplacementPrompt(t *testing.T) {
|
||||
backend, _ := NewIWDBackend()
|
||||
backend.state = &BackendState{}
|
||||
broker := &fakePromptBroker{
|
||||
asked: make(chan PromptRequest, 1),
|
||||
reply: PromptReply{Cancel: true},
|
||||
}
|
||||
backend.promptBroker = broker
|
||||
|
||||
att := &connectAttempt{
|
||||
ssid: "Home",
|
||||
netPath: "/test",
|
||||
start: time.Now(),
|
||||
deadline: time.Now().Add(15 * time.Second),
|
||||
}
|
||||
|
||||
backend.finalizeAttempt(att, "bad-credentials")
|
||||
|
||||
select {
|
||||
case <-broker.asked:
|
||||
t.Fatal("unsaved network should not trigger a replacement prompt")
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
}
|
||||
}
|
||||
|
||||
func TestIWDBackend_BadCredentialsAfterPromptRetry_NoReplacementPrompt(t *testing.T) {
|
||||
backend, _ := NewIWDBackend()
|
||||
backend.state = &BackendState{}
|
||||
broker := &fakePromptBroker{
|
||||
asked: make(chan PromptRequest, 1),
|
||||
reply: PromptReply{Cancel: true},
|
||||
}
|
||||
backend.promptBroker = broker
|
||||
|
||||
att := &connectAttempt{
|
||||
ssid: "Home",
|
||||
netPath: "/test",
|
||||
saved: true,
|
||||
sawPromptRetry: true,
|
||||
start: time.Now(),
|
||||
deadline: time.Now().Add(15 * time.Second),
|
||||
}
|
||||
|
||||
backend.finalizeAttempt(att, "bad-credentials")
|
||||
|
||||
select {
|
||||
case <-broker.asked:
|
||||
t.Fatal("attempt that already prompted should not trigger a replacement prompt")
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnectAttempt_DoubleFinalization(t *testing.T) {
|
||||
backend, _ := NewIWDBackend()
|
||||
backend.state = &BackendState{}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||
"github.com/godbus/dbus/v5"
|
||||
)
|
||||
|
||||
@@ -476,6 +478,73 @@ func (b *IWDBackend) finalizeAttempt(att *connectAttempt, code string) {
|
||||
if b.onStateChange != nil {
|
||||
b.onStateChange()
|
||||
}
|
||||
|
||||
if code == errdefs.ErrBadCredentials {
|
||||
b.maybeReplaceSavedPSK(att)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *IWDBackend) maybeReplaceSavedPSK(att *connectAttempt) {
|
||||
if b.promptBroker == nil || !att.saved {
|
||||
return
|
||||
}
|
||||
|
||||
att.mu.Lock()
|
||||
prompted := att.sawPromptRetry
|
||||
att.mu.Unlock()
|
||||
if prompted {
|
||||
return
|
||||
}
|
||||
|
||||
b.sigWG.Add(1)
|
||||
go func() {
|
||||
defer b.sigWG.Done()
|
||||
b.requestReplacementPSK(att.ssid)
|
||||
}()
|
||||
}
|
||||
|
||||
func (b *IWDBackend) requestReplacementPSK(ssid string) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
go func() {
|
||||
select {
|
||||
case <-b.stopChan:
|
||||
cancel()
|
||||
case <-ctx.Done():
|
||||
}
|
||||
}()
|
||||
|
||||
token, err := b.promptBroker.Ask(ctx, PromptRequest{
|
||||
SSID: ssid,
|
||||
SettingName: "802-11-wireless-security",
|
||||
Fields: []string{"psk"},
|
||||
Reason: "wrong-password",
|
||||
})
|
||||
if err != nil {
|
||||
log.Warnf("failed to request replacement credentials for %s: %v", ssid, err)
|
||||
return
|
||||
}
|
||||
|
||||
reply, err := b.promptBroker.Wait(ctx, token)
|
||||
if err != nil || reply.Cancel {
|
||||
return
|
||||
}
|
||||
|
||||
psk, ok := reply.Secrets["psk"]
|
||||
if !ok || psk == "" {
|
||||
return
|
||||
}
|
||||
|
||||
if err := b.ForgetWiFiNetwork(ssid); err != nil {
|
||||
log.Warnf("failed to forget %s before credential replacement: %v", ssid, err)
|
||||
}
|
||||
|
||||
b.storePendingPSK(ssid, psk)
|
||||
|
||||
if err := b.ConnectWiFi(ConnectionRequest{SSID: ssid}); err != nil {
|
||||
log.Warnf("failed to reconnect %s with replacement credentials: %v", ssid, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *IWDBackend) startAttemptWatchdog(att *connectAttempt) {
|
||||
@@ -560,7 +629,7 @@ func (b *IWDBackend) ConnectWiFi(req ConnectionRequest) error {
|
||||
return fmt.Errorf("no WiFi device available")
|
||||
}
|
||||
|
||||
networkPath, err := b.findNetworkPath(req.SSID)
|
||||
networkPath, saved, err := b.findNetworkPath(req.SSID)
|
||||
if err != nil {
|
||||
b.setConnectError(errdefs.ErrNoSuchSSID)
|
||||
if b.onStateChange != nil {
|
||||
@@ -572,6 +641,7 @@ func (b *IWDBackend) ConnectWiFi(req ConnectionRequest) error {
|
||||
att := &connectAttempt{
|
||||
ssid: req.SSID,
|
||||
netPath: networkPath,
|
||||
saved: saved,
|
||||
start: time.Now(),
|
||||
deadline: time.Now().Add(15 * time.Second),
|
||||
}
|
||||
@@ -619,26 +689,39 @@ func (b *IWDBackend) ConnectWiFi(req ConnectionRequest) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *IWDBackend) findNetworkPath(ssid string) (dbus.ObjectPath, error) {
|
||||
func (b *IWDBackend) findNetworkPath(ssid string) (dbus.ObjectPath, bool, error) {
|
||||
obj := b.conn.Object(iwdBusName, iwdObjectPath)
|
||||
|
||||
var objects map[dbus.ObjectPath]map[string]map[string]dbus.Variant
|
||||
err := obj.Call(dbusObjectManager+".GetManagedObjects", 0).Store(&objects)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
var netPath dbus.ObjectPath
|
||||
saved := false
|
||||
for path, interfaces := range objects {
|
||||
if netProps, ok := interfaces[iwdNetworkInterface]; ok {
|
||||
if nameVar, ok := netProps["Name"]; ok {
|
||||
if name, ok := nameVar.Value().(string); ok && name == ssid {
|
||||
return path, nil
|
||||
netPath = path
|
||||
}
|
||||
}
|
||||
}
|
||||
if knownProps, ok := interfaces[iwdKnownNetworkInterface]; ok {
|
||||
if nameVar, ok := knownProps["Name"]; ok {
|
||||
if name, ok := nameVar.Value().(string); ok && name == ssid {
|
||||
saved = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("network not found")
|
||||
if netPath == "" {
|
||||
return "", false, fmt.Errorf("network not found")
|
||||
}
|
||||
|
||||
return netPath, saved, nil
|
||||
}
|
||||
|
||||
func (b *IWDBackend) DisconnectWiFi() error {
|
||||
|
||||
Reference in New Issue
Block a user