├── .gitignore ├── LICENSE ├── Makefile ├── README.md └── mqtt-exec.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | mqtt-exec 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 Natanael Copa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | VERSION=0.4 3 | LIBS=-lmosquitto 4 | CFLAGS ?= -g -Wall -Werror 5 | WITH_TLS := 1 6 | 7 | ifeq ($(WITH_TLS),1) 8 | CFLAGS += -DWITH_TLS 9 | endif 10 | 11 | mqtt-exec: mqtt-exec.c 12 | $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) $(LIBS) 13 | 14 | clean: 15 | rm -f mqtt-exec 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | mqtt-exec 2 | ========= 3 | 4 | A simple mqtt subscriber that executes a command on mqtt messages 5 | 6 | Build requriements 7 | ------------------ 8 | - C compiler + make 9 | - libmosquitto 10 | 11 | 12 | Example usage 13 | ------------- 14 | This example shows how to get messages as desktop notifications: 15 | 16 | `mqtt-exec -h $mqtt_host -t $topic -v -- /usr/bin/notify-send -t 3000 -i network-server` 17 | -------------------------------------------------------------------------------- /mqtt-exec.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2013-2019 Natanael Copa 2 | * 3 | * This work is licensed under the terms of the MIT license. 4 | * For a copy, see . 5 | * 6 | */ 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | struct userdata { 18 | char **topics; 19 | size_t topic_count; 20 | int command_argc; 21 | int verbose; 22 | char **command_argv; 23 | int qos; 24 | }; 25 | 26 | void log_cb(struct mosquitto *mosq, void *obj, int level, const char *str) 27 | { 28 | printf("%s\n", str); 29 | } 30 | 31 | void message_cb(struct mosquitto *mosq, void *obj, 32 | const struct mosquitto_message *msg) 33 | { 34 | struct userdata *ud = (struct userdata *)obj; 35 | if (msg->payloadlen || ud->verbose) { 36 | if (ud->command_argv && fork() == 0) { 37 | /* mosquitto ignores SIGPIPE, this is a problem as it 38 | * gets inherited by all processes spawned by mqtt-exec 39 | * restore the default handler explicitly for now. */ 40 | signal(SIGPIPE, SIG_DFL); 41 | 42 | if (ud->verbose) 43 | ud->command_argv[ud->command_argc-2] = msg->topic; 44 | ud->command_argv[ud->command_argc-1] = 45 | msg->payloadlen ? msg->payload : NULL; 46 | execv(ud->command_argv[0], ud->command_argv); 47 | perror(ud->command_argv[0]); 48 | _exit(1); 49 | } 50 | } 51 | } 52 | 53 | void connect_cb(struct mosquitto *mosq, void *obj, int result) 54 | { 55 | struct userdata *ud = (struct userdata *)obj; 56 | fflush(stderr); 57 | if (result == 0) { 58 | size_t i; 59 | for (i = 0; i < ud->topic_count; i++) 60 | mosquitto_subscribe(mosq, NULL, ud->topics[i], ud->qos); 61 | } else { 62 | fprintf(stderr, "%s\n", mosquitto_connack_string(result)); 63 | } 64 | } 65 | 66 | int usage(int retcode) 67 | { 68 | int major, minor, rev; 69 | 70 | mosquitto_lib_version(&major, &minor, &rev); 71 | printf( 72 | "mqtt-exec - execute command on mqtt messages\n" 73 | "libmosquitto version: %d.%d.%d\n" 74 | "\n" 75 | "usage: mqtt-exec [ARGS...] -t TOPIC ... -- CMD [CMD ARGS...]\n" 76 | "\n" 77 | "options:\n" 78 | " -c,--disable-clean-session Disable the 'clean session' flag\n" 79 | " -d,--debug Enable debugging\n" 80 | " -h,--host HOST Connect to HOST. Default is localhost\n" 81 | " -i,--id ID The id to use for this client\n" 82 | " -k,--keepalive SEC Set keepalive to SEC. Default is 60\n" 83 | " -p,--port PORT Set TCP port to PORT. Default is 1883\n" 84 | " -P,--password PASSWORD Set password for authentication\n" 85 | " -q,--qos QOS Set Quality of Serive to level. Default is 0\n" 86 | " -t,--topic TOPIC Set MQTT topic to TOPIC. May be repeated\n" 87 | " -u,--username USERNAME Set username for authentication\n" 88 | " -v,--verbose Pass over the topic to application as first arg\n" 89 | " --will-topic TOPIC Set the client Will topic to TOPIC\n" 90 | " --will-payload MSG Set the client Will message to MSG\n" 91 | " --will-qos QOS Set the QoS level for client Will message\n" 92 | " --will-retain Make the client Will retained\n" 93 | #ifdef WITH_TLS 94 | " --cafile FILE Path to file containing CA certificates\n" 95 | " --capath DIR Path to directory containing CA certificates\n" 96 | " --cert FILE Client certificate for authentication\n" 97 | " --key FILE Client private key for authentication\n" 98 | " --ciphers LIST OpenSSL compatible list of TLS ciphers\n" 99 | " --tls-version VERSION TLS protocol version: tlsv1.2 tlsv1.1 tlsv1\n" 100 | " --psk KEY Pre-shared-key in hexadecimal (no leading 0x)\n" 101 | " --psk-identity STRING Client identity string for TLS-PSK mode\n" 102 | #endif 103 | "\n", major, minor, rev); 104 | return retcode; 105 | } 106 | 107 | static int perror_ret(const char *msg) 108 | { 109 | perror(msg); 110 | return 1; 111 | } 112 | 113 | static int valid_qos_range(int qos, const char *type) 114 | { 115 | if (qos >= 0 && qos <= 2) 116 | return 1; 117 | 118 | fprintf(stderr, "%d: %s out of range\n", qos, type); 119 | return 0; 120 | } 121 | 122 | int main(int argc, char *argv[]) 123 | { 124 | static struct option opts[] = { 125 | {"disable-clean-session", no_argument, 0, 'c' }, 126 | {"debug", no_argument, 0, 'd' }, 127 | {"host", required_argument, 0, 'h' }, 128 | {"id", required_argument, 0, 'i' }, 129 | {"keepalive", required_argument, 0, 'k' }, 130 | {"port", required_argument, 0, 'p' }, 131 | {"qos", required_argument, 0, 'q' }, 132 | {"topic", required_argument, 0, 't' }, 133 | {"verbose", no_argument, 0, 'v' }, 134 | {"username", required_argument, 0, 'u' }, 135 | {"password", required_argument, 0, 'P' }, 136 | {"will-topic", required_argument, 0, 0x1001 }, 137 | {"will-payload", required_argument, 0, 0x1002 }, 138 | {"will-qos", required_argument, 0, 0x1003 }, 139 | {"will-retain", no_argument, 0, 0x1004 }, 140 | #ifdef WITH_TLS 141 | {"cafile", required_argument, 0, 0x2001 }, 142 | {"capath", required_argument, 0, 0x2002 }, 143 | {"cert", required_argument, 0, 0x2003 }, 144 | {"key", required_argument, 0, 0x2004 }, 145 | {"ciphers", required_argument, 0, 0x2005 }, 146 | {"tls-version", required_argument, 0, 0x2006 }, 147 | {"psk", required_argument, 0, 0x2007 }, 148 | {"psk-identity",required_argument, 0, 0x2008 }, 149 | #endif 150 | { 0, 0, 0, 0} 151 | }; 152 | int debug = 0; 153 | bool clean_session = true; 154 | const char *host = "localhost"; 155 | int port = 1883; 156 | int keepalive = 60; 157 | int i, c, rc = 1; 158 | struct userdata ud; 159 | char *id = NULL; 160 | struct mosquitto *mosq = NULL; 161 | char *username = NULL; 162 | char *password = NULL; 163 | 164 | char *will_payload = NULL; 165 | int will_qos = 0; 166 | bool will_retain = false; 167 | char *will_topic = NULL; 168 | #ifdef WITH_TLS 169 | char *cafile = NULL; 170 | char *capath = NULL; 171 | char *certfile = NULL; 172 | char *keyfile = NULL; 173 | char *ciphers = NULL; 174 | char *tls_version = NULL; 175 | char *psk = NULL; 176 | char *psk_identity = NULL; 177 | #endif 178 | 179 | memset(&ud, 0, sizeof(ud)); 180 | 181 | while ((c = getopt_long(argc, argv, "cdh:i:k:p:P:q:t:u:v", opts, &i)) != -1) { 182 | switch(c) { 183 | case 'c': 184 | clean_session = false; 185 | break; 186 | case 'd': 187 | debug = 1; 188 | break; 189 | case 'h': 190 | host = optarg; 191 | break; 192 | case 'i': 193 | if (strlen(optarg) > MOSQ_MQTT_ID_MAX_LENGTH) { 194 | fprintf(stderr, "specified id is longer than %d chars\n", 195 | MOSQ_MQTT_ID_MAX_LENGTH); 196 | return 1; 197 | } 198 | id = optarg; 199 | break; 200 | case 'k': 201 | keepalive = atoi(optarg); 202 | break; 203 | case 'p': 204 | port = atoi(optarg); 205 | break; 206 | case 'P': 207 | password = optarg; 208 | break; 209 | case 'q': 210 | ud.qos = atoi(optarg); 211 | if (!valid_qos_range(ud.qos, "QoS")) 212 | return 1; 213 | break; 214 | case 't': 215 | ud.topic_count++; 216 | ud.topics = realloc(ud.topics, 217 | sizeof(char *) * ud.topic_count); 218 | ud.topics[ud.topic_count-1] = optarg; 219 | break; 220 | case 'u': 221 | username = optarg; 222 | break; 223 | case 'v': 224 | ud.verbose = 1; 225 | break; 226 | case 0x1001: 227 | will_topic = optarg; 228 | break; 229 | case 0x1002: 230 | will_payload = optarg; 231 | break; 232 | case 0x1003: 233 | will_qos = atoi(optarg); 234 | if (!valid_qos_range(will_qos, "will QoS")) 235 | return 1; 236 | break; 237 | case 0x1004: 238 | will_retain = 1; 239 | break; 240 | #ifdef WITH_TLS 241 | case 0x2001: 242 | cafile = optarg; 243 | break; 244 | case 0x2002: 245 | capath = optarg; 246 | break; 247 | case 0x2003: 248 | certfile = optarg; 249 | break; 250 | case 0x2004: 251 | keyfile = optarg; 252 | break; 253 | case 0x2005: 254 | ciphers = optarg; 255 | break; 256 | case 0x2006: 257 | tls_version = optarg; 258 | break; 259 | case 0x2007: 260 | psk = optarg; 261 | break; 262 | case 0x2008: 263 | psk_identity = optarg; 264 | break; 265 | #endif 266 | case '?': 267 | return usage(1); 268 | } 269 | } 270 | 271 | if ((ud.topics == NULL) || (optind == argc)) 272 | return usage(2); 273 | 274 | ud.command_argc = (argc - optind) + 1 + ud.verbose; 275 | ud.command_argv = malloc((ud.command_argc + 1) * sizeof(char *)); 276 | if (ud.command_argv == NULL) 277 | return perror_ret("malloc"); 278 | 279 | for (i=0; i <= ud.command_argc; i++) 280 | ud.command_argv[i] = optind+i < argc ? argv[optind+i] : NULL; 281 | 282 | mosquitto_lib_init(); 283 | mosq = mosquitto_new(id, clean_session, &ud); 284 | if (mosq == NULL) 285 | return perror_ret("mosquitto_new"); 286 | 287 | if (debug) { 288 | printf("host=%s:%d\nid=%s\ntopic_count=%zu\ncommand=%s\n", 289 | host, port, id ? id : "(null)", ud.topic_count, 290 | ud.command_argv[0]); 291 | mosquitto_log_callback_set(mosq, log_cb); 292 | } 293 | 294 | if (will_topic && mosquitto_will_set(mosq, will_topic, 295 | will_payload ? strlen(will_payload) : 0, 296 | will_payload, will_qos, 297 | will_retain)) { 298 | fprintf(stderr, "Failed to set will\n"); 299 | goto cleanup; 300 | } 301 | 302 | if (username && !password) 303 | password = getenv("MQTT_EXEC_PASSWORD"); 304 | 305 | if (!username != !password) { 306 | fprintf(stderr, "Need to set both username and password\n"); 307 | goto cleanup; 308 | } 309 | 310 | if (username && password) 311 | mosquitto_username_pw_set(mosq, username, password); 312 | 313 | #ifdef WITH_TLS 314 | if ((cafile || capath) && mosquitto_tls_set(mosq, cafile, capath, certfile, 315 | keyfile, NULL)) { 316 | fprintf(stderr, "Failed to set TLS options\n"); 317 | goto cleanup; 318 | } 319 | if (psk && mosquitto_tls_psk_set(mosq, psk, psk_identity, NULL)) { 320 | fprintf(stderr, "Failed to set TLS-PSK\n"); 321 | goto cleanup; 322 | } 323 | if ((tls_version || ciphers) && mosquitto_tls_opts_set(mosq, 1, tls_version, 324 | ciphers)) { 325 | fprintf(stderr, "Failed to set TLS options\n"); 326 | goto cleanup; 327 | } 328 | #endif 329 | 330 | mosquitto_connect_callback_set(mosq, connect_cb); 331 | mosquitto_message_callback_set(mosq, message_cb); 332 | 333 | /* let kernel reap the children */ 334 | signal(SIGCHLD, SIG_IGN); 335 | 336 | rc = mosquitto_connect(mosq, host, port, keepalive); 337 | if (rc != MOSQ_ERR_SUCCESS) { 338 | if (rc == MOSQ_ERR_ERRNO) 339 | return perror_ret("mosquitto_connect_bind"); 340 | fprintf(stderr, "Unable to connect (%d)\n", rc); 341 | goto cleanup; 342 | } 343 | 344 | rc = mosquitto_loop_forever(mosq, -1, 1); 345 | 346 | cleanup: 347 | mosquitto_destroy(mosq); 348 | mosquitto_lib_cleanup(); 349 | return rc; 350 | 351 | } 352 | --------------------------------------------------------------------------------