nldev.c - nldev - NetLink DEVice manager; a lightweight netlink frontend for mdev.
(HTM) git clone git://r-36.net/nldev
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) README
(DIR) LICENSE
---
nldev.c (6568B)
---
1 /* See LICENSE for copyright information. */
2
3 #include <unistd.h>
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <stdarg.h>
7 #include <poll.h>
8 #include <ctype.h>
9 #include <string.h>
10 #include <errno.h>
11 #include <signal.h>
12 #include <libgen.h>
13 #include <fcntl.h>
14 #include <sys/socket.h>
15 #include <sys/types.h>
16 #include <sys/wait.h>
17 #include <sys/stat.h>
18 #include <linux/types.h>
19 #include <linux/netlink.h>
20
21 typedef struct {
22 char *action; /* ACTION to run rule for */
23 char *subsystem; /* SUBSYSTEM to run the rule for, NULL for any */
24 char *envvar; /* other environment variable to run rule for, NULL for any */
25 char *runpath;
26 } Rule;
27
28 #include "arg.h"
29 #include "config.h"
30
31 #define LENGTH(X) (sizeof X / sizeof X[0])
32
33 char *argv0;
34 int listfd = -1;
35 int dofork = 0, dodebug = 0;
36
37 void
38 edie(char *fmt, ...)
39 {
40 va_list fmtargs;
41
42 va_start(fmtargs, fmt);
43 vfprintf(stderr, fmt, fmtargs);
44 va_end(fmtargs);
45 fprintf(stderr, ": ");
46
47 perror(NULL);
48
49 exit(1);
50 }
51
52 void
53 die(char *fmt, ...)
54 {
55 va_list fmtargs;
56
57 va_start(fmtargs, fmt);
58 vfprintf(stderr, fmt, fmtargs);
59 va_end(fmtargs);
60
61 exit(1);
62 }
63
64 void
65 dbg(char *fmt, ...)
66 {
67 va_list fmtargs;
68
69 if (dodebug) {
70 fprintf(stderr, "%s: ", argv0);
71 va_start(fmtargs, fmt);
72 vfprintf(stderr, fmt, fmtargs);
73 va_end(fmtargs);
74 fprintf(stderr, "\n");
75 }
76 }
77
78 void
79 disableoom(void)
80 {
81 int fd;
82
83 fd = open("/proc/self/oom_score_adj", O_RDWR);
84 if (fd < 0) {
85 fd = open("/proc/self/oom_adj", O_RDWR);
86 if (fd < 0)
87 edie("disabling oom failed.");
88 write(fd, "-17", 3);
89 close(fd);
90 } else {
91 write(fd, "-1000", 5);
92 close(fd);
93 }
94 }
95
96 void
97 child(char *runpath)
98 {
99 int fd, pid;
100
101 if (!(pid = fork())) {
102 if (dofork && !dodebug) {
103 fd = open("/dev/null", O_RDWR);
104 if (fd >= 0) {
105 dup2(fd, 1);
106 dup2(fd, 2);
107 if (fd > 2)
108 close(fd);
109 }
110 }
111
112 dbg("running %s", runpath);
113 if (execlp(runpath, basename(runpath), NULL) < 0)
114 edie("execvp");
115 exit(0);
116 }
117 if (pid < 0)
118 edie("fork");
119 }
120
121 void
122 sighandler(int sig)
123 {
124 switch(sig) {
125 case SIGHUP:
126 case SIGINT:
127 case SIGQUIT:
128 case SIGABRT:
129 case SIGTERM:
130 if (listfd >= 0) {
131 shutdown(listfd, SHUT_RDWR);
132 close(listfd);
133 }
134 exit(0);
135 break;
136 default:
137 break;
138 }
139 }
140
141 void
142 initsignals(void)
143 {
144 signal(SIGHUP, sighandler);
145 signal(SIGINT, sighandler);
146 signal(SIGQUIT, sighandler);
147 signal(SIGABRT, sighandler);
148 signal(SIGTERM, sighandler);
149
150 signal(SIGCHLD, SIG_IGN);
151 signal(SIGPIPE, SIG_IGN);
152 }
153
154 int
155 init_netlink_socket(void)
156 {
157 struct sockaddr_nl nls;
158 int fd, slen;
159
160 memset(&nls, 0, sizeof(nls));
161 nls.nl_family = AF_NETLINK;
162 nls.nl_pid = getpid();
163 nls.nl_groups = -1;
164
165 fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
166 if (fd < 0)
167 edie("socket");
168
169 slen = 16*1024;
170 if (setsockopt(fd, SOL_SOCKET, SO_RCVBUFFORCE, &slen,
171 sizeof(slen)) < 0) {
172 edie("setsockopt");
173 }
174 slen = 1;
175 if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &slen,
176 sizeof(slen)) < 0) {
177 edie("setsockopt");
178 }
179
180 if (bind(fd, (void *)&nls, sizeof(nls)))
181 edie("bind");
182
183 fcntl(fd, F_SETFD, FD_CLOEXEC);
184 return fd;
185 }
186
187 void
188 usage(void)
189 {
190 die("usage: %s [-hdb] [-ku] [-f subsystem] [-r run]\n", argv0);
191 }
192
193 int
194 main(int argc, char *argv[])
195 {
196 struct sockaddr_nl cnls;
197 struct pollfd fds;
198 struct msghdr hdr;
199 struct iovec iov;
200 char buf[4097], *subsystem, *runpath, *key, *value,
201 cbuf[CMSG_SPACE(sizeof(struct ucred))];
202 struct cmsghdr *chdr;
203 struct ucred *cred;
204 int i, len, slen, showudev, showkernel;
205
206 showkernel = 1;
207 showudev = 1;
208 subsystem = NULL;
209 runpath = NULL;
210
211 ARGBEGIN {
212 case 'b':
213 dofork = 1;
214 break;
215 case 'd':
216 dodebug = 1;
217 break;
218 case 'f':
219 subsystem = EARGF(usage());
220 if (!runpath)
221 runpath = "/bin/mdev";
222 break;
223 case 'k':
224 showudev = 0;
225 break;
226 case 'u':
227 showkernel = 0;
228 break;
229 case 'r':
230 runpath = EARGF(usage());
231 break;
232 default:
233 usage();
234 } ARGEND;
235
236 fds.events = POLLIN;
237 fds.fd = init_netlink_socket();
238 listfd = fds.fd;
239
240 if (dofork) {
241 if (daemon(0, 0) < 0)
242 edie("daemon");
243 umask(022);
244 }
245
246 initsignals();
247 disableoom();
248
249 buf[sizeof(buf)-1] = '\0';
250 while (poll(&fds, 1, -1) > -1) {
251 clearenv();
252 setenv("PATH", "/sbin:/bin", 1);
253
254 iov.iov_base = &buf;
255 iov.iov_len = sizeof(buf);
256 memset(&hdr, 0, sizeof(hdr));
257 hdr.msg_iov = &iov;
258 hdr.msg_iovlen = 1;
259 hdr.msg_control = cbuf;
260 hdr.msg_controllen = sizeof(cbuf);
261 hdr.msg_name = &cnls;
262 hdr.msg_namelen = sizeof(cnls);
263
264 len = recvmsg(fds.fd, &hdr, 0);
265 if (len < 0) {
266 if (errno == EINTR)
267 continue;
268 edie("recvmsg");
269 }
270 if (len < 32 || len >= sizeof(buf))
271 continue;
272
273 chdr = CMSG_FIRSTHDR(&hdr);
274 if (chdr == NULL || chdr->cmsg_type != SCM_CREDENTIALS)
275 continue;
276
277 /*
278 * Don't allow anyone but root to send us messages.
279 *
280 * We will allow users to send us messages, when
281 * udev is enabled. Udev is just a toy you should
282 * only use for testing.
283 */
284 cred = (struct ucred *)CMSG_DATA(chdr);
285 if (cred->uid != 0 && !showudev)
286 continue;
287
288 if (!memcmp(buf, "libudev", 8)) {
289 /*
290 * Receiving messages from udev is insecure.
291 */
292 if (!showudev)
293 continue;
294 } else {
295 if (!showkernel)
296 continue;
297 /*
298 * Kernel messages shouldn't come from the
299 * userspace.
300 */
301 if (cnls.nl_pid > 0)
302 continue;
303 }
304
305 slen = 0;
306 for (i = 0; i < len; i += slen + 1) {
307 key = buf + i;
308 value = strchr(key, '=');
309 slen = strlen(buf+i);
310
311 if (!slen || value == NULL)
312 continue;
313 if (subsystem && !strncmp(key, "SUBSYSTEM=", 10)
314 && !strstr(key+10, subsystem)) {
315 dbg("subsystem filter '%s' applied.",
316 subsystem);
317 break;
318 }
319
320 value[0] = '\0';
321 value++;
322
323 /*
324 * We generally trust the kernel. But there
325 * might be some udev flaw. (It's >20k sloc!)
326 */
327 if (strcmp(key, "PATH")) {
328 setenv(key, value, 1);
329 dbg("%s = \"%s\"", key, value);
330 }
331 }
332 if (getenv("ACTION") != NULL &&
333 getenv("DEVPATH") != NULL &&
334 getenv("SUBSYSTEM") != NULL &&
335 getenv("SEQNUM") != NULL) {
336 if (runpath) {
337 child(runpath);
338 } else {
339 for (i = 0; i < LENGTH(rules); i+=1) {
340 if (rules[i].action == NULL
341 || rules[i].runpath == NULL) {
342 continue;
343 }
344 if (strcmp(getenv("ACTION"), rules[i].action)) {
345 continue;
346 }
347 if (rules[i].subsystem != NULL) {
348 if (strcmp(getenv("SUBSYSTEM"),
349 rules[i].subsystem)) {
350 continue;
351 }
352 }
353 if (rules[i].envvar != NULL) {
354 if (getenv(rules[i].envvar) == NULL) {
355 continue;
356 }
357 }
358 child(rules[i].runpath);
359 }
360 }
361 }
362 }
363
364 shutdown(listfd, SHUT_RDWR);
365 close(listfd);
366
367 return 0;
368 }
369