diff --git a/cmd/camget/camget.go b/cmd/camget/camget.go index 7ee0df9c1..56c81a554 100644 --- a/cmd/camget/camget.go +++ b/cmd/camget/camget.go @@ -37,14 +37,15 @@ import ( ) var ( - flagVersion = flag.Bool("version", false, "show version") - flagVerbose = flag.Bool("verbose", false, "be verbose") - flagHTTP = flag.Bool("verbose_http", false, "show HTTP request summaries") - flagCheck = flag.Bool("check", false, "just check for the existence of listed blobs; returning 0 if all are present") - flagOutput = flag.String("o", "-", "Output file/directory to create. Use -f to overwrite.") - flagGraph = flag.Bool("graph", false, "Output a graphviz directed graph .dot file of the provided root schema blob, to be rendered with 'dot -Tsvg -o graph.svg graph.dot'") - flagContents = flag.Bool("contents", false, "If true and the target blobref is a 'bytes' or 'file' schema blob, the contents of that file are output instead.") - flagShared = flag.String("shared", "", "If non-empty, the URL of a \"share\" blob. The URL will be used as the root of future fetches. Only \"haveref\" shares are currently supported.") + flagVersion = flag.Bool("version", false, "show version") + flagVerbose = flag.Bool("verbose", false, "be verbose") + flagHTTP = flag.Bool("verbose_http", false, "show HTTP request summaries") + flagCheck = flag.Bool("check", false, "just check for the existence of listed blobs; returning 0 if all are present") + flagOutput = flag.String("o", "-", "Output file/directory to create. Use -f to overwrite.") + flagGraph = flag.Bool("graph", false, "Output a graphviz directed graph .dot file of the provided root schema blob, to be rendered with 'dot -Tsvg -o graph.svg graph.dot'") + flagContents = flag.Bool("contents", false, "If true and the target blobref is a 'bytes' or 'file' schema blob, the contents of that file are output instead.") + flagShared = flag.String("shared", "", "If non-empty, the URL of a \"share\" blob. The URL will be used as the root of future fetches. Only \"haveref\" shares are currently supported.") + flagInsecureTLS = flag.Bool("insecure", false, "If set, when using TLS, the server's certificates verification is disabled, and they are not checked against the trustedCerts in the client configuration either.") ) func main() { @@ -70,7 +71,8 @@ func main() { if flag.NArg() != 0 { log.Fatal("No arguments permitted when using --shared") } - cl1, target, err := client.NewFromShareRoot(*flagShared) + cl1, target, err := client.NewFromShareRoot(*flagShared, + client.OptionInsecure(*flagInsecureTLS)) if err != nil { log.Fatal(err) } @@ -88,6 +90,7 @@ func main() { } } + cl.InsecureTLS = *flagInsecureTLS tr := cl.TransportForConfig(&client.TransportConfig{ Verbose: *flagHTTP, }) diff --git a/pkg/client/client.go b/pkg/client/client.go index c29b50e7a..6d7ee10cc 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -86,6 +86,8 @@ type Client struct { // The camlistore server prints the fingerprint to add to the config // when starting. trustedCerts []string + // if set, we also skip the check against trustedCerts + InsecureTLS bool pendStatMu sync.Mutex // guards pendStat pendStat map[string][]statReq // blobref -> reqs; for next batch(es) @@ -170,9 +172,25 @@ func (c *Client) TransportForConfig(tc *TransportConfig) http.RoundTripper { return transport } +type ClientOption interface { + modifyClient(*Client) +} + +func OptionInsecure(v bool) ClientOption { + return optionInsecure(v) +} + +type optionInsecure bool + +func (o optionInsecure) modifyClient(c *Client) { + c.InsecureTLS = bool(o) +} + var shareURLRx = regexp.MustCompile(`^(.+)/(` + blobref.Pattern + ")$") -func NewFromShareRoot(shareBlobURL string) (c *Client, target *blobref.BlobRef, err error) { +// NewFromShareRoot uses shareBlobURL to set up and return a client that +// will be used to fetch shared blobs. +func NewFromShareRoot(shareBlobURL string, opts ...ClientOption) (c *Client, target *blobref.BlobRef, err error) { var root string m := shareURLRx.FindStringSubmatch(shareBlobURL) if m == nil { @@ -189,6 +207,9 @@ func NewFromShareRoot(shareBlobURL string) (c *Client, target *blobref.BlobRef, c.via = make(map[string]string) root = m[2] + for _, v := range opts { + v.modifyClient(c) + } c.SetHTTPClient(&http.Client{Transport: c.TransportForConfig(nil)}) req := c.newRequest("GET", shareBlobURL, nil) @@ -645,6 +666,12 @@ func (c *Client) doReqGated(req *http.Request) (*http.Response, error) { return c.httpClient.Do(req) } +// insecureTLS returns whether the client is using TLS without any +// verification of the server's cert. +func (c *Client) insecureTLS() bool { + return c.useTLS() && c.InsecureTLS +} + // selfVerifiedSSL returns whether the client config has fingerprints for // (self-signed) trusted certificates. // When true, we run with InsecureSkipVerify and it is our responsibility @@ -658,7 +685,7 @@ func (c *Client) selfVerifiedSSL() bool { // dialing ourselves, and we do not want the http transport layer // to redo it. func (c *Client) condRewriteURL(url string) string { - if c.selfVerifiedSSL() { + if c.selfVerifiedSSL() || c.insecureTLS() { return strings.Replace(url, "https://", "http://", 1) } return url @@ -689,7 +716,7 @@ func (c *Client) TLSConfig() (*tls.Config, error) { // the TLS handshake. func (c *Client) DialFunc() func(network, addr string) (net.Conn, error) { trustedCerts := c.GetTrustedCerts() - if !c.useTLS() || len(trustedCerts) == 0 { + if !c.useTLS() || (!c.InsecureTLS && len(trustedCerts) == 0) { // No TLS, or TLS with normal/full verification if onAndroid() { return func(network, addr string) (net.Conn, error) { @@ -717,6 +744,9 @@ func (c *Client) DialFunc() func(network, addr string) (net.Conn, error) { return nil, err } } + if c.InsecureTLS { + return conn, nil + } certs := conn.ConnectionState().PeerCertificates if certs == nil || len(certs) < 1 { return nil, errors.New("Could not get server's certificate from the TLS connection.")