daemon.c (9774B)
1 #define _GNU_SOURCE 2 #include <libgen.h> 3 #include <stdio.h> 4 #include <stdlib.h> 5 #include <string.h> 6 #include <strings.h> 7 #include <unistd.h> 8 9 #include "finwo/cnfparse.h" 10 11 #include "config/daemon.h" 12 #include "dataplane/plugin.h" 13 #include "util/config.h" 14 15 static struct linkd_daemon_cfg cfg; 16 static int cfg_defaults_applied = 0; 17 18 // --------------------------------------------------------------------------- 19 // string-list helpers 20 21 static int list_append(char ***list, size_t *n, const char *val) { 22 char **grown = realloc(*list, sizeof(char *) * (*n + 1)); 23 if (!grown) return -1; 24 *list = grown; 25 (*list)[*n] = strdup(val); 26 if (!(*list)[*n]) return -1; 27 (*n)++; 28 return 0; 29 } 30 31 static void list_free(char ***list, size_t *n) { 32 for (size_t i = 0; i < *n; i++) free((*list)[i]); 33 free(*list); 34 *list = NULL; 35 *n = 0; 36 } 37 38 // Replace rather than append: a later config_iface overrides, not merges. 39 static struct cnf_directive *take_list(struct cnf_directive *dir, char ***list, size_t *n) { 40 if (dir->argc < 1) { 41 cfg_error("%s needs at least one path or glob", dir->name); 42 cnf_directive_free(dir); 43 return NULL; 44 } 45 list_free(list, n); 46 for (size_t i = 0; i < dir->argc; i++) { 47 if (list_append(list, n, dir->argv[i]) != 0) { 48 cfg_error("out of memory reading %s", dir->name); 49 cnf_directive_free(dir); 50 return NULL; 51 } 52 } 53 cnf_directive_free(dir); 54 return NULL; 55 } 56 57 // --------------------------------------------------------------------------- 58 // directives 59 60 static struct cnf_directive *h_config_ports(FILE *fd, struct cnf_directive *dir, void *user) { 61 (void)fd; (void)user; 62 return take_list(dir, &cfg.ports, &cfg.ports_n); 63 } 64 65 static struct cnf_directive *h_config_iface(FILE *fd, struct cnf_directive *dir, void *user) { 66 (void)fd; (void)user; 67 return take_list(dir, &cfg.iface, &cfg.iface_n); 68 } 69 70 // Additive: listening on several addresses at once is the point. 71 static struct cnf_directive *h_listen(FILE *fd, struct cnf_directive *dir, void *user) { 72 (void)fd; (void)user; 73 if (dir->argc != 1) { 74 cfg_error("listen needs exactly one address, e.g. unix:///var/run/linkd.sock"); 75 cnf_directive_free(dir); 76 return NULL; 77 } 78 const char *a = dir->argv[0]; 79 if (strncmp(a, "unix://", 7) && strncmp(a, "tcp://", 6)) { 80 cfg_error("listen: unsupported address `%s' (expected unix:// or tcp://)", a); 81 cnf_directive_free(dir); 82 return NULL; 83 } 84 if (list_append(&cfg.listen, &cfg.listen_n, a) != 0) { 85 cfg_error("out of memory reading listen"); 86 } 87 cnf_directive_free(dir); 88 return NULL; 89 } 90 91 // Not split on whitespace here: cnfparse already tokenised, so `dhcp_client 92 // udhcpc -f -i {{interface}}` arrives as four argv elements and no shell is 93 // ever involved in reassembling them. 94 static struct cnf_directive *h_dhcp_client(FILE *fd, struct cnf_directive *dir, void *user) { 95 (void)fd; (void)user; 96 return take_list(dir, &cfg.dhcp_client, &cfg.dhcp_client_n); 97 } 98 99 static struct cnf_directive *h_authfile(FILE *fd, struct cnf_directive *dir, void *user) { 100 (void)fd; (void)user; 101 if (dir->argc != 1) { 102 cfg_error("authfile needs exactly one path"); 103 cnf_directive_free(dir); 104 return NULL; 105 } 106 free(cfg.authfile); 107 cfg.authfile = strdup(dir->argv[0]); 108 if (!cfg.authfile) cfg_error("out of memory reading authfile"); 109 cnf_directive_free(dir); 110 return NULL; 111 } 112 113 // Opens a stanza. Known sub-directives handled here; the first unknown one 114 // ends the stanza and is handed back for re-dispatch. 115 static struct cnf_directive *h_plugin(FILE *fd, struct cnf_directive *dir, void *user) { 116 (void)user; 117 118 if (dir->argc != 1) { 119 cfg_error("plugin needs exactly one executable path or tcp:// address"); 120 cnf_directive_free(dir); 121 return NULL; 122 } 123 124 struct linkd_plugin_cfg *p = calloc(1, sizeof(*p)); 125 if (!p) { 126 cfg_error("out of memory reading plugin"); 127 cnf_directive_free(dir); 128 return NULL; 129 } 130 p->target = strdup(dir->argv[0]); 131 if (!p->target) { 132 free(p); 133 cfg_error("out of memory reading plugin"); 134 cnf_directive_free(dir); 135 return NULL; 136 } 137 cnf_directive_free(dir); 138 139 // Append: plugins are broadcast to in declaration order. 140 struct linkd_plugin_cfg **tail = &cfg.plugins; 141 while (*tail) tail = &(*tail)->next; 142 *tail = p; 143 144 for (;;) { 145 struct cnf_directive *sub = cnf_directive_read(fd); 146 if (!sub) return NULL; // EOF ends the stanza 147 148 if (!strcasecmp(sub->name, "optional")) { 149 if (sub->argc != 0) { 150 cfg_error("plugin %s: optional takes no arguments", p->target); 151 cnf_directive_free(sub); 152 return NULL; 153 } 154 p->optional = 1; 155 cnf_directive_free(sub); 156 continue; 157 } 158 159 // Unknown to us: the plugin gets first refusal. Starting it here is what 160 // makes its config vocabulary discoverable at all -- CONFIG LIST cannot 161 // be asked of a process that is not running. 162 struct dp_plugin *dp = dp_plugin_for(p->target); 163 if (dp && dp_plugin_config_supports(dp, sub->name)) { 164 if (dp_plugin_config_set(dp, sub->name, sub->argv, sub->argc) != 0) { 165 cfg_error("plugin %s rejected `%s'", p->target, sub->name); 166 cnf_directive_free(sub); 167 return NULL; 168 } 169 cnf_directive_free(sub); 170 continue; 171 } 172 173 // Neither side claims it: ends the stanza, hand it back for re-dispatch. 174 return sub; 175 } 176 } 177 178 // --------------------------------------------------------------------------- 179 180 void daemon_cfg_register(void) { 181 struct cfg_ns *ns = cfg_ns_get("linkd"); 182 cfg_register_directive(ns, "config_ports", h_config_ports); 183 cfg_register_directive(ns, "config_iface", h_config_iface); 184 cfg_register_directive(ns, "listen", h_listen); 185 cfg_register_directive(ns, "authfile", h_authfile); 186 cfg_register_directive(ns, "dhcp_client", h_dhcp_client); 187 cfg_register_directive(ns, "plugin", h_plugin); 188 } 189 190 // Per-field, so a config setting only `listen` still gets working paths. 191 static void apply_defaults(void) { 192 if (cfg_defaults_applied) return; 193 cfg_defaults_applied = 1; 194 195 if (cfg.ports_n == 0) { 196 list_append(&cfg.ports, &cfg.ports_n, "/etc/network/ports"); 197 list_append(&cfg.ports, &cfg.ports_n, "/etc/network/ports.d/*.cnf"); 198 } 199 if (cfg.iface_n == 0) { 200 list_append(&cfg.iface, &cfg.iface_n, "/etc/network/interfaces"); 201 list_append(&cfg.iface, &cfg.iface_n, "/etc/network/interfaces.d/*.cnf"); 202 } 203 if (cfg.listen_n == 0) { 204 list_append(&cfg.listen, &cfg.listen_n, "unix:///var/run/linkd.sock"); 205 } 206 } 207 208 // `source` expands the glob, so a directory with no drop-ins is not an error. 209 static int load_confdir(void) { 210 static const char stub[] = "source " LINKD_CONFIG_DIR "/*.cnf\n"; 211 212 FILE *fd = fmemopen((void *)stub, sizeof(stub) - 1, "r"); 213 if (!fd) { 214 perror("Could not open in-memory config"); 215 return CFG_RET_ERROR; 216 } 217 218 cfg_error_reset(); 219 int rc = cfg_parse(cfg_ns_get("linkd"), NULL, fd, NULL); 220 fclose(fd); 221 return rc; 222 } 223 224 int daemon_cfg_load(const char *path) { 225 int explicit_path = path != NULL; 226 if (!path) path = LINKD_CONFIG_PATH; 227 228 if (access(path, R_OK) != 0) { 229 int rc = CFG_RET_OK; 230 if (!explicit_path) rc = load_confdir(); 231 apply_defaults(); 232 return rc; 233 } 234 235 FILE *fd = fopen(path, "r"); 236 if (!fd) { 237 fprintf(stderr, "config: cannot open %s\n", path); 238 return CFG_RET_ERROR; 239 } 240 241 // Relative `source` resolves against the config file's own directory. 242 char *dup = strdup(path); 243 char *wd = dup ? dirname(dup) : NULL; 244 245 cfg_error_reset(); 246 int rc = cfg_parse(cfg_ns_get("linkd"), wd, fd, NULL); 247 fclose(fd); 248 free(dup); 249 250 if (rc != CFG_RET_OK) return rc; 251 252 apply_defaults(); 253 return CFG_RET_OK; 254 } 255 256 int load_namespace(const char *ns_name, char **patterns, size_t n) { 257 if (n == 0) return CFG_RET_OK; 258 259 size_t len = strlen("source"); 260 for (size_t i = 0; i < n; i++) len += 1 + strlen(patterns[i]); 261 len += 2; // newline + NUL 262 263 char *stub = malloc(len); 264 if (!stub) { 265 fprintf(stderr, "config: out of memory building %s source list\n", ns_name); 266 return CFG_RET_ERROR; 267 } 268 269 char *p = stub + sprintf(stub, "source"); 270 for (size_t i = 0; i < n; i++) p += sprintf(p, " %s", patterns[i]); 271 *p++ = '\n'; 272 *p = '\0'; 273 274 FILE *fd = fmemopen(stub, strlen(stub), "r"); 275 if (!fd) { 276 perror("Could not open in-memory config"); 277 free(stub); 278 return CFG_RET_ERROR; 279 } 280 281 // wd NULL: patterns come from linkd.cnf and are expected to be absolute. 282 cfg_error_reset(); 283 int rc = cfg_parse(cfg_ns_get(ns_name), NULL, fd, NULL); 284 if (rc < 0) { 285 fprintf(stderr, "config: error reading %s configuration\n", ns_name); 286 } 287 288 fclose(fd); 289 free(stub); 290 return rc; 291 } 292 293 const char *daemon_cfg_unix_socket(void) { 294 apply_defaults(); 295 for (size_t i = 0; i < cfg.listen_n; i++) { 296 if (!strncmp(cfg.listen[i], "unix://", 7)) return cfg.listen[i] + 7; 297 } 298 return NULL; 299 } 300 301 const char *linkd_client_socket(void) { 302 static int loaded = 0; 303 if (!loaded) { 304 loaded = 1; 305 daemon_cfg_register(); 306 // Failure is not fatal here: fall through to the default rather than 307 // leaving the operator with no way to reach a running daemon. 308 daemon_cfg_load(getenv("LINKD_CONFIG")); 309 } 310 const char *p = daemon_cfg_unix_socket(); 311 return p ? p : "/var/run/linkd.sock"; 312 } 313 314 const struct linkd_daemon_cfg *daemon_cfg(void) { 315 apply_defaults(); 316 return &cfg; 317 } 318 319 void daemon_cfg_free(void) { 320 list_free(&cfg.ports, &cfg.ports_n); 321 list_free(&cfg.iface, &cfg.iface_n); 322 list_free(&cfg.listen, &cfg.listen_n); 323 list_free(&cfg.dhcp_client, &cfg.dhcp_client_n); 324 free(cfg.authfile); 325 cfg.authfile = NULL; 326 327 struct linkd_plugin_cfg *p = cfg.plugins; 328 while (p) { 329 struct linkd_plugin_cfg *next = p->next; 330 free(p->target); 331 free(p); 332 p = next; 333 } 334 cfg.plugins = NULL; 335 cfg_defaults_applied = 0; 336 }