Completed
Pull Request — master (#769)
by
unknown
19:24 queued 08:28
created

scan.detectWpCore   F

Complexity

Conditions 15

Size

Total Lines 46
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 15
eloc 31
dl 0
loc 46
rs 2.9998
c 0
b 0
f 0
nop 1

How to fix   Complexity   

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