From 51a5b4ad6ea917d40f92861fbc1afcfa5a9af6bb Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Thu, 19 Sep 2024 21:49:02 +0200 Subject: [PATCH] misc: small fix or general refactoring i did not bother commenting --- epson.yml | 125 ++++++ go.mod | 3 +- go.sum | 2 + modules/events_stream/events_view_mdns.go | 4 +- modules/mdns/mdns_advertise.go | 139 ------- modules/modules.go | 4 +- modules/net_probe/net_probe.go | 4 +- modules/zerogod/zerogod_acceptor.go | 109 ++++++ modules/zerogod/zerogod_advertise.go | 197 ++++++++++ .../zerogod_discovery.go} | 129 ++----- modules/zerogod/zerogod_endpoint_update.go | 72 ++++ modules/zerogod/zerogod_generic_handler.go | 69 ++++ modules/zerogod/zerogod_ipp_handler.go | 355 ++++++++++++++++++ .../mdns_save.go => zerogod/zerogod_save.go} | 4 +- .../mdns_show.go => zerogod/zerogod_show.go} | 4 +- printer3.yml | 187 +++++++++ test.yml | 207 ++++++++++ 17 files changed, 1375 insertions(+), 239 deletions(-) create mode 100644 epson.yml delete mode 100644 modules/mdns/mdns_advertise.go create mode 100644 modules/zerogod/zerogod_acceptor.go create mode 100644 modules/zerogod/zerogod_advertise.go rename modules/{mdns/mdns_discovery.go => zerogod/zerogod_discovery.go} (62%) create mode 100644 modules/zerogod/zerogod_endpoint_update.go create mode 100644 modules/zerogod/zerogod_generic_handler.go create mode 100644 modules/zerogod/zerogod_ipp_handler.go rename modules/{mdns/mdns_save.go => zerogod/zerogod_save.go} (88%) rename modules/{mdns/mdns_show.go => zerogod/zerogod_show.go} (95%) create mode 100644 printer3.yml create mode 100644 test.yml diff --git a/epson.yml b/epson.yml new file mode 100644 index 00000000..e54dc8af --- /dev/null +++ b/epson.yml @@ -0,0 +1,125 @@ +_http._tcp.local.: + servicerecord: + instance: EPSON\ XP-630\ Series-59F5BA-03 + service: _http._tcp + domain: local. + port: 8080 + +_ipp._tcp.local.: + servicerecord: + instance: EPSON\ XP-630\ Series-59F5BA-03 + service: _ipp._tcp + domain: local. + port: 6631 + +_ipps._tcp.local.: + servicerecord: + instance: EPSON\ XP-630\ Series-59F5BA-03 + service: _ipps._tcp + domain: local. + port: 6633 + text: + - txtvers=1 + - ty=EPSON XP-630 Series + - usb_MFG=EPSON + - usb_MDL=XP-630 Series + - product=(EPSON XP-630 Series) + - pdl=application/octet-stream,image/pwg-raster,image/urf,image/jpeg + - rp=ipp/print + - qtotal=1 + - Color=T + - Duplex=T + - Scan=T + - Fax=F + - kind=document,envelope,label,photo + - PaperMax=legal-A4 + - URF=CP1,MT1-3-5-8-10-11-12,PQ4-5,OB9,OFU0,RS360,SRGB24,W8,DM3,IS1-7,V1.4 + - mopria-certified=1.2 + - priority=30 + - adminurl=http://EPSON59F5BA.local.:80/PRESENTATION/BONJOUR + - note= + - UUID=cfe92100-67c4-11d4-a45f-44d24459f5ba + - TLS=1.2 + +_pdl-datastream._tcp.local.: + servicerecord: + instance: EPSON\ XP-630\ Series-59F5BA-03 + service: _pdl-datastream._tcp + domain: local. + port: 9100 + +_printer._tcp.local.: + servicerecord: + instance: EPSON\ XP-630\ Series-59F5BA-03 + service: _printer._tcp + domain: local. + port: 515 + text: + - txtvers=1 + - priority=50 + - ty=EPSON XP-630 Series + - usb_MFG=EPSON + - usb_MDL=XP-630 Series + - product=(EPSON XP-630 Series) + - pdl=raw + - rp=auto + - qtotal=1 + - adminurl=http://EPSON59F5BA.local.:80/PRESENTATION/BONJOUR + - note= + +_privet._tcp.local.: + servicerecord: + instance: EPSON\ XP-630\ Series-59F5BA-03 + service: _privet._tcp + domain: local. + port: 8081 + text: + - txtvers=1 + - ty=EPSON XP-630 Series (EPSON59F5BA) + - url=https://www.google.com/cloudprint + - type=printer + - id=0936a89f-33d7-80f5-c1bc-7421d40a78b5 + - cs=offline + +_scanner._tcp.local.: + servicerecord: + instance: EPSON\ XP-630\ Series-59F5BA-03 + service: _scanner._tcp + domain: local. + port: 1865 + text: + - txtvers=1 + - ty=EPSON XP-630 Series + - adminurl=http://EPSON59F5BA.local.:80/PRESENTATION/BONJOUR + - mfg=EPSON + - mdl=XP-630 Series + - UUID=cfe92100-67c4-11d4-a45f-44d24459f5ba + - scannerAvailable=0 + - note= + +_smb._tcp.local.: + servicerecord: + instance: EPSON\ XP-630\ Series-59F5BA-03 + service: _smb._tcp + domain: local. + port: 4445 + +_uscan._tcp.local.: + servicerecord: + instance: EPSON\ XP-630\ Series-59F5BA-03 + service: _uscan._tcp + domain: local. + port: 4443 + text: + - txtvers=1 + - vers=2.5 + - representation=/PRESENTATION/AIRPRINT/PRINTER_128.PNG + - rs=eSCL + - ty=EPSON XP-630 Series + - pdl=application/pdf,image/jpeg + - cs=color,grayscale,binary + - is=platen + - duplex=F + - adminurl=http://EPSON59F5BA.local.:80/PRESENTATION/BONJOUR + - UUID=cfe92100-67c4-11d4-a45f-44d24459f5ba + - note= diff --git a/go.mod b/go.mod index 71d834bb..cd7dfdef 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ require ( github.com/google/gousb v1.1.3 github.com/gorilla/mux v1.8.1 github.com/gorilla/websocket v1.5.3 + github.com/grandcat/zeroconf v1.0.0 github.com/hashicorp/go-bexpr v0.1.14 github.com/inconshreveable/go-vhost v1.0.0 github.com/jpillora/go-tld v1.2.1 @@ -29,6 +30,7 @@ require ( github.com/mdlayher/dhcp6 v0.0.0-20190311162359-2a67805d7d0b github.com/miekg/dns v1.1.61 github.com/mitchellh/go-homedir v1.1.0 + github.com/phin1x/go-ipp v1.6.1 github.com/robertkrimen/otto v0.4.0 github.com/stratoberry/go-gpsd v1.3.0 github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 @@ -47,7 +49,6 @@ require ( github.com/golang/mock v1.6.0 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-querystring v1.1.0 // indirect - github.com/grandcat/zeroconf v1.0.0 // indirect github.com/josharian/native v1.1.0 // indirect github.com/kr/binarydist v0.1.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect diff --git a/go.sum b/go.sum index 40606672..a2930b76 100644 --- a/go.sum +++ b/go.sum @@ -101,6 +101,8 @@ github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxd github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/pointerstructure v1.2.1 h1:ZhBBeX8tSlRpu/FFhXH4RC4OJzFlqsQhoHZAz4x7TIw= github.com/mitchellh/pointerstructure v1.2.1/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= +github.com/phin1x/go-ipp v1.6.1 h1:oxJXi92BO2FZhNcG3twjnxKFH1liTQ46vbbZx+IN/80= +github.com/phin1x/go-ipp v1.6.1/go.mod h1:GZwyNds6grdLi2xRBX22Cvt7Dh7ITWsML0bjrqBF5uo= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/modules/events_stream/events_view_mdns.go b/modules/events_stream/events_view_mdns.go index c0cd70a9..a02331db 100644 --- a/modules/events_stream/events_view_mdns.go +++ b/modules/events_stream/events_view_mdns.go @@ -4,13 +4,13 @@ import ( "fmt" "io" - "github.com/bettercap/bettercap/v2/modules/mdns" + "github.com/bettercap/bettercap/v2/modules/zerogod" "github.com/bettercap/bettercap/v2/session" "github.com/evilsocket/islazy/tui" ) func (mod *EventsStream) viewMDNSEvent(output io.Writer, e session.Event) { - event := e.Data.(mdns.ServiceDiscoveryEvent) + event := e.Data.(zerogod.ServiceDiscoveryEvent) fmt.Fprintf(output, "[%s] [%s] service %s detected for %s (%s):%d with %d records\n", e.Time.Format(mod.timeFormat), tui.Green(e.Tag), diff --git a/modules/mdns/mdns_advertise.go b/modules/mdns/mdns_advertise.go deleted file mode 100644 index 893a1dc7..00000000 --- a/modules/mdns/mdns_advertise.go +++ /dev/null @@ -1,139 +0,0 @@ -package mdns - -import ( - "errors" - "fmt" - "io/ioutil" - "os" - "strings" - - "github.com/evilsocket/islazy/tui" - "github.com/grandcat/zeroconf" - yaml "gopkg.in/yaml.v3" -) - -/* -type multiService struct { - mod *MDNSModule - services []*MDNSService -} - -func (m multiService) Records(q dns.Question) []dns.RR { - records := make([]dns.RR, 0) - - m.mod.Debug("QUESTION: %+v", q) - - if strings.HasPrefix(q.Name, "_services._dns-sd._udp.") { - for _, svc := range m.services { - records = append(records, svc.Records(q)...) - } - } else { - for _, svc := range m.services { - if svcRecords := svc.Records(q); len(svcRecords) > 0 { - records = svcRecords - break - } - } - } - - if num := len(records); num == 0 { - m.mod.Debug("unhandled service %+v", q) - } else { - m.mod.Info("responding to query %s with %d records", tui.Green(q.Name), num) - if q.Name == "_services._dns-sd._udp.local." { - for _, r := range records { - m.mod.Info(" %+v", r) - } - } - } - - return records -} -*/ - -type Advertiser struct { - Filename string - Mapping map[string]zeroconf.ServiceEntry - Servers map[string]*zeroconf.Server -} - -func (mod *MDNSModule) startAdvertiser(fileName string) error { - if mod.advertiser != nil { - return fmt.Errorf("advertiser already started for %s", mod.advertiser.Filename) - } - - data, err := ioutil.ReadFile(fileName) - if err != nil { - return fmt.Errorf("could not read %s: %v", fileName, err) - } - - mapping := make(map[string]zeroconf.ServiceEntry) - if err = yaml.Unmarshal(data, &mapping); err != nil { - return fmt.Errorf("could not deserialize %s: %v", fileName, err) - } - - hostName, err := os.Hostname() - if err != nil { - return fmt.Errorf("could not get hostname: %v", err) - } - if !strings.HasSuffix(hostName, ".") { - hostName += "." - } - - ifName := mod.Session.Interface.Name() - /* - iface, err := net.InterfaceByName(ifName) - if err != nil { - return fmt.Errorf("error getting interface %s: %v", ifName, err) - } - */ - - mod.Info("loaded %d services from %s, advertising with host=%s iface=%s ipv4=%s ipv6=%s", - len(mapping), - fileName, - hostName, - ifName, - mod.Session.Interface.IpAddress, - mod.Session.Interface.Ip6Address) - - advertiser := &Advertiser{ - Filename: fileName, - Mapping: mapping, - Servers: make(map[string]*zeroconf.Server), - } - - for key, svc := range mapping { - server, err := zeroconf.Register(svc.Instance, svc.Service, svc.Domain, svc.Port, svc.Text, nil) - if err != nil { - return fmt.Errorf("could not create service %s: %v", svc.Instance, err) - } - - mod.Info("advertising service %s", tui.Yellow(svc.Service)) - - advertiser.Servers[key] = server - } - - mod.advertiser = advertiser - - mod.Debug("%+v", *mod.advertiser) - - return nil -} - -func (mod *MDNSModule) stopAdvertiser() error { - if mod.advertiser == nil { - return errors.New("advertiser not started") - } - - mod.Info("stopping %d services ...", len(mod.advertiser.Mapping)) - - for key, server := range mod.advertiser.Servers { - mod.Info("stopping %s ...", key) - server.Shutdown() - } - - mod.Info("all services stopped") - - mod.advertiser = nil - return nil -} diff --git a/modules/modules.go b/modules/modules.go index 60eb6264..8af00309 100644 --- a/modules/modules.go +++ b/modules/modules.go @@ -19,7 +19,6 @@ import ( "github.com/bettercap/bettercap/v2/modules/https_proxy" "github.com/bettercap/bettercap/v2/modules/https_server" "github.com/bettercap/bettercap/v2/modules/mac_changer" - "github.com/bettercap/bettercap/v2/modules/mdns" "github.com/bettercap/bettercap/v2/modules/mysql_server" "github.com/bettercap/bettercap/v2/modules/ndp_spoof" "github.com/bettercap/bettercap/v2/modules/net_probe" @@ -33,6 +32,7 @@ import ( "github.com/bettercap/bettercap/v2/modules/update" "github.com/bettercap/bettercap/v2/modules/wifi" "github.com/bettercap/bettercap/v2/modules/wol" + "github.com/bettercap/bettercap/v2/modules/zerogod" "github.com/bettercap/bettercap/v2/session" ) @@ -55,7 +55,7 @@ func LoadModules(sess *session.Session) { sess.Register(https_server.NewHttpsServer(sess)) sess.Register(mac_changer.NewMacChanger(sess)) sess.Register(mysql_server.NewMySQLServer(sess)) - sess.Register(mdns.NewMDNSModule(sess)) + sess.Register(zerogod.NewZeroGod(sess)) sess.Register(net_sniff.NewSniffer(sess)) sess.Register(packet_proxy.NewPacketProxy(sess)) sess.Register(net_probe.NewProber(sess)) diff --git a/modules/net_probe/net_probe.go b/modules/net_probe/net_probe.go index d62da6b4..35c66292 100644 --- a/modules/net_probe/net_probe.go +++ b/modules/net_probe/net_probe.go @@ -118,7 +118,7 @@ func (mod *Prober) Start() error { } if mod.probes.MDNS { - mod.Session.Run("mdns.discovery on") + mod.Session.Run("zerogod.discovery on") } fromIP := mod.Session.Interface.IP @@ -158,7 +158,7 @@ func (mod *Prober) Start() error { func (mod *Prober) Stop() error { return mod.SetRunning(false, func() { if mod.probes.MDNS { - mod.Session.Run("mdns.discovery off") + mod.Session.Run("zerogod.discovery off") } mod.waitGroup.Wait() diff --git a/modules/zerogod/zerogod_acceptor.go b/modules/zerogod/zerogod_acceptor.go new file mode 100644 index 00000000..081d6a85 --- /dev/null +++ b/modules/zerogod/zerogod_acceptor.go @@ -0,0 +1,109 @@ +package zerogod + +import ( + "context" + "crypto/tls" + "fmt" + "net" + "strings" + + "github.com/evilsocket/islazy/tui" +) + +type Handler struct { + TLS bool + Handle func(mod *ZeroGod, client net.Conn, srvHost string, srvPort int, srvTLS bool) +} + +// TODO: add more and possibly autodetect from peeking at the first bytes sent by the client +var TCP_HANDLERS = map[string]Handler{ + "_ipp": { + Handle: ippClientHandler, + }, + "_ipps": { + TLS: true, + Handle: ippClientHandler, + }, + // TODO: _http at least +} + +type Acceptor struct { + mod *ZeroGod + srvHost string + port uint16 + service string + tlsConfig *tls.Config + listener net.Listener + running bool + context context.Context + ctxCancel context.CancelFunc + handler Handler +} + +func NewAcceptor(mod *ZeroGod, service string, srvHost string, port uint16, tlsConfig *tls.Config) *Acceptor { + context, ctcCancel := context.WithCancel(context.Background()) + acceptor := Acceptor{ + mod: mod, + port: port, + service: service, + context: context, + ctxCancel: ctcCancel, + srvHost: srvHost, + } + + for svcName, svcHandler := range TCP_HANDLERS { + if strings.Contains(service, svcName) { + acceptor.tlsConfig = tlsConfig + acceptor.handler = svcHandler + break + } + } + + if acceptor.handler.Handle == nil { + mod.Warning("no protocol handler found for service %s, using generic dump handler", tui.Yellow(service)) + acceptor.handler.Handle = handleGenericTCP + } else { + mod.Info("found %s protocol handler", tui.Green(service)) + } + + return &acceptor +} + +func (a *Acceptor) Start() (err error) { + var lc net.ListenConfig + + if a.listener, err = lc.Listen(a.context, "tcp", fmt.Sprintf("0.0.0.0:%d", a.port)); err != nil { + return err + } + + if a.tlsConfig != nil { + a.listener = tls.NewListener(a.listener, a.tlsConfig) + } + + a.running = true + go func() { + a.mod.Debug("tcp listener for port %d (%s) started", a.port, tui.Green(a.service)) + for a.running { + if conn, err := a.listener.Accept(); err != nil { + if a.running { + a.mod.Error("%v", err) + } + } else { + a.mod.Info("accepted connection for service %s (port %d): %v", tui.Green(a.service), a.port, conn.RemoteAddr()) + go a.handler.Handle(a.mod, conn, a.srvHost, int(a.port), a.tlsConfig != nil) + } + } + a.mod.Debug("tcp listener for port %d (%s) stopped", a.port, tui.Green(a.service)) + }() + + return nil +} + +func (a *Acceptor) Stop() { + a.mod.Debug("stopping tcp listener for port %d", a.port) + a.running = false + a.ctxCancel() + <-a.context.Done() + a.listener.Close() + a.mod.Debug("tcp listener for port %d stopped", a.port) +} diff --git a/modules/zerogod/zerogod_advertise.go b/modules/zerogod/zerogod_advertise.go new file mode 100644 index 00000000..2fd5bfba --- /dev/null +++ b/modules/zerogod/zerogod_advertise.go @@ -0,0 +1,197 @@ +package zerogod + +import ( + "crypto/tls" + "errors" + "fmt" + "io/ioutil" + "os" + "strings" + "time" + + tls_utils "github.com/bettercap/bettercap/v2/tls" + "github.com/evilsocket/islazy/fs" + "github.com/evilsocket/islazy/tui" + "github.com/grandcat/zeroconf" + yaml "gopkg.in/yaml.v3" +) + +type Advertiser struct { + Filename string + Mapping map[string]zeroconf.ServiceEntry + Servers map[string]*zeroconf.Server + Acceptors map[string]*Acceptor +} + +type setupResult struct { + err error + key string + server *zeroconf.Server +} + +func (mod *ZeroGod) startAdvertiser(fileName string) error { + if mod.advertiser != nil { + return fmt.Errorf("advertiser already started for %s", mod.advertiser.Filename) + } + + var certFile string + var keyFile string + var err error + + // read tls configuration + if err, certFile = mod.StringParam("zerogod.advertise.certificate"); err != nil { + return err + } else if certFile, err = fs.Expand(certFile); err != nil { + return err + } + + if err, keyFile = mod.StringParam("zerogod.advertise.key"); err != nil { + return err + } else if keyFile, err = fs.Expand(keyFile); err != nil { + return err + } + + if !fs.Exists(certFile) || !fs.Exists(keyFile) { + cfg, err := tls_utils.CertConfigFromModule("zerogod.advertise", mod.SessionModule) + if err != nil { + return err + } + + mod.Debug("%+v", cfg) + mod.Info("generating server TLS key to %s", keyFile) + mod.Info("generating server TLS certificate to %s", certFile) + if err := tls_utils.Generate(cfg, certFile, keyFile, false); err != nil { + return err + } + } else { + mod.Info("loading server TLS key from %s", keyFile) + mod.Info("loading server TLS certificate from %s", certFile) + } + + cert, err := tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + return err + } + + tlsConfig := tls.Config{ + Certificates: []tls.Certificate{cert}, + InsecureSkipVerify: true, + } + + data, err := ioutil.ReadFile(fileName) + if err != nil { + return fmt.Errorf("could not read %s: %v", fileName, err) + } + + mapping := make(map[string]zeroconf.ServiceEntry) + if err = yaml.Unmarshal(data, &mapping); err != nil { + return fmt.Errorf("could not deserialize %s: %v", fileName, err) + } + + hostName, err := os.Hostname() + if err != nil { + return fmt.Errorf("could not get hostname: %v", err) + } + if !strings.HasSuffix(hostName, ".") { + hostName += "." + } + + mod.Info("loaded %d services from %s, advertising with host=%s iface=%s ipv4=%s ipv6=%s", + len(mapping), + fileName, + hostName, + mod.Session.Interface.Name(), + mod.Session.Interface.IpAddress, + mod.Session.Interface.Ip6Address) + + advertiser := &Advertiser{ + Filename: fileName, + Mapping: mapping, + Servers: make(map[string]*zeroconf.Server), + Acceptors: make(map[string]*Acceptor), + } + + svcChan := make(chan setupResult) + + // paralleize initialization + for key, svc := range mapping { + go func(key string, svc zeroconf.ServiceEntry) { + mod.Info("unregistering instance %s ...", tui.Yellow(fmt.Sprintf("%s.%s.%s", svc.Instance, svc.Service, svc.Domain))) + + // create a first instance just to deregister it from the network + server, err := zeroconf.Register(svc.Instance, svc.Service, svc.Domain, svc.Port, svc.Text, nil) + if err != nil { + svcChan <- setupResult{err: fmt.Errorf("could not create service %s: %v", svc.Instance, err)} + return + } + server.Shutdown() + + // give some time to the network to adjust + time.Sleep(time.Duration(1) * time.Second) + + // now create it again to actually advertise + if server, err = zeroconf.Register(svc.Instance, svc.Service, svc.Domain, svc.Port, svc.Text, nil); err != nil { + svcChan <- setupResult{err: fmt.Errorf("could not create service %s: %v", svc.Instance, err)} + return + } + + mod.Info("advertising service %s", tui.Yellow(svc.Service)) + + svcChan <- setupResult{ + key: key, + server: server, + } + }(key, svc) + } + + for res := range svcChan { + if res.err != nil { + return res.err + } + advertiser.Servers[res.key] = res.server + if len(advertiser.Servers) == len(mapping) { + break + } + } + + // now create the tcp acceptors + for key, svc := range mapping { + acceptor := NewAcceptor(mod, key, hostName, uint16(svc.Port), &tlsConfig) + if err := acceptor.Start(); err != nil { + return err + } + advertiser.Acceptors[key] = acceptor + } + + mod.advertiser = advertiser + + mod.Debug("%+v", *mod.advertiser) + + return nil +} + +func (mod *ZeroGod) stopAdvertiser() error { + if mod.advertiser == nil { + return errors.New("advertiser not started") + } + + mod.Info("stopping %d services ...", len(mod.advertiser.Mapping)) + + for key, server := range mod.advertiser.Servers { + mod.Info("stopping %s ...", key) + server.Shutdown() + } + + mod.Info("all services stopped") + + mod.Info("stopping %d acceptors ...", len(mod.advertiser.Acceptors)) + + for _, acceptor := range mod.advertiser.Acceptors { + acceptor.Stop() + } + + mod.Info("all acceptors stopped") + + mod.advertiser = nil + return nil +} diff --git a/modules/mdns/mdns_discovery.go b/modules/zerogod/zerogod_discovery.go similarity index 62% rename from modules/mdns/mdns_discovery.go rename to modules/zerogod/zerogod_discovery.go index 5c3fe437..669bfe11 100644 --- a/modules/mdns/mdns_discovery.go +++ b/modules/zerogod/zerogod_discovery.go @@ -1,20 +1,18 @@ -package mdns +package zerogod import ( "context" - "fmt" "strings" - "github.com/bettercap/bettercap/v2/modules/syn_scan" "github.com/bettercap/bettercap/v2/network" "github.com/bettercap/bettercap/v2/session" - "github.com/evilsocket/islazy/str" + "github.com/bettercap/bettercap/v2/tls" "github.com/evilsocket/islazy/tui" "github.com/grandcat/zeroconf" ) -type MDNSModule struct { +type ZeroGod struct { session.SessionModule advertiser *Advertiser @@ -24,59 +22,59 @@ type MDNSModule struct { mapping map[string]map[string]*zeroconf.ServiceEntry } -func NewMDNSModule(s *session.Session) *MDNSModule { - mod := &MDNSModule{ - SessionModule: session.NewSessionModule("mdns", s), +func NewZeroGod(s *session.Session) *ZeroGod { + mod := &ZeroGod{ + SessionModule: session.NewSessionModule("zerogod", s), mapping: make(map[string]map[string]*zeroconf.ServiceEntry), resolvers: make(map[string]*zeroconf.Resolver), } mod.SessionModule.Requires("net.recon") - mod.AddHandler(session.NewModuleHandler("mdns.discovery on", "", + mod.AddHandler(session.NewModuleHandler("zerogod.discovery on", "", "Start DNS-SD / mDNS discovery.", func(args []string) error { return mod.Start() })) - mod.AddHandler(session.NewModuleHandler("mdns.discovery off", "", + mod.AddHandler(session.NewModuleHandler("zerogod.discovery off", "", "Stop DNS-SD / mDNS discovery.", func(args []string) error { return mod.Stop() })) // TODO: add autocomplete - mod.AddHandler(session.NewModuleHandler("mdns.show", "", + mod.AddHandler(session.NewModuleHandler("zerogod.show", "", "Show discovered services.", func(args []string) error { return mod.show("", false) })) - mod.AddHandler(session.NewModuleHandler("mdns.show-full", "", + mod.AddHandler(session.NewModuleHandler("zerogod.show-full", "", "Show discovered services and their DNS records.", func(args []string) error { return mod.show("", true) })) - mod.AddHandler(session.NewModuleHandler("mdns.show ADDRESS", "mdns.show (.+)", + mod.AddHandler(session.NewModuleHandler("zerogod.show ADDRESS", "zerogod.show (.+)", "Show discovered services given an ip address.", func(args []string) error { return mod.show(args[0], false) })) - mod.AddHandler(session.NewModuleHandler("mdns.show-full ADDRESS", "mdns.show-full (.+)", + mod.AddHandler(session.NewModuleHandler("zerogod.show-full ADDRESS", "zerogod.show-full (.+)", "Show discovered services and DNS records given an ip address.", func(args []string) error { return mod.show(args[0], true) })) - mod.AddHandler(session.NewModuleHandler("mdns.save ADDRESS FILENAME", "mdns.save (.+) (.+)", + mod.AddHandler(session.NewModuleHandler("zerogod.save ADDRESS FILENAME", "zerogod.save (.+) (.+)", "Save the mDNS information of a given ADDRESS in the FILENAME yaml file.", func(args []string) error { return mod.save(args[0], args[1]) })) - mod.AddHandler(session.NewModuleHandler("mdns.advertise FILENAME", "mdns.advertise (.+)", + mod.AddHandler(session.NewModuleHandler("zerogod.advertise FILENAME", "zerogod.advertise (.+)", "Start advertising the mDNS services from the FILENAME yaml file.", func(args []string) error { if args[0] == "off" { @@ -85,28 +83,40 @@ func NewMDNSModule(s *session.Session) *MDNSModule { return mod.startAdvertiser(args[0]) })) - mod.AddHandler(session.NewModuleHandler("mdns.advertise off", "", + mod.AddHandler(session.NewModuleHandler("zerogod.advertise off", "", "Start a previously started advertiser.", func(args []string) error { return mod.stopAdvertiser() })) + mod.AddParam(session.NewStringParameter("zerogod.advertise.certificate", + "~/.bettercap-zerogod.cert.pem", + "", + "TLS certificate file (will be auto generated if filled but not existing) to use for advertised TCP services.")) + + mod.AddParam(session.NewStringParameter("zerogod.advertise.key", + "~/.bettercap-zerogod.key.pem", + "", + "TLS key file (will be auto generated if filled but not existing) to use for advertised TCP services.")) + + tls.CertConfigToModule("zerogod.advertise", &mod.SessionModule, tls.DefaultLegitConfig) + return mod } -func (mod *MDNSModule) Name() string { - return "mdns" +func (mod *ZeroGod) Name() string { + return "zerogod" } -func (mod *MDNSModule) Description() string { - return "A DNS-SD / mDNS module for discovery and spoofing." +func (mod *ZeroGod) Description() string { + return "A DNS-SD / mDNS / Bonjour / Zeroconf module for discovery and spoofing." } -func (mod *MDNSModule) Author() string { +func (mod *ZeroGod) Author() string { return "Simone Margaritelli " } -func (mod *MDNSModule) Configure() (err error) { +func (mod *ZeroGod) Configure() (err error) { if mod.Running() { return session.ErrAlreadyStarted(mod.Name()) } @@ -127,68 +137,7 @@ type ServiceDiscoveryEvent struct { Endpoint *network.Endpoint `json:"endpoint"` } -func (mod *MDNSModule) updateEndpointMeta(address string, endpoint *network.Endpoint, svc *zeroconf.ServiceEntry) { - mod.Debug("found endpoint %s for address %s", endpoint.HwAddress, address) - - // TODO: this is shit and needs to be refactored - - // update mdns metadata - meta := make(map[string]string) - - svcType := svc.Service - - meta[fmt.Sprintf("mdns:%s:name", svcType)] = svc.ServiceName() - meta[fmt.Sprintf("mdns:%s:hostname", svcType)] = svc.HostName - - // TODO: include all - if len(svc.AddrIPv4) > 0 { - meta[fmt.Sprintf("mdns:%s:ipv4", svcType)] = svc.AddrIPv4[0].String() - } - - if len(svc.AddrIPv6) > 0 { - meta[fmt.Sprintf("mdns:%s:ipv6", svcType)] = svc.AddrIPv6[0].String() - } - - meta[fmt.Sprintf("mdns:%s:port", svcType)] = fmt.Sprintf("%d", svc.Port) - - for _, field := range svc.Text { - field = str.Trim(field) - if len(field) == 0 { - continue - } - - key := "" - value := "" - - if strings.Contains(field, "=") { - parts := strings.SplitN(field, "=", 2) - key = parts[0] - value = parts[1] - } else { - key = field - } - - meta[fmt.Sprintf("mdns:%s:info:%s", svcType, key)] = value - } - - mod.Debug("meta for %s: %v", address, meta) - - endpoint.OnMeta(meta) - - // update ports - ports := endpoint.Meta.GetOr("ports", map[int]*syn_scan.OpenPort{}).(map[int]*syn_scan.OpenPort) - if _, found := ports[svc.Port]; !found { - ports[svc.Port] = &syn_scan.OpenPort{ - Proto: "tcp", - Port: svc.Port, - Service: network.GetServiceByPort(svc.Port, "tcp"), - } - } - - endpoint.Meta.Set("ports", ports) -} - -func (mod *MDNSModule) onServiceDiscovered(svc *zeroconf.ServiceEntry) { +func (mod *ZeroGod) onServiceDiscovered(svc *zeroconf.ServiceEntry) { mod.Debug("%++v", *svc) if svc.Service == "_services._dns-sd._udp" && len(svc.AddrIPv4) == 0 && len(svc.AddrIPv6) == 0 { @@ -243,7 +192,7 @@ func (mod *MDNSModule) onServiceDiscovered(svc *zeroconf.ServiceEntry) { session.I.Refresh() } -func (mod *MDNSModule) startResolver(service string) error { +func (mod *ZeroGod) startResolver(service string) error { mod.Debug("starting resolver for service %s", tui.Yellow(service)) resolver, err := zeroconf.NewResolver(nil) @@ -273,7 +222,7 @@ func (mod *MDNSModule) startResolver(service string) error { return nil } -func (mod *MDNSModule) Start() (err error) { +func (mod *ZeroGod) Start() (err error) { if err = mod.Configure(); err != nil { return err } @@ -292,10 +241,12 @@ func (mod *MDNSModule) Start() (err error) { }) } -func (mod *MDNSModule) Stop() error { +func (mod *ZeroGod) Stop() error { return mod.SetRunning(false, func() { + mod.stopAdvertiser() + if mod.rootCancel != nil { - mod.Debug("stopping mDNS discovery") + mod.Debug("stopping discovery") mod.rootCancel() <-mod.rootContext.Done() diff --git a/modules/zerogod/zerogod_endpoint_update.go b/modules/zerogod/zerogod_endpoint_update.go new file mode 100644 index 00000000..7c1d7087 --- /dev/null +++ b/modules/zerogod/zerogod_endpoint_update.go @@ -0,0 +1,72 @@ +package zerogod + +import ( + "fmt" + "strings" + + "github.com/bettercap/bettercap/v2/modules/syn_scan" + "github.com/bettercap/bettercap/v2/network" + "github.com/evilsocket/islazy/str" + "github.com/grandcat/zeroconf" +) + +func (mod *ZeroGod) updateEndpointMeta(address string, endpoint *network.Endpoint, svc *zeroconf.ServiceEntry) { + mod.Debug("found endpoint %s for address %s", endpoint.HwAddress, address) + + // TODO: this is shit and needs to be refactored + + // update mdns metadata + meta := make(map[string]string) + + svcType := svc.Service + + meta[fmt.Sprintf("mdns:%s:name", svcType)] = svc.ServiceName() + meta[fmt.Sprintf("mdns:%s:hostname", svcType)] = svc.HostName + + // TODO: include all + if len(svc.AddrIPv4) > 0 { + meta[fmt.Sprintf("mdns:%s:ipv4", svcType)] = svc.AddrIPv4[0].String() + } + + if len(svc.AddrIPv6) > 0 { + meta[fmt.Sprintf("mdns:%s:ipv6", svcType)] = svc.AddrIPv6[0].String() + } + + meta[fmt.Sprintf("mdns:%s:port", svcType)] = fmt.Sprintf("%d", svc.Port) + + for _, field := range svc.Text { + field = str.Trim(field) + if len(field) == 0 { + continue + } + + key := "" + value := "" + + if strings.Contains(field, "=") { + parts := strings.SplitN(field, "=", 2) + key = parts[0] + value = parts[1] + } else { + key = field + } + + meta[fmt.Sprintf("mdns:%s:info:%s", svcType, key)] = value + } + + mod.Debug("meta for %s: %v", address, meta) + + endpoint.OnMeta(meta) + + // update ports + ports := endpoint.Meta.GetOr("ports", map[int]*syn_scan.OpenPort{}).(map[int]*syn_scan.OpenPort) + if _, found := ports[svc.Port]; !found { + ports[svc.Port] = &syn_scan.OpenPort{ + Proto: "tcp", + Port: svc.Port, + Service: network.GetServiceByPort(svc.Port, "tcp"), + } + } + + endpoint.Meta.Set("ports", ports) +} diff --git a/modules/zerogod/zerogod_generic_handler.go b/modules/zerogod/zerogod_generic_handler.go new file mode 100644 index 00000000..a0e1a176 --- /dev/null +++ b/modules/zerogod/zerogod_generic_handler.go @@ -0,0 +1,69 @@ +package zerogod + +import ( + "fmt" + "net" +) + +func Dump(by []byte) string { + s := "" + n := len(by) + rowcount := 0 + width := 16 + + stop := (n / width) * width + k := 0 + for i := 0; i <= stop; i += width { + k++ + if i+width < n { + rowcount = width + } else { + rowcount = min(k*width, n) % width + } + + s += fmt.Sprintf("%02d ", i) + for j := 0; j < rowcount; j++ { + s += fmt.Sprintf("%02x ", by[i+j]) + } + for j := rowcount; j < width; j++ { + s += " " + } + s += fmt.Sprintf(" '%s'\n", viewString(by[i:(i+rowcount)])) + } + + return s +} + +func min(a, b int) int { + if a < b { + return a + } + return b +} + +func viewString(b []byte) string { + r := []rune(string(b)) + for i := range r { + if r[i] < 32 || r[i] > 126 { + r[i] = '.' + } + } + return string(r) +} + +func handleGenericTCP(mod *ZeroGod, client net.Conn, srvHost string, srvPort int, srvTLS bool) { + defer client.Close() + + buf := make([]byte, 1024) + for { + if read, err := client.Read(buf); err != nil { + mod.Error("error while reading from %v: %v", client.RemoteAddr(), err) + break + } else if read == 0 { + mod.Error("error while reading from %v: no data", client.RemoteAddr()) + break + } else { + mod.Info("read %d bytes from %v:\n%s\n", read, client.RemoteAddr(), Dump(buf[0:read])) + } + } +} diff --git a/modules/zerogod/zerogod_ipp_handler.go b/modules/zerogod/zerogod_ipp_handler.go new file mode 100644 index 00000000..06b6ef09 --- /dev/null +++ b/modules/zerogod/zerogod_ipp_handler.go @@ -0,0 +1,355 @@ +package zerogod + +import ( + "bufio" + "bytes" + "fmt" + "io" + "net" + "net/http" + "time" + + "github.com/evilsocket/islazy/ops" + "github.com/evilsocket/islazy/tui" + + "github.com/phin1x/go-ipp" +) + +var IPP_REQUEST_NAMES = map[int16]string{ + // https://tools.ietf.org/html/rfc2911#section-4.4.15 + 0x0002: "Print-Job", + 0x0003: "Print-URI", + 0x0004: "Validate-Job", + 0x0005: "Create-Job", + 0x0006: "Send-Document", + 0x0007: "Send-URI", + 0x0008: "Cancel-Job", + 0x0009: "Get-Job-Attributes", + 0x000A: "Get-Jobs", + 0x000B: "Get-Printer-Attributes", + 0x000C: "Hold-Job", + 0x000D: "Release-Job", + 0x000E: "Restart-Job", + 0x0010: "Pause-Printer", + 0x0011: "Resume-Printer", + 0x0012: "Purge-Jobs", + // https://web.archive.org/web/20061024184939/http://uw714doc.sco.com/en/cups/ipp.html + 0x4001: "CUPS-Get-Default", + 0x4002: "CUPS-Get-Printers", + 0x4003: "CUPS-Add-Modify-Printer", + 0x4004: "CUPS-Delete-Printer", + 0x4005: "CUPS-Get-Classes", + 0x4006: "CUPS-Add-Modify-Class", + 0x4007: "CUPS-Delete-Class", + 0x4008: "CUPS-Accept-Jobs", + 0x4009: "CUPS-Reject-Jobs", + 0x400A: "CUPS-Set-Default", + 0x400B: "CUPS-Get-Devices", + 0x400C: "CUPS-Get-PPDs", + 0x400D: "CUPS-Move-Job", +} + +func ippClientHandler(mod *ZeroGod, client net.Conn, srvHost string, srvPort int, srvTLS bool) { + defer client.Close() + + buf := make([]byte, 4096) + + // read raw request + read, err := client.Read(buf) + if err != nil { + if err == io.EOF { + return + } + mod.Error("error while reading from %v: %v", client.RemoteAddr(), err) + return + } else if read == 0 { + mod.Error("error while reading from %v: no data", client.RemoteAddr()) + return + } + + raw_req := buf[0:read] + + mod.Debug("read %d bytes from %v:\n%s\n", read, client.RemoteAddr(), Dump(raw_req)) + + // parse as http + reader := bufio.NewReader(bytes.NewReader(raw_req)) + http_req, err := http.ReadRequest(reader) + if err != nil { + mod.Error("error while parsing http request from %v: %v", client.RemoteAddr(), err) + return + } + + mod.Info("%v -> %s", client.RemoteAddr(), tui.Green(http_req.UserAgent())) + + ipp_body := http_req.Body + + // check for an Expect 100-continue + if http_req.Header.Get("Expect") == "100-continue" { + // inform the client we're ready to read the request body + client.Write([]byte("HTTP/1.1 100 Continue\r\n\r\n")) + // read the body + read, err := client.Read(buf) + if err != nil { + if err == io.EOF { + return + } + mod.Error("error while reading ipp body from %v: %v", client.RemoteAddr(), err) + return + } else if read == 0 { + mod.Error("error while reading ipp body from %v: no data", client.RemoteAddr()) + return + } + + ipp_body = io.NopCloser(bytes.NewReader(buf[0:read])) + } + + // parse as IPP + ipp_req, err := ipp.NewRequestDecoder(ipp_body).Decode(nil) + if err != nil { + mod.Error("error while parsing ip request from %v: %v", client.RemoteAddr(), err) + return + } + + ipp_op_name := fmt.Sprintf("", ipp_req.Operation) + if name, found := IPP_REQUEST_NAMES[ipp_req.Operation]; found { + ipp_op_name = name + } + + mod.Info("%v op=%s attributes=%v", client.RemoteAddr(), tui.Bold(ipp_op_name), ipp_req.OperationAttributes) + + switch ipp_req.Operation { + // Get-Printer-Attributes + case 0x000B: + ippOnGetPrinterAttributes(mod, client, ipp_req, srvHost, srvPort, srvTLS) + + default: + ippOnUnhandledRequest(mod, client, ipp_req, ipp_op_name) + } +} + +func ippSendResponse(mod *ZeroGod, client net.Conn, response *ipp.Response) { + mod.Debug("SENDING %++v", *response) + + resp_data, err := response.Encode() + if err != nil { + mod.Error("error while encoding ipp response: %v", err) + return + } + + headers := [][]byte{ + []byte("HTTP/1.1 200 OK\r\n"), + []byte("Content-Type: application/ipp\r\n"), + []byte(fmt.Sprintf("Content-Length: %d\r\n", len(resp_data))), + []byte("Connection: close\r\n"), + []byte("\r\n"), + } + + for _, header := range headers { + if _, err := client.Write(header); err != nil { + mod.Error("error while writing header: %v", err) + return + } + } + + if _, err = client.Write(resp_data); err != nil { + mod.Error("error while writing ipp response data: %v", err) + return + } + + mod.Debug("sent %d of ipp response to %v", len(resp_data), client.RemoteAddr()) +} + +func ippOnUnhandledRequest(mod *ZeroGod, client net.Conn, ipp_req *ipp.Request, ipp_op_name string) { + ipp_resp := ipp.NewResponse(ipp.StatusErrorOperationNotSupported, ipp_req.RequestId) + + ippSendResponse(mod, client, ipp_resp) + + mod.Warning("unhandled request from %v: operation=%s", client.RemoteAddr(), ipp_op_name) +} + +func ippOnGetPrinterAttributes(mod *ZeroGod, client net.Conn, ipp_req *ipp.Request, srvHost string, srvPort int, srvTLS bool) { + ipp_resp := ipp.NewResponse(ipp.StatusOk, ipp_req.RequestId) + + // https://tools.ietf.org/html/rfc2911 section 3.1.4.2 Response Operation Attributes + ipp_resp.OperationAttributes["attributes-charset"] = []ipp.Attribute{ + { + Value: "utf-8", + Tag: ipp.TagCharset, + }, + } + ipp_resp.OperationAttributes["attributes-natural-language"] = []ipp.Attribute{ + { + Value: "en", + Tag: ipp.TagLanguage, + }, + } + + // rfc2911 section 4.4 + ipp.AttributeTagMapping["printer-uri-supported"] = ipp.TagUri + ipp.AttributeTagMapping["uri-authentication-supported"] = ipp.TagKeyword + ipp.AttributeTagMapping["uri-security-supported"] = ipp.TagKeyword + ipp.AttributeTagMapping["printer-name"] = ipp.TagName + ipp.AttributeTagMapping["printer-info"] = ipp.TagText + ipp.AttributeTagMapping["printer-make-and-model"] = ipp.TagText + ipp.AttributeTagMapping["printer-state"] = ipp.TagEnum + ipp.AttributeTagMapping["printer-state-reasons"] = ipp.TagKeyword + ipp.AttributeTagMapping["ipp-versions-supported"] = ipp.TagKeyword + ipp.AttributeTagMapping["operations-supported"] = ipp.TagEnum + ipp.AttributeTagMapping["multiple-document-jobs-supported"] = ipp.TagBoolean + ipp.AttributeTagMapping["charset-configured"] = ipp.TagCharset + ipp.AttributeTagMapping["charset-supported"] = ipp.TagCharset + ipp.AttributeTagMapping["natural-language-configured"] = ipp.TagLanguage + ipp.AttributeTagMapping["generated-natural-language-supported"] = ipp.TagLanguage + ipp.AttributeTagMapping["document-format-default"] = ipp.TagMimeType + ipp.AttributeTagMapping["document-format-supported"] = ipp.TagMimeType + ipp.AttributeTagMapping["printer-is-accepting-jobs"] = ipp.TagBoolean + ipp.AttributeTagMapping["queued-job-count"] = ipp.TagInteger + ipp.AttributeTagMapping["pdl-override-supported"] = ipp.TagKeyword + ipp.AttributeTagMapping["printer-up-time"] = ipp.TagInteger + ipp.AttributeTagMapping["compression-supported"] = ipp.TagKeyword + + ipp_resp.PrinterAttributes = []ipp.Attributes{ + { + "printer-uri-supported": []ipp.Attribute{ + { + Value: fmt.Sprintf("%s://%s:%d/printer", ops.Ternary(srvTLS, "ipps", "ipp"), srvHost, srvPort), + Tag: ipp.TagUri, + }, + }, + "uri-authentication-supported": []ipp.Attribute{ + { + Value: "none", + Tag: ipp.TagKeyword, + }, + }, + "uri-security-supported": []ipp.Attribute{ + { + Value: ops.Ternary(srvTLS, "tls", "none"), + Tag: ipp.TagKeyword, + }, + }, + "printer-name": []ipp.Attribute{ + { + Value: "PRINTER_NAME", + Tag: ipp.TagName, + }, + }, + "printer-info": []ipp.Attribute{ + { + Value: "PRINTER_INFO", + Tag: ipp.TagText, + }, + }, + "printer-make-and-model": []ipp.Attribute{ + { + Value: "PRINTER_MAKE PRINTER_MODEL", + Tag: ipp.TagText, + }, + }, + "printer-state": []ipp.Attribute{ + { + Value: 3, // idle + Tag: ipp.TagEnum, + }, + }, + "printer-state-reasons": []ipp.Attribute{ + { + Value: "none", + Tag: ipp.TagKeyword, + }, + }, + "ipp-versions-supported": []ipp.Attribute{ + { + Value: "1.1", + Tag: ipp.TagKeyword, + }, + }, + "operations-supported": []ipp.Attribute{ + { + Value: []int{ + 0x0002, // print job (required by cups) + 0x0004, // validate job (required by cups) + 0x0008, // cancel job (required by cups) + 0x0009, // get job attributes (required by cups) + 0x000b, // get printer attributes + }, + Tag: ipp.TagEnum, + }, + }, + "multiple-document-jobs-supported": []ipp.Attribute{ + { + Value: false, + Tag: ipp.TagBoolean, + }, + }, + "charset-configured": []ipp.Attribute{ + { + Value: "utf-8", + Tag: ipp.TagCharset, + }, + }, + "charset-supported": []ipp.Attribute{ + { + Value: "utf-8", + Tag: ipp.TagCharset, + }, + }, + "natural-language-configured": []ipp.Attribute{ + { + Value: "en", + Tag: ipp.TagLanguage, + }, + }, + "generated-natural-language-supported": []ipp.Attribute{ + { + Value: "en", + Tag: ipp.TagLanguage, + }, + }, + "document-format-default": []ipp.Attribute{ + { + Value: "application/pdf", + Tag: ipp.TagMimeType, + }, + }, + "document-format-supported": []ipp.Attribute{ + { + Value: "application/pdf", + Tag: ipp.TagMimeType, + }, + }, + "printer-is-accepting-jobs": []ipp.Attribute{ + { + Value: true, + Tag: ipp.TagBoolean, + }, + }, + "queued-job-count": []ipp.Attribute{ + { + Value: 0, + Tag: ipp.TagInteger, + }, + }, + "pdl-override-supported": []ipp.Attribute{ + { + Value: "not-attempted", + Tag: ipp.TagKeyword, + }, + }, + "printer-up-time": []ipp.Attribute{ + { + Value: time.Now().Unix(), + Tag: ipp.TagInteger, + }, + }, + "compression-supported": []ipp.Attribute{ + { + Value: "none", + Tag: ipp.TagKeyword, + }, + }, + }, + } + + ippSendResponse(mod, client, ipp_resp) +} diff --git a/modules/mdns/mdns_save.go b/modules/zerogod/zerogod_save.go similarity index 88% rename from modules/mdns/mdns_save.go rename to modules/zerogod/zerogod_save.go index d1d099b2..60b1ef42 100644 --- a/modules/mdns/mdns_save.go +++ b/modules/zerogod/zerogod_save.go @@ -1,4 +1,4 @@ -package mdns +package zerogod import ( "fmt" @@ -7,7 +7,7 @@ import ( yaml "gopkg.in/yaml.v3" ) -func (mod *MDNSModule) save(address, filename string) error { +func (mod *ZeroGod) save(address, filename string) error { if address == "" { return fmt.Errorf("address cannot be empty") } diff --git a/modules/mdns/mdns_show.go b/modules/zerogod/zerogod_show.go similarity index 95% rename from modules/mdns/mdns_show.go rename to modules/zerogod/zerogod_show.go index 3ad238bb..46f4ea8b 100644 --- a/modules/mdns/mdns_show.go +++ b/modules/zerogod/zerogod_show.go @@ -1,4 +1,4 @@ -package mdns +package zerogod import ( "fmt" @@ -14,7 +14,7 @@ type entry struct { services map[string]*zeroconf.ServiceEntry } -func (mod *MDNSModule) show(filter string, withData bool) error { +func (mod *ZeroGod) show(filter string, withData bool) error { fmt.Fprintf(mod.Session.Events.Stdout, "\n") // convert to list for sorting diff --git a/printer3.yml b/printer3.yml new file mode 100644 index 00000000..77aab236 --- /dev/null +++ b/printer3.yml @@ -0,0 +1,187 @@ +EPSON\ XP-630\ Series-59F5BA-02._http._tcp.local.: + servicerecord: + instance: EPSON\ XP-630\ Series-59F5BA-02 + service: _http._tcp + domain: local. + hostname: EPSON59F5BA.local. + port: 80 + text: + - "" + ttl: 4500 + addripv4: + - 192.168.50.21 + addripv6: + - fe80::46d2:44ff:fe59:f5ba +EPSON\ XP-630\ Series-59F5BA-02._ipp._tcp.local.: + servicerecord: + instance: EPSON\ XP-630\ Series-59F5BA-02 + service: _ipp._tcp + domain: local. + hostname: EPSON59F5BA.local. + port: 631 + text: [] + ttl: 120 + addripv4: + - 192.168.50.21 + addripv6: + - fe80::46d2:44ff:fe59:f5ba +EPSON\ XP-630\ Series-59F5BA-02._ipps._tcp.local.: + servicerecord: + instance: EPSON\ XP-630\ Series-59F5BA-02 + service: _ipps._tcp + domain: local. + hostname: EPSON59F5BA.local. + port: 631 + text: + - txtvers=1 + - ty=EPSON XP-630 Series + - usb_MFG=EPSON + - usb_MDL=XP-630 Series + - product=(EPSON XP-630 Series) + - pdl=application/octet-stream,image/pwg-raster,image/urf,image/jpeg + - rp=ipp/print + - qtotal=1 + - Color=T + - Duplex=T + - Scan=T + - Fax=F + - kind=document,envelope,label,photo + - PaperMax=legal-A4 + - URF=CP1,MT1-3-5-8-10-11-12,PQ4-5,OB9,OFU0,RS360,SRGB24,W8,DM3,IS1-7,V1.4 + - mopria-certified=1.2 + - priority=30 + - adminurl=http://EPSON59F5BA.local.:80/PRESENTATION/BONJOUR + - note= + - UUID=cfe92100-67c4-11d4-a45f-44d24459f5ba + - TLS=1.2 + ttl: 4500 + addripv4: + - 192.168.50.21 + addripv6: + - fe80::46d2:44ff:fe59:f5ba +EPSON\ XP-630\ Series-59F5BA-02._pdl-datastream._tcp.local.: + servicerecord: + instance: EPSON\ XP-630\ Series-59F5BA-02 + service: _pdl-datastream._tcp + domain: local. + hostname: EPSON59F5BA.local. + port: 9100 + text: + - txtvers=1 + - priority=40 + - ty=EPSON XP-630 Series + - usb_MFG=EPSON + - usb_MDL=XP-630 Series + - product=(EPSON XP-630 Series) + - pdl=raw + - qtotal=1 + - adminurl=http://EPSON59F5BA.local.:80/PRESENTATION/BONJOUR + - note= + ttl: 4500 + addripv4: + - 192.168.50.21 + addripv6: + - fe80::46d2:44ff:fe59:f5ba +EPSON\ XP-630\ Series-59F5BA-02._printer._tcp.local.: + servicerecord: + instance: EPSON\ XP-630\ Series-59F5BA-02 + service: _printer._tcp + domain: local. + hostname: EPSON59F5BA.local. + port: 515 + text: + - txtvers=1 + - priority=50 + - ty=EPSON XP-630 Series + - usb_MFG=EPSON + - usb_MDL=XP-630 Series + - product=(EPSON XP-630 Series) + - pdl=raw + - rp=auto + - qtotal=1 + - adminurl=http://EPSON59F5BA.local.:80/PRESENTATION/BONJOUR + - note= + ttl: 4500 + addripv4: + - 192.168.50.21 + addripv6: + - fe80::46d2:44ff:fe59:f5ba +EPSON\ XP-630\ Series-59F5BA-02._privet._tcp.local.: + servicerecord: + instance: EPSON\ XP-630\ Series-59F5BA-02 + service: _privet._tcp + domain: local. + hostname: EPSON59F5BA.local. + port: 80 + text: + - txtvers=1 + - ty=EPSON XP-630 Series (EPSON59F5BA) + - url=https://www.google.com/cloudprint + - type=printer + - id=0936a89f-33d7-80f5-c1bc-7421d40a78b5 + - cs=offline + ttl: 4500 + addripv4: + - 192.168.50.21 + addripv6: + - fe80::46d2:44ff:fe59:f5ba +EPSON\ XP-630\ Series-59F5BA-02._scanner._tcp.local.: + servicerecord: + instance: EPSON\ XP-630\ Series-59F5BA-02 + service: _scanner._tcp + domain: local. + hostname: EPSON59F5BA.local. + port: 1865 + text: + - txtvers=1 + - ty=EPSON XP-630 Series + - adminurl=http://EPSON59F5BA.local.:80/PRESENTATION/BONJOUR + - mfg=EPSON + - mdl=XP-630 Series + - UUID=cfe92100-67c4-11d4-a45f-44d24459f5ba + - scannerAvailable=0 + - note= + ttl: 4500 + addripv4: + - 192.168.50.21 + addripv6: + - fe80::46d2:44ff:fe59:f5ba +EPSON\ XP-630\ Series-59F5BA-02._smb._tcp.local.: + servicerecord: + instance: EPSON\ XP-630\ Series-59F5BA-02 + service: _smb._tcp + domain: local. + hostname: EPSON59F5BA.local. + port: 445 + text: + - "" + ttl: 4500 + addripv4: + - 192.168.50.21 + addripv6: + - fe80::46d2:44ff:fe59:f5ba +EPSON\ XP-630\ Series-59F5BA-02._uscan._tcp.local.: + servicerecord: + instance: EPSON\ XP-630\ Series-59F5BA-02 + service: _uscan._tcp + domain: local. + hostname: EPSON59F5BA.local. + port: 443 + text: + - txtvers=1 + - vers=2.5 + - representation=/PRESENTATION/AIRPRINT/PRINTER_128.PNG + - rs=eSCL + - ty=EPSON XP-630 Series + - pdl=application/pdf,image/jpeg + - cs=color,grayscale,binary + - is=platen + - duplex=F + - adminurl=http://EPSON59F5BA.local.:80/PRESENTATION/BONJOUR + - UUID=cfe92100-67c4-11d4-a45f-44d24459f5ba + - note= + ttl: 4500 + addripv4: + - 192.168.50.21 + addripv6: + - fe80::46d2:44ff:fe59:f5ba diff --git a/test.yml b/test.yml new file mode 100644 index 00000000..d4a70eb1 --- /dev/null +++ b/test.yml @@ -0,0 +1,207 @@ +EPSON\ XP-630\ Series-59F5BA-04._http._tcp.local.: + servicerecord: + instance: EPSON\ XP-630\ Series-59F5BA-04 + service: _http._tcp + domain: local. + hostname: EPSON59F5BA.local. + port: 80 + text: + - "" + ttl: 4500 + addripv4: + - 192.168.50.21 + addripv6: + - fe80::46d2:44ff:fe59:f5ba +EPSON\ XP-630\ Series-59F5BA-04._ipp._tcp.local.: + servicerecord: + instance: EPSON\ XP-630\ Series-59F5BA-04 + service: _ipp._tcp + domain: local. + hostname: EPSON59F5BA.local. + port: 6633 + text: + - txtvers=1 + - ty=EPSON XP-630 Series + - usb_MFG=EPSON + - usb_MDL=XP-630 Series + - product=(EPSON XP-630 Series) + - pdl=application/octet-stream,image/pwg-raster,image/urf,image/jpeg + - rp=ipp/print + - qtotal=1 + - Color=T + - Duplex=T + - Scan=T + - Fax=F + - kind=document,envelope,label,photo + - PaperMax=legal-A4 + - URF=CP1,MT1-3-5-8-10-11-12,PQ4-5,OB9,OFU0,RS360,SRGB24,W8,DM3,IS1-7,V1.4 + - mopria-certified=1.2 + - priority=30 + - adminurl=http://EPSON59F5BA.local.:80/PRESENTATION/BONJOUR + - note= + - UUID=cfe92100-67c4-11d4-a45f-44d24459f5ba + - TLS=1.2 + ttl: 4500 + addripv4: + - 192.168.50.21 + addripv6: + - fe80::46d2:44ff:fe59:f5ba +EPSON\ XP-630\ Series-59F5BA-04._ipps._tcp.local.: + servicerecord: + instance: EPSON\ XP-630\ Series-59F5BA-04 + service: _ipps._tcp + domain: local. + hostname: EPSON59F5BA.local. + port: 6631 + text: + - txtvers=1 + - ty=EPSON XP-630 Series + - usb_MFG=EPSON + - usb_MDL=XP-630 Series + - product=(EPSON XP-630 Series) + - pdl=application/octet-stream,image/pwg-raster,image/urf,image/jpeg + - rp=ipp/print + - qtotal=1 + - Color=T + - Duplex=T + - Scan=T + - Fax=F + - kind=document,envelope,label,photo + - PaperMax=legal-A4 + - URF=CP1,MT1-3-5-8-10-11-12,PQ4-5,OB9,OFU0,RS360,SRGB24,W8,DM3,IS1-7,V1.4 + - mopria-certified=1.2 + - priority=30 + - adminurl=http://EPSON59F5BA.local.:80/PRESENTATION/BONJOUR + - note= + - UUID=cfe92100-67c4-11d4-a45f-44d24459f5ba + - TLS=1.2 + ttl: 4500 + addripv4: + - 192.168.50.21 + addripv6: + - fe80::46d2:44ff:fe59:f5ba +EPSON\ XP-630\ Series-59F5BA-04._pdl-datastream._tcp.local.: + servicerecord: + instance: EPSON\ XP-630\ Series-59F5BA-04 + service: _pdl-datastream._tcp + domain: local. + hostname: EPSON59F5BA.local. + port: 9100 + text: + - txtvers=1 + - priority=40 + - ty=EPSON XP-630 Series + - usb_MFG=EPSON + - usb_MDL=XP-630 Series + - product=(EPSON XP-630 Series) + - pdl=raw + - qtotal=1 + - adminurl=http://EPSON59F5BA.local.:80/PRESENTATION/BONJOUR + - note= + ttl: 4500 + addripv4: + - 192.168.50.21 + addripv6: + - fe80::46d2:44ff:fe59:f5ba +EPSON\ XP-630\ Series-59F5BA-04._printer._tcp.local.: + servicerecord: + instance: EPSON\ XP-630\ Series-59F5BA-04 + service: _printer._tcp + domain: local. + hostname: EPSON59F5BA.local. + port: 1515 + text: + - txtvers=1 + - priority=50 + - ty=EPSON XP-630 Series + - usb_MFG=EPSON + - usb_MDL=XP-630 Series + - product=(EPSON XP-630 Series) + - pdl=raw + - rp=auto + - qtotal=1 + - adminurl=http://EPSON59F5BA.local.:80/PRESENTATION/BONJOUR + - note= + ttl: 4500 + addripv4: + - 192.168.50.21 + addripv6: + - fe80::46d2:44ff:fe59:f5ba +EPSON\ XP-630\ Series-59F5BA-04._privet._tcp.local.: + servicerecord: + instance: EPSON\ XP-630\ Series-59F5BA-04 + service: _privet._tcp + domain: local. + hostname: EPSON59F5BA.local. + port: 8081 + text: + - txtvers=1 + - ty=EPSON XP-630 Series (EPSON59F5BA) + - url=https://www.google.com/cloudprint + - type=printer + - id=0936a89f-33d7-80f5-c1bc-7421d40a78b5 + - cs=offline + ttl: 4500 + addripv4: + - 192.168.50.21 + addripv6: + - fe80::46d2:44ff:fe59:f5ba +EPSON\ XP-630\ Series-59F5BA-04._scanner._tcp.local.: + servicerecord: + instance: EPSON\ XP-630\ Series-59F5BA-04 + service: _scanner._tcp + domain: local. + hostname: EPSON59F5BA.local. + port: 1865 + text: + - txtvers=1 + - ty=EPSON XP-630 Series + - adminurl=http://EPSON59F5BA.local.:80/PRESENTATION/BONJOUR + - mfg=EPSON + - mdl=XP-630 Series + - UUID=cfe92100-67c4-11d4-a45f-44d24459f5ba + - scannerAvailable=0 + - note= + ttl: 4500 + addripv4: + - 192.168.50.21 + addripv6: + - fe80::46d2:44ff:fe59:f5ba +EPSON\ XP-630\ Series-59F5BA-04._smb._tcp.local.: + servicerecord: + instance: EPSON\ XP-630\ Series-59F5BA-04 + service: _smb._tcp + domain: local. + hostname: EPSON59F5BA.local. + port: 1445 + text: [] + ttl: 120 + addripv4: + - 192.168.50.21 + addripv6: + - fe80::46d2:44ff:fe59:f5ba +EPSON\ XP-630\ Series-59F5BA-04._uscan._tcp.local.: + servicerecord: + instance: EPSON\ XP-630\ Series-59F5BA-04 + service: _uscan._tcp + domain: local. + hostname: EPSON59F5BA.local. + port: 8443 + text: + - txtvers=1 + - vers=2.5 + - representation=/PRESENTATION/AIRPRINT/PRINTER_128.PNG + - rs=eSCL + - ty=EPSON XP-630 Series + - pdl=application/pdf,image/jpeg + - cs=color,grayscale,binary + - is=platen + - duplex=F + - adminurl=http://EPSON59F5BA.local.:80/PRESENTATION/BONJOUR + - UUID=cfe92100-67c4-11d4-a45f-44d24459f5ba + - note= + ttl: 4500 + addripv4: + - 192.168.50.21 + addripv6: + - fe80::46d2:44ff:fe59:f5ba