summaryrefslogtreecommitdiff
path: root/nslcd_systemd/nslcd_systemd.go
blob: 0999106a73efbdcd1608f6911d5d101af6c55c65 (plain)
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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
// Copyright (C) 2015-2017 Luke Shumaker <lukeshu@sbcglobal.net>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
// 02110-1301 USA

// Package nslcd_systemd does the legwork for implementing a systemd
// socket-activated nslcd server.
//
// You just need to implement the Backend interface, then pass it to
// Main, which will return the exit code for the process.  Everything
// but the backend is taken care of for you!
//
// 	package main
//
// 	import (
// 		"os"
//
// 		"git.lukeshu.com/go/libnslcd/nslcd_server"
// 		"git.lukeshu.com/go/libnslcd/nslcd_systemd"
// 	)
//
// 	func main() {
// 		backend := ...
// 		limits := nslcd_server.Limits{ ... }
// 		os.Exit(int(nslcd_systemd.Main(backend, limits)))
// 	}
package nslcd_systemd // import "git.lukeshu.com/go/libnslcd/nslcd_systemd"

import (
	"fmt"
	"net"
	"os"
	"os/signal"
	"sync"
	"time"

	"git.lukeshu.com/go/libnslcd/nslcd_server"
	"git.lukeshu.com/go/libsystemd/sd_daemon"
	"golang.org/x/sys/unix"
)

type Backend interface {
	nslcd_server.Backend
	Init() error
	Reload() error
	Close()
}

func get_socket() (socket net.Listener, err error) {
	fds := sd_daemon.ListenFds(true)
	if fds == nil {
		err = fmt.Errorf("Failed to aquire sockets from systemd")
		return
	}
	if len(fds) != 1 {
		err = fmt.Errorf("Wrong number of sockets from systemd: expected %d but got %d", 1, len(fds))
		return
	}
	socket, err = net.FileListener(fds[0])
	fds[0].Close()
	return
}

func getpeercred(conn *net.UnixConn) (cred unix.Ucred, err error) {
	rawconn, err := conn.SyscallConn()
	if err != nil {
		return
	}
	var _cred *unix.Ucred
	var _err error
	err = rawconn.Control(func(fd uintptr) {
		_cred, _err = unix.GetsockoptUcred(int(fd), unix.SOL_SOCKET, unix.SO_PEERCRED)
	})
	if err != nil {
		return
	}
	if _err != nil {
		err = _err
		return
	}
	cred = *_cred
	return
}

func handler(backend nslcd_server.Backend, limits nslcd_server.Limits, conn *net.UnixConn) {
	defer conn.Close()
	cred, err := getpeercred(conn)
	if err != nil {
		sd_daemon.Log.Debug("Connection from unknown client")
	} else {
		sd_daemon.Log.Debug(fmt.Sprintf("Connection from pid=%v uid=%v gid=%v",
			cred.Pid, cred.Uid, cred.Gid))
	}
	err = nslcd_server.HandleRequest(backend, limits, conn, cred)
	if err != nil {
		sd_daemon.Log.Notice(fmt.Sprintf("Error while handling request: %v", err))
	}
}

func Main(backend Backend, limits nslcd_server.Limits) uint8 {
	defer sd_daemon.Recover()
	var err error = nil

	sigs := make(chan os.Signal)
	signal.Notify(sigs, unix.SIGTERM, unix.SIGHUP)

	disable_nss_module()

	err = backend.Init()
	if err != nil {
		sd_daemon.Log.Err(fmt.Sprintf("Could not initialize backend: %v", err))
		sd_daemon.Notification{State: "STOPPING=1"}.Send(false)
		return sd_daemon.EXIT_FAILURE
	}
	defer backend.Close()

	var wg sync.WaitGroup
	defer wg.Wait()

	socket, err := get_socket()
	if err != nil {
		sd_daemon.Log.Err(fmt.Sprintf("%v", err))
		sd_daemon.Notification{State: "STOPPING=1"}.Send(false)
		return sd_daemon.EXIT_NOTRUNNING
	}
	defer func() { socket.(*net.UnixListener).SetDeadline(time.Now()) }()

	socket_error := make(chan error)
	wg.Add(1)
	go func() {
		defer sd_daemon.Recover()
		defer wg.Done()

		var tempDelay time.Duration
		last := false
		for !last {
			conn, err := socket.Accept()
			if err != nil {
				if ne, ok := err.(net.Error); ok && ne.Timeout() {
					last = true
				} else if ne, ok := err.(net.Error); ok && ne.Temporary() {
					sd_daemon.Log.Notice(fmt.Sprintf("temporary error %v", err))
					if tempDelay == 0 {
						tempDelay = 5 * time.Millisecond
					} else {
						tempDelay *= 2
					}
					if max := 1 * time.Second; tempDelay > max {
						tempDelay = max
					}
					time.Sleep(tempDelay)
				} else {
					socket_error <- err
					last = true
				}
			}
			if conn != nil {
				wg.Add(1)
				go func() {
					defer sd_daemon.Recover()
					defer wg.Done()
					handler(backend, limits, conn.(*net.UnixConn))
				}()
			}
		}
	}()

	defer sd_daemon.Notification{State: "STOPPING=1"}.Send(false)
	sd_daemon.Notification{State: "READY=1"}.Send(false)
	for {
		select {
		case sig := <-sigs:
			switch sig {
			case unix.SIGTERM:
				sd_daemon.Log.Notice("Received SIGTERM, shutting down")
				return sd_daemon.EXIT_SUCCESS
			case unix.SIGHUP:
				sd_daemon.Log.Notice("Received SIGHUP, reloading")
				sd_daemon.Notification{State: "RELOADING=1"}.Send(false)
				err := backend.Reload()
				if err != nil {
					sd_daemon.Log.Notice(fmt.Sprintf("Could not reload backend: %v", err))
					return sd_daemon.EXIT_NOTRUNNING
				}
				sd_daemon.Notification{State: "READY=1"}.Send(false)
			}
		case err = <-socket_error:
			sd_daemon.Log.Err(fmt.Sprintf("%v", err))
			return sd_daemon.EXIT_NETWORK
		}
	}
}