nldev

NetLink DEVice manager; a lightweight netlink frontend for mdev.
git clone git://r-36.net/nldev
Log | Files | Refs | 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