Commit d3fa11ec authored by Sophie Brun's avatar Sophie Brun

New upstream version 2.20

parent 0a36f3e6
...@@ -4,7 +4,7 @@ FROM golang:alpine AS build-env ...@@ -4,7 +4,7 @@ FROM golang:alpine AS build-env
ENV SRC_DIR $GOPATH/src/github.com/bettercap/bettercap ENV SRC_DIR $GOPATH/src/github.com/bettercap/bettercap
RUN apk add --update ca-certificates RUN apk add --update ca-certificates
RUN apk add --no-cache --update bash iptables wireless-tools build-base libpcap-dev libusb-1.0-dev linux-headers libnetfilter_queue-dev git RUN apk add --no-cache --update bash iptables wireless-tools build-base libpcap-dev libusb-dev linux-headers libnetfilter_queue-dev git
WORKDIR $SRC_DIR WORKDIR $SRC_DIR
ADD . $SRC_DIR ADD . $SRC_DIR
...@@ -13,11 +13,13 @@ RUN make deps ...@@ -13,11 +13,13 @@ RUN make deps
RUN make RUN make
# get caplets # get caplets
RUN git clone https://github.com/bettercap/caplets RUN mkdir -p /usr/local/share/bettercap
RUN git clone https://github.com/bettercap/caplets /usr/local/share/bettercap/caplets
# final stage # final stage
FROM alpine FROM alpine
RUN apk add --no-cache --update bash iproute2 libpcap libnetfilter_queue wireless-tools RUN apk add --update ca-certificates
RUN apk add --no-cache --update bash iproute2 libpcap libusb-dev libnetfilter_queue wireless-tools
COPY --from=build-env /go/src/github.com/bettercap/bettercap/bettercap /app/ COPY --from=build-env /go/src/github.com/bettercap/bettercap/bettercap /app/
COPY --from=build-env /go/src/github.com/bettercap/bettercap/caplets /app/ COPY --from=build-env /go/src/github.com/bettercap/bettercap/caplets /app/
WORKDIR /app WORKDIR /app
......
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:c309b41787813f80ec393023471014b0be16346bba6e16bf5fa01ce1d310a4ea" digest = "1:a2c142e6c2aa1c71796c748bbe42d224e23d6638fd5b3ae153e70a4b08a8da4e"
name = "github.com/bettercap/gatt" name = "github.com/bettercap/gatt"
packages = [ packages = [
".", ".",
...@@ -40,7 +40,7 @@ ...@@ -40,7 +40,7 @@
"xpc", "xpc",
] ]
pruneopts = "UT" pruneopts = "UT"
revision = "1353e80bee488dc02d1f7e42759c1352492bf18b" revision = "277ee0d0ef94d26e3190252c59fa34dde0df4f26"
[[projects]] [[projects]]
branch = "master" branch = "master"
...@@ -83,7 +83,7 @@ ...@@ -83,7 +83,7 @@
revision = "2ce16c963a8ac5bd6af851d4877e38701346983f" revision = "2ce16c963a8ac5bd6af851d4877e38701346983f"
[[projects]] [[projects]]
digest = "1:a5fab5a807cac4da733996f46b917283fe43aac5fd32798a3c9279a44eb5156c" digest = "1:da1be9af4c3f262bd385cc722b08d98d4a47ddea57731e98b85c7ba21b35bc31"
name = "github.com/evilsocket/islazy" name = "github.com/evilsocket/islazy"
packages = [ packages = [
"data", "data",
...@@ -96,8 +96,8 @@ ...@@ -96,8 +96,8 @@
"zip", "zip",
] ]
pruneopts = "UT" pruneopts = "UT"
revision = "e6f5e33089826f0d17eaab3c8c3603048d615a0e" revision = "6ef79e84ded205e48f296d21e3bc65d1cf4f5c78"
version = "v1.10.2" version = "v1.10.3"
[[projects]] [[projects]]
branch = "master" branch = "master"
......
...@@ -6,6 +6,9 @@ all: deps build ...@@ -6,6 +6,9 @@ all: deps build
deps: godep golint gofmt gomegacheck deps: godep golint gofmt gomegacheck
@dep ensure @dep ensure
build_with_race_detector: resources
@go build -race -o $(TARGET) .
build: resources build: resources
@go build -o $(TARGET) . @go build -o $(TARGET) .
......
...@@ -97,19 +97,21 @@ fi ...@@ -97,19 +97,21 @@ fi
printf "@ Building for $WHAT ...\n\n" printf "@ Building for $WHAT ...\n\n"
case "$WHAT" in if [[ "$WHAT" == "all" || "$WHAT" == "linux" ]]; then
all|linux)
build_linux_amd64 && create_archive bettercap_linux_amd64_$VERSION.zip build_linux_amd64 && create_archive bettercap_linux_amd64_$VERSION.zip
;; fi
all|osx|mac|macos)
if [[ "$WHAT" == "all" || "$WHAT" == "osx" || "$WHAT" == "mac" || "$WHAT" == "macos" ]]; then
build_macos_amd64 && create_archive bettercap_macos_amd64_$VERSION.zip build_macos_amd64 && create_archive bettercap_macos_amd64_$VERSION.zip
;; fi
all|windows|win)
if [[ "$WHAT" == "all" || "$WHAT" == "win" || "$WHAT" == "windows" ]]; then
build_windows_amd64 && create_exe_archive bettercap_windows_amd64_$VERSION.zip build_windows_amd64 && create_exe_archive bettercap_windows_amd64_$VERSION.zip
;; fi
all|android)
if [[ "$WHAT" == "all" || "$WHAT" == "android" ]]; then
build_android_arm && create_archive bettercap_android_arm_$VERSION.zip build_android_arm && create_archive bettercap_android_arm_$VERSION.zip
esac fi
sha256sum * > checksums.txt sha256sum * > checksums.txt
......
...@@ -8,11 +8,32 @@ import ( ...@@ -8,11 +8,32 @@ import (
"github.com/evilsocket/islazy/fs" "github.com/evilsocket/islazy/fs"
) )
type Script struct {
Path string `json:"path"`
Size int64 `json:"size"`
Code []string `json:"code"`
}
func newScript(path string, size int64) Script {
return Script{
Path: path,
Size: size,
Code: make([]string, 0),
}
}
type Caplet struct { type Caplet struct {
Name string Script
Path string Name string `json:"name"`
Size int64 Scripts []Script `json:"scripts"`
Code []string }
func NewCaplet(name string, path string, size int64) Caplet {
return Caplet{
Script: newScript(path, size),
Name: name,
Scripts: make([]Script, 0),
}
} }
func (cap *Caplet) Eval(argv []string, lineCb func(line string) error) error { func (cap *Caplet) Eval(argv []string, lineCb func(line string) error) error {
...@@ -23,6 +44,10 @@ func (cap *Caplet) Eval(argv []string, lineCb func(line string) error) error { ...@@ -23,6 +44,10 @@ func (cap *Caplet) Eval(argv []string, lineCb func(line string) error) error {
// temporarily change the working directory // temporarily change the working directory
return fs.Chdir(filepath.Dir(cap.Path), func() error { return fs.Chdir(filepath.Dir(cap.Path), func() error {
for _, line := range cap.Code { for _, line := range cap.Code {
// skip empty lines and comments
if line == "" || line[0] == '#' {
continue
}
// replace $0 with argv[0], $1 with argv[1] and so on // replace $0 with argv[0], $1 with argv[1] and so on
for i, arg := range argv { for i, arg := range argv {
what := fmt.Sprintf("$%d", i) what := fmt.Sprintf("$%d", i)
......
...@@ -2,6 +2,7 @@ package caplets ...@@ -2,6 +2,7 @@ package caplets
import ( import (
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"sort" "sort"
...@@ -16,22 +17,22 @@ var ( ...@@ -16,22 +17,22 @@ var (
cacheLock = sync.Mutex{} cacheLock = sync.Mutex{}
) )
func List() []Caplet { func List() []*Caplet {
caplets := make([]Caplet, 0) caplets := make([]*Caplet, 0)
for _, searchPath := range LoadPaths { for _, searchPath := range LoadPaths {
files, _ := filepath.Glob(searchPath + "/*" + Suffix) files, _ := filepath.Glob(searchPath + "/*" + Suffix)
files2, _ := filepath.Glob(searchPath + "/*/*" + Suffix) files2, _ := filepath.Glob(searchPath + "/*/*" + Suffix)
for _, fileName := range append(files, files2...) { for _, fileName := range append(files, files2...) {
if stats, err := os.Stat(fileName); err == nil { if _, err := os.Stat(fileName); err == nil {
base := strings.Replace(fileName, searchPath+"/", "", -1) base := strings.Replace(fileName, searchPath+"/", "", -1)
base = strings.Replace(base, Suffix, "", -1) base = strings.Replace(base, Suffix, "", -1)
caplets = append(caplets, Caplet{ if err, caplet := Load(base); err != nil {
Name: base, fmt.Fprintf(os.Stderr, "wtf: %v\n", err)
Path: fileName, } else {
Size: stats.Size(), caplets = append(caplets, caplet)
}) }
} }
} }
} }
...@@ -51,6 +52,7 @@ func Load(name string) (error, *Caplet) { ...@@ -51,6 +52,7 @@ func Load(name string) (error, *Caplet) {
return nil, caplet return nil, caplet
} }
baseName := name
names := []string{} names := []string{}
if !strings.HasSuffix(name, Suffix) { if !strings.HasSuffix(name, Suffix) {
name += Suffix name += Suffix
...@@ -64,23 +66,41 @@ func Load(name string) (error, *Caplet) { ...@@ -64,23 +66,41 @@ func Load(name string) (error, *Caplet) {
names = append(names, name) names = append(names, name)
} }
for _, filename := range names { for _, fileName := range names {
if fs.Exists(filename) { if stats, err := os.Stat(fileName); err == nil {
cap := &Caplet{ cap := &Caplet{
Path: filename, Script: newScript(fileName, stats.Size()),
Code: make([]string, 0), Name: baseName,
Scripts: make([]Script, 0),
} }
cache[name] = cap cache[name] = cap
if reader, err := fs.LineReader(filename); err != nil { if reader, err := fs.LineReader(fileName); err != nil {
return fmt.Errorf("error reading caplet %s: %v", filename, err), nil return fmt.Errorf("error reading caplet %s: %v", fileName, err), nil
} else { } else {
for line := range reader { for line := range reader {
if line == "" || line[0] == '#' {
continue
}
cap.Code = append(cap.Code, line) cap.Code = append(cap.Code, line)
} }
// the caplet has a dedicated folder
if strings.Contains(baseName, "/") || strings.Contains(baseName, "\\") {
dir := filepath.Dir(fileName)
// get all secondary .cap and .js files
if files, err := ioutil.ReadDir(dir); err == nil && len(files) > 0 {
for _, f := range files {
subFileName := filepath.Join(dir, f.Name())
if subFileName != fileName && (strings.HasSuffix(subFileName, ".cap") || strings.HasSuffix(subFileName, ".js")) {
if reader, err := fs.LineReader(subFileName); err == nil {
script := newScript(subFileName, f.Size())
for line := range reader {
script.Code = append(script.Code, line)
}
cap.Scripts = append(cap.Scripts, script)
}
}
}
}
}
} }
return nil, cap return nil, cap
......
...@@ -2,7 +2,7 @@ package core ...@@ -2,7 +2,7 @@ package core
const ( const (
Name = "bettercap" Name = "bettercap"
Version = "2.19" Version = "2.20"
Author = "Simone 'evilsocket' Margaritelli" Author = "Simone 'evilsocket' Margaritelli"
Website = "https://bettercap.org/" Website = "https://bettercap.org/"
) )
// +build android
package core
func Shell(cmd string) (string, error) {
return Exec("/system/bin/sh", []string{"-c", cmd})
}
// +build !windows // +build !windows,!android
package core package core
......
...@@ -42,7 +42,7 @@ func NewRestAPI(s *session.Session) *RestAPI { ...@@ -42,7 +42,7 @@ func NewRestAPI(s *session.Session) *RestAPI {
} }
mod.AddParam(session.NewStringParameter("api.rest.address", mod.AddParam(session.NewStringParameter("api.rest.address",
session.ParamIfaceAddress, "127.0.0.1",
session.IPv4Validator, session.IPv4Validator,
"Address to bind the API REST server to.")) "Address to bind the API REST server to."))
...@@ -172,7 +172,12 @@ func (mod *RestAPI) Configure() error { ...@@ -172,7 +172,12 @@ func (mod *RestAPI) Configure() error {
router := mux.NewRouter() router := mux.NewRouter()
router.Methods("OPTIONS").HandlerFunc(mod.corsRoute)
router.HandleFunc("/api/file", mod.fileRoute)
router.HandleFunc("/api/events", mod.eventsRoute) router.HandleFunc("/api/events", mod.eventsRoute)
router.HandleFunc("/api/session", mod.sessionRoute) router.HandleFunc("/api/session", mod.sessionRoute)
router.HandleFunc("/api/session/ble", mod.sessionRoute) router.HandleFunc("/api/session/ble", mod.sessionRoute)
router.HandleFunc("/api/session/ble/{mac}", mod.sessionRoute) router.HandleFunc("/api/session/ble/{mac}", mod.sessionRoute)
......
...@@ -3,7 +3,11 @@ package api_rest ...@@ -3,7 +3,11 @@ package api_rest
import ( import (
"crypto/subtle" "crypto/subtle"
"encoding/json" "encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http" "net/http"
"os"
"strconv" "strconv"
"strings" "strings"
...@@ -22,7 +26,7 @@ type APIResponse struct { ...@@ -22,7 +26,7 @@ type APIResponse struct {
} }
func (mod *RestAPI) setAuthFailed(w http.ResponseWriter, r *http.Request) { func (mod *RestAPI) setAuthFailed(w http.ResponseWriter, r *http.Request) {
mod.Warning("Unauthorized authentication attempt from %s", r.RemoteAddr) mod.Warning("Unauthorized authentication attempt from %s to %s", r.RemoteAddr, r.URL.String())
w.Header().Set("WWW-Authenticate", `Basic realm="auth"`) w.Header().Set("WWW-Authenticate", `Basic realm="auth"`)
w.WriteHeader(401) w.WriteHeader(401)
...@@ -41,7 +45,10 @@ func (mod *RestAPI) setSecurityHeaders(w http.ResponseWriter) { ...@@ -41,7 +45,10 @@ func (mod *RestAPI) setSecurityHeaders(w http.ResponseWriter) {
w.Header().Add("X-Content-Type-Options", "nosniff") w.Header().Add("X-Content-Type-Options", "nosniff")
w.Header().Add("X-XSS-Protection", "1; mode=block") w.Header().Add("X-XSS-Protection", "1; mode=block")
w.Header().Add("Referrer-Policy", "same-origin") w.Header().Add("Referrer-Policy", "same-origin")
w.Header().Set("Access-Control-Allow-Origin", mod.allowOrigin) w.Header().Set("Access-Control-Allow-Origin", mod.allowOrigin)
w.Header().Add("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
w.Header().Add("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
} }
func (mod *RestAPI) checkAuth(r *http.Request) bool { func (mod *RestAPI) checkAuth(r *http.Request) bool {
...@@ -151,11 +158,16 @@ func (mod *RestAPI) runSessionCommand(w http.ResponseWriter, r *http.Request) { ...@@ -151,11 +158,16 @@ func (mod *RestAPI) runSessionCommand(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Bad Request", 400) http.Error(w, "Bad Request", 400)
} else if err = json.NewDecoder(r.Body).Decode(&cmd); err != nil { } else if err = json.NewDecoder(r.Body).Decode(&cmd); err != nil {
http.Error(w, "Bad Request", 400) http.Error(w, "Bad Request", 400)
} else if err = session.I.Run(cmd.Command); err != nil { }
for _, aCommand := range session.ParseCommands(cmd.Command) {
if err = mod.Session.Run(aCommand); err != nil {
http.Error(w, err.Error(), 400) http.Error(w, err.Error(), 400)
} else { return
mod.toJSON(w, APIResponse{Success: true})
} }
}
mod.toJSON(w, APIResponse{Success: true})
} }
func (mod *RestAPI) showEvents(w http.ResponseWriter, r *http.Request) { func (mod *RestAPI) showEvents(w http.ResponseWriter, r *http.Request) {
...@@ -164,7 +176,13 @@ func (mod *RestAPI) showEvents(w http.ResponseWriter, r *http.Request) { ...@@ -164,7 +176,13 @@ func (mod *RestAPI) showEvents(w http.ResponseWriter, r *http.Request) {
if mod.useWebsocket { if mod.useWebsocket {
mod.startStreamingEvents(w, r) mod.startStreamingEvents(w, r)
} else { } else {
events := session.I.Events.Sorted() events := make([]session.Event, 0)
for _, e := range session.I.Events.Sorted() {
if mod.Session.EventsIgnoreList.Ignored(e) == false {
events = append(events, e)
}
}
nevents := len(events) nevents := len(events)
nmax := nevents nmax := nevents
n := nmax n := nmax
...@@ -190,6 +208,11 @@ func (mod *RestAPI) clearEvents(w http.ResponseWriter, r *http.Request) { ...@@ -190,6 +208,11 @@ func (mod *RestAPI) clearEvents(w http.ResponseWriter, r *http.Request) {
session.I.Events.Clear() session.I.Events.Clear()
} }
func (mod *RestAPI) corsRoute(w http.ResponseWriter, r *http.Request) {
mod.setSecurityHeaders(w)
w.WriteHeader(http.StatusNoContent)
}
func (mod *RestAPI) sessionRoute(w http.ResponseWriter, r *http.Request) { func (mod *RestAPI) sessionRoute(w http.ResponseWriter, r *http.Request) {
mod.setSecurityHeaders(w) mod.setSecurityHeaders(w)
...@@ -250,6 +273,44 @@ func (mod *RestAPI) sessionRoute(w http.ResponseWriter, r *http.Request) { ...@@ -250,6 +273,44 @@ func (mod *RestAPI) sessionRoute(w http.ResponseWriter, r *http.Request) {
} }
} }
func (mod *RestAPI) readFile(fileName string, w http.ResponseWriter, r *http.Request) {
fp, err := os.Open(fileName)
if err != nil {
msg := fmt.Sprintf("could not open %s for reading: %s", fileName, err)
mod.Debug(msg)
http.Error(w, msg, 404)
return
}
defer fp.Close()
w.Header().Set("Content-type", "application/octet-stream")
io.Copy(w, fp)
}
func (mod *RestAPI) writeFile(fileName string, w http.ResponseWriter, r *http.Request) {
data, err := ioutil.ReadAll(r.Body)
if err != nil {
msg := fmt.Sprintf("invalid file upload: %s", err)
mod.Warning(msg)
http.Error(w, msg, 404)
return
}
err = ioutil.WriteFile(fileName, data, 0666)
if err != nil {
msg := fmt.Sprintf("can't write to %s: %s", fileName, err)
mod.Warning(msg)
http.Error(w, msg, 404)
return
}
mod.toJSON(w, APIResponse{
Success: true,
Message: fmt.Sprintf("%s created", fileName),
})
}
func (mod *RestAPI) eventsRoute(w http.ResponseWriter, r *http.Request) { func (mod *RestAPI) eventsRoute(w http.ResponseWriter, r *http.Request) {
mod.setSecurityHeaders(w) mod.setSecurityHeaders(w)
...@@ -266,3 +327,22 @@ func (mod *RestAPI) eventsRoute(w http.ResponseWriter, r *http.Request) { ...@@ -266,3 +327,22 @@ func (mod *RestAPI) eventsRoute(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Bad Request", 400) http.Error(w, "Bad Request", 400)
} }
} }
func (mod *RestAPI) fileRoute(w http.ResponseWriter, r *http.Request) {
mod.setSecurityHeaders(w)
if !mod.checkAuth(r) {
mod.setAuthFailed(w, r)
return
}
fileName := r.URL.Query().Get("name")
if fileName != "" && r.Method == "GET" {
mod.readFile(fileName, w, r)
} else if fileName != "" && r.Method == "POST" {
mod.writeFile(fileName, w, r)
} else {
http.Error(w, "Bad Request", 400)
}
}
...@@ -127,9 +127,14 @@ func (mod *ArpSpoofer) Start() error { ...@@ -127,9 +127,14 @@ func (mod *ArpSpoofer) Start() error {
return err return err
} }
nTargets := len(mod.addresses) + len(mod.macs)
if nTargets == 0 {
mod.Warning("list of targets is empty, module not starting.")
return nil
}
return mod.SetRunning(true, func() { return mod.SetRunning(true, func() {
neighbours := []net.IP{} neighbours := []net.IP{}
nTargets := len(mod.addresses) + len(mod.macs)
if mod.internal { if mod.internal {
list, _ := iprange.ParseList(mod.Session.Interface.CIDR()) list, _ := iprange.ParseList(mod.Session.Interface.CIDR())
...@@ -214,23 +219,19 @@ func (mod *ArpSpoofer) getTargets(probe bool) map[string]net.HardwareAddr { ...@@ -214,23 +219,19 @@ func (mod *ArpSpoofer) getTargets(probe bool) map[string]net.HardwareAddr {
// add targets specified by IP address // add targets specified by IP address
for _, ip := range mod.addresses { for _, ip := range mod.addresses {
if mod.Session.Skip(ip) { if mod.Session.Skip(ip) {
mod.Debug("skipping IP %s from arp spoofing.", ip)
continue continue
} }
// do we have this ip mac address? // do we have this ip mac address?
if hw, err := mod.Session.FindMAC(ip, probe); err != nil { if hw, err := mod.Session.FindMAC(ip, probe); err == nil {
mod.Debug("could not find hardware address for %s", ip.String())
} else {
targets[ip.String()] = hw targets[ip.String()] = hw
} }
} }
// add targets specified by MAC address // add targets specified by MAC address
for _, hw := range mod.macs { for _, hw := range mod.macs {
if ip, err := network.ArpInverseLookup(mod.Session.Interface.Name(), hw.String(), false); err != nil { if ip, err := network.ArpInverseLookup(mod.Session.Interface.Name(), hw.String(), false); err == nil {
mod.Warning("could not find IP address for %s", hw.String()) if mod.Session.Skip(net.ParseIP(ip)) {
} else if mod.Session.Skip(net.ParseIP(ip)) { continue
mod.Debug("skipping address %s from arp spoofing.", ip) }
} else {
targets[ip] = hw targets[ip] = hw
} }
} }
...@@ -295,6 +296,7 @@ func (mod *ArpSpoofer) arpSpoofTargets(saddr net.IP, smac net.HardwareAddr, chec ...@@ -295,6 +296,7 @@ func (mod *ArpSpoofer) arpSpoofTargets(saddr net.IP, smac net.HardwareAddr, chec
} }
if gwPacket != nil { if gwPacket != nil {
mod.Debug("sending %d bytes of ARP packet to the gateway", len(gwPacket))
if err = mod.Session.Queue.Send(gwPacket); err != nil { if err = mod.Session.Queue.Send(gwPacket); err != nil {
mod.Error("error while sending packet: %v", err) mod.Error("error while sending packet: %v", err)
} }
......
...@@ -6,7 +6,6 @@ package ble ...@@ -6,7 +6,6 @@ package ble
import ( import (
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"io/ioutil"
golog "log" golog "log"
"time" "time"
...@@ -15,6 +14,8 @@ import ( ...@@ -15,6 +14,8 @@ import (
"github.com/bettercap/bettercap/session" "github.com/bettercap/bettercap/session"
"github.com/bettercap/gatt" "github.com/bettercap/gatt"
"github.com/evilsocket/islazy/str"
) )
type BLERecon struct { type BLERecon struct {
...@@ -41,6 +42,8 @@ func NewBLERecon(s *session.Session) *BLERecon { ...@@ -41,6 +42,8 @@ func NewBLERecon(s *session.Session) *BLERecon {
connected: false, connected: false,
} }
mod.InitState("scanning")
mod.selector = utils.ViewSelectorFor(&mod.SessionModule, mod.selector = utils.ViewSelectorFor(&mod.SessionModule,
"ble.show", "ble.show",
[]string{"rssi", "mac", "seen"}, "rssi asc") []string{"rssi", "mac", "seen"}, "rssi asc")
...@@ -126,15 +129,25 @@ func (mod *BLERecon) isEnumerating() bool { ...@@ -126,15 +129,25 @@ func (mod *BLERecon) isEnumerating() bool {
return mod.currDevice != nil return mod.currDevice != nil
} }
type dummyWriter struct {
mod *BLERecon
}
func (w dummyWriter) Write(p []byte) (n int, err error) {
w.mod.Debug("[gatt.log] %s", str.Trim(string(p)))
return len(p), nil
}
func (mod *BLERecon) Configure() (err error) { func (mod *B