1package commands
2
3import (
4	"context"
5	"encoding/xml"
6	"flag"
7	"os"
8	"strings"
9	"time"
10
11	"github.com/google/subcommands"
12	"github.com/inconshreveable/log15"
13	c "github.com/kotakanbe/goval-dictionary/config"
14	"github.com/kotakanbe/goval-dictionary/db"
15	"github.com/kotakanbe/goval-dictionary/fetcher"
16	"github.com/kotakanbe/goval-dictionary/models"
17	"github.com/kotakanbe/goval-dictionary/util"
18	"github.com/ymomoi/goval-parser/oval"
19)
20
21// FetchUbuntuCmd is Subcommand for fetch RedHat OVAL
22type FetchUbuntuCmd struct {
23	Debug     bool
24	DebugSQL  bool
25	Quiet     bool
26	NoDetails bool
27	LogDir    string
28	LogJSON   bool
29	DBPath    string
30	DBType    string
31	HTTPProxy string
32}
33
34// Name return subcommand name
35func (*FetchUbuntuCmd) Name() string { return "fetch-ubuntu" }
36
37// Synopsis return synopsis
38func (*FetchUbuntuCmd) Synopsis() string { return "Fetch Vulnerability dictionary from Ubuntu" }
39
40// Usage return usage
41func (*FetchUbuntuCmd) Usage() string {
42	return `fetch-ubuntu:
43	fetch-ubuntu
44		[-dbtype=sqlite3|mysql|postgres|redis]
45		[-dbpath=$PWD/oval.sqlite3 or connection string]
46		[-http-proxy=http://192.168.0.1:8080]
47		[-debug]
48		[-debug-sql]
49		[-quiet]
50		[-no-details]
51		[-log-dir=/path/to/log]
52		[-log-json]
53
54For the first time, run the below command to fetch data for all versions.
55	$ goval-dictionary fetch-ubuntu 14 16 18 19 20
56
57`
58}
59
60// SetFlags set flag
61func (p *FetchUbuntuCmd) SetFlags(f *flag.FlagSet) {
62	f.BoolVar(&p.Debug, "debug", false, "debug mode")
63	f.BoolVar(&p.DebugSQL, "debug-sql", false, "SQL debug mode")
64	f.BoolVar(&p.Quiet, "quiet", false, "quiet mode (no output)")
65	f.BoolVar(&p.NoDetails, "no-details", false, "without vulnerability details")
66
67	defaultLogDir := util.GetDefaultLogDir()
68	f.StringVar(&p.LogDir, "log-dir", defaultLogDir, "/path/to/log")
69	f.BoolVar(&p.LogJSON, "log-json", false, "output log as JSON")
70
71	pwd := os.Getenv("PWD")
72	f.StringVar(&p.DBPath, "dbpath", pwd+"/oval.sqlite3",
73		"/path/to/sqlite3 or SQL connection string")
74
75	f.StringVar(&p.DBType, "dbtype", "sqlite3",
76		"Database type to store data in (sqlite3, mysql, postgres or redis supported)")
77
78	f.StringVar(
79		&p.HTTPProxy,
80		"http-proxy",
81		"",
82		"http://proxy-url:port (default: empty)",
83	)
84}
85
86// Execute execute
87func (p *FetchUbuntuCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
88	c.Conf.Quiet = p.Quiet
89	c.Conf.DebugSQL = p.DebugSQL
90	c.Conf.Debug = p.Debug
91	c.Conf.DBPath = p.DBPath
92	c.Conf.DBType = p.DBType
93	c.Conf.HTTPProxy = p.HTTPProxy
94	c.Conf.NoDetails = p.NoDetails
95
96	util.SetLogger(p.LogDir, c.Conf.Quiet, c.Conf.Debug, p.LogJSON)
97	if !c.Conf.Validate() {
98		return subcommands.ExitUsageError
99	}
100
101	if len(f.Args()) == 0 {
102		log15.Error("Specify versions to fetch")
103		return subcommands.ExitUsageError
104	}
105
106	driver, locked, err := db.NewDB(c.Ubuntu, c.Conf.DBType, c.Conf.DBPath, c.Conf.DebugSQL)
107	if err != nil {
108		if locked {
109			log15.Error("Failed to open DB. Close DB connection before fetching", "err", err)
110			return subcommands.ExitFailure
111		}
112		log15.Error("Failed to open DB", "err", err)
113		return subcommands.ExitFailure
114	}
115
116	// Distinct
117	v := map[string]bool{}
118	vers := []string{}
119	for _, arg := range f.Args() {
120		v[arg] = true
121	}
122	for k := range v {
123		vers = append(vers, k)
124	}
125
126	results, err := fetcher.FetchUbuntuFiles(vers)
127	if err != nil {
128		log15.Error("Failed to fetch files", "err", err)
129		return subcommands.ExitFailure
130	}
131
132	for _, r := range results {
133		ovalroot := oval.Root{}
134		if err = xml.Unmarshal(r.Body, &ovalroot); err != nil {
135			log15.Error("Failed to unmarshal", "url", r.URL, "err", err)
136			return subcommands.ExitUsageError
137		}
138		log15.Info("Fetched", "URL", r.URL, "OVAL definitions", len(ovalroot.Definitions.Definitions))
139
140		defs := models.ConvertUbuntuToModel(&ovalroot)
141
142		var timeformat = "2006-01-02T15:04:05"
143		t, err := time.Parse(timeformat, ovalroot.Generator.Timestamp)
144		if err != nil {
145			log15.Error("Failed to parse time", "err", err)
146			return subcommands.ExitFailure
147		}
148
149		root := models.Root{
150			Family:      c.Ubuntu,
151			OSVersion:   r.Target,
152			Definitions: defs,
153			Timestamp:   time.Now(),
154		}
155
156		ss := strings.Split(r.URL, "/")
157		fmeta := models.FetchMeta{
158			Timestamp: t,
159			FileName:  ss[len(ss)-1],
160		}
161
162		if err := driver.InsertOval(c.Ubuntu, &root, fmeta); err != nil {
163			log15.Error("Failed to insert OVAL", "err", err)
164			return subcommands.ExitFailure
165		}
166		if err := driver.InsertFetchMeta(fmeta); err != nil {
167			log15.Error("Failed to insert meta", "err", err)
168			return subcommands.ExitFailure
169		}
170	}
171
172	return subcommands.ExitSuccess
173}
174