140 lines
4.5 KiB
Go
140 lines
4.5 KiB
Go
package ble
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os/exec"
|
|
"time"
|
|
|
|
"github.com/godbus/dbus/v5"
|
|
)
|
|
|
|
const (
|
|
agentPath = "/org/bluez/agent/noinputnooutput"
|
|
agentInterface = "org.bluez.Agent1"
|
|
agentManager = "org.bluez.AgentManager1"
|
|
bluezService = "org.bluez"
|
|
bluezPath = "/org/bluez"
|
|
)
|
|
|
|
// NoInputNoOutputAgent implements org.bluez.Agent1 with NoInputNoOutput capability.
|
|
// This disables pairing prompts and allows "Just Works" pairing.
|
|
type NoInputNoOutputAgent struct{}
|
|
|
|
// Release is called when the agent is unregistered.
|
|
func (a *NoInputNoOutputAgent) Release() *dbus.Error {
|
|
return nil
|
|
}
|
|
|
|
// RequestPinCode returns an error as PIN codes are not supported with NoInputNoOutput.
|
|
func (a *NoInputNoOutputAgent) RequestPinCode(device dbus.ObjectPath) (string, *dbus.Error) {
|
|
return "", dbus.NewError("org.bluez.Error.Rejected", []any{"NoInputNoOutput agent"})
|
|
}
|
|
|
|
// DisplayPinCode does nothing as we have no display.
|
|
func (a *NoInputNoOutputAgent) DisplayPinCode(device dbus.ObjectPath, pincode string) *dbus.Error {
|
|
return nil
|
|
}
|
|
|
|
// RequestPasskey returns an error as passkeys are not supported with NoInputNoOutput.
|
|
func (a *NoInputNoOutputAgent) RequestPasskey(device dbus.ObjectPath) (uint32, *dbus.Error) {
|
|
return 0, dbus.NewError("org.bluez.Error.Rejected", []any{"NoInputNoOutput agent"})
|
|
}
|
|
|
|
// DisplayPasskey does nothing as we have no display.
|
|
func (a *NoInputNoOutputAgent) DisplayPasskey(device dbus.ObjectPath, passkey uint32, entered uint16) *dbus.Error {
|
|
return nil
|
|
}
|
|
|
|
// RequestConfirmation auto-accepts pairing for "Just Works" mode.
|
|
func (a *NoInputNoOutputAgent) RequestConfirmation(device dbus.ObjectPath, passkey uint32) *dbus.Error {
|
|
return nil // Accept pairing silently
|
|
}
|
|
|
|
// RequestAuthorization auto-accepts authorization for "Just Works" mode.
|
|
func (a *NoInputNoOutputAgent) RequestAuthorization(device dbus.ObjectPath) *dbus.Error {
|
|
return nil // Accept authorization silently
|
|
}
|
|
|
|
// AuthorizeService auto-accepts service authorization.
|
|
func (a *NoInputNoOutputAgent) AuthorizeService(device dbus.ObjectPath, uuid string) *dbus.Error {
|
|
return nil // Accept service authorization silently
|
|
}
|
|
|
|
// Cancel is called when an operation is canceled.
|
|
func (a *NoInputNoOutputAgent) Cancel() *dbus.Error {
|
|
return nil
|
|
}
|
|
|
|
// RegisterNoInputNoOutputAgent connects to system DBus and registers a NoInputNoOutput
|
|
// agent with BlueZ. This disables pairing prompts for BLE connections.
|
|
func RegisterNoInputNoOutputAgent() error {
|
|
conn, err := dbus.SystemBus()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to connect to system DBus: %w", err)
|
|
}
|
|
|
|
agent := &NoInputNoOutputAgent{}
|
|
|
|
// Export the agent object on DBus
|
|
err = conn.Export(agent, agentPath, agentInterface)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to export agent: %w", err)
|
|
}
|
|
|
|
// Get the AgentManager interface
|
|
agentMgr := conn.Object(bluezService, bluezPath)
|
|
|
|
// Register our agent with NoInputNoOutput capability
|
|
call := agentMgr.Call(agentManager+".RegisterAgent", 0, dbus.ObjectPath(agentPath), "NoInputNoOutput")
|
|
if call.Err != nil {
|
|
return fmt.Errorf("failed to register agent: %w", call.Err)
|
|
}
|
|
|
|
// Request to be the default agent
|
|
call = agentMgr.Call(agentManager+".RequestDefaultAgent", 0, dbus.ObjectPath(agentPath))
|
|
if call.Err != nil {
|
|
return fmt.Errorf("failed to set default agent: %w", call.Err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SetAdapterNotPairable sets the Pairable property to false on the default adapter.
|
|
// This is optional and prevents the adapter from initiating pairing.
|
|
func SetAdapterNotPairable() error {
|
|
conn, err := dbus.SystemBus()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to connect to system DBus: %w", err)
|
|
}
|
|
|
|
// Get the default adapter (hci0)
|
|
adapter := conn.Object(bluezService, "/org/bluez/hci0")
|
|
|
|
// Set Pairable to false using org.freedesktop.DBus.Properties interface
|
|
call := adapter.Call("org.freedesktop.DBus.Properties.Set", 0,
|
|
"org.bluez.Adapter1", "Pairable", dbus.MakeVariant(false))
|
|
if call.Err != nil {
|
|
return fmt.Errorf("failed to set Pairable: %w", call.Err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// DisableBonding runs btmgmt to disable bonding on the default adapter.
|
|
// This prevents phones from initiating pairing requests.
|
|
func DisableBonding() error {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
defer cancel()
|
|
|
|
cmd := exec.CommandContext(ctx, "btmgmt", "-i", "0", "bondable", "off")
|
|
output, err := cmd.CombinedOutput()
|
|
if ctx.Err() == context.DeadlineExceeded {
|
|
return fmt.Errorf("btmgmt timed out")
|
|
}
|
|
if err != nil {
|
|
return fmt.Errorf("failed to disable bonding: %w (output: %s)", err, output)
|
|
}
|
|
return nil
|
|
}
|