Completed
Pull Request — master (#769)
by
unknown
10:56
created

scan.detectWp   C

Complexity

Conditions 10

Size

Total Lines 27
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 17
dl 0
loc 27
rs 5.9999
c 0
b 0
f 0
nop 1

How to fix   Complexity   

Complexity

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