1package commands 2 3import ( 4 "context" 5 "flag" 6 "os" 7 "strconv" 8 "time" 9 10 "github.com/google/subcommands" 11 "github.com/kotakanbe/go-cve-dictionary/config" 12 c "github.com/kotakanbe/go-cve-dictionary/config" 13 db "github.com/kotakanbe/go-cve-dictionary/db" 14 jvn "github.com/kotakanbe/go-cve-dictionary/fetcher/jvn/xml" 15 log "github.com/kotakanbe/go-cve-dictionary/log" 16 "github.com/kotakanbe/go-cve-dictionary/models" 17 util "github.com/kotakanbe/go-cve-dictionary/util" 18) 19 20// FetchJvnCmd is Subcommand for fetch JVN information. 21type FetchJvnCmd struct { 22 debug bool 23 debugSQL bool 24 quiet bool 25 logDir string 26 logJSON bool 27 dbpath string 28 dbtype string 29 dumpPath string 30 latest bool 31 last2Y bool 32 years bool 33 httpProxy string 34 light bool 35 force bool 36} 37 38// Name return subcommand name 39func (*FetchJvnCmd) Name() string { return "fetchjvn" } 40 41// Synopsis return synopsis 42func (*FetchJvnCmd) Synopsis() string { return "Fetch Vulnerability dictionary from JVN" } 43 44// Usage return usage 45func (*FetchJvnCmd) Usage() string { 46 return `fetchjvn: 47 fetchjvn 48 [-latest] 49 [-last2y] 50 [-years] 1998 1999 ... 51 [-dbpath=$PWD/cve.sqlite3 or connection string] 52 [-dbtype=mysql|postgres|sqlite3|redis] 53 [-http-proxy=http://192.168.0.1:8080] 54 [-debug] 55 [-debug-sql] 56 [-quiet] 57 [-log-dir=/path/to/log] 58 [-log-json] 59 [-light] 60 [-force] 61 62` 63} 64 65// SetFlags set flag 66func (p *FetchJvnCmd) SetFlags(f *flag.FlagSet) { 67 f.BoolVar(&p.debug, "debug", false, "debug mode") 68 f.BoolVar(&p.debugSQL, "debug-sql", false, "SQL debug mode") 69 f.BoolVar(&p.quiet, "quiet", false, "quiet mode (no output)") 70 71 defaultLogDir := util.GetDefaultLogDir() 72 f.StringVar(&p.logDir, "log-dir", defaultLogDir, "/path/to/log") 73 f.BoolVar(&p.logJSON, "log-json", false, "output log as JSON") 74 75 pwd := os.Getenv("PWD") 76 f.StringVar(&p.dbpath, "dbpath", pwd+"/cve.sqlite3", 77 "/path/to/sqlite3 or SQL connection string") 78 79 f.StringVar(&p.dbtype, "dbtype", "sqlite3", 80 "Database type to store data in (sqlite3, mysql, postgres or redis supported)") 81 82 f.BoolVar(&p.latest, "latest", false, 83 "Refresh JVN data for latest.") 84 85 f.BoolVar(&p.last2Y, "last2y", false, 86 "Refresh JVN data in the last two years.") 87 88 f.BoolVar(&p.years, "years", false, 89 "Refresh JVN data of specific years.") 90 91 f.StringVar( 92 &p.httpProxy, 93 "http-proxy", 94 "", 95 "http://proxy-url:port (default: empty)", 96 ) 97 98 f.BoolVar(&p.light, "light", false, "Don't collect *HEAVY* CPE relate data") 99 f.BoolVar(&p.force, "force", false, "Force update") 100} 101 102// Execute execute 103func (p *FetchJvnCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { 104 c.Conf.Debug = p.debug 105 c.Conf.Quiet = p.quiet 106 c.Conf.DebugSQL = p.debugSQL 107 c.Conf.DBPath = p.dbpath 108 c.Conf.DBType = p.dbtype 109 // c.Conf.DumpPath = p.dumpPath 110 c.Conf.HTTPProxy = p.httpProxy 111 c.Conf.Light = p.light 112 c.Conf.Force = p.force 113 114 log.SetLogger(p.logDir, c.Conf.Quiet, c.Conf.Debug, p.logJSON) 115 if !c.Conf.Validate() { 116 return subcommands.ExitUsageError 117 } 118 119 years := []int{} 120 thisYear := time.Now().Year() 121 122 switch { 123 case p.latest: 124 years = append(years, c.Latest) 125 case p.last2Y: 126 for i := 0; i < 2; i++ { 127 years = append(years, thisYear-i) 128 } 129 years = append(years, c.Latest) 130 case p.years: 131 if len(f.Args()) == 0 { 132 log.Errorf("Specify years to fetch (from 1998 to %d)", thisYear) 133 return subcommands.ExitUsageError 134 } 135 for _, arg := range f.Args() { 136 year, err := strconv.Atoi(arg) 137 if err != nil || year < 1998 || time.Now().Year() < year { 138 log.Errorf("Specify years to fetch (from 1998 to %d), arg: %s", thisYear, arg) 139 return subcommands.ExitUsageError 140 } 141 found := false 142 for _, y := range years { 143 if y == year { 144 found = true 145 break 146 } 147 } 148 if !found { 149 years = append(years, year) 150 } 151 } 152 years = append(years, c.Latest) 153 default: 154 log.Errorf("specify -latest, -last2y or -years") 155 return subcommands.ExitUsageError 156 } 157 158 driver, locked, err := db.NewDB(c.Conf.DBType, c.Conf.DBPath, c.Conf.DebugSQL) 159 if err != nil { 160 if locked { 161 log.Errorf("Failed to Open DB. Close DB connection before fetching: %s", err) 162 return subcommands.ExitFailure 163 } 164 log.Errorf("%s", err) 165 return subcommands.ExitFailure 166 } 167 defer func() { 168 _ = driver.CloseDB() 169 }() 170 171 metas, err := jvn.FetchLatestFeedMeta(driver, years) 172 if err != nil { 173 log.Errorf("%s", err) 174 return subcommands.ExitFailure 175 } 176 177 if len(metas) == 0 { 178 log.Errorf("No meta files fetched") 179 return subcommands.ExitFailure 180 } 181 182 //TODO use meta.Status() 183 needUpdates := []models.FeedMeta{} 184 if config.Conf.Force { 185 needUpdates = metas 186 } else { 187 for _, m := range metas { 188 if m.Newly() { 189 needUpdates = append(needUpdates, m) 190 log.Infof("Newly : %s", m.URL) 191 } else if m.OutDated() { 192 needUpdates = append(needUpdates, m) 193 log.Infof("Outdated : %s", m.URL) 194 } else { 195 log.Infof("Up to date: %s", m.URL) 196 } 197 } 198 } 199 200 if len(needUpdates) == 0 { 201 log.Infof("Already up to date") 202 return subcommands.ExitSuccess 203 } 204 205 log.Infof("Fetching CVE information from JVN.") 206 cves, err := jvn.FetchConvert(needUpdates) 207 if err != nil { 208 log.Errorf("Failed to fetch JVN: %s", err) 209 return subcommands.ExitFailure 210 } 211 log.Infof("Fetched %d CVEs", len(cves)) 212 213 log.Infof("Inserting JVN into DB (%s).", driver.Name()) 214 if err := driver.InsertJvn(cves); err != nil { 215 log.Fatalf("Failed to insert. dbpath: %s, err: %s", c.Conf.DBPath, err) 216 return subcommands.ExitFailure 217 } 218 219 if err := jvn.UpdateMeta(driver, needUpdates); err != nil { 220 log.Fatalf("Failed to Update meta. dbpath: %s, err: %s", c.Conf.DBPath, err) 221 return subcommands.ExitFailure 222 } 223 return subcommands.ExitSuccess 224} 225