├── version.go ├── .gitignore ├── Makefile ├── go.mod ├── scripts └── normalize_dns.sh ├── LICENSE.md ├── README.md ├── utils.go ├── cmd ├── inetdata-hostnames2domains │ └── main.go ├── inetdata-lines2mtbl │ └── main.go ├── inetdata-csv2mtbl │ └── main.go ├── inetdata-json2mtbl │ └── main.go ├── inetdata-csvrollup │ └── main.go ├── inetdata-ct2hostnames │ └── main.go ├── mapi │ └── main.go ├── inetdata-dns2mtbl │ └── main.go ├── mq │ └── main.go ├── inetdata-arin-org2cidrs │ └── main.go ├── inetdata-zone2csv │ └── main.go ├── inetdata-csvsplit │ └── main.go ├── inetdata-arin-xml2json │ └── main.go ├── inetdata-sonardnsv2-split │ └── main.go ├── inetdata-ct-tail │ └── main.go ├── inetdata-ct2csv │ └── main.go ├── inetdata-ct2mtbl │ └── main.go └── inetdata-arin-xml2csv │ └── main.go ├── ip.go └── go.sum /version.go: -------------------------------------------------------------------------------- 1 | package inetdata 2 | 3 | var Version string = "0.0.37" 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | bin/* 26 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ALL: 2 | @pkg-config --exists libmtbl || (echo "Missing libmtbl: sudo apt install libmtbl-dev" && exit 1) 3 | @go get github.com/mitchellh/gox && \ 4 | go get -u ./... && \ 5 | go fmt ./... && \ 6 | go vet ./... && \ 7 | go build ./... && \ 8 | go install ./... && \ 9 | gox -output="release/{{.OS}}-{{.Arch}}/{{.Dir}}" -osarch="linux/amd64" ./... && \ 10 | sudo cp release/*/* /usr/local/bin 11 | 12 | .PHONY: ALL 13 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hdm/inetdata-parsers 2 | 3 | go 1.13 4 | 5 | replace github.com/coreos/bbolt v1.3.4 => go.etcd.io/bbolt v1.3.4 6 | 7 | replace github.com/coreos/bbolt v1.3.5 => go.etcd.io/bbolt v1.3.5 8 | 9 | require ( 10 | github.com/google/certificate-transparency-go v1.1.0 11 | github.com/gorilla/mux v1.7.4 12 | github.com/hdm/golang-mtbl v0.0.0-20180326181718-10a74bf74458 13 | github.com/peterbourgon/mergemap v0.0.0-20130613134717-e21c03b7a721 14 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f 15 | ) 16 | -------------------------------------------------------------------------------- /scripts/normalize_dns.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | src=$1 4 | mem=$(echo `free -g | grep ^Mem | awk '{print $2}'` / 4.0 | bc)G 5 | tmp=${HOME} 6 | thr=`nproc` 7 | base=`basename $1` 8 | out=`echo $base | cut -f 1 -d .` 9 | export LC_ALL=C 10 | 11 | time ( 12 | nice pigz -dc ${src} | \ 13 | # head -n 10000000 | \ 14 | nice inetdata-csvsplit -m 8 -t ${tmp} ${out} 15 | ) 16 | 17 | # Generate a forward-lookup mtbl 18 | time (nice pigz -dc ${out}-names.gz | nice inetdata-dns2mtbl -m 8 -t ${tmp} ${out}.mtbl) 19 | 20 | # Generate an inverse-lookup mtbl 21 | time (nice pigz -dc ${out}-names-inverse.gz | nice inetdata-dns2mtbl -m 8 -t ${tmp} ${out}-inverse.mtbl) 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Special Circumstances, LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Internet Data Processing Tools 2 | 3 | Process internet data from various sources. Works with [inetdata](https://github.com/hdm/inetdata) 4 | 5 | ## Dependencies 6 | 7 | ### Ubuntu 16.04 8 | ``` 9 | $ sudo apt-get install build-essential git make pigz p7zip-full libmtbl-dev mtbl-bin pkg-config 10 | ``` 11 | 12 | ### Golang 13 | * Download the latest golang binary (1.8+) from https://golang.org/dl/ 14 | * Extract to the filesystem with: 15 | ``` # tar -C /usr/local -xzf go$VERSION.$OS-$ARCH.tar.gz``` 16 | 17 | ### Build 18 | 19 | Configure your GO environment if you haven't done so, by adding the following to ~/.bashrc 20 | 21 | ``` 22 | $ echo 'export GOPATH=$HOME' >> ~/.bashrc 23 | $ echo 'export PATH=$PATH:/usr/local/go/bin:$GOPATH/bin' >> ~/.bashrc 24 | 25 | $ source ~/.bashrc 26 | ``` 27 | 28 | Clone this repository into the correct path: 29 | ``` 30 | $ mkdir -p $GOPATH/src/github.com/hdm/ 31 | $ cd $GOPATH/src/github.com/hdm/ 32 | $ git clone https://github.com/hdm/inetdata-parsers.git 33 | ``` 34 | 35 | ### Install 36 | ``` 37 | $ cd $GOPATH/src/github.com/hdm/inetdata-parsers/ 38 | $ make 39 | 40 | Number of parallel builds: 3 41 | --> linux/amd64: github.com/fathom6/inetdata-parsers/cmd/mapi 42 | --> linux/amd64: github.com/fathom6/inetdata-parsers/cmd/mq 43 | --> linux/amd64: github.com/fathom6/inetdata-parsers/cmd/inetdata-arin-xml2json 44 | --> linux/amd64: github.com/fathom6/inetdata-parsers/cmd/inetdata-csvsplit 45 | --> linux/amd64: github.com/fathom6/inetdata-parsers/cmd/inetdata-arin-org2cidrs 46 | --> linux/amd64: github.com/fathom6/inetdata-parsers/cmd/inetdata-csv2mtbl 47 | --> linux/amd64: github.com/fathom6/inetdata-parsers/cmd/inetdata-dns2mtbl 48 | --> linux/amd64: github.com/fathom6/inetdata-parsers/cmd/inetdata-ct2csv 49 | --> linux/amd64: github.com/fathom6/inetdata-parsers/cmd/inetdata-ct2hostnames 50 | --> linux/amd64: github.com/fathom6/inetdata-parsers/cmd/inetdata-ct2hostnames-sync 51 | --> linux/amd64: github.com/fathom6/inetdata-parsers/cmd/inetdata-ct2mtbl 52 | --> linux/amd64: github.com/fathom6/inetdata-parsers/cmd/inetdata-csvrollup 53 | --> linux/amd64: github.com/fathom6/inetdata-parsers/cmd/inetdata-lines2mtbl 54 | --> linux/amd64: github.com/fathom6/inetdata-parsers/cmd/inetdata-hostnames2domains 55 | --> linux/amd64: github.com/fathom6/inetdata-parsers/cmd/inetdata-json2mtbl 56 | --> linux/amd64: github.com/fathom6/inetdata-parsers/cmd/inetdata-sonardnsv2-split 57 | --> linux/amd64: github.com/fathom6/inetdata-parsers/cmd/inetdata-zone2csv 58 | 59 | ``` 60 | 61 | 62 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package inetdata 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "os" 8 | "regexp" 9 | 10 | mtbl "github.com/hdm/golang-mtbl" 11 | ) 12 | 13 | var Match_SHA1 = regexp.MustCompile(`^[a-zA-Z0-9]{40}$`) 14 | 15 | var MTBLCompressionTypes = map[string]int{ 16 | "none": mtbl.COMPRESSION_NONE, 17 | "snappy": mtbl.COMPRESSION_SNAPPY, 18 | "zlib": mtbl.COMPRESSION_ZLIB, 19 | "lz4": mtbl.COMPRESSION_LZ4, 20 | "lz4hc": mtbl.COMPRESSION_LZ4HC, 21 | } 22 | 23 | const MTBL_KEY_LIMIT = 1024 * 1024 * 512 24 | 25 | const MTBL_VAL_LIMIT = 1024 * 1024 * 1024 26 | 27 | var Split_WS = regexp.MustCompile(`\s+`) 28 | 29 | func PrintVersion(app string) { 30 | fmt.Fprintf(os.Stderr, "%s v%s\n", app, Version) 31 | } 32 | 33 | func ReverseKey(s string) string { 34 | b := make([]byte, len(s)) 35 | var j int = len(s) - 1 36 | for i := 0; i <= j; i++ { 37 | b[j-i] = s[i] 38 | } 39 | return string(b) 40 | } 41 | 42 | func ReverseKeyBytes(s []byte) []byte { 43 | b := make([]byte, len(s)) 44 | var j int = len(s) - 1 45 | for i := 0; i <= j; i++ { 46 | b[j-i] = s[i] 47 | } 48 | return b 49 | } 50 | 51 | func ReadLines(input *os.File, out chan<- string) error { 52 | var ( 53 | frontbufferSize = 50000 54 | r = bufio.NewReaderSize(input, frontbufferSize) 55 | ) 56 | return ReadLinesFromReader(r, out) 57 | } 58 | 59 | func ReadLinesFromReader(input io.Reader, out chan<- string) error { 60 | 61 | var ( 62 | backbufferSize = 200000 63 | frontbufferSize = 50000 64 | r = bufio.NewReaderSize(input, frontbufferSize) 65 | buf []byte 66 | pred []byte 67 | err error 68 | ) 69 | 70 | if backbufferSize <= frontbufferSize { 71 | backbufferSize = (frontbufferSize / 3) * 4 72 | } 73 | 74 | for { 75 | buf, err = r.ReadSlice('\n') 76 | 77 | if err == bufio.ErrBufferFull { 78 | if len(buf) == 0 { 79 | continue 80 | } 81 | 82 | if pred == nil { 83 | pred = make([]byte, len(buf), backbufferSize) 84 | copy(pred, buf) 85 | } else { 86 | pred = append(pred, buf...) 87 | } 88 | continue 89 | } else if err == io.EOF && len(buf) == 0 && len(pred) == 0 { 90 | break 91 | } 92 | 93 | if len(pred) > 0 { 94 | buf, pred = append(pred, buf...), pred[:0] 95 | } 96 | 97 | if len(buf) > 0 && buf[len(buf)-1] == '\n' { 98 | buf = buf[:len(buf)-1] 99 | } 100 | 101 | if len(buf) == 0 { 102 | continue 103 | } 104 | 105 | out <- string(buf) 106 | } 107 | 108 | close(out) 109 | 110 | if err != nil && err != io.EOF { 111 | return err 112 | } 113 | 114 | return nil 115 | } 116 | -------------------------------------------------------------------------------- /cmd/inetdata-hostnames2domains/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/hdm/inetdata-parsers" 7 | "golang.org/x/net/publicsuffix" 8 | "os" 9 | "regexp" 10 | "runtime" 11 | "strings" 12 | "sync" 13 | "sync/atomic" 14 | "time" 15 | ) 16 | 17 | var output_count int64 = 0 18 | var input_count int64 = 0 19 | var wg sync.WaitGroup 20 | 21 | func usage() { 22 | fmt.Println("Usage: " + os.Args[0] + " [options]") 23 | fmt.Println("") 24 | fmt.Println("Reads a list of hostnames from stdin and generates a list of all domain names") 25 | fmt.Println("") 26 | fmt.Println("Options:") 27 | flag.PrintDefaults() 28 | } 29 | 30 | func showProgress(quit chan int) { 31 | start := time.Now() 32 | for { 33 | select { 34 | case <-quit: 35 | return 36 | case <-time.After(time.Second * 1): 37 | icount := atomic.LoadInt64(&input_count) 38 | ocount := atomic.LoadInt64(&output_count) 39 | 40 | if icount == 0 && ocount == 0 { 41 | // Reset start, so that we show stats only from our first input 42 | start = time.Now() 43 | continue 44 | } 45 | elapsed := time.Since(start) 46 | if elapsed.Seconds() > 1.0 { 47 | fmt.Fprintf(os.Stderr, "[*] [inetdata-hostnames2domains] Read %d and wrote %d records in %d seconds (%d/s in, %d/s out)\n", 48 | icount, 49 | ocount, 50 | int(elapsed.Seconds()), 51 | int(float64(icount)/elapsed.Seconds()), 52 | int(float64(ocount)/elapsed.Seconds())) 53 | } 54 | } 55 | } 56 | } 57 | 58 | func inputParser(c <-chan string) { 59 | 60 | digits := regexp.MustCompile(`^\d+\.`) 61 | 62 | for r := range c { 63 | 64 | raw := strings.TrimSpace(r) 65 | if len(raw) == 0 { 66 | continue 67 | } 68 | 69 | // Remove leading dots from the name 70 | for len(raw) > 2 && (raw[0:1] == ".") { 71 | raw = raw[1:] 72 | } 73 | 74 | // Remove any trailing dots from the domain name 75 | for len(raw) > 1 && raw[len(raw)-1:] == "." { 76 | raw = raw[:len(raw)-1] 77 | } 78 | 79 | // Make sure it looks like a FQHN 80 | bits := strings.SplitN(raw, ".", -1) 81 | if len(bits) < 2 { 82 | continue 83 | } 84 | 85 | // Lookup the public part of the domain name 86 | domain, _ := publicsuffix.PublicSuffix(raw) 87 | 88 | atomic.AddInt64(&input_count, 1) 89 | 90 | // Print each component of the FQHN 91 | for i := 0; i < len(bits)-1; i++ { 92 | name := strings.Join(bits[i:], ".") 93 | 94 | // Skip public suffixes (.com.au, .com, etc) 95 | if name == domain { 96 | continue 97 | } 98 | 99 | // Skip any names containing obviously bad bytes, but keep processing the domain 100 | if strings.IndexAny(name, "\x00\x09\x20\"'/?\\[]{}()=~!@#$%%^&<>`|:;\xFF") != -1 { 101 | continue 102 | } 103 | 104 | // Skip hostnames/subdomains that are entirely numerical 105 | if digits.Match([]byte(name)) { 106 | continue 107 | } 108 | 109 | fmt.Println(name) 110 | atomic.AddInt64(&output_count, 1) 111 | } 112 | } 113 | wg.Done() 114 | } 115 | 116 | func main() { 117 | 118 | runtime.GOMAXPROCS(runtime.NumCPU()) 119 | os.Setenv("LC_ALL", "C") 120 | 121 | flag.Usage = func() { usage() } 122 | version := flag.Bool("version", false, "Show the version and build timestamp") 123 | 124 | flag.Parse() 125 | 126 | if *version { 127 | inetdata.PrintVersion("inetdata-hostnames2domains") 128 | os.Exit(0) 129 | } 130 | 131 | // Progress tracker 132 | quit := make(chan int) 133 | go showProgress(quit) 134 | 135 | // Parse stdin 136 | c_inp := make(chan string) 137 | 138 | // Only one parser allowed given the rollup use case 139 | go inputParser(c_inp) 140 | wg.Add(1) 141 | 142 | // Reader closers c_inp on completion 143 | e := inetdata.ReadLines(os.Stdin, c_inp) 144 | if e != nil { 145 | fmt.Fprintf(os.Stderr, "Error reading input: %s\n", e) 146 | } 147 | 148 | wg.Wait() 149 | quit <- 0 150 | 151 | } 152 | -------------------------------------------------------------------------------- /cmd/inetdata-lines2mtbl/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "flag" 6 | "fmt" 7 | "os" 8 | "runtime" 9 | "time" 10 | 11 | mtbl "github.com/hdm/golang-mtbl" 12 | "github.com/hdm/inetdata-parsers" 13 | ) 14 | 15 | var merge_count int64 = 0 16 | var input_count int64 = 0 17 | 18 | func usage() { 19 | fmt.Println("Usage: " + os.Args[0] + " [options]") 20 | fmt.Println("") 21 | fmt.Println("Creates a MTBL database from a CSV input.") 22 | fmt.Println("") 23 | fmt.Println("Options:") 24 | flag.PrintDefaults() 25 | } 26 | 27 | func showProgress(quit chan int) { 28 | start := time.Now() 29 | for { 30 | select { 31 | case <-quit: 32 | return 33 | case <-time.After(time.Second * 1): 34 | elapsed := time.Since(start) 35 | if elapsed.Seconds() > 1.0 { 36 | fmt.Fprintf(os.Stderr, "[*] Processed %d records in %d seconds (%d/s) (merged: %d)\n", 37 | input_count, 38 | int(elapsed.Seconds()), 39 | int(float64(input_count)/elapsed.Seconds()), 40 | merge_count) 41 | } 42 | } 43 | } 44 | } 45 | 46 | func mergeFunc(key []byte, val0 []byte, val1 []byte) (mergedVal []byte) { 47 | merge_count++ 48 | return val0 49 | } 50 | 51 | func main() { 52 | 53 | runtime.GOMAXPROCS(runtime.NumCPU()) 54 | os.Setenv("LC_ALL", "C") 55 | 56 | flag.Usage = func() { usage() } 57 | 58 | reverse_key := flag.Bool("r", false, "Store the key in reverse order") 59 | compression := flag.String("c", "snappy", "The compression type to use (none, snappy, zlib, lz4, lz4hc)") 60 | sort_skip := flag.Bool("S", false, "Skip the sorting phase and assume keys are in pre-sorted order") 61 | sort_tmp := flag.String("t", "", "The temporary directory to use for the sorting phase") 62 | sort_mem := flag.Uint64("m", 1, "The maximum amount of memory to use, in gigabytes, for the sorting phase") 63 | version := flag.Bool("version", false, "Show the version and build timestamp") 64 | 65 | flag.Parse() 66 | 67 | if *version { 68 | inetdata.PrintVersion("inetdata-lines2mtbl") 69 | os.Exit(0) 70 | } 71 | 72 | if len(flag.Args()) != 1 { 73 | usage() 74 | os.Exit(1) 75 | } 76 | 77 | fname := flag.Args()[0] 78 | 79 | sort_opt := mtbl.SorterOptions{Merge: mergeFunc, MaxMemory: 1000000000} 80 | sort_opt.MaxMemory *= *sort_mem 81 | if len(*sort_tmp) > 0 { 82 | sort_opt.TempDir = *sort_tmp 83 | } 84 | 85 | compression_alg, ok := inetdata.MTBLCompressionTypes[*compression] 86 | if !ok { 87 | fmt.Fprintf(os.Stderr, "Invalid compression algorithm: %s\n", *compression) 88 | os.Exit(1) 89 | } 90 | 91 | s := mtbl.SorterInit(&sort_opt) 92 | defer s.Destroy() 93 | 94 | w, we := mtbl.WriterInit(fname, &mtbl.WriterOptions{Compression: compression_alg}) 95 | defer w.Destroy() 96 | 97 | if we != nil { 98 | fmt.Fprintf(os.Stderr, "Error: %s\n", we) 99 | os.Exit(1) 100 | } 101 | 102 | quit := make(chan int) 103 | go showProgress(quit) 104 | 105 | vstr := "1" 106 | scanner := bufio.NewScanner(os.Stdin) 107 | for scanner.Scan() { 108 | kstr := scanner.Text() 109 | 110 | input_count++ 111 | if len(kstr) == 0 { 112 | continue 113 | } 114 | 115 | if *reverse_key { 116 | kstr = inetdata.ReverseKey(kstr) 117 | } 118 | 119 | if len(kstr) > inetdata.MTBL_KEY_LIMIT || len(vstr) > inetdata.MTBL_VAL_LIMIT { 120 | fmt.Printf("Failed to entry with long key or value\n") 121 | continue 122 | } 123 | 124 | if *sort_skip { 125 | if e := w.Add([]byte(kstr), []byte(vstr)); e != nil { 126 | fmt.Printf("Failed to add %v -> %v: %v\n", kstr, vstr, e) 127 | } 128 | } else { 129 | if e := s.Add([]byte(kstr), []byte(vstr)); e != nil { 130 | fmt.Printf("Failed to add %v -> %v: %v\n", kstr, vstr, e) 131 | } 132 | } 133 | } 134 | 135 | if !*sort_skip { 136 | if e := s.Write(w); e != nil { 137 | fmt.Fprintf(os.Stderr, "Error: %s\n", e) 138 | os.Exit(1) 139 | } 140 | } 141 | 142 | quit <- 1 143 | } 144 | -------------------------------------------------------------------------------- /cmd/inetdata-csv2mtbl/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "flag" 6 | "fmt" 7 | "os" 8 | "runtime" 9 | "strings" 10 | 11 | mtbl "github.com/hdm/golang-mtbl" 12 | "github.com/hdm/inetdata-parsers" 13 | ) 14 | 15 | func usage() { 16 | fmt.Println("Usage: " + os.Args[0] + " [options]") 17 | fmt.Println("") 18 | fmt.Println("Creates a MTBL database from a CSV input.") 19 | fmt.Println("") 20 | fmt.Println("Options:") 21 | flag.PrintDefaults() 22 | } 23 | 24 | func mergeFunc(key []byte, val0 []byte, val1 []byte) (mergedVal []byte) { 25 | return []byte(string(val0) + " " + string(val1)) 26 | } 27 | 28 | func main() { 29 | 30 | runtime.GOMAXPROCS(runtime.NumCPU()) 31 | os.Setenv("LC_ALL", "C") 32 | 33 | flag.Usage = func() { usage() } 34 | 35 | indexKey := flag.Int("k", 1, "The field index to use as the key") 36 | indexVal := flag.Int("v", 2, "The field index to use as the value") 37 | reverseKey := flag.Bool("r", false, "Store the key in reverse order") 38 | maxFields := flag.Int("M", -1, "The maximum number of fields to parse with the delimiter") 39 | compression := flag.String("c", "snappy", "The compression type to use (none, snappy, zlib, lz4, lz4hc)") 40 | delimiter := flag.String("d", ",", "The delimiter to use as a field separator") 41 | sortSkip := flag.Bool("S", false, "Skip the sorting phase and assume keys are in pre-sorted order") 42 | sortTmp := flag.String("t", "", "The temporary directory to use for the sorting phase") 43 | sortMem := flag.Uint64("m", 1, "The maximum amount of memory to use, in gigabytes, for the sorting phase") 44 | 45 | version := flag.Bool("version", false, "Show the version and build timestamp") 46 | 47 | flag.Parse() 48 | 49 | if *version { 50 | inetdata.PrintVersion("inetdata-csv2mtbl") 51 | os.Exit(0) 52 | } 53 | 54 | if len(flag.Args()) != 1 { 55 | usage() 56 | os.Exit(1) 57 | } 58 | 59 | fname := flag.Args()[0] 60 | 61 | sortOpt := mtbl.SorterOptions{Merge: mergeFunc, MaxMemory: 1000000000} 62 | sortOpt.MaxMemory *= *sortMem 63 | if len(*sortTmp) > 0 { 64 | sortOpt.TempDir = *sortTmp 65 | } 66 | 67 | compAlg, ok := inetdata.MTBLCompressionTypes[*compression] 68 | if !ok { 69 | fmt.Fprintf(os.Stderr, "Invalid compression algorithm: %s\n", *compression) 70 | os.Exit(1) 71 | } 72 | 73 | s := mtbl.SorterInit(&sortOpt) 74 | defer s.Destroy() 75 | 76 | w, we := mtbl.WriterInit(fname, &mtbl.WriterOptions{Compression: compAlg}) 77 | defer w.Destroy() 78 | 79 | if we != nil { 80 | fmt.Fprintf(os.Stderr, "Error: %s\n", we) 81 | os.Exit(1) 82 | } 83 | 84 | scanner := bufio.NewScanner(os.Stdin) 85 | // Tune Scanner's value for MaxScanTokenSize which defaults to 65,536 86 | // Lines longer than MaxScanTokenSize will cause the Scanner to fail 87 | // Set the intial buffsize to twice the default 88 | buf := make([]byte, 0, 128*1024) 89 | scanner.Buffer(buf, len(buf)*2) 90 | 91 | var cline uint = 1 92 | for scanner.Scan() { 93 | cline++ 94 | raw := strings.TrimSpace(scanner.Text()) 95 | if len(raw) == 0 { 96 | continue 97 | } 98 | 99 | bits := strings.SplitN(raw, *delimiter, *maxFields) 100 | 101 | if len(bits) < *indexKey { 102 | fmt.Fprintf(os.Stderr, "No key: %s\n", raw) 103 | continue 104 | } 105 | 106 | if len(bits) < *indexVal { 107 | fmt.Fprintf(os.Stderr, "No value: %s\n", raw) 108 | continue 109 | } 110 | 111 | kstr := bits[*indexKey-1] 112 | if len(kstr) == 0 { 113 | continue 114 | } 115 | 116 | vstr := bits[*indexVal-1] 117 | if len(vstr) == 0 { 118 | continue 119 | } 120 | 121 | if *reverseKey { 122 | kstr = inetdata.ReverseKey(kstr) 123 | } 124 | 125 | if len(kstr) > inetdata.MTBL_KEY_LIMIT || len(vstr) > inetdata.MTBL_VAL_LIMIT { 126 | fmt.Printf("Failed to add entry with long key or value\n") 127 | continue 128 | } 129 | 130 | if *sortSkip { 131 | if e := w.Add([]byte(kstr), []byte(vstr)); e != nil { 132 | fmt.Printf("Failed to add %v -> %v: %v\n", kstr, vstr, e) 133 | } 134 | } else { 135 | if e := s.Add([]byte(kstr), []byte(vstr)); e != nil { 136 | fmt.Printf("Failed to add %v -> %v: %v\n", kstr, vstr, e) 137 | } 138 | } 139 | } 140 | 141 | if err := scanner.Err(); err != nil { 142 | fmt.Fprintf(os.Stderr, "Error while processing line %d : %s\n", cline, err) 143 | os.Exit(1) 144 | } 145 | 146 | if !*sortSkip { 147 | if e := s.Write(w); e != nil { 148 | fmt.Fprintf(os.Stderr, "Error: %s\n", e) 149 | os.Exit(1) 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /cmd/inetdata-json2mtbl/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "encoding/json" 6 | "flag" 7 | "fmt" 8 | "os" 9 | "runtime" 10 | 11 | mtbl "github.com/hdm/golang-mtbl" 12 | "github.com/hdm/inetdata-parsers" 13 | "github.com/peterbourgon/mergemap" 14 | ) 15 | 16 | const MERGE_MODE_COMBINE = 0 17 | const MERGE_MODE_FIRST = 1 18 | const MERGE_MODE_LAST = 2 19 | 20 | var merge_mode = MERGE_MODE_COMBINE 21 | 22 | func usage() { 23 | fmt.Println("Usage: " + os.Args[0] + " [options]") 24 | fmt.Println("") 25 | fmt.Println("Creates a MTBL database from a JSON input.") 26 | fmt.Println("") 27 | fmt.Println("Options:") 28 | flag.PrintDefaults() 29 | } 30 | 31 | func mergeFunc(key []byte, val0 []byte, val1 []byte) (mergedVal []byte) { 32 | 33 | if merge_mode == MERGE_MODE_FIRST { 34 | return val0 35 | } 36 | 37 | if merge_mode == MERGE_MODE_LAST { 38 | return val1 39 | } 40 | 41 | // MERGE_MODE_COMBINE 42 | var v0, v1 map[string]interface{} 43 | 44 | if e := json.Unmarshal(val0, &v0); e != nil { 45 | return val1 46 | } 47 | 48 | if e := json.Unmarshal(val1, &v1); e != nil { 49 | return val0 50 | } 51 | 52 | m := mergemap.Merge(v0, v1) 53 | d, e := json.Marshal(m) 54 | if e != nil { 55 | fmt.Fprintf(os.Stderr, "JSON merge error: %v -> %v + %v\n", e, val0, val1) 56 | return val0 57 | } 58 | 59 | return d 60 | } 61 | 62 | func main() { 63 | 64 | runtime.GOMAXPROCS(runtime.NumCPU()) 65 | os.Setenv("LC_ALL", "C") 66 | 67 | flag.Usage = func() { usage() } 68 | 69 | kname := flag.String("k", "", "The field name to use as the key") 70 | reverse_key := flag.Bool("r", false, "Store the key in reverse order") 71 | compression := flag.String("c", "snappy", "The compression type to use (none, snappy, zlib, lz4, lz4hc)") 72 | sort_tmp := flag.String("t", "", "The temporary directory to use for the sorting phase") 73 | sort_mem := flag.Uint64("m", 1, "The maximum amount of memory to use, in gigabytes, for the sorting phase") 74 | selected_merge_mode := flag.String("M", "combine", "The merge mode: combine, first, or last") 75 | version := flag.Bool("version", false, "Show the version and build timestamp") 76 | 77 | flag.Parse() 78 | 79 | if *version { 80 | inetdata.PrintVersion("inetdata-json2mtbl") 81 | os.Exit(0) 82 | } 83 | 84 | if len(flag.Args()) != 1 { 85 | usage() 86 | os.Exit(1) 87 | } 88 | 89 | if len(*kname) == 0 { 90 | fmt.Fprintf(os.Stderr, "Error: missing key name (-k) parameter\n") 91 | usage() 92 | os.Exit(1) 93 | } 94 | 95 | fname := flag.Args()[0] 96 | 97 | switch *selected_merge_mode { 98 | case "combine": 99 | merge_mode = MERGE_MODE_COMBINE 100 | case "first": 101 | merge_mode = MERGE_MODE_FIRST 102 | case "last": 103 | merge_mode = MERGE_MODE_LAST 104 | default: 105 | fmt.Fprintf(os.Stderr, "Error: Invalid merge mode specified: %s\n", *selected_merge_mode) 106 | usage() 107 | os.Exit(1) 108 | } 109 | 110 | sort_opt := mtbl.SorterOptions{Merge: mergeFunc, MaxMemory: 1000000000} 111 | sort_opt.MaxMemory *= *sort_mem 112 | if len(*sort_tmp) > 0 { 113 | sort_opt.TempDir = *sort_tmp 114 | } 115 | 116 | compression_alg, ok := inetdata.MTBLCompressionTypes[*compression] 117 | if !ok { 118 | fmt.Fprintf(os.Stderr, "Invalid compression algorithm: %s\n", *compression) 119 | os.Exit(1) 120 | } 121 | 122 | s := mtbl.SorterInit(&sort_opt) 123 | defer s.Destroy() 124 | 125 | w, we := mtbl.WriterInit(fname, &mtbl.WriterOptions{Compression: compression_alg}) 126 | defer w.Destroy() 127 | 128 | if we != nil { 129 | fmt.Fprintf(os.Stderr, "Error: %s\n", we) 130 | os.Exit(1) 131 | } 132 | 133 | scanner := bufio.NewScanner(os.Stdin) 134 | buf := make([]byte, 0, 1024*1024*8) 135 | scanner.Buffer(buf, 1024*1024*8) 136 | 137 | for scanner.Scan() { 138 | raw := scanner.Bytes() 139 | if len(raw) == 0 { 140 | continue 141 | } 142 | 143 | var v map[string]interface{} 144 | 145 | if e := json.Unmarshal(raw, &v); e != nil { 146 | fmt.Fprintf(os.Stderr, "Invalid JSON: %v -> %v\n", e, string(raw)) 147 | continue 148 | } 149 | 150 | kval, ok := v[*kname] 151 | if !ok { 152 | fmt.Fprintf(os.Stderr, "Missing key: %v -> %v\n", *kname, string(raw)) 153 | continue 154 | } 155 | 156 | kstr := kval.(string) 157 | 158 | if *reverse_key { 159 | kstr = inetdata.ReverseKey(kstr) 160 | } 161 | 162 | if len(kstr) > inetdata.MTBL_KEY_LIMIT || len(raw) > inetdata.MTBL_VAL_LIMIT { 163 | fmt.Printf("Failed to entry with long key or value\n") 164 | continue 165 | } 166 | 167 | if e := s.Add([]byte(kstr), []byte(raw)); e != nil { 168 | fmt.Printf("Failed to add %v -> %v: %v\n", kstr, raw, e) 169 | continue 170 | } 171 | 172 | } 173 | 174 | if e := s.Write(w); e != nil { 175 | fmt.Fprintf(os.Stderr, "Error: %s\n", e) 176 | os.Exit(1) 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /cmd/inetdata-csvrollup/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "runtime" 8 | "strings" 9 | "sync" 10 | "sync/atomic" 11 | "time" 12 | 13 | "github.com/hdm/inetdata-parsers" 14 | ) 15 | 16 | var output_count int64 = 0 17 | var input_count int64 = 0 18 | var stdout_lock sync.Mutex 19 | var wg sync.WaitGroup 20 | 21 | type OutputKey struct { 22 | Key string 23 | Vals []string 24 | } 25 | 26 | func usage() { 27 | fmt.Println("Usage: " + os.Args[0] + " [options]") 28 | fmt.Println("") 29 | fmt.Println("Reads a pre-sorted (-u -t , -k 1) CSV from stdin, treats all bytes after the first comma") 30 | fmt.Println("as the value, merges values with the same key using a null byte, outputs an unsorted") 31 | fmt.Println("merged CSV as output.") 32 | fmt.Println("") 33 | fmt.Println("Options:") 34 | flag.PrintDefaults() 35 | } 36 | 37 | func showProgress(quit chan int) { 38 | start := time.Now() 39 | for { 40 | select { 41 | case <-quit: 42 | return 43 | case <-time.After(time.Second * 1): 44 | icount := atomic.LoadInt64(&input_count) 45 | ocount := atomic.LoadInt64(&output_count) 46 | 47 | if icount == 0 && ocount == 0 { 48 | // Reset start, so that we show stats only from our first input 49 | start = time.Now() 50 | continue 51 | } 52 | elapsed := time.Since(start) 53 | if elapsed.Seconds() > 1.0 { 54 | fmt.Fprintf(os.Stderr, "[*] [inetdata-csvrollup] Read %d and wrote %d records in %d seconds (%d/s in, %d/s out)\n", 55 | icount, 56 | ocount, 57 | int(elapsed.Seconds()), 58 | int(float64(icount)/elapsed.Seconds()), 59 | int(float64(ocount)/elapsed.Seconds())) 60 | } 61 | } 62 | } 63 | } 64 | 65 | func writeOutput(o chan string, q chan bool) { 66 | for r := range o { 67 | os.Stdout.Write([]byte(r)) 68 | } 69 | q <- true 70 | } 71 | 72 | func mergeAndEmit(c chan OutputKey, o chan string) { 73 | 74 | for r := range c { 75 | 76 | unique := map[string]bool{} 77 | 78 | for i := range r.Vals { 79 | vals := strings.SplitN(r.Vals[i], "\x00", -1) 80 | for v := range vals { 81 | unique[vals[v]] = true 82 | } 83 | } 84 | 85 | out := make([]string, len(unique)) 86 | i := 0 87 | for v := range unique { 88 | out[i] = v 89 | i++ 90 | } 91 | atomic.AddInt64(&output_count, 1) 92 | o <- fmt.Sprintf("%s,%s\n", r.Key, strings.Join(out, "\x00")) 93 | } 94 | 95 | wg.Done() 96 | } 97 | 98 | // allowedIdentical checks to see if the record type is one of the few 99 | // DNS record types allowed to point to itself (name=value) 100 | func allowedIdentical(record_type string) bool { 101 | switch record_type { 102 | case 103 | "ns", 104 | "r-ns", 105 | "mx", 106 | "r-mx": 107 | return true 108 | } 109 | return false 110 | } 111 | 112 | func inputParser(c <-chan string, outc chan<- OutputKey) { 113 | 114 | // Track current key and value array 115 | ckey := "" 116 | cval := []string{} 117 | 118 | for r := range c { 119 | 120 | raw := strings.TrimSpace(r) 121 | if len(raw) == 0 { 122 | continue 123 | } 124 | 125 | bits := strings.SplitN(raw, ",", 2) 126 | 127 | if len(bits) < 2 || len(bits[0]) == 0 { 128 | fmt.Fprintf(os.Stderr, "[-] Invalid line: %q\n", raw) 129 | continue 130 | } 131 | 132 | // Tons of records with a blank (".") DNS response, just ignore 133 | if len(bits[1]) == 0 { 134 | continue 135 | } 136 | 137 | atomic.AddInt64(&input_count, 1) 138 | 139 | key := bits[0] 140 | val := bits[1] 141 | 142 | // First key hit 143 | if ckey == "" { 144 | ckey = key 145 | } 146 | 147 | // Next key hit 148 | if ckey != key { 149 | if len(cval) != 0 { 150 | outc <- OutputKey{Key: ckey, Vals: cval} 151 | } 152 | ckey = key 153 | cval = []string{} 154 | } 155 | 156 | // Cleanup common scan artifacts, not comprehensive 157 | 158 | // Ignore any records where key is empty or identical to the value 159 | // (with the exception of certain types) 160 | if len(val) >= len(key) { 161 | parts := strings.SplitN(val, ",", 2) 162 | if len(parts) == 2 && !allowedIdentical(parts[0]) { 163 | if len(parts[1]) == 0 || key == parts[1] { 164 | continue 165 | } 166 | } 167 | } 168 | 169 | // TXT records start with an erroneous pipe character 170 | if len(val) > 5 && val[0:5] == "txt,|" { 171 | val = "txt," + val[5:] 172 | } 173 | 174 | // DNSSEC-related TXT records often have trailing bytes 175 | if len(val) >= 38 && (val[0:6] == "txt,31" || val[0:6] == "txt,00" || val[0:6] == "txt,aa") { 176 | val = val[0:38] 177 | } 178 | 179 | // Mangled TXT value, ignore 180 | if len(val) >= 5 && len(val) <= 10 && val[0:5] == "txt,~" { 181 | continue 182 | } 183 | 184 | // New data value 185 | cval = append(cval, val) 186 | } 187 | 188 | if len(ckey) > 0 && len(cval) > 0 { 189 | outc <- OutputKey{Key: ckey, Vals: cval} 190 | } 191 | 192 | close(outc) 193 | wg.Done() 194 | } 195 | 196 | func main() { 197 | 198 | runtime.GOMAXPROCS(runtime.NumCPU()) 199 | os.Setenv("LC_ALL", "C") 200 | 201 | flag.Usage = func() { usage() } 202 | version := flag.Bool("version", false, "Show the version and build timestamp") 203 | 204 | flag.Parse() 205 | 206 | if *version { 207 | inetdata.PrintVersion("inetdata-csvrollup") 208 | os.Exit(0) 209 | } 210 | 211 | // Progress tracker 212 | quit := make(chan int) 213 | go showProgress(quit) 214 | 215 | // Output merger and writer 216 | outc := make(chan OutputKey, 1000) 217 | outl := make(chan string, 1000) 218 | outq := make(chan bool, 1) 219 | 220 | for i := 0; i < runtime.NumCPU(); i++ { 221 | go mergeAndEmit(outc, outl) 222 | wg.Add(1) 223 | } 224 | 225 | // Not covered by the waitgroup 226 | go writeOutput(outl, outq) 227 | 228 | // Parse stdin 229 | c_inp := make(chan string, 1000) 230 | 231 | // Only one parser allowed given the rollup use case 232 | go inputParser(c_inp, outc) 233 | wg.Add(1) 234 | 235 | // Reader closers c_inp on completion 236 | e := inetdata.ReadLines(os.Stdin, c_inp) 237 | if e != nil { 238 | fmt.Fprintf(os.Stderr, "Error reading input: %s\n", e) 239 | } 240 | 241 | wg.Wait() 242 | 243 | close(outl) 244 | 245 | <-outq 246 | close(outq) 247 | 248 | quit <- 0 249 | 250 | } 251 | -------------------------------------------------------------------------------- /cmd/inetdata-ct2hostnames/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | ct "github.com/google/certificate-transparency-go" 8 | "github.com/google/certificate-transparency-go/tls" 9 | "github.com/google/certificate-transparency-go/x509" 10 | "github.com/hdm/inetdata-parsers" 11 | "golang.org/x/net/publicsuffix" 12 | "os" 13 | "runtime" 14 | "strings" 15 | "sync" 16 | "sync/atomic" 17 | "time" 18 | ) 19 | 20 | var output_count int64 = 0 21 | var input_count int64 = 0 22 | var timestamps *bool 23 | 24 | var wi sync.WaitGroup 25 | var wo sync.WaitGroup 26 | 27 | type CTEntry struct { 28 | LeafInput []byte `json:"leaf_input"` 29 | ExtraData []byte `json:"extra_data"` 30 | } 31 | 32 | func usage() { 33 | fmt.Println("Usage: " + os.Args[0] + " [options]") 34 | fmt.Println("") 35 | fmt.Println("Reads a CT log in JSONL format (one line per record) and emits hostnames") 36 | fmt.Println("") 37 | fmt.Println("Options:") 38 | flag.PrintDefaults() 39 | } 40 | 41 | func showProgress(quit chan int) { 42 | start := time.Now() 43 | for { 44 | select { 45 | case <-quit: 46 | return 47 | case <-time.After(time.Second * 1): 48 | icount := atomic.LoadInt64(&input_count) 49 | ocount := atomic.LoadInt64(&output_count) 50 | 51 | if icount == 0 && ocount == 0 { 52 | // Reset start, so that we show stats only from our first input 53 | start = time.Now() 54 | continue 55 | } 56 | elapsed := time.Since(start) 57 | if elapsed.Seconds() > 1.0 { 58 | fmt.Fprintf(os.Stderr, "[*] [inetdata-ct2hostnames] Read %d and wrote %d records in %d seconds (%d/s in, %d/s out)\n", 59 | icount, 60 | ocount, 61 | int(elapsed.Seconds()), 62 | int(float64(icount)/elapsed.Seconds()), 63 | int(float64(ocount)/elapsed.Seconds())) 64 | } 65 | } 66 | } 67 | } 68 | 69 | func outputWriter(o <-chan string) { 70 | for name := range o { 71 | fmt.Println(name) 72 | atomic.AddInt64(&output_count, 1) 73 | } 74 | wo.Done() 75 | } 76 | 77 | func inputParser(c <-chan string, o chan<- string) { 78 | 79 | for r := range c { 80 | var entry CTEntry 81 | 82 | if err := json.Unmarshal([]byte(r), &entry); err != nil { 83 | fmt.Fprintf(os.Stderr, "Error parsing input: %s\n", r) 84 | continue 85 | } 86 | 87 | var leaf ct.MerkleTreeLeaf 88 | 89 | if rest, err := tls.Unmarshal(entry.LeafInput, &leaf); err != nil { 90 | fmt.Fprintf(os.Stderr, "Failed to unmarshal MerkleTreeLeaf: %v (%s)", err, r) 91 | continue 92 | } else if len(rest) > 0 { 93 | fmt.Fprintf(os.Stderr, "Trailing data (%d bytes) after MerkleTreeLeaf: %q", len(rest), rest) 94 | continue 95 | } 96 | 97 | var cert *x509.Certificate 98 | var err error 99 | 100 | switch leaf.TimestampedEntry.EntryType { 101 | case ct.X509LogEntryType: 102 | 103 | cert, err = x509.ParseCertificate(leaf.TimestampedEntry.X509Entry.Data) 104 | if err != nil && !strings.Contains(err.Error(), "NonFatalErrors:") { 105 | fmt.Fprintf(os.Stderr, "Failed to parse cert: %s\n", err.Error()) 106 | continue 107 | } 108 | 109 | case ct.PrecertLogEntryType: 110 | 111 | cert, err = x509.ParseTBSCertificate(leaf.TimestampedEntry.PrecertEntry.TBSCertificate) 112 | if err != nil && !strings.Contains(err.Error(), "NonFatalErrors:") { 113 | fmt.Fprintf(os.Stderr, "Failed to parse precert: %s\n", err.Error()) 114 | continue 115 | } 116 | 117 | default: 118 | fmt.Fprintf(os.Stderr, "Unknown entry type: %v (%s)", leaf.TimestampedEntry.EntryType, r) 119 | continue 120 | } 121 | 122 | // Valid input 123 | atomic.AddInt64(&input_count, 1) 124 | 125 | var names = make(map[string]struct{}) 126 | 127 | if _, err := publicsuffix.EffectiveTLDPlusOne(cert.Subject.CommonName); err == nil { 128 | // Make sure the CN looks like an actual hostname 129 | if strings.Contains(cert.Subject.CommonName, " ") || 130 | strings.Contains(cert.Subject.CommonName, ":") || 131 | inetdata.MatchIPv4.Match([]byte(cert.Subject.CommonName)) { 132 | continue 133 | } 134 | names[strings.ToLower(cert.Subject.CommonName)] = struct{}{} 135 | } 136 | 137 | for _, alt := range cert.DNSNames { 138 | if _, err := publicsuffix.EffectiveTLDPlusOne(alt); err == nil { 139 | // Make sure the CN looks like an actual hostname 140 | if strings.Contains(alt, " ") || 141 | strings.Contains(alt, ":") || 142 | inetdata.MatchIPv4.Match([]byte(alt)) { 143 | continue 144 | } 145 | names[strings.ToLower(alt)] = struct{}{} 146 | } 147 | } 148 | 149 | // Write the names to the output channel 150 | if *timestamps { 151 | for n := range names { 152 | o <- fmt.Sprintf("%d\t%s", leaf.TimestampedEntry.Timestamp, n) 153 | } 154 | } else { 155 | for n := range names { 156 | o <- n 157 | } 158 | } 159 | } 160 | 161 | wi.Done() 162 | } 163 | 164 | func main() { 165 | 166 | runtime.GOMAXPROCS(runtime.NumCPU()) 167 | os.Setenv("LC_ALL", "C") 168 | 169 | flag.Usage = func() { usage() } 170 | version := flag.Bool("version", false, "Show the version and build timestamp") 171 | timestamps = flag.Bool("timestamps", false, "Prefix all extracted names with the CT entry timestamp") 172 | 173 | flag.Parse() 174 | 175 | if *version { 176 | inetdata.PrintVersion("inetdata-ct2hostnames") 177 | os.Exit(0) 178 | } 179 | 180 | // Start the progress tracker 181 | quit := make(chan int) 182 | go showProgress(quit) 183 | 184 | // Input 185 | c_inp := make(chan string) 186 | 187 | // Output 188 | c_out := make(chan string) 189 | 190 | // Launch one input parser per core 191 | for i := 0; i < runtime.NumCPU(); i++ { 192 | go inputParser(c_inp, c_out) 193 | } 194 | wi.Add(runtime.NumCPU()) 195 | 196 | // Launch a single output writer 197 | go outputWriter(c_out) 198 | wo.Add(1) 199 | 200 | // Reader closers c_inp on completion 201 | e := inetdata.ReadLines(os.Stdin, c_inp) 202 | if e != nil { 203 | fmt.Fprintf(os.Stderr, "Error reading input: %s\n", e) 204 | } 205 | 206 | // Wait for the input parsers 207 | wi.Wait() 208 | 209 | // Close the output handle 210 | close(c_out) 211 | 212 | // Wait for the output goroutine 213 | wo.Wait() 214 | 215 | // Stop the progress monitor 216 | quit <- 0 217 | } 218 | -------------------------------------------------------------------------------- /ip.go: -------------------------------------------------------------------------------- 1 | package inetdata 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "fmt" 7 | "math" 8 | "net" 9 | "os" 10 | "regexp" 11 | "strings" 12 | ) 13 | 14 | // MatchIPv6 is a regular expression for validating IPv6 addresses 15 | var MatchIPv6 = regexp.MustCompile(`^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?$`) 16 | 17 | // MatchIPv4 is a regular expression for validating IPv4 addresses 18 | var MatchIPv4 = regexp.MustCompile(`^(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))$`) 19 | 20 | //IPv4Masks is a precalculated lookup table for IPv4 CIDR 21 | var IPv4Masks = map[uint32]uint32{ 22 | 1: 32, 23 | 2: 31, 24 | 4: 30, 25 | 8: 29, 26 | 16: 28, 27 | 32: 27, 28 | 64: 26, 29 | 128: 25, 30 | 256: 24, 31 | 512: 23, 32 | 1024: 22, 33 | 2048: 21, 34 | 4096: 20, 35 | 8192: 19, 36 | 16384: 18, 37 | 32768: 17, 38 | 65536: 16, 39 | 131072: 15, 40 | 262144: 14, 41 | 524288: 13, 42 | 1048576: 12, 43 | 2097152: 11, 44 | 4194304: 10, 45 | 8388608: 9, 46 | 16777216: 8, 47 | 33554432: 7, 48 | 67108864: 6, 49 | 134217728: 5, 50 | 268435456: 4, 51 | 536870912: 3, 52 | 1073741824: 2, 53 | 2147483648: 1, 54 | } 55 | 56 | //IPv4MaskSizes is a precalculated lookup table for IPv4 CIDR mask sizes 57 | var IPv4MaskSizes = []uint32{ 58 | 2147483648, 59 | 1073741824, 60 | 536870912, 61 | 268435456, 62 | 134217728, 63 | 67108864, 64 | 33554432, 65 | 16777216, 66 | 8388608, 67 | 4194304, 68 | 2097152, 69 | 1048576, 70 | 524288, 71 | 262144, 72 | 131072, 73 | 65536, 74 | 32768, 75 | 16384, 76 | 8192, 77 | 4096, 78 | 2048, 79 | 1024, 80 | 512, 81 | 256, 82 | 128, 83 | 64, 84 | 32, 85 | 16, 86 | 8, 87 | 4, 88 | 2, 89 | 1, 90 | } 91 | 92 | // IPv42UInt converts IPv4 addresses to unsigned integers 93 | func IPv42UInt(ips string) (uint32, error) { 94 | ip := net.ParseIP(ips) 95 | if ip == nil { 96 | return 0, errors.New("Invalid IPv4 address") 97 | } 98 | ip = ip.To4() 99 | return binary.BigEndian.Uint32(ip), nil 100 | } 101 | 102 | // UInt2IPv4 converts unsigned integers to IPv4 addresses 103 | func UInt2IPv4(ipi uint32) string { 104 | ipb := make([]byte, 4) 105 | binary.BigEndian.PutUint32(ipb, ipi) 106 | ip := net.IP(ipb) 107 | return ip.String() 108 | } 109 | 110 | // IPv4Range2CIDRs converts a start and stop IPv4 range to a list of CIDRs 111 | func IPv4Range2CIDRs(sIP string, eIP string) ([]string, error) { 112 | 113 | sI, sE := IPv42UInt(sIP) 114 | if sE != nil { 115 | return []string{}, sE 116 | } 117 | 118 | eI, eE := IPv42UInt(eIP) 119 | if eE != nil { 120 | return []string{}, eE 121 | } 122 | 123 | if sI > eI { 124 | return []string{}, errors.New("Start address is bigger than end address") 125 | } 126 | 127 | return IPv4UIntRange2CIDRs(sI, eI), nil 128 | } 129 | 130 | // IPv4UIntRange2CIDRs converts a range of insigned integers into IPv4 CIDRs 131 | func IPv4UIntRange2CIDRs(sI uint32, eI uint32) []string { 132 | cidrs := []string{} 133 | 134 | // Ranges are inclusive 135 | size := eI - sI + 1 136 | 137 | if size == 0 { 138 | return cidrs 139 | } 140 | 141 | for i := range IPv4MaskSizes { 142 | 143 | maskSize := IPv4MaskSizes[i] 144 | 145 | if maskSize > size { 146 | continue 147 | } 148 | 149 | // Exact match of the block size 150 | if maskSize == size { 151 | cidrs = append(cidrs, fmt.Sprintf("%s/%d", UInt2IPv4(sI), IPv4Masks[maskSize])) 152 | break 153 | } 154 | 155 | // Chop off the biggest block that fits 156 | cidrs = append(cidrs, fmt.Sprintf("%s/%d", UInt2IPv4(sI), IPv4Masks[maskSize])) 157 | sI = sI + maskSize 158 | 159 | // Look for additional blocks 160 | newCidrs := IPv4UIntRange2CIDRs(sI, eI) 161 | 162 | // Merge those blocks into out results 163 | for x := range newCidrs { 164 | cidrs = append(cidrs, newCidrs[x]) 165 | } 166 | break 167 | 168 | } 169 | return cidrs 170 | } 171 | 172 | //AddressesFromCIDR parses a CIDR and writes individual IPs to a channel 173 | func AddressesFromCIDR(cidr string, o chan<- string) { 174 | if len(cidr) == 0 { 175 | return 176 | } 177 | 178 | // We may receive bare IP addresses, not CIDRs sometimes 179 | if !strings.Contains(cidr, "/") { 180 | if strings.Contains(cidr, ":") { 181 | cidr = cidr + "/128" 182 | } else { 183 | cidr = cidr + "/32" 184 | } 185 | } 186 | 187 | // Parse CIDR into base address + mask 188 | ip, net, err := net.ParseCIDR(cidr) 189 | if err != nil { 190 | fmt.Fprintf(os.Stderr, "Invalid CIDR %s: %s\n", cidr, err.Error()) 191 | return 192 | } 193 | 194 | // Verify IPv4 for now 195 | ip4 := net.IP.To4() 196 | if ip4 == nil { 197 | fmt.Fprintf(os.Stderr, "Invalid IPv4 CIDR %s\n", cidr) 198 | return 199 | } 200 | 201 | netBase, err := IPv42UInt(net.IP.String()) 202 | if err != nil { 203 | fmt.Fprintf(os.Stderr, "Invalid IPv4 Address %s: %s\n", ip.String(), err.Error()) 204 | return 205 | } 206 | 207 | maskOnes, maskTotal := net.Mask.Size() 208 | 209 | // Does not work for IPv6 due to cast to uint32 210 | netSize := uint32(math.Pow(2, float64(maskTotal-maskOnes))) 211 | 212 | curBase := netBase 213 | endBase := netBase + netSize 214 | curAddr := curBase 215 | 216 | for curAddr = curBase; curAddr < endBase; curAddr++ { 217 | o <- UInt2IPv4(curAddr) 218 | } 219 | 220 | return 221 | } 222 | -------------------------------------------------------------------------------- /cmd/mapi/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/gorilla/mux" 8 | "github.com/hdm/golang-mtbl" 9 | "github.com/hdm/inetdata-parsers" 10 | "log" 11 | "math" 12 | "net" 13 | "net/http" 14 | "os" 15 | "path/filepath" 16 | "runtime" 17 | "strings" 18 | s "strings" 19 | ) 20 | 21 | var prefix *string 22 | var domain *string 23 | var cidr *string 24 | 25 | func findPaths() []string { 26 | pathS, err := os.Getwd() 27 | if err != nil { 28 | panic(err) 29 | } 30 | var paths []string 31 | 32 | filepath.Walk(pathS, func(path string, f os.FileInfo, _ error) error { 33 | if !f.IsDir() && s.HasSuffix(f.Name(), ".mtbl") { 34 | paths = append(paths, path) 35 | } 36 | return nil 37 | }) 38 | return paths 39 | } 40 | 41 | var paths = findPaths() 42 | 43 | func writeOutputR(key_bytes []byte, val_bytes []byte, w http.ResponseWriter) { 44 | 45 | key := string(key_bytes) 46 | val := string(val_bytes) 47 | 48 | key = inetdata.ReverseKey(key) 49 | 50 | o := make(map[string]interface{}) 51 | v := make([][]string, 1) 52 | 53 | if de := json.Unmarshal([]byte(val), &v); de != nil { 54 | fmt.Fprintf(os.Stderr, "Could not unmarshal %s -> %s as json: %s\n", key, val, de) 55 | o["key"] = string(key) 56 | o["val"] = string(val) 57 | json.NewEncoder(w).Encode(o) 58 | return 59 | } 60 | 61 | o["key"] = string(key) 62 | o["val"] = v 63 | 64 | json.NewEncoder(w).Encode(o) 65 | } 66 | 67 | func writeOutput(key_bytes []byte, val_bytes []byte, w http.ResponseWriter) { 68 | 69 | key := string(key_bytes) 70 | val := string(val_bytes) 71 | 72 | o := make(map[string]interface{}) 73 | v := make([][]string, 1) 74 | 75 | if de := json.Unmarshal([]byte(val), &v); de != nil { 76 | fmt.Fprintf(w, "%s\n", val) 77 | return 78 | } 79 | 80 | o["key"] = string(key) 81 | o["val"] = v 82 | 83 | json.NewEncoder(w).Encode(o) 84 | } 85 | 86 | func searchDomain(w http.ResponseWriter, req *http.Request) { 87 | params := mux.Vars(req) 88 | rdomain := []byte(inetdata.ReverseKey(params["id"])) 89 | 90 | for i := range paths { 91 | 92 | path := paths[i] 93 | 94 | r, e := mtbl.ReaderInit(path, &mtbl.ReaderOptions{VerifyChecksums: true}) 95 | if e != nil { 96 | fmt.Fprintf(os.Stderr, "Error reading %s: %s\n", path, e) 97 | continue 98 | } 99 | defer r.Destroy() 100 | 101 | dot_rdomain := append(rdomain, '.') 102 | it := mtbl.IterPrefix(r, rdomain) 103 | for { 104 | key_bytes, val_bytes, ok := it.Next() 105 | if !ok { 106 | break 107 | } 108 | 109 | if bytes.Compare(key_bytes, rdomain) == 0 || 110 | bytes.Compare(key_bytes[0:len(dot_rdomain)], dot_rdomain) == 0 { 111 | writeOutputR(key_bytes, val_bytes, w) 112 | } 113 | } 114 | } 115 | } 116 | 117 | func searchPrefixIPv4(w http.ResponseWriter, req *http.Request) { 118 | params := mux.Vars(req) 119 | prefix := []byte(params["ip"]) 120 | 121 | for i := range paths { 122 | 123 | path := paths[i] 124 | 125 | r, e := mtbl.ReaderInit(path, &mtbl.ReaderOptions{VerifyChecksums: true}) 126 | if e != nil { 127 | fmt.Fprintf(os.Stderr, "Error reading %s: %s\n", path, e) 128 | continue 129 | } 130 | defer r.Destroy() 131 | 132 | it := mtbl.IterPrefix(r, []byte(prefix)) 133 | for { 134 | key_bytes, val_bytes, ok := it.Next() 135 | if !ok { 136 | break 137 | } 138 | writeOutput(key_bytes, val_bytes, w) 139 | } 140 | } 141 | } 142 | 143 | func cidrPrefixIPv4(r *mtbl.Reader, prefix string, w http.ResponseWriter) { 144 | it := mtbl.IterPrefix(r, []byte(prefix)) 145 | for { 146 | key_bytes, val_bytes, ok := it.Next() 147 | if !ok { 148 | break 149 | } 150 | 151 | if inetdata.MatchIPv4.Match(key_bytes) { 152 | writeOutput(key_bytes, val_bytes, w) 153 | } 154 | } 155 | } 156 | 157 | func searchCIDR(w http.ResponseWriter, req *http.Request) { 158 | params := mux.Vars(req) 159 | ip := string(params["ip"]) 160 | cidr := string(params["id"]) 161 | cidr = ip + "/" + cidr 162 | 163 | // Parse CIDR into base address + mask 164 | ip2, net2, err := net.ParseCIDR(cidr) 165 | if err != nil { 166 | fmt.Fprintf(os.Stderr, "Invalid CIDR %s: %s\n", cidr, err.Error()) 167 | return 168 | } 169 | 170 | // Verify IPv4 for now 171 | ip4 := net2.IP.To4() 172 | if ip4 == nil { 173 | fmt.Fprintf(os.Stderr, "Invalid IPv4 CIDR %s\n", cidr) 174 | return 175 | } 176 | 177 | net_base, err := inetdata.IPv42UInt(net2.IP.String()) 178 | if err != nil { 179 | fmt.Fprintf(os.Stderr, "Invalid IPv4 Address %s: %s\n", ip2.String(), err.Error()) 180 | return 181 | } 182 | 183 | mask_ones, mask_total := net2.Mask.Size() 184 | 185 | // Does not work for IPv6 due to cast to uint32 186 | net_size := uint32(math.Pow(2, float64(mask_total-mask_ones))) 187 | 188 | cur_base := net_base 189 | end_base := net_base + net_size - 1 190 | 191 | var ndots uint32 = 3 192 | var block_size uint32 = 256 193 | 194 | // Handle massive network blocks 195 | if mask_ones <= 8 { 196 | ndots = 1 197 | block_size = 256 * 256 * 256 198 | } else if mask_ones <= 16 { 199 | ndots = 2 200 | block_size = 256 * 256 201 | } 202 | 203 | for i := range paths { 204 | 205 | path := paths[i] 206 | r, e := mtbl.ReaderInit(path, &mtbl.ReaderOptions{VerifyChecksums: true}) 207 | if e != nil { 208 | fmt.Fprintf(os.Stderr, "Error reading %s: %s\n", path, e) 209 | continue 210 | } 211 | defer r.Destroy() 212 | // Iterate by block size 213 | for ; (end_base - cur_base + 1) >= block_size; cur_base += block_size { 214 | ip_prefix := strings.Join(strings.SplitN(inetdata.UInt2IPv4(cur_base), ".", 4)[0:ndots], ".") + "." 215 | cidrPrefixIPv4(r, ip_prefix, w) 216 | } 217 | 218 | // Handle any leftovers by looking up a full /24 and ignoring stuff outside our range 219 | ip_prefix := strings.Join(strings.SplitN(inetdata.UInt2IPv4(cur_base), ".", 4)[0:3], ".") + "." 220 | 221 | it := mtbl.IterPrefix(r, []byte(ip_prefix)) 222 | for { 223 | key_bytes, val_bytes, ok := it.Next() 224 | if !ok { 225 | break 226 | } 227 | 228 | // Only print results that are valid IPV4 addresses within our CIDR range 229 | cur_val, _ := inetdata.IPv42UInt(string(key_bytes)) 230 | if cur_val >= cur_base && cur_val <= end_base { 231 | if inetdata.MatchIPv4.Match(key_bytes) { 232 | writeOutput(key_bytes, val_bytes, w) 233 | } 234 | } 235 | } 236 | } 237 | } 238 | 239 | func main() { 240 | 241 | runtime.GOMAXPROCS(runtime.NumCPU()) 242 | os.Setenv("LC_ALL", "C") 243 | 244 | router := mux.NewRouter() 245 | router.HandleFunc("/domain/{id}", searchDomain).Methods("GET") 246 | router.HandleFunc("/ip/{ip}", searchPrefixIPv4).Methods("GET") 247 | router.HandleFunc("/ip/{ip}/{id}", searchCIDR).Methods("GET") 248 | // TODO: router.HandleFunc("/whois/{id}", searchAll).Methods("GET") 249 | log.Fatal(http.ListenAndServe(":8091", router)) 250 | } 251 | -------------------------------------------------------------------------------- /cmd/inetdata-dns2mtbl/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "os" 8 | "runtime" 9 | "strings" 10 | "sync" 11 | "sync/atomic" 12 | "time" 13 | 14 | mtbl "github.com/hdm/golang-mtbl" 15 | "github.com/hdm/inetdata-parsers" 16 | ) 17 | 18 | const MERGE_MODE_COMBINE = 0 19 | const MERGE_MODE_FIRST = 1 20 | const MERGE_MODE_LAST = 2 21 | 22 | var merge_mode = MERGE_MODE_COMBINE 23 | 24 | var compression_types = map[string]int{ 25 | "none": mtbl.COMPRESSION_NONE, 26 | "snappy": mtbl.COMPRESSION_SNAPPY, 27 | "zlib": mtbl.COMPRESSION_ZLIB, 28 | "lz4": mtbl.COMPRESSION_LZ4, 29 | "lz4hc": mtbl.COMPRESSION_LZ4HC, 30 | } 31 | 32 | var merge_count int64 = 0 33 | var input_count int64 = 0 34 | var output_count int64 = 0 35 | var invalid_count int64 = 0 36 | 37 | type NewRecord struct { 38 | Key []byte 39 | Val []byte 40 | } 41 | 42 | var wg sync.WaitGroup 43 | 44 | func usage() { 45 | fmt.Println("Usage: " + os.Args[0] + " [options] ") 46 | fmt.Println("") 47 | fmt.Println("Creates a MTBL database from a Sonar FDNS pre-sorted and pre-merged CSV input") 48 | fmt.Println("") 49 | fmt.Println("Options:") 50 | flag.PrintDefaults() 51 | } 52 | 53 | func showProgress(quit chan int) { 54 | start := time.Now() 55 | for { 56 | select { 57 | case <-quit: 58 | return 59 | case <-time.After(time.Second * 1): 60 | icount := atomic.LoadInt64(&input_count) 61 | ocount := atomic.LoadInt64(&output_count) 62 | mcount := atomic.LoadInt64(&merge_count) 63 | ecount := atomic.LoadInt64(&invalid_count) 64 | 65 | if icount == 0 && ocount == 0 { 66 | // Reset start, so that we show stats only from our first input 67 | start = time.Now() 68 | continue 69 | } 70 | elapsed := time.Since(start) 71 | if elapsed.Seconds() > 1.0 { 72 | fmt.Fprintf(os.Stderr, "[*] [inetdata-dns2mtbl] Read %d and wrote %d records in %d seconds (%d/s in, %d/s out) (merged: %d, invalid: %d)\n", 73 | icount, 74 | ocount, 75 | int(elapsed.Seconds()), 76 | int(float64(icount)/elapsed.Seconds()), 77 | int(float64(ocount)/elapsed.Seconds()), 78 | mcount, ecount) 79 | } 80 | } 81 | } 82 | } 83 | 84 | func mergeFunc(key []byte, val0 []byte, val1 []byte) (mergedVal []byte) { 85 | 86 | atomic.AddInt64(&merge_count, 1) 87 | 88 | if merge_mode == MERGE_MODE_FIRST { 89 | return val0 90 | } 91 | 92 | if merge_mode == MERGE_MODE_LAST { 93 | return val1 94 | } 95 | 96 | // MERGE_MODE_COMBINE 97 | var unique = make(map[string]bool) 98 | var v0, v1, m [][]string 99 | 100 | // fmt.Fprintf(os.Stderr, "MERGE[%v] %v -> %v\n", string(key), string(val0), string(val1)) 101 | 102 | if e := json.Unmarshal(val0, &v0); e != nil { 103 | return val1 104 | } 105 | 106 | if e := json.Unmarshal(val1, &v1); e != nil { 107 | return val0 108 | } 109 | 110 | for i := range v0 { 111 | if len(v0[i]) == 0 { 112 | continue 113 | } 114 | unique[strings.Join(v0[i], "\x00")] = true 115 | } 116 | 117 | for i := range v1 { 118 | if len(v1[i]) == 0 { 119 | continue 120 | } 121 | unique[strings.Join(v1[i], "\x00")] = true 122 | } 123 | 124 | for i := range unique { 125 | m = append(m, strings.SplitN(i, "\x00", 2)) 126 | } 127 | 128 | d, e := json.Marshal(m) 129 | if e != nil { 130 | fmt.Fprintf(os.Stderr, "JSON merge error: %v -> %v + %v\n", e, val0, val1) 131 | return val0 132 | } 133 | 134 | return d 135 | } 136 | 137 | func writeToMtbl(s *mtbl.Sorter, c chan NewRecord, d chan bool) { 138 | for r := range c { 139 | if len(r.Key) > inetdata.MTBL_KEY_LIMIT { 140 | fmt.Fprintf(os.Stderr, "[-] Failed to add key larger than %d: %s... (%d bytes)", inetdata.MTBL_KEY_LIMIT, string(r.Key[0:1024]), len(r.Key)) 141 | continue 142 | } 143 | if len(r.Val) > inetdata.MTBL_VAL_LIMIT { 144 | fmt.Fprintf(os.Stderr, "[-] Failed to add value larger than %d for key %s: %s... (%d bytes)", inetdata.MTBL_VAL_LIMIT, string(r.Key), string(r.Val[0:1024]), len(r.Val)) 145 | continue 146 | } 147 | if e := s.Add(r.Key, r.Val); e != nil { 148 | fmt.Fprintf(os.Stderr, "[-] Failed to add key=%v (%v): %v\n", r.Key, r.Val, e) 149 | continue 150 | } 151 | atomic.AddInt64(&output_count, 1) 152 | } 153 | d <- true 154 | } 155 | 156 | func inputParser(d chan string, c chan NewRecord) { 157 | 158 | for raw := range d { 159 | 160 | bits := strings.SplitN(raw, ",", 2) 161 | 162 | if len(bits) != 2 { 163 | atomic.AddInt64(&invalid_count, 1) 164 | continue 165 | } 166 | 167 | atomic.AddInt64(&input_count, 1) 168 | 169 | name := bits[0] 170 | data := bits[1] 171 | 172 | if len(name) == 0 || len(data) == 0 { 173 | atomic.AddInt64(&invalid_count, 1) 174 | continue 175 | } 176 | vals := strings.SplitN(data, "\x00", -1) 177 | 178 | var outp [][]string 179 | for i := range vals { 180 | info := strings.SplitN(vals[i], ",", 2) 181 | 182 | if len(info) == 1 { 183 | // This is a single-mapped value without a type prefix 184 | // Types: a, aaaa 185 | outp = append(outp, []string{vals[i]}) 186 | } else { 187 | // This is a pair-mapped value with a dns record type 188 | // Types: fdns, cname, ns, mx, ptr 189 | outp = append(outp, info) 190 | } 191 | } 192 | 193 | json, e := json.Marshal(outp) 194 | if e != nil { 195 | fmt.Fprintf(os.Stderr, "[-] Could not marshal %v: %s\n", outp, e) 196 | continue 197 | } 198 | 199 | // Reverse the key unless its an IP address 200 | if !(inetdata.MatchIPv4.Match([]byte(name)) || inetdata.MatchIPv6.Match([]byte(name))) { 201 | name = inetdata.ReverseKey(name) 202 | } 203 | 204 | c <- NewRecord{Key: []byte(name), Val: json} 205 | } 206 | wg.Done() 207 | } 208 | 209 | func main() { 210 | 211 | runtime.GOMAXPROCS(runtime.NumCPU()) 212 | os.Setenv("LC_ALL", "C") 213 | 214 | flag.Usage = func() { usage() } 215 | 216 | compression := flag.String("c", "snappy", "The compression type to use (none, snappy, zlib, lz4, lz4hc)") 217 | sort_tmp := flag.String("t", "", "The temporary directory to use for the sorting phase") 218 | sort_mem := flag.Uint64("m", 1024, "The maximum amount of memory to use, in megabytes, for the sorting phase, per output file") 219 | selected_merge_mode := flag.String("M", "combine", "The merge mode: combine, first, or last") 220 | version := flag.Bool("version", false, "Show the version and build timestamp") 221 | 222 | flag.Parse() 223 | 224 | if *version { 225 | inetdata.PrintVersion("inetdata-dns2mtbl") 226 | os.Exit(0) 227 | } 228 | 229 | if len(flag.Args()) != 1 { 230 | usage() 231 | os.Exit(1) 232 | } 233 | 234 | switch *selected_merge_mode { 235 | case "combine": 236 | merge_mode = MERGE_MODE_COMBINE 237 | case "first": 238 | merge_mode = MERGE_MODE_FIRST 239 | case "last": 240 | merge_mode = MERGE_MODE_LAST 241 | default: 242 | fmt.Fprintf(os.Stderr, "Error: Invalid merge mode specified: %s\n", *selected_merge_mode) 243 | usage() 244 | os.Exit(1) 245 | } 246 | 247 | fname := flag.Args()[0] 248 | _ = os.Remove(fname) 249 | 250 | sort_opt := mtbl.SorterOptions{Merge: mergeFunc, MaxMemory: 1024 * 1024} 251 | sort_opt.MaxMemory *= *sort_mem 252 | 253 | if len(*sort_tmp) > 0 { 254 | sort_opt.TempDir = *sort_tmp 255 | } 256 | 257 | compression_alg, ok := inetdata.MTBLCompressionTypes[*compression] 258 | if !ok { 259 | fmt.Fprintf(os.Stderr, "[-] Invalid compression algorithm: %s\n", *compression) 260 | os.Exit(1) 261 | } 262 | 263 | s := mtbl.SorterInit(&sort_opt) 264 | w, w_e := mtbl.WriterInit(fname, &mtbl.WriterOptions{Compression: compression_alg}) 265 | 266 | if w_e != nil { 267 | fmt.Fprintf(os.Stderr, "[-] Error: %s\n", w_e) 268 | os.Exit(1) 269 | } 270 | 271 | s_ch := make(chan NewRecord, 1000) 272 | s_done := make(chan bool, 1) 273 | 274 | go writeToMtbl(s, s_ch, s_done) 275 | 276 | p_ch := make(chan string, 1000) 277 | for i := 0; i < runtime.NumCPU(); i++ { 278 | go inputParser(p_ch, s_ch) 279 | wg.Add(1) 280 | } 281 | 282 | quit := make(chan int) 283 | go showProgress(quit) 284 | 285 | // Reader closes input on completion 286 | e := inetdata.ReadLines(os.Stdin, p_ch) 287 | if e != nil { 288 | fmt.Fprintf(os.Stderr, "Error reading input: %s\n", e) 289 | } 290 | 291 | wg.Wait() 292 | 293 | close(s_ch) 294 | <-s_done 295 | 296 | if e := s.Write(w); e != nil { 297 | fmt.Fprintf(os.Stderr, "[-] Error writing MTBL: %s\n", e) 298 | os.Exit(1) 299 | } 300 | 301 | quit <- 0 302 | 303 | s.Destroy() 304 | w.Destroy() 305 | } 306 | -------------------------------------------------------------------------------- /cmd/mq/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "flag" 7 | "fmt" 8 | "github.com/hdm/golang-mtbl" 9 | "github.com/hdm/inetdata-parsers" 10 | "io/ioutil" 11 | "math" 12 | "net" 13 | "os" 14 | "runtime" 15 | "strings" 16 | ) 17 | 18 | var key_only *bool 19 | var val_only *bool 20 | var prefix *string 21 | var rev_prefix *string 22 | var rev_key *bool 23 | var no_quotes *bool 24 | var as_json *bool 25 | var version *bool 26 | var domain *string 27 | var cidr *string 28 | 29 | func usage() { 30 | fmt.Println("Usage: " + os.Args[0] + " [options] ... ") 31 | fmt.Println("") 32 | fmt.Println("Queries one or more MTBL databases") 33 | fmt.Println("") 34 | fmt.Println("Options:") 35 | flag.PrintDefaults() 36 | } 37 | 38 | func findPaths(args []string) []string { 39 | var paths []string 40 | for i := range args { 41 | path := args[i] 42 | info, e := os.Stat(path) 43 | if e != nil { 44 | fmt.Fprintf(os.Stderr, "Error: Path %s : %v\n", path, e) 45 | os.Exit(1) 46 | } 47 | 48 | if info.Mode().IsRegular() { 49 | paths = append(paths, path) 50 | continue 51 | } 52 | 53 | if info.Mode().IsDir() { 54 | if files, e := ioutil.ReadDir(path); e == nil { 55 | for _, f := range files { 56 | if f.Mode().IsRegular() { 57 | npath := path + string(os.PathSeparator) + f.Name() 58 | paths = append(paths, npath) 59 | } 60 | } 61 | } 62 | } 63 | } 64 | return paths 65 | } 66 | 67 | func writeOutput(key_bytes []byte, val_bytes []byte) { 68 | 69 | key := string(key_bytes) 70 | val := string(val_bytes) 71 | 72 | if *rev_key { 73 | key = inetdata.ReverseKey(key) 74 | } 75 | 76 | if *as_json { 77 | o := make(map[string]interface{}) 78 | v := make([][]string, 1) 79 | 80 | if de := json.Unmarshal([]byte(val), &v); de != nil { 81 | fmt.Fprintf(os.Stderr, "Could not unmarshal %s -> %s as json: %s\n", key, val, de) 82 | return 83 | } 84 | 85 | o["key"] = string(key) 86 | o["val"] = v 87 | 88 | b, je := json.Marshal(o) 89 | if je != nil { 90 | fmt.Fprintf(os.Stderr, "Could not marshal %s -> %s as json: %s\n", key, val, je) 91 | return 92 | } 93 | fmt.Println(string(b)) 94 | 95 | } else if *key_only { 96 | fmt.Printf("%s\n", key) 97 | } else if *val_only { 98 | if *no_quotes { 99 | fmt.Printf("%s\n", val) 100 | } else { 101 | fmt.Printf("%q\n", val) 102 | } 103 | } else { 104 | if *no_quotes { 105 | fmt.Printf("%s\t%s\n", key, val) 106 | } else { 107 | fmt.Printf("%s\t%q\n", key, val) 108 | } 109 | } 110 | } 111 | 112 | func searchPrefix(r *mtbl.Reader, prefix string) { 113 | it := mtbl.IterPrefix(r, []byte(prefix)) 114 | for { 115 | key_bytes, val_bytes, ok := it.Next() 116 | if !ok { 117 | break 118 | } 119 | writeOutput(key_bytes, val_bytes) 120 | } 121 | } 122 | 123 | func searchAll(r *mtbl.Reader) { 124 | it := mtbl.IterAll(r) 125 | for { 126 | key_bytes, val_bytes, ok := it.Next() 127 | if !ok { 128 | break 129 | } 130 | writeOutput(key_bytes, val_bytes) 131 | } 132 | } 133 | 134 | func searchDomain(r *mtbl.Reader, domain string) { 135 | rdomain := []byte(inetdata.ReverseKey(domain)) 136 | 137 | // Domain searches always use reversed keys 138 | *rev_key = true 139 | 140 | dot_rdomain := append(rdomain, '.') 141 | it := mtbl.IterPrefix(r, rdomain) 142 | for { 143 | key_bytes, val_bytes, ok := it.Next() 144 | if !ok { 145 | break 146 | } 147 | 148 | if bytes.Compare(key_bytes, rdomain) == 0 || 149 | bytes.Compare(key_bytes[0:len(dot_rdomain)], dot_rdomain) == 0 { 150 | writeOutput(key_bytes, val_bytes) 151 | } 152 | } 153 | } 154 | 155 | func searchPrefixIPv4(r *mtbl.Reader, prefix string) { 156 | it := mtbl.IterPrefix(r, []byte(prefix)) 157 | for { 158 | key_bytes, val_bytes, ok := it.Next() 159 | if !ok { 160 | break 161 | } 162 | 163 | if inetdata.MatchIPv4.Match(key_bytes) { 164 | writeOutput(key_bytes, val_bytes) 165 | } 166 | } 167 | } 168 | 169 | func searchCIDR(r *mtbl.Reader, cidr string) { 170 | 171 | if len(cidr) == 0 { 172 | return 173 | } 174 | 175 | // We may receive bare IP addresses, not CIDRs sometimes 176 | if !strings.Contains(cidr, "/") { 177 | if strings.Contains(cidr, ":") { 178 | cidr = cidr + "/128" 179 | } else { 180 | cidr = cidr + "/32" 181 | } 182 | } 183 | 184 | // Parse CIDR into base address + mask 185 | ip, net, err := net.ParseCIDR(cidr) 186 | if err != nil { 187 | fmt.Fprintf(os.Stderr, "Invalid CIDR %s: %s\n", cidr, err.Error()) 188 | return 189 | } 190 | 191 | // Verify IPv4 for now 192 | ip4 := net.IP.To4() 193 | if ip4 == nil { 194 | fmt.Fprintf(os.Stderr, "Invalid IPv4 CIDR %s\n", cidr) 195 | return 196 | } 197 | 198 | net_base, err := inetdata.IPv42UInt(net.IP.String()) 199 | if err != nil { 200 | fmt.Fprintf(os.Stderr, "Invalid IPv4 Address %s: %s\n", ip.String(), err.Error()) 201 | return 202 | } 203 | 204 | mask_ones, mask_total := net.Mask.Size() 205 | 206 | // Does not work for IPv6 due to cast to uint32 207 | net_size := uint32(math.Pow(2, float64(mask_total-mask_ones))) 208 | 209 | cur_base := net_base 210 | end_base := net_base + net_size - 1 211 | 212 | var ndots uint32 = 3 213 | var block_size uint32 = 256 214 | 215 | // Handle massive network blocks 216 | if mask_ones <= 8 { 217 | ndots = 1 218 | block_size = 256 * 256 * 256 219 | } else if mask_ones <= 16 { 220 | ndots = 2 221 | block_size = 256 * 256 222 | } 223 | 224 | // Iterate by block size 225 | for ; (end_base - cur_base + 1) >= block_size; cur_base += block_size { 226 | ip_prefix := strings.Join(strings.SplitN(inetdata.UInt2IPv4(cur_base), ".", 4)[0:ndots], ".") + "." 227 | searchPrefixIPv4(r, ip_prefix) 228 | } 229 | 230 | // Handle any leftovers by looking up a full /24 and ignoring stuff outside our range 231 | ip_prefix := strings.Join(strings.SplitN(inetdata.UInt2IPv4(cur_base), ".", 4)[0:3], ".") + "." 232 | 233 | it := mtbl.IterPrefix(r, []byte(ip_prefix)) 234 | for { 235 | key_bytes, val_bytes, ok := it.Next() 236 | if !ok { 237 | break 238 | } 239 | 240 | // Only print results that are valid IPV4 addresses within our CIDR range 241 | cur_val, _ := inetdata.IPv42UInt(string(key_bytes)) 242 | if cur_val >= cur_base && cur_val <= end_base { 243 | if inetdata.MatchIPv4.Match(key_bytes) { 244 | writeOutput(key_bytes, val_bytes) 245 | } 246 | } 247 | } 248 | } 249 | 250 | func main() { 251 | 252 | runtime.GOMAXPROCS(runtime.NumCPU()) 253 | os.Setenv("LC_ALL", "C") 254 | 255 | flag.Usage = func() { usage() } 256 | 257 | key_only = flag.Bool("k", false, "Display key names only") 258 | val_only = flag.Bool("v", false, "Display values only") 259 | prefix = flag.String("p", "", "Only return keys with this prefix") 260 | rev_prefix = flag.String("r", "", "Only return keys with this prefix in reverse form") 261 | rev_key = flag.Bool("R", false, "Display matches with the key in reverse form") 262 | no_quotes = flag.Bool("n", false, "Print raw values, not quoted values") 263 | as_json = flag.Bool("j", false, "Print each record as a single line of JSON") 264 | version = flag.Bool("version", false, "Show the version and build timestamp") 265 | domain = flag.String("domain", "", "Search for all matches for a specified domain") 266 | cidr = flag.String("cidr", "", "Search for all matches for the specified CIDR") 267 | 268 | flag.Parse() 269 | 270 | if *version { 271 | inetdata.PrintVersion("mq") 272 | os.Exit(0) 273 | } 274 | 275 | if len(flag.Args()) == 0 { 276 | usage() 277 | os.Exit(1) 278 | } 279 | 280 | if *key_only && *val_only { 281 | fmt.Fprintf(os.Stderr, "Error: Only one of -k or -v can be specified\n") 282 | usage() 283 | os.Exit(1) 284 | } 285 | 286 | if len(*prefix) > 0 && len(*rev_prefix) > 0 { 287 | fmt.Fprintf(os.Stderr, "Error: Only one of -p or -r can be specified\n") 288 | usage() 289 | os.Exit(1) 290 | } 291 | 292 | if len(*domain) > 0 && (len(*prefix) > 0 || len(*rev_prefix) > 0 || len(*cidr) > 0) { 293 | fmt.Fprintf(os.Stderr, "Error: Only one of -p, -r, -domain, or -cidr can be specified\n") 294 | usage() 295 | os.Exit(1) 296 | } 297 | 298 | if len(*cidr) > 0 && (len(*prefix) > 0 || len(*rev_prefix) > 0 || len(*domain) > 0) { 299 | fmt.Fprintf(os.Stderr, "Error: Only one of -p, -r, -domain, or -cidr can be specified\n") 300 | usage() 301 | os.Exit(1) 302 | } 303 | 304 | paths := findPaths(flag.Args()) 305 | 306 | exit_code := 0 307 | 308 | for i := range paths { 309 | 310 | path := paths[i] 311 | 312 | r, e := mtbl.ReaderInit(path, &mtbl.ReaderOptions{VerifyChecksums: true}) 313 | if e != nil { 314 | fmt.Fprintf(os.Stderr, "Error reading %s: %s\n", path, e) 315 | exit_code = 1 316 | continue 317 | } 318 | 319 | defer r.Destroy() 320 | 321 | if len(*domain) > 0 { 322 | searchDomain(r, *domain) 323 | continue 324 | } 325 | 326 | if len(*cidr) > 0 { 327 | searchCIDR(r, *cidr) 328 | continue 329 | } 330 | 331 | if len(*prefix) > 0 { 332 | searchPrefix(r, *prefix) 333 | continue 334 | } 335 | 336 | if len(*rev_prefix) > 0 { 337 | p := inetdata.ReverseKey(*rev_prefix) 338 | searchPrefix(r, p) 339 | continue 340 | } 341 | 342 | searchAll(r) 343 | } 344 | 345 | os.Exit(exit_code) 346 | } 347 | -------------------------------------------------------------------------------- /cmd/inetdata-arin-org2cidrs/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "net/url" 9 | "os" 10 | "regexp" 11 | "strings" 12 | ) 13 | 14 | var IsCustomerHandle = regexp.MustCompile(`^C[A-F0-9]{8}$`) 15 | 16 | type ARIN_OrgNets struct { 17 | Nets struct { 18 | InaccuracyReportURL string `json:"@inaccuracyReportUrl"` 19 | TermsOfUse string `json:"@termsOfUse"` 20 | LimitExceeded struct { 21 | Limit string `json:"@limit"` 22 | Value string `json:"$"` 23 | } `json:"limitExceeded"` 24 | NetRef []struct { 25 | EndAddress string `json:"@endAddress"` 26 | StartAddress string `json:"@startAddress"` 27 | Handle string `json:"@handle"` 28 | Name string `json:"@name"` 29 | Value string `json:"$"` 30 | } `json:"netRef"` 31 | } `json:"nets"` 32 | } 33 | 34 | type ARIN_OrgNet struct { 35 | Nets struct { 36 | InaccuracyReportURL string `json:"@inaccuracyReportUrl"` 37 | TermsOfUse string `json:"@termsOfUse"` 38 | LimitExceeded struct { 39 | Limit string `json:"@limit"` 40 | Value string `json:"$"` 41 | } `json:"limitExceeded"` 42 | NetRef struct { 43 | EndAddress string `json:"@endAddress"` 44 | StartAddress string `json:"@startAddress"` 45 | Handle string `json:"@handle"` 46 | Name string `json:"@name"` 47 | Value string `json:"$"` 48 | } `json:"netRef"` 49 | } `json:"nets"` 50 | } 51 | 52 | type ARIN_Nets struct { 53 | Net struct { 54 | InaccuracyReportURL string `json:"@inaccuracyReportUrl"` 55 | TermsOfUse string `json:"@termsOfUse"` 56 | RegistrationDate struct { 57 | Value string `json:"$"` 58 | } `json:"registrationDate"` 59 | Ref struct { 60 | Value string `json:"$"` 61 | } `json:"ref"` 62 | EndAddress struct { 63 | Value string `json:"$"` 64 | } `json:"endAddress"` 65 | Handle struct { 66 | Value string `json:"$"` 67 | } `json:"handle"` 68 | Name struct { 69 | Value string `json:"$"` 70 | } `json:"name"` 71 | NetBlocks struct { 72 | NetBlock []struct { 73 | CidrLength struct { 74 | Value string `json:"$"` 75 | } `json:"cidrLength"` 76 | EndAddress struct { 77 | Value string `json:"$"` 78 | } `json:"endAddress"` 79 | Description struct { 80 | Value string `json:"$"` 81 | } `json:"description"` 82 | Type struct { 83 | Value string `json:"$"` 84 | } `json:"type"` 85 | StartAddress struct { 86 | Value string `json:"$"` 87 | } `json:"startAddress"` 88 | } `json:"netBlock"` 89 | } `json:"netBlocks"` 90 | Resources struct { 91 | InaccuracyReportURL string `json:"@inaccuracyReportUrl"` 92 | TermsOfUse string `json:"@termsOfUse"` 93 | LimitExceeded struct { 94 | Limit string `json:"@limit"` 95 | Value string `json:"$"` 96 | } `json:"limitExceeded"` 97 | } `json:"resources"` 98 | OrgRef struct { 99 | Handle string `json:"@handle"` 100 | Name string `json:"@name"` 101 | Value string `json:"$"` 102 | } `json:"orgRef"` 103 | ParentNetRef struct { 104 | Handle string `json:"@handle"` 105 | Name string `json:"@name"` 106 | Value string `json:"$"` 107 | } `json:"parentNetRef"` 108 | StartAddress struct { 109 | Value string `json:"$"` 110 | } `json:"startAddress"` 111 | UpdateDate struct { 112 | Value string `json:"$"` 113 | } `json:"updateDate"` 114 | Version struct { 115 | Value string `json:"$"` 116 | } `json:"version"` 117 | } `json:"net"` 118 | } 119 | 120 | type ARIN_Net struct { 121 | Net struct { 122 | InaccuracyReportURL string `json:"@inaccuracyReportUrl"` 123 | TermsOfUse string `json:"@termsOfUse"` 124 | RegistrationDate struct { 125 | Value string `json:"$"` 126 | } `json:"registrationDate"` 127 | Ref struct { 128 | Value string `json:"$"` 129 | } `json:"ref"` 130 | EndAddress struct { 131 | Value string `json:"$"` 132 | } `json:"endAddress"` 133 | Handle struct { 134 | Value string `json:"$"` 135 | } `json:"handle"` 136 | Name struct { 137 | Value string `json:"$"` 138 | } `json:"name"` 139 | NetBlocks struct { 140 | NetBlock struct { 141 | CidrLength struct { 142 | Value string `json:"$"` 143 | } `json:"cidrLength"` 144 | EndAddress struct { 145 | Value string `json:"$"` 146 | } `json:"endAddress"` 147 | Description struct { 148 | Value string `json:"$"` 149 | } `json:"description"` 150 | Type struct { 151 | Value string `json:"$"` 152 | } `json:"type"` 153 | StartAddress struct { 154 | Value string `json:"$"` 155 | } `json:"startAddress"` 156 | } `json:"netBlock"` 157 | } `json:"netBlocks"` 158 | Resources struct { 159 | InaccuracyReportURL string `json:"@inaccuracyReportUrl"` 160 | TermsOfUse string `json:"@termsOfUse"` 161 | LimitExceeded struct { 162 | Limit string `json:"@limit"` 163 | Value string `json:"$"` 164 | } `json:"limitExceeded"` 165 | } `json:"resources"` 166 | OrgRef struct { 167 | Handle string `json:"@handle"` 168 | Name string `json:"@name"` 169 | Value string `json:"$"` 170 | } `json:"orgRef"` 171 | ParentNetRef struct { 172 | Handle string `json:"@handle"` 173 | Name string `json:"@name"` 174 | Value string `json:"$"` 175 | } `json:"parentNetRef"` 176 | StartAddress struct { 177 | Value string `json:"$"` 178 | } `json:"startAddress"` 179 | UpdateDate struct { 180 | Value string `json:"$"` 181 | } `json:"updateDate"` 182 | Version struct { 183 | Value string `json:"$"` 184 | } `json:"version"` 185 | } `json:"net"` 186 | } 187 | 188 | func LookupOrgNets(org string) ([]string, error) { 189 | handles := []string{} 190 | 191 | safe_org := url.QueryEscape(org) 192 | 193 | u := "" 194 | 195 | // Organizations are split into Customers and Non-Customers, which 196 | // determines which API endpoint to use. Fortunately we can tell 197 | // which one is what based on the naming convention. 198 | if IsCustomerHandle.Match([]byte(org)) { 199 | u = fmt.Sprintf("http://whois.arin.net/rest/customer/%s/nets", safe_org) 200 | } else { 201 | u = fmt.Sprintf("http://whois.arin.net/rest/org/%s/nets", safe_org) 202 | } 203 | 204 | req, err := http.NewRequest("GET", u, nil) 205 | if err != nil { 206 | return handles, err 207 | } 208 | req.Header.Set("Accept", "application/json") 209 | 210 | client := &http.Client{} 211 | resp, err := client.Do(req) 212 | if err != nil { 213 | return handles, err 214 | } 215 | 216 | defer resp.Body.Close() 217 | 218 | content, err := ioutil.ReadAll(resp.Body) 219 | if err != nil { 220 | return handles, err 221 | } 222 | 223 | // No network handles associated with this organization 224 | if strings.Contains(string(content), "No related resources were found for the handle provided") { 225 | return handles, nil 226 | } 227 | 228 | var nets ARIN_OrgNets 229 | 230 | if err := json.Unmarshal(content, &nets); err == nil { 231 | for i := range nets.Nets.NetRef { 232 | handles = append(handles, nets.Nets.NetRef[i].Handle) 233 | } 234 | } else { 235 | // Try to decode as a single-net organization 236 | var net ARIN_OrgNet 237 | if err := json.Unmarshal(content, &net); err != nil { 238 | return handles, err 239 | } 240 | 241 | handles = append(handles, net.Nets.NetRef.Handle) 242 | } 243 | 244 | return handles, nil 245 | } 246 | 247 | func LookupNetCidrs(handle string) ([]string, error) { 248 | cidrs := []string{} 249 | 250 | safe_handle := url.QueryEscape(handle) 251 | u := fmt.Sprintf("http://whois.arin.net/rest/net/%s", safe_handle) 252 | 253 | req, err := http.NewRequest("GET", u, nil) 254 | if err != nil { 255 | return cidrs, err 256 | } 257 | req.Header.Set("Accept", "application/json") 258 | 259 | client := &http.Client{} 260 | resp, err := client.Do(req) 261 | if err != nil { 262 | return cidrs, err 263 | } 264 | 265 | defer resp.Body.Close() 266 | 267 | content, err := ioutil.ReadAll(resp.Body) 268 | if err != nil { 269 | return cidrs, err 270 | } 271 | 272 | var nets ARIN_Nets 273 | 274 | if err := json.Unmarshal(content, &nets); err == nil { 275 | for i := range nets.Net.NetBlocks.NetBlock { 276 | cidrs = append(cidrs, fmt.Sprintf("%s/%s", nets.Net.NetBlocks.NetBlock[i].StartAddress.Value, nets.Net.NetBlocks.NetBlock[i].CidrLength.Value)) 277 | } 278 | } else { 279 | // Try to decode as a single-block network 280 | var net ARIN_Net 281 | if err := json.Unmarshal(content, &net); err != nil { 282 | return cidrs, err 283 | } 284 | cidrs = append(cidrs, fmt.Sprintf("%s/%s", net.Net.NetBlocks.NetBlock.StartAddress.Value, net.Net.NetBlocks.NetBlock.CidrLength.Value)) 285 | } 286 | 287 | return cidrs, nil 288 | } 289 | 290 | func main() { 291 | 292 | if len(os.Args) != 2 { 293 | fmt.Println("Usage: inetdata-arin-org2nets ") 294 | os.Exit(1) 295 | } 296 | 297 | org := os.Args[1] 298 | 299 | handles, e := LookupOrgNets(org) 300 | if e != nil { 301 | fmt.Fprintf(os.Stderr, "Could not list network handles: %s", e.Error()) 302 | os.Exit(1) 303 | } 304 | 305 | for i := range handles { 306 | cidrs, e := LookupNetCidrs(handles[i]) 307 | if e != nil { 308 | fmt.Fprintf(os.Stderr, "Could not list CIDRs for %s: %s", handles[i], e.Error()) 309 | continue 310 | } 311 | fmt.Println(strings.Join(cidrs, "\n")) 312 | } 313 | } 314 | -------------------------------------------------------------------------------- /cmd/inetdata-zone2csv/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/hdm/inetdata-parsers" 7 | "os" 8 | "regexp" 9 | "runtime" 10 | "strings" 11 | "sync" 12 | "sync/atomic" 13 | "time" 14 | ) 15 | 16 | const ZONE_MODE_UNKNOWN = 0 17 | const ZONE_MODE_COM = 1 // Also NET, ORG, INFO, MOBI 18 | const ZONE_MODE_BIZ = 2 // Also XXX 19 | const ZONE_MODE_SK = 3 20 | const ZONE_MODE_US = 4 21 | const ZONE_MODE_CZDS = 5 22 | 23 | var zone_mode = 0 24 | var zone_name = "" 25 | var zone_matched = false 26 | 27 | var output_count int64 = 0 28 | var input_count int64 = 0 29 | var stdout_lock sync.Mutex 30 | var wg sync.WaitGroup 31 | 32 | type OutputKey struct { 33 | Key string 34 | Vals []string 35 | } 36 | 37 | type OutputChannels struct { 38 | IP4 chan string 39 | IP6 chan string 40 | InverseNames chan string 41 | Names chan string 42 | } 43 | 44 | func usage() { 45 | fmt.Println("Usage: " + os.Args[0] + " [options]") 46 | fmt.Println("") 47 | fmt.Println("Reads a zone file from stdin, generates CSV files keyed off domain names, including ") 48 | fmt.Println("forward, inverse, and glue addresses for IPv4 and IPv6.") 49 | fmt.Println("") 50 | fmt.Println("Options:") 51 | flag.PrintDefaults() 52 | } 53 | 54 | func showProgress(quit chan int) { 55 | start := time.Now() 56 | for { 57 | select { 58 | case <-quit: 59 | return 60 | case <-time.After(time.Second * 1): 61 | icount := atomic.LoadInt64(&input_count) 62 | ocount := atomic.LoadInt64(&output_count) 63 | 64 | if icount == 0 && ocount == 0 { 65 | // Reset start, so that we show stats only from our first input 66 | start = time.Now() 67 | continue 68 | } 69 | elapsed := time.Since(start) 70 | if elapsed.Seconds() > 1.0 { 71 | fmt.Fprintf(os.Stderr, "[*] [inetdata-zone2csv] Read %d and wrote %d records in %d seconds (%d/s in, %d/s out)\n", 72 | icount, 73 | ocount, 74 | int(elapsed.Seconds()), 75 | int(float64(icount)/elapsed.Seconds()), 76 | int(float64(ocount)/elapsed.Seconds())) 77 | } 78 | } 79 | } 80 | } 81 | 82 | func outputWriter(fd *os.File, c chan string) { 83 | for r := range c { 84 | fd.Write([]byte(r)) 85 | atomic.AddInt64(&output_count, 1) 86 | } 87 | wg.Done() 88 | } 89 | 90 | func writeRecord(c_names chan string, name string, rtype string, value string) { 91 | switch rtype { 92 | case "ns": 93 | c_names <- fmt.Sprintf("%s,%s,%s\n", name, rtype, value) 94 | 95 | case "a": 96 | if inetdata.MatchIPv4.Match([]byte(value)) { 97 | c_names <- fmt.Sprintf("%s,%s,%s\n", name, rtype, value) 98 | } 99 | 100 | case "aaaa": 101 | if inetdata.MatchIPv6.Match([]byte(value)) { 102 | c_names <- fmt.Sprintf("%s,%s,%s\n", name, rtype, value) 103 | } 104 | } 105 | } 106 | 107 | func normalizeName(name string) string { 108 | // Leave empty names alone 109 | if len(name) == 0 { 110 | return name 111 | } 112 | 113 | // Leave IP addresses alone 114 | if inetdata.MatchIPv4.Match([]byte(name)) || inetdata.MatchIPv6.Match([]byte(name)) { 115 | return name 116 | } 117 | 118 | if name[len(name)-1:] == "." { 119 | // Remove the trailing dot 120 | name = name[:len(name)-1] 121 | } else { 122 | // Add the domain to complete the name 123 | name = fmt.Sprintf("%s.%s", name, zone_name) 124 | } 125 | return name 126 | } 127 | 128 | func parseZoneCOM(raw string, c_names chan string) { 129 | bits := inetdata.Split_WS.Split(strings.ToLower(raw), -1) 130 | if len(bits) != 3 { 131 | return 132 | } 133 | 134 | name, rtype, value := normalizeName(bits[0]), bits[1], normalizeName(bits[2]) 135 | writeRecord(c_names, name, rtype, value) 136 | } 137 | 138 | func parseZoneBIZ(raw string, c_names chan string) { 139 | bits := inetdata.Split_WS.Split(strings.ToLower(raw), -1) 140 | if len(bits) != 5 { 141 | return 142 | } 143 | 144 | name, rtype, value := normalizeName(bits[0]), bits[3], normalizeName(bits[4]) 145 | writeRecord(c_names, name, rtype, value) 146 | } 147 | 148 | func parseZoneUS(raw string, c_names chan string) { 149 | bits := inetdata.Split_WS.Split(strings.ToLower(raw), -1) 150 | if len(bits) != 4 { 151 | return 152 | } 153 | 154 | name, rtype, value := normalizeName(bits[0]), bits[2], normalizeName(bits[3]) 155 | writeRecord(c_names, name, rtype, value) 156 | } 157 | 158 | func parseZoneSK(raw string, c_names chan string) { 159 | bits := strings.SplitN(strings.ToLower(raw), ";", -1) 160 | if len(bits) < 5 { 161 | return 162 | } 163 | 164 | name := normalizeName(bits[0]) 165 | if len(name) == 0 { 166 | return 167 | } 168 | 169 | ns1, ns2, ns3, ns4 := normalizeName(bits[5]), normalizeName(bits[6]), normalizeName(bits[7]), normalizeName(bits[8]) 170 | 171 | if len(ns1) > 0 { 172 | writeRecord(c_names, name, "ns", ns1) 173 | } 174 | 175 | if len(ns2) > 0 { 176 | writeRecord(c_names, name, "ns", ns2) 177 | } 178 | 179 | if len(ns3) > 0 { 180 | writeRecord(c_names, name, "ns", ns3) 181 | } 182 | 183 | if len(ns4) > 0 { 184 | writeRecord(c_names, name, "ns", ns4) 185 | } 186 | } 187 | 188 | func parseZoneCZDS(raw string, c_names chan string) { 189 | bits := inetdata.Split_WS.Split(strings.ToLower(raw), -1) 190 | if len(bits) != 5 { 191 | return 192 | } 193 | 194 | name, rtype, value := normalizeName(bits[0]), bits[3], normalizeName(bits[4]) 195 | writeRecord(c_names, name, rtype, value) 196 | } 197 | 198 | func inputParser(c chan string, c_names chan string) { 199 | 200 | lines_read := 0 201 | for r := range c { 202 | 203 | raw := strings.TrimSpace(r) 204 | 205 | if len(raw) == 0 { 206 | continue 207 | } 208 | 209 | atomic.AddInt64(&input_count, 1) 210 | 211 | if zone_mode != ZONE_MODE_UNKNOWN && zone_matched == false { 212 | zone_matched = true 213 | 214 | // Spawn more parsers 215 | for i := 0; i < runtime.NumCPU()-1; i++ { 216 | go inputParser(c, c_names) 217 | wg.Add(1) 218 | } 219 | } 220 | 221 | switch zone_mode { 222 | case ZONE_MODE_UNKNOWN: 223 | 224 | // Versign Zone Format 225 | if strings.Contains(raw, "$ORIGIN COM.") { 226 | zone_mode = ZONE_MODE_COM 227 | zone_name = "com" 228 | continue 229 | } 230 | 231 | if strings.Contains(raw, "$ORIGIN INFO.") { 232 | zone_mode = ZONE_MODE_COM 233 | zone_name = "info" 234 | continue 235 | } 236 | 237 | if strings.Contains(raw, "$ORIGIN MOBI.") { 238 | zone_mode = ZONE_MODE_COM 239 | zone_name = "mobi" 240 | continue 241 | } 242 | 243 | if strings.Contains(raw, "$ORIGIN NET.") { 244 | zone_mode = ZONE_MODE_COM 245 | zone_name = "net" 246 | continue 247 | } 248 | 249 | if strings.Contains(raw, "$ORIGIN org.") { 250 | zone_mode = ZONE_MODE_COM 251 | zone_name = "org" 252 | continue 253 | } 254 | 255 | // US zone format 256 | if strings.Contains(raw, "US. IN SOA A.CCTLD.US HOSTMASTER.NEUSTAR.US") { 257 | zone_mode = ZONE_MODE_US 258 | zone_name = "us" 259 | continue 260 | } 261 | 262 | // BIZ/XXX zone format 263 | if strings.Contains(raw, "BIZ. 900 IN SOA A.GTLD.BIZ.") { 264 | zone_mode = ZONE_MODE_BIZ 265 | zone_name = "biz" 266 | continue 267 | } 268 | 269 | if strings.Contains(raw, "xxx. 86400 in soa a0.xxx.afilias-nst.info.") { 270 | zone_mode = ZONE_MODE_BIZ 271 | zone_name = "xxx" 272 | continue 273 | } 274 | 275 | // SK static zone 276 | if strings.Contains(raw, "domena;ID reg;ID drzitela;NEW") { 277 | zone_mode = ZONE_MODE_SK 278 | zone_name = "sk" 279 | continue 280 | } 281 | 282 | // CZDS 283 | if matched, _ := regexp.Match(`^[a-zA-Z0-9\-]+\.\s+\d+\s+in\s+soa\s+`, []byte(raw)); matched { 284 | zone_mode = ZONE_MODE_CZDS 285 | zone_name = "" 286 | continue 287 | } 288 | 289 | lines_read++ 290 | 291 | if lines_read > 100 { 292 | fmt.Fprintf(os.Stderr, "[-] Could not determine zone format, giving up: %s\n", raw) 293 | os.Exit(1) 294 | } 295 | 296 | case ZONE_MODE_COM: 297 | parseZoneCOM(raw, c_names) 298 | 299 | case ZONE_MODE_BIZ: 300 | parseZoneBIZ(raw, c_names) 301 | 302 | case ZONE_MODE_SK: 303 | parseZoneSK(raw, c_names) 304 | 305 | case ZONE_MODE_US: 306 | parseZoneUS(raw, c_names) 307 | 308 | case ZONE_MODE_CZDS: 309 | parseZoneCZDS(raw, c_names) 310 | 311 | default: 312 | panic("Unknown zone mode") 313 | } 314 | } 315 | 316 | wg.Done() 317 | } 318 | 319 | func main() { 320 | 321 | runtime.GOMAXPROCS(runtime.NumCPU()) 322 | os.Setenv("LC_ALL", "C") 323 | 324 | flag.Usage = func() { usage() } 325 | version := flag.Bool("version", false, "Show the version and build timestamp") 326 | 327 | flag.Parse() 328 | 329 | if *version { 330 | inetdata.PrintVersion("inetdata-zone2csv") 331 | os.Exit(0) 332 | } 333 | 334 | // Progress tracker 335 | quit := make(chan int) 336 | go showProgress(quit) 337 | 338 | // Write output 339 | c_names := make(chan string, 1000) 340 | go outputWriter(os.Stdout, c_names) 341 | 342 | // Read input 343 | c_inp := make(chan string, 1000) 344 | go inputParser(c_inp, c_names) 345 | wg.Add(1) 346 | 347 | // Reader closers c_inp on completion 348 | e := inetdata.ReadLines(os.Stdin, c_inp) 349 | if e != nil { 350 | fmt.Fprintf(os.Stderr, "Error reading input: %s\n", e) 351 | } 352 | 353 | // Wait for the input parser to finish 354 | wg.Wait() 355 | 356 | // Close the output channel 357 | close(c_names) 358 | 359 | // Wait for the channel writers to finish 360 | wg.Add(1) 361 | wg.Wait() 362 | 363 | // Stop the main process monitoring 364 | quit <- 0 365 | } 366 | -------------------------------------------------------------------------------- /cmd/inetdata-csvsplit/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/hdm/inetdata-parsers" 7 | "io" 8 | "os" 9 | "os/exec" 10 | "runtime" 11 | "strings" 12 | "sync" 13 | "sync/atomic" 14 | "time" 15 | ) 16 | 17 | var output_count int64 = 0 18 | var input_count int64 = 0 19 | var stdout_lock sync.Mutex 20 | var wg1 sync.WaitGroup 21 | var wg2 sync.WaitGroup 22 | 23 | type OutputKey struct { 24 | Key string 25 | Vals []string 26 | } 27 | 28 | func usage() { 29 | fmt.Println("Usage: " + os.Args[0] + " [options]") 30 | fmt.Println("") 31 | fmt.Println("Reads an unsorted DNS CSV from stdin, writes out sorted and merged normal and inverse CSVs.") 32 | fmt.Println("") 33 | fmt.Println("Options:") 34 | flag.PrintDefaults() 35 | } 36 | 37 | func showProgress(quit chan int) { 38 | start := time.Now() 39 | for { 40 | select { 41 | case <-quit: 42 | return 43 | case <-time.After(time.Second * 1): 44 | icount := atomic.LoadInt64(&input_count) 45 | ocount := atomic.LoadInt64(&output_count) 46 | 47 | if icount == 0 && ocount == 0 { 48 | // Reset start, so that we show stats only from our first input 49 | start = time.Now() 50 | continue 51 | } 52 | elapsed := time.Since(start) 53 | if elapsed.Seconds() > 1.0 { 54 | fmt.Fprintf(os.Stderr, "[*] [inetdata-csvsplit] Read %d and wrote %d records in %d seconds (%d/s in, %d/s out)\n", 55 | icount, 56 | ocount, 57 | int(elapsed.Seconds()), 58 | int(float64(icount)/elapsed.Seconds()), 59 | int(float64(ocount)/elapsed.Seconds())) 60 | } 61 | } 62 | } 63 | } 64 | 65 | func outputWriter(fd io.WriteCloser, c chan string) { 66 | for r := range c { 67 | fd.Write([]byte(r)) 68 | atomic.AddInt64(&output_count, 1) 69 | } 70 | wg1.Done() 71 | } 72 | 73 | func inputParser(c chan string, c_names chan string, c_inverse chan string) { 74 | 75 | for r := range c { 76 | 77 | raw := strings.TrimSpace(r) 78 | 79 | if len(raw) == 0 { 80 | continue 81 | } 82 | 83 | var name, rtype, value string 84 | 85 | bits := strings.SplitN(raw, ",", 3) 86 | 87 | if len(bits) < 2 || len(bits[0]) == 0 { 88 | fmt.Fprintf(os.Stderr, "[-] Invalid line: %q\n", raw) 89 | continue 90 | } 91 | 92 | // Tons of records with a blank (".") DNS response, just ignore 93 | if len(bits[1]) == 0 { 94 | continue 95 | } 96 | 97 | name = bits[0] 98 | 99 | if len(bits) == 3 { 100 | // FDNS data with three fields 101 | rtype = bits[1] 102 | value = bits[2] 103 | } 104 | 105 | if len(bits) == 2 { 106 | // RDNS data with two fields 107 | value = bits[1] 108 | 109 | // Determine the field type based on pattern 110 | if inetdata.MatchIPv4.Match([]byte(name)) { 111 | rtype = "a" 112 | } else if inetdata.MatchIPv6.Match([]byte(name)) { 113 | rtype = "aaaa" 114 | } else { 115 | fmt.Fprintf(os.Stderr, "[-] Unknown two-field format: %s\n", raw) 116 | continue 117 | } 118 | } 119 | 120 | // Skip any record that refers to itself 121 | if value == name { 122 | continue 123 | } 124 | 125 | // Skip any record with an empty value 126 | if len(value) == 0 { 127 | continue 128 | } 129 | 130 | atomic.AddInt64(&input_count, 1) 131 | 132 | switch rtype { 133 | case "a": 134 | // Skip invalid IPv4 records (TODO: verify logic) 135 | if !(inetdata.MatchIPv4.Match([]byte(value)) || inetdata.MatchIPv4.Match([]byte(name))) { 136 | continue 137 | } 138 | c_names <- fmt.Sprintf("%s,%s,%s\n", name, rtype, value) 139 | c_inverse <- fmt.Sprintf("%s,r-%s,%s\n", value, rtype, name) 140 | 141 | case "aaaa": 142 | // Skip invalid IPv6 records (TODO: verify logic) 143 | if !(inetdata.MatchIPv6.Match([]byte(value)) || inetdata.MatchIPv6.Match([]byte(name))) { 144 | continue 145 | } 146 | c_names <- fmt.Sprintf("%s,%s,%s\n", name, rtype, value) 147 | c_inverse <- fmt.Sprintf("%s,r-%s,%s\n", value, rtype, name) 148 | 149 | case "cname", "ns", "ptr": 150 | c_names <- fmt.Sprintf("%s,%s,%s\n", name, rtype, value) 151 | c_inverse <- fmt.Sprintf("%s,r-%s,%s\n", value, rtype, name) 152 | 153 | case "mx": 154 | parts := strings.SplitN(value, " ", 2) 155 | if len(parts) != 2 || len(parts[1]) == 0 { 156 | continue 157 | } 158 | c_names <- fmt.Sprintf("%s,%s,%s\n", name, rtype, parts[1]) 159 | c_inverse <- fmt.Sprintf("%s,r-%s,%s\n", parts[1], rtype, name) 160 | 161 | default: 162 | // No inverse output for other record types (TXT, DNSSEC, etc) 163 | c_names <- fmt.Sprintf("%s,%s,%s\n", name, rtype, value) 164 | } 165 | } 166 | wg2.Done() 167 | } 168 | 169 | func main() { 170 | 171 | runtime.GOMAXPROCS(runtime.NumCPU()) 172 | os.Setenv("LC_ALL", "C") 173 | 174 | flag.Usage = func() { usage() } 175 | sort_tmp := flag.String("t", "", "The temporary directory to use for the sorting phase") 176 | sort_mem := flag.Uint64("m", 1, "The maximum amount of memory to use, in gigabytes, for each of the six sort processes") 177 | version := flag.Bool("version", false, "Show the version and build timestamp") 178 | 179 | flag.Parse() 180 | 181 | if *version { 182 | inetdata.PrintVersion("inetdata-csvsplit") 183 | os.Exit(0) 184 | } 185 | 186 | if len(flag.Args()) != 1 { 187 | flag.Usage() 188 | os.Exit(1) 189 | } 190 | 191 | if len(*sort_tmp) == 0 { 192 | *sort_tmp = os.Getenv("HOME") 193 | } 194 | 195 | if len(*sort_tmp) == 0 { 196 | flag.Usage() 197 | os.Exit(1) 198 | } 199 | 200 | // Output files 201 | base := flag.Args()[0] 202 | out_fds := []*os.File{} 203 | 204 | suffix := []string{"-names.gz", "-names-inverse.gz"} 205 | for i := range suffix { 206 | fd, e := os.Create(base + suffix[i]) 207 | if e != nil { 208 | fmt.Fprintf(os.Stderr, "Error: failed to create %s: %s\n", base+suffix[i], e) 209 | os.Exit(1) 210 | } 211 | out_fds = append(out_fds, fd) 212 | defer fd.Close() 213 | } 214 | 215 | // Sort and compression pipes 216 | sort_input := [2]io.WriteCloser{} 217 | subprocs := []*exec.Cmd{} 218 | 219 | for i := range out_fds { 220 | 221 | // Create a sort process 222 | sort_proc := exec.Command("nice", 223 | "sort", 224 | "-u", 225 | "--key=1", 226 | "--field-separator=,", 227 | "--compress-program=pigz", 228 | fmt.Sprintf("--parallel=%d", runtime.NumCPU()), 229 | fmt.Sprintf("--temporary-directory=%s", *sort_tmp), 230 | fmt.Sprintf("--buffer-size=%dG", *sort_mem)) 231 | 232 | // Configure stdio 233 | sort_stdin, sie := sort_proc.StdinPipe() 234 | if sie != nil { 235 | fmt.Fprintf(os.Stderr, "Error: failed to create sort stdin pipe: %s\n", sie) 236 | os.Exit(1) 237 | } 238 | 239 | sort_stdout, soe := sort_proc.StdoutPipe() 240 | if soe != nil { 241 | fmt.Fprintf(os.Stderr, "Error: failed to create sort stdout pipe: %s\n", soe) 242 | os.Exit(1) 243 | } 244 | 245 | sort_proc.Stderr = os.Stderr 246 | sort_input[i] = sort_stdin 247 | subprocs = append(subprocs, sort_proc) 248 | 249 | // Start the sort process 250 | if e := sort_proc.Start(); e != nil { 251 | fmt.Fprintf(os.Stderr, "Error: failed to execute the sort command: %s\n", e) 252 | os.Exit(1) 253 | } 254 | 255 | // Create the inetdata-csvrollup process 256 | roll_proc := exec.Command("nice", "inetdata-csvrollup") 257 | 258 | // Configure stdio 259 | roll_stdout, roe := roll_proc.StdoutPipe() 260 | if roe != nil { 261 | fmt.Fprintf(os.Stderr, "Error: failed to create sort stdout pipe: %s\n", roe) 262 | os.Exit(1) 263 | } 264 | 265 | roll_proc.Stderr = os.Stderr 266 | roll_proc.Stdin = sort_stdout 267 | subprocs = append(subprocs, roll_proc) 268 | 269 | // Start the rollup process 270 | if e := roll_proc.Start(); e != nil { 271 | fmt.Fprintf(os.Stderr, "Error: failed to execute the inetdata-csvrollup command: %s\n", e) 272 | os.Exit(1) 273 | } 274 | 275 | // Create a second sort process 276 | sort2_proc := exec.Command("nice", 277 | "sort", 278 | "-u", 279 | "--key=1", 280 | "--field-separator=,", 281 | "--compress-program=pigz", 282 | fmt.Sprintf("--parallel=%d", runtime.NumCPU()), 283 | fmt.Sprintf("--temporary-directory=%s", *sort_tmp), 284 | fmt.Sprintf("--buffer-size=%dG", *sort_mem)) 285 | 286 | sort2_stdout, ssoe := sort2_proc.StdoutPipe() 287 | if ssoe != nil { 288 | fmt.Fprintf(os.Stderr, "Error: failed to create sort stdout pipe: %s\n", ssoe) 289 | os.Exit(1) 290 | } 291 | sort2_proc.Stdin = roll_stdout 292 | sort2_proc.Stderr = os.Stderr 293 | 294 | subprocs = append(subprocs, sort2_proc) 295 | 296 | // Start the sort process 297 | if e := sort2_proc.Start(); e != nil { 298 | fmt.Fprintf(os.Stderr, "Error: failed to execute the second sort command: %s\n", e) 299 | os.Exit(1) 300 | } 301 | 302 | // Create a pigz compressor process 303 | pigz_proc := exec.Command("nice", "pigz", "-c") 304 | 305 | // Configure stdio 306 | pigz_proc.Stderr = os.Stderr 307 | 308 | // Feed output file with pigz output 309 | pigz_proc.Stdout = out_fds[i] 310 | 311 | // Feed pigz with sort output 312 | pigz_proc.Stdin = sort2_stdout 313 | 314 | // Start the pigz process 315 | e := pigz_proc.Start() 316 | if e != nil { 317 | fmt.Fprintf(os.Stderr, "Error: failed to execute the pigz command: %s\n", e) 318 | os.Exit(1) 319 | } 320 | 321 | subprocs = append(subprocs, pigz_proc) 322 | } 323 | 324 | c_names := make(chan string, 1000) 325 | c_inverse := make(chan string, 1000) 326 | 327 | go outputWriter(sort_input[0], c_names) 328 | go outputWriter(sort_input[1], c_inverse) 329 | wg1.Add(2) 330 | 331 | // Progress tracker 332 | quit := make(chan int) 333 | go showProgress(quit) 334 | 335 | // Parse stdin 336 | c_inp := make(chan string, 1000) 337 | go inputParser(c_inp, c_names, c_inverse) 338 | go inputParser(c_inp, c_names, c_inverse) 339 | wg2.Add(2) 340 | 341 | // Reader closes c_inp on completion 342 | e := inetdata.ReadLines(os.Stdin, c_inp) 343 | if e != nil { 344 | fmt.Fprintf(os.Stderr, "Error reading input: %s\n", e) 345 | } 346 | 347 | // Wait for the input parsers to finish 348 | wg2.Wait() 349 | 350 | close(c_names) 351 | close(c_inverse) 352 | 353 | // Wait for the channel writers to finish 354 | wg1.Wait() 355 | 356 | for i := range sort_input { 357 | sort_input[i].Close() 358 | } 359 | 360 | // Stop the main process monitoring, since stats are now static 361 | quit <- 0 362 | 363 | // Wait for the downstream processes to complete 364 | for i := range subprocs { 365 | subprocs[i].Wait() 366 | } 367 | 368 | for i := range out_fds { 369 | out_fds[i].Close() 370 | } 371 | } 372 | -------------------------------------------------------------------------------- /cmd/inetdata-arin-xml2json/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Convert ARIN Bulk XML into JSONL output 4 | 5 | import ( 6 | "encoding/json" 7 | "encoding/xml" 8 | "flag" 9 | "fmt" 10 | "os" 11 | ) 12 | 13 | // ARIN POC Record 14 | 15 | type ARIN_POC_emails struct { 16 | Email string `xml:"email,omitempty" json:"email,omitempty"` 17 | } 18 | 19 | type ARIN_POC_iso3166_1 struct { 20 | Code2 string `xml:"code2,omitempty" json:"code2,omitempty"` 21 | Code3 string `xml:"code3,omitempty" json:"code3,omitempty"` 22 | E164 string `xml:"e164,omitempty" json:"e164,omitempty"` 23 | Name string `xml:"name,omitempty" json:"name,omitempty"` 24 | } 25 | 26 | type ARIN_POC_line struct { 27 | Number string `xml:"number,attr" json:",omitempty"` 28 | Text string `xml:",chardata" json:",omitempty"` 29 | } 30 | 31 | type ARIN_POC_name struct { 32 | Text string `xml:",chardata" json:",omitempty"` 33 | } 34 | 35 | type ARIN_POC_number struct { 36 | PhoneNumber string `xml:"phoneNumber,omitempty" json:"phoneNumber,omitempty"` 37 | PhoneType string `xml:"phoneType,omitempty" json:"phoneType,omitempty"` 38 | PocHandle string `xml:"pocHandle,omitempty" json:"pocHandle,omitempty"` 39 | } 40 | 41 | type ARIN_POC_phone struct { 42 | Number *ARIN_POC_number `xml:"number,omitempty" json:"number,omitempty"` 43 | Type *ARIN_POC_type `xml:"type,omitempty" json:"type,omitempty"` 44 | } 45 | 46 | type ARIN_POC_phones struct { 47 | Phone *ARIN_POC_phone `xml:"phone,omitempty" json:"phone,omitempty"` 48 | } 49 | 50 | type ARIN_POC_poc struct { 51 | ARIN_Type string `xml:"arin,omitempty" json:"arin,omitempty"` 52 | City string `xml:"city,omitempty" json:"city,omitempty"` 53 | Emails *ARIN_POC_emails `xml:"emails,omitempty" json:"emails,omitempty"` 54 | FirstName string `xml:"firstName,omitempty" json:"firstName,omitempty"` 55 | Handle string `xml:"handle,omitempty" json:"handle,omitempty"` 56 | IsRoleAccount string `xml:"isRoleAccount,omitempty" json:"isRoleAccount,omitempty"` 57 | Iso3166_1 *ARIN_POC_iso3166_1 `xml:"iso3166-1,omitempty" json:"iso3166-1,omitempty"` 58 | Iso3166_2 string `xml:"iso3166-2,omitempty" json:"iso3166-2,omitempty"` 59 | LastName string `xml:"lastName,omitempty" json:"lastName,omitempty"` 60 | Phones *ARIN_POC_phones `xml:"phones,omitempty" json:"phones,omitempty"` 61 | PostalCode string `xml:"postalCode,omitempty" json:"postalCode,omitempty"` 62 | Ref string `xml:"ref,omitempty" json:"ref,omitempty"` 63 | RegistrationDate string `xml:"registrationDate,omitempty" json:"registrationDate,omitempty"` 64 | StreetAddress *ARIN_POC_streetAddress `xml:"streetAddress,omitempty" json:"streetAddress,omitempty"` 65 | UpdateDate *string `xml:"updateDate,omitempty" json:"updateDate,omitempty"` 66 | } 67 | 68 | type ARIN_POC_streetAddress struct { 69 | Line []*ARIN_POC_line `xml:"line,omitempty" json:"line,omitempty"` 70 | } 71 | 72 | type ARIN_POC_type struct { 73 | Code string `xml:"code,omitempty" json:"code,omitempty"` 74 | Description string `xml:"description,omitempty" json:"description,omitempty"` 75 | } 76 | 77 | // ARIN Organization Record 78 | 79 | type ARIN_ORG_iso3166_1 struct { 80 | Code2 string `xml:"code2,omitempty" json:"code2,omitempty"` 81 | Code3 string `xml:"code3,omitempty" json:"code3,omitempty"` 82 | E164 string `xml:"e164,omitempty" json:"e164,omitempty"` 83 | Name string `xml:"name,omitempty" json:"name,omitempty"` 84 | } 85 | 86 | type ARIN_ORG_line struct { 87 | Number string `xml:"number,attr" json:",omitempty"` 88 | Text string `xml:",chardata" json:",omitempty"` 89 | } 90 | 91 | type ARIN_ORG_org struct { 92 | ARIN_Type string `xml:"arin,omitempty" json:"arin,omitempty"` 93 | City string `xml:"city,omitempty" json:"city,omitempty"` 94 | Customer string `xml:"customer,omitempty" json:"customer,omitempty"` 95 | Handle string `xml:"handle,omitempty" json:"handle,omitempty"` 96 | Iso3166_1 *ARIN_ORG_iso3166_1 `xml:"iso3166-1,omitempty" json:"iso3166-1,omitempty"` 97 | Iso3166_2 string `xml:"iso3166-2,omitempty" json:"iso3166-2,omitempty"` 98 | Name string `xml:"name,omitempty" json:"name,omitempty"` 99 | PocLinks *ARIN_ORG_pocLinks `xml:"pocLinks,omitempty" json:"pocLinks,omitempty"` 100 | PostalCode string `xml:"postalCode,omitempty" json:"postalCode,omitempty"` 101 | Ref string `xml:"ref,omitempty" json:"ref,omitempty"` 102 | RegistrationDate string `xml:"registrationDate,omitempty" json:"registrationDate,omitempty"` 103 | StreetAddress *ARIN_ORG_streetAddress `xml:"streetAddress,omitempty" json:"streetAddress,omitempty"` 104 | UpdateDate string `xml:"updateDate,omitempty" json:"updateDate,omitempty"` 105 | } 106 | 107 | type ARIN_ORG_pocLink struct { 108 | Description string `xml:"description,attr" json:",omitempty"` 109 | Function string `xml:"function,attr" json:",omitempty"` 110 | Handle string `xml:"handle,attr" json:",omitempty"` 111 | } 112 | 113 | type ARIN_ORG_pocLinks struct { 114 | PocLink []*ARIN_ORG_pocLink `xml:"pocLink,omitempty" json:"pocLink,omitempty"` 115 | } 116 | 117 | type ARIN_ORG_streetAddress struct { 118 | Line []*ARIN_ORG_line `xml:"line,omitempty" json:"line,omitempty"` 119 | } 120 | 121 | // ARIN Network Record 122 | 123 | type ARIN_NET_net struct { 124 | ARIN_Type string `xml:"arin,omitempty" json:"arin,omitempty"` 125 | EndAddress string `xml:"endAddress,omitempty" json:"endAddress,omitempty"` 126 | Handle string `xml:"handle,omitempty" json:"handle,omitempty"` 127 | Name string `xml:"name,omitempty" json:"name,omitempty"` 128 | NetBlocks *ARIN_NET_netBlocks `xml:"netBlocks,omitempty" json:"netBlocks,omitempty"` 129 | OrgHandle string `xml:"orgHandle,omitempty" json:"orgHandle,omitempty"` 130 | ParentNetHandle string `xml:"parentNetHandle,omitempty" json:"parentNetHandle,omitempty"` 131 | PocLinks *ARIN_NET_pocLinks `xml:"pocLinks,omitempty" json:"pocLinks,omitempty"` 132 | Ref string `xml:"ref,omitempty" json:"ref,omitempty"` 133 | RegistrationDate string `xml:"registrationDate,omitempty" json:"registrationDate,omitempty"` 134 | StartAddress string `xml:"startAddress,omitempty" json:"startAddress,omitempty"` 135 | UpdateDate string `xml:"updateDate,omitempty" json:"updateDate,omitempty"` 136 | Version string `xml:"version,omitempty" json:"version,omitempty"` 137 | } 138 | 139 | type ARIN_NET_netBlock struct { 140 | CidrLength string `xml:"cidrLength,omitempty" json:"cidrLength,omitempty"` 141 | EndAddress string `xml:"endAddress,omitempty" json:"endAddress,omitempty"` 142 | StartAddress string `xml:"startAddress,omitempty" json:"startAddress,omitempty"` 143 | Type string `xml:"type,omitempty" json:"type,omitempty"` 144 | } 145 | 146 | type ARIN_NET_netBlocks struct { 147 | NetBlock *ARIN_NET_netBlock `xml:"netBlock,omitempty" json:"netBlock,omitempty"` 148 | } 149 | 150 | type ARIN_NET_pocLinks struct { 151 | } 152 | 153 | // ARIN ASN Record 154 | 155 | type ARIN_ASN_asn struct { 156 | ARIN_Type string `xml:"arin,omitempty" json:"arin,omitempty"` 157 | ARIN_ASN_comment *ARIN_ASN_comment `xml:"comment,omitempty" json:"comment,omitempty"` 158 | EndAsNumber string `xml:"endAsNumber,omitempty" json:"endAsNumber,omitempty"` 159 | Handle string `xml:"handle,omitempty" json:"handle,omitempty"` 160 | Name string `xml:"name,omitempty" json:"name,omitempty"` 161 | OrgHandle string `xml:"orgHandle,omitempty" json:"orgHandle,omitempty"` 162 | PocLinks *ARIN_ASN_pocLinks `xml:"pocLinks,omitempty" json:"pocLinks,omitempty"` 163 | Ref string `xml:"ref,omitempty" json:"ref,omitempty"` 164 | RegistrationDate string `xml:"registrationDate,omitempty" json:"registrationDate,omitempty"` 165 | StartAsNumber string `xml:"startAsNumber,omitempty" json:"startAsNumber,omitempty"` 166 | UpdateDate string `xml:"updateDate,omitempty" json:"updateDate,omitempty"` 167 | } 168 | 169 | type ARIN_ASN_comment struct { 170 | Line []*ARIN_ASN_line `xml:"line,omitempty" json:"line,omitempty"` 171 | } 172 | 173 | type ARIN_ASN_line struct { 174 | Number string `xml:"number,attr" json:",omitempty"` 175 | Text string `xml:",chardata" json:",omitempty"` 176 | } 177 | 178 | type ARIN_ASN_pocLink struct { 179 | Description string `xml:"description,attr" json:",omitempty"` 180 | Function string `xml:"function,attr" json:",omitempty"` 181 | Handle string `xml:"handle,attr" json:",omitempty"` 182 | } 183 | 184 | type ARIN_ASN_pocLinks struct { 185 | PocLink []*ARIN_ASN_pocLink `xml:"pocLink,omitempty" json:"pocLink,omitempty"` 186 | } 187 | 188 | func processRecord(decoder *xml.Decoder, el *xml.StartElement, rtype string, value interface{}) { 189 | decoder.DecodeElement(&value, el) 190 | if value == nil { 191 | fmt.Fprintf(os.Stderr, "Could not decode record type %s\n", rtype) 192 | return 193 | } 194 | 195 | b, e := json.Marshal(value) 196 | if e != nil { 197 | fmt.Fprintf(os.Stderr, "Could not marshal type: %s\n", e.Error()) 198 | return 199 | } 200 | fmt.Println(string(b)) 201 | } 202 | 203 | func processFile(name string) { 204 | xmlFile, err := os.Open(name) 205 | if err != nil { 206 | fmt.Fprintf(os.Stderr, "Could not open file: %s\n", err.Error()) 207 | return 208 | } 209 | defer xmlFile.Close() 210 | 211 | decoder := xml.NewDecoder(xmlFile) 212 | var inElement string 213 | for { 214 | t, _ := decoder.Token() 215 | if t == nil { 216 | break 217 | } 218 | switch se := t.(type) { 219 | case xml.StartElement: 220 | inElement = se.Name.Local 221 | switch inElement { 222 | case "poc": 223 | processRecord(decoder, &se, inElement, &ARIN_POC_poc{}) 224 | case "net": 225 | processRecord(decoder, &se, inElement, &ARIN_NET_net{}) 226 | case "org": 227 | processRecord(decoder, &se, inElement, &ARIN_ORG_org{}) 228 | case "asn": 229 | processRecord(decoder, &se, inElement, &ARIN_ASN_asn{}) 230 | } 231 | default: 232 | } 233 | } 234 | } 235 | 236 | func main() { 237 | flag.Parse() 238 | for i := range flag.Args() { 239 | processFile(flag.Args()[i]) 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /cmd/inetdata-sonardnsv2-split/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "io" 8 | "os" 9 | "os/exec" 10 | "runtime" 11 | "strings" 12 | "sync" 13 | "sync/atomic" 14 | "time" 15 | 16 | "github.com/hdm/inetdata-parsers" 17 | ) 18 | 19 | var output_count int64 = 0 20 | var input_count int64 = 0 21 | var stdout_lock sync.Mutex 22 | var wg1 sync.WaitGroup 23 | var wg2 sync.WaitGroup 24 | 25 | type OutputKey struct { 26 | Key string 27 | Vals []string 28 | } 29 | 30 | type DNSRecord struct { 31 | Timestamp string `json:"timestamp"` 32 | Name string `json:"name"` 33 | Type string `json:"type"` 34 | Value string `json:"value"` 35 | } 36 | 37 | func usage() { 38 | fmt.Println("Usage: " + os.Args[0] + " [options]") 39 | fmt.Println("") 40 | fmt.Println("Reads an unsorted Sonar v2 FDNS/RDNS JSONL from stdin, writes out sorted and merged normal and inverse CSVs.") 41 | fmt.Println("") 42 | fmt.Println("Options:") 43 | flag.PrintDefaults() 44 | } 45 | 46 | func showProgress(quit chan int) { 47 | start := time.Now() 48 | for { 49 | select { 50 | case <-quit: 51 | return 52 | case <-time.After(time.Second * 1): 53 | icount := atomic.LoadInt64(&input_count) 54 | ocount := atomic.LoadInt64(&output_count) 55 | 56 | if icount == 0 && ocount == 0 { 57 | // Reset start, so that we show stats only from our first input 58 | start = time.Now() 59 | continue 60 | } 61 | elapsed := time.Since(start) 62 | if elapsed.Seconds() > 1.0 { 63 | fmt.Fprintf(os.Stderr, "[*] [inetdata-sonardnsv2-split] Read %d and wrote %d records in %d seconds (%d/s in, %d/s out)\n", 64 | icount, 65 | ocount, 66 | int(elapsed.Seconds()), 67 | int(float64(icount)/elapsed.Seconds()), 68 | int(float64(ocount)/elapsed.Seconds())) 69 | } 70 | } 71 | } 72 | } 73 | 74 | func outputWriter(fd io.WriteCloser, c chan string) { 75 | for r := range c { 76 | fd.Write([]byte(r)) 77 | atomic.AddInt64(&output_count, 1) 78 | } 79 | wg1.Done() 80 | } 81 | 82 | func inputParser(c chan string, c_names chan string, c_inverse chan string) { 83 | 84 | for r := range c { 85 | 86 | rec := DNSRecord{} 87 | 88 | mapped := map[string]string{} 89 | err := json.Unmarshal([]byte(r), &mapped) 90 | if err != nil { 91 | fmt.Fprintf(os.Stderr, "Bad JSON: %s\n", r) 92 | continue 93 | } 94 | 95 | if val, ok := mapped["name"]; ok { 96 | rec.Name = strings.TrimSpace(val) 97 | } else { 98 | continue 99 | } 100 | 101 | if val, ok := mapped["type"]; ok { 102 | rec.Type = strings.TrimSpace(val) 103 | } else { 104 | continue 105 | } 106 | 107 | if val, ok := mapped["value"]; ok { 108 | rec.Value = strings.TrimSpace(val) 109 | } else { 110 | continue 111 | } 112 | 113 | if val, ok := mapped["timestamp"]; ok { 114 | rec.Timestamp = strings.TrimSpace(val) 115 | } else { 116 | continue 117 | } 118 | 119 | // Skip any record that refers to itself (except NS) 120 | if rec.Value == rec.Name && rec.Type != "ns" { 121 | continue 122 | } 123 | 124 | // Skip any record with an empty value 125 | if len(rec.Value) == 0 || len(rec.Name) == 0 { 126 | continue 127 | } 128 | 129 | if rec.Type == "ptr" { 130 | // Determine the field type based on pattern 131 | if inetdata.MatchIPv4.Match([]byte(rec.Name)) { 132 | rec.Type = "a" 133 | } else if inetdata.MatchIPv6.Match([]byte(rec.Name)) { 134 | rec.Type = "aaaa" 135 | } 136 | } 137 | 138 | atomic.AddInt64(&input_count, 1) 139 | 140 | switch rec.Type { 141 | case "a": 142 | // Skip invalid IPv4 records (TODO: verify logic) 143 | if !(inetdata.MatchIPv4.Match([]byte(rec.Value)) || inetdata.MatchIPv4.Match([]byte(rec.Name))) { 144 | continue 145 | } 146 | c_names <- fmt.Sprintf("%s,%s,%s\n", rec.Name, rec.Type, rec.Value) 147 | c_inverse <- fmt.Sprintf("%s,r-%s,%s\n", rec.Value, rec.Type, rec.Name) 148 | 149 | case "aaaa": 150 | // Skip invalid IPv6 records (TODO: verify logic) 151 | if !(inetdata.MatchIPv6.Match([]byte(rec.Value)) || inetdata.MatchIPv6.Match([]byte(rec.Name))) { 152 | continue 153 | } 154 | c_names <- fmt.Sprintf("%s,%s,%s\n", rec.Name, rec.Type, rec.Value) 155 | c_inverse <- fmt.Sprintf("%s,r-%s,%s\n", rec.Value, rec.Type, rec.Name) 156 | 157 | case "cname", "ns", "ptr": 158 | c_names <- fmt.Sprintf("%s,%s,%s\n", rec.Name, rec.Type, rec.Value) 159 | c_inverse <- fmt.Sprintf("%s,r-%s,%s\n", rec.Value, rec.Type, rec.Name) 160 | 161 | case "mx": 162 | parts := strings.SplitN(rec.Value, " ", 2) 163 | if len(parts) != 2 || len(parts[1]) == 0 { 164 | continue 165 | } 166 | c_names <- fmt.Sprintf("%s,%s,%s\n", rec.Name, rec.Type, parts[1]) 167 | c_inverse <- fmt.Sprintf("%s,r-%s,%s\n", parts[1], rec.Type, rec.Name) 168 | 169 | default: 170 | // No inverse output for other record types (TXT, DNSSEC, etc) 171 | c_names <- fmt.Sprintf("%s,%s,%s\n", rec.Name, rec.Type, rec.Value) 172 | } 173 | } 174 | wg2.Done() 175 | } 176 | 177 | func main() { 178 | 179 | runtime.GOMAXPROCS(runtime.NumCPU()) 180 | os.Setenv("LC_ALL", "C") 181 | 182 | flag.Usage = func() { usage() } 183 | sort_tmp := flag.String("t", "", "The temporary directory to use for the sorting phase") 184 | sort_mem := flag.Uint64("m", 1, "The maximum amount of memory to use, in gigabytes, for each of the six sort processes") 185 | version := flag.Bool("version", false, "Show the version and build timestamp") 186 | 187 | flag.Parse() 188 | 189 | if *version { 190 | inetdata.PrintVersion("inetdata-sonardnsv2-split") 191 | os.Exit(0) 192 | } 193 | 194 | if len(flag.Args()) != 1 { 195 | flag.Usage() 196 | os.Exit(1) 197 | } 198 | 199 | if len(*sort_tmp) == 0 { 200 | *sort_tmp = os.Getenv("HOME") 201 | } 202 | 203 | if len(*sort_tmp) == 0 { 204 | flag.Usage() 205 | os.Exit(1) 206 | } 207 | 208 | // Output files 209 | base := flag.Args()[0] 210 | out_fds := []*os.File{} 211 | 212 | suffix := []string{"-names.gz", "-names-inverse.gz"} 213 | for i := range suffix { 214 | fd, e := os.Create(base + suffix[i]) 215 | if e != nil { 216 | fmt.Fprintf(os.Stderr, "Error: failed to create %s: %s\n", base+suffix[i], e) 217 | os.Exit(1) 218 | } 219 | out_fds = append(out_fds, fd) 220 | defer fd.Close() 221 | } 222 | 223 | // Sort and compression pipes 224 | sort_input := [2]io.WriteCloser{} 225 | subprocs := []*exec.Cmd{} 226 | 227 | for i := range out_fds { 228 | 229 | // Create a sort process 230 | sort_proc := exec.Command("nice", 231 | "sort", 232 | "-u", 233 | "--key=1", 234 | "--field-separator=,", 235 | "--compress-program=pigz", 236 | fmt.Sprintf("--parallel=%d", runtime.NumCPU()), 237 | fmt.Sprintf("--temporary-directory=%s", *sort_tmp), 238 | fmt.Sprintf("--buffer-size=%dG", *sort_mem)) 239 | 240 | // Configure stdio 241 | sort_stdin, sie := sort_proc.StdinPipe() 242 | if sie != nil { 243 | fmt.Fprintf(os.Stderr, "Error: failed to create sort stdin pipe: %s\n", sie) 244 | os.Exit(1) 245 | } 246 | 247 | sort_stdout, soe := sort_proc.StdoutPipe() 248 | if soe != nil { 249 | fmt.Fprintf(os.Stderr, "Error: failed to create sort stdout pipe: %s\n", soe) 250 | os.Exit(1) 251 | } 252 | 253 | sort_proc.Stderr = os.Stderr 254 | sort_input[i] = sort_stdin 255 | subprocs = append(subprocs, sort_proc) 256 | 257 | // Start the sort process 258 | if e := sort_proc.Start(); e != nil { 259 | fmt.Fprintf(os.Stderr, "Error: failed to execute the sort command: %s\n", e) 260 | os.Exit(1) 261 | } 262 | 263 | // Create the inetdata-csvrollup process 264 | roll_proc := exec.Command("nice", "inetdata-csvrollup") 265 | 266 | // Configure stdio 267 | roll_stdout, roe := roll_proc.StdoutPipe() 268 | if roe != nil { 269 | fmt.Fprintf(os.Stderr, "Error: failed to create sort stdout pipe: %s\n", roe) 270 | os.Exit(1) 271 | } 272 | 273 | roll_proc.Stderr = os.Stderr 274 | roll_proc.Stdin = sort_stdout 275 | subprocs = append(subprocs, roll_proc) 276 | 277 | // Start the rollup process 278 | if e := roll_proc.Start(); e != nil { 279 | fmt.Fprintf(os.Stderr, "Error: failed to execute the inetdata-csvrollup command: %s\n", e) 280 | os.Exit(1) 281 | } 282 | 283 | // Create a second sort process 284 | sort2_proc := exec.Command("nice", 285 | "sort", 286 | "-u", 287 | "--key=1", 288 | "--field-separator=,", 289 | "--compress-program=pigz", 290 | fmt.Sprintf("--parallel=%d", runtime.NumCPU()), 291 | fmt.Sprintf("--temporary-directory=%s", *sort_tmp), 292 | fmt.Sprintf("--buffer-size=%dG", *sort_mem)) 293 | 294 | sort2_stdout, ssoe := sort2_proc.StdoutPipe() 295 | if ssoe != nil { 296 | fmt.Fprintf(os.Stderr, "Error: failed to create sort stdout pipe: %s\n", ssoe) 297 | os.Exit(1) 298 | } 299 | sort2_proc.Stdin = roll_stdout 300 | sort2_proc.Stderr = os.Stderr 301 | 302 | subprocs = append(subprocs, sort2_proc) 303 | 304 | // Start the sort process 305 | if e := sort2_proc.Start(); e != nil { 306 | fmt.Fprintf(os.Stderr, "Error: failed to execute the second sort command: %s\n", e) 307 | os.Exit(1) 308 | } 309 | 310 | // Create a pigz compressor process 311 | pigz_proc := exec.Command("nice", "pigz", "-c") 312 | 313 | // Configure stdio 314 | pigz_proc.Stderr = os.Stderr 315 | 316 | // Feed output file with pigz output 317 | pigz_proc.Stdout = out_fds[i] 318 | 319 | // Feed pigz with sort output 320 | pigz_proc.Stdin = sort2_stdout 321 | 322 | // Start the pigz process 323 | e := pigz_proc.Start() 324 | if e != nil { 325 | fmt.Fprintf(os.Stderr, "Error: failed to execute the pigz command: %s\n", e) 326 | os.Exit(1) 327 | } 328 | 329 | subprocs = append(subprocs, pigz_proc) 330 | } 331 | 332 | c_names := make(chan string, 1000) 333 | c_inverse := make(chan string, 1000) 334 | 335 | go outputWriter(sort_input[0], c_names) 336 | go outputWriter(sort_input[1], c_inverse) 337 | wg1.Add(2) 338 | 339 | // Progress tracker 340 | quit := make(chan int) 341 | go showProgress(quit) 342 | 343 | // Parse stdin 344 | c_inp := make(chan string, 1000) 345 | go inputParser(c_inp, c_names, c_inverse) 346 | go inputParser(c_inp, c_names, c_inverse) 347 | wg2.Add(2) 348 | 349 | // Reader closes c_inp on completion 350 | e := inetdata.ReadLines(os.Stdin, c_inp) 351 | if e != nil { 352 | fmt.Fprintf(os.Stderr, "Error reading input: %s\n", e) 353 | } 354 | 355 | // Wait for the input parsers to finish 356 | wg2.Wait() 357 | 358 | close(c_names) 359 | close(c_inverse) 360 | 361 | // Wait for the channel writers to finish 362 | wg1.Wait() 363 | 364 | for i := range sort_input { 365 | sort_input[i].Close() 366 | } 367 | 368 | // Stop the main process monitoring, since stats are now static 369 | quit <- 0 370 | 371 | // Wait for the downstream processes to complete 372 | for i := range subprocs { 373 | subprocs[i].Wait() 374 | } 375 | 376 | for i := range out_fds { 377 | out_fds[i].Close() 378 | } 379 | } 380 | -------------------------------------------------------------------------------- /cmd/inetdata-ct-tail/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/sha1" 5 | "crypto/tls" 6 | "encoding/hex" 7 | "encoding/json" 8 | "errors" 9 | "flag" 10 | "fmt" 11 | "io/ioutil" 12 | "net/http" 13 | "os" 14 | "regexp" 15 | "runtime" 16 | "strings" 17 | "sync" 18 | "sync/atomic" 19 | "time" 20 | 21 | ct "github.com/google/certificate-transparency-go" 22 | ct_tls "github.com/google/certificate-transparency-go/tls" 23 | "github.com/google/certificate-transparency-go/x509" 24 | "golang.org/x/net/publicsuffix" 25 | ) 26 | 27 | // MatchIPv6 is a regular expression for validating IPv6 addresses 28 | var MatchIPv6 = regexp.MustCompile(`^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?$`) 29 | 30 | // MatchIPv4 is a regular expression for validating IPv4 addresses 31 | var MatchIPv4 = regexp.MustCompile(`^(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))$`) 32 | 33 | var CTLogs = []string{ 34 | "https://ct.googleapis.com/logs/argon2019/", 35 | "https://ct.googleapis.com/logs/argon2020/", 36 | "https://ct.googleapis.com/logs/argon2021/", 37 | "https://ct.googleapis.com/logs/xenon2019/", 38 | "https://ct.googleapis.com/logs/xenon2020/", 39 | "https://ct.googleapis.com/logs/xenon2021/", 40 | "https://ct.googleapis.com/logs/xenon2022/", 41 | "https://ct.googleapis.com/aviator/", 42 | "https://ct.googleapis.com/icarus/", 43 | "https://ct.googleapis.com/pilot/", 44 | "https://ct.googleapis.com/rocketeer/", 45 | "https://ct.googleapis.com/skydiver/", 46 | "https://ct.cloudflare.com/logs/nimbus2019/", 47 | "https://ct.cloudflare.com/logs/nimbus2020/", 48 | "https://ct.cloudflare.com/logs/nimbus2021/", 49 | "https://ct.cloudflare.com/logs/nimbus2022/", 50 | "https://ct.cloudflare.com/logs/nimbus2023/", 51 | "https://ct1.digicert-ct.com/log/", 52 | "https://ct2.digicert-ct.com/log/", 53 | "https://yeti2019.ct.digicert.com/log/", 54 | "https://yeti2020.ct.digicert.com/log/", 55 | "https://yeti2021.ct.digicert.com/log/", 56 | "https://yeti2022.ct.digicert.com/log/", 57 | "https://yeti2023.ct.digicert.com/log/", 58 | "https://nessie2019.ct.digicert.com/log/", 59 | "https://nessie2020.ct.digicert.com/log/", 60 | "https://nessie2021.ct.digicert.com/log/", 61 | "https://nessie2022.ct.digicert.com/log/", 62 | "https://nessie2023.ct.digicert.com/log/", 63 | "https://ct.ws.symantec.com/", 64 | "https://vega.ws.symantec.com/", 65 | "https://sirius.ws.symantec.com/", 66 | "https://log.certly.io/", 67 | "https://ct.izenpe.com/", 68 | "https://ctlog.wosign.com/", 69 | "https://ctlog.api.venafi.com/", 70 | "https://ctlog-gen2.api.venafi.com/", 71 | "https://ctserver.cnnic.cn/", 72 | "https://ct.startssl.com/", 73 | "https://sabre.ct.comodo.com/", 74 | "https://mammoth.ct.comodo.com/", 75 | } 76 | 77 | var output_count int64 = 0 78 | var input_count int64 = 0 79 | var number *int 80 | var follow *bool 81 | 82 | var wd sync.WaitGroup 83 | var wi sync.WaitGroup 84 | var wo sync.WaitGroup 85 | 86 | type CTEntry struct { 87 | LeafInput []byte `json:"leaf_input"` 88 | ExtraData []byte `json:"extra_data"` 89 | } 90 | 91 | type CTEntries struct { 92 | Entries []CTEntry `json:"entries"` 93 | } 94 | 95 | type CTEntriesError struct { 96 | ErrorMessage string `json:"error_message"` 97 | Success bool `json:"success"` 98 | } 99 | 100 | type CTHead struct { 101 | TreeSize int64 `json:"tree_size"` 102 | Timestamp int64 `json:"timestamp"` 103 | SHA256RootHash string `json:"sha256_root_hash"` 104 | TreeHeadSignature string `json:"tree_head_signature"` 105 | } 106 | 107 | func usage() { 108 | fmt.Println("Usage: " + os.Args[0] + " [options]") 109 | fmt.Println("") 110 | fmt.Println("Synchronizes data from one or more CT logs and extract hostnames") 111 | fmt.Println("") 112 | fmt.Println("Options:") 113 | flag.PrintDefaults() 114 | } 115 | 116 | func scrubX509Value(bit string) string { 117 | bit = strings.Replace(bit, "\x00", "[0x00]", -1) 118 | bit = strings.Replace(bit, " ", "_", -1) 119 | return bit 120 | } 121 | 122 | func downloadJSON(url string) ([]byte, error) { 123 | 124 | req, err := http.NewRequest("GET", url, nil) 125 | if err != nil { 126 | return []byte{}, err 127 | } 128 | 129 | req.Header.Set("Accept", "application/json") 130 | 131 | tr := &http.Transport{ 132 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 133 | } 134 | 135 | client := &http.Client{Transport: tr} 136 | 137 | resp, err := client.Do(req) 138 | if err != nil { 139 | return []byte{}, err 140 | } 141 | 142 | defer resp.Body.Close() 143 | 144 | content, err := ioutil.ReadAll(resp.Body) 145 | if err != nil { 146 | return []byte{}, err 147 | } 148 | 149 | return content, err 150 | } 151 | 152 | func downloadSTH(logurl string) (CTHead, error) { 153 | var sth CTHead 154 | url := fmt.Sprintf("%s/ct/v1/get-sth", logurl) 155 | data, err := downloadJSON(url) 156 | if err != nil { 157 | return sth, err 158 | } 159 | 160 | err = json.Unmarshal(data, &sth) 161 | return sth, err 162 | } 163 | 164 | func downloadEntries(logurl string, start_index int64, stop_index int64) (CTEntries, error) { 165 | var entries CTEntries 166 | var entries_error CTEntriesError 167 | 168 | url := fmt.Sprintf("%s/ct/v1/get-entries?start=%d&end=%d", logurl, start_index, stop_index) 169 | data, err := downloadJSON(url) 170 | if err != nil { 171 | return entries, err 172 | } 173 | 174 | if strings.Contains(string(data), "\"error_message\":") { 175 | err = json.Unmarshal(data, &entries_error) 176 | if err != nil { 177 | return entries, err 178 | } 179 | return entries, errors.New(entries_error.ErrorMessage) 180 | } 181 | 182 | err = json.Unmarshal(data, &entries) 183 | return entries, err 184 | } 185 | 186 | func logNameToPath(name string) string { 187 | bits := strings.SplitN(name, "//", 2) 188 | return strings.Replace(bits[1], "/", "_", -1) 189 | } 190 | 191 | func downloadLog(log string, c_inp chan<- CTEntry) { 192 | var iteration int64 = 0 193 | var current_index int64 = 0 194 | 195 | defer wd.Done() 196 | 197 | for { 198 | 199 | if iteration > 0 { 200 | fmt.Fprintf(os.Stderr, "[*] Sleeping for 10 seconds (%s) at index %d\n", log, current_index) 201 | time.Sleep(time.Duration(10) * time.Second) 202 | } 203 | 204 | sth, sth_err := downloadSTH(log) 205 | if sth_err != nil { 206 | fmt.Fprintf(os.Stderr, "[-] Failed to download STH for %s: %s\n", log, sth_err) 207 | } 208 | 209 | var start_index int64 = 0 210 | 211 | if iteration == 0 { 212 | start_index = sth.TreeSize - int64(*number) 213 | if start_index < 0 { 214 | start_index = 0 215 | } 216 | current_index = start_index 217 | } else { 218 | start_index = current_index 219 | } 220 | 221 | var entry_count int64 = 1000 222 | 223 | for index := start_index; index < sth.TreeSize; index += entry_count { 224 | stop_index := index + entry_count - 1 225 | if stop_index >= sth.TreeSize { 226 | stop_index = sth.TreeSize - 1 227 | } 228 | 229 | entries, err := downloadEntries(log, index, stop_index) 230 | if err != nil { 231 | fmt.Fprintf(os.Stderr, "[-] Failed to download entries for %s: index %d -> %s\n", log, index, err) 232 | return 233 | } 234 | for entry_index := range entries.Entries { 235 | c_inp <- entries.Entries[entry_index] 236 | } 237 | } 238 | 239 | // Move our index to the end of the last tree 240 | current_index = sth.TreeSize 241 | iteration++ 242 | 243 | // Break after one loop unless we are in follow mode 244 | if !*follow { 245 | break 246 | } 247 | 248 | } 249 | } 250 | 251 | func outputWriter(o <-chan string) { 252 | for name := range o { 253 | fmt.Print(name) 254 | atomic.AddInt64(&output_count, 1) 255 | } 256 | wo.Done() 257 | } 258 | 259 | func inputParser(c <-chan CTEntry, o chan<- string) { 260 | 261 | for entry := range c { 262 | 263 | var leaf ct.MerkleTreeLeaf 264 | 265 | if rest, err := ct_tls.Unmarshal(entry.LeafInput, &leaf); err != nil { 266 | fmt.Fprintf(os.Stderr, "[-] Failed to unmarshal MerkleTreeLeaf: %v (%v)", err, entry) 267 | continue 268 | } else if len(rest) > 0 { 269 | fmt.Fprintf(os.Stderr, "[-] Trailing data (%d bytes) after MerkleTreeLeaf: %q", len(rest), rest) 270 | continue 271 | } 272 | 273 | var cert *x509.Certificate 274 | var err error 275 | 276 | switch leaf.TimestampedEntry.EntryType { 277 | case ct.X509LogEntryType: 278 | 279 | cert, err = x509.ParseCertificate(leaf.TimestampedEntry.X509Entry.Data) 280 | if err != nil && !strings.Contains(err.Error(), "NonFatalErrors:") { 281 | fmt.Fprintf(os.Stderr, "[-] Failed to parse cert: %s\n", err.Error()) 282 | continue 283 | } 284 | 285 | case ct.PrecertLogEntryType: 286 | 287 | cert, err = x509.ParseTBSCertificate(leaf.TimestampedEntry.PrecertEntry.TBSCertificate) 288 | if err != nil && !strings.Contains(err.Error(), "NonFatalErrors:") { 289 | fmt.Fprintf(os.Stderr, "[-] Failed to parse precert: %s\n", err.Error()) 290 | continue 291 | } 292 | 293 | default: 294 | fmt.Fprintf(os.Stderr, "[-] Unknown entry type: %v (%v)", leaf.TimestampedEntry.EntryType, entry) 295 | continue 296 | } 297 | 298 | // Valid input 299 | atomic.AddInt64(&input_count, 1) 300 | 301 | var names = make(map[string]struct{}) 302 | 303 | if _, err := publicsuffix.EffectiveTLDPlusOne(cert.Subject.CommonName); err == nil { 304 | // Make sure this looks like an actual hostname or IP address 305 | if !(MatchIPv4.Match([]byte(cert.Subject.CommonName)) || 306 | MatchIPv6.Match([]byte(cert.Subject.CommonName))) && 307 | (strings.Contains(cert.Subject.CommonName, " ") || 308 | strings.Contains(cert.Subject.CommonName, ":")) { 309 | continue 310 | } 311 | names[strings.ToLower(cert.Subject.CommonName)] = struct{}{} 312 | } 313 | 314 | for _, alt := range cert.DNSNames { 315 | if _, err := publicsuffix.EffectiveTLDPlusOne(alt); err == nil { 316 | // Make sure this looks like an actual hostname or IP address 317 | if !(MatchIPv4.Match([]byte(cert.Subject.CommonName)) || 318 | MatchIPv6.Match([]byte(cert.Subject.CommonName))) && 319 | (strings.Contains(alt, " ") || 320 | strings.Contains(alt, ":")) { 321 | continue 322 | } 323 | names[strings.ToLower(alt)] = struct{}{} 324 | } 325 | } 326 | 327 | sha1hash := "" 328 | 329 | // Write the names to the output channel 330 | for n := range names { 331 | if len(sha1hash) == 0 { 332 | sha1 := sha1.Sum(cert.Raw) 333 | sha1hash = hex.EncodeToString(sha1[:]) 334 | } 335 | 336 | // Dump associated email addresses if available 337 | for _, extra := range cert.EmailAddresses { 338 | o <- fmt.Sprintf("%s,email,%s\n", n, strings.ToLower(scrubX509Value(extra))) 339 | } 340 | 341 | // Dump associated IP addresses if we have at least one name 342 | for _, extra := range cert.IPAddresses { 343 | o <- fmt.Sprintf("%s,ip,%s\n", n, extra) 344 | } 345 | 346 | o <- fmt.Sprintf("%s,ts,%d\n", n, leaf.TimestampedEntry.Timestamp) 347 | o <- fmt.Sprintf("%s,cn,%s\n", n, strings.ToLower(scrubX509Value(cert.Subject.CommonName))) 348 | o <- fmt.Sprintf("%s,sha1,%s\n", n, sha1hash) 349 | 350 | // Dump associated SANs 351 | for _, extra := range cert.DNSNames { 352 | o <- fmt.Sprintf("%s,dns,%s\n", strings.ToLower(extra), n) 353 | } 354 | } 355 | } 356 | 357 | wi.Done() 358 | } 359 | 360 | func main() { 361 | 362 | runtime.GOMAXPROCS(runtime.NumCPU()) 363 | os.Setenv("LC_ALL", "C") 364 | 365 | flag.Usage = func() { usage() } 366 | logurl := flag.String("logurl", "", "Only read from the specified CT log url") 367 | number = flag.Int("n", 100, "The number of entries from the end to start from") 368 | follow = flag.Bool("f", false, "Follow the tail of the CT log") 369 | 370 | flag.Parse() 371 | 372 | logs := []string{} 373 | if len(*logurl) > 0 { 374 | logs = append(logs, *logurl) 375 | } else { 376 | for idx := range CTLogs { 377 | logs = append(logs, CTLogs[idx]) 378 | } 379 | } 380 | 381 | // Input 382 | c_inp := make(chan CTEntry) 383 | 384 | // Output 385 | c_out := make(chan string) 386 | 387 | // Launch one input parser per core 388 | for i := 0; i < runtime.NumCPU(); i++ { 389 | go inputParser(c_inp, c_out) 390 | } 391 | wi.Add(runtime.NumCPU()) 392 | 393 | // Launch a single output writer 394 | go outputWriter(c_out) 395 | wo.Add(1) 396 | 397 | for idx := range logs { 398 | go downloadLog(logs[idx], c_inp) 399 | wd.Add(1) 400 | } 401 | // Wait for downloaders 402 | wd.Wait() 403 | 404 | // Close the input channel 405 | close(c_inp) 406 | 407 | // Wait for the input parsers 408 | wi.Wait() 409 | 410 | // Close the output handle 411 | close(c_out) 412 | 413 | // Wait for the output goroutine 414 | wo.Wait() 415 | } 416 | -------------------------------------------------------------------------------- /cmd/inetdata-ct2csv/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/sha1" 5 | "encoding/hex" 6 | "encoding/json" 7 | "flag" 8 | "fmt" 9 | ct "github.com/google/certificate-transparency-go" 10 | "github.com/google/certificate-transparency-go/tls" 11 | "github.com/google/certificate-transparency-go/x509" 12 | "github.com/hdm/inetdata-parsers" 13 | "golang.org/x/net/publicsuffix" 14 | "io" 15 | "net" 16 | "os" 17 | "os/exec" 18 | "runtime" 19 | "strings" 20 | "sync" 21 | "sync/atomic" 22 | "time" 23 | ) 24 | 25 | var merge_count int64 = 0 26 | var output_count int64 = 0 27 | var input_count int64 = 0 28 | var invalid_count int64 = 0 29 | var timestamps *bool 30 | 31 | var wg_raw_ct_input sync.WaitGroup 32 | var wg_parsed_ct_writer sync.WaitGroup 33 | var wg_sorted_ct_parser sync.WaitGroup 34 | var wg_sort_reader sync.WaitGroup 35 | 36 | type CTEntry struct { 37 | LeafInput []byte `json:"leaf_input"` 38 | ExtraData []byte `json:"extra_data"` 39 | } 40 | 41 | type ParsedCTEntry struct { 42 | Sha1Hash string `json:"h"` 43 | Timestamp uint64 `json:"t"` 44 | CommonName string `json:"cn,omitempty"` 45 | DNS []string `json:"dns,omitempty"` 46 | IP []net.IP `json:"ip,omitempty"` 47 | Email []string `json:"email,omitempty"` 48 | } 49 | 50 | type ParsedCTEntryOutput struct { 51 | Certs []ParsedCTEntry `json:"certs"` 52 | } 53 | 54 | type NewRecord struct { 55 | Key []byte 56 | Val []byte 57 | } 58 | 59 | func usage() { 60 | fmt.Println("Usage: " + os.Args[0] + " [options]") 61 | fmt.Println("") 62 | fmt.Println("Reads a CT log in JSONL format (one line per record) and emits a CSV") 63 | fmt.Println("") 64 | fmt.Println("Options:") 65 | flag.PrintDefaults() 66 | } 67 | 68 | func showProgress(quit chan int) { 69 | start := time.Now() 70 | for { 71 | select { 72 | case <-quit: 73 | return 74 | case <-time.After(time.Second * 1): 75 | icount := atomic.LoadInt64(&input_count) 76 | ocount := atomic.LoadInt64(&output_count) 77 | mcount := atomic.LoadInt64(&merge_count) 78 | ecount := atomic.LoadInt64(&invalid_count) 79 | 80 | if icount == 0 && ocount == 0 { 81 | // Reset start, so that we show stats only from our first input 82 | start = time.Now() 83 | continue 84 | } 85 | elapsed := time.Since(start) 86 | if elapsed.Seconds() > 1.0 { 87 | fmt.Fprintf(os.Stderr, "[*] [inetdata-ct2csv] Read %d and wrote %d records in %d seconds (%d/s in, %d/s out) (merged: %d, invalid: %d)\n", 88 | icount, 89 | ocount, 90 | int(elapsed.Seconds()), 91 | int(float64(icount)/elapsed.Seconds()), 92 | int(float64(ocount)/elapsed.Seconds()), 93 | mcount, ecount) 94 | } 95 | } 96 | } 97 | } 98 | 99 | func scrubX509Value(bit string) string { 100 | bit = strings.Replace(bit, "\x00", "[0x00]", -1) 101 | bit = strings.Replace(bit, " ", "_", -1) 102 | return bit 103 | } 104 | 105 | func writeToOutput(c chan NewRecord, d chan bool) { 106 | 107 | for r := range c { 108 | fmt.Fprintf(os.Stdout, "%s\t%s\n", r.Key, r.Val) 109 | } 110 | 111 | // Signal that we are done 112 | d <- true 113 | } 114 | 115 | func sortedCTParser(d chan string, c chan NewRecord) { 116 | 117 | for raw := range d { 118 | 119 | bits := strings.SplitN(raw, ",", 2) 120 | 121 | if len(bits) != 2 { 122 | continue 123 | } 124 | 125 | name := bits[0] 126 | data := bits[1] 127 | 128 | if len(name) == 0 || len(data) == 0 { 129 | atomic.AddInt64(&invalid_count, 1) 130 | continue 131 | } 132 | vals := strings.SplitN(data, "\x00", -1) 133 | 134 | outm := ParsedCTEntryOutput{} 135 | 136 | for i := range vals { 137 | info := ParsedCTEntry{} 138 | 139 | if err := json.Unmarshal([]byte(vals[i]), &info); err != nil { 140 | fmt.Fprintf(os.Stderr, "[-] Could not unmarshal %s: %s\n", vals[i], err) 141 | continue 142 | } 143 | outm.Certs = append(outm.Certs, info) 144 | } 145 | 146 | json, e := json.Marshal(outm) 147 | if e != nil { 148 | fmt.Fprintf(os.Stderr, "[-] Could not marshal %v: %s\n", outm, e) 149 | continue 150 | } 151 | 152 | // Reverse the key unless its an IP address or SHA1 hash 153 | if !(inetdata.MatchIPv4.Match([]byte(name)) || 154 | inetdata.MatchIPv6.Match([]byte(name)) || 155 | inetdata.Match_SHA1.Match([]byte(name))) { 156 | name = inetdata.ReverseKey(name) 157 | } 158 | 159 | c <- NewRecord{Key: []byte(name), Val: json} 160 | } 161 | 162 | close(c) 163 | 164 | wg_sorted_ct_parser.Done() 165 | } 166 | 167 | func parsedCTWriter(o <-chan string, fd io.WriteCloser) { 168 | for r := range o { 169 | fd.Write([]byte(r)) 170 | } 171 | wg_parsed_ct_writer.Done() 172 | } 173 | 174 | func rawCTReader(c <-chan string, o chan<- string) { 175 | 176 | for r := range c { 177 | var entry CTEntry 178 | 179 | if err := json.Unmarshal([]byte(r), &entry); err != nil { 180 | fmt.Fprintf(os.Stderr, "Error parsing input: %s\n", r) 181 | continue 182 | } 183 | 184 | var leaf ct.MerkleTreeLeaf 185 | 186 | if rest, err := tls.Unmarshal(entry.LeafInput, &leaf); err != nil { 187 | fmt.Fprintf(os.Stderr, "Failed to unmarshal MerkleTreeLeaf: %v (%s)", err, r) 188 | continue 189 | } else if len(rest) > 0 { 190 | fmt.Fprintf(os.Stderr, "Trailing data (%d bytes) after MerkleTreeLeaf: %q", len(rest), rest) 191 | continue 192 | } 193 | 194 | var cert *x509.Certificate 195 | var err error 196 | 197 | switch leaf.TimestampedEntry.EntryType { 198 | case ct.X509LogEntryType: 199 | 200 | cert, err = x509.ParseCertificate(leaf.TimestampedEntry.X509Entry.Data) 201 | if err != nil && !strings.Contains(err.Error(), "NonFatalErrors:") { 202 | fmt.Fprintf(os.Stderr, "Failed to parse cert: %s\n", err.Error()) 203 | continue 204 | } 205 | 206 | case ct.PrecertLogEntryType: 207 | 208 | cert, err = x509.ParseTBSCertificate(leaf.TimestampedEntry.PrecertEntry.TBSCertificate) 209 | if err != nil && !strings.Contains(err.Error(), "NonFatalErrors:") { 210 | fmt.Fprintf(os.Stderr, "Failed to parse precert: %s\n", err.Error()) 211 | continue 212 | } 213 | 214 | default: 215 | fmt.Fprintf(os.Stderr, "Unknown entry type: %v (%s)", leaf.TimestampedEntry.EntryType, r) 216 | continue 217 | } 218 | 219 | // Valid input 220 | atomic.AddInt64(&input_count, 1) 221 | 222 | var names = make(map[string]struct{}) 223 | 224 | if _, err := publicsuffix.EffectiveTLDPlusOne(cert.Subject.CommonName); err == nil { 225 | // Make sure this looks like an actual hostname or IP address 226 | if !(inetdata.MatchIPv4.Match([]byte(cert.Subject.CommonName)) || 227 | inetdata.MatchIPv6.Match([]byte(cert.Subject.CommonName))) && 228 | (strings.Contains(cert.Subject.CommonName, " ") || 229 | strings.Contains(cert.Subject.CommonName, ":")) { 230 | continue 231 | } 232 | names[strings.ToLower(cert.Subject.CommonName)] = struct{}{} 233 | } 234 | 235 | for _, alt := range cert.DNSNames { 236 | if _, err := publicsuffix.EffectiveTLDPlusOne(alt); err == nil { 237 | // Make sure this looks like an actual hostname or IP address 238 | if !(inetdata.MatchIPv4.Match([]byte(cert.Subject.CommonName)) || 239 | inetdata.MatchIPv6.Match([]byte(cert.Subject.CommonName))) && 240 | (strings.Contains(alt, " ") || 241 | strings.Contains(alt, ":")) { 242 | continue 243 | } 244 | names[strings.ToLower(alt)] = struct{}{} 245 | } 246 | } 247 | 248 | for _, alt := range cert.IPAddresses { 249 | names[fmt.Sprintf("%s", alt)] = struct{}{} 250 | } 251 | 252 | sha1 := sha1.Sum(cert.Raw) 253 | sha1hash := hex.EncodeToString(sha1[:]) 254 | wrote_hash := false 255 | 256 | // Write the names to the output channel 257 | for n := range names { 258 | 259 | info := ParsedCTEntry{Sha1Hash: sha1hash, Timestamp: leaf.TimestampedEntry.Timestamp} 260 | info.CommonName = scrubX509Value(cert.Subject.CommonName) 261 | 262 | // Dump associated email addresses if available 263 | for _, extra := range cert.EmailAddresses { 264 | info.Email = append(info.Email, scrubX509Value(extra)) 265 | } 266 | 267 | // Dump associated IP addresses if we have at least one name 268 | for _, extra := range cert.IPAddresses { 269 | info.IP = append(info.IP, extra) 270 | } 271 | 272 | // Dump associated SANs (overkill, but saves a second lookup) 273 | for _, extra := range cert.DNSNames { 274 | info.DNS = append(info.DNS, extra) 275 | } 276 | 277 | info_bytes, err := json.Marshal(info) 278 | if err != nil { 279 | fmt.Fprintf(os.Stderr, "Failed to marshal: %s %+v", n, info) 280 | continue 281 | } 282 | 283 | if wrote_hash == false { 284 | o <- fmt.Sprintf("%s,%s\n", sha1hash, info_bytes) 285 | wrote_hash = true 286 | } 287 | 288 | o <- fmt.Sprintf("%s,%s\n", n, info_bytes) 289 | } 290 | } 291 | 292 | wg_raw_ct_input.Done() 293 | } 294 | 295 | func main() { 296 | 297 | runtime.GOMAXPROCS(runtime.NumCPU()) 298 | os.Setenv("LC_ALL", "C") 299 | 300 | flag.Usage = func() { usage() } 301 | sort_tmp := flag.String("t", "", "The temporary directory to use for the sorting phase") 302 | sort_mem := flag.Uint64("m", 1, "The maximum amount of memory to use, in gigabytes, for the sorting phases") 303 | version := flag.Bool("version", false, "Show the version and build timestamp") 304 | 305 | flag.Parse() 306 | 307 | if *version { 308 | inetdata.PrintVersion("inetdata-ct2csv") 309 | os.Exit(0) 310 | } 311 | 312 | if len(*sort_tmp) == 0 { 313 | *sort_tmp = os.Getenv("HOME") 314 | } 315 | 316 | if len(*sort_tmp) == 0 { 317 | flag.Usage() 318 | os.Exit(1) 319 | } 320 | 321 | jsonl_writer_ch := make(chan NewRecord, 1) 322 | jsonl_writer_done := make(chan bool, 1) 323 | 324 | // Read from the jsonl_writer_ch for NewRecords and write to the CSV writer 325 | go writeToOutput(jsonl_writer_ch, jsonl_writer_done) 326 | 327 | // Create the sort and rollup pipeline 328 | subprocs := []*exec.Cmd{} 329 | 330 | // Create a sort process 331 | sort_proc := exec.Command("nice", 332 | "sort", 333 | "-u", 334 | "--key=1", 335 | "--field-separator=,", 336 | "--compress-program=pigz", 337 | fmt.Sprintf("--parallel=%d", runtime.NumCPU()), 338 | fmt.Sprintf("--temporary-directory=%s", *sort_tmp), 339 | fmt.Sprintf("--buffer-size=%dG", *sort_mem)) 340 | 341 | // Configure stdio 342 | sort_stdin, sie := sort_proc.StdinPipe() 343 | if sie != nil { 344 | fmt.Fprintf(os.Stderr, "Error: failed to create sort stdin pipe: %s\n", sie) 345 | os.Exit(1) 346 | } 347 | 348 | sort_stdout, soe := sort_proc.StdoutPipe() 349 | if soe != nil { 350 | fmt.Fprintf(os.Stderr, "Error: failed to create sort stdout pipe: %s\n", soe) 351 | os.Exit(1) 352 | } 353 | 354 | sort_proc.Stderr = os.Stderr 355 | subprocs = append(subprocs, sort_proc) 356 | 357 | // Start the sort process 358 | if e := sort_proc.Start(); e != nil { 359 | fmt.Fprintf(os.Stderr, "Error: failed to execute the sort command: %s\n", e) 360 | os.Exit(1) 361 | } 362 | 363 | // Create the inetdata-csvrollup process 364 | roll_proc := exec.Command("nice", "inetdata-csvrollup") 365 | 366 | // Configure stdio 367 | roll_stdout, roe := roll_proc.StdoutPipe() 368 | if roe != nil { 369 | fmt.Fprintf(os.Stderr, "Error: failed to create sort stdout pipe: %s\n", roe) 370 | os.Exit(1) 371 | } 372 | 373 | roll_proc.Stderr = os.Stderr 374 | roll_proc.Stdin = sort_stdout 375 | subprocs = append(subprocs, roll_proc) 376 | 377 | // Start the rollup process 378 | if e := roll_proc.Start(); e != nil { 379 | fmt.Fprintf(os.Stderr, "Error: failed to execute the inetdata-csvrollup command: %s\n", e) 380 | os.Exit(1) 381 | } 382 | 383 | // Create a second sort process 384 | sort2_proc := exec.Command("nice", 385 | "sort", 386 | "-u", 387 | "--key=1", 388 | "--field-separator=,", 389 | "--compress-program=pigz", 390 | fmt.Sprintf("--parallel=%d", runtime.NumCPU()), 391 | fmt.Sprintf("--temporary-directory=%s", *sort_tmp), 392 | fmt.Sprintf("--buffer-size=%dG", *sort_mem)) 393 | 394 | sort2_stdout, ssoe := sort2_proc.StdoutPipe() 395 | if ssoe != nil { 396 | fmt.Fprintf(os.Stderr, "Error: failed to create sort stdout pipe: %s\n", ssoe) 397 | os.Exit(1) 398 | } 399 | sort2_proc.Stdin = roll_stdout 400 | sort2_proc.Stderr = os.Stderr 401 | 402 | subprocs = append(subprocs, sort2_proc) 403 | 404 | // Start the sort process 405 | if e := sort2_proc.Start(); e != nil { 406 | fmt.Fprintf(os.Stderr, "Error: failed to execute the second sort command: %s\n", e) 407 | os.Exit(1) 408 | } 409 | 410 | // Read rollup entries, convert to json, send to the CSV writer 411 | c_ct_sorted_output := make(chan string) 412 | wg_sorted_ct_parser.Add(1) 413 | go sortedCTParser(c_ct_sorted_output, jsonl_writer_ch) 414 | 415 | wg_sort_reader.Add(1) 416 | go func() { 417 | // Read rollup entries from the sort pipe and send to the parser 418 | e := inetdata.ReadLinesFromReader(sort2_stdout, c_ct_sorted_output) 419 | if e != nil { 420 | fmt.Fprintf(os.Stderr, "Error reading sort 2 input: %s\n", e) 421 | os.Exit(1) 422 | } 423 | wg_sort_reader.Done() 424 | }() 425 | 426 | // Start the progress tracker 427 | quit := make(chan int) 428 | go showProgress(quit) 429 | 430 | // Large channel buffer evens out spikey per-record processing time 431 | c_ct_raw_input := make(chan string, 4096) 432 | 433 | // Output 434 | c_ct_parsed_output := make(chan string) 435 | 436 | // Launch one input parser per core 437 | wg_raw_ct_input.Add(runtime.NumCPU()) 438 | for i := 0; i < runtime.NumCPU(); i++ { 439 | go rawCTReader(c_ct_raw_input, c_ct_parsed_output) 440 | } 441 | 442 | // Launch a writer that feeds parsed entries into the sort input pipe 443 | wg_parsed_ct_writer.Add(1) 444 | go parsedCTWriter(c_ct_parsed_output, sort_stdin) 445 | 446 | // Read CT JSON from stdin, parse, and send to sort 447 | e := inetdata.ReadLines(os.Stdin, c_ct_raw_input) 448 | if e != nil { 449 | fmt.Fprintf(os.Stderr, "Error reading input: %s\n", e) 450 | } 451 | 452 | // Wait for the input parsers 453 | wg_raw_ct_input.Wait() 454 | 455 | // Close the output handle 456 | close(c_ct_parsed_output) 457 | 458 | // Wait for the output goroutine 459 | wg_parsed_ct_writer.Wait() 460 | 461 | // Close the sort 1 input pipe 462 | sort_stdin.Close() 463 | 464 | // Wait for the sort reader 465 | wg_sort_reader.Wait() 466 | 467 | // Wait for subproceses to complete 468 | for i := range subprocs { 469 | subprocs[i].Wait() 470 | } 471 | 472 | // Wait for the sortedCT processor 473 | wg_sorted_ct_parser.Wait() 474 | 475 | // Wait for the json writer to finish 476 | <-jsonl_writer_done 477 | 478 | // Stop the progress monitor 479 | quit <- 0 480 | } 481 | -------------------------------------------------------------------------------- /cmd/inetdata-ct2mtbl/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/sha1" 5 | "encoding/hex" 6 | "encoding/json" 7 | "flag" 8 | "fmt" 9 | "io" 10 | "os" 11 | "os/exec" 12 | "runtime" 13 | "sort" 14 | "strings" 15 | "sync" 16 | "sync/atomic" 17 | "time" 18 | 19 | ct "github.com/google/certificate-transparency-go" 20 | "github.com/google/certificate-transparency-go/tls" 21 | "github.com/google/certificate-transparency-go/x509" 22 | mtbl "github.com/hdm/golang-mtbl" 23 | "github.com/hdm/inetdata-parsers" 24 | "golang.org/x/net/publicsuffix" 25 | ) 26 | 27 | const MERGE_MODE_COMBINE = 0 28 | const MERGE_MODE_FIRST = 1 29 | const MERGE_MODE_LAST = 2 30 | 31 | var merge_mode = MERGE_MODE_COMBINE 32 | 33 | var compression_types = map[string]int{ 34 | "none": mtbl.COMPRESSION_NONE, 35 | "snappy": mtbl.COMPRESSION_SNAPPY, 36 | "zlib": mtbl.COMPRESSION_ZLIB, 37 | "lz4": mtbl.COMPRESSION_LZ4, 38 | "lz4hc": mtbl.COMPRESSION_LZ4HC, 39 | } 40 | 41 | var merge_count int64 = 0 42 | var output_count int64 = 0 43 | var input_count int64 = 0 44 | var invalid_count int64 = 0 45 | var timestamps *bool 46 | 47 | var wg_raw_ct_input sync.WaitGroup 48 | var wg_parsed_ct_writer sync.WaitGroup 49 | var wg_sorted_ct_parser sync.WaitGroup 50 | var wg_sort_reader sync.WaitGroup 51 | 52 | type CTEntry struct { 53 | LeafInput []byte `json:"leaf_input"` 54 | ExtraData []byte `json:"extra_data"` 55 | } 56 | 57 | type NewRecord struct { 58 | Key []byte 59 | Val []byte 60 | } 61 | 62 | func usage() { 63 | fmt.Println("Usage: " + os.Args[0] + " [options] ") 64 | fmt.Println("") 65 | fmt.Println("Reads a CT log in JSONL format (one line per record) and emits a MTBL") 66 | fmt.Println("") 67 | fmt.Println("Options:") 68 | flag.PrintDefaults() 69 | } 70 | 71 | func showProgress(quit chan int) { 72 | start := time.Now() 73 | for { 74 | select { 75 | case <-quit: 76 | return 77 | case <-time.After(time.Second * 1): 78 | icount := atomic.LoadInt64(&input_count) 79 | ocount := atomic.LoadInt64(&output_count) 80 | mcount := atomic.LoadInt64(&merge_count) 81 | ecount := atomic.LoadInt64(&invalid_count) 82 | 83 | if icount == 0 && ocount == 0 { 84 | // Reset start, so that we show stats only from our first input 85 | start = time.Now() 86 | continue 87 | } 88 | elapsed := time.Since(start) 89 | if elapsed.Seconds() > 1.0 { 90 | fmt.Fprintf(os.Stderr, "[*] [inetdata-ct2mtbl] Read %d and wrote %d records in %d seconds (%d/s in, %d/s out) (merged: %d, invalid: %d)\n", 91 | icount, 92 | ocount, 93 | int(elapsed.Seconds()), 94 | int(float64(icount)/elapsed.Seconds()), 95 | int(float64(ocount)/elapsed.Seconds()), 96 | mcount, ecount) 97 | } 98 | } 99 | } 100 | } 101 | 102 | func scrubX509Value(bit string) string { 103 | bit = strings.Replace(bit, "\x00", "[0x00]", -1) 104 | bit = strings.Replace(bit, " ", "_", -1) 105 | return bit 106 | } 107 | 108 | func mergeFunc(key []byte, val0 []byte, val1 []byte) (mergedVal []byte) { 109 | 110 | atomic.AddInt64(&merge_count, 1) 111 | 112 | if merge_mode == MERGE_MODE_FIRST { 113 | return val0 114 | } 115 | 116 | if merge_mode == MERGE_MODE_LAST { 117 | return val1 118 | } 119 | 120 | // MERGE_MODE_COMBINE 121 | var unique = make(map[string]bool) 122 | var v0, v1, m [][]string 123 | 124 | // fmt.Fprintf(os.Stderr, "MERGE[%v] %v -> %v\n", string(key), string(val0), string(val1)) 125 | 126 | if e := json.Unmarshal(val0, &v0); e != nil { 127 | return val1 128 | } 129 | 130 | if e := json.Unmarshal(val1, &v1); e != nil { 131 | return val0 132 | } 133 | 134 | for i := range v0 { 135 | if len(v0[i]) == 0 { 136 | continue 137 | } 138 | unique[strings.Join(v0[i], "\x00")] = true 139 | } 140 | 141 | for i := range v1 { 142 | if len(v1[i]) == 0 { 143 | continue 144 | } 145 | unique[strings.Join(v1[i], "\x00")] = true 146 | } 147 | 148 | for i := range unique { 149 | m = append(m, strings.SplitN(i, "\x00", 2)) 150 | } 151 | 152 | d, e := json.Marshal(m) 153 | if e != nil { 154 | fmt.Fprintf(os.Stderr, "JSON merge error: %v -> %v + %v\n", e, val0, val1) 155 | return val0 156 | } 157 | 158 | return d 159 | } 160 | 161 | func writeToMtbl(s *mtbl.Sorter, c chan NewRecord, d chan bool) { 162 | for r := range c { 163 | if len(r.Key) > inetdata.MTBL_KEY_LIMIT { 164 | fmt.Fprintf(os.Stderr, "[-] Failed to add key larger than %d: %s... (%d bytes)", inetdata.MTBL_KEY_LIMIT, string(r.Key[0:1024]), len(r.Key)) 165 | continue 166 | } 167 | if len(r.Val) > inetdata.MTBL_VAL_LIMIT { 168 | fmt.Fprintf(os.Stderr, "[-] Failed to add value larger than %d for key %s: %s... (%d bytes)", inetdata.MTBL_VAL_LIMIT, string(r.Key), string(r.Val[0:1024]), len(r.Val)) 169 | continue 170 | } 171 | if e := s.Add(r.Key, r.Val); e != nil { 172 | fmt.Fprintf(os.Stderr, "[-] Failed to add key=%v (%v): %v\n", r.Key, r.Val, e) 173 | continue 174 | } 175 | atomic.AddInt64(&output_count, 1) 176 | } 177 | d <- true 178 | } 179 | 180 | func sortedCTParser(d chan string, c chan NewRecord) { 181 | 182 | for raw := range d { 183 | 184 | bits := strings.SplitN(raw, ",", 2) 185 | 186 | if len(bits) != 2 { 187 | continue 188 | } 189 | 190 | name := bits[0] 191 | data := bits[1] 192 | 193 | if len(name) == 0 || len(data) == 0 { 194 | atomic.AddInt64(&invalid_count, 1) 195 | continue 196 | } 197 | vals := strings.SplitN(data, "\x00", -1) 198 | 199 | outm := make(map[string][]string) 200 | for i := range vals { 201 | info := strings.SplitN(vals[i], ",", 2) 202 | if len(info) < 2 { 203 | continue 204 | } 205 | 206 | dkey := info[0] 207 | dval := info[1] 208 | 209 | cval, exists := outm[dkey] 210 | if exists { 211 | cval := append(cval, dval) 212 | outm[dkey] = cval 213 | } else { 214 | outm[dkey] = []string{dval} 215 | } 216 | 217 | } 218 | 219 | var outp [][]string 220 | 221 | for r := range outm { 222 | sorted_vals := outm[r] 223 | sort.Strings(sorted_vals) 224 | joined_vals := strings.Join(sorted_vals, " ") 225 | outp = append(outp, []string{r, joined_vals}) 226 | } 227 | 228 | json, e := json.Marshal(outp) 229 | if e != nil { 230 | fmt.Fprintf(os.Stderr, "[-] Could not marshal %v: %s\n", outm, e) 231 | continue 232 | } 233 | 234 | // Reverse the key unless its an IP address 235 | if !(inetdata.MatchIPv4.Match([]byte(name)) || inetdata.MatchIPv6.Match([]byte(name))) { 236 | name = inetdata.ReverseKey(name) 237 | } 238 | 239 | c <- NewRecord{Key: []byte(name), Val: json} 240 | } 241 | 242 | close(c) 243 | 244 | wg_sorted_ct_parser.Done() 245 | } 246 | 247 | func parsedCTWriter(o <-chan string, fd io.WriteCloser) { 248 | for r := range o { 249 | fd.Write([]byte(r)) 250 | } 251 | wg_parsed_ct_writer.Done() 252 | } 253 | 254 | func rawCTReader(c <-chan string, o chan<- string) { 255 | 256 | for r := range c { 257 | var entry CTEntry 258 | 259 | if err := json.Unmarshal([]byte(r), &entry); err != nil { 260 | fmt.Fprintf(os.Stderr, "Error parsing input: %s\n", r) 261 | continue 262 | } 263 | 264 | var leaf ct.MerkleTreeLeaf 265 | 266 | if rest, err := tls.Unmarshal(entry.LeafInput, &leaf); err != nil { 267 | fmt.Fprintf(os.Stderr, "Failed to unmarshal MerkleTreeLeaf: %v (%s)", err, r) 268 | continue 269 | } else if len(rest) > 0 { 270 | fmt.Fprintf(os.Stderr, "Trailing data (%d bytes) after MerkleTreeLeaf: %q", len(rest), rest) 271 | continue 272 | } 273 | 274 | var cert *x509.Certificate 275 | var err error 276 | 277 | switch leaf.TimestampedEntry.EntryType { 278 | case ct.X509LogEntryType: 279 | 280 | cert, err = x509.ParseCertificate(leaf.TimestampedEntry.X509Entry.Data) 281 | if err != nil && !strings.Contains(err.Error(), "NonFatalErrors:") { 282 | fmt.Fprintf(os.Stderr, "Failed to parse cert: %s\n", err.Error()) 283 | continue 284 | } 285 | 286 | case ct.PrecertLogEntryType: 287 | 288 | cert, err = x509.ParseTBSCertificate(leaf.TimestampedEntry.PrecertEntry.TBSCertificate) 289 | if err != nil && !strings.Contains(err.Error(), "NonFatalErrors:") { 290 | fmt.Fprintf(os.Stderr, "Failed to parse precert: %s\n", err.Error()) 291 | continue 292 | } 293 | 294 | default: 295 | fmt.Fprintf(os.Stderr, "Unknown entry type: %v (%s)", leaf.TimestampedEntry.EntryType, r) 296 | continue 297 | } 298 | 299 | // Valid input 300 | atomic.AddInt64(&input_count, 1) 301 | 302 | var names = make(map[string]struct{}) 303 | 304 | if _, err := publicsuffix.EffectiveTLDPlusOne(cert.Subject.CommonName); err == nil { 305 | // Make sure this looks like an actual hostname or IP address 306 | if !(inetdata.MatchIPv4.Match([]byte(cert.Subject.CommonName)) || 307 | inetdata.MatchIPv6.Match([]byte(cert.Subject.CommonName))) && 308 | (strings.Contains(cert.Subject.CommonName, " ") || 309 | strings.Contains(cert.Subject.CommonName, ":")) { 310 | continue 311 | } 312 | names[strings.ToLower(cert.Subject.CommonName)] = struct{}{} 313 | } 314 | 315 | for _, alt := range cert.DNSNames { 316 | if _, err := publicsuffix.EffectiveTLDPlusOne(alt); err == nil { 317 | // Make sure this looks like an actual hostname or IP address 318 | if !(inetdata.MatchIPv4.Match([]byte(cert.Subject.CommonName)) || 319 | inetdata.MatchIPv6.Match([]byte(cert.Subject.CommonName))) && 320 | (strings.Contains(alt, " ") || 321 | strings.Contains(alt, ":")) { 322 | continue 323 | } 324 | names[strings.ToLower(alt)] = struct{}{} 325 | } 326 | } 327 | 328 | sha1hash := "" 329 | 330 | // Write the names to the output channel 331 | for n := range names { 332 | if len(sha1hash) == 0 { 333 | sha1 := sha1.Sum(cert.Raw) 334 | sha1hash = hex.EncodeToString(sha1[:]) 335 | } 336 | 337 | // Dump associated email addresses if available 338 | for _, extra := range cert.EmailAddresses { 339 | o <- fmt.Sprintf("%s,email,%s\n", n, strings.ToLower(scrubX509Value(extra))) 340 | } 341 | 342 | // Dump associated IP addresses if we have at least one name 343 | for _, extra := range cert.IPAddresses { 344 | o <- fmt.Sprintf("%s,ip,%s\n", n, extra) 345 | } 346 | 347 | o <- fmt.Sprintf("%s,ts,%d\n", n, leaf.TimestampedEntry.Timestamp) 348 | o <- fmt.Sprintf("%s,cn,%s\n", n, strings.ToLower(scrubX509Value(cert.Subject.CommonName))) 349 | o <- fmt.Sprintf("%s,sha1,%s\n", n, sha1hash) 350 | 351 | // Dump associated SANs (overkill, but saves a second lookup) 352 | for _, extra := range cert.DNSNames { 353 | o <- fmt.Sprintf("%s,dns,%s\n", n, strings.ToLower(extra)) 354 | } 355 | } 356 | } 357 | 358 | wg_raw_ct_input.Done() 359 | } 360 | 361 | func main() { 362 | 363 | runtime.GOMAXPROCS(runtime.NumCPU()) 364 | os.Setenv("LC_ALL", "C") 365 | 366 | flag.Usage = func() { usage() } 367 | compression := flag.String("c", "snappy", "The compression type to use (none, snappy, zlib, lz4, lz4hc)") 368 | sort_tmp := flag.String("t", "", "The temporary directory to use for the sorting phase") 369 | sort_mem := flag.Uint64("m", 1, "The maximum amount of memory to use, in gigabytes, for the sorting phases") 370 | selected_merge_mode := flag.String("M", "combine", "The merge mode: combine, first, or last") 371 | version := flag.Bool("version", false, "Show the version and build timestamp") 372 | 373 | flag.Parse() 374 | 375 | if *version { 376 | inetdata.PrintVersion("inetdata-ct2mtbl") 377 | os.Exit(0) 378 | } 379 | 380 | if len(*sort_tmp) == 0 { 381 | *sort_tmp = os.Getenv("HOME") 382 | } 383 | 384 | if len(*sort_tmp) == 0 { 385 | flag.Usage() 386 | os.Exit(1) 387 | } 388 | 389 | // Configure the MTBL output 390 | 391 | if len(flag.Args()) != 1 { 392 | usage() 393 | os.Exit(1) 394 | } 395 | 396 | switch *selected_merge_mode { 397 | case "combine": 398 | merge_mode = MERGE_MODE_COMBINE 399 | case "first": 400 | merge_mode = MERGE_MODE_FIRST 401 | case "last": 402 | merge_mode = MERGE_MODE_LAST 403 | default: 404 | fmt.Fprintf(os.Stderr, "Error: Invalid merge mode specified: %s\n", *selected_merge_mode) 405 | usage() 406 | os.Exit(1) 407 | } 408 | 409 | fname := flag.Args()[0] 410 | _ = os.Remove(fname) 411 | 412 | sort_opt := mtbl.SorterOptions{Merge: mergeFunc, MaxMemory: 1024 * 1024} 413 | sort_opt.MaxMemory *= (*sort_mem * 1024) 414 | 415 | if len(*sort_tmp) > 0 { 416 | sort_opt.TempDir = *sort_tmp 417 | } 418 | 419 | compression_alg, ok := inetdata.MTBLCompressionTypes[*compression] 420 | if !ok { 421 | fmt.Fprintf(os.Stderr, "[-] Invalid compression algorithm: %s\n", *compression) 422 | os.Exit(1) 423 | } 424 | 425 | mtbl_sorter := mtbl.SorterInit(&sort_opt) 426 | mtbl_writer, w_e := mtbl.WriterInit(fname, &mtbl.WriterOptions{Compression: compression_alg}) 427 | if w_e != nil { 428 | fmt.Fprintf(os.Stderr, "[-] Error: %s\n", w_e) 429 | os.Exit(1) 430 | } 431 | 432 | defer mtbl_sorter.Destroy() 433 | defer mtbl_writer.Destroy() 434 | 435 | mtbl_sorter_ch := make(chan NewRecord, 1) 436 | mtbl_sorter_done := make(chan bool, 1) 437 | 438 | // Read from the mtbl_sorter_ch for NewRecords and write to the MTBL sorter 439 | go writeToMtbl(mtbl_sorter, mtbl_sorter_ch, mtbl_sorter_done) 440 | 441 | // Create the sort and rollup pipeline 442 | subprocs := []*exec.Cmd{} 443 | 444 | // Create a sort process 445 | sort_proc := exec.Command("nice", 446 | "sort", 447 | "-u", 448 | "--key=1", 449 | "--field-separator=,", 450 | "--compress-program=pigz", 451 | fmt.Sprintf("--parallel=%d", runtime.NumCPU()), 452 | fmt.Sprintf("--temporary-directory=%s", *sort_tmp), 453 | fmt.Sprintf("--buffer-size=%dG", *sort_mem)) 454 | 455 | // Configure stdio 456 | sort_stdin, sie := sort_proc.StdinPipe() 457 | if sie != nil { 458 | fmt.Fprintf(os.Stderr, "Error: failed to create sort stdin pipe: %s\n", sie) 459 | os.Exit(1) 460 | } 461 | 462 | sort_stdout, soe := sort_proc.StdoutPipe() 463 | if soe != nil { 464 | fmt.Fprintf(os.Stderr, "Error: failed to create sort stdout pipe: %s\n", soe) 465 | os.Exit(1) 466 | } 467 | 468 | sort_proc.Stderr = os.Stderr 469 | subprocs = append(subprocs, sort_proc) 470 | 471 | // Start the sort process 472 | if e := sort_proc.Start(); e != nil { 473 | fmt.Fprintf(os.Stderr, "Error: failed to execute the sort command: %s\n", e) 474 | os.Exit(1) 475 | } 476 | 477 | // Create the inetdata-csvrollup process 478 | roll_proc := exec.Command("nice", "inetdata-csvrollup") 479 | 480 | // Configure stdio 481 | roll_stdout, roe := roll_proc.StdoutPipe() 482 | if roe != nil { 483 | fmt.Fprintf(os.Stderr, "Error: failed to create sort stdout pipe: %s\n", roe) 484 | os.Exit(1) 485 | } 486 | 487 | roll_proc.Stderr = os.Stderr 488 | roll_proc.Stdin = sort_stdout 489 | subprocs = append(subprocs, roll_proc) 490 | 491 | // Start the rollup process 492 | if e := roll_proc.Start(); e != nil { 493 | fmt.Fprintf(os.Stderr, "Error: failed to execute the inetdata-csvrollup command: %s\n", e) 494 | os.Exit(1) 495 | } 496 | 497 | // Create a second sort process 498 | sort2_proc := exec.Command("nice", 499 | "sort", 500 | "-u", 501 | "--key=1", 502 | "--field-separator=,", 503 | "--compress-program=pigz", 504 | fmt.Sprintf("--parallel=%d", runtime.NumCPU()), 505 | fmt.Sprintf("--temporary-directory=%s", *sort_tmp), 506 | fmt.Sprintf("--buffer-size=%dG", *sort_mem)) 507 | 508 | sort2_stdout, ssoe := sort2_proc.StdoutPipe() 509 | if ssoe != nil { 510 | fmt.Fprintf(os.Stderr, "Error: failed to create sort stdout pipe: %s\n", ssoe) 511 | os.Exit(1) 512 | } 513 | sort2_proc.Stdin = roll_stdout 514 | sort2_proc.Stderr = os.Stderr 515 | 516 | subprocs = append(subprocs, sort2_proc) 517 | 518 | // Start the sort process 519 | if e := sort2_proc.Start(); e != nil { 520 | fmt.Fprintf(os.Stderr, "Error: failed to execute the second sort command: %s\n", e) 521 | os.Exit(1) 522 | } 523 | 524 | // Read rollup entries, convert to json, send to the MTBL writer 525 | c_ct_sorted_output := make(chan string) 526 | wg_sorted_ct_parser.Add(1) 527 | go sortedCTParser(c_ct_sorted_output, mtbl_sorter_ch) 528 | 529 | wg_sort_reader.Add(1) 530 | go func() { 531 | // Read rollup entries from the sort pipe and send to the parser 532 | e := inetdata.ReadLinesFromReader(sort2_stdout, c_ct_sorted_output) 533 | if e != nil { 534 | fmt.Fprintf(os.Stderr, "Error reading sort 2 input: %s\n", e) 535 | os.Exit(1) 536 | } 537 | wg_sort_reader.Done() 538 | }() 539 | 540 | // Start the progress tracker 541 | quit := make(chan int) 542 | go showProgress(quit) 543 | 544 | // Large channel buffer evens out spikey per-record processing time 545 | c_ct_raw_input := make(chan string, 4096) 546 | 547 | // Output 548 | c_ct_parsed_output := make(chan string) 549 | 550 | // Launch one input parser per core 551 | wg_raw_ct_input.Add(runtime.NumCPU()) 552 | for i := 0; i < runtime.NumCPU(); i++ { 553 | go rawCTReader(c_ct_raw_input, c_ct_parsed_output) 554 | } 555 | 556 | // Launch a writer that feeds parsed entries into the sort input pipe 557 | wg_parsed_ct_writer.Add(1) 558 | go parsedCTWriter(c_ct_parsed_output, sort_stdin) 559 | 560 | // Read CT JSON from stdin, parse, and send to sort 561 | e := inetdata.ReadLines(os.Stdin, c_ct_raw_input) 562 | if e != nil { 563 | fmt.Fprintf(os.Stderr, "Error reading input: %s\n", e) 564 | } 565 | 566 | // Wait for the input parsers 567 | wg_raw_ct_input.Wait() 568 | 569 | // Close the output handle 570 | close(c_ct_parsed_output) 571 | 572 | // Wait for the output goroutine 573 | wg_parsed_ct_writer.Wait() 574 | 575 | // Close the sort 1 input pipe 576 | sort_stdin.Close() 577 | 578 | // Wait for the sort reader 579 | wg_sort_reader.Wait() 580 | 581 | // Wait for subproceses to complete 582 | for i := range subprocs { 583 | subprocs[i].Wait() 584 | } 585 | 586 | // Wait for the sortedCT processor 587 | wg_sorted_ct_parser.Wait() 588 | 589 | // Wait for the MTBL sorter to finish 590 | <-mtbl_sorter_done 591 | 592 | // Finalize the MTBL sorter with a write 593 | if e = mtbl_sorter.Write(mtbl_writer); e != nil { 594 | fmt.Fprintf(os.Stderr, "[-] Error writing MTBL: %s\n", e) 595 | os.Exit(1) 596 | } 597 | 598 | // Stop the progress monitor 599 | quit <- 0 600 | } 601 | -------------------------------------------------------------------------------- /cmd/inetdata-arin-xml2csv/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Convert ARIN Bulk XML into CSV output 4 | 5 | import ( 6 | "encoding/csv" 7 | "encoding/xml" 8 | "flag" 9 | "fmt" 10 | "os" 11 | "regexp" 12 | "strings" 13 | ) 14 | 15 | // ARIN POC Record 16 | 17 | type ARIN_POC_emails struct { 18 | Email []string `xml:"email,omitempty" json:"email,omitempty"` 19 | } 20 | 21 | type ARIN_POC_iso3166_1 struct { 22 | Code2 string `xml:"code2,omitempty" json:"code2,omitempty"` 23 | Code3 string `xml:"code3,omitempty" json:"code3,omitempty"` 24 | E164 string `xml:"e164,omitempty" json:"e164,omitempty"` 25 | Name string `xml:"name,omitempty" json:"name,omitempty"` 26 | } 27 | 28 | type ARIN_POC_line struct { 29 | Number string `xml:"number,attr" json:",omitempty"` 30 | Text string `xml:",chardata" json:",omitempty"` 31 | } 32 | 33 | type ARIN_POC_name struct { 34 | Text string `xml:",chardata" json:",omitempty"` 35 | } 36 | 37 | type ARIN_POC_number struct { 38 | PhoneNumber string `xml:"phoneNumber,omitempty" json:"phoneNumber,omitempty"` 39 | PhoneType string `xml:"phoneType,omitempty" json:"phoneType,omitempty"` 40 | PocHandle string `xml:"pocHandle,omitempty" json:"pocHandle,omitempty"` 41 | } 42 | 43 | type ARIN_POC_phone struct { 44 | Number *ARIN_POC_number `xml:"number,omitempty" json:"number,omitempty"` 45 | Type *ARIN_POC_type `xml:"type,omitempty" json:"type,omitempty"` 46 | } 47 | 48 | type ARIN_POC_phones struct { 49 | Phone []*ARIN_POC_phone `xml:"phone,omitempty" json:"phone,omitempty"` 50 | } 51 | 52 | type ARIN_POC_poc struct { 53 | City string `xml:"city,omitempty" json:"city,omitempty"` 54 | Emails *ARIN_POC_emails `xml:"emails,omitempty" json:"emails,omitempty"` 55 | FirstName string `xml:"firstName,omitempty" json:"firstName,omitempty"` 56 | Handle string `xml:"handle,omitempty" json:"handle,omitempty"` 57 | IsRoleAccount string `xml:"isRoleAccount,omitempty" json:"isRoleAccount,omitempty"` 58 | Iso3166_1 *ARIN_POC_iso3166_1 `xml:"iso3166-1,omitempty" json:"iso3166-1,omitempty"` 59 | Iso3166_2 string `xml:"iso3166-2,omitempty" json:"iso3166-2,omitempty"` 60 | LastName string `xml:"lastName,omitempty" json:"lastName,omitempty"` 61 | Phones *ARIN_POC_phones `xml:"phones,omitempty" json:"phones,omitempty"` 62 | PostalCode string `xml:"postalCode,omitempty" json:"postalCode,omitempty"` 63 | Ref string `xml:"ref,omitempty" json:"ref,omitempty"` 64 | RegistrationDate string `xml:"registrationDate,omitempty" json:"registrationDate,omitempty"` 65 | StreetAddress *ARIN_POC_streetAddress `xml:"streetAddress,omitempty" json:"streetAddress,omitempty"` 66 | UpdateDate *string `xml:"updateDate,omitempty" json:"updateDate,omitempty"` 67 | } 68 | 69 | type ARIN_POC_streetAddress struct { 70 | Line []*ARIN_POC_line `xml:"line,omitempty" json:"line,omitempty"` 71 | } 72 | 73 | type ARIN_POC_type struct { 74 | Code string `xml:"code,omitempty" json:"code,omitempty"` 75 | Description string `xml:"description,omitempty" json:"description,omitempty"` 76 | } 77 | 78 | // ARIN Organization Record 79 | 80 | type ARIN_ORG_iso3166_1 struct { 81 | Code2 string `xml:"code2,omitempty" json:"code2,omitempty"` 82 | Code3 string `xml:"code3,omitempty" json:"code3,omitempty"` 83 | E164 string `xml:"e164,omitempty" json:"e164,omitempty"` 84 | Name string `xml:"name,omitempty" json:"name,omitempty"` 85 | } 86 | 87 | type ARIN_ORG_line struct { 88 | Number string `xml:"number,attr" json:",omitempty"` 89 | Text string `xml:",chardata" json:",omitempty"` 90 | } 91 | 92 | type ARIN_ORG_org struct { 93 | City string `xml:"city,omitempty" json:"city,omitempty"` 94 | Customer string `xml:"customer,omitempty" json:"customer,omitempty"` 95 | Handle string `xml:"handle,omitempty" json:"handle,omitempty"` 96 | Iso3166_1 *ARIN_ORG_iso3166_1 `xml:"iso3166-1,omitempty" json:"iso3166-1,omitempty"` 97 | Iso3166_2 string `xml:"iso3166-2,omitempty" json:"iso3166-2,omitempty"` 98 | Name string `xml:"name,omitempty" json:"name,omitempty"` 99 | PocLinks *ARIN_ORG_pocLinks `xml:"pocLinks,omitempty" json:"pocLinks,omitempty"` 100 | PostalCode string `xml:"postalCode,omitempty" json:"postalCode,omitempty"` 101 | Ref string `xml:"ref,omitempty" json:"ref,omitempty"` 102 | RegistrationDate string `xml:"registrationDate,omitempty" json:"registrationDate,omitempty"` 103 | StreetAddress *ARIN_ORG_streetAddress `xml:"streetAddress,omitempty" json:"streetAddress,omitempty"` 104 | UpdateDate string `xml:"updateDate,omitempty" json:"updateDate,omitempty"` 105 | } 106 | 107 | type ARIN_ORG_pocLink struct { 108 | Description string `xml:"description,attr" json:",omitempty"` 109 | Function string `xml:"function,attr" json:",omitempty"` 110 | Handle string `xml:"handle,attr" json:",omitempty"` 111 | } 112 | 113 | type ARIN_ORG_pocLinks struct { 114 | PocLink []*ARIN_ORG_pocLink `xml:"pocLink,omitempty" json:"pocLink,omitempty"` 115 | } 116 | 117 | type ARIN_ORG_streetAddress struct { 118 | Line []*ARIN_ORG_line `xml:"line,omitempty" json:"line,omitempty"` 119 | } 120 | 121 | // ARIN Network Record 122 | 123 | type ARIN_NET_net struct { 124 | EndAddress string `xml:"endAddress,omitempty" json:"endAddress,omitempty"` 125 | Handle string `xml:"handle,omitempty" json:"handle,omitempty"` 126 | Name string `xml:"name,omitempty" json:"name,omitempty"` 127 | NetBlocks *ARIN_NET_netBlocks `xml:"netBlocks,omitempty" json:"netBlocks,omitempty"` 128 | OrgHandle string `xml:"orgHandle,omitempty" json:"orgHandle,omitempty"` 129 | ParentNetHandle string `xml:"parentNetHandle,omitempty" json:"parentNetHandle,omitempty"` 130 | PocLinks *ARIN_NET_pocLinks `xml:"pocLinks,omitempty" json:"pocLinks,omitempty"` 131 | Ref string `xml:"ref,omitempty" json:"ref,omitempty"` 132 | RegistrationDate string `xml:"registrationDate,omitempty" json:"registrationDate,omitempty"` 133 | StartAddress string `xml:"startAddress,omitempty" json:"startAddress,omitempty"` 134 | UpdateDate string `xml:"updateDate,omitempty" json:"updateDate,omitempty"` 135 | Version string `xml:"version,omitempty" json:"version,omitempty"` 136 | } 137 | 138 | type ARIN_NET_netBlock struct { 139 | CidrLength string `xml:"cidrLength,omitempty" json:"cidrLength,omitempty"` 140 | EndAddress string `xml:"endAddress,omitempty" json:"endAddress,omitempty"` 141 | StartAddress string `xml:"startAddress,omitempty" json:"startAddress,omitempty"` 142 | Type string `xml:"type,omitempty" json:"type,omitempty"` 143 | } 144 | 145 | type ARIN_NET_netBlocks struct { 146 | NetBlock *ARIN_NET_netBlock `xml:"netBlock,omitempty" json:"netBlock,omitempty"` 147 | } 148 | 149 | type ARIN_NET_pocLink struct { 150 | Description string `xml:"description,attr" json:",omitempty"` 151 | Function string `xml:"function,attr" json:",omitempty"` 152 | Handle string `xml:"handle,attr" json:",omitempty"` 153 | } 154 | 155 | type ARIN_NET_pocLinks struct { 156 | PocLink []*ARIN_NET_pocLink `xml:"pocLink,omitempty" json:"pocLink,omitempty"` 157 | } 158 | 159 | // ARIN ASN Record 160 | 161 | type ARIN_ASN_asn struct { 162 | ARIN_ASN_comment *ARIN_ASN_comment `xml:"comment,omitempty" json:"comment,omitempty"` 163 | EndAsNumber string `xml:"endAsNumber,omitempty" json:"endAsNumber,omitempty"` 164 | Handle string `xml:"handle,omitempty" json:"handle,omitempty"` 165 | Name string `xml:"name,omitempty" json:"name,omitempty"` 166 | OrgHandle string `xml:"orgHandle,omitempty" json:"orgHandle,omitempty"` 167 | PocLinks *ARIN_ASN_pocLinks `xml:"pocLinks,omitempty" json:"pocLinks,omitempty"` 168 | Ref string `xml:"ref,omitempty" json:"ref,omitempty"` 169 | RegistrationDate string `xml:"registrationDate,omitempty" json:"registrationDate,omitempty"` 170 | StartAsNumber string `xml:"startAsNumber,omitempty" json:"startAsNumber,omitempty"` 171 | UpdateDate string `xml:"updateDate,omitempty" json:"updateDate,omitempty"` 172 | } 173 | 174 | type ARIN_ASN_comment struct { 175 | Line []*ARIN_ASN_line `xml:"line,omitempty" json:"line,omitempty"` 176 | } 177 | 178 | type ARIN_ASN_line struct { 179 | Number string `xml:"number,attr" json:",omitempty"` 180 | Text string `xml:",chardata" json:",omitempty"` 181 | } 182 | 183 | type ARIN_ASN_pocLink struct { 184 | Description string `xml:"description,attr" json:",omitempty"` 185 | Function string `xml:"function,attr" json:",omitempty"` 186 | Handle string `xml:"handle,attr" json:",omitempty"` 187 | } 188 | 189 | type ARIN_ASN_pocLinks struct { 190 | PocLink []*ARIN_ASN_pocLink `xml:"pocLink,omitempty" json:"pocLink,omitempty"` 191 | } 192 | 193 | func escapeBackslashes(s string) string { 194 | return strings.Replace(s, "\\", "\\\\", -1) 195 | } 196 | 197 | func scrubHighAscii(s string) string { 198 | re := regexp.MustCompile("[\u007f-\u00ff]") 199 | return re.ReplaceAllString(s, "?") 200 | } 201 | 202 | func escapeCell(s string) string { 203 | s = escapeBackslashes(s) 204 | s = scrubHighAscii(s) 205 | return s 206 | } 207 | 208 | func processFile(name string) { 209 | xmlFile, err := os.Open(name) 210 | if err != nil { 211 | fmt.Fprintf(os.Stderr, "Could not open file: %s\n", err.Error()) 212 | return 213 | } 214 | defer xmlFile.Close() 215 | 216 | writer := csv.NewWriter(os.Stdout) 217 | defer writer.Flush() 218 | 219 | decoder := xml.NewDecoder(xmlFile) 220 | var inElement string 221 | for { 222 | t, _ := decoder.Token() 223 | if t == nil { 224 | break 225 | } 226 | 227 | switch se := t.(type) { 228 | case xml.StartElement: 229 | inElement = se.Name.Local 230 | switch inElement { 231 | 232 | case "poc": 233 | value := ARIN_POC_poc{} 234 | 235 | decoder.DecodeElement(&value, &se) 236 | if value.Handle == "" { 237 | fmt.Fprintf(os.Stderr, "Could not decode record type %s\n", inElement) 238 | continue 239 | } 240 | 241 | record := []string{ 242 | value.Handle, 243 | "", // Email1 - 1 244 | "", // Email2 - 2 245 | "", // Email3 - 3 246 | value.FirstName, 247 | value.LastName, 248 | value.IsRoleAccount, 249 | "", // StreetAddress - 7 250 | value.City, 251 | value.Iso3166_2, 252 | value.PostalCode, 253 | value.Iso3166_1.Name, 254 | value.Iso3166_1.Code2, 255 | value.Iso3166_1.Code3, 256 | value.Iso3166_1.E164, 257 | "", // Phone1 - 13 258 | "", // Phone2 - 14 259 | "", // Phone3 - 15 260 | value.RegistrationDate, 261 | string(*value.UpdateDate), 262 | } 263 | 264 | if record[7] == "N" { 265 | record[7] = "false" 266 | } else { 267 | record[7] = "true" 268 | } 269 | 270 | // Extract the email addresses 271 | if value.Emails != nil && value.Emails.Email != nil { 272 | for ei := range value.Emails.Email { 273 | if ei > 2 { 274 | break 275 | } 276 | record[1+ei] = value.Emails.Email[ei] 277 | } 278 | } 279 | 280 | // Extract the street address 281 | if value.StreetAddress != nil && value.StreetAddress.Line != nil { 282 | address := "" 283 | for ei := range value.StreetAddress.Line { 284 | line := strings.Replace(value.StreetAddress.Line[ei].Text, "\t", " ", -1) 285 | if address == "" { 286 | address = line 287 | } else { 288 | address = address + "\t" + line 289 | } 290 | } 291 | record[7] = address 292 | } 293 | 294 | // Extract the phone numbers 295 | if value.Phones != nil && value.Phones.Phone != nil { 296 | for ei := range value.Phones.Phone { 297 | if value.Phones.Phone[ei] == nil { 298 | break 299 | } 300 | if ei > 2 { 301 | break 302 | } 303 | phone := value.Phones.Phone[ei] 304 | record[13+ei] = phone.Number.PhoneNumber 305 | } 306 | } 307 | 308 | // Sanitize the records 309 | for i := range record { 310 | record[i] = escapeCell(record[i]) 311 | } 312 | 313 | // Output CSV 314 | if err := writer.Write(record); err != nil { 315 | fmt.Fprintf(os.Stderr, "Could not write CSV: %v\n", record) 316 | continue 317 | } 318 | 319 | case "org": 320 | value := ARIN_ORG_org{} 321 | 322 | decoder.DecodeElement(&value, &se) 323 | if value.Handle == "" { 324 | fmt.Fprintf(os.Stderr, "Could not decode record type %s\n", inElement) 325 | continue 326 | } 327 | 328 | record := []string{ 329 | value.Handle, 330 | value.Name, 331 | value.Customer, 332 | "", // StreetAddress - 3 333 | value.City, 334 | value.Iso3166_2, 335 | value.PostalCode, 336 | value.Iso3166_1.Name, 337 | value.Iso3166_1.Code2, 338 | value.Iso3166_1.Code3, 339 | value.Iso3166_1.E164, 340 | "", // Admin POC - 11 341 | "", // NOC POC 342 | "", // Tech POC 343 | "", // Abuse POC 344 | value.RegistrationDate, 345 | value.UpdateDate, 346 | } 347 | 348 | if record[7] == "N" { 349 | record[7] = "false" 350 | } else { 351 | record[7] = "true" 352 | } 353 | 354 | // Extract the street address 355 | if value.StreetAddress != nil && value.StreetAddress.Line != nil { 356 | address := "" 357 | for ei := range value.StreetAddress.Line { 358 | line := strings.Replace(value.StreetAddress.Line[ei].Text, "\t", " ", -1) 359 | if address == "" { 360 | address = line 361 | } else { 362 | address = address + "\t" + line 363 | } 364 | } 365 | record[3] = address 366 | } 367 | 368 | // Extract the POC handles 369 | if value.PocLinks != nil && value.PocLinks.PocLink != nil { 370 | for ei := range value.PocLinks.PocLink { 371 | if value.PocLinks.PocLink[ei] == nil { 372 | continue 373 | } 374 | poc := value.PocLinks.PocLink[ei] 375 | 376 | switch poc.Description { 377 | case "Admin": 378 | record[11] = poc.Handle 379 | case "NOC": 380 | record[12] = poc.Handle 381 | case "Tech": 382 | record[13] = poc.Handle 383 | case "Abuse": 384 | record[14] = poc.Handle 385 | } 386 | } 387 | } 388 | 389 | // Sanitize the records 390 | for i := range record { 391 | record[i] = escapeCell(record[i]) 392 | } 393 | 394 | // Output CSV 395 | if err := writer.Write(record); err != nil { 396 | fmt.Fprintf(os.Stderr, "Could not write CSV: %v\n", record) 397 | continue 398 | } 399 | 400 | case "net": 401 | value := ARIN_NET_net{} 402 | 403 | decoder.DecodeElement(&value, &se) 404 | if value.Handle == "" { 405 | fmt.Fprintf(os.Stderr, "Could not decode record type %s\n", inElement) 406 | continue 407 | } 408 | 409 | record := []string{ 410 | value.Handle, 411 | value.ParentNetHandle, 412 | value.OrgHandle, 413 | value.Name, 414 | value.StartAddress, 415 | value.EndAddress, 416 | "", // Admin POC - 6 417 | "", // NOC POC 418 | "", // Tech POC 419 | "", // Abuse POC 420 | value.RegistrationDate, 421 | value.UpdateDate, 422 | value.Version, 423 | } 424 | 425 | // Extract the POC handles 426 | if value.PocLinks != nil && value.PocLinks.PocLink != nil { 427 | for ei := range value.PocLinks.PocLink { 428 | if value.PocLinks.PocLink[ei] == nil { 429 | continue 430 | } 431 | poc := value.PocLinks.PocLink[ei] 432 | 433 | switch poc.Description { 434 | case "Admin": 435 | record[6] = poc.Handle 436 | case "NOC": 437 | record[7] = poc.Handle 438 | case "Tech": 439 | record[8] = poc.Handle 440 | case "Abuse": 441 | record[9] = poc.Handle 442 | } 443 | } 444 | } 445 | 446 | // Sanitize the records 447 | for i := range record { 448 | record[i] = escapeCell(record[i]) 449 | } 450 | 451 | // Output CSV 452 | if err := writer.Write(record); err != nil { 453 | fmt.Fprintf(os.Stderr, "Could not write CSV: %v\n", record) 454 | continue 455 | } 456 | 457 | case "asn": 458 | value := ARIN_ASN_asn{} 459 | 460 | decoder.DecodeElement(&value, &se) 461 | if value.Handle == "" { 462 | fmt.Fprintf(os.Stderr, "Could not decode record type %s\n", inElement) 463 | continue 464 | } 465 | 466 | record := []string{ 467 | value.Handle, 468 | value.OrgHandle, 469 | value.Name, 470 | value.StartAsNumber, 471 | value.EndAsNumber, 472 | "", // Admin POC - 5 473 | "", // NOC POC 474 | "", // Tech POC 475 | "", // Abuse POC 476 | "", // AS Comments - 9 477 | value.RegistrationDate, 478 | value.UpdateDate, 479 | } 480 | 481 | // Extract the POC handles 482 | if value.PocLinks != nil && value.PocLinks.PocLink != nil { 483 | for ei := range value.PocLinks.PocLink { 484 | if value.PocLinks.PocLink[ei] == nil { 485 | continue 486 | } 487 | poc := value.PocLinks.PocLink[ei] 488 | 489 | switch poc.Description { 490 | case "Admin": 491 | record[5] = poc.Handle 492 | case "NOC": 493 | record[6] = poc.Handle 494 | case "Tech": 495 | record[7] = poc.Handle 496 | case "Abuse": 497 | record[8] = poc.Handle 498 | } 499 | } 500 | } 501 | 502 | // Extract the comments field 503 | if value.ARIN_ASN_comment != nil && value.ARIN_ASN_comment.Line != nil { 504 | comment := "" 505 | for ei := range value.ARIN_ASN_comment.Line { 506 | line := strings.Replace(value.ARIN_ASN_comment.Line[ei].Text, "\t", " ", -1) 507 | if comment == "" { 508 | comment = line 509 | } else { 510 | comment = comment + "\t" + line 511 | } 512 | } 513 | record[9] = comment 514 | } 515 | 516 | // Sanitize the records 517 | for i := range record { 518 | record[i] = escapeCell(record[i]) 519 | } 520 | 521 | // Output CSV 522 | if err := writer.Write(record); err != nil { 523 | fmt.Fprintf(os.Stderr, "Could not write CSV: %v\n", record) 524 | continue 525 | } 526 | 527 | } 528 | default: 529 | } 530 | } 531 | } 532 | 533 | func main() { 534 | flag.Parse() 535 | for i := range flag.Args() { 536 | processFile(flag.Args()[i]) 537 | } 538 | } 539 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | contrib.go.opencensus.io/exporter/stackdriver v0.12.1/go.mod h1:iwB6wGarfphGGe/e5CWqyUk/cLzKnWsOKPVW3no6OTw= 5 | contrib.go.opencensus.io/resource v0.1.1/go.mod h1:F361eGI91LCmW1I/Saf+rX0+OFcigGlFvXwEGEnkRLA= 6 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 7 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 8 | github.com/OpenPeeDeeP/depguard v1.0.0 h1:k9QF73nrHT3nPLz3lu6G5s+3Hi8Je36ODr1F5gjAXXM= 9 | github.com/OpenPeeDeeP/depguard v1.0.0/go.mod h1:7/4sitnI9YlQgTLLk734QlzXT8DuHVnAyztLplQjk+o= 10 | github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= 11 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 12 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 13 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 14 | github.com/aws/aws-sdk-go v1.19.18/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= 15 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 16 | github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= 17 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 18 | github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= 19 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 20 | github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 21 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 22 | github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s= 23 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 24 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 25 | github.com/coreos/etcd v3.3.13+incompatible h1:8F3hqu9fGYLBifCmRCJsicFqDx/D68Rt3q1JMazcgBQ= 26 | github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 27 | github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= 28 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 29 | github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= 30 | github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 31 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8= 32 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 33 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg= 34 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 35 | github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= 36 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 37 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 38 | github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= 39 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 40 | github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= 41 | github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 42 | github.com/fatih/color v1.6.0 h1:66qjqZk8kalYAvDRtM1AdAJQI0tj4Wrue3Eq3B3pmFU= 43 | github.com/fatih/color v1.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 44 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 45 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 46 | github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= 47 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 48 | github.com/go-critic/go-critic v0.3.5-0.20190526074819-1df300866540 h1:djv/qAomOVj8voCHt0M0OYwR/4vfDq1zNKSPKjJCexs= 49 | github.com/go-critic/go-critic v0.3.5-0.20190526074819-1df300866540/go.mod h1:+sE8vrLDS2M0pZkBk0wy6+nLdKexVDrl/jBqQOTDThA= 50 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 51 | github.com/go-lintpack/lintpack v0.5.2 h1:DI5mA3+eKdWeJ40nU4d6Wc26qmdG8RCi/btYq0TuRN0= 52 | github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM= 53 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 54 | github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= 55 | github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= 56 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 57 | github.com/go-toolsmith/astcast v1.0.0 h1:JojxlmI6STnFVG9yOImLeGREv8W2ocNUM+iOhR6jE7g= 58 | github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4= 59 | github.com/go-toolsmith/astcopy v1.0.0 h1:OMgl1b1MEpjFQ1m5ztEO06rz5CUd3oBv9RF7+DyvdG8= 60 | github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ= 61 | github.com/go-toolsmith/astequal v0.0.0-20180903214952-dcb477bfacd6/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= 62 | github.com/go-toolsmith/astequal v1.0.0 h1:4zxD8j3JRFNyLN46lodQuqz3xdKSrur7U/sr0SDS/gQ= 63 | github.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= 64 | github.com/go-toolsmith/astfmt v0.0.0-20180903215011-8f8ee99c3086/go.mod h1:mP93XdblcopXwlyN4X4uodxXQhldPGZbcEJIimQHrkg= 65 | github.com/go-toolsmith/astfmt v1.0.0 h1:A0vDDXt+vsvLEdbMFJAUBI/uTbRw1ffOPnxsILnFL6k= 66 | github.com/go-toolsmith/astfmt v1.0.0/go.mod h1:cnWmsOAuq4jJY6Ct5YWlVLmcmLMn1JUPuQIHCY7CJDw= 67 | github.com/go-toolsmith/astinfo v0.0.0-20180906194353-9809ff7efb21/go.mod h1:dDStQCHtmZpYOmjRP/8gHHnCCch3Zz3oEgCdZVdtweU= 68 | github.com/go-toolsmith/astp v0.0.0-20180903215135-0af7e3c24f30/go.mod h1:SV2ur98SGypH1UjcPpCatrV5hPazG6+IfNHbkDXBRrk= 69 | github.com/go-toolsmith/astp v1.0.0 h1:alXE75TXgcmupDsMK1fRAy0YUzLzqPVvBKoyWV+KPXg= 70 | github.com/go-toolsmith/astp v1.0.0/go.mod h1:RSyrtpVlfTFGDYRbrjyWP1pYu//tSFcvdYrA8meBmLI= 71 | github.com/go-toolsmith/pkgload v0.0.0-20181119091011-e9e65178eee8/go.mod h1:WoMrjiy4zvdS+Bg6z9jZH82QXwkcgCBX6nOfnmdaHks= 72 | github.com/go-toolsmith/pkgload v1.0.0/go.mod h1:5eFArkbO80v7Z0kdngIxsRXRMTaX4Ilcwuh3clNrQJc= 73 | github.com/go-toolsmith/strparse v1.0.0 h1:Vcw78DnpCAKlM20kSbAyO4mPfJn/lyYA4BJUDxe2Jb4= 74 | github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= 75 | github.com/go-toolsmith/typep v1.0.0 h1:zKymWyA1TRYvqYrYDrfEMZULyrhcnGY3x7LDKU2XQaA= 76 | github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= 77 | github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= 78 | github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= 79 | github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= 80 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 81 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= 82 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 83 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk= 84 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 85 | github.com/golang/mock v1.0.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 86 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 87 | github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk= 88 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 89 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 90 | github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= 91 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 92 | github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 h1:23T5iq8rbUYlhpt5DB4XJkc6BU31uODLD1o1gKvZmD0= 93 | github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4= 94 | github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9uMCefW1WDie15eSP/4MssdenaM= 95 | github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= 96 | github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6 h1:YYWNAGTKWhKpcLLt7aSj/odlKrSrelQwlovBpDuf19w= 97 | github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0= 98 | github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613 h1:9kfjN3AdxcbsZBf8NjltjWihK2QfBBBZuv91cMFfDHw= 99 | github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8= 100 | github.com/golangci/go-tools v0.0.0-20190318055746-e32c54105b7c h1:/7detzz5stiXWPzkTlPTzkBEIIE4WGpppBJYjKqBiPI= 101 | github.com/golangci/go-tools v0.0.0-20190318055746-e32c54105b7c/go.mod h1:unzUULGw35sjyOYjUt0jMTXqHlZPpPc6e+xfO4cd6mM= 102 | github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3 h1:pe9JHs3cHHDQgOFXJJdYkK6fLz2PWyYtP4hthoCMvs8= 103 | github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3/go.mod h1:JXrF4TWy4tXYn62/9x8Wm/K/dm06p8tCKwFRDPZG/1o= 104 | github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee h1:J2XAy40+7yz70uaOiMbNnluTg7gyQhtGqLQncQh+4J8= 105 | github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU= 106 | github.com/golangci/gofmt v0.0.0-20181222123516-0b8337e80d98 h1:0OkFarm1Zy2CjCiDKfK9XHgmc2wbDlRMD2hD8anAJHU= 107 | github.com/golangci/gofmt v0.0.0-20181222123516-0b8337e80d98/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU= 108 | github.com/golangci/golangci-lint v1.17.2-0.20190910081718-bad04bb7378f h1:Ocb3mZ76SbwTM6VKfiMPEppkwzitinZFRW9E6zAD5qc= 109 | github.com/golangci/golangci-lint v1.17.2-0.20190910081718-bad04bb7378f/go.mod h1:kaqo8l0OZKYPtjNmG4z4HrWLgcYNIJ9B9q3LWri9uLg= 110 | github.com/golangci/gosec v0.0.0-20190211064107-66fb7fc33547 h1:fUdgm/BdKvwOHxg5AhNbkNRp2mSy8sxTXyBVs/laQHo= 111 | github.com/golangci/gosec v0.0.0-20190211064107-66fb7fc33547/go.mod h1:0qUabqiIQgfmlAmulqxyiGkkyF6/tOGSnY2cnPVwrzU= 112 | github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc h1:gLLhTLMk2/SutryVJ6D4VZCU3CUqr8YloG7FPIBWFpI= 113 | github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc/go.mod h1:e5tpTHCfVze+7EpLEozzMB3eafxo2KT5veNg1k6byQU= 114 | github.com/golangci/lint-1 v0.0.0-20190420132249-ee948d087217 h1:En/tZdwhAn0JNwLuXzP3k2RVtMqMmOEK7Yu/g3tmtJE= 115 | github.com/golangci/lint-1 v0.0.0-20190420132249-ee948d087217/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg= 116 | github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca h1:kNY3/svz5T29MYHubXix4aDDuE3RWHkPvopM/EDv/MA= 117 | github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o= 118 | github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770 h1:EL/O5HGrF7Jaq0yNhBLucz9hTuRzj2LdwGBOaENgxIk= 119 | github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA= 120 | github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21 h1:leSNB7iYzLYSSx3J/s5sVf4Drkc68W2wm4Ixh/mr0us= 121 | github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21/go.mod h1:tf5+bzsHdTM0bsB7+8mt0GUMvjCgwLpTapNZHU8AajI= 122 | github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0 h1:HVfrLniijszjS1aiNg8JbBMO2+E1WIQ+j/gL4SQqGPg= 123 | github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4= 124 | github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 h1:zwtduBRr5SSWhqsYNgcuWO2kFlpdOZbP0+yRjmvPGys= 125 | github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ= 126 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 127 | github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= 128 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 129 | github.com/google/certificate-transparency-go v1.1.0 h1:10MlrYzh5wfkToxWI4yJzffsxLfxcEDlOATMx/V9Kzw= 130 | github.com/google/certificate-transparency-go v1.1.0/go.mod h1:i+Q7XY+ArBveOUT36jiHGfuSK1fHICIg6sUkRxPAbCs= 131 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 132 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 133 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 134 | github.com/google/monologue v0.0.0-20190606152607-4b11a32b5934 h1:0+3qDY6030dpAiEdmBqIsz3lg2SgXAvPEEq2sjm5UBk= 135 | github.com/google/monologue v0.0.0-20190606152607-4b11a32b5934/go.mod h1:6NTfaQoUpg5QmPsCUWLR3ig33FHrKXhTtWzF0DVdmuk= 136 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 137 | github.com/google/trillian v1.2.2-0.20190612132142-05461f4df60a/go.mod h1:YPmUVn5NGwgnDUgqlVyFGMTgaWlnSvH7W5p+NdOG8UA= 138 | github.com/google/trillian-examples v0.0.0-20190603134952-4e75ba15216c/go.mod h1:WgL3XZ3pA8/9cm7yxqWrZE6iZkESB2ItGxy5Fo6k2lk= 139 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 140 | github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= 141 | github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 142 | github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= 143 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 144 | github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3 h1:JVnpOZS+qxli+rgVl98ILOXVNbW+kb5wcxeGx8ShUIw= 145 | github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= 146 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c= 147 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 148 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= 149 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 150 | github.com/grpc-ecosystem/grpc-gateway v1.9.0 h1:bM6ZAFZmc/wPFaRDi0d5L7hGEZEx/2u+Tmr2evNHDiI= 151 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 152 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 153 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 154 | github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w= 155 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 156 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 157 | github.com/hdm/golang-mtbl v0.0.0-20180326181718-10a74bf74458 h1:p7uUUoHI+k03cboKoG28PZ12cN/LSKAu69b4DKjtX2Q= 158 | github.com/hdm/golang-mtbl v0.0.0-20180326181718-10a74bf74458/go.mod h1:Od9FYCD3juHFq+9szzlvxC8ZsL++m/mp11gWYLiJYOc= 159 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 160 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 161 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 162 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= 163 | github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= 164 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 165 | github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= 166 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 167 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 168 | github.com/juju/ratelimit v1.0.1/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk= 169 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 170 | github.com/kisielk/gotool v0.0.0-20161130080628-0de1eaf82fa3/go.mod h1:jxZFDH7ILpTPQTk+E2s+z4CUas9lVNjIuKR4c5/zKgM= 171 | github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= 172 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 173 | github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= 174 | github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= 175 | github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= 176 | github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= 177 | github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= 178 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 179 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 180 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 181 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 182 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 183 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 184 | github.com/letsencrypt/pkcs11key v2.0.1-0.20170608213348-396559074696+incompatible/go.mod h1:iGYXKqDXt0cpBthCHdr9ZdsQwyGlYFh/+8xa4WzIQ34= 185 | github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 186 | github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= 187 | github.com/magiconair/properties v1.7.6/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 188 | github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= 189 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 190 | github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= 191 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 192 | github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI= 193 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 194 | github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= 195 | github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 196 | github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 197 | github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= 198 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= 199 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 200 | github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= 201 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 202 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 203 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 204 | github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk= 205 | github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 206 | github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= 207 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 208 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 209 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 210 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= 211 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 212 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= 213 | github.com/mozilla/tls-observatory v0.0.0-20180409132520-8791a200eb40/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= 214 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 215 | github.com/nbutton23/zxcvbn-go v0.0.0-20160627004424-a22cb81b2ecd/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= 216 | github.com/nbutton23/zxcvbn-go v0.0.0-20171102151520-eafdab6b0663 h1:Ri1EhipkbhWsffPJ3IPlrb4SkTOPa2PfRXp3jchBczw= 217 | github.com/nbutton23/zxcvbn-go v0.0.0-20171102151520-eafdab6b0663/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= 218 | github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88= 219 | github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= 220 | github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 221 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 222 | github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= 223 | github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 224 | github.com/pelletier/go-toml v1.1.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 225 | github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= 226 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 227 | github.com/peterbourgon/mergemap v0.0.0-20130613134717-e21c03b7a721 h1:ArxMo6jAOO2KuRsepZ0hTaH4hZCi2CCW4P9PV59HHH0= 228 | github.com/peterbourgon/mergemap v0.0.0-20130613134717-e21c03b7a721/go.mod h1:jQyRpOpE/KbvPc0VKXjAqctYglwUO5W6zAcGcFfbvlo= 229 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 230 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 231 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 232 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 233 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 234 | github.com/prometheus/client_golang v0.9.4 h1:Y8E/JaaPbmFSW2V81Ab/d8yZFYQQGbni1b1jPcG9Y6A= 235 | github.com/prometheus/client_golang v0.9.4/go.mod h1:oCXIBxdI62A4cR6aTRJCgetEjecSIYzOEaeAn4iYEpM= 236 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 237 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= 238 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 239 | github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw= 240 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 241 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 242 | github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs= 243 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 244 | github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI= 245 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 246 | github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 247 | github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= 248 | github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= 249 | github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= 250 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 251 | github.com/shirou/gopsutil v0.0.0-20180427012116-c95755e4bcd7/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= 252 | github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= 253 | github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= 254 | github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= 255 | github.com/sirupsen/logrus v1.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= 256 | github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= 257 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 258 | github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E= 259 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 260 | github.com/sourcegraph/go-diff v0.5.1 h1:gO6i5zugwzo1RVTvgvfwCOSVegNuvnNi6bAD1QCmkHs= 261 | github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE= 262 | github.com/spf13/afero v1.1.0/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 263 | github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= 264 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 265 | github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= 266 | github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= 267 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 268 | github.com/spf13/cobra v0.0.2/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 269 | github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= 270 | github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= 271 | github.com/spf13/jwalterweatherman v0.0.0-20180109140146-7c0cea34c8ec/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 272 | github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= 273 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 274 | github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 275 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= 276 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 277 | github.com/spf13/viper v1.0.2/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= 278 | github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M= 279 | github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= 280 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 281 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 282 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 283 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 284 | github.com/timakin/bodyclose v0.0.0-20190721030226-87058b9bfcec h1:AmoEvWAO3nDx1MEcMzPh+GzOOIA5Znpv6++c7bePPY0= 285 | github.com/timakin/bodyclose v0.0.0-20190721030226-87058b9bfcec/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= 286 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ= 287 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 288 | github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4= 289 | github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= 290 | github.com/ultraware/funlen v0.0.1 h1:UeC9tpM4wNWzUJfan8z9sFE4QCzjjzlCZmuJN+aOkH0= 291 | github.com/ultraware/funlen v0.0.1/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= 292 | github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= 293 | github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= 294 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 295 | github.com/valyala/fasthttp v1.2.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s= 296 | github.com/valyala/quicktemplate v1.1.1/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4= 297 | github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= 298 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= 299 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 300 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 301 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 302 | go.etcd.io/etcd v3.3.13+incompatible h1:jCejD5EMnlGxFvcGRyEV4VGlENZc7oPQX6o0t7n3xbw= 303 | go.etcd.io/etcd v3.3.13+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI= 304 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 305 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 306 | go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= 307 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 308 | go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= 309 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 310 | go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= 311 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 312 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 313 | golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 314 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 315 | golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a h1:YX8ljsm6wXlHZO+aRz9Exqr0evNhKRNe5K/gi+zKh4U= 316 | golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 317 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 318 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 319 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 320 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 321 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 322 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 323 | golang.org/x/net v0.0.0-20170915142106-8351a756f30f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 324 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 325 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 326 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 327 | golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 328 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 329 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 330 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 331 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 332 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 333 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 334 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 335 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 336 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f h1:QBjCr1Fz5kw158VqdE9JfI9cJnl/ymnJWAdMuinqL7Y= 337 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 338 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 339 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 340 | golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 341 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 342 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 343 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 344 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 345 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 346 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 347 | golang.org/x/sys v0.0.0-20171026204733-164713f0dfce/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 348 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 349 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 350 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 351 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 352 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 353 | golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 354 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 355 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 356 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 357 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 358 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= 359 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 360 | golang.org/x/text v0.0.0-20170915090833-1cbadb444a80/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 361 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 362 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 363 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 364 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 365 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg= 366 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 367 | golang.org/x/tools v0.0.0-20170915040203-e531a2a1c15f/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 368 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 369 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 370 | golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 371 | golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 372 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 373 | golang.org/x/tools v0.0.0-20190121143147-24cd39ecf745/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 374 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 375 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 376 | golang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 377 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 378 | golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 379 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 380 | golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 381 | golang.org/x/tools v0.0.0-20190909030654-5b82db07426d h1:PhtdWYteEBebOX7KXm4qkIAVSUTHQ883/2hRB92r9lk= 382 | golang.org/x/tools v0.0.0-20190909030654-5b82db07426d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 383 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 384 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 385 | google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 386 | google.golang.org/api v0.6.0/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4= 387 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 388 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 389 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 390 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 391 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 392 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 393 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 394 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 395 | google.golang.org/genproto v0.0.0-20190605220351-eb0b1bdb6ae6 h1:XRqWpmQ5ACYxWuYX495S0sHawhPGOVrh62WzgXsQnWs= 396 | google.golang.org/genproto v0.0.0-20190605220351-eb0b1bdb6ae6/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= 397 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 398 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 399 | google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8= 400 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 401 | gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= 402 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 403 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 404 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 405 | gopkg.in/cheggaaa/pb.v1 v1.0.28 h1:n1tBJnnK2r7g9OW2btFH91V92STTUevLXYFb8gy9EMk= 406 | gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= 407 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 408 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 409 | gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= 410 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 411 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 412 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 413 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 414 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 415 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 416 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 417 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 418 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 419 | mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed h1:WX1yoOaKQfddO/mLzdV4wptyWgoH/6hwLs7QHTixo0I= 420 | mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= 421 | mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b h1:DxJ5nJdkhDlLok9K6qO+5290kphDJbHOQO1DFFFTeBo= 422 | mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= 423 | mvdan.cc/unparam v0.0.0-20190209190245-fbb59629db34 h1:duVSyluuJA+u0BnkcLR01smoLrGgDTfWt5c8ODYG8fU= 424 | mvdan.cc/unparam v0.0.0-20190209190245-fbb59629db34/go.mod h1:H6SUd1XjIs+qQCyskXg5OFSrilMRUkD8ePJpHKDPaeY= 425 | sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4 h1:JPJh2pk3+X4lXAkZIk2RuE/7/FoK9maXw+TNPJhVS/c= 426 | sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= 427 | --------------------------------------------------------------------------------