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

This commit is contained in:
Simone Margaritelli 2024-09-19 21:49:02 +02:00
commit 51a5b4ad6e
17 changed files with 1375 additions and 239 deletions

View file

@ -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)
}

View file

@ -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
}

View file

@ -0,0 +1,260 @@
package zerogod
import (
"context"
"strings"
"github.com/bettercap/bettercap/v2/network"
"github.com/bettercap/bettercap/v2/session"
"github.com/bettercap/bettercap/v2/tls"
"github.com/evilsocket/islazy/tui"
"github.com/grandcat/zeroconf"
)
type ZeroGod struct {
session.SessionModule
advertiser *Advertiser
rootContext context.Context
rootCancel context.CancelFunc
resolvers map[string]*zeroconf.Resolver
mapping map[string]map[string]*zeroconf.ServiceEntry
}
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("zerogod.discovery on", "",
"Start DNS-SD / mDNS discovery.",
func(args []string) error {
return mod.Start()
}))
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("zerogod.show", "",
"Show discovered services.",
func(args []string) error {
return mod.show("", false)
}))
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("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("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("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("zerogod.advertise FILENAME", "zerogod.advertise (.+)",
"Start advertising the mDNS services from the FILENAME yaml file.",
func(args []string) error {
if args[0] == "off" {
return mod.stopAdvertiser()
}
return mod.startAdvertiser(args[0])
}))
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 *ZeroGod) Name() string {
return "zerogod"
}
func (mod *ZeroGod) Description() string {
return "A DNS-SD / mDNS / Bonjour / Zeroconf module for discovery and spoofing."
}
func (mod *ZeroGod) Author() string {
return "Simone Margaritelli <evilsocket@gmail.com>"
}
func (mod *ZeroGod) Configure() (err error) {
if mod.Running() {
return session.ErrAlreadyStarted(mod.Name())
}
if mod.rootContext != nil {
mod.rootCancel()
}
mod.mapping = make(map[string]map[string]*zeroconf.ServiceEntry)
mod.resolvers = make(map[string]*zeroconf.Resolver)
mod.rootContext, mod.rootCancel = context.WithCancel(context.Background())
return
}
type ServiceDiscoveryEvent struct {
Service zeroconf.ServiceEntry `json:"service"`
Endpoint *network.Endpoint `json:"endpoint"`
}
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 {
svcName := strings.Replace(svc.Instance, ".local", "", 1)
if _, found := mod.resolvers[svcName]; !found {
mod.Debug("discovered service %s", tui.Green(svcName))
if err := mod.startResolver(svcName); err != nil {
mod.Error("%v", err)
}
}
return
}
mod.Debug("discovered instance %s (%s) [%v / %v]:%d",
tui.Green(svc.ServiceInstanceName()),
tui.Dim(svc.HostName),
svc.AddrIPv4,
svc.AddrIPv6,
svc.Port)
event := ServiceDiscoveryEvent{
Service: *svc,
Endpoint: nil,
}
addresses := append(svc.AddrIPv4, svc.AddrIPv6...)
for _, ip := range addresses {
address := ip.String()
if event.Endpoint = mod.Session.Lan.GetByIp(address); event.Endpoint != nil {
// update endpoint metadata
mod.updateEndpointMeta(address, event.Endpoint, svc)
// update internal module mapping
if ipServices, found := mod.mapping[address]; found {
ipServices[svc.ServiceInstanceName()] = svc
} else {
mod.mapping[address] = map[string]*zeroconf.ServiceEntry{
svc.ServiceInstanceName(): svc,
}
}
break
}
}
if event.Endpoint == nil {
// TODO: this is probably an IPv6 only record, try to somehow check which known IPv4 it is
mod.Debug("got mdns entry for unknown ip: %++v", *svc)
}
session.I.Events.Add("mdns.service", event)
session.I.Refresh()
}
func (mod *ZeroGod) startResolver(service string) error {
mod.Debug("starting resolver for service %s", tui.Yellow(service))
resolver, err := zeroconf.NewResolver(nil)
if err != nil {
return err
}
// start listening
channel := make(chan *zeroconf.ServiceEntry)
go func() {
for entry := range channel {
mod.onServiceDiscovered(entry)
}
}()
// start browsing
go func() {
err = resolver.Browse(mod.rootContext, service, "local.", channel)
if err != nil {
mod.Error("%v", err)
}
mod.Debug("resolver for service %s stopped", tui.Yellow(service))
}()
mod.resolvers[service] = resolver
return nil
}
func (mod *ZeroGod) Start() (err error) {
if err = mod.Configure(); err != nil {
return err
}
// start the root discovery
if err = mod.startResolver("_services._dns-sd._udp"); err != nil {
return err
}
return mod.SetRunning(true, func() {
mod.Info("service discovery started")
<-mod.rootContext.Done()
mod.Info("service discovery stopped")
})
}
func (mod *ZeroGod) Stop() error {
return mod.SetRunning(false, func() {
mod.stopAdvertiser()
if mod.rootCancel != nil {
mod.Debug("stopping discovery")
mod.rootCancel()
<-mod.rootContext.Done()
mod.Debug("stopped")
mod.rootContext = nil
mod.rootCancel = nil
}
})
}

