misc: small fix or general refactoring i did not bother commenting

This commit is contained in:
Simone Margaritelli 2024-09-20 12:06:32 +02:00
commit 17ba1be16c
12 changed files with 239 additions and 498 deletions

3
.gitignore vendored
View file

@ -15,4 +15,5 @@ stage/
/snap
.idea
/cover.out
/can-data
/can-data
/test*.yml

View file

@ -1,26 +0,0 @@
- name: TEST_NAME
service: _ipps._tcp
domain: local.
port: 6631
records:
- 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

View file

@ -1,27 +0,0 @@
- name: TEST_NAME
service: _ipps._tcp
domain: local.
port: 6631
responder: 134.122.95.96
records:
- 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

View file

@ -12,10 +12,10 @@ import (
type Handler struct {
TLS bool
Handle func(mod *ZeroGod, client net.Conn, srvHost string, srvPort int, srvTLS bool)
Handle func(ctx *HandlerContext)
}
// TODO: add more and possibly autodetect from peeking at the first bytes sent by the client
// TODO: possibly autodetect from peeking at the first bytes sent by the client
var TCP_HANDLERS = map[string]Handler{
"_ipp": {
Handle: ippClientHandler,
@ -28,27 +28,38 @@ var TCP_HANDLERS = map[string]Handler{
}
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
mod *ZeroGod
srvHost string
port uint16
service string
tlsConfig *tls.Config
listener net.Listener
running bool
context context.Context
ctxCancel context.CancelFunc
handler Handler
ippAttributes map[string]string
}
func NewAcceptor(mod *ZeroGod, service string, srvHost string, port uint16, tlsConfig *tls.Config) *Acceptor {
type HandlerContext struct {
mod *ZeroGod
client net.Conn
srvHost string
srvPort int
srvTLS bool
ippAttributes map[string]string
}
func NewAcceptor(mod *ZeroGod, service string, srvHost string, port uint16, tlsConfig *tls.Config, ippAttributes map[string]string) *Acceptor {
context, ctcCancel := context.WithCancel(context.Background())
acceptor := Acceptor{
mod: mod,
port: port,
service: service,
context: context,
ctxCancel: ctcCancel,
srvHost: srvHost,
mod: mod,
port: port,
service: service,
context: context,
ctxCancel: ctcCancel,
srvHost: srvHost,
ippAttributes: ippAttributes,
}
for svcName, svcHandler := range TCP_HANDLERS {
@ -90,7 +101,14 @@ func (a *Acceptor) Start() (err error) {
}
} 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)
go a.handler.Handle(&HandlerContext{
mod: a.mod,
client: conn,
srvHost: a.srvHost,
srvPort: int(a.port),
srvTLS: a.tlsConfig != nil,
ippAttributes: a.ippAttributes,
})
}
}
a.mod.Debug("tcp listener for port %d (%s) stopped", a.port, tui.Green(a.service))

View file

@ -5,7 +5,6 @@ import (
"errors"
"fmt"
"io/ioutil"
"net"
"os"
"strings"
"time"
@ -13,15 +12,13 @@ import (
tls_utils "github.com/bettercap/bettercap/v2/tls"
"github.com/bettercap/bettercap/v2/zeroconf"
"github.com/evilsocket/islazy/fs"
"github.com/evilsocket/islazy/tui"
yaml "gopkg.in/yaml.v3"
)
type Advertiser struct {
Filename string
Services []ServiceData
Servers []*zeroconf.Server
Services []*ServiceData
Acceptors []*Acceptor
}
@ -99,7 +96,7 @@ func (mod *ZeroGod) startAdvertiser(fileName string) error {
return fmt.Errorf("could not read %s: %v", fileName, err)
}
var services []ServiceData
var services []*ServiceData
if err = yaml.Unmarshal(data, &services); err != nil {
return fmt.Errorf("could not deserialize %s: %v", fileName, err)
}
@ -109,101 +106,47 @@ func (mod *ZeroGod) startAdvertiser(fileName string) error {
advertiser := &Advertiser{
Filename: fileName,
Services: services,
Servers: make([]*zeroconf.Server, 0),
Acceptors: make([]*Acceptor, 0),
}
svcChan := make(chan setupResult)
// paralleize initialization
for _, svc := range services {
go func(svc ServiceData) {
mod.Info("unregistering instance %s ...", tui.Yellow(svc.FullName()))
svcChan := make(chan error)
for _, svc := range advertiser.Services {
go func(svc *ServiceData) {
// deregister the service from the network first
if err := svc.Unregister(); err != nil {
svcChan <- setupResult{err: fmt.Errorf("could not unregister service %s: %v", svc.FullName(), err)}
if err := svc.Unregister(mod); err != nil {
svcChan <- fmt.Errorf("could not unregister service %s: %v", svc.FullName(), err)
return
}
// give some time to the network to adjust
time.Sleep(time.Duration(1) * time.Second)
var server *zeroconf.Server
// now create it again to actually advertise
if svc.Responder == "" {
// use our own IP
if server, err = zeroconf.Register(
svc.Name,
svc.Service,
svc.Domain,
svc.Port,
svc.Records,
nil); err != nil {
svcChan <- setupResult{err: fmt.Errorf("could not create service %s: %v", svc.FullName(), err)}
return
}
mod.Info("advertising %s with responder=%s port=%d",
tui.Yellow(svc.FullName()),
tui.Red(svc.Responder),
svc.Port)
// register it
if err := svc.Register(mod); err != nil {
svcChan <- err
} else {
responderHostName := ""
// try first to do a reverse DNS of the ip
if addr, err := net.LookupAddr(svc.Responder); err == nil && len(addr) > 0 {
responderHostName = addr[0]
} else {
mod.Debug("could not get responder %s reverse dns entry: %v", svc.Responder, err)
}
// if we don't have a host, create a .nip.io representation
if responderHostName == "" {
responderHostName = fmt.Sprintf("%s.nip.io.", strings.ReplaceAll(svc.Responder, ".", "-"))
}
// use external responder
if server, err = zeroconf.RegisterExternalResponder(
svc.Name,
svc.Service,
svc.Domain,
svc.Port,
responderHostName,
[]string{svc.Responder},
svc.Records,
nil); err != nil {
svcChan <- setupResult{err: fmt.Errorf("could not create service %s: %v", svc.FullName(), err)}
return
}
mod.Info("advertising %s with responder=%s hostname=%s port=%d",
tui.Yellow(svc.FullName()),
tui.Red(svc.Responder),
tui.Yellow(responderHostName),
svc.Port)
}
svcChan <- setupResult{
server: server,
svcChan <- nil
}
}(svc)
}
for res := range svcChan {
if res.err != nil {
return res.err
got := 0
for err := range svcChan {
if err != nil {
return err
}
advertiser.Servers = append(advertiser.Servers, res.server)
if len(advertiser.Servers) == len(advertiser.Services) {
got++
if got == len(advertiser.Services) {
break
}
}
// now create the tcp acceptors for entries without an explicit responder address
for _, svc := range advertiser.Services {
// if no external responder has been specified
if svc.Responder == "" {
acceptor := NewAcceptor(mod, svc.FullName(), hostName, uint16(svc.Port), tlsConfig)
acceptor := NewAcceptor(mod, svc.FullName(), hostName, uint16(svc.Port), tlsConfig, svc.IPP)
if err := acceptor.Start(); err != nil {
return err
}
@ -225,9 +168,8 @@ func (mod *ZeroGod) stopAdvertiser() error {
mod.Info("stopping %d services ...", len(mod.advertiser.Services))
for key, server := range mod.advertiser.Servers {
mod.Info("stopping %s ...", key)
server.Shutdown()
for _, service := range mod.advertiser.Services {
service.Unregister(mod)
}
mod.Info("all services stopped")

View file

@ -42,7 +42,6 @@ func NewZeroGod(s *session.Session) *ZeroGod {
return mod.Stop()
}))
// TODO: add autocomplete
mod.AddHandler(session.NewModuleHandler("zerogod.show", "",
"Show discovered services.",
func(args []string) error {
@ -55,6 +54,7 @@ func NewZeroGod(s *session.Session) *ZeroGod {
return mod.show("", true)
}))
// TODO: add autocomplete
mod.AddHandler(session.NewModuleHandler("zerogod.show ADDRESS", "zerogod.show (.+)",
"Show discovered services given an ip address.",
func(args []string) error {

View file

@ -23,13 +23,11 @@ func (mod *ZeroGod) updateEndpointMeta(address string, endpoint *network.Endpoin
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()
for i, ip := range svc.AddrIPv4 {
meta[fmt.Sprintf("mdns:%s:ipv4[%d]", svcType, i)] = ip.String()
}
if len(svc.AddrIPv6) > 0 {
meta[fmt.Sprintf("mdns:%s:ipv6", svcType)] = svc.AddrIPv6[0].String()
for i, ip := range svc.AddrIPv6 {
meta[fmt.Sprintf("mdns:%s:ipv6[%d]", svcType, i)] = ip.String()
}
meta[fmt.Sprintf("mdns:%s:port", svcType)] = fmt.Sprintf("%d", svc.Port)

View file

@ -2,7 +2,6 @@ package zerogod
import (
"fmt"
"net"
)
func Dump(by []byte) string {
@ -51,19 +50,19 @@ func viewString(b []byte) string {
return string(r)
}
func handleGenericTCP(mod *ZeroGod, client net.Conn, srvHost string, srvPort int, srvTLS bool) {
defer client.Close()
func handleGenericTCP(ctx *HandlerContext) {
defer ctx.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)
if read, err := ctx.client.Read(buf); err != nil {
ctx.mod.Error("error while reading from %v: %v", ctx.client.RemoteAddr(), err)
break
} else if read == 0 {
mod.Error("error while reading from %v: no data", client.RemoteAddr())
ctx.mod.Error("error while reading from %v: no data", ctx.client.RemoteAddr())
break
} else {
mod.Info("read %d bytes from %v:\n%s\n", read, client.RemoteAddr(), Dump(buf[0:read]))
ctx.mod.Info("read %d bytes from %v:\n%s\n", read, ctx.client.RemoteAddr(), Dump(buf[0:read]))
}
}
}

View file

@ -5,7 +5,6 @@ import (
"bytes"
"fmt"
"io"
"net"
"net/http"
"time"
@ -49,6 +48,15 @@ var IPP_REQUEST_NAMES = map[int16]string{
0x400D: "CUPS-Move-Job",
}
var IPP_USER_ATTRIBUTES = map[string]string{
"printer-name": "PRINTER_NAME",
"printer-info": "PRINTER_INFO",
"printer-make-and-model": "PRINTER_MAKE PRINTER_MODEL",
"printer-location": "PRINTER_LOCATION",
"printer-privacy-policy-uri": "https://www.bettercap.org/",
"ppd-name": "everywhere",
}
func init() {
ipp.AttributeTagMapping["printer-uri-supported"] = ipp.TagUri
ipp.AttributeTagMapping["uri-authentication-supported"] = ipp.TagKeyword
@ -74,56 +82,57 @@ func init() {
ipp.AttributeTagMapping["compression-supported"] = ipp.TagKeyword
ipp.AttributeTagMapping["printer-privacy-policy-uri"] = ipp.TagUri
ipp.AttributeTagMapping["printer-location"] = ipp.TagText
ipp.AttributeTagMapping["ppd-name"] = ipp.TagName
}
func ippClientHandler(mod *ZeroGod, client net.Conn, srvHost string, srvPort int, srvTLS bool) {
defer client.Close()
func ippClientHandler(ctx *HandlerContext) {
defer ctx.client.Close()
buf := make([]byte, 4096)
// read raw request
read, err := client.Read(buf)
read, err := ctx.client.Read(buf)
if err != nil {
if err == io.EOF {
return
}
mod.Error("error while reading from %v: %v", client.RemoteAddr(), err)
ctx.mod.Error("error while reading from %v: %v", ctx.client.RemoteAddr(), err)
return
} else if read == 0 {
mod.Error("error while reading from %v: no data", client.RemoteAddr())
ctx.mod.Error("error while reading from %v: no data", ctx.client.RemoteAddr())
return
}
raw_req := buf[0:read]
mod.Debug("read %d bytes from %v:\n%s\n", read, client.RemoteAddr(), Dump(raw_req))
ctx.mod.Debug("read %d bytes from %v:\n%s\n", read, ctx.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)
ctx.mod.Error("error while parsing http request from %v: %v", ctx.client.RemoteAddr(), err)
return
}
mod.Info("%v -> %s", client.RemoteAddr(), tui.Green(http_req.UserAgent()))
ctx.mod.Info("%v -> %s", ctx.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"))
ctx.client.Write([]byte("HTTP/1.1 100 Continue\r\n\r\n"))
// read the body
read, err := client.Read(buf)
read, err := ctx.client.Read(buf)
if err != nil {
if err == io.EOF {
return
}
mod.Error("error while reading ipp body from %v: %v", client.RemoteAddr(), err)
ctx.mod.Error("error while reading ipp body from %v: %v", ctx.client.RemoteAddr(), err)
return
} else if read == 0 {
mod.Error("error while reading ipp body from %v: no data", client.RemoteAddr())
ctx.mod.Error("error while reading ipp body from %v: no data", ctx.client.RemoteAddr())
return
}
@ -133,7 +142,7 @@ func ippClientHandler(mod *ZeroGod, client net.Conn, srvHost string, srvPort int
// 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)
ctx.mod.Error("error while parsing ip request from %v: %v", ctx.client.RemoteAddr(), err)
return
}
@ -142,24 +151,24 @@ func ippClientHandler(mod *ZeroGod, client net.Conn, srvHost string, srvPort int
ipp_op_name = name
}
mod.Info("%v op=%s attributes=%v", client.RemoteAddr(), tui.Bold(ipp_op_name), ipp_req.OperationAttributes)
ctx.mod.Info("%v op=%s attributes=%v", ctx.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)
ippOnGetPrinterAttributes(ctx, ipp_req)
default:
ippOnUnhandledRequest(mod, client, ipp_req, ipp_op_name)
ippOnUnhandledRequest(ctx, ipp_req, ipp_op_name)
}
}
func ippSendResponse(mod *ZeroGod, client net.Conn, response *ipp.Response) {
mod.Debug("SENDING %++v", *response)
func ippSendResponse(ctx *HandlerContext, response *ipp.Response) {
ctx.mod.Debug("SENDING %++v", *response)
resp_data, err := response.Encode()
if err != nil {
mod.Error("error while encoding ipp response: %v", err)
ctx.mod.Error("error while encoding ipp response: %v", err)
return
}
@ -172,29 +181,29 @@ func ippSendResponse(mod *ZeroGod, client net.Conn, response *ipp.Response) {
}
for _, header := range headers {
if _, err := client.Write(header); err != nil {
mod.Error("error while writing header: %v", err)
if _, err := ctx.client.Write(header); err != nil {
ctx.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)
if _, err = ctx.client.Write(resp_data); err != nil {
ctx.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())
ctx.mod.Debug("sent %d of ipp response to %v", len(resp_data), ctx.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)
func ippOnUnhandledRequest(ctx *HandlerContext, ipp_req *ipp.Request, ipp_op_name string) {
ctx.mod.Warning("unhandled request from %v: operation=%s", ctx.client.RemoteAddr(), ipp_op_name)
ippSendResponse(mod, client, ipp_resp)
mod.Warning("unhandled request from %v: operation=%s", client.RemoteAddr(), ipp_op_name)
ippSendResponse(ctx, ipp.NewResponse(
ipp.StatusErrorOperationNotSupported,
ipp_req.RequestId))
}
func ippOnGetPrinterAttributes(mod *ZeroGod, client net.Conn, ipp_req *ipp.Request, srvHost string, srvPort int, srvTLS bool) {
func ippOnGetPrinterAttributes(ctx *HandlerContext, ipp_req *ipp.Request) {
ipp_resp := ipp.NewResponse(ipp.StatusOk, ipp_req.RequestId)
// https://tools.ietf.org/html/rfc2911 section 3.1.4.2 Response Operation Attributes
@ -211,52 +220,15 @@ func ippOnGetPrinterAttributes(mod *ZeroGod, client net.Conn, ipp_req *ipp.Reque
},
}
/*
"""
marker-names (1setOf nameWithoutLanguage) = Black ink,Cyan ink,Magenta ink,Yellow ink
marker-colors (1setOf nameWithoutLanguage) = #000000,#00FFFF,#FF00FF,#FFFF00
marker-types (1setOf keyword) = ink-cartridge,ink-cartridge,ink-cartridge,ink-cartridge
marker-low-levels (1setOf integer) = 15,15,15,15
marker-high-levels (1setOf integer) = 100,100,100,100
marker-levels (1setOf integer) = 57,94,95,95
"""
markers = {
(
SectionEnum.printer,
b'marker-names',
TagEnum.text_without_language
): [b'TestMarker'],
(
SectionEnum.printer,
b'marker-colors',
TagEnum.text_without_language
): [b'#FF0000'],
(
SectionEnum.printer,
b'marker-types',
TagEnum.text_without_language
): [b'ink-cartridge'],
(
SectionEnum.printer,
b'marker-low-levels',
TagEnum.integer
): [Integer(15).bytes()],
(
SectionEnum.printer,
b'marker-high-levels',
TagEnum.integer
): [Integer(100).bytes()],
(
SectionEnum.printer,
b'marker-levels',
TagEnum.integer
): [Integer(66).bytes()],
}
*/
// TODO: allow customization of these attributes.
// collect user attributes
userProps := make(map[string]string)
for name, defaultValue := range IPP_USER_ATTRIBUTES {
if value, found := ctx.ippAttributes[name]; found {
userProps[name] = value
} else {
userProps[name] = defaultValue
}
}
// rfc2911 section 4.4
ipp_resp.PrinterAttributes = []ipp.Attributes{
@ -264,61 +236,58 @@ func ippOnGetPrinterAttributes(mod *ZeroGod, client net.Conn, ipp_req *ipp.Reque
// custom
"printer-name": []ipp.Attribute{
{
Value: "PRINTER_NAME",
Value: userProps["printer-name"],
Tag: ipp.TagName,
},
},
"printer-info": []ipp.Attribute{
{
Value: "PRINTER_INFO",
Value: userProps["printer-info"],
Tag: ipp.TagText,
},
},
"printer-make-and-model": []ipp.Attribute{
{
Value: "PRINTER_MAKE PRINTER_MODEL",
Value: userProps["printer-make-and-model"],
Tag: ipp.TagText,
},
},
"printer-location": []ipp.Attribute{
{
Value: "PRINTER_LOCATION",
Value: userProps["printer-location"],
Tag: ipp.TagText,
},
},
"printer-privacy-policy-uri": []ipp.Attribute{
{
Value: "https://www.google.com/",
Value: userProps["printer-privacy-policy-uri"],
Tag: ipp.TagUri,
},
},
// constants
/*
"ppd-name": []ipp.Attribute{
{
Value: "everywhere",
Tag: ipp.TagName,
},
"ppd-name": []ipp.Attribute{
{
Value: userProps["ppd-name"],
Tag: ipp.TagName,
},
*/
},
"printer-uri-supported": []ipp.Attribute{
{
Value: fmt.Sprintf("%s://%s:%d/printer", ops.Ternary(srvTLS, "ipps", "ipp"), srvHost, srvPort),
Value: fmt.Sprintf("%s://%s:%d/printer", ops.Ternary(ctx.srvTLS, "ipps", "ipp"), ctx.srvHost, ctx.srvPort),
Tag: ipp.TagUri,
},
},
"uri-security-supported": []ipp.Attribute{
{
Value: ops.Ternary(ctx.srvTLS, "tls", "none"),
Tag: ipp.TagKeyword,
},
},
"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-state": []ipp.Attribute{
{
Value: 3, // idle
@ -424,5 +393,5 @@ func ippOnGetPrinterAttributes(mod *ZeroGod, client net.Conn, ipp_req *ipp.Reque
},
}
ippSendResponse(mod, client, ipp_resp)
ippSendResponse(ctx, ipp_resp)
}

View file

@ -3,38 +3,12 @@ package zerogod
import (
"fmt"
"io/ioutil"
"strings"
"github.com/bettercap/bettercap/v2/zeroconf"
"github.com/evilsocket/islazy/str"
yaml "gopkg.in/yaml.v3"
)
type ServiceData struct {
Name string `yaml:"name"` // Instance name (e.g. "My web page")
Service string `yaml:"service"` // Service name (e.g. _http._tcp.)
Domain string `yaml:"domain"` // If blank, assumes "local"
Port int `yaml:"port"` // Service port
Records []string `yaml:"records,omitempty"` // Service DNS text records
Responder string `yaml:"responder,omitempty"` // Optional IP to use instead of our tcp acceptor
}
func (svc ServiceData) FullName() string {
return fmt.Sprintf("%s.%s.%s",
strings.Trim(svc.Name, "."),
strings.Trim(svc.Service, "."),
strings.Trim(svc.Domain, "."))
}
func (svc ServiceData) Unregister() error {
if server, err := zeroconf.Register(svc.Name, svc.Service, svc.Domain, svc.Port, svc.Records, nil); err != nil {
return err
} else {
server.Shutdown()
}
return nil
}
func svcEntriesToData(services map[string]*zeroconf.ServiceEntry) []ServiceData {
data := make([]ServiceData, 0)
for _, svc := range services {

View file

@ -0,0 +1,100 @@
package zerogod
import (
"fmt"
"net"
"strings"
"github.com/bettercap/bettercap/v2/zeroconf"
"github.com/evilsocket/islazy/tui"
)
type ServiceData struct {
Name string `yaml:"name"` // Instance name (e.g. "My web page")
Service string `yaml:"service"` // Service name (e.g. _http._tcp.)
Domain string `yaml:"domain"` // If blank, assumes "local"
Port int `yaml:"port"` // Service port
Records []string `yaml:"records,omitempty"` // Service DNS text records
Responder string `yaml:"responder,omitempty"` // Optional IP to use instead of our tcp acceptor
IPP map[string]string `yaml:"ipp,omitempty"` // Optional IPP attributes map
server *zeroconf.Server
}
func (svc ServiceData) FullName() string {
return fmt.Sprintf("%s.%s.%s",
strings.Trim(svc.Name, "."),
strings.Trim(svc.Service, "."),
strings.Trim(svc.Domain, "."))
}
func (svc *ServiceData) Register(mod *ZeroGod) (err error) {
// now create it again to actually advertise
if svc.Responder == "" {
// use our own IP
if svc.server, err = zeroconf.Register(
svc.Name,
svc.Service,
svc.Domain,
svc.Port,
svc.Records,
nil); err != nil {
return fmt.Errorf("could not create service %s: %v", svc.FullName(), err)
}
mod.Info("advertising %s with responder=%s port=%d",
tui.Yellow(svc.FullName()),
tui.Red(svc.Responder),
svc.Port)
} else {
responderHostName := ""
// try first to do a reverse DNS of the ip
if addr, err := net.LookupAddr(svc.Responder); err == nil && len(addr) > 0 {
responderHostName = addr[0]
} else {
mod.Debug("could not get responder %s reverse dns entry: %v", svc.Responder, err)
}
// if we don't have a host, create a .nip.io representation
if responderHostName == "" {
responderHostName = fmt.Sprintf("%s.nip.io.", strings.ReplaceAll(svc.Responder, ".", "-"))
}
// use external responder
if svc.server, err = zeroconf.RegisterExternalResponder(
svc.Name,
svc.Service,
svc.Domain,
svc.Port,
responderHostName,
[]string{svc.Responder},
svc.Records,
nil); err != nil {
return fmt.Errorf("could not create service %s: %v", svc.FullName(), err)
}
mod.Info("advertising %s with responder=%s hostname=%s port=%d",
tui.Yellow(svc.FullName()),
tui.Red(svc.Responder),
tui.Yellow(responderHostName),
svc.Port)
}
return
}
func (svc *ServiceData) Unregister(mod *ZeroGod) error {
mod.Info("unregistering instance %s ...", tui.Yellow(svc.FullName()))
err := (error)(nil)
if svc.server == nil {
// if we haven't been registered yet, create the server
if svc.server, err = zeroconf.Register(svc.Name, svc.Service, svc.Domain, svc.Port, svc.Records, nil); err != nil {
return err
}
}
svc.server.Shutdown()
return nil
}

207
test.yml
View file

@ -1,207 +0,0 @@
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