Passed
Pull Request — master (#769)
by
unknown
09:45
created

scan.detectWpCore   F

Complexity

Conditions 16

Size

Total Lines 55
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 16
eloc 37
dl 0
loc 55
rs 2.4
c 0
b 0
f 0
nop 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like scan.detectWpCore often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
/* Vuls - Vulnerability Scanner
2
Copyright (C) 2016  Future Corporation , Japan.
3
4
This program is free software: you can redistribute it and/or modify
5
it under the terms of the GNU General Public License as published by
6
the Free Software Foundation, either version 3 of the License, or
7
(at your option) any later version.
8
9
This program is distributed in the hope that it will be useful,
10
but WITHOUT ANY WARRANTY; without even the implied warranty of
11
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
GNU General Public License for more details.
13
14
You should have received a copy of the GNU General Public License
15
along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
*/
17
18
package scan
19
20
import (
21
	"bufio"
22
	"encoding/json"
23
	"fmt"
24
	"net"
25
	"regexp"
26
	"strings"
27
	"time"
28
29
	"github.com/future-architect/vuls/config"
30
	"github.com/future-architect/vuls/models"
31
	"github.com/hashicorp/go-version"
32
	"github.com/sirupsen/logrus"
33
)
34
35
type base struct {
36
	ServerInfo config.ServerInfo
37
	Distro     config.Distro
38
	Platform   models.Platform
39
	osPackages
40
41
	log  *logrus.Entry
42
	errs []error
43
}
44
45
func (l *base) scanWp() (err error) {
46
	if len(l.ServerInfo.WpPath) == 0 && len(l.ServerInfo.WpToken) == 0 {
47
		return nil
48
	}
49
	if len(l.ServerInfo.WpPath) == 0 {
50
		err = fmt.Errorf("not found : WpPath")
51
		return
52
	}
53
	if len(l.ServerInfo.WpToken) == 0 {
54
		err = fmt.Errorf("not found : WpToken")
55
		return
56
	}
57
58
	var unsecures []models.VulnInfo
59
	if unsecures, err = detectWp(l.ServerInfo); err != nil {
60
		l.log.Errorf("Failed to scan wordpress: %s", err)
61
		return err
62
	}
63
	l.WpVulnInfos = map[string]models.VulnInfo{}
64
	for _, i := range unsecures {
65
		l.WpVulnInfos[i.CveID] = i
66
	}
67
68
	return
69
}
70
71
//WpCveInfos is for wpvulndb's json
72
type WpCveInfos struct {
73
	ReleaseDate     string      `json:"release_date"`
74
	ChangelogURL    string      `json:"changelog_url"`
75
	Status          string      `json:"status"`
76
	LatestVersion   string      `json:"latest_version"`
77
	LastUpdated     string      `json:"last_updated"`
78
	Popular         bool        `json:"popular"`
79
	Vulnerabilities []WpCveInfo `json:"vulnerabilities"`
80
	Error           string      `json:"error"`
81
}
82
83
//WpCveInfo is for wpvulndb's json
84
type WpCveInfo struct {
85
	ID            int        `json:"id"`
86
	Title         string     `json:"title"`
87
	CreatedAt     string     `json:"created_at"`
88
	UpdatedAt     string     `json:"updated_at"`
89
	PublishedDate string     `json:"Published_date"`
90
	VulnType      string     `json:"Vuln_type"`
91
	References    References `json:"references"`
92
	FixedIn       string     `json:"fixed_in"`
93
}
94
95
//References is for wpvulndb's json
96
type References struct {
97
	URL     []string `json:"url"`
98
	Cve     []string `json:"cve"`
99
	Secunia []string `json:"secunia"`
100
}
101
102
func detectWp(c config.ServerInfo) (vinfos []models.VulnInfo, err error) {
103
104
	var coreVuln []models.VulnInfo
105
	if coreVuln, err = detectWpCore(c); err != nil {
106
		return
107
	}
108
	for _, i := range coreVuln {
109
		vinfos = append(vinfos, i)
110
	}
111
112
	var themeVuln []models.VulnInfo
113
	if themeVuln, err = detectWpTheme(c); err != nil {
114
		return
115
	}
116
	for _, i := range themeVuln {
117
		vinfos = append(vinfos, i)
118
	}
119
120
	var pluginVuln []models.VulnInfo
121
	if pluginVuln, err = detectWpPlugin(c); err != nil {
122
		return
123
	}
124
	for _, i := range pluginVuln {
125
		vinfos = append(vinfos, i)
126
	}
127
128
	return
129
}
130
131
func detectWpCore(c config.ServerInfo) (vinfos []models.VulnInfo, err error) {
132
	cmd := fmt.Sprintf("wp core version --path=%s", c.WpPath)
133
134
	var coreVersion string
135
	if r := exec(c, cmd, noSudo); r.isSuccess() {
136
		tmp := strings.Split(r.Stdout, ".")
137
		coreVersion = strings.Join(tmp, "")
138
		coreVersion = strings.TrimRight(coreVersion, "\r\n")
139
		if len(coreVersion) == 0 {
140
			return
141
		}
142
	}
143
	cmd = fmt.Sprintf("curl -k -H 'Authorization: Token token=%s' https://wpvulndb.com/api/v3/wordpresses/%s", c.WpToken, coreVersion)
144
	if r := exec(c, cmd, noSudo); r.isSuccess() {
145
		data := map[string]WpCveInfos{}
146
		if err = json.Unmarshal([]byte(r.Stdout), &data); err != nil {
147
			return
148
		}
149
		for _, i := range data {
150
			if len(i.Vulnerabilities) == 0 {
151
				continue
152
			}
153
			for _, e := range i.Vulnerabilities {
154
				if len(e.References.Cve) == 0 {
155
					continue
156
				}
157
				NotFixedYet := false
158
				if len(e.FixedIn) == 0 {
159
					NotFixedYet = true
160
				}
161
				var cveIDs []string
162
				for _, k := range e.References.Cve {
163
					cveIDs = append(cveIDs, "CVE-"+k)
164
				}
165
166
				for _, cveID := range cveIDs {
167
					vinfos = append(vinfos, models.VulnInfo{
168
						CveID: cveID,
169
						CveContents: models.NewCveContents(
170
							models.CveContent{
171
								CveID: cveID,
172
								Title: e.Title,
173
							},
174
						),
175
						AffectedPackages: models.PackageStatuses{
176
							{
177
								NotFixedYet: NotFixedYet,
178
							},
179
						},
180
					})
181
				}
182
			}
183
		}
184
	}
185
	return
186
}
187
188
//WpStatus is for wp command
189
type WpStatus struct {
190
	Name    string `json:"-"`
191
	Status  string `json:"-"`
192
	Update  string `json:"-"`
193
	Version string `json:"-"`
194
}
195
196
func detectWpTheme(c config.ServerInfo) (vinfos []models.VulnInfo, err error) {
197
	cmd := fmt.Sprintf("wp theme list --path=%s", c.WpPath)
198
199
	var themes []WpStatus
200
	if r := exec(c, cmd, noSudo); r.isSuccess() {
201
		themes = parseStatus(r.Stdout)
202
	}
203
204
	for _, theme := range themes {
205
		cmd := fmt.Sprintf("curl -H 'Authorization: Token token=%s' https://wpvulndb.com/api/v3/themes/%s", c.WpToken, theme.Name)
206
		if r := exec(c, cmd, noSudo); r.isSuccess() {
207
			data := map[string]WpCveInfos{}
208
			if err = json.Unmarshal([]byte(r.Stdout), &data); err != nil {
209
				var jsonError WpCveInfos
210
				if err = json.Unmarshal([]byte(r.Stdout), &jsonError); err != nil {
211
					return
212
				}
213
				continue
214
			}
215
			for _, i := range data {
216
				if len(i.Vulnerabilities) == 0 {
217
					continue
218
				}
219
				for _, e := range i.Vulnerabilities {
220
					if len(e.References.Cve) == 0 {
221
						continue
222
					}
223
					NotFixedYet := false
224
					if len(e.FixedIn) == 0 {
225
						NotFixedYet = true
226
					}
227
					if len(e.FixedIn) == 0 {
228
						e.FixedIn = "0"
229
					}
230
					var v1 *version.Version
231
					v1, err = version.NewVersion(theme.Version)
232
					if err != nil {
233
						return
234
					}
235
					var v2 *version.Version
236
					v2, err = version.NewVersion(e.FixedIn)
237
					if err != nil {
238
						return
239
					}
240
					if v1.LessThan(v2) {
241
						var cveIDs []string
242
						for _, k := range e.References.Cve {
243
							cveIDs = append(cveIDs, "CVE-"+k)
244
						}
245
246
						for _, cveID := range cveIDs {
247
							vinfos = append(vinfos, models.VulnInfo{
248
								CveID: cveID,
249
								CveContents: models.NewCveContents(
250
									models.CveContent{
251
										CveID: cveID,
252
										Title: e.Title,
253
									},
254
								),
255
								AffectedPackages: models.PackageStatuses{
256
									{
257
										NotFixedYet: NotFixedYet,
258
									},
259
								},
260
							})
261
						}
262
					}
263
				}
264
			}
265
266
		}
267
	}
268
	return
269
}
270
271
func detectWpPlugin(c config.ServerInfo) (vinfos []models.VulnInfo, err error) {
272
	cmd := fmt.Sprintf("wp plugin list --path=%s", c.WpPath)
273
274
	var plugins []WpStatus
275
	if r := exec(c, cmd, noSudo); r.isSuccess() {
276
		plugins = parseStatus(r.Stdout)
277
	}
278
279
	for _, plugin := range plugins {
280
		cmd := fmt.Sprintf("curl -H 'Authorization: Token token=%s' https://wpvulndb.com/api/v3/plugins/%s", c.WpToken, plugin.Name)
281
282
		if r := exec(c, cmd, noSudo); r.isSuccess() {
283
			data := map[string]WpCveInfos{}
284
			if err = json.Unmarshal([]byte(r.Stdout), &data); err != nil {
285
				var jsonError WpCveInfos
286
				if err = json.Unmarshal([]byte(r.Stdout), &jsonError); err != nil {
287
					return
288
				}
289
				continue
290
			}
291
			for _, i := range data {
292
				if len(i.Vulnerabilities) == 0 {
293
					continue
294
				}
295
				for _, e := range i.Vulnerabilities {
296
					if len(e.References.Cve) == 0 {
297
						continue
298
					}
299
					NotFixedYet := false
300
					if len(e.FixedIn) == 0 {
301
						NotFixedYet = true
302
					}
303
					if len(e.FixedIn) == 0 {
304
						e.FixedIn = "0"
305
					}
306
					var v1 *version.Version
307
					v1, err = version.NewVersion(plugin.Version)
308
					if err != nil {
309
						return
310
					}
311
					var v2 *version.Version
312
					v2, err = version.NewVersion(e.FixedIn)
313
					if err != nil {
314
						return
315
					}
316
					if v1.LessThan(v2) {
317
						var cveIDs []string
318
						for _, k := range e.References.Cve {
319
							cveIDs = append(cveIDs, "CVE-"+k)
320
						}
321
322
						for _, cveID := range cveIDs {
323
							vinfos = append(vinfos, models.VulnInfo{
324
								CveID: cveID,
325
								CveContents: models.NewCveContents(
326
									models.CveContent{
327
										CveID: cveID,
328
										Title: e.Title,
329
									},
330
								),
331
								AffectedPackages: models.PackageStatuses{
332
									{
333
										NotFixedYet: NotFixedYet,
334
									},
335
								},
336
							})
337
						}
338
					}
339
				}
340
			}
341
		}
342
	}
343
	return
344
}
345
346
func parseStatus(r string) (themes []WpStatus) {
347
	tmp := strings.Split(r, "\r\n")
348
	tmp = unset(tmp, 0)
349
	tmp = unset(tmp, 0)
350
	tmp = unset(tmp, 0)
351
	tmp = unset(tmp, len(tmp)-1)
352
	tmp = unset(tmp, len(tmp)-1)
353
	for _, k := range tmp {
354
		theme := strings.Split(k, "|")
355
		themes = append(themes, WpStatus{
356
			Name:    strings.TrimSpace(theme[1]),
357
			Status:  strings.TrimSpace(theme[2]),
358
			Update:  strings.TrimSpace(theme[3]),
359
			Version: strings.TrimSpace(theme[4]),
360
		})
361
	}
362
	return
363
}
364
365
func unset(s []string, i int) []string {
366
	if i >= len(s) {
367
		return s
368
	}
369
	return append(s[:i], s[i+1:]...)
370
}
371
372
func (l *base) wpConvertToModel() models.VulnInfos {
373
	return l.WpVulnInfos
374
}
375
376
func (l *base) exec(cmd string, sudo bool) execResult {
377
	return exec(l.ServerInfo, cmd, sudo, l.log)
378
}
379
380
func (l *base) setServerInfo(c config.ServerInfo) {
381
	l.ServerInfo = c
382
}
383
384
func (l *base) getServerInfo() config.ServerInfo {
385
	return l.ServerInfo
386
}
387
388
func (l *base) setDistro(fam, rel string) {
389
	d := config.Distro{
390
		Family:  fam,
391
		Release: rel,
392
	}
393
	l.Distro = d
394
395
	s := l.getServerInfo()
396
	s.Distro = d
397
	l.setServerInfo(s)
398
}
399
400
func (l *base) getDistro() config.Distro {
401
	return l.Distro
402
}
403
404
func (l *base) setPlatform(p models.Platform) {
405
	l.Platform = p
406
}
407
408
func (l *base) getPlatform() models.Platform {
409
	return l.Platform
410
}
411
412
func (l *base) runningKernel() (release, version string, err error) {
413
	r := l.exec("uname -r", noSudo)
414
	if !r.isSuccess() {
415
		return "", "", fmt.Errorf("Failed to SSH: %s", r)
416
	}
417
	release = strings.TrimSpace(r.Stdout)
418
419
	switch l.Distro.Family {
420
	case config.Debian:
421
		r := l.exec("uname -a", noSudo)
422
		if !r.isSuccess() {
423
			return "", "", fmt.Errorf("Failed to SSH: %s", r)
424
		}
425
		ss := strings.Fields(r.Stdout)
426
		if 6 < len(ss) {
427
			version = ss[6]
428
		}
429
	}
430
	return
431
}
432
433
func (l *base) allContainers() (containers []config.Container, err error) {
434
	switch l.ServerInfo.ContainerType {
435
	case "", "docker":
436
		stdout, err := l.dockerPs("-a --format '{{.ID}} {{.Names}} {{.Image}}'")
437
		if err != nil {
438
			return containers, err
439
		}
440
		return l.parseDockerPs(stdout)
441
	case "lxd":
442
		stdout, err := l.lxdPs("-c n")
443
		if err != nil {
444
			return containers, err
445
		}
446
		return l.parseLxdPs(stdout)
447
	case "lxc":
448
		stdout, err := l.lxcPs("-1")
449
		if err != nil {
450
			return containers, err
451
		}
452
		return l.parseLxcPs(stdout)
453
	default:
454
		return containers, fmt.Errorf(
455
			"Not supported yet: %s", l.ServerInfo.ContainerType)
456
	}
457
}
458
459
func (l *base) runningContainers() (containers []config.Container, err error) {
460
	switch l.ServerInfo.ContainerType {
461
	case "", "docker":
462
		stdout, err := l.dockerPs("--format '{{.ID}} {{.Names}} {{.Image}}'")
463
		if err != nil {
464
			return containers, err
465
		}
466
		return l.parseDockerPs(stdout)
467
	case "lxd":
468
		stdout, err := l.lxdPs("volatile.last_state.power=RUNNING -c n")
469
		if err != nil {
470
			return containers, err
471
		}
472
		return l.parseLxdPs(stdout)
473
	case "lxc":
474
		stdout, err := l.lxcPs("-1 --running")
475
		if err != nil {
476
			return containers, err
477
		}
478
		return l.parseLxcPs(stdout)
479
	default:
480
		return containers, fmt.Errorf(
481
			"Not supported yet: %s", l.ServerInfo.ContainerType)
482
	}
483
}
484
485
func (l *base) exitedContainers() (containers []config.Container, err error) {
486
	switch l.ServerInfo.ContainerType {
487
	case "", "docker":
488
		stdout, err := l.dockerPs("--filter 'status=exited' --format '{{.ID}} {{.Names}} {{.Image}}'")
489
		if err != nil {
490
			return containers, err
491
		}
492
		return l.parseDockerPs(stdout)
493
	case "lxd":
494
		stdout, err := l.lxdPs("volatile.last_state.power=STOPPED -c n")
495
		if err != nil {
496
			return containers, err
497
		}
498
		return l.parseLxdPs(stdout)
499
	case "lxc":
500
		stdout, err := l.lxcPs("-1 --stopped")
501
		if err != nil {
502
			return containers, err
503
		}
504
		return l.parseLxcPs(stdout)
505
	default:
506
		return containers, fmt.Errorf(
507
			"Not supported yet: %s", l.ServerInfo.ContainerType)
508
	}
509
}
510
511
func (l *base) dockerPs(option string) (string, error) {
512
	cmd := fmt.Sprintf("docker ps %s", option)
513
	r := l.exec(cmd, noSudo)
514
	if !r.isSuccess() {
515
		return "", fmt.Errorf("Failed to SSH: %s", r)
516
	}
517
	return r.Stdout, nil
518
}
519
520
func (l *base) lxdPs(option string) (string, error) {
521
	cmd := fmt.Sprintf("lxc list %s", option)
522
	r := l.exec(cmd, noSudo)
523
	if !r.isSuccess() {
524
		return "", fmt.Errorf("failed to SSH: %s", r)
525
	}
526
	return r.Stdout, nil
527
}
528
529
func (l *base) lxcPs(option string) (string, error) {
530
	cmd := fmt.Sprintf("lxc-ls %s 2>/dev/null", option)
531
	r := l.exec(cmd, sudo)
532
	if !r.isSuccess() {
533
		return "", fmt.Errorf("failed to SSH: %s", r)
534
	}
535
	return r.Stdout, nil
536
}
537
538
func (l *base) parseDockerPs(stdout string) (containers []config.Container, err error) {
539
	lines := strings.Split(stdout, "\n")
540
	for _, line := range lines {
541
		fields := strings.Fields(line)
542
		if len(fields) == 0 {
543
			break
544
		}
545
		if len(fields) != 3 {
546
			return containers, fmt.Errorf("Unknown format: %s", line)
547
		}
548
		containers = append(containers, config.Container{
549
			ContainerID: fields[0],
550
			Name:        fields[1],
551
			Image:       fields[2],
552
		})
553
	}
554
	return
555
}
556
557
func (l *base) parseLxdPs(stdout string) (containers []config.Container, err error) {
558
	lines := strings.Split(stdout, "\n")
559
	for i, line := range lines[3:] {
560
		if i%2 == 1 {
561
			continue
562
		}
563
		fields := strings.Fields(strings.Replace(line, "|", " ", -1))
564
		if len(fields) == 0 {
565
			break
566
		}
567
		if len(fields) != 1 {
568
			return containers, fmt.Errorf("Unknown format: %s", line)
569
		}
570
		containers = append(containers, config.Container{
571
			ContainerID: fields[0],
572
			Name:        fields[0],
573
		})
574
	}
575
	return
576
}
577
578
func (l *base) parseLxcPs(stdout string) (containers []config.Container, err error) {
579
	lines := strings.Split(stdout, "\n")
580
	for _, line := range lines {
581
		fields := strings.Fields(line)
582
		if len(fields) == 0 {
583
			break
584
		}
585
		containers = append(containers, config.Container{
586
			ContainerID: fields[0],
587
			Name:        fields[0],
588
		})
589
	}
590
	return
591
}
592
593
// ip executes ip command and returns IP addresses
594
func (l *base) ip() ([]string, []string, error) {
595
	// e.g.
596
	// 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000\    link/ether 52:54:00:2a:86:4c brd ff:ff:ff:ff:ff:ff
597
	// 2: eth0    inet 10.0.2.15/24 brd 10.0.2.255 scope global eth0
598
	// 2: eth0    inet6 fe80::5054:ff:fe2a:864c/64 scope link \       valid_lft forever preferred_lft forever
599
	r := l.exec("/sbin/ip -o addr", noSudo)
600
	if !r.isSuccess() {
601
		return nil, nil, fmt.Errorf("Failed to detect IP address: %v", r)
602
	}
603
	ipv4Addrs, ipv6Addrs := l.parseIP(r.Stdout)
604
	return ipv4Addrs, ipv6Addrs, nil
605
}
606
607
// parseIP parses the results of ip command
608
func (l *base) parseIP(stdout string) (ipv4Addrs []string, ipv6Addrs []string) {
609
	lines := strings.Split(stdout, "\n")
610
	for _, line := range lines {
611
		fields := strings.Fields(line)
612
		if len(fields) < 4 {
613
			continue
614
		}
615
		ip, _, err := net.ParseCIDR(fields[3])
616
		if err != nil {
617
			continue
618
		}
619
		if !ip.IsGlobalUnicast() {
620
			continue
621
		}
622
		if ipv4 := ip.To4(); ipv4 != nil {
623
			ipv4Addrs = append(ipv4Addrs, ipv4.String())
624
		} else {
625
			ipv6Addrs = append(ipv6Addrs, ip.String())
626
		}
627
	}
628
	return
629
}
630
631
func (l *base) detectPlatform() {
632
	if l.getServerInfo().Mode.IsOffline() {
633
		l.setPlatform(models.Platform{Name: "unknown"})
634
		return
635
	}
636
	ok, instanceID, err := l.detectRunningOnAws()
637
	if err != nil {
638
		l.setPlatform(models.Platform{Name: "other"})
639
		return
640
	}
641
	if ok {
642
		l.setPlatform(models.Platform{
643
			Name:       "aws",
644
			InstanceID: instanceID,
645
		})
646
		return
647
	}
648
649
	//TODO Azure, GCP...
650
	l.setPlatform(models.Platform{Name: "other"})
651
	return
652
}
653
654
func (l *base) detectRunningOnAws() (ok bool, instanceID string, err error) {
655
	if r := l.exec("type curl", noSudo); r.isSuccess() {
656
		cmd := "curl --max-time 1 --noproxy 169.254.169.254 http://169.254.169.254/latest/meta-data/instance-id"
657
		r := l.exec(cmd, noSudo)
658
		if r.isSuccess() {
659
			id := strings.TrimSpace(r.Stdout)
660
			if !l.isAwsInstanceID(id) {
661
				return false, "", nil
662
			}
663
			return true, id, nil
664
		}
665
666
		switch r.ExitStatus {
667
		case 28, 7:
668
			// Not running on AWS
669
			//  7   Failed to connect to host.
670
			// 28  operation timeout.
671
			return false, "", nil
672
		}
673
	}
674
675
	if r := l.exec("type wget", noSudo); r.isSuccess() {
676
		cmd := "wget --tries=3 --timeout=1 --no-proxy -q -O - http://169.254.169.254/latest/meta-data/instance-id"
677
		r := l.exec(cmd, noSudo)
678
		if r.isSuccess() {
679
			id := strings.TrimSpace(r.Stdout)
680
			if !l.isAwsInstanceID(id) {
681
				return false, "", nil
682
			}
683
			return true, id, nil
684
		}
685
686
		switch r.ExitStatus {
687
		case 4, 8:
688
			// Not running on AWS
689
			// 4   Network failure
690
			// 8   Server issued an error response.
691
			return false, "", nil
692
		}
693
	}
694
	return false, "", fmt.Errorf(
695
		"Failed to curl or wget to AWS instance metadata on %s. container: %s",
696
		l.ServerInfo.ServerName, l.ServerInfo.Container.Name)
697
}
698
699
// http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/resource-ids.html
700
var awsInstanceIDPattern = regexp.MustCompile(`^i-[0-9a-f]+$`)
701
702
func (l *base) isAwsInstanceID(str string) bool {
703
	return awsInstanceIDPattern.MatchString(str)
704
}
705
706
func (l *base) convertToModel() models.ScanResult {
707
	ctype := l.ServerInfo.ContainerType
708
	if l.ServerInfo.Container.ContainerID != "" && ctype == "" {
709
		ctype = "docker"
710
	}
711
	container := models.Container{
712
		ContainerID: l.ServerInfo.Container.ContainerID,
713
		Name:        l.ServerInfo.Container.Name,
714
		Image:       l.ServerInfo.Container.Image,
715
		Type:        ctype,
716
	}
717
718
	errs := []string{}
719
	for _, e := range l.errs {
720
		errs = append(errs, fmt.Sprintf("%s", e))
721
	}
722
723
	return models.ScanResult{
724
		JSONVersion:   models.JSONVersion,
725
		ServerName:    l.ServerInfo.ServerName,
726
		ScannedAt:     time.Now(),
727
		ScanMode:      l.ServerInfo.Mode.String(),
728
		Family:        l.Distro.Family,
729
		Release:       l.Distro.Release,
730
		Container:     container,
731
		Platform:      l.Platform,
732
		IPv4Addrs:     l.ServerInfo.IPv4Addrs,
733
		IPv6Addrs:     l.ServerInfo.IPv6Addrs,
734
		ScannedCves:   l.VulnInfos,
735
		RunningKernel: l.Kernel,
736
		Packages:      l.Packages,
737
		SrcPackages:   l.SrcPackages,
738
		Optional:      l.ServerInfo.Optional,
739
		Errors:        errs,
740
	}
741
}
742
743
func (l *base) setErrs(errs []error) {
744
	l.errs = errs
745
}
746
747
func (l *base) getErrs() []error {
748
	return l.errs
749
}
750
751
const (
752
	systemd  = "systemd"
753
	upstart  = "upstart"
754
	sysVinit = "init"
755
)
756
757
// https://unix.stackexchange.com/questions/196166/how-to-find-out-if-a-system-uses-sysv-upstart-or-systemd-initsystem
758
func (l *base) detectInitSystem() (string, error) {
759
	var f func(string) (string, error)
760
	f = func(cmd string) (string, error) {
761
		r := l.exec(cmd, sudo)
762
		if !r.isSuccess() {
763
			return "", fmt.Errorf("Failed to stat %s: %s", cmd, r)
764
		}
765
		scanner := bufio.NewScanner(strings.NewReader(r.Stdout))
766
		scanner.Scan()
767
		line := strings.TrimSpace(scanner.Text())
768
		if strings.Contains(line, "systemd") {
769
			return systemd, nil
770
		} else if strings.Contains(line, "upstart") {
771
			return upstart, nil
772
		} else if strings.Contains(line, "File: ‘/proc/1/exe’ -> ‘/sbin/init’") ||
773
			strings.Contains(line, "File: `/proc/1/exe' -> `/sbin/init'") {
774
			return f("stat /sbin/init")
775
		} else if line == "File: ‘/sbin/init’" ||
776
			line == "File: `/sbin/init'" {
777
			r := l.exec("/sbin/init --version", noSudo)
778
			if r.isSuccess() {
779
				if strings.Contains(r.Stdout, "upstart") {
780
					return upstart, nil
781
				}
782
			}
783
			return sysVinit, nil
784
		}
785
		return "", fmt.Errorf("Failed to detect a init system: %s", line)
786
	}
787
	return f("stat /proc/1/exe")
788
}
789
790
func (l *base) detectServiceName(pid string) (string, error) {
791
	cmd := fmt.Sprintf("systemctl status --quiet --no-pager %s", pid)
792
	r := l.exec(cmd, noSudo)
793
	if !r.isSuccess() {
794
		return "", fmt.Errorf("Failed to stat %s: %s", cmd, r)
795
	}
796
	return l.parseSystemctlStatus(r.Stdout), nil
797
}
798
799
func (l *base) parseSystemctlStatus(stdout string) string {
800
	scanner := bufio.NewScanner(strings.NewReader(stdout))
801
	scanner.Scan()
802
	line := scanner.Text()
803
	ss := strings.Fields(line)
804
	if len(ss) < 2 || strings.HasPrefix(line, "Failed to get unit for PID") {
805
		return ""
806
	}
807
	return ss[1]
808
}
809