1package config
2
3import (
4	"errors"
5	"fmt"
6	"os"
7	"path/filepath"
8	"runtime"
9	"strconv"
10	"strings"
11
12	syslog "github.com/RackSec/srslog"
13	valid "github.com/asaskevich/govalidator"
14	log "github.com/sirupsen/logrus"
15	"golang.org/x/xerrors"
16)
17
18// Version of Vuls
19var Version = "`make build` or `make install` will show the version"
20
21// Revision of Git
22var Revision string
23
24// Conf has Configuration
25var Conf Config
26
27const (
28	// RedHat is
29	RedHat = "redhat"
30
31	// Debian is
32	Debian = "debian"
33
34	// Ubuntu is
35	Ubuntu = "ubuntu"
36
37	// CentOS is
38	CentOS = "centos"
39
40	// Fedora is
41	Fedora = "fedora"
42
43	// Amazon is
44	Amazon = "amazon"
45
46	// Oracle is
47	Oracle = "oracle"
48
49	// FreeBSD is
50	FreeBSD = "freebsd"
51
52	// Raspbian is
53	Raspbian = "raspbian"
54
55	// Windows is
56	Windows = "windows"
57
58	// OpenSUSE is
59	OpenSUSE = "opensuse"
60
61	// OpenSUSELeap is
62	OpenSUSELeap = "opensuse.leap"
63
64	// SUSEEnterpriseServer is
65	SUSEEnterpriseServer = "suse.linux.enterprise.server"
66
67	// SUSEEnterpriseDesktop is
68	SUSEEnterpriseDesktop = "suse.linux.enterprise.desktop"
69
70	// SUSEOpenstackCloud is
71	SUSEOpenstackCloud = "suse.openstack.cloud"
72
73	// Alpine is
74	Alpine = "alpine"
75)
76
77const (
78	// ServerTypePseudo is used for ServerInfo.Type
79	ServerTypePseudo = "pseudo"
80)
81
82//Config is struct of Configuration
83type Config struct {
84	Debug      bool   `json:"debug,omitempty"`
85	DebugSQL   bool   `json:"debugSQL,omitempty"`
86	Lang       string `json:"lang,omitempty"`
87	HTTPProxy  string `valid:"url" json:"httpProxy,omitempty"`
88	LogDir     string `json:"logDir,omitempty"`
89	ResultsDir string `json:"resultsDir,omitempty"`
90	Pipe       bool   `json:"pipe,omitempty"`
91	Quiet      bool   `json:"quiet,omitempty"`
92	NoProgress bool   `json:"noProgress,omitempty"`
93
94	Default       ServerInfo            `json:"default,omitempty"`
95	Servers       map[string]ServerInfo `json:"servers,omitempty"`
96	CvssScoreOver float64               `json:"cvssScoreOver,omitempty"`
97
98	IgnoreUnscoredCves    bool `json:"ignoreUnscoredCves,omitempty"`
99	IgnoreUnfixed         bool `json:"ignoreUnfixed,omitempty"`
100	IgnoreGitHubDismissed bool `json:"ignore_git_hub_dismissed,omitempty"`
101
102	SSHNative bool `json:"sshNative,omitempty"`
103	SSHConfig bool `json:"sshConfig,omitempty"`
104
105	ContainersOnly bool `json:"containersOnly,omitempty"`
106	LibsOnly       bool `json:"libsOnly,omitempty"`
107	WordPressOnly  bool `json:"wordpressOnly,omitempty"`
108
109	CacheDBPath     string `json:"cacheDBPath,omitempty"`
110	TrivyCacheDBDir string `json:"trivyCacheDBDir,omitempty"`
111
112	SkipBroken bool `json:"skipBroken,omitempty"`
113	Vvv        bool `json:"vvv,omitempty"`
114	UUID       bool `json:"uuid,omitempty"`
115	DetectIPS  bool `json:"detectIps,omitempty"`
116
117	CveDict    GoCveDictConf  `json:"cveDict,omitempty"`
118	OvalDict   GovalDictConf  `json:"ovalDict,omitempty"`
119	Gost       GostConf       `json:"gost,omitempty"`
120	Exploit    ExploitConf    `json:"exploit,omitempty"`
121	Metasploit MetasploitConf `json:"metasploit,omitempty"`
122
123	Slack    SlackConf    `json:"-"`
124	EMail    SMTPConf     `json:"-"`
125	HTTP     HTTPConf     `json:"-"`
126	Syslog   SyslogConf   `json:"-"`
127	AWS      AWS          `json:"-"`
128	Azure    Azure        `json:"-"`
129	Stride   StrideConf   `json:"-"`
130	HipChat  HipChatConf  `json:"-"`
131	ChatWork ChatWorkConf `json:"-"`
132	Telegram TelegramConf `json:"-"`
133	Saas     SaasConf     `json:"-"`
134
135	RefreshCve        bool `json:"refreshCve,omitempty"`
136	ToSlack           bool `json:"toSlack,omitempty"`
137	ToStride          bool `json:"toStride,omitempty"`
138	ToHipChat         bool `json:"toHipChat,omitempty"`
139	ToChatWork        bool `json:"toChatWork,omitempty"`
140	ToTelegram        bool `json:"ToTelegram,omitempty"`
141	ToEmail           bool `json:"toEmail,omitempty"`
142	ToSyslog          bool `json:"toSyslog,omitempty"`
143	ToLocalFile       bool `json:"toLocalFile,omitempty"`
144	ToS3              bool `json:"toS3,omitempty"`
145	ToAzureBlob       bool `json:"toAzureBlob,omitempty"`
146	ToSaas            bool `json:"toSaas,omitempty"`
147	ToHTTP            bool `json:"toHTTP,omitempty"`
148	FormatXML         bool `json:"formatXML,omitempty"`
149	FormatJSON        bool `json:"formatJSON,omitempty"`
150	FormatOneEMail    bool `json:"formatOneEMail,omitempty"`
151	FormatOneLineText bool `json:"formatOneLineText,omitempty"`
152	FormatList        bool `json:"formatList,omitempty"`
153	FormatFullText    bool `json:"formatFullText,omitempty"`
154	FormatCsvList     bool `json:"formatCsvList,omitempty"`
155	GZIP              bool `json:"gzip,omitempty"`
156	Diff              bool `json:"diff,omitempty"`
157	WpIgnoreInactive  bool `json:"wpIgnoreInactive,omitempty"`
158}
159
160// ValidateOnConfigtest validates
161func (c Config) ValidateOnConfigtest() bool {
162	errs := c.checkSSHKeyExist()
163
164	if runtime.GOOS == "windows" && !c.SSHNative {
165		errs = append(errs, xerrors.New("-ssh-native-insecure is needed on windows"))
166	}
167
168	_, err := valid.ValidateStruct(c)
169	if err != nil {
170		errs = append(errs, err)
171	}
172
173	for _, err := range errs {
174		log.Error(err)
175	}
176
177	return len(errs) == 0
178}
179
180// ValidateOnScan validates configuration
181func (c Config) ValidateOnScan() bool {
182	errs := c.checkSSHKeyExist()
183
184	if runtime.GOOS == "windows" && !c.SSHNative {
185		errs = append(errs, xerrors.New("-ssh-native-insecure is needed on windows"))
186	}
187
188	if len(c.ResultsDir) != 0 {
189		if ok, _ := valid.IsFilePath(c.ResultsDir); !ok {
190			errs = append(errs, xerrors.Errorf(
191				"JSON base directory must be a *Absolute* file path. -results-dir: %s", c.ResultsDir))
192		}
193	}
194
195	if len(c.CacheDBPath) != 0 {
196		if ok, _ := valid.IsFilePath(c.CacheDBPath); !ok {
197			errs = append(errs, xerrors.Errorf(
198				"Cache DB path must be a *Absolute* file path. -cache-dbpath: %s",
199				c.CacheDBPath))
200		}
201	}
202
203	_, err := valid.ValidateStruct(c)
204	if err != nil {
205		errs = append(errs, err)
206	}
207
208	for _, err := range errs {
209		log.Error(err)
210	}
211
212	return len(errs) == 0
213}
214
215func (c Config) checkSSHKeyExist() (errs []error) {
216	for serverName, v := range c.Servers {
217		if v.Type == ServerTypePseudo {
218			continue
219		}
220		if v.KeyPath != "" {
221			if _, err := os.Stat(v.KeyPath); err != nil {
222				errs = append(errs, xerrors.Errorf(
223					"%s is invalid. keypath: %s not exists", serverName, v.KeyPath))
224			}
225		}
226	}
227	return errs
228}
229
230// ValidateOnReportDB validates configuration
231func (c Config) ValidateOnReportDB() bool {
232	errs := []error{}
233
234	if err := validateDB("cvedb", c.CveDict.Type, c.CveDict.SQLite3Path, c.CveDict.URL); err != nil {
235		errs = append(errs, err)
236	}
237
238	if err := validateDB("ovaldb", c.OvalDict.Type, c.OvalDict.SQLite3Path, c.OvalDict.URL); err != nil {
239		errs = append(errs, err)
240	}
241
242	if err := validateDB("gostdb", c.Gost.Type, c.Gost.SQLite3Path, c.Gost.URL); err != nil {
243		errs = append(errs, err)
244	}
245
246	if err := validateDB("exploitdb", c.Exploit.Type, c.Exploit.SQLite3Path, c.Exploit.URL); err != nil {
247		errs = append(errs, err)
248	}
249
250	if err := validateDB("msfdb", c.Metasploit.Type, c.Metasploit.SQLite3Path, c.Metasploit.URL); err != nil {
251		errs = append(errs, err)
252	}
253
254	for _, err := range errs {
255		log.Error(err)
256	}
257
258	return len(errs) == 0
259}
260
261// ValidateOnReport validates configuration
262func (c Config) ValidateOnReport() bool {
263	errs := []error{}
264
265	if len(c.ResultsDir) != 0 {
266		if ok, _ := valid.IsFilePath(c.ResultsDir); !ok {
267			errs = append(errs, xerrors.Errorf(
268				"JSON base directory must be a *Absolute* file path. -results-dir: %s", c.ResultsDir))
269		}
270	}
271
272	_, err := valid.ValidateStruct(c)
273	if err != nil {
274		errs = append(errs, err)
275	}
276
277	if mailerrs := c.EMail.Validate(); 0 < len(mailerrs) {
278		errs = append(errs, mailerrs...)
279	}
280
281	if slackerrs := c.Slack.Validate(); 0 < len(slackerrs) {
282		errs = append(errs, slackerrs...)
283	}
284
285	if hipchaterrs := c.HipChat.Validate(); 0 < len(hipchaterrs) {
286		errs = append(errs, hipchaterrs...)
287	}
288
289	if chatworkerrs := c.ChatWork.Validate(); 0 < len(chatworkerrs) {
290		errs = append(errs, chatworkerrs...)
291	}
292
293	if strideerrs := c.Stride.Validate(); 0 < len(strideerrs) {
294		errs = append(errs, strideerrs...)
295	}
296
297	if telegramerrs := c.Telegram.Validate(); 0 < len(telegramerrs) {
298		errs = append(errs, telegramerrs...)
299	}
300
301	if saaserrs := c.Saas.Validate(); 0 < len(saaserrs) {
302		errs = append(errs, saaserrs...)
303	}
304
305	if syslogerrs := c.Syslog.Validate(); 0 < len(syslogerrs) {
306		errs = append(errs, syslogerrs...)
307	}
308
309	if httperrs := c.HTTP.Validate(); 0 < len(httperrs) {
310		errs = append(errs, httperrs...)
311	}
312
313	for _, err := range errs {
314		log.Error(err)
315	}
316
317	return len(errs) == 0
318}
319
320// ValidateOnTui validates configuration
321func (c Config) ValidateOnTui() bool {
322	errs := []error{}
323
324	if len(c.ResultsDir) != 0 {
325		if ok, _ := valid.IsFilePath(c.ResultsDir); !ok {
326			errs = append(errs, xerrors.Errorf(
327				"JSON base directory must be a *Absolute* file path. -results-dir: %s", c.ResultsDir))
328		}
329	}
330
331	if err := validateDB("cvedb", c.CveDict.Type, c.CveDict.SQLite3Path, c.CveDict.URL); err != nil {
332		errs = append(errs, err)
333	}
334
335	for _, err := range errs {
336		log.Error(err)
337	}
338
339	return len(errs) == 0
340}
341
342// validateDB validates configuration
343//  dictionaryDB name is 'cvedb' or 'ovaldb'
344func validateDB(dictionaryDBName, dbType, dbPath, dbURL string) error {
345	log.Infof("-%s-type: %s, -%s-url: %s, -%s-path: %s",
346		dictionaryDBName, dbType, dictionaryDBName, dbURL, dictionaryDBName, dbPath)
347
348	switch dbType {
349	case "sqlite3":
350		if dbURL != "" {
351			return xerrors.Errorf("To use SQLite3, specify -%s-type=sqlite3 and -%s-path. To use as http server mode, specify -%s-type=http and -%s-url",
352				dictionaryDBName, dictionaryDBName, dictionaryDBName, dictionaryDBName)
353		}
354		if ok, _ := valid.IsFilePath(dbPath); !ok {
355			return xerrors.Errorf("SQLite3 path must be a *Absolute* file path. -%s-path: %s",
356				dictionaryDBName, dbPath)
357		}
358	case "mysql":
359		if dbURL == "" {
360			return xerrors.Errorf(`MySQL connection string is needed. -%s-url="user:pass@tcp(localhost:3306)/dbname"`,
361				dictionaryDBName)
362		}
363	case "postgres":
364		if dbURL == "" {
365			return xerrors.Errorf(`PostgreSQL connection string is needed. -%s-url="host=myhost user=user dbname=dbname sslmode=disable password=password"`,
366				dictionaryDBName)
367		}
368	case "redis":
369		if dbURL == "" {
370			return xerrors.Errorf(`Redis connection string is needed. -%s-url="redis://localhost/0"`,
371				dictionaryDBName)
372		}
373	case "http":
374		if dbURL == "" {
375			return xerrors.Errorf(`URL is needed. -%s-url="http://localhost:1323"`,
376				dictionaryDBName)
377		}
378	default:
379		return xerrors.Errorf("%s type must be either 'sqlite3', 'mysql', 'postgres', 'redis' or 'http'.  -%s-type: %s",
380			dictionaryDBName, dictionaryDBName, dbType)
381	}
382	return nil
383}
384
385// SMTPConf is smtp config
386type SMTPConf struct {
387	SMTPAddr      string   `toml:"smtpAddr,omitempty" json:"-"`
388	SMTPPort      string   `toml:"smtpPort,omitempty" valid:"port" json:"-"`
389	User          string   `toml:"user,omitempty" json:"-"`
390	Password      string   `toml:"password,omitempty" json:"-"`
391	From          string   `toml:"from,omitempty" json:"-"`
392	To            []string `toml:"to,omitempty" json:"-"`
393	Cc            []string `toml:"cc,omitempty" json:"-"`
394	SubjectPrefix string   `toml:"subjectPrefix,omitempty" json:"-"`
395}
396
397func checkEmails(emails []string) (errs []error) {
398	for _, addr := range emails {
399		if len(addr) == 0 {
400			return
401		}
402		if ok := valid.IsEmail(addr); !ok {
403			errs = append(errs, xerrors.Errorf("Invalid email address. email: %s", addr))
404		}
405	}
406	return
407}
408
409// Validate SMTP configuration
410func (c *SMTPConf) Validate() (errs []error) {
411	if !Conf.ToEmail {
412		return
413	}
414	// Check Emails fromat
415	emails := []string{}
416	emails = append(emails, c.From)
417	emails = append(emails, c.To...)
418	emails = append(emails, c.Cc...)
419
420	if emailErrs := checkEmails(emails); 0 < len(emailErrs) {
421		errs = append(errs, emailErrs...)
422	}
423
424	if len(c.SMTPAddr) == 0 {
425		errs = append(errs, xerrors.New("email.smtpAddr must not be empty"))
426	}
427	if len(c.SMTPPort) == 0 {
428		errs = append(errs, xerrors.New("email.smtpPort must not be empty"))
429	}
430	if len(c.To) == 0 {
431		errs = append(errs, xerrors.New("email.To required at least one address"))
432	}
433	if len(c.From) == 0 {
434		errs = append(errs, xerrors.New("email.From required at least one address"))
435	}
436
437	_, err := valid.ValidateStruct(c)
438	if err != nil {
439		errs = append(errs, err)
440	}
441	return
442}
443
444// StrideConf is stride config
445type StrideConf struct {
446	HookURL   string `json:"-"`
447	AuthToken string `json:"-"`
448}
449
450// Validate validates configuration
451func (c *StrideConf) Validate() (errs []error) {
452	if !Conf.ToStride {
453		return
454	}
455
456	if len(c.HookURL) == 0 {
457		errs = append(errs, xerrors.New("stride.HookURL must not be empty"))
458	}
459
460	if len(c.AuthToken) == 0 {
461		errs = append(errs, xerrors.New("stride.AuthToken must not be empty"))
462	}
463
464	_, err := valid.ValidateStruct(c)
465	if err != nil {
466		errs = append(errs, err)
467	}
468	return
469}
470
471// SlackConf is slack config
472type SlackConf struct {
473	HookURL     string   `valid:"url" json:"-" toml:"hookURL,omitempty"`
474	LegacyToken string   `json:"-" toml:"legacyToken,omitempty"`
475	Channel     string   `json:"-" toml:"channel,omitempty"`
476	IconEmoji   string   `json:"-" toml:"iconEmoji,omitempty"`
477	AuthUser    string   `json:"-" toml:"authUser,omitempty"`
478	NotifyUsers []string `toml:"notifyUsers,omitempty" json:"-"`
479	Text        string   `json:"-"`
480}
481
482// Validate validates configuration
483func (c *SlackConf) Validate() (errs []error) {
484	if !Conf.ToSlack {
485		return
486	}
487
488	if len(c.HookURL) == 0 && len(c.LegacyToken) == 0 {
489		errs = append(errs, xerrors.New("slack.hookURL or slack.LegacyToken must not be empty"))
490	}
491
492	if len(c.Channel) == 0 {
493		errs = append(errs, xerrors.New("slack.channel must not be empty"))
494	} else {
495		if !(strings.HasPrefix(c.Channel, "#") ||
496			c.Channel == "${servername}") {
497			errs = append(errs, xerrors.Errorf(
498				"channel's prefix must be '#', channel: %s", c.Channel))
499		}
500	}
501
502	if len(c.AuthUser) == 0 {
503		errs = append(errs, xerrors.New("slack.authUser must not be empty"))
504	}
505
506	_, err := valid.ValidateStruct(c)
507	if err != nil {
508		errs = append(errs, err)
509	}
510
511	return
512}
513
514// HipChatConf is HipChat config
515type HipChatConf struct {
516	AuthToken string `json:"-"`
517	Room      string `json:"-"`
518}
519
520// Validate validates configuration
521func (c *HipChatConf) Validate() (errs []error) {
522	if !Conf.ToHipChat {
523		return
524	}
525	if len(c.Room) == 0 {
526		errs = append(errs, xerrors.New("hipchat.room must not be empty"))
527	}
528
529	if len(c.AuthToken) == 0 {
530		errs = append(errs, xerrors.New("hipchat.AuthToken must not be empty"))
531	}
532
533	_, err := valid.ValidateStruct(c)
534	if err != nil {
535		errs = append(errs, err)
536	}
537	return
538}
539
540// ChatWorkConf is ChatWork config
541type ChatWorkConf struct {
542	APIToken string `json:"-"`
543	Room     string `json:"-"`
544}
545
546// Validate validates configuration
547func (c *ChatWorkConf) Validate() (errs []error) {
548	if !Conf.ToChatWork {
549		return
550	}
551	if len(c.Room) == 0 {
552		errs = append(errs, xerrors.New("chatWorkConf.room must not be empty"))
553	}
554
555	if len(c.APIToken) == 0 {
556		errs = append(errs, xerrors.New("chatWorkConf.ApiToken must not be empty"))
557	}
558
559	_, err := valid.ValidateStruct(c)
560	if err != nil {
561		errs = append(errs, err)
562	}
563	return
564}
565
566// TelegramConf is Telegram config
567type TelegramConf struct {
568	Token  string `json:"-"`
569	ChatID string `json:"-"`
570}
571
572// Validate validates configuration
573func (c *TelegramConf) Validate() (errs []error) {
574	if !Conf.ToTelegram {
575		return
576	}
577	if len(c.ChatID) == 0 {
578		errs = append(errs, xerrors.New("TelegramConf.ChatID must not be empty"))
579	}
580
581	if len(c.Token) == 0 {
582		errs = append(errs, xerrors.New("TelegramConf.Token must not be empty"))
583	}
584
585	_, err := valid.ValidateStruct(c)
586	if err != nil {
587		errs = append(errs, err)
588	}
589	return
590}
591
592// SaasConf is stride config
593type SaasConf struct {
594	GroupID int64  `json:"-"`
595	Token   string `json:"-"`
596	URL     string `json:"-"`
597}
598
599// Validate validates configuration
600func (c *SaasConf) Validate() (errs []error) {
601	if !Conf.ToSaas {
602		return
603	}
604
605	if c.GroupID == 0 {
606		errs = append(errs, xerrors.New("saas.GroupID must not be empty"))
607	}
608
609	if len(c.Token) == 0 {
610		errs = append(errs, xerrors.New("saas.Token must not be empty"))
611	}
612
613	if len(c.URL) == 0 {
614		errs = append(errs, xerrors.New("saas.URL must not be empty"))
615	}
616
617	_, err := valid.ValidateStruct(c)
618	if err != nil {
619		errs = append(errs, err)
620	}
621	return
622}
623
624// SyslogConf is syslog config
625type SyslogConf struct {
626	Protocol string `json:"-"`
627	Host     string `valid:"host" json:"-"`
628	Port     string `valid:"port" json:"-"`
629	Severity string `json:"-"`
630	Facility string `json:"-"`
631	Tag      string `json:"-"`
632	Verbose  bool   `json:"-"`
633}
634
635// Validate validates configuration
636func (c *SyslogConf) Validate() (errs []error) {
637	if !Conf.ToSyslog {
638		return nil
639	}
640	//  If protocol is empty, it will connect to the local syslog server.
641	if len(c.Protocol) > 0 && c.Protocol != "tcp" && c.Protocol != "udp" {
642		errs = append(errs, errors.New(`syslog.protocol must be "tcp" or "udp"`))
643	}
644
645	// Default port: 514
646	if c.Port == "" {
647		c.Port = "514"
648	}
649
650	if _, err := c.GetSeverity(); err != nil {
651		errs = append(errs, err)
652	}
653
654	if _, err := c.GetFacility(); err != nil {
655		errs = append(errs, err)
656	}
657
658	if _, err := valid.ValidateStruct(c); err != nil {
659		errs = append(errs, err)
660	}
661	return errs
662}
663
664// GetSeverity gets severity
665func (c *SyslogConf) GetSeverity() (syslog.Priority, error) {
666	if c.Severity == "" {
667		return syslog.LOG_INFO, nil
668	}
669
670	switch c.Severity {
671	case "emerg":
672		return syslog.LOG_EMERG, nil
673	case "alert":
674		return syslog.LOG_ALERT, nil
675	case "crit":
676		return syslog.LOG_CRIT, nil
677	case "err":
678		return syslog.LOG_ERR, nil
679	case "warning":
680		return syslog.LOG_WARNING, nil
681	case "notice":
682		return syslog.LOG_NOTICE, nil
683	case "info":
684		return syslog.LOG_INFO, nil
685	case "debug":
686		return syslog.LOG_DEBUG, nil
687	default:
688		return -1, xerrors.Errorf("Invalid severity: %s", c.Severity)
689	}
690}
691
692// GetFacility gets facility
693func (c *SyslogConf) GetFacility() (syslog.Priority, error) {
694	if c.Facility == "" {
695		return syslog.LOG_AUTH, nil
696	}
697
698	switch c.Facility {
699	case "kern":
700		return syslog.LOG_KERN, nil
701	case "user":
702		return syslog.LOG_USER, nil
703	case "mail":
704		return syslog.LOG_MAIL, nil
705	case "daemon":
706		return syslog.LOG_DAEMON, nil
707	case "auth":
708		return syslog.LOG_AUTH, nil
709	case "syslog":
710		return syslog.LOG_SYSLOG, nil
711	case "lpr":
712		return syslog.LOG_LPR, nil
713	case "news":
714		return syslog.LOG_NEWS, nil
715	case "uucp":
716		return syslog.LOG_UUCP, nil
717	case "cron":
718		return syslog.LOG_CRON, nil
719	case "authpriv":
720		return syslog.LOG_AUTHPRIV, nil
721	case "ftp":
722		return syslog.LOG_FTP, nil
723	case "local0":
724		return syslog.LOG_LOCAL0, nil
725	case "local1":
726		return syslog.LOG_LOCAL1, nil
727	case "local2":
728		return syslog.LOG_LOCAL2, nil
729	case "local3":
730		return syslog.LOG_LOCAL3, nil
731	case "local4":
732		return syslog.LOG_LOCAL4, nil
733	case "local5":
734		return syslog.LOG_LOCAL5, nil
735	case "local6":
736		return syslog.LOG_LOCAL6, nil
737	case "local7":
738		return syslog.LOG_LOCAL7, nil
739	default:
740		return -1, xerrors.Errorf("Invalid facility: %s", c.Facility)
741	}
742}
743
744// HTTPConf is HTTP config
745type HTTPConf struct {
746	URL string `valid:"url" json:"-"`
747}
748
749// Validate validates configuration
750func (c *HTTPConf) Validate() (errs []error) {
751	if !Conf.ToHTTP {
752		return nil
753	}
754
755	if _, err := valid.ValidateStruct(c); err != nil {
756		errs = append(errs, err)
757	}
758	return errs
759}
760
761const httpKey = "VULS_HTTP_URL"
762
763// Overwrite set options with the following priority.
764// 1. Command line option
765// 2. Environment variable
766// 3. config.toml
767func (c *HTTPConf) Overwrite(cmdOpt HTTPConf) {
768	if os.Getenv(httpKey) != "" {
769		c.URL = os.Getenv(httpKey)
770	}
771	if cmdOpt.URL != "" {
772		c.URL = cmdOpt.URL
773	}
774}
775
776// GoCveDictConf is go-cve-dictionary config
777type GoCveDictConf struct {
778	// DB type of CVE dictionary (sqlite3, mysql, postgres or redis)
779	Type string
780
781	// http://cve-dictionary.com:1323 or DB connection string
782	URL string `json:"-"`
783
784	// /path/to/cve.sqlite3
785	SQLite3Path string `json:"-"`
786}
787
788func (cnf *GoCveDictConf) setDefault() {
789	if cnf.Type == "" {
790		cnf.Type = "sqlite3"
791	}
792	if cnf.URL == "" && cnf.SQLite3Path == "" {
793		wd, _ := os.Getwd()
794		cnf.SQLite3Path = filepath.Join(wd, "cve.sqlite3")
795	}
796}
797
798const cveDBType = "CVEDB_TYPE"
799const cveDBURL = "CVEDB_URL"
800const cveDBPATH = "CVEDB_SQLITE3_PATH"
801
802// Overwrite set options with the following priority.
803// 1. Command line option
804// 2. Environment variable
805// 3. config.toml
806func (cnf *GoCveDictConf) Overwrite(cmdOpt GoCveDictConf) {
807	if os.Getenv(cveDBType) != "" {
808		cnf.Type = os.Getenv(cveDBType)
809	}
810	if os.Getenv(cveDBURL) != "" {
811		cnf.URL = os.Getenv(cveDBURL)
812	}
813	if os.Getenv(cveDBPATH) != "" {
814		cnf.SQLite3Path = os.Getenv(cveDBPATH)
815	}
816
817	if cmdOpt.Type != "" {
818		cnf.Type = cmdOpt.Type
819	}
820	if cmdOpt.URL != "" {
821		cnf.URL = cmdOpt.URL
822	}
823	if cmdOpt.SQLite3Path != "" {
824		cnf.SQLite3Path = cmdOpt.SQLite3Path
825	}
826	cnf.setDefault()
827}
828
829// IsFetchViaHTTP returns wether fetch via http
830func (cnf *GoCveDictConf) IsFetchViaHTTP() bool {
831	return Conf.CveDict.Type == "http"
832}
833
834// GovalDictConf is goval-dictionary config
835type GovalDictConf struct {
836
837	// DB type of OVAL dictionary (sqlite3, mysql, postgres or redis)
838	Type string
839
840	// http://goval-dictionary.com:1324 or DB connection string
841	URL string `json:"-"`
842
843	// /path/to/oval.sqlite3
844	SQLite3Path string `json:"-"`
845}
846
847func (cnf *GovalDictConf) setDefault() {
848	if cnf.Type == "" {
849		cnf.Type = "sqlite3"
850	}
851	if cnf.URL == "" && cnf.SQLite3Path == "" {
852		wd, _ := os.Getwd()
853		cnf.SQLite3Path = filepath.Join(wd, "oval.sqlite3")
854	}
855}
856
857const govalType = "OVALDB_TYPE"
858const govalURL = "OVALDB_URL"
859const govalPATH = "OVALDB_SQLITE3_PATH"
860
861// Overwrite set options with the following priority.
862// 1. Command line option
863// 2. Environment variable
864// 3. config.toml
865func (cnf *GovalDictConf) Overwrite(cmdOpt GovalDictConf) {
866	if os.Getenv(govalType) != "" {
867		cnf.Type = os.Getenv(govalType)
868	}
869	if os.Getenv(govalURL) != "" {
870		cnf.URL = os.Getenv(govalURL)
871	}
872	if os.Getenv(govalPATH) != "" {
873		cnf.SQLite3Path = os.Getenv(govalPATH)
874	}
875
876	if cmdOpt.Type != "" {
877		cnf.Type = cmdOpt.Type
878	}
879	if cmdOpt.URL != "" {
880		cnf.URL = cmdOpt.URL
881	}
882	if cmdOpt.SQLite3Path != "" {
883		cnf.SQLite3Path = cmdOpt.SQLite3Path
884	}
885	cnf.setDefault()
886}
887
888// IsFetchViaHTTP returns wether fetch via http
889func (cnf *GovalDictConf) IsFetchViaHTTP() bool {
890	return Conf.OvalDict.Type == "http"
891}
892
893// GostConf is gost config
894type GostConf struct {
895	// DB type for gost dictionary (sqlite3, mysql, postgres or redis)
896	Type string
897
898	// http://gost-dictionary.com:1324 or DB connection string
899	URL string `json:"-"`
900
901	// /path/to/gost.sqlite3
902	SQLite3Path string `json:"-"`
903}
904
905func (cnf *GostConf) setDefault() {
906	if cnf.Type == "" {
907		cnf.Type = "sqlite3"
908	}
909	if cnf.URL == "" && cnf.SQLite3Path == "" {
910		wd, _ := os.Getwd()
911		cnf.SQLite3Path = filepath.Join(wd, "gost.sqlite3")
912	}
913}
914
915const gostDBType = "GOSTDB_TYPE"
916const gostDBURL = "GOSTDB_URL"
917const gostDBPATH = "GOSTDB_SQLITE3_PATH"
918
919// Overwrite set options with the following priority.
920// 1. Command line option
921// 2. Environment variable
922// 3. config.toml
923func (cnf *GostConf) Overwrite(cmdOpt GostConf) {
924	if os.Getenv(gostDBType) != "" {
925		cnf.Type = os.Getenv(gostDBType)
926	}
927	if os.Getenv(gostDBURL) != "" {
928		cnf.URL = os.Getenv(gostDBURL)
929	}
930	if os.Getenv(gostDBPATH) != "" {
931		cnf.SQLite3Path = os.Getenv(gostDBPATH)
932	}
933
934	if cmdOpt.Type != "" {
935		cnf.Type = cmdOpt.Type
936	}
937	if cmdOpt.URL != "" {
938		cnf.URL = cmdOpt.URL
939	}
940	if cmdOpt.SQLite3Path != "" {
941		cnf.SQLite3Path = cmdOpt.SQLite3Path
942	}
943	cnf.setDefault()
944}
945
946// IsFetchViaHTTP returns wether fetch via http
947func (cnf *GostConf) IsFetchViaHTTP() bool {
948	return Conf.Gost.Type == "http"
949}
950
951// ExploitConf is exploit config
952type ExploitConf struct {
953	// DB type for exploit dictionary (sqlite3, mysql, postgres or redis)
954	Type string
955
956	// http://exploit-dictionary.com:1324 or DB connection string
957	URL string `json:"-"`
958
959	// /path/to/exploit.sqlite3
960	SQLite3Path string `json:"-"`
961}
962
963func (cnf *ExploitConf) setDefault() {
964	if cnf.Type == "" {
965		cnf.Type = "sqlite3"
966	}
967	if cnf.URL == "" && cnf.SQLite3Path == "" {
968		wd, _ := os.Getwd()
969		cnf.SQLite3Path = filepath.Join(wd, "go-exploitdb.sqlite3")
970	}
971}
972
973const exploitDBType = "EXPLOITDB_TYPE"
974const exploitDBURL = "EXPLOITDB_URL"
975const exploitDBPATH = "EXPLOITDB_SQLITE3_PATH"
976
977// Overwrite set options with the following priority.
978// 1. Command line option
979// 2. Environment variable
980// 3. config.toml
981func (cnf *ExploitConf) Overwrite(cmdOpt ExploitConf) {
982	if os.Getenv(exploitDBType) != "" {
983		cnf.Type = os.Getenv(exploitDBType)
984	}
985	if os.Getenv(exploitDBURL) != "" {
986		cnf.URL = os.Getenv(exploitDBURL)
987	}
988	if os.Getenv(exploitDBPATH) != "" {
989		cnf.SQLite3Path = os.Getenv(exploitDBPATH)
990	}
991
992	if cmdOpt.Type != "" {
993		cnf.Type = cmdOpt.Type
994	}
995	if cmdOpt.URL != "" {
996		cnf.URL = cmdOpt.URL
997	}
998	if cmdOpt.SQLite3Path != "" {
999		cnf.SQLite3Path = cmdOpt.SQLite3Path
1000	}
1001	cnf.setDefault()
1002}
1003
1004// IsFetchViaHTTP returns wether fetch via http
1005func (cnf *ExploitConf) IsFetchViaHTTP() bool {
1006	return Conf.Exploit.Type == "http"
1007}
1008
1009// MetasploitConf is metasploit config
1010type MetasploitConf struct {
1011	// DB type for metasploit dictionary (sqlite3, mysql, postgres or redis)
1012	Type string
1013
1014	// http://metasploit-dictionary.com:1324 or DB connection string
1015	URL string `json:"-"`
1016
1017	// /path/to/metasploit.sqlite3
1018	SQLite3Path string `json:"-"`
1019}
1020
1021func (cnf *MetasploitConf) setDefault() {
1022	if cnf.Type == "" {
1023		cnf.Type = "sqlite3"
1024	}
1025	if cnf.URL == "" && cnf.SQLite3Path == "" {
1026		wd, _ := os.Getwd()
1027		cnf.SQLite3Path = filepath.Join(wd, "go-msfdb.sqlite3")
1028	}
1029}
1030
1031const metasploitDBType = "METASPLOITDB_TYPE"
1032const metasploitDBURL = "METASPLOITDB_URL"
1033const metasploitDBPATH = "METASPLOITDB_SQLITE3_PATH"
1034
1035// Overwrite set options with the following priority.
1036// 1. Command line option
1037// 2. Environment variable
1038// 3. config.toml
1039func (cnf *MetasploitConf) Overwrite(cmdOpt MetasploitConf) {
1040	if os.Getenv(metasploitDBType) != "" {
1041		cnf.Type = os.Getenv(metasploitDBType)
1042	}
1043	if os.Getenv(metasploitDBURL) != "" {
1044		cnf.URL = os.Getenv(metasploitDBURL)
1045	}
1046	if os.Getenv(metasploitDBPATH) != "" {
1047		cnf.SQLite3Path = os.Getenv(metasploitDBPATH)
1048	}
1049
1050	if cmdOpt.Type != "" {
1051		cnf.Type = cmdOpt.Type
1052	}
1053	if cmdOpt.URL != "" {
1054		cnf.URL = cmdOpt.URL
1055	}
1056	if cmdOpt.SQLite3Path != "" {
1057		cnf.SQLite3Path = cmdOpt.SQLite3Path
1058	}
1059	cnf.setDefault()
1060}
1061
1062// IsFetchViaHTTP returns wether fetch via http
1063func (cnf *MetasploitConf) IsFetchViaHTTP() bool {
1064	return Conf.Metasploit.Type == "http"
1065}
1066
1067// AWS is aws config
1068type AWS struct {
1069	// AWS profile to use
1070	Profile string `json:"profile"`
1071
1072	// AWS region to use
1073	Region string `json:"region"`
1074
1075	// S3 bucket name
1076	S3Bucket string `json:"s3Bucket"`
1077
1078	// /bucket/path/to/results
1079	S3ResultsDir string `json:"s3ResultsDir"`
1080
1081	// The Server-side encryption algorithm used when storing the reports in S3 (e.g., AES256, aws:kms).
1082	S3ServerSideEncryption string `json:"s3ServerSideEncryption"`
1083}
1084
1085// Azure is azure config
1086type Azure struct {
1087	// Azure account name to use. AZURE_STORAGE_ACCOUNT environment variable is used if not specified
1088	AccountName string `json:"accountName"`
1089
1090	// Azure account key to use. AZURE_STORAGE_ACCESS_KEY environment variable is used if not specified
1091	AccountKey string `json:"-"`
1092
1093	// Azure storage container name
1094	ContainerName string `json:"containerName"`
1095}
1096
1097// ServerInfo has SSH Info, additional CPE packages to scan.
1098type ServerInfo struct {
1099	ServerName             string                      `toml:"-" json:"serverName,omitempty"`
1100	User                   string                      `toml:"user,omitempty" json:"user,omitempty"`
1101	Host                   string                      `toml:"host,omitempty" json:"host,omitempty"`
1102	JumpServer             []string                    `toml:"jumpServer,omitempty" json:"jumpServer,omitempty"`
1103	Port                   string                      `toml:"port,omitempty" json:"port,omitempty"`
1104	SSHConfigPath          string                      `toml:"sshConfigPath,omitempty" json:"sshConfigPath,omitempty"`
1105	KeyPath                string                      `toml:"keyPath,omitempty" json:"keyPath,omitempty"`
1106	KeyPassword            string                      `json:"-,omitempty" toml:"-"`
1107	CpeNames               []string                    `toml:"cpeNames,omitempty" json:"cpeNames,omitempty"`
1108	ScanMode               []string                    `toml:"scanMode,omitempty" json:"scanMode,omitempty"`
1109	DependencyCheckXMLPath string                      `toml:"dependencyCheckXMLPath,omitempty" json:"-"` // TODO Deprecated remove in near future
1110	OwaspDCXMLPath         string                      `toml:"owaspDCXMLPath,omitempty" json:"owaspDCXMLPath,omitempty"`
1111	ContainersIncluded     []string                    `toml:"containersIncluded,omitempty" json:"containersIncluded,omitempty"`
1112	ContainersExcluded     []string                    `toml:"containersExcluded,omitempty" json:"containersExcluded,omitempty"`
1113	ContainerType          string                      `toml:"containerType,omitempty" json:"containerType,omitempty"`
1114	Containers             map[string]ContainerSetting `toml:"containers" json:"containers,omitempty"`
1115	IgnoreCves             []string                    `toml:"ignoreCves,omitempty" json:"ignoreCves,omitempty"`
1116	IgnorePkgsRegexp       []string                    `toml:"ignorePkgsRegexp,omitempty" json:"ignorePkgsRegexp,omitempty"`
1117	GitHubRepos            map[string]GitHubConf       `toml:"githubs" json:"githubs,omitempty"` // key: owner/repo
1118	UUIDs                  map[string]string           `toml:"uuids,omitempty" json:"uuids,omitempty"`
1119	Memo                   string                      `toml:"memo,omitempty" json:"memo,omitempty"`
1120	Enablerepo             []string                    `toml:"enablerepo,omitempty" json:"enablerepo,omitempty"` // For CentOS, RHEL, Amazon
1121	Optional               map[string]interface{}      `toml:"optional,omitempty" json:"optional,omitempty"`     // Optional key-value set that will be outputted to JSON
1122	Lockfiles              []string                    `toml:"lockfiles,omitempty" json:"lockfiles,omitempty"`   // ie) path/to/package-lock.json
1123	FindLock               bool                        `toml:"findLock,omitempty" json:"findLock,omitempty"`
1124	Type                   string                      `toml:"type,omitempty" json:"type,omitempty"` // "pseudo" or ""
1125	WordPress              WordPressConf               `toml:"wordpress,omitempty" json:"wordpress,omitempty"`
1126	IgnoredJSONKeys        []string                    `toml:"ignoredJSONKeys,omitempty" json:"ignoredJSONKeys,omitempty"`
1127
1128	// internal use
1129	IPv4Addrs       []string       `toml:"-" json:"ipv4Addrs,omitempty"`
1130	IPv6Addrs       []string       `toml:"-" json:"ipv6Addrs,omitempty"`
1131	IPSIdentifiers  map[IPS]string `toml:"-" json:"ipsIdentifiers,omitempty"`
1132	LogMsgAnsiColor string         `toml:"-" json:"-"` // DebugLog Color
1133	Container       Container      `toml:"-" json:"-"`
1134	Distro          Distro         `toml:"-" json:"-"`
1135	Mode            ScanMode       `toml:"-" json:"-"`
1136}
1137
1138// ContainerSetting is used for loading container setting in config.toml
1139type ContainerSetting struct {
1140	Cpes             []string `json:"cpes,omitempty"`
1141	OwaspDCXMLPath   string   `json:"owaspDCXMLPath"`
1142	IgnorePkgsRegexp []string `json:"ignorePkgsRegexp,omitempty"`
1143	IgnoreCves       []string `json:"ignoreCves,omitempty"`
1144}
1145
1146// WordPressConf used for WordPress Scanning
1147type WordPressConf struct {
1148	OSUser         string `toml:"osUser" json:"osUser,omitempty"`
1149	DocRoot        string `toml:"docRoot" json:"docRoot,omitempty"`
1150	CmdPath        string `toml:"cmdPath" json:"cmdPath,omitempty"`
1151	WPVulnDBToken  string `toml:"wpVulnDBToken" json:"-,omitempty"`
1152	IgnoreInactive bool   `json:"ignoreInactive,omitempty"`
1153}
1154
1155// GitHubConf is used for GitHub integration
1156type GitHubConf struct {
1157	Token string `json:"-"`
1158}
1159
1160// ScanMode has a type of scan mode. fast, fast-root, deep and offline
1161type ScanMode struct {
1162	flag byte
1163}
1164
1165// Set mode
1166func (s *ScanMode) Set(f byte) {
1167	s.flag |= f
1168}
1169
1170// IsFast return whether scan mode is fast
1171func (s ScanMode) IsFast() bool {
1172	return s.flag&Fast == Fast
1173}
1174
1175// IsFastRoot return whether scan mode is fastroot
1176func (s ScanMode) IsFastRoot() bool {
1177	return s.flag&FastRoot == FastRoot
1178}
1179
1180// IsDeep return whether scan mode is deep
1181func (s ScanMode) IsDeep() bool {
1182	return s.flag&Deep == Deep
1183}
1184
1185// IsOffline return whether scan mode is offline
1186func (s ScanMode) IsOffline() bool {
1187	return s.flag&Offline == Offline
1188}
1189
1190func (s ScanMode) validate() error {
1191	numTrue := 0
1192	for _, b := range []bool{s.IsFast(), s.IsFastRoot(), s.IsDeep()} {
1193		if b {
1194			numTrue++
1195		}
1196	}
1197	if numTrue == 0 {
1198		s.Set(Fast)
1199	} else if s.IsDeep() && s.IsOffline() {
1200		return xerrors.New("Don't specify both of -deep and offline")
1201	} else if numTrue != 1 {
1202		return xerrors.New("Specify only one of -fast, -fast-root or -deep")
1203	}
1204	return nil
1205}
1206
1207func (s ScanMode) String() string {
1208	ss := ""
1209	if s.IsFast() {
1210		ss = "fast"
1211	} else if s.IsFastRoot() {
1212		ss = "fast-root"
1213	} else if s.IsDeep() {
1214		ss = "deep"
1215	}
1216	if s.IsOffline() {
1217		ss += " offline"
1218	}
1219	return ss + " mode"
1220}
1221
1222const (
1223	// Fast is fast scan mode
1224	Fast = byte(1 << iota)
1225	// FastRoot is fast-root scan mode
1226	FastRoot
1227	// Deep is deep scan mode
1228	Deep
1229	// Offline is offline scan mode
1230	Offline
1231)
1232
1233// GetServerName returns ServerName if this serverInfo is about host.
1234// If this serverInfo is about a container, returns containerID@ServerName
1235func (s ServerInfo) GetServerName() string {
1236	if len(s.Container.ContainerID) == 0 {
1237		return s.ServerName
1238	}
1239	return fmt.Sprintf("%s@%s", s.Container.Name, s.ServerName)
1240}
1241
1242// Distro has distribution info
1243type Distro struct {
1244	Family  string
1245	Release string
1246}
1247
1248func (l Distro) String() string {
1249	return fmt.Sprintf("%s %s", l.Family, l.Release)
1250}
1251
1252// MajorVersion returns Major version
1253func (l Distro) MajorVersion() (int, error) {
1254	if l.Family == Amazon {
1255		ss := strings.Fields(l.Release)
1256		if len(ss) == 1 {
1257			return 1, nil
1258		}
1259		return strconv.Atoi(ss[0])
1260	}
1261	if 0 < len(l.Release) {
1262		return strconv.Atoi(strings.Split(l.Release, ".")[0])
1263	}
1264	return 0, xerrors.New("Release is empty")
1265}
1266
1267// IsContainer returns whether this ServerInfo is about container
1268func (s ServerInfo) IsContainer() bool {
1269	return 0 < len(s.Container.ContainerID)
1270}
1271
1272// SetContainer set container
1273func (s *ServerInfo) SetContainer(d Container) {
1274	s.Container = d
1275}
1276
1277// Container has Container information.
1278type Container struct {
1279	ContainerID string
1280	Name        string
1281	Image       string
1282}
1283