linkd

Control plane daemon for unos
git clone git://git.finwo.net/app/linkd
Log | Files | Refs | README

client.c (5109B)


      1 #define _GNU_SOURCE
      2 #include <errno.h>
      3 #include <stdio.h>
      4 #include <stdlib.h>
      5 #include <string.h>
      6 #include <sys/socket.h>
      7 #include <sys/time.h>
      8 #include <sys/un.h>
      9 #include <unistd.h>
     10 
     11 #include "finwo/resp.h"
     12 
     13 #include "cli/client.h"
     14 #include "config/daemon.h"
     15 
     16 #define CLIENT_TIMEOUT_S 10
     17 
     18 static int connect_daemon(const char **path_out) {
     19   const char *sockpath = linkd_client_socket();
     20   *path_out = sockpath;
     21 
     22   int fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
     23   if (fd < 0) {
     24     perror("socket");
     25     return -1;
     26   }
     27 
     28   struct sockaddr_un addr;
     29   memset(&addr, 0, sizeof(addr));
     30   addr.sun_family = AF_UNIX;
     31   strncpy(addr.sun_path, sockpath, sizeof(addr.sun_path) - 1);
     32 
     33   if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
     34     fprintf(stderr, "connect %s failed: %s\nIs linkd running?\n",
     35             sockpath, strerror(errno));
     36     close(fd);
     37     return -1;
     38   }
     39 
     40   struct timeval tv = { .tv_sec = CLIENT_TIMEOUT_S, .tv_usec = 0 };
     41   setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
     42   setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
     43   return fd;
     44 }
     45 
     46 static const char *map_get(const resp_object *m, const char *key) {
     47   if (!m || m->type != RESPT_ARRAY) return NULL;
     48   for (size_t i = 0; i + 1 < m->u.arr.n; i += 2) {
     49     const resp_object *k = &m->u.arr.elem[i];
     50     if ((k->type == RESPT_BULK || k->type == RESPT_SIMPLE) && k->u.s &&
     51         !strcmp(k->u.s, key)) {
     52       const resp_object *v = &m->u.arr.elem[i + 1];
     53       if (v->type == RESPT_BULK || v->type == RESPT_SIMPLE) return v->u.s;
     54     }
     55   }
     56   return NULL;
     57 }
     58 
     59 // An interface map renders as the config syntax it came from, so ifquery
     60 // output can be pasted back into /etc/network/interfaces.
     61 static int render_iface_map(const resp_object *m) {
     62   const char *name = map_get(m, "name");
     63   if (!name) return 0;
     64 
     65   printf("iface %s\n", name);
     66   for (size_t i = 0; i + 1 < m->u.arr.n; i += 2) {
     67     const resp_object *k = &m->u.arr.elem[i];
     68     const resp_object *v = &m->u.arr.elem[i + 1];
     69     if (k->type != RESPT_BULK && k->type != RESPT_SIMPLE) continue;
     70     if (!strcmp(k->u.s, "name")) continue;
     71     if (v->type == RESPT_BULK || v->type == RESPT_SIMPLE) {
     72       printf("  %s %s\n", k->u.s, v->u.s);
     73     } else if (v->type == RESPT_INT) {
     74       printf("  %s %lld\n", k->u.s, v->u.i);
     75     }
     76   }
     77   return 1;
     78 }
     79 
     80 static void render(const resp_object *o, int depth) {
     81   if (!o) return;
     82   switch (o->type) {
     83     case RESPT_SIMPLE:
     84       printf("%s\n", o->u.s ? o->u.s : "");
     85       break;
     86     case RESPT_BULK:
     87       if (o->u.s) fputs(o->u.s, stdout);
     88       if (o->u.s && o->u.s[strlen(o->u.s) - 1] != '\n') fputc('\n', stdout);
     89       break;
     90     case RESPT_INT:
     91       printf("%lld\n", o->u.i);
     92       break;
     93     case RESPT_ARRAY:
     94       if (depth == 0 && render_iface_map(o)) break;
     95       for (size_t i = 0; i < o->u.arr.n; i++) {
     96         const resp_object *e = &o->u.arr.elem[i];
     97         if (e->type == RESPT_ARRAY && render_iface_map(e)) continue;
     98         render(e, depth + 1);
     99       }
    100       break;
    101     default:
    102       break;
    103   }
    104 }
    105 
    106 static int send_obj(int fd, resp_object *o) {
    107   char  *buf = NULL;
    108   size_t len = 0;
    109   int    rc  = resp_serialize(o, &buf, &len);
    110   resp_free(o);
    111   if (rc != 0) return -1;
    112 
    113   size_t sent = 0;
    114   while (sent < len) {
    115     ssize_t n = write(fd, buf + sent, len - sent);
    116     if (n <= 0) {
    117       if (n < 0 && errno == EINTR) continue;
    118       fprintf(stderr, "write failed: %s\n", strerror(errno));
    119       free(buf);
    120       return -1;
    121     }
    122     sent += (size_t)n;
    123   }
    124   free(buf);
    125   return 0;
    126 }
    127 
    128 // $LINKD_AUTH as "user:password", or just "password" for the default user.
    129 static int maybe_auth(int fd) {
    130   const char *cred = getenv("LINKD_AUTH");
    131   if (!cred || !*cred) return 0;
    132 
    133   char *copy = strdup(cred);
    134   if (!copy) return -1;
    135   char *sep  = strchr(copy, ':');
    136   const char *user = "default";
    137   const char *pass = copy;
    138   if (sep) { *sep = '\0'; user = copy; pass = sep + 1; }
    139 
    140   resp_object *a = resp_array_init();
    141   resp_array_append_bulk(a, "AUTH");
    142   resp_array_append_bulk(a, user);
    143   resp_array_append_bulk(a, pass);
    144   free(copy);
    145 
    146   if (send_obj(fd, a) != 0) return -1;
    147 
    148   resp_object *r = resp_read(fd);
    149   if (!r) {
    150     fprintf(stderr, "no reply to AUTH\n");
    151     return -1;
    152   }
    153   int bad = (r->type == RESPT_ERROR);
    154   if (bad) fprintf(stderr, "%s\n", r->u.s ? r->u.s : "AUTH failed");
    155   resp_free(r);
    156   return bad ? -1 : 0;
    157 }
    158 
    159 int linkd_call(int argc, const char **argv) {
    160   const char *sockpath = NULL;
    161   int fd = connect_daemon(&sockpath);
    162   if (fd < 0) return 1;
    163 
    164   if (maybe_auth(fd) != 0) { close(fd); return 1; }
    165 
    166   resp_object *req = resp_array_init();
    167   if (!req) { close(fd); return 1; }
    168   for (int i = 0; i < argc; i++) resp_array_append_bulk(req, argv[i]);
    169 
    170   if (send_obj(fd, req) != 0) { close(fd); return 1; }
    171 
    172   resp_object *reply = resp_read(fd);
    173   close(fd);
    174 
    175   if (!reply) {
    176     fprintf(stderr, "no reply from linkd\n");
    177     return 1;
    178   }
    179 
    180   int status = 0;
    181   if (reply->type == RESPT_ERROR) {
    182     fprintf(stderr, "%s\n", reply->u.s ? reply->u.s : "error");
    183     status = 1;
    184   } else {
    185     render(reply, 0);
    186   }
    187   resp_free(reply);
    188   return status;
    189 }