Completed
Pull Request — master (#769)
by
unknown
11:04
created

scan.detectWpCore   F

Complexity

Conditions 14

Size

Total Lines 37
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

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