File: service_unix.go

package info (click to toggle)
golang-gopkg-hlandau-service.v2 2.0.16-4
  • links: PTS, VCS
  • area: main
  • in suites: buster, sid
  • size: 156 kB
  • sloc: makefile: 2
file content (185 lines) | stat: -rw-r--r-- 4,625 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
// +build !windows

package service

import (
	"fmt"
	"gopkg.in/hlandau/easyconfig.v1/cflag"
	"gopkg.in/hlandau/service.v2/daemon"
	"gopkg.in/hlandau/service.v2/daemon/bansuid"
	"gopkg.in/hlandau/svcutils.v1/caps"
	"gopkg.in/hlandau/svcutils.v1/passwd"
	"gopkg.in/hlandau/svcutils.v1/pidfile"
	"gopkg.in/hlandau/svcutils.v1/systemd"
	"os"
	"strconv"
)

// This will always point to a path which the platform guarantees is an empty
// directory. You can use it as your default chroot path if your service doesn't
// access the filesystem after it's started.
//
// On Linux, the FHS provides that "/var/empty" is an empty directory, so it
// points to that.
var EmptyChrootPath = daemon.EmptyChrootPath

var (
	uidFlag       = cflag.String(fg, "uid", "", "UID to run as (default: don't drop privileges)")
	gidFlag       = cflag.String(fg, "gid", "", "GID to run as (default: don't drop privileges)")
	daemonizeFlag = cflag.Bool(fg, "daemon", false, "Run as daemon? (doesn't fork)")
	stderrFlag    = cflag.Bool(fg, "stderr", false, "Keep stderr open when daemonizing")
	chrootFlag    = cflag.String(fg, "chroot", "", "Chroot to a directory (must set UID, GID) (\"/\" disables)")
	pidfileFlag   = cflag.String(fg, "pidfile", "", "Write PID to file with given filename and hold a write lock")
	forkFlag      = cflag.Bool(fg, "fork", false, "Fork? (implies -daemon)")
)

func systemdUpdateStatus(status string) error {
	return systemd.NotifySend(status)
}

func (info *Info) serviceMain() error {
	if forkFlag.Value() {
		isParent, err := daemon.Fork()
		if err != nil {
			return err
		}

		if isParent {
			os.Exit(0)
		}

		daemonizeFlag.SetValue(true)
	}

	err := daemon.Init()
	if err != nil {
		return err
	}

	err = systemdUpdateStatus("\n")
	if err == nil {
		info.systemd = true
	}

	// default:                   daemon=no,  stderr=yes
	// --daemon:                  daemon=yes, stderr=no
	// systemd/--daemon --stderr: daemon=yes, stderr=yes
	// systemd --daemon:          daemon=yes, stderr=no
	daemonize := daemonizeFlag.Value()
	keepStderr := stderrFlag.Value()
	if !daemonize && info.systemd {
		daemonize = true
		keepStderr = true
	}

	if daemonize {
		err := daemon.Daemonize(keepStderr)
		if err != nil {
			return err
		}
	}

	if pidfileFlag.Value() != "" {
		info.pidFileName = pidfileFlag.Value()

		err = info.openPIDFile()
		if err != nil {
			return err
		}

		defer info.closePIDFile()
	}

	return info.runInteractively()
}

func (info *Info) openPIDFile() error {
	f, err := pidfile.Open(info.pidFileName)
	info.pidFile = f
	return err
}

func (info *Info) closePIDFile() {
	if info.pidFile != nil {
		info.pidFile.Close()
	}
}

func (h *ihandler) DropPrivileges() error {
	if h.dropped {
		return nil
	}

	// Extras
	if !h.info.NoBanSuid {
		// Try and bansuid, but don't process errors. It may not be supported on
		// the current platform, and Linux won't allow SECUREBITS to be set unless
		// one is root (or has the right capability). This is basically a
		// best-effort thing.
		bansuid.BanSuid()
	}

	// Various fixups
	if uidFlag.Value() != "" && gidFlag.Value() == "" {
		gid, err := passwd.GetGIDForUID(uidFlag.Value())
		if err != nil {
			return err
		}
		gidFlag.SetValue(strconv.FormatInt(int64(gid), 10))
	}

	if h.info.DefaultChroot == "" {
		h.info.DefaultChroot = "/"
	}

	chrootPath := chrootFlag.Value()
	if chrootPath == "" {
		chrootPath = h.info.DefaultChroot
	}

	uid := -1
	gid := -1
	if uidFlag.Value() != "" {
		var err error
		uid, err = passwd.ParseUID(uidFlag.Value())
		if err != nil {
			return err
		}

		gid, err = passwd.ParseGID(gidFlag.Value())
		if err != nil {
			return err
		}
	}

	if (uid <= 0) != (gid <= 0) {
		return fmt.Errorf("Either both or neither of the UID and GID must be positive")
	}

	if uid > 0 {
		chrootErr, err := daemon.DropPrivileges(uid, gid, chrootPath)
		if err != nil {
			return fmt.Errorf("Failed to drop privileges: %v", err)
		}
		if chrootErr != nil && chrootFlag.Value() != "" && chrootFlag.Value() != "/" {
			return fmt.Errorf("Failed to chroot: %v", chrootErr)
		}
	} else if chrootFlag.Value() != "" && chrootFlag.Value() != "/" {
		return fmt.Errorf("Must use privilege dropping to use chroot; set -uid")
	}

	// If we still have any caps (maybe because we didn't setuid), try and drop them.
	err := caps.Drop()
	if err != nil {
		return fmt.Errorf("cannot drop caps: %v", err)
	}

	if !h.info.AllowRoot && daemon.IsRoot() {
		return fmt.Errorf("Daemon must not run as root or with capabilities; run as non-root user or use -uid")
	}

	h.dropped = true
	return nil
}

// © 2015 Hugo Landau <hlandau@devever.net>  ISC License