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

scan.contentConvertVinfo   F

Complexity

Conditions 18

Size

Total Lines 60
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 18
eloc 41
dl 0
loc 60
rs 1.2
c 0
b 0
f 0
nop 3

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

Complexity

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