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

scan.*base.parseIP   B

Complexity

Conditions 7

Size

Total Lines 21
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

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