Skip to content

Commit

Permalink
Add optional TLS support to MQTT (closes #1633)
Browse files Browse the repository at this point in the history
  • Loading branch information
zuckschwerdt committed Jan 28, 2021
1 parent febe5d8 commit 61d24ba
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 21 deletions.
37 changes: 37 additions & 0 deletions include/optparse.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,43 @@
#include <strings.h>
#endif

/// TLS settings.
typedef struct tls_opts {
/// Client certificate to present to the server.
const char *tls_cert;
/// Private key corresponding to the certificate.
/// If tls_cert is set but tls_key is not, tls_cert is used.
const char *tls_key;
/// Verify server certificate using this CA bundle. If set to "*", then TLS
/// is enabled but no cert verification is performed.
const char *tls_ca_cert;
/// Colon-delimited list of acceptable cipher suites.
/// Names depend on the library used, for example:
/// ECDH-ECDSA-AES128-GCM-SHA256:DHE-RSA-AES128-SHA256 (OpenSSL)
/// For OpenSSL the list can be obtained by running "openssl ciphers".
/// If NULL, a reasonable default is used.
const char *tls_cipher_suites;
/// Server name verification. If tls_ca_cert is set and the certificate has
/// passed verification, its subject will be verified against this string.
/// By default (if tls_server_name is NULL) hostname part of the address will
/// be used. Wildcard matching is supported. A special value of "*" disables
/// name verification.
const char *tls_server_name;
/// PSK identity is a NUL-terminated string.
/// Note: Default list of cipher suites does not include PSK suites, if you
/// want to use PSK you will need to set tls_cipher_suites as well.
const char *tls_psk_identity;
/// PSK key hex string, must be either 16 or 32 bytes (32 or 64 hex digits)
/// for AES-128 or AES-256 respectively.
const char *tls_psk_key;
} tls_opts_t;

/// Parse a TLS option.
///
/// @sa tls_opts_t
/// @return 0 if the option was valid, error code otherwise
int tls_param(tls_opts_t *tls_opts, char *key, char *val);

/// Convert string to bool with fallback default.
/// Parses "true", "yes", "on", "enable" (not case-sensitive) to 1, atoi() otherwise.
int atobv(char *arg, int def);
Expand Down
2 changes: 1 addition & 1 deletion include/output_mqtt.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@

struct mg_mgr;

struct data_output *data_output_mqtt_create(struct mg_mgr *mgr, char const *host, char const *port, char *opts, char const *dev_hint);
struct data_output *data_output_mqtt_create(struct mg_mgr *mgr, char *param, char const *dev_hint);

#endif /* INCLUDE_OUTPUT_MQTT_H_ */
23 changes: 23 additions & 0 deletions src/optparse.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,29 @@
#include <limits.h>
#include <string.h>

int tls_param(tls_opts_t *tls_opts, char *key, char *val)
{
if (!tls_opts || !key || !*key)
return 1;
else if (!strcasecmp(key, "tls_cert"))
tls_opts->tls_cert = val;
else if (!strcasecmp(key, "tls_key"))
tls_opts->tls_key = val;
else if (!strcasecmp(key, "tls_ca_cert"))
tls_opts->tls_ca_cert = val;
else if (!strcasecmp(key, "tls_cipher_suites"))
tls_opts->tls_cipher_suites = val;
else if (!strcasecmp(key, "tls_server_name"))
tls_opts->tls_server_name = val;
else if (!strcasecmp(key, "tls_psk_identity"))
tls_opts->tls_psk_identity = val;
else if (!strcasecmp(key, "tls_psk_key"))
tls_opts->tls_psk_key = val;
else
return 1;
return 0;
}

int atobv(char *arg, int def)
{
if (!arg)
Expand Down
62 changes: 49 additions & 13 deletions src/output_mqtt.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
/* MQTT client abstraction */

typedef struct mqtt_client {
struct mg_send_mqtt_handshake_opts opts;
struct mg_connect_opts connect_opts;
struct mg_send_mqtt_handshake_opts mqtt_opts;
struct mg_connection *conn;
int prev_status;
char address[253 + 6 + 1]; // dns max + port
Expand All @@ -52,7 +53,7 @@ static void mqtt_client_event(struct mg_connection *nc, int ev, void *ev_data)
fprintf(stderr, "MQTT Connected...\n");
mg_set_protocol_mqtt(nc);
if (ctx)
mg_send_mqtt_handshake_opt(nc, ctx->client_id, ctx->opts);
mg_send_mqtt_handshake_opt(nc, ctx->client_id, ctx->mqtt_opts);
}
else {
// Error, print only once
Expand Down Expand Up @@ -88,23 +89,26 @@ static void mqtt_client_event(struct mg_connection *nc, int ev, void *ev_data)
if (ctx->prev_status == 0)
fprintf(stderr, "MQTT Connection failed...\n");
// reconnect
struct mg_connect_opts opts = {.user_data = ctx};
ctx->conn = mg_connect_opt(nc->mgr, ctx->address, mqtt_client_event, opts);
char const *error_string = NULL;
ctx->connect_opts.error_string = &error_string;
ctx->conn = mg_connect_opt(nc->mgr, ctx->address, mqtt_client_event, ctx->connect_opts);
ctx->connect_opts.error_string = NULL;
if (!ctx->conn) {
fprintf(stderr, "MQTT connect(%s) failed\n", ctx->address);
fprintf(stderr, "MQTT connect (%s) failed%s%s\n", ctx->address,
error_string ? ": " : "", error_string ? error_string : "");
}
break;
}
}

static mqtt_client_t *mqtt_client_init(struct mg_mgr *mgr, char const *host, char const *port, char const *user, char const *pass, char const *client_id, int retain)
static mqtt_client_t *mqtt_client_init(struct mg_mgr *mgr, tls_opts_t *tls_opts, char const *host, char const *port, char const *user, char const *pass, char const *client_id, int retain)
{
mqtt_client_t *ctx = calloc(1, sizeof(*ctx));
if (!ctx)
FATAL_CALLOC("mqtt_client_init()");

ctx->opts.user_name = user;
ctx->opts.password = pass;
ctx->mqtt_opts.user_name = user;
ctx->mqtt_opts.password = pass;
ctx->publish_flags = MG_MQTT_QOS(0) | (retain ? MG_MQTT_RETAIN : 0);
// TODO: these should be user configurable options
//ctx->opts.keepalive = 60;
Expand All @@ -119,10 +123,28 @@ static mqtt_client_t *mqtt_client_init(struct mg_mgr *mgr, char const *host, cha
else
snprintf(ctx->address, sizeof(ctx->address), "%s:%s", host, port);

struct mg_connect_opts opts = {.user_data = ctx};
ctx->conn = mg_connect_opt(mgr, ctx->address, mqtt_client_event, opts);
ctx->connect_opts.user_data = ctx;
if (tls_opts && tls_opts->tls_ca_cert) {
#if MG_ENABLE_SSL
ctx->connect_opts.ssl_cert = tls_opts->tls_cert;
ctx->connect_opts.ssl_key = tls_opts->tls_key;
ctx->connect_opts.ssl_ca_cert = tls_opts->tls_ca_cert;
ctx->connect_opts.ssl_cipher_suites = tls_opts->tls_cipher_suites;
ctx->connect_opts.ssl_server_name = tls_opts->tls_server_name;
ctx->connect_opts.ssl_psk_identity = tls_opts->tls_psk_identity;
ctx->connect_opts.ssl_psk_key = tls_opts->tls_psk_key;
#else
fprintf(stderr, "mqtts (TLS) not available\n");
exit(1);
#endif
}
char const *error_string = NULL;
ctx->connect_opts.error_string = &error_string;
ctx->conn = mg_connect_opt(mgr, ctx->address, mqtt_client_event, ctx->connect_opts);
ctx->connect_opts.error_string = NULL;
if (!ctx->conn) {
fprintf(stderr, "MQTT connect(%s) failed\n", ctx->address);
fprintf(stderr, "MQTT connect (%s) failed%s%s\n", ctx->address,
error_string ? ": " : "", error_string ? error_string : "");
exit(1);
}

Expand Down Expand Up @@ -446,7 +468,7 @@ static char *mqtt_topic_default(char const *topic, char const *base, char const
return ret;
}

struct data_output *data_output_mqtt_create(struct mg_mgr *mgr, char const *host, char const *port, char *opts, char const *dev_hint)
struct data_output *data_output_mqtt_create(struct mg_mgr *mgr, char *param, char const *dev_hint)
{
data_output_mqtt_t *mqtt = calloc(1, sizeof(data_output_mqtt_t));
if (!mqtt)
Expand Down Expand Up @@ -479,6 +501,17 @@ struct data_output *data_output_mqtt_create(struct mg_mgr *mgr, char const *host
char *pass = NULL;
int retain = 0;

// parse host and port
tls_opts_t tls_opts = {0};
if (strncmp(param, "mqtts", 5) == 0) {
tls_opts.tls_ca_cert = "*"; // TLS is enabled but no cert verification is performed.
}
param = arg_param(param);
char *host = "localhost";
char *port = tls_opts.tls_ca_cert ? "8883" : "1883";
char *opts = hostport_param(param, &host, &port);
fprintf(stderr, "Publishing MQTT data to %s port %s%s\n", host, port, tls_opts.tls_ca_cert ? " (TLS)" : "");

// parse auth and format options
char *key, *val;
while (getkwargs(&opts, &key, &val)) {
Expand Down Expand Up @@ -516,6 +549,9 @@ struct data_output *data_output_mqtt_create(struct mg_mgr *mgr, char const *host
// TODO: Home Assistant MQTT discovery https://www.home-assistant.io/docs/mqtt/discovery/
//else if (!strcasecmp(key, "a") || !strcasecmp(key, "hass"))
// mqtt->hass = mqtt_topic_default(val, NULL, "homeassistant"); // discovery prefix
else if (!tls_param(&tls_opts, key, val)) {
// ok
}
else {
fprintf(stderr, "Invalid key \"%s\" option.\n", key);
exit(1);
Expand All @@ -542,7 +578,7 @@ struct data_output *data_output_mqtt_create(struct mg_mgr *mgr, char const *host
mqtt->output.print_int = print_mqtt_int;
mqtt->output.output_free = data_output_mqtt_free;

mqtt->mqc = mqtt_client_init(mgr, host, port, user, pass, client_id, retain);
mqtt->mqc = mqtt_client_init(mgr, &tls_opts, host, port, user, pass, client_id, retain);

return &mqtt->output;
}
7 changes: 1 addition & 6 deletions src/r_api.c
Original file line number Diff line number Diff line change
Expand Up @@ -908,12 +908,7 @@ void add_kv_output(r_cfg_t *cfg, char *param)

void add_mqtt_output(r_cfg_t *cfg, char *param)
{
char *host = "localhost";
char *port = "1883";
char *opts = hostport_param(param, &host, &port);
fprintf(stderr, "Publishing MQTT data to %s port %s\n", host, port);

list_push(&cfg->output_handler, data_output_mqtt_create(get_mgr(cfg), host, port, opts, cfg->dev_query));
list_push(&cfg->output_handler, data_output_mqtt_create(get_mgr(cfg), param, cfg->dev_query));
}

void add_influx_output(r_cfg_t *cfg, char *param)
Expand Down
2 changes: 1 addition & 1 deletion src/rtl_433.c
Original file line number Diff line number Diff line change
Expand Up @@ -1028,7 +1028,7 @@ static void parse_conf_option(r_cfg_t *cfg, int opt, char *arg)
add_kv_output(cfg, arg_param(arg));
}
else if (strncmp(arg, "mqtt", 4) == 0) {
add_mqtt_output(cfg, arg_param(arg));
add_mqtt_output(cfg, arg);
}
else if (strncmp(arg, "influx", 6) == 0) {
add_influx_output(cfg, arg);
Expand Down

0 comments on commit 61d24ba

Please sign in to comment.