2021-02-26 08:40:29 +00:00
package ipp
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"net"
"net/http"
"os"
"strconv"
)
2021-04-27 15:29:48 +00:00
var SocketNotFoundError = errors . New ( "unable to locate CUPS socket" )
var CertNotFoundError = errors . New ( "unable to locate CUPS certificate" )
2021-02-26 08:40:29 +00:00
var (
DefaultSocketSearchPaths = [ ] string { "/var/run/cupsd" , "/var/run/cups/cups.sock" , "/run/cups/cups.sock" }
DefaultCertSearchPaths = [ ] string { "/etc/cups/certs/0" , "/run/cups/certs/0" }
)
2021-04-27 15:37:02 +00:00
const DefaultRequestRetryLimit = 3
2021-02-26 08:40:29 +00:00
type SocketAdapter struct {
host string
useTLS bool
SocketSearchPaths [ ] string
CertSearchPaths [ ] string
2021-04-27 15:37:02 +00:00
//RequestRetryLimit is the number of times a request will be retried when receiving an authorized status. This usually happens when a CUPs cert is expired, and a retry will use the newly generated cert. Default 3.
RequestRetryLimit int
2021-02-26 08:40:29 +00:00
}
func NewSocketAdapter ( host string , useTLS bool ) * SocketAdapter {
return & SocketAdapter {
host : host ,
useTLS : useTLS ,
SocketSearchPaths : DefaultSocketSearchPaths ,
CertSearchPaths : DefaultCertSearchPaths ,
2021-04-27 15:37:02 +00:00
RequestRetryLimit : DefaultRequestRetryLimit ,
2021-02-26 08:40:29 +00:00
}
}
2021-04-27 15:32:21 +00:00
//DoRequest performs the given IPP request to the given URL, returning the IPP response or an error if one occurred.
//Additional data will be written to an io.Writer if additionalData is not nil
func ( h * SocketAdapter ) SendRequest ( url string , r * Request , additionalData io . Writer ) ( * Response , error ) {
2021-04-27 15:37:02 +00:00
for i := 0 ; i < h . RequestRetryLimit ; i ++ {
2021-02-26 08:40:29 +00:00
// encode request
payload , err := r . Encode ( )
if err != nil {
return nil , fmt . Errorf ( "unable to encode IPP request: %v" , err )
}
2021-05-06 00:24:53 +00:00
var body io . Reader
size := len ( payload )
if r . File != nil && r . FileSize != - 1 {
size += r . FileSize
body = io . MultiReader ( bytes . NewBuffer ( payload ) , r . File )
} else {
body = bytes . NewBuffer ( payload )
}
req , err := http . NewRequest ( "POST" , url , body )
2021-02-26 08:40:29 +00:00
if err != nil {
return nil , fmt . Errorf ( "unable to create HTTP request: %v" , err )
}
sock , err := h . GetSocket ( )
if err != nil {
return nil , err
}
// if cert isn't found, do a request to generate it
cert , err := h . GetCert ( )
2021-04-27 15:29:48 +00:00
if err != nil && err != CertNotFoundError {
2021-02-26 08:40:29 +00:00
return nil , err
}
2021-05-06 00:24:53 +00:00
req . Header . Set ( "Content-Length" , strconv . Itoa ( size ) )
2021-02-26 08:40:29 +00:00
req . Header . Set ( "Content-Type" , ContentTypeIPP )
req . Header . Set ( "Authorization" , fmt . Sprintf ( "Local %s" , cert ) )
unixClient := http . Client {
Transport : & http . Transport {
DialContext : func ( ctx context . Context , _ , _ string ) ( net . Conn , error ) {
return net . Dial ( "unix" , sock )
} ,
} ,
}
// send request
resp , err := unixClient . Do ( req )
if err != nil {
return nil , fmt . Errorf ( "unable to perform HTTP request: %v" , err )
}
if resp . StatusCode == http . StatusUnauthorized {
// retry with newly generated cert
resp . Body . Close ( )
continue
}
if resp . StatusCode != http . StatusOK {
resp . Body . Close ( )
return nil , fmt . Errorf ( "server did not return Status OK: %d" , resp . StatusCode )
}
// buffer response to avoid read issues
buf := new ( bytes . Buffer )
if _ , err := io . Copy ( buf , resp . Body ) ; err != nil {
resp . Body . Close ( )
return nil , fmt . Errorf ( "unable to buffer response: %v" , err )
}
resp . Body . Close ( )
// decode reply
2021-04-27 15:32:21 +00:00
ippResp , err := NewResponseDecoder ( bytes . NewReader ( buf . Bytes ( ) ) ) . Decode ( additionalData )
2021-02-26 08:40:29 +00:00
if err != nil {
return nil , fmt . Errorf ( "unable to decode IPP response: %v" , err )
}
if err = ippResp . CheckForErrors ( ) ; err != nil {
return nil , fmt . Errorf ( "received error IPP response: %v" , err )
}
return ippResp , nil
}
return nil , errors . New ( "request retry limit exceeded" )
}
//GetSocket returns the path to the cupsd socket by searching SocketSearchPaths
func ( h * SocketAdapter ) GetSocket ( ) ( string , error ) {
for _ , path := range h . SocketSearchPaths {
fi , err := os . Stat ( path )
if err != nil {
if os . IsNotExist ( err ) {
continue
} else if os . IsPermission ( err ) {
return "" , errors . New ( "unable to access socket: Access denied" )
}
return "" , fmt . Errorf ( "unable to access socket: %v" , err )
}
if fi . Mode ( ) & os . ModeSocket != 0 {
return path , nil
}
}
2021-04-27 15:29:48 +00:00
return "" , SocketNotFoundError
2021-02-26 08:40:29 +00:00
}
//GetCert returns the current CUPs authentication certificate by searching CertSearchPaths
func ( h * SocketAdapter ) GetCert ( ) ( string , error ) {
for _ , path := range h . CertSearchPaths {
f , err := os . Open ( path )
if err != nil {
if os . IsNotExist ( err ) {
continue
} else if os . IsPermission ( err ) {
return "" , errors . New ( "unable to access certificate: Access denied" )
}
return "" , fmt . Errorf ( "unable to access certificate: %v" , err )
}
defer f . Close ( )
buf := new ( bytes . Buffer )
if _ , err := io . Copy ( buf , f ) ; err != nil {
return "" , fmt . Errorf ( "unable to access certificate: %v" , err )
}
return buf . String ( ) , nil
}
2021-04-27 15:29:48 +00:00
return "" , CertNotFoundError
2021-02-26 08:40:29 +00:00
}
func ( h * SocketAdapter ) GetHttpUri ( namespace string , object interface { } ) string {
proto := "http"
if h . useTLS {
proto = "https"
}
uri := fmt . Sprintf ( "%s://%s" , proto , h . host )
if namespace != "" {
uri = fmt . Sprintf ( "%s/%s" , uri , namespace )
}
if object != nil {
uri = fmt . Sprintf ( "%s/%v" , uri , object )
}
return uri
}
func ( h * SocketAdapter ) TestConnection ( ) error {
2021-04-27 15:44:01 +00:00
sock , err := h . GetSocket ( )
if err != nil {
return err
}
conn , err := net . Dial ( "unix" , sock )
if err != nil {
return err
}
conn . Close ( )
2021-02-26 08:40:29 +00:00
return nil
}