]> the.earth.li Git - mqtt-arp.git/blobdiff - mqtt-arp.c
Add basic config file parsing
[mqtt-arp.git] / mqtt-arp.c
index 9d4887012b8bcb826730878698ae7481bff318f2..2836366b40391dfa6d9373e8fb9e316268f91d28 100644 (file)
  * You should have received a copy of the GNU General Public License
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
+#include <ctype.h>
+#include <errno.h>
+#include <getopt.h>
+#include <signal.h>
 #include <stdbool.h>
 #include <stdint.h>
 #include <stdlib.h>
 
 #include <mosquitto.h>
 
-#define MQTT_HOST "mqtt-host"
-#define MQTT_PORT 8883
-#define MQTT_USERNAME "username"
-#define MQTT_PASSWORD "password"
-#define MQTT_TOPIC "location/by-mac"
-#define LOCATION "home"
+/* Defaults. All overridable from command line. */
+#define MQTT_HOST      "mqtt-host"
+#define MQTT_PORT      8883
+#define MQTT_TOPIC     "location/by-mac"
+#define LOCATION       "home"
+#define CONFIG_FILE    "/etc/mqtt-arp.conf"
+
+/* How often (in seconds) to report that we see a device */
+#define REPORT_INTERVAL        (2 * 60)
+/* How long to wait without seeing a device before reporting it's gone */
+#define EXPIRY_TIME    (10 * 60)
+/* Maximum number of MAC addresses to watch for */
+#define MAX_MACS       8
 
 struct mac_entry {
        bool valid;
@@ -45,13 +56,24 @@ struct mac_entry {
        time_t last_reported;
 };
 
+struct ma_config {
+       char *mqtt_host;
+       int mqtt_port;
+       char *mqtt_username;
+       char *mqtt_password;
+       char *mqtt_topic;
+       char *location;
+       char *capath;
+       struct mac_entry macs[MAX_MACS];
+};
+
 bool debug = false;
+bool want_shutdown = false;
 
-struct mac_entry macs[] = {
-       { true, { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66 }, 0, 0 },
-       { true, { 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff }, 0, 0 },
-       { false }
-};
+void shutdown_request(int signal)
+{
+       want_shutdown = true;
+}
 
 bool mac_compare(uint8_t *a, uint8_t *b)
 {
@@ -69,7 +91,8 @@ bool mac_compare(uint8_t *a, uint8_t *b)
        return true;
 }
 
-int mqtt_mac_presence(struct mosquitto *mosq, uint8_t *mac, bool present)
+int mqtt_mac_presence(struct ma_config *config, struct mosquitto *mosq,
+               uint8_t *mac, bool present)
 {
        char topic[128];
        int ret;
@@ -79,25 +102,25 @@ int mqtt_mac_presence(struct mosquitto *mosq, uint8_t *mac, bool present)
        t = time(NULL);
 
        i = 0;
-       while (macs[i].valid) {
-               if (mac_compare(mac, macs[i].mac))
+       while (i < MAX_MACS && config->macs[i].valid) {
+               if (mac_compare(mac, config->macs[i].mac))
                        break;
                i++;
        }
 
-       if (!macs[i].valid)
+       if (i >= MAX_MACS || !config->macs[i].valid)
                return 0;
 
-       macs[i].last_seen = t;
+       config->macs[i].last_seen = t;
        /* Report no more often than every 2 minutes */
-       if (present && macs[i].last_reported + 60 * 2 > t)
+       if (present && config->macs[i].last_reported + REPORT_INTERVAL > t)
                return 0;
 
-       macs[i].last_reported = t;
+       config->macs[i].last_reported = t;
 
        snprintf(topic, sizeof(topic),
                "%s/%02X:%02X:%02X:%02X:%02X:%02X",
-               MQTT_TOPIC,
+               config->mqtt_topic,
                mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
 
        if (debug)
@@ -105,7 +128,8 @@ int mqtt_mac_presence(struct mosquitto *mosq, uint8_t *mac, bool present)
 
        if (present)
                ret = mosquitto_publish(mosq, NULL, topic,
-                               strlen(LOCATION), LOCATION, 0, 0);
+                               strlen(config->location), config->location,
+                               0, 0);
        else
                ret = mosquitto_publish(mosq, NULL, topic,
                                strlen("unknown"), "unknown", 0, 0);
@@ -113,7 +137,7 @@ int mqtt_mac_presence(struct mosquitto *mosq, uint8_t *mac, bool present)
        return ret;
 }
 
-void prune_macs(struct mosquitto *mosq)
+void prune_macs(struct ma_config *config, struct mosquitto *mosq)
 {
        time_t t;
        int i;
@@ -121,12 +145,14 @@ void prune_macs(struct mosquitto *mosq)
        t = time(NULL);
 
        i = 0;
-       while (macs[i].valid) {
-               /* Expire after 5 minutes */
-               if (macs[i].last_seen && macs[i].last_seen + 60 * 5 < t) {
-                       mqtt_mac_presence(mosq, macs[i].mac, false);
-                       macs[i].last_seen = 0;
-                       macs[i].last_reported = 0;
+       while (i < MAX_MACS && config->macs[i].valid) {
+               /* Expire if we haven't seen MAC in EXPIRY_TIME */
+               if (config->macs[i].last_seen &&
+                               config->macs[i].last_seen + EXPIRY_TIME < t) {
+                       mqtt_mac_presence(config, mosq,
+                                       config->macs[i].mac, false);
+                       config->macs[i].last_seen = 0;
+                       config->macs[i].last_reported = 0;
                }
                i++;
        }
@@ -139,64 +165,19 @@ void mosq_log_callback(struct mosquitto *mosq, void *userdata, int level,
                printf("%i:%s\n", level, str);
 }
 
-int main(int argc, char *argv[])
+void main_loop(struct ma_config *config, struct mosquitto *mosq, int sock)
 {
-       int ret, sock;
-       struct sockaddr_nl group_addr;
-       struct nlmsghdr *hdr;
        uint8_t buf[4096];
-       ssize_t received;
+       uint8_t *data;
+       struct nlmsghdr *hdr;
        struct ndmsg *nd;
        struct nlattr *attr;
-       uint8_t *data;
+       ssize_t received;
        time_t t;
-       struct mosquitto *mosq;
-
-       bzero(&group_addr, sizeof(group_addr));
-       group_addr.nl_family = AF_NETLINK;
-       group_addr.nl_pid = getpid();
-       group_addr.nl_groups = RTMGRP_NEIGH;
-
-       sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
-       if (sock < 0) {
-               perror("Couldn't open netlink socket");
-               exit(EXIT_FAILURE);
-       }
-
-       if (bind(sock, (struct sockaddr *) &group_addr,
-                       sizeof(group_addr)) < 0) {
-               perror("Failed to bind to netlink socket");
-               exit(EXIT_FAILURE);
-       }
-
-       mosquitto_lib_init();
-       mosq = mosquitto_new("mqtt-arp", true, NULL);
-       if (!mosq) {
-               printf("Couldn't allocate mosquitto structure\n");
-               exit(EXIT_FAILURE);
-       }
-
-       mosquitto_log_callback_set(mosq, mosq_log_callback);
-
-       mosquitto_username_pw_set(mosq, MQTT_USERNAME, MQTT_PASSWORD);
-       mosquitto_tls_set(mosq, "/etc/ssl/certs/ca-certificates.crt",
-                       NULL, NULL, NULL, NULL);
-
-       ret = mosquitto_connect(mosq, MQTT_HOST, MQTT_PORT, 60);
-       if (ret) {
-               printf("Unable to connect to MQTT server.\n");
-               exit(EXIT_FAILURE);
-       }
-
-       ret = mosquitto_loop_start(mosq);
-       if (ret) {
-               printf("Unable to start Mosquitto loop.\n");
-               exit(EXIT_FAILURE);
-       }
 
        hdr = (struct nlmsghdr *) buf;
        nd = (struct ndmsg *) (hdr + 1);
-       while (1) {
+       while (!want_shutdown) {
                received = recv(sock, buf, sizeof(buf), 0);
                if (debug) {
                        t = time(NULL);
@@ -219,22 +200,247 @@ int main(int argc, char *argv[])
                                        nd->ndm_type);
                        }
                        attr = (struct nlattr *) (nd + 1);
-                       while (attr->nla_len > 0) {
-                               data = (((uint8_t *) attr) + 4);
+                       while (((uint8_t *) attr - buf) < hdr->nlmsg_len) {
+                               data = (((uint8_t *) attr) + NLA_HDRLEN);
                                if (attr->nla_type == NDA_LLADDR &&
                                        nd->ndm_state == NUD_REACHABLE) {
-                                       mqtt_mac_presence(mosq, data, true);
+                                       mqtt_mac_presence(config, mosq,
+                                                       data, true);
                                }
-                               attr = (struct nlattr *)
-                                       (((uint8_t *) attr) + attr->nla_len);
+                               attr = (struct nlattr *) (((uint8_t *) attr) +
+                                               NLA_ALIGN(attr->nla_len));
                        }
                        break;
                case RTM_DELNEIGH:
                case RTM_GETNEIGH:
+                       break;
                default:
                        printf("Unknown message type: %d\n", hdr->nlmsg_type);
                }
 
-               prune_macs(mosq);
+               prune_macs(config, mosq);
        }
+
+}
+
+struct mosquitto *mqtt_init(struct ma_config *config)
+{
+       struct mosquitto *mosq;
+       int ret;
+
+       mosquitto_lib_init();
+       mosq = mosquitto_new("mqtt-arp", true, NULL);
+       if (!mosq) {
+               printf("Couldn't allocate mosquitto structure\n");
+               exit(EXIT_FAILURE);
+       }
+
+       mosquitto_log_callback_set(mosq, mosq_log_callback);
+
+       /* DTRT if username is NULL */
+       mosquitto_username_pw_set(mosq,
+                       config->mqtt_username,
+                       config->mqtt_password);
+       if (config->capath)
+               mosquitto_tls_set(mosq, config->capath,
+                               NULL, NULL, NULL, NULL);
+
+       ret = mosquitto_connect(mosq, config->mqtt_host,
+                       config->mqtt_port, 60);
+       if (ret) {
+               printf("Unable to connect to MQTT server.\n");
+               exit(EXIT_FAILURE);
+       }
+
+       ret = mosquitto_loop_start(mosq);
+       if (ret) {
+               printf("Unable to start Mosquitto loop.\n");
+               exit(EXIT_FAILURE);
+       }
+
+       return mosq;
+}
+
+int netlink_init(void)
+{
+       int sock;
+       struct sockaddr_nl group_addr;
+
+       bzero(&group_addr, sizeof(group_addr));
+       group_addr.nl_family = AF_NETLINK;
+       group_addr.nl_pid = getpid();
+       group_addr.nl_groups = RTMGRP_NEIGH;
+
+       sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+       if (sock < 0) {
+               perror("Couldn't open netlink socket");
+               exit(EXIT_FAILURE);
+       }
+
+       if (bind(sock, (struct sockaddr *) &group_addr,
+                       sizeof(group_addr)) < 0) {
+               perror("Failed to bind to netlink socket");
+               exit(EXIT_FAILURE);
+       }
+
+       return sock;
+}
+
+int read_config(char *file, struct ma_config *config, int *macs)
+{
+       FILE *f;
+       char line[256];
+       int i;
+
+       f = fopen(file, "r");
+       if (f == NULL)
+               return errno;
+
+#define INT_OPTION(opt, var) \
+       if (strncmp(line, opt " ", sizeof(opt)) == 0) { \
+               var = atoi(&line[sizeof(opt)]);          \
+       }
+#define STRING_OPTION(opt, var) \
+       if (strncmp(line, opt " ", sizeof(opt)) == 0) { \
+               var = strdup(&line[sizeof(opt)]);       \
+       }
+
+       while (fgets(line, sizeof(line), f) != NULL) {
+               for (i = strlen(line) - 1; i >= 0 && isspace(line[i]); i--)
+                       line[i] = '\0';
+               if (line[0] == '\0' || line[0] == '#')
+                       continue;
+
+               if (strncmp(line, "mac ", 4) == 0) {
+                       if (*macs >= MAX_MACS) {
+                               printf("Can only accept %d MAC addresses to"
+                                       " watch for.\n", MAX_MACS);
+                               exit(EXIT_FAILURE);
+                       }
+                       sscanf(&line[4],
+                               "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx",
+                               &config->macs[*macs].mac[0],
+                               &config->macs[*macs].mac[1],
+                               &config->macs[*macs].mac[2],
+                               &config->macs[*macs].mac[3],
+                               &config->macs[*macs].mac[4],
+                               &config->macs[*macs].mac[5]);
+                       config->macs[*macs].valid = true;
+                       (*macs)++;
+               } else
+               STRING_OPTION("mqtt_host", config->mqtt_host) else
+               INT_OPTION("mqtt_port", config->mqtt_port) else
+               STRING_OPTION("mqtt_user", config->mqtt_username) else
+               STRING_OPTION("mqtt_pass", config->mqtt_password) else
+               STRING_OPTION("mqtt_topic", config->mqtt_topic) else
+               STRING_OPTION("location", config->location) else
+               STRING_OPTION("capath", config->capath)
+       }
+       fclose(f);
+
+       return 0;
+}
+
+struct option long_options[] = {
+       { "capath", required_argument, 0, 'c' },
+       { "host", required_argument, 0, 'h' },
+       { "location", required_argument, 0, 'l' },
+       { "mac", required_argument, 0, 'm' },
+       { "password", required_argument, 0, 'P' },
+       { "port", required_argument, 0, 'p' },
+       { "topic", required_argument, 0, 't' },
+       { "username", required_argument, 0, 'u' },
+       { "verbose", no_argument, 0, 'v' },
+       { 0, 0, 0, 0 }
+};
+
+int main(int argc, char *argv[])
+{
+       int sock;
+       struct mosquitto *mosq;
+       struct ma_config config;
+       int option_index = 0;
+       int macs = 0;
+       char c;
+
+       bzero(&config, sizeof(config));
+       config.mqtt_port = MQTT_PORT;
+
+       /* Read config before parsing command line */
+       read_config(CONFIG_FILE, &config, &macs);
+
+       while (1) {
+               c = getopt_long(argc, argv, "c:h:l:m:p:P:t:u:v",
+                               long_options, &option_index);
+
+               if (c == -1)
+                       break;
+
+               switch (c) {
+               case 'c':
+                       config.capath = optarg;
+                       break;
+               case 'h':
+                       config.mqtt_host = optarg;
+                       break;
+               case 'l':
+                       config.location = optarg;
+                       break;
+               case 'm':
+                       if (macs >= MAX_MACS) {
+                               printf("Can only accept %d MAC addresses to"
+                                       " watch for.\n", MAX_MACS);
+                               exit(EXIT_FAILURE);
+                       }
+                       sscanf(optarg,
+                               "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx",
+                               &config.macs[macs].mac[0],
+                               &config.macs[macs].mac[1],
+                               &config.macs[macs].mac[2],
+                               &config.macs[macs].mac[3],
+                               &config.macs[macs].mac[4],
+                               &config.macs[macs].mac[5]);
+                       config.macs[macs].valid = true;
+                       macs++;
+                       break;
+               case 'p':
+                       config.mqtt_port = atoi(optarg);
+                       break;
+               case 'P':
+                       config.mqtt_password = optarg;
+                       break;
+               case 't':
+                       config.mqtt_topic = optarg;
+                       break;
+               case 'u':
+                       config.mqtt_username = optarg;
+                       break;
+               case 'v':
+                       debug = true;
+                       break;
+               default:
+                       printf("Unrecognized option: %c\n", c);
+                       exit(EXIT_FAILURE);
+               }
+       }
+
+       if (!config.mqtt_host)
+               config.mqtt_host = MQTT_HOST;
+       if (!config.mqtt_topic)
+               config.mqtt_topic = MQTT_TOPIC;
+       if (!config.location)
+               config.location = LOCATION;
+
+       signal(SIGTERM, shutdown_request);
+
+       sock = netlink_init();
+       mosq = mqtt_init(&config);
+
+       main_loop(&config, mosq, sock);
+
+       mosquitto_disconnect(mosq);
+       mosquitto_loop_stop(mosq, true);
+       mosquitto_destroy(mosq);
+       mosquitto_lib_cleanup();
+       close(sock);
 }