1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
|
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Gosumcheck checks a go.sum file against a go.sum database server.
//
// Usage:
//
// gosumcheck [-h H] [-k key] [-u url] [-v] go.sum
//
// The -h flag changes the tile height (default 8).
//
// The -k flag changes the go.sum database server key.
//
// The -u flag overrides the URL of the server (usually set from the key name).
//
// The -v flag enables verbose output.
// In particular, it causes gosumcheck to report
// the URL and elapsed time for each server request.
//
// WARNING! WARNING! WARNING!
//
// Gosumcheck is meant as a proof of concept demo and should not be
// used in production scripts or continuous integration testing.
// It does not cache any downloaded information from run to run,
// making it expensive and also keeping it from detecting server
// misbehavior or successful HTTPS man-in-the-middle timeline forks.
//
// To discourage misuse in automated settings, gosumcheck does not
// set any exit status to report whether any problems were found.
package main
import (
"flag"
"fmt"
"io"
"log"
"net/http"
"os"
"os/exec"
"strings"
"sync"
"time"
"golang.org/x/exp/sumdb/internal/sumweb"
)
func usage() {
fmt.Fprintf(os.Stderr, "usage: gosumcheck [-h H] [-k key] [-u url] [-v] go.sum...\n")
os.Exit(2)
}
var (
height = flag.Int("h", 8, "tile height")
vkey = flag.String("k", "sum.golang.org+033de0ae+Ac4zctda0e5eza+HJyk9SxEdh+s3Ux18htTTAD8OuAn8", "key")
url = flag.String("u", "", "url to server (overriding name)")
vflag = flag.Bool("v", false, "enable verbose output")
)
func main() {
log.SetPrefix("notecheck: ")
log.SetFlags(0)
flag.Usage = usage
flag.Parse()
if flag.NArg() < 1 {
usage()
}
conn := sumweb.NewConn(new(client))
// Look in environment explicitly, so that if 'go env' is old and
// doesn't know about GONOSUMDB, we at least get anything
// set in the environment.
env := os.Getenv("GONOSUMDB")
if env == "" {
out, err := exec.Command("go", "env", "GONOSUMDB").CombinedOutput()
if err != nil {
log.Fatalf("go env GONOSUMDB: %v\n%s", err, out)
}
env = strings.TrimSpace(string(out))
}
conn.SetGONOSUMDB(env)
for _, arg := range flag.Args() {
data, err := os.ReadFile(arg)
if err != nil {
log.Fatal(err)
}
checkGoSum(conn, arg, data)
}
}
func checkGoSum(conn *sumweb.Conn, name string, data []byte) {
lines := strings.Split(string(data), "\n")
if lines[len(lines)-1] != "" {
log.Printf("error: final line missing newline")
return
}
lines = lines[:len(lines)-1]
errs := make([]string, len(lines))
var wg sync.WaitGroup
for i, line := range lines {
wg.Add(1)
go func(i int, line string) {
defer wg.Done()
f := strings.Fields(line)
if len(f) != 3 {
errs[i] = "invalid number of fields"
return
}
dbLines, err := conn.Lookup(f[0], f[1])
if err != nil {
if err == sumweb.ErrGONOSUMDB {
errs[i] = fmt.Sprintf("%s@%s: %v", f[0], f[1], err)
} else {
// Otherwise Lookup properly adds the prefix itself.
errs[i] = err.Error()
}
return
}
hashAlgPrefix := f[0] + " " + f[1] + " " + f[2][:strings.Index(f[2], ":")+1]
for _, dbLine := range dbLines {
if dbLine == line {
return
}
if strings.HasPrefix(dbLine, hashAlgPrefix) {
errs[i] = fmt.Sprintf("%s@%s hash mismatch: have %s, want %s", f[0], f[1], line, dbLine)
return
}
}
errs[i] = fmt.Sprintf("%s@%s hash algorithm mismatch: have %s, want one of:\n\t%s", f[0], f[1], line, strings.Join(dbLines, "\n\t"))
}(i, line)
}
wg.Wait()
for i, err := range errs {
if err != "" {
fmt.Printf("%s:%d: %s\n", name, i+1, err)
}
}
}
type client struct{}
func (*client) ReadConfig(file string) ([]byte, error) {
if file == "key" {
return []byte(*vkey), nil
}
if strings.HasSuffix(file, "/latest") {
// Looking for cached latest tree head.
// Empty result means empty tree.
return []byte{}, nil
}
return nil, fmt.Errorf("unknown config %s", file)
}
func (*client) WriteConfig(file string, old, new []byte) error {
// Ignore writes.
return nil
}
func (*client) ReadCache(file string) ([]byte, error) {
return nil, fmt.Errorf("no cache")
}
func (*client) WriteCache(file string, data []byte) {
// Ignore writes.
}
func (*client) Log(msg string) {
log.Print(msg)
}
func (*client) SecurityError(msg string) {
log.Fatal(msg)
}
func init() {
http.DefaultClient.Timeout = 1 * time.Minute
}
func (*client) ReadRemote(path string) ([]byte, error) {
name := *vkey
if i := strings.Index(name, "+"); i >= 0 {
name = name[:i]
}
start := time.Now()
target := "https://" + name + path
if *url != "" {
target = *url + path
}
resp, err := http.Get(target)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("GET %v: %v", target, resp.Status)
}
data, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
if err != nil {
return nil, err
}
if *vflag {
fmt.Fprintf(os.Stderr, "%.3fs %s\n", time.Since(start).Seconds(), target)
}
return data, nil
}
|