Test Failed
Pull Request — master (#769)
by
unknown
10:01
created

scan.*base.wpConvertToModel   A

Complexity

Conditions 1

Size

Total Lines 2
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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