diff --git a/.gitignore b/.gitignore index 54d34c48..29ef182b 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,5 @@ stage/ /snap .idea /cover.out -/can-data \ No newline at end of file +/can-data +/test*.yml \ No newline at end of file diff --git a/epson_xp630.yml b/epson_xp630.yml deleted file mode 100644 index 98ef51f6..00000000 --- a/epson_xp630.yml +++ /dev/null @@ -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 \ No newline at end of file diff --git a/external_responder.yml b/external_responder.yml deleted file mode 100644 index b846d53b..00000000 --- a/external_responder.yml +++ /dev/null @@ -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 \ No newline at end of file diff --git a/modules/zerogod/zerogod_acceptor.go b/modules/zerogod/zerogod_acceptor.go index 081d6a85..81988f02 100644 --- a/modules/zerogod/zerogod_acceptor.go +++ b/modules/zerogod/zerogod_acceptor.go @@ -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)) diff --git a/modules/zerogod/zerogod_advertise.go b/modules/zerogod/zerogod_advertise.go index 5578b805..2aa8ae27 100644 --- a/modules/zerogod/zerogod_advertise.go +++ b/modules/zerogod/zerogod_advertise.go @@ -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") diff --git a/modules/zerogod/zerogod_discovery.go b/modules/zerogod/zerogod_discovery.go index 12f03b65..8a88b45e 100644 --- a/modules/zerogod/zerogod_discovery.go +++ b/modules/zerogod/zerogod_discovery.go @@ -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 { diff --git a/modules/zerogod/zerogod_endpoint_update.go b/modules/zerogod/zerogod_endpoint_update.go index af64df03..634af366 100644 --- a/modules/zerogod/zerogod_endpoint_update.go +++ b/modules/zerogod/zerogod_endpoint_update.go @@ -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) diff --git a/modules/zerogod/zerogod_generic_handler.go b/modules/zerogod/zerogod_generic_handler.go index a0e1a176..f3b57a1a 100644 --- a/modules/zerogod/zerogod_generic_handler.go +++ b/modules/zerogod/zerogod_generic_handler.go @@ -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])) } } } diff --git a/modules/zerogod/zerogod_ipp_handler.go b/modules/zerogod/zerogod_ipp_handler.go index b60af411..e39a3911 100644 --- a/modules/zerogod/zerogod_ipp_handler.go +++ b/modules/zerogod/zerogod_ipp_handler.go @@ -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) } diff --git a/modules/zerogod/zerogod_save.go b/modules/zerogod/zerogod_save.go index 86bdb021..c3fd634e 100644 --- a/modules/zerogod/zerogod_save.go +++ b/modules/zerogod/zerogod_save.go @@ -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 { diff --git a/modules/zerogod/zerogod_service.go b/modules/zerogod/zerogod_service.go new file mode 100644 index 00000000..1ef651b3 --- /dev/null +++ b/modules/zerogod/zerogod_service.go @@ -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 +} diff --git a/test.yml b/test.yml deleted file mode 100644 index d4a70eb1..00000000 --- a/test.yml +++ /dev/null @@ -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