View file

@ -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)
}

View file

@ -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]))
}
}
}

View file

@ -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("<unknown 0x%x>", 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)
}

View file

@ -0,0 +1,35 @@
package zerogod
import (
"fmt"
"io/ioutil"
yaml "gopkg.in/yaml.v3"
)
func (mod *ZeroGod) save(address, filename string) error {
if address == "" {
return fmt.Errorf("address cannot be empty")
}
if filename == "" {
return fmt.Errorf("filename cannot be empty")
}
if ipServices, found := mod.mapping[address]; found {
data, err := yaml.Marshal(ipServices)
if err != nil {
return err
}
err = ioutil.WriteFile(filename, data, 0644)
if err != nil {
return err
}
mod.Info("mDNS information saved to %s", filename)
} else {
return fmt.Errorf("no mDNS information found for address %s", address)
}
return nil
}

View file

@ -0,0 +1,76 @@
package zerogod
import (
"fmt"
"sort"
"github.com/evilsocket/islazy/str"
"github.com/evilsocket/islazy/tui"
"github.com/grandcat/zeroconf"
)
type entry struct {
ip string
services map[string]*zeroconf.ServiceEntry
}
func (mod *ZeroGod) show(filter string, withData bool) error {
fmt.Fprintf(mod.Session.Events.Stdout, "\n")
// convert to list for sorting
entries := make([]entry, 0)
for ip, services := range mod.mapping {
if filter == "" || ip == filter {
entries = append(entries, entry{ip, services})
}
}
sort.Slice(entries, func(i, j int) bool {
return entries[i].ip < entries[j].ip
})
for _, entry := range entries {
if endpoint := mod.Session.Lan.GetByIp(entry.ip); endpoint != nil {
fmt.Fprintf(mod.Session.Events.Stdout, "* %s (%s)\n", tui.Bold(endpoint.IpAddress), tui.Dim(endpoint.Vendor))
} else {
fmt.Fprintf(mod.Session.Events.Stdout, "* %s\n", tui.Bold(entry.ip))
}
for name, svc := range entry.services {
fmt.Fprintf(mod.Session.Events.Stdout, " %s (%s) [%v / %v]:%s\n",
tui.Green(name),
tui.Dim(svc.HostName),
svc.AddrIPv4,
svc.AddrIPv6,
tui.Red(fmt.Sprintf("%d", svc.Port)),
)
numFields := len(svc.Text)
if withData {
if numFields > 0 {
for _, field := range svc.Text {
if field = str.Trim(field); len(field) > 0 {
fmt.Fprintf(mod.Session.Events.Stdout, " %s\n", field)
}
}
} else {
fmt.Fprintf(mod.Session.Events.Stdout, " %s\n", tui.Dim("no data"))
}
} else {
if numFields > 0 {
fmt.Fprintf(mod.Session.Events.Stdout, " <%d records>\n", numFields)
} else {
fmt.Fprintf(mod.Session.Events.Stdout, " %s\n", tui.Dim("<no records>"))
}
}
}
fmt.Fprintf(mod.Session.Events.Stdout, "\n")
}
if len(entries) > 0 {
mod.Session.Refresh()
}
return nil
}