mirror of
https://github.com/bettercap/bettercap
synced 2025-08-14 02:36:57 -07:00
misc: small fix or general refactoring i did not bother commenting
This commit is contained in:
parent
17ba1be16c
commit
6af2de6de9
6 changed files with 463 additions and 51 deletions
|
@ -3,17 +3,28 @@ package zerogod
|
|||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/evilsocket/islazy/fs"
|
||||
"github.com/evilsocket/islazy/ops"
|
||||
"github.com/evilsocket/islazy/str"
|
||||
"github.com/evilsocket/islazy/tui"
|
||||
|
||||
"github.com/phin1x/go-ipp"
|
||||
)
|
||||
|
||||
const IPP_CHUNK_MAX_LINE_SIZE = 1024
|
||||
|
||||
var IPP_REQUEST_NAMES = map[int16]string{
|
||||
// https://tools.ietf.org/html/rfc2911#section-4.4.15
|
||||
0x0002: "Print-Job",
|
||||
|
@ -57,6 +68,31 @@ var IPP_USER_ATTRIBUTES = map[string]string{
|
|||
"ppd-name": "everywhere",
|
||||
}
|
||||
|
||||
type ClientData struct {
|
||||
IP string `json:"ip"`
|
||||
UA string `json:"user_agent"`
|
||||
}
|
||||
|
||||
type JobData struct {
|
||||
Name string `json:"name"`
|
||||
UUID string `json:"uuid"`
|
||||
User string `json:"username"`
|
||||
}
|
||||
|
||||
type DocumentData struct {
|
||||
Name string `json:"name"`
|
||||
Format string `json:"format"`
|
||||
Data []byte `json:"data"`
|
||||
}
|
||||
|
||||
type PrintData struct {
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
Service string `json:"service"`
|
||||
Client ClientData `json:"client"`
|
||||
Job JobData `json:"job"`
|
||||
Document DocumentData `json:"document"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
ipp.AttributeTagMapping["printer-uri-supported"] = ipp.TagUri
|
||||
ipp.AttributeTagMapping["uri-authentication-supported"] = ipp.TagKeyword
|
||||
|
@ -83,11 +119,115 @@ func init() {
|
|||
ipp.AttributeTagMapping["printer-privacy-policy-uri"] = ipp.TagUri
|
||||
ipp.AttributeTagMapping["printer-location"] = ipp.TagText
|
||||
ipp.AttributeTagMapping["ppd-name"] = ipp.TagName
|
||||
ipp.AttributeTagMapping["job-state-reasons"] = ipp.TagKeyword
|
||||
ipp.AttributeTagMapping["job-state"] = ipp.TagEnum
|
||||
ipp.AttributeTagMapping["job-uri"] = ipp.TagUri
|
||||
ipp.AttributeTagMapping["job-id"] = ipp.TagInteger
|
||||
ipp.AttributeTagMapping["job-printer-uri"] = ipp.TagUri
|
||||
ipp.AttributeTagMapping["job-name"] = ipp.TagName
|
||||
ipp.AttributeTagMapping["job-originating-user-name"] = ipp.TagName
|
||||
ipp.AttributeTagMapping["time-at-creation"] = ipp.TagInteger
|
||||
ipp.AttributeTagMapping["time-at-completed"] = ipp.TagInteger
|
||||
ipp.AttributeTagMapping["job-printer-up-time"] = ipp.TagInteger
|
||||
}
|
||||
|
||||
func ippReadChunkSizeHex(ctx *HandlerContext) string {
|
||||
var buf []byte
|
||||
|
||||
for b := make([]byte, 1); ; {
|
||||
if n, err := ctx.client.Read(b); err != nil {
|
||||
ctx.mod.Error("could not read chunked byte: %v", err)
|
||||
} else if n == 0 {
|
||||
break
|
||||
} else if b[0] == '\n' {
|
||||
break
|
||||
} else {
|
||||
// ctx.mod.Info("buf += 0x%x (%c)", b[0], b[0])
|
||||
buf = append(buf, b[0])
|
||||
}
|
||||
|
||||
if len(buf) >= IPP_CHUNK_MAX_LINE_SIZE {
|
||||
ctx.mod.Warning("buffer size exceeded %d bytes when reading chunk size", IPP_CHUNK_MAX_LINE_SIZE)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return str.Trim(string(buf))
|
||||
}
|
||||
|
||||
func ippReadChunkSize(ctx *HandlerContext) (uint64, error) {
|
||||
if chunkSizeHex := ippReadChunkSizeHex(ctx); chunkSizeHex != "" {
|
||||
ctx.mod.Debug("got chunk size: 0x%s", chunkSizeHex)
|
||||
return strconv.ParseUint(chunkSizeHex, 16, 64)
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func ippReadChunkedBody(ctx *HandlerContext) ([]byte, error) {
|
||||
var chunkedBody []byte
|
||||
// read chunked loop
|
||||
for {
|
||||
// read the next chunk size
|
||||
if chunkSize, err := ippReadChunkSize(ctx); err != nil {
|
||||
return nil, fmt.Errorf("error reading next chunk size: %v", err)
|
||||
} else if chunkSize == 0 {
|
||||
break
|
||||
} else {
|
||||
chunk := make([]byte, chunkSize)
|
||||
if n, err := ctx.client.Read(chunk); err != nil {
|
||||
return nil, fmt.Errorf("error while reading chunk of %d bytes: %v", chunkSize, err)
|
||||
} else if n != int(chunkSize) {
|
||||
return nil, fmt.Errorf("expected chunk of size %d, got %d bytes", chunkSize, n)
|
||||
} else {
|
||||
chunkedBody = append(chunkedBody, chunk...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return chunkedBody, nil
|
||||
}
|
||||
|
||||
func ippReadRequestBody(ctx *HandlerContext, http_req *http.Request) (io.ReadCloser, error) {
|
||||
ipp_body := http_req.Body
|
||||
|
||||
// check for an Expect 100-continue
|
||||
if http_req.Header.Get("Expect") == "100-continue" {
|
||||
buf := make([]byte, 4096)
|
||||
|
||||
// inform the client we're ready to read the request body
|
||||
ctx.client.Write([]byte("HTTP/1.1 100 Continue\r\n\r\n"))
|
||||
|
||||
if slices.Contains(http_req.TransferEncoding, "chunked") {
|
||||
ctx.mod.Debug("detected chunked encoding")
|
||||
if body, err := ippReadChunkedBody(ctx); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
ipp_body = io.NopCloser(bytes.NewReader(body))
|
||||
}
|
||||
} else {
|
||||
// read the body in a single step
|
||||
read, err := ctx.client.Read(buf)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("error while reading ipp body from %v: %v", ctx.client.RemoteAddr(), err)
|
||||
} else if read == 0 {
|
||||
return nil, fmt.Errorf("error while reading ipp body from %v: no data", ctx.client.RemoteAddr())
|
||||
}
|
||||
|
||||
ipp_body = io.NopCloser(bytes.NewReader(buf[0:read]))
|
||||
}
|
||||
}
|
||||
|
||||
return ipp_body, nil
|
||||
}
|
||||
|
||||
func ippClientHandler(ctx *HandlerContext) {
|
||||
defer ctx.client.Close()
|
||||
|
||||
clientIP := strings.SplitN(ctx.client.RemoteAddr().String(), ":", 2)[0]
|
||||
|
||||
buf := make([]byte, 4096)
|
||||
|
||||
// read raw request
|
||||
|
@ -96,53 +236,38 @@ func ippClientHandler(ctx *HandlerContext) {
|
|||
if err == io.EOF {
|
||||
return
|
||||
}
|
||||
ctx.mod.Error("error while reading from %v: %v", ctx.client.RemoteAddr(), err)
|
||||
ctx.mod.Warning("error while reading from %v: %v", clientIP, err)
|
||||
return
|
||||
} else if read == 0 {
|
||||
ctx.mod.Error("error while reading from %v: no data", ctx.client.RemoteAddr())
|
||||
ctx.mod.Warning("error while reading from %v: no data", clientIP)
|
||||
return
|
||||
}
|
||||
|
||||
raw_req := buf[0:read]
|
||||
|
||||
ctx.mod.Debug("read %d bytes from %v:\n%s\n", read, ctx.client.RemoteAddr(), Dump(raw_req))
|
||||
ctx.mod.Debug("read %d bytes from %v:\n%s\n", read, clientIP, Dump(raw_req))
|
||||
|
||||
// parse as http
|
||||
reader := bufio.NewReader(bytes.NewReader(raw_req))
|
||||
http_req, err := http.ReadRequest(reader)
|
||||
if err != nil {
|
||||
ctx.mod.Error("error while parsing http request from %v: %v", ctx.client.RemoteAddr(), err)
|
||||
ctx.mod.Error("error while parsing http request from %v: %v", clientIP, err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.mod.Info("%v -> %s", ctx.client.RemoteAddr(), tui.Green(http_req.UserAgent()))
|
||||
clientUA := http_req.UserAgent()
|
||||
ctx.mod.Debug("%v -> %s", clientIP, tui.Green(clientUA))
|
||||
|
||||
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
|
||||
ctx.client.Write([]byte("HTTP/1.1 100 Continue\r\n\r\n"))
|
||||
// read the body
|
||||
read, err := ctx.client.Read(buf)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return
|
||||
}
|
||||
ctx.mod.Error("error while reading ipp body from %v: %v", ctx.client.RemoteAddr(), err)
|
||||
return
|
||||
} else if read == 0 {
|
||||
ctx.mod.Error("error while reading ipp body from %v: no data", ctx.client.RemoteAddr())
|
||||
return
|
||||
}
|
||||
|
||||
ipp_body = io.NopCloser(bytes.NewReader(buf[0:read]))
|
||||
ipp_body, err := ippReadRequestBody(ctx, http_req)
|
||||
if err != nil {
|
||||
ctx.mod.Error("%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// parse as IPP
|
||||
ipp_req, err := ipp.NewRequestDecoder(ipp_body).Decode(nil)
|
||||
if err != nil {
|
||||
ctx.mod.Error("error while parsing ip request from %v: %v", ctx.client.RemoteAddr(), err)
|
||||
ctx.mod.Error("error while parsing ipp request from %v: %v -> %++v", clientIP, err, *http_req)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -151,12 +276,29 @@ func ippClientHandler(ctx *HandlerContext) {
|
|||
ipp_op_name = name
|
||||
}
|
||||
|
||||
ctx.mod.Info("%v op=%s attributes=%v", ctx.client.RemoteAddr(), tui.Bold(ipp_op_name), ipp_req.OperationAttributes)
|
||||
ctx.mod.Info("%s <- %s (%s) %s",
|
||||
tui.Yellow(ctx.service),
|
||||
clientIP,
|
||||
tui.Green(clientUA),
|
||||
tui.Bold(ipp_op_name))
|
||||
ctx.mod.Debug(" %++v", *ipp_req)
|
||||
|
||||
switch ipp_req.Operation {
|
||||
// Get-Printer-Attributes
|
||||
case 0x000B:
|
||||
ippOnGetPrinterAttributes(ctx, ipp_req)
|
||||
// Validate-Job
|
||||
case 0x0004:
|
||||
ippOnValidateJob(ctx, ipp_req)
|
||||
// Get-Jobs
|
||||
case 0x000A:
|
||||
ippOnGetJobs(ctx, ipp_req)
|
||||
// Print-Job
|
||||
case 0x0002:
|
||||
ippOnPrintJob(ctx, http_req, ipp_req)
|
||||
// Get-Job-Attributes
|
||||
case 0x0009:
|
||||
ippOnGetJobAttributes(ctx, ipp_req)
|
||||
|
||||
default:
|
||||
ippOnUnhandledRequest(ctx, ipp_req, ipp_op_name)
|
||||
|
@ -196,13 +338,275 @@ func ippSendResponse(ctx *HandlerContext, response *ipp.Response) {
|
|||
}
|
||||
|
||||
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)
|
||||
ctx.mod.Warning("unhandled request from %v: operation=%s - %++v", ctx.client.RemoteAddr(), ipp_op_name, *ipp_req)
|
||||
|
||||
ippSendResponse(ctx, ipp.NewResponse(
|
||||
ipp.StatusErrorOperationNotSupported,
|
||||
ipp_req.RequestId))
|
||||
}
|
||||
|
||||
func ippOnValidateJob(ctx *HandlerContext, ipp_req *ipp.Request) {
|
||||
jobName := "<unknown>"
|
||||
jobUUID := "<unknown>"
|
||||
jobUser := "<unknown>"
|
||||
|
||||
if value, found := ipp_req.OperationAttributes["job-name"]; found {
|
||||
jobName = value.(string)
|
||||
}
|
||||
|
||||
if value, found := ipp_req.OperationAttributes["requesting-user-name"]; found {
|
||||
jobUser = value.(string)
|
||||
}
|
||||
|
||||
if value, found := ipp_req.JobAttributes["job-uuid"]; found {
|
||||
jobUUID = value.(string)
|
||||
}
|
||||
|
||||
ctx.mod.Debug("validating job_name=%s job_uuid=%s job_user=%s", tui.Yellow(jobName), tui.Dim(jobUUID), tui.Green(jobUser))
|
||||
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
||||
ippSendResponse(ctx, ipp_resp)
|
||||
}
|
||||
|
||||
func ippOnGetJobAttributes(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
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
||||
jobID := 666
|
||||
|
||||
ipp_resp.OperationAttributes["job-uri"] = []ipp.Attribute{
|
||||
{
|
||||
Value: fmt.Sprintf("%s://%s:%d/jobs/%d", ops.Ternary(ctx.srvTLS, "ipps", "ipp"), ctx.srvHost, ctx.srvPort, jobID),
|
||||
Tag: ipp.TagUri,
|
||||
},
|
||||
}
|
||||
ipp_resp.OperationAttributes["job-id"] = []ipp.Attribute{
|
||||
{
|
||||
Value: jobID,
|
||||
Tag: ipp.TagInteger,
|
||||
},
|
||||
}
|
||||
ipp_resp.OperationAttributes["job-state"] = []ipp.Attribute{
|
||||
{
|
||||
Value: 9, // 9=completed https://tools.ietf.org/html/rfc2911#section-4.3.7
|
||||
Tag: ipp.TagEnum,
|
||||
},
|
||||
}
|
||||
ipp_resp.OperationAttributes["job-state-reasons"] = []ipp.Attribute{
|
||||
{
|
||||
Value: []string{
|
||||
"job-completed-successfully",
|
||||
},
|
||||
Tag: ipp.TagKeyword,
|
||||
},
|
||||
}
|
||||
ipp_resp.OperationAttributes["job-printer-uri"] = []ipp.Attribute{
|
||||
{
|
||||
Value: fmt.Sprintf("%s://%s:%d/printer", ops.Ternary(ctx.srvTLS, "ipps", "ipp"), ctx.srvHost, ctx.srvPort),
|
||||
Tag: ipp.TagUri,
|
||||
},
|
||||
}
|
||||
ipp_resp.OperationAttributes["job-name"] = []ipp.Attribute{
|
||||
{
|
||||
Value: "Print job 666",
|
||||
Tag: ipp.TagName,
|
||||
},
|
||||
}
|
||||
ipp_resp.OperationAttributes["job-originating-user-name"] = []ipp.Attribute{
|
||||
{
|
||||
Value: "bettercap", // TODO: check if this must match the actual job user from a print operation
|
||||
Tag: ipp.TagName,
|
||||
},
|
||||
}
|
||||
ipp_resp.OperationAttributes["time-at-creation"] = []ipp.Attribute{
|
||||
{
|
||||
Value: 0,
|
||||
Tag: ipp.TagInteger,
|
||||
},
|
||||
}
|
||||
ipp_resp.OperationAttributes["time-at-completed"] = []ipp.Attribute{
|
||||
{
|
||||
Value: 0,
|
||||
Tag: ipp.TagInteger,
|
||||
},
|
||||
}
|
||||
ipp_resp.OperationAttributes["job-printer-up-time"] = []ipp.Attribute{
|
||||
{
|
||||
Value: time.Now().Unix(),
|
||||
Tag: ipp.TagInteger,
|
||||
},
|
||||
}
|
||||
|
||||
ippSendResponse(ctx, ipp_resp)
|
||||
}
|
||||
|
||||
func ippOnGetJobs(ctx *HandlerContext, ipp_req *ipp.Request) {
|
||||
jobUser := "<unknown>"
|
||||
if value, found := ipp_req.OperationAttributes["requesting-user-name"]; found {
|
||||
jobUser = value.(string)
|
||||
}
|
||||
|
||||
ctx.mod.Debug("responding with empty jobs list to requesting_user=%s", tui.Green(jobUser))
|
||||
|
||||
// respond with an empty list of jobs, which probably breaks the rfc
|
||||
// if the client asked for completed jobs https://tools.ietf.org/html/rfc2911#section-3.2.6.2
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
||||
ippSendResponse(ctx, ipp_resp)
|
||||
}
|
||||
|
||||
func ippOnPrintJob(ctx *HandlerContext, http_req *http.Request, ipp_req *ipp.Request) {
|
||||
var err error
|
||||
|
||||
createdAt := time.Now()
|
||||
|
||||
data := PrintData{
|
||||
CreatedAt: createdAt,
|
||||
Service: ctx.service,
|
||||
Client: ClientData{
|
||||
UA: http_req.UserAgent(),
|
||||
IP: strings.SplitN(ctx.client.RemoteAddr().String(), ":", 2)[0],
|
||||
},
|
||||
Job: JobData{},
|
||||
Document: DocumentData{},
|
||||
}
|
||||
|
||||
if value, found := ipp_req.OperationAttributes["job-name"]; found {
|
||||
data.Job.Name = value.(string)
|
||||
}
|
||||
if value, found := ipp_req.OperationAttributes["requesting-user-name"]; found {
|
||||
data.Job.User = value.(string)
|
||||
}
|
||||
if value, found := ipp_req.JobAttributes["job-uuid"]; found {
|
||||
data.Job.UUID = value.(string)
|
||||
}
|
||||
if value, found := ipp_req.JobAttributes["document-name-supplied"]; found {
|
||||
data.Document.Name = value.(string)
|
||||
}
|
||||
if value, found := ipp_req.OperationAttributes["document-format"]; found {
|
||||
data.Document.Format = value.(string)
|
||||
}
|
||||
|
||||
// TODO: check if not chunked
|
||||
data.Document.Data, err = ippReadChunkedBody(ctx)
|
||||
if err != nil {
|
||||
ctx.mod.Error("could not read document body: %v", err)
|
||||
}
|
||||
|
||||
var docPath string
|
||||
if err, docPath = ctx.mod.StringParam("zerogod.ipp.save_path"); err != nil {
|
||||
ctx.mod.Error("can't read parameter zerogod.ipp.save_path: %v", err)
|
||||
} else if docPath, err = fs.Expand(docPath); err != nil {
|
||||
ctx.mod.Error("can't expand %s: %v", docPath, err)
|
||||
} else {
|
||||
// make sure the path exists
|
||||
if err := os.MkdirAll(docPath, 0755); err != nil {
|
||||
ctx.mod.Error("could not create directory %s: %v", docPath, err)
|
||||
}
|
||||
|
||||
docName := path.Join(docPath, fmt.Sprintf("%d.json", createdAt.UnixMicro()))
|
||||
ctx.mod.Debug("saving to %s: %++v", docName, data)
|
||||
jsonData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
ctx.mod.Error("could not marshal data to json: %v", err)
|
||||
} else if err := ioutil.WriteFile(docName, jsonData, 0644); err != nil {
|
||||
ctx.mod.Error("could not write data to %s: %v", docName, err)
|
||||
} else {
|
||||
ctx.mod.Info(" document saved to %s", tui.Yellow(docName))
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
||||
jobID := 666
|
||||
|
||||
ipp_resp.OperationAttributes["job-uri"] = []ipp.Attribute{
|
||||
{
|
||||
Value: fmt.Sprintf("%s://%s:%d/jobs/%d", ops.Ternary(ctx.srvTLS, "ipps", "ipp"), ctx.srvHost, ctx.srvPort, jobID),
|
||||
Tag: ipp.TagUri,
|
||||
},
|
||||
}
|
||||
ipp_resp.OperationAttributes["job-id"] = []ipp.Attribute{
|
||||
{
|
||||
Value: jobID,
|
||||
Tag: ipp.TagInteger,
|
||||
},
|
||||
}
|
||||
ipp_resp.OperationAttributes["job-state"] = []ipp.Attribute{
|
||||
{
|
||||
Value: 3, // 3=pending https://tools.ietf.org/html/rfc2911#section-4.3.7
|
||||
Tag: ipp.TagEnum,
|
||||
},
|
||||
}
|
||||
ipp_resp.OperationAttributes["job-state-reasons"] = []ipp.Attribute{
|
||||
{
|
||||
Value: []string{
|
||||
"job-incoming",
|
||||
"job-data-insufficient",
|
||||
},
|
||||
Tag: ipp.TagKeyword,
|
||||
},
|
||||
}
|
||||
|
||||
ippSendResponse(ctx, ipp_resp)
|
||||
}
|
||||
|
||||
func ippOnGetPrinterAttributes(ctx *HandlerContext, ipp_req *ipp.Request) {
|
||||
ipp_resp := ipp.NewResponse(ipp.StatusOk, ipp_req.RequestId)
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue