zgrab2/modules/ipp/scanner.go

212 lines
6.8 KiB
Go
Raw Normal View History

2018-06-07 19:13:01 +00:00
// Package ipp provides a zgrab2 module that scans for ipp.
// TODO: Describe module, the flags, the probe, the output, etc.
package ipp
//TODO: Clean up these imports
2018-06-07 19:13:01 +00:00
import (
//"bytes"
"encoding/binary"
2018-06-07 19:13:01 +00:00
//"errors"
//"fmt"
//"io"
"net/http"
"strconv"
"strings"
2018-06-07 19:13:01 +00:00
//"net"
//"net/url"
//"time"
log "github.com/sirupsen/logrus"
"github.com/zmap/zgrab2"
)
const (
ContentType string = "application/ipp"
)
2018-06-07 19:13:01 +00:00
//TODO: Tag relevant results and exlain in comments
// ScanResults instances are returned by the module's Scan function.
type ScanResults struct {
//TODO: ?Include the request sent as well??
//TODO: Include a full response or at least a blob in the data (at least in verbose mode)
//Response *http.Response `json:"response,omitempty"`
2018-06-07 19:13:01 +00:00
MajorVersion int8 `json:"version_major"`
MinorVersion int8 `json:"version_minor"`
VersionString string `json:"version_string,omitempty"`
2018-06-07 19:13:01 +00:00
CUPSVersion string `json:"cups_version,omitempty"`
TLSLog *zgrab2.TLSLog `json:"tls,omitempty"`
2018-06-07 19:13:01 +00:00
}
// TODO: Annotate every flag thoroughly
// TODO: Add more protocol-specific flags as needed
2018-06-07 19:13:01 +00:00
// Flags holds the command-line configuration for the ipp scan module.
// Populated by the framework.
type Flags struct {
zgrab2.BaseFlags
//FIXME: Borrowed from http module
MaxRead int `long:"max-size" default:"256" description:"Max kilobytes to read in response to an HTTP request"`
// TODO: Protocols that support TLS should include zgrab2.TLSFlags (do once implemented)
// TODO: Maybe implement both an ipps connection and upgrade to https
IPPSecure bool `long:"ipps" description:"Perform a TLS handshake immediately upon connecting."`
2018-06-07 19:13:01 +00:00
Verbose bool `long:"verbose" description:"More verbose logging, include debug fields in the scan results"`
}
// Module implements the zgrab2.Module interface.
type Module struct {
// TODO: Add any module-global state if necessary
2018-06-07 19:13:01 +00:00
}
// Scanner implements the zgrab2.Scanner interface.
type Scanner struct {
config *Flags
// TODO: Add scan state if any is necessary
2018-06-07 19:13:01 +00:00
}
// RegisterModule registers the zgrab2 module.
func RegisterModule() {
var module Module
_, err := zgrab2.AddCommand("ipp", "ipp", "Probe for ipp", 631, &module)
if err != nil {
log.Fatal(err)
}
}
// NewFlags returns a default Flags object.
func (module *Module) NewFlags() interface{} {
return new(Flags)
}
// NewScanner returns a new Scanner instance.
func (module *Module) NewScanner() zgrab2.Scanner {
return new(Scanner)
}
// Validate checks that the flags are valid.
// On success, returns nil.
// On failure, returns an error instance describing the error.
func (flags *Flags) Validate(args []string) error {
return nil
}
// Help returns the module's help string.
func (flags *Flags) Help() string {
//TODO: Write a help string
return ""
}
// Init initializes the Scanner.
func (scanner *Scanner) Init(flags zgrab2.ScanFlags) error {
f, _ := flags.(*Flags)
scanner.config = f
//TODO: Take action in response to flags which were set
return nil
}
// InitPerSender initializes the scanner for a given sender.
func (scanner *Scanner) InitPerSender(senderID int) error {
return nil
}
// GetName returns the Scanner name defined in the Flags.
func (scanner *Scanner) GetName() string {
return scanner.config.Name
}
// Protocol returns the protocol identifier of the scan.
func (scanner *Scanner) Protocol() string {
return "ipp"
}
// GetPort returns the port being scanned.
func (scanner *Scanner) GetPort() uint {
return scanner.config.Port
}
//FIXME: Maybe switch to ipp/ipps schemes, at least optionally
func getIPPURL(https bool, host string, port uint16, endpoint string) string {
var proto string
if https {
proto = "https"
} else {
proto = "http"
}
return proto + "://" + host + ":" + strconv.FormatUint(uint64(port), 10) + endpoint
}
// FIXME: Ensure that an actual content type is being used with some library
func ippInContentType(resp http.Response) bool {
return strings.Contains(resp.Header.Get("Content-Type"), ContentType)
}
// FIXME: Change to instead return (*ScanResults, *zgrab2.ScanError)
// TODO: Doesn't support TLS at all right now
func (scanner *Scanner) grab(target zgrab2.ScanTarget) (int8, int8, *zgrab2.ScanError) {
//FIXME: This is not where this hostname assignment logic should live
host := target.Domain
if host == "" {
host = target.IP.String()
}
//FIXME: ?Should just use endpoint "/", since we get the same response as "/ipp" on CUPS??
uri := getIPPURL(scanner.config.IPPSecure, host, uint16(scanner.config.BaseFlags.Port), "/ipp")
b := getPrinterAttributesRequest(uri)
resp, err := http.Post(uri, ContentType, &b)
2018-06-07 19:13:01 +00:00
if err != nil {
return 0, 0, zgrab2.DetectScanError(err)
2018-06-07 19:13:01 +00:00
}
if resp != nil && resp.Body != nil {
defer resp.Body.Close()
} else {
// FIXME: Is empty body allowed in IPP?
// Cite RFC!!
// Empty body is not allowed in valid IPP
return 0, 0, zgrab2.NewScanError(zgrab2.SCAN_PROTOCOL_ERROR, nil)
}
// FIXME: Maybe add something to handle redirects
// FIXME: Probably return the whole response for further inspection by ztag, rather
// than grabbing first 2 bytes. In that case, implement maxRead like http module
//Check to make sure that the repsonse received is actually IPP
//Content-Type header matches is sufficient
//HTTP on port 631 is sufficient
//Still record data in the case of protocol error to see what that data looks like
// TODO: Record server-header version numbers
//protocols := resp.Header.Get("Server")
// TODO: Change this to perform two separate binary.Reads
var version uint16
// TODO: Determine whether errors other than protocol (ie: too few bytes) can be triggered here
if err := binary.Read(resp.Body, binary.BigEndian, &version); err != nil {
// FIXME: Determine whether sending fewer than 2 bytes is a protocol or application error
// I believe it's protocol, since the version must be specified (iirc)
// FIXME: Cite RFC!!
return 0, 0, zgrab2.NewScanError(zgrab2.SCAN_PROTOCOL_ERROR, err)
}
// Returns signed integers because "every integer MUST be encoded as a signed integer"
// (Source: https://tools.ietf.org/html/rfc8010#section-3)
return int8(version >> 8), int8(version & 0xff), nil
}
// Scan TODO: describe how scan operates
//1. Send a request (currently get-printer-attributes)
//2. Take in that response & read out version numbers
func (scanner *Scanner) Scan(target zgrab2.ScanTarget) (zgrab2.ScanStatus, interface{}, error) {
// TODO: use Connection again, at least when implementing TLS
major, minor, err := scanner.grab(target)
results := &ScanResults{}
results.MajorVersion = major
results.MinorVersion = minor
// FIXME: Triggering even though error IS nil
// FIXME: This is a sloppy bodge to handle the issue
if major == 0 && minor == 0 && err != nil {
// TODO: Consider mimicking HTTP Scan's retryHTTPS functionality
return zgrab2.TryGetScanStatus(err), results, err
}
return zgrab2.SCAN_SUCCESS, results, nil
2018-06-07 19:13:01 +00:00
}