luad

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

luad.c (8607B)


      1 #include <unistd.h>
      2 
      3 #include <lua.h>
      4 #include <lauxlib.h>
      5 #include <lualib.h>
      6 
      7 #include <sys/socket.h>
      8 #include <string.h>
      9 #include <stdlib.h>
     10 #include <stdint.h>
     11 #include <stdbool.h>
     12 #include <errno.h>
     13 #include <error.h>
     14 #include <sys/un.h>
     15 
     16 #include <signal.h>
     17 #include <sys/wait.h>
     18 
     19 #include <poll.h>
     20 #include <arpa/inet.h>
     21 
     22 #include <stdarg.h>
     23 #include <stdnoreturn.h>
     24 
     25 static int sigchild_fd = -1;
     26 
     27 static void handle_sigchld(int num) {
     28     if(sigchild_fd >= 0) {
     29         (void) !write(sigchild_fd, "\n", 1);
     30     }
     31 }
     32 
     33 static void noreturn die(char *fmt, ...) {
     34     va_list ap;
     35     va_start(ap, fmt);
     36     if(errno > 0) {
     37         fprintf(stderr, "luad: %s: ", strerror(errno));
     38     } else {
     39         fprintf(stderr, "luad: ");
     40     }
     41     vfprintf(stderr, fmt, ap);
     42     exit(1);
     43 }
     44 
     45 
     46 int open_socket(char * pathname) {
     47     struct sockaddr_un addr;
     48     addr.sun_family = AF_UNIX;
     49     if(strlen(pathname) >= sizeof(addr.sun_path)) {
     50         die("pathname \"%s\" is too long to  bind as unix socket (max %lu)",
     51             pathname, sizeof(addr.sun_path));
     52     }
     53     strcpy(addr.sun_path, pathname);
     54 
     55     int fd = socket(AF_LOCAL, SOCK_DGRAM, PF_LOCAL);
     56     if(fd < 0) {
     57         die("can't create socket \"%s\"", pathname);
     58     }
     59 
     60     if((bind(fd, (struct sockaddr *) &addr, sizeof addr)) < 0) {
     61         die("can't bind socket \"%s\"", pathname);
     62     }
     63     return fd;
     64 }
     65 
     66 int * fds_from_msg(struct msghdr *msg, int * peer_fds) {
     67     // * there may be multiple SCM_RIGHTS messages
     68     // * we expect three fds
     69     // * if we get some number other than three, we should
     70     //   close all of them and return NULL because someone is up to no good
     71     int i = 0;
     72     peer_fds[0] = -1;
     73     peer_fds[1] = -1;
     74     peer_fds[2] = -1;
     75 
     76     for (struct cmsghdr* cmsg = CMSG_FIRSTHDR(msg);
     77          cmsg != NULL;
     78          cmsg = CMSG_NXTHDR(msg, cmsg)) {
     79         if(cmsg->cmsg_level == SOL_SOCKET &&
     80            cmsg->cmsg_type == SCM_RIGHTS) {
     81             int passed_fd_count = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof (int);
     82             if(i + passed_fd_count <= 3) {
     83                 memcpy(&peer_fds[i], CMSG_DATA(cmsg), passed_fd_count * sizeof(int));
     84 
     85             } else {
     86                 int * reject_fds = (int *) CMSG_DATA(cmsg);
     87                 for(int fd = 0; fd < passed_fd_count; fd++) {
     88                     close(reject_fds[fd]);
     89                 }
     90             }
     91             i += passed_fd_count;
     92         }
     93     }
     94     if(i != 3) {
     95         /* if the peer sent too many fds, we closed the excess ones
     96          * in the reject_fds clause above and now we'll close the
     97          * first three.
     98          * if they sent too few, we'll close whatever they sent
     99          */
    100 
    101         if(peer_fds[0] >=0) close(peer_fds[0]);
    102         if(peer_fds[1] >=0) close(peer_fds[1]);
    103         if(peer_fds[2] >=0) close(peer_fds[2]);
    104         return NULL;
    105     } else {
    106         return peer_fds;
    107     }
    108 }
    109 
    110 void start_lua_child(lua_State *L,
    111                      char * command_line, int command_line_length,
    112                      int * fds) {
    113     char * word = command_line;
    114     char * command_line_end = command_line + command_line_length;
    115     char * pathname = NULL;
    116     int i = 0;
    117 
    118     fflush(stdout); fflush(stderr);
    119     for(int i=0; i < 3; i++) {
    120         dup2(fds[i], i);
    121     }
    122 
    123     lua_newtable(L);            /* arg */
    124     while(word < command_line_end) {
    125         char * word_end = memchr(word, '\0', command_line_end - word);
    126         if(!word_end) break;
    127         if(!pathname)  {
    128             pathname = strndup(word, (word_end - word));
    129         }
    130         lua_pushlstring(L, word, (word_end - word));
    131         lua_seti(L, -2, i++);
    132         word = word_end + 1;
    133     }
    134     if(! pathname) {
    135         die("no script name received from client");
    136     }
    137     lua_setglobal(L, "arg");
    138     int status = luaL_dofile(L, pathname);
    139     if (status) {
    140         die("Lua error: %s", lua_tostring(L, -1));
    141     }
    142 }
    143 
    144 pid_t fork_lua(lua_State *L,
    145               char * command_line, int command_line_length,
    146                int * fds) {
    147     pid_t pid = fork();
    148     if(pid == 0) {
    149         start_lua_child(L, command_line, command_line_length, fds);
    150         exit(0);
    151     } else if(pid > 0) {
    152         free(command_line);
    153         return pid;
    154     } else {
    155         die("fork failed");
    156     }
    157 }
    158 
    159 struct peer_entry {
    160     pid_t pid;
    161     int addrlen;
    162     struct sockaddr_un addr;
    163 } peer_entries[500];
    164 
    165 bool register_peer(pid_t pid, struct sockaddr_un *addr, int addrlen) {
    166     if(addrlen > sizeof (struct sockaddr_un))
    167         return false;
    168 
    169     int peer_entries_max = sizeof peer_entries / sizeof(peer_entries[0]);
    170 
    171     for(int i=0; i < peer_entries_max; i++) {
    172         struct peer_entry *p = &peer_entries[i];
    173         if(p->pid == 0) {
    174             p->pid = pid;
    175             p->addrlen = addrlen;
    176             memcpy(&(p->addr), addr, addrlen);
    177             return true;
    178         }
    179     }
    180     return false;
    181 }
    182 
    183 struct peer_entry* find_peer(pid_t pid) {
    184     int peer_entries_max = sizeof peer_entries / sizeof(peer_entries[0]);
    185 
    186     for(int i=0; i < peer_entries_max; i++) {
    187         struct peer_entry *p = &peer_entries[i];
    188         if(p->pid == pid) {
    189             return p;
    190         }
    191     }
    192     return NULL;
    193 }
    194 
    195 void hangup_peer(int socket, struct sockaddr_un * peer_addr,
    196                  size_t peer_addr_len,
    197                  uint32_t retval) {
    198     sendto(socket, (void *) &retval, 4, 0, (struct sockaddr *) peer_addr, peer_addr_len);
    199 }
    200 
    201 int main(int argc, char **argv)
    202 {
    203     int status;
    204     lua_State *L = luaL_newstate();
    205     luaL_openlibs(L);
    206     int sigpipe_fds[2];
    207 
    208     char * socket_path = getenv("LUAD_SOCKET_PATH");
    209     if(! socket_path) {
    210         error(1, 0, "Missing required environment variable LUAD_SOCKET_PATH");
    211     }
    212 
    213     if(pipe(sigpipe_fds) < 0) {
    214         die("can't create pipe");
    215     }
    216     sigchild_fd = sigpipe_fds[1];
    217 
    218     signal(SIGCHLD, handle_sigchld);
    219 
    220     if (argc >= 2) {
    221         status = luaL_dofile(L, argv[1]);
    222         if (status) {
    223             error(1, 0, "Error: %s", lua_tostring(L, -1));
    224         }
    225     }
    226 
    227     int socket = open_socket(socket_path);
    228     struct sockaddr_un peer_addr;
    229 
    230     char buf[1025];
    231     struct iovec iov[1];
    232     iov[0].iov_base = buf;
    233     iov[0].iov_len = sizeof buf;
    234     struct cmsghdr* cmsg = (struct cmsghdr *) calloc(1000, 1);
    235 
    236     struct msghdr msg = {
    237         .msg_name = &peer_addr,
    238         .msg_namelen = sizeof peer_addr,
    239         .msg_iov = iov,
    240         .msg_iovlen = 1,
    241         .msg_control = cmsg,
    242         .msg_controllen = 1000
    243     };
    244 
    245     struct pollfd pollfds[2] = {
    246         {
    247             .fd = sigpipe_fds[0],
    248             .events = POLLIN,
    249             .revents = 0
    250         },
    251         {
    252             .fd = socket,
    253             .events = POLLIN,
    254             .revents = 0
    255         },
    256     };
    257 
    258     while(1) {
    259         pollfds[0].revents = 0;
    260         pollfds[1].revents = 0;
    261 
    262         /* if find_peer says the peer table is full, don't poll the
    263          * socket
    264          */
    265         if(poll(pollfds, find_peer(0) ? 2 : 1, 10000) == 0)
    266             continue;
    267 
    268         if(pollfds[1].revents) {
    269             int len = recvmsg(socket, &msg , 0);
    270             if(len <= 0) continue;
    271 
    272             int peer_fds[3];
    273             if(fds_from_msg(&msg, peer_fds)) {
    274                 char *argline = malloc(len);
    275                 if(! argline) {
    276                     die("malloc failed");
    277                 }
    278                 memmove(argline, buf, len);
    279 
    280                 if(msg.msg_flags & (MSG_TRUNC | MSG_CTRUNC)) {
    281                     hangup_peer(socket, &peer_addr, msg.msg_namelen, 0xffff);
    282                 } else {
    283                     pid_t child_pid = fork_lua(L, argline, len, peer_fds);
    284                     register_peer(child_pid, &peer_addr, msg.msg_namelen);
    285                 }
    286                 /* close peer fds in parent */
    287                 close(peer_fds[0]);
    288                 close(peer_fds[1]);
    289                 close(peer_fds[2]);
    290             }
    291         }
    292 
    293         if(pollfds[0].revents) {
    294             pid_t pid;
    295             int wstatus;
    296 
    297             if(read(pollfds[1].fd, buf, sizeof buf) < 0)
    298                 continue;
    299 
    300             while((pid = waitpid(0, &wstatus, WNOHANG)) > 0) {
    301                 struct peer_entry *peer = find_peer(pid);
    302                 if(peer) {
    303                     uint32_t ret = 0;
    304                     if(WIFEXITED(wstatus)) {
    305                         ret = htonl(WEXITSTATUS(wstatus));
    306                     } else if(WIFSIGNALED(wstatus)) {
    307                         ret = htonl(128 + WTERMSIG(wstatus));
    308                     }
    309                     hangup_peer(socket, &(peer->addr), peer->addrlen, ret);
    310                     peer->pid = 0;
    311                 }
    312             }
    313         }
    314     }
    315     return 0;
    316 }