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