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

scan.unset   A

Complexity

Conditions 2

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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