1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-10 07:25:37 -05:00
Files
DankMaterialShell/backend/internal/server/network/agent_iwd.go
2025-11-12 17:18:45 -05:00

307 lines
8.0 KiB
Go

package network
import (
"context"
"errors"
"fmt"
"time"
"github.com/AvengeMedia/DankMaterialShell/backend/internal/errdefs"
"github.com/godbus/dbus/v5"
)
const (
iwdAgentManagerPath = "/net/connman/iwd"
iwdAgentManagerIface = "net.connman.iwd.AgentManager"
iwdAgentInterface = "net.connman.iwd.Agent"
iwdAgentObjectPath = "/com/danklinux/iwdagent"
)
type ConnectionStateChecker interface {
IsConnectingTo(ssid string) bool
}
type IWDAgent struct {
conn *dbus.Conn
objPath dbus.ObjectPath
prompts PromptBroker
onUserCanceled func()
onPromptRetry func(ssid string)
lastRequestSSID string
stateChecker ConnectionStateChecker
}
const iwdAgentIntrospectXML = `
<node>
<interface name="net.connman.iwd.Agent">
<method name="Release">
<annotation name="org.freedesktop.DBus.Method.NoReply" value="true"/>
</method>
<method name="RequestPassphrase">
<arg type="o" name="network" direction="in"/>
<arg type="s" name="passphrase" direction="out"/>
</method>
<method name="RequestPrivateKeyPassphrase">
<arg type="o" name="network" direction="in"/>
<arg type="s" name="passphrase" direction="out"/>
</method>
<method name="RequestUserNameAndPassword">
<arg type="o" name="network" direction="in"/>
<arg type="s" name="username" direction="out"/>
<arg type="s" name="password" direction="out"/>
</method>
<method name="RequestUserPassword">
<arg type="o" name="network" direction="in"/>
<arg type="s" name="user" direction="in"/>
<arg type="s" name="password" direction="out"/>
</method>
<method name="Cancel">
<arg type="s" name="reason" direction="in"/>
<annotation name="org.freedesktop.DBus.Method.NoReply" value="true"/>
</method>
</interface>
</node>`
func NewIWDAgent(conn *dbus.Conn, prompts PromptBroker) (*IWDAgent, error) {
if conn == nil {
return nil, fmt.Errorf("dbus connection is nil")
}
agent := &IWDAgent{
conn: conn,
objPath: dbus.ObjectPath(iwdAgentObjectPath),
prompts: prompts,
}
if err := conn.Export(agent, agent.objPath, iwdAgentInterface); err != nil {
return nil, fmt.Errorf("failed to export IWD agent: %w", err)
}
if err := conn.Export(agent, agent.objPath, "org.freedesktop.DBus.Introspectable"); err != nil {
return nil, fmt.Errorf("failed to export introspection: %w", err)
}
mgr := conn.Object("net.connman.iwd", dbus.ObjectPath(iwdAgentManagerPath))
call := mgr.Call(iwdAgentManagerIface+".RegisterAgent", 0, agent.objPath)
if call.Err != nil {
return nil, fmt.Errorf("failed to register agent with iwd: %w", call.Err)
}
return agent, nil
}
func (a *IWDAgent) Close() {
if a.conn != nil {
mgr := a.conn.Object("net.connman.iwd", dbus.ObjectPath(iwdAgentManagerPath))
mgr.Call(iwdAgentManagerIface+".UnregisterAgent", 0, a.objPath)
}
}
func (a *IWDAgent) SetStateChecker(checker ConnectionStateChecker) {
a.stateChecker = checker
}
func (a *IWDAgent) getNetworkName(networkPath dbus.ObjectPath) string {
netObj := a.conn.Object("net.connman.iwd", networkPath)
nameVar, err := netObj.GetProperty("net.connman.iwd.Network.Name")
if err == nil {
if name, ok := nameVar.Value().(string); ok {
return name
}
}
return string(networkPath)
}
func (a *IWDAgent) RequestPassphrase(network dbus.ObjectPath) (string, *dbus.Error) {
ssid := a.getNetworkName(network)
if a.stateChecker != nil && !a.stateChecker.IsConnectingTo(ssid) {
return "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
}
if a.prompts == nil {
if a.onUserCanceled != nil {
a.onUserCanceled()
}
return "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
}
if a.lastRequestSSID == ssid {
if a.onPromptRetry != nil {
a.onPromptRetry(ssid)
}
}
a.lastRequestSSID = ssid
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
token, err := a.prompts.Ask(ctx, PromptRequest{
SSID: ssid,
Fields: []string{"psk"},
})
if err != nil {
if a.onUserCanceled != nil {
a.onUserCanceled()
}
return "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
}
reply, err := a.prompts.Wait(ctx, token)
if err != nil {
if reply.Cancel || errors.Is(err, errdefs.ErrSecretPromptCancelled) {
if a.onUserCanceled != nil {
a.onUserCanceled()
}
}
return "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
}
if passphrase, ok := reply.Secrets["psk"]; ok {
return passphrase, nil
}
if a.onUserCanceled != nil {
a.onUserCanceled()
}
return "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
}
func (a *IWDAgent) RequestPrivateKeyPassphrase(network dbus.ObjectPath) (string, *dbus.Error) {
ssid := a.getNetworkName(network)
if a.stateChecker != nil && !a.stateChecker.IsConnectingTo(ssid) {
return "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
}
if a.prompts == nil {
return "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
}
if a.lastRequestSSID == ssid {
if a.onPromptRetry != nil {
a.onPromptRetry(ssid)
}
}
a.lastRequestSSID = ssid
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
token, err := a.prompts.Ask(ctx, PromptRequest{
SSID: ssid,
Fields: []string{"private-key-password"},
})
if err != nil {
return "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
}
reply, err := a.prompts.Wait(ctx, token)
if err != nil || reply.Cancel {
return "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
}
if passphrase, ok := reply.Secrets["private-key-password"]; ok {
return passphrase, nil
}
return "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
}
func (a *IWDAgent) RequestUserNameAndPassword(network dbus.ObjectPath) (string, string, *dbus.Error) {
ssid := a.getNetworkName(network)
if a.stateChecker != nil && !a.stateChecker.IsConnectingTo(ssid) {
return "", "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
}
if a.prompts == nil {
return "", "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
}
if a.lastRequestSSID == ssid {
if a.onPromptRetry != nil {
a.onPromptRetry(ssid)
}
}
a.lastRequestSSID = ssid
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
token, err := a.prompts.Ask(ctx, PromptRequest{
SSID: ssid,
Fields: []string{"identity", "password"},
})
if err != nil {
return "", "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
}
reply, err := a.prompts.Wait(ctx, token)
if err != nil || reply.Cancel {
return "", "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
}
username, hasUser := reply.Secrets["identity"]
password, hasPass := reply.Secrets["password"]
if hasUser && hasPass {
return username, password, nil
}
return "", "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
}
func (a *IWDAgent) RequestUserPassword(network dbus.ObjectPath, user string) (string, *dbus.Error) {
ssid := a.getNetworkName(network)
if a.stateChecker != nil && !a.stateChecker.IsConnectingTo(ssid) {
return "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
}
if a.prompts == nil {
return "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
}
if a.lastRequestSSID == ssid {
if a.onPromptRetry != nil {
a.onPromptRetry(ssid)
}
}
a.lastRequestSSID = ssid
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
token, err := a.prompts.Ask(ctx, PromptRequest{
SSID: ssid,
Fields: []string{"password"},
})
if err != nil {
return "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
}
reply, err := a.prompts.Wait(ctx, token)
if err != nil || reply.Cancel {
return "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
}
if password, ok := reply.Secrets["password"]; ok {
return password, nil
}
return "", dbus.NewError("net.connman.iwd.Agent.Error.Canceled", nil)
}
func (a *IWDAgent) Cancel(reason string) *dbus.Error {
return nil
}
func (a *IWDAgent) Release() *dbus.Error {
return nil
}
func (a *IWDAgent) Introspect() (string, *dbus.Error) {
return iwdAgentIntrospectXML, nil
}