diff --git a/ahash/ahash.go b/ahash/ahash.go index b487d76..25e3b31 100644 --- a/ahash/ahash.go +++ b/ahash/ahash.go @@ -45,13 +45,6 @@ func sha512Slicer(bs []byte) []byte { return sum[:] } -var sliceFunctions = map[string]func([]byte) []byte{ - "sha224": sha224Slicer, - "sha256": sha256Slicer, - "sha384": sha384Slicer, - "sha512": sha512Slicer, -} - // Hash represents a generic hash function that may or may not be secure. It // satisfies the hash.Hash interface. type Hash struct { diff --git a/assert/assert.go b/assert/assert.go index aee0ee3..54d0079 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -94,7 +94,7 @@ func NoError(err error, s ...string) { } if nil != err { - die(err.Error()) + die(err.Error(), s...) } } @@ -170,5 +170,5 @@ func ErrorEqT(t *testing.T, expected, actual error) { should = fmt.Sprintf("have '%s'", actual) } - die(fmt.Sprintf("assert.Error2: expected '%s', but %s", expected, should)) + t.Fatalf("assert.Error2: expected '%s', but %s", expected, should) } diff --git a/cmd/certexpiry/main.go b/cmd/certexpiry/main.go index 8525ea7..5be5b60 100644 --- a/cmd/certexpiry/main.go +++ b/cmd/certexpiry/main.go @@ -10,9 +10,9 @@ import ( "strings" "time" - "github.com/cloudflare/cfssl/helpers" "git.wntrmute.dev/kyle/goutils/die" "git.wntrmute.dev/kyle/goutils/lib" + "github.com/cloudflare/cfssl/helpers" ) var warnOnly bool diff --git a/cmd/certverify/main.go b/cmd/certverify/main.go index a76b5d3..dd2b47f 100644 --- a/cmd/certverify/main.go +++ b/cmd/certverify/main.go @@ -8,10 +8,10 @@ import ( "os" "time" - "github.com/cloudflare/cfssl/helpers" - "github.com/cloudflare/cfssl/revoke" "git.wntrmute.dev/kyle/goutils/die" "git.wntrmute.dev/kyle/goutils/lib" + "github.com/cloudflare/cfssl/helpers" + "github.com/cloudflare/cfssl/revoke" ) func printRevocation(cert *x509.Certificate) { diff --git a/cmd/cruntar/main.go b/cmd/cruntar/main.go index 0ee5052..95dcd56 100644 --- a/cmd/cruntar/main.go +++ b/cmd/cruntar/main.go @@ -12,6 +12,7 @@ import ( "path/filepath" "git.wntrmute.dev/kyle/goutils/die" + "git.wntrmute.dev/kyle/goutils/fileutil" ) var ( @@ -56,7 +57,7 @@ func processFile(tfr *tar.Reader, hdr *tar.Header, top string) error { } filePath := filepath.Clean(filepath.Join(top, hdr.Name)) switch hdr.Typeflag { - case tar.TypeReg, tar.TypeRegA: + case tar.TypeReg: file, err := os.Create(filePath) if err != nil { return err @@ -92,6 +93,10 @@ func processFile(tfr *tar.Reader, hdr *tar.Header, top string) error { return err } case tar.TypeSymlink: + if !fileutil.ValidateSymlink(hdr.Linkname, top) { + return fmt.Errorf("symlink %s is outside the top-level %s", + hdr.Linkname, top) + } path := linkTarget(hdr.Linkname, top) if ok, err := filepath.Match(top+"/*", filepath.Clean(path)); !ok { return fmt.Errorf("symlink %s isn't in %s", hdr.Linkname, top) diff --git a/cmd/showimp/main.go b/cmd/showimp/main.go index e8d561d..6299e55 100644 --- a/cmd/showimp/main.go +++ b/cmd/showimp/main.go @@ -12,36 +12,23 @@ import ( "sort" "strings" + "git.wntrmute.dev/kyle/goutils/dbg" "git.wntrmute.dev/kyle/goutils/die" - "git.wntrmute.dev/kyle/goutils/logging" ) var ( gopath string project string - debug bool ) var ( - stdLibRegexp = regexp.MustCompile(`^\w+(/\w+)*$`) - sourceRegexp = regexp.MustCompile(`^[^.].*\.go$`) - log = logging.NewConsole() - imports = map[string]bool{} + debug = dbg.New() fset = &token.FileSet{} + imports = map[string]bool{} + sourceRegexp = regexp.MustCompile(`^[^.].*\.go$`) + stdLibRegexp = regexp.MustCompile(`^\w+(/\w+)*$`) ) -func debugf(format string, args ...interface{}) { - if debug { - fmt.Printf(format, args...) - } -} - -func debugln(args ...interface{}) { - if debug { - fmt.Println(args...) - } -} - func init() { gopath = os.Getenv("GOPATH") if gopath == "" { @@ -75,7 +62,7 @@ func walkFile(path string, info os.FileInfo, err error) error { return nil } - debugln(path) + debug.Println(path) f, err := parser.ParseFile(fset, path, nil, parser.ImportsOnly) if err != nil { @@ -85,16 +72,16 @@ func walkFile(path string, info os.FileInfo, err error) error { for _, importSpec := range f.Imports { importPath := strings.Trim(importSpec.Path.Value, `"`) if stdLibRegexp.MatchString(importPath) { - debugln("standard lib:", importPath) + debug.Println("standard lib:", importPath) continue } else if strings.HasPrefix(importPath, project) { - debugln("internal import:", importPath) + debug.Println("internal import:", importPath) continue } else if strings.HasPrefix(importPath, "golang.org/") { - debugln("extended lib:", importPath) + debug.Println("extended lib:", importPath) continue } - debugln("import:", importPath) + debug.Println("import:", importPath) imports[importPath] = true } @@ -108,7 +95,7 @@ func main() { var noVendor bool flag.StringVar(&ignoreLine, "i", "", "comma-separated list of directories to ignore") flag.BoolVar(&noVendor, "nv", false, "ignore the vendor directory") - flag.BoolVar(&debug, "v", false, "log debugging information") + flag.BoolVar(&debug.Enabled, "v", false, "log debugging information") flag.Parse() if noVendor { diff --git a/dbg/dbg.go b/dbg/dbg.go index 97e64ba..88ceaee 100644 --- a/dbg/dbg.go +++ b/dbg/dbg.go @@ -55,21 +55,21 @@ func To(w io.WriteCloser) *DebugPrinter { } // Print calls fmt.Print if Enabled is true. -func (dbg DebugPrinter) Print(v ...interface{}) { +func (dbg *DebugPrinter) Print(v ...interface{}) { if dbg.Enabled { fmt.Fprint(dbg.out, v...) } } // Println calls fmt.Println if Enabled is true. -func (dbg DebugPrinter) Println(v ...interface{}) { +func (dbg *DebugPrinter) Println(v ...interface{}) { if dbg.Enabled { fmt.Fprintln(dbg.out, v...) } } // Printf calls fmt.Printf if Enabled is true. -func (dbg DebugPrinter) Printf(format string, v ...interface{}) { +func (dbg *DebugPrinter) Printf(format string, v ...interface{}) { if dbg.Enabled { fmt.Fprintf(dbg.out, format, v...) } diff --git a/fileutil/fileutil.go b/fileutil/fileutil.go index cab5fda..d652b6a 100644 --- a/fileutil/fileutil.go +++ b/fileutil/fileutil.go @@ -1,10 +1,11 @@ +//go:build !windows +// +build !windows + // Package fileutil contains common file functions. package fileutil import ( "os" - "path/filepath" - "strings" "golang.org/x/sys/unix" ) @@ -47,10 +48,3 @@ const ( func Access(path string, mode int) error { return unix.Access(path, uint32(mode)) } - -// ValidateSymlink checks to make sure a symlink exists in some top-level -// directory. -func ValidateSymlink(symlink, topLevel string) bool { - target, err := filepath.EvalSymlinks(symlink) - return strings.HasPrefix(target, topLevel) -} diff --git a/fileutil/fileutil_windows.go b/fileutil/fileutil_windows.go new file mode 100644 index 0000000..d4efb3b --- /dev/null +++ b/fileutil/fileutil_windows.go @@ -0,0 +1,49 @@ +//go:build windows +// +build windows + +// Package fileutil contains common file functions. +package fileutil + +import ( + "errors" + "os" +) + +// FileDoesExist returns true if the file exists. +func FileDoesExist(path string) bool { + _, err := os.Stat(path) + return !os.IsNotExist(err) +} + +// DirectoryDoesExist returns true if the file exists. +func DirectoryDoesExist(path string) bool { + fi, err := os.Stat(path) + if err != nil { + return false + } + + return fi.Mode().IsDir() +} + +const ( + // AccessExists checks whether the file exists. This is invalid outside of + // Unix systems. + AccessExists = 0 + + // AccessRead checks whether the user has read permissions on + // the file. This is invalid outside of Unix systems. + AccessRead = 0 + + // AccessWrite checks whether the user has write permissions + // on the file. This is invalid outside of Unix systems. + AccessWrite = 0 + + // AccessExec checks whether the user has executable + // permissions on the file. This is invalid outside of Unix systems. + AccessExec = 0 +) + +// Access is a Unix-only call, and has no meaning on Windows. +func Access(path string, mode int) error { + return errors.New("fileutil: Access is meaningless on Windows") +} diff --git a/fileutil/symlinks.go b/fileutil/symlinks.go new file mode 100644 index 0000000..1663e93 --- /dev/null +++ b/fileutil/symlinks.go @@ -0,0 +1,16 @@ +package fileutil + +import ( + "path/filepath" + "strings" +) + +// ValidateSymlink checks to make sure a symlink exists in some top-level +// directory. +func ValidateSymlink(symlink, topLevel string) bool { + target, err := filepath.EvalSymlinks(symlink) + if err != nil { + return false + } + return strings.HasPrefix(target, topLevel) +} diff --git a/go.mod b/go.mod index cf3b826..5a3c0e7 100644 --- a/go.mod +++ b/go.mod @@ -13,11 +13,3 @@ require ( golang.org/x/sys v0.0.0-20220412211240-33da011f77ad gopkg.in/yaml.v2 v2.4.0 ) - -require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/google/certificate-transparency-go v1.0.21 // indirect - github.com/kr/fs v0.1.0 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect -) diff --git a/go.sum b/go.sum index 72136aa..72c9020 100644 --- a/go.sum +++ b/go.sum @@ -81,16 +81,22 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= diff --git a/lib/lib.go b/lib/lib.go index ef618b9..30191d5 100644 --- a/lib/lib.go +++ b/lib/lib.go @@ -89,7 +89,7 @@ func Duration(d time.Duration) string { if d >= yearDuration { years := d / yearDuration s += fmt.Sprintf("%dy", years) - d -= (years * yearDuration) + d -= years * yearDuration } if d >= dayDuration { @@ -103,7 +103,7 @@ func Duration(d time.Duration) string { d %= 1 * time.Second hours := d / time.Hour - d -= (hours * time.Hour) + d -= hours * time.Hour s += fmt.Sprintf("%dh%s", hours, d) return s } diff --git a/logging/log.go b/logging/log.go index f43d94e..5a32302 100644 --- a/logging/log.go +++ b/logging/log.go @@ -8,6 +8,67 @@ import ( ) // Logger provides a standardised logging interface. +// +// Log messages consist of four components: +// +// 1. The **level** attaches a notion of priority to the log message. +// Several log levels are available: +// +// + FATAL (32): the system is in an unsuable state, and cannot +// continue to run. Most of the logging for this will cause the +// program to exit with an error code. +// + CRITICAL (16): critical conditions. The error, if uncorrected, is +// likely to cause a fatal condition shortly. An example is running +// out of disk space. This is something that the ops team should get +// paged for. +// + ERROR (8): error conditions. A single error doesn't require an +// ops team to be paged, but repeated errors should often trigger a +// page based on threshold triggers. An example is a network +// failure: it might be a transient failure (these do happen), but +// most of the time it's self-correcting. +// + WARNING (4): warning conditions. An example of this is a bad +// request sent to a server. This isn't an error on the part of the +// program, but it may be indicative of other things. Like errors, +// the ops team shouldn't be paged for errors, but a page might be +// triggered if a certain threshold of warnings is reached (which is +// typically much higher than errors). For example, repeated +// warnings might be a sign that the system is under attack. +// + INFO (2): informational message. This is a normal log message +// that is used to deliver information, such as recording +// requests. Ops teams are never paged for informational +// messages. This is the default log level. +// + DEBUG (1): debug-level message. These are only used during +// development or if a deployed system repeatedly sees abnormal +// errors. +// +// The numeric values indicate the priority of a given level. +// +// 2. The **actor** is used to specify which component is generating +// the log message. This could be the program name, or it could be +// a specific component inside the system. +// +// 3. The **event** is a short message indicating what happened. This is +// most like the traditional log message. +// +// 4. The **attributes** are an optional set of key-value string pairs that +// provide additional information. +// +// Additionally, each log message has an associated timestamp. For the +// text-based logs, this is "%FT%T%z"; for the binary logs, this is a +// 64-bit Unix timestamp. An example text-based timestamp might look like :: +// +// [2016-03-27T20:59:27-0700] [INFO] [actor:server event:request received] client=192.168.2.5 request-size=839 +// +// Note that this is organised in a manner that facilitates parsing:: +// +// /\[(\d{4}-\d{3}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{4})\] \[(\w+\)]\) \[actor:(.+?) event:(.+?)\]/ +// +// will cover the header: +// +// + ``$1`` contains the timestamp +// + ``$2`` contains the level +// + ``$3`` contains the actor +// + ``$4`` contains the event type Logger interface { // SetLevel sets the minimum log level. SetLevel(Level) @@ -23,66 +84,6 @@ type Logger interface { // Close gives the Logger the opportunity to perform any cleanup. Close() error - // Log messages consist of four components: - // - // 1. The **level** attaches a notion of priority to the log message. - // Several log levels are available: - // - // + FATAL (32): the system is in an unsuable state, and cannot - // continue to run. Most of the logging for this will cause the - // program to exit with an error code. - // + CRITICAL (16): critical conditions. The error, if uncorrected, is - // likely to cause a fatal condition shortly. An example is running - // out of disk space. This is something that the ops team should get - // paged for. - // + ERROR (8): error conditions. A single error doesn't require an - // ops team to be paged, but repeated errors should often trigger a - // page based on threshold triggers. An example is a network - // failure: it might be a transient failure (these do happen), but - // most of the time it's self-correcting. - // + WARNING (4): warning conditions. An example of this is a bad - // request sent to a server. This isn't an error on the part of the - // program, but it may be indicative of other things. Like errors, - // the ops team shouldn't be paged for errors, but a page might be - // triggered if a certain threshold of warnings is reached (which is - // typically much higher than errors). For example, repeated - // warnings might be a sign that the system is under attack. - // + INFO (2): informational message. This is a normal log message - // that is used to deliver information, such as recording - // requests. Ops teams are never paged for informational - // messages. This is the default log level. - // + DEBUG (1): debug-level message. These are only used during - // development or if a deployed system repeatedly sees abnormal - // errors. - // - // The numeric values indicate the priority of a given level. - // - // 2. The **actor** is used to specify which component is generating - // the log message. This could be the program name, or it could be - // a specific component inside the system. - // - // 3. The **event** is a short message indicating what happened. This is - // most like the traditional log message. - // - // 4. The **attributes** are an optional set of key-value string pairs that - // provide additional information. - // - // Additionally, each log message has an associated timestamp. For the - // text-based logs, this is "%FT%T%z"; for the binary logs, this is a - // 64-bit Unix timestamp. An example text-based timestamp might look like :: - // - // [2016-03-27T20:59:27-0700] [INFO] [actor:server event:request received] client=192.168.2.5 request-size=839 - // - // Note that this is organised in a manner that facilitates parsing:: - // - // /\[(\d{4}-\d{3}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{4})\] \[(\w+\)]\) \[actor:(.+?) event:(.+?)\]/ - // - // will cover the header: - // - // + ``$1`` contains the timestamp - // + ``$2`` contains the level - // + ``$3`` contains the actor - // + ``$4`` contains the event Debug(actor, event string, attrs map[string]string) Info(actor, event string, attrs map[string]string) Warn(actor, event string, attrs map[string]string) diff --git a/seekbuf/seekbuf.go b/seekbuf/seekbuf.go index 253f988..595433c 100644 --- a/seekbuf/seekbuf.go +++ b/seekbuf/seekbuf.go @@ -1,4 +1,4 @@ -// seekbuf implements a read-seekable buffer. +// Package seekbuf implements a read-seekable buffer. package seekbuf import "io" diff --git a/syslog/logger.go b/syslog/logger.go index 8677f85..f7f2f78 100644 --- a/syslog/logger.go +++ b/syslog/logger.go @@ -1,4 +1,4 @@ -// syslog is a syslog-type facility for logging. +// Package syslog is a syslog-type facility for logging. package syslog import ( @@ -100,12 +100,12 @@ type Options struct { // DefaultOptions returns a sane set of defaults for syslog, using the program // name as the tag name. withSyslog controls whether logs should be sent to // syslog, too. -func DefaultOptions(tag string, withSyslog bool) { +func DefaultOptions(tag string, withSyslog bool) *Options { if tag == "" { tag = os.Args[0] } - return &DefaultOptions{ + return &Options{ Level: "WARNING", Tag: tag, Facility: "daemon", @@ -113,15 +113,15 @@ func DefaultOptions(tag string, withSyslog bool) { } } -// DefaultDefaultOptions returns a sane set of debug defaults for syslog, +// DefaultDebugOptions returns a sane set of debug defaults for syslog, // using the program name as the tag name. withSyslog controls whether logs // should be sent to syslog, too. -func DefaultDebugOptions(tag string, withSyslog bool) { +func DefaultDebugOptions(tag string, withSyslog bool) *Options { if tag == "" { tag = os.Args[0] } - return &DefaultOptions{ + return &Options{ Level: "DEBUG", Facility: "daemon", WriteSyslog: withSyslog, @@ -131,7 +131,7 @@ func DefaultDebugOptions(tag string, withSyslog bool) { func Setup(opts *Options) error { priority, ok := priorities[opts.Level] if !ok { - return fmt.Errorf("log: unknown priority %s", level) + return fmt.Errorf("log: unknown priority %s", opts.Level) } log.p = priority