From 0364bd1be7b2bfd4348e5f20acb708ea4c9e350d Mon Sep 17 00:00:00 2001 From: Roger Light Date: Wed, 7 May 2014 23:27:00 +0100 Subject: [PATCH] Initial contribution. --- .gitignore | 39 + CMakeLists.txt | 74 + ChangeLog.txt | 1093 ++++++++++ LICENSE.txt | 2 + Makefile | 70 + THANKS.txt | 57 + aclfile.example | 9 + changeset | 1 + client/CMakeLists.txt | 12 + client/Makefile | 34 + client/pub_client.c | 785 ++++++++ client/sub_client.c | 650 ++++++ compiling.txt | 17 + config.h | 19 + config.mk | 218 ++ doc/historical/old-regex.txt | 43 + doc/historical/topic-match.kds | 7 + edl-v10 | 31 + epl-v10 | 221 +++ examples/mysql_log/Makefile | 15 + examples/mysql_log/mysql_log.c | 118 ++ examples/temperature_conversion/Makefile | 18 + examples/temperature_conversion/main.cpp | 23 + examples/temperature_conversion/readme.txt | 6 + .../temperature_conversion.cpp | 45 + .../temperature_conversion.h | 17 + installer/mosquitto-cygwin.nsi | 144 ++ installer/mosquitto.nsi | 134 ++ lib/CMakeLists.txt | 71 + lib/Makefile | 96 + lib/cpp/CMakeLists.txt | 18 + lib/cpp/Makefile | 31 + lib/cpp/mosquittopp.cpp | 292 +++ lib/cpp/mosquittopp.h | 102 + lib/dummypthread.h | 13 + lib/jsws/mosquitto.js | 363 ++++ lib/linker.version | 71 + lib/logging_mosq.c | 56 + lib/logging_mosq.h | 23 + lib/memory_mosq.c | 121 ++ lib/memory_mosq.h | 37 + lib/messages_mosq.c | 387 ++++ lib/messages_mosq.h | 31 + lib/mosquitto.c | 1293 ++++++++++++ lib/mosquitto.h | 1409 +++++++++++++ lib/mosquitto_internal.h | 230 +++ lib/mqtt3_protocol.h | 53 + lib/net_mosq.c | 1116 +++++++++++ lib/net_mosq.h | 91 + lib/read_handle.c | 155 ++ lib/read_handle.h | 34 + lib/read_handle_client.c | 63 + lib/read_handle_shared.c | 258 +++ lib/send_client_mosq.c | 214 ++ lib/send_mosq.c | 276 +++ lib/send_mosq.h | 37 + lib/srv_mosq.c | 93 + lib/thread_mosq.c | 101 + lib/time_mosq.c | 82 + lib/time_mosq.h | 22 + lib/tls_mosq.c | 134 ++ lib/tls_mosq.h | 36 + lib/util_mosq.c | 307 +++ lib/util_mosq.h | 35 + lib/will_mosq.c | 117 ++ lib/will_mosq.h | 26 + logo/mosquitto-14x14.png | Bin 0 -> 513 bytes logo/mosquitto-16x16.png | Bin 0 -> 566 bytes logo/mosquitto.svg | 182 ++ man/CMakeLists.txt | 5 + man/Makefile | 74 + man/html.xsl | 7 + man/libmosquitto.3.xml | 487 +++++ man/manpage.xsl | 14 + man/mosquitto-tls.7.xml | 100 + man/mosquitto.8.xml | 559 ++++++ man/mosquitto.conf.5.xml | 1283 ++++++++++++ man/mosquitto_passwd.1.xml | 145 ++ man/mosquitto_pub.1.xml | 461 +++++ man/mosquitto_sub.1.xml | 491 +++++ man/mqtt.7.xml | 187 ++ misc/currentcost/cc128_log_mysql.pl | 55 + misc/currentcost/cc128_parse.pl | 45 + misc/currentcost/cc128_read.pl | 23 + misc/currentcost/cc128_read.py | 22 + .../gnome-panel/CurrentCostMQTT.py | 69 + .../gnome-panel/CurrentCostMQTT.server | 28 + misc/currentcost/gnome-panel/currentcost.png | Bin 0 -> 5316 bytes mosquitto.conf | 745 +++++++ pskfile.example | 2 + pwfile.example | 3 + readme-windows.txt | 5 + readme.txt | 20 + security/mosquitto.apparmor | 27 + service/monit/mosquitto.monit | 4 + service/svscan/run | 3 + service/upstart/mosquitto.conf | 8 + src/CMakeLists.txt | 105 + src/Makefile | 118 ++ src/bridge.c | 249 +++ src/conf.c | 1762 +++++++++++++++++ src/context.c | 203 ++ src/database.c | 901 +++++++++ src/db_dump/Makefile | 16 + src/db_dump/db_dump.c | 399 ++++ src/lib_load.h | 37 + src/logging.c | 222 +++ src/loop.c | 363 ++++ src/mosquitto.c | 389 ++++ src/mosquitto.db | Bin 0 -> 68602 bytes src/mosquitto_broker.h | 421 ++++ src/mosquitto_passwd.c | 458 +++++ src/mosquitto_plugin.h | 181 ++ src/net.c | 521 +++++ src/persist.c | 839 ++++++++ src/persist.h | 35 + src/read_handle.c | 283 +++ src/read_handle_client.c | 117 ++ src/read_handle_server.c | 695 +++++++ src/security.c | 221 +++ src/security_default.c | 796 ++++++++ src/send_server.c | 76 + src/service.c | 143 ++ src/subs.c | 662 +++++++ src/sys_tree.c | 330 +++ src/uthash.h | 948 +++++++++ test/Makefile | 91 + test/broker/01-connect-anon-denied.conf | 3 + test/broker/01-connect-anon-denied.pwfile | 1 + test/broker/01-connect-anon-denied.py | 38 + test/broker/01-connect-invalid-id-0-311.py | 37 + test/broker/01-connect-invalid-id-0.py | 37 + test/broker/01-connect-invalid-id-24.py | 39 + test/broker/01-connect-invalid-id-missing.py | 36 + test/broker/01-connect-invalid-protonum.py | 39 + test/broker/01-connect-success.py | 40 + .../01-connect-uname-no-password-denied.conf | 3 + ...01-connect-uname-no-password-denied.pwfile | 1 + .../01-connect-uname-no-password-denied.py | 39 + .../01-connect-uname-password-denied.conf | 3 + .../01-connect-uname-password-denied.pwfile | 1 + .../01-connect-uname-password-denied.py | 40 + .../01-connect-uname-password-success.conf | 3 + .../01-connect-uname-password-success.pwfile | 1 + .../01-connect-uname-password-success.py | 40 + test/broker/02-subpub-qos0.py | 51 + test/broker/02-subpub-qos1.py | 58 + test/broker/02-subpub-qos2.py | 69 + test/broker/02-subscribe-qos0.py | 46 + test/broker/02-subscribe-qos1.py | 46 + test/broker/02-subscribe-qos2.py | 46 + test/broker/02-unsubscribe-qos0.py | 47 + test/broker/02-unsubscribe-qos1.py | 46 + test/broker/02-unsubscribe-qos2.py | 46 + test/broker/03-pattern-matching-helper.py | 28 + test/broker/03-pattern-matching.py | 90 + .../03-publish-b2c-disconnect-qos1-helper.py | 33 + .../03-publish-b2c-disconnect-qos1.conf | 3 + test/broker/03-publish-b2c-disconnect-qos1.py | 73 + .../03-publish-b2c-disconnect-qos2-helper.py | 40 + .../03-publish-b2c-disconnect-qos2.conf | 3 + test/broker/03-publish-b2c-disconnect-qos2.py | 79 + .../03-publish-b2c-timeout-qos1-helper.py | 35 + test/broker/03-publish-b2c-timeout-qos1.conf | 3 + test/broker/03-publish-b2c-timeout-qos1.py | 62 + .../03-publish-b2c-timeout-qos2-helper.py | 40 + test/broker/03-publish-b2c-timeout-qos2.conf | 3 + test/broker/03-publish-b2c-timeout-qos2.py | 72 + .../03-publish-c2b-disconnect-qos2.conf | 2 + test/broker/03-publish-c2b-disconnect-qos2.py | 77 + test/broker/03-publish-c2b-timeout-qos2.conf | 2 + test/broker/03-publish-c2b-timeout-qos2.py | 56 + test/broker/03-publish-qos1.py | 46 + test/broker/03-publish-qos2.py | 51 + test/broker/04-retain-qos0-clear.py | 75 + test/broker/04-retain-qos0-fresh.py | 51 + test/broker/04-retain-qos0-repeated.py | 60 + test/broker/04-retain-qos0.py | 48 + test/broker/04-retain-qos1-qos0.py | 55 + test/broker/05-clean-session-qos1-helper.py | 35 + test/broker/05-clean-session-qos1.py | 62 + .../06-bridge-b2br-disconnect-qos1.conf | 9 + test/broker/06-bridge-b2br-disconnect-qos1.py | 95 + .../06-bridge-b2br-disconnect-qos2.conf | 11 + test/broker/06-bridge-b2br-disconnect-qos2.py | 112 ++ .../06-bridge-br2b-disconnect-qos1-helper.py | 33 + .../06-bridge-br2b-disconnect-qos1.conf | 10 + test/broker/06-bridge-br2b-disconnect-qos1.py | 91 + .../06-bridge-br2b-disconnect-qos2-helper.py | 38 + .../06-bridge-br2b-disconnect-qos2.conf | 11 + test/broker/06-bridge-br2b-disconnect-qos2.py | 114 ++ .../06-bridge-reconnect-local-out-helper.py | 35 + .../broker/06-bridge-reconnect-local-out.conf | 9 + test/broker/06-bridge-reconnect-local-out.py | 81 + test/broker/07-will-acl-denied.acl | 2 + test/broker/07-will-acl-denied.conf | 2 + test/broker/07-will-acl-denied.py | 42 + test/broker/07-will-null-helper.py | 28 + test/broker/07-will-null-topic.py | 37 + test/broker/07-will-null.py | 53 + test/broker/07-will-qos0-helper.py | 27 + test/broker/07-will-qos0.py | 52 + test/broker/08-ssl-bridge-helper.py | 29 + test/broker/08-ssl-bridge.conf | 12 + test/broker/08-ssl-bridge.py | 71 + test/broker/08-ssl-connect-cert-auth-crl.conf | 9 + test/broker/08-ssl-connect-cert-auth-crl.py | 49 + .../08-ssl-connect-cert-auth-expired.conf | 8 + .../08-ssl-connect-cert-auth-expired.py | 53 + .../08-ssl-connect-cert-auth-revoked.conf | 9 + .../08-ssl-connect-cert-auth-revoked.py | 52 + .../08-ssl-connect-cert-auth-without.conf | 8 + .../08-ssl-connect-cert-auth-without.py | 53 + test/broker/08-ssl-connect-cert-auth.conf | 8 + test/broker/08-ssl-connect-cert-auth.py | 51 + test/broker/08-ssl-connect-identity.conf | 9 + test/broker/08-ssl-connect-identity.py | 50 + .../08-ssl-connect-no-auth-wrong-ca.conf | 7 + .../broker/08-ssl-connect-no-auth-wrong-ca.py | 49 + test/broker/08-ssl-connect-no-auth.conf | 7 + test/broker/08-ssl-connect-no-auth.py | 51 + test/broker/08-ssl-connect-no-identity.conf | 9 + test/broker/08-ssl-connect-no-identity.py | 50 + test/broker/08-tls-psk-bridge.conf | 14 + test/broker/08-tls-psk-bridge.conf2 | 14 + test/broker/08-tls-psk-bridge.psk | 1 + test/broker/08-tls-psk-bridge.py | 81 + test/broker/08-tls-psk-pub.conf | 15 + test/broker/08-tls-psk-pub.psk | 1 + test/broker/08-tls-psk-pub.py | 71 + test/broker/09-plugin-auth-unpwd-fail.conf | 3 + test/broker/09-plugin-auth-unpwd-fail.py | 40 + test/broker/09-plugin-auth-unpwd-success.conf | 3 + test/broker/09-plugin-auth-unpwd-success.py | 40 + test/broker/10-listener-mount-point-helper.py | 28 + test/broker/10-listener-mount-point.conf | 7 + test/broker/10-listener-mount-point.py | 52 + test/broker/Makefile | 93 + test/broker/c/08-tls-psk-bridge.c | 63 + test/broker/c/08-tls-psk-pub.c | 67 + test/broker/c/Makefile | 23 + test/broker/c/auth_plugin.c | 55 + test/broker/readme.txt | 14 + test/fake_user.c | 114 ++ test/lib/01-con-discon-success.py | 65 + test/lib/01-keepalive-pingreq.py | 69 + test/lib/01-no-clean-session.py | 56 + test/lib/01-unpwd-set.py | 56 + test/lib/01-will-set.py | 58 + test/lib/01-will-unpwd-set.py | 60 + test/lib/02-subscribe-qos0.py | 75 + test/lib/02-subscribe-qos1.py | 75 + test/lib/02-subscribe-qos2.py | 75 + test/lib/02-unsubscribe.py | 65 + test/lib/03-publish-b2c-qos1.py | 82 + test/lib/03-publish-b2c-qos2.py | 95 + test/lib/03-publish-c2b-qos1-disconnect.py | 77 + test/lib/03-publish-c2b-qos1-timeout.py | 83 + test/lib/03-publish-c2b-qos2-disconnect.py | 93 + test/lib/03-publish-c2b-qos2-timeout.py | 94 + test/lib/03-publish-c2b-qos2.py | 89 + test/lib/03-publish-qos0-no-payload.py | 69 + test/lib/03-publish-qos0.py | 69 + test/lib/04-retain-qos0.py | 59 + test/lib/08-ssl-bad-cacert.py | 38 + test/lib/08-ssl-connect-cert-auth.py | 73 + test/lib/08-ssl-connect-no-auth.py | 70 + test/lib/08-ssl-fake-cacert.py | 61 + test/lib/09-util-topic-matching.py | 30 + test/lib/09-util-topic-tokenise.py | 27 + test/lib/Makefile | 48 + test/lib/c/01-con-discon-success.c | 41 + test/lib/c/01-keepalive-pingreq.c | 33 + test/lib/c/01-no-clean-session.c | 25 + test/lib/c/01-unpwd-set.c | 26 + test/lib/c/01-will-set.c | 26 + test/lib/c/01-will-unpwd-set.c | 27 + test/lib/c/02-subscribe-qos0.c | 47 + test/lib/c/02-subscribe-qos1.c | 47 + test/lib/c/02-subscribe-qos2.c | 47 + test/lib/c/02-unsubscribe.c | 47 + test/lib/c/03-publish-b2c-qos1.c | 64 + test/lib/c/03-publish-b2c-qos2.c | 66 + test/lib/c/03-publish-c2b-qos1-disconnect.c | 57 + test/lib/c/03-publish-c2b-qos1-timeout.c | 49 + test/lib/c/03-publish-c2b-qos2-disconnect.c | 57 + test/lib/c/03-publish-c2b-qos2-timeout.c | 49 + test/lib/c/03-publish-c2b-qos2.c | 48 + test/lib/c/03-publish-qos0-no-payload.c | 48 + test/lib/c/03-publish-qos0.c | 48 + test/lib/c/04-retain-qos0.c | 36 + test/lib/c/08-ssl-bad-cacert.c | 21 + test/lib/c/08-ssl-connect-cert-auth.c | 44 + test/lib/c/08-ssl-connect-no-auth.c | 45 + test/lib/c/08-ssl-fake-cacert.c | 35 + test/lib/c/09-util-topic-matching.c | 48 + test/lib/c/09-util-topic-tokenise.c | 130 ++ test/lib/c/Makefile | 102 + test/lib/cpp/01-con-discon-success.cpp | 53 + test/lib/cpp/01-keepalive-pingreq.cpp | 41 + test/lib/cpp/01-no-clean-session.cpp | 33 + test/lib/cpp/01-unpwd-set.cpp | 34 + test/lib/cpp/01-will-set.cpp | 37 + test/lib/cpp/01-will-unpwd-set.cpp | 35 + test/lib/cpp/02-subscribe-qos0.cpp | 55 + test/lib/cpp/02-subscribe-qos1.cpp | 56 + test/lib/cpp/02-subscribe-qos2.cpp | 56 + test/lib/cpp/02-unsubscribe.cpp | 55 + test/lib/cpp/03-publish-b2c-qos1.cpp | 76 + test/lib/cpp/03-publish-b2c-qos2.cpp | 78 + .../cpp/03-publish-c2b-qos1-disconnect.cpp | 68 + test/lib/cpp/03-publish-c2b-qos1-timeout.cpp | 60 + .../cpp/03-publish-c2b-qos2-disconnect.cpp | 68 + test/lib/cpp/03-publish-c2b-qos2-timeout.cpp | 60 + test/lib/cpp/03-publish-c2b-qos2.cpp | 59 + test/lib/cpp/03-publish-qos0-no-payload.cpp | 56 + test/lib/cpp/03-publish-qos0.cpp | 56 + test/lib/cpp/04-retain-qos0.cpp | 45 + test/lib/cpp/08-ssl-bad-cacert.cpp | 31 + test/lib/cpp/08-ssl-connect-cert-auth.cpp | 53 + test/lib/cpp/08-ssl-connect-no-auth.cpp | 53 + test/lib/cpp/08-ssl-fake-cacert.cpp | 42 + test/lib/cpp/09-util-topic-matching.cpp | 47 + test/lib/cpp/09-util-topic-tokenise.cpp | 129 ++ test/lib/cpp/Makefile | 102 + test/mosq_test.py | 345 ++++ test/msgsps_common.h | 3 + test/msgsps_pub.c | 135 ++ test/msgsps_sub.c | 86 + test/packet-gen.c | 53 + test/qos.c | 186 ++ test/random_client.c | 198 ++ test/ssl/all-ca.crt | 75 + test/ssl/client-expired.crt | 61 + test/ssl/client-revoked.crt | 61 + test/ssl/client-revoked.csr | 12 + test/ssl/client-revoked.key | 15 + test/ssl/client.crt | 61 + test/ssl/client.csr | 12 + test/ssl/client.key | 15 + test/ssl/crl.pem | 10 + test/ssl/demoCA/crlnumber | 1 + test/ssl/demoCA/index.txt | 1 + test/ssl/demoCA/index.txt.attr | 1 + test/ssl/demoCA/serial | 1 + test/ssl/gen.sh | 70 + test/ssl/openssl.cnf | 406 ++++ test/ssl/readme.txt | 2 + test/ssl/rootCA/crlnumber | 1 + test/ssl/rootCA/index.txt | 2 + test/ssl/rootCA/index.txt.attr | 1 + test/ssl/rootCA/serial | 1 + test/ssl/server-expired.crt | 0 test/ssl/server.crt | 60 + test/ssl/server.csr | 12 + test/ssl/server.key | 15 + test/ssl/signingCA/crlnumber | 1 + test/ssl/signingCA/index.txt | 4 + test/ssl/signingCA/index.txt.attr | 1 + test/ssl/signingCA/serial | 1 + test/ssl/test-alt-ca.crt | 58 + test/ssl/test-alt-ca.key | 15 + test/ssl/test-bad-root-ca.crt | 17 + test/ssl/test-bad-root-ca.key | 15 + test/ssl/test-ca.srl | 1 + test/ssl/test-fake-root-ca.crt | 17 + test/ssl/test-fake-root-ca.key | 15 + test/ssl/test-root-ca.crt | 17 + test/ssl/test-root-ca.key | 15 + test/ssl/test-signing-ca.crt | 58 + test/ssl/test-signing-ca.key | 15 + test/to-test | 7 + 372 files changed, 39627 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 ChangeLog.txt create mode 100644 LICENSE.txt create mode 100644 Makefile create mode 100644 THANKS.txt create mode 100644 aclfile.example create mode 100644 changeset create mode 100644 client/CMakeLists.txt create mode 100644 client/Makefile create mode 100644 client/pub_client.c create mode 100644 client/sub_client.c create mode 100644 compiling.txt create mode 100644 config.h create mode 100644 config.mk create mode 100644 doc/historical/old-regex.txt create mode 100644 doc/historical/topic-match.kds create mode 100644 edl-v10 create mode 100644 epl-v10 create mode 100644 examples/mysql_log/Makefile create mode 100644 examples/mysql_log/mysql_log.c create mode 100644 examples/temperature_conversion/Makefile create mode 100644 examples/temperature_conversion/main.cpp create mode 100644 examples/temperature_conversion/readme.txt create mode 100644 examples/temperature_conversion/temperature_conversion.cpp create mode 100644 examples/temperature_conversion/temperature_conversion.h create mode 100644 installer/mosquitto-cygwin.nsi create mode 100644 installer/mosquitto.nsi create mode 100644 lib/CMakeLists.txt create mode 100644 lib/Makefile create mode 100644 lib/cpp/CMakeLists.txt create mode 100644 lib/cpp/Makefile create mode 100644 lib/cpp/mosquittopp.cpp create mode 100644 lib/cpp/mosquittopp.h create mode 100644 lib/dummypthread.h create mode 100644 lib/jsws/mosquitto.js create mode 100644 lib/linker.version create mode 100644 lib/logging_mosq.c create mode 100644 lib/logging_mosq.h create mode 100644 lib/memory_mosq.c create mode 100644 lib/memory_mosq.h create mode 100644 lib/messages_mosq.c create mode 100644 lib/messages_mosq.h create mode 100644 lib/mosquitto.c create mode 100644 lib/mosquitto.h create mode 100644 lib/mosquitto_internal.h create mode 100644 lib/mqtt3_protocol.h create mode 100644 lib/net_mosq.c create mode 100644 lib/net_mosq.h create mode 100644 lib/read_handle.c create mode 100644 lib/read_handle.h create mode 100644 lib/read_handle_client.c create mode 100644 lib/read_handle_shared.c create mode 100644 lib/send_client_mosq.c create mode 100644 lib/send_mosq.c create mode 100644 lib/send_mosq.h create mode 100644 lib/srv_mosq.c create mode 100644 lib/thread_mosq.c create mode 100644 lib/time_mosq.c create mode 100644 lib/time_mosq.h create mode 100644 lib/tls_mosq.c create mode 100644 lib/tls_mosq.h create mode 100644 lib/util_mosq.c create mode 100644 lib/util_mosq.h create mode 100644 lib/will_mosq.c create mode 100644 lib/will_mosq.h create mode 100644 logo/mosquitto-14x14.png create mode 100644 logo/mosquitto-16x16.png create mode 100644 logo/mosquitto.svg create mode 100644 man/CMakeLists.txt create mode 100644 man/Makefile create mode 100644 man/html.xsl create mode 100644 man/libmosquitto.3.xml create mode 100644 man/manpage.xsl create mode 100644 man/mosquitto-tls.7.xml create mode 100644 man/mosquitto.8.xml create mode 100644 man/mosquitto.conf.5.xml create mode 100644 man/mosquitto_passwd.1.xml create mode 100644 man/mosquitto_pub.1.xml create mode 100644 man/mosquitto_sub.1.xml create mode 100644 man/mqtt.7.xml create mode 100755 misc/currentcost/cc128_log_mysql.pl create mode 100755 misc/currentcost/cc128_parse.pl create mode 100755 misc/currentcost/cc128_read.pl create mode 100755 misc/currentcost/cc128_read.py create mode 100755 misc/currentcost/gnome-panel/CurrentCostMQTT.py create mode 100644 misc/currentcost/gnome-panel/CurrentCostMQTT.server create mode 100644 misc/currentcost/gnome-panel/currentcost.png create mode 100644 mosquitto.conf create mode 100644 pskfile.example create mode 100644 pwfile.example create mode 100644 readme-windows.txt create mode 100644 readme.txt create mode 100644 security/mosquitto.apparmor create mode 100644 service/monit/mosquitto.monit create mode 100755 service/svscan/run create mode 100644 service/upstart/mosquitto.conf create mode 100644 src/CMakeLists.txt create mode 100644 src/Makefile create mode 100644 src/bridge.c create mode 100644 src/conf.c create mode 100644 src/context.c create mode 100644 src/database.c create mode 100644 src/db_dump/Makefile create mode 100644 src/db_dump/db_dump.c create mode 100644 src/lib_load.h create mode 100644 src/logging.c create mode 100644 src/loop.c create mode 100644 src/mosquitto.c create mode 100644 src/mosquitto.db create mode 100644 src/mosquitto_broker.h create mode 100644 src/mosquitto_passwd.c create mode 100644 src/mosquitto_plugin.h create mode 100644 src/net.c create mode 100644 src/persist.c create mode 100644 src/persist.h create mode 100644 src/read_handle.c create mode 100644 src/read_handle_client.c create mode 100644 src/read_handle_server.c create mode 100644 src/security.c create mode 100644 src/security_default.c create mode 100644 src/send_server.c create mode 100644 src/service.c create mode 100644 src/subs.c create mode 100644 src/sys_tree.c create mode 100644 src/uthash.h create mode 100644 test/Makefile create mode 100644 test/broker/01-connect-anon-denied.conf create mode 100644 test/broker/01-connect-anon-denied.pwfile create mode 100755 test/broker/01-connect-anon-denied.py create mode 100755 test/broker/01-connect-invalid-id-0-311.py create mode 100755 test/broker/01-connect-invalid-id-0.py create mode 100755 test/broker/01-connect-invalid-id-24.py create mode 100755 test/broker/01-connect-invalid-id-missing.py create mode 100755 test/broker/01-connect-invalid-protonum.py create mode 100755 test/broker/01-connect-success.py create mode 100644 test/broker/01-connect-uname-no-password-denied.conf create mode 100644 test/broker/01-connect-uname-no-password-denied.pwfile create mode 100755 test/broker/01-connect-uname-no-password-denied.py create mode 100644 test/broker/01-connect-uname-password-denied.conf create mode 100644 test/broker/01-connect-uname-password-denied.pwfile create mode 100755 test/broker/01-connect-uname-password-denied.py create mode 100644 test/broker/01-connect-uname-password-success.conf create mode 100644 test/broker/01-connect-uname-password-success.pwfile create mode 100755 test/broker/01-connect-uname-password-success.py create mode 100755 test/broker/02-subpub-qos0.py create mode 100755 test/broker/02-subpub-qos1.py create mode 100755 test/broker/02-subpub-qos2.py create mode 100755 test/broker/02-subscribe-qos0.py create mode 100755 test/broker/02-subscribe-qos1.py create mode 100755 test/broker/02-subscribe-qos2.py create mode 100755 test/broker/02-unsubscribe-qos0.py create mode 100755 test/broker/02-unsubscribe-qos1.py create mode 100755 test/broker/02-unsubscribe-qos2.py create mode 100755 test/broker/03-pattern-matching-helper.py create mode 100755 test/broker/03-pattern-matching.py create mode 100755 test/broker/03-publish-b2c-disconnect-qos1-helper.py create mode 100644 test/broker/03-publish-b2c-disconnect-qos1.conf create mode 100755 test/broker/03-publish-b2c-disconnect-qos1.py create mode 100755 test/broker/03-publish-b2c-disconnect-qos2-helper.py create mode 100644 test/broker/03-publish-b2c-disconnect-qos2.conf create mode 100755 test/broker/03-publish-b2c-disconnect-qos2.py create mode 100755 test/broker/03-publish-b2c-timeout-qos1-helper.py create mode 100644 test/broker/03-publish-b2c-timeout-qos1.conf create mode 100755 test/broker/03-publish-b2c-timeout-qos1.py create mode 100755 test/broker/03-publish-b2c-timeout-qos2-helper.py create mode 100644 test/broker/03-publish-b2c-timeout-qos2.conf create mode 100755 test/broker/03-publish-b2c-timeout-qos2.py create mode 100644 test/broker/03-publish-c2b-disconnect-qos2.conf create mode 100755 test/broker/03-publish-c2b-disconnect-qos2.py create mode 100644 test/broker/03-publish-c2b-timeout-qos2.conf create mode 100755 test/broker/03-publish-c2b-timeout-qos2.py create mode 100755 test/broker/03-publish-qos1.py create mode 100755 test/broker/03-publish-qos2.py create mode 100755 test/broker/04-retain-qos0-clear.py create mode 100755 test/broker/04-retain-qos0-fresh.py create mode 100755 test/broker/04-retain-qos0-repeated.py create mode 100755 test/broker/04-retain-qos0.py create mode 100755 test/broker/04-retain-qos1-qos0.py create mode 100755 test/broker/05-clean-session-qos1-helper.py create mode 100755 test/broker/05-clean-session-qos1.py create mode 100644 test/broker/06-bridge-b2br-disconnect-qos1.conf create mode 100755 test/broker/06-bridge-b2br-disconnect-qos1.py create mode 100644 test/broker/06-bridge-b2br-disconnect-qos2.conf create mode 100755 test/broker/06-bridge-b2br-disconnect-qos2.py create mode 100755 test/broker/06-bridge-br2b-disconnect-qos1-helper.py create mode 100644 test/broker/06-bridge-br2b-disconnect-qos1.conf create mode 100755 test/broker/06-bridge-br2b-disconnect-qos1.py create mode 100755 test/broker/06-bridge-br2b-disconnect-qos2-helper.py create mode 100644 test/broker/06-bridge-br2b-disconnect-qos2.conf create mode 100755 test/broker/06-bridge-br2b-disconnect-qos2.py create mode 100755 test/broker/06-bridge-reconnect-local-out-helper.py create mode 100644 test/broker/06-bridge-reconnect-local-out.conf create mode 100755 test/broker/06-bridge-reconnect-local-out.py create mode 100644 test/broker/07-will-acl-denied.acl create mode 100644 test/broker/07-will-acl-denied.conf create mode 100755 test/broker/07-will-acl-denied.py create mode 100755 test/broker/07-will-null-helper.py create mode 100755 test/broker/07-will-null-topic.py create mode 100755 test/broker/07-will-null.py create mode 100755 test/broker/07-will-qos0-helper.py create mode 100755 test/broker/07-will-qos0.py create mode 100755 test/broker/08-ssl-bridge-helper.py create mode 100644 test/broker/08-ssl-bridge.conf create mode 100755 test/broker/08-ssl-bridge.py create mode 100644 test/broker/08-ssl-connect-cert-auth-crl.conf create mode 100755 test/broker/08-ssl-connect-cert-auth-crl.py create mode 100644 test/broker/08-ssl-connect-cert-auth-expired.conf create mode 100755 test/broker/08-ssl-connect-cert-auth-expired.py create mode 100644 test/broker/08-ssl-connect-cert-auth-revoked.conf create mode 100755 test/broker/08-ssl-connect-cert-auth-revoked.py create mode 100644 test/broker/08-ssl-connect-cert-auth-without.conf create mode 100755 test/broker/08-ssl-connect-cert-auth-without.py create mode 100644 test/broker/08-ssl-connect-cert-auth.conf create mode 100755 test/broker/08-ssl-connect-cert-auth.py create mode 100644 test/broker/08-ssl-connect-identity.conf create mode 100755 test/broker/08-ssl-connect-identity.py create mode 100644 test/broker/08-ssl-connect-no-auth-wrong-ca.conf create mode 100755 test/broker/08-ssl-connect-no-auth-wrong-ca.py create mode 100644 test/broker/08-ssl-connect-no-auth.conf create mode 100755 test/broker/08-ssl-connect-no-auth.py create mode 100644 test/broker/08-ssl-connect-no-identity.conf create mode 100755 test/broker/08-ssl-connect-no-identity.py create mode 100644 test/broker/08-tls-psk-bridge.conf create mode 100644 test/broker/08-tls-psk-bridge.conf2 create mode 100644 test/broker/08-tls-psk-bridge.psk create mode 100755 test/broker/08-tls-psk-bridge.py create mode 100644 test/broker/08-tls-psk-pub.conf create mode 100644 test/broker/08-tls-psk-pub.psk create mode 100755 test/broker/08-tls-psk-pub.py create mode 100644 test/broker/09-plugin-auth-unpwd-fail.conf create mode 100755 test/broker/09-plugin-auth-unpwd-fail.py create mode 100644 test/broker/09-plugin-auth-unpwd-success.conf create mode 100755 test/broker/09-plugin-auth-unpwd-success.py create mode 100755 test/broker/10-listener-mount-point-helper.py create mode 100644 test/broker/10-listener-mount-point.conf create mode 100755 test/broker/10-listener-mount-point.py create mode 100644 test/broker/Makefile create mode 100644 test/broker/c/08-tls-psk-bridge.c create mode 100644 test/broker/c/08-tls-psk-pub.c create mode 100644 test/broker/c/Makefile create mode 100644 test/broker/c/auth_plugin.c create mode 100644 test/broker/readme.txt create mode 100644 test/fake_user.c create mode 100755 test/lib/01-con-discon-success.py create mode 100755 test/lib/01-keepalive-pingreq.py create mode 100755 test/lib/01-no-clean-session.py create mode 100755 test/lib/01-unpwd-set.py create mode 100755 test/lib/01-will-set.py create mode 100755 test/lib/01-will-unpwd-set.py create mode 100755 test/lib/02-subscribe-qos0.py create mode 100755 test/lib/02-subscribe-qos1.py create mode 100755 test/lib/02-subscribe-qos2.py create mode 100755 test/lib/02-unsubscribe.py create mode 100755 test/lib/03-publish-b2c-qos1.py create mode 100755 test/lib/03-publish-b2c-qos2.py create mode 100755 test/lib/03-publish-c2b-qos1-disconnect.py create mode 100755 test/lib/03-publish-c2b-qos1-timeout.py create mode 100755 test/lib/03-publish-c2b-qos2-disconnect.py create mode 100755 test/lib/03-publish-c2b-qos2-timeout.py create mode 100755 test/lib/03-publish-c2b-qos2.py create mode 100755 test/lib/03-publish-qos0-no-payload.py create mode 100755 test/lib/03-publish-qos0.py create mode 100755 test/lib/04-retain-qos0.py create mode 100755 test/lib/08-ssl-bad-cacert.py create mode 100755 test/lib/08-ssl-connect-cert-auth.py create mode 100755 test/lib/08-ssl-connect-no-auth.py create mode 100755 test/lib/08-ssl-fake-cacert.py create mode 100755 test/lib/09-util-topic-matching.py create mode 100755 test/lib/09-util-topic-tokenise.py create mode 100644 test/lib/Makefile create mode 100644 test/lib/c/01-con-discon-success.c create mode 100644 test/lib/c/01-keepalive-pingreq.c create mode 100644 test/lib/c/01-no-clean-session.c create mode 100644 test/lib/c/01-unpwd-set.c create mode 100644 test/lib/c/01-will-set.c create mode 100644 test/lib/c/01-will-unpwd-set.c create mode 100644 test/lib/c/02-subscribe-qos0.c create mode 100644 test/lib/c/02-subscribe-qos1.c create mode 100644 test/lib/c/02-subscribe-qos2.c create mode 100644 test/lib/c/02-unsubscribe.c create mode 100644 test/lib/c/03-publish-b2c-qos1.c create mode 100644 test/lib/c/03-publish-b2c-qos2.c create mode 100644 test/lib/c/03-publish-c2b-qos1-disconnect.c create mode 100644 test/lib/c/03-publish-c2b-qos1-timeout.c create mode 100644 test/lib/c/03-publish-c2b-qos2-disconnect.c create mode 100644 test/lib/c/03-publish-c2b-qos2-timeout.c create mode 100644 test/lib/c/03-publish-c2b-qos2.c create mode 100644 test/lib/c/03-publish-qos0-no-payload.c create mode 100644 test/lib/c/03-publish-qos0.c create mode 100644 test/lib/c/04-retain-qos0.c create mode 100644 test/lib/c/08-ssl-bad-cacert.c create mode 100644 test/lib/c/08-ssl-connect-cert-auth.c create mode 100644 test/lib/c/08-ssl-connect-no-auth.c create mode 100644 test/lib/c/08-ssl-fake-cacert.c create mode 100644 test/lib/c/09-util-topic-matching.c create mode 100644 test/lib/c/09-util-topic-tokenise.c create mode 100644 test/lib/c/Makefile create mode 100644 test/lib/cpp/01-con-discon-success.cpp create mode 100644 test/lib/cpp/01-keepalive-pingreq.cpp create mode 100644 test/lib/cpp/01-no-clean-session.cpp create mode 100644 test/lib/cpp/01-unpwd-set.cpp create mode 100644 test/lib/cpp/01-will-set.cpp create mode 100644 test/lib/cpp/01-will-unpwd-set.cpp create mode 100644 test/lib/cpp/02-subscribe-qos0.cpp create mode 100644 test/lib/cpp/02-subscribe-qos1.cpp create mode 100644 test/lib/cpp/02-subscribe-qos2.cpp create mode 100644 test/lib/cpp/02-unsubscribe.cpp create mode 100644 test/lib/cpp/03-publish-b2c-qos1.cpp create mode 100644 test/lib/cpp/03-publish-b2c-qos2.cpp create mode 100644 test/lib/cpp/03-publish-c2b-qos1-disconnect.cpp create mode 100644 test/lib/cpp/03-publish-c2b-qos1-timeout.cpp create mode 100644 test/lib/cpp/03-publish-c2b-qos2-disconnect.cpp create mode 100644 test/lib/cpp/03-publish-c2b-qos2-timeout.cpp create mode 100644 test/lib/cpp/03-publish-c2b-qos2.cpp create mode 100644 test/lib/cpp/03-publish-qos0-no-payload.cpp create mode 100644 test/lib/cpp/03-publish-qos0.cpp create mode 100644 test/lib/cpp/04-retain-qos0.cpp create mode 100644 test/lib/cpp/08-ssl-bad-cacert.cpp create mode 100644 test/lib/cpp/08-ssl-connect-cert-auth.cpp create mode 100644 test/lib/cpp/08-ssl-connect-no-auth.cpp create mode 100644 test/lib/cpp/08-ssl-fake-cacert.cpp create mode 100644 test/lib/cpp/09-util-topic-matching.cpp create mode 100644 test/lib/cpp/09-util-topic-tokenise.cpp create mode 100644 test/lib/cpp/Makefile create mode 100644 test/mosq_test.py create mode 100644 test/msgsps_common.h create mode 100644 test/msgsps_pub.c create mode 100644 test/msgsps_sub.c create mode 100644 test/packet-gen.c create mode 100644 test/qos.c create mode 100644 test/random_client.c create mode 100644 test/ssl/all-ca.crt create mode 100644 test/ssl/client-expired.crt create mode 100644 test/ssl/client-revoked.crt create mode 100644 test/ssl/client-revoked.csr create mode 100644 test/ssl/client-revoked.key create mode 100644 test/ssl/client.crt create mode 100644 test/ssl/client.csr create mode 100644 test/ssl/client.key create mode 100644 test/ssl/crl.pem create mode 100644 test/ssl/demoCA/crlnumber create mode 100644 test/ssl/demoCA/index.txt create mode 100644 test/ssl/demoCA/index.txt.attr create mode 100644 test/ssl/demoCA/serial create mode 100755 test/ssl/gen.sh create mode 100644 test/ssl/openssl.cnf create mode 100644 test/ssl/readme.txt create mode 100644 test/ssl/rootCA/crlnumber create mode 100644 test/ssl/rootCA/index.txt create mode 100644 test/ssl/rootCA/index.txt.attr create mode 100644 test/ssl/rootCA/serial create mode 100644 test/ssl/server-expired.crt create mode 100644 test/ssl/server.crt create mode 100644 test/ssl/server.csr create mode 100644 test/ssl/server.key create mode 100644 test/ssl/signingCA/crlnumber create mode 100644 test/ssl/signingCA/index.txt create mode 100644 test/ssl/signingCA/index.txt.attr create mode 100644 test/ssl/signingCA/serial create mode 100644 test/ssl/test-alt-ca.crt create mode 100644 test/ssl/test-alt-ca.key create mode 100644 test/ssl/test-bad-root-ca.crt create mode 100644 test/ssl/test-bad-root-ca.key create mode 100644 test/ssl/test-ca.srl create mode 100644 test/ssl/test-fake-root-ca.crt create mode 100644 test/ssl/test-fake-root-ca.key create mode 100644 test/ssl/test-root-ca.crt create mode 100644 test/ssl/test-root-ca.key create mode 100644 test/ssl/test-signing-ca.crt create mode 100644 test/ssl/test-signing-ca.key create mode 100644 test/to-test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..1a001570b9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +*.o +*.exe +*.db +c/*.test +cpp/*.test +*.pyc + +client/mosquitto_pub +client/mosquitto_sub +examples/mysql_log/mosquitto_mysql_log +examples/temperature_conversion/mqtt_temperature_conversion +man/mosquitto.8 +man/mosquitto-tls.7 +man/mosquitto.conf.5 +man/libmosquitto.3 +man/mosquitto_passwd.1 +man/mosquitto_pub.1 +man/mosquitto_sub.1 +man/mqtt.7 +src/db_dump/mosquitto_db_dump +src/mosquitto +src/mosquitto_passwd +test/broker/broker.pid +test/test_client +test/fake_user +test/msgsps_pub +test/msgsps_sub +test/msgsps_pub.dat +test/msgsps_sub.dat +test/broker/c/auth_plugin.so + +lib/cpp/libmosquittopp.so* +lib/libmosquitto.so* +lib/libmosquitto.a + +test/ssl/*.csr + +build/ +dist/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000000..6b02ad98e1 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,74 @@ +# This is a cmake script. + +set(CMAKE_LEGACY_CYGWIN_WIN32 0) + +project(mosquitto) + +cmake_minimum_required(VERSION 2.6) + +set (VERSION 1.3.1) + +if (WIN32) + execute_process(COMMAND cmd /c echo %DATE% %TIME% OUTPUT_VARIABLE TIMESTAMP + OUTPUT_STRIP_TRAILING_WHITESPACE) +else (WIN32) + execute_process(COMMAND date "+%F %T%z" OUTPUT_VARIABLE TIMESTAMP + OUTPUT_STRIP_TRAILING_WHITESPACE) +endif (WIN32) + +add_definitions (-DCMAKE -DVERSION=\"${VERSION}\" -DTIMESTAMP=\"${TIMESTAMP}\") + +if (WIN32) + set (BINDIR .) + set (SBINDIR .) + set (SYSCONFDIR .) + set (LIBDIR .) + set (INCLUDEDIR include) + set (DATAROOTDIR share) + set (MANDIR man) + set (SHAREDEST .) + add_definitions("-D_CRT_SECURE_NO_WARNINGS") +else (WIN32) + set (BINDIR bin) + set (SBINDIR sbin) + if (${CMAKE_INSTALL_PREFIX} STREQUAL /usr) + set (SYSCONFDIR /etc/mosquitto) + else (${CMAKE_INSTALL_PREFIX} STREQUAL /usr) + set (SYSCONFDIR etc/mosquitto) + endif (${CMAKE_INSTALL_PREFIX} STREQUAL /usr) + + set (LIBDIR lib${LIB_SUFFIX}) + set (INCLUDEDIR include) + set (DATAROOTDIR share) + set (MANDIR ${DATAROOTDIR}/man) + set (SHAREDIR ${DATAROOTDIR}/mosquitto) +endif (WIN32) + +option(WITH_TLS + "Include SSL/TLS support?" ON) +option(WITH_TLS_PSK + "Include TLS-PSK support (requires WITH_TLS)?" ON) +if (${WITH_TLS} STREQUAL ON) + find_package(OpenSSL REQUIRED) + add_definitions("-DWITH_TLS") + if (${WITH_TLS_PSK} STREQUAL ON) + add_definitions("-DWITH_TLS_PSK") + endif (${WITH_TLS_PSK} STREQUAL ON) +else (${WITH_TLS} STREQUAL ON) + set (OPENSSL_INCLUDE_DIR "") +endif (${WITH_TLS} STREQUAL ON) + +# ======================================== +# Include projects +# ======================================== + +add_subdirectory(lib) +add_subdirectory(client) +add_subdirectory(src) +add_subdirectory(man) + +# ======================================== +# Install config file +# ======================================== + +install(FILES mosquitto.conf aclfile.example pskfile.example pwfile.example DESTINATION ${SYSCONFDIR}) diff --git a/ChangeLog.txt b/ChangeLog.txt new file mode 100644 index 0000000000..f9f24f7017 --- /dev/null +++ b/ChangeLog.txt @@ -0,0 +1,1093 @@ +1.3.1 - 20140324 +================ + +Broker: +- Prevent possible crash on client reconnect. Closes bug #1294108. +- Don't accept zero length unsubscription strings (MQTT v3.1.1 fix) +- Don't accept QoS 3 (MQTT v3.1.1 fix) +- Don't disconnect clients immediately on HUP to give chance for all data to + be read. +- Reject invalid un/subscriptions e.g. foo/+bar #/bar. +- Take more care not to disconnect clients that are sending large messages. + +Client library: +- Fix socketpair code on the Mac. +- Fix compilation for WITH_THREADING=no. +- Break out of select() when calling mosquitto_loop_stop(). +- Reject invalid un/subscriptions e.g. foo/+bar #/bar. + +Clients: +- Fix keepalive value on mosquitto_pub. +- Fix possibility of mosquitto_pub not exiting after sending messages when + using -l. + +1.3 - 20140316 +============== + +Broker: +- The broker no longer ignores the auth_plugin_init() return value. +- Accept SSLv2/SSLv3 HELLOs when using TLSv1, whilst keeping SSLv2 and SSLv3 + disabled. This increases client compatibility without sacrificing security. +- The $SYS tree can now be disabled at runtime as well as at compile time. +- When remapping bridged topics, only check for matches when the message + direction is correct. This allows two identical topics to be remapped + differently for both in and out. +- Change "$SYS/broker/heap/current size" to "$SYS/broker/heap/current" for + easier parsing. +- Change "$SYS/broker/heap/maximum size" to "$SYS/broker/heap/maximum" for + easier parsing. +- Topics are no longer normalised from e.g a///topic to a/topic. This matches + the behaviour as clarified by the Oasis MQTT spec. This will lead to + unexpected behaviour if you were using topics of this form. +- Log when outgoing messages for a client begin to drop off the end of the + queue. +- Bridge clients are recognised as bridges even after reloading from + persistence. +- Basic support for MQTT v3.1.1. This does not include being able to bridge to + an MQTT v3.1.1 broker. +- Username is displayed in log if present when a client connects. +- Support for 0 length client ids (v3.1.1 only) that result in automatically + generated client ids on the broker (see option allow_zero_length_clientid). +- Ability to set the prefix of automatically generated client ids (see option + auto_id_prefix). +- Add support for TLS session resumption. +- When using TLS, the server now chooses the cipher to use when negotiating + with the client. +- Weak TLS ciphers are now disabled by default. + +Client library: +- Fix support for Python 2.6, 3.0, 3.1. +- Add support for un/subscribing to multiple topics at once in un/subscribe(). +- Clients now close their socket after sending DISCONNECT. +- Python client now contains its version number. +- C library mosquitto_want_write() now supports TLS clients. +- Fix possible memory leak in C/C++ library when communicating with + a broker that doesn't follow the spec. +- Return strerror() through mosquitto_strerror() to make error printing + easier. +- Topics are no longer normalised from e.g a///topic to a/topic. This matches + the behaviour as clarified by the Oasis MQTT spec. This will lead to + unexpected behaviour if you were using topics of this form. +- Add support for SRV lookups. +- Break out of select() on publish(), subscribe() etc. when using the threaded + interface. Fixes bug #1270062. +- Handle incoming and outgoing messages separately. Fixes bug #1263172. +- Don't terminate threads on mosquitto_destroy() when a client is not using + the threaded interface but does use their own thread. Fixes bug #1291473. + +Clients: +- Add --ciphers to allow specifying which TLS ciphers to support. +- Add support for SRV lookups. +- Add -N to sub client to suppress printing of EOL after the payload. +- Add -T to sub client to suppress printing of a topic hierarchy. + +1.2.3 - 20131202 +================ + +Broker: +- Don't always attempt to call read() for SSL clients, irrespective of whether + they were ready to read or not. Reduces syscalls significantly. +- Possible memory leak fixes. +- Further fix for bug #1226040: multiple retained messages being delivered for + subscriptions ending in #. +- Fix bridge reconnections when using multiple bridge addresses. + +Client library: +- Fix possible memory leak in C/C++ library when communicating with + a broker that doesn't follow the spec. +- Block in Python loop_stop() until all messages are sent, as the + documentation states should happen. +- Fix for asynchronous connections on Windows. Closes bug #1249202. +- Module version is now available in mosquitto.py. + +Clients: +- mosquitto_sub now uses fwrite() instead of printf() to output messages, so + messages with NULL characters aren't truncated. + +1.2.2 - 20131021 +================ + +Broker: +- Fix compliance with max_inflight_messages when a non-clean session client + reconnects. Closes one of the issues on bug #1237389. + +Client library: +- Fix incorrect inflight message accounting, which caused messages to go + unsent. Partial fix for bug #1237351. +- Fix potential memory corruption when sending QoS>0 messages at a high rate + using the threaded interface. Further fix for #1237351. +- Fix incorrect delay scaling when exponential_backoff=true in + mosquitto_reconnect_delay_set(). +- Some pep8 fixes for Python. + +1.2.1 - 20130918 +================ + +Broker: +- The broker no longer ignores the auth_plugin_init() return value. Closes + bug #1215084. +- Use RTLD_GLOBAL when opening authentication plugins on posix systems. Fixes + resolving of symbols in libraries used by authentication plugins. +- Add/fix some config documentation. +- Fix ACLs for topics with $SYS. +- Clients loaded from the persistence file on startup were not being added to + the client hash, causing subtle problems when the client reconnected, + including ACLs failing. This has been fixed. +- Add note to mosquitto-tls man page stating that certificates need to be + unique. Closes bug #1221285. +- Fix incorrect retained message delivery when using wildcard subs in some + circumstances. Fixes bug #1226040. + +Client library: +- Fix support for Python 2.6, 3.0, 3.1. +- Fix TLS subjectAltName verification and segfaults. +- Handle EAGAIN in Python on Windows. Closes bug #1220004. +- Fix compilation when using WITH_TLS=no. +- Don't fail reconnecting in Python when broker is temporarily unavailable. + +1.2 - 20130708 +============== + +Broker: +- Replace O(n) username lookup on CONNECT with a roughly O(1) hashtable version. +- It is now possible to disable $SYS at compile time. +- Add dropped publish messages to load tree in $SYS. Closes bug #1183318. +- Add support for logging SUBSCRIBE/UNSUBSCRIBE events. +- Add "log_dest file" logging support. +- Auth plugin ACL check function now passes the client id as well as username + and password. +- The queue_qos0_messages option wasn't working correctly, this has now been + fixed. Closes bug #1125200. +- Don't drop all messages for disconnected durable clients when + max_queued_messages=0. +- Add support for "log_type all". +- Add support for "-v" option on the command line to provide the equivalent of + "log_type all" without needing a config file. +- Add the "upgrade_outgoing_qos" option, a non-standard feature. +- Persistence data is now written to a temporary file which is atomically + renamed on completion, so a crash during writing will not produce a corrupt + file. +- mosquitto.conf is now installed as mosquitto.conf.example +- Configuration file errors are now reported with filename and line number. +- The broker now uses a monotonic clock if available, to avoid changes in time + causing client disconnections or message retries. +- Clean session and keepalive status are now display the log when a client + connects. +- Add support for TLSv1.2 and TLSv1.1. +- Clients that connect with zero length will topics are now rejected. +- Add the ability to set a maximum allowed PUBLISH payload size. +- Fix an ACL with topic "#" incorrectly granting access to $SYS. +- Fix retained messages incorrectly being set on wildcard topics, leading to + duplicate retained messages being sent on subscription. Closes bug #1116233. +- Don't discard listener values when no "port" option given. Closes bug + #1131406. +- Client password check was always failing when security was being reapplied + after a config reload. This meant that all clients were being disconnected. + This has been fixed. +- Fix build when WITH_TLS=no. Closes bug #1174971. +- Fix single outgoing packets not being sent in a timely fashion if they were + not sent in one call to write(). Closes bug #1176796. +- Fix remapping of messages for clients connected to a listener with + mount_point set. Closes bug #1180765. +- Fix duplicate retained messages being sent for some wildcard patterns. +- If a client connects with a will topic to which they do not have write + access, they are now disconnected with CONNACK "not authorised". +- Fix retained messages on topic foo being incorrectly delivered to + subscriptions of /# +- Fix handling of SSL errors on SSL_accept(). +- Fix handling of QoS 2 messages on client reconnect. +- Drop privileges now sets supplementary groups correctly. +- Fix load reporting interval (is now 60s). +- Be strict with malformed PUBLISH packets - clients are now disconnected + rather than the packet discarded. This goes inline with future OASIS spec + changes and makes other changes more straightforward. +- Process incoming messages denied by ACL properly so that clients don't keep + resending them. + +- Add support for round_robin bridge option. +- Add bridge support for verifying remote server certificate subject against + the remote hostname. +- Fix problem with out of order calls to free() when restarting a lazy bridge. +- The broker now attempts to resolve bind_address and bridge addresses + immediately when parsing the config file in order to detect invalid hosts. +- Bridges now set their notification state before attempting to connect, so if + they fail to connect the state can still be seen. +- Fix bridge notification payload length - no need to send a null byte. + +- mosquitto_passwd utility now reports errors more clearly. +- Fix "mosquitto_passwd -U". + +Client library: +- Add support for TLSv1.2 and TLSv1.1, except for on the Python module. +- Add support for verifying remote server certificate subject against the + remote hostname. +- Add mosquitto_reconnect_async() support and make asynchronous connections + truely asynchronous rather than simply deferred. DNS lookups are still + blocking, so asynchronous connections require an IP address instead of + hostname. +- Allow control of reconnection timeouts in mosquitto_loop_forever() and after + mosquitto_loop_start() by using mosquitto_reconnect_delay_set(). +- Fix building on Android NDK. +- Re-raise unhandled errors in Python so as not to provide confusing error + messages later on. +- Python module supports IPv6 connections. +- mosquitto_sub_topic_tokenise() was behaving incorrectly if the last topic + hierarchy had only a single character. This has been fixed. Closes bug + #1163348. +- Fix possible crash after disconnects when using the threaded interface with + TLS. +- Allow build/install without Python. Closes bug #1174972. +- Add support for binding connection to a local interface. +- Implement maximum inflight messages handling. +- Fix Python client not handling will_payload==None. +- Fix potential memory leak when setting username/password. +- Fix handling of QoS 2 messages on reconnect. +- Improve handling of mosquitto_disconnect() with threaded mode. + + +Clients: +- Add support for TLSv1.2 and TLSv1.1. +- Sub client can now suppress printing of messages with the retain bit set. +- Add support for binding connection to a local interface. +- Implement maximum inflight messages handling for the pub client. + +1.1.3 - 20130211 +================ + +Broker: +- mosquitto_passwd utility now uses tmpfile() to generate its temporary data + storage file. It also creates a backup file that can be used to recover data + if an errors occur. + +Other: +- Build script fixes to help packaging on Debian. + +1.1.2 - 20130130 +================ + +Client library: +- Fix tls_cert_reqs not being set to SSL_VERIFY_PEER by default. This meant + that clients were not verifying the server certificate when connecting over + TLS. This affects the C, C++ and Python libraries. + +1.1.1 - 20130116 +================ + +Broker: +- Fix crash on reload if using acl patterns. + +Client library: +- Fix static C++ functions not being exported on Windows. Fixes bug #1098256. + +1.1 - 20121219 +============== + +Broker: +- Add $SYS/broker/messages/dropped +- Add $SYS/broker/clients/expired +- Replace $SYS/broker/+/per second/+ with moving average versions published at + $SYS/broker/load/# +- Add $SYS/broker/load/sockets/+ and $SYS/broker/load/connections/+ +- Documentation on password file format has been fixed. +- Disable SSL compression. This reduces memory usage significantly and removes + the possibility of CRIME type attacks. +- Enable SSL_MODE_RELEASE_BUFFERS mode to reduce SSL memory usage further. +- Add allow_duplicate_messages option. +- ACL files can now have comment lines with # as the first character. +- Display message on startup about which config is being loaded. +- Fix max_inflight_messages and max_queued_messages not being applied. +- Fix documentation error in mosquitto.conf. +- Ensure that QoS 2 queued messages are sent out in a timely manner. +- Local bridges now act on clean_session correctly. +- Local bridges with clean_session==false now remove unused subscriptions on + broker restart. +- The $SYS/broker/heap/# messages now no longer include "bytes" as part of the + string for ease of use. + +Client library: +- Free memory used by OpenSSL in mosquitto_lib_cleanup() where possible. +- Change WebSocket subprotocol name to mqttv3.1 to make future changes easier + and for compatibility with other implementations. +- mosquitto_loop_read() and mosquitto_loop_write() now handle errors + themselves rather than having mosquitto_loop() handle their errors. This + makes using them in a separate event loop more straightforward. +- Add mosquitto_loop_forever() / loop_forever() function call to make simple + clients easier. +- Disable SSL compression. This reduces memory usage significantly and removes + the possibility of CRIME type attacks. +- Enable SSL_MODE_RELEASE_BUFFERS mode to reduce SSL memory usage further. +- mosquitto_tls_set() will now return an error or raise an exception + immediately if the CA certificate or client certificate/key cannot be + accessed. +- Fix potential memory leaks on connection failures. +- Don't produce return error from mosquitto_loop() if a system call is + interrupted. This prevents disconnects/reconnects in threaded mode and + simplifies non-threaded client handling. +- Ignore SIGPIPE to prevent unnecessary client quits in threaded mode. +- Fix document error for mosquitto_message_retry_set(). +- Fix mosquitto_topic_matches_sub() for subscriptions with + as the final + character. Fixes bug #1085797. +- Rename all "obj" parameters to "userdata" for consistency with other + libraries. +- Reset errno before network read/write to ensure EAGAIN isn't mistakenly + returned. +- The message queue length is now tracked and used to determine the maximum + number of packets to process at once. This removes the need for the + max_packets parameter which is now unused. +- Fix incorrect error value in Python error_string() function. Fixes bug + #1086777. +- Reset last message in/out timer in Python module when we send a PINGREQ. + Fixes too-early disconnects. + +Clients: +- Clients now display their own version number and library version number in + their help messages. +- Fix "mosquitto_pub -l -q 2" disconnecting before all messages were + transmitted. +- Fix potential out-of-bounds array access with client ids. Fixes bug + #1083182. + +Other: +- mosquitto_passwd can now convert password files with plain text files to + hashed versions. + +1.0.5 - 20121103 +================ + +Broker: +- Fix crash when the broker has use_identity_as_username set to true but a + client connects without a certificate. +- mosquitto_passwd should only be installed if WITH_TLS=yes. + +Library: +- Use symbolic errno values rather than numbers in Python module to avoid + cross platform issues (incorrect errno on Mac OS). + +Other: +- Build script fixes for FreeBSD. + +1.0.4 - 20121017 +================ + +Broker: +- Deal with poll() POLLIN/POLLOUT before POLL[RD]HUP to correctly handle the + case where a client sends data and immediately closes its socket. + +Library: +- Fix memory leak with messages of QoS=2. Fixes bug #1064981. +- Fix potential thread synchronisation problem with outgoing packets in the + Python module. Fixes bug #1064977. + +Clients: +- Fix "mosquitto_sub -l" incorrectly only sending one message per second. + +1.0.3 - 20120927 +================ + +Broker: +- Fix loading of psk files. +- Don't return an error when reloading config if an ACL file isn't defined. + This was preventing psk files being reloaded. +- Clarify meaning of $SYS/broker/clients/total in mosquitto(8) man page. +- Clarify meaning of $SYS/broker/messages/stored in mosquitto(8) man page. +- Fix non-retained message delivery when subscribing to #. +- Fix retained message delivery for subs to foo/# with retained messages at + foo. +- Include the filename in password/acl file loading errors. + +Library: +- Fix possible AttributeError when self._sock == None in Python module. +- Fix reconnecting after a timeout in Python module. +- Fix reconnecting when there were outgoing packets in the queue in the Python + module. +- Fix problem with mutex initialisation causing crashes on some Windows + installations. + +1.0.2 - 20120919 +================ + +Broker: +- If the broker was configured for persistence, a durable client had a + subscription to topics in $SYS/# and had messages in its queue when the + broker restarted, then the persistent database would have messages missing + and so the broker would not restart properly. This has been fixed. + +Library: +- Fix threading problem on some systems. + +Tests: +- Close socket after 08-ssl-connect-no-auth-wrong-ca.py test to prevent + subsequent tests having problems. + +Build scripts: +- Install pskfile.example in CMake. Fixes bug #1037504. + +Other: +- Fix db_dump parameter printing message store and sub chunks. + +1.0.1 - 20120815 +================ + +Broker: +- Fix default log_dest when running as a Windows service. + +Client library: +- Fix incorrect parameters in Python on_log() callback call. Fixes bug + #1036818. + +Clients: +- Clients now don't display TLS/TLS-PSK usage help if they don't support it. + +Build scripts: +- Fix TLS-PSK support in the CMake build files. +- Fix man page installation in the CMake build files. +- Fix SYSCONFDIR in cmake on *nix when installing to /usr. Fixes bug #1036908. + +Documentation: +- Fix mqtt/MQTT capitalisation in man pages. +- Update compiling.txt. +- Fix incorrect callback docs in mosquitto.py. Fixes bug #1036607. +- Fix various doc typos and remove obsolete script. Fixes bug #1037088. + +1.0 - 20120814 +============== + +Broker: + +- Add SSL/TLS support. +- Add TLS-PSK support, providing a simpler encryption method for constrained + devices. +- Passwords are now salted+hashed if compiled with WITH_TLS (recommended). +- Add mosquitto_passwd for handling password files. +- Add $SYS/broker/publish/messages/{sent|received} to show the number of + PUBLISH messages sent/received. +- Add $SYS/broker/publish/bytes/{sent|received} to show the number of + PUBLISH bytes sent/received. +- Add reload parameter for security init/cleanup functions. +- Add option for expiring disconnected persistent clients. +- Add option for queueing of QoS 0 messages when persistent clients are + disconnected. +- Enforce client id limits in the broker (only when WITH_STRICT_PROTOCOL is + defined). +- Fix reloading of log configuration. +- Add support for try_private config option for bridge connections. +- Add support for autosave_on_changes config option. +- Add support for include_dir config option. +- Add support for topic remapping. +- Usernames were being lost when a non clean-session client reconnected, + potentially causing problems with ACLs. This has been fixed. +- Significant improvement to memory handling on Windows. +- Bridges with outgoing topics will now set the retain flag correctly so that + messages will be retained on the remote broker. +- Incoming bridge connections are now detected by checking if bit 8 of the + protocol version number is set. This requires support from the remote broker. +- Add support for notification_topic option. +- Add $SYS/broker/subscriptions/count and $SYS/broker/retained messages/count. +- Add restart_timeout to control the amount of time an automatic bridge will + wait before reconnecting. +- Overlapping subscriptions are now handled properly. Fixes bug #928538. +- Fix reloading of persistence_file and persistence_location. +- Fix broker crash on incorrect protocol number. +- Fix missing COMPAT_ECONNRESET define on Windows. +- Clients that had disconnected were not always being detected immediately on + Linux. This has been fixed. +- Don't save $SYS messages to the on-disk persistent db. All $SYS messages + should be reconstructed on a restart. This means bridge connection + notifications will now be correct on a restart. +- Fix reloading of bridge clients from the persistent db. This means that + outgoing bridged topics should always work. +- Local bridges are now no longer restricted by local ACLs. +- Discard publish messages with zero length topics. +- Drop to "mosquitto" user even if no config file specified. +- Don't incorrectly allow topic access if ACL patterns but no normal ACL rules + are defined. + +Client library: + +- Add SSL/TLS support. +- Add TLS-PSK support, providing a simpler encryption method for constrained + devices. +- Add javascript/websockets client library. +- Add "struct mosquitto *mosq" parameter for all callbacks in the client + library. This is a binary incompatible change so the soversion of the + libraries has been incremented. The new parameter should make it easier to + use callbacks in practice. +- Add mosquitto_want_write() for use when using own select() loop with + mosquitto_socket(). +- Add mosquitto_connect_async() to provide a non-blocking connect client call. +- Add mosquitto_user_data_set() to allow user data pointer to be updated. +- Add "int rc" parameter to disconnect callback to indicate whether disconnect + was unexpected or the result of calling mosquitto_disconnect(). +- Add mosquitto_strerror() for obtaining a string description of error numbers. +- Add mosquitto_connack_string() for obtaining a string description of MQTT + connection results. +- Add mosquitto_will_clear() and change mosquitto_will_set() to only set the + will. +- Add mosquitto_sub_topic_tokenise() and mosquitto_sub_topic_tokens_free() + utility functions to tokenise a subscription/topic string into a string + array. +- Add mosquitto_topic_matches_sub() to check whether a topic matches a + subscription. +- Replaced mosquitto_log_init() with mosquitto_log_callback_set() to allow + clients to decide what to do with log messages. +- Client will now disconnect itself from the broker if it doesn't receive a + PINGRESP in the keepalive period after sending a PINGREQ. +- Client will now send a PINGREQ if it has not received a message from the + broker in keepalive seconds. +- mosquitto_new() will now generate a random client id if the id parameter is + NULL. +- Added max_packets to mosquitto_loop(), mosquitto_loop_read() and + mosquitto_loop_write() to control the maximum number of packets that are + handled per call. +- Payload parameters are now void * instead of uint8_t *. +- The clean_session parameter has been moved from mosquitto_connect() to + mosquitto_new() because it is a client parameter rather than a connection + parameter. +- Functions now use int instead of uint*_t where possible. +- mosquitto_new() now sets errno to indicate failure type. +- Return MOSQ_ERR_INVAL on zero length topic. +- Fix automatic client id generation on Windows. +- mosquitto_loop_misq() can now return MOSQ_ERR_NO_CONN. +- Compile static library as well as dynamic library with default makefiles. +- Rename C++ namespace from mosquittopp to mosqpp to remove ambiguity. +- C++ lib_init(), lib_version() and lib_cleanup() are now in the mosqpp + namespace directly, not mosquittopp class members. +- The Python library is now written in pure Python and so no longer depends on + libmosquitto. +- The Python library includes SSL/TLS support. +- The Python library should now be compatible with Python 3. + +Other: + +- Fix db_dump reading of retained messages. +- Add example of logging all messages to mysql. +- Add C++ client example. +- Fix potential buffer overflow in pub/sub clients. +- Add "make binary" target that doesn't make documents. +- Add "--help" arguments to pub/sub clients. +- Fix building on Solaris. + +0.15 - 20120205 +=============== + +- Add support for $SYS/broker/clients/maximum and $SYS/broker/clients/active + topics. +- Add support for $SYS messages/byte per second received/sent topics. +- Updated mosquitto man page - $SYS hierarchy and signal support were out of + date. +- Auto generated pub/sub client ids now include the hostname. +- Tool for dumping persistent DB contents is available in src/db_dump. It isn't + installed by default. +- Enforce topic length checks in client library. +- Implement "once" and "lazy" bridge start types. +- Add new return type MOSQ_ERR_ERRNO to indicate that the errno variable should + be checked for the real error code. +- Add support for connection_messages config option. +- mosquitto_sub will now refuse to run if the -c option (disable clean session) + is given and no client id is provided. +- mosquitto_pub now gives more useful error messages on invalid input or other + error conditions. +- Fix Python will_set() true/True typo. +- Fix messages to topic "a/b" incorrectly matching on a subscription "a" if + another subscription "a/#" exists. + +0.14.4 - 20120106 +================= + +- Fix local bridge notification messages. +- Fix return values for more internal library calls. +- Fix incorrect out of memory checks in library and broker. +- Never time out local bridge connections. + +0.14.3 - 20111210 +================= + +- Fix potential crash when client connects with an invalid CONNECT packet. +- Fix incorrect invalid socket comparison on Windows. +- Server shouldn't crash when a message is published to foo/ when a + subscription to foo/# exists (bug #901697). +- SO_REUSEADDR doesn't work the same on Windows, so don't use it. +- Cygwin builds now support Windows service features. +- Fix $SYS/broker/bytes/sent reporting. + +0.14.2 - 20111123 +================= + +- Add uninstall target for libs. +- Don't try to write packet whilst in a callback. + +0.14.1 - 20111117 +================= + +- Fix Python sytax errors (bug #891673). + +0.14 - 20111116 +=============== + +- Add support for matching ACLs based on client id and username. +- Add a Windows installer file (NSIS based). +- Add native support for running the broker as a Windows service. This is the + default when installed using the new installer. +- Fix client count for listeners. When clients disconnect, decrement the + count. Allow max_connections to work again. +- Attempt to send all packets immediately upon being queued. This will result + in more immediate network communication in many cases. +- Log IP address when reporting CONNACK packets if the client id isn't yet + known. +- Fix payload length calculation in python will_set function. +- Fix Python publish and will_set functions for payload=None. +- Fix keepalive value being lost when reconnecting a client (bug #880863). +- Persistence file writing now uses portable file functions, so the Cygwin + broker build should no longer be necessary. +- Duplicate code between the client and broker side has been reduced. +- Queued messages for clients reconnecting with clean_session=false set were + not being sent until the next message for that client was received. This has + been fixed (bug #890724). +- Fix subscriptions to # incorrectly matching against topics beginning with / + +0.13 - 20110920 +=============== + +- Implement bridge state notification messages. +- Save client last used mid in persistent database (DB version number bumped). +- Expose message id in Python MosquittoMessage. +- It is now possible to set the topic QoS level for bridges. +- Python MosquittoMessage payload parameter is now a Python string, not a + ctypes object which makes it much easier to use. +- Fix queueing of messages for disconnected clients. The max_queued_messages + option is now obeyed. +- C++ library is now in its own namespace, mosquittopp. +- Add support for adding log message timestamps in the broker. +- Fix missing mosquitto_username_pw_set() python binding. +- Fix keepalive timeout for reconnecting non clean-session clients. Prevents + immediate disconnection on reconnection. +- Fix subscription wildcard matching - a subscription of +/+ will now match + against /foo +- Fix subscription wildcard matching - a subscription of foo/# will now match + against foo +- When restoring persistent database, clients should be set to non + clean-session or their subscriptions will be immediately removed. +- Fix SUBACK payload for multiple topic subscriptions. +- Don't send retained messages when a client subscribes to a topic it is + already subscribed to. + +0.12 - 20110725 +=============== + +- Reload (most) configuration on SIGHUP. +- Memory tracking is no longer compiled in the client library. +- Add --help option to mosquitto to display usage. +- Add --id-prefix option to clients to allow easier use with brokers that are + using the clientid_prefix option. +- Fix compilation on QNX. +- Add -P as a synonym argument for --pw in the clients. +- Fix python MosquittoMessage payload parameter. This is now returned as a + pointer to an array of c_uint8 values so binary data is handled correctly. + If a string is needed, use msg.payload_str +- Fix memory leaks on client authentication. +- If password_file is not defined then clients can now connect even if they + use a username/password. +- Add mosquitto_reconnect() to the client library. +- Add option for compiling with liberal protocol compliance support (enabled + by default). +- Fix problems with clients reconnecting and old messages remaining in the + message store. +- Display both ip and client id in the log message when a client connects. + Change the socket connection message to make it more obvious that it is just + a socket connection being made (bug #801135). +- Fix retained message delivery where a subscription contains a +. +- Be more lenient when reloading persistent database to reduce errors with + empty retained messages. + +0.11.3 - 20110707 +================= + +- Don't complain and quit if persistence_file option is given (bug #802423). +- Initialise listeners correctly when clients with duplicate client ids + connect. Bug #801678. +- Memory tracking is now disabled for Symbian builds due to lack of malloc.h. +- Fix memory tracking compilation for kFreeBSD. +- Python callbacks can now be used with class member functions. +- Fix persistent database writing of client message chunks which caused + errors when restoring (bug #798164). + +0.11.2 - 20110626 +================= + +- Don't free contexts in mqtt3_context_disconnect() (bug #799688 / #801678). +- Only free will if present when freeing a client context. + +0.11.1 - 20110620 +================= + +- Fix buffer overrun when checking for + and # in topics (bug #799688). +- Pub client now quits if publish fails. + +0.11 - 20110619 +=============== + +- Removed all old sqlite code. +- Remove client id limit in clients. +- Implemented $SYS/broker/heap/maximum size +- Implemented $SYS/broker/clients/inactive to show the number of disconnected + non-clean session clients. +- $SYS/broker/heap/current size and maximum size messages now include "bytes" + to match rsmb message format. +- Implemented the retained_persistence config file option - a synonym of the + "persistence" option. +- Added security_external.c to broker source to make it easier for third + parties to add support for their existing username/password and ACL database + for security checks. See external_security_checks.txt. +- $SYS messages are now only republished when their value changes. +- Windows native broker now responds to command line arguments. +- Simplify client disconnecting so wills gets sent in all cases (bug #792468). +- Clients now have a --quiet option. +- The on_disconnect() callback will always be called now, even if the client + has disconnected unexpectedly. +- Always close persistent DB file after restoring. +- Return error code when exiting the clients. +- mosquitto_publish() now returns MOSQ_ERR_INVAL if the topic contains + or # +- mosquitto now silently rejects published messages with + or # in the topic. +- max_connections is now a per-listener setting instead of global. +- Connection count is now reduced when clients disconnect (bug #797983). + +0.10.2 - 20110106 +================= + +- Don't abort when connecting if the first connection fails. This is important + on e.g. Windows 7, where IPV6 is offered as the first choice but may not be + available. +- Deal with long logging messages properly (bug #785882). +- Fix library compilation on Symbian - no pselect() available. +- Don't stop processing subscriptions on received messages after a + subscription with # matches. (bug #791206). + +0.10.1 - 20110512 +================= + +- Fix Windows compilation. +- Fix mosquitto.py on Windows - call lib init/cleanup. +- Don't abort when connecting if given an unknown address type (assuming an + IPv4 or IPv6 address is given). + +0.10 - 20110429 +=============== + +- Implement support for the password_file option and accompanying + authentication requirements in the broker. +- Implement topic Access Control Lists. +- mosquitto_will_set() and mosquitto_publish() now return + MOSQ_ERR_PAYLOAD_SIZE if the payload is too large (>268,435,455 bytes). +- Bridge support can now be disabled at compile time. +- Group together network writes for outgoing packets - don't send single byte + writes! +- Add support for clientid_prefixes variable. +- Add support for the clientid config variable for controlling bridge client + ids. +- Remove 32-bit database ID support because htobe64() no longer used. +- Multiple client subscriptions to the same topic result in only a single + subscription. Bug #744077. + +0.9.3 - 20110310 +================ + +- Set retained message status for QoS 2 messages (bug #726535). +- Only abort with an error when opening listening sockets if no address family + is available, rather than aborting when any address family is not available. +- Don't clean queued messages when a non clean session client reconnects. +- Make mosquitto.py compatible with Python <2.6. +- Fix mosquitto.h header includes for Windows. + +0.9.2 - 20110208 +================ + +- Only send a single DISCONNECT command when using -l in the pub client. +- Set QoS=1 on PUBREL commands to meet protocol spec. +- Don't leak sockets on connection failure in the library. +- Install man pages when building under cmake. +- Fix crash bug on malformed CONNECT message. +- Clients are now rejected if their socket peer name cannot be obtained on + connection. +- Fix a number of potential problems caused when a client with a duplicate id + connects. +- Install mosquitto.conf under cmake. + +0.9.1 - 20101203 +================ + +- Add missing code for parsing the "bind_address" configuration option. +- Fix missing include when compiling with tcp-wrappers support. +- Add linker version script for C library to control exported functions. + +0.9 - 20101114 +============== + +- Client and message data is now stored in memory with custom routines rather + than a sqlite database. This removes the dependencies on sqlite, pcre and + sqlite3-pcre. It also means that the persistent database format has had to + be reimplemented in a custom format. Optional support for importing old + sqlite databases is provided. +- Added IPv6 support for mosquitto and the clients. +- Provide username and password support for the clients and client libraries. + This is part of the new MQTT v3.1 spec. +- The broker supports the username and password connection flags, but will not + do anything with the username and password. +- Python callback functions now optionally take an extra argument which will + return the user object passed to the Mosquitto() constructor, or the calling + python object itself if nothing was given to Mosquitto(). +- Remove the mosquitto command line option "-i interface". +- Remove the mosquitto.conf "interface" variable. +- Add support for the listener config variable (replaces the interface + variable) +- Add support for the bind_address config variable. +- Change the port config variable behaviour to match that of rsmb (applies to + the default listener only, can be given just once). +- Fix QoS 2 protocol compliance - stop sending duplicate messages and handle + timeouts correctly. Fixes bug #598290. +- Set retain flag correctly for outgoing messages. It should only be set for + messages sent in response to a subscribe command (ie. stale data). +- Fix bug in returning correct CONNACK result to on_connect client callback. +- Don't send client will if it is disconnected for exceeding its keepalive + timer. +- Fix client library unsubscribe function incorrectly sending a SUBSCRIBE + command when it should be UNSUBSCRIBE. +- Fix max_inflight_messages and max_queued_messages operation. These + parameters now apply only to QoS 1 and 2 messages and are used regardless of + the client connection state. +- mosquitto.conf now installed to /etc/mosquitto/mosquitto.conf instead of + /etc/mosquitto.conf. The /etc/mosquitto/ directory will be used for password + and access control files in the future. +- Give the compile time option of using 32-bit integers for the database IDs + instead of 64-bit integers. This is useful where htobe64()/be64toh() are not + available or for embedded systems for example. +- The DUP bit is now set correctly when resending PUBREL messages. +- A port to Windows native has been partially completed. This currently drops a + number of features, including the ability to change configuration parameters + and persistent storage. + +0.8.3 - 20101004 +================ + +- Fix QoS 2 protocol compliance - stop sending duplicate messages and handle + timeouts correctly. Fixes bug #598290. (backported from future 0.9 code) + +0.8.2 - 20100815 +================ + +- Fix default loop() timeout value in mosquitto.py. Previous value was 0, + causing high cpu load. +- Fix message handling problem in client library when more than one message was + in the client queue. +- Fix the logic used to determine whether a QoS>0 message needs to be retried. +- Fix the Python sub.py example so that it quits on error. + +0.8.1 - 20100812 +================ + +- Improve python interface +- Fix incorrect return value from message delete function +- Use logging function to print error messages in clients. +- Fix python installation script DESTDIR. +- Fix library destination path for 64-bit machines. + +0.8 - 20100807 +============== + +- Topics starting with a / are treated as distinct to those not starting with + a /. For example, /topic/path is different to topic/path. This matches the + behaviour of rsmb. +- Correctly calculate the will QoS on a new client connection (bug #597451). +- Add "addresses" configuration file variable as an alias of "address", for + better rsmb compatibility. +- Bridge clean_session setting is now false, to give more sensible behaviour + and be more compatible with rsmb. +- Add cleansession variable for configuring bridges. +- Add keepalive_interval variable for bridges. +- Remove default topic subscription for mosquitto_sub because the old + behaviour was too confusing. +- Added a C client library, which the pub and sub clients now use. +- Added a C++ client library (bound to the C library). +- Added a Python client library (bound to the C library). +- Added CMake build scripts to allow the library and clients (not the broker) + to be compiled natively on Windows. + +0.7 - 20100615 +============== + +- mosquitto_pub can now send null (zero length) messages. +- Don't store QoS=0 messages for disconnected clients with subscriptions of + QoS>0. +- accept() all available sockets when new clients are connecting, rather than + just one. +- Add option to print debug messages in pub and sub clients. +- hg revision is now exported via $SYS/broker/changeset +- Send Will when client exceeds keepalive timer and is disconnected. +- Check to see if a client has a will before sending it. +- Correctly deal with clients connecting with the same id multiple times. +- Add compile time option to disable heap memory tracking. +- Use poll() instead of select() to allow >1024 clients. +- Implement max_connections. +- Run VACUUM on in-memory database on receiving SIGUSR2. +- Fix bridge keepalive timeouts and reconnects. +- Don't attempt to drop root privileges when running on Windows as this isn't + well supported (bug #586231). + +0.6.1 - 20100506 +================ + +- Fix DB auto upgrade for messages table. + +0.6 - 20100505 +============== + +- Basic support for connecting multiple MQTT brokers together (bridging). +- mosquitto_sub can now subscribe to multiple topics (limited to a global QoS). +- mosquitto_pub can now send a file as a message. +- mosquitto_pub can now read all of stdin and send it as a message. +- mosquitto_pub can now read stdin and send each line as a message. +- mosquitto will now correctly run VACUUM on the persistent database on exit. +- Implement a more efficient database design, so that only one copy of each + message is held in the database, rather than one per subscribed client. +- Add the store_cleanup_interval config option for dealing with the internal + message store. +- Add support for disabling "clean session" for the sub client. +- Add support for automatic upgrading of the mosquitto DB from v1 to v2. +- Add persistence_file config option to allow changing the filename of the + persistence database. This allows multiple mosquitto DBs to be stored in the + same location whilst keeping persistence_location compatible with rsmb. +- Don't store QoS=0 messages for disconnected clients. Fixes bug #572608. This + wasn't correctly fixed in version 0.5. +- Don't disconnect clients if they send a PUBLISH with zero length payload + (bug #573610). +- If a retained message is received with a zero length payload, the retained + message for that topic is deleted. +- Send through zero length messages. +- Produce a warning on unsupported rsmb options instead of quitting. +- Describe clean session flag in the mqtt man page. +- Implement the max_inflight_messages and max_queued_messages features in the + broker. + +0.5.4 - 20100311 +================ + +- Fix memory allocation in mqtt3_fix_sub_topic() (bug #531861). +- Remove accidental limit of 100 client connections. +- Fix mosquitto_pub handling of messages with QoS>0 (bug #537061). + +0.5.3 - 20100303 +================ + +- Will messages are now only sent when a client disconnects unexpectedly. +- Fix all incoming topics/subscriptions that start with a / or contain + multiple / in a row (//). +- Do actually disconnect client when it sends an empty subscription/topic string. +- Add missing $SYS/broker/clients/total to man page. + +0.5.2 - 20100302 +================ + +- Always update last backup time, so that the backup doesn't run every time + through the main loop once autosave_interval has been reached. +- Report $SYS/broker/uptime in the same format as rsmb. +- Make mandatory options obvious in usage output and man page of mosquitto_pub. + Fixes bug #529990. +- Treat subscriptions with a trailing slash correctly. This should fix bugs + #530369 and #530099. + +0.5.1 - 20100227 +================ + +- Must daemonise before pid file is written. + +0.5 - 20100227 +============== + +- No longer store QoS=0 messages for disconnected clients that do not have + clean start set. +- Rename msg_timeout option to retry_interval for better rsmb compatibility. +- Change persistence behaviour. The database is now stored in memory even if + persistence is enabled. It is written to disk when mosquitto exits and also at + periodic intervals as defined by the new autosave_interval option. +- The writing of the persistence database may be forced by sending mosquitto + the SIGUSR1 signal. +- Clients that do not send CONNECT as their first command are now + disconnected. +- Boolean configuration values may now be specified with true/false as well as + 1/0. +- Log message on CONNECT with invalid protocol or protocol version. +- Default sqlite3-pcre path on Linux is now /usr/lib/sqlite3/pcre.so to match + future sqlite3-pcre packages. +- Add mosquitto_sub and mosquitto_pub, simple clients for subscribe/publish. +- Add man pages for clients. +- Add general man page on mqtt. +- Root privileges are now dropped only after attempting to write a pid file + (if configured). This means that the pid file can be written to /var/run/ + directly and should fix bug #523183. + +0.4.2 - 20100203 +================ + +- Fix segfault on client connect with invalid protocol name/version. + +0.4.1 - 20100112 +=============== + +- Fix regex used for finding retained messages to send on new subscription. + +0.4 - 20100105 +============== + +- Added support for wildcard subscriptions using + and #. +- All network operations are now non-blocking and can cope with partial + packets, meaning that networking should be a lot more reliable. +- Total messsages/bytes sent/received are now available in $SYS. +- Improved logging information - use client ip address and id instead of + socket number. +- Broker build timestamp is available in $SYS. +- Keepalive==0 is now correctly treated as "never disconnect". +- Fixed manpage installation. +- Fixed incorrect $SYS hierarchy locations in documentation and code. +- Debug type log messages are no longer sent to "topics". +- Default logging destination no longer includes "topics" to prevent possible + error logging to the db before it is initialised. +- Periodic $SYS messages can now be disabled. +- stdout and stderr are flushed when logging to them to give more timely + updates. +- dup is now set correctly when resending messages. +- Database format bumped due to topic column naming fix. + +0.3 - 20091217 +============== + +- The port option in the configuration file and --port command line argument + may now be given any number of times to make mosquitto listen on multiple + sockets. +- Add new config file and command line option "interface" to specify an + interface to listen on, rather than all interfaces. +- Added host access control through tcp-wrappers support. +- Set SO_REUSEADDR on the listening socket so restart is much quicker. +- Added support for tracking current heap memory usage - this is published on + the topic "$SYS/broker/heap/current size" +- Added code for logging to stderr, stdout, syslog and topics. +- Added logging to numerous places - still plenty of scope for more. + +0.2 - 20091204 +============== + +- Replaced the command line option --foreground with --daemon, swapping the + default behaviour. +- Added the command line option --config-file, to specify a config file to + load. If this is not given, no config file is load and the default options + are used. +- Added the command line option --port for specifying the port to listen on. + This overrides values in the config file. +- Don't use persistence by default. +- Default behaviour is now more sane when run by a normal user with no command + line options (combination of above changes). +- Added option user to config file, defaulting to a value of mosquitto. If + this value isn't blank and mosquitto is started by root, then it will drop + privileges by changing to the user and its primary group. This replaces the + current behaviour of refusing to start if run by root. +- Fix non-persistent mode, which would never work in the previous release. +- Added information on default values of msg_timeout and sys_interval to the + mosquitto.conf man page. (closes bug #492045). diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000000..56bef4e153 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,2 @@ +This project is dual licensed under the Eclipse Public License 1.0 and the +Eclipse Distribution License 1.0 as described in the epl-v10 and edl-v10 files. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..f36667ea6a --- /dev/null +++ b/Makefile @@ -0,0 +1,70 @@ +include config.mk + +DIRS=lib client src +DOCDIRS=man +DISTDIRS=man + +.PHONY : all mosquitto docs binary clean reallyclean test install uninstall dist sign copy + +all : mosquitto docs + +docs : + set -e; for d in ${DOCDIRS}; do $(MAKE) -C $${d}; done + +binary : mosquitto + +mosquitto : + set -e; for d in ${DIRS}; do $(MAKE) -C $${d}; done + +clean : + set -e; for d in ${DIRS}; do $(MAKE) -C $${d} clean; done + set -e; for d in ${DOCDIRS}; do $(MAKE) -C $${d} clean; done + $(MAKE) -C test clean + +reallyclean : + set -e; for d in ${DIRS}; do $(MAKE) -C $${d} reallyclean; done + set -e; for d in ${DOCDIRS}; do $(MAKE) -C $${d} reallyclean; done + $(MAKE) -C test reallyclean + -rm -f *.orig + +test : mosquitto + $(MAKE) -C test test + +install : mosquitto + set -e; for d in ${DIRS}; do $(MAKE) -C $${d} install; done + set -e; for d in ${DOCDIRS}; do $(MAKE) -C $${d} install; done + $(INSTALL) -d ${DESTDIR}/etc/mosquitto + $(INSTALL) -m 644 mosquitto.conf ${DESTDIR}/etc/mosquitto/mosquitto.conf.example + $(INSTALL) -m 644 aclfile.example ${DESTDIR}/etc/mosquitto/aclfile.example + $(INSTALL) -m 644 pwfile.example ${DESTDIR}/etc/mosquitto/pwfile.example + $(INSTALL) -m 644 pskfile.example ${DESTDIR}/etc/mosquitto/pskfile.example + +uninstall : + set -e; for d in ${DIRS}; do $(MAKE) -C $${d} uninstall; done + rm -f ${DESTDIR}/etc/mosquitto/mosquitto.conf + rm -f ${DESTDIR}/etc/mosquitto/aclfile.example + rm -f ${DESTDIR}/etc/mosquitto/pwfile.example + rm -f ${DESTDIR}/etc/mosquitto/pskfile.example + +dist : reallyclean + set -e; for d in ${DISTDIRS}; do $(MAKE) -C $${d} dist; done + + echo $$(hg log -r . --template "{node}") > changeset + mkdir -p dist/mosquitto-${VERSION} + cp -r client changeset examples installer lib logo man misc security service src test ChangeLog.txt CMakeLists.txt LICENSE.txt LICENSE-3rd-party.txt Makefile compiling.txt config.h config.mk readme.txt readme-windows.txt mosquitto.conf aclfile.example pskfile.example pwfile.example dist/mosquitto-${VERSION}/ + cd dist; tar -zcf mosquitto-${VERSION}.tar.gz mosquitto-${VERSION}/ + set -e; for m in man/*.xml; \ + do \ + hfile=$$(echo $${m} | sed -e 's#man/\(.*\)\.xml#\1#' | sed -e 's/\./-/g'); \ + $(XSLTPROC) $(DB_HTML_XSL) $${m} > dist/$${hfile}.html; \ + done + + +sign : dist + cd dist; gpg --detach-sign -a mosquitto-${VERSION}.tar.gz + +copy : sign + cd dist; scp mosquitto-${VERSION}.tar.gz mosquitto-${VERSION}.tar.gz.asc mosquitto:site/mosquitto.org/files/source/ + cd dist; scp *.html mosquitto:site/mosquitto.org/man/ + scp ChangeLog.txt mosquitto:site/mosquitto.org/ + diff --git a/THANKS.txt b/THANKS.txt new file mode 100644 index 0000000000..bc48605e12 --- /dev/null +++ b/THANKS.txt @@ -0,0 +1,57 @@ +These people have reported bugs / provided patches / done something else to aid +the mosquitto project. Thanks to you all! + +If you think I've missed you off the list, please rest assured that it wasn't +intentional and get in touch and I'll fix it. + +Adam Rudd +Andrew Elwell +Andy Piper +Andy Stanford-Clark +Bart Van Der Meerssche +Ben Tobin +Brad Stancel +Chris Willing +Craig Hollabaugh +Dan Anderson +Dariusz Suchojad +David Huang +David Monro +Dirk O. Kaar +Dominik Obermaier +Dominik Zajac +Ed Morris +Frank Hansen +Gary Koh +Joan Zapata +Karl Palsson +Larry Lendo +Martin Assarsson +Marty Lee +Matt Daubney +Michael C +Michael Frisch +Michael Rushton +Mike Bush +Neil Bothwick +Nicholas Humfrey +Nicholas O'Leary +Nithin Kumar +Paul Diston +Peter George +Rob Pridham +Robin Gingras +Roland de Boo +Sebastian Kroll +Sharon Ben-Asher +sskaje +Stefan Hudelmaier +Stefano Costa +Stephen Woods +Steven Lougheed +Thomas Hilbig +Tobias Assarsson +Toby Jaffey +Vicente Ruiz +Wayne Ingram +Yuvraaj Kelkar diff --git a/aclfile.example b/aclfile.example new file mode 100644 index 0000000000..5c3f3ca7f4 --- /dev/null +++ b/aclfile.example @@ -0,0 +1,9 @@ +# This affects access control for clients with no username. +topic read $SYS/# + +# This only affects clients with username "roger". +user roger +topic foo/bar + +# This affects all clients. +pattern write $SYS/broker/connection/%c/state diff --git a/changeset b/changeset new file mode 100644 index 0000000000..2743423552 --- /dev/null +++ b/changeset @@ -0,0 +1 @@ +b216c4246a7e9ff2be360ca2a7d102c5ea101f04 diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt new file mode 100644 index 0000000000..ebf2085ebc --- /dev/null +++ b/client/CMakeLists.txt @@ -0,0 +1,12 @@ +include_directories(${mosquitto_SOURCE_DIR}/lib + ${STDBOOL_H_PATH} ${STDINT_H_PATH}) +link_directories(${mosquitto_BINARY_DIR}/lib) + +add_executable(mosquitto_pub pub_client.c) +add_executable(mosquitto_sub sub_client.c) + +target_link_libraries(mosquitto_pub mosquitto) +target_link_libraries(mosquitto_sub mosquitto) + +install(TARGETS mosquitto_pub RUNTIME DESTINATION ${BINDIR} LIBRARY DESTINATION ${LIBDIR}) +install(TARGETS mosquitto_sub RUNTIME DESTINATION ${BINDIR} LIBRARY DESTINATION ${LIBDIR}) diff --git a/client/Makefile b/client/Makefile new file mode 100644 index 0000000000..54a12f09ae --- /dev/null +++ b/client/Makefile @@ -0,0 +1,34 @@ +include ../config.mk + +.PHONY: all install uninstall reallyclean clean + +all : mosquitto_pub mosquitto_sub + +mosquitto_pub : pub_client.o ../lib/libmosquitto.so.${SOVERSION} + ${CC} $< -o $@ ${CLIENT_LDFLAGS} + +mosquitto_sub : sub_client.o ../lib/libmosquitto.so.${SOVERSION} + ${CC} $< -o $@ ${CLIENT_LDFLAGS} + +pub_client.o : pub_client.c ../lib/libmosquitto.so.${SOVERSION} + ${CC} -c $< -o $@ ${CLIENT_CFLAGS} + +sub_client.o : sub_client.c ../lib/libmosquitto.so.${SOVERSION} + ${CC} -c $< -o $@ ${CLIENT_CFLAGS} + +../lib/libmosquitto.so.${SOVERSION} : + $(MAKE) -C ../lib + +install : all + $(INSTALL) -d ${DESTDIR}$(prefix)/bin + $(INSTALL) -s mosquitto_pub ${DESTDIR}${prefix}/bin/mosquitto_pub + $(INSTALL) -s mosquitto_sub ${DESTDIR}${prefix}/bin/mosquitto_sub + +uninstall : + -rm -f ${DESTDIR}${prefix}/bin/mosquitto_pub + -rm -f ${DESTDIR}${prefix}/bin/mosquitto_sub + +reallyclean : clean + +clean : + -rm -f *.o mosquitto_pub mosquitto_sub diff --git a/client/pub_client.c b/client/pub_client.c new file mode 100644 index 0000000000..d08dcb6947 --- /dev/null +++ b/client/pub_client.c @@ -0,0 +1,785 @@ +/* +Copyright (c) 2009-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + + +#include +#include +#include +#include +#include +#ifndef WIN32 +#include +#else +#include +#include +#define snprintf sprintf_s +#endif + +#include + +#define MSGMODE_NONE 0 +#define MSGMODE_CMD 1 +#define MSGMODE_STDIN_LINE 2 +#define MSGMODE_STDIN_FILE 3 +#define MSGMODE_FILE 4 +#define MSGMODE_NULL 5 + +#define STATUS_CONNECTING 0 +#define STATUS_CONNACK_RECVD 1 +#define STATUS_WAITING 2 + +/* Global variables for use in callbacks. See sub_client.c for an example of + * using a struct to hold variables for use in callbacks. */ +static char *topic = NULL; +static char *message = NULL; +static long msglen = 0; +static int qos = 0; +static int retain = 0; +static int mode = MSGMODE_NONE; +static int status = STATUS_CONNECTING; +static int mid_sent = 0; +static int last_mid = -1; +static int last_mid_sent = -1; +static bool connected = true; +static char *username = NULL; +static char *password = NULL; +static bool disconnect_sent = false; +static bool quiet = false; + +void my_connect_callback(struct mosquitto *mosq, void *obj, int result) +{ + int rc = MOSQ_ERR_SUCCESS; + + if(!result){ + switch(mode){ + case MSGMODE_CMD: + case MSGMODE_FILE: + case MSGMODE_STDIN_FILE: + rc = mosquitto_publish(mosq, &mid_sent, topic, msglen, message, qos, retain); + break; + case MSGMODE_NULL: + rc = mosquitto_publish(mosq, &mid_sent, topic, 0, NULL, qos, retain); + break; + case MSGMODE_STDIN_LINE: + status = STATUS_CONNACK_RECVD; + break; + } + if(rc){ + if(!quiet){ + switch(rc){ + case MOSQ_ERR_INVAL: + fprintf(stderr, "Error: Invalid input. Does your topic contain '+' or '#'?\n"); + break; + case MOSQ_ERR_NOMEM: + fprintf(stderr, "Error: Out of memory when trying to publish message.\n"); + break; + case MOSQ_ERR_NO_CONN: + fprintf(stderr, "Error: Client not connected when trying to publish.\n"); + break; + case MOSQ_ERR_PROTOCOL: + fprintf(stderr, "Error: Protocol error when communicating with broker.\n"); + break; + case MOSQ_ERR_PAYLOAD_SIZE: + fprintf(stderr, "Error: Message payload is too large.\n"); + break; + } + } + mosquitto_disconnect(mosq); + } + }else{ + if(result && !quiet){ + fprintf(stderr, "%s\n", mosquitto_connack_string(result)); + } + } +} + +void my_disconnect_callback(struct mosquitto *mosq, void *obj, int rc) +{ + connected = false; +} + +void my_publish_callback(struct mosquitto *mosq, void *obj, int mid) +{ + last_mid_sent = mid; + if(mode == MSGMODE_STDIN_LINE){ + if(mid == last_mid){ + mosquitto_disconnect(mosq); + disconnect_sent = true; + } + }else if(disconnect_sent == false){ + mosquitto_disconnect(mosq); + disconnect_sent = true; + } +} + +void my_log_callback(struct mosquitto *mosq, void *obj, int level, const char *str) +{ + printf("%s\n", str); +} + +int load_stdin(void) +{ + long pos = 0, rlen; + char buf[1024]; + + mode = MSGMODE_STDIN_FILE; + + while(!feof(stdin)){ + rlen = fread(buf, 1, 1024, stdin); + message = realloc(message, pos+rlen); + if(!message){ + if(!quiet) fprintf(stderr, "Error: Out of memory.\n"); + return 1; + } + memcpy(&(message[pos]), buf, rlen); + pos += rlen; + } + msglen = pos; + + if(!msglen){ + if(!quiet) fprintf(stderr, "Error: Zero length input.\n"); + return 1; + } + + return 0; +} + +int load_file(const char *filename) +{ + long pos, rlen; + FILE *fptr = NULL; + + fptr = fopen(filename, "rb"); + if(!fptr){ + if(!quiet) fprintf(stderr, "Error: Unable to open file \"%s\".\n", filename); + return 1; + } + mode = MSGMODE_FILE; + fseek(fptr, 0, SEEK_END); + msglen = ftell(fptr); + if(msglen > 268435455){ + fclose(fptr); + if(!quiet) fprintf(stderr, "Error: File \"%s\" is too large (>268,435,455 bytes).\n", filename); + return 1; + }else if(msglen == 0){ + fclose(fptr); + if(!quiet) fprintf(stderr, "Error: File \"%s\" is empty.\n", filename); + return 1; + }else if(msglen < 0){ + fclose(fptr); + if(!quiet) fprintf(stderr, "Error: Unable to determine size of file \"%s\".\n", filename); + return 1; + } + fseek(fptr, 0, SEEK_SET); + message = malloc(msglen); + if(!message){ + fclose(fptr); + if(!quiet) fprintf(stderr, "Error: Out of memory.\n"); + return 1; + } + pos = 0; + while(pos < msglen){ + rlen = fread(&(message[pos]), sizeof(char), msglen-pos, fptr); + pos += rlen; + } + fclose(fptr); + return 0; +} + +void print_usage(void) +{ + int major, minor, revision; + + mosquitto_lib_version(&major, &minor, &revision); + printf("mosquitto_pub is a simple mqtt client that will publish a message on a single topic and exit.\n"); + printf("mosquitto_pub version %s running on libmosquitto %d.%d.%d.\n\n", VERSION, major, minor, revision); + printf("Usage: mosquitto_pub [-h host] [-p port] [-q qos] [-r] {-f file | -l | -n | -m message} -t topic\n"); + printf(" [-A bind_address] [-S]\n"); + printf(" [-i id] [-I id_prefix]\n"); + printf(" [-d] [--quiet]\n"); + printf(" [-M max_inflight]\n"); + printf(" [-u username [-P password]]\n"); + printf(" [--will-topic [--will-payload payload] [--will-qos qos] [--will-retain]]\n"); +#ifdef WITH_TLS + printf(" [{--cafile file | --capath dir} [--cert file] [--key file]\n"); + printf(" [--ciphers ciphers] [--insecure]]\n"); +#ifdef WITH_TLS_PSK + printf(" [--psk hex-key --psk-identity identity [--ciphers ciphers]]\n"); +#endif +#endif + printf(" mosquitto_pub --help\n\n"); + printf(" -A : bind the outgoing socket to this host/ip address. Use to control which interface\n"); + printf(" the client communicates over.\n"); + printf(" -d : enable debug messages.\n"); + printf(" -f : send the contents of a file as the message.\n"); + printf(" -h : mqtt host to connect to. Defaults to localhost.\n"); + printf(" -i : id to use for this client. Defaults to mosquitto_pub_ appended with the process id.\n"); + printf(" -I : define the client id as id_prefix appended with the process id. Useful for when the\n"); + printf(" broker is using the clientid_prefixes option.\n"); + printf(" -l : read messages from stdin, sending a separate message for each line.\n"); + printf(" -m : message payload to send.\n"); + printf(" -M : the maximum inflight messages for QoS 1/2..\n"); + printf(" -n : send a null (zero length) message.\n"); + printf(" -p : network port to connect to. Defaults to 1883.\n"); + printf(" -q : quality of service level to use for all messages. Defaults to 0.\n"); + printf(" -r : message should be retained.\n"); + printf(" -s : read message from stdin, sending the entire input as a message.\n"); + printf(" -S : use SRV lookups to determine which host to connect to.\n"); + printf(" -t : mqtt topic to publish to.\n"); + printf(" -u : provide a username (requires MQTT 3.1 broker)\n"); + printf(" -P : provide a password (requires MQTT 3.1 broker)\n"); + printf(" --help : display this message.\n"); + printf(" --quiet : don't print error messages.\n"); + printf(" --will-payload : payload for the client Will, which is sent by the broker in case of\n"); + printf(" unexpected disconnection. If not given and will-topic is set, a zero\n"); + printf(" length message will be sent.\n"); + printf(" --will-qos : QoS level for the client Will.\n"); + printf(" --will-retain : if given, make the client Will retained.\n"); + printf(" --will-topic : the topic on which to publish the client Will.\n"); +#ifdef WITH_TLS + printf(" --cafile : path to a file containing trusted CA certificates to enable encrypted\n"); + printf(" communication.\n"); + printf(" --capath : path to a directory containing trusted CA certificates to enable encrypted\n"); + printf(" communication.\n"); + printf(" --cert : client certificate for authentication, if required by server.\n"); + printf(" --key : client private key for authentication, if required by server.\n"); + printf(" --ciphers : openssl compatible list of TLS ciphers to support.\n"); + printf(" --tls-version : TLS protocol version, can be one of tlsv1.2 tlsv1.1 or tlsv1.\n"); + printf(" Defaults to tlsv1.2 if available.\n"); + printf(" --insecure : do not check that the server certificate hostname matches the remote\n"); + printf(" hostname. Using this option means that you cannot be sure that the\n"); + printf(" remote host is the server you wish to connect to and so is insecure.\n"); + printf(" Do not use this option in a production environment.\n"); +#ifdef WITH_TLS_PSK + printf(" --psk : pre-shared-key in hexadecimal (no leading 0x) to enable TLS-PSK mode.\n"); + printf(" --psk-identity : client identity string for TLS-PSK mode.\n"); +#endif +#endif + printf("\nSee http://mosquitto.org/ for more information.\n\n"); +} + +int main(int argc, char *argv[]) +{ + char *id = NULL; + char *id_prefix = NULL; + int i; + char *host = "localhost"; + int port = 1883; + char *bind_address = NULL; + int keepalive = 60; + char buf[1024]; + bool debug = false; + struct mosquitto *mosq = NULL; + int rc; + int rc2; + char hostname[256]; + char err[1024]; + int len; + unsigned int max_inflight = 20; + + char *will_payload = NULL; + long will_payloadlen = 0; + int will_qos = 0; + bool will_retain = false; + char *will_topic = NULL; + + bool insecure = false; + char *cafile = NULL; + char *capath = NULL; + char *certfile = NULL; + char *keyfile = NULL; + char *tls_version = NULL; + + char *psk = NULL; + char *psk_identity = NULL; + + char *ciphers = NULL; + + bool use_srv = false; + + for(i=1; i65535){ + fprintf(stderr, "Error: Invalid port given: %d\n", port); + print_usage(); + return 1; + } + } + i++; + }else if(!strcmp(argv[i], "-A")){ + if(i==argc-1){ + fprintf(stderr, "Error: -A argument given but no address specified.\n\n"); + print_usage(); + return 1; + }else{ + bind_address = argv[i+1]; + } + i++; + }else if(!strcmp(argv[i], "--cafile")){ + if(i==argc-1){ + fprintf(stderr, "Error: --cafile argument given but no file specified.\n\n"); + print_usage(); + return 1; + }else{ + cafile = argv[i+1]; + } + i++; + }else if(!strcmp(argv[i], "--capath")){ + if(i==argc-1){ + fprintf(stderr, "Error: --capath argument given but no directory specified.\n\n"); + print_usage(); + return 1; + }else{ + capath = argv[i+1]; + } + i++; + }else if(!strcmp(argv[i], "--cert")){ + if(i==argc-1){ + fprintf(stderr, "Error: --cert argument given but no file specified.\n\n"); + print_usage(); + return 1; + }else{ + certfile = argv[i+1]; + } + i++; + }else if(!strcmp(argv[i], "--ciphers")){ + if(i==argc-1){ + fprintf(stderr, "Error: --ciphers argument given but no ciphers specified.\n\n"); + print_usage(); + return 1; + }else{ + ciphers = argv[i+1]; + } + i++; + }else if(!strcmp(argv[i], "-d") || !strcmp(argv[i], "--debug")){ + debug = true; + }else if(!strcmp(argv[i], "-f") || !strcmp(argv[i], "--file")){ + if(mode != MSGMODE_NONE){ + fprintf(stderr, "Error: Only one type of message can be sent at once.\n\n"); + print_usage(); + return 1; + }else if(i==argc-1){ + fprintf(stderr, "Error: -f argument given but no file specified.\n\n"); + print_usage(); + return 1; + }else{ + if(load_file(argv[i+1])) return 1; + } + i++; + }else if(!strcmp(argv[i], "--help")){ + print_usage(); + return 0; + }else if(!strcmp(argv[i], "-h") || !strcmp(argv[i], "--host")){ + if(i==argc-1){ + fprintf(stderr, "Error: -h argument given but no host specified.\n\n"); + print_usage(); + return 1; + }else{ + host = argv[i+1]; + } + i++; + }else if(!strcmp(argv[i], "--insecure")){ + insecure = true; + }else if(!strcmp(argv[i], "-i") || !strcmp(argv[i], "--id")){ + if(id_prefix){ + fprintf(stderr, "Error: -i and -I argument cannot be used together.\n\n"); + print_usage(); + return 1; + } + if(i==argc-1){ + fprintf(stderr, "Error: -i argument given but no id specified.\n\n"); + print_usage(); + return 1; + }else{ + id = argv[i+1]; + } + i++; + }else if(!strcmp(argv[i], "-I") || !strcmp(argv[i], "--id-prefix")){ + if(id){ + fprintf(stderr, "Error: -i and -I argument cannot be used together.\n\n"); + print_usage(); + return 1; + } + if(i==argc-1){ + fprintf(stderr, "Error: -I argument given but no id prefix specified.\n\n"); + print_usage(); + return 1; + }else{ + id_prefix = argv[i+1]; + } + i++; + }else if(!strcmp(argv[i], "--key")){ + if(i==argc-1){ + fprintf(stderr, "Error: --key argument given but no file specified.\n\n"); + print_usage(); + return 1; + }else{ + keyfile = argv[i+1]; + } + i++; + }else if(!strcmp(argv[i], "-l") || !strcmp(argv[i], "--stdin-line")){ + if(mode != MSGMODE_NONE){ + fprintf(stderr, "Error: Only one type of message can be sent at once.\n\n"); + print_usage(); + return 1; + }else{ + mode = MSGMODE_STDIN_LINE; + } + }else if(!strcmp(argv[i], "-m") || !strcmp(argv[i], "--message")){ + if(mode != MSGMODE_NONE){ + fprintf(stderr, "Error: Only one type of message can be sent at once.\n\n"); + print_usage(); + return 1; + }else if(i==argc-1){ + fprintf(stderr, "Error: -m argument given but no message specified.\n\n"); + print_usage(); + return 1; + }else{ + message = argv[i+1]; + msglen = strlen(message); + mode = MSGMODE_CMD; + } + i++; + }else if(!strcmp(argv[i], "-M")){ + if(i==argc-1){ + fprintf(stderr, "Error: -M argument given but max_inflight not specified.\n\n"); + print_usage(); + return 1; + }else{ + max_inflight = atoi(argv[i+1]); + } + i++; + }else if(!strcmp(argv[i], "-n") || !strcmp(argv[i], "--null-message")){ + if(mode != MSGMODE_NONE){ + fprintf(stderr, "Error: Only one type of message can be sent at once.\n\n"); + print_usage(); + return 1; + }else{ + mode = MSGMODE_NULL; + } + }else if(!strcmp(argv[i], "--psk")){ + if(i==argc-1){ + fprintf(stderr, "Error: --psk argument given but no key specified.\n\n"); + print_usage(); + return 1; + }else{ + psk = argv[i+1]; + } + i++; + }else if(!strcmp(argv[i], "--psk-identity")){ + if(i==argc-1){ + fprintf(stderr, "Error: --psk-identity argument given but no identity specified.\n\n"); + print_usage(); + return 1; + }else{ + psk_identity = argv[i+1]; + } + i++; + }else if(!strcmp(argv[i], "-q") || !strcmp(argv[i], "--qos")){ + if(i==argc-1){ + fprintf(stderr, "Error: -q argument given but no QoS specified.\n\n"); + print_usage(); + return 1; + }else{ + qos = atoi(argv[i+1]); + if(qos<0 || qos>2){ + fprintf(stderr, "Error: Invalid QoS given: %d\n", qos); + print_usage(); + return 1; + } + } + i++; + }else if(!strcmp(argv[i], "--quiet")){ + quiet = true; + }else if(!strcmp(argv[i], "-r") || !strcmp(argv[i], "--retain")){ + retain = 1; + }else if(!strcmp(argv[i], "-s") || !strcmp(argv[i], "--stdin-file")){ + if(mode != MSGMODE_NONE){ + fprintf(stderr, "Error: Only one type of message can be sent at once.\n\n"); + print_usage(); + return 1; + }else{ + if(load_stdin()) return 1; + } + }else if(!strcmp(argv[i], "-S")){ + use_srv = true; + }else if(!strcmp(argv[i], "-t") || !strcmp(argv[i], "--topic")){ + if(i==argc-1){ + fprintf(stderr, "Error: -t argument given but no topic specified.\n\n"); + print_usage(); + return 1; + }else{ + topic = argv[i+1]; + } + i++; + }else if(!strcmp(argv[i], "--tls-version")){ + if(i==argc-1){ + fprintf(stderr, "Error: --tls-version argument given but no version specified.\n\n"); + print_usage(); + return 1; + }else{ + tls_version = argv[i+1]; + } + i++; + }else if(!strcmp(argv[i], "-u") || !strcmp(argv[i], "--username")){ + if(i==argc-1){ + fprintf(stderr, "Error: -u argument given but no username specified.\n\n"); + print_usage(); + return 1; + }else{ + username = argv[i+1]; + } + i++; + }else if(!strcmp(argv[i], "-P") || !strcmp(argv[i], "--pw")){ + if(i==argc-1){ + fprintf(stderr, "Error: -P argument given but no password specified.\n\n"); + print_usage(); + return 1; + }else{ + password = argv[i+1]; + } + i++; + }else if(!strcmp(argv[i], "--will-payload")){ + if(i==argc-1){ + fprintf(stderr, "Error: --will-payload argument given but no will payload specified.\n\n"); + print_usage(); + return 1; + }else{ + will_payload = argv[i+1]; + will_payloadlen = strlen(will_payload); + } + i++; + }else if(!strcmp(argv[i], "--will-qos")){ + if(i==argc-1){ + fprintf(stderr, "Error: --will-qos argument given but no will QoS specified.\n\n"); + print_usage(); + return 1; + }else{ + will_qos = atoi(argv[i+1]); + if(will_qos < 0 || will_qos > 2){ + fprintf(stderr, "Error: Invalid will QoS %d.\n\n", will_qos); + return 1; + } + } + i++; + }else if(!strcmp(argv[i], "--will-retain")){ + will_retain = true; + }else if(!strcmp(argv[i], "--will-topic")){ + if(i==argc-1){ + fprintf(stderr, "Error: --will-topic argument given but no will topic specified.\n\n"); + print_usage(); + return 1; + }else{ + will_topic = argv[i+1]; + } + i++; + }else{ + fprintf(stderr, "Error: Unknown option '%s'.\n",argv[i]); + print_usage(); + return 1; + } + } + + if(!topic || mode == MSGMODE_NONE){ + fprintf(stderr, "Error: Both topic and message must be supplied.\n"); + print_usage(); + return 1; + } + + if(will_payload && !will_topic){ + fprintf(stderr, "Error: Will payload given, but no will topic given.\n"); + print_usage(); + return 1; + } + if(will_retain && !will_topic){ + fprintf(stderr, "Error: Will retain given, but no will topic given.\n"); + print_usage(); + return 1; + } + if(password && !username){ + if(!quiet) fprintf(stderr, "Warning: Not using password since username not set.\n"); + } + if((certfile && !keyfile) || (keyfile && !certfile)){ + fprintf(stderr, "Error: Both certfile and keyfile must be provided if one of them is.\n"); + print_usage(); + return 1; + } + if((cafile || capath) && psk){ + if(!quiet) fprintf(stderr, "Error: Only one of --psk or --cafile/--capath may be used at once.\n"); + return 1; + } + if(psk && !psk_identity){ + if(!quiet) fprintf(stderr, "Error: --psk-identity required if --psk used.\n"); + return 1; + } + + mosquitto_lib_init(); + + if(id_prefix){ + id = malloc(strlen(id_prefix)+10); + if(!id){ + if(!quiet) fprintf(stderr, "Error: Out of memory.\n"); + mosquitto_lib_cleanup(); + return 1; + } + snprintf(id, strlen(id_prefix)+10, "%s%d", id_prefix, getpid()); + }else if(!id){ + hostname[0] = '\0'; + gethostname(hostname, 256); + hostname[255] = '\0'; + len = strlen("mosqpub/-") + 6 + strlen(hostname); + id = malloc(len); + if(!id){ + if(!quiet) fprintf(stderr, "Error: Out of memory.\n"); + mosquitto_lib_cleanup(); + return 1; + } + snprintf(id, len, "mosqpub/%d-%s", getpid(), hostname); + if(strlen(id) > MOSQ_MQTT_ID_MAX_LENGTH){ + /* Enforce maximum client id length of 23 characters */ + id[MOSQ_MQTT_ID_MAX_LENGTH] = '\0'; + } + } + + mosq = mosquitto_new(id, true, NULL); + if(!mosq){ + switch(errno){ + case ENOMEM: + if(!quiet) fprintf(stderr, "Error: Out of memory.\n"); + break; + case EINVAL: + if(!quiet) fprintf(stderr, "Error: Invalid id.\n"); + break; + } + mosquitto_lib_cleanup(); + return 1; + } + if(debug){ + mosquitto_log_callback_set(mosq, my_log_callback); + } + if(will_topic && mosquitto_will_set(mosq, will_topic, will_payloadlen, will_payload, will_qos, will_retain)){ + if(!quiet) fprintf(stderr, "Error: Problem setting will.\n"); + mosquitto_lib_cleanup(); + return 1; + } + if(username && mosquitto_username_pw_set(mosq, username, password)){ + if(!quiet) fprintf(stderr, "Error: Problem setting username and password.\n"); + mosquitto_lib_cleanup(); + return 1; + } + if((cafile || capath) && mosquitto_tls_set(mosq, cafile, capath, certfile, keyfile, NULL)){ + if(!quiet) fprintf(stderr, "Error: Problem setting TLS options.\n"); + mosquitto_lib_cleanup(); + return 1; + } + if(insecure && mosquitto_tls_insecure_set(mosq, true)){ + if(!quiet) fprintf(stderr, "Error: Problem setting TLS insecure option.\n"); + mosquitto_lib_cleanup(); + return 1; + } + if(psk && mosquitto_tls_psk_set(mosq, psk, psk_identity, NULL)){ + if(!quiet) fprintf(stderr, "Error: Problem setting TLS-PSK options.\n"); + mosquitto_lib_cleanup(); + return 1; + } + if(tls_version && mosquitto_tls_opts_set(mosq, 1, tls_version, ciphers)){ + if(!quiet) fprintf(stderr, "Error: Problem setting TLS options.\n"); + mosquitto_lib_cleanup(); + return 1; + } + mosquitto_max_inflight_messages_set(mosq, max_inflight); + mosquitto_connect_callback_set(mosq, my_connect_callback); + mosquitto_disconnect_callback_set(mosq, my_disconnect_callback); + mosquitto_publish_callback_set(mosq, my_publish_callback); + + if(use_srv){ + rc = mosquitto_connect_srv(mosq, host, keepalive, bind_address); + }else{ + rc = mosquitto_connect_bind(mosq, host, port, keepalive, bind_address); + } + if(rc){ + if(!quiet){ + if(rc == MOSQ_ERR_ERRNO){ +#ifndef WIN32 + strerror_r(errno, err, 1024); +#else + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, errno, 0, (LPTSTR)&err, 1024, NULL); +#endif + fprintf(stderr, "Error: %s\n", err); + }else{ + fprintf(stderr, "Unable to connect (%d).\n", rc); + } + } + mosquitto_lib_cleanup(); + return rc; + } + + if(mode == MSGMODE_STDIN_LINE){ + mosquitto_loop_start(mosq); + } + + do{ + if(mode == MSGMODE_STDIN_LINE){ + if(status == STATUS_CONNACK_RECVD){ + if(fgets(buf, 1024, stdin)){ + buf[strlen(buf)-1] = '\0'; + rc2 = mosquitto_publish(mosq, &mid_sent, topic, strlen(buf), buf, qos, retain); + if(rc2){ + if(!quiet) fprintf(stderr, "Error: Publish returned %d, disconnecting.\n", rc2); + mosquitto_disconnect(mosq); + } + }else if(feof(stdin)){ + last_mid = mid_sent; + status = STATUS_WAITING; + } + }else if(status == STATUS_WAITING){ + if(last_mid_sent == last_mid && disconnect_sent == false){ + mosquitto_disconnect(mosq); + disconnect_sent = true; + } +#ifdef WIN32 + Sleep(100); +#else + usleep(100000); +#endif + } + rc = MOSQ_ERR_SUCCESS; + }else{ + rc = mosquitto_loop(mosq, -1, 1); + } + }while(rc == MOSQ_ERR_SUCCESS && connected); + + if(mode == MSGMODE_STDIN_LINE){ + mosquitto_loop_stop(mosq, false); + } + + if(message && mode == MSGMODE_FILE){ + free(message); + } + mosquitto_destroy(mosq); + mosquitto_lib_cleanup(); + + if(rc){ + fprintf(stderr, "Error: %s\n", mosquitto_strerror(rc)); + } + return rc; +} diff --git a/client/sub_client.c b/client/sub_client.c new file mode 100644 index 0000000000..4dd31d8b1a --- /dev/null +++ b/client/sub_client.c @@ -0,0 +1,650 @@ +/* +Copyright (c) 2009-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#include +#include +#include +#include +#include +#ifndef WIN32 +#include +#else +#include +#include +#define snprintf sprintf_s +#endif + +#include + +/* This struct is used to pass data to callbacks. + * An instance "ud" is created in main() and populated, then passed to + * mosquitto_new(). */ +struct userdata { + char **topics; + int topic_count; + int topic_qos; + char **filter_outs; + int filter_out_count; + char *username; + char *password; + int verbose; + bool quiet; + bool no_retain; + bool eol; +}; + +void my_message_callback(struct mosquitto *mosq, void *obj, const struct mosquitto_message *message) +{ + struct userdata *ud; + int i; + bool res; + + assert(obj); + ud = (struct userdata *)obj; + + if(message->retain && ud->no_retain) return; + if(ud->filter_outs){ + for(i=0; ifilter_out_count; i++){ + mosquitto_topic_matches_sub(ud->filter_outs[i], message->topic, &res); + if(res) return; + } + } + + if(ud->verbose){ + if(message->payloadlen){ + printf("%s ", message->topic); + fwrite(message->payload, 1, message->payloadlen, stdout); + if(ud->eol){ + printf("\n"); + } + }else{ + if(ud->eol){ + printf("%s (null)\n", message->topic); + } + } + fflush(stdout); + }else{ + if(message->payloadlen){ + fwrite(message->payload, 1, message->payloadlen, stdout); + if(ud->eol){ + printf("\n"); + } + fflush(stdout); + } + } +} + +void my_connect_callback(struct mosquitto *mosq, void *obj, int result) +{ + int i; + struct userdata *ud; + + assert(obj); + ud = (struct userdata *)obj; + + if(!result){ + for(i=0; itopic_count; i++){ + mosquitto_subscribe(mosq, NULL, ud->topics[i], ud->topic_qos); + } + }else{ + if(result && !ud->quiet){ + fprintf(stderr, "%s\n", mosquitto_connack_string(result)); + } + } +} + +void my_subscribe_callback(struct mosquitto *mosq, void *obj, int mid, int qos_count, const int *granted_qos) +{ + int i; + struct userdata *ud; + + assert(obj); + ud = (struct userdata *)obj; + + if(!ud->quiet) printf("Subscribed (mid: %d): %d", mid, granted_qos[0]); + for(i=1; iquiet) printf(", %d", granted_qos[i]); + } + if(!ud->quiet) printf("\n"); +} + +void my_log_callback(struct mosquitto *mosq, void *obj, int level, const char *str) +{ + printf("%s\n", str); +} + +void print_usage(void) +{ + int major, minor, revision; + + mosquitto_lib_version(&major, &minor, &revision); + printf("mosquitto_sub is a simple mqtt client that will subscribe to a single topic and print all messages it receives.\n"); + printf("mosquitto_sub version %s running on libmosquitto %d.%d.%d.\n\n", VERSION, major, minor, revision); + printf("Usage: mosquitto_sub [-c] [-h host] [-k keepalive] [-p port] [-q qos] [-R] -t topic ...\n"); + printf(" [-T filter_out]\n"); + printf(" [-A bind_address] [-S]\n"); + printf(" [-i id] [-I id_prefix]\n"); + printf(" [-d] [-N] [--quiet] [-v]\n"); + printf(" [-u username [-P password]]\n"); + printf(" [--will-topic [--will-payload payload] [--will-qos qos] [--will-retain]]\n"); +#ifdef WITH_TLS + printf(" [{--cafile file | --capath dir} [--cert file] [--key file]\n"); + printf(" [--ciphers ciphers] [--insecure]]\n"); +#ifdef WITH_TLS_PSK + printf(" [--psk hex-key --psk-identity identity [--ciphers ciphers]]\n"); +#endif +#endif + printf(" mosquitto_sub --help\n\n"); + printf(" -A : bind the outgoing socket to this host/ip address. Use to control which interface\n"); + printf(" the client communicates over.\n"); + printf(" -c : disable 'clean session' (store subscription and pending messages when client disconnects).\n"); + printf(" -d : enable debug messages.\n"); + printf(" -h : mqtt host to connect to. Defaults to localhost.\n"); + printf(" -i : id to use for this client. Defaults to mosquitto_sub_ appended with the process id.\n"); + printf(" -I : define the client id as id_prefix appended with the process id. Useful for when the\n"); + printf(" broker is using the clientid_prefixes option.\n"); + printf(" -k : keep alive in seconds for this client. Defaults to 60.\n"); + printf(" -N : do not add an end of line character when printing the payload.\n"); + printf(" -p : network port to connect to. Defaults to 1883.\n"); + printf(" -q : quality of service level to use for the subscription. Defaults to 0.\n"); + printf(" -R : do not print stale messages (those with retain set).\n"); + printf(" -S : use SRV lookups to determine which host to connect to.\n"); + printf(" -t : mqtt topic to subscribe to. May be repeated multiple times.\n"); + printf(" -u : provide a username (requires MQTT 3.1 broker)\n"); + printf(" -v : print published messages verbosely.\n"); + printf(" -P : provide a password (requires MQTT 3.1 broker)\n"); + printf(" --help : display this message.\n"); + printf(" --quiet : don't print error messages.\n"); + printf(" --will-payload : payload for the client Will, which is sent by the broker in case of\n"); + printf(" unexpected disconnection. If not given and will-topic is set, a zero\n"); + printf(" length message will be sent.\n"); + printf(" --will-qos : QoS level for the client Will.\n"); + printf(" --will-retain : if given, make the client Will retained.\n"); + printf(" --will-topic : the topic on which to publish the client Will.\n"); +#ifdef WITH_TLS + printf(" --cafile : path to a file containing trusted CA certificates to enable encrypted\n"); + printf(" certificate based communication.\n"); + printf(" --capath : path to a directory containing trusted CA certificates to enable encrypted\n"); + printf(" communication.\n"); + printf(" --cert : client certificate for authentication, if required by server.\n"); + printf(" --key : client private key for authentication, if required by server.\n"); + printf(" --ciphers : openssl compatible list of TLS ciphers to support.\n"); + printf(" --tls-version : TLS protocol version, can be one of tlsv1.2 tlsv1.1 or tlsv1.\n"); + printf(" Defaults to tlsv1.2 if available.\n"); + printf(" --insecure : do not check that the server certificate hostname matches the remote\n"); + printf(" hostname. Using this option means that you cannot be sure that the\n"); + printf(" remote host is the server you wish to connect to and so is insecure.\n"); + printf(" Do not use this option in a production environment.\n"); +#ifdef WITH_TLS_PSK + printf(" --psk : pre-shared-key in hexadecimal (no leading 0x) to enable TLS-PSK mode.\n"); + printf(" --psk-identity : client identity string for TLS-PSK mode.\n"); +#endif +#endif + printf("\nSee http://mosquitto.org/ for more information.\n\n"); +} + +int main(int argc, char *argv[]) +{ + char *id = NULL; + char *id_prefix = NULL; + int i; + char *host = "localhost"; + int port = 1883; + char *bind_address = NULL; + int keepalive = 60; + bool clean_session = true; + bool debug = false; + struct mosquitto *mosq = NULL; + int rc; + char hostname[256]; + char err[1024]; + struct userdata ud; + int len; + + char *will_payload = NULL; + long will_payloadlen = 0; + int will_qos = 0; + bool will_retain = false; + char *will_topic = NULL; + + bool insecure = false; + char *cafile = NULL; + char *capath = NULL; + char *certfile = NULL; + char *keyfile = NULL; + char *tls_version = NULL; + + char *psk = NULL; + char *psk_identity = NULL; + + char *ciphers = NULL; + + bool use_srv = false; + + memset(&ud, 0, sizeof(struct userdata)); + ud.eol = true; + + for(i=1; i65535){ + fprintf(stderr, "Error: Invalid port given: %d\n", port); + print_usage(); + return 1; + } + } + i++; + }else if(!strcmp(argv[i], "-A")){ + if(i==argc-1){ + fprintf(stderr, "Error: -A argument given but no address specified.\n\n"); + print_usage(); + return 1; + }else{ + bind_address = argv[i+1]; + } + i++; + }else if(!strcmp(argv[i], "-c") || !strcmp(argv[i], "--disable-clean-session")){ + clean_session = false; + }else if(!strcmp(argv[i], "--cafile")){ + if(i==argc-1){ + fprintf(stderr, "Error: --cafile argument given but no file specified.\n\n"); + print_usage(); + return 1; + }else{ + cafile = argv[i+1]; + } + i++; + }else if(!strcmp(argv[i], "--capath")){ + if(i==argc-1){ + fprintf(stderr, "Error: --capath argument given but no directory specified.\n\n"); + print_usage(); + return 1; + }else{ + capath = argv[i+1]; + } + i++; + }else if(!strcmp(argv[i], "--cert")){ + if(i==argc-1){ + fprintf(stderr, "Error: --cert argument given but no file specified.\n\n"); + print_usage(); + return 1; + }else{ + certfile = argv[i+1]; + } + i++; + }else if(!strcmp(argv[i], "--ciphers")){ + if(i==argc-1){ + fprintf(stderr, "Error: --ciphers argument given but no ciphers specified.\n\n"); + print_usage(); + return 1; + }else{ + ciphers = argv[i+1]; + } + i++; + }else if(!strcmp(argv[i], "-d") || !strcmp(argv[i], "--debug")){ + debug = true; + }else if(!strcmp(argv[i], "--help")){ + print_usage(); + return 0; + }else if(!strcmp(argv[i], "-h") || !strcmp(argv[i], "--host")){ + if(i==argc-1){ + fprintf(stderr, "Error: -h argument given but no host specified.\n\n"); + print_usage(); + return 1; + }else{ + host = argv[i+1]; + } + i++; + }else if(!strcmp(argv[i], "--insecure")){ + insecure = true; + }else if(!strcmp(argv[i], "-i") || !strcmp(argv[i], "--id")){ + if(id_prefix){ + fprintf(stderr, "Error: -i and -I argument cannot be used together.\n\n"); + print_usage(); + return 1; + } + if(i==argc-1){ + fprintf(stderr, "Error: -i argument given but no id specified.\n\n"); + print_usage(); + return 1; + }else{ + id = argv[i+1]; + } + i++; + }else if(!strcmp(argv[i], "-I") || !strcmp(argv[i], "--id-prefix")){ + if(id){ + fprintf(stderr, "Error: -i and -I argument cannot be used together.\n\n"); + print_usage(); + return 1; + } + if(i==argc-1){ + fprintf(stderr, "Error: -I argument given but no id prefix specified.\n\n"); + print_usage(); + return 1; + }else{ + id_prefix = argv[i+1]; + } + i++; + }else if(!strcmp(argv[i], "-k") || !strcmp(argv[i], "--keepalive")){ + if(i==argc-1){ + fprintf(stderr, "Error: -k argument given but no keepalive specified.\n\n"); + print_usage(); + return 1; + }else{ + keepalive = atoi(argv[i+1]); + if(keepalive>65535){ + fprintf(stderr, "Error: Invalid keepalive given: %d\n", keepalive); + print_usage(); + return 1; + } + } + i++; + }else if(!strcmp(argv[i], "--key")){ + if(i==argc-1){ + fprintf(stderr, "Error: --key argument given but no file specified.\n\n"); + print_usage(); + return 1; + }else{ + keyfile = argv[i+1]; + } + i++; + }else if(!strcmp(argv[i], "-N")){ + ud.eol = false; + }else if(!strcmp(argv[i], "--psk")){ + if(i==argc-1){ + fprintf(stderr, "Error: --psk argument given but no key specified.\n\n"); + print_usage(); + return 1; + }else{ + psk = argv[i+1]; + } + i++; + }else if(!strcmp(argv[i], "--psk-identity")){ + if(i==argc-1){ + fprintf(stderr, "Error: --psk-identity argument given but no identity specified.\n\n"); + print_usage(); + return 1; + }else{ + psk_identity = argv[i+1]; + } + i++; + }else if(!strcmp(argv[i], "-q") || !strcmp(argv[i], "--qos")){ + if(i==argc-1){ + fprintf(stderr, "Error: -q argument given but no QoS specified.\n\n"); + print_usage(); + return 1; + }else{ + ud.topic_qos = atoi(argv[i+1]); + if(ud.topic_qos<0 || ud.topic_qos>2){ + fprintf(stderr, "Error: Invalid QoS given: %d\n", ud.topic_qos); + print_usage(); + return 1; + } + } + i++; + }else if(!strcmp(argv[i], "--quiet")){ + ud.quiet = true; + }else if(!strcmp(argv[i], "-R")){ + ud.no_retain = true; + }else if(!strcmp(argv[i], "-S")){ + use_srv = true; + }else if(!strcmp(argv[i], "-t") || !strcmp(argv[i], "--topic")){ + if(i==argc-1){ + fprintf(stderr, "Error: -t argument given but no topic specified.\n\n"); + print_usage(); + return 1; + }else{ + ud.topic_count++; + ud.topics = realloc(ud.topics, ud.topic_count*sizeof(char *)); + ud.topics[ud.topic_count-1] = argv[i+1]; + } + i++; + }else if(!strcmp(argv[i], "-T") || !strcmp(argv[i], "--filter-out")){ + if(i==argc-1){ + fprintf(stderr, "Error: -T argument given but no topic filter specified.\n\n"); + print_usage(); + return 1; + }else{ + ud.filter_out_count++; + ud.filter_outs = realloc(ud.filter_outs, ud.filter_out_count*sizeof(char *)); + ud.filter_outs[ud.filter_out_count-1] = argv[i+1]; + } + i++; + }else if(!strcmp(argv[i], "--tls-version")){ + if(i==argc-1){ + fprintf(stderr, "Error: --tls-version argument given but no version specified.\n\n"); + print_usage(); + return 1; + }else{ + tls_version = argv[i+1]; + } + i++; + }else if(!strcmp(argv[i], "-u") || !strcmp(argv[i], "--username")){ + if(i==argc-1){ + fprintf(stderr, "Error: -u argument given but no username specified.\n\n"); + print_usage(); + return 1; + }else{ + ud.username = argv[i+1]; + } + i++; + }else if(!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose")){ + ud.verbose = 1; + }else if(!strcmp(argv[i], "-P") || !strcmp(argv[i], "--pw")){ + if(i==argc-1){ + fprintf(stderr, "Error: -P argument given but no password specified.\n\n"); + print_usage(); + return 1; + }else{ + ud.password = argv[i+1]; + } + i++; + }else if(!strcmp(argv[i], "--will-payload")){ + if(i==argc-1){ + fprintf(stderr, "Error: --will-payload argument given but no will payload specified.\n\n"); + print_usage(); + return 1; + }else{ + will_payload = argv[i+1]; + will_payloadlen = strlen(will_payload); + } + i++; + }else if(!strcmp(argv[i], "--will-qos")){ + if(i==argc-1){ + fprintf(stderr, "Error: --will-qos argument given but no will QoS specified.\n\n"); + print_usage(); + return 1; + }else{ + will_qos = atoi(argv[i+1]); + if(will_qos < 0 || will_qos > 2){ + fprintf(stderr, "Error: Invalid will QoS %d.\n\n", will_qos); + return 1; + } + } + i++; + }else if(!strcmp(argv[i], "--will-retain")){ + will_retain = true; + }else if(!strcmp(argv[i], "--will-topic")){ + if(i==argc-1){ + fprintf(stderr, "Error: --will-topic argument given but no will topic specified.\n\n"); + print_usage(); + return 1; + }else{ + will_topic = argv[i+1]; + } + i++; + }else{ + fprintf(stderr, "Error: Unknown option '%s'.\n",argv[i]); + print_usage(); + return 1; + } + } + + if(clean_session == false && (id_prefix || !id)){ + if(!ud.quiet) fprintf(stderr, "Error: You must provide a client id if you are using the -c option.\n"); + return 1; + } + + if(ud.topic_count == 0){ + fprintf(stderr, "Error: You must specify a topic to subscribe to.\n"); + print_usage(); + return 1; + } + if(will_payload && !will_topic){ + fprintf(stderr, "Error: Will payload given, but no will topic given.\n"); + print_usage(); + return 1; + } + if(will_retain && !will_topic){ + fprintf(stderr, "Error: Will retain given, but no will topic given.\n"); + print_usage(); + return 1; + } + if(ud.password && !ud.username){ + if(!ud.quiet) fprintf(stderr, "Warning: Not using password since username not set.\n"); + } + if((certfile && !keyfile) || (keyfile && !certfile)){ + fprintf(stderr, "Error: Both certfile and keyfile must be provided if one of them is.\n"); + print_usage(); + return 1; + } + if((cafile || capath) && psk){ + if(!ud.quiet) fprintf(stderr, "Error: Only one of --psk or --cafile/--capath may be used at once.\n"); + return 1; + } + if(psk && !psk_identity){ + if(!ud.quiet) fprintf(stderr, "Error: --psk-identity required if --psk used.\n"); + return 1; + } + + mosquitto_lib_init(); + + if(id_prefix){ + id = malloc(strlen(id_prefix)+10); + if(!id){ + if(!ud.quiet) fprintf(stderr, "Error: Out of memory.\n"); + mosquitto_lib_cleanup(); + return 1; + } + snprintf(id, strlen(id_prefix)+10, "%s%d", id_prefix, getpid()); + }else if(!id){ + hostname[0] = '\0'; + gethostname(hostname, 256); + hostname[255] = '\0'; + len = strlen("mosqsub/-") + 6 + strlen(hostname); + id = malloc(len); + if(!id){ + if(!ud.quiet) fprintf(stderr, "Error: Out of memory.\n"); + mosquitto_lib_cleanup(); + return 1; + } + snprintf(id, len, "mosqsub/%d-%s", getpid(), hostname); + if(strlen(id) > MOSQ_MQTT_ID_MAX_LENGTH){ + /* Enforce maximum client id length of 23 characters */ + id[MOSQ_MQTT_ID_MAX_LENGTH] = '\0'; + } + } + + mosq = mosquitto_new(id, clean_session, &ud); + if(!mosq){ + switch(errno){ + case ENOMEM: + if(!ud.quiet) fprintf(stderr, "Error: Out of memory.\n"); + break; + case EINVAL: + if(!ud.quiet) fprintf(stderr, "Error: Invalid id and/or clean_session.\n"); + break; + } + mosquitto_lib_cleanup(); + return 1; + } + if(debug){ + mosquitto_log_callback_set(mosq, my_log_callback); + } + if(will_topic && mosquitto_will_set(mosq, will_topic, will_payloadlen, will_payload, will_qos, will_retain)){ + if(!ud.quiet) fprintf(stderr, "Error: Problem setting will.\n"); + mosquitto_lib_cleanup(); + return 1; + } + if(ud.username && mosquitto_username_pw_set(mosq, ud.username, ud.password)){ + if(!ud.quiet) fprintf(stderr, "Error: Problem setting username and password.\n"); + mosquitto_lib_cleanup(); + return 1; + } + if((cafile || capath) && mosquitto_tls_set(mosq, cafile, capath, certfile, keyfile, NULL)){ + if(!ud.quiet) fprintf(stderr, "Error: Problem setting TLS options.\n"); + mosquitto_lib_cleanup(); + return 1; + } + if(insecure && mosquitto_tls_insecure_set(mosq, true)){ + if(!ud.quiet) fprintf(stderr, "Error: Problem setting TLS insecure option.\n"); + mosquitto_lib_cleanup(); + return 1; + } + if(psk && mosquitto_tls_psk_set(mosq, psk, psk_identity, NULL)){ + if(!ud.quiet) fprintf(stderr, "Error: Problem setting TLS-PSK options.\n"); + mosquitto_lib_cleanup(); + return 1; + } + if(tls_version && mosquitto_tls_opts_set(mosq, 1, tls_version, ciphers)){ + if(!ud.quiet) fprintf(stderr, "Error: Problem setting TLS options.\n"); + mosquitto_lib_cleanup(); + return 1; + } + mosquitto_connect_callback_set(mosq, my_connect_callback); + mosquitto_message_callback_set(mosq, my_message_callback); + if(debug){ + mosquitto_subscribe_callback_set(mosq, my_subscribe_callback); + } + + if(use_srv){ + rc = mosquitto_connect_srv(mosq, host, keepalive, bind_address); + }else{ + rc = mosquitto_connect_bind(mosq, host, port, keepalive, bind_address); + } + if(rc){ + if(!ud.quiet){ + if(rc == MOSQ_ERR_ERRNO){ +#ifndef WIN32 + strerror_r(errno, err, 1024); +#else + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, errno, 0, (LPTSTR)&err, 1024, NULL); +#endif + fprintf(stderr, "Error: %s\n", err); + }else{ + fprintf(stderr, "Unable to connect (%d).\n", rc); + } + } + mosquitto_lib_cleanup(); + return rc; + } + + rc = mosquitto_loop_forever(mosq, -1, 1); + + mosquitto_destroy(mosq); + mosquitto_lib_cleanup(); + + if(rc){ + fprintf(stderr, "Error: %s\n", mosquitto_strerror(rc)); + } + return rc; +} + diff --git a/compiling.txt b/compiling.txt new file mode 100644 index 0000000000..f439c44d5a --- /dev/null +++ b/compiling.txt @@ -0,0 +1,17 @@ +The following packages are required for mosquitto: + +* tcp-wrappers (optional, package name libwrap0-dev) +* openssl (version 1.0.0 or greater if TLS-PSK support is needed) +* On Windows, the Redhat pthreads library is required if threading support is + to be included. + +To compile, run "make", but also see the file config.mk for more details on the +various options that can be compiled in. + +Where possible use the Makefiles to compile. This is particularly relevant for +the client libraries as symbol information will be included. Use cmake to +compile on Windows or Mac. + +If you have any questions, problems or suggestions (particularly related to +installing on a more unusual device like a plug-computer) then please get in +touch using the details in readme.txt. diff --git a/config.h b/config.h new file mode 100644 index 0000000000..cdc62497bf --- /dev/null +++ b/config.h @@ -0,0 +1,19 @@ +/* ============================================================ + * Control compile time options. + * ============================================================ + * + * Compile time options have moved to config.mk. + */ + + +/* ============================================================ + * Compatibility defines + * + * Generally for Windows native support. + * ============================================================ */ +#ifdef WIN32 +#define snprintf sprintf_s +#define strcasecmp strcmpi +#define strtok_r strtok_s +#define strerror_r(e, b, l) strerror_s(b, l, e) +#endif diff --git a/config.mk b/config.mk new file mode 100644 index 0000000000..08e37f1c7d --- /dev/null +++ b/config.mk @@ -0,0 +1,218 @@ +# ============================================================================= +# User configuration section. +# +# Largely, these are options that are designed to make mosquitto run more +# easily in restrictive environments by removing features. +# +# Modify the variable below to enable/disable features. +# +# Can also be overriden at the command line, e.g.: +# +# make WITH_TLS=no +# ============================================================================= + +# Uncomment to compile the broker with tcpd/libwrap support. +#WITH_WRAP:=yes + +# Comment out to disable SSL/TLS support in the broker and client. +# Disabling this will also mean that passwords must be stored in plain text. It +# is strongly recommended that you only disable WITH_TLS if you are not using +# password authentication at all. +WITH_TLS:=yes + +# Comment out to disable TLS/PSK support in the broker and client. Requires +# WITH_TLS=yes. +# This must be disabled if using openssl < 1.0. +WITH_TLS_PSK:=yes + +# Comment out to disable client client threading support. +WITH_THREADING:=yes + +# Uncomment to compile the broker with strict protocol support. This means that +# both the client library and the broker will be very strict about protocol +# compliance on incoming data. Neither of them will return an error on +# incorrect "remaining length" values if this is commented out. The old +# behaviour (prior to 0.12) is equivalent to compiling with +# WITH_STRICT_PROTOCOL defined and means that clients will be immediately +# disconnected from the broker on non-compliance. +#WITH_STRICT_PROTOCOL:=yes + +# Comment out to remove bridge support from the broker. This allow the broker +# to connect to other brokers and subscribe/publish to topics. You probably +# want to leave this included unless you want to save a very small amount of +# memory size and CPU time. +WITH_BRIDGE:=yes + +# Comment out to remove persistent database support from the broker. This +# allows the broker to store retained messages and durable subscriptions to a +# file periodically and on shutdown. This is usually desirable (and is +# suggested by the MQTT spec), but it can be disabled if required. +WITH_PERSISTENCE:=yes + +# Comment out to remove memory tracking support from the broker. If disabled, +# mosquitto won't track heap memory usage nor export '$SYS/broker/heap/current +# size', but will use slightly less memory and CPU time. +WITH_MEMORY_TRACKING:=yes + +# Compile with database upgrading support? If disabled, mosquitto won't +# automatically upgrade old database versions. +# Not currently supported. +#WITH_DB_UPGRADE:=yes + +# Comment out to remove publishing of the $SYS topic hierarchy containing +# information about the broker state. +WITH_SYS_TREE:=yes + +# Build with Python module. Comment out if Python is not installed, or required +# Python modules are not available. +WITH_PYTHON:=yes + +# Build with SRV lookup support. +WITH_SRV:=yes + +# ============================================================================= +# End of user configuration +# ============================================================================= + + +# Also bump lib/mosquitto.h, lib/python/setup.py, CMakeLists.txt, +# installer/mosquitto.nsi, installer/mosquitto-cygwin.nsi +VERSION=1.3.1 +TIMESTAMP:=$(shell date "+%F %T%z") + +# Client library SO version. Bump if incompatible API/ABI changes are made. +SOVERSION=1 + +# Man page generation requires xsltproc and docbook-xsl +XSLTPROC=xsltproc +# For html generation +DB_HTML_XSL=man/html.xsl + +#MANCOUNTRIES=en_GB + +UNAME:=$(shell uname -s) +ifeq ($(UNAME),SunOS) + ifeq ($(CC),cc) + CFLAGS?=-O + else + CFLAGS?=-Wall -ggdb -O2 + endif +else + CFLAGS?=-Wall -ggdb -O2 +endif + +LIB_CFLAGS:=${CFLAGS} ${CPPFLAGS} -I. -I.. -I../lib +LIB_CXXFLAGS:=$(LIB_CFLAGS) ${CPPFLAGS} +LIB_LDFLAGS:=${LDFLAGS} + +BROKER_CFLAGS:=${LIB_CFLAGS} ${CPPFLAGS} -DVERSION="\"${VERSION}\"" -DTIMESTAMP="\"${TIMESTAMP}\"" -DWITH_BROKER +CLIENT_CFLAGS:=${CFLAGS} ${CPPFLAGS} -I../lib -DVERSION="\"${VERSION}\"" + +ifeq ($(UNAME),FreeBSD) + BROKER_LIBS:=-lm +else + BROKER_LIBS:=-ldl -lm +endif +LIB_LIBS:= +PASSWD_LIBS:= + +ifeq ($(UNAME),Linux) + BROKER_LIBS:=$(BROKER_LIBS) -lrt + LIB_LIBS:=$(LIB_LIBS) -lrt +endif + +CLIENT_LDFLAGS:=$(LDFLAGS) -L../lib ../lib/libmosquitto.so.${SOVERSION} + +ifeq ($(UNAME),SunOS) + ifeq ($(CC),cc) + LIB_CFLAGS:=$(LIB_CFLAGS) -xc99 -KPIC + else + LIB_CFLAGS:=$(LIB_CFLAGS) -fPIC + endif + + ifeq ($(CXX),CC) + LIB_CXXFLAGS:=$(LIB_CXXFLAGS) -KPIC + else + LIB_CXXFLAGS:=$(LIB_CXXFLAGS) -fPIC + endif +else + LIB_CFLAGS:=$(LIB_CFLAGS) -fPIC + LIB_CXXFLAGS:=$(LIB_CXXFLAGS) -fPIC +endif + +ifneq ($(UNAME),SunOS) + LIB_LDFLAGS:=$(LIB_LDFLAGS) -Wl,--version-script=linker.version -Wl,-soname,libmosquitto.so.$(SOVERSION) +endif + +ifeq ($(UNAME),QNX) + BROKER_LIBS:=$(BROKER_LIBS) -lsocket + LIB_LIBS:=$(LIB_LIBS) -lsocket +endif + +ifeq ($(WITH_WRAP),yes) + BROKER_LIBS:=$(BROKER_LIBS) -lwrap + BROKER_CFLAGS:=$(BROKER_CFLAGS) -DWITH_WRAP +endif + +ifeq ($(WITH_TLS),yes) + BROKER_LIBS:=$(BROKER_LIBS) -lssl -lcrypto + LIB_LIBS:=$(LIB_LIBS) -lssl -lcrypto + BROKER_CFLAGS:=$(BROKER_CFLAGS) -DWITH_TLS + LIB_CFLAGS:=$(LIB_CFLAGS) -DWITH_TLS + PASSWD_LIBS:=-lcrypto + CLIENT_CFLAGS:=$(CLIENT_CFLAGS) -DWITH_TLS + + ifeq ($(WITH_TLS_PSK),yes) + BROKER_CFLAGS:=$(BROKER_CFLAGS) -DWITH_TLS_PSK + LIB_CFLAGS:=$(LIB_CFLAGS) -DWITH_TLS_PSK + CLIENT_CFLAGS:=$(CLIENT_CFLAGS) -DWITH_TLS_PSK + endif +endif + +ifeq ($(WITH_THREADING),yes) + LIB_LIBS:=$(LIB_LIBS) -lpthread + LIB_CFLAGS:=$(LIB_CFLAGS) -DWITH_THREADING +endif + +ifeq ($(WITH_STRICT_PROTOCOL),yes) + LIB_CFLAGS:=$(LIB_CFLAGS) -DWITH_STRICT_PROTOCOL + BROKER_CFLAGS:=$(BROKER_CFLAGS) -DWITH_STRICT_PROTOCOL +endif + +ifeq ($(WITH_BRIDGE),yes) + BROKER_CFLAGS:=$(BROKER_CFLAGS) -DWITH_BRIDGE +endif + +ifeq ($(WITH_PERSISTENCE),yes) + BROKER_CFLAGS:=$(BROKER_CFLAGS) -DWITH_PERSISTENCE +endif + +ifeq ($(WITH_MEMORY_TRACKING),yes) + ifneq ($(UNAME),SunOS) + BROKER_CFLAGS:=$(BROKER_CFLAGS) -DWITH_MEMORY_TRACKING + endif +endif + +#ifeq ($(WITH_DB_UPGRADE),yes) +# BROKER_CFLAGS:=$(BROKER_CFLAGS) -DWITH_DB_UPGRADE +#endif + +ifeq ($(WITH_SYS_TREE),yes) + BROKER_CFLAGS:=$(BROKER_CFLAGS) -DWITH_SYS_TREE +endif + +ifeq ($(WITH_SRV),yes) + LIB_CFLAGS:=$(LIB_CFLAGS) -DWITH_SRV + LIB_LIBS:=$(LIB_LIBS) -lcares +endif + +ifeq ($(UNAME),SunOS) + BROKER_LIBS:=$(BROKER_LIBS) -lsocket -lnsl + LIB_LIBS:=$(LIB_LIBS) -lsocket -lnsl +endif + + +INSTALL?=install +prefix=/usr/local +mandir=${prefix}/share/man +localedir=${prefix}/share/locale diff --git a/doc/historical/old-regex.txt b/doc/historical/old-regex.txt new file mode 100644 index 0000000000..10fd170dff --- /dev/null +++ b/doc/historical/old-regex.txt @@ -0,0 +1,43 @@ +This is the description of the regex used previously for topic/subscription +matching. It is reproduced here for posterity. + +When a message is ready to be published at the broker, we need to check all +of the subscriptions to see which ones the message should be sent to. This +would be easy without wildcards, but requires a bit more work with them. + +The regex used to do the matching is of the form below for a topic of a/b/c: + +^(?:(?:(a|\+)(?!$))(?:(?:/(?:(b|\+)(?!$)))(?:(?:/(?:c|\+))|/#)?|/#)?|#)$ + +In general, we're matching (a or +) followed by (the next levels of +hierarchy or #). +More specifically, all the levels of hierarchy must match, unless the last +level is #. + +^(?: # Must start at beginning of string + (?: # (Level 1 hierarchy) + (a|\+)(?!$) # Match a or +, but only if not EOL. + ) # AND + (?: + (?: # (Level 2 hierarchy) + / # Match / + (?: # AND + (b|\+)(?!$) # Match b or +, but only if not EOL. + ) + ) # AND + (?: + (?: # (Level 3 hierarchy) + / # Match / + (?: # AND + c|\+ # Match c or +. + ) + ) + | # OR (instead of level 3) + /# # Match /# at level 3 + )? # Level 3 exist 1/0 times + | # OR (instead of level 2) + /# # Match /# at level 2 + )? # Level 2 exist 1/0 times + | # OR (instead of level 1) + # # Match # at level 1 + )$ # Must end on EOL. diff --git a/doc/historical/topic-match.kds b/doc/historical/topic-match.kds new file mode 100644 index 0000000000..9e2e8a6796 --- /dev/null +++ b/doc/historical/topic-match.kds @@ -0,0 +1,7 @@ +S'^(?:(?:(a|\\+)(?!$))(?:(?:/(?:(b|\\+)(?!$)))(?:(?:/(?:c|\\+))|/#)?|/#)?|#)$' +p1 +.S'a/#\na/b/c\na/b/+\na/b\na/+\n+\n+/b\n+/+/+\n+/b/c\na/c' +p2 +.I8 +.S'' +. \ No newline at end of file diff --git a/edl-v10 b/edl-v10 new file mode 100644 index 0000000000..0d500b9839 --- /dev/null +++ b/edl-v10 @@ -0,0 +1,31 @@ +Eclipse Distribution License - v 1.0 + +Copyright (c) 2007, Eclipse Foundation, Inc. and its licensors. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + Neither the name of the Eclipse Foundation, Inc. nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/epl-v10 b/epl-v10 new file mode 100644 index 0000000000..190869d113 --- /dev/null +++ b/epl-v10 @@ -0,0 +1,221 @@ +Eclipse Public License - v 1.0 + +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC +LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM +CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + + +1. DEFINITIONS + +"Contribution" means: + a) in the case of the initial Contributor, the initial code and + documentation distributed under this Agreement, and + + b) in the case of each subsequent Contributor: + + i) changes to the Program, and + ii) additions to the Program; + +where such changes and/or additions to the Program originate from and are +distributed by that particular Contributor. A Contribution 'originates' from a +Contributor if it was added to the Program by such Contributor itself or anyone +acting on such Contributor's behalf. Contributions do not include additions to +the Program which: (i) are separate modules of software distributed in +conjunction with the Program under their own license agreement, and (ii) are +not derivative works of the Program. + +"Contributor" means any person or entity that distributes the Program. + +"Licensed Patents " mean patent claims licensable by a Contributor which are +necessarily infringed by the use or sale of its Contribution alone or when +combined with the Program. + +"Program" means the Contributions distributed in accordance with this Agreement. + +"Recipient" means anyone who receives the Program under this Agreement, +including all Contributors. + + +2. GRANT OF RIGHTS + + a) Subject to the terms of this Agreement, each Contributor hereby grants + Recipient a non-exclusive, worldwide, royalty-free copyright license to + reproduce, prepare derivative works of, publicly display, publicly + perform, distribute and sublicense the Contribution of such Contributor, + if any, and such derivative works, in source code and object code form. + + b) Subject to the terms of this Agreement, each Contributor hereby grants + Recipient a non-exclusive, worldwide, royalty-free patent license under + Licensed Patents to make, use, sell, offer to sell, import and otherwise + transfer the Contribution of such Contributor, if any, in source code and + object code form. This patent license shall apply to the combination of the + Contribution and the Program if, at the time the Contribution is added by the + Contributor, such addition of the Contribution causes such combination to be + covered by the Licensed Patents. The patent license shall not apply to any + other combinations which include the Contribution. No hardware per se is + licensed hereunder. + + c) Recipient understands that although each Contributor grants the licenses + to its Contributions set forth herein, no assurances are provided by any + Contributor that the Program does not infringe the patent or other + intellectual property rights of any other entity. Each Contributor disclaims + any liability to Recipient for claims brought by any other entity based on + infringement of intellectual property rights or otherwise. As a condition to + exercising the rights and licenses granted hereunder, each Recipient hereby + assumes sole responsibility to secure any other intellectual property rights + needed, if any. For example, if a third party patent license is required to + allow Recipient to distribute the Program, it is Recipient's responsibility + to acquire that license before distributing the Program. + + d) Each Contributor represents that to its knowledge it has sufficient + copyright rights in its Contribution, if any, to grant the copyright license + set forth in this Agreement. + + +3. REQUIREMENTS + + A Contributor may choose to distribute the Program in object code form under + its own license agreement, provided that: + + a) it complies with the terms and conditions of this Agreement; and + + b) its license agreement: + + i) effectively disclaims on behalf of all Contributors all warranties and + conditions, express and implied, including warranties or conditions of + title and non-infringement, and implied warranties or conditions of + merchantability and fitness for a particular purpose; + + ii) effectively excludes on behalf of all Contributors all liability for + damages, including direct, indirect, special, incidental and consequential + damages, such as lost profits; + + iii) states that any provisions which differ from this Agreement are offered + by that Contributor alone and not by any other party; and + + iv) states that source code for the Program is available from such + Contributor, and informs licensees how to obtain it in a reasonable manner + on or through a medium customarily used for software exchange. + + When the Program is made available in source code form: + + a) it must be made available under this Agreement; and + + b) a copy of this Agreement must be included with each copy of the Program. + + Contributors may not remove or alter any copyright notices contained within + the Program. + + Each Contributor must identify itself as the originator of its Contribution, + if any, in a manner that reasonably allows subsequent Recipients to identify + the originator of the Contribution. + + +4. COMMERCIAL DISTRIBUTION + + Commercial distributors of software may accept certain responsibilities with + respect to end users, business partners and the like. While this license is + intended to facilitate the commercial use of the Program, the Contributor who + includes the Program in a commercial product offering should do so in a + manner which does not create potential liability for other Contributors. + Therefore, if a Contributor includes the Program in a commercial product + offering, such Contributor ("Commercial Contributor") hereby agrees to defend + and indemnify every other Contributor ("Indemnified Contributor") against any + losses, damages and costs (collectively "Losses") arising from claims, + lawsuits and other legal actions brought by a third party against the + Indemnified Contributor to the extent caused by the acts or omissions of such + Commercial Contributor in connection with its distribution of the Program in + a commercial product offering. The obligations in this section do not apply + to any claims or Losses relating to any actual or alleged intellectual + property infringement. In order to qualify, an Indemnified Contributor must: + a) promptly notify the Commercial Contributor in writing of such claim, and + b) allow the Commercial Contributor to control, and cooperate with the + Commercial Contributor in, the defense and any related settlement + negotiations. The Indemnified Contributor may participate in any such claim + at its own expense. + + For example, a Contributor might include the Program in a commercial product + offering, Product X. That Contributor is then a Commercial Contributor. If + that Commercial Contributor then makes performance claims, or offers + warranties related to Product X, those performance claims and warranties are + such Commercial Contributor's responsibility alone. Under this section, the + Commercial Contributor would have to defend claims against the other + Contributors related to those performance claims and warranties, and if a + court requires any other Contributor to pay any damages as a result, the + Commercial Contributor must pay those damages. + + +5. NO WARRANTY + + EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON + AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER + EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR + CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A + PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the + appropriateness of using and distributing the Program and assumes all risks + associated with its exercise of rights under this Agreement , including but + not limited to the risks and costs of program errors, compliance with + applicable laws, damage to or loss of data, programs or equipment, and + unavailability or interruption of operations. + + +6. DISCLAIMER OF LIABILITY + + EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY + CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION + LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE + EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY + OF SUCH DAMAGES. + + +7. GENERAL + + If any provision of this Agreement is invalid or unenforceable under + applicable law, it shall not affect the validity or enforceability of the + remainder of the terms of this Agreement, and without further action by the + parties hereto, such provision shall be reformed to the minimum extent + necessary to make such provision valid and enforceable. + + If Recipient institutes patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Program itself + (excluding combinations of the Program with other software or hardware) + infringes such Recipient's patent(s), then such Recipient's rights granted + under Section 2(b) shall terminate as of the date such litigation is filed. + + All Recipient's rights under this Agreement shall terminate if it fails to + comply with any of the material terms or conditions of this Agreement and + does not cure such failure in a reasonable period of time after becoming + aware of such noncompliance. If all Recipient's rights under this Agreement + terminate, Recipient agrees to cease use and distribution of the Program as + soon as reasonably practicable. However, Recipient's obligations under this + Agreement and any licenses granted by Recipient relating to the Program shall + continue and survive. + + Everyone is permitted to copy and distribute copies of this Agreement, but in + order to avoid inconsistency the Agreement is copyrighted and may only be + modified in the following manner. The Agreement Steward reserves the right to + publish new versions (including revisions) of this Agreement from time to + time. No one other than the Agreement Steward has the right to modify this + Agreement. The Eclipse Foundation is the initial Agreement Steward. The + Eclipse Foundation may assign the responsibility to serve as the Agreement + Steward to a suitable separate entity. Each new version of the Agreement will + be given a distinguishing version number. The Program (including + Contributions) may always be distributed subject to the version of the + Agreement under which it was received. In addition, after a new version of + the Agreement is published, Contributor may elect to distribute the Program + (including its Contributions) under the new version. Except as expressly + stated in Sections 2(a) and 2(b) above, Recipient receives no rights or + licenses to the intellectual property of any Contributor under this + Agreement, whether expressly, by implication, estoppel or otherwise. All + rights in the Program not expressly granted under this Agreement are + reserved. + + This Agreement is governed by the laws of the State of New York and the + intellectual property laws of the United States of America. No party to this + Agreement will bring a legal action under this Agreement more than one year + after the cause of action arose. Each party waives its rights to a jury trial + in any resulting litigation. + diff --git a/examples/mysql_log/Makefile b/examples/mysql_log/Makefile new file mode 100644 index 0000000000..e8c8b2cc37 --- /dev/null +++ b/examples/mysql_log/Makefile @@ -0,0 +1,15 @@ +CFLAGS=-Wall -ggdb +LDFLAGS=../../lib/libmosquitto.so.1 -lmysqlclient + +.PHONY: all clean + +all : mosquitto_mysql_log + +mosquitto_mysql_log : mysql_log.o + ${CC} $^ -o $@ ${LDFLAGS} + +mysql_log.o : mysql_log.c + ${CC} -c $^ -o $@ ${CFLAGS} -I../../lib + +clean : + -rm -f *.o mosquitto_mysql_log diff --git a/examples/mysql_log/mysql_log.c b/examples/mysql_log/mysql_log.c new file mode 100644 index 0000000000..565d7e9d99 --- /dev/null +++ b/examples/mysql_log/mysql_log.c @@ -0,0 +1,118 @@ +#include +#include +#include + +#ifndef WIN32 +# include +#else +# include +# define snprintf sprintf_s +#endif + +#include +#include + +#define db_host "localhost" +#define db_username "mqtt_log" +#define db_password "password" +#define db_database "mqtt_log" +#define db_port 3306 + +#define db_query "INSERT INTO mqtt_log (topic, payload) VALUES (?,?)" + +#define mqtt_host "localhost" +#define mqtt_port 1883 + +static int run = 1; +static MYSQL_STMT *stmt = NULL; + +void handle_signal(int s) +{ + run = 0; +} + +void connect_callback(struct mosquitto *mosq, void *obj, int result) +{ +} + +void message_callback(struct mosquitto *mosq, void *obj, const struct mosquitto_message *message) +{ + MYSQL_BIND bind[2]; + + memset(bind, 0, sizeof(bind)); + + bind[0].buffer_type = MYSQL_TYPE_STRING; + bind[0].buffer = message->topic; + bind[1].buffer_type = MYSQL_TYPE_STRING; + bind[1].buffer = message->payload; + + mysql_stmt_bind_param(stmt, bind); + mysql_stmt_execute(stmt); +} + +int main(int argc, char *argv[]) +{ + MYSQL *connection; + my_bool reconnect = true; + char clientid[24]; + struct mosquitto *mosq; + int rc = 0; + + signal(SIGINT, handle_signal); + signal(SIGTERM, handle_signal); + + mysql_library_init(0, NULL, NULL); + mosquitto_lib_init(); + + connection = mysql_init(NULL); + + if(connection){ + mysql_options(connection, MYSQL_OPT_RECONNECT, &reconnect); + + connection = mysql_real_connect(connection, db_host, db_username, db_password, db_database, db_port, NULL, 0); + + if(connection){ + stmt = mysql_stmt_init(connection); + + mysql_stmt_prepare(stmt, db_query, strlen(db_query)); + + memset(clientid, 0, 24); + snprintf(clientid, 23, "mysql_log_%d", getpid()); + mosq = mosquitto_new(clientid, true, connection); + if(mosq){ + mosquitto_connect_callback_set(mosq, connect_callback); + mosquitto_message_callback_set(mosq, message_callback); + + + rc = mosquitto_connect(mosq, mqtt_host, mqtt_port, 60); + + mosquitto_subscribe(mosq, NULL, "#", 0); + + while(run){ + rc = mosquitto_loop(mosq, -1, 1); + if(run && rc){ + sleep(20); + mosquitto_reconnect(mosq); + } + } + mosquitto_destroy(mosq); + } + mysql_stmt_close(stmt); + + mysql_close(connection); + }else{ + fprintf(stderr, "Error: Unable to connect to database.\n"); + printf("%s\n", mysql_error(connection)); + rc = 1; + } + }else{ + fprintf(stderr, "Error: Unable to start mysql.\n"); + rc = 1; + } + + mysql_library_end(); + mosquitto_lib_cleanup(); + + return rc; +} + diff --git a/examples/temperature_conversion/Makefile b/examples/temperature_conversion/Makefile new file mode 100644 index 0000000000..0f20ac413f --- /dev/null +++ b/examples/temperature_conversion/Makefile @@ -0,0 +1,18 @@ +CFLAGS=-Wall -ggdb -I../../lib -I../../lib/cpp +LDFLAGS=-L../../lib ../../lib/cpp/libmosquittopp.so.1 ../../lib/libmosquitto.so.1 + +.PHONY: all clean + +all : mqtt_temperature_conversion + +mqtt_temperature_conversion : main.o temperature_conversion.o + ${CXX} $^ -o $@ ${LDFLAGS} + +main.o : main.cpp + ${CXX} -c $^ -o $@ ${CFLAGS} + +temperature_conversion.o : temperature_conversion.cpp + ${CXX} -c $^ -o $@ ${CFLAGS} + +clean : + -rm -f *.o mqtt_temperature_conversion diff --git a/examples/temperature_conversion/main.cpp b/examples/temperature_conversion/main.cpp new file mode 100644 index 0000000000..2bd90b4abb --- /dev/null +++ b/examples/temperature_conversion/main.cpp @@ -0,0 +1,23 @@ +#include "temperature_conversion.h" + +int main(int argc, char *argv[]) +{ + class mqtt_tempconv *tempconv; + int rc; + + mosqpp::lib_init(); + + tempconv = new mqtt_tempconv("tempconv", "localhost", 1883); + + while(1){ + rc = tempconv->loop(); + if(rc){ + tempconv->reconnect(); + } + } + + mosqpp::lib_cleanup(); + + return 0; +} + diff --git a/examples/temperature_conversion/readme.txt b/examples/temperature_conversion/readme.txt new file mode 100644 index 0000000000..a706a7cc11 --- /dev/null +++ b/examples/temperature_conversion/readme.txt @@ -0,0 +1,6 @@ +This is a simple example of the C++ library mosquittopp. + +It is a client that subscribes to the topic temperature/celsius which should +have temperature data in text form being published to it. It reads this data as +a Celsius temperature, converts to Farenheit and republishes on +temperature/farenheit. diff --git a/examples/temperature_conversion/temperature_conversion.cpp b/examples/temperature_conversion/temperature_conversion.cpp new file mode 100644 index 0000000000..aa5b067707 --- /dev/null +++ b/examples/temperature_conversion/temperature_conversion.cpp @@ -0,0 +1,45 @@ +#include +#include + +#include "temperature_conversion.h" +#include + +mqtt_tempconv::mqtt_tempconv(const char *id, const char *host, int port) : mosquittopp(id) +{ + int keepalive = 60; + + /* Connect immediately. This could also be done by calling + * mqtt_tempconv->connect(). */ + connect(host, port, keepalive); +}; + +void mqtt_tempconv::on_connect(int rc) +{ + printf("Connected with code %d.\n", rc); + if(rc == 0){ + /* Only attempt to subscribe on a successful connect. */ + subscribe(NULL, "temperature/celsius"); + } +} + +void mqtt_tempconv::on_message(const struct mosquitto_message *message) +{ + double temp_celsius, temp_farenheit; + char buf[51]; + + if(!strcmp(message->topic, "temperature/celsius")){ + memset(buf, 0, 51*sizeof(char)); + /* Copy N-1 bytes to ensure always 0 terminated. */ + memcpy(buf, message->payload, 50*sizeof(char)); + temp_celsius = atof(buf); + temp_farenheit = temp_celsius*9.0/5.0 + 32.0; + snprintf(buf, 50, "%f", temp_farenheit); + publish(NULL, "temperature/farenheit", strlen(buf), buf); + } +} + +void mqtt_tempconv::on_subscribe(int mid, int qos_count, const int *granted_qos) +{ + printf("Subscription succeeded.\n"); +} + diff --git a/examples/temperature_conversion/temperature_conversion.h b/examples/temperature_conversion/temperature_conversion.h new file mode 100644 index 0000000000..4d9951fce9 --- /dev/null +++ b/examples/temperature_conversion/temperature_conversion.h @@ -0,0 +1,17 @@ +#ifndef TEMPERATURE_CONVERSION_H +#define TEMPERATURE_CONVERSION_H + +#include + +class mqtt_tempconv : public mosqpp::mosquittopp +{ + public: + mqtt_tempconv(const char *id, const char *host, int port); + ~mqtt_tempconv(); + + void on_connect(int rc); + void on_message(const struct mosquitto_message *message); + void on_subscribe(int mid, int qos_count, const int *granted_qos); +}; + +#endif diff --git a/installer/mosquitto-cygwin.nsi b/installer/mosquitto-cygwin.nsi new file mode 100644 index 0000000000..c79c3cd25f --- /dev/null +++ b/installer/mosquitto-cygwin.nsi @@ -0,0 +1,144 @@ +; NSIS installer script for mosquitto + +!include "MUI.nsh" + +; For environment variable code +!include "WinMessages.nsh" +!define env_hklm 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"' + +Name "mosquitto" +!define VERSION 1.3.1 +OutFile "mosquitto-${VERSION}-install-cygwin.exe" + +InstallDir "$PROGRAMFILES\mosquitto" + +;-------------------------------- +; Installer pages +!insertmacro MUI_PAGE_WELCOME +!insertmacro MUI_PAGE_COMPONENTS +!insertmacro MUI_PAGE_DIRECTORY +!insertmacro MUI_PAGE_INSTFILES +!insertmacro MUI_PAGE_FINISH + + +;-------------------------------- +; Uninstaller pages +!insertmacro MUI_UNPAGE_WELCOME +!insertmacro MUI_UNPAGE_CONFIRM +!insertmacro MUI_UNPAGE_INSTFILES +!insertmacro MUI_UNPAGE_FINISH + +;-------------------------------- +; Languages + +!insertmacro MUI_LANGUAGE "English" + +;-------------------------------- +; Installer sections + +Section "Files" SecInstall + SectionIn RO + SetOutPath "$INSTDIR" + File "c:\cygwin\bin\cygwin1.dll" + File "c:\cygwin\bin\cyggcc_s-1.dll" + File "c:\cygwin\bin\cygcrypto-1.0.0.dll" + File "c:\cygwin\bin\cygssl-1.0.0.dll" + File "c:\cygwin\bin\cygz.dll" + File "..\src\mosquitto.exe" + File "..\build\src\Release\mosquitto_passwd.exe" + File "..\build\client\Release\mosquitto_pub.exe" + File "..\build\client\Release\mosquitto_sub.exe" + File "..\build\lib\Release\mosquitto.dll" + File "..\build\lib\cpp\Release\mosquittopp.dll" + File "..\aclfile.example" + File "..\ChangeLog.txt" + File "..\mosquitto.conf" + File "..\pwfile.example" + File "..\readme.txt" + File "..\readme-windows.txt" + File "C:\pthreads\Pre-built.2\dll\x86\pthreadVC2.dll" + File "C:\OpenSSL-Win32\libeay32.dll" + File "C:\OpenSSL-Win32\ssleay32.dll" + File "..\LICENSE.txt" + File "..\LICENSE-3rd-party.txt" + + SetOutPath "$INSTDIR\devel" + File "..\lib\mosquitto.h" + File "..\build\lib\Release\mosquitto.lib" + File "..\lib\cpp\mosquittopp.h" + File "..\build\lib\cpp\Release\mosquittopp.lib" + File "..\src\mosquitto_plugin.h" + + SetOutPath "$INSTDIR\python" + File "..\lib\python\mosquitto.py" + File "..\lib\python\setup.py" + File "..\lib\python\sub.py" + + WriteUninstaller "$INSTDIR\Uninstall.exe" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Mosquitto" "DisplayName" "Mosquitto MQTT broker" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Mosquitto" "UninstallString" "$\"$INSTDIR\Uninstall.exe$\"" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Mosquitto" "QuietUninstallString" "$\"$INSTDIR\Uninstall.exe$\" /S" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Mosquitto" "HelpLink" "http://mosquitto.org/" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Mosquitto" "URLInfoAbout" "http://mosquitto.org/" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Mosquitto" "DisplayVersion" "${VERSION}" + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Mosquitto" "NoModify" "1" + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Mosquitto" "NoRepair" "1" + + WriteRegExpandStr ${env_hklm} MOSQUITTO_DIR $INSTDIR + SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000 +SectionEnd + +Section "Service" SecService + ExecWait '"$INSTDIR\mosquitto.exe" install' +SectionEnd + +Section "Uninstall" + ExecWait '"$INSTDIR\mosquitto.exe" uninstall' + Delete "$INSTDIR\cygwin1.dll" + Delete "$INSTDIR\cyggcc_s-1.dll" + Delete "$INSTDIR\cygcrypto-1.0.0.dll" + Delete "$INSTDIR\cygssl-1.0.0.dll" + Delete "$INSTDIR\cygz.dll" + Delete "$INSTDIR\mosquitto.exe" + Delete "$INSTDIR\mosquitto_passwd.exe" + Delete "$INSTDIR\mosquitto_pub.exe" + Delete "$INSTDIR\mosquitto_sub.exe" + Delete "$INSTDIR\mosquitto.dll" + Delete "$INSTDIR\mosquittopp.dll" + Delete "$INSTDIR\aclfile.example" + Delete "$INSTDIR\ChangeLog.txt" + Delete "$INSTDIR\mosquitto.conf" + Delete "$INSTDIR\pwfile.example" + Delete "$INSTDIR\readme.txt" + Delete "$INSTDIR\readme-windows.txt" + Delete "$INSTDIR\pthreadVC2.dll" + Delete "$INSTDIR\libeay32.dll" + Delete "$INSTDIR\ssleay32.dll" + Delete "$INSTDIR\LICENSE.txt" + Delete "$INSTDIR\LICENSE-3rd-party.txt" + + Delete "$INSTDIR\devel\mosquitto.h" + Delete "$INSTDIR\devel\mosquitto.lib" + Delete "$INSTDIR\devel\mosquittopp.h" + Delete "$INSTDIR\devel\mosquittopp.lib" + Delete "$INSTDIR\devel\mosquitto_plugin.h" + + Delete "$INSTDIR\python\mosquitto.py" + Delete "$INSTDIR\python\setup.py" + Delete "$INSTDIR\python\sub.py" + + Delete "$INSTDIR\Uninstall.exe" + RMDir "$INSTDIR" + DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Mosquitto" + + DeleteRegValue ${env_hklm} MOSQUITTO_DIR + SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000 +SectionEnd + +LangString DESC_SecInstall ${LANG_ENGLISH} "The main installation." +LangString DESC_SecService ${LANG_ENGLISH} "Install mosquitto as a Windows service?" +!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN + !insertmacro MUI_DESCRIPTION_TEXT ${SecInstall} $(DESC_SecInstall) + !insertmacro MUI_DESCRIPTION_TEXT ${SecService} $(DESC_SecService) +!insertmacro MUI_FUNCTION_DESCRIPTION_END + diff --git a/installer/mosquitto.nsi b/installer/mosquitto.nsi new file mode 100644 index 0000000000..30db4e2871 --- /dev/null +++ b/installer/mosquitto.nsi @@ -0,0 +1,134 @@ +; NSIS installer script for mosquitto + +!include "MUI.nsh" + +; For environment variable code +!include "WinMessages.nsh" +!define env_hklm 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"' + +Name "mosquitto" +!define VERSION 1.3.1 +OutFile "mosquitto-${VERSION}-install-win32.exe" + +InstallDir "$PROGRAMFILES\mosquitto" + +;-------------------------------- +; Installer pages +!insertmacro MUI_PAGE_WELCOME +!insertmacro MUI_PAGE_COMPONENTS +!insertmacro MUI_PAGE_DIRECTORY +!insertmacro MUI_PAGE_INSTFILES +!insertmacro MUI_PAGE_FINISH + + +;-------------------------------- +; Uninstaller pages +!insertmacro MUI_UNPAGE_WELCOME +!insertmacro MUI_UNPAGE_CONFIRM +!insertmacro MUI_UNPAGE_INSTFILES +!insertmacro MUI_UNPAGE_FINISH + +;-------------------------------- +; Languages + +!insertmacro MUI_LANGUAGE "English" + +;-------------------------------- +; Installer sections + +Section "Files" SecInstall + SectionIn RO + SetOutPath "$INSTDIR" + File "..\build\src\Release\mosquitto.exe" + File "..\build\src\Release\mosquitto_passwd.exe" + File "..\build\client\Release\mosquitto_pub.exe" + File "..\build\client\Release\mosquitto_sub.exe" + File "..\build\lib\Release\mosquitto.dll" + File "..\build\lib\cpp\Release\mosquittopp.dll" + File "..\aclfile.example" + File "..\ChangeLog.txt" + File "..\mosquitto.conf" + File "..\pwfile.example" + File "..\readme.txt" + File "..\readme-windows.txt" + File "C:\pthreads\Pre-built.2\dll\x86\pthreadVC2.dll" + File "C:\OpenSSL-Win32\libeay32.dll" + File "C:\OpenSSL-Win32\ssleay32.dll" + File "..\LICENSE.txt" + File "..\LICENSE-3rd-party.txt" + + SetOutPath "$INSTDIR\devel" + File "..\lib\mosquitto.h" + File "..\build\lib\Release\mosquitto.lib" + File "..\lib\cpp\mosquittopp.h" + File "..\build\lib\cpp\Release\mosquittopp.lib" + File "..\src\mosquitto_plugin.h" + + SetOutPath "$INSTDIR\python" + File "..\lib\python\mosquitto.py" + File "..\lib\python\setup.py" + File "..\lib\python\sub.py" + + WriteUninstaller "$INSTDIR\Uninstall.exe" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Mosquitto" "DisplayName" "Mosquitto MQTT broker" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Mosquitto" "UninstallString" "$\"$INSTDIR\Uninstall.exe$\"" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Mosquitto" "QuietUninstallString" "$\"$INSTDIR\Uninstall.exe$\" /S" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Mosquitto" "HelpLink" "http://mosquitto.org/" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Mosquitto" "URLInfoAbout" "http://mosquitto.org/" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Mosquitto" "DisplayVersion" "${VERSION}" + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Mosquitto" "NoModify" "1" + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Mosquitto" "NoRepair" "1" + + WriteRegExpandStr ${env_hklm} MOSQUITTO_DIR $INSTDIR + SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000 +SectionEnd + +Section "Service" SecService + ExecWait '"$INSTDIR\mosquitto.exe" install' +SectionEnd + +Section "Uninstall" + ExecWait '"$INSTDIR\mosquitto.exe" uninstall' + Delete "$INSTDIR\mosquitto.exe" + Delete "$INSTDIR\mosquitto_passwd.exe" + Delete "$INSTDIR\mosquitto_pub.exe" + Delete "$INSTDIR\mosquitto_sub.exe" + Delete "$INSTDIR\mosquitto.dll" + Delete "$INSTDIR\mosquittopp.dll" + Delete "$INSTDIR\aclfile.example" + Delete "$INSTDIR\ChangeLog.txt" + Delete "$INSTDIR\mosquitto.conf" + Delete "$INSTDIR\pwfile.example" + Delete "$INSTDIR\readme.txt" + Delete "$INSTDIR\readme-windows.txt" + Delete "$INSTDIR\pthreadVC2.dll" + Delete "$INSTDIR\libeay32.dll" + Delete "$INSTDIR\ssleay32.dll" + Delete "$INSTDIR\LICENSE.txt" + Delete "$INSTDIR\LICENSE-3rd-party.txt" + + Delete "$INSTDIR\devel\mosquitto.h" + Delete "$INSTDIR\devel\mosquitto.lib" + Delete "$INSTDIR\devel\mosquittopp.h" + Delete "$INSTDIR\devel\mosquittopp.lib" + Delete "$INSTDIR\devel\mosquitto_plugin.h" + + Delete "$INSTDIR\python\mosquitto.py" + Delete "$INSTDIR\python\setup.py" + Delete "$INSTDIR\python\sub.py" + + Delete "$INSTDIR\Uninstall.exe" + RMDir "$INSTDIR" + DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Mosquitto" + + DeleteRegValue ${env_hklm} MOSQUITTO_DIR + SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000 +SectionEnd + +LangString DESC_SecInstall ${LANG_ENGLISH} "The main installation." +LangString DESC_SecService ${LANG_ENGLISH} "Install mosquitto as a Windows service?" +!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN + !insertmacro MUI_DESCRIPTION_TEXT ${SecInstall} $(DESC_SecInstall) + !insertmacro MUI_DESCRIPTION_TEXT ${SecService} $(DESC_SecService) +!insertmacro MUI_FUNCTION_DESCRIPTION_END + diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt new file mode 100644 index 0000000000..2ae6f6a6a5 --- /dev/null +++ b/lib/CMakeLists.txt @@ -0,0 +1,71 @@ +add_subdirectory(cpp) + +option(WITH_THREADING "Include client library threading support?" ON) +if (${WITH_THREADING} STREQUAL ON) + add_definitions("-DWITH_THREADING") + if (WIN32) + set (PTHREAD_LIBRARIES C:\\pthreads\\Pre-built.2\\lib\\x86\\pthreadVC2.lib) + set (PTHREAD_INCLUDE_DIR C:\\pthreads\\Pre-built.2\\include) + else (WIN32) + set (PTHREAD_LIBRARIES pthread) + set (PTHREAD_INCLUDE_DIR "") + endif (WIN32) +else (${WITH_THREADING} STREQUAL ON) + set (PTHREAD_LIBRARIES "") + set (PTHREAD_INCLUDE_DIR "") +endif (${WITH_THREADING} STREQUAL ON) + +include_directories(${mosquitto_SOURCE_DIR} ${mosquitto_SOURCE_DIR}/lib + ${STDBOOL_H_PATH} ${STDINT_H_PATH} + ${OPENSSL_INCLUDE_DIR} ${PTHREAD_INCLUDE_DIR}) +link_directories(${mosquitto_SOURCE_DIR}/lib) + +add_library(libmosquitto SHARED + logging_mosq.c logging_mosq.h + memory_mosq.c memory_mosq.h + messages_mosq.c messages_mosq.h + mosquitto.c mosquitto.h + mosquitto_internal.h + mqtt3_protocol.h + net_mosq.c net_mosq.h + read_handle.c read_handle.h + read_handle_client.c + read_handle_shared.c + send_client_mosq.c + send_mosq.c send_mosq.h + srv_mosq.c + thread_mosq.c + time_mosq.c + tls_mosq.c + util_mosq.c util_mosq.h + will_mosq.c will_mosq.h) + +set (LIBRARIES ${OPENSSL_LIBRARIES} ${PTHREAD_LIBRARIES}) + +if (UNIX AND NOT APPLE) + set (LIBRARIES ${LIBRARIES} rt) +endif (UNIX AND NOT APPLE) + +if (WIN32) + set (LIBRARIES ${LIBRARIES} ws2_32) +endif (WIN32) + +option(WITH_SRV "Include SRV lookup support?" ON) +if (${WITH_SRV} STREQUAL ON) + set (LIBRARIES ${LIBRARIES} cares) +endif (${WITH_SRV} STREQUAL ON) + +target_link_libraries(libmosquitto ${LIBRARIES}) + +set_target_properties(libmosquitto PROPERTIES + OUTPUT_NAME mosquitto + VERSION ${VERSION} + SOVERSION 1 +) + +install(TARGETS libmosquitto RUNTIME DESTINATION ${BINDIR} LIBRARY DESTINATION ${LIBDIR}) +install(FILES mosquitto.h DESTINATION ${INCLUDEDIR}) + +if (UNIX) + install(CODE "EXEC_PROGRAM(/sbin/ldconfig)") +endif (UNIX) diff --git a/lib/Makefile b/lib/Makefile new file mode 100644 index 0000000000..518d45663c --- /dev/null +++ b/lib/Makefile @@ -0,0 +1,96 @@ +include ../config.mk + +.PHONY : really clean install + +MOSQ_OBJS=mosquitto.o \ + logging_mosq.o \ + memory_mosq.o \ + messages_mosq.o \ + net_mosq.o \ + read_handle.o \ + read_handle_client.o \ + read_handle_shared.o \ + send_mosq.o \ + send_client_mosq.o \ + srv_mosq.o \ + thread_mosq.o \ + time_mosq.o \ + tls_mosq.o \ + util_mosq.o \ + will_mosq.o + +all : libmosquitto.so.${SOVERSION} libmosquitto.a + $(MAKE) -C cpp + +install : all + $(INSTALL) -d ${DESTDIR}$(prefix)/lib${LIB_SUFFIX}/ + $(INSTALL) -s libmosquitto.so.${SOVERSION} ${DESTDIR}${prefix}/lib${LIB_SUFFIX}/libmosquitto.so.${SOVERSION} + ln -sf libmosquitto.so.${SOVERSION} ${DESTDIR}${prefix}/lib${LIB_SUFFIX}/libmosquitto.so + $(INSTALL) -d ${DESTDIR}${prefix}/include/ + $(INSTALL) mosquitto.h ${DESTDIR}${prefix}/include/mosquitto.h + $(MAKE) -C cpp install + +uninstall : + -rm -f ${DESTDIR}${prefix}/lib${LIB_SUFFIX}/libmosquitto.so.${SOVERSION} + -rm -f ${DESTDIR}${prefix}/lib${LIB_SUFFIX}/libmosquitto.so + -rm -f ${DESTDIR}${prefix}/include/mosquitto.h + +reallyclean : clean + +clean : + -rm -f *.o libmosquitto.so.${SOVERSION} libmosquitto.so libmosquitto.a + $(MAKE) -C cpp clean + +libmosquitto.so.${SOVERSION} : ${MOSQ_OBJS} + $(CC) -shared $(LIB_LDFLAGS) $^ -o $@ ${LIB_LIBS} + +libmosquitto.a : ${MOSQ_OBJS} + $(AR) cr $@ $^ +mosquitto.o : mosquitto.c mosquitto.h + $(CC) $(LIB_CFLAGS) -c $< -o $@ + +logging_mosq.o : logging_mosq.c logging_mosq.h + $(CC) $(LIB_CFLAGS) -c $< -o $@ + +messages_mosq.o : messages_mosq.c messages_mosq.h + $(CC) $(LIB_CFLAGS) -c $< -o $@ + +memory_mosq.o : memory_mosq.c memory_mosq.h + $(CC) $(LIB_CFLAGS) -c $< -o $@ + +net_mosq.o : net_mosq.c net_mosq.h + $(CC) $(LIB_CFLAGS) -c $< -o $@ + +read_handle.o : read_handle.c read_handle.h + $(CC) $(LIB_CFLAGS) -c $< -o $@ + +read_handle_client.o : read_handle_client.c read_handle.h + $(CC) $(LIB_CFLAGS) -c $< -o $@ + +read_handle_shared.o : read_handle_shared.c read_handle.h + $(CC) $(LIB_CFLAGS) -c $< -o $@ + +send_mosq.o : send_mosq.c send_mosq.h + $(CC) $(LIB_CFLAGS) -c $< -o $@ + +send_client_mosq.o : send_client_mosq.c send_mosq.h + $(CC) $(LIB_CFLAGS) -c $< -o $@ + +srv_mosq.o : srv_mosq.c + $(CC) $(LIB_CFLAGS) -c $< -o $@ + +thread_mosq.o : thread_mosq.c + $(CC) $(LIB_CFLAGS) -c $< -o $@ + +time_mosq.o : time_mosq.c + $(CC) $(LIB_CFLAGS) -c $< -o $@ + +tls_mosq.o : tls_mosq.c + $(CC) $(LIB_CFLAGS) -c $< -o $@ + +util_mosq.o : util_mosq.c util_mosq.h + $(CC) $(LIB_CFLAGS) -c $< -o $@ + +will_mosq.o : will_mosq.c will_mosq.h + $(CC) $(LIB_CFLAGS) -c $< -o $@ + diff --git a/lib/cpp/CMakeLists.txt b/lib/cpp/CMakeLists.txt new file mode 100644 index 0000000000..68b1453a89 --- /dev/null +++ b/lib/cpp/CMakeLists.txt @@ -0,0 +1,18 @@ +include_directories(${mosquitto_SOURCE_DIR}/lib ${mosquitto_SOURCE_DIR}/lib/cpp + ${STDBOOL_H_PATH} ${STDINT_H_PATH}) +link_directories(${mosquitto_BINARY_DIR}/lib) + +add_library(mosquittopp SHARED + mosquittopp.cpp mosquittopp.h) + +target_link_libraries(mosquittopp mosquitto) +set_target_properties(mosquittopp PROPERTIES + VERSION ${VERSION} + SOVERSION 1 +) +install(TARGETS mosquittopp RUNTIME DESTINATION ${BINDIR} LIBRARY DESTINATION ${LIBDIR}) +install(FILES mosquittopp.h DESTINATION ${INCLUDEDIR}) + +if (UNIX) + install(CODE "EXEC_PROGRAM(/sbin/ldconfig)") +endif (UNIX) diff --git a/lib/cpp/Makefile b/lib/cpp/Makefile new file mode 100644 index 0000000000..9dc8d8099c --- /dev/null +++ b/lib/cpp/Makefile @@ -0,0 +1,31 @@ +include ../../config.mk + +ifneq ($(UNAME),SunOS) + LIB_LDFLAGS:=$(LDFLAGS) -Wl,-soname,libmosquittopp.so.${SOVERSION} +endif + +.PHONY : clean install + +all : libmosquittopp.so.${SOVERSION} + +install : all + $(INSTALL) -d ${DESTDIR}$(prefix)/lib${LIB_SUFFIX}/ + $(INSTALL) -s libmosquittopp.so.${SOVERSION} ${DESTDIR}${prefix}/lib${LIB_SUFFIX}/libmosquittopp.so.${SOVERSION} + ln -sf libmosquittopp.so.${SOVERSION} ${DESTDIR}${prefix}/lib${LIB_SUFFIX}/libmosquittopp.so + $(INSTALL) -d ${DESTDIR}${prefix}/include/ + $(INSTALL) mosquittopp.h ${DESTDIR}${prefix}/include/mosquittopp.h + +uninstall : + -rm -f ${DESTDIR}${prefix}/lib${LIB_SUFFIX}/libmosquittopp.so.${SOVERSION} + -rm -f ${DESTDIR}${prefix}/lib${LIB_SUFFIX}/libmosquittopp.so + -rm -f ${DESTDIR}${prefix}/include/mosquittopp.h + +clean : + -rm -f *.o libmosquittopp.so.${SOVERSION} + +libmosquittopp.so.${SOVERSION} : mosquittopp.o + $(CXX) -shared $(LIB_LDFLAGS) $< -o $@ ../libmosquitto.so.${SOVERSION} + +mosquittopp.o : mosquittopp.cpp mosquittopp.h + $(CXX) $(LIB_CXXFLAGS) -c $< -o $@ + diff --git a/lib/cpp/mosquittopp.cpp b/lib/cpp/mosquittopp.cpp new file mode 100644 index 0000000000..349ef055f6 --- /dev/null +++ b/lib/cpp/mosquittopp.cpp @@ -0,0 +1,292 @@ +/* +Copyright (c) 2010-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#include +#include +#include + +namespace mosqpp { + +static void on_connect_wrapper(struct mosquitto *mosq, void *userdata, int rc) +{ + class mosquittopp *m = (class mosquittopp *)userdata; + m->on_connect(rc); +} + +static void on_disconnect_wrapper(struct mosquitto *mosq, void *userdata, int rc) +{ + class mosquittopp *m = (class mosquittopp *)userdata; + m->on_disconnect(rc); +} + +static void on_publish_wrapper(struct mosquitto *mosq, void *userdata, int mid) +{ + class mosquittopp *m = (class mosquittopp *)userdata; + m->on_publish(mid); +} + +static void on_message_wrapper(struct mosquitto *mosq, void *userdata, const struct mosquitto_message *message) +{ + class mosquittopp *m = (class mosquittopp *)userdata; + m->on_message(message); +} + +static void on_subscribe_wrapper(struct mosquitto *mosq, void *userdata, int mid, int qos_count, const int *granted_qos) +{ + class mosquittopp *m = (class mosquittopp *)userdata; + m->on_subscribe(mid, qos_count, granted_qos); +} + +static void on_unsubscribe_wrapper(struct mosquitto *mosq, void *userdata, int mid) +{ + class mosquittopp *m = (class mosquittopp *)userdata; + m->on_unsubscribe(mid); +} + + +static void on_log_wrapper(struct mosquitto *mosq, void *userdata, int level, const char *str) +{ + class mosquittopp *m = (class mosquittopp *)userdata; + m->on_log(level, str); +} + +int lib_version(int *major, int *minor, int *revision) +{ + if(major) *major = LIBMOSQUITTO_MAJOR; + if(minor) *minor = LIBMOSQUITTO_MINOR; + if(revision) *revision = LIBMOSQUITTO_REVISION; + return LIBMOSQUITTO_VERSION_NUMBER; +} + +int lib_init() +{ + return mosquitto_lib_init(); +} + +int lib_cleanup() +{ + return mosquitto_lib_cleanup(); +} + +const char* strerror(int mosq_errno) +{ + return mosquitto_strerror(mosq_errno); +} + +const char* connack_string(int connack_code) +{ + return mosquitto_connack_string(connack_code); +} + +int sub_topic_tokenise(const char *subtopic, char ***topics, int *count) +{ + return mosquitto_sub_topic_tokenise(subtopic, topics, count); +} + +int sub_topic_tokens_free(char ***topics, int count) +{ + return mosquitto_sub_topic_tokens_free(topics, count); +} + +int topic_matches_sub(const char *sub, const char *topic, bool *result) +{ + return mosquitto_topic_matches_sub(sub, topic, result); +} + +mosquittopp::mosquittopp(const char *id, bool clean_session) +{ + m_mosq = mosquitto_new(id, clean_session, this); + mosquitto_connect_callback_set(m_mosq, on_connect_wrapper); + mosquitto_disconnect_callback_set(m_mosq, on_disconnect_wrapper); + mosquitto_publish_callback_set(m_mosq, on_publish_wrapper); + mosquitto_message_callback_set(m_mosq, on_message_wrapper); + mosquitto_subscribe_callback_set(m_mosq, on_subscribe_wrapper); + mosquitto_unsubscribe_callback_set(m_mosq, on_unsubscribe_wrapper); + mosquitto_log_callback_set(m_mosq, on_log_wrapper); +} + +mosquittopp::~mosquittopp() +{ + mosquitto_destroy(m_mosq); +} + +int mosquittopp::reinitialise(const char *id, bool clean_session) +{ + int rc; + rc = mosquitto_reinitialise(m_mosq, id, clean_session, this); + if(rc == MOSQ_ERR_SUCCESS){ + mosquitto_connect_callback_set(m_mosq, on_connect_wrapper); + mosquitto_disconnect_callback_set(m_mosq, on_disconnect_wrapper); + mosquitto_publish_callback_set(m_mosq, on_publish_wrapper); + mosquitto_message_callback_set(m_mosq, on_message_wrapper); + mosquitto_subscribe_callback_set(m_mosq, on_subscribe_wrapper); + mosquitto_unsubscribe_callback_set(m_mosq, on_unsubscribe_wrapper); + mosquitto_log_callback_set(m_mosq, on_log_wrapper); + } + return rc; +} + +int mosquittopp::connect(const char *host, int port, int keepalive) +{ + return mosquitto_connect(m_mosq, host, port, keepalive); +} + +int mosquittopp::connect(const char *host, int port, int keepalive, const char *bind_address) +{ + return mosquitto_connect_bind(m_mosq, host, port, keepalive, bind_address); +} + +int mosquittopp::connect_async(const char *host, int port, int keepalive) +{ + return mosquitto_connect_async(m_mosq, host, port, keepalive); +} + +int mosquittopp::connect_async(const char *host, int port, int keepalive, const char *bind_address) +{ + return mosquitto_connect_bind_async(m_mosq, host, port, keepalive, bind_address); +} + +int mosquittopp::reconnect() +{ + return mosquitto_reconnect(m_mosq); +} + +int mosquittopp::reconnect_async() +{ + return mosquitto_reconnect_async(m_mosq); +} + +int mosquittopp::disconnect() +{ + return mosquitto_disconnect(m_mosq); +} + +int mosquittopp::socket() +{ + return mosquitto_socket(m_mosq); +} + +int mosquittopp::will_set(const char *topic, int payloadlen, const void *payload, int qos, bool retain) +{ + return mosquitto_will_set(m_mosq, topic, payloadlen, payload, qos, retain); +} + +int mosquittopp::will_clear() +{ + return mosquitto_will_clear(m_mosq); +} + +int mosquittopp::username_pw_set(const char *username, const char *password) +{ + return mosquitto_username_pw_set(m_mosq, username, password); +} + +int mosquittopp::publish(int *mid, const char *topic, int payloadlen, const void *payload, int qos, bool retain) +{ + return mosquitto_publish(m_mosq, mid, topic, payloadlen, payload, qos, retain); +} + +void mosquittopp::reconnect_delay_set(unsigned int reconnect_delay, unsigned int reconnect_delay_max, bool reconnect_exponential_backoff) +{ + mosquitto_reconnect_delay_set(m_mosq, reconnect_delay, reconnect_delay_max, reconnect_exponential_backoff); +} + +int mosquittopp::max_inflight_messages_set(unsigned int max_inflight_messages) +{ + return mosquitto_max_inflight_messages_set(m_mosq, max_inflight_messages); +} + +void mosquittopp::message_retry_set(unsigned int message_retry) +{ + mosquitto_message_retry_set(m_mosq, message_retry); +} + +int mosquittopp::subscribe(int *mid, const char *sub, int qos) +{ + return mosquitto_subscribe(m_mosq, mid, sub, qos); +} + +int mosquittopp::unsubscribe(int *mid, const char *sub) +{ + return mosquitto_unsubscribe(m_mosq, mid, sub); +} + +int mosquittopp::loop(int timeout, int max_packets) +{ + return mosquitto_loop(m_mosq, timeout, max_packets); +} + +int mosquittopp::loop_misc() +{ + return mosquitto_loop_misc(m_mosq); +} + +int mosquittopp::loop_read(int max_packets) +{ + return mosquitto_loop_read(m_mosq, max_packets); +} + +int mosquittopp::loop_write(int max_packets) +{ + return mosquitto_loop_write(m_mosq, max_packets); +} + +int mosquittopp::loop_forever(int timeout, int max_packets) +{ + return mosquitto_loop_forever(m_mosq, timeout, max_packets); +} + +int mosquittopp::loop_start() +{ + return mosquitto_loop_start(m_mosq); +} + +int mosquittopp::loop_stop(bool force) +{ + return mosquitto_loop_stop(m_mosq, force); +} + +bool mosquittopp::want_write() +{ + return mosquitto_want_write(m_mosq); +} + +void mosquittopp::user_data_set(void *userdata) +{ + mosquitto_user_data_set(m_mosq, userdata); +} + +int mosquittopp::tls_set(const char *cafile, const char *capath, const char *certfile, const char *keyfile, int (*pw_callback)(char *buf, int size, int rwflag, void *userdata)) +{ + return mosquitto_tls_set(m_mosq, cafile, capath, certfile, keyfile, pw_callback); +} + +int mosquittopp::tls_opts_set(int cert_reqs, const char *tls_version, const char *ciphers) +{ + return mosquitto_tls_opts_set(m_mosq, cert_reqs, tls_version, ciphers); +} + +int mosquittopp::tls_insecure_set(bool value) +{ + return mosquitto_tls_insecure_set(m_mosq, value); +} + +int mosquittopp::tls_psk_set(const char *psk, const char *identity, const char *ciphers) +{ + return mosquitto_tls_psk_set(m_mosq, psk, identity, ciphers); +} + +} diff --git a/lib/cpp/mosquittopp.h b/lib/cpp/mosquittopp.h new file mode 100644 index 0000000000..ef02923d1e --- /dev/null +++ b/lib/cpp/mosquittopp.h @@ -0,0 +1,102 @@ +/* +Copyright (c) 2010-2013 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#ifndef _MOSQUITTOPP_H_ +#define _MOSQUITTOPP_H_ + +#ifdef _WIN32 +# ifdef mosquittopp_EXPORTS +# define mosqpp_EXPORT __declspec(dllexport) +# else +# define mosqpp_EXPORT __declspec(dllimport) +# endif +#else +# define mosqpp_EXPORT +#endif + +#include +#include +#include + +namespace mosqpp { + +mosqpp_EXPORT const char *strerror(int mosq_errno); +mosqpp_EXPORT const char *connack_string(int connack_code); +mosqpp_EXPORT int sub_topic_tokenise(const char *subtopic, char ***topics, int *count); +mosqpp_EXPORT int sub_topic_tokens_free(char ***topics, int count); +mosqpp_EXPORT int lib_version(int *major, int *minor, int *revision); +mosqpp_EXPORT int lib_init(); +mosqpp_EXPORT int lib_cleanup(); +mosqpp_EXPORT int topic_matches_sub(const char *sub, const char *topic, bool *result); + +/* + * Class: mosquittopp + * + * A mosquitto client class. This is a C++ wrapper class for the mosquitto C + * library. Please see mosquitto.h for details of the functions. + */ +class mosqpp_EXPORT mosquittopp { + private: + struct mosquitto *m_mosq; + public: + mosquittopp(const char *id=NULL, bool clean_session=true); + ~mosquittopp(); + + int reinitialise(const char *id, bool clean_session); + int socket(); + int will_set(const char *topic, int payloadlen=0, const void *payload=NULL, int qos=0, bool retain=false); + int will_clear(); + int username_pw_set(const char *username, const char *password=NULL); + int connect(const char *host, int port=1883, int keepalive=60); + int connect_async(const char *host, int port=1883, int keepalive=60); + int connect(const char *host, int port, int keepalive, const char *bind_address); + int connect_async(const char *host, int port, int keepalive, const char *bind_address); + int reconnect(); + int reconnect_async(); + int disconnect(); + int publish(int *mid, const char *topic, int payloadlen=0, const void *payload=NULL, int qos=0, bool retain=false); + int subscribe(int *mid, const char *sub, int qos=0); + int unsubscribe(int *mid, const char *sub); + void reconnect_delay_set(unsigned int reconnect_delay, unsigned int reconnect_delay_max, bool reconnect_exponential_backoff); + int max_inflight_messages_set(unsigned int max_inflight_messages); + void message_retry_set(unsigned int message_retry); + void user_data_set(void *userdata); + int tls_set(const char *cafile, const char *capath=NULL, const char *certfile=NULL, const char *keyfile=NULL, int (*pw_callback)(char *buf, int size, int rwflag, void *userdata)=NULL); + int tls_opts_set(int cert_reqs, const char *tls_version=NULL, const char *ciphers=NULL); + int tls_insecure_set(bool value); + int tls_psk_set(const char *psk, const char *identity, const char *ciphers=NULL); + + int loop(int timeout=-1, int max_packets=1); + int loop_misc(); + int loop_read(int max_packets=1); + int loop_write(int max_packets=1); + int loop_forever(int timeout=-1, int max_packets=1); + int loop_start(); + int loop_stop(bool force=false); + bool want_write(); + + virtual void on_connect(int rc) {return;}; + virtual void on_disconnect(int rc) {return;}; + virtual void on_publish(int mid) {return;}; + virtual void on_message(const struct mosquitto_message *message) {return;}; + virtual void on_subscribe(int mid, int qos_count, const int *granted_qos) {return;}; + virtual void on_unsubscribe(int mid) {return;}; + virtual void on_log(int level, const char *str) {return;}; + virtual void on_error() {return;}; +}; + +} +#endif diff --git a/lib/dummypthread.h b/lib/dummypthread.h new file mode 100644 index 0000000000..31f3251e28 --- /dev/null +++ b/lib/dummypthread.h @@ -0,0 +1,13 @@ +#ifndef _DUMMYPTHREAD_H_ +#define _DUMMYPTHREAD_H_ + +#define pthread_create(A, B, C, D) +#define pthread_join(A, B) +#define pthread_cancel(A) + +#define pthread_mutex_init(A, B) +#define pthread_mutex_destroy(A) +#define pthread_mutex_lock(A) +#define pthread_mutex_unlock(A) + +#endif diff --git a/lib/jsws/mosquitto.js b/lib/jsws/mosquitto.js new file mode 100644 index 0000000000..1eb4fc881d --- /dev/null +++ b/lib/jsws/mosquitto.js @@ -0,0 +1,363 @@ +/* +Copyright (c) 2012 Roger Light +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of mosquitto nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*/ + +/* Mosquitto MQTT Javascript/Websocket client */ +/* Provides complete support for QoS 0. + * Will not cause an error on QoS 1/2 packets. + */ + +var CONNECT = 0x10; +var CONNACK = 0x20; +var PUBLISH = 0x30; +var PUBACK = 0x40; +var PUBREC = 0x50; +var PUBREL = 0x60; +var PUBCOMP = 0x70; +var SUBSCRIBE = 0x80; +var SUBACK = 0x90; +var UNSUBSCRIBE = 0xA0; +var UNSUBACK = 0xB0; +var PINGREQ = 0xC0; +var PINGRESP = 0xD0; +var DISCONNECT = 0xE0; + +function AB2S(buffer) { + var binary = ''; + var bytes = new Uint8Array(buffer); + var len = bytes.byteLength; + for(var i=0; i 0){ + this.send(this.m.out_queue.pop()); + } + setTimeout(function(_this){_this.mqtt_ping();}, 60000, this.m); + }, + + ws_onclose : function(evt) { + if(this.m.ondisconnect){ + this.m.ondisconnect(evt.data); + } + }, + + ws_onmessage : function(evt) { + var i8V = new Int8Array(evt.data); + buffer = evt.data; + var q=0; + while(i8V.length > 0 && q < 1000){ + q++; + switch(i8V[0] & 0xF0){ + case CONNACK: + var rl = i8V[1]; + var rc = i8V[2]; + if(this.m.onconnect){ + this.m.onconnect(rc); + } + buffer = buffer.slice(rl+2); + i8V = new Int8Array(buffer); + break; + case PUBLISH: + var i=1; + var mult = 1; + var rl = 0; + var count = 0; + var digit; + var qos = (i8V[0] & 0x06) >> 1; + var retain = (i8V[0] & 0x01); + var mid = 0; + do{ + count++; + digit = i8V[i++]; + rl += (digit & 127)*mult; + mult *= 128; + }while((digit & 128) != 0); + + var topiclen = i8V[i++]*256 + i8V[i++]; + var atopic = buffer.slice(i, i+topiclen); + i+=topiclen; + var topic = AB2S(atopic); + if(qos > 0){ + mid = i8V[i++]*256 + i8V[i++]; + } + var apayload = buffer.slice(i, rl+count+1); + var payload = AB2S(apayload); + + buffer = buffer.slice(rl+1+count); + i8V = new Int8Array(buffer); + + if(this.m.onmessage){ + this.m.onmessage(topic, payload, qos, retain); + } + break; + case PUBREC: + case PUBREL: + case PUBACK: + case PUBCOMP: + case SUBACK: + case UNSUBACK: + case PINGRESP: + var rl = i8V[1]; + buffer = buffer.slice(rl+2); + i8V = new Int8Array(buffer); + break; + } + } + }, + + get_remaining_count : function(remaining_length) + { + if(remaining_length >= 0 && remaining_length < 128){ + return 1; + }else if(remaining_length >= 128 && remaining_length < 16384){ + return 2; + }else if(remaining_length >= 16384 && remaining_length < 2097152){ + return 3; + }else if(remaining_length >= 2097152 && remaining_length < 268435456){ + return 4; + }else{ + return -1; + } + }, + + generate_mid : function() + { + var mid = this.mid; + this.mid++; + if(this.mid == 256) this.mid = 0; + return mid; + }, + + queue : function(buffer) + { + this.out_queue.push(buffer); + }, + + send_cmd_with_mid : function(cmd, mid) + { + var buffer = new ArrayBuffer(4); + var i8V = new Int8Array(buffer); + i8V[0] = cmd; + i8V[1] = 2; + i8V[2] = mid%128; + i8V[3] = mid/128; + if(this.ws.readyState == 1){ + this.ws.send(buffer); + }else{ + this.queue(buffer); + } + }, + + unsubscribe : function(topic) + { + var rl = 2+2+topic.length; + var remaining_count = this.get_remaining_count(rl); + var buffer = new ArrayBuffer(1+remaining_count+rl); + var i8V = new Int8Array(buffer); + + var i=0; + i8V[i++] = UNSUBSCRIBE | 0x02; + do{ + digit = Math.floor(rl % 128); + rl = Math.floor(rl / 128); + if(rl > 0){ + digit = digit | 0x80; + } + i8V[i++] = digit; + }while(rl > 0); + i8V[i++] = 0; + i8V[i++] = this.generate_mid(); + i8V[i++] = 0; + i8V[i++] = topic.length; + for(var j=0; j 0){ + digit = digit | 0x80; + } + i8V[i++] = digit; + }while(rl > 0); + i8V[i++] = 0; + i8V[i++] = this.generate_mid(); + i8V[i++] = 0; + i8V[i++] = topic.length; + for(var j=0; j 0){ + digit = digit | 0x80; + } + i8V[i++] = digit; + }while(rl > 0); + i8V[i++] = 0; + i8V[i++] = topic.length; + for(var j=0; j + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ +#include +#include +#include +#include + +#include +#include +#include + +int _mosquitto_log_printf(struct mosquitto *mosq, int priority, const char *fmt, ...) +{ + va_list va; + char *s; + int len; + + assert(mosq); + assert(fmt); + + pthread_mutex_lock(&mosq->log_callback_mutex); + if(mosq->on_log){ + len = strlen(fmt) + 500; + s = _mosquitto_malloc(len*sizeof(char)); + if(!s){ + pthread_mutex_unlock(&mosq->log_callback_mutex); + return MOSQ_ERR_NOMEM; + } + + va_start(va, fmt); + vsnprintf(s, len, fmt, va); + va_end(va); + s[len-1] = '\0'; /* Ensure string is null terminated. */ + + mosq->on_log(mosq, mosq->userdata, priority, s); + + _mosquitto_free(s); + } + pthread_mutex_unlock(&mosq->log_callback_mutex); + + return MOSQ_ERR_SUCCESS; +} + diff --git a/lib/logging_mosq.h b/lib/logging_mosq.h new file mode 100644 index 0000000000..3430fc2589 --- /dev/null +++ b/lib/logging_mosq.h @@ -0,0 +1,23 @@ +/* +Copyright (c) 2009-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ +#ifndef _LOGGING_MOSQ_H_ +#define _LOGGING_MOSQ_H_ + +#include + +int _mosquitto_log_printf(struct mosquitto *mosq, int priority, const char *fmt, ...); + +#endif diff --git a/lib/memory_mosq.c b/lib/memory_mosq.c new file mode 100644 index 0000000000..1b7e61b345 --- /dev/null +++ b/lib/memory_mosq.c @@ -0,0 +1,121 @@ +/* +Copyright (c) 2009-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#include + +#include +#include + +#include + +#ifdef REAL_WITH_MEMORY_TRACKING +# if defined(__APPLE__) +# include +# define malloc_usable_size malloc_size +# elif defined(__FreeBSD__) +# include +# else +# include +# endif +#endif + +#ifdef REAL_WITH_MEMORY_TRACKING +static unsigned long memcount = 0; +static unsigned long max_memcount = 0; +#endif + +void *_mosquitto_calloc(size_t nmemb, size_t size) +{ + void *mem = calloc(nmemb, size); + +#ifdef REAL_WITH_MEMORY_TRACKING + memcount += malloc_usable_size(mem); + if(memcount > max_memcount){ + max_memcount = memcount; + } +#endif + + return mem; +} + +void _mosquitto_free(void *mem) +{ +#ifdef REAL_WITH_MEMORY_TRACKING + memcount -= malloc_usable_size(mem); +#endif + free(mem); +} + +void *_mosquitto_malloc(size_t size) +{ + void *mem = malloc(size); + +#ifdef REAL_WITH_MEMORY_TRACKING + memcount += malloc_usable_size(mem); + if(memcount > max_memcount){ + max_memcount = memcount; + } +#endif + + return mem; +} + +#ifdef REAL_WITH_MEMORY_TRACKING +unsigned long _mosquitto_memory_used(void) +{ + return memcount; +} + +unsigned long _mosquitto_max_memory_used(void) +{ + return max_memcount; +} +#endif + +void *_mosquitto_realloc(void *ptr, size_t size) +{ + void *mem; +#ifdef REAL_WITH_MEMORY_TRACKING + if(ptr){ + memcount -= malloc_usable_size(ptr); + } +#endif + mem = realloc(ptr, size); + +#ifdef REAL_WITH_MEMORY_TRACKING + memcount += malloc_usable_size(mem); + if(memcount > max_memcount){ + max_memcount = memcount; + } +#endif + + return mem; +} + +char *_mosquitto_strdup(const char *s) +{ + char *str = strdup(s); + +#ifdef REAL_WITH_MEMORY_TRACKING + memcount += malloc_usable_size(str); + if(memcount > max_memcount){ + max_memcount = memcount; + } +#endif + + return str; +} + diff --git a/lib/memory_mosq.h b/lib/memory_mosq.h new file mode 100644 index 0000000000..02594c094d --- /dev/null +++ b/lib/memory_mosq.h @@ -0,0 +1,37 @@ +/* +Copyright (c) 2010-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#ifndef _MEMORY_MOSQ_H_ +#define _MEMORY_MOSQ_H_ + +#include +#include + +#if defined(WITH_MEMORY_TRACKING) && defined(WITH_BROKER) && !defined(WIN32) && !defined(__SYMBIAN32__) +#define REAL_WITH_MEMORY_TRACKING +#endif + +void *_mosquitto_calloc(size_t nmemb, size_t size); +void _mosquitto_free(void *mem); +void *_mosquitto_malloc(size_t size); +#ifdef REAL_WITH_MEMORY_TRACKING +unsigned long _mosquitto_memory_used(void); +unsigned long _mosquitto_max_memory_used(void); +#endif +void *_mosquitto_realloc(void *ptr, size_t size); +char *_mosquitto_strdup(const char *s); + +#endif diff --git a/lib/messages_mosq.c b/lib/messages_mosq.c new file mode 100644 index 0000000000..e3e8a19011 --- /dev/null +++ b/lib/messages_mosq.c @@ -0,0 +1,387 @@ +/* +Copyright (c) 2010-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +void _mosquitto_message_cleanup(struct mosquitto_message_all **message) +{ + struct mosquitto_message_all *msg; + + if(!message || !*message) return; + + msg = *message; + + if(msg->msg.topic) _mosquitto_free(msg->msg.topic); + if(msg->msg.payload) _mosquitto_free(msg->msg.payload); + _mosquitto_free(msg); +} + +void _mosquitto_message_cleanup_all(struct mosquitto *mosq) +{ + struct mosquitto_message_all *tmp; + + assert(mosq); + + while(mosq->in_messages){ + tmp = mosq->in_messages->next; + _mosquitto_message_cleanup(&mosq->in_messages); + mosq->in_messages = tmp; + } + while(mosq->out_messages){ + tmp = mosq->out_messages->next; + _mosquitto_message_cleanup(&mosq->out_messages); + mosq->out_messages = tmp; + } +} + +int mosquitto_message_copy(struct mosquitto_message *dst, const struct mosquitto_message *src) +{ + if(!dst || !src) return MOSQ_ERR_INVAL; + + dst->mid = src->mid; + dst->topic = _mosquitto_strdup(src->topic); + if(!dst->topic) return MOSQ_ERR_NOMEM; + dst->qos = src->qos; + dst->retain = src->retain; + if(src->payloadlen){ + dst->payload = _mosquitto_malloc(src->payloadlen); + if(!dst->payload){ + _mosquitto_free(dst->topic); + return MOSQ_ERR_NOMEM; + } + memcpy(dst->payload, src->payload, src->payloadlen); + dst->payloadlen = src->payloadlen; + }else{ + dst->payloadlen = 0; + dst->payload = NULL; + } + return MOSQ_ERR_SUCCESS; +} + +int _mosquitto_message_delete(struct mosquitto *mosq, uint16_t mid, enum mosquitto_msg_direction dir) +{ + struct mosquitto_message_all *message; + int rc; + assert(mosq); + + rc = _mosquitto_message_remove(mosq, mid, dir, &message); + if(rc == MOSQ_ERR_SUCCESS){ + _mosquitto_message_cleanup(&message); + } + return rc; +} + +void mosquitto_message_free(struct mosquitto_message **message) +{ + struct mosquitto_message *msg; + + if(!message || !*message) return; + + msg = *message; + + if(msg->topic) _mosquitto_free(msg->topic); + if(msg->payload) _mosquitto_free(msg->payload); + _mosquitto_free(msg); +} + +void _mosquitto_message_queue(struct mosquitto *mosq, struct mosquitto_message_all *message, enum mosquitto_msg_direction dir) +{ + /* mosq->*_message_mutex should be locked before entering this function */ + assert(mosq); + assert(message); + + if(dir == mosq_md_out){ + mosq->out_queue_len++; + message->next = NULL; + if(mosq->out_messages_last){ + mosq->out_messages_last->next = message; + }else{ + mosq->out_messages = message; + } + mosq->out_messages_last = message; + }else{ + mosq->in_queue_len++; + if(message->msg.qos > 0 && (mosq->max_inflight_messages == 0 || mosq->inflight_messages < mosq->max_inflight_messages)){ + mosq->inflight_messages++; + } + message->next = NULL; + if(mosq->in_messages_last){ + mosq->in_messages_last->next = message; + }else{ + mosq->in_messages = message; + } + mosq->in_messages_last = message; + } +} + +void _mosquitto_messages_reconnect_reset(struct mosquitto *mosq) +{ + struct mosquitto_message_all *message; + struct mosquitto_message_all *prev = NULL; + assert(mosq); + + pthread_mutex_lock(&mosq->in_message_mutex); + message = mosq->in_messages; + mosq->in_queue_len = 0; + while(message){ + mosq->in_queue_len++; + message->timestamp = 0; + if(message->msg.qos != 2){ + if(prev){ + prev->next = message->next; + _mosquitto_message_cleanup(&message); + message = prev; + }else{ + mosq->in_messages = message->next; + _mosquitto_message_cleanup(&message); + message = mosq->in_messages; + } + }else{ + /* Message state can be preserved here because it should match + * whatever the client has got. */ + } + prev = message; + message = message->next; + } + mosq->in_messages_last = prev; + pthread_mutex_unlock(&mosq->in_message_mutex); + + + pthread_mutex_lock(&mosq->out_message_mutex); + mosq->inflight_messages = 0; + message = mosq->out_messages; + mosq->out_queue_len = 0; + while(message){ + mosq->out_queue_len++; + message->timestamp = 0; + + if(message->msg.qos > 0){ + mosq->inflight_messages++; + } + if(mosq->max_inflight_messages == 0 || mosq->inflight_messages < mosq->max_inflight_messages){ + if(message->msg.qos == 1){ + message->state = mosq_ms_wait_for_puback; + }else if(message->msg.qos == 2){ + /* Should be able to preserve state. */ + } + }else{ + message->state = mosq_ms_invalid; + } + prev = message; + message = message->next; + } + mosq->out_messages_last = prev; + pthread_mutex_unlock(&mosq->out_message_mutex); +} + +int _mosquitto_message_remove(struct mosquitto *mosq, uint16_t mid, enum mosquitto_msg_direction dir, struct mosquitto_message_all **message) +{ + struct mosquitto_message_all *cur, *prev = NULL; + bool found = false; + int rc; + assert(mosq); + assert(message); + + if(dir == mosq_md_out){ + pthread_mutex_lock(&mosq->out_message_mutex); + cur = mosq->out_messages; + while(cur){ + if(cur->msg.mid == mid){ + if(prev){ + prev->next = cur->next; + }else{ + mosq->out_messages = cur->next; + } + *message = cur; + mosq->out_queue_len--; + if(cur->next == NULL){ + mosq->out_messages_last = prev; + }else if(!mosq->out_messages){ + mosq->out_messages_last = NULL; + } + if(cur->msg.qos > 0){ + mosq->inflight_messages--; + } + found = true; + break; + } + prev = cur; + cur = cur->next; + } + + if(found){ + cur = mosq->out_messages; + while(cur){ + if(mosq->max_inflight_messages == 0 || mosq->inflight_messages < mosq->max_inflight_messages){ + if(cur->msg.qos > 0 && cur->state == mosq_ms_invalid){ + mosq->inflight_messages++; + if(cur->msg.qos == 1){ + cur->state = mosq_ms_wait_for_puback; + }else if(cur->msg.qos == 2){ + cur->state = mosq_ms_wait_for_pubrec; + } + rc = _mosquitto_send_publish(mosq, cur->msg.mid, cur->msg.topic, cur->msg.payloadlen, cur->msg.payload, cur->msg.qos, cur->msg.retain, cur->dup); + if(rc){ + pthread_mutex_unlock(&mosq->out_message_mutex); + return rc; + } + } + }else{ + pthread_mutex_unlock(&mosq->out_message_mutex); + return MOSQ_ERR_SUCCESS; + } + cur = cur->next; + } + pthread_mutex_unlock(&mosq->out_message_mutex); + return MOSQ_ERR_SUCCESS; + }else{ + pthread_mutex_unlock(&mosq->out_message_mutex); + return MOSQ_ERR_NOT_FOUND; + } + }else{ + pthread_mutex_lock(&mosq->in_message_mutex); + cur = mosq->in_messages; + while(cur){ + if(cur->msg.mid == mid){ + if(prev){ + prev->next = cur->next; + }else{ + mosq->in_messages = cur->next; + } + *message = cur; + mosq->in_queue_len--; + if(cur->next == NULL){ + mosq->in_messages_last = prev; + }else if(!mosq->in_messages){ + mosq->in_messages_last = NULL; + } + if(cur->msg.qos == 2){ + mosq->inflight_messages--; + } + found = true; + break; + } + prev = cur; + cur = cur->next; + } + + pthread_mutex_unlock(&mosq->in_message_mutex); + if(found){ + return MOSQ_ERR_SUCCESS; + }else{ + return MOSQ_ERR_NOT_FOUND; + } + } +} + +#ifdef WITH_THREADING +void _mosquitto_message_retry_check_actual(struct mosquitto *mosq, struct mosquitto_message_all *messages, pthread_mutex_t mutex) +#else +void _mosquitto_message_retry_check_actual(struct mosquitto *mosq, struct mosquitto_message_all *messages) +#endif +{ + time_t now = mosquitto_time(); + assert(mosq); + +#ifdef WITH_THREADING + pthread_mutex_lock(&mutex); +#endif + + while(messages){ + if(messages->timestamp + mosq->message_retry < now){ + switch(messages->state){ + case mosq_ms_wait_for_puback: + case mosq_ms_wait_for_pubrec: + messages->timestamp = now; + messages->dup = true; + _mosquitto_send_publish(mosq, messages->msg.mid, messages->msg.topic, messages->msg.payloadlen, messages->msg.payload, messages->msg.qos, messages->msg.retain, messages->dup); + break; + case mosq_ms_wait_for_pubrel: + messages->timestamp = now; + messages->dup = true; + _mosquitto_send_pubrec(mosq, messages->msg.mid); + break; + case mosq_ms_wait_for_pubcomp: + messages->timestamp = now; + messages->dup = true; + _mosquitto_send_pubrel(mosq, messages->msg.mid, true); + break; + default: + break; + } + } + messages = messages->next; + } +#ifdef WITH_THREADING + pthread_mutex_unlock(&mutex); +#endif +} + +void _mosquitto_message_retry_check(struct mosquitto *mosq) +{ +#ifdef WITH_THREADING + _mosquitto_message_retry_check_actual(mosq, mosq->out_messages, mosq->out_message_mutex); + _mosquitto_message_retry_check_actual(mosq, mosq->in_messages, mosq->in_message_mutex); +#else + _mosquitto_message_retry_check_actual(mosq, mosq->out_messages); + _mosquitto_message_retry_check_actual(mosq, mosq->in_messages); +#endif +} + +void mosquitto_message_retry_set(struct mosquitto *mosq, unsigned int message_retry) +{ + assert(mosq); + if(mosq) mosq->message_retry = message_retry; +} + +int _mosquitto_message_out_update(struct mosquitto *mosq, uint16_t mid, enum mosquitto_msg_state state) +{ + struct mosquitto_message_all *message; + assert(mosq); + + pthread_mutex_lock(&mosq->out_message_mutex); + message = mosq->out_messages; + while(message){ + if(message->msg.mid == mid){ + message->state = state; + message->timestamp = mosquitto_time(); + pthread_mutex_unlock(&mosq->out_message_mutex); + return MOSQ_ERR_SUCCESS; + } + message = message->next; + } + pthread_mutex_unlock(&mosq->out_message_mutex); + return MOSQ_ERR_NOT_FOUND; +} + +int mosquitto_max_inflight_messages_set(struct mosquitto *mosq, unsigned int max_inflight_messages) +{ + if(!mosq) return MOSQ_ERR_INVAL; + + mosq->max_inflight_messages = max_inflight_messages; + + return MOSQ_ERR_SUCCESS; +} + diff --git a/lib/messages_mosq.h b/lib/messages_mosq.h new file mode 100644 index 0000000000..d38910fe54 --- /dev/null +++ b/lib/messages_mosq.h @@ -0,0 +1,31 @@ +/* +Copyright (c) 2010-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ +#ifndef _MESSAGES_MOSQ_H_ +#define _MESSAGES_MOSQ_H_ + +#include +#include + +void _mosquitto_message_cleanup_all(struct mosquitto *mosq); +void _mosquitto_message_cleanup(struct mosquitto_message_all **message); +int _mosquitto_message_delete(struct mosquitto *mosq, uint16_t mid, enum mosquitto_msg_direction dir); +void _mosquitto_message_queue(struct mosquitto *mosq, struct mosquitto_message_all *message, enum mosquitto_msg_direction dir); +void _mosquitto_messages_reconnect_reset(struct mosquitto *mosq); +int _mosquitto_message_remove(struct mosquitto *mosq, uint16_t mid, enum mosquitto_msg_direction dir, struct mosquitto_message_all **message); +void _mosquitto_message_retry_check(struct mosquitto *mosq); +int _mosquitto_message_out_update(struct mosquitto *mosq, uint16_t mid, enum mosquitto_msg_state state); + +#endif diff --git a/lib/mosquitto.c b/lib/mosquitto.c new file mode 100644 index 0000000000..3cdc6cf3b9 --- /dev/null +++ b/lib/mosquitto.c @@ -0,0 +1,1293 @@ +/* +Copyright (c) 2010-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#include +#include +#include +#include +#include +#ifndef WIN32 +#include +#include +#include +#else +#include +#include +typedef int ssize_t; +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if !defined(WIN32) && !defined(__SYMBIAN32__) +#define HAVE_PSELECT +#endif + +void _mosquitto_destroy(struct mosquitto *mosq); +static int _mosquitto_reconnect(struct mosquitto *mosq, bool blocking); +static int _mosquitto_connect_init(struct mosquitto *mosq, const char *host, int port, int keepalive, const char *bind_address); + +int mosquitto_lib_version(int *major, int *minor, int *revision) +{ + if(major) *major = LIBMOSQUITTO_MAJOR; + if(minor) *minor = LIBMOSQUITTO_MINOR; + if(revision) *revision = LIBMOSQUITTO_REVISION; + return LIBMOSQUITTO_VERSION_NUMBER; +} + +int mosquitto_lib_init(void) +{ +#ifdef WIN32 + srand(GetTickCount()); +#else + struct timeval tv; + + gettimeofday(&tv, NULL); + srand(tv.tv_sec*1000 + tv.tv_usec/1000); +#endif + + _mosquitto_net_init(); + + return MOSQ_ERR_SUCCESS; +} + +int mosquitto_lib_cleanup(void) +{ + _mosquitto_net_cleanup(); + + return MOSQ_ERR_SUCCESS; +} + +struct mosquitto *mosquitto_new(const char *id, bool clean_session, void *userdata) +{ + struct mosquitto *mosq = NULL; + int rc; + + if(clean_session == false && id == NULL){ + errno = EINVAL; + return NULL; + } + +#ifndef WIN32 + signal(SIGPIPE, SIG_IGN); +#endif + + mosq = (struct mosquitto *)_mosquitto_calloc(1, sizeof(struct mosquitto)); + if(mosq){ + mosq->sock = INVALID_SOCKET; + mosq->sockpairR = INVALID_SOCKET; + mosq->sockpairW = INVALID_SOCKET; +#ifdef WITH_THREADING + mosq->thread_id = pthread_self(); +#endif + rc = mosquitto_reinitialise(mosq, id, clean_session, userdata); + if(rc){ + mosquitto_destroy(mosq); + if(rc == MOSQ_ERR_INVAL){ + errno = EINVAL; + }else if(rc == MOSQ_ERR_NOMEM){ + errno = ENOMEM; + } + return NULL; + } + }else{ + errno = ENOMEM; + } + return mosq; +} + +int mosquitto_reinitialise(struct mosquitto *mosq, const char *id, bool clean_session, void *userdata) +{ + int i; + + if(!mosq) return MOSQ_ERR_INVAL; + + if(clean_session == false && id == NULL){ + return MOSQ_ERR_INVAL; + } + + _mosquitto_destroy(mosq); + memset(mosq, 0, sizeof(struct mosquitto)); + + if(userdata){ + mosq->userdata = userdata; + }else{ + mosq->userdata = mosq; + } + mosq->sock = INVALID_SOCKET; + mosq->sockpairR = INVALID_SOCKET; + mosq->sockpairW = INVALID_SOCKET; + mosq->keepalive = 60; + mosq->message_retry = 20; + mosq->last_retry_check = 0; + mosq->clean_session = clean_session; + if(id){ + if(strlen(id) == 0){ + return MOSQ_ERR_INVAL; + } + mosq->id = _mosquitto_strdup(id); + }else{ + mosq->id = (char *)_mosquitto_calloc(24, sizeof(char)); + if(!mosq->id){ + return MOSQ_ERR_NOMEM; + } + mosq->id[0] = 'm'; + mosq->id[1] = 'o'; + mosq->id[2] = 's'; + mosq->id[3] = 'q'; + mosq->id[4] = '/'; + + for(i=5; i<23; i++){ + mosq->id[i] = (rand()%73)+48; + } + } + mosq->in_packet.payload = NULL; + _mosquitto_packet_cleanup(&mosq->in_packet); + mosq->out_packet = NULL; + mosq->current_out_packet = NULL; + mosq->last_msg_in = mosquitto_time(); + mosq->last_msg_out = mosquitto_time(); + mosq->ping_t = 0; + mosq->last_mid = 0; + mosq->state = mosq_cs_new; + mosq->in_messages = NULL; + mosq->in_messages_last = NULL; + mosq->out_messages = NULL; + mosq->out_messages_last = NULL; + mosq->max_inflight_messages = 20; + mosq->will = NULL; + mosq->on_connect = NULL; + mosq->on_publish = NULL; + mosq->on_message = NULL; + mosq->on_subscribe = NULL; + mosq->on_unsubscribe = NULL; + mosq->host = NULL; + mosq->port = 1883; + mosq->in_callback = false; + mosq->in_queue_len = 0; + mosq->out_queue_len = 0; + mosq->reconnect_delay = 1; + mosq->reconnect_delay_max = 1; + mosq->reconnect_exponential_backoff = false; + mosq->threaded = false; +#ifdef WITH_TLS + mosq->ssl = NULL; + mosq->tls_cert_reqs = SSL_VERIFY_PEER; + mosq->tls_insecure = false; +#endif +#ifdef WITH_THREADING + pthread_mutex_init(&mosq->callback_mutex, NULL); + pthread_mutex_init(&mosq->log_callback_mutex, NULL); + pthread_mutex_init(&mosq->state_mutex, NULL); + pthread_mutex_init(&mosq->out_packet_mutex, NULL); + pthread_mutex_init(&mosq->current_out_packet_mutex, NULL); + pthread_mutex_init(&mosq->msgtime_mutex, NULL); + pthread_mutex_init(&mosq->in_message_mutex, NULL); + pthread_mutex_init(&mosq->out_message_mutex, NULL); + mosq->thread_id = pthread_self(); +#endif + + return MOSQ_ERR_SUCCESS; +} + +int mosquitto_will_set(struct mosquitto *mosq, const char *topic, int payloadlen, const void *payload, int qos, bool retain) +{ + if(!mosq) return MOSQ_ERR_INVAL; + return _mosquitto_will_set(mosq, topic, payloadlen, payload, qos, retain); +} + +int mosquitto_will_clear(struct mosquitto *mosq) +{ + if(!mosq) return MOSQ_ERR_INVAL; + return _mosquitto_will_clear(mosq); +} + +int mosquitto_username_pw_set(struct mosquitto *mosq, const char *username, const char *password) +{ + if(!mosq) return MOSQ_ERR_INVAL; + + if(mosq->username){ + _mosquitto_free(mosq->username); + mosq->username = NULL; + } + if(mosq->password){ + _mosquitto_free(mosq->password); + mosq->password = NULL; + } + + if(username){ + mosq->username = _mosquitto_strdup(username); + if(!mosq->username) return MOSQ_ERR_NOMEM; + if(password){ + mosq->password = _mosquitto_strdup(password); + if(!mosq->password){ + _mosquitto_free(mosq->username); + mosq->username = NULL; + return MOSQ_ERR_NOMEM; + } + } + } + return MOSQ_ERR_SUCCESS; +} + +int mosquitto_reconnect_delay_set(struct mosquitto *mosq, unsigned int reconnect_delay, unsigned int reconnect_delay_max, bool reconnect_exponential_backoff) +{ + if(!mosq) return MOSQ_ERR_INVAL; + + mosq->reconnect_delay = reconnect_delay; + mosq->reconnect_delay_max = reconnect_delay_max; + mosq->reconnect_exponential_backoff = reconnect_exponential_backoff; + + return MOSQ_ERR_SUCCESS; + +} + +void _mosquitto_destroy(struct mosquitto *mosq) +{ + struct _mosquitto_packet *packet; + if(!mosq) return; + +#ifdef WITH_THREADING + if(mosq->threaded && !pthread_equal(mosq->thread_id, pthread_self())){ + pthread_cancel(mosq->thread_id); + pthread_join(mosq->thread_id, NULL); + mosq->threaded = false; + } + + if(mosq->id){ + /* If mosq->id is not NULL then the client has already been initialised + * and so the mutexes need destroying. If mosq->id is NULL, the mutexes + * haven't been initialised. */ + pthread_mutex_destroy(&mosq->callback_mutex); + pthread_mutex_destroy(&mosq->log_callback_mutex); + pthread_mutex_destroy(&mosq->state_mutex); + pthread_mutex_destroy(&mosq->out_packet_mutex); + pthread_mutex_destroy(&mosq->current_out_packet_mutex); + pthread_mutex_destroy(&mosq->msgtime_mutex); + pthread_mutex_destroy(&mosq->in_message_mutex); + pthread_mutex_destroy(&mosq->out_message_mutex); + } +#endif + if(mosq->sock != INVALID_SOCKET){ + _mosquitto_socket_close(mosq); + } + _mosquitto_message_cleanup_all(mosq); + _mosquitto_will_clear(mosq); +#ifdef WITH_TLS + if(mosq->ssl){ + SSL_free(mosq->ssl); + } + if(mosq->ssl_ctx){ + SSL_CTX_free(mosq->ssl_ctx); + } + if(mosq->tls_cafile) _mosquitto_free(mosq->tls_cafile); + if(mosq->tls_capath) _mosquitto_free(mosq->tls_capath); + if(mosq->tls_certfile) _mosquitto_free(mosq->tls_certfile); + if(mosq->tls_keyfile) _mosquitto_free(mosq->tls_keyfile); + if(mosq->tls_pw_callback) mosq->tls_pw_callback = NULL; + if(mosq->tls_version) _mosquitto_free(mosq->tls_version); + if(mosq->tls_ciphers) _mosquitto_free(mosq->tls_ciphers); + if(mosq->tls_psk) _mosquitto_free(mosq->tls_psk); + if(mosq->tls_psk_identity) _mosquitto_free(mosq->tls_psk_identity); +#endif + + if(mosq->address){ + _mosquitto_free(mosq->address); + mosq->address = NULL; + } + if(mosq->id){ + _mosquitto_free(mosq->id); + mosq->id = NULL; + } + if(mosq->username){ + _mosquitto_free(mosq->username); + mosq->username = NULL; + } + if(mosq->password){ + _mosquitto_free(mosq->password); + mosq->password = NULL; + } + if(mosq->host){ + _mosquitto_free(mosq->host); + mosq->host = NULL; + } + if(mosq->bind_address){ + _mosquitto_free(mosq->bind_address); + mosq->bind_address = NULL; + } + + /* Out packet cleanup */ + if(mosq->out_packet && !mosq->current_out_packet){ + mosq->current_out_packet = mosq->out_packet; + mosq->out_packet = mosq->out_packet->next; + } + while(mosq->current_out_packet){ + packet = mosq->current_out_packet; + /* Free data and reset values */ + mosq->current_out_packet = mosq->out_packet; + if(mosq->out_packet){ + mosq->out_packet = mosq->out_packet->next; + } + + _mosquitto_packet_cleanup(packet); + _mosquitto_free(packet); + } + + _mosquitto_packet_cleanup(&mosq->in_packet); + if(mosq->sockpairR != INVALID_SOCKET){ + COMPAT_CLOSE(mosq->sockpairR); + mosq->sockpairR = INVALID_SOCKET; + } + if(mosq->sockpairW != INVALID_SOCKET){ + COMPAT_CLOSE(mosq->sockpairW); + mosq->sockpairW = INVALID_SOCKET; + } +} + +void mosquitto_destroy(struct mosquitto *mosq) +{ + if(!mosq) return; + + _mosquitto_destroy(mosq); + _mosquitto_free(mosq); +} + +int mosquitto_socket(struct mosquitto *mosq) +{ + if(!mosq) return MOSQ_ERR_INVAL; + return mosq->sock; +} + +static int _mosquitto_connect_init(struct mosquitto *mosq, const char *host, int port, int keepalive, const char *bind_address) +{ + if(!mosq) return MOSQ_ERR_INVAL; + if(!host || port <= 0) return MOSQ_ERR_INVAL; + + if(mosq->host) _mosquitto_free(mosq->host); + mosq->host = _mosquitto_strdup(host); + if(!mosq->host) return MOSQ_ERR_NOMEM; + mosq->port = port; + + if(mosq->bind_address) _mosquitto_free(mosq->bind_address); + if(bind_address){ + mosq->bind_address = _mosquitto_strdup(bind_address); + if(!mosq->bind_address) return MOSQ_ERR_NOMEM; + } + + mosq->keepalive = keepalive; + + if(_mosquitto_socketpair(&mosq->sockpairR, &mosq->sockpairW)){ + _mosquitto_log_printf(mosq, MOSQ_LOG_WARNING, + "Warning: Unable to open socket pair, outgoing publish commands may be delayed."); + } + + return MOSQ_ERR_SUCCESS; +} + +int mosquitto_connect(struct mosquitto *mosq, const char *host, int port, int keepalive) +{ + return mosquitto_connect_bind(mosq, host, port, keepalive, NULL); +} + +int mosquitto_connect_bind(struct mosquitto *mosq, const char *host, int port, int keepalive, const char *bind_address) +{ + int rc; + rc = _mosquitto_connect_init(mosq, host, port, keepalive, bind_address); + if(rc) return rc; + + pthread_mutex_lock(&mosq->state_mutex); + mosq->state = mosq_cs_new; + pthread_mutex_unlock(&mosq->state_mutex); + + return _mosquitto_reconnect(mosq, true); +} + +int mosquitto_connect_async(struct mosquitto *mosq, const char *host, int port, int keepalive) +{ + return mosquitto_connect_bind_async(mosq, host, port, keepalive, NULL); +} + +int mosquitto_connect_bind_async(struct mosquitto *mosq, const char *host, int port, int keepalive, const char *bind_address) +{ + int rc = _mosquitto_connect_init(mosq, host, port, keepalive, bind_address); + if(rc) return rc; + + pthread_mutex_lock(&mosq->state_mutex); + mosq->state = mosq_cs_connect_async; + pthread_mutex_unlock(&mosq->state_mutex); + + return _mosquitto_reconnect(mosq, false); +} + +int mosquitto_reconnect_async(struct mosquitto *mosq) +{ + return _mosquitto_reconnect(mosq, false); +} + +int mosquitto_reconnect(struct mosquitto *mosq) +{ + return _mosquitto_reconnect(mosq, true); +} + +static int _mosquitto_reconnect(struct mosquitto *mosq, bool blocking) +{ + int rc; + struct _mosquitto_packet *packet; + if(!mosq) return MOSQ_ERR_INVAL; + if(!mosq->host || mosq->port <= 0) return MOSQ_ERR_INVAL; + + pthread_mutex_lock(&mosq->state_mutex); + mosq->state = mosq_cs_new; + pthread_mutex_unlock(&mosq->state_mutex); + + pthread_mutex_lock(&mosq->msgtime_mutex); + mosq->last_msg_in = mosquitto_time(); + mosq->last_msg_out = mosquitto_time(); + pthread_mutex_unlock(&mosq->msgtime_mutex); + + mosq->ping_t = 0; + + _mosquitto_packet_cleanup(&mosq->in_packet); + + pthread_mutex_lock(&mosq->current_out_packet_mutex); + pthread_mutex_lock(&mosq->out_packet_mutex); + + if(mosq->out_packet && !mosq->current_out_packet){ + mosq->current_out_packet = mosq->out_packet; + mosq->out_packet = mosq->out_packet->next; + } + + while(mosq->current_out_packet){ + packet = mosq->current_out_packet; + /* Free data and reset values */ + mosq->current_out_packet = mosq->out_packet; + if(mosq->out_packet){ + mosq->out_packet = mosq->out_packet->next; + } + + _mosquitto_packet_cleanup(packet); + _mosquitto_free(packet); + } + pthread_mutex_unlock(&mosq->out_packet_mutex); + pthread_mutex_unlock(&mosq->current_out_packet_mutex); + + _mosquitto_messages_reconnect_reset(mosq); + + rc = _mosquitto_socket_connect(mosq, mosq->host, mosq->port, mosq->bind_address, blocking); + if(rc){ + return rc; + } + + return _mosquitto_send_connect(mosq, mosq->keepalive, mosq->clean_session); +} + +int mosquitto_disconnect(struct mosquitto *mosq) +{ + if(!mosq) return MOSQ_ERR_INVAL; + + pthread_mutex_lock(&mosq->state_mutex); + mosq->state = mosq_cs_disconnecting; + pthread_mutex_unlock(&mosq->state_mutex); + + if(mosq->sock == INVALID_SOCKET) return MOSQ_ERR_NO_CONN; + return _mosquitto_send_disconnect(mosq); +} + +int mosquitto_publish(struct mosquitto *mosq, int *mid, const char *topic, int payloadlen, const void *payload, int qos, bool retain) +{ + struct mosquitto_message_all *message; + uint16_t local_mid; + + if(!mosq || !topic || qos<0 || qos>2) return MOSQ_ERR_INVAL; + if(strlen(topic) == 0) return MOSQ_ERR_INVAL; + if(payloadlen < 0 || payloadlen > MQTT_MAX_PAYLOAD) return MOSQ_ERR_PAYLOAD_SIZE; + + if(_mosquitto_topic_wildcard_len_check(topic) != MOSQ_ERR_SUCCESS){ + return MOSQ_ERR_INVAL; + } + + local_mid = _mosquitto_mid_generate(mosq); + if(mid){ + *mid = local_mid; + } + + if(qos == 0){ + return _mosquitto_send_publish(mosq, local_mid, topic, payloadlen, payload, qos, retain, false); + }else{ + message = _mosquitto_calloc(1, sizeof(struct mosquitto_message_all)); + if(!message) return MOSQ_ERR_NOMEM; + + message->next = NULL; + message->timestamp = mosquitto_time(); + message->msg.mid = local_mid; + message->msg.topic = _mosquitto_strdup(topic); + if(!message->msg.topic){ + _mosquitto_message_cleanup(&message); + return MOSQ_ERR_NOMEM; + } + if(payloadlen){ + message->msg.payloadlen = payloadlen; + message->msg.payload = _mosquitto_malloc(payloadlen*sizeof(uint8_t)); + if(!message->msg.payload){ + _mosquitto_message_cleanup(&message); + return MOSQ_ERR_NOMEM; + } + memcpy(message->msg.payload, payload, payloadlen*sizeof(uint8_t)); + }else{ + message->msg.payloadlen = 0; + message->msg.payload = NULL; + } + message->msg.qos = qos; + message->msg.retain = retain; + message->dup = false; + + pthread_mutex_lock(&mosq->out_message_mutex); + _mosquitto_message_queue(mosq, message, mosq_md_out); + if(mosq->max_inflight_messages == 0 || mosq->inflight_messages < mosq->max_inflight_messages){ + mosq->inflight_messages++; + if(qos == 1){ + message->state = mosq_ms_wait_for_puback; + }else if(qos == 2){ + message->state = mosq_ms_wait_for_pubrec; + } + pthread_mutex_unlock(&mosq->out_message_mutex); + return _mosquitto_send_publish(mosq, message->msg.mid, message->msg.topic, message->msg.payloadlen, message->msg.payload, message->msg.qos, message->msg.retain, message->dup); + }else{ + message->state = mosq_ms_invalid; + pthread_mutex_unlock(&mosq->out_message_mutex); + return MOSQ_ERR_SUCCESS; + } + } +} + +int mosquitto_subscribe(struct mosquitto *mosq, int *mid, const char *sub, int qos) +{ + if(!mosq) return MOSQ_ERR_INVAL; + if(mosq->sock == INVALID_SOCKET) return MOSQ_ERR_NO_CONN; + + if(_mosquitto_topic_wildcard_pos_check(sub)) return MOSQ_ERR_INVAL; + + return _mosquitto_send_subscribe(mosq, mid, false, sub, qos); +} + +int mosquitto_unsubscribe(struct mosquitto *mosq, int *mid, const char *sub) +{ + if(!mosq) return MOSQ_ERR_INVAL; + if(mosq->sock == INVALID_SOCKET) return MOSQ_ERR_NO_CONN; + + if(_mosquitto_topic_wildcard_pos_check(sub)) return MOSQ_ERR_INVAL; + + return _mosquitto_send_unsubscribe(mosq, mid, false, sub); +} + +int mosquitto_tls_set(struct mosquitto *mosq, const char *cafile, const char *capath, const char *certfile, const char *keyfile, int (*pw_callback)(char *buf, int size, int rwflag, void *userdata)) +{ +#ifdef WITH_TLS + FILE *fptr; + + if(!mosq || (!cafile && !capath) || (certfile && !keyfile) || (!certfile && keyfile)) return MOSQ_ERR_INVAL; + + if(cafile){ + fptr = _mosquitto_fopen(cafile, "rt"); + if(fptr){ + fclose(fptr); + }else{ + return MOSQ_ERR_INVAL; + } + mosq->tls_cafile = _mosquitto_strdup(cafile); + + if(!mosq->tls_cafile){ + return MOSQ_ERR_NOMEM; + } + }else if(mosq->tls_cafile){ + _mosquitto_free(mosq->tls_cafile); + mosq->tls_cafile = NULL; + } + + if(capath){ + mosq->tls_capath = _mosquitto_strdup(capath); + if(!mosq->tls_capath){ + return MOSQ_ERR_NOMEM; + } + }else if(mosq->tls_capath){ + _mosquitto_free(mosq->tls_capath); + mosq->tls_capath = NULL; + } + + if(certfile){ + fptr = _mosquitto_fopen(certfile, "rt"); + if(fptr){ + fclose(fptr); + }else{ + if(mosq->tls_cafile){ + _mosquitto_free(mosq->tls_cafile); + mosq->tls_cafile = NULL; + } + if(mosq->tls_capath){ + _mosquitto_free(mosq->tls_capath); + mosq->tls_capath = NULL; + } + return MOSQ_ERR_INVAL; + } + mosq->tls_certfile = _mosquitto_strdup(certfile); + if(!mosq->tls_certfile){ + return MOSQ_ERR_NOMEM; + } + }else{ + if(mosq->tls_certfile) _mosquitto_free(mosq->tls_certfile); + mosq->tls_certfile = NULL; + } + + if(keyfile){ + fptr = _mosquitto_fopen(keyfile, "rt"); + if(fptr){ + fclose(fptr); + }else{ + if(mosq->tls_cafile){ + _mosquitto_free(mosq->tls_cafile); + mosq->tls_cafile = NULL; + } + if(mosq->tls_capath){ + _mosquitto_free(mosq->tls_capath); + mosq->tls_capath = NULL; + } + if(mosq->tls_certfile){ + _mosquitto_free(mosq->tls_certfile); + mosq->tls_certfile = NULL; + } + return MOSQ_ERR_INVAL; + } + mosq->tls_keyfile = _mosquitto_strdup(keyfile); + if(!mosq->tls_keyfile){ + return MOSQ_ERR_NOMEM; + } + }else{ + if(mosq->tls_keyfile) _mosquitto_free(mosq->tls_keyfile); + mosq->tls_keyfile = NULL; + } + + mosq->tls_pw_callback = pw_callback; + + + return MOSQ_ERR_SUCCESS; +#else + return MOSQ_ERR_NOT_SUPPORTED; + +#endif +} + +int mosquitto_tls_opts_set(struct mosquitto *mosq, int cert_reqs, const char *tls_version, const char *ciphers) +{ +#ifdef WITH_TLS + if(!mosq) return MOSQ_ERR_INVAL; + + mosq->tls_cert_reqs = cert_reqs; + if(tls_version){ +#if OPENSSL_VERSION_NUMBER >= 0x10001000L + if(!strcasecmp(tls_version, "tlsv1.2") + || !strcasecmp(tls_version, "tlsv1.1") + || !strcasecmp(tls_version, "tlsv1")){ + + mosq->tls_version = _mosquitto_strdup(tls_version); + if(!mosq->tls_version) return MOSQ_ERR_NOMEM; + }else{ + return MOSQ_ERR_INVAL; + } +#else + if(!strcasecmp(tls_version, "tlsv1")){ + mosq->tls_version = _mosquitto_strdup(tls_version); + if(!mosq->tls_version) return MOSQ_ERR_NOMEM; + }else{ + return MOSQ_ERR_INVAL; + } +#endif + }else{ +#if OPENSSL_VERSION_NUMBER >= 0x10001000L + mosq->tls_version = _mosquitto_strdup("tlsv1.2"); +#else + mosq->tls_version = _mosquitto_strdup("tlsv1"); +#endif + if(!mosq->tls_version) return MOSQ_ERR_NOMEM; + } + if(ciphers){ + mosq->tls_ciphers = _mosquitto_strdup(ciphers); + if(!mosq->tls_ciphers) return MOSQ_ERR_NOMEM; + }else{ + mosq->tls_ciphers = NULL; + } + + + return MOSQ_ERR_SUCCESS; +#else + return MOSQ_ERR_NOT_SUPPORTED; + +#endif +} + + +int mosquitto_tls_insecure_set(struct mosquitto *mosq, bool value) +{ +#ifdef WITH_TLS + if(!mosq) return MOSQ_ERR_INVAL; + mosq->tls_insecure = value; + return MOSQ_ERR_SUCCESS; +#else + return MOSQ_ERR_NOT_SUPPORTED; +#endif +} + + +int mosquitto_tls_psk_set(struct mosquitto *mosq, const char *psk, const char *identity, const char *ciphers) +{ +#ifdef REAL_WITH_TLS_PSK + if(!mosq || !psk || !identity) return MOSQ_ERR_INVAL; + + /* Check for hex only digits */ + if(strspn(psk, "0123456789abcdefABCDEF") < strlen(psk)){ + return MOSQ_ERR_INVAL; + } + mosq->tls_psk = _mosquitto_strdup(psk); + if(!mosq->tls_psk) return MOSQ_ERR_NOMEM; + + mosq->tls_psk_identity = _mosquitto_strdup(identity); + if(!mosq->tls_psk_identity){ + _mosquitto_free(mosq->tls_psk); + return MOSQ_ERR_NOMEM; + } + if(ciphers){ + mosq->tls_ciphers = _mosquitto_strdup(ciphers); + if(!mosq->tls_ciphers) return MOSQ_ERR_NOMEM; + }else{ + mosq->tls_ciphers = NULL; + } + + return MOSQ_ERR_SUCCESS; +#else + return MOSQ_ERR_NOT_SUPPORTED; +#endif +} + + +int mosquitto_loop(struct mosquitto *mosq, int timeout, int max_packets) +{ +#ifdef HAVE_PSELECT + struct timespec local_timeout; +#else + struct timeval local_timeout; +#endif + fd_set readfds, writefds; + int fdcount; + int rc; + char pairbuf; + int maxfd = 0; + + if(!mosq || max_packets < 1) return MOSQ_ERR_INVAL; + + FD_ZERO(&readfds); + FD_ZERO(&writefds); + if(mosq->sock != INVALID_SOCKET){ + maxfd = mosq->sock; + FD_SET(mosq->sock, &readfds); + pthread_mutex_lock(&mosq->current_out_packet_mutex); + pthread_mutex_lock(&mosq->out_packet_mutex); + if(mosq->out_packet || mosq->current_out_packet){ + FD_SET(mosq->sock, &writefds); +#ifdef WITH_TLS + }else if(mosq->ssl && mosq->want_write){ + FD_SET(mosq->sock, &writefds); +#endif + } + pthread_mutex_unlock(&mosq->out_packet_mutex); + pthread_mutex_unlock(&mosq->current_out_packet_mutex); + }else{ +#ifdef WITH_SRV + if(mosq->achan){ + pthread_mutex_lock(&mosq->state_mutex); + if(mosq->state == mosq_cs_connect_srv){ + rc = ares_fds(mosq->achan, &readfds, &writefds); + if(rc > maxfd){ + maxfd = rc; + } + }else{ + return MOSQ_ERR_NO_CONN; + } + pthread_mutex_unlock(&mosq->state_mutex); + } +#else + return MOSQ_ERR_NO_CONN; +#endif + } + if(mosq->sockpairR != INVALID_SOCKET){ + /* sockpairR is used to break out of select() before the timeout, on a + * call to publish() etc. */ + FD_SET(mosq->sockpairR, &readfds); + if(mosq->sockpairR > maxfd){ + maxfd = mosq->sockpairR; + } + } + + if(timeout >= 0){ + local_timeout.tv_sec = timeout/1000; +#ifdef HAVE_PSELECT + local_timeout.tv_nsec = (timeout-local_timeout.tv_sec*1000)*1e6; +#else + local_timeout.tv_usec = (timeout-local_timeout.tv_sec*1000)*1000; +#endif + }else{ + local_timeout.tv_sec = 1; +#ifdef HAVE_PSELECT + local_timeout.tv_nsec = 0; +#else + local_timeout.tv_usec = 0; +#endif + } + +#ifdef HAVE_PSELECT + fdcount = pselect(maxfd+1, &readfds, &writefds, NULL, &local_timeout, NULL); +#else + fdcount = select(maxfd+1, &readfds, &writefds, NULL, &local_timeout); +#endif + if(fdcount == -1){ +#ifdef WIN32 + errno = WSAGetLastError(); +#endif + if(errno == EINTR){ + return MOSQ_ERR_SUCCESS; + }else{ + return MOSQ_ERR_ERRNO; + } + }else{ + if(mosq->sock != INVALID_SOCKET){ + if(FD_ISSET(mosq->sock, &readfds)){ + rc = mosquitto_loop_read(mosq, max_packets); + if(rc || mosq->sock == INVALID_SOCKET){ + return rc; + } + } + if(mosq->sockpairR >= 0 && FD_ISSET(mosq->sockpairR, &readfds)){ +#ifndef WIN32 + if(read(mosq->sockpairR, &pairbuf, 1) == 0){ + } +#else + recv(mosq->sockpairR, &pairbuf, 1, 0); +#endif + /* Fake write possible, to stimulate output write even though + * we didn't ask for it, because at that point the publish or + * other command wasn't present. */ + FD_SET(mosq->sock, &writefds); + } + if(FD_ISSET(mosq->sock, &writefds)){ + rc = mosquitto_loop_write(mosq, max_packets); + if(rc || mosq->sock == INVALID_SOCKET){ + return rc; + } + } + } +#ifdef WITH_SRV + if(mosq->achan){ + ares_process(mosq->achan, &readfds, &writefds); + } +#endif + } + return mosquitto_loop_misc(mosq); +} + +int mosquitto_loop_forever(struct mosquitto *mosq, int timeout, int max_packets) +{ + int run = 1; + int rc; + unsigned int reconnects = 0; + unsigned long reconnect_delay; + + if(!mosq) return MOSQ_ERR_INVAL; + + if(mosq->state == mosq_cs_connect_async){ + mosquitto_reconnect(mosq); + } + + while(run){ + do{ + rc = mosquitto_loop(mosq, timeout, max_packets); + if (reconnects !=0 && rc == MOSQ_ERR_SUCCESS){ + reconnects = 0; + } + }while(rc == MOSQ_ERR_SUCCESS); + if(errno == EPROTO){ + return rc; + } + pthread_mutex_lock(&mosq->state_mutex); + if(mosq->state == mosq_cs_disconnecting){ + run = 0; + pthread_mutex_unlock(&mosq->state_mutex); + }else{ + pthread_mutex_unlock(&mosq->state_mutex); + + if(mosq->reconnect_delay > 0 && mosq->reconnect_exponential_backoff){ + reconnect_delay = mosq->reconnect_delay*reconnects*reconnects; + }else{ + reconnect_delay = mosq->reconnect_delay; + } + + if(reconnect_delay > mosq->reconnect_delay_max){ + reconnect_delay = mosq->reconnect_delay_max; + }else{ + reconnects++; + } + +#ifdef WIN32 + Sleep(reconnect_delay*1000); +#else + sleep(reconnect_delay); +#endif + + pthread_mutex_lock(&mosq->state_mutex); + if(mosq->state == mosq_cs_disconnecting){ + run = 0; + pthread_mutex_unlock(&mosq->state_mutex); + }else{ + pthread_mutex_unlock(&mosq->state_mutex); + mosquitto_reconnect(mosq); + } + } + } + return rc; +} + +int mosquitto_loop_misc(struct mosquitto *mosq) +{ + time_t now; + int rc; + + if(!mosq) return MOSQ_ERR_INVAL; + if(mosq->sock == INVALID_SOCKET) return MOSQ_ERR_NO_CONN; + + now = mosquitto_time(); + + _mosquitto_check_keepalive(mosq); + if(mosq->last_retry_check+1 < now){ + _mosquitto_message_retry_check(mosq); + mosq->last_retry_check = now; + } + if(mosq->ping_t && now - mosq->ping_t >= mosq->keepalive){ + /* mosq->ping_t != 0 means we are waiting for a pingresp. + * This hasn't happened in the keepalive time so we should disconnect. + */ + _mosquitto_socket_close(mosq); + pthread_mutex_lock(&mosq->state_mutex); + if(mosq->state == mosq_cs_disconnecting){ + rc = MOSQ_ERR_SUCCESS; + }else{ + rc = 1; + } + pthread_mutex_unlock(&mosq->state_mutex); + pthread_mutex_lock(&mosq->callback_mutex); + if(mosq->on_disconnect){ + mosq->in_callback = true; + mosq->on_disconnect(mosq, mosq->userdata, rc); + mosq->in_callback = false; + } + pthread_mutex_unlock(&mosq->callback_mutex); + return MOSQ_ERR_CONN_LOST; + } + return MOSQ_ERR_SUCCESS; +} + +static int _mosquitto_loop_rc_handle(struct mosquitto *mosq, int rc) +{ + if(rc){ + _mosquitto_socket_close(mosq); + pthread_mutex_lock(&mosq->state_mutex); + if(mosq->state == mosq_cs_disconnecting){ + rc = MOSQ_ERR_SUCCESS; + } + pthread_mutex_unlock(&mosq->state_mutex); + pthread_mutex_lock(&mosq->callback_mutex); + if(mosq->on_disconnect){ + mosq->in_callback = true; + mosq->on_disconnect(mosq, mosq->userdata, rc); + mosq->in_callback = false; + } + pthread_mutex_unlock(&mosq->callback_mutex); + return rc; + } + return rc; +} + +int mosquitto_loop_read(struct mosquitto *mosq, int max_packets) +{ + int rc; + int i; + if(max_packets < 1) return MOSQ_ERR_INVAL; + + pthread_mutex_lock(&mosq->out_message_mutex); + max_packets = mosq->out_queue_len; + pthread_mutex_unlock(&mosq->out_message_mutex); + + pthread_mutex_lock(&mosq->in_message_mutex); + max_packets += mosq->in_queue_len; + pthread_mutex_unlock(&mosq->in_message_mutex); + + if(max_packets < 1) max_packets = 1; + /* Queue len here tells us how many messages are awaiting processing and + * have QoS > 0. We should try to deal with that many in this loop in order + * to keep up. */ + for(i=0; iout_message_mutex); + max_packets = mosq->out_queue_len; + pthread_mutex_unlock(&mosq->out_message_mutex); + + pthread_mutex_lock(&mosq->in_message_mutex); + max_packets += mosq->in_queue_len; + pthread_mutex_unlock(&mosq->in_message_mutex); + + if(max_packets < 1) max_packets = 1; + /* Queue len here tells us how many messages are awaiting processing and + * have QoS > 0. We should try to deal with that many in this loop in order + * to keep up. */ + for(i=0; iout_packet || mosq->current_out_packet){ + return true; +#ifdef WITH_TLS + }else if(mosq->ssl && mosq->want_write){ + return true; +#endif + }else{ + return false; + } +} + +void mosquitto_connect_callback_set(struct mosquitto *mosq, void (*on_connect)(struct mosquitto *, void *, int)) +{ + pthread_mutex_lock(&mosq->callback_mutex); + mosq->on_connect = on_connect; + pthread_mutex_unlock(&mosq->callback_mutex); +} + +void mosquitto_disconnect_callback_set(struct mosquitto *mosq, void (*on_disconnect)(struct mosquitto *, void *, int)) +{ + pthread_mutex_lock(&mosq->callback_mutex); + mosq->on_disconnect = on_disconnect; + pthread_mutex_unlock(&mosq->callback_mutex); +} + +void mosquitto_publish_callback_set(struct mosquitto *mosq, void (*on_publish)(struct mosquitto *, void *, int)) +{ + pthread_mutex_lock(&mosq->callback_mutex); + mosq->on_publish = on_publish; + pthread_mutex_unlock(&mosq->callback_mutex); +} + +void mosquitto_message_callback_set(struct mosquitto *mosq, void (*on_message)(struct mosquitto *, void *, const struct mosquitto_message *)) +{ + pthread_mutex_lock(&mosq->callback_mutex); + mosq->on_message = on_message; + pthread_mutex_unlock(&mosq->callback_mutex); +} + +void mosquitto_subscribe_callback_set(struct mosquitto *mosq, void (*on_subscribe)(struct mosquitto *, void *, int, int, const int *)) +{ + pthread_mutex_lock(&mosq->callback_mutex); + mosq->on_subscribe = on_subscribe; + pthread_mutex_unlock(&mosq->callback_mutex); +} + +void mosquitto_unsubscribe_callback_set(struct mosquitto *mosq, void (*on_unsubscribe)(struct mosquitto *, void *, int)) +{ + pthread_mutex_lock(&mosq->callback_mutex); + mosq->on_unsubscribe = on_unsubscribe; + pthread_mutex_unlock(&mosq->callback_mutex); +} + +void mosquitto_log_callback_set(struct mosquitto *mosq, void (*on_log)(struct mosquitto *, void *, int, const char *)) +{ + pthread_mutex_lock(&mosq->log_callback_mutex); + mosq->on_log = on_log; + pthread_mutex_unlock(&mosq->log_callback_mutex); +} + +void mosquitto_user_data_set(struct mosquitto *mosq, void *userdata) +{ + if(mosq){ + mosq->userdata = userdata; + } +} + +const char *mosquitto_strerror(int mosq_errno) +{ + switch(mosq_errno){ + case MOSQ_ERR_SUCCESS: + return "No error."; + case MOSQ_ERR_NOMEM: + return "Out of memory."; + case MOSQ_ERR_PROTOCOL: + return "A network protocol error occurred when communicating with the broker."; + case MOSQ_ERR_INVAL: + return "Invalid function arguments provided."; + case MOSQ_ERR_NO_CONN: + return "The client is not currently connected."; + case MOSQ_ERR_CONN_REFUSED: + return "The connection was refused."; + case MOSQ_ERR_NOT_FOUND: + return "Message not found (internal error)."; + case MOSQ_ERR_CONN_LOST: + return "The connection was lost."; + case MOSQ_ERR_TLS: + return "A TLS error occurred."; + case MOSQ_ERR_PAYLOAD_SIZE: + return "Payload too large."; + case MOSQ_ERR_NOT_SUPPORTED: + return "This feature is not supported."; + case MOSQ_ERR_AUTH: + return "Authorisation failed."; + case MOSQ_ERR_ACL_DENIED: + return "Access denied by ACL."; + case MOSQ_ERR_UNKNOWN: + return "Unknown error."; + case MOSQ_ERR_ERRNO: + return strerror(errno); + default: + return "Unknown error."; + } +} + +const char *mosquitto_connack_string(int connack_code) +{ + switch(connack_code){ + case 0: + return "Connection Accepted."; + case 1: + return "Connection Refused: unacceptable protocol version."; + case 2: + return "Connection Refused: identifier rejected."; + case 3: + return "Connection Refused: broker unavailable."; + case 4: + return "Connection Refused: bad user name or password."; + case 5: + return "Connection Refused: not authorised."; + default: + return "Connection Refused: unknown reason."; + } +} + +int mosquitto_sub_topic_tokenise(const char *subtopic, char ***topics, int *count) +{ + int len; + int hier_count = 1; + int start, stop; + int hier; + int tlen; + int i, j; + + if(!subtopic || !topics || !count) return MOSQ_ERR_INVAL; + + len = strlen(subtopic); + + for(i=0; i len-1){ + /* Separator at end of line */ + }else{ + hier_count++; + } + } + } + + (*topics) = _mosquitto_calloc(hier_count, sizeof(char *)); + if(!(*topics)) return MOSQ_ERR_NOMEM; + + start = 0; + stop = 0; + hier = 0; + + for(i=0; i + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#ifndef _MOSQUITTO_H_ +#define _MOSQUITTO_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(WIN32) && !defined(WITH_BROKER) +# ifdef libmosquitto_EXPORTS +# define libmosq_EXPORT __declspec(dllexport) +# else +# define libmosq_EXPORT __declspec(dllimport) +# endif +#else +# define libmosq_EXPORT +#endif + +#ifdef WIN32 +# ifndef __cplusplus +# define bool char +# define true 1 +# define false 0 +# endif +#else +# ifndef __cplusplus +# include +# endif +#endif + +#define LIBMOSQUITTO_MAJOR 1 +#define LIBMOSQUITTO_MINOR 3 +#define LIBMOSQUITTO_REVISION 1 +/* LIBMOSQUITTO_VERSION_NUMBER looks like 1002001 for e.g. version 1.2.1. */ +#define LIBMOSQUITTO_VERSION_NUMBER (LIBMOSQUITTO_MAJOR*1000000+LIBMOSQUITTO_MINOR*1000+LIBMOSQUITTO_REVISION) + +/* Log types */ +#define MOSQ_LOG_NONE 0x00 +#define MOSQ_LOG_INFO 0x01 +#define MOSQ_LOG_NOTICE 0x02 +#define MOSQ_LOG_WARNING 0x04 +#define MOSQ_LOG_ERR 0x08 +#define MOSQ_LOG_DEBUG 0x10 +#define MOSQ_LOG_SUBSCRIBE 0x20 +#define MOSQ_LOG_UNSUBSCRIBE 0x40 +#define MOSQ_LOG_ALL 0xFFFF + +/* Error values */ +enum mosq_err_t { + MOSQ_ERR_CONN_PENDING = -1, + MOSQ_ERR_SUCCESS = 0, + MOSQ_ERR_NOMEM = 1, + MOSQ_ERR_PROTOCOL = 2, + MOSQ_ERR_INVAL = 3, + MOSQ_ERR_NO_CONN = 4, + MOSQ_ERR_CONN_REFUSED = 5, + MOSQ_ERR_NOT_FOUND = 6, + MOSQ_ERR_CONN_LOST = 7, + MOSQ_ERR_TLS = 8, + MOSQ_ERR_PAYLOAD_SIZE = 9, + MOSQ_ERR_NOT_SUPPORTED = 10, + MOSQ_ERR_AUTH = 11, + MOSQ_ERR_ACL_DENIED = 12, + MOSQ_ERR_UNKNOWN = 13, + MOSQ_ERR_ERRNO = 14, + MOSQ_ERR_EAI = 15 +}; + +/* MQTT specification restricts client ids to a maximum of 23 characters */ +#define MOSQ_MQTT_ID_MAX_LENGTH 23 + +struct mosquitto_message{ + int mid; + char *topic; + void *payload; + int payloadlen; + int qos; + bool retain; +}; + +struct mosquitto; + +/* + * Topic: Threads + * libmosquitto provides thread safe operation, with the exception of + * which is not thread safe. + */ +/*************************************************** + * Important note + * + * The following functions that deal with network operations will return + * MOSQ_ERR_SUCCESS on success, but this does not mean that the operation has + * taken place. An attempt will be made to write the network data, but if the + * socket is not available for writing at that time then the packet will not be + * sent. To ensure the packet is sent, call mosquitto_loop() (which must also + * be called to process incoming network data). + * This is especially important when disconnecting a client that has a will. If + * the broker does not receive the DISCONNECT command, it will assume that the + * client has disconnected unexpectedly and send the will. + * + * mosquitto_connect() + * mosquitto_disconnect() + * mosquitto_subscribe() + * mosquitto_unsubscribe() + * mosquitto_publish() + ***************************************************/ + +/* + * Function: mosquitto_lib_version + * + * Can be used to obtain version information for the mosquitto library. + * This allows the application to compare the library version against the + * version it was compiled against by using the LIBMOSQUITTO_MAJOR, + * LIBMOSQUITTO_MINOR and LIBMOSQUITTO_REVISION defines. + * + * Parameters: + * major - an integer pointer. If not NULL, the major version of the + * library will be returned in this variable. + * minor - an integer pointer. If not NULL, the minor version of the + * library will be returned in this variable. + * revision - an integer pointer. If not NULL, the revision of the library will + * be returned in this variable. + * + * Returns: + * LIBMOSQUITTO_VERSION_NUMBER, which is a unique number based on the major, + * minor and revision values. + * See Also: + * , + */ +libmosq_EXPORT int mosquitto_lib_version(int *major, int *minor, int *revision); + +/* + * Function: mosquitto_lib_init + * + * Must be called before any other mosquitto functions. + * + * This function is *not* thread safe. + * + * Returns: + * MOSQ_ERR_SUCCESS - always + * + * See Also: + * , + */ +libmosq_EXPORT int mosquitto_lib_init(void); + +/* + * Function: mosquitto_lib_cleanup + * + * Call to free resources associated with the library. + * + * Returns: + * MOSQ_ERR_SUCCESS - always + * + * See Also: + * , + */ +libmosq_EXPORT int mosquitto_lib_cleanup(void); + +/* + * Function: mosquitto_new + * + * Create a new mosquitto client instance. + * + * Parameters: + * id - String to use as the client id. If NULL, a random client id + * will be generated. If id is NULL, clean_session must be true. + * clean_session - set to true to instruct the broker to clean all messages + * and subscriptions on disconnect, false to instruct it to + * keep them. See the man page mqtt(7) for more details. + * Note that a client will never discard its own outgoing + * messages on disconnect. Calling or + * will cause the messages to be resent. + * Use to reset a client to its + * original state. + * Must be set to true if the id parameter is NULL. + * obj - A user pointer that will be passed as an argument to any + * callbacks that are specified. + * + * Returns: + * Pointer to a struct mosquitto on success. + * NULL on failure. Interrogate errno to determine the cause for the failure: + * - ENOMEM on out of memory. + * - EINVAL on invalid input parameters. + * + * See Also: + * , , + */ +libmosq_EXPORT struct mosquitto *mosquitto_new(const char *id, bool clean_session, void *obj); + +/* + * Function: mosquitto_destroy + * + * Use to free memory associated with a mosquitto client instance. + * + * Parameters: + * mosq - a struct mosquitto pointer to free. + * + * See Also: + * , + */ +libmosq_EXPORT void mosquitto_destroy(struct mosquitto *mosq); + +/* + * Function: mosquitto_reinitialise + * + * This function allows an existing mosquitto client to be reused. Call on a + * mosquitto instance to close any open network connections, free memory + * and reinitialise the client with the new parameters. The end result is the + * same as the output of . + * + * Parameters: + * mosq - a valid mosquitto instance. + * id - string to use as the client id. If NULL, a random client id + * will be generated. If id is NULL, clean_session must be true. + * clean_session - set to true to instruct the broker to clean all messages + * and subscriptions on disconnect, false to instruct it to + * keep them. See the man page mqtt(7) for more details. + * Must be set to true if the id parameter is NULL. + * obj - A user pointer that will be passed as an argument to any + * callbacks that are specified. + * + * Returns: + * MOSQ_ERR_SUCCESS - on success. + * MOSQ_ERR_INVAL - if the input parameters were invalid. + * MOSQ_ERR_NOMEM - if an out of memory condition occurred. + * + * See Also: + * , + */ +libmosq_EXPORT int mosquitto_reinitialise(struct mosquitto *mosq, const char *id, bool clean_session, void *obj); + +/* + * Function: mosquitto_will_set + * + * Configure will information for a mosquitto instance. By default, clients do + * not have a will. This must be called before calling . + * + * Parameters: + * mosq - a valid mosquitto instance. + * topic - the topic on which to publish the will. + * payloadlen - the size of the payload (bytes). Valid values are between 0 and + * 268,435,455. + * payload - pointer to the data to send. If payloadlen > 0 this must be a + * valid memory location. + * qos - integer value 0, 1 or 2 indicating the Quality of Service to be + * used for the will. + * retain - set to true to make the will a retained message. + * + * Returns: + * MOSQ_ERR_SUCCESS - on success. + * MOSQ_ERR_INVAL - if the input parameters were invalid. + * MOSQ_ERR_NOMEM - if an out of memory condition occurred. + * MOSQ_ERR_PAYLOAD_SIZE - if payloadlen is too large. + */ +libmosq_EXPORT int mosquitto_will_set(struct mosquitto *mosq, const char *topic, int payloadlen, const void *payload, int qos, bool retain); + +/* + * Function: mosquitto_will_clear + * + * Remove a previously configured will. This must be called before calling + * . + * + * Parameters: + * mosq - a valid mosquitto instance. + * + * Returns: + * MOSQ_ERR_SUCCESS - on success. + * MOSQ_ERR_INVAL - if the input parameters were invalid. + */ +libmosq_EXPORT int mosquitto_will_clear(struct mosquitto *mosq); + +/* + * Function: mosquitto_username_pw_set + * + * Configure username and password for a mosquitton instance. This is only + * supported by brokers that implement the MQTT spec v3.1. By default, no + * username or password will be sent. + * If username is NULL, the password argument is ignored. + * This must be called before calling mosquitto_connect(). + * + * This is must be called before calling . + * + * Parameters: + * mosq - a valid mosquitto instance. + * username - the username to send as a string, or NULL to disable + * authentication. + * password - the password to send as a string. Set to NULL when username is + * valid in order to send just a username. + * + * Returns: + * MOSQ_ERR_SUCCESS - on success. + * MOSQ_ERR_INVAL - if the input parameters were invalid. + * MOSQ_ERR_NOMEM - if an out of memory condition occurred. + */ +libmosq_EXPORT int mosquitto_username_pw_set(struct mosquitto *mosq, const char *username, const char *password); + +/* + * Function: mosquitto_connect + * + * Connect to an MQTT broker. + * + * Parameters: + * mosq - a valid mosquitto instance. + * host - the hostname or ip address of the broker to connect to. + * port - the network port to connect to. Usually 1883. + * keepalive - the number of seconds after which the broker should send a PING + * message to the client if no other messages have been exchanged + * in that time. + * + * Returns: + * MOSQ_ERR_SUCCESS - on success. + * MOSQ_ERR_INVAL - if the input parameters were invalid. + * MOSQ_ERR_ERRNO - if a system call returned an error. The variable errno + * contains the error code, even on Windows. + * Use strerror_r() where available or FormatMessage() on + * Windows. + * + * See Also: + * , , , , + */ +libmosq_EXPORT int mosquitto_connect(struct mosquitto *mosq, const char *host, int port, int keepalive); + +/* + * Function: mosquitto_connect_bind + * + * Connect to an MQTT broker. This extends the functionality of + * by adding the bind_address parameter. Use this function + * if you need to restrict network communication over a particular interface. + * + * Parameters: + * mosq - a valid mosquitto instance. + * host - the hostname or ip address of the broker to connect to. + * port - the network port to connect to. Usually 1883. + * keepalive - the number of seconds after which the broker should send a PING + * message to the client if no other messages have been exchanged + * in that time. + * bind_address - the hostname or ip address of the local network interface to + * bind to. + * + * Returns: + * MOSQ_ERR_SUCCESS - on success. + * MOSQ_ERR_INVAL - if the input parameters were invalid. + * MOSQ_ERR_ERRNO - if a system call returned an error. The variable errno + * contains the error code, even on Windows. + * Use strerror_r() where available or FormatMessage() on + * Windows. + * + * See Also: + * , , + */ +libmosq_EXPORT int mosquitto_connect_bind(struct mosquitto *mosq, const char *host, int port, int keepalive, const char *bind_address); + +/* + * Function: mosquitto_connect_async + * + * Connect to an MQTT broker. This is a non-blocking call. If you use + * your client must use the threaded interface + * . If you need to use , you must use + * to connect the client. + * + * May be called before or after . + * + * Parameters: + * mosq - a valid mosquitto instance. + * host - the hostname or ip address of the broker to connect to. + * port - the network port to connect to. Usually 1883. + * keepalive - the number of seconds after which the broker should send a PING + * message to the client if no other messages have been exchanged + * in that time. + * + * Returns: + * MOSQ_ERR_SUCCESS - on success. + * MOSQ_ERR_INVAL - if the input parameters were invalid. + * MOSQ_ERR_ERRNO - if a system call returned an error. The variable errno + * contains the error code, even on Windows. + * Use strerror_r() where available or FormatMessage() on + * Windows. + * + * See Also: + * , , , , + */ +libmosq_EXPORT int mosquitto_connect_async(struct mosquitto *mosq, const char *host, int port, int keepalive); + +/* + * Function: mosquitto_connect_async + * + * Connect to an MQTT broker. This is a non-blocking call. If you use + * your client must use the threaded interface + * . If you need to use , you must use + * to connect the client. + * + * This extends the functionality of by adding the + * bind_address parameter. Use this function if you need to restrict network + * communication over a particular interface. + * + * May be called before or after . + * + * Parameters: + * mosq - a valid mosquitto instance. + * host - the hostname or ip address of the broker to connect to. + * port - the network port to connect to. Usually 1883. + * keepalive - the number of seconds after which the broker should send a PING + * message to the client if no other messages have been exchanged + * in that time. + * bind_address - the hostname or ip address of the local network interface to + * bind to. + * + * Returns: + * MOSQ_ERR_SUCCESS - on success. + * MOSQ_ERR_INVAL - if the input parameters were invalid. + * MOSQ_ERR_ERRNO - if a system call returned an error. The variable errno + * contains the error code, even on Windows. + * Use strerror_r() where available or FormatMessage() on + * Windows. + * + * See Also: + * , , + */ +libmosq_EXPORT int mosquitto_connect_bind_async(struct mosquitto *mosq, const char *host, int port, int keepalive, const char *bind_address); + +/* + * Function: mosquitto_connect_srv + * + * Connect to an MQTT broker. This is a non-blocking call. If you use + * your client must use the threaded interface + * . If you need to use , you must use + * to connect the client. + * + * This extends the functionality of by adding the + * bind_address parameter. Use this function if you need to restrict network + * communication over a particular interface. + * + * May be called before or after . + * + * Parameters: + * mosq - a valid mosquitto instance. + * host - the hostname or ip address of the broker to connect to. + * keepalive - the number of seconds after which the broker should send a PING + * message to the client if no other messages have been exchanged + * in that time. + * bind_address - the hostname or ip address of the local network interface to + * bind to. + * + * Returns: + * MOSQ_ERR_SUCCESS - on success. + * MOSQ_ERR_INVAL - if the input parameters were invalid. + * MOSQ_ERR_ERRNO - if a system call returned an error. The variable errno + * contains the error code, even on Windows. + * Use strerror_r() where available or FormatMessage() on + * Windows. + * + * See Also: + * , , + */ +libmosq_EXPORT int mosquitto_connect_srv(struct mosquitto *mosq, const char *host, int keepalive, const char *bind_address); + +/* + * Function: mosquitto_reconnect + * + * Reconnect to a broker. + * + * This function provides an easy way of reconnecting to a broker after a + * connection has been lost. It uses the values that were provided in the + * call. It must not be called before + * . + * + * Parameters: + * mosq - a valid mosquitto instance. + * + * Returns: + * MOSQ_ERR_SUCCESS - on success. + * MOSQ_ERR_INVAL - if the input parameters were invalid. + * MOSQ_ERR_NOMEM - if an out of memory condition occurred. + * + * Returns: + * MOSQ_ERR_SUCCESS - on success. + * MOSQ_ERR_INVAL - if the input parameters were invalid. + * MOSQ_ERR_ERRNO - if a system call returned an error. The variable errno + * contains the error code, even on Windows. + * Use strerror_r() where available or FormatMessage() on + * Windows. + * + * See Also: + * , , + */ +libmosq_EXPORT int mosquitto_reconnect(struct mosquitto *mosq); + +/* + * Function: mosquitto_reconnect_async + * + * Reconnect to a broker. Non blocking version of . + * + * This function provides an easy way of reconnecting to a broker after a + * connection has been lost. It uses the values that were provided in the + * or calls. It must not be + * called before . + * + * Parameters: + * mosq - a valid mosquitto instance. + * + * Returns: + * MOSQ_ERR_SUCCESS - on success. + * MOSQ_ERR_INVAL - if the input parameters were invalid. + * MOSQ_ERR_NOMEM - if an out of memory condition occurred. + * + * Returns: + * MOSQ_ERR_SUCCESS - on success. + * MOSQ_ERR_INVAL - if the input parameters were invalid. + * MOSQ_ERR_ERRNO - if a system call returned an error. The variable errno + * contains the error code, even on Windows. + * Use strerror_r() where available or FormatMessage() on + * Windows. + * + * See Also: + * , + */ +libmosq_EXPORT int mosquitto_reconnect_async(struct mosquitto *mosq); + +/* + * Function: mosquitto_disconnect + * + * Disconnect from the broker. + * + * Parameters: + * mosq - a valid mosquitto instance. + * + * Returns: + * MOSQ_ERR_SUCCESS - on success. + * MOSQ_ERR_INVAL - if the input parameters were invalid. + * MOSQ_ERR_NO_CONN - if the client isn't connected to a broker. + */ +libmosq_EXPORT int mosquitto_disconnect(struct mosquitto *mosq); + +/* + * Function: mosquitto_publish + * + * Publish a message on a given topic. + * + * Parameters: + * mosq - a valid mosquitto instance. + * mid - pointer to an int. If not NULL, the function will set this + * to the message id of this particular message. This can be then + * used with the publish callback to determine when the message + * has been sent. + * Note that although the MQTT protocol doesn't use message ids + * for messages with QoS=0, libmosquitto assigns them message ids + * so they can be tracked with this parameter. + * payloadlen - the size of the payload (bytes). Valid values are between 0 and + * 268,435,455. + * payload - pointer to the data to send. If payloadlen > 0 this must be a + * valid memory location. + * qos - integer value 0, 1 or 2 indicating the Quality of Service to be + * used for the message. + * retain - set to true to make the message retained. + * + * Returns: + * MOSQ_ERR_SUCCESS - on success. + * MOSQ_ERR_INVAL - if the input parameters were invalid. + * MOSQ_ERR_NOMEM - if an out of memory condition occurred. + * MOSQ_ERR_NO_CONN - if the client isn't connected to a broker. + * MOSQ_ERR_PROTOCOL - if there is a protocol error communicating with the + * broker. + * MOSQ_ERR_PAYLOAD_SIZE - if payloadlen is too large. + * + * See Also: + * + */ +libmosq_EXPORT int mosquitto_publish(struct mosquitto *mosq, int *mid, const char *topic, int payloadlen, const void *payload, int qos, bool retain); + +/* + * Function: mosquitto_subscribe + * + * Subscribe to a topic. + * + * Parameters: + * mosq - a valid mosquitto instance. + * mid - a pointer to an int. If not NULL, the function will set this to + * the message id of this particular message. This can be then used + * with the subscribe callback to determine when the message has been + * sent. + * sub - the subscription pattern. + * qos - the requested Quality of Service for this subscription. + * + * Returns: + * MOSQ_ERR_SUCCESS - on success. + * MOSQ_ERR_INVAL - if the input parameters were invalid. + * MOSQ_ERR_NOMEM - if an out of memory condition occurred. + * MOSQ_ERR_NO_CONN - if the client isn't connected to a broker. + */ +libmosq_EXPORT int mosquitto_subscribe(struct mosquitto *mosq, int *mid, const char *sub, int qos); + +/* + * Function: mosquitto_unsubscribe + * + * Unsubscribe from a topic. + * + * Parameters: + * mosq - a valid mosquitto instance. + * mid - a pointer to an int. If not NULL, the function will set this to + * the message id of this particular message. This can be then used + * with the unsubscribe callback to determine when the message has been + * sent. + * sub - the unsubscription pattern. + * + * Returns: + * MOSQ_ERR_SUCCESS - on success. + * MOSQ_ERR_INVAL - if the input parameters were invalid. + * MOSQ_ERR_NOMEM - if an out of memory condition occurred. + * MOSQ_ERR_NO_CONN - if the client isn't connected to a broker. + */ +libmosq_EXPORT int mosquitto_unsubscribe(struct mosquitto *mosq, int *mid, const char *sub); + +/* + * Function: mosquitto_message_copy + * + * Copy the contents of a mosquitto message to another message. + * Useful for preserving a message received in the on_message() callback. + * + * Parameters: + * dst - a pointer to a valid mosquitto_message struct to copy to. + * src - a pointer to a valid mosquitto_message struct to copy from. + * + * Returns: + * MOSQ_ERR_SUCCESS - on success. + * MOSQ_ERR_INVAL - if the input parameters were invalid. + * MOSQ_ERR_NOMEM - if an out of memory condition occurred. + * + * See Also: + * + */ +libmosq_EXPORT int mosquitto_message_copy(struct mosquitto_message *dst, const struct mosquitto_message *src); + +/* + * Function: mosquitto_message_free + * + * Completely free a mosquitto_message struct. + * + * Parameters: + * message - pointer to a mosquitto_message pointer to free. + * + * See Also: + * + */ +libmosq_EXPORT void mosquitto_message_free(struct mosquitto_message **message); + +/* + * Function: mosquitto_loop + * + * The main network loop for the client. You must call this frequently in order + * to keep communications between the client and broker working. If incoming + * data is present it will then be processed. Outgoing commands, from e.g. + * , are normally sent immediately that their function is + * called, but this is not always possible. will also attempt + * to send any remaining outgoing messages, which also includes commands that + * are part of the flow for messages with QoS>0. + * + * An alternative approach is to use to run the client + * loop in its own thread. + * + * This calls select() to monitor the client network socket. If you want to + * integrate mosquitto client operation with your own select() call, use + * , , and + * . + * + * Threads: + * + * Parameters: + * mosq - a valid mosquitto instance. + * timeout - Maximum number of milliseconds to wait for network activity + * in the select() call before timing out. Set to 0 for instant + * return. Set negative to use the default of 1000ms. + * max_packets - this parameter is currently unused and should be set to 1 for + * future compatibility. + * + * Returns: + * MOSQ_ERR_SUCCESS - on success. + * MOSQ_ERR_INVAL - if the input parameters were invalid. + * MOSQ_ERR_NOMEM - if an out of memory condition occurred. + * MOSQ_ERR_NO_CONN - if the client isn't connected to a broker. + * MOSQ_ERR_CONN_LOST - if the connection to the broker was lost. + * MOSQ_ERR_PROTOCOL - if there is a protocol error communicating with the + * broker. + * MOSQ_ERR_ERRNO - if a system call returned an error. The variable errno + * contains the error code, even on Windows. + * Use strerror_r() where available or FormatMessage() on + * Windows. + * See Also: + * , , + */ +libmosq_EXPORT int mosquitto_loop(struct mosquitto *mosq, int timeout, int max_packets); + +/* + * Function: mosquitto_loop_forever + * + * This function call loop() for you in an infinite blocking loop. It is useful + * for the case where you only want to run the MQTT client loop in your + * program. + * + * It handles reconnecting in case server connection is lost. If you call + * mosquitto_disconnect() in a callback it will return. + * + * Parameters: + * mosq - a valid mosquitto instance. + * timeout - Maximum number of milliseconds to wait for network activity + * in the select() call before timing out. Set to 0 for instant + * return. Set negative to use the default of 1000ms. + * max_packets - this parameter is currently unused and should be set to 1 for + * future compatibility. + * + * Returns: + * MOSQ_ERR_SUCCESS - on success. + * MOSQ_ERR_INVAL - if the input parameters were invalid. + * MOSQ_ERR_NOMEM - if an out of memory condition occurred. + * MOSQ_ERR_NO_CONN - if the client isn't connected to a broker. + * MOSQ_ERR_CONN_LOST - if the connection to the broker was lost. + * MOSQ_ERR_PROTOCOL - if there is a protocol error communicating with the + * broker. + * MOSQ_ERR_ERRNO - if a system call returned an error. The variable errno + * contains the error code, even on Windows. + * Use strerror_r() where available or FormatMessage() on + * Windows. + * + * See Also: + * , + */ +libmosq_EXPORT int mosquitto_loop_forever(struct mosquitto *mosq, int timeout, int max_packets); + +/* + * Function: mosquitto_loop_start + * + * This is part of the threaded client interface. Call this once to start a new + * thread to process network traffic. This provides an alternative to + * repeatedly calling yourself. + * + * Parameters: + * mosq - a valid mosquitto instance. + * + * Returns: + * MOSQ_ERR_SUCCESS - on success. + * MOSQ_ERR_INVAL - if the input parameters were invalid. + * MOSQ_ERR_NOT_SUPPORTED - if thread support is not available. + * + * See Also: + * , , , + */ +libmosq_EXPORT int mosquitto_loop_start(struct mosquitto *mosq); + +/* + * Function: mosquitto_loop_stop + * + * This is part of the threaded client interface. Call this once to stop the + * network thread previously created with . This call + * will block until the network thread finishes. For the network thread to end, + * you must have previously called or have set the force + * parameter to true. + * + * Parameters: + * mosq - a valid mosquitto instance. + * force - set to true to force thread cancellation. If false, + * must have already been called. + * + * Returns: + * MOSQ_ERR_SUCCESS - on success. + * MOSQ_ERR_INVAL - if the input parameters were invalid. + * MOSQ_ERR_NOT_SUPPORTED - if thread support is not available. + * + * See Also: + * , + */ +libmosq_EXPORT int mosquitto_loop_stop(struct mosquitto *mosq, bool force); + +/* + * Function: mosquitto_socket + * + * Return the socket handle for a mosquitto instance. Useful if you want to + * include a mosquitto client in your own select() calls. + * + * Parameters: + * mosq - a valid mosquitto instance. + * + * Returns: + * The socket for the mosquitto client or -1 on failure. + */ +libmosq_EXPORT int mosquitto_socket(struct mosquitto *mosq); + +/* + * Function: mosquitto_loop_read + * + * Carry out network read operations. + * This should only be used if you are not using mosquitto_loop() and are + * monitoring the client network socket for activity yourself. + * + * Parameters: + * mosq - a valid mosquitto instance. + * max_packets - this parameter is currently unused and should be set to 1 for + * future compatibility. + * + * Returns: + * MOSQ_ERR_SUCCESS - on success. + * MOSQ_ERR_INVAL - if the input parameters were invalid. + * MOSQ_ERR_NOMEM - if an out of memory condition occurred. + * MOSQ_ERR_NO_CONN - if the client isn't connected to a broker. + * MOSQ_ERR_CONN_LOST - if the connection to the broker was lost. + * MOSQ_ERR_PROTOCOL - if there is a protocol error communicating with the + * broker. + * MOSQ_ERR_ERRNO - if a system call returned an error. The variable errno + * contains the error code, even on Windows. + * Use strerror_r() where available or FormatMessage() on + * Windows. + * + * See Also: + * , , + */ +libmosq_EXPORT int mosquitto_loop_read(struct mosquitto *mosq, int max_packets); + +/* + * Function: mosquitto_loop_write + * + * Carry out network write operations. + * This should only be used if you are not using mosquitto_loop() and are + * monitoring the client network socket for activity yourself. + * + * Parameters: + * mosq - a valid mosquitto instance. + * max_packets - this parameter is currently unused and should be set to 1 for + * future compatibility. + * + * Returns: + * MOSQ_ERR_SUCCESS - on success. + * MOSQ_ERR_INVAL - if the input parameters were invalid. + * MOSQ_ERR_NOMEM - if an out of memory condition occurred. + * MOSQ_ERR_NO_CONN - if the client isn't connected to a broker. + * MOSQ_ERR_CONN_LOST - if the connection to the broker was lost. + * MOSQ_ERR_PROTOCOL - if there is a protocol error communicating with the + * broker. + * MOSQ_ERR_ERRNO - if a system call returned an error. The variable errno + * contains the error code, even on Windows. + * Use strerror_r() where available or FormatMessage() on + * Windows. + * + * See Also: + * , , , + */ +libmosq_EXPORT int mosquitto_loop_write(struct mosquitto *mosq, int max_packets); + +/* + * Function: mosquitto_loop_misc + * + * Carry out miscellaneous operations required as part of the network loop. + * This should only be used if you are not using mosquitto_loop() and are + * monitoring the client network socket for activity yourself. + * + * This function deals with handling PINGs and checking whether messages need + * to be retried, so should be called fairly frequently. + * + * Parameters: + * mosq - a valid mosquitto instance. + * + * Returns: + * MOSQ_ERR_SUCCESS - on success. + * MOSQ_ERR_INVAL - if the input parameters were invalid. + * MOSQ_ERR_NO_CONN - if the client isn't connected to a broker. + * + * See Also: + * , , + */ +libmosq_EXPORT int mosquitto_loop_misc(struct mosquitto *mosq); + +/* + * Function: mosquitto_want_write + * + * Returns true if there is data ready to be written on the socket. + * + * Parameters: + * mosq - a valid mosquitto instance. + * + * See Also: + * , , + */ +libmosq_EXPORT bool mosquitto_want_write(struct mosquitto *mosq); + +/* + * Function: mosquitto_tls_set + * + * Configure the client for certificate based SSL/TLS support. Must be called + * before . + * + * Cannot be used in conjunction with . + * + * Define the Certificate Authority certificates to be trusted (ie. the server + * certificate must be signed with one of these certificates) using cafile. + * + * If the server you are connecting to requires clients to provide a + * certificate, define certfile and keyfile with your client certificate and + * private key. If your private key is encrypted, provide a password callback + * function or you will have to enter the password at the command line. + * + * Parameters: + * mosq - a valid mosquitto instance. + * cafile - path to a file containing the PEM encoded trusted CA + * certificate files. Either cafile or capath must not be NULL. + * capath - path to a directory containing the PEM encoded trusted CA + * certificate files. See mosquitto.conf for more details on + * configuring this directory. Either cafile or capath must not + * be NULL. + * certfile - path to a file containing the PEM encoded certificate file + * for this client. If NULL, keyfile must also be NULL and no + * client certificate will be used. + * keyfile - path to a file containing the PEM encoded private key for + * this client. If NULL, certfile must also be NULL and no + * client certificate will be used. + * pw_callback - if keyfile is encrypted, set pw_callback to allow your client + * to pass the correct password for decryption. If set to NULL, + * the password must be entered on the command line. + * Your callback must write the password into "buf", which is + * "size" bytes long. The return value must be the length of the + * password. "userdata" will be set to the calling mosquitto + * instance. + * + * Returns: + * MOSQ_ERR_SUCCESS - on success. + * MOSQ_ERR_INVAL - if the input parameters were invalid. + * MOSQ_ERR_NOMEM - if an out of memory condition occurred. + * + * See Also: + * , , + */ +libmosq_EXPORT int mosquitto_tls_set(struct mosquitto *mosq, + const char *cafile, const char *capath, + const char *certfile, const char *keyfile, + int (*pw_callback)(char *buf, int size, int rwflag, void *userdata)); + +/* + * Function: mosquitto_tls_insecure_set + * + * Configure verification of the server hostname in the server certificate. If + * value is set to true, it is impossible to guarantee that the host you are + * connecting to is not impersonating your server. This can be useful in + * initial server testing, but makes it possible for a malicious third party to + * impersonate your server through DNS spoofing, for example. + * Do not use this function in a real system. Setting value to true makes the + * connection encryption pointless. + * Must be called before . + * + * Parameters: + * mosq - a valid mosquitto instance. + * value - if set to false, the default, certificate hostname checking is + * performed. If set to true, no hostname checking is performed and + * the connection is insecure. + * + * Returns: + * MOSQ_ERR_SUCCESS - on success. + * MOSQ_ERR_INVAL - if the input parameters were invalid. + * + * See Also: + * + */ +libmosq_EXPORT int mosquitto_tls_insecure_set(struct mosquitto *mosq, bool value); + +/* + * Function: mosquitto_tls_opts_set + * + * Set advanced SSL/TLS options. Must be called before . + * + * Parameters: + * mosq - a valid mosquitto instance. + * cert_reqs - an integer defining the verification requirements the client + * will impose on the server. This can be one of: + * * SSL_VERIFY_NONE (0): the server will not be verified in any way. + * * SSL_VERIFY_PEER (1): the server certificate will be verified + * and the connection aborted if the verification fails. + * The default and recommended value is SSL_VERIFY_PEER. Using + * SSL_VERIFY_NONE provides no security. + * tls_version - the version of the SSL/TLS protocol to use as a string. If NULL, + * the default value is used. The default value and the + * available values depend on the version of openssl that the + * library was compiled against. For openssl >= 1.0.1, the + * available options are tlsv1.2, tlsv1.1 and tlsv1, with tlv1.2 + * as the default. For openssl < 1.0.1, only tlsv1 is available. + * ciphers - a string describing the ciphers available for use. See the + * "openssl ciphers" tool for more information. If NULL, the + * default ciphers will be used. + * + * Returns: + * MOSQ_ERR_SUCCESS - on success. + * MOSQ_ERR_INVAL - if the input parameters were invalid. + * MOSQ_ERR_NOMEM - if an out of memory condition occurred. + * + * See Also: + * + */ +libmosq_EXPORT int mosquitto_tls_opts_set(struct mosquitto *mosq, int cert_reqs, const char *tls_version, const char *ciphers); + +/* + * Function: mosquitto_tls_psk_set + * + * Configure the client for pre-shared-key based TLS support. Must be called + * before . + * + * Cannot be used in conjunction with . + * + * Parameters: + * mosq - a valid mosquitto instance. + * psk - the pre-shared-key in hex format with no leading "0x". + * identity - the identity of this client. May be used as the username + * depending on the server settings. + * ciphers - a string describing the PSK ciphers available for use. See the + * "openssl ciphers" tool for more information. If NULL, the + * default ciphers will be used. + * + * Returns: + * MOSQ_ERR_SUCCESS - on success. + * MOSQ_ERR_INVAL - if the input parameters were invalid. + * MOSQ_ERR_NOMEM - if an out of memory condition occurred. + * + * See Also: + * + */ +libmosq_EXPORT int mosquitto_tls_psk_set(struct mosquitto *mosq, const char *psk, const char *identity, const char *ciphers); + +/* + * Function: mosquitto_connect_callback_set + * + * Set the connect callback. This is called when the broker sends a CONNACK + * message in response to a connection. + * + * Parameters: + * mosq - a valid mosquitto instance. + * on_connect - a callback function in the following form: + * void callback(struct mosquitto *mosq, void *obj, int rc) + * + * Callback Parameters: + * mosq - the mosquitto instance making the callback. + * obj - the user data provided in + * rc - the return code of the connection response, one of: + * + * * 0 - success + * * 1 - connection refused (unacceptable protocol version) + * * 2 - connection refused (identifier rejected) + * * 3 - connection refused (broker unavailable) + * * 4-255 - reserved for future use + */ +libmosq_EXPORT void mosquitto_connect_callback_set(struct mosquitto *mosq, void (*on_connect)(struct mosquitto *, void *, int)); + +/* + * Function: mosquitto_disconnect_callback_set + * + * Set the disconnect callback. This is called when the broker has received the + * DISCONNECT command and has disconnected the client. + * + * Parameters: + * mosq - a valid mosquitto instance. + * on_disconnect - a callback function in the following form: + * void callback(struct mosquitto *mosq, void *obj) + * + * Callback Parameters: + * mosq - the mosquitto instance making the callback. + * obj - the user data provided in + * rc - integer value indicating the reason for the disconnect. A value of 0 + * means the client has called . Any other value + * indicates that the disconnect is unexpected. + */ +libmosq_EXPORT void mosquitto_disconnect_callback_set(struct mosquitto *mosq, void (*on_disconnect)(struct mosquitto *, void *, int)); + +/* + * Function: mosquitto_publish_callback_set + * + * Set the publish callback. This is called when a message initiated with + * has been sent to the broker successfully. + * + * Parameters: + * mosq - a valid mosquitto instance. + * on_publish - a callback function in the following form: + * void callback(struct mosquitto *mosq, void *obj, int mid) + * + * Callback Parameters: + * mosq - the mosquitto instance making the callback. + * obj - the user data provided in + * mid - the message id of the sent message. + */ +libmosq_EXPORT void mosquitto_publish_callback_set(struct mosquitto *mosq, void (*on_publish)(struct mosquitto *, void *, int)); + +/* + * Function: mosquitto_message_callback_set + * + * Set the message callback. This is called when a message is received from the + * broker. + * + * Parameters: + * mosq - a valid mosquitto instance. + * on_message - a callback function in the following form: + * void callback(struct mosquitto *mosq, void *obj, const struct mosquitto_message *message) + * + * Callback Parameters: + * mosq - the mosquitto instance making the callback. + * obj - the user data provided in + * message - the message data. This variable and associated memory will be + * freed by the library after the callback completes. The client + * should make copies of any of the data it requires. + * + * See Also: + * + */ +libmosq_EXPORT void mosquitto_message_callback_set(struct mosquitto *mosq, void (*on_message)(struct mosquitto *, void *, const struct mosquitto_message *)); + +/* + * Function: mosquitto_subscribe_callback_set + * + * Set the subscribe callback. This is called when the broker responds to a + * subscription request. + * + * Parameters: + * mosq - a valid mosquitto instance. + * on_subscribe - a callback function in the following form: + * void callback(struct mosquitto *mosq, void *obj, int mid, int qos_count, const int *granted_qos) + * + * Callback Parameters: + * mosq - the mosquitto instance making the callback. + * obj - the user data provided in + * mid - the message id of the subscribe message. + * qos_count - the number of granted subscriptions (size of granted_qos). + * granted_qos - an array of integers indicating the granted QoS for each of + * the subscriptions. + */ +libmosq_EXPORT void mosquitto_subscribe_callback_set(struct mosquitto *mosq, void (*on_subscribe)(struct mosquitto *, void *, int, int, const int *)); + +/* + * Function: mosquitto_unsubscribe_callback_set + * + * Set the unsubscribe callback. This is called when the broker responds to a + * unsubscription request. + * + * Parameters: + * mosq - a valid mosquitto instance. + * on_unsubscribe - a callback function in the following form: + * void callback(struct mosquitto *mosq, void *obj, int mid) + * + * Callback Parameters: + * mosq - the mosquitto instance making the callback. + * obj - the user data provided in + * mid - the message id of the unsubscribe message. + */ +libmosq_EXPORT void mosquitto_unsubscribe_callback_set(struct mosquitto *mosq, void (*on_unsubscribe)(struct mosquitto *, void *, int)); + +/* + * Function: mosquitto_log_callback_set + * + * Set the logging callback. This should be used if you want event logging + * information from the client library. + * + * mosq - a valid mosquitto instance. + * on_log - a callback function in the following form: + * void callback(struct mosquitto *mosq, void *obj, int level, const char *str) + * + * Callback Parameters: + * mosq - the mosquitto instance making the callback. + * obj - the user data provided in + * level - the log message level from the values: + * MOSQ_LOG_INFO + * MOSQ_LOG_NOTICE + * MOSQ_LOG_WARNING + * MOSQ_LOG_ERR + * MOSQ_LOG_DEBUG + * str - the message string. + */ +libmosq_EXPORT void mosquitto_log_callback_set(struct mosquitto *mosq, void (*on_log)(struct mosquitto *, void *, int, const char *)); + +/* + * Function: mosquitto_reconnect_delay_set + * + * Control the behaviour of the client when it has unexpectedly disconnected in + * or after . The default + * behaviour if this function is not used is to repeatedly attempt to reconnect + * with a delay of 1 second until the connection succeeds. + * + * Use reconnect_delay parameter to change the delay between successive + * reconnection attempts. You may also enable exponential backoff of the time + * between reconnections by setting reconnect_exponential_backoff to true and + * set an upper bound on the delay with reconnect_delay_max. + * + * Example 1: + * delay=2, delay_max=10, exponential_backoff=False + * Delays would be: 2, 4, 6, 8, 10, 10, ... + * + * Example 2: + * delay=3, delay_max=30, exponential_backoff=True + * Delays would be: 3, 6, 12, 24, 30, 30, ... + * + * Parameters: + * mosq - a valid mosquitto instance. + * reconnect_delay - the number of seconds to wait between + * reconnects. + * reconnect_delay_max - the maximum number of seconds to wait + * between reconnects. + * reconnect_exponential_backoff - use exponential backoff between + * reconnect attempts. Set to true to enable + * exponential backoff. + * + * Returns: + * MOSQ_ERR_SUCCESS - on success. + * MOSQ_ERR_INVAL - if the input parameters were invalid. + */ +libmosq_EXPORT int mosquitto_reconnect_delay_set(struct mosquitto *mosq, unsigned int reconnect_delay, unsigned int reconnect_delay_max, bool reconnect_exponential_backoff); + +/* + * Function: mosquitto_max_inflight_messages_set + * + * Set the number of QoS 1 and 2 messages that can be "in flight" at one time. + * An in flight message is part way through its delivery flow. Attempts to send + * further messages with will result in the messages being + * queued until the number of in flight messages reduces. + * + * A higher number here results in greater message throughput, but if set + * higher than the maximum in flight messages on the broker may lead to + * delays in the messages being acknowledged. + * + * Set to 0 for no maximum. + * + * Parameters: + * mosq - a valid mosquitto instance. + * max_inflight_messages - the maximum number of inflight messages. Defaults + * to 20. + * + * Returns: + * MOSQ_ERR_SUCCESS - on success. + * MOSQ_ERR_INVAL - if the input parameters were invalid. + */ +libmosq_EXPORT int mosquitto_max_inflight_messages_set(struct mosquitto *mosq, unsigned int max_inflight_messages); + +/* + * Function: mosquitto_message_retry_set + * + * Set the number of seconds to wait before retrying messages. This applies to + * publish messages with QoS>0. May be called at any time. + * + * Parameters: + * mosq - a valid mosquitto instance. + * message_retry - the number of seconds to wait for a response before + * retrying. Defaults to 20. + */ +libmosq_EXPORT void mosquitto_message_retry_set(struct mosquitto *mosq, unsigned int message_retry); + +/* + * Function: mosquitto_user_data_set + * + * When is called, the pointer given as the "obj" parameter + * will be passed to the callbacks as user data. The + * function allows this obj parameter to be updated at any time. This function + * will not modify the memory pointed to by the current user data pointer. If + * it is dynamically allocated memory you must free it yourself. + * + * Parameters: + * mosq - a valid mosquitto instance. + * obj - A user pointer that will be passed as an argument to any callbacks + * that are specified. + */ +libmosq_EXPORT void mosquitto_user_data_set(struct mosquitto *mosq, void *obj); + + +/* ============================================================================= + * + * Section: Utility functions + * + * ============================================================================= + */ + +/* + * Function: mosquitto_strerror + * + * Call to obtain a const string description of a mosquitto error number. + * + * Parameters: + * mosq_errno - a mosquitto error number. + * + * Returns: + * A constant string describing the error. + */ +libmosq_EXPORT const char *mosquitto_strerror(int mosq_errno); + +/* + * Function: mosquitto_connack_string + * + * Call to obtain a const string description of an MQTT connection result. + * + * Parameters: + * connack_code - an MQTT connection result. + * + * Returns: + * A constant string describing the result. + */ +libmosq_EXPORT const char *mosquitto_connack_string(int connack_code); + +/* + * Function: mosquitto_sub_topic_tokenise + * + * Tokenise a topic or subscription string into an array of strings + * representing the topic hierarchy. + * + * For example: + * + * subtopic: "a/deep/topic/hierarchy" + * + * Would result in: + * + * topics[0] = "a" + * topics[1] = "deep" + * topics[2] = "topic" + * topics[3] = "hierarchy" + * + * and: + * + * subtopic: "/a/deep/topic/hierarchy/" + * + * Would result in: + * + * topics[0] = NULL + * topics[1] = "a" + * topics[2] = "deep" + * topics[3] = "topic" + * topics[4] = "hierarchy" + * + * Parameters: + * subtopic - the subscription/topic to tokenise + * topics - a pointer to store the array of strings + * count - an int pointer to store the number of items in the topics array. + * + * Returns: + * MOSQ_ERR_SUCCESS - on success + * MOSQ_ERR_NOMEM - if an out of memory condition occurred. + * + * Example: + * + * > char **topics; + * > int topic_count; + * > int i; + * > + * > mosquitto_sub_topic_tokenise("$SYS/broker/uptime", &topics, &topic_count); + * > + * > for(i=0; i printf("%d: %s\n", i, topics[i]); + * > } + * + * See Also: + * + */ +libmosq_EXPORT int mosquitto_sub_topic_tokenise(const char *subtopic, char ***topics, int *count); + +/* + * Function: mosquitto_sub_topic_tokens_free + * + * Free memory that was allocated in . + * + * Parameters: + * topics - pointer to string array. + * count - count of items in string array. + * + * Returns: + * MOSQ_ERR_SUCCESS - on success + * MOSQ_ERR_INVAL - if the input parameters were invalid. + * + * See Also: + * + */ +libmosq_EXPORT int mosquitto_sub_topic_tokens_free(char ***topics, int count); + +/* + * Function: mosquitto_topic_matches_sub + * + * Check whether a topic matches a subscription. + * + * For example: + * + * foo/bar would match the subscription foo/# or +/bar + * non/matching would not match the subscription non/+/+ + * + * Parameters: + * sub - subscription string to check topic against. + * topic - topic to check. + * result - bool pointer to hold result. Will be set to true if the topic + * matches the subscription. + * + * Returns: + * MOSQ_ERR_SUCCESS - on success + * MOSQ_ERR_INVAL - if the input parameters were invalid. + * MOSQ_ERR_NOMEM - if an out of memory condition occurred. + */ +libmosq_EXPORT int mosquitto_topic_matches_sub(const char *sub, const char *topic, bool *result); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/mosquitto_internal.h b/lib/mosquitto_internal.h new file mode 100644 index 0000000000..d2d2c6ff1d --- /dev/null +++ b/lib/mosquitto_internal.h @@ -0,0 +1,230 @@ +/* +Copyright (c) 2010-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#ifndef _MOSQUITTO_INTERNAL_H_ +#define _MOSQUITTO_INTERNAL_H_ + +#include + +#ifdef WIN32 +# include +#endif + +#ifdef WITH_TLS +#include +#endif +#include + +#if defined(WITH_THREADING) && !defined(WITH_BROKER) +# include +#else +# include +#endif + +#ifdef WITH_SRV +# include +#endif + +#ifdef WIN32 +# if _MSC_VER < 1600 + typedef unsigned char uint8_t; + typedef unsigned short uint16_t; + typedef unsigned int uint32_t; + typedef unsigned long long uint64_t; +# else +# include +# endif +#else +# include +#endif + +#include "mosquitto.h" +#include "time_mosq.h" +#ifdef WITH_BROKER +struct mosquitto_client_msg; +#endif + +enum mosquitto_msg_direction { + mosq_md_in = 0, + mosq_md_out = 1 +}; + +enum mosquitto_msg_state { + mosq_ms_invalid = 0, + mosq_ms_publish_qos0 = 1, + mosq_ms_publish_qos1 = 2, + mosq_ms_wait_for_puback = 3, + mosq_ms_publish_qos2 = 4, + mosq_ms_wait_for_pubrec = 5, + mosq_ms_resend_pubrel = 6, + mosq_ms_wait_for_pubrel = 7, + mosq_ms_resend_pubcomp = 8, + mosq_ms_wait_for_pubcomp = 9, + mosq_ms_send_pubrec = 10, + mosq_ms_queued = 11 +}; + +enum mosquitto_client_state { + mosq_cs_new = 0, + mosq_cs_connected = 1, + mosq_cs_disconnecting = 2, + mosq_cs_connect_async = 3, + mosq_cs_connect_pending = 4, + mosq_cs_connect_srv = 5 +}; + +enum _mosquitto_protocol { + mosq_p_invalid = 0, + mosq_p_mqtt31 = 1, + mosq_p_mqtt311 = 2, + mosq_p_mqtts = 3 +}; + +enum _mosquitto_transport { + mosq_t_invalid = 0, + mosq_t_tcp = 1, + mosq_t_ws = 2, + mosq_t_sctp = 3 +}; + +struct _mosquitto_packet{ + uint8_t command; + uint8_t have_remaining; + uint8_t remaining_count; + uint16_t mid; + uint32_t remaining_mult; + uint32_t remaining_length; + uint32_t packet_length; + uint32_t to_process; + uint32_t pos; + uint8_t *payload; + struct _mosquitto_packet *next; +}; + +struct mosquitto_message_all{ + struct mosquitto_message_all *next; + time_t timestamp; + //enum mosquitto_msg_direction direction; + enum mosquitto_msg_state state; + bool dup; + struct mosquitto_message msg; +}; + +struct mosquitto { +#ifndef WIN32 + int sock; +# ifndef WITH_BROKER + int sockpairR, sockpairW; +# endif +#else + SOCKET sock; +# ifndef WITH_BROKER + SOCKET sockpairR, sockpairW; +# endif +#endif + enum _mosquitto_protocol protocol; + char *address; + char *id; + char *username; + char *password; + uint16_t keepalive; + bool clean_session; + enum mosquitto_client_state state; + time_t last_msg_in; + time_t last_msg_out; + time_t ping_t; + uint16_t last_mid; + struct _mosquitto_packet in_packet; + struct _mosquitto_packet *current_out_packet; + struct _mosquitto_packet *out_packet; + struct mosquitto_message *will; +#ifdef WITH_TLS + SSL *ssl; + SSL_CTX *ssl_ctx; + char *tls_cafile; + char *tls_capath; + char *tls_certfile; + char *tls_keyfile; + int (*tls_pw_callback)(char *buf, int size, int rwflag, void *userdata); + int tls_cert_reqs; + char *tls_version; + char *tls_ciphers; + char *tls_psk; + char *tls_psk_identity; + bool tls_insecure; +#endif + bool want_write; +#if defined(WITH_THREADING) && !defined(WITH_BROKER) + pthread_mutex_t callback_mutex; + pthread_mutex_t log_callback_mutex; + pthread_mutex_t msgtime_mutex; + pthread_mutex_t out_packet_mutex; + pthread_mutex_t current_out_packet_mutex; + pthread_mutex_t state_mutex; + pthread_mutex_t in_message_mutex; + pthread_mutex_t out_message_mutex; + pthread_t thread_id; +#endif +#ifdef WITH_BROKER + bool is_bridge; + struct _mqtt3_bridge *bridge; + struct mosquitto_client_msg *msgs; + struct mosquitto_client_msg *last_msg; + int msg_count; + int msg_count12; + struct _mosquitto_acl_user *acl_list; + struct _mqtt3_listener *listener; + time_t disconnect_t; + int pollfd_index; + int db_index; + struct _mosquitto_packet *out_packet_last; + bool is_dropping; +#else + void *userdata; + bool in_callback; + unsigned int message_retry; + time_t last_retry_check; + struct mosquitto_message_all *in_messages; + struct mosquitto_message_all *in_messages_last; + struct mosquitto_message_all *out_messages; + struct mosquitto_message_all *out_messages_last; + void (*on_connect)(struct mosquitto *, void *userdata, int rc); + void (*on_disconnect)(struct mosquitto *, void *userdata, int rc); + void (*on_publish)(struct mosquitto *, void *userdata, int mid); + void (*on_message)(struct mosquitto *, void *userdata, const struct mosquitto_message *message); + void (*on_subscribe)(struct mosquitto *, void *userdata, int mid, int qos_count, const int *granted_qos); + void (*on_unsubscribe)(struct mosquitto *, void *userdata, int mid); + void (*on_log)(struct mosquitto *, void *userdata, int level, const char *str); + //void (*on_error)(); + char *host; + int port; + int in_queue_len; + int out_queue_len; + char *bind_address; + unsigned int reconnect_delay; + unsigned int reconnect_delay_max; + bool reconnect_exponential_backoff; + bool threaded; + struct _mosquitto_packet *out_packet_last; + int inflight_messages; + int max_inflight_messages; +# ifdef WITH_SRV + ares_channel achan; +# endif +#endif +}; + +#endif diff --git a/lib/mqtt3_protocol.h b/lib/mqtt3_protocol.h new file mode 100644 index 0000000000..0e8ac8d8e3 --- /dev/null +++ b/lib/mqtt3_protocol.h @@ -0,0 +1,53 @@ +/* +Copyright (c) 2009-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#ifndef _MQTT3_PROTOCOL_H_ +#define _MQTT3_PROTOCOL_H_ + +/* For version 3 of the MQTT protocol */ + +#define PROTOCOL_NAME_v31 "MQIsdp" +#define PROTOCOL_VERSION_v31 3 + +#define PROTOCOL_NAME_v311 "MQTT" +#define PROTOCOL_VERSION_v311 4 + +/* Message types */ +#define CONNECT 0x10 +#define CONNACK 0x20 +#define PUBLISH 0x30 +#define PUBACK 0x40 +#define PUBREC 0x50 +#define PUBREL 0x60 +#define PUBCOMP 0x70 +#define SUBSCRIBE 0x80 +#define SUBACK 0x90 +#define UNSUBSCRIBE 0xA0 +#define UNSUBACK 0xB0 +#define PINGREQ 0xC0 +#define PINGRESP 0xD0 +#define DISCONNECT 0xE0 + +#define CONNACK_ACCEPTED 0 +#define CONNACK_REFUSED_PROTOCOL_VERSION 1 +#define CONNACK_REFUSED_IDENTIFIER_REJECTED 2 +#define CONNACK_REFUSED_SERVER_UNAVAILABLE 3 +#define CONNACK_REFUSED_BAD_USERNAME_PASSWORD 4 +#define CONNACK_REFUSED_NOT_AUTHORIZED 5 + +#define MQTT_MAX_PAYLOAD 268435455 + +#endif diff --git a/lib/net_mosq.c b/lib/net_mosq.c new file mode 100644 index 0000000000..42ea4ce919 --- /dev/null +++ b/lib/net_mosq.c @@ -0,0 +1,1116 @@ +/* +Copyright (c) 2009-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#include +#include +#include +#include +#include +#ifndef WIN32 +#include +#include +#include +#else +#include +#include +#endif + +#ifdef ANDROID +#include +#include +#include +#endif + +#ifdef __FreeBSD__ +# include +#endif + +#ifdef __SYMBIAN32__ +#include +#endif + +#ifdef __QNX__ +#ifndef AI_ADDRCONFIG +#define AI_ADDRCONFIG 0 +#endif +#include +#include +#endif + +#ifdef WITH_TLS +#include +#include +#endif + +#ifdef WITH_BROKER +# include +# ifdef WITH_SYS_TREE + extern uint64_t g_bytes_received; + extern uint64_t g_bytes_sent; + extern unsigned long g_msgs_received; + extern unsigned long g_msgs_sent; + extern unsigned long g_pub_msgs_received; + extern unsigned long g_pub_msgs_sent; +# endif +#else +# include +#endif + +#include +#include +#include +#include +#include +#include + +#ifdef WITH_TLS +int tls_ex_index_mosq = -1; +#endif + +void _mosquitto_net_init(void) +{ +#ifdef WIN32 + WSADATA wsaData; + WSAStartup(MAKEWORD(2,2), &wsaData); +#endif + +#ifdef WITH_SRV + ares_library_init(ARES_LIB_INIT_ALL); +#endif + +#ifdef WITH_TLS + SSL_load_error_strings(); + SSL_library_init(); + OpenSSL_add_all_algorithms(); + if(tls_ex_index_mosq == -1){ + tls_ex_index_mosq = SSL_get_ex_new_index(0, "client context", NULL, NULL, NULL); + } +#endif +} + +void _mosquitto_net_cleanup(void) +{ +#ifdef WITH_TLS + ERR_free_strings(); + EVP_cleanup(); + CRYPTO_cleanup_all_ex_data(); +#endif + +#ifdef WITH_SRV + ares_library_cleanup(); +#endif + +#ifdef WIN32 + WSACleanup(); +#endif +} + +void _mosquitto_packet_cleanup(struct _mosquitto_packet *packet) +{ + if(!packet) return; + + /* Free data and reset values */ + packet->command = 0; + packet->have_remaining = 0; + packet->remaining_count = 0; + packet->remaining_mult = 1; + packet->remaining_length = 0; + if(packet->payload) _mosquitto_free(packet->payload); + packet->payload = NULL; + packet->to_process = 0; + packet->pos = 0; +} + +int _mosquitto_packet_queue(struct mosquitto *mosq, struct _mosquitto_packet *packet) +{ +#ifndef WITH_BROKER + char sockpair_data = 0; +#endif + assert(mosq); + assert(packet); + + packet->pos = 0; + packet->to_process = packet->packet_length; + + packet->next = NULL; + pthread_mutex_lock(&mosq->out_packet_mutex); + if(mosq->out_packet){ + mosq->out_packet_last->next = packet; + }else{ + mosq->out_packet = packet; + } + mosq->out_packet_last = packet; + pthread_mutex_unlock(&mosq->out_packet_mutex); +#ifdef WITH_BROKER + return _mosquitto_packet_write(mosq); +#else + + /* Write a single byte to sockpairW (connected to sockpairR) to break out + * of select() if in threaded mode. */ + if(mosq->sockpairW != INVALID_SOCKET){ +#ifndef WIN32 + if(write(mosq->sockpairW, &sockpair_data, 1)){ + } +#else + send(mosq->sockpairW, &sockpair_data, 1, 0); +#endif + } + + if(mosq->in_callback == false && mosq->threaded == false){ + return _mosquitto_packet_write(mosq); + }else{ + return MOSQ_ERR_SUCCESS; + } +#endif +} + +/* Close a socket associated with a context and set it to -1. + * Returns 1 on failure (context is NULL) + * Returns 0 on success. + */ +int _mosquitto_socket_close(struct mosquitto *mosq) +{ + int rc = 0; + + assert(mosq); +#ifdef WITH_TLS + if(mosq->ssl){ + SSL_shutdown(mosq->ssl); + SSL_free(mosq->ssl); + mosq->ssl = NULL; + } + if(mosq->ssl_ctx){ + SSL_CTX_free(mosq->ssl_ctx); + mosq->ssl_ctx = NULL; + } +#endif + + if(mosq->sock != INVALID_SOCKET){ + rc = COMPAT_CLOSE(mosq->sock); + mosq->sock = INVALID_SOCKET; + } + + return rc; +} + +#ifdef REAL_WITH_TLS_PSK +static unsigned int psk_client_callback(SSL *ssl, const char *hint, + char *identity, unsigned int max_identity_len, + unsigned char *psk, unsigned int max_psk_len) +{ + struct mosquitto *mosq; + int len; + + mosq = SSL_get_ex_data(ssl, tls_ex_index_mosq); + if(!mosq) return 0; + + snprintf(identity, max_identity_len, "%s", mosq->tls_psk_identity); + + len = _mosquitto_hex2bin(mosq->tls_psk, psk, max_psk_len); + if (len < 0) return 0; + return len; +} +#endif + +int _mosquitto_try_connect(const char *host, uint16_t port, int *sock, const char *bind_address, bool blocking) +{ + struct addrinfo hints; + struct addrinfo *ainfo, *rp; + struct addrinfo *ainfo_bind, *rp_bind; + int s; + int rc; +#ifdef WIN32 + uint32_t val = 1; +#endif + + *sock = INVALID_SOCKET; + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = PF_UNSPEC; + hints.ai_flags = AI_ADDRCONFIG; + hints.ai_socktype = SOCK_STREAM; + + s = getaddrinfo(host, NULL, &hints, &ainfo); + if(s){ + errno = s; + return MOSQ_ERR_EAI; + } + + if(bind_address){ + s = getaddrinfo(bind_address, NULL, &hints, &ainfo_bind); + if(s){ + freeaddrinfo(ainfo); + errno = s; + return MOSQ_ERR_EAI; + } + } + + for(rp = ainfo; rp != NULL; rp = rp->ai_next){ + *sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if(*sock == INVALID_SOCKET) continue; + + if(rp->ai_family == PF_INET){ + ((struct sockaddr_in *)rp->ai_addr)->sin_port = htons(port); + }else if(rp->ai_family == PF_INET6){ + ((struct sockaddr_in6 *)rp->ai_addr)->sin6_port = htons(port); + }else{ + continue; + } + + if(bind_address){ + for(rp_bind = ainfo_bind; rp_bind != NULL; rp_bind = rp_bind->ai_next){ + if(bind(*sock, rp_bind->ai_addr, rp_bind->ai_addrlen) == 0){ + break; + } + } + if(!rp_bind){ + COMPAT_CLOSE(*sock); + continue; + } + } + + if(!blocking){ + /* Set non-blocking */ + if(_mosquitto_socket_nonblock(*sock)){ + COMPAT_CLOSE(*sock); + continue; + } + } + + rc = connect(*sock, rp->ai_addr, rp->ai_addrlen); +#ifdef WIN32 + errno = WSAGetLastError(); +#endif + if(rc == 0 || errno == EINPROGRESS || errno == COMPAT_EWOULDBLOCK){ + if(blocking){ + /* Set non-blocking */ + if(_mosquitto_socket_nonblock(*sock)){ + COMPAT_CLOSE(*sock); + continue; + } + } + break; + } + + COMPAT_CLOSE(*sock); + *sock = INVALID_SOCKET; + } + freeaddrinfo(ainfo); + if(bind_address){ + freeaddrinfo(ainfo_bind); + } + if(!rp){ + return MOSQ_ERR_ERRNO; + } + return MOSQ_ERR_SUCCESS; +} + +/* Create a socket and connect it to 'ip' on port 'port'. + * Returns -1 on failure (ip is NULL, socket creation/connection error) + * Returns sock number on success. + */ +int _mosquitto_socket_connect(struct mosquitto *mosq, const char *host, uint16_t port, const char *bind_address, bool blocking) +{ + int sock = INVALID_SOCKET; + int rc; +#ifdef WITH_TLS + int ret; + BIO *bio; +#endif + + if(!mosq || !host || !port) return MOSQ_ERR_INVAL; + +#ifdef WITH_TLS + if(mosq->tls_cafile || mosq->tls_capath || mosq->tls_psk){ + blocking = true; + } +#endif + + rc = _mosquitto_try_connect(host, port, &sock, bind_address, blocking); + if(rc != MOSQ_ERR_SUCCESS) return rc; + +#ifdef WITH_TLS + if(mosq->tls_cafile || mosq->tls_capath || mosq->tls_psk){ +#if OPENSSL_VERSION_NUMBER >= 0x10001000L + if(!mosq->tls_version || !strcmp(mosq->tls_version, "tlsv1.2")){ + mosq->ssl_ctx = SSL_CTX_new(TLSv1_2_client_method()); + }else if(!strcmp(mosq->tls_version, "tlsv1.1")){ + mosq->ssl_ctx = SSL_CTX_new(TLSv1_1_client_method()); + }else if(!strcmp(mosq->tls_version, "tlsv1")){ + mosq->ssl_ctx = SSL_CTX_new(TLSv1_client_method()); + }else{ + _mosquitto_log_printf(mosq, MOSQ_LOG_ERR, "Error: Protocol %s not supported.", mosq->tls_version); + COMPAT_CLOSE(sock); + return MOSQ_ERR_INVAL; + } +#else + if(!mosq->tls_version || !strcmp(mosq->tls_version, "tlsv1")){ + mosq->ssl_ctx = SSL_CTX_new(TLSv1_client_method()); + }else{ + _mosquitto_log_printf(mosq, MOSQ_LOG_ERR, "Error: Protocol %s not supported.", mosq->tls_version); + COMPAT_CLOSE(sock); + return MOSQ_ERR_INVAL; + } +#endif + if(!mosq->ssl_ctx){ + _mosquitto_log_printf(mosq, MOSQ_LOG_ERR, "Error: Unable to create TLS context."); + COMPAT_CLOSE(sock); + return MOSQ_ERR_TLS; + } + +#if OPENSSL_VERSION_NUMBER >= 0x10000000 + /* Disable compression */ + SSL_CTX_set_options(mosq->ssl_ctx, SSL_OP_NO_COMPRESSION); +#endif +#ifdef SSL_MODE_RELEASE_BUFFERS + /* Use even less memory per SSL connection. */ + SSL_CTX_set_mode(mosq->ssl_ctx, SSL_MODE_RELEASE_BUFFERS); +#endif + + if(mosq->tls_ciphers){ + ret = SSL_CTX_set_cipher_list(mosq->ssl_ctx, mosq->tls_ciphers); + if(ret == 0){ + _mosquitto_log_printf(mosq, MOSQ_LOG_ERR, "Error: Unable to set TLS ciphers. Check cipher list \"%s\".", mosq->tls_ciphers); + COMPAT_CLOSE(sock); + return MOSQ_ERR_TLS; + } + } + if(mosq->tls_cafile || mosq->tls_capath){ + ret = SSL_CTX_load_verify_locations(mosq->ssl_ctx, mosq->tls_cafile, mosq->tls_capath); + if(ret == 0){ +#ifdef WITH_BROKER + if(mosq->tls_cafile && mosq->tls_capath){ + _mosquitto_log_printf(mosq, MOSQ_LOG_ERR, "Error: Unable to load CA certificates, check bridge_cafile \"%s\" and bridge_capath \"%s\".", mosq->tls_cafile, mosq->tls_capath); + }else if(mosq->tls_cafile){ + _mosquitto_log_printf(mosq, MOSQ_LOG_ERR, "Error: Unable to load CA certificates, check bridge_cafile \"%s\".", mosq->tls_cafile); + }else{ + _mosquitto_log_printf(mosq, MOSQ_LOG_ERR, "Error: Unable to load CA certificates, check bridge_capath \"%s\".", mosq->tls_capath); + } +#else + if(mosq->tls_cafile && mosq->tls_capath){ + _mosquitto_log_printf(mosq, MOSQ_LOG_ERR, "Error: Unable to load CA certificates, check cafile \"%s\" and capath \"%s\".", mosq->tls_cafile, mosq->tls_capath); + }else if(mosq->tls_cafile){ + _mosquitto_log_printf(mosq, MOSQ_LOG_ERR, "Error: Unable to load CA certificates, check cafile \"%s\".", mosq->tls_cafile); + }else{ + _mosquitto_log_printf(mosq, MOSQ_LOG_ERR, "Error: Unable to load CA certificates, check capath \"%s\".", mosq->tls_capath); + } +#endif + COMPAT_CLOSE(sock); + return MOSQ_ERR_TLS; + } + if(mosq->tls_cert_reqs == 0){ + SSL_CTX_set_verify(mosq->ssl_ctx, SSL_VERIFY_NONE, NULL); + }else{ + SSL_CTX_set_verify(mosq->ssl_ctx, SSL_VERIFY_PEER, _mosquitto_server_certificate_verify); + } + + if(mosq->tls_pw_callback){ + SSL_CTX_set_default_passwd_cb(mosq->ssl_ctx, mosq->tls_pw_callback); + SSL_CTX_set_default_passwd_cb_userdata(mosq->ssl_ctx, mosq); + } + + if(mosq->tls_certfile){ + ret = SSL_CTX_use_certificate_chain_file(mosq->ssl_ctx, mosq->tls_certfile); + if(ret != 1){ +#ifdef WITH_BROKER + _mosquitto_log_printf(mosq, MOSQ_LOG_ERR, "Error: Unable to load client certificate, check bridge_certfile \"%s\".", mosq->tls_certfile); +#else + _mosquitto_log_printf(mosq, MOSQ_LOG_ERR, "Error: Unable to load client certificate \"%s\".", mosq->tls_certfile); +#endif + COMPAT_CLOSE(sock); + return MOSQ_ERR_TLS; + } + } + if(mosq->tls_keyfile){ + ret = SSL_CTX_use_PrivateKey_file(mosq->ssl_ctx, mosq->tls_keyfile, SSL_FILETYPE_PEM); + if(ret != 1){ +#ifdef WITH_BROKER + _mosquitto_log_printf(mosq, MOSQ_LOG_ERR, "Error: Unable to load client key file, check bridge_keyfile \"%s\".", mosq->tls_keyfile); +#else + _mosquitto_log_printf(mosq, MOSQ_LOG_ERR, "Error: Unable to load client key file \"%s\".", mosq->tls_keyfile); +#endif + COMPAT_CLOSE(sock); + return MOSQ_ERR_TLS; + } + ret = SSL_CTX_check_private_key(mosq->ssl_ctx); + if(ret != 1){ + _mosquitto_log_printf(mosq, MOSQ_LOG_ERR, "Error: Client certificate/key are inconsistent."); + COMPAT_CLOSE(sock); + return MOSQ_ERR_TLS; + } + } +#ifdef REAL_WITH_TLS_PSK + }else if(mosq->tls_psk){ + SSL_CTX_set_psk_client_callback(mosq->ssl_ctx, psk_client_callback); +#endif + } + + mosq->ssl = SSL_new(mosq->ssl_ctx); + if(!mosq->ssl){ + COMPAT_CLOSE(sock); + return MOSQ_ERR_TLS; + } + SSL_set_ex_data(mosq->ssl, tls_ex_index_mosq, mosq); + bio = BIO_new_socket(sock, BIO_NOCLOSE); + if(!bio){ + COMPAT_CLOSE(sock); + return MOSQ_ERR_TLS; + } + SSL_set_bio(mosq->ssl, bio, bio); + + ret = SSL_connect(mosq->ssl); + if(ret != 1){ + ret = SSL_get_error(mosq->ssl, ret); + if(ret == SSL_ERROR_WANT_READ){ + /* We always try to read anyway */ + }else if(ret == SSL_ERROR_WANT_WRITE){ + mosq->want_write = true; + }else{ + COMPAT_CLOSE(sock); + return MOSQ_ERR_TLS; + } + } + } +#endif + + mosq->sock = sock; + + return MOSQ_ERR_SUCCESS; +} + +int _mosquitto_read_byte(struct _mosquitto_packet *packet, uint8_t *byte) +{ + assert(packet); + if(packet->pos+1 > packet->remaining_length) return MOSQ_ERR_PROTOCOL; + + *byte = packet->payload[packet->pos]; + packet->pos++; + + return MOSQ_ERR_SUCCESS; +} + +void _mosquitto_write_byte(struct _mosquitto_packet *packet, uint8_t byte) +{ + assert(packet); + assert(packet->pos+1 <= packet->packet_length); + + packet->payload[packet->pos] = byte; + packet->pos++; +} + +int _mosquitto_read_bytes(struct _mosquitto_packet *packet, void *bytes, uint32_t count) +{ + assert(packet); + if(packet->pos+count > packet->remaining_length) return MOSQ_ERR_PROTOCOL; + + memcpy(bytes, &(packet->payload[packet->pos]), count); + packet->pos += count; + + return MOSQ_ERR_SUCCESS; +} + +void _mosquitto_write_bytes(struct _mosquitto_packet *packet, const void *bytes, uint32_t count) +{ + assert(packet); + assert(packet->pos+count <= packet->packet_length); + + memcpy(&(packet->payload[packet->pos]), bytes, count); + packet->pos += count; +} + +int _mosquitto_read_string(struct _mosquitto_packet *packet, char **str) +{ + uint16_t len; + int rc; + + assert(packet); + rc = _mosquitto_read_uint16(packet, &len); + if(rc) return rc; + + if(packet->pos+len > packet->remaining_length) return MOSQ_ERR_PROTOCOL; + + *str = _mosquitto_calloc(len+1, sizeof(char)); + if(*str){ + memcpy(*str, &(packet->payload[packet->pos]), len); + packet->pos += len; + }else{ + return MOSQ_ERR_NOMEM; + } + + return MOSQ_ERR_SUCCESS; +} + +void _mosquitto_write_string(struct _mosquitto_packet *packet, const char *str, uint16_t length) +{ + assert(packet); + _mosquitto_write_uint16(packet, length); + _mosquitto_write_bytes(packet, str, length); +} + +int _mosquitto_read_uint16(struct _mosquitto_packet *packet, uint16_t *word) +{ + uint8_t msb, lsb; + + assert(packet); + if(packet->pos+2 > packet->remaining_length) return MOSQ_ERR_PROTOCOL; + + msb = packet->payload[packet->pos]; + packet->pos++; + lsb = packet->payload[packet->pos]; + packet->pos++; + + *word = (msb<<8) + lsb; + + return MOSQ_ERR_SUCCESS; +} + +void _mosquitto_write_uint16(struct _mosquitto_packet *packet, uint16_t word) +{ + _mosquitto_write_byte(packet, MOSQ_MSB(word)); + _mosquitto_write_byte(packet, MOSQ_LSB(word)); +} + +ssize_t _mosquitto_net_read(struct mosquitto *mosq, void *buf, size_t count) +{ +#ifdef WITH_TLS + int ret; + int err; + char ebuf[256]; + unsigned long e; +#endif + assert(mosq); + errno = 0; +#ifdef WITH_TLS + if(mosq->ssl){ + ret = SSL_read(mosq->ssl, buf, count); + if(ret <= 0){ + err = SSL_get_error(mosq->ssl, ret); + if(err == SSL_ERROR_WANT_READ){ + ret = -1; + errno = EAGAIN; + }else if(err == SSL_ERROR_WANT_WRITE){ + ret = -1; + mosq->want_write = true; + errno = EAGAIN; + }else{ + e = ERR_get_error(); + while(e){ + _mosquitto_log_printf(mosq, MOSQ_LOG_ERR, "OpenSSL Error: %s", ERR_error_string(e, ebuf)); + e = ERR_get_error(); + } + errno = EPROTO; + } + } + return (ssize_t )ret; + }else{ + /* Call normal read/recv */ + +#endif + +#ifndef WIN32 + return read(mosq->sock, buf, count); +#else + return recv(mosq->sock, buf, count, 0); +#endif + +#ifdef WITH_TLS + } +#endif +} + +ssize_t _mosquitto_net_write(struct mosquitto *mosq, void *buf, size_t count) +{ +#ifdef WITH_TLS + int ret; + int err; + char ebuf[256]; + unsigned long e; +#endif + assert(mosq); + + errno = 0; +#ifdef WITH_TLS + if(mosq->ssl){ + ret = SSL_write(mosq->ssl, buf, count); + if(ret < 0){ + err = SSL_get_error(mosq->ssl, ret); + if(err == SSL_ERROR_WANT_READ){ + ret = -1; + errno = EAGAIN; + }else if(err == SSL_ERROR_WANT_WRITE){ + ret = -1; + mosq->want_write = true; + errno = EAGAIN; + }else{ + e = ERR_get_error(); + while(e){ + _mosquitto_log_printf(mosq, MOSQ_LOG_ERR, "OpenSSL Error: %s", ERR_error_string(e, ebuf)); + e = ERR_get_error(); + } + errno = EPROTO; + } + } + return (ssize_t )ret; + }else{ + /* Call normal write/send */ +#endif + +#ifndef WIN32 + return write(mosq->sock, buf, count); +#else + return send(mosq->sock, buf, count, 0); +#endif + +#ifdef WITH_TLS + } +#endif +} + +int _mosquitto_packet_write(struct mosquitto *mosq) +{ + ssize_t write_length; + struct _mosquitto_packet *packet; + + if(!mosq) return MOSQ_ERR_INVAL; + if(mosq->sock == INVALID_SOCKET) return MOSQ_ERR_NO_CONN; + + pthread_mutex_lock(&mosq->current_out_packet_mutex); + pthread_mutex_lock(&mosq->out_packet_mutex); + if(mosq->out_packet && !mosq->current_out_packet){ + mosq->current_out_packet = mosq->out_packet; + mosq->out_packet = mosq->out_packet->next; + if(!mosq->out_packet){ + mosq->out_packet_last = NULL; + } + } + pthread_mutex_unlock(&mosq->out_packet_mutex); + + while(mosq->current_out_packet){ + packet = mosq->current_out_packet; + + while(packet->to_process > 0){ + write_length = _mosquitto_net_write(mosq, &(packet->payload[packet->pos]), packet->to_process); + if(write_length > 0){ +#if defined(WITH_BROKER) && defined(WITH_SYS_TREE) + g_bytes_sent += write_length; +#endif + packet->to_process -= write_length; + packet->pos += write_length; + }else{ +#ifdef WIN32 + errno = WSAGetLastError(); +#endif + if(errno == EAGAIN || errno == COMPAT_EWOULDBLOCK){ + pthread_mutex_unlock(&mosq->current_out_packet_mutex); + return MOSQ_ERR_SUCCESS; + }else{ + pthread_mutex_unlock(&mosq->current_out_packet_mutex); + switch(errno){ + case COMPAT_ECONNRESET: + return MOSQ_ERR_CONN_LOST; + default: + return MOSQ_ERR_ERRNO; + } + } + } + } + +#ifdef WITH_BROKER +# ifdef WITH_SYS_TREE + g_msgs_sent++; + if(((packet->command)&0xF6) == PUBLISH){ + g_pub_msgs_sent++; + } +# endif +#else + if(((packet->command)&0xF6) == PUBLISH){ + pthread_mutex_lock(&mosq->callback_mutex); + if(mosq->on_publish){ + /* This is a QoS=0 message */ + mosq->in_callback = true; + mosq->on_publish(mosq, mosq->userdata, packet->mid); + mosq->in_callback = false; + } + pthread_mutex_unlock(&mosq->callback_mutex); + }else if(((packet->command)&0xF0) == DISCONNECT){ + /* FIXME what cleanup needs doing here? + * incoming/outgoing messages? */ + _mosquitto_socket_close(mosq); + + /* Start of duplicate, possibly unnecessary code. + * This does leave things in a consistent state at least. */ + /* Free data and reset values */ + pthread_mutex_lock(&mosq->out_packet_mutex); + mosq->current_out_packet = mosq->out_packet; + if(mosq->out_packet){ + mosq->out_packet = mosq->out_packet->next; + if(!mosq->out_packet){ + mosq->out_packet_last = NULL; + } + } + pthread_mutex_unlock(&mosq->out_packet_mutex); + + _mosquitto_packet_cleanup(packet); + _mosquitto_free(packet); + + pthread_mutex_lock(&mosq->msgtime_mutex); + mosq->last_msg_out = mosquitto_time(); + pthread_mutex_unlock(&mosq->msgtime_mutex); + /* End of duplicate, possibly unnecessary code */ + + pthread_mutex_lock(&mosq->callback_mutex); + if(mosq->on_disconnect){ + mosq->in_callback = true; + mosq->on_disconnect(mosq, mosq->userdata, 0); + mosq->in_callback = false; + } + pthread_mutex_unlock(&mosq->current_out_packet_mutex); + return MOSQ_ERR_SUCCESS; + } +#endif + + /* Free data and reset values */ + pthread_mutex_lock(&mosq->out_packet_mutex); + mosq->current_out_packet = mosq->out_packet; + if(mosq->out_packet){ + mosq->out_packet = mosq->out_packet->next; + if(!mosq->out_packet){ + mosq->out_packet_last = NULL; + } + } + pthread_mutex_unlock(&mosq->out_packet_mutex); + + _mosquitto_packet_cleanup(packet); + _mosquitto_free(packet); + + pthread_mutex_lock(&mosq->msgtime_mutex); + mosq->last_msg_out = mosquitto_time(); + pthread_mutex_unlock(&mosq->msgtime_mutex); + } + pthread_mutex_unlock(&mosq->current_out_packet_mutex); + return MOSQ_ERR_SUCCESS; +} + +#ifdef WITH_BROKER +int _mosquitto_packet_read(struct mosquitto_db *db, struct mosquitto *mosq) +#else +int _mosquitto_packet_read(struct mosquitto *mosq) +#endif +{ + uint8_t byte; + ssize_t read_length; + int rc = 0; + + if(!mosq) return MOSQ_ERR_INVAL; + if(mosq->sock == INVALID_SOCKET) return MOSQ_ERR_NO_CONN; + /* This gets called if pselect() indicates that there is network data + * available - ie. at least one byte. What we do depends on what data we + * already have. + * If we've not got a command, attempt to read one and save it. This should + * always work because it's only a single byte. + * Then try to read the remaining length. This may fail because it is may + * be more than one byte - will need to save data pending next read if it + * does fail. + * Then try to read the remaining payload, where 'payload' here means the + * combined variable header and actual payload. This is the most likely to + * fail due to longer length, so save current data and current position. + * After all data is read, send to _mosquitto_handle_packet() to deal with. + * Finally, free the memory and reset everything to starting conditions. + */ + if(!mosq->in_packet.command){ + read_length = _mosquitto_net_read(mosq, &byte, 1); + if(read_length == 1){ + mosq->in_packet.command = byte; +#ifdef WITH_BROKER +# ifdef WITH_SYS_TREE + g_bytes_received++; +# endif + /* Clients must send CONNECT as their first command. */ + if(!(mosq->bridge) && mosq->state == mosq_cs_new && (byte&0xF0) != CONNECT) return MOSQ_ERR_PROTOCOL; +#endif + }else{ + if(read_length == 0) return MOSQ_ERR_CONN_LOST; /* EOF */ +#ifdef WIN32 + errno = WSAGetLastError(); +#endif + if(errno == EAGAIN || errno == COMPAT_EWOULDBLOCK){ + return MOSQ_ERR_SUCCESS; + }else{ + switch(errno){ + case COMPAT_ECONNRESET: + return MOSQ_ERR_CONN_LOST; + default: + return MOSQ_ERR_ERRNO; + } + } + } + } + if(!mosq->in_packet.have_remaining){ + do{ + read_length = _mosquitto_net_read(mosq, &byte, 1); + if(read_length == 1){ + mosq->in_packet.remaining_count++; + /* Max 4 bytes length for remaining length as defined by protocol. + * Anything more likely means a broken/malicious client. + */ + if(mosq->in_packet.remaining_count > 4) return MOSQ_ERR_PROTOCOL; + +#if defined(WITH_BROKER) && defined(WITH_SYS_TREE) + g_bytes_received++; +#endif + mosq->in_packet.remaining_length += (byte & 127) * mosq->in_packet.remaining_mult; + mosq->in_packet.remaining_mult *= 128; + }else{ + if(read_length == 0) return MOSQ_ERR_CONN_LOST; /* EOF */ +#ifdef WIN32 + errno = WSAGetLastError(); +#endif + if(errno == EAGAIN || errno == COMPAT_EWOULDBLOCK){ + return MOSQ_ERR_SUCCESS; + }else{ + switch(errno){ + case COMPAT_ECONNRESET: + return MOSQ_ERR_CONN_LOST; + default: + return MOSQ_ERR_ERRNO; + } + } + } + }while((byte & 128) != 0); + + if(mosq->in_packet.remaining_length > 0){ + mosq->in_packet.payload = _mosquitto_malloc(mosq->in_packet.remaining_length*sizeof(uint8_t)); + if(!mosq->in_packet.payload) return MOSQ_ERR_NOMEM; + mosq->in_packet.to_process = mosq->in_packet.remaining_length; + } + mosq->in_packet.have_remaining = 1; + } + while(mosq->in_packet.to_process>0){ + read_length = _mosquitto_net_read(mosq, &(mosq->in_packet.payload[mosq->in_packet.pos]), mosq->in_packet.to_process); + if(read_length > 0){ +#if defined(WITH_BROKER) && defined(WITH_SYS_TREE) + g_bytes_received += read_length; +#endif + mosq->in_packet.to_process -= read_length; + mosq->in_packet.pos += read_length; + }else{ +#ifdef WIN32 + errno = WSAGetLastError(); +#endif + if(errno == EAGAIN || errno == COMPAT_EWOULDBLOCK){ + if(mosq->in_packet.to_process > 1000){ + /* Update last_msg_in time if more than 1000 bytes left to + * receive. Helps when receiving large messages. + * This is an arbitrary limit, but with some consideration. + * If a client can't send 1000 bytes in a second it + * probably shouldn't be using a 1 second keep alive. */ + pthread_mutex_lock(&mosq->msgtime_mutex); + mosq->last_msg_in = mosquitto_time(); + pthread_mutex_unlock(&mosq->msgtime_mutex); + } + return MOSQ_ERR_SUCCESS; + }else{ + switch(errno){ + case COMPAT_ECONNRESET: + return MOSQ_ERR_CONN_LOST; + default: + return MOSQ_ERR_ERRNO; + } + } + } + } + + /* All data for this packet is read. */ + mosq->in_packet.pos = 0; +#ifdef WITH_BROKER +# ifdef WITH_SYS_TREE + g_msgs_received++; + if(((mosq->in_packet.command)&0xF5) == PUBLISH){ + g_pub_msgs_received++; + } +# endif + rc = mqtt3_packet_handle(db, mosq); +#else + rc = _mosquitto_packet_handle(mosq); +#endif + + /* Free data and reset values */ + _mosquitto_packet_cleanup(&mosq->in_packet); + + pthread_mutex_lock(&mosq->msgtime_mutex); + mosq->last_msg_in = mosquitto_time(); + pthread_mutex_unlock(&mosq->msgtime_mutex); + return rc; +} + +int _mosquitto_socket_nonblock(int sock) +{ +#ifndef WIN32 + int opt; + /* Set non-blocking */ + opt = fcntl(sock, F_GETFL, 0); + if(opt == -1){ + COMPAT_CLOSE(sock); + return 1; + } + if(fcntl(sock, F_SETFL, opt | O_NONBLOCK) == -1){ + /* If either fcntl fails, don't want to allow this client to connect. */ + COMPAT_CLOSE(sock); + return 1; + } +#else + opt = 1; + if(ioctlsocket(sock, FIONBIO, &opt)){ + COMPAT_CLOSE(sock); + return 1; + } +#endif + return 0; +} + + +#ifndef WITH_BROKER +int _mosquitto_socketpair(int *pairR, int *pairW) +{ +#ifdef WIN32 + int family[2] = {AF_INET, AF_INET6}; + int i; + struct sockaddr_storage ss; + struct sockaddr_in *sa = (struct sockaddr_in *)&ss; + struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&ss; + socklen_t ss_len; + int spR, spW; + + int listensock; + + *pairR = -1; + *pairW = -1; + + for(i=0; i<2; i++){ + memset(&ss, 0, sizeof(ss)); + if(family[i] == AF_INET){ + sa->sin_family = family[i]; + sa->sin_addr.s_addr = htonl(INADDR_LOOPBACK); + sa->sin_port = 0; + ss_len = sizeof(struct sockaddr_in); + }else if(family[i] == AF_INET6){ + sa6->sin6_family = family[i]; + sa6->sin6_addr = in6addr_loopback; + sa6->sin6_port = 0; + ss_len = sizeof(struct sockaddr_in6); + }else{ + return MOSQ_ERR_INVAL; + } + + listensock = socket(family[i], SOCK_STREAM, IPPROTO_TCP); + if(listensock == -1){ + continue; + } + + if(bind(listensock, (struct sockaddr *)&ss, ss_len) == -1){ + COMPAT_CLOSE(listensock); + continue; + } + + if(listen(listensock, 1) == -1){ + COMPAT_CLOSE(listensock); + continue; + } + memset(&ss, 0, sizeof(ss)); + ss_len = sizeof(ss); + if(getsockname(listensock, (struct sockaddr *)&ss, &ss_len) < 0){ + COMPAT_CLOSE(listensock); + continue; + } + + if(_mosquitto_socket_nonblock(listensock)){ + continue; + } + + if(family[i] == AF_INET){ + sa->sin_family = family[i]; + sa->sin_addr.s_addr = htonl(INADDR_LOOPBACK); + ss_len = sizeof(struct sockaddr_in); + }else if(family[i] == AF_INET6){ + sa6->sin6_family = family[i]; + sa6->sin6_addr = in6addr_loopback; + ss_len = sizeof(struct sockaddr_in6); + } + + spR = socket(family[i], SOCK_STREAM, IPPROTO_TCP); + if(spR == -1){ + COMPAT_CLOSE(listensock); + continue; + } + if(_mosquitto_socket_nonblock(spR)){ + COMPAT_CLOSE(listensock); + continue; + } + if(connect(spR, (struct sockaddr *)&ss, ss_len) < 0){ +#ifdef WIN32 + errno = WSAGetLastError(); +#endif + if(errno != EINPROGRESS && errno != COMPAT_EWOULDBLOCK){ + COMPAT_CLOSE(spR); + COMPAT_CLOSE(listensock); + continue; + } + } + spW = accept(listensock, NULL, 0); + if(spW == -1){ +#ifdef WIN32 + errno = WSAGetLastError(); +#endif + if(errno != EINPROGRESS && errno != COMPAT_EWOULDBLOCK){ + COMPAT_CLOSE(spR); + COMPAT_CLOSE(listensock); + continue; + } + } + + if(_mosquitto_socket_nonblock(spW)){ + COMPAT_CLOSE(spR); + COMPAT_CLOSE(listensock); + continue; + } + COMPAT_CLOSE(listensock); + + *pairR = spR; + *pairW = spW; + return MOSQ_ERR_SUCCESS; + } + return MOSQ_ERR_UNKNOWN; +#else + int sv[2]; + + if(socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1){ + return MOSQ_ERR_ERRNO; + } + if(_mosquitto_socket_nonblock(sv[0])){ + COMPAT_CLOSE(sv[0]); + COMPAT_CLOSE(sv[1]); + return MOSQ_ERR_ERRNO; + } + if(_mosquitto_socket_nonblock(sv[1])){ + COMPAT_CLOSE(sv[0]); + COMPAT_CLOSE(sv[1]); + return MOSQ_ERR_ERRNO; + } + *pairR = sv[0]; + *pairW = sv[1]; + return MOSQ_ERR_SUCCESS; +#endif +} +#endif diff --git a/lib/net_mosq.h b/lib/net_mosq.h new file mode 100644 index 0000000000..77e143675d --- /dev/null +++ b/lib/net_mosq.h @@ -0,0 +1,91 @@ +/* +Copyright (c) 2010-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ +#ifndef _NET_MOSQ_H_ +#define _NET_MOSQ_H_ + +#ifndef WIN32 +#include +#else +#include +typedef int ssize_t; +#endif + +#include +#include + +#ifdef WITH_BROKER +struct mosquitto_db; +#endif + +#ifdef WIN32 +# define COMPAT_CLOSE(a) closesocket(a) +# define COMPAT_ECONNRESET WSAECONNRESET +# define COMPAT_EWOULDBLOCK WSAEWOULDBLOCK +#else +# define COMPAT_CLOSE(a) close(a) +# define COMPAT_ECONNRESET ECONNRESET +# define COMPAT_EWOULDBLOCK EWOULDBLOCK +#endif + +#ifndef WIN32 +#else +#endif + +/* For when not using winsock libraries. */ +#ifndef INVALID_SOCKET +#define INVALID_SOCKET -1 +#endif + +/* Macros for accessing the MSB and LSB of a uint16_t */ +#define MOSQ_MSB(A) (uint8_t)((A & 0xFF00) >> 8) +#define MOSQ_LSB(A) (uint8_t)(A & 0x00FF) + +void _mosquitto_net_init(void); +void _mosquitto_net_cleanup(void); + +void _mosquitto_packet_cleanup(struct _mosquitto_packet *packet); +int _mosquitto_packet_queue(struct mosquitto *mosq, struct _mosquitto_packet *packet); +int _mosquitto_socket_connect(struct mosquitto *mosq, const char *host, uint16_t port, const char *bind_address, bool blocking); +int _mosquitto_socket_close(struct mosquitto *mosq); +int _mosquitto_try_connect(const char *host, uint16_t port, int *sock, const char *bind_address, bool blocking); +int _mosquitto_socket_nonblock(int sock); +int _mosquitto_socketpair(int *sp1, int *sp2); + +int _mosquitto_read_byte(struct _mosquitto_packet *packet, uint8_t *byte); +int _mosquitto_read_bytes(struct _mosquitto_packet *packet, void *bytes, uint32_t count); +int _mosquitto_read_string(struct _mosquitto_packet *packet, char **str); +int _mosquitto_read_uint16(struct _mosquitto_packet *packet, uint16_t *word); + +void _mosquitto_write_byte(struct _mosquitto_packet *packet, uint8_t byte); +void _mosquitto_write_bytes(struct _mosquitto_packet *packet, const void *bytes, uint32_t count); +void _mosquitto_write_string(struct _mosquitto_packet *packet, const char *str, uint16_t length); +void _mosquitto_write_uint16(struct _mosquitto_packet *packet, uint16_t word); + +ssize_t _mosquitto_net_read(struct mosquitto *mosq, void *buf, size_t count); +ssize_t _mosquitto_net_write(struct mosquitto *mosq, void *buf, size_t count); + +int _mosquitto_packet_write(struct mosquitto *mosq); +#ifdef WITH_BROKER +int _mosquitto_packet_read(struct mosquitto_db *db, struct mosquitto *mosq); +#else +int _mosquitto_packet_read(struct mosquitto *mosq); +#endif + +#ifdef WITH_TLS +int _mosquitto_socket_apply_tls(struct mosquitto *mosq); +#endif + +#endif diff --git a/lib/read_handle.c b/lib/read_handle.c new file mode 100644 index 0000000000..d83294a704 --- /dev/null +++ b/lib/read_handle.c @@ -0,0 +1,155 @@ +/* +Copyright (c) 2009-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int _mosquitto_packet_handle(struct mosquitto *mosq) +{ + assert(mosq); + + switch((mosq->in_packet.command)&0xF0){ + case PINGREQ: + return _mosquitto_handle_pingreq(mosq); + case PINGRESP: + return _mosquitto_handle_pingresp(mosq); + case PUBACK: + return _mosquitto_handle_pubackcomp(mosq, "PUBACK"); + case PUBCOMP: + return _mosquitto_handle_pubackcomp(mosq, "PUBCOMP"); + case PUBLISH: + return _mosquitto_handle_publish(mosq); + case PUBREC: + return _mosquitto_handle_pubrec(mosq); + case PUBREL: + return _mosquitto_handle_pubrel(NULL, mosq); + case CONNACK: + return _mosquitto_handle_connack(mosq); + case SUBACK: + return _mosquitto_handle_suback(mosq); + case UNSUBACK: + return _mosquitto_handle_unsuback(mosq); + default: + /* If we don't recognise the command, return an error straight away. */ + _mosquitto_log_printf(mosq, MOSQ_LOG_ERR, "Error: Unrecognised command %d\n", (mosq->in_packet.command)&0xF0); + return MOSQ_ERR_PROTOCOL; + } +} + +int _mosquitto_handle_publish(struct mosquitto *mosq) +{ + uint8_t header; + struct mosquitto_message_all *message; + int rc = 0; + uint16_t mid; + + assert(mosq); + + message = _mosquitto_calloc(1, sizeof(struct mosquitto_message_all)); + if(!message) return MOSQ_ERR_NOMEM; + + header = mosq->in_packet.command; + + message->dup = (header & 0x08)>>3; + message->msg.qos = (header & 0x06)>>1; + message->msg.retain = (header & 0x01); + + rc = _mosquitto_read_string(&mosq->in_packet, &message->msg.topic); + if(rc){ + _mosquitto_message_cleanup(&message); + return rc; + } + if(!strlen(message->msg.topic)){ + _mosquitto_message_cleanup(&message); + return MOSQ_ERR_PROTOCOL; + } + + if(message->msg.qos > 0){ + rc = _mosquitto_read_uint16(&mosq->in_packet, &mid); + if(rc){ + _mosquitto_message_cleanup(&message); + return rc; + } + message->msg.mid = (int)mid; + } + + message->msg.payloadlen = mosq->in_packet.remaining_length - mosq->in_packet.pos; + if(message->msg.payloadlen){ + message->msg.payload = _mosquitto_calloc(message->msg.payloadlen+1, sizeof(uint8_t)); + if(!message->msg.payload){ + _mosquitto_message_cleanup(&message); + return MOSQ_ERR_NOMEM; + } + rc = _mosquitto_read_bytes(&mosq->in_packet, message->msg.payload, message->msg.payloadlen); + if(rc){ + _mosquitto_message_cleanup(&message); + return rc; + } + } + _mosquitto_log_printf(mosq, MOSQ_LOG_DEBUG, + "Client %s received PUBLISH (d%d, q%d, r%d, m%d, '%s', ... (%ld bytes))", + mosq->id, message->dup, message->msg.qos, message->msg.retain, + message->msg.mid, message->msg.topic, + (long)message->msg.payloadlen); + + message->timestamp = mosquitto_time(); + switch(message->msg.qos){ + case 0: + pthread_mutex_lock(&mosq->callback_mutex); + if(mosq->on_message){ + mosq->in_callback = true; + mosq->on_message(mosq, mosq->userdata, &message->msg); + mosq->in_callback = false; + } + pthread_mutex_unlock(&mosq->callback_mutex); + _mosquitto_message_cleanup(&message); + return MOSQ_ERR_SUCCESS; + case 1: + rc = _mosquitto_send_puback(mosq, message->msg.mid); + pthread_mutex_lock(&mosq->callback_mutex); + if(mosq->on_message){ + mosq->in_callback = true; + mosq->on_message(mosq, mosq->userdata, &message->msg); + mosq->in_callback = false; + } + pthread_mutex_unlock(&mosq->callback_mutex); + _mosquitto_message_cleanup(&message); + return rc; + case 2: + rc = _mosquitto_send_pubrec(mosq, message->msg.mid); + pthread_mutex_lock(&mosq->in_message_mutex); + message->state = mosq_ms_wait_for_pubrel; + _mosquitto_message_queue(mosq, message, mosq_md_in); + pthread_mutex_unlock(&mosq->in_message_mutex); + return rc; + default: + _mosquitto_message_cleanup(&message); + return MOSQ_ERR_PROTOCOL; + } +} + diff --git a/lib/read_handle.h b/lib/read_handle.h new file mode 100644 index 0000000000..018353f819 --- /dev/null +++ b/lib/read_handle.h @@ -0,0 +1,34 @@ +/* +Copyright (c) 2010-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ +#ifndef _READ_HANDLE_H_ +#define _READ_HANDLE_H_ + +#include +struct mosquitto_db; + +int _mosquitto_packet_handle(struct mosquitto *mosq); +int _mosquitto_handle_connack(struct mosquitto *mosq); +int _mosquitto_handle_pingreq(struct mosquitto *mosq); +int _mosquitto_handle_pingresp(struct mosquitto *mosq); +int _mosquitto_handle_pubackcomp(struct mosquitto *mosq, const char *type); +int _mosquitto_handle_publish(struct mosquitto *mosq); +int _mosquitto_handle_pubrec(struct mosquitto *mosq); +int _mosquitto_handle_pubrel(struct mosquitto_db *db, struct mosquitto *mosq); +int _mosquitto_handle_suback(struct mosquitto *mosq); +int _mosquitto_handle_unsuback(struct mosquitto *mosq); + + +#endif diff --git a/lib/read_handle_client.c b/lib/read_handle_client.c new file mode 100644 index 0000000000..22f5898d01 --- /dev/null +++ b/lib/read_handle_client.c @@ -0,0 +1,63 @@ +/* +Copyright (c) 2009-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#include + +#include +#include +#include +#include +#include + +int _mosquitto_handle_connack(struct mosquitto *mosq) +{ + uint8_t byte; + uint8_t result; + int rc; + + assert(mosq); +#ifdef WITH_STRICT_PROTOCOL + if(mosq->in_packet.remaining_length != 2){ + return MOSQ_ERR_PROTOCOL; + } +#endif + _mosquitto_log_printf(mosq, MOSQ_LOG_DEBUG, "Client %s received CONNACK", mosq->id); + rc = _mosquitto_read_byte(&mosq->in_packet, &byte); // Reserved byte, not used + if(rc) return rc; + rc = _mosquitto_read_byte(&mosq->in_packet, &result); + if(rc) return rc; + pthread_mutex_lock(&mosq->callback_mutex); + if(mosq->on_connect){ + mosq->in_callback = true; + mosq->on_connect(mosq, mosq->userdata, result); + mosq->in_callback = false; + } + pthread_mutex_unlock(&mosq->callback_mutex); + switch(result){ + case 0: + mosq->state = mosq_cs_connected; + return MOSQ_ERR_SUCCESS; + case 1: + case 2: + case 3: + case 4: + case 5: + return MOSQ_ERR_CONN_REFUSED; + default: + return MOSQ_ERR_PROTOCOL; + } +} + diff --git a/lib/read_handle_shared.c b/lib/read_handle_shared.c new file mode 100644 index 0000000000..b368a029c3 --- /dev/null +++ b/lib/read_handle_shared.c @@ -0,0 +1,258 @@ +/* +Copyright (c) 2009-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef WITH_BROKER +#include +#endif + +int _mosquitto_handle_pingreq(struct mosquitto *mosq) +{ + assert(mosq); +#ifdef WITH_STRICT_PROTOCOL + if(mosq->in_packet.remaining_length != 0){ + return MOSQ_ERR_PROTOCOL; + } +#endif +#ifdef WITH_BROKER + _mosquitto_log_printf(NULL, MOSQ_LOG_DEBUG, "Received PINGREQ from %s", mosq->id); +#else + _mosquitto_log_printf(mosq, MOSQ_LOG_DEBUG, "Client %s received PINGREQ", mosq->id); +#endif + return _mosquitto_send_pingresp(mosq); +} + +int _mosquitto_handle_pingresp(struct mosquitto *mosq) +{ + assert(mosq); +#ifdef WITH_STRICT_PROTOCOL + if(mosq->in_packet.remaining_length != 0){ + return MOSQ_ERR_PROTOCOL; + } +#endif + mosq->ping_t = 0; /* No longer waiting for a PINGRESP. */ +#ifdef WITH_BROKER + _mosquitto_log_printf(NULL, MOSQ_LOG_DEBUG, "Received PINGRESP from %s", mosq->id); +#else + _mosquitto_log_printf(mosq, MOSQ_LOG_DEBUG, "Client %s received PINGRESP", mosq->id); +#endif + return MOSQ_ERR_SUCCESS; +} + +int _mosquitto_handle_pubackcomp(struct mosquitto *mosq, const char *type) +{ + uint16_t mid; + int rc; + + assert(mosq); +#ifdef WITH_STRICT_PROTOCOL + if(mosq->in_packet.remaining_length != 2){ + return MOSQ_ERR_PROTOCOL; + } +#endif + rc = _mosquitto_read_uint16(&mosq->in_packet, &mid); + if(rc) return rc; +#ifdef WITH_BROKER + _mosquitto_log_printf(NULL, MOSQ_LOG_DEBUG, "Received %s from %s (Mid: %d)", type, mosq->id, mid); + + if(mid){ + rc = mqtt3_db_message_delete(mosq, mid, mosq_md_out); + if(rc) return rc; + } +#else + _mosquitto_log_printf(mosq, MOSQ_LOG_DEBUG, "Client %s received %s (Mid: %d)", mosq->id, type, mid); + + if(!_mosquitto_message_delete(mosq, mid, mosq_md_out)){ + /* Only inform the client the message has been sent once. */ + pthread_mutex_lock(&mosq->callback_mutex); + if(mosq->on_publish){ + mosq->in_callback = true; + mosq->on_publish(mosq, mosq->userdata, mid); + mosq->in_callback = false; + } + pthread_mutex_unlock(&mosq->callback_mutex); + } +#endif + + return MOSQ_ERR_SUCCESS; +} + +int _mosquitto_handle_pubrec(struct mosquitto *mosq) +{ + uint16_t mid; + int rc; + + assert(mosq); +#ifdef WITH_STRICT_PROTOCOL + if(mosq->in_packet.remaining_length != 2){ + return MOSQ_ERR_PROTOCOL; + } +#endif + rc = _mosquitto_read_uint16(&mosq->in_packet, &mid); + if(rc) return rc; +#ifdef WITH_BROKER + _mosquitto_log_printf(NULL, MOSQ_LOG_DEBUG, "Received PUBREC from %s (Mid: %d)", mosq->id, mid); + + rc = mqtt3_db_message_update(mosq, mid, mosq_md_out, mosq_ms_wait_for_pubcomp); +#else + _mosquitto_log_printf(mosq, MOSQ_LOG_DEBUG, "Client %s received PUBREC (Mid: %d)", mosq->id, mid); + + rc = _mosquitto_message_out_update(mosq, mid, mosq_ms_wait_for_pubcomp); +#endif + if(rc) return rc; + rc = _mosquitto_send_pubrel(mosq, mid, false); + if(rc) return rc; + + return MOSQ_ERR_SUCCESS; +} + +int _mosquitto_handle_pubrel(struct mosquitto_db *db, struct mosquitto *mosq) +{ + uint16_t mid; +#ifndef WITH_BROKER + struct mosquitto_message_all *message = NULL; +#endif + int rc; + + assert(mosq); +#ifdef WITH_STRICT_PROTOCOL + if(mosq->in_packet.remaining_length != 2){ + return MOSQ_ERR_PROTOCOL; + } +#endif + if(mosq->protocol == mosq_p_mqtt311){ + if((mosq->in_packet.command&0x0F) != 0x02){ + return MOSQ_ERR_PROTOCOL; + } + } + rc = _mosquitto_read_uint16(&mosq->in_packet, &mid); + if(rc) return rc; +#ifdef WITH_BROKER + _mosquitto_log_printf(NULL, MOSQ_LOG_DEBUG, "Received PUBREL from %s (Mid: %d)", mosq->id, mid); + + if(mqtt3_db_message_release(db, mosq, mid, mosq_md_in)){ + /* Message not found. Still send a PUBCOMP anyway because this could be + * due to a repeated PUBREL after a client has reconnected. */ + } +#else + _mosquitto_log_printf(mosq, MOSQ_LOG_DEBUG, "Client %s received PUBREL (Mid: %d)", mosq->id, mid); + + if(!_mosquitto_message_remove(mosq, mid, mosq_md_in, &message)){ + /* Only pass the message on if we have removed it from the queue - this + * prevents multiple callbacks for the same message. */ + pthread_mutex_lock(&mosq->callback_mutex); + if(mosq->on_message){ + mosq->in_callback = true; + mosq->on_message(mosq, mosq->userdata, &message->msg); + mosq->in_callback = false; + } + pthread_mutex_unlock(&mosq->callback_mutex); + _mosquitto_message_cleanup(&message); + } +#endif + rc = _mosquitto_send_pubcomp(mosq, mid); + if(rc) return rc; + + return MOSQ_ERR_SUCCESS; +} + +int _mosquitto_handle_suback(struct mosquitto *mosq) +{ + uint16_t mid; + uint8_t qos; + int *granted_qos; + int qos_count; + int i = 0; + int rc; + + assert(mosq); +#ifdef WITH_BROKER + _mosquitto_log_printf(NULL, MOSQ_LOG_DEBUG, "Received SUBACK from %s", mosq->id); +#else + _mosquitto_log_printf(mosq, MOSQ_LOG_DEBUG, "Client %s received SUBACK", mosq->id); +#endif + rc = _mosquitto_read_uint16(&mosq->in_packet, &mid); + if(rc) return rc; + + qos_count = mosq->in_packet.remaining_length - mosq->in_packet.pos; + granted_qos = _mosquitto_malloc(qos_count*sizeof(int)); + if(!granted_qos) return MOSQ_ERR_NOMEM; + while(mosq->in_packet.pos < mosq->in_packet.remaining_length){ + rc = _mosquitto_read_byte(&mosq->in_packet, &qos); + if(rc){ + _mosquitto_free(granted_qos); + return rc; + } + granted_qos[i] = (int)qos; + i++; + } +#ifndef WITH_BROKER + pthread_mutex_lock(&mosq->callback_mutex); + if(mosq->on_subscribe){ + mosq->in_callback = true; + mosq->on_subscribe(mosq, mosq->userdata, mid, qos_count, granted_qos); + mosq->in_callback = false; + } + pthread_mutex_unlock(&mosq->callback_mutex); +#endif + _mosquitto_free(granted_qos); + + return MOSQ_ERR_SUCCESS; +} + +int _mosquitto_handle_unsuback(struct mosquitto *mosq) +{ + uint16_t mid; + int rc; + + assert(mosq); +#ifdef WITH_STRICT_PROTOCOL + if(mosq->in_packet.remaining_length != 2){ + return MOSQ_ERR_PROTOCOL; + } +#endif +#ifdef WITH_BROKER + _mosquitto_log_printf(NULL, MOSQ_LOG_DEBUG, "Received UNSUBACK from %s", mosq->id); +#else + _mosquitto_log_printf(mosq, MOSQ_LOG_DEBUG, "Client %s received UNSUBACK", mosq->id); +#endif + rc = _mosquitto_read_uint16(&mosq->in_packet, &mid); + if(rc) return rc; +#ifndef WITH_BROKER + pthread_mutex_lock(&mosq->callback_mutex); + if(mosq->on_unsubscribe){ + mosq->in_callback = true; + mosq->on_unsubscribe(mosq, mosq->userdata, mid); + mosq->in_callback = false; + } + pthread_mutex_unlock(&mosq->callback_mutex); +#endif + + return MOSQ_ERR_SUCCESS; +} + diff --git a/lib/send_client_mosq.c b/lib/send_client_mosq.c new file mode 100644 index 0000000000..5c883c99cd --- /dev/null +++ b/lib/send_client_mosq.c @@ -0,0 +1,214 @@ +/* +Copyright (c) 2009-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#ifdef WITH_BROKER +#include +#endif + +int _mosquitto_send_connect(struct mosquitto *mosq, uint16_t keepalive, bool clean_session) +{ + struct _mosquitto_packet *packet = NULL; + int payloadlen; + uint8_t will = 0; + uint8_t byte; + int rc; + uint8_t version = PROTOCOL_VERSION_v31; + + assert(mosq); + assert(mosq->id); + + packet = _mosquitto_calloc(1, sizeof(struct _mosquitto_packet)); + if(!packet) return MOSQ_ERR_NOMEM; + + payloadlen = 2+strlen(mosq->id); + if(mosq->will){ + will = 1; + assert(mosq->will->topic); + + payloadlen += 2+strlen(mosq->will->topic) + 2+mosq->will->payloadlen; + } + if(mosq->username){ + payloadlen += 2+strlen(mosq->username); + if(mosq->password){ + payloadlen += 2+strlen(mosq->password); + } + } + + packet->command = CONNECT; + packet->remaining_length = 12+payloadlen; + rc = _mosquitto_packet_alloc(packet); + if(rc){ + _mosquitto_free(packet); + return rc; + } + + /* Variable header */ + _mosquitto_write_string(packet, PROTOCOL_NAME_v31, strlen(PROTOCOL_NAME_v31)); +#if defined(WITH_BROKER) && defined(WITH_BRIDGE) + if(mosq->bridge && mosq->bridge->try_private && mosq->bridge->try_private_accepted){ + version |= 0x80; + }else{ + } +#endif + _mosquitto_write_byte(packet, version); + byte = (clean_session&0x1)<<1; + if(will){ + byte = byte | ((mosq->will->retain&0x1)<<5) | ((mosq->will->qos&0x3)<<3) | ((will&0x1)<<2); + } + if(mosq->username){ + byte = byte | 0x1<<7; + if(mosq->password){ + byte = byte | 0x1<<6; + } + } + _mosquitto_write_byte(packet, byte); + _mosquitto_write_uint16(packet, keepalive); + + /* Payload */ + _mosquitto_write_string(packet, mosq->id, strlen(mosq->id)); + if(will){ + _mosquitto_write_string(packet, mosq->will->topic, strlen(mosq->will->topic)); + _mosquitto_write_string(packet, (const char *)mosq->will->payload, mosq->will->payloadlen); + } + if(mosq->username){ + _mosquitto_write_string(packet, mosq->username, strlen(mosq->username)); + if(mosq->password){ + _mosquitto_write_string(packet, mosq->password, strlen(mosq->password)); + } + } + + mosq->keepalive = keepalive; +#ifdef WITH_BROKER +# ifdef WITH_BRIDGE + _mosquitto_log_printf(mosq, MOSQ_LOG_DEBUG, "Bridge %s sending CONNECT", mosq->id); +# endif +#else + _mosquitto_log_printf(mosq, MOSQ_LOG_DEBUG, "Client %s sending CONNECT", mosq->id); +#endif + return _mosquitto_packet_queue(mosq, packet); +} + +int _mosquitto_send_disconnect(struct mosquitto *mosq) +{ + assert(mosq); +#ifdef WITH_BROKER +# ifdef WITH_BRIDGE + _mosquitto_log_printf(mosq, MOSQ_LOG_DEBUG, "Bridge %s sending DISCONNECT", mosq->id); +# endif +#else + _mosquitto_log_printf(mosq, MOSQ_LOG_DEBUG, "Client %s sending DISCONNECT", mosq->id); +#endif + return _mosquitto_send_simple_command(mosq, DISCONNECT); +} + +int _mosquitto_send_subscribe(struct mosquitto *mosq, int *mid, bool dup, const char *topic, uint8_t topic_qos) +{ + /* FIXME - only deals with a single topic */ + struct _mosquitto_packet *packet = NULL; + uint32_t packetlen; + uint16_t local_mid; + int rc; + + assert(mosq); + assert(topic); + + packet = _mosquitto_calloc(1, sizeof(struct _mosquitto_packet)); + if(!packet) return MOSQ_ERR_NOMEM; + + packetlen = 2 + 2+strlen(topic) + 1; + + packet->command = SUBSCRIBE | (dup<<3) | (1<<1); + packet->remaining_length = packetlen; + rc = _mosquitto_packet_alloc(packet); + if(rc){ + _mosquitto_free(packet); + return rc; + } + + /* Variable header */ + local_mid = _mosquitto_mid_generate(mosq); + if(mid) *mid = (int)local_mid; + _mosquitto_write_uint16(packet, local_mid); + + /* Payload */ + _mosquitto_write_string(packet, topic, strlen(topic)); + _mosquitto_write_byte(packet, topic_qos); + +#ifdef WITH_BROKER +# ifdef WITH_BRIDGE + _mosquitto_log_printf(mosq, MOSQ_LOG_DEBUG, "Bridge %s sending SUBSCRIBE (Mid: %d, Topic: %s, QoS: %d)", mosq->id, local_mid, topic, topic_qos); +# endif +#else + _mosquitto_log_printf(mosq, MOSQ_LOG_DEBUG, "Client %s sending SUBSCRIBE (Mid: %d, Topic: %s, QoS: %d)", mosq->id, local_mid, topic, topic_qos); +#endif + + return _mosquitto_packet_queue(mosq, packet); +} + + +int _mosquitto_send_unsubscribe(struct mosquitto *mosq, int *mid, bool dup, const char *topic) +{ + /* FIXME - only deals with a single topic */ + struct _mosquitto_packet *packet = NULL; + uint32_t packetlen; + uint16_t local_mid; + int rc; + + assert(mosq); + assert(topic); + + packet = _mosquitto_calloc(1, sizeof(struct _mosquitto_packet)); + if(!packet) return MOSQ_ERR_NOMEM; + + packetlen = 2 + 2+strlen(topic); + + packet->command = UNSUBSCRIBE | (dup<<3) | (1<<1); + packet->remaining_length = packetlen; + rc = _mosquitto_packet_alloc(packet); + if(rc){ + _mosquitto_free(packet); + return rc; + } + + /* Variable header */ + local_mid = _mosquitto_mid_generate(mosq); + if(mid) *mid = (int)local_mid; + _mosquitto_write_uint16(packet, local_mid); + + /* Payload */ + _mosquitto_write_string(packet, topic, strlen(topic)); + +#ifdef WITH_BROKER +# ifdef WITH_BRIDGE + _mosquitto_log_printf(mosq, MOSQ_LOG_DEBUG, "Bridge %s sending UNSUBSCRIBE (Mid: %d, Topic: %s)", mosq->id, local_mid, topic); +# endif +#else + _mosquitto_log_printf(mosq, MOSQ_LOG_DEBUG, "Client %s sending UNSUBSCRIBE (Mid: %d, Topic: %s)", mosq->id, local_mid, topic); +#endif + return _mosquitto_packet_queue(mosq, packet); +} + diff --git a/lib/send_mosq.c b/lib/send_mosq.c new file mode 100644 index 0000000000..765fa9c3ac --- /dev/null +++ b/lib/send_mosq.c @@ -0,0 +1,276 @@ +/* +Copyright (c) 2009-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef WITH_BROKER +#include +# ifdef WITH_SYS_TREE +extern uint64_t g_pub_bytes_sent; +# endif +#endif + +int _mosquitto_send_pingreq(struct mosquitto *mosq) +{ + int rc; + assert(mosq); +#ifdef WITH_BROKER + _mosquitto_log_printf(NULL, MOSQ_LOG_DEBUG, "Sending PINGREQ to %s", mosq->id); +#else + _mosquitto_log_printf(mosq, MOSQ_LOG_DEBUG, "Client %s sending PINGREQ", mosq->id); +#endif + rc = _mosquitto_send_simple_command(mosq, PINGREQ); + if(rc == MOSQ_ERR_SUCCESS){ + mosq->ping_t = mosquitto_time(); + } + return rc; +} + +int _mosquitto_send_pingresp(struct mosquitto *mosq) +{ +#ifdef WITH_BROKER + if(mosq) _mosquitto_log_printf(NULL, MOSQ_LOG_DEBUG, "Sending PINGRESP to %s", mosq->id); +#else + if(mosq) _mosquitto_log_printf(mosq, MOSQ_LOG_DEBUG, "Client %s sending PINGRESP", mosq->id); +#endif + return _mosquitto_send_simple_command(mosq, PINGRESP); +} + +int _mosquitto_send_puback(struct mosquitto *mosq, uint16_t mid) +{ +#ifdef WITH_BROKER + if(mosq) _mosquitto_log_printf(NULL, MOSQ_LOG_DEBUG, "Sending PUBACK to %s (Mid: %d)", mosq->id, mid); +#else + if(mosq) _mosquitto_log_printf(mosq, MOSQ_LOG_DEBUG, "Client %s sending PUBACK (Mid: %d)", mosq->id, mid); +#endif + return _mosquitto_send_command_with_mid(mosq, PUBACK, mid, false); +} + +int _mosquitto_send_pubcomp(struct mosquitto *mosq, uint16_t mid) +{ +#ifdef WITH_BROKER + if(mosq) _mosquitto_log_printf(NULL, MOSQ_LOG_DEBUG, "Sending PUBCOMP to %s (Mid: %d)", mosq->id, mid); +#else + if(mosq) _mosquitto_log_printf(mosq, MOSQ_LOG_DEBUG, "Client %s sending PUBCOMP (Mid: %d)", mosq->id, mid); +#endif + return _mosquitto_send_command_with_mid(mosq, PUBCOMP, mid, false); +} + +int _mosquitto_send_publish(struct mosquitto *mosq, uint16_t mid, const char *topic, uint32_t payloadlen, const void *payload, int qos, bool retain, bool dup) +{ +#ifdef WITH_BROKER + size_t len; +#ifdef WITH_BRIDGE + int i; + struct _mqtt3_bridge_topic *cur_topic; + bool match; + int rc; + char *mapped_topic = NULL; + char *topic_temp = NULL; +#endif +#endif + assert(mosq); + assert(topic); + + if(mosq->sock == INVALID_SOCKET) return MOSQ_ERR_NO_CONN; +#ifdef WITH_BROKER + if(mosq->listener && mosq->listener->mount_point){ + len = strlen(mosq->listener->mount_point); + if(len < strlen(topic)){ + topic += len; + }else{ + /* Invalid topic string. Should never happen, but silently swallow the message anyway. */ + return MOSQ_ERR_SUCCESS; + } + } +#ifdef WITH_BRIDGE + if(mosq->bridge && mosq->bridge->topics && mosq->bridge->topic_remapping){ + for(i=0; ibridge->topic_count; i++){ + cur_topic = &mosq->bridge->topics[i]; + if((cur_topic->direction == bd_both || cur_topic->direction == bd_out) + && (cur_topic->remote_prefix || cur_topic->local_prefix)){ + /* Topic mapping required on this topic if the message matches */ + + rc = mosquitto_topic_matches_sub(cur_topic->local_topic, topic, &match); + if(rc){ + return rc; + } + if(match){ + mapped_topic = _mosquitto_strdup(topic); + if(!mapped_topic) return MOSQ_ERR_NOMEM; + if(cur_topic->local_prefix){ + /* This prefix needs removing. */ + if(!strncmp(cur_topic->local_prefix, mapped_topic, strlen(cur_topic->local_prefix))){ + topic_temp = _mosquitto_strdup(mapped_topic+strlen(cur_topic->local_prefix)); + _mosquitto_free(mapped_topic); + if(!topic_temp){ + return MOSQ_ERR_NOMEM; + } + mapped_topic = topic_temp; + } + } + + if(cur_topic->remote_prefix){ + /* This prefix needs adding. */ + len = strlen(mapped_topic) + strlen(cur_topic->remote_prefix)+1; + topic_temp = _mosquitto_calloc(len+1, sizeof(char)); + if(!topic_temp){ + _mosquitto_free(mapped_topic); + return MOSQ_ERR_NOMEM; + } + snprintf(topic_temp, len, "%s%s", cur_topic->remote_prefix, mapped_topic); + _mosquitto_free(mapped_topic); + mapped_topic = topic_temp; + } + _mosquitto_log_printf(NULL, MOSQ_LOG_DEBUG, "Sending PUBLISH to %s (d%d, q%d, r%d, m%d, '%s', ... (%ld bytes))", mosq->id, dup, qos, retain, mid, mapped_topic, (long)payloadlen); +#ifdef WITH_SYS_TREE + g_pub_bytes_sent += payloadlen; +#endif + rc = _mosquitto_send_real_publish(mosq, mid, mapped_topic, payloadlen, payload, qos, retain, dup); + _mosquitto_free(mapped_topic); + return rc; + } + } + } + } +#endif + _mosquitto_log_printf(NULL, MOSQ_LOG_DEBUG, "Sending PUBLISH to %s (d%d, q%d, r%d, m%d, '%s', ... (%ld bytes))", mosq->id, dup, qos, retain, mid, topic, (long)payloadlen); +# ifdef WITH_SYS_TREE + g_pub_bytes_sent += payloadlen; +# endif +#else + _mosquitto_log_printf(mosq, MOSQ_LOG_DEBUG, "Client %s sending PUBLISH (d%d, q%d, r%d, m%d, '%s', ... (%ld bytes))", mosq->id, dup, qos, retain, mid, topic, (long)payloadlen); +#endif + + return _mosquitto_send_real_publish(mosq, mid, topic, payloadlen, payload, qos, retain, dup); +} + +int _mosquitto_send_pubrec(struct mosquitto *mosq, uint16_t mid) +{ +#ifdef WITH_BROKER + if(mosq) _mosquitto_log_printf(NULL, MOSQ_LOG_DEBUG, "Sending PUBREC to %s (Mid: %d)", mosq->id, mid); +#else + if(mosq) _mosquitto_log_printf(mosq, MOSQ_LOG_DEBUG, "Client %s sending PUBREC (Mid: %d)", mosq->id, mid); +#endif + return _mosquitto_send_command_with_mid(mosq, PUBREC, mid, false); +} + +int _mosquitto_send_pubrel(struct mosquitto *mosq, uint16_t mid, bool dup) +{ +#ifdef WITH_BROKER + if(mosq) _mosquitto_log_printf(NULL, MOSQ_LOG_DEBUG, "Sending PUBREL to %s (Mid: %d)", mosq->id, mid); +#else + if(mosq) _mosquitto_log_printf(mosq, MOSQ_LOG_DEBUG, "Client %s sending PUBREL (Mid: %d)", mosq->id, mid); +#endif + return _mosquitto_send_command_with_mid(mosq, PUBREL|2, mid, dup); +} + +/* For PUBACK, PUBCOMP, PUBREC, and PUBREL */ +int _mosquitto_send_command_with_mid(struct mosquitto *mosq, uint8_t command, uint16_t mid, bool dup) +{ + struct _mosquitto_packet *packet = NULL; + int rc; + + assert(mosq); + packet = _mosquitto_calloc(1, sizeof(struct _mosquitto_packet)); + if(!packet) return MOSQ_ERR_NOMEM; + + packet->command = command; + if(dup){ + packet->command |= 8; + } + packet->remaining_length = 2; + rc = _mosquitto_packet_alloc(packet); + if(rc){ + _mosquitto_free(packet); + return rc; + } + + packet->payload[packet->pos+0] = MOSQ_MSB(mid); + packet->payload[packet->pos+1] = MOSQ_LSB(mid); + + return _mosquitto_packet_queue(mosq, packet); +} + +/* For DISCONNECT, PINGREQ and PINGRESP */ +int _mosquitto_send_simple_command(struct mosquitto *mosq, uint8_t command) +{ + struct _mosquitto_packet *packet = NULL; + int rc; + + assert(mosq); + packet = _mosquitto_calloc(1, sizeof(struct _mosquitto_packet)); + if(!packet) return MOSQ_ERR_NOMEM; + + packet->command = command; + packet->remaining_length = 0; + + rc = _mosquitto_packet_alloc(packet); + if(rc){ + _mosquitto_free(packet); + return rc; + } + + return _mosquitto_packet_queue(mosq, packet); +} + +int _mosquitto_send_real_publish(struct mosquitto *mosq, uint16_t mid, const char *topic, uint32_t payloadlen, const void *payload, int qos, bool retain, bool dup) +{ + struct _mosquitto_packet *packet = NULL; + int packetlen; + int rc; + + assert(mosq); + assert(topic); + + packetlen = 2+strlen(topic) + payloadlen; + if(qos > 0) packetlen += 2; /* For message id */ + packet = _mosquitto_calloc(1, sizeof(struct _mosquitto_packet)); + if(!packet) return MOSQ_ERR_NOMEM; + + packet->mid = mid; + packet->command = PUBLISH | ((dup&0x1)<<3) | (qos<<1) | retain; + packet->remaining_length = packetlen; + rc = _mosquitto_packet_alloc(packet); + if(rc){ + _mosquitto_free(packet); + return rc; + } + /* Variable header (topic string) */ + _mosquitto_write_string(packet, topic, strlen(topic)); + if(qos > 0){ + _mosquitto_write_uint16(packet, mid); + } + + /* Payload */ + if(payloadlen){ + _mosquitto_write_bytes(packet, payload, payloadlen); + } + + return _mosquitto_packet_queue(mosq, packet); +} diff --git a/lib/send_mosq.h b/lib/send_mosq.h new file mode 100644 index 0000000000..4500fac98d --- /dev/null +++ b/lib/send_mosq.h @@ -0,0 +1,37 @@ +/* +Copyright (c) 2010-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ +#ifndef _SEND_MOSQ_H_ +#define _SEND_MOSQ_H_ + +#include + +int _mosquitto_send_simple_command(struct mosquitto *mosq, uint8_t command); +int _mosquitto_send_command_with_mid(struct mosquitto *mosq, uint8_t command, uint16_t mid, bool dup); +int _mosquitto_send_real_publish(struct mosquitto *mosq, uint16_t mid, const char *topic, uint32_t payloadlen, const void *payload, int qos, bool retain, bool dup); + +int _mosquitto_send_connect(struct mosquitto *mosq, uint16_t keepalive, bool clean_session); +int _mosquitto_send_disconnect(struct mosquitto *mosq); +int _mosquitto_send_pingreq(struct mosquitto *mosq); +int _mosquitto_send_pingresp(struct mosquitto *mosq); +int _mosquitto_send_puback(struct mosquitto *mosq, uint16_t mid); +int _mosquitto_send_pubcomp(struct mosquitto *mosq, uint16_t mid); +int _mosquitto_send_publish(struct mosquitto *mosq, uint16_t mid, const char *topic, uint32_t payloadlen, const void *payload, int qos, bool retain, bool dup); +int _mosquitto_send_pubrec(struct mosquitto *mosq, uint16_t mid); +int _mosquitto_send_pubrel(struct mosquitto *mosq, uint16_t mid, bool dup); +int _mosquitto_send_subscribe(struct mosquitto *mosq, int *mid, bool dup, const char *topic, uint8_t topic_qos); +int _mosquitto_send_unsubscribe(struct mosquitto *mosq, int *mid, bool dup, const char *topic); + +#endif diff --git a/lib/srv_mosq.c b/lib/srv_mosq.c new file mode 100644 index 0000000000..276080d6b3 --- /dev/null +++ b/lib/srv_mosq.c @@ -0,0 +1,93 @@ +/* +Copyright (c) 2013,2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#ifdef WITH_SRV +# include + +# include +# include +# include +#endif + +#include "logging_mosq.h" +#include "memory_mosq.h" +#include "mosquitto_internal.h" +#include "mosquitto.h" + +#ifdef WITH_SRV +static void srv_callback(void *arg, int status, int timeouts, unsigned char *abuf, int alen) +{ + struct mosquitto *mosq = arg; + struct ares_srv_reply *reply = NULL; + if(status == ARES_SUCCESS){ + status = ares_parse_srv_reply(abuf, alen, &reply); + if(status == ARES_SUCCESS){ + // FIXME - choose which answer to use based on rfc2782 page 3. */ + mosquitto_connect(mosq, reply->host, reply->port, mosq->keepalive); + } + }else{ + _mosquitto_log_printf(mosq, MOSQ_LOG_ERR, "Error: SRV lookup failed (%d).", status); + exit(1); + } +} +#endif + +int mosquitto_connect_srv(struct mosquitto *mosq, const char *host, int keepalive, const char *bind_address) +{ +#ifdef WITH_SRV + char *h; + int rc; + if(!mosq) return MOSQ_ERR_INVAL; + + rc = ares_init(&mosq->achan); + if(rc != ARES_SUCCESS){ + return MOSQ_ERR_UNKNOWN; + } + + if(!host){ + // get local domain + }else{ +#ifdef WITH_TLS + if(mosq->tls_cafile || mosq->tls_capath || mosq->tls_psk){ + h = _mosquitto_malloc(strlen(host) + strlen("_secure-mqtt._tcp.") + 1); + if(!h) return MOSQ_ERR_NOMEM; + sprintf(h, "_secure-mqtt._tcp.%s", host); + }else{ +#endif + h = _mosquitto_malloc(strlen(host) + strlen("_mqtt._tcp.") + 1); + if(!h) return MOSQ_ERR_NOMEM; + sprintf(h, "_mqtt._tcp.%s", host); +#ifdef WITH_TLS + } +#endif + ares_search(mosq->achan, h, ns_c_in, ns_t_srv, srv_callback, mosq); + _mosquitto_free(h); + } + + pthread_mutex_lock(&mosq->state_mutex); + mosq->state = mosq_cs_connect_srv; + pthread_mutex_unlock(&mosq->state_mutex); + + mosq->keepalive = keepalive; + + return MOSQ_ERR_SUCCESS; + +#else + return MOSQ_ERR_NOT_SUPPORTED; +#endif +} + + diff --git a/lib/thread_mosq.c b/lib/thread_mosq.c new file mode 100644 index 0000000000..d5eb6bcea9 --- /dev/null +++ b/lib/thread_mosq.c @@ -0,0 +1,101 @@ +/* +Copyright (c) 2011-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#include + +#ifndef WIN32 +#include +#endif + +#include +#include + +void *_mosquitto_thread_main(void *obj); + +int mosquitto_loop_start(struct mosquitto *mosq) +{ +#ifdef WITH_THREADING + if(!mosq || mosq->threaded) return MOSQ_ERR_INVAL; + + mosq->threaded = true; + pthread_create(&mosq->thread_id, NULL, _mosquitto_thread_main, mosq); + return MOSQ_ERR_SUCCESS; +#else + return MOSQ_ERR_NOT_SUPPORTED; +#endif +} + +int mosquitto_loop_stop(struct mosquitto *mosq, bool force) +{ +#ifdef WITH_THREADING +# ifndef WITH_BROKER + char sockpair_data = 0; +# endif + + if(!mosq || !mosq->threaded) return MOSQ_ERR_INVAL; + + + /* Write a single byte to sockpairW (connected to sockpairR) to break out + * of select() if in threaded mode. */ + if(mosq->sockpairW != INVALID_SOCKET){ +#ifndef WIN32 + if(write(mosq->sockpairW, &sockpair_data, 1)){ + } +#else + send(mosq->sockpairW, &sockpair_data, 1, 0); +#endif + } + + if(force){ + pthread_cancel(mosq->thread_id); + } + pthread_join(mosq->thread_id, NULL); + mosq->thread_id = pthread_self(); + mosq->threaded = false; + + return MOSQ_ERR_SUCCESS; +#else + return MOSQ_ERR_NOT_SUPPORTED; +#endif +} + +#ifdef WITH_THREADING +void *_mosquitto_thread_main(void *obj) +{ + struct mosquitto *mosq = obj; + + if(!mosq) return NULL; + + pthread_mutex_lock(&mosq->state_mutex); + if(mosq->state == mosq_cs_connect_async){ + pthread_mutex_unlock(&mosq->state_mutex); + mosquitto_reconnect(mosq); + }else{ + pthread_mutex_unlock(&mosq->state_mutex); + } + + if(!mosq->keepalive){ + /* Sleep for a day if keepalive disabled. */ + mosquitto_loop_forever(mosq, mosq->keepalive*1000*86400, 1); + }else{ + /* Sleep for our keepalive value. publish() etc. will wake us up. */ + mosquitto_loop_forever(mosq, mosq->keepalive*1000, 1); + } + + return obj; +} +#endif + diff --git a/lib/time_mosq.c b/lib/time_mosq.c new file mode 100644 index 0000000000..0f85bbc45c --- /dev/null +++ b/lib/time_mosq.c @@ -0,0 +1,82 @@ +/* +Copyright (c) 2013,2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#ifdef __APPLE__ +#include +#include +#endif + +#ifdef WIN32 +# define _WIN32_WINNT _WIN32_WINNT_VISTA +# include +#else +# include +#endif +#include + +#include "mosquitto.h" +#include "time_mosq.h" + +#ifdef WIN32 +static bool tick64 = false; + +void _windows_time_version_check(void) +{ + OSVERSIONINFO vi; + + tick64 = false; + + memset(&vi, 0, sizeof(OSVERSIONINFO)); + vi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + if(GetVersionEx(&vi)){ + if(vi.dwMajorVersion > 5){ + tick64 = true; + } + } +} +#endif + +time_t mosquitto_time(void) +{ +#ifdef WIN32 + if(tick64){ + return GetTickCount64()/1000; + }else{ + return GetTickCount()/1000; /* FIXME - need to deal with overflow. */ + } +#elif _POSIX_TIMERS>0 && defined(_POSIX_MONOTONIC_CLOCK) + struct timespec tp; + + clock_gettime(CLOCK_MONOTONIC, &tp); + return tp.tv_sec; +#elif defined(__APPLE__) + static mach_timebase_info_data_t tb; + uint64_t ticks; + uint64_t sec; + + ticks = mach_absolute_time(); + + if(tb.denom == 0){ + mach_timebase_info(&tb); + } + sec = ticks*tb.numer/tb.denom/1000000000; + + return (time_t)sec; +#else + return time(NULL); +#endif +} + diff --git a/lib/time_mosq.h b/lib/time_mosq.h new file mode 100644 index 0000000000..106279c388 --- /dev/null +++ b/lib/time_mosq.h @@ -0,0 +1,22 @@ +/* +Copyright (c) 2013,2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#ifndef _TIME_MOSQ_H_ +#define _TIME_MOSQ_H_ + +time_t mosquitto_time(void); + +#endif diff --git a/lib/tls_mosq.c b/lib/tls_mosq.c new file mode 100644 index 0000000000..9c497428bd --- /dev/null +++ b/lib/tls_mosq.c @@ -0,0 +1,134 @@ +/* +Copyright (c) 2013,2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#ifdef WITH_TLS + +#ifdef WIN32 +# include +# include +#else +# include +#endif + +#include +#include +#include +#include + +#ifdef WITH_BROKER +# include "mosquitto_broker.h" +#endif +#include "mosquitto_internal.h" +#include "tls_mosq.h" + +extern int tls_ex_index_mosq; + +int _mosquitto_server_certificate_verify(int preverify_ok, X509_STORE_CTX *ctx) +{ + /* Preverify should have already checked expiry, revocation. + * We need to verify the hostname. */ + struct mosquitto *mosq; + SSL *ssl; + X509 *cert; + + /* Always reject if preverify_ok has failed. */ + if(!preverify_ok) return 0; + + ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); + mosq = SSL_get_ex_data(ssl, tls_ex_index_mosq); + if(!mosq) return 0; + + if(mosq->tls_insecure == false){ + if(X509_STORE_CTX_get_error_depth(ctx) == 0){ + /* FIXME - use X509_check_host() etc. for sufficiently new openssl (>=1.1.x) */ + cert = X509_STORE_CTX_get_current_cert(ctx); + /* This is the peer certificate, all others are upwards in the chain. */ +#if defined(WITH_BROKER) + return _mosquitto_verify_certificate_hostname(cert, mosq->bridge->addresses[mosq->bridge->cur_address].address); +#else + return _mosquitto_verify_certificate_hostname(cert, mosq->host); +#endif + }else{ + return preverify_ok; + } + }else{ + return preverify_ok; + } +} + +/* This code is based heavily on the example provided in "Secure Programming + * Cookbook for C and C++". + */ +int _mosquitto_verify_certificate_hostname(X509 *cert, const char *hostname) +{ + int i; + char name[256]; + X509_NAME *subj; + bool have_san_dns = false; + STACK_OF(GENERAL_NAME) *san; + const GENERAL_NAME *nval; + const unsigned char *data; + unsigned char ipv6_addr[16]; + unsigned char ipv4_addr[4]; + int ipv6_ok; + int ipv4_ok; + +#ifdef WIN32 + ipv6_ok = InetPton(AF_INET6, hostname, &ipv6_addr); + ipv4_ok = InetPton(AF_INET, hostname, &ipv4_addr); +#else + ipv6_ok = inet_pton(AF_INET6, hostname, &ipv6_addr); + ipv4_ok = inet_pton(AF_INET, hostname, &ipv4_addr); +#endif + + san = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); + if(san){ + for(i=0; itype == GEN_DNS){ + data = ASN1_STRING_data(nval->d.dNSName); + if(data && !strcasecmp((char *)data, hostname)){ + return 1; + } + have_san_dns = true; + }else if(nval->type == GEN_IPADD){ + data = ASN1_STRING_data(nval->d.iPAddress); + if(nval->d.iPAddress->length == 4 && ipv4_ok){ + if(!memcmp(ipv4_addr, data, 4)){ + return 1; + } + }else if(nval->d.iPAddress->length == 16 && ipv6_ok){ + if(!memcmp(ipv6_addr, data, 16)){ + return 1; + } + } + } + } + if(have_san_dns){ + /* Only check CN if subjectAltName DNS entry does not exist. */ + return 0; + } + } + subj = X509_get_subject_name(cert); + if(X509_NAME_get_text_by_NID(subj, NID_commonName, name, sizeof(name)) > 0){ + name[sizeof(name) - 1] = '\0'; + if (!strcasecmp(name, hostname)) return 1; + } + return 0; +} + +#endif + diff --git a/lib/tls_mosq.h b/lib/tls_mosq.h new file mode 100644 index 0000000000..cf06eff35e --- /dev/null +++ b/lib/tls_mosq.h @@ -0,0 +1,36 @@ +/* +Copyright (c) 2013,2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#ifndef _TLS_MOSQ_H_ +#define _TLS_MOSQ_H_ + +#ifdef WITH_TLS + +#include +#ifdef WITH_TLS_PSK +# if OPENSSL_VERSION_NUMBER >= 0x10000000 +# define REAL_WITH_TLS_PSK +# else +# warning "TLS-PSK not supported, openssl too old." +# endif +#endif + +int _mosquitto_server_certificate_verify(int preverify_ok, X509_STORE_CTX *ctx); +int _mosquitto_verify_certificate_hostname(X509 *cert, const char *hostname); + +#endif /* WITH_TLS */ + +#endif diff --git a/lib/util_mosq.c b/lib/util_mosq.c new file mode 100644 index 0000000000..de7b5004d5 --- /dev/null +++ b/lib/util_mosq.c @@ -0,0 +1,307 @@ +/* +Copyright (c) 2009-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#include +#include + +#ifdef WIN32 +#include +#endif + + +#include +#include +#include +#include +#include +#include +#include + +#ifdef WITH_BROKER +#include +#endif + +int _mosquitto_packet_alloc(struct _mosquitto_packet *packet) +{ + uint8_t remaining_bytes[5], byte; + uint32_t remaining_length; + int i; + + assert(packet); + + remaining_length = packet->remaining_length; + packet->payload = NULL; + packet->remaining_count = 0; + do{ + byte = remaining_length % 128; + remaining_length = remaining_length / 128; + /* If there are more digits to encode, set the top bit of this digit */ + if(remaining_length > 0){ + byte = byte | 0x80; + } + remaining_bytes[packet->remaining_count] = byte; + packet->remaining_count++; + }while(remaining_length > 0 && packet->remaining_count < 5); + if(packet->remaining_count == 5) return MOSQ_ERR_PAYLOAD_SIZE; + packet->packet_length = packet->remaining_length + 1 + packet->remaining_count; + packet->payload = _mosquitto_malloc(sizeof(uint8_t)*packet->packet_length); + if(!packet->payload) return MOSQ_ERR_NOMEM; + + packet->payload[0] = packet->command; + for(i=0; iremaining_count; i++){ + packet->payload[i+1] = remaining_bytes[i]; + } + packet->pos = 1 + packet->remaining_count; + + return MOSQ_ERR_SUCCESS; +} + +void _mosquitto_check_keepalive(struct mosquitto *mosq) +{ + time_t last_msg_out; + time_t last_msg_in; + time_t now = mosquitto_time(); +#ifndef WITH_BROKER + int rc; +#endif + + assert(mosq); +#if defined(WITH_BROKER) && defined(WITH_BRIDGE) + /* Check if a lazy bridge should be timed out due to idle. */ + if(mosq->bridge && mosq->bridge->start_type == bst_lazy + && mosq->sock != INVALID_SOCKET + && now - mosq->last_msg_out >= mosq->bridge->idle_timeout){ + + _mosquitto_log_printf(NULL, MOSQ_LOG_NOTICE, "Bridge connection %s has exceeded idle timeout, disconnecting.", mosq->id); + _mosquitto_socket_close(mosq); + return; + } +#endif + pthread_mutex_lock(&mosq->msgtime_mutex); + last_msg_out = mosq->last_msg_out; + last_msg_in = mosq->last_msg_in; + pthread_mutex_unlock(&mosq->msgtime_mutex); + if(mosq->keepalive && mosq->sock != INVALID_SOCKET && + (now - last_msg_out >= mosq->keepalive || now - last_msg_in >= mosq->keepalive)){ + + if(mosq->state == mosq_cs_connected && mosq->ping_t == 0){ + _mosquitto_send_pingreq(mosq); + /* Reset last msg times to give the server time to send a pingresp */ + pthread_mutex_lock(&mosq->msgtime_mutex); + mosq->last_msg_in = now; + mosq->last_msg_out = now; + pthread_mutex_unlock(&mosq->msgtime_mutex); + }else{ +#ifdef WITH_BROKER + if(mosq->listener){ + mosq->listener->client_count--; + assert(mosq->listener->client_count >= 0); + } + mosq->listener = NULL; +#endif + _mosquitto_socket_close(mosq); +#ifndef WITH_BROKER + pthread_mutex_lock(&mosq->state_mutex); + if(mosq->state == mosq_cs_disconnecting){ + rc = MOSQ_ERR_SUCCESS; + }else{ + rc = 1; + } + pthread_mutex_unlock(&mosq->state_mutex); + pthread_mutex_lock(&mosq->callback_mutex); + if(mosq->on_disconnect){ + mosq->in_callback = true; + mosq->on_disconnect(mosq, mosq->userdata, rc); + mosq->in_callback = false; + } + pthread_mutex_unlock(&mosq->callback_mutex); +#endif + } + } +} + +uint16_t _mosquitto_mid_generate(struct mosquitto *mosq) +{ + assert(mosq); + + mosq->last_mid++; + if(mosq->last_mid == 0) mosq->last_mid++; + + return mosq->last_mid; +} + +/* Search for + or # in a topic. Return MOSQ_ERR_INVAL if found. + * Also returns MOSQ_ERR_INVAL if the topic string is too long. + * Returns MOSQ_ERR_SUCCESS if everything is fine. + */ +int _mosquitto_topic_wildcard_len_check(const char *str) +{ + int len = 0; + while(str && str[0]){ + if(str[0] == '+' || str[0] == '#'){ + return MOSQ_ERR_INVAL; + } + len++; + str = &str[1]; + } + if(len > 65535) return MOSQ_ERR_INVAL; + + return MOSQ_ERR_SUCCESS; +} + +/* Search for + or # in a topic, check they aren't in invalid positions such as foo/#/bar, foo/+bar or foo/bar#. + * Return MOSQ_ERR_INVAL if invalid position found. + * Also returns MOSQ_ERR_INVAL if the topic string is too long. + * Returns MOSQ_ERR_SUCCESS if everything is fine. + */ +int _mosquitto_topic_wildcard_pos_check(const char *str) +{ + char c = '\0'; + int len = 0; + while(str && str[0]){ + if(str[0] == '+'){ + if((c != '\0' && c != '/') || (str[1] != '\0' && str[1] != '/')){ + return MOSQ_ERR_INVAL; + } + }else if(str[0] == '#'){ + if((c != '\0' && c != '/') || str[1] != '\0'){ + return MOSQ_ERR_INVAL; + } + } + len++; + c = str[0]; + str = &str[1]; + } + if(len > 65535) return MOSQ_ERR_INVAL; + + return MOSQ_ERR_SUCCESS; +} + +/* Does a topic match a subscription? */ +int mosquitto_topic_matches_sub(const char *sub, const char *topic, bool *result) +{ + int slen, tlen; + int spos, tpos; + bool multilevel_wildcard = false; + + if(!sub || !topic || !result) return MOSQ_ERR_INVAL; + + slen = strlen(sub); + tlen = strlen(topic); + + if(slen && tlen){ + if((sub[0] == '$' && topic[0] != '$') + || (topic[0] == '$' && sub[0] != '$')){ + + *result = false; + return MOSQ_ERR_SUCCESS; + } + } + + spos = 0; + tpos = 0; + + while(spos < slen && tpos < tlen){ + if(sub[spos] == topic[tpos]){ + spos++; + tpos++; + if(spos == slen && tpos == tlen){ + *result = true; + return MOSQ_ERR_SUCCESS; + }else if(tpos == tlen && spos == slen-1 && sub[spos] == '+'){ + spos++; + *result = true; + return MOSQ_ERR_SUCCESS; + } + }else{ + if(sub[spos] == '+'){ + spos++; + while(tpos < tlen && topic[tpos] != '/'){ + tpos++; + } + if(tpos == tlen && spos == slen){ + *result = true; + return MOSQ_ERR_SUCCESS; + } + }else if(sub[spos] == '#'){ + multilevel_wildcard = true; + if(spos+1 != slen){ + *result = false; + return MOSQ_ERR_SUCCESS; + }else{ + *result = true; + return MOSQ_ERR_SUCCESS; + } + }else{ + *result = false; + return MOSQ_ERR_SUCCESS; + } + } + if(tpos == tlen-1){ + /* Check for e.g. foo matching foo/# */ + if(spos == slen-3 + && sub[spos+1] == '/' + && sub[spos+2] == '#'){ + *result = true; + multilevel_wildcard = true; + return MOSQ_ERR_SUCCESS; + } + } + } + if(multilevel_wildcard == false && (tpos < tlen || spos < slen)){ + *result = false; + } + + return MOSQ_ERR_SUCCESS; +} + +#ifdef REAL_WITH_TLS_PSK +int _mosquitto_hex2bin(const char *hex, unsigned char *bin, int bin_max_len) +{ + BIGNUM *bn = NULL; + int len; + + if(BN_hex2bn(&bn, hex) == 0){ + if(bn) BN_free(bn); + return 0; + } + if(BN_num_bytes(bn) > bin_max_len){ + BN_free(bn); + return 0; + } + + len = BN_bn2bin(bn, bin); + BN_free(bn); + return len; +} +#endif + +FILE *_mosquitto_fopen(const char *path, const char *mode) +{ +#ifdef WIN32 + char buf[MAX_PATH]; + int rc; + rc = ExpandEnvironmentStrings(path, buf, MAX_PATH); + if(rc == 0 || rc == MAX_PATH){ + return NULL; + }else{ + return fopen(buf, mode); + } +#else + return fopen(path, mode); +#endif +} + diff --git a/lib/util_mosq.h b/lib/util_mosq.h new file mode 100644 index 0000000000..e99e462a65 --- /dev/null +++ b/lib/util_mosq.h @@ -0,0 +1,35 @@ +/* +Copyright (c) 2009-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ +#ifndef _UTIL_MOSQ_H_ +#define _UTIL_MOSQ_H_ + +#include + +#include "tls_mosq.h" +#include "mosquitto.h" + +int _mosquitto_packet_alloc(struct _mosquitto_packet *packet); +void _mosquitto_check_keepalive(struct mosquitto *mosq); +uint16_t _mosquitto_mid_generate(struct mosquitto *mosq); +int _mosquitto_topic_wildcard_len_check(const char *str); +int _mosquitto_topic_wildcard_pos_check(const char *str); +FILE *_mosquitto_fopen(const char *path, const char *mode); + +#ifdef REAL_WITH_TLS_PSK +int _mosquitto_hex2bin(const char *hex, unsigned char *bin, int bin_max_len); +#endif + +#endif diff --git a/lib/will_mosq.c b/lib/will_mosq.c new file mode 100644 index 0000000000..e78faa893f --- /dev/null +++ b/lib/will_mosq.c @@ -0,0 +1,117 @@ +/* +Copyright (c) 2010-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#include +#include +#include +#include +#ifndef WIN32 +#include +#include +#else +#include +typedef int ssize_t; +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int _mosquitto_will_set(struct mosquitto *mosq, const char *topic, int payloadlen, const void *payload, int qos, bool retain) +{ + int rc = MOSQ_ERR_SUCCESS; + + if(!mosq || !topic) return MOSQ_ERR_INVAL; + if(payloadlen < 0 || payloadlen > MQTT_MAX_PAYLOAD) return MOSQ_ERR_PAYLOAD_SIZE; + if(payloadlen > 0 && !payload) return MOSQ_ERR_INVAL; + + if(_mosquitto_topic_wildcard_pos_check(topic)) return MOSQ_ERR_INVAL; + + if(mosq->will){ + if(mosq->will->topic){ + _mosquitto_free(mosq->will->topic); + mosq->will->topic = NULL; + } + if(mosq->will->payload){ + _mosquitto_free(mosq->will->payload); + mosq->will->payload = NULL; + } + _mosquitto_free(mosq->will); + mosq->will = NULL; + } + + mosq->will = _mosquitto_calloc(1, sizeof(struct mosquitto_message)); + if(!mosq->will) return MOSQ_ERR_NOMEM; + mosq->will->topic = _mosquitto_strdup(topic); + if(!mosq->will->topic){ + rc = MOSQ_ERR_NOMEM; + goto cleanup; + } + mosq->will->payloadlen = payloadlen; + if(mosq->will->payloadlen > 0){ + if(!payload){ + rc = MOSQ_ERR_INVAL; + goto cleanup; + } + mosq->will->payload = _mosquitto_malloc(sizeof(char)*mosq->will->payloadlen); + if(!mosq->will->payload){ + rc = MOSQ_ERR_NOMEM; + goto cleanup; + } + + memcpy(mosq->will->payload, payload, payloadlen); + } + mosq->will->qos = qos; + mosq->will->retain = retain; + + return MOSQ_ERR_SUCCESS; + +cleanup: + if(mosq->will){ + if(mosq->will->topic) _mosquitto_free(mosq->will->topic); + if(mosq->will->payload) _mosquitto_free(mosq->will->payload); + } + _mosquitto_free(mosq->will); + mosq->will = NULL; + + return rc; +} + +int _mosquitto_will_clear(struct mosquitto *mosq) +{ + if(!mosq->will) return MOSQ_ERR_SUCCESS; + + if(mosq->will->topic){ + _mosquitto_free(mosq->will->topic); + mosq->will->topic = NULL; + } + if(mosq->will->payload){ + _mosquitto_free(mosq->will->payload); + mosq->will->payload = NULL; + } + _mosquitto_free(mosq->will); + mosq->will = NULL; + + return MOSQ_ERR_SUCCESS; +} + diff --git a/lib/will_mosq.h b/lib/will_mosq.h new file mode 100644 index 0000000000..66ac9af17b --- /dev/null +++ b/lib/will_mosq.h @@ -0,0 +1,26 @@ +/* +Copyright (c) 2010-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#ifndef _WILL_MOSQ_H_ +#define _WILL_MOSQ_H_ + +#include +#include + +int _mosquitto_will_set(struct mosquitto *mosq, const char *topic, int payloadlen, const void *payload, int qos, bool retain); +int _mosquitto_will_clear(struct mosquitto *mosq); + +#endif diff --git a/logo/mosquitto-14x14.png b/logo/mosquitto-14x14.png new file mode 100644 index 0000000000000000000000000000000000000000..4fcda77a547b3ca994974ce8a20693c87e49e50d GIT binary patch literal 513 zcmV+c0{;DpP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L006iE006iFFyNyM00007bV*G`2iXe= z3I!T|zY#S600DtXL_t(2&$W`R%lc3p#qZrS5b*_!CRuR`zEow^V35J0AQppvh0QM7 zv}h9qSrY~p%Y}uBh(U0I3b!d-HhkN-`~HIPd&(1@@|<)yeBhjLFaV(6NkG4r_+$Eb zJf_pBZQFt%c%IkkbmH;&H)>hd<#LHcBD2}-bUNkp`A{h2`~K_oB7{_{Rq;R9?RF7D z7~^a;! zSS;3Vw-rVCG=B|;!_{h~s;Vf8ilQ*aQmIs_RI+W`aU9b$!{M+j%Lt*bN~I!6lB%lv z{XUb)P)f_?@_M~)Hk-HGjZ(VVYycpa%c0R|1ONb_>pB4VzOUPx#24YJ`L;z3#PykTIhYRok000SaNLh0L007be007bf2AvEY00007bV*G`2iXe= z2|6_R@ra)Q00Fj1L_t(2&$W_2tKv`)#pm9AiJ&CWN(&8WA=p?1t;9+YECj7AY{Wkx zSjJab*jZ>{VQ&@0!Y^Q_`2d0-f<#0FC5>oMRE*xch0EK=vMgBauR1fsDQ3(cWipxVc1tNm-*-r*QV5|) zByv0+{eHjQZWjcB<2Z~l&+|T?4*-4}DwWD^w|nXP{m!y10C>G#x7*G0JOC`0%VaY7 z0RZduda+oX&u5G=Ap`(emgRXq7K@ptsq6afcEcD4gTY3lfljAWKA$&DGn>uM=kwKS zWf;ce@xT}(gd|DI<#M7Z0>EaovDs`tK0iNIRlQs;k|c2)XBbAcS|x-q40AXfbX^~h z$4;kH6h%Q0tX3;_I2;Va)M~XvA~Bgvn$2dn+l@w}01%JI5klANwcTzH27_P0O{Y@; z2#3QKiv<8M#sClq1OOlu3f1d%kH)x^~e;2YW@ArGtG!;cD z7K`b0dN>@yY&QGK>kq^8`TXioN;OT>G!6at=X=}m6Njz-NlRn8LI3~&07*qoM6N<$ Eg3?>(K>z>% literal 0 HcmV?d00001 diff --git a/logo/mosquitto.svg b/logo/mosquitto.svg new file mode 100644 index 0000000000..7839c1a6a5 --- /dev/null +++ b/logo/mosquitto.svg @@ -0,0 +1,182 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + diff --git a/man/CMakeLists.txt b/man/CMakeLists.txt new file mode 100644 index 0000000000..b2a28dbe6b --- /dev/null +++ b/man/CMakeLists.txt @@ -0,0 +1,5 @@ +install(FILES mosquitto_passwd.1 mosquitto_pub.1 mosquitto_sub.1 DESTINATION $(MANDIR)/man1) +install(FILES libmosquitto.3 DESTINATION $(MANDIR)/man3) +install(FILES mosquitto.conf.5 DESTINATION $(MANDIR)/man5) +install(FILES mosquitto-tls.7 mqtt.7 DESTINATION $(MANDIR)/man7) +install(FILES mosquitto.8 DESTINATION $(MANDIR)/man8) diff --git a/man/Makefile b/man/Makefile new file mode 100644 index 0000000000..ff9f62da88 --- /dev/null +++ b/man/Makefile @@ -0,0 +1,74 @@ +include ../config.mk + +.PHONY : all clean install uninstall dist + +all : mosquitto.8 mosquitto-tls.7 mosquitto.conf.5 mosquitto_passwd.1 mosquitto_pub.1 mosquitto_sub.1 mqtt.7 libmosquitto.3 + +clean : + +reallyclean : clean + -rm -f *.orig + +dist : mosquitto.8 mosquitto-tls.7 mosquitto.conf.5 mosquitto_passwd.1 mosquitto_pub.1 mosquitto_sub.1 mqtt.7 libmosquitto.3 + +install : + $(INSTALL) -d ${DESTDIR}$(mandir)/man8 + $(INSTALL) -m 644 mosquitto.8 ${DESTDIR}${mandir}/man8/mosquitto.8 + $(INSTALL) -d ${DESTDIR}$(mandir)/man5 + $(INSTALL) -m 644 mosquitto.conf.5 ${DESTDIR}${mandir}/man5/mosquitto.conf.5 + $(INSTALL) -d ${DESTDIR}$(mandir)/man1 + $(INSTALL) -m 644 mosquitto_passwd.1 ${DESTDIR}${mandir}/man1/mosquitto_passwd.1 + $(INSTALL) -m 644 mosquitto_pub.1 ${DESTDIR}${mandir}/man1/mosquitto_pub.1 + $(INSTALL) -m 644 mosquitto_sub.1 ${DESTDIR}${mandir}/man1/mosquitto_sub.1 + $(INSTALL) -d ${DESTDIR}$(mandir)/man7 + $(INSTALL) -m 644 mqtt.7 ${DESTDIR}${mandir}/man7/mqtt.7 + $(INSTALL) -m 644 mosquitto-tls.7 ${DESTDIR}${mandir}/man7/mosquitto-tls.7 + $(INSTALL) -d ${DESTDIR}$(mandir)/man3 + $(INSTALL) -m 644 libmosquitto.3 ${DESTDIR}${mandir}/man3/libmosquitto.3 + +uninstall : + -rm -f ${DESTDIR}${mandir}/man8/mosquitto.8 + -rm -f ${DESTDIR}${mandir}/man5/mosquitto.conf.5 + -rm -f ${DESTDIR}${mandir}/man1/mosquitto_passwd.1 + -rm -f ${DESTDIR}${mandir}/man1/mosquitto_pub.1 + -rm -f ${DESTDIR}${mandir}/man1/mosquitto_sub.1 + -rm -f ${DESTDIR}${mandir}/man7/mqtt.7 + -rm -f ${DESTDIR}${mandir}/man7/mosquitto-tls.7 + -rm -f ${DESTDIR}${mandir}/man3/libmosquitto.3 + +mosquitto.8 : mosquitto.8.xml + $(XSLTPROC) $^ + +mosquitto.conf.5 : mosquitto.conf.5.xml manpage.xsl + $(XSLTPROC) $< + +mosquitto_passwd.1 : mosquitto_passwd.1.xml + $(XSLTPROC) $^ + +mosquitto_pub.1 : mosquitto_pub.1.xml + $(XSLTPROC) $^ + +mosquitto_sub.1 : mosquitto_sub.1.xml + $(XSLTPROC) $^ + +mqtt.7 : mqtt.7.xml + $(XSLTPROC) $^ + +mosquitto-tls.7 : mosquitto-tls.7.xml + $(XSLTPROC) $^ + +libmosquitto.3 : libmosquitto.3.xml + $(XSLTPROC) $^ + +potgen : + xml2po -o po/mosquitto/mosquitto.8.pot mosquitto.8.xml + xml2po -o po/mosquitto.conf/mosquitto.conf.5.pot mosquitto.conf.5.xml + xml2po -o po/mosquitto_passwd/mosquitto_passwd.1.pot mosquitto_passwd.1.xml + xml2po -o po/mosquitto_pub/mosquitto_pub.1.pot mosquitto_pub.1.xml + xml2po -o po/mosquitto_sub/mosquitto_sub.1.pot mosquitto_sub.1.xml + xml2po -o po/mqtt/mqtt.7.pot mqtt.7.xml + xml2po -o po/mosquitto-tls/mosquitto-tls.7.pot mosquitto-tls.7.xml + xml2po -o po/libmosquitto/libmosquitto.3.pot libmosquitto.3.xml + +# To merge new translations do: +# /usr/bin/xml2po -p de.po chapter1.xml > chapter1.de.xml diff --git a/man/html.xsl b/man/html.xsl new file mode 100644 index 0000000000..366bf5d09a --- /dev/null +++ b/man/html.xsl @@ -0,0 +1,7 @@ + + + + man.css + + ansi + diff --git a/man/libmosquitto.3.xml b/man/libmosquitto.3.xml new file mode 100644 index 0000000000..959d49d004 --- /dev/null +++ b/man/libmosquitto.3.xml @@ -0,0 +1,487 @@ + + + + + + libmosquitto + 3 + Mosquitto Project + Library calls + + + + libmosquitto + MQTT version 3.1 client library + + + + Description + This is an overview of how to use libmosquitto to create MQTT + aware client programs. There may be separate man pages on each of the + functions described here in the future. There is also a binding for + libmosquitto for C++ and a Python implementation. They are not + documented here but operate in a similar way. + This is fairly incomplete, please see mosquitto.h for a better + description of the functions. + + + + libmosquitto symbol names + All public functions in libmosquitto have the prefix + "mosquitto_". Any other functions defined in the source code are to be + treated as private functions and may change between any release. Do not + use these functions! + + + + Functions + + + Library version + + int mosquitto_lib_version + int *major + int *minor + int *revision + + Obtain version information about the library. If any of + major, minor or revision are not NULL they will return the + corresponding version numbers. The return value is an integer + representation of the complete version number (e.g. 1009001 for 1.9.1) + that can be used for comparisons. + + + + Library initialisation and cleanup + + int mosquitto_lib_init + + + int mosquitto_lib_cleanup + + Call mosquitto_lib_init() before using any of the other + library functions and mosquitto_lib_cleanup() after finishing + with the library. + + + + Client constructor/destructor + + struct mosquitto *mosquitto_new + const char *id + bool clean_session + void *userdata + + Create a new mosquitto client instance. + + void mosquitto_destroy + struct mosquitto *mosq + + Use to free memory associated with a mosquitto client instance. + + int mosquitto_reinitialise + struct mosquitto *mosq + const char *id + bool clean_session + void *userdata + + + + + Authentication and encryption + + int mosquitto_username_pw_set + struct mosquitto *mosq + const char *username + const char *password + + + int mosquitto_tls_set + struct mosquitto *mosq + const char *cafile + const char *capath + const char *certfile + const char *keyfile + int (*pw_callback)(char *buf, int size, int rwflag, void *userdata) + + + + int mosquitto_tls_opts_set + struct mosquitto *mosq + int cert_reqs + const char *tls_version + const char *ciphers + + + int mosquitto_tls_insecure_set + struct mosquitto *mosq + bool value + + + int mosquitto_tls_psk_set + struct mosquitto *mosq + const char *psk + const char *identity + const char *ciphers + + + + + Wills + + int mosquitto_will_set + struct mosquitto *mosq + const char *topic + int payloadlen + const void *payload + int qos + bool retain + + + int mosquitto_will_clear + struct mosquitto *mosq + + + + + Connect/disconnect + + int mosquitto_connect + struct mosquitto *mosq + const char *host + int port + int keepalive + + + int mosquitto_connect_bind + struct mosquitto *mosq + const char *host + int port + int keepalive + const char *bind_address + + + int mosquitto_connect_async + struct mosquitto *mosq + const char *host + int port + int keepalive + + + int mosquitto_connect_bind_async + struct mosquitto *mosq + const char *host + int port + int keepalive + const char *bind_address + + + int mosquitto_reconnect + struct mosquitto *mosq + + + int mosquitto_reconnect_async + struct mosquitto *mosq + + + int mosquitto_disconnect + struct mosquitto *mosq + + + + + Publish + + int mosquitto_publish + struct mosquitto *mosq + int *mid + const char *topic + int payloadlen + const void *payload + int qos + bool retain + + + + + Subscribe/unsubscribe + + int mosquitto_subscribe + struct mosquitto *mosq + int *mid + const char *sub + int qos + + + int mosquitto_unsubscribe + struct mosquitto *mosq + int *mid + const char *sub + + + + + Network loop + + int mosquitto_loop + struct mosquitto *mosq + int timeout + int max_packets + + + int mosquitto_loop_read + struct mosquitto *mosq + int max_packets + + + int mosquitto_loop_write + struct mosquitto *mosq + int max_packets + + + int mosquitto_loop_misc + struct mosquitto *mosq + + + int mosquitto_loop_forever + struct mosquitto *mosq + int timeout + int max_packets + + + int mosquitto_socket + struct mosquitto *mosq + + + bool mosquitto_want_write + struct mosquitto *mosq + + + + + Threaded network loop + + int mosquitto_loop_start + struct mosquitto *mosq + + + int mosquitto_loop_stop + struct mosquitto *mosq + bool force + + + + + Misc client functions + + int mosquitto_max_inflight_messages_set + struct mosquitto *mosq + unsigned int max_inflight_messages + + + int mosquitto_message_retry_set + struct mosquitto *mosq + unsigned int message_retry + + + int mosquitto_reconnect_delay_set + struct mosquitto *mosq + unsigned int reconnect_delay + unsigned int reconnect_delay_max + bool reconnect_exponential_backoff + + + int mosquitto_user_data_set + struct mosquitto *mosq + void *userdata + + + + + Callbacks + + int mosquitto_connect_callback_set + struct mosquitto *mosq + void (*on_connect)(struct mosquitto *, void *, int) + + + int mosquitto_disconnect_callback_set + struct mosquitto *mosq + void (*on_disconnect)(struct mosquitto *, void *, int) + + + int mosquitto_publish_callback_set + struct mosquitto *mosq + void (*on_publish)(struct mosquitto *, void *, int) + + + int mosquitto_message_callback_set + struct mosquitto *mosq + void (*on_message)(struct mosquitto *, void *, const struct mosquitto_message *) + + + int mosquitto_subscribe_callback_set + struct mosquitto *mosq + void (*on_subscribe)(struct mosquitto *, void *, int, int, const int *) + + + int mosquitto_unsubscribe_callback_set + struct mosquitto *mosq + void (*on_unsubscribe)(struct mosquitto *, void *, int) + + + int mosquitto_log_callback_set + struct mosquitto *mosq + void (*on_unsubscribe)(struct mosquitto *, void *, int, const char *) + + + + + Utility functions + + const char *mosquitto_connack_string + int connack_code + + + int mosquitto_message_copy + struct mosquitto_message *dst + const struct mosquitto_message *src + + + int mosquitto_message_free + struct mosquitto_message **message + + + const char *mosquitto_strerror + int mosq_errno + + + int mosquitto_sub_topic_tokenise + const char *subtopic + char ***topics + int *count + + + int mosquitto_sub_topic_tokens_free + char ***topics + int count + + + int mosquitto_topic_matches_sub + const char *sub + const char *topic + bool *result + + + + + + Examples + +#include <stdio.h> +#include <mosquitto.h> + +void my_message_callback(struct mosquitto *mosq, void *userdata, const struct mosquitto_message *message) +{ + if(message->payloadlen){ + printf("%s %s\n", message->topic, message->payload); + }else{ + printf("%s (null)\n", message->topic); + } + fflush(stdout); +} + +void my_connect_callback(struct mosquitto *mosq, void *userdata, int result) +{ + int i; + if(!result){ + /* Subscribe to broker information topics on successful connect. */ + mosquitto_subscribe(mosq, NULL, "$SYS/#", 2); + }else{ + fprintf(stderr, "Connect failed\n"); + } +} + +void my_subscribe_callback(struct mosquitto *mosq, void *userdata, int mid, int qos_count, const int *granted_qos) +{ + int i; + + printf("Subscribed (mid: %d): %d", mid, granted_qos[0]); + for(i=1; i<qos_count; i++){ + printf(", %d", granted_qos[i]); + } + printf("\n"); +} + +void my_log_callback(struct mosquitto *mosq, void *userdata, int level, const char *str) +{ + /* Pring all log messages regardless of level. */ + printf("%s\n", str); +} + +int main(int argc, char *argv[]) +{ + char id[30]; + int i; + char *host = "localhost"; + int port = 1883; + int keepalive = 60; + bool clean_session = true; + struct mosquitto *mosq = NULL; + + mosquitto_lib_init(); + mosq = mosquitto_new(id, clean_session, NULL); + if(!mosq){ + fprintf(stderr, "Error: Out of memory.\n"); + return 1; + } + mosquitto_log_callback_set(mosq, my_log_callback); + + mosquitto_connect_callback_set(mosq, my_connect_callback); + mosquitto_message_callback_set(mosq, my_message_callback); + mosquitto_subscribe_callback_set(mosq, my_subscribe_callback); + + if(mosquitto_connect(mosq, host, port, keepalive)){ + fprintf(stderr, "Unable to connect.\n"); + return 1; + } + + while(!mosquitto_loop(mosq, -1)){ + } + mosquitto_destroy(mosq); + mosquitto_lib_cleanup(); + return 0; +} + + + + See Also + + + + mosquitto + 8 + + + mqtt + 7 + + + + + + + Acknowledgements + This product includes software developed by the OpenSSL Project + for use in the OpenSSL Toolkit. (http://www.openssl.org/) + This product includes cryptographic software written by Eric + Young (eay@cryptsoft.com) + This product includes software written by Tim Hudson + (tjh@cryptsoft.com) + + + + Author + Roger Light roger@atchoo.org + + diff --git a/man/manpage.xsl b/man/manpage.xsl new file mode 100644 index 0000000000..0ebd8d4a4e --- /dev/null +++ b/man/manpage.xsl @@ -0,0 +1,14 @@ + + + + + + 0 + 0 + + http://mosquitto.org/man/ + + + + ansi + diff --git a/man/mosquitto-tls.7.xml b/man/mosquitto-tls.7.xml new file mode 100644 index 0000000000..1fa40f7f9f --- /dev/null +++ b/man/mosquitto-tls.7.xml @@ -0,0 +1,100 @@ + + + + + + mosquitto-tls + 7 + Mosquitto Project + Conventions and miscellaneous + + + + mosquitto-tls + Configure SSL/TLS support for Mosquitto + + + + Description + mosquitto provides SSL support for encrypted + network connections and authentication. This manual describes how + to create the files needed. + It is important to use different certificate subject + parameters for your CA, server and clients. If the certificates + appear identical, even though generated separately, the + broker/client will not be able to distinguish between them and + you will experience difficult to diagnose errors. + + + + Certificate Authority + Generate a certificate authority certificate and key. + + openssl req -new -x509 -days <duration> -extensions v3_ca -keyout ca.key -out ca.crt + + + + + Server + Generate a server key. + + openssl genrsa -des3 -out server.key 2048 + + + Generate a server key without encryption. + + openssl genrsa -out server.key 2048 + + + Generate a certificate signing request to send to the CA. + + openssl req -out server.csr -key server.key -new + + + Send the CSR to the CA, or sign it with your CA key: + + openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days <duration> + + + + + Client + Generate a client key. + + openssl genrsa -des3 -out client.key 2048 + + + Generate a certificate signing request to send to the CA. + + openssl req -out client.csr -key client.key -new + + + Send the CSR to the CA, or sign it with your CA key: + + openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days <duration> + + + + + See Also + + + + mosquitto + 8 + + + + + mosquitto-conf + 5 + + + + + + + Author + Roger Light roger@atchoo.org + + diff --git a/man/mosquitto.8.xml b/man/mosquitto.8.xml new file mode 100644 index 0000000000..5773e97c7d --- /dev/null +++ b/man/mosquitto.8.xml @@ -0,0 +1,559 @@ + + + + + + mosquitto + 8 + Mosquitto Project + System management commands + + + + mosquitto + an MQTT broker + + + + + mosquitto + -c config file + + -d + --daemon + + -p port number + -v + + + + + Description + mosquitto is a broker for the MQTT protocol version 3.1. + + + + Options + + + + + + Load configuration from a file. If not given, the default values as described in mosquitto.conf5 are used. + + + + + + + Run mosquitto in the background as a daemon. All other behaviour remains the same. + + + + + + + Listen on the port specified instead of the default 1883. This acts in addition to the port setting in the config file. May be specified multiple times to open multiple sockets listening on different ports. This socket will be bound to all network interfaces. + + + + + + + Use verbose logging. This is equivalent to setting + to in + the configuration file. This overrides and logging + options given in the configuration file. + + + + + + + Configuration + The broker can be configured using a configuration file as + described in + mosquitto.conf5 + and this is the main point of information for mosquitto. + The files required for SSL/TLS support are described in + mosquitto-tls7. + + + + + Broker Status + Clients can find information about the broker by subscribing to + topics in the $SYS hierarchy as follows. Topics marked as static are + only sent once per client on subscription. All other topics are updated + every seconds. If + is 0, then updates are not sent. + + + + + The total number of bytes received since the broker + started. + + + + + + The total number of bytes sent since the broker + started. + + + + + + The repository changeset (revision) associated with + this build. Static. + + + + + + The number of currently connected clients + + + + + + The number of disconnected persistent clients that + have been expired and removed through the + persistent_client_expiration option. + + + + + + The total number of persistent clients (with clean + session disabled) that are registered at the broker but are + currently disconnected. + + + + + + The maximum number of active clients that have been + connected to the broker. This is only calculated when the + $SYS topic tree is updated, so short lived client + connections may not be counted. + + + + + + The total number of active and inactive clients + currently connected and registered on the + broker. + + + + + + When bridges are configured to/from the broker, + common practice is to provide a status topic that + indicates the state of the connection. This is provided + within $SYS/broker/connection/ by default. If the value + of the topic is 1 the connection is active, if 0 then + it is not active. See the Bridges section below for + more information on bridges. + + + + + + The current size of the heap memory in use by + mosquitto. Note that this topic may be unavailable + depending on compile time options. + + + + + + The largest amount of heap memory used by + mosquitto. Note that this topic may be unavailable + depending on compile time options. + + + + + + The moving average of the number of CONNECT packets + received by the broker over different time intervals. + The final "+" of the hierarchy can be 1min, 5min or + 15min. The value returned represents the number of + connections received in 1 minute, averaged over 1, 5 or + 15 minutes. + + + + + + The moving average of the number of bytes received by + the broker over different time intervals. The final "+" + of the hierarchy can be 1min, 5min or 15min. The value + returned represents the number of bytes received in 1 + minute, averaged over 1, 5 or 15 minutes. + + + + + + The moving average of the number of bytes sent by the + broker over different time intervals. The final "+" of + the hierarchy can be 1min, 5min or 15min. The value + returned represents the number of bytes sent in 1 + minute, averaged over 1, 5 or 15 minutes. + + + + + + The moving average of the number of all types of MQTT + messages received by the broker over different time + intervals. The final "+" of the hierarchy can be 1min, + 5min or 15min. The value returned represents the number + of messages received in 1 minute, averaged over 1, 5 or + 15 minutes. + + + + + + The moving average of the number of all types of MQTT + messages sent by the broker over different time + intervals. The final "+" of the hierarchy can be 1min, + 5min or 15min. The value returned represents the number + of messages send in 1 minute, averaged over 1, 5 or 15 + minutes. + + + + + + The moving average of the number of publish messages + dropped by the broker over different time intervals. + This shows the rate at which durable clients that are + disconnected are losing messages. The final "+" of the + hierarchy can be 1min, 5min or 15min. The value + returned represents the number of messages dropped in 1 + minute, averaged over 1, 5 or 15 minutes. + + + + + + The moving average of the number of publish messages + received by the broker over different time intervals. + The final "+" of the hierarchy can be 1min, 5min or + 15min. The value returned represents the number of + publish messages received in 1 minute, averaged over 1, + 5 or 15 minutes. + + + + + + The moving average of the number of publish messages + sent by the broker over different time intervals. The + final "+" of the hierarchy can be 1min, 5min or 15min. + The value returned represents the number of publish + messages sent in 1 minute, averaged over 1, 5 or 15 + minutes. + + + + + + The moving average of the number of socket + connections opened to the broker over different time + intervals. The final "+" of the hierarchy can be 1min, + 5min or 15min. The value returned represents the number + of socket connections in 1 minute, averaged over 1, 5 + or 15 minutes. + + + + + + The number of messages with QoS>0 that are awaiting + acknowledgments. + + + + + + The total number of messages of any type received since the broker started. + + + + + + The total number of messages of any type sent since the broker started. + + + + + + The number of messages currently held in the message + store. This includes retained messages and messages + queued for durable clients. + + + + + + The total number of publish messages that have been + dropped due to inflight/queuing limits. See the + max_inflight_messages and max_queued_messages options + in + mosquitto.conf5 + for more information. + + + + + + The total number of PUBLISH messages received since the broker started. + + + + + + The total number of PUBLISH messages sent since the broker started. + + + + + + The total number of retained messages active on the broker. + + + + + + The total number of subscriptions active on the broker. + + + + + + The timestamp at which this particular build of the broker was made. Static. + + + + + + The amount of time in seconds the broker has been online. + + + + + + The version of the broker. Static. + + + + + + + Wildcard Topic Subscriptions + In addition to allowing clients to subscribe to specific topics, + mosquitto also allows the use of two wildcards in subscriptions. + is the wildcard used to match a single level of + hierarchy. For example, for a topic of "a/b/c/d", the following example + subscriptions will match: + + a/b/c/d + +/b/c/d + a/+/c/d + a/+/+/d + +/+/+/+ + + The following subscriptions will not match: + + a/b/c + b/+/c/d + +/+/+ + + The second wildcard is and is used to match + all subsequent levels of hierarchy. With a topic of "a/b/c/d", the + following example subscriptions will match: + + a/b/c/d + # + a/# + a/b/# + a/b/c/# + +/b/c/# + + The $SYS hierarchy does not match a subscription of + "#". If you want to observe the entire $SYS hierarchy, subscribe to + $SYS/#. + Note that the wildcards must be only ever used on their own, so a + subscription of "a/b+/c" is not valid use of a wildcard. The + wildcard must only ever be used as the final + character of a subscription. + + + + Bridges + Multiple brokers can be connected together with the bridging + functionality. This is useful where it is desirable to share + information between locations, but where not all of the information + needs to be shared. An example could be where a number of users are + running a broker to help record power usage and for a number of other + reasons. The power usage could be shared through bridging all of the + user brokers to a common broker, allowing the power usage of all + users to be collected and compared. The other information would remain + local to each + broker. + For information on configuring bridges, see + mosquitto.conf5. + + + + Signals + + + SIGHUP + + Upon receiving the SIGHUP signal, mosquitto will + attempt to reload configuration file data, assuming that + the argument was provided when + mosquitto was started. Not all configuration parameters can + be reloaded without restarting. See + mosquitto.conf5 + for details. + + + + SIGUSR1 + + Upon receiving the SIGUSR1 signal, mosquitto will + write the persistence database to disk. This signal is only + acted upon if persistence is enabled. + + + + SIGUSR2 + + The SIGUSR2 signal causes mosquitto to print out the + current subscription tree, along with information about + where retained messages exist. This is intended as a + testing feature only and may be removed at any time. + + + + + + + Files + + + /etc/mosquitto/mosquitto.conf + + Configuration file. See mosquitto.conf5. + + + + /var/lib/mosquitto/mosquitto.db + + Persistent message data storage location if persist enabled. + + + + /etc/hosts.allow + /etc/hosts.deny + + Host access control via tcp-wrappers as described in hosts_access5. + + + + + + + Bugs + mosquitto bug information can be found at http://launchpad.net/mosquitto + + + + See Also + + + + mqtt + 7 + + + + + mosquitto-tls + 7 + + + + + mosquitto.conf + 5 + + + + + hosts_access + 5 + + + + + mosquitto_passwd + 1 + + + + + mosquitto_pub + 1 + + + + + mosquitto_sub + 1 + + + + + libmosquitto + 3 + + + + + + + Thanks + Thanks to Andy Stanford-Clark for being one of the people who + came up with MQTT in the first place. Thanks to Andy and Nicholas + O'Leary for providing clarifications of the protocol. + Thanks also to everybody at the Ubuntu UK Podcast and Linux + Outlaws for organising OggCamp, where Andy gave a talk that + inspired mosquitto. + + + + Acknowledgements + This product includes software developed by the OpenSSL Project + for use in the OpenSSL Toolkit. (http://www.openssl.org/) + This product includes cryptographic software written by Eric + Young (eay@cryptsoft.com) + This product includes software written by Tim Hudson + (tjh@cryptsoft.com) + + + + Author + Roger Light roger@atchoo.org + + diff --git a/man/mosquitto.conf.5.xml b/man/mosquitto.conf.5.xml new file mode 100644 index 0000000000..4d6028eaf2 --- /dev/null +++ b/man/mosquitto.conf.5.xml @@ -0,0 +1,1283 @@ + + + + + + mosquitto.conf + 5 + Mosquitto Project + File formats and conventions + + + + mosquitto.conf + the configuration file for mosquitto + + + + + mosquitto.conf + + + + + Description + mosquitto.conf is the configuration file for + mosquitto. This file can reside anywhere as long as mosquitto can read + it. By default, mosquitto does not need a configuration file and will + use the default values listed below. See + mosquitto8 + for information on how to load a configuration file. + + + + File Format + All lines with a # as the very first character are treated as a + comment. + Configuration lines start with a variable name. The variable + value is separated from the name by a single space. + + + + Authentication + The authentication options described below allow a wide range of + possibilities in conjunction with the listener options. This + section aims to clarify the possibilities. + The simplest option is to have no authentication at all. This is + the default if no other options are given. Unauthenticated + encrypted support is provided by using the certificate based + SSL/TLS based options cafile/capath, certfile and keyfile. + MQTT provides username/password authentication as part of the + protocol. Use the password_file option to define the valid + usernames and passwords. Be sure to use network encryption if you + are using this option otherwise the username and password will be + vulnerable to interception. + When using certificate based encryption there are two options + that affect authentication. The first is require_certificate, which + may be set to true or false. If false, the SSL/TLS component of the + client will verify the server but there is no requirement for the + client to provide anything for the server: authentication is + limited to the MQTT built in username/password. If + require_certificate is true, the client must provide a valid + certificate in order to connect successfully. In this case, the + second option, use_identity_as_username, becomes relevant. If set + to true, the Common Name (CN) from the client certificate is used + instead of the MQTT username for access control purposes. The + password is not replaced because it is assumed that only + authenticated clients have valid certificates. If + use_identity_as_username is false, the client must authenticate as + normal (if required by password_file) through the MQTT + options. + When using pre-shared-key based encryption through the psk_hint + and psk_file options, the client must provide a valid identity and + key in order to connect to the broker before any MQTT communication + takes place. If use_identity_as_username is true, the PSK identity + is used instead of the MQTT username for access control purposes. + If use_identity_as_username is false, the client may still + authenticate using the MQTT username/password if using the + password_file option. + Both certificate and PSK based encryption are configured on a per-listener basis. + Authentication plugins can be created to replace the + password_file and psk_file options (as well as the ACL options) + with e.g. SQL based lookups. + It is possible to support multiple authentication schemes at + once. A config could be created that had a listener for all of the + different encryption options described above and hence a large + number of ways of authenticating. + + + + General Options + + + file path + + Set the path to an access control list file. If + defined, the contents of the file are used to control + client access to topics on the broker. + If this parameter is defined then only the topics + listed will have access. Topic access is added with + lines of the format: + + topic [read|write] <topic> + + The access type is controlled using "read" or + "write". This parameter is optional - if not given then + the access is read/write. <topic> can contain + the + or # wildcards as in subscriptions. + + The first set of topics are applied to anonymous + clients, assuming is + true. User specific topic ACLs are added after a user + line as follows: + + user <username> + + The username referred to here is the same as in + e. It is not the + clientid. + + It is also possible to define ACLs based on pattern + substitution within the topic. The form is the same as + for the topic keyword, but using pattern as the + keyword. + pattern [read|write] <topic> + + The patterns available for substition are: + + %c to match the client id of the client + %u to match the username of the client + + The substitution pattern must be the only text for + that level of hierarchy. Pattern ACLs apply to all + users even if the "user" keyword has previously been + given. + + Example: + pattern write sensor/%u/data + Allow access for bridge connection messages: + pattern write $SYS/broker/connection/%c/state + + If the first character of a line of the ACL file is a + # it is treated as a comment. + + Reloaded on reload signal. The currently loaded ACLs + will be freed and reloaded. Existing subscriptions will + be affected after the reload. + + + + [ true | false ] + + Boolean value that determines whether clients that + connect without providing a username are allowed to + connect. If set to false + then another means of connection should be created to + control authenticated client access. Defaults to + true. + Reloaded on reload signal. + + + + [ true | false ] + + If a client is subscribed to multiple subscriptions + that overlap, e.g. foo/# and foo/+/baz , then MQTT + expects that when the broker receives a message on a + topic that matches both subscriptions, such as + foo/bar/baz, then the client should only receive the + message once. + Mosquitto keeps track of which clients a message has + been sent to in order to meet this requirement. This + option allows this behaviour to be disabled, which may + be useful if you have a large number of clients + subscribed to the same set of topics and want to + minimise memory usage. + It can be safely set to + true if you know in advance + that your clients will never have overlapping + subscriptions, otherwise your clients must be able to + correctly deal with duplicate messages even when then + have QoS=2. + Defaults to true. + Reloaded on reload signal. + + + + value + + Options to be passed to the auth plugin. See the + specific plugin instructions. + + + + file path + + Specify an external module to use for authentication + and access control. This allows custom + username/password and access control functions to be + created. + Not currently reloaded on reload signal. + + + + seconds + + The number of seconds that mosquitto will wait + between each time it saves the in-memory database to + disk. If set to 0, the in-memory database will only be + saved when mosquitto exits or when receiving the + SIGUSR1 signal. Note that this setting only has an + effect if persistence is enabled. Defaults to 1800 + seconds (30 minutes). + Reloaded on reload signal. + + + + [ true | false ] + + If true, mosquitto will + count the number of subscription changes, retained + messages received and queued messages and if the total + exceeds then the + in-memory database will be saved to disk. If + false, mosquitto will save + the in-memory database to disk by treating + as a time in + seconds. + Reloaded on reload signal. + + + + prefix + + If defined, only clients that have a clientid with a + prefix that matches clientid_prefixes will be allowed + to connect to the broker. For example, setting + "secure-" here would mean a client "secure-client" + could connect but another with clientid "mqtt" + couldn't. By default, all client ids are valid. + Reloaded on reload signal. Note that currently + connected clients will be unaffected by any + changes. + + + + [ true | false ] + + If set to true, the log + will include entries when clients connect and + disconnect. If set to false, + these entries will not appear. + Reloaded on reload signal. + + + + dir + + External configuration files may be included by using + the include_dir option. This defines a directory that + will be searched for config files. All files that end + in '.conf' will be loaded as a configuration file. It + is best to have this as the last option in the main + file. This option will only be processed from the main + configuration file. The directory specified must not + contain the main configuration file. + + + + destinations + + Send log messages to a particular destination. + Possible destinations are: + + . + and + log to the console on the + named output. + uses the userspace syslog + facility which usually ends up in /var/log/messages or + similar and topic logs to the broker topic + '$SYS/broker/log/<severity>', where severity is + one of D, E, W, N, I, M which are debug, error, + warning, notice, information and message. Message type + severity is used by the subscribe and unsubscribe + log_type options and publishes log messages at + $SYS/broker/log/M/subscribe and + $SYS/broker/log/M/unsubscribe. + The destination requires an + additional parameter which is the file to be logged to, + e.g. "log_dest file /var/log/mosquitto.log". The file + will be closed and reopened when the broker receives a + HUP signal. Only a single file destination may be + configured. + Use "log_dest none" if you wish to disable logging. + Defaults to stderr. This option may be specified + multiple times. + Note that if the broker is running as a Windows + service it will default to "log_dest none" and neither + stdout nor stderr logging is available. + Reloaded on reload signal. + + + + [ true | false ] + + Boolean value, if set to + true a timestamp value will + be added to each log entry. The default is + true. + Reloaded on reload signal. + + + + types + + Choose types of messages to log. Possible types are: + debug, + error, + warning, + notice, + information, + none, + all. Defaults to + error, + warning, notice + and + information. This option + may be specified multiple times. Note that the + debug type (used for + decoding incoming/outgoing network packets) is never + logged in topics. + Reloaded on reload signal. + + + + count + + The maximum number of QoS 1 or 2 messages that can be + in the process of being transmitted simultaneously. + This includes messages currently going through + handshakes and messages that are being retried. + Defaults to 20. Set to 0 for no maximum. If set to 1, + this will guarantee in-order delivery of + messages. + Reloaded on reload signal. + + + + count + + The maximum number of QoS 1 or 2 messages to hold in + the queue above those messages that are currently in + flight. Defaults to 100. Set to 0 for no maximum (not + recommended). See also the + option. + Reloaded on reload signal. + + + + limit + + This option sets the maximum publish payload size + that the broker will allow. Received messages that + exceed this size will not be accepted by the broker. + The default value is 0, which means that all valid MQTT + messages are accepted. MQTT imposes a maximum payload + size of 268435455 bytes. + + + + file path + + Set the path to a password file. If defined, the + contents of the file are used to control client access + to the broker. The file can be created using the + mosquitto_passwd1 + utility. If mosquitto is compiled without TLS support + (it is recommended that TLS support is included), then + the password file should be a text file with each line + in the format "username:password", where the colon and + password are optional but recommended. If + is set to + false, only users defined in + this file will be able to connect. Setting + to + true when + password_fileis defined is + valid and could be used with acl_file to have e.g. read + only guest/anonymous accounts and defined users that + can publish. + Reloaded on reload signal. The currently loaded + username and password data will be freed and reloaded. + Clients that are already connected will not be + affected. + See also + mosquitto_passwd1. + + + [ true | false ] + + If true, connection, + subscription and message data will be written to the + disk in mosquitto.db at the location dictated by + persistence_location. When mosquitto is restarted, it + will reload the information stored in mosquitto.db. The + data will be written to disk when mosquitto closes and + also at periodic intervals as defined by + autosave_interval. Writing of the persistence database + may also be forced by sending mosquitto the SIGUSR1 + signal. If false, the data + will be stored in memory only. Defaults to + false. + Reloaded on reload signal. + + + + file name + + The filename to use for the persistent database. + Defaults to mosquitto.db. + Reloaded on reload signal. + + + + path + + The path where the persistence database should be + stored. Must end in a trailing slash. If not given, + then the current directory is used. + Reloaded on reload signal. + + + + duration + + This option allows persistent clients (those with + clean session set to false) to be removed if they do + not reconnect within a certain time frame. This is a + non-standard option. As far as the MQTT spec is + concerned, persistent clients persist forever. + Badly designed clients may set clean session to false + whilst using a randomly generated client id. This leads + to persistent clients that will never reconnect. This + option allows these clients to be removed. + The expiration period should be an integer followed + by one of d w m y for day, week, month and year + respectively. For example: + + persistent_client_expiration 2m + persistent_client_expiration 14d + persistent_client_expiration 1y + + As this is a non-standard option, the default if not + set is to never expire persistent clients. + Reloaded on reload signal. + + + + file path + + Write a pid file to the file specified. If not given + (the default), no pid file will be written. If the pid + file cannot be written, mosquitto will exit. This + option only has an effect is mosquitto is run in daemon + mode. + If mosquitto is being automatically started by an + init script it will usually be required to write a pid + file. This should then be configured as e.g. + /var/run/mosquitto.pid + Not reloaded on reload signal. + + + + file path + + Set the path to a pre-shared-key file. This option + requires a listener to be have PSK support enabled. If + defined, the contents of the file are used to control + client access to the broker. Each line should be in the + format "identity:key", where the key is a hexadecimal + string with no leading "0x". A client connecting to a + listener that has PSK support enabled must provide a + matching identity and PSK to allow the encrypted + connection to proceed. + Reloaded on reload signal. The currently loaded + identity and key data will be freed and reloaded. + Clients that are already connected will not be + affected. + + + [ true | false ] + + Set to true to queue + messages with QoS 0 when a persistent client is + disconnected. These messages are included in the limit + imposed by max_queued_messages. Defaults to + false. + Note that the MQTT v3.1 spec states that only QoS 1 + and 2 messages should be saved in this situation so + this is a non-standard option. + Reloaded on reload signal. + + + + [ true | false ] + + This is a synonym of the + option. + Reloaded on reload signal. + + + + seconds + + The integer number of seconds after a QoS=1 or QoS=2 + message has been sent that mosquitto will wait before + retrying when no response is received. If unset, + defaults to 20 seconds. + Reloaded on reload signal. + + + + seconds + + The integer number of seconds between the internal + message store being cleaned of messages that are no + longer referenced. Lower values will result in lower + memory usage but more processor time, higher values + will have the opposite effect. Setting a value of 0 + means the unreferenced messages will be disposed of as + quickly as possible. Defaults to 10 seconds. + Reloaded on reload signal. + + + + seconds + + The integer number of seconds between updates of the + $SYS subscription hierarchy, which provides status + information about the broker. If unset, defaults to 10 + seconds. + Set to 0 to disable publishing the $SYS hierarchy + completely. + Reloaded on reload signal. + + + + [ true | false ] + + The MQTT specification requires that the QoS of a + message delivered to a subscriber is never upgraded to + match the QoS of the subscription. Enabling this option + changes this behaviour. If + is set + true, messages sent to a + subscriber will always match the QoS of its + subscription. This is a non-standard option not + provided for by the spec. Defaults to + false. + Reloaded on reload signal. + + + + username + + When run as root, change to this user and its primary + group on startup. If mosquitto is unable to change to + this user and group, it will exit with an error. The + user specified must have read/write access to the + persistence database if it is to be written. If run as + a non-root user, this setting has no effect. Defaults + to mosquitto. + This setting has no effect on Windows and so you + should run mosquitto as the user you wish it to run + as. + Not reloaded on reload signal. + + + + + + + Listeners + The network ports that mosquitto listens on can be controlled + using listeners. The default listener options can be overridden and + further listeners can be created. + + General Options + + + address + + Listen for incoming network connections on the + specified IP address/hostname only. This is useful + to restrict access to certain network interfaces. + To restrict access to mosquitto to the local host + only, use "bind_address localhost". This only + applies to the default listener. Use the listener + variable to control other listeners. + Not reloaded on reload signal. + + + + port + + Listen for incoming network connection on the + specified port. A second optional argument allows + the listener to be bound to a specific ip + address/hostname. If this variable is used and + neither nor + are used then the default + listener will not be started. This option may be + specified multiple times. See also the + option. + Not reloaded on reload signal. + + + + count + + Limit the total number of clients connected for + the current listener. Set to -1 + to have "unlimited" connections. Note that other + limits may be imposed that are outside the control + of mosquitto. See e.g. + limits.conf5. + Not reloaded on reload signal. + + + + topic prefix + + This option is used with the listener option to + isolate groups of clients. When a client connects + to a listener which uses this option, the string + argument is attached to the start of all topics for + this client. This prefix is removed when any + messages are sent to the client. This means a + client connected to a listener with mount point + example can only see + messages that are published in the topic hierarchy + example and above. + Not reloaded on reload signal. + + + + port number + + Set the network port for the default listener to + listen on. Defaults to 1883. + Not reloaded on reload signal. + + + + + + Certificate based SSL/TLS Support + The following options are available for all listeners to + configure certificate based SSL support. See also + "Pre-shared-key based SSL/TLS support". + + + file path + + At least one of or + must be provided to allow + SSL support. + is used to define the + path to a file containing the PEM encoded CA + certificates that are trusted. + + + + directory path + + At least one of or + must be provided to allow + SSL support. + is used to define a + directory that contains PEM encoded CA certificates + that are trusted. For to + work correctly, the certificates files must have + ".pem" as the file ending and you must run + "c_rehash <path to capath>" each time you + add/remove a certificate. + + + + file path + + Path to the PEM encoded server certificate. + + + + cipher:list + + The list of allowed ciphers, each separated with + a colon. Available ciphers can be obtained using + the "openssl ciphers" command. + + + + file path + + If you have + set to true, you can + create a certificate revocation list file to revoke + access to particular client certificates. If you + have done this, use crlfile to point to the PEM + encoded revocation file. + + + + file path + + Path to the PEM encoded keyfile. + + + + [ true | false ] + + By default an SSL/TLS enabled listener will + operate in a similar fashion to a https enabled web + server, in that the server has a certificate signed + by a CA and the client will verify that it is a + trusted certificate. The overall aim is encryption + of the network traffic. By setting + to + true, the client must + provide a valid certificate in order for the + network connection to proceed. This allows access + to the broker to be controlled outside of the + mechanisms provided by MQTT. + + + + version + + Configure the version of the TLS protocol to be + used for this listener. Possible values are + tlsv1.2, + tlsv1.1 and + tlsv1. Defaults to + tlsv1.2. + + + + [ true | false ] + + If is + true, you may set + to + true to use the CN value + from the client certificate as a username. If this + is true, the + option will not be + used for this listener. + + + + + + Pre-shared-key based SSL/TLS Support + The following options are available for all listeners to + configure pre-shared-key based SSL support. See also + "Certificate based SSL/TLS support". + + + cipher:list + + When using PSK, the encryption ciphers used will + be chosen from the list of available PSK ciphers. + If you want to control which ciphers are available, + use this option. The list of available ciphers can + be optained using the "openssl ciphers" command and + should be provided in the same format as the output + of that command. + + + + hint + + The option enables + pre-shared-key support for this listener and also + acts as an identifier for this listener. The hint + is sent to clients and may be used locally to aid + authentication. The hint is a free form string that + doesn't have much meaning in itself, so feel free + to be creative. + If this option is provided, see + to define the pre-shared + keys to be used or create a security plugin to + handle them. + + + + version + + Configure the version of the TLS protocol to be + used for this listener. Possible values are + tlsv1.2, + tlsv1.1 and + tlsv1. Defaults to + tlsv1.2. + + + + [ true | false ] + + Set to + have the psk identity sent by the client used as + its username. The username will be checked as + normal, so or + another means of authentication checking must be + used. No password will be used. + + + + + + + + Configuring Bridges + Multiple bridges (connections to other brokers) can be configured + using the following variables. + Bridges cannot currently be reloaded on reload signal. + + + address[:port] [address[:port]] + address[:port] [address[:port]] + + Specify the address and optionally the port of the + bridge to connect to. This must be given for each + bridge connection. If the port is not specified, the + default of 1883 is used. + Multiple host addresses can be specified on the + address config. See the + option for more details on the behaviour of bridges + with multiple addresses. + + + + [ true | false ] + + Set the clean session option for this bridge. Setting + to false (the default), + means that all subscriptions on the remote broker are + kept in case of the network connection dropping. If set + to true, all subscriptions + and messages on the remote broker will be cleaned up if + the connection drops. Note that setting to + true may cause a large + amount of retained messages to be sent each time the + bridge reconnects. + If you are using bridges with + set to + false (the default), then + you may get unexpected behaviour from incoming topics + if you change what topics you are subscribing to. This + is because the remote broker keeps the subscription for + the old topic. If you have this problem, connect your + bridge with set to + true, then reconnect with + cleansession set to false as + normal. + + + + id + + Set the client id for this bridge connection. If not + defined, this defaults to 'name.hostname', where name + is the connection name and hostname is the hostname of + this computer. + + + + name + + This variable marks the start of a new bridge + connection. It is also used to give the bridge a name + which is used as the client id on the remote + broker. + + + + seconds + + Set the number of seconds after which the bridge + should send a ping if no other traffic has occurred. + Defaults to 60. A minimum value of 5 seconds + isallowed. + + + + seconds + + Set the amount of time a bridge using the lazy start + type must be idle before it will be stopped. Defaults + to 60 seconds. + + + + [ true | false ] + + If set to true, publish + notification messages to the local and remote brokers + giving information about the state of the bridge + connection. Retained messages are published to the + topic $SYS/broker/connection/<clientid>/state + unless otherwise set with + s. If the message + is 1 then the connection is active, or 0 if the + connection has failed. Defaults to + true. + + + + topic + + Choose the topic on which notifications will be + published for this bridge. If not set the messages will + be sent on the topic + $SYS/broker/connection/<clientid>/state. + + + + value + + Configure a password for the bridge. This is used for + authentication purposes when connecting to a broker + that support MQTT v3.1 and requires a username and/or + password to connect. This option is only valid if a + username is also supplied. + + + + value + + Set the amount of time a bridge using the automatic + start type will wait until attempting to reconnect. + Defaults to 30 seconds. + + + + [ true | false ] + + If the bridge has more than one address given in the + address/addresses configuration, the round_robin option + defines the behaviour of the bridge on a failure of the + bridge connection. If round_robin is + false, the default value, + then the first address is treated as the main bridge + connection. If the connection fails, the other + secondary addresses will be attempted in turn. Whilst + connected to a secondary bridge, the bridge will + periodically attempt to reconnect to the main bridge + until successful. + If round_robin is true, + then all addresses are treated as equals. If a + connection fails, the next address will be tried and if + successful will remain connected until it fails. + + + + [ automatic | lazy | once ] + + Set the start type of the bridge. This controls how + the bridge starts and can be one of three types: + automatic, lazy + and once. Note + that RSMB provides a fourth start type "manual" which + isn't currently supported by mosquitto. + + automatic is the default + start type and means that the bridge connection will be + started automatically when the broker starts and also + restarted after a short delay (30 seconds) if the + connection fails. + + Bridges using the lazy + start type will be started automatically when the + number of queued messages exceeds the number set with + the option. It will be + stopped automatically after the time set by the + parameter. Use this start + type if you wish the connection to only be active when + it is needed. + + A bridge using the once + start type will be started automatically when the + broker starts but will not be restarted if the + connection fails. + + + + count + + Set the number of messages that need to be queued for + a bridge with lazy start type to be restarted. + Defaults to 10 messages. + + + + pattern [[[ out | in | both ] qos-level] local-prefix remote-prefix] + + Define a topic pattern to be shared between the two + brokers. Any topics matching the pattern (which may + include wildcards) are shared. The second parameter + defines the direction that the messages will be shared + in, so it is possible to import messages from a remote + broker using in, export + messages to a remote broker using + out or share messages in + both directions. If this parameter is not defined, the + default of out is used. The + QoS level defines the publish/subscribe QoS level used + for this topic and defaults to 0. + The local-prefix and + remote-prefix options allow + topics to be remapped when publishing to and receiving + from remote brokers. This allows a topic tree from the + local broker to be inserted into the topic tree of the + remote broker at an appropriate place. + For incoming topics, the bridge will prepend the + pattern with the remote prefix and subscribe to the + resulting topic on the remote broker. When a matching + incoming message is received, the remote prefix will be + removed from the topic and then the local prefix + added. + For outgoing topics, the bridge will prepend the + pattern with the local prefix and subscribe to the + resulting topic on the local broker. When an outgoing + message is processed, the local prefix will be removed + from the topic then the remote prefix added. + When using topic mapping, an empty prefix can be + defined using the place marker + "". Using the empty marker + for the topic itself is also valid. The table below + defines what combination of empty or value is + valid. + + + + + + Topic + Local Prefix + Remote Prefix + Validity + + + + 1valuevaluevaluevalid + 2valuevalue""valid + 3value""valuevalid + 4value""""valid (no remapping) + 5""valuevaluevalid (remap single local topic to remote) + 6""value""invalid + 7""""valueinvalid + 8""""""invalid + + + + To remap an entire topic tree, use e.g.: + +topic # both 2 local/topic/ remote/topic/ + + This option can be specified multiple times per + bridge. + Care must be taken to ensure that loops are not + created with this option. If you are experiencing high + CPU load from a broker, it is possible that you have a + loop where each broker is forever forwarding each other + the same messages. + See also the option if + you have messages arriving on unexpected topics when + using incoming topics. + + + The configuration below connects a bridge to the + broker at . It + subscribes to the remote topic + and + republishes the messages received to the local topic + + +connection test-mosquitto-org +address test.mosquitto.org +cleansession true +topic clients/total in 0 test/mosquitto/org $SYS/broker/ + + + + + + [ true | false ] + + If try_private is set to + true, the bridge will + attempt to indicate to the remote broker that it is a + bridge not an ordinary client. If successful, this + means that loop detection will be more effective and + that retained messages will be propagated correctly. + Not all brokers support this feature so it may be + necessary to set to + false if your bridge does + not connect properly. + Defaults to true. + + + + name + + Configure a for the bridge. + This is used for authentication purposes when + connecting to a broker that support MQTT v3.1 and + requires a username and/or password to connect. See + also the option. + + + + + SSL/TLS Support + The following options are available for all bridges to + configure SSL/TLS support. + + + file path + + One of or + must be provided to + allow SSL/TLS support. + bridge_cafile is used to define the path to a file + containing the PEM encoded CA certificates that + have signed the certificate for the remote broker. + + + + + file path + + One of or + must be provided to + allow SSL/TLS support. + bridge_capath is used to define the path to a + directory containing the PEM encoded CA + certificates that have signed the certificate for + the remote broker. For bridge_capath to work + correctly, the certificate files must have ".crt" + as the file ending and you must run "c_rehash + <path to bridge_capath>" each time you + add/remove a certificate. + + + + file path + + Path to the PEM encoded client certificate for + this bridge, if required by the remote + broker. + + + + identity + + Pre-shared-key encryption provides an alternative + to certificate based encryption. A bridge can be + configured to use PSK with the + and + options. This is the + client identity used with PSK encryption. Only one + of certificate and PSK based encryption can be used + on one bridge at once. + + + + [ true | false ] + + When using certificate based TLS, the bridge will + attempt to verify the hostname provided in the + remote certificate matches the host/address being + connected to. This may cause problems in testing + scenarios, so may + be set to false to + disable the hostname verification. + Setting this option to + true means that a + malicious third party could potentially inpersonate + your server, so it should always be set to + false in production + environments. + + + + file path + + Path to the PEM encoded private key for this + bridge, if required by the remote broker. + + + + key + + Pre-shared-key encryption provides an alternative + to certificate based encryption. A bridge can be + configured to use PSK with the + and + options. This is the + pre-shared-key in hexadecimal format with no "0x". + Only one of certificate and PSK based encryption + can be used on one bridge at once. + + + + version + + Configure the version of the TLS protocol to be + used for this bridge. Possible values are + tlsv1.2, + tlsv1.1 and + tlsv1. Defaults to + tlsv1.2. The remote + broker must support the same version of TLS for the + connection to succeed. + + + + + + + + Files + mosquitto.conf + + + + Bugs + mosquitto bug information can be found at http://launchpad.net/mosquitto + + + + See Also + + + + mosquitto + 8 + + + + + mosquitto_passwd + 1 + + + + + mosquitto-tls + 7 + + + + + mqtt + 7 + + + + + limits.conf + 5 + + + + + + + Author + Roger Light roger@atchoo.org + + diff --git a/man/mosquitto_passwd.1.xml b/man/mosquitto_passwd.1.xml new file mode 100644 index 0000000000..7a3b4c77c6 --- /dev/null +++ b/man/mosquitto_passwd.1.xml @@ -0,0 +1,145 @@ + + + + + + mosquitto_passwd + 1 + Mosquitto Project + Commands + + + + mosquitto_passwd + manage password files for mosquitto + + + + + mosquitto_passwd + + + + + passwordfile + username + + + mosquitto_passwd + + passwordfile + + + + + Description + mosquitto_passwd is a tool for managing + password files the the mosquitto MQTT broker. + Usernames must not contain ":". Passwords are stored in a similar + format to + crypt3. + + + + Options + + + + + Create a new password file. If the file already + exists, it will be overwritten. + + + + + + Delete the specified user from the password + file. + + + + + + This option can be used to upgrade/convert a password + file with plain text passwords into one using hashed + passwords. It will modify the specified file. It does + not detect whether passwords are already hashed, so + using it on a password file that already contains + hashed passwords will generate new hashes based on the + old hashes and render the password file + unusable. + + + + + + The password file to modify. + + + + + + The username to add/update/delete. + + + + + + + + Examples + Add a user to a new password file: + + mosquitto_passwd -c /etc/mosquitto/passwd ral + + Delete a user from a password file + + mosquitto_passwd -D /etc/mosquitto/passwd ral + + + + + Bugs + mosquitto_passwd bug information can be found at + http://launchpad.net/mosquitto + + + + See Also + + + + mosquitto + 8 + + + + + mosquitto.conf + 5 + + + + + mqtt + 7 + + + + + + + Acknowledgements + This product includes software developed by the OpenSSL Project + for use in the OpenSSL Toolkit. (http://www.openssl.org/) + This product includes cryptographic software written by Eric + Young (eay@cryptsoft.com) + This product includes software written by Tim Hudson + (tjh@cryptsoft.com) + + + + Author + Roger Light roger@atchoo.org + + diff --git a/man/mosquitto_pub.1.xml b/man/mosquitto_pub.1.xml new file mode 100644 index 0000000000..1ab932d1bf --- /dev/null +++ b/man/mosquitto_pub.1.xml @@ -0,0 +1,461 @@ + + + + + + mosquitto_pub + 1 + Mosquitto Project + Commands + + + + mosquitto_pub + an MQTT version 3.1 client for publishing simple messages + + + + + mosquitto_pub + bind_address + + hostname + client_id + client id prefix + port number + message QoS + + + + + file + + message + + + + + username + password + + + topic + payload + qos + + + + + + file + dir + + file + file + ciphers + version + + + + hex-key + identity + ciphers + version + + + message-topic + + + mosquitto_pub + + + + + + + + Description + mosquitto_pub is a simple MQTT version 3.1 client that will publish a single message on a topic and exit. + + + + Options + + + + + Bind the outgoing connection to a local ip + address/hostname. Use this argument if you need to + restrict network communication to a particular + interface. + + + + + + Define the path to a file containing PEM encoded CA + certificates that are trusted. Used to enable SSL + communication. + See also + + + + + + Define the path to a directory containing PEM encoded CA + certificates that are trusted. Used to enable SSL + communication. + For to work correctly, the + certificate files must have ".crt" as the file ending + and you must run "c_rehash <path to capath>" each + time you add/remove a certificate. + See also + + + + + + Define the path to a file containing a PEM encoded + certificate for this client, if required by the + server. + See also . + + + + + + An openssl compatible list of TLS ciphers to support + in the client. See + ciphers1 + for more information. + + + + + + + Enable debug messages. + + + + + + + Send the contents of a file as the message. + + + + + + Display usage information. + + + + + + + Specify the host to connect to. Defaults to localhost. + + + + + + + The id to use for this client. If not given, defaults + to mosquitto_pub_ appended with the process id of the + client. Cannot be used at the same time as the + argument. + + + + + + + Provide a prefix that the client id will be built + from by appending the process id of the client. This is + useful where the broker is using the clientid_prefixes + option. Cannot be used at the same time as the + argument. + + + + + + When using certificate based encryption, this option + disables verification of the server hostname in the + server certificate. This can be useful when testing + initial server configurations but makes it possible for + a malicious third party to impersonate your server + through DNS spoofing, for example. Use this option in + testing only. If you need to + resort to using this option in a production + environment, your setup is at fault and there is no + point using encryption. + + + + + + Define the path to a file containing a PEM encoded + private key for this client, if required by the + server. + See also . + + + + + + + Send messages read from stdin, splitting separate lines into separate messages. Note that blank lines won't be sent. + + + + + + + Send a single message from the command line. + + + + + + + Send a null (zero length) message. + + + + + + + Connect to the port specified instead of the default 1883. + + + + + + + Provide a password to be used for authenticating with + the broker. Using this argument without also specifying a + username is invalid. This requires a broker that supports + MQTT v3.1. See also the option. + + + + + + Provide the hexadecimal (no leading 0x) + pre-shared-key matching the one used on the broker to + use TLS-PSK encryption support. + must also be provided + to enable TLS-PSK. + + + + + + The client identity to use with TLS-PSK support. This + may be used instead of a username if the broker is + configured to do so. + + + + + + + Specify the quality of service to use for the message, from 0, 1 and 2. Defaults to 0. + + + + + + If this argument is given, no runtime errors will be + printed. This excludes any error messages given in case of + invalid user input (e.g. using without a + port). + + + + + + + If retain is given, the message will be retained as a "last known good" value on the broker. See mqtt7 for more information. + + + + + + + Send a message read from stdin, sending the entire content as a single message. + + + + + + Use SRV lookups to determine which host to connect + to. Performs lookups to + when used in + conjunction with , otherwise uses + . + + + + + + + The MQTT topic on which to publish the message. See mqtt7 for more information on MQTT topics. + + + + + + Choose which TLS protocol version to use when + communicating with the broker. Valid options are + , and + . The default value is + . If the installed version of + openssl is too old, only will be + available. Must match the protocol version used by the + broker. + + + + + + + Provide a username to be used for authenticating with + the broker. This requires a broker that supports MQTT v3.1. + See also the argument. + + + + + + Specify a message that will be stored by the broker + and sent out if this client disconnects unexpectedly. This + must be used in conjunction with . + + + + + + The QoS to use for the Will. Defaults to 0. This must + be used in conjunction with . + + + + + + If given, if the client disconnects unexpectedly the + message sent out will be treated as a retained message. + This must be used in conjunction with . + + + + + + The topic on which to send a Will, in the event that + the client disconnects unexpectedly. + + + + + + + Wills + mosquitto_sub can register a message with the broker that will be + sent out if it disconnects unexpectedly. See + mqtt7 + for more information. + The minimum requirement for this is to use to + specify which topic the will should be sent out on. This will result in + a non-retained, zero length message with QoS 0. + Use the , + and + arguments to modify the other will parameters. + + + + Examples + Publish temperature information to localhost with QoS 1: + + mosquitto_pub -t sensors/temperature -m 32 -q 1 + + Publish timestamp and temperature information to a remote host on a non-standard port and QoS 0: + + mosquitto_pub -h 192.168.1.1 + -p 1885 -t + sensors/temperature -m "1266193804 + 32" + + Publish light switch status. Message is set to retained because there may be a long period of time between light switch events: + + mosquitto_pub -r -t switches/kitchen_lights/status -m "on" + + Send the contents of a file in two ways: + + mosquitto_pub -t my/topic -f ./data + mosquitto_pub -t my/topic -s < ./data + + Send parsed electricity usage data from a Current Cost meter, reading from stdin with one line/reading as one message: + + read_cc128.pl | mosquitto_pub -t sensors/cc128 -l + + + + + Bugs + mosquitto_pub bug information can be found at http://launchpad.net/mosquitto + + + + See Also + + + + mqtt + 7 + + + + + mosquitto_sub + 1 + + + + + mosquitto + 8 + + + + + libmosquitto + 3 + + + + + mosquitto-tls + 7 + + + + + + + Acknowledgements + This product includes software developed by the OpenSSL Project + for use in the OpenSSL Toolkit. (http://www.openssl.org/) + This product includes cryptographic software written by Eric + Young (eay@cryptsoft.com) + This product includes software written by Tim Hudson + (tjh@cryptsoft.com) + + + + Author + Roger Light roger@atchoo.org + + diff --git a/man/mosquitto_sub.1.xml b/man/mosquitto_sub.1.xml new file mode 100644 index 0000000000..07bfb6a2d7 --- /dev/null +++ b/man/mosquitto_sub.1.xml @@ -0,0 +1,491 @@ + + + + + + mosquitto_sub + 1 + Mosquitto Project + Commands + + + + mosquitto_sub + an MQTT version 3.1 client for subscribing to topics + + + + + mosquitto_sub + bind_address + + + hostname + client_id + client id prefix + keepalive time + port number + message QoS + + + + + + + username + password + + + topic + payload + qos + + + + + + file + dir + + file + file + version + + + + hex-key + identity + version + + + filter-out + message-topic + + + mosquitto_sub + + + + + + + + Description + mosquitto_sub is a simple MQTT version 3.1 + client that will subscribe to a topic and print the messages that it + receives. + + + + Options + + + + + Bind the outgoing connection to a local ip + address/hostname. Use this argument if you need to + restrict network communication to a particular + interface. + + + + + + + Disable the 'clean session' flag. This means that all + of the subscriptions for the client will be maintained + after it disconnects, along with subsequent QoS 1 and QoS 2 + messages that arrive. When the client reconnects, it will + receive all of the queued messages. + If using this option, it is recommended that the + client id is set manually with + + + + + + Define the path to a file containing PEM encoded CA + certificates that are trusted. Used to enable SSL + communication. + See also + + + + + + Define the path to a directory containing PEM encoded CA + certificates that are trusted. Used to enable SSL + communication. + For to work correctly, the + certificate files must have ".crt" as the file ending + and you must run "c_rehash <path to capath>" each + time you add/remove a certificate. + See also + + + + + + Define the path to a file containing a PEM encoded + certificate for this client, if required by the + server. + See also . + + + + + + An openssl compatible list of TLS ciphers to support + in the client. See + ciphers1 + for more information. + + + + + + + Enable debug messages. + + + + + + Display usage information. + + + + + + + Specify the host to connect to. Defaults to localhost. + + + + + + + The id to use for this client. If not given, defaults + to mosquitto_sub_ appended with the process id of the + client. Cannot be used at the same time as the + argument. + + + + + + + Provide a prefix that the client id will be built + from by appending the process id of the client. This is + useful where the broker is using the clientid_prefixes + option. Cannot be used at the same time as the + argument. + + + + + + When using certificate based encryption, this option + disables verification of the server hostname in the + server certificate. This can be useful when testing + initial server configurations but makes it possible for + a malicious third party to impersonate your server + through DNS spoofing, for example. Use this option in + testing only. If you need to + resort to using this option in a production + environment, your setup is at fault and there is no + point using encryption. + + + + + + + The number of seconds between sending PING commands + to the broker for the purposes of informing it we are still + connected and functioning. Defaults to 60 seconds. + + + + + + Define the path to a file containing a PEM encoded + private key for this client, if required by the + server. + See also . + + + + + + Do not append an end of line character to the payload + when printing. This allows streaming of payload data + from multiple messages directly to another application + unmodified. Only really makes sense when not using + . + + + + + + + Connect to the port specified instead of the default 1883. + + + + + + + Provide a password to be used for authenticating with + the broker. Using this argument without also specifying a + username is invalid. This requires a broker that supports + MQTT v3.1. See also the option. + + + + + + Provide the hexadecimal (no leading 0x) + pre-shared-key matching the one used on the broker to + use TLS-PSK encryption support. + must also be provided + to enable TLS-PSK. + + + + + + The client identity to use with TLS-PSK support. This + may be used instead of a username if the broker is + configured to do so. + + + + + + + Specify the quality of service desired for the + incoming messages, from 0, 1 and 2. Defaults to 0. See + mqtt7 + for more information on QoS. + The QoS is identical for all topics subscribed to in + a single instance of mosquitto_sub. + + + + + + If this argument is given, no runtime errors will be + printed. This excludes any error messages given in case of + invalid user input (e.g. using without a + port). + + + + + + If this argument is given, messages that are received + that have the retain bit set will not be printed. + Messages with retain set are "stale", in that it is not + known when they were originally published. When + subscribing to a wildcard topic there may be a large + number of retained messages. This argument suppresses + their display. + + + + + + Use SRV lookups to determine which host to connect + to. Performs lookups to + when used in + conjunction with , otherwise uses + . + + + + + + + The MQTT topic to subscribe to. See + mqtt7 + for more information on MQTT topics. + This option may be repeated to subscribe to multiple topics. + + + + + + + Suppress printing of topics that match the filter. + This allows subscribing to a wildcard topic and only + printing a partial set of the wildcard + hierarchy. + For example, subscribe to the BBC tree, but suppress output from Radio 3: + + mosquitto_sub -t + bbc/# -T + bbc/radio3 + + This option may be repeated to filter out multiple + topics or topic trees. + + + + + + Choose which TLS protocol version to use when + communicating with the broker. Valid options are + , and + . The default value is + . If the installed version of + openssl is too old, only will be + available. Must match the protocol version used by the + broker. + + + + + + + Provide a username to be used for authenticating with + the broker. This requires a broker that supports MQTT v3.1. + See also the argument. + + + + + + + Print received messages verbosely. With this + argument, messages will be printed as "topic payload". When + this argument is not given, the messages are printed as + "payload". + + + + + + Specify a message that will be stored by the broker + and sent out if this client disconnects unexpectedly. This + must be used in conjunction with . + + + + + + The QoS to use for the Will. Defaults to 0. This must + be used in conjunction with . + + + + + + If given, if the client disconnects unexpectedly the + message sent out will be treated as a retained message. + This must be used in conjunction with . + + + + + + The topic on which to send a Will, in the event that + the client disconnects unexpectedly. + + + + + + + Wills + mosquitto_sub can register a message with the broker that will be + sent out if it disconnects unexpectedly. See + mqtt7 + for more information. + The minimum requirement for this is to use to + specify which topic the will should be sent out on. This will result in + a non-retained, zero length message with QoS 0. + Use the , and arguments to + modify the other will parameters. + + + + Examples + Note that these really are examples - the subscriptions will work + if you run them as shown, but there must be something publishing + messages on those topics for you to receive anything. + Subscribe to temperature information on localhost with QoS 1: + + mosquitto_sub -t sensors/temperature -q 1 + + Subscribe to hard drive temperature updates on multiple + machines/hard drives. This expects each machine to be publishing its + hard drive temperature to + sensors/machines/HOSTNAME/temperature/HD_NAME. + + mosquitto_sub -t sensors/machines/+/temperature/+ + + Subscribe to all broker status messages: + + mosquitto_sub -v -t \$SYS/# + + + + + Bugs + mosquitto_sub bug information can be found at + http://launchpad.net/mosquitto + + + + See Also + + + + mqtt + 7 + + + + + mosquitto_pub + 1 + + + + + mosquitto + 8 + + + + + libmosquitto + 3 + + + + + mosquitto-tls + 7 + + + + + + + Acknowledgements + This product includes software developed by the OpenSSL Project + for use in the OpenSSL Toolkit. (http://www.openssl.org/) + This product includes cryptographic software written by Eric + Young (eay@cryptsoft.com) + This product includes software written by Tim Hudson + (tjh@cryptsoft.com) + + + + Author + Roger Light roger@atchoo.org + + diff --git a/man/mqtt.7.xml b/man/mqtt.7.xml new file mode 100644 index 0000000000..d467fce96d --- /dev/null +++ b/man/mqtt.7.xml @@ -0,0 +1,187 @@ + + + + + + mqtt + 7 + Mosquitto Project + Conventions and miscellaneous + + + + mqtt + MQ Telemetry Transport + + + + + MQTT + + + + + Description + MQTT is a lightweight publish/subscribe + messaging protocol. It is useful for use with low power sensors, but + is applicable to many scenarios. This manual describes + some of the features of MQTT version 3.1, to assist end users in + getting the most out of the protocol. For more complete information on + MQTT, see http://mqtt.org/. + + + + Publish/Subscribe + The MQTT protocol is based on the principle of publishing + messages and subscribing to topics, or "pub/sub". Multiple clients + connect to a broker and subscribe to topics that they are interested + in. Clients also connect to the broker and publish messages to topics. + Many clients may subscribe to the same topics and do with the + information as they please. The broker and MQTT act as a simple, common + interface for everything to connect to. This means that you if you have + clients that dump subscribed messages to a database, to Twitter, + Cosm or even a simple text file, then it becomes very simple to add + new sensors or other data input to a database, Twitter or so on. + + + + Topics/Subscriptions + Messages in MQTT are published on topics. There is no need to + configure a topic, publishing on it is enough. Topics are treated as a + hierarchy, using a slash (/) as a separator. This allows sensible + arrangement of common themes to be created, much in the same way as a + filesystem. For example, multiple computers may all publish their + hard drive temperature information on the following topic, with their + own computer and hard drive name being replaced as appropriate: + + sensors/COMPUTER_NAME/temperature/HARDDRIVE_NAME + + Clients can receive messages by creating subscriptions. A + subscription may be to an explicit topic, in which case only messages + to that topic will be received, or it may include wildcards. Two + wildcards are available, or . + can be used as a wildcard for a single level + of hierarchy. It could be used with the topic above to get information + on all computers and hard drives as follows: + + sensors/+/temperature/+ + + As another example, for a topic of "a/b/c/d", the following + example subscriptions will match: + + a/b/c/d + +/b/c/d + a/+/c/d + a/+/+/d + +/+/+/+ + + The following subscriptions will not match: + + a/b/c + b/+/c/d + +/+/+ + + can be used as a wildcard for all remaining levels of + hierarchy. This means that it must be the final character in a + subscription. With a topic of "a/b/c/d", the following example + subscriptions will match: + + a/b/c/d + # + a/# + a/b/# + a/b/c/# + +/b/c/# + + Zero length topic levels are valid, which can lead to some + slightly non-obvious behaviour. For example, a topic of "a//topic" + would correctly match against a subscription of "a/+/topic". + Likewise, zero length topic levels can exist at both the beginning + and the end of a topic string, so "/a/topic" would match against a + subscription of "+/a/topic", "#" or "/#", and a topic "a/topic/" + would match against a subscription of "a/topic/+" or + "a/topic/#". + + + + Quality of Service + MQTT defines three levels of Quality of Service (QoS). The QoS + defines how hard the broker/client will try to ensure that a message is + received. Messages may be sent at any QoS level, and clients may + attempt to subscribe to topics at any QoS level. This means that the + client chooses the maximum QoS it will receive. For example, if a + message is published at QoS 2 and a client is subscribed with QoS 0, + the message will be delivered to that client with QoS 0. If a second + client is also subscribed to the same topic, but with QoS 2, then it + will receive the same message but with QoS 2. For a second example, if + a client is subscribed with QoS 2 and a message is published on QoS 0, + the client will receive it on QoS 0. + Higher levels of QoS are more reliable, but involve higher + latency and have higher bandwidth requirements. + + 0: The broker/client will deliver the message once, with no confirmation. + 1: The broker/client will deliver the message at least once, with confirmation required. + 2: The broker/client will deliver the message exactly once by using a four step handshake. + + + + + Retained Messages + All messages may be set to be retained. This means that the + broker will keep the message even after sending it to all current + subscribers. If a new subscription is made that matches the topic of + the retained message, then the message will be sent to the client. This + is useful as a "last known good" mechanism. If a topic is only updated + infrequently, then without a retained message, a newly subscribed + client may have to wait a long time to receive an update. With a + retained message, the client will receive an instant update. + + + + Clean session / Durable connections + On connection, a client sets the "clean session" flag, which is + sometimes also known as the "clean start" flag. If clean session is set + to false, then the connection is treated as durable. This means that + when the client disconnects, any subscriptions it has will remain and + any subsequent QoS 1 or 2 messages will be stored until it connects + again in the future. If clean session is true, then all subscriptions + will be removed for the client when it disconnects. + + + + Wills + When a client connects to a broker, it may inform the broker that + it has a will. This is a message that it wishes the broker to send when + the client disconnects unexpectedly. The will message has a topic, + QoS and retain status just the same as any other message. + + + + See Also + + + + mosquitto + 8 + + + + + mosquitto_pub + 1 + + + + + mosquitto_sub + 1 + + + + + + + Author + Roger Light roger@atchoo.org + + diff --git a/misc/currentcost/cc128_log_mysql.pl b/misc/currentcost/cc128_log_mysql.pl new file mode 100755 index 0000000000..112047986b --- /dev/null +++ b/misc/currentcost/cc128_log_mysql.pl @@ -0,0 +1,55 @@ +#!/usr/bin/perl + +# Log CurrentCost power meter data to a mysql database. +# Assumes data is coming in on MQTT topic sensors/cc128 +# and in format timestamp,temperature,ch1_data +# e.g. 1276605752,12.7,86 + +# To create database, table and user: +# +# CREATE DATABASE powermeter; +# USE 'powermeter'; +# CREATE TABLE powermeter ( +# `id` INT NOT NULL auto_increment, +# `timestamp` INT NOT NULL, +# `temperature` FLOAT NOT NULL DEFAULT 0.0, +# `ch1` INT NOT NULL DEFAULT 0, +# PRIMARY KEY (`id`), +# UNIQUE KEY `timestamp` (`timestamp`) +# ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +# +# CREATE USER 'powermeter'@'localhost' IDENTIFIED BY ''; +# GRANT ALL ON powermeter.* to 'powermeter'@'localhost'; + +use strict; +use DBI(); +use FileHandle; + +local $| = 1; + +my $dbname = "powermeter"; +my $dbhost = "localhost"; +my $dbusername = "powermeter"; +my $dbpassword = "password"; +my $dbtable = "powermeter"; + +my $subclient = "/usr/bin/mosquitto_sub -t sensors/cc128"; +open(SUB, "$subclient|"); +SUB->autoflush(1); + +my $dbh = DBI->connect("DBI:mysql:database=$dbname;host=$dbhost", + "$dbusername", "$dbpassword", {'RaiseError' => 1}); + +my $query = "INSERT INTO powermeter (timestamp, temperature, ch1) VALUES (?,?,?)"; + +my @vals; +my ($timestamp, $temperature, $ch1); +while (my $line = ) { + @vals = split(/,/, $line); + $timestamp = @vals[0]; + $temperature = @vals[1]; + $ch1 = @vals[2]; + $dbh->do($query, undef, $timestamp, $temperature, $ch1); +} +$dbh->disconnect(); + diff --git a/misc/currentcost/cc128_parse.pl b/misc/currentcost/cc128_parse.pl new file mode 100755 index 0000000000..0236344d6e --- /dev/null +++ b/misc/currentcost/cc128_parse.pl @@ -0,0 +1,45 @@ +#!/usr/bin/perl -w + +# Read raw cc128 data and republish without xml. +# Probably only works if you have a single channel. + +use strict; +use HTTP::Date "str2time"; +use FileHandle; + +local $| = 1; + +my $subclient = "/usr/bin/mosquitto_sub -t sensors/cc128/raw -q 2"; +my $pubclient = "/usr/bin/mosquitto_pub -t sensors/cc128 -q 2 -l"; +my $pubclient_ch1 = "/usr/bin/mosquitto_pub -t sensors/cc128/ch1 -q 2 -l"; + +open(SUB, "$subclient|"); +open(PUB, "|$pubclient"); +open(PUB_CH1, "|$pubclient_ch1"); + +SUB->autoflush(1); +PUB->autoflush(1); +PUB_CH1->autoflush(1); + +while (my $line = ) { + #CC128-v0.120000215.7003112100108 + if ($line =~ m# *([\-\d.]+)0[0-9]*10*(\d+) $now){ + $r_stamp -= 86400; + } + + print PUB "$r_stamp,$temp,$watts\n"; + print PUB_CH1 "$r_stamp $watts\n"; + } +} diff --git a/misc/currentcost/cc128_read.pl b/misc/currentcost/cc128_read.pl new file mode 100755 index 0000000000..e1310e5b9d --- /dev/null +++ b/misc/currentcost/cc128_read.pl @@ -0,0 +1,23 @@ +#!/usr/bin/perl -w + +# Reads data from a Current Cost device via serial port. +# Spawns + +use strict; +use Device::SerialPort qw( :PARAM :STAT 0.07 ); + +my $pubclient = "mosquitto_pub -t sensors/cc128/raw -q 2 -l"; +my $PORT = "/dev/ttyUSB0"; +local $| = 1; + +my $ob = Device::SerialPort->new($PORT); +$ob->baudrate(57600); +$ob->write_settings; + +open(SERIAL, "+<$PORT"); +open(MQTT, "|$pubclient"); +while (my $line = ) { + print(MQTT "$line"); +} + +close(MQTT); diff --git a/misc/currentcost/cc128_read.py b/misc/currentcost/cc128_read.py new file mode 100755 index 0000000000..d2a9c17da4 --- /dev/null +++ b/misc/currentcost/cc128_read.py @@ -0,0 +1,22 @@ +#!/usr/bin/python -u + +import mosquitto +import serial + +usb = serial.Serial(port='/dev/ttyUSB0', baudrate=57600) + +mosq = mosquitto.Mosquitto() +mosq.connect("localhost") +mosq.loop_start() + +running = True +try: + while running: + line = usb.readline() + mosq.publish("cc128/raw", line) +except usb.SerialException, e: + running = False + +mosq.disconnect() +mosq.loop_stop() + diff --git a/misc/currentcost/gnome-panel/CurrentCostMQTT.py b/misc/currentcost/gnome-panel/CurrentCostMQTT.py new file mode 100755 index 0000000000..dcc59958dc --- /dev/null +++ b/misc/currentcost/gnome-panel/CurrentCostMQTT.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python + +import gnomeapplet +import gobject +import gtk +import mosquitto +import os +import platform +import pygtk +import sys + +class CurrentCostMQTT(gnomeapplet.Applet): + def on_message(self, mosq, obj, msg): + # Message format is "power" + self.label.set_text(msg.payload+"W") + + def set_label(self, val): + self.label.set_text(val) + + def on_change_background(self, applet, type, color, pixmap): + applet.set_style(None) + applet.modify_style(gtk.RcStyle()) + + if type == gnomeapplet.COLOR_BACKGROUND: + applet.modify_bg(gtk.STATE_NORMAL, color) + elif type == gnomeapplet.PIXMAP_BACKGROUND: + style = applet.get_style().copy() + style.bg_pixmap[gtk.STATE_NORMAL] = pixmap + applet.set_style(style) + + def show_menu(self, widget, event): + print "menu" + + def __init__(self, applet, iid): + self.applet = applet + self.label = gtk.Label("0W") + self.event_box = gtk.EventBox() + self.event_box.add(self.label) + self.event_box.set_events(gtk.gdk.BUTTON_PRESS_MASK) + self.event_box.connect("button_press_event", self.show_menu) + self.applet.add(self.event_box) + self.applet.set_background_widget(applet) + self.applet.show_all() + self.mosq = mosquitto.Mosquitto() + self.mosq.on_message = self.on_message + self.mosq.connect("localhost") + self.mosq.loop_start() + self.mosq.subscribe("sensors/cc128/ch1", 0) + self.applet.connect('change-background', self.on_change_background) + +def CurrentCostMQTT_factory(applet, iid): + CurrentCostMQTT(applet, iid) + return gtk.TRUE + +if len(sys.argv) == 2: + if sys.argv[1] == "-d": #Debug mode + main_window = gtk.Window(gtk.WINDOW_TOPLEVEL) + main_window.set_title("Python Applet") + main_window.connect("destroy", gtk.main_quit) + app = gnomeapplet.Applet() + CurrentCostMQTT_factory(app,None) + app.reparent(main_window) + main_window.show_all() + gtk.main() + sys.exit() + +if __name__ == '__main__': + gnomeapplet.bonobo_factory("OAFIID:CurrentCostMQTT_Factory", gnomeapplet.Applet.__gtype__, "MQTT", "0", CurrentCostMQTT_factory) + diff --git a/misc/currentcost/gnome-panel/CurrentCostMQTT.server b/misc/currentcost/gnome-panel/CurrentCostMQTT.server new file mode 100644 index 0000000000..9e74bdac3a --- /dev/null +++ b/misc/currentcost/gnome-panel/CurrentCostMQTT.server @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/misc/currentcost/gnome-panel/currentcost.png b/misc/currentcost/gnome-panel/currentcost.png new file mode 100644 index 0000000000000000000000000000000000000000..df6d01a89b05ad254d497331751e0b862c916c42 GIT binary patch literal 5316 zcmV;#6g%sQP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01ejw01b%&W)SP500007bV*G`2igb+ z2@oD_%JM7#000?uMObu0Z*6U5Zgc=ca%Ew3Wn>_CX>@2HM@dakSAh-}000ypNklDyI-g@-(dwL!_JNqJcmrE{5kQ7BnvVQV@3j9swer2iv%ABIS!qwU!6=Qe|tC_u8l?`g7>Hj!7Hjt2mzUUqN-=FGR9zy0TDzXjuVzD6>7EWQM1|j z4{M#}Kdi*^O@KScT^{RnFh>6G9N6oP5D_+(Ym2S2c`%s#!`?~n6TM!qJs1qm#%c&S z=heFasDx0?ww8-Q2&#GRB!qx5Mw28ViDQY&E5<-2O=-7Vr>pBLzqiqD{%aB01=u?5 zQms~5tuMVd2aZQGymxHh-{Ym{o&aEwg^wK{cRzh}eEeMRq(@N{09YHL8q_(5f<)Gq zvpm0+e;YzTi^7R>o-EIp&1MvNP6$D*wNkHDNs@#(vb5W6*4Ni=ZJb~Kt*Vhf1NifA ze3K76|2)=O>XrB>J8(3}xPALJKl|b{0QAPiCwjf!=eDj_W-{5rLXXjU-$*iwW~kgfy2`Y_wH};SDt^WocY{;a(ip%v)kJ{42PqTRuYL4 zD=4a}Qm%p}iXx0L0B6^C8o!7Q?>&p(dGDFcGDf2@y#&z)t1c0#s*JZ zz51JtO8hy1uYKzlH*Y@0YCU;(2M$gra;{xffKj2pxxM}1GjF|hoAG$;n~kPPDyihI zz&lSIM}!a-#;dBzV&)&6@%dFl2(oZu5!mHb5HUI$jilG>F_}!LR+qHhYKJ(9?OLb9 zlTSYRFFN()vjDqCC$hP=yg&w`(@9Rdns8cp+ObuDlZpGs_wH?d=FXivjK^bbG#VyJ z<9Tp;7U*bt>2xHD zBKrM4MPA^2pw()!y4(U_b!C<6QU&kbj~Mooj8;|jkul{7!UCmq4z&(r4DEKCD2fPG=i`eSR>+BK)DQQ_RL9UmP<-Cmbgt0ifg>UcaR%Q65l#;~@! zLRu+tc%`#U9LMawOV=3kQ#>QDRR+5NL zt4VAuAt++(kDYx)P-6)QLBad-a}k609_KR=d!S|y9`f#g+BF7g6 zs^!cs#+CbD&;TJkOm3>2ZMh^#SgI~D%QCVor`zo^7z|!k)lCE3n9XK4kB^VB)@rp{ zlW8{7FpsF~SS(Q|xP4I${Lf@la5G!Rt9Yaog&)>z4k9It`?V8BU#@*y*w&R!S{`Wk|=w6r84l(_7Q z0`Cf1%?4{LD`&)M|KO1OTif_DanWuysiXPwh?Pc-@vkYTR2?*X3 zg5rF@d53d;9^9d%-maV&V=Rc^U0^oLI5|1NJAd8y;5UcEQhga?$cvoZWhjm$ww&KM zhk$_P=w!g&@d<;`gwbeRa;PNY+-gURDj16kLQnyOJj;m$&abbD1yUnixpJ9GrCc%9 za*TIQf^%h%&$D{aQqg%=;EP;bk(U4vLMe)}3}a<80@_89BM8=53ZD~##}zs2moCt3 zl$8PR99!GFjAuDZRyaO7=KT5^h)}OpY1HfV#u>(dM7G3u=UA#&IlsPomOnPmt#W>2 zgE!y4LzGmwe)Tb~T|AF-e$HxzFrTuZoUvb3$`vXcQ5+FP5v^9E9f?S~Km%ip;0@j> zaTIg)@yAFbL&*+LWp8(nB(kVFcJ}wVdi5%`YK2<8rt2GPviH5WNGlb@m+&VBR#!Sy zt4msHWlv-x9=m*zt*!esn+=}3d4p;a=}##zyz_enB=@Aod!Kt>hLadM+so(I*Eqjc z&Ybhg*6u!|=>%(p5R~IypOe9`)CFQ#Us)xIBh)**bL6ubl_ciM;r=SI@jz#3E`iD$h-*KMNbj>3T+81HfJ5LBfz4s!hK6d32X_5$_ zgVB(;?``1&YC{l3be0$FA0Es3jWxAEt668Y)7FEdBe6tUxSVEVMLNqZ1<0}j5u-^G zi}y~iUweGcp`7N#>}>5bnNHM*Eg5<#S_Dw<1J)YyEZ4#n5=Svn9CPK;g??nL-K|uX zR!>g{vC{# zK%kH$vYcCA$IgxD562uI_vG5O$B3+vasoZZSlRFP^jqKgE~C*<#2A9A7-P!}ADn~` zG_scQcr4TDlqia**Bh*KKC)|)B)+p$UBY`$mSv=A%Ju6vXf+xD%w`kbzVkLg3|NZ> zkM{vFmXm&;(|-SKW47BZ(l{m*j#{N8OXtVirnFS5U)ZAf-w=Ou)MrNtJ(U7 z0e+BHk~_^toh+M5631#y`Qnq4Q+9TDOVMKO8PzQrSIHtz2SaxE56)Jh*=TU}(iK|s z{1Mc%^b3vW>9k&nhL%pJeP+zv``h#fV}dnm1aYANvEX%{xh=!dn7zXzz53W?0od4B zXL+SVY%Ktz@szCxyDCNmgtSs+XYYV+?*y0UGn=^G|dPistC>%h@d6>ol$>`jr8QC&))t% zfEvKLT%#z`Ja^o^x21XEaaw>0>U<8O8Yi*(lH2Gb>P;WHY6yxCLBl*JXb5!LZB|#7 zzY@W>0ZbNx6_IJH)%aqilFn6Ep~K-omOCqmi9|yw1znNLyaXa41aaOYCX(@V#?j#s zfOz#-gMdo^^n`<>BXO#Dbre1qYlWae3YUwNj8wv0p=l^f^Q9grwS^B}R8@>IYQ(U) zxykkGPkd2CreFWocdVIBX9|!Q`LZkg7r=1;!JgdTexU7EgSFKTKDct*h(&{z_uiHI zR!S;y)IDJ^7+^(+Ou7H>ZS63c&Pseo@WnibJW^f>K?w_%AWF~x0=^V8Xeeu^-g)UP zw^?6X|H4vizYOrB1(>W|QDc#q?-p!8?xv2ZzV>=B=lDTiXmqW6~r60$Gqdy#yRw{TG zP#?~S{Ery_NO@5}jTq86QI{8fJQ~SzyUDXp-~65TKmW`t05AXDU+3tks{qwl-n~b* zy?@N5jfWEO)|lV_^JC_+hT0Q4P9=9U z&1OmxEr}~~H6|cp7kz<}?3WD^F(PUOu~CEu4b$n=%(9HNwH1B#*=PRc&%XHFs|#CR zf8%YQee&8*=~AAKm`tZ!KEHPM+3lVEzxUl+-+gs=XYX= z`Z}9tlII15o1g@uCo%Ys??|<>t_uYKr z-z;qSpRc{nlTSTGJ&oR5pZJIOw)xVRzQk|+vwyN!`kP<>=6C+l+jsB({e#1U7=iV3 z=cw1~WtG}{&1N(5JQwFmS*a`nR5TbP#u!9VYYmZ)P#n~@b6-WAlVOU1_8`XBuK z&;7N}w`$cN0DSUOpXKGh^>JQ$_U8Y2r~Ys-=1>3lEByLD_|>z|-n_s2ORvBF`rqmH zx*tC|JzWB&TB}h>mq?PO`OKO!ZybZ{`R-+FaP{sFW%8ot#9vl>wo#$Yn2BNc9Q9I z8s<qh51QT)UDlca~>A^S<|uL}c{n@BZx{{&D=F4}H)-@z~{1VtpWeq+$M(j{gM{ WQPaL!tZ2Ca0000" each time you add/remove a certificate. +#cafile +#capath + +# Path to the PEM encoded server certificate. +#certfile + +# Path to the PEM encoded keyfile. +#keyfile + +# This option defines the version of the TLS protocol to use for this listener. +# The default value will always be the highest version that is available for +# the version of openssl that the broker was compiled against. For openssl >= +# 1.0.1 the valid values are tlsv1.2 tlsv1.1 and tlsv1. For openssl < 1.0.1 the +# valid values are tlsv1. +#tls_version + +# By default a TLS enabled listener will operate in a similar fashion to a +# https enabled web server, in that the server has a certificate signed by a CA +# and the client will verify that it is a trusted certificate. The overall aim +# is encryption of the network traffic. By setting require_certificate to true, +# the client must provide a valid certificate in order for the network +# connection to proceed. This allows access to the broker to be controlled +# outside of the mechanisms provided by MQTT. +#require_certificate false + +# If require_certificate is true, you may set use_identity_as_username to true +# to use the CN value from the client certificate as a username. If this is +# true, the password_file option will not be used for this listener. +#use_identity_as_username false + +# If you have require_certificate set to true, you can create a certificate +# revocation list file to revoke access to particular client certificates. If +# you have done this, use crlfile to point to the PEM encoded revocation file. +#crlfile + +# If you wish to control which encryption ciphers are used, use the ciphers +# option. The list of available ciphers can be optained using the "openssl +# ciphers" command and should be provided in the same format as the output of +# that command. +# If unset defaults to DEFAULT:!aNULL:!eNULL:!LOW:!EXPORT:!SSLv2:@STRENGTH +#ciphers DEFAULT:!aNULL:!eNULL:!LOW:!EXPORT:!SSLv2:@STRENGTH + +# ----------------------------------------------------------------- +# Pre-shared-key based SSL/TLS support +# ----------------------------------------------------------------- +# The following options can be used to enable PSK based SSL/TLS support for +# this listener. Note that the recommended port for MQTT over TLS is 8883, but +# this must be set manually. +# +# See also the mosquitto-tls man page and the "Certificate based SSL/TLS +# support" section. Only one of certificate or PSK encryption support can be +# enabled for any listener. + +# The psk_hint option enables pre-shared-key support for this listener and also +# acts as an identifier for this listener. The hint is sent to clients and may +# be used locally to aid authentication. The hint is a free form string that +# doesn't have much meaning in itself, so feel free to be creative. +# If this option is provided, see psk_file to define the pre-shared keys to be +# used or create a security plugin to handle them. +#psk_hint + +# Set use_identity_as_username to have the psk identity sent by the client used +# as its username. Authentication will be carried out using the PSK rather than +# the MQTT username/password and so password_file will not be used for this +# listener. +#use_identity_as_username false + +# When using PSK, the encryption ciphers used will be chosen from the list of +# available PSK ciphers. If you want to control which ciphers are available, +# use the "ciphers" option. The list of available ciphers can be optained +# using the "openssl ciphers" command and should be provided in the same format +# as the output of that command. +#ciphers + +# ================================================================= +# Extra listeners +# ================================================================= + +# Listen on a port/ip address combination. By using this variable +# multiple times, mosquitto can listen on more than one port. If +# this variable is used and neither bind_address nor port given, +# then the default listener will not be started. +# The port number to listen on must be given. Optionally, an ip +# address or host name may be supplied as a second argument. In +# this case, mosquitto will attempt to bind the listener to that +# address and so restrict access to the associated network and +# interface. By default, mosquitto will listen on all interfaces. +# listener port-number [ip address/host name] +#listener + +# The maximum number of client connections to allow. This is +# a per listener setting. +# Default is -1, which means unlimited connections. +# Note that other process limits mean that unlimited connections +# are not really possible. Typically the default maximum number of +# connections possible is around 1024. +#max_connections -1 + +# The listener can be restricted to operating within a topic hierarchy using +# the mount_point option. This is achieved be prefixing the mount_point string +# to all topics for any clients connected to this listener. This prefixing only +# happens internally to the broker; the client will not see the prefix. +#mount_point + +# ----------------------------------------------------------------- +# Certificate based SSL/TLS support +# ----------------------------------------------------------------- +# The following options can be used to enable certificate based SSL/TLS support +# for this listener. Note that the recommended port for MQTT over TLS is 8883, +# but this must be set manually. +# +# See also the mosquitto-tls man page and the "Pre-shared-key based SSL/TLS +# support" section. Only one of certificate or PSK encryption support can be +# enabled for any listener. + +# At least one of cafile or capath must be defined to enable certificate based +# TLS encryption. They both define methods of accessing the PEM encoded +# Certificate Authority certificates that have signed your server certificate +# and that you wish to trust. +# cafile defines the path to a file containing the CA certificates. +# capath defines a directory that will be searched for files +# containing the CA certificates. For capath to work correctly, the +# certificate files must have ".crt" as the file ending and you must run +# "c_rehash " each time you add/remove a certificate. +#cafile +#capath + +# Path to the PEM encoded server certificate. +#certfile + +# Path to the PEM encoded keyfile. +#keyfile + +# By default an TLS enabled listener will operate in a similar fashion to a +# https enabled web server, in that the server has a certificate signed by a CA +# and the client will verify that it is a trusted certificate. The overall aim +# is encryption of the network traffic. By setting require_certificate to true, +# the client must provide a valid certificate in order for the network +# connection to proceed. This allows access to the broker to be controlled +# outside of the mechanisms provided by MQTT. +#require_certificate false + +# If require_certificate is true, you may set use_identity_as_username to true +# to use the CN value from the client certificate as a username. If this is +# true, the password_file option will not be used for this listener. +#use_identity_as_username false + +# If you have require_certificate set to true, you can create a certificate +# revocation list file to revoke access to particular client certificates. If +# you have done this, use crlfile to point to the PEM encoded revocation file. +#crlfile + +# If you wish to control which encryption ciphers are used, use the ciphers +# option. The list of available ciphers can be optained using the "openssl +# ciphers" command and should be provided in the same format as the output of +# that command. +#ciphers + +# ----------------------------------------------------------------- +# Pre-shared-key based SSL/TLS support +# ----------------------------------------------------------------- +# The following options can be used to enable PSK based SSL/TLS support for +# this listener. Note that the recommended port for MQTT over TLS is 8883, but +# this must be set manually. +# +# See also the mosquitto-tls man page and the "Certificate based SSL/TLS +# support" section. Only one of certificate or PSK encryption support can be +# enabled for any listener. + +# The psk_hint option enables pre-shared-key support for this listener and also +# acts as an identifier for this listener. The hint is sent to clients and may +# be used locally to aid authentication. The hint is a free form string that +# doesn't have much meaning in itself, so feel free to be creative. +# If this option is provided, see psk_file to define the pre-shared keys to be +# used or create a security plugin to handle them. +#psk_hint + +# Set use_identity_as_username to have the psk identity sent by the client used +# as its username. Authentication will be carried out using the PSK rather than +# the MQTT username/password and so password_file will not be used for this +# listener. +#use_identity_as_username false + +# When using PSK, the encryption ciphers used will be chosen from the list of +# available PSK ciphers. If you want to control which ciphers are available, +# use the "ciphers" option. The list of available ciphers can be optained +# using the "openssl ciphers" command and should be provided in the same format +# as the output of that command. +#ciphers + +# ================================================================= +# Persistence +# ================================================================= + +# If persistence is enabled, save the in-memory database to disk +# every autosave_interval seconds. If set to 0, the persistence +# database will only be written when mosquitto exits. See also +# autosave_on_changes. +# Note that writing of the persistence database can be forced by +# sending mosquitto a SIGUSR1 signal. +#autosave_interval 1800 + +# If true, mosquitto will count the number of subscription changes, retained +# messages received and queued messages and if the total exceeds +# autosave_interval then the in-memory database will be saved to disk. +# If false, mosquitto will save the in-memory database to disk by treating +# autosave_interval as a time in seconds. +#autosave_on_changes false + +# Save persistent message data to disk (true/false). +# This saves information about all messages, including +# subscriptions, currently in-flight messages and retained +# messages. +# retained_persistence is a synonym for this option. +#persistence false + +# The filename to use for the persistent database, not including +# the path. +#persistence_file mosquitto.db + +# Location for persistent database. Must include trailing / +# Default is an empty string (current directory). +# Set to e.g. /var/lib/mosquitto/ if running as a proper service on Linux or +# similar. +#persistence_location + +# ================================================================= +# Logging +# ================================================================= + +# Places to log to. Use multiple log_dest lines for multiple +# logging destinations. +# Possible destinations are: stdout stderr syslog topic file +# +# stdout and stderr log to the console on the named output. +# +# syslog uses the userspace syslog facility which usually ends up +# in /var/log/messages or similar. +# +# topic logs to the broker topic '$SYS/broker/log/', +# where severity is one of D, E, W, N, I, M which are debug, error, +# warning, notice, information and message. Message type severity is used by +# the subscribe/unsubscribe log_types and publishes log messages to +# $SYS/broker/log/M/susbcribe or $SYS/broker/log/M/unsubscribe. +# +# The file destination requires an additional parameter which is the file to be +# logged to, e.g. "log_dest file /var/log/mosquitto.log". The file will be +# closed and reopened when the broker receives a HUP signal. Only a single file +# destination may be configured. +# +# Note that if the broker is running as a Windows service it will default to +# "log_dest none" and neither stdout nor stderr logging is available. +# Use "log_dest none" if you wish to disable logging. +#log_dest stderr + +# Types of messages to log. Use multiple log_type lines for logging +# multiple types of messages. +# Possible types are: debug, error, warning, notice, information, +# none, subscribe, unsubscribe, all. +# Note that debug type messages are for decoding the incoming/outgoing +# network packets. They are not logged in "topics". +#log_type error +#log_type warning +#log_type notice +#log_type information + +# If set to true, client connection and disconnection messages will be included +# in the log. +#connection_messages true + +# If set to true, add a timestamp value to each log message. +#log_timestamp true + +# ================================================================= +# Security +# ================================================================= + +# If set, only clients that have a matching prefix on their +# clientid will be allowed to connect to the broker. By default, +# all clients may connect. +# For example, setting "secure-" here would mean a client "secure- +# client" could connect but another with clientid "mqtt" couldn't. +#clientid_prefixes + +# Boolean value that determines whether clients that connect +# without providing a username are allowed to connect. If set to +# false then a password file should be created (see the +# password_file option) to control authenticated client access. +# Defaults to true. +#allow_anonymous true + +# In addition to the clientid_prefixes, allow_anonymous and TLS +# authentication options, username based authentication is also +# possible. The default support is described in "Default +# authentication and topic access control" below. The auth_plugin +# allows another authentication method to be used. +# Specify the path to the loadable plugin and see the +# "Authentication and topic access plugin options" section below. +#auth_plugin + +# ----------------------------------------------------------------- +# Default authentication and topic access control +# ----------------------------------------------------------------- + +# Control access to the broker using a password file. This file can be +# generated using the mosquitto_passwd utility. If TLS support is not compiled +# into mosquitto (it is recommended that TLS support should be included) then +# plain text passwords are used, in which case the file should be a text file +# with lines in the format: +# username:password +# The password (and colon) may be omitted if desired, although this +# offers very little in the way of security. +# +# See the TLS client require_certificate and use_identity_as_username options +# for alternative authentication options. +#password_file + +# Access may also be controlled using a pre-shared-key file. This requires +# TLS-PSK support and a listener configured to use it. The file should be text +# lines in the format: +# identity:key +# The key should be in hexadecimal format without a leading "0x". +#psk_file + +# Control access to topics on the broker using an access control list +# file. If this parameter is defined then only the topics listed will +# have access. +# If the first character of a line of the ACL file is a # it is treated as a +# comment. +# Topic access is added with lines of the format: +# +# topic [read|write] +# +# The access type is controlled using "read" or "write". This parameter +# is optional - if not given then the access is read/write. +# can contain the + or # wildcards as in subscriptions. +# +# The first set of topics are applied to anonymous clients, assuming +# allow_anonymous is true. User specific topic ACLs are added after a +# user line as follows: +# +# user +# +# The username referred to here is the same as in password_file. It is +# not the clientid. +# +# +# If is also possible to define ACLs based on pattern substitution within the +# topic. The patterns available for substition are: +# +# %c to match the client id of the client +# %u to match the username of the client +# +# The substitution pattern must be the only text for that level of hierarchy. +# +# The form is the same as for the topic keyword, but using pattern as the +# keyword. +# Pattern ACLs apply to all users even if the "user" keyword has previously +# been given. +# +# If using bridges with usernames and ACLs, connection messages can be allowed +# with the following pattern: +# pattern write $SYS/broker/connection/%c/state +# +# pattern [read|write] +# +# Example: +# +# pattern write sensor/%u/data +# +#acl_file + +# ----------------------------------------------------------------- +# Authentication and topic access plugin options +# ----------------------------------------------------------------- + +# If the auth_plugin option above is used, define options to pass to the +# plugin here as described by the plugin instructions. All options named +# using the format auth_opt_* will be passed to the plugin, for example: +# +# auth_opt_db_host +# auth_opt_db_port +# auth_opt_db_username +# auth_opt_db_password + + +# ================================================================= +# Bridges +# ================================================================= + +# A bridge is a way of connecting multiple MQTT brokers together. +# Create a new bridge using the "connection" option as described below. Set +# options for the bridges using the remaining parameters. You must specify the +# address and at least one topic to subscribe to. +# Each connection must have a unique name. +# The address line may have multiple host address and ports specified. See +# below in the round_robin description for more details on bridge behaviour if +# multiple addresses are used. +# The direction that the topic will be shared can be chosen by +# specifying out, in or both, where the default value is out. +# The QoS level of the bridged communication can be specified with the next +# topic option. The default QoS level is 0, to change the QoS the topic +# direction must also be given. +# The local and remote prefix options allow a topic to be remapped when it is +# bridged to/from the remote broker. This provides the ability to place a topic +# tree in an appropriate location. +# For more details see the mosquitto.conf man page. +# Multiple topics can be specified per connection, but be careful +# not to create any loops. +# If you are using bridges with cleansession set to false (the default), then +# you may get unexpected behaviour from incoming topics if you change what +# topics you are subscribing to. This is because the remote broker keeps the +# subscription for the old topic. If you have this problem, connect your bridge +# with cleansession set to true, then reconnect with cleansession set to false +# as normal. +#connection +#address [:] [[:]] +#topic [[[out | in | both] qos-level] local-prefix remote-prefix] + +# If the bridge has more than one address given in the address/addresses +# configuration, the round_robin option defines the behaviour of the bridge on +# a failure of the bridge connection. If round_robin is false, the default +# value, then the first address is treated as the main bridge connection. If +# the connection fails, the other secondary addresses will be attempted in +# turn. Whilst connected to a secondary bridge, the bridge will periodically +# attempt to reconnect to the main bridge until successful. +# If round_robin is true, then all addresses are treated as equals. If a +# connection fails, the next address will be tried and if successful will +# remain connected until it fails +#round_robin false + +# Set the client id for this bridge connection. If not defined, +# this defaults to 'name.hostname' where name is the connection +# name and hostname is the hostname of this computer. +#clientid + +# Set the clean session variable for this bridge. +# When set to true, when the bridge disconnects for any reason, all +# messages and subscriptions will be cleaned up on the remote +# broker. Note that with cleansession set to true, there may be a +# significant amount of retained messages sent when the bridge +# reconnects after losing its connection. +# When set to false, the subscriptions and messages are kept on the +# remote broker, and delivered when the bridge reconnects. +#cleansession false + +# If set to true, publish notification messages to the local and remote brokers +# giving information about the state of the bridge connection. Retained +# messages are published to the topic $SYS/broker/connection//state +# unless the notification_topic option is used. +# If the message is 1 then the connection is active, or 0 if the connection has +# failed. +#notifications true + +# Choose the topic on which notification messages for this bridge are +# published. If not set, messages are published on the topic +# $SYS/broker/connection//state +#notification_topic + +# Set the keepalive interval for this bridge connection, in +# seconds. +#keepalive_interval 60 + +# Set the start type of the bridge. This controls how the bridge starts and +# can be one of three types: automatic, lazy and once. Note that RSMB provides +# a fourth start type "manual" which isn't currently supported by mosquitto. +# +# "automatic" is the default start type and means that the bridge connection +# will be started automatically when the broker starts and also restarted +# after a short delay (30 seconds) if the connection fails. +# +# Bridges using the "lazy" start type will be started automatically when the +# number of queued messages exceeds the number set with the "threshold" +# parameter. It will be stopped automatically after the time set by the +# "idle_timeout" parameter. Use this start type if you wish the connection to +# only be active when it is needed. +# +# A bridge using the "once" start type will be started automatically when the +# broker starts but will not be restarted if the connection fails. +#start_type automatic + +# Set the amount of time a bridge using the automatic start type will wait +# until attempting to reconnect. Defaults to 30 seconds. +#restart_timeout 30 + +# Set the amount of time a bridge using the lazy start type must be idle before +# it will be stopped. Defaults to 60 seconds. +#idle_timeout 60 + +# Set the number of messages that need to be queued for a bridge with lazy +# start type to be restarted. Defaults to 10 messages. +# Must be less than max_queued_messages. +#threshold 10 + +# If try_private is set to true, the bridge will attempt to indicate to the +# remote broker that it is a bridge not an ordinary client. If successful, this +# means that loop detection will be more effective and that retained messages +# will be propagated correctly. Not all brokers support this feature so it may +# be necessary to set try_private to false if your bridge does not connect +# properly. +#try_private true + +# Set the username to use when connecting to an MQTT v3.1 broker +# that requires authentication. +#username + +# Set the password to use when connecting to an MQTT v3.1 broker +# that requires authentication. This option is only used if +# username is also set. +#password + +# ----------------------------------------------------------------- +# Certificate based SSL/TLS support +# ----------------------------------------------------------------- +# Either bridge_cafile or bridge_capath must be defined to enable TLS support +# for this bridge. +# bridge_cafile defines the path to a file containing the +# Certificate Authority certificates that have signed the remote broker +# certificate. +# bridge_capath defines a directory that will be searched for files containing +# the CA certificates. For bridge_capath to work correctly, the certificate +# files must have ".crt" as the file ending and you must run "c_rehash " each time you add/remove a certificate. +#bridge_cafile +#bridge_capath + +# Path to the PEM encoded client certificate, if required by the remote broker. +#bridge_certfile + +# Path to the PEM encoded client private key, if required by the remote broker. +#bridge_keyfile + +# When using certificate based encryption, bridge_insecure disables +# verification of the server hostname in the server certificate. This can be +# useful when testing initial server configurations, but makes it possible for +# a malicious third party to impersonate your server through DNS spoofing, for +# example. Use this option in testing only. If you need to resort to using this +# option in a production environment, your setup is at fault and there is no +# point using encryption. +#bridge_insecure false + +# ----------------------------------------------------------------- +# PSK based SSL/TLS support +# ----------------------------------------------------------------- +# Pre-shared-key encryption provides an alternative to certificate based +# encryption. A bridge can be configured to use PSK with the bridge_identity +# and bridge_psk options. These are the client PSK identity, and pre-shared-key +# in hexadecimal format with no "0x". Only one of certificate and PSK based +# encryption can be used on one +# bridge at once. +#bridge_identity +#bridge_psk + + +# ================================================================= +# External config files +# ================================================================= + +# External configuration files may be included by using the +# include_dir option. This defines a directory that will be searched +# for config files. All files that end in '.conf' will be loaded as +# a configuration file. It is best to have this as the last option +# in the main file. This option will only be processed from the main +# configuration file. The directory specified must not contain the +# main configuration file. +#include_dir + +# ================================================================= +# Unsupported rsmb options - for the future +# ================================================================= + +#addresses +#round_robin + +# ================================================================= +# rsmb options - unlikely to ever be supported +# ================================================================= + +#ffdc_output +#max_log_entries +#trace_level +#trace_output diff --git a/pskfile.example b/pskfile.example new file mode 100644 index 0000000000..95299cb479 --- /dev/null +++ b/pskfile.example @@ -0,0 +1,2 @@ +id:deadbeef +easy:12345 diff --git a/pwfile.example b/pwfile.example new file mode 100644 index 0000000000..58b94c9aa9 --- /dev/null +++ b/pwfile.example @@ -0,0 +1,3 @@ +roger:$6$clQ4Ocu312S0qWgl$Cv2wUxgEN73c6C6jlBkswqR4AkHsvDLWvtEXZZ8NpsBLgP1WAo/qA+WXcmEN/mjDNgdUwcxRAveqNMs2xUVQYA== +sub_client:$6$U+qg0/32F0g2Fh+n$fBPSkq/rfNyEQ/TkEjRgwGTTVBpvNhKSyGShovH9KHewsvJ731tD5Zx26IHhR5RYCICt0L9qBW0/KK31UkCliw== +pub_client:$6$vxQ89y+7WrsnL2yn$fSPMmEZn9TSrC8s/jaPmxJ9NijWpkP2e7bMJLz78JXR1vW2x8+T3FZ23byJA6xs5Mt+LeOybAHwcUv0OCl40rA== diff --git a/readme-windows.txt b/readme-windows.txt new file mode 100644 index 0000000000..52d85dd25b --- /dev/null +++ b/readme-windows.txt @@ -0,0 +1,5 @@ +Mosquitto is now installed as a Windows service. You can start/stop it from +the control panel as well as running it as a normal executable. + +When running as a service, the configuration in mosquitto.conf in the +installation directory is used so modify this to your needs. diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000000..aac19eea5e --- /dev/null +++ b/readme.txt @@ -0,0 +1,20 @@ +Mosquitto +========= + +Mosquitto is an open source implementation of a server for version 3.1 of the +MQTT protocol. + +See the following links for more information on MQTT: + +http://mqtt.org/ +http://www.ibm.com/developerworks/webservices/library/ws-mqtt/index.html + +Mosquitto project information is available at the following locations: + +http://mosquitto.org/ (main homepage) +http://launchpad.net/mosquitto (bug tracking, translations) +http://bitbucket.org/oojah/mosquitto (hg source code repository) + +There is also a public test server available at http://test.mosquitto.org/ + +Mosquitto was written by Roger Light diff --git a/security/mosquitto.apparmor b/security/mosquitto.apparmor new file mode 100644 index 0000000000..bc157074e3 --- /dev/null +++ b/security/mosquitto.apparmor @@ -0,0 +1,27 @@ +/usr/sbin/mosquitto { + #include + #include + + /usr/sbin/mosquitto r, + /etc/mosquitto/mosquitto.conf r, + /etc/mosquitto/ca_certificates/* r, + /etc/mosquitto/cert/* r, + /etc/mosquitto/conf.d/* r, + /var/lib/mosquitto/ r, + /var/lib/mosquitto/mosquitto.db rwk, + /var/run/mosquitto.pid rw, + + network inet stream, + network inet6 stream, + network inet dgram, + network inet6 dgram, + + # For drop privileges + capability setgid, + capability setuid, + + # For tcp-wrappers + /lib{,32,64}/libwrap.so* rm, + /etc/hosts.allow r, + /etc/hosts.deny r, +} diff --git a/service/monit/mosquitto.monit b/service/monit/mosquitto.monit new file mode 100644 index 0000000000..8bcbbeab55 --- /dev/null +++ b/service/monit/mosquitto.monit @@ -0,0 +1,4 @@ +check process mosquitto with pidfile /var/run/mosquitto.pid + start = "/etc/init.d/mosquitto start" + stop = "/etc/init.d/mosquitto stop" + diff --git a/service/svscan/run b/service/svscan/run new file mode 100755 index 0000000000..2bd9a70d34 --- /dev/null +++ b/service/svscan/run @@ -0,0 +1,3 @@ +#!/bin/sh + +/usr/sbin/mosquitto -c /etc/mosquitto/mosquitto.conf diff --git a/service/upstart/mosquitto.conf b/service/upstart/mosquitto.conf new file mode 100644 index 0000000000..2f817823ed --- /dev/null +++ b/service/upstart/mosquitto.conf @@ -0,0 +1,8 @@ +description "Mosquitto MQTTv3.1 broker" +author "Roger Light " + +start on net-device-up + +respawn + +exec /usr/sbin/mosquitto -c /etc/mosquitto/mosquitto.conf diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000000..0d99fd5657 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,105 @@ +include_directories(${mosquitto_SOURCE_DIR} ${mosquitto_SOURCE_DIR}/src + ${mosquitto_SOURCE_DIR}/lib ${OPENSSL_INCLUDE_DIR} + ${STDBOOL_H_PATH} ${STDINT_H_PATH}) + +set (MOSQ_SRCS + conf.c + context.c + database.c + lib_load.h + logging.c + loop.c + ../lib/memory_mosq.c ../lib/memory_mosq.h + mosquitto.c + mosquitto_broker.h + net.c + ../lib/net_mosq.c ../lib/net_mosq.h + persist.c persist.h + read_handle.c read_handle_client.c read_handle_server.c + ../lib/read_handle_shared.c ../lib/read_handle.h + subs.c + security.c security_default.c + ../lib/send_client_mosq.c ../lib/send_mosq.h + ../lib/send_mosq.c ../lib/send_mosq.h + send_server.c + sys_tree.c + ../lib/time_mosq.c + ../lib/tls_mosq.c + ../lib/util_mosq.c ../lib/util_mosq.h + ../lib/will_mosq.c ../lib/will_mosq.h) + +option(INC_BRIDGE_SUPPORT + "Include bridge support for connecting to other brokers?" ON) +if (${INC_BRIDGE_SUPPORT} STREQUAL ON) + set (MOSQ_SRCS ${MOSQ_SRCS} bridge.c) + add_definitions("-DWITH_BRIDGE") +endif (${INC_BRIDGE_SUPPORT} STREQUAL ON) + + +option(USE_LIBWRAP + "Include tcp-wrappers support?" OFF) + +if (${USE_LIBWRAP} STREQUAL ON) + set (MOSQ_LIBS ${MOSQ_LIBS} wrap) + add_definitions("-DWITH_WRAP") +endif (${USE_LIBWRAP} STREQUAL ON) + +option(INC_DB_UPGRADE + "Include database upgrade support? (recommended)" ON) + +option(INC_MEMTRACK + "Include memory tracking support?" ON) +if (${INC_MEMTRACK} STREQUAL ON) + add_definitions("-DWITH_MEMORY_TRACKING") +endif (${INC_MEMTRACK} STREQUAL ON) + +option(WITH_PERSISTENCE + "Include persistence support?" ON) +if (${WITH_PERSISTENCE} STREQUAL ON) + add_definitions("-DWITH_PERSISTENCE") +endif (${WITH_PERSISTENCE} STREQUAL ON) + +option(WITH_SYS_TREE + "Include $SYS tree support?" ON) +if (${WITH_SYS_TREE} STREQUAL ON) + add_definitions("-DWITH_SYS_TREE") +endif (${WITH_SYS_TREE} STREQUAL ON) + +if (WIN32 OR CYGWIN) + set (MOSQ_SRCS ${MOSQ_SRCS} service.c) +endif (WIN32 OR CYGWIN) + +add_definitions (-DWITH_BROKER) + +add_executable(mosquitto ${MOSQ_SRCS}) + +set (MOSQ_LIBS ${OPENSSL_LIBRARIES}) + +if (UNIX) + if (APPLE) + set (MOSQ_LIBS ${MOSQ_LIBS} dl m) + else (APPLE) + set (MOSQ_LIBS ${MOSQ_LIBS} rt dl m) + endif (APPLE) +endif (UNIX) + +if (WIN32) + set (MOSQ_LIBS ${MOSQ_LIBS} ws2_32) +endif (WIN32) + +target_link_libraries(mosquitto ${MOSQ_LIBS}) + +install(TARGETS mosquitto RUNTIME DESTINATION ${SBINDIR} LIBRARY DESTINATION ${LIBDIR}) +install(FILES mosquitto_plugin.h DESTINATION ${INCLUDEDIR}) + + +if (${WITH_TLS} STREQUAL ON) + add_executable(mosquitto_passwd mosquitto_passwd.c) + target_link_libraries(mosquitto_passwd ${OPENSSL_LIBRARIES}) + install(TARGETS mosquitto_passwd RUNTIME DESTINATION ${BINDIR} LIBRARY DESTINATION ${LIBDIR}) +endif (${WITH_TLS} STREQUAL ON) + +if (UNIX) + install(CODE "EXEC_PROGRAM(/sbin/ldconfig)") +endif (UNIX) + diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000000..5dbf06facc --- /dev/null +++ b/src/Makefile @@ -0,0 +1,118 @@ +include ../config.mk + +.PHONY: all install uninstall clean reallyclean + +ifeq ($(WITH_TLS),yes) +all : mosquitto mosquitto_passwd +else +all : mosquitto +endif + +mosquitto : mosquitto.o bridge.o conf.o context.o database.o logging.o loop.o memory_mosq.o persist.o net.o net_mosq.o read_handle.o read_handle_client.o read_handle_server.o read_handle_shared.o security.o security_default.o send_client_mosq.o send_mosq.o send_server.o service.o subs.o sys_tree.o time_mosq.o tls_mosq.o util_mosq.o will_mosq.o + ${CC} $^ -o $@ ${LDFLAGS} $(BROKER_LIBS) + +mosquitto.o : mosquitto.c mosquitto_broker.h + ${CC} $(BROKER_CFLAGS) -c $< -o $@ -DCHANGESET=\"$$(cat ../changeset)\" + +bridge.o : bridge.c mosquitto_broker.h + ${CC} $(BROKER_CFLAGS) -c $< -o $@ + +conf.o : conf.c mosquitto_broker.h + ${CC} $(BROKER_CFLAGS) -c $< -o $@ + +context.o : context.c mosquitto_broker.h + ${CC} $(BROKER_CFLAGS) -c $< -o $@ + +database.o : database.c mosquitto_broker.h + ${CC} $(BROKER_CFLAGS) -c $< -o $@ + +logging.o : logging.c mosquitto_broker.h + ${CC} $(BROKER_CFLAGS) -c $< -o $@ + +loop.o : loop.c mosquitto_broker.h + ${CC} $(BROKER_CFLAGS) -c $< -o $@ + +memory_mosq.o : ../lib/memory_mosq.c ../lib/memory_mosq.h + ${CC} $(BROKER_CFLAGS) -c $< -o $@ + +net.o : net.c mosquitto_broker.h + ${CC} $(BROKER_CFLAGS) -c $< -o $@ + +net_mosq.o : ../lib/net_mosq.c ../lib/net_mosq.h + ${CC} $(BROKER_CFLAGS) -c $< -o $@ + +persist.o : persist.c persist.h mosquitto_broker.h + ${CC} $(BROKER_CFLAGS) -c $< -o $@ + +read_handle.o : read_handle.c mosquitto_broker.h + ${CC} $(BROKER_CFLAGS) -c $< -o $@ + +read_handle_client.o : read_handle_client.c mosquitto_broker.h + ${CC} $(BROKER_CFLAGS) -c $< -o $@ + +read_handle_server.o : read_handle_server.c mosquitto_broker.h + ${CC} $(BROKER_CFLAGS) -c $< -o $@ + +read_handle_shared.o : ../lib/read_handle_shared.c ../lib/read_handle.h + ${CC} $(BROKER_CFLAGS) -c $< -o $@ + +security.o : security.c mosquitto_broker.h + ${CC} $(BROKER_CFLAGS) -c $< -o $@ + +security_default.o : security_default.c mosquitto_broker.h + ${CC} $(BROKER_CFLAGS) -c $< -o $@ + +send_client_mosq.o : ../lib/send_client_mosq.c ../lib/send_mosq.h + ${CC} $(BROKER_CFLAGS) -c $< -o $@ + +send_mosq.o : ../lib/send_mosq.c ../lib/send_mosq.h + ${CC} $(BROKER_CFLAGS) -c $< -o $@ + +send_server.o : send_server.c mosquitto_broker.h + ${CC} $(BROKER_CFLAGS) -c $< -o $@ + +service.o : service.c mosquitto_broker.h + ${CC} $(BROKER_CFLAGS) -c $< -o $@ + +subs.o : subs.c mosquitto_broker.h + ${CC} $(BROKER_CFLAGS) -c $< -o $@ + +sys_tree.o : sys_tree.c mosquitto_broker.h + ${CC} $(BROKER_CFLAGS) -c $< -o $@ + +time_mosq.o : ../lib/time_mosq.c ../lib/time_mosq.h + ${CC} $(BROKER_CFLAGS) -c $< -o $@ + +tls_mosq.o : ../lib/tls_mosq.c + ${CC} $(BROKER_CFLAGS) -c $< -o $@ + +util_mosq.o : ../lib/util_mosq.c ../lib/util_mosq.h + ${CC} $(BROKER_CFLAGS) -c $< -o $@ + +will_mosq.o : ../lib/will_mosq.c ../lib/will_mosq.h + ${CC} $(BROKER_CFLAGS) -c $< -o $@ + +mosquitto_passwd : mosquitto_passwd.o + ${CC} $^ -o $@ ${LDFLAGS} $(PASSWD_LIBS) + +mosquitto_passwd.o : mosquitto_passwd.c + ${CC} $(CFLAGS) ${CPPFLAGS} -c $< -o $@ + +install : all + $(INSTALL) -d ${DESTDIR}$(prefix)/sbin + $(INSTALL) -s mosquitto ${DESTDIR}${prefix}/sbin/mosquitto + $(INSTALL) mosquitto_plugin.h ${DESTDIR}${prefix}/include/mosquitto_plugin.h +ifeq ($(WITH_TLS),yes) + $(INSTALL) -s mosquitto_passwd ${DESTDIR}${prefix}/bin/mosquitto_passwd +endif + +uninstall : + -rm -f ${DESTDIR}${prefix}/sbin/mosquitto + -rm -f ${DESTDIR}${prefix}/include/mosquitto_plugin.h + -rm -f ${DESTDIR}${prefix}/bin/mosquitto_passwd + +clean : + -rm -f *.o mosquitto mosquitto_passwd + +reallyclean : clean + -rm -rf *.orig *.db diff --git a/src/bridge.c b/src/bridge.c new file mode 100644 index 0000000000..4a8c584334 --- /dev/null +++ b/src/bridge.c @@ -0,0 +1,249 @@ +/* +Copyright (c) 2009-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#include +#include +#include +#include + +#ifndef WIN32 +#include +#include +#else +#include +#include +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef WITH_BRIDGE + +int mqtt3_bridge_new(struct mosquitto_db *db, struct _mqtt3_bridge *bridge) +{ + int i; + struct mosquitto *new_context = NULL; + int null_index = -1; + struct mosquitto **tmp_contexts; + char hostname[256]; + int len; + char *id; + + assert(db); + assert(bridge); + + if(bridge->clientid){ + id = _mosquitto_strdup(bridge->clientid); + }else{ + if(!gethostname(hostname, 256)){ + len = strlen(hostname) + strlen(bridge->name) + 2; + id = _mosquitto_malloc(len); + if(!id){ + return MOSQ_ERR_NOMEM; + } + snprintf(id, len, "%s.%s", hostname, bridge->name); + }else{ + return 1; + } + } + if(!id){ + return MOSQ_ERR_NOMEM; + } + + /* Search for existing id (possible from persistent db) and also look for a + * gap in the db->contexts[] array in case the id isn't found. */ + for(i=0; icontext_count; i++){ + if(db->contexts[i]){ + if(!strcmp(db->contexts[i]->id, id)){ + new_context = db->contexts[i]; + break; + } + }else if(db->contexts[i] == NULL && null_index == -1){ + null_index = i; + break; + } + } + if(!new_context){ + /* id wasn't found, so generate a new context */ + new_context = mqtt3_context_init(-1); + if(!new_context){ + return MOSQ_ERR_NOMEM; + } + if(null_index == -1){ + /* There were no gaps in the db->contexts[] array, so need to append. */ + db->context_count++; + tmp_contexts = _mosquitto_realloc(db->contexts, sizeof(struct mosquitto*)*db->context_count); + if(tmp_contexts){ + db->contexts = tmp_contexts; + db->contexts[db->context_count-1] = new_context; + }else{ + _mosquitto_free(new_context); + return MOSQ_ERR_NOMEM; + } + }else{ + db->contexts[null_index] = new_context; + } + new_context->id = id; + }else{ + /* id was found, so context->id already in memory. */ + _mosquitto_free(id); + } + new_context->bridge = bridge; + new_context->is_bridge = true; + + new_context->username = new_context->bridge->username; + new_context->password = new_context->bridge->password; + +#ifdef WITH_TLS + new_context->tls_cafile = new_context->bridge->tls_cafile; + new_context->tls_capath = new_context->bridge->tls_capath; + new_context->tls_certfile = new_context->bridge->tls_certfile; + new_context->tls_keyfile = new_context->bridge->tls_keyfile; + new_context->tls_version = new_context->bridge->tls_version; + new_context->tls_insecure = new_context->bridge->tls_insecure; +#ifdef REAL_WITH_TLS_PSK + new_context->tls_psk_identity = new_context->bridge->tls_psk_identity; + new_context->tls_psk = new_context->bridge->tls_psk; +#endif +#endif + + bridge->try_private_accepted = true; + + return mqtt3_bridge_connect(db, new_context); +} + +int mqtt3_bridge_connect(struct mosquitto_db *db, struct mosquitto *context) +{ + int rc; + int i; + char *notification_topic; + int notification_topic_len; + uint8_t notification_payload; + + if(!context || !context->bridge) return MOSQ_ERR_INVAL; + + context->state = mosq_cs_new; + context->sock = -1; + context->last_msg_in = mosquitto_time(); + context->last_msg_out = mosquitto_time(); + context->keepalive = context->bridge->keepalive; + context->clean_session = context->bridge->clean_session; + context->in_packet.payload = NULL; + context->ping_t = 0; + context->bridge->lazy_reconnect = false; + mqtt3_bridge_packet_cleanup(context); + mqtt3_db_message_reconnect_reset(context); + + if(context->clean_session){ + mqtt3_db_messages_delete(context); + } + + /* Delete all local subscriptions even for clean_session==false. We don't + * remove any messages and the next loop carries out the resubscription + * anyway. This means any unwanted subs will be removed. + */ + mqtt3_subs_clean_session(db, context, &db->subs); + + for(i=0; ibridge->topic_count; i++){ + if(context->bridge->topics[i].direction == bd_out || context->bridge->topics[i].direction == bd_both){ + _mosquitto_log_printf(NULL, MOSQ_LOG_DEBUG, "Bridge %s doing local SUBSCRIBE on topic %s", context->id, context->bridge->topics[i].local_topic); + if(mqtt3_sub_add(db, context, context->bridge->topics[i].local_topic, context->bridge->topics[i].qos, &db->subs)) return 1; + } + } + + if(context->bridge->notifications){ + notification_payload = '0'; + if(context->bridge->notification_topic){ + mqtt3_db_messages_easy_queue(db, context, context->bridge->notification_topic, 1, 1, ¬ification_payload, 1); + rc = _mosquitto_will_set(context, context->bridge->notification_topic, 1, ¬ification_payload, 1, true); + if(rc != MOSQ_ERR_SUCCESS){ + return rc; + } + }else{ + notification_topic_len = strlen(context->id)+strlen("$SYS/broker/connection//state"); + notification_topic = _mosquitto_malloc(sizeof(char)*(notification_topic_len+1)); + if(!notification_topic) return MOSQ_ERR_NOMEM; + + snprintf(notification_topic, notification_topic_len+1, "$SYS/broker/connection/%s/state", context->id); + mqtt3_db_messages_easy_queue(db, context, notification_topic, 1, 1, ¬ification_payload, 1); + rc = _mosquitto_will_set(context, notification_topic, 1, ¬ification_payload, 1, true); + if(rc != MOSQ_ERR_SUCCESS){ + _mosquitto_free(notification_topic); + return rc; + } + _mosquitto_free(notification_topic); + } + } + + _mosquitto_log_printf(NULL, MOSQ_LOG_NOTICE, "Connecting bridge %s (%s:%d)", context->bridge->name, context->bridge->addresses[context->bridge->cur_address].address, context->bridge->addresses[context->bridge->cur_address].port); + rc = _mosquitto_socket_connect(context, context->bridge->addresses[context->bridge->cur_address].address, context->bridge->addresses[context->bridge->cur_address].port, NULL, true); + if(rc != MOSQ_ERR_SUCCESS){ + if(rc == MOSQ_ERR_TLS){ + return rc; /* Error already printed */ + }else if(rc == MOSQ_ERR_ERRNO){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error creating bridge: %s.", strerror(errno)); + }else if(rc == MOSQ_ERR_EAI){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error creating bridge: %s.", gai_strerror(errno)); + } + + return rc; + } + + rc = _mosquitto_send_connect(context, context->keepalive, context->clean_session); + if(rc == MOSQ_ERR_SUCCESS){ + return MOSQ_ERR_SUCCESS; + }else if(rc == MOSQ_ERR_ERRNO && errno == ENOTCONN){ + return MOSQ_ERR_SUCCESS; + }else{ + if(rc == MOSQ_ERR_TLS){ + return rc; /* Error already printed */ + }else if(rc == MOSQ_ERR_ERRNO){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error creating bridge: %s.", strerror(errno)); + }else if(rc == MOSQ_ERR_EAI){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error creating bridge: %s.", gai_strerror(errno)); + } + _mosquitto_socket_close(context); + return rc; + } +} + +void mqtt3_bridge_packet_cleanup(struct mosquitto *context) +{ + struct _mosquitto_packet *packet; + if(!context) return; + + _mosquitto_packet_cleanup(context->current_out_packet); + while(context->out_packet){ + _mosquitto_packet_cleanup(context->out_packet); + packet = context->out_packet; + context->out_packet = context->out_packet->next; + _mosquitto_free(packet); + } + + _mosquitto_packet_cleanup(&(context->in_packet)); +} + +#endif diff --git a/src/conf.c b/src/conf.c new file mode 100644 index 0000000000..b603a9333b --- /dev/null +++ b/src/conf.c @@ -0,0 +1,1762 @@ +/* +Copyright (c) 2009-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#include + +#include +#include +#include +#include +#include + +#ifdef WIN32 +#else +# include +#endif + +#ifndef WIN32 +# include +# include +#else +# include +# include +#endif + +#include +#include +#include "tls_mosq.h" +#include "util_mosq.h" +#include "mqtt3_protocol.h" + +struct config_recurse { + int log_dest; + int log_dest_set; + int log_type; + int log_type_set; + int max_inflight_messages; + int max_queued_messages; +}; + +#if defined(WIN32) || defined(__CYGWIN__) +#include +extern SERVICE_STATUS_HANDLE service_handle; +#endif + +static int _conf_parse_bool(char **token, const char *name, bool *value, char *saveptr); +static int _conf_parse_int(char **token, const char *name, int *value, char *saveptr); +static int _conf_parse_string(char **token, const char *name, char **value, char *saveptr); +static int _config_read_file(struct mqtt3_config *config, bool reload, const char *file, struct config_recurse *config_tmp, int level, int *lineno); + +static int _conf_attempt_resolve(const char *host, const char *text, int log, const char *msg) +{ + struct addrinfo gai_hints; + struct addrinfo *gai_res; + int rc; + + memset(&gai_hints, 0, sizeof(struct addrinfo)); + gai_hints.ai_family = PF_UNSPEC; + gai_hints.ai_flags = AI_ADDRCONFIG; + gai_hints.ai_socktype = SOCK_STREAM; + gai_res = NULL; + rc = getaddrinfo(host, NULL, &gai_hints, &gai_res); + if(gai_res){ + freeaddrinfo(gai_res); + } + if(rc != 0){ +#ifndef WIN32 + if(rc == EAI_SYSTEM){ + if(errno == ENOENT){ + _mosquitto_log_printf(NULL, log, "%s: Unable to resolve %s %s.", msg, text, host); + }else{ + _mosquitto_log_printf(NULL, log, "%s: Error resolving %s: %s.", msg, text, strerror(errno)); + } + }else{ + _mosquitto_log_printf(NULL, log, "%s: Error resolving %s: %s.", msg, text, gai_strerror(rc)); + } +#else + if(rc == WSAHOST_NOT_FOUND){ + _mosquitto_log_printf(NULL, log, "%s: Error resolving %s.", msg, text); + } +#endif + return MOSQ_ERR_INVAL; + } + return MOSQ_ERR_SUCCESS; +} + +static void _config_init_reload(struct mqtt3_config *config) +{ + int i; + /* Set defaults */ + if(config->acl_file) _mosquitto_free(config->acl_file); + config->acl_file = NULL; + config->allow_anonymous = true; + config->allow_duplicate_messages = false; + config->allow_zero_length_clientid = true; + config->auto_id_prefix = NULL; + config->auto_id_prefix_len = 0; + config->autosave_interval = 1800; + config->autosave_on_changes = false; + if(config->clientid_prefixes) _mosquitto_free(config->clientid_prefixes); + config->connection_messages = true; + config->clientid_prefixes = NULL; + if(config->log_fptr){ + fclose(config->log_fptr); + config->log_fptr = NULL; + } + if(config->log_file){ + _mosquitto_free(config->log_file); + config->log_file = NULL; + } +#if defined(WIN32) || defined(__CYGWIN__) + if(service_handle){ + /* This is running as a Windows service. Default to no logging. Using + * stdout/stderr is forbidden because the first clients to connect will + * get log information sent to them for some reason. */ + config->log_dest = MQTT3_LOG_NONE; + }else{ + config->log_dest = MQTT3_LOG_STDERR; + } +#else + config->log_dest = MQTT3_LOG_STDERR; + if(config->verbose){ + config->log_type = INT_MAX; + }else{ + config->log_type = MOSQ_LOG_ERR | MOSQ_LOG_WARNING | MOSQ_LOG_NOTICE | MOSQ_LOG_INFO; + } +#endif + config->log_timestamp = true; + if(config->password_file) _mosquitto_free(config->password_file); + config->password_file = NULL; + config->persistence = false; + if(config->persistence_location) _mosquitto_free(config->persistence_location); + config->persistence_location = NULL; + if(config->persistence_file) _mosquitto_free(config->persistence_file); + config->persistence_file = NULL; + config->persistent_client_expiration = 0; + if(config->psk_file) _mosquitto_free(config->psk_file); + config->psk_file = NULL; + config->queue_qos0_messages = false; + config->retry_interval = 20; + config->store_clean_interval = 10; + config->sys_interval = 10; + config->upgrade_outgoing_qos = false; + if(config->auth_options){ + for(i=0; iauth_option_count; i++){ + _mosquitto_free(config->auth_options[i].key); + _mosquitto_free(config->auth_options[i].value); + } + _mosquitto_free(config->auth_options); + config->auth_options = NULL; + config->auth_option_count = 0; + } +} + +void mqtt3_config_init(struct mqtt3_config *config) +{ + memset(config, 0, sizeof(struct mqtt3_config)); + _config_init_reload(config); + config->config_file = NULL; + config->daemon = false; + config->default_listener.host = NULL; + config->default_listener.port = 0; + config->default_listener.max_connections = -1; + config->default_listener.mount_point = NULL; + config->default_listener.socks = NULL; + config->default_listener.sock_count = 0; + config->default_listener.client_count = 0; +#ifdef WITH_TLS + config->default_listener.tls_version = NULL; + config->default_listener.cafile = NULL; + config->default_listener.capath = NULL; + config->default_listener.certfile = NULL; + config->default_listener.keyfile = NULL; + config->default_listener.ciphers = NULL; + config->default_listener.psk_hint = NULL; + config->default_listener.require_certificate = false; + config->default_listener.crlfile = NULL; + config->default_listener.use_identity_as_username = false; +#endif + config->listeners = NULL; + config->listener_count = 0; + config->pid_file = NULL; + config->user = NULL; +#ifdef WITH_BRIDGE + config->bridges = NULL; + config->bridge_count = 0; +#endif + config->auth_plugin = NULL; + config->verbose = false; + config->message_size_limit = 0; +} + +void mqtt3_config_cleanup(struct mqtt3_config *config) +{ + int i; +#ifdef WITH_BRIDGE + int j; +#endif + + if(config->acl_file) _mosquitto_free(config->acl_file); + if(config->auto_id_prefix) _mosquitto_free(config->auto_id_prefix); + if(config->clientid_prefixes) _mosquitto_free(config->clientid_prefixes); + if(config->config_file) _mosquitto_free(config->config_file); + if(config->password_file) _mosquitto_free(config->password_file); + if(config->persistence_location) _mosquitto_free(config->persistence_location); + if(config->persistence_file) _mosquitto_free(config->persistence_file); + if(config->persistence_filepath) _mosquitto_free(config->persistence_filepath); + if(config->psk_file) _mosquitto_free(config->psk_file); + if(config->listeners){ + for(i=0; ilistener_count; i++){ + if(config->listeners[i].host) _mosquitto_free(config->listeners[i].host); + if(config->listeners[i].mount_point) _mosquitto_free(config->listeners[i].mount_point); + if(config->listeners[i].socks) _mosquitto_free(config->listeners[i].socks); +#ifdef WITH_TLS + if(config->listeners[i].cafile) _mosquitto_free(config->listeners[i].cafile); + if(config->listeners[i].capath) _mosquitto_free(config->listeners[i].capath); + if(config->listeners[i].certfile) _mosquitto_free(config->listeners[i].certfile); + if(config->listeners[i].keyfile) _mosquitto_free(config->listeners[i].keyfile); + if(config->listeners[i].ciphers) _mosquitto_free(config->listeners[i].ciphers); + if(config->listeners[i].psk_hint) _mosquitto_free(config->listeners[i].psk_hint); + if(config->listeners[i].crlfile) _mosquitto_free(config->listeners[i].crlfile); + if(config->listeners[i].tls_version) _mosquitto_free(config->listeners[i].tls_version); + if(config->listeners[i].ssl_ctx) SSL_CTX_free(config->listeners[i].ssl_ctx); +#endif + } + _mosquitto_free(config->listeners); + } +#ifdef WITH_BRIDGE + if(config->bridges){ + for(i=0; ibridge_count; i++){ + if(config->bridges[i].name) _mosquitto_free(config->bridges[i].name); + if(config->bridges[i].addresses){ + for(j=0; jbridges[i].address_count; j++){ + _mosquitto_free(config->bridges[i].addresses[j].address); + } + _mosquitto_free(config->bridges[i].addresses); + } + if(config->bridges[i].clientid) _mosquitto_free(config->bridges[i].clientid); + if(config->bridges[i].username) _mosquitto_free(config->bridges[i].username); + if(config->bridges[i].password) _mosquitto_free(config->bridges[i].password); + if(config->bridges[i].topics){ + for(j=0; jbridges[i].topic_count; j++){ + if(config->bridges[i].topics[j].topic) _mosquitto_free(config->bridges[i].topics[j].topic); + if(config->bridges[i].topics[j].local_prefix) _mosquitto_free(config->bridges[i].topics[j].local_prefix); + if(config->bridges[i].topics[j].remote_prefix) _mosquitto_free(config->bridges[i].topics[j].remote_prefix); + if(config->bridges[i].topics[j].local_topic) _mosquitto_free(config->bridges[i].topics[j].local_topic); + if(config->bridges[i].topics[j].remote_topic) _mosquitto_free(config->bridges[i].topics[j].remote_topic); + } + _mosquitto_free(config->bridges[i].topics); + } + if(config->bridges[i].notification_topic) _mosquitto_free(config->bridges[i].notification_topic); +#ifdef REAL_WITH_TLS_PSK + if(config->bridges[i].tls_psk_identity) _mosquitto_free(config->bridges[i].tls_psk_identity); + if(config->bridges[i].tls_psk) _mosquitto_free(config->bridges[i].tls_psk); +#endif + } + _mosquitto_free(config->bridges); + } +#endif + if(config->auth_plugin) _mosquitto_free(config->auth_plugin); + if(config->auth_options){ + for(i=0; iauth_option_count; i++){ + _mosquitto_free(config->auth_options[i].key); + _mosquitto_free(config->auth_options[i].value); + } + _mosquitto_free(config->auth_options); + config->auth_options = NULL; + config->auth_option_count = 0; + } + if(config->log_fptr){ + fclose(config->log_fptr); + config->log_fptr = NULL; + } + if(config->log_file){ + _mosquitto_free(config->log_file); + config->log_file = NULL; + } +} + +static void print_usage(void) +{ + printf("mosquitto version %s (build date %s)\n\n", VERSION, TIMESTAMP); + printf("mosquitto is an MQTT v3.1 broker.\n\n"); + printf("Usage: mosquitto [-c config_file] [-d] [-h] [-p port]\n\n"); + printf(" -c : specify the broker config file.\n"); + printf(" -d : put the broker into the background after starting.\n"); + printf(" -h : display this help.\n"); + printf(" -p : start the broker listening on the specified port.\n"); + printf(" Not recommended in conjunction with the -c option.\n"); + printf(" -v : verbose mode - enable all logging types. This overrides\n"); + printf(" any logging options given in the config file.\n"); + printf("\nSee http://mosquitto.org/ for more information.\n\n"); +} + +int mqtt3_config_parse_args(struct mqtt3_config *config, int argc, char *argv[]) +{ + int i; + int port_tmp; + + for(i=1; iconfig_file = _mosquitto_strdup(argv[i+1]); + if(!config->config_file){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory."); + return MOSQ_ERR_NOMEM; + } + + if(mqtt3_config_read(config, false)){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Unable to open configuration file."); + return MOSQ_ERR_INVAL; + } + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: -c argument given, but no config file specified."); + return MOSQ_ERR_INVAL; + } + i++; + }else if(!strcmp(argv[i], "-d") || !strcmp(argv[i], "--daemon")){ + config->daemon = true; + }else if(!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")){ + print_usage(); + return MOSQ_ERR_INVAL; + }else if(!strcmp(argv[i], "-p") || !strcmp(argv[i], "--port")){ + if(i65535){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid port specified (%d).", port_tmp); + return MOSQ_ERR_INVAL; + }else{ + if(config->default_listener.port){ + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: Default listener port specified multiple times. Only the latest will be used."); + } + config->default_listener.port = port_tmp; + } + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: -p argument given, but no port specified."); + return MOSQ_ERR_INVAL; + } + i++; + }else if(!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose")){ + config->verbose = true; + }else{ + fprintf(stderr, "Error: Unknown option '%s'.\n",argv[i]); + print_usage(); + return MOSQ_ERR_INVAL; + } + } + + if(config->listener_count == 0 +#ifdef WITH_TLS + || config->default_listener.cafile + || config->default_listener.capath + || config->default_listener.certfile + || config->default_listener.keyfile + || config->default_listener.ciphers + || config->default_listener.psk_hint + || config->default_listener.require_certificate + || config->default_listener.crlfile + || config->default_listener.use_identity_as_username +#endif + || config->default_listener.host + || config->default_listener.port + || config->default_listener.max_connections != -1 + || config->default_listener.mount_point){ + + config->listener_count++; + config->listeners = _mosquitto_realloc(config->listeners, sizeof(struct _mqtt3_listener)*config->listener_count); + if(!config->listeners){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory."); + return MOSQ_ERR_NOMEM; + } + if(config->default_listener.port){ + config->listeners[config->listener_count-1].port = config->default_listener.port; + }else{ + config->listeners[config->listener_count-1].port = 1883; + } + if(config->default_listener.host){ + config->listeners[config->listener_count-1].host = config->default_listener.host; + }else{ + config->listeners[config->listener_count-1].host = NULL; + } + if(config->default_listener.mount_point){ + config->listeners[config->listener_count-1].mount_point = config->default_listener.mount_point; + }else{ + config->listeners[config->listener_count-1].mount_point = NULL; + } + config->listeners[config->listener_count-1].max_connections = config->default_listener.max_connections; + config->listeners[config->listener_count-1].client_count = 0; + config->listeners[config->listener_count-1].socks = NULL; + config->listeners[config->listener_count-1].sock_count = 0; + config->listeners[config->listener_count-1].client_count = 0; +#ifdef WITH_TLS + config->listeners[config->listener_count-1].tls_version = config->default_listener.tls_version; + config->listeners[config->listener_count-1].cafile = config->default_listener.cafile; + config->listeners[config->listener_count-1].capath = config->default_listener.capath; + config->listeners[config->listener_count-1].certfile = config->default_listener.certfile; + config->listeners[config->listener_count-1].keyfile = config->default_listener.keyfile; + config->listeners[config->listener_count-1].ciphers = config->default_listener.ciphers; + config->listeners[config->listener_count-1].psk_hint = config->default_listener.psk_hint; + config->listeners[config->listener_count-1].require_certificate = config->default_listener.require_certificate; + config->listeners[config->listener_count-1].ssl_ctx = NULL; + config->listeners[config->listener_count-1].crlfile = config->default_listener.crlfile; + config->listeners[config->listener_count-1].use_identity_as_username = config->default_listener.use_identity_as_username; +#endif + } + + /* Default to drop to mosquitto user if we are privileged and no user specified. */ + if(!config->user){ + config->user = "mosquitto"; + } + if(config->verbose){ + config->log_type = INT_MAX; + } + return MOSQ_ERR_SUCCESS; +} + +int mqtt3_config_read(struct mqtt3_config *config, bool reload) +{ + int rc = MOSQ_ERR_SUCCESS; + struct config_recurse cr; + int lineno; +#ifdef WITH_BRIDGE + int i; +#endif + + cr.log_dest = MQTT3_LOG_NONE; + cr.log_dest_set = 0; + cr.log_type = MOSQ_LOG_NONE; + cr.log_type_set = 0; + cr.max_inflight_messages = 20; + cr.max_queued_messages = 100; + + if(!config->config_file) return 0; + + if(reload){ + /* Re-initialise appropriate config vars to default for reload. */ + _config_init_reload(config); + } + rc = _config_read_file(config, reload, config->config_file, &cr, 0, &lineno); + if(rc){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error found at %s:%d.", config->config_file, lineno); + return rc; + } + +#ifdef WITH_PERSISTENCE + if(config->persistence){ + if(!config->persistence_file){ + config->persistence_file = _mosquitto_strdup("mosquitto.db"); + if(!config->persistence_file) return MOSQ_ERR_NOMEM; + } + if(config->persistence_filepath){ + _mosquitto_free(config->persistence_filepath); + } + if(config->persistence_location && strlen(config->persistence_location)){ + config->persistence_filepath = _mosquitto_malloc(strlen(config->persistence_location) + strlen(config->persistence_file) + 1); + if(!config->persistence_filepath) return MOSQ_ERR_NOMEM; + sprintf(config->persistence_filepath, "%s%s", config->persistence_location, config->persistence_file); + }else{ + config->persistence_filepath = _mosquitto_strdup(config->persistence_file); + if(!config->persistence_filepath) return MOSQ_ERR_NOMEM; + } + } +#endif + /* Default to drop to mosquitto user if no other user specified. This must + * remain here even though it is covered in mqtt3_parse_args() because this + * function may be called on its own. */ + if(!config->user){ + config->user = "mosquitto"; + } + + mqtt3_db_limits_set(cr.max_inflight_messages, cr.max_queued_messages); + +#ifdef WITH_BRIDGE + for(i=0; ibridge_count; i++){ + if(!config->bridges[i].name || !config->bridges[i].addresses || !config->bridges[i].topic_count){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid bridge configuration."); + return MOSQ_ERR_INVAL; + } +#ifdef REAL_WITH_TLS_PSK + if(config->bridges[i].tls_psk && !config->bridges[i].tls_psk_identity){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid bridge configuration: missing bridge_identity.\n"); + return MOSQ_ERR_INVAL; + } + if(config->bridges[i].tls_psk_identity && !config->bridges[i].tls_psk){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid bridge configuration: missing bridge_psk.\n"); + return MOSQ_ERR_INVAL; + } +#endif + } +#endif + + if(cr.log_dest_set){ + config->log_dest = cr.log_dest; + } + if(config->verbose){ + config->log_type = MOSQ_LOG_DEBUG | MOSQ_LOG_ERR | MOSQ_LOG_WARNING | MOSQ_LOG_NOTICE | MOSQ_LOG_INFO; + }else if(cr.log_type_set){ + config->log_type = cr.log_type; + } + return MOSQ_ERR_SUCCESS; +} + +int _config_read_file(struct mqtt3_config *config, bool reload, const char *file, struct config_recurse *cr, int level, int *lineno) +{ + int rc; + FILE *fptr = NULL; + char buf[1024]; + char *token; + int port_tmp; + char *saveptr = NULL; +#ifdef WITH_BRIDGE + struct _mqtt3_bridge *cur_bridge = NULL; + struct _mqtt3_bridge_topic *cur_topic; +#endif + time_t expiration_mult; + char *key; + char *conf_file; +#ifdef WIN32 + HANDLE fh; + char dirpath[MAX_PATH]; + WIN32_FIND_DATA find_data; +#else + DIR *dh; + struct dirent *de; +#endif + int len; + struct _mqtt3_listener *cur_listener = &config->default_listener; +#ifdef WITH_BRIDGE + char *address; + int i; +#endif + int lineno_ext; + + fptr = _mosquitto_fopen(file, "rt"); + if(!fptr){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Unable to open config file %s\n", file); + return 1; + } + + *lineno = 0; + + while(fgets(buf, 1024, fptr)){ + (*lineno)++; + if(buf[0] != '#' && buf[0] != 10 && buf[0] != 13){ + while(buf[strlen(buf)-1] == 10 || buf[strlen(buf)-1] == 13){ + buf[strlen(buf)-1] = 0; + } + token = strtok_r(buf, " ", &saveptr); + if(token){ + if(!strcmp(token, "acl_file")){ + if(reload){ + if(config->acl_file){ + _mosquitto_free(config->acl_file); + config->acl_file = NULL; + } + } + if(_conf_parse_string(&token, "acl_file", &config->acl_file, saveptr)) return MOSQ_ERR_INVAL; + }else if(!strcmp(token, "address") || !strcmp(token, "addresses")){ +#ifdef WITH_BRIDGE + if(reload) continue; // FIXME + if(!cur_bridge || cur_bridge->addresses){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid bridge configuration."); + return MOSQ_ERR_INVAL; + } + while((token = strtok_r(NULL, " ", &saveptr))){ + cur_bridge->address_count++; + cur_bridge->addresses = _mosquitto_realloc(cur_bridge->addresses, sizeof(struct bridge_address)*cur_bridge->address_count); + if(!cur_bridge->addresses){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory."); + return MOSQ_ERR_NOMEM; + } + cur_bridge->addresses[cur_bridge->address_count-1].address = token; + } + for(i=0; iaddress_count; i++){ + address = strtok_r(cur_bridge->addresses[i].address, ":", &saveptr); + if(address){ + token = strtok_r(NULL, ":", &saveptr); + if(token){ + port_tmp = atoi(token); + if(port_tmp < 1 || port_tmp > 65535){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid port value (%d).", port_tmp); + return MOSQ_ERR_INVAL; + } + cur_bridge->addresses[i].port = port_tmp; + }else{ + cur_bridge->addresses[i].port = 1883; + } + cur_bridge->addresses[i].address = _mosquitto_strdup(address); + _conf_attempt_resolve(address, "bridge address", MOSQ_LOG_WARNING, "Warning"); + } + } + if(cur_bridge->address_count == 0){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Empty address value in configuration."); + return MOSQ_ERR_INVAL; + } +#else + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: Bridge support not available."); +#endif + }else if(!strcmp(token, "allow_anonymous")){ + if(_conf_parse_bool(&token, "allow_anonymous", &config->allow_anonymous, saveptr)) return MOSQ_ERR_INVAL; + }else if(!strcmp(token, "allow_duplicate_messages")){ + if(_conf_parse_bool(&token, "allow_duplicate_messages", &config->allow_duplicate_messages, saveptr)) return MOSQ_ERR_INVAL; + }else if(!strcmp(token, "allow_zero_length_clientid")){ + if(_conf_parse_bool(&token, "allow_zero_length_clientid", &config->allow_zero_length_clientid, saveptr)) return MOSQ_ERR_INVAL; + }else if(!strncmp(token, "auth_opt_", 9)){ + if(strlen(token) < 12){ + /* auth_opt_ == 9, + one digit key == 10, + one space == 11, + one value == 12 */ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid auth_opt_ config option."); + return MOSQ_ERR_INVAL; + } + key = _mosquitto_strdup(&token[9]); + if(!key){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory"); + return MOSQ_ERR_NOMEM; + }else if(strlen(key) == 0){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid auth_opt_ config option."); + return MOSQ_ERR_INVAL; + } + token += 9+strlen(key)+1; + if(token[0]){ + config->auth_option_count++; + config->auth_options = _mosquitto_realloc(config->auth_options, config->auth_option_count*sizeof(struct mosquitto_auth_opt)); + if(!config->auth_options){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory."); + return MOSQ_ERR_NOMEM; + } + config->auth_options[config->auth_option_count-1].key = key; + config->auth_options[config->auth_option_count-1].value = _mosquitto_strdup(token); + if(!config->auth_options[config->auth_option_count-1].value){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory."); + return MOSQ_ERR_NOMEM; + } + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Empty %s value in configuration.", key); + return MOSQ_ERR_INVAL; + } + }else if(!strcmp(token, "auth_plugin")){ + if(reload) continue; // Auth plugin not currently valid for reloading. + if(_conf_parse_string(&token, "auth_plugin", &config->auth_plugin, saveptr)) return MOSQ_ERR_INVAL; + }else if(!strcmp(token, "auto_id_prefix")){ + if(_conf_parse_string(&token, "auto_id_prefix", &config->auto_id_prefix, saveptr)) return MOSQ_ERR_INVAL; + if(config->auto_id_prefix){ + config->auto_id_prefix_len = strlen(config->auto_id_prefix); + }else{ + config->auto_id_prefix_len = 0; + } + }else if(!strcmp(token, "autosave_interval")){ + if(_conf_parse_int(&token, "autosave_interval", &config->autosave_interval, saveptr)) return MOSQ_ERR_INVAL; + if(config->autosave_interval < 0) config->autosave_interval = 0; + }else if(!strcmp(token, "autosave_on_changes")){ + if(_conf_parse_bool(&token, "autosave_on_changes", &config->autosave_on_changes, saveptr)) return MOSQ_ERR_INVAL; + }else if(!strcmp(token, "bind_address")){ + if(reload) continue; // Listener not valid for reloading. + if(_conf_parse_string(&token, "default listener bind_address", &config->default_listener.host, saveptr)) return MOSQ_ERR_INVAL; + if(_conf_attempt_resolve(config->default_listener.host, "bind_address", MOSQ_LOG_ERR, "Error")){ + return MOSQ_ERR_INVAL; + } + }else if(!strcmp(token, "bridge_cafile")){ +#if defined(WITH_BRIDGE) && defined(WITH_TLS) + if(reload) continue; // FIXME + if(!cur_bridge){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid bridge configuration."); + return MOSQ_ERR_INVAL; + } +#ifdef REAL_WITH_TLS_PSK + if(cur_bridge->tls_psk_identity || cur_bridge->tls_psk){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Cannot use both certificate and psk encryption in a single bridge."); + return MOSQ_ERR_INVAL; + } +#endif + token = strtok_r(NULL, " ", &saveptr); + if(token){ + if(cur_bridge->tls_cafile){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Duplicate bridge_cafile value in bridge configuration."); + return MOSQ_ERR_INVAL; + } + cur_bridge->tls_cafile = _mosquitto_strdup(token); + if(!cur_bridge->tls_cafile){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory"); + return MOSQ_ERR_NOMEM; + } + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Empty bridge_cafile value in configuration."); + return MOSQ_ERR_INVAL; + } +#else + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: Bridge and/or TLS support not available."); +#endif + }else if(!strcmp(token, "bridge_capath")){ +#if defined(WITH_BRIDGE) && defined(WITH_TLS) + if(reload) continue; // FIXME + if(!cur_bridge){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid bridge configuration."); + return MOSQ_ERR_INVAL; + } +#ifdef REAL_WITH_TLS_PSK + if(cur_bridge->tls_psk_identity || cur_bridge->tls_psk){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Cannot use both certificate and psk encryption in a single bridge."); + return MOSQ_ERR_INVAL; + } +#endif + token = strtok_r(NULL, " ", &saveptr); + if(token){ + if(cur_bridge->tls_capath){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Duplicate bridge_capath value in bridge configuration."); + return MOSQ_ERR_INVAL; + } + cur_bridge->tls_capath = _mosquitto_strdup(token); + if(!cur_bridge->tls_capath){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory"); + return MOSQ_ERR_NOMEM; + } + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Empty bridge_capath value in configuration."); + return MOSQ_ERR_INVAL; + } +#else + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: Bridge and/or TLS support not available."); +#endif + }else if(!strcmp(token, "bridge_certfile")){ +#if defined(WITH_BRIDGE) && defined(WITH_TLS) + if(reload) continue; // FIXME + if(!cur_bridge){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid bridge configuration."); + return MOSQ_ERR_INVAL; + } +#ifdef REAL_WITH_TLS_PSK + if(cur_bridge->tls_psk_identity || cur_bridge->tls_psk){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Cannot use both certificate and psk encryption in a single bridge."); + return MOSQ_ERR_INVAL; + } +#endif + token = strtok_r(NULL, " ", &saveptr); + if(token){ + if(cur_bridge->tls_certfile){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Duplicate bridge_certfile value in bridge configuration."); + return MOSQ_ERR_INVAL; + } + cur_bridge->tls_certfile = _mosquitto_strdup(token); + if(!cur_bridge->tls_certfile){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory"); + return MOSQ_ERR_NOMEM; + } + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Empty bridge_certfile value in configuration."); + return MOSQ_ERR_INVAL; + } +#else + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: Bridge and/or TLS support not available."); +#endif + }else if(!strcmp(token, "bridge_identity")){ +#if defined(WITH_BRIDGE) && defined(REAL_WITH_TLS_PSK) + if(reload) continue; // FIXME + if(!cur_bridge){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid bridge configuration."); + return MOSQ_ERR_INVAL; + } + if(cur_bridge->tls_cafile || cur_bridge->tls_capath || cur_bridge->tls_certfile || cur_bridge->tls_keyfile){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Cannot use both certificate and identity encryption in a single bridge."); + return MOSQ_ERR_INVAL; + } + token = strtok_r(NULL, " ", &saveptr); + if(token){ + if(cur_bridge->tls_psk_identity){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Duplicate bridge_identity value in bridge configuration."); + return MOSQ_ERR_INVAL; + } + cur_bridge->tls_psk_identity = _mosquitto_strdup(token); + if(!cur_bridge->tls_psk_identity){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory"); + return MOSQ_ERR_NOMEM; + } + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Empty bridge_identity value in configuration."); + return MOSQ_ERR_INVAL; + } +#else + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: Bridge and/or TLS-PSK support not available."); +#endif + }else if(!strcmp(token, "bridge_insecure")){ +#if defined(WITH_BRIDGE) && defined(WITH_TLS) + if(reload) continue; // FIXME + if(!cur_bridge){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid bridge configuration."); + return MOSQ_ERR_INVAL; + } + if(_conf_parse_bool(&token, "bridge_insecure", &cur_bridge->tls_insecure, saveptr)) return MOSQ_ERR_INVAL; + if(cur_bridge->tls_insecure){ + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: Bridge %s using insecure mode.", cur_bridge->name); + } +#else + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: Bridge and/or TLS-PSK support not available."); +#endif + }else if(!strcmp(token, "bridge_keyfile")){ +#if defined(WITH_BRIDGE) && defined(WITH_TLS) + if(reload) continue; // FIXME + if(!cur_bridge){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid bridge configuration."); + return MOSQ_ERR_INVAL; + } +#ifdef REAL_WITH_TLS_PSK + if(cur_bridge->tls_psk_identity || cur_bridge->tls_psk){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Cannot use both certificate and psk encryption in a single bridge."); + return MOSQ_ERR_INVAL; + } +#endif + token = strtok_r(NULL, " ", &saveptr); + if(token){ + if(cur_bridge->tls_keyfile){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Duplicate bridge_keyfile value in bridge configuration."); + return MOSQ_ERR_INVAL; + } + cur_bridge->tls_keyfile = _mosquitto_strdup(token); + if(!cur_bridge->tls_keyfile){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory"); + return MOSQ_ERR_NOMEM; + } + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Empty bridge_keyfile value in configuration."); + return MOSQ_ERR_INVAL; + } +#else + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: Bridge and/or TLS support not available."); +#endif + }else if(!strcmp(token, "bridge_psk")){ +#if defined(WITH_BRIDGE) && defined(REAL_WITH_TLS_PSK) + if(reload) continue; // FIXME + if(!cur_bridge){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid bridge configuration."); + return MOSQ_ERR_INVAL; + } + if(cur_bridge->tls_cafile || cur_bridge->tls_capath || cur_bridge->tls_certfile || cur_bridge->tls_keyfile){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Cannot use both certificate and psk encryption in a single bridge."); + return MOSQ_ERR_INVAL; + } + token = strtok_r(NULL, " ", &saveptr); + if(token){ + if(cur_bridge->tls_psk){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Duplicate bridge_psk value in bridge configuration."); + return MOSQ_ERR_INVAL; + } + cur_bridge->tls_psk = _mosquitto_strdup(token); + if(!cur_bridge->tls_psk){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory"); + return MOSQ_ERR_NOMEM; + } + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Empty bridge_psk value in configuration."); + return MOSQ_ERR_INVAL; + } +#else + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: Bridge and/or TLS-PSK support not available."); +#endif + }else if(!strcmp(token, "bridge_tls_version")){ +#if defined(WITH_BRIDGE) && defined(WITH_TLS) + if(reload) continue; // FIXME + if(!cur_bridge){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid bridge configuration."); + return MOSQ_ERR_INVAL; + } + token = strtok_r(NULL, " ", &saveptr); + if(token){ + if(cur_bridge->tls_version){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Duplicate bridge_tls_version value in bridge configuration."); + return MOSQ_ERR_INVAL; + } + cur_bridge->tls_version = _mosquitto_strdup(token); + if(!cur_bridge->tls_version){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory"); + return MOSQ_ERR_NOMEM; + } + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Empty bridge_tls_version value in configuration."); + return MOSQ_ERR_INVAL; + } +#else + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: Bridge and/or TLS support not available."); +#endif + }else if(!strcmp(token, "cafile")){ +#if defined(WITH_TLS) + if(reload) continue; // Listeners not valid for reloading. + if(cur_listener->psk_hint){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Cannot use both certificate and psk encryption in a single listener."); + return MOSQ_ERR_INVAL; + } + if(_conf_parse_string(&token, "cafile", &cur_listener->cafile, saveptr)) return MOSQ_ERR_INVAL; +#else + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: TLS support not available."); +#endif + }else if(!strcmp(token, "capath")){ +#ifdef WITH_TLS + if(reload) continue; // Listeners not valid for reloading. + if(_conf_parse_string(&token, "capath", &cur_listener->capath, saveptr)) return MOSQ_ERR_INVAL; +#else + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: TLS support not available."); +#endif + }else if(!strcmp(token, "certfile")){ +#ifdef WITH_TLS + if(reload) continue; // Listeners not valid for reloading. + if(cur_listener->psk_hint){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Cannot use both certificate and psk encryption in a single listener."); + return MOSQ_ERR_INVAL; + } + if(_conf_parse_string(&token, "certfile", &cur_listener->certfile, saveptr)) return MOSQ_ERR_INVAL; +#else + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: TLS support not available."); +#endif + }else if(!strcmp(token, "ciphers")){ +#ifdef WITH_TLS + if(reload) continue; // Listeners not valid for reloading. + if(_conf_parse_string(&token, "ciphers", &cur_listener->ciphers, saveptr)) return MOSQ_ERR_INVAL; +#else + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: TLS support not available."); +#endif + }else if(!strcmp(token, "clientid")){ +#ifdef WITH_BRIDGE + if(reload) continue; // FIXME + if(!cur_bridge){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid bridge configuration."); + return MOSQ_ERR_INVAL; + } + token = strtok_r(NULL, " ", &saveptr); + if(token){ + if(cur_bridge->clientid){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Duplicate clientid value in bridge configuration."); + return MOSQ_ERR_INVAL; + } + cur_bridge->clientid = _mosquitto_strdup(token); + if(!cur_bridge->clientid){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory"); + return MOSQ_ERR_NOMEM; + } + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Empty clientid value in configuration."); + return MOSQ_ERR_INVAL; + } +#else + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: Bridge support not available."); +#endif + }else if(!strcmp(token, "cleansession")){ +#ifdef WITH_BRIDGE + if(reload) continue; // FIXME + if(!cur_bridge){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid bridge configuration."); + return MOSQ_ERR_INVAL; + } + if(_conf_parse_bool(&token, "cleansession", &cur_bridge->clean_session, saveptr)) return MOSQ_ERR_INVAL; +#else + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: Bridge support not available."); +#endif + }else if(!strcmp(token, "clientid_prefixes")){ + if(reload){ + if(config->clientid_prefixes){ + _mosquitto_free(config->clientid_prefixes); + config->clientid_prefixes = NULL; + } + } + if(_conf_parse_string(&token, "clientid_prefixes", &config->clientid_prefixes, saveptr)) return MOSQ_ERR_INVAL; + }else if(!strcmp(token, "connection")){ +#ifdef WITH_BRIDGE + if(reload) continue; // FIXME + token = strtok_r(NULL, " ", &saveptr); + if(token){ + config->bridge_count++; + config->bridges = _mosquitto_realloc(config->bridges, config->bridge_count*sizeof(struct _mqtt3_bridge)); + if(!config->bridges){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory."); + return MOSQ_ERR_NOMEM; + } + cur_bridge = &(config->bridges[config->bridge_count-1]); + memset(cur_bridge, 0, sizeof(struct _mqtt3_bridge)); + cur_bridge->name = _mosquitto_strdup(token); + if(!cur_bridge->name){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory."); + return MOSQ_ERR_NOMEM; + } + cur_bridge->keepalive = 60; + cur_bridge->notifications = true; + cur_bridge->start_type = bst_automatic; + cur_bridge->idle_timeout = 60; + cur_bridge->restart_timeout = 30; + cur_bridge->threshold = 10; + cur_bridge->try_private = true; + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Empty connection value in configuration."); + return MOSQ_ERR_INVAL; + } +#else + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: Bridge support not available."); +#endif + }else if(!strcmp(token, "connection_messages")){ + if(_conf_parse_bool(&token, token, &config->connection_messages, saveptr)) return MOSQ_ERR_INVAL; + }else if(!strcmp(token, "crlfile")){ +#ifdef WITH_TLS + if(reload) continue; // Listeners not valid for reloading. + if(_conf_parse_string(&token, "crlfile", &cur_listener->crlfile, saveptr)) return MOSQ_ERR_INVAL; +#else + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: TLS support not available."); +#endif + }else if(!strcmp(token, "idle_timeout")){ +#ifdef WITH_BRIDGE + if(reload) continue; // FIXME + if(!cur_bridge){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid bridge configuration."); + return MOSQ_ERR_INVAL; + } + if(_conf_parse_int(&token, "idle_timeout", &cur_bridge->idle_timeout, saveptr)) return MOSQ_ERR_INVAL; + if(cur_bridge->idle_timeout < 1){ + _mosquitto_log_printf(NULL, MOSQ_LOG_NOTICE, "idle_timeout interval too low, using 1 second."); + cur_bridge->idle_timeout = 1; + } +#else + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: Bridge support not available."); +#endif + }else if(!strcmp(token, "include_dir")){ + if(level == 0){ + /* Only process include_dir from the main config file. */ + token = strtok_r(NULL, " ", &saveptr); + if(!token){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Empty include_dir value in configuration."); + } +#ifdef WIN32 + snprintf(dirpath, MAX_PATH, "%s\\*.conf", token); + fh = FindFirstFile(dirpath, &find_data); + if(fh == INVALID_HANDLE_VALUE){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Unable to open include_dir '%s'.", token); + return 1; + } + + do{ + len = strlen(token)+1+strlen(find_data.cFileName)+1; + conf_file = _mosquitto_calloc(len+1, sizeof(char)); + if(!conf_file){ + FindClose(fh); + return MOSQ_ERR_NOMEM; + } + snprintf(conf_file, len, "%s\\%s", token, find_data.cFileName); + + rc = _config_read_file(config, reload, conf_file, cr, level+1, &lineno_ext); + if(rc){ + FindClose(fh); + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error found at %s:%d.", conf_file, lineno_ext); + _mosquitto_free(conf_file); + return rc; + } + _mosquitto_free(conf_file); + }while(FindNextFile(fh, &find_data)); + + FindClose(fh); +#else + dh = opendir(token); + if(!dh){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Unable to open include_dir '%s'.", token); + return 1; + } + while((de = readdir(dh)) != NULL){ + if(strlen(de->d_name) > 5){ + if(!strcmp(&de->d_name[strlen(de->d_name)-5], ".conf")){ + len = strlen(token)+1+strlen(de->d_name)+1; + conf_file = _mosquitto_calloc(len+1, sizeof(char)); + if(!conf_file){ + closedir(dh); + return MOSQ_ERR_NOMEM; + } + snprintf(conf_file, len, "%s/%s", token, de->d_name); + + rc = _config_read_file(config, reload, conf_file, cr, level+1, &lineno_ext); + if(rc){ + closedir(dh); + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error found at %s:%d.", conf_file, lineno_ext); + _mosquitto_free(conf_file); + return rc; + } + _mosquitto_free(conf_file); + } + } + } + closedir(dh); +#endif + } + }else if(!strcmp(token, "keepalive_interval")){ +#ifdef WITH_BRIDGE + if(reload) continue; // FIXME + if(!cur_bridge){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid bridge configuration."); + return MOSQ_ERR_INVAL; + } + if(_conf_parse_int(&token, "keepalive_interval", &cur_bridge->keepalive, saveptr)) return MOSQ_ERR_INVAL; + if(cur_bridge->keepalive < 5){ + _mosquitto_log_printf(NULL, MOSQ_LOG_NOTICE, "keepalive interval too low, using 5 seconds."); + cur_bridge->keepalive = 5; + } +#else + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: Bridge support not available."); +#endif + }else if(!strcmp(token, "keyfile")){ +#ifdef WITH_TLS + if(reload) continue; // Listeners not valid for reloading. + if(_conf_parse_string(&token, "keyfile", &cur_listener->keyfile, saveptr)) return MOSQ_ERR_INVAL; +#else + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: TLS support not available."); +#endif + }else if(!strcmp(token, "listener")){ + if(reload) continue; // Listeners not valid for reloading. + token = strtok_r(NULL, " ", &saveptr); + if(token){ + config->listener_count++; + config->listeners = _mosquitto_realloc(config->listeners, sizeof(struct _mqtt3_listener)*config->listener_count); + if(!config->listeners){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory."); + return MOSQ_ERR_NOMEM; + } + port_tmp = atoi(token); + if(port_tmp < 1 || port_tmp > 65535){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid port value (%d).", port_tmp); + return MOSQ_ERR_INVAL; + } + cur_listener = &config->listeners[config->listener_count-1]; + memset(cur_listener, 0, sizeof(struct _mqtt3_listener)); + cur_listener->port = port_tmp; + token = strtok_r(NULL, " ", &saveptr); + if(token){ + cur_listener->host = _mosquitto_strdup(token); + }else{ + cur_listener->host = NULL; + } + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Empty listener value in configuration."); + return MOSQ_ERR_INVAL; + } + }else if(!strcmp(token, "log_dest")){ + token = strtok_r(NULL, " ", &saveptr); + if(token){ + cr->log_dest_set = 1; + if(!strcmp(token, "none")){ + cr->log_dest = MQTT3_LOG_NONE; + }else if(!strcmp(token, "syslog")){ + cr->log_dest |= MQTT3_LOG_SYSLOG; + }else if(!strcmp(token, "stdout")){ + cr->log_dest |= MQTT3_LOG_STDOUT; + }else if(!strcmp(token, "stderr")){ + cr->log_dest |= MQTT3_LOG_STDERR; + }else if(!strcmp(token, "topic")){ + cr->log_dest |= MQTT3_LOG_TOPIC; + }else if(!strcmp(token, "file")){ + cr->log_dest |= MQTT3_LOG_FILE; + if(config->log_fptr || config->log_file){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Duplicate \"log_dest file\" value."); + return MOSQ_ERR_INVAL; + } + /* Get remaining string. */ + token = &token[strlen(token)+1]; + while(token[0] == ' '){ + token++; + } + if(token[0]){ + config->log_file = _mosquitto_strdup(token); + if(!config->log_file){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory"); + return MOSQ_ERR_NOMEM; + } + config->log_fptr = _mosquitto_fopen(config->log_file, "at"); + if(!config->log_fptr){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Unable to open log file %s for writing.", config->log_file); + return MOSQ_ERR_INVAL; + } + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Empty \"log_dest file\" value in configuration."); + return MOSQ_ERR_INVAL; + } + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid log_dest value (%s).", token); + return MOSQ_ERR_INVAL; + } +#if defined(WIN32) || defined(__CYGWIN__) + if(service_handle){ + if(cr->log_dest == MQTT3_LOG_STDOUT || cr->log_dest == MQTT3_LOG_STDERR){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Cannot log to stdout/stderr when running as a Windows service."); + return MOSQ_ERR_INVAL; + } + } +#endif + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Empty log_dest value in configuration."); + return MOSQ_ERR_INVAL; + } + }else if(!strcmp(token, "log_timestamp")){ + if(_conf_parse_bool(&token, token, &config->log_timestamp, saveptr)) return MOSQ_ERR_INVAL; + }else if(!strcmp(token, "log_type")){ + token = strtok_r(NULL, " ", &saveptr); + if(token){ + cr->log_type_set = 1; + if(!strcmp(token, "none")){ + cr->log_type = MOSQ_LOG_NONE; + }else if(!strcmp(token, "information")){ + cr->log_type |= MOSQ_LOG_INFO; + }else if(!strcmp(token, "notice")){ + cr->log_type |= MOSQ_LOG_NOTICE; + }else if(!strcmp(token, "warning")){ + cr->log_type |= MOSQ_LOG_WARNING; + }else if(!strcmp(token, "error")){ + cr->log_type |= MOSQ_LOG_ERR; + }else if(!strcmp(token, "debug")){ + cr->log_type |= MOSQ_LOG_DEBUG; + }else if(!strcmp(token, "subscribe")){ + cr->log_type |= MOSQ_LOG_SUBSCRIBE; + }else if(!strcmp(token, "unsubscribe")){ + cr->log_type |= MOSQ_LOG_UNSUBSCRIBE; + }else if(!strcmp(token, "all")){ + cr->log_type = INT_MAX; + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid log_type value (%s).", token); + return MOSQ_ERR_INVAL; + } + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Empty log_type value in configuration."); + } + }else if(!strcmp(token, "max_connections")){ + if(reload) continue; // Listeners not valid for reloading. + token = strtok_r(NULL, " ", &saveptr); + if(token){ + cur_listener->max_connections = atoi(token); + if(cur_listener->max_connections < 0) cur_listener->max_connections = -1; + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Empty max_connections value in configuration."); + } + }else if(!strcmp(token, "max_inflight_messages")){ + token = strtok_r(NULL, " ", &saveptr); + if(token){ + cr->max_inflight_messages = atoi(token); + if(cr->max_inflight_messages < 0) cr->max_inflight_messages = 0; + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Empty max_inflight_messages value in configuration."); + } + }else if(!strcmp(token, "max_queued_messages")){ + token = strtok_r(NULL, " ", &saveptr); + if(token){ + cr->max_queued_messages = atoi(token); + if(cr->max_queued_messages < 0) cr->max_queued_messages = 0; + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Empty max_queued_messages value in configuration."); + } + }else if(!strcmp(token, "message_size_limit")){ + if(_conf_parse_int(&token, "message_size_limit", &config->message_size_limit, saveptr)) return MOSQ_ERR_INVAL; + if(config->message_size_limit < 0 || config->message_size_limit > MQTT_MAX_PAYLOAD){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid message_size_limit value (%d).", config->message_size_limit); + return MOSQ_ERR_INVAL; + } + }else if(!strcmp(token, "mount_point")){ + if(reload) continue; // Listeners not valid for reloading. + if(config->listener_count == 0){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: You must use create a listener before using the mount_point option in the configuration file."); + return MOSQ_ERR_INVAL; + } + if(_conf_parse_string(&token, "mount_point", &cur_listener->mount_point, saveptr)) return MOSQ_ERR_INVAL; + if(_mosquitto_topic_wildcard_len_check(cur_listener->mount_point) != MOSQ_ERR_SUCCESS){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, + "Error: Invalid mount_point '%s'. Does it contain a wildcard character?", + cur_listener->mount_point); + return MOSQ_ERR_INVAL; + } + }else if(!strcmp(token, "notifications")){ +#ifdef WITH_BRIDGE + if(reload) continue; // FIXME + if(!cur_bridge){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid bridge configuration."); + return MOSQ_ERR_INVAL; + } + if(_conf_parse_bool(&token, "notifications", &cur_bridge->notifications, saveptr)) return MOSQ_ERR_INVAL; +#else + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: Bridge support not available."); +#endif + }else if(!strcmp(token, "notification_topic")){ +#ifdef WITH_BRIDGE + if(reload) continue; // FIXME + if(!cur_bridge){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid bridge configuration."); + return MOSQ_ERR_INVAL; + } + if(_conf_parse_string(&token, "notification_topic", &cur_bridge->notification_topic, saveptr)) return MOSQ_ERR_INVAL; +#else + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: Bridge support not available."); +#endif + }else if(!strcmp(token, "password")){ +#ifdef WITH_BRIDGE + if(reload) continue; // FIXME + if(!cur_bridge){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid bridge configuration."); + return MOSQ_ERR_INVAL; + } + token = strtok_r(NULL, " ", &saveptr); + if(token){ + if(cur_bridge->password){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Duplicate password value in bridge configuration."); + return MOSQ_ERR_INVAL; + } + cur_bridge->password = _mosquitto_strdup(token); + if(!cur_bridge->password){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory"); + return MOSQ_ERR_NOMEM; + } + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Empty password value in configuration."); + return MOSQ_ERR_INVAL; + } +#else + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: Bridge support not available."); +#endif + }else if(!strcmp(token, "password_file")){ + if(reload){ + if(config->password_file){ + _mosquitto_free(config->password_file); + config->password_file = NULL; + } + } + if(_conf_parse_string(&token, "password_file", &config->password_file, saveptr)) return MOSQ_ERR_INVAL; + }else if(!strcmp(token, "persistence") || !strcmp(token, "retained_persistence")){ + if(_conf_parse_bool(&token, token, &config->persistence, saveptr)) return MOSQ_ERR_INVAL; + }else if(!strcmp(token, "persistence_file")){ + if(_conf_parse_string(&token, "persistence_file", &config->persistence_file, saveptr)) return MOSQ_ERR_INVAL; + }else if(!strcmp(token, "persistence_location")){ + if(_conf_parse_string(&token, "persistence_location", &config->persistence_location, saveptr)) return MOSQ_ERR_INVAL; + }else if(!strcmp(token, "persistent_client_expiration")){ + token = strtok_r(NULL, " ", &saveptr); + if(token){ + switch(token[strlen(token)-1]){ + case 'd': + expiration_mult = 86400; + break; + case 'w': + expiration_mult = 86400*7; + break; + case 'm': + expiration_mult = 86400*30; + break; + case 'y': + expiration_mult = 86400*365; + break; + default: + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid persistent_client_expiration duration in configuration."); + return MOSQ_ERR_INVAL; + } + token[strlen(token)-1] = '\0'; + config->persistent_client_expiration = atoi(token)*expiration_mult; + if(config->persistent_client_expiration <= 0){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid persistent_client_expiration duration in configuration."); + return MOSQ_ERR_INVAL; + } + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Empty persistent_client_expiration value in configuration."); + } + }else if(!strcmp(token, "pid_file")){ + if(reload) continue; // pid file not valid for reloading. + if(_conf_parse_string(&token, "pid_file", &config->pid_file, saveptr)) return MOSQ_ERR_INVAL; + }else if(!strcmp(token, "port")){ + if(reload) continue; // Listener not valid for reloading. + if(config->default_listener.port){ + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: Default listener port specified multiple times. Only the latest will be used."); + } + if(_conf_parse_int(&token, "port", &port_tmp, saveptr)) return MOSQ_ERR_INVAL; + if(port_tmp < 1 || port_tmp > 65535){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid port value (%d).", port_tmp); + return MOSQ_ERR_INVAL; + } + config->default_listener.port = port_tmp; + }else if(!strcmp(token, "psk_file")){ +#ifdef REAL_WITH_TLS_PSK + if(reload){ + if(config->psk_file){ + _mosquitto_free(config->psk_file); + config->psk_file = NULL; + } + } + if(_conf_parse_string(&token, "psk_file", &config->psk_file, saveptr)) return MOSQ_ERR_INVAL; +#else + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: TLS/TLS-PSK support not available."); +#endif + }else if(!strcmp(token, "psk_hint")){ +#ifdef REAL_WITH_TLS_PSK + if(reload) continue; // Listeners not valid for reloading. + if(_conf_parse_string(&token, "psk_hint", &cur_listener->psk_hint, saveptr)) return MOSQ_ERR_INVAL; +#else + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: TLS/TLS-PSK support not available."); +#endif + }else if(!strcmp(token, "queue_qos0_messages")){ + if(_conf_parse_bool(&token, token, &config->queue_qos0_messages, saveptr)) return MOSQ_ERR_INVAL; + }else if(!strcmp(token, "require_certificate")){ +#ifdef WITH_TLS + if(reload) continue; // Listeners not valid for reloading. + if(_conf_parse_bool(&token, "require_certificate", &cur_listener->require_certificate, saveptr)) return MOSQ_ERR_INVAL; +#else + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: TLS support not available."); +#endif + }else if(!strcmp(token, "restart_timeout")){ +#ifdef WITH_BRIDGE + if(reload) continue; // FIXME + if(!cur_bridge){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid bridge configuration."); + return MOSQ_ERR_INVAL; + } + if(_conf_parse_int(&token, "restart_timeout", &cur_bridge->restart_timeout, saveptr)) return MOSQ_ERR_INVAL; + if(cur_bridge->restart_timeout < 1){ + _mosquitto_log_printf(NULL, MOSQ_LOG_NOTICE, "restart_timeout interval too low, using 1 second."); + cur_bridge->restart_timeout = 1; + } +#else + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: Bridge support not available."); +#endif + }else if(!strcmp(token, "retry_interval")){ + if(_conf_parse_int(&token, "retry_interval", &config->retry_interval, saveptr)) return MOSQ_ERR_INVAL; + if(config->retry_interval < 1 || config->retry_interval > 3600){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid retry_interval value (%d).", config->retry_interval); + return MOSQ_ERR_INVAL; + } + }else if(!strcmp(token, "round_robin")){ +#ifdef WITH_BRIDGE + if(reload) continue; // FIXME + if(!cur_bridge){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid bridge configuration."); + return MOSQ_ERR_INVAL; + } + if(_conf_parse_bool(&token, "round_robin", &cur_bridge->round_robin, saveptr)) return MOSQ_ERR_INVAL; +#else + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: Bridge support not available."); +#endif + }else if(!strcmp(token, "start_type")){ +#ifdef WITH_BRIDGE + if(reload) continue; // FIXME + if(!cur_bridge){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid bridge configuration."); + return MOSQ_ERR_INVAL; + } + token = strtok_r(NULL, " ", &saveptr); + if(token){ + if(!strcmp(token, "automatic")){ + cur_bridge->start_type = bst_automatic; + }else if(!strcmp(token, "lazy")){ + cur_bridge->start_type = bst_lazy; + }else if(!strcmp(token, "manual")){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Manual start_type not supported."); + return MOSQ_ERR_INVAL; + }else if(!strcmp(token, "once")){ + cur_bridge->start_type = bst_once; + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid start_type value in configuration (%s).", token); + return MOSQ_ERR_INVAL; + } + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Empty start_type value in configuration."); + return MOSQ_ERR_INVAL; + } +#else + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: Bridge support not available."); +#endif + }else if(!strcmp(token, "store_clean_interval")){ + if(_conf_parse_int(&token, "store_clean_interval", &config->store_clean_interval, saveptr)) return MOSQ_ERR_INVAL; + if(config->store_clean_interval < 0 || config->store_clean_interval > 65535){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid store_clean_interval value (%d).", config->store_clean_interval); + return MOSQ_ERR_INVAL; + } + }else if(!strcmp(token, "sys_interval")){ + if(_conf_parse_int(&token, "sys_interval", &config->sys_interval, saveptr)) return MOSQ_ERR_INVAL; + if(config->sys_interval < 0 || config->sys_interval > 65535){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid sys_interval value (%d).", config->sys_interval); + return MOSQ_ERR_INVAL; + } + }else if(!strcmp(token, "threshold")){ +#ifdef WITH_BRIDGE + if(reload) continue; // FIXME + if(!cur_bridge){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid bridge configuration."); + return MOSQ_ERR_INVAL; + } + if(_conf_parse_int(&token, "threshold", &cur_bridge->threshold, saveptr)) return MOSQ_ERR_INVAL; + if(cur_bridge->threshold < 1){ + _mosquitto_log_printf(NULL, MOSQ_LOG_NOTICE, "threshold too low, using 1 message."); + cur_bridge->threshold = 1; + } +#else + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: Bridge support not available."); +#endif + }else if(!strcmp(token, "tls_version")){ +#if defined(WITH_TLS) + if(reload) continue; // Listeners not valid for reloading. + if(_conf_parse_string(&token, "tls_version", &cur_listener->tls_version, saveptr)) return MOSQ_ERR_INVAL; +#else + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: TLS support not available."); +#endif + }else if(!strcmp(token, "topic")){ +#ifdef WITH_BRIDGE + if(reload) continue; // FIXME + if(!cur_bridge){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid bridge configuration."); + return MOSQ_ERR_INVAL; + } + token = strtok_r(NULL, " ", &saveptr); + if(token){ + cur_bridge->topic_count++; + cur_bridge->topics = _mosquitto_realloc(cur_bridge->topics, + sizeof(struct _mqtt3_bridge_topic)*cur_bridge->topic_count); + if(!cur_bridge->topics){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory"); + return MOSQ_ERR_NOMEM; + } + cur_topic = &cur_bridge->topics[cur_bridge->topic_count-1]; + if(!strcmp(token, "\"\"")){ + cur_topic->topic = NULL; + }else{ + cur_topic->topic = _mosquitto_strdup(token); + if(!cur_topic->topic){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory"); + return MOSQ_ERR_NOMEM; + } + } + cur_topic->direction = bd_out; + cur_topic->qos = 0; + cur_topic->local_prefix = NULL; + cur_topic->remote_prefix = NULL; + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Empty topic value in configuration."); + return MOSQ_ERR_INVAL; + } + token = strtok_r(NULL, " ", &saveptr); + if(token){ + if(!strcasecmp(token, "out")){ + cur_topic->direction = bd_out; + }else if(!strcasecmp(token, "in")){ + cur_topic->direction = bd_in; + }else if(!strcasecmp(token, "both")){ + cur_topic->direction = bd_both; + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid bridge topic direction '%s'.", token); + return MOSQ_ERR_INVAL; + } + token = strtok_r(NULL, " ", &saveptr); + if(token){ + cur_topic->qos = atoi(token); + if(cur_topic->qos < 0 || cur_topic->qos > 2){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid bridge QoS level '%s'.", token); + return MOSQ_ERR_INVAL; + } + + token = strtok_r(NULL, " ", &saveptr); + if(token){ + cur_bridge->topic_remapping = true; + if(!strcmp(token, "\"\"")){ + cur_topic->local_prefix = NULL; + }else{ + if(_mosquitto_topic_wildcard_len_check(token) != MOSQ_ERR_SUCCESS){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid bridge topic local prefix '%s'.", token); + return MOSQ_ERR_INVAL; + } + cur_topic->local_prefix = _mosquitto_strdup(token); + if(!cur_topic->local_prefix){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory"); + return MOSQ_ERR_NOMEM; + } + } + + token = strtok_r(NULL, " ", &saveptr); + if(token){ + if(!strcmp(token, "\"\"")){ + cur_topic->remote_prefix = NULL; + }else{ + if(_mosquitto_topic_wildcard_len_check(token) != MOSQ_ERR_SUCCESS){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid bridge topic remote prefix '%s'.", token); + return MOSQ_ERR_INVAL; + } + cur_topic->remote_prefix = _mosquitto_strdup(token); + if(!cur_topic->remote_prefix){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory"); + return MOSQ_ERR_NOMEM; + } + } + } + } + } + } + if(cur_topic->topic == NULL && + (cur_topic->local_prefix == NULL || cur_topic->remote_prefix == NULL)){ + + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid bridge remapping."); + return MOSQ_ERR_INVAL; + } + if(cur_topic->local_prefix){ + if(cur_topic->topic){ + len = strlen(cur_topic->topic) + strlen(cur_topic->local_prefix)+1; + cur_topic->local_topic = _mosquitto_calloc(len+1, sizeof(char)); + if(!cur_topic->local_topic){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory"); + return MOSQ_ERR_NOMEM; + } + snprintf(cur_topic->local_topic, len+1, "%s%s", cur_topic->local_prefix, cur_topic->topic); + }else{ + cur_topic->local_topic = _mosquitto_strdup(cur_topic->local_prefix); + if(!cur_topic->local_topic){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory"); + return MOSQ_ERR_NOMEM; + } + } + }else{ + cur_topic->local_topic = _mosquitto_strdup(cur_topic->topic); + if(!cur_topic->local_topic){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory"); + return MOSQ_ERR_NOMEM; + } + } + + if(cur_topic->remote_prefix){ + if(cur_topic->topic){ + len = strlen(cur_topic->topic) + strlen(cur_topic->remote_prefix)+1; + cur_topic->remote_topic = _mosquitto_calloc(len+1, sizeof(char)); + if(!cur_topic->remote_topic){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory"); + return MOSQ_ERR_NOMEM; + } + snprintf(cur_topic->remote_topic, len, "%s%s", cur_topic->remote_prefix, cur_topic->topic); + }else{ + cur_topic->remote_topic = _mosquitto_strdup(cur_topic->remote_prefix); + if(!cur_topic->remote_topic){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory"); + return MOSQ_ERR_NOMEM; + } + } + }else{ + cur_topic->remote_topic = _mosquitto_strdup(cur_topic->topic); + if(!cur_topic->remote_topic){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory"); + return MOSQ_ERR_NOMEM; + } + } +#else + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: Bridge support not available."); +#endif + }else if(!strcmp(token, "try_private")){ +#ifdef WITH_BRIDGE + if(reload) continue; // FIXME + if(!cur_bridge){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid bridge configuration."); + return MOSQ_ERR_INVAL; + } + if(_conf_parse_bool(&token, "try_private", &cur_bridge->try_private, saveptr)) return MOSQ_ERR_INVAL; +#else + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: Bridge support not available."); +#endif + }else if(!strcmp(token, "upgrade_outgoing_qos")){ + if(_conf_parse_bool(&token, token, &config->upgrade_outgoing_qos, saveptr)) return MOSQ_ERR_INVAL; + }else if(!strcmp(token, "use_identity_as_username")){ +#ifdef WITH_TLS + if(reload) continue; // Listeners not valid for reloading. + if(_conf_parse_bool(&token, "use_identity_as_username", &cur_listener->use_identity_as_username, saveptr)) return MOSQ_ERR_INVAL; +#else + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: TLS support not available."); +#endif + }else if(!strcmp(token, "user")){ + if(reload) continue; // Drop privileges user not valid for reloading. + if(_conf_parse_string(&token, "user", &config->user, saveptr)) return MOSQ_ERR_INVAL; + }else if(!strcmp(token, "username")){ +#ifdef WITH_BRIDGE + if(reload) continue; // FIXME + if(!cur_bridge){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid bridge configuration."); + return MOSQ_ERR_INVAL; + } + token = strtok_r(NULL, " ", &saveptr); + if(token){ + if(cur_bridge->username){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Duplicate username value in bridge configuration."); + return MOSQ_ERR_INVAL; + } + cur_bridge->username = _mosquitto_strdup(token); + if(!cur_bridge->username){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory"); + return MOSQ_ERR_NOMEM; + } + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Empty username value in configuration."); + return MOSQ_ERR_INVAL; + } +#else + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: Bridge support not available."); +#endif + }else if(!strcmp(token, "trace_level") + || !strcmp(token, "ffdc_output") + || !strcmp(token, "max_log_entries") + || !strcmp(token, "trace_output")){ + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: Unsupported rsmb configuration option \"%s\".", token); + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Unknown configuration variable \"%s\".", token); + return MOSQ_ERR_INVAL; + } + } + } + } + fclose(fptr); + + return MOSQ_ERR_SUCCESS; +} + +static int _conf_parse_bool(char **token, const char *name, bool *value, char *saveptr) +{ + *token = strtok_r(NULL, " ", &saveptr); + if(*token){ + if(!strcmp(*token, "false") || !strcmp(*token, "0")){ + *value = false; + }else if(!strcmp(*token, "true") || !strcmp(*token, "1")){ + *value = true; + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid %s value (%s).", name, *token); + } + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Empty %s value in configuration.", name); + return MOSQ_ERR_INVAL; + } + + return MOSQ_ERR_SUCCESS; +} + +static int _conf_parse_int(char **token, const char *name, int *value, char *saveptr) +{ + *token = strtok_r(NULL, " ", &saveptr); + if(*token){ + *value = atoi(*token); + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Empty %s value in configuration.", name); + return MOSQ_ERR_INVAL; + } + + return MOSQ_ERR_SUCCESS; +} + +static int _conf_parse_string(char **token, const char *name, char **value, char *saveptr) +{ + *token = strtok_r(NULL, " ", &saveptr); + if(*token){ + if(*value){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Duplicate %s value in configuration.", name); + return MOSQ_ERR_INVAL; + } + *value = _mosquitto_strdup(*token); + if(!*value){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory"); + return MOSQ_ERR_NOMEM; + } + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Empty %s value in configuration.", name); + return MOSQ_ERR_INVAL; + } + return MOSQ_ERR_SUCCESS; +} diff --git a/src/context.c b/src/context.c new file mode 100644 index 0000000000..08388a0c58 --- /dev/null +++ b/src/context.c @@ -0,0 +1,203 @@ +/* +Copyright (c) 2009-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#include + +#include + +#include +#include +#include + +#include "uthash.h" + +struct mosquitto *mqtt3_context_init(int sock) +{ + struct mosquitto *context; + char address[1024]; + + context = _mosquitto_calloc(1, sizeof(struct mosquitto)); + if(!context) return NULL; + + context->state = mosq_cs_new; + context->sock = sock; + context->last_msg_in = mosquitto_time(); + context->last_msg_out = mosquitto_time(); + context->keepalive = 60; /* Default to 60s */ + context->clean_session = true; + context->disconnect_t = 0; + context->id = NULL; + context->last_mid = 0; + context->will = NULL; + context->username = NULL; + context->password = NULL; + context->listener = NULL; + context->acl_list = NULL; + /* is_bridge records whether this client is a bridge or not. This could be + * done by looking at context->bridge for bridges that we create ourself, + * but incoming bridges need some other way of being recorded. */ + context->is_bridge = false; + + context->in_packet.payload = NULL; + _mosquitto_packet_cleanup(&context->in_packet); + context->out_packet = NULL; + context->current_out_packet = NULL; + + context->address = NULL; + if(sock != -1){ + if(!_mosquitto_socket_get_address(sock, address, 1024)){ + context->address = _mosquitto_strdup(address); + } + if(!context->address){ + /* getpeername and inet_ntop failed and not a bridge */ + _mosquitto_free(context); + return NULL; + } + } + context->bridge = NULL; + context->msgs = NULL; + context->last_msg = NULL; + context->msg_count = 0; + context->msg_count12 = 0; +#ifdef WITH_TLS + context->ssl = NULL; +#endif + + return context; +} + +/* + * This will result in any outgoing packets going unsent. If we're disconnected + * forcefully then it is usually an error condition and shouldn't be a problem, + * but it will mean that CONNACK messages will never get sent for bad protocol + * versions for example. + */ +void mqtt3_context_cleanup(struct mosquitto_db *db, struct mosquitto *context, bool do_free) +{ + struct _mosquitto_packet *packet; + struct mosquitto_client_msg *msg, *next; + struct _clientid_index_hash *find_cih; + + if(!context) return; + + if(context->username){ + _mosquitto_free(context->username); + context->username = NULL; + } + if(context->password){ + _mosquitto_free(context->password); + context->password = NULL; + } +#ifdef WITH_BRIDGE + if(context->bridge){ + if(context->bridge->username){ + context->bridge->username = NULL; + } + if(context->bridge->password){ + context->bridge->password = NULL; + } + } +#endif +#ifdef WITH_TLS + if(context->ssl){ + SSL_free(context->ssl); + context->ssl = NULL; + } +#endif + if(context->sock != -1){ + if(context->listener){ + context->listener->client_count--; + assert(context->listener->client_count >= 0); + } + _mosquitto_socket_close(context); + context->listener = NULL; + } + if(context->clean_session && db){ + mqtt3_subs_clean_session(db, context, &db->subs); + mqtt3_db_messages_delete(context); + } + if(context->address){ + _mosquitto_free(context->address); + context->address = NULL; + } + if(context->id){ + assert(db); /* db can only be NULL here if the client hasn't sent a + CONNECT and hence wouldn't have an id. */ + + // Remove the context's ID from the DB hash + HASH_FIND_STR(db->clientid_index_hash, context->id, find_cih); + if(find_cih){ + // FIXME - internal level debug? _mosquitto_log_printf(NULL, MOSQ_LOG_INFO, "Found id for client \"%s\", their index was %d.", context->id, find_cih->db_context_index); + HASH_DEL(db->clientid_index_hash, find_cih); + _mosquitto_free(find_cih); + }else{ + // FIXME - internal level debug? _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Unable to find id for client \"%s\".", context->id); + } + _mosquitto_free(context->id); + context->id = NULL; + } + _mosquitto_packet_cleanup(&(context->in_packet)); + _mosquitto_packet_cleanup(context->current_out_packet); + context->current_out_packet = NULL; + while(context->out_packet){ + _mosquitto_packet_cleanup(context->out_packet); + packet = context->out_packet; + context->out_packet = context->out_packet->next; + _mosquitto_free(packet); + } + if(context->will){ + if(context->will->topic) _mosquitto_free(context->will->topic); + if(context->will->payload) _mosquitto_free(context->will->payload); + _mosquitto_free(context->will); + context->will = NULL; + } + if(do_free || context->clean_session){ + msg = context->msgs; + while(msg){ + next = msg->next; + msg->store->ref_count--; + _mosquitto_free(msg); + msg = next; + } + context->msgs = NULL; + context->last_msg = NULL; + } + if(do_free){ + _mosquitto_free(context); + } +} + +void mqtt3_context_disconnect(struct mosquitto_db *db, struct mosquitto *ctxt) +{ + if(ctxt->state != mosq_cs_disconnecting && ctxt->will){ + /* Unexpected disconnect, queue the client will. */ + mqtt3_db_messages_easy_queue(db, ctxt, ctxt->will->topic, ctxt->will->qos, ctxt->will->payloadlen, ctxt->will->payload, ctxt->will->retain); + } + if(ctxt->will){ + if(ctxt->will->topic) _mosquitto_free(ctxt->will->topic); + if(ctxt->will->payload) _mosquitto_free(ctxt->will->payload); + _mosquitto_free(ctxt->will); + ctxt->will = NULL; + } + if(ctxt->listener){ + ctxt->listener->client_count--; + assert(ctxt->listener->client_count >= 0); + ctxt->listener = NULL; + } + ctxt->disconnect_t = mosquitto_time(); + _mosquitto_socket_close(ctxt); +} + diff --git a/src/database.c b/src/database.c new file mode 100644 index 0000000000..da3b1c81fc --- /dev/null +++ b/src/database.c @@ -0,0 +1,901 @@ +/* +Copyright (c) 2009-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#include +#include + +#include + +#include +#include +#include +#include + +static int max_inflight = 20; +static int max_queued = 100; +#ifdef WITH_SYS_TREE +extern unsigned long g_msgs_dropped; +#endif + +int mqtt3_db_open(struct mqtt3_config *config, struct mosquitto_db *db) +{ + int rc = 0; + struct _mosquitto_subhier *child; + + if(!config || !db) return MOSQ_ERR_INVAL; + + db->last_db_id = 0; + + db->context_count = 1; + db->contexts = _mosquitto_malloc(sizeof(struct mosquitto*)*db->context_count); + if(!db->contexts) return MOSQ_ERR_NOMEM; + db->contexts[0] = NULL; + // Initialize the hashtable + db->clientid_index_hash = NULL; + + db->subs.next = NULL; + db->subs.subs = NULL; + db->subs.topic = ""; + + child = _mosquitto_malloc(sizeof(struct _mosquitto_subhier)); + if(!child){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory."); + return MOSQ_ERR_NOMEM; + } + child->next = NULL; + child->topic = _mosquitto_strdup(""); + if(!child->topic){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory."); + return MOSQ_ERR_NOMEM; + } + child->subs = NULL; + child->children = NULL; + child->retained = NULL; + db->subs.children = child; + + child = _mosquitto_malloc(sizeof(struct _mosquitto_subhier)); + if(!child){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory."); + return MOSQ_ERR_NOMEM; + } + child->next = NULL; + child->topic = _mosquitto_strdup("$SYS"); + if(!child->topic){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory."); + return MOSQ_ERR_NOMEM; + } + child->subs = NULL; + child->children = NULL; + child->retained = NULL; + db->subs.children->next = child; + + db->unpwd = NULL; + +#ifdef WITH_PERSISTENCE + if(config->persistence && config->persistence_filepath){ + if(mqtt3_db_restore(db)) return 1; + } +#endif + + return rc; +} + +static void subhier_clean(struct _mosquitto_subhier *subhier) +{ + struct _mosquitto_subhier *next; + struct _mosquitto_subleaf *leaf, *nextleaf; + + while(subhier){ + next = subhier->next; + leaf = subhier->subs; + while(leaf){ + nextleaf = leaf->next; + _mosquitto_free(leaf); + leaf = nextleaf; + } + if(subhier->retained){ + subhier->retained->ref_count--; + } + subhier_clean(subhier->children); + if(subhier->topic) _mosquitto_free(subhier->topic); + + _mosquitto_free(subhier); + subhier = next; + } +} + +int mqtt3_db_close(struct mosquitto_db *db) +{ + subhier_clean(db->subs.children); + mqtt3_db_store_clean(db); + + return MOSQ_ERR_SUCCESS; +} + +/* Returns the number of client currently in the database. + * This includes inactive clients. + * Returns 1 on failure (count is NULL) + * Returns 0 on success. + */ +int mqtt3_db_client_count(struct mosquitto_db *db, unsigned int *count, unsigned int *inactive_count) +{ + int i; + + if(!db || !count || !inactive_count) return MOSQ_ERR_INVAL; + + *count = 0; + *inactive_count = 0; + for(i=0; icontext_count; i++){ + if(db->contexts[i]){ + (*count)++; + if(db->contexts[i]->sock == INVALID_SOCKET){ + (*inactive_count)++; + } + } + } + + return MOSQ_ERR_SUCCESS; +} + +static void _message_remove(struct mosquitto *context, struct mosquitto_client_msg **msg, struct mosquitto_client_msg *last) +{ + if(!context || !msg || !(*msg)){ + return; + } + + /* FIXME - it would be nice to be able to remove the stored message here if ref_count==0 */ + (*msg)->store->ref_count--; + if(last){ + last->next = (*msg)->next; + if(!last->next){ + context->last_msg = last; + } + }else{ + context->msgs = (*msg)->next; + if(!context->msgs){ + context->last_msg = NULL; + } + } + context->msg_count--; + if((*msg)->qos > 0){ + context->msg_count12--; + } + _mosquitto_free(*msg); + if(last){ + *msg = last->next; + }else{ + *msg = context->msgs; + } +} + +int mqtt3_db_message_delete(struct mosquitto *context, uint16_t mid, enum mosquitto_msg_direction dir) +{ + struct mosquitto_client_msg *tail, *last = NULL; + int msg_index = 0; + bool deleted = false; + + if(!context) return MOSQ_ERR_INVAL; + + tail = context->msgs; + while(tail){ + msg_index++; + if(tail->state == mosq_ms_queued && msg_index <= max_inflight){ + tail->timestamp = mosquitto_time(); + if(tail->direction == mosq_md_out){ + switch(tail->qos){ + case 0: + tail->state = mosq_ms_publish_qos0; + break; + case 1: + tail->state = mosq_ms_publish_qos1; + break; + case 2: + tail->state = mosq_ms_publish_qos2; + break; + } + }else{ + if(tail->qos == 2){ + tail->state = mosq_ms_wait_for_pubrel; + } + } + } + if(tail->mid == mid && tail->direction == dir){ + msg_index--; + _message_remove(context, &tail, last); + deleted = true; + }else{ + last = tail; + tail = tail->next; + } + if(msg_index > max_inflight && deleted){ + return MOSQ_ERR_SUCCESS; + } + } + + return MOSQ_ERR_SUCCESS; +} + +int mqtt3_db_message_insert(struct mosquitto_db *db, struct mosquitto *context, uint16_t mid, enum mosquitto_msg_direction dir, int qos, bool retain, struct mosquitto_msg_store *stored) +{ + struct mosquitto_client_msg *msg; + enum mosquitto_msg_state state = mosq_ms_invalid; + int rc = 0; + int i; + char **dest_ids; + + assert(stored); + if(!context) return MOSQ_ERR_INVAL; + + /* Check whether we've already sent this message to this client + * for outgoing messages only. + * If retain==true then this is a stale retained message and so should be + * sent regardless. FIXME - this does mean retained messages will received + * multiple times for overlapping subscriptions, although this is only the + * case for SUBSCRIPTION with multiple subs in so is a minor concern. + */ + if(db->config->allow_duplicate_messages == false + && dir == mosq_md_out && retain == false && stored->dest_ids){ + + for(i=0; idest_id_count; i++){ + if(!strcmp(stored->dest_ids[i], context->id)){ + /* We have already sent this message to this client. */ + return MOSQ_ERR_SUCCESS; + } + } + } + if(context->sock == INVALID_SOCKET){ + /* Client is not connected only queue messages with QoS>0. */ + if(qos == 0 && !db->config->queue_qos0_messages){ + if(!context->bridge){ + return 2; + }else{ + if(context->bridge->start_type != bst_lazy){ + return 2; + } + } + } + } + + if(context->sock != INVALID_SOCKET){ + if(qos == 0 || max_inflight == 0 || context->msg_count12 < max_inflight){ + if(dir == mosq_md_out){ + switch(qos){ + case 0: + state = mosq_ms_publish_qos0; + break; + case 1: + state = mosq_ms_publish_qos1; + break; + case 2: + state = mosq_ms_publish_qos2; + break; + } + }else{ + if(qos == 2){ + state = mosq_ms_wait_for_pubrel; + }else{ + return 1; + } + } + }else if(max_queued == 0 || context->msg_count12-max_inflight < max_queued){ + state = mosq_ms_queued; + rc = 2; + }else{ + /* Dropping message due to full queue. */ + if(context->is_dropping == false){ + context->is_dropping = true; + _mosquitto_log_printf(NULL, MOSQ_LOG_NOTICE, + "Outgoing messages are being dropped for client %s.", + context->id); + } +#ifdef WITH_SYS_TREE + g_msgs_dropped++; +#endif + return 2; + } + }else{ + if(max_queued > 0 && context->msg_count12 >= max_queued){ +#ifdef WITH_SYS_TREE + g_msgs_dropped++; +#endif + if(context->is_dropping == false){ + context->is_dropping = true; + _mosquitto_log_printf(NULL, MOSQ_LOG_NOTICE, + "Outgoing messages are being dropped for client %s.", + context->id); + } + return 2; + }else{ + state = mosq_ms_queued; + } + } + assert(state != mosq_ms_invalid); + +#ifdef WITH_PERSISTENCE + if(state == mosq_ms_queued){ + db->persistence_changes++; + } +#endif + + msg = _mosquitto_malloc(sizeof(struct mosquitto_client_msg)); + if(!msg) return MOSQ_ERR_NOMEM; + msg->next = NULL; + msg->store = stored; + msg->store->ref_count++; + msg->mid = mid; + msg->timestamp = mosquitto_time(); + msg->direction = dir; + msg->state = state; + msg->dup = false; + msg->qos = qos; + msg->retain = retain; + if(context->last_msg){ + context->last_msg->next = msg; + context->last_msg = msg; + }else{ + context->msgs = msg; + context->last_msg = msg; + } + context->msg_count++; + if(qos > 0){ + context->msg_count12++; + } + + if(db->config->allow_duplicate_messages == false && dir == mosq_md_out && retain == false){ + /* Record which client ids this message has been sent to so we can avoid duplicates. + * Outgoing messages only. + * If retain==true then this is a stale retained message and so should be + * sent regardless. FIXME - this does mean retained messages will received + * multiple times for overlapping subscriptions, although this is only the + * case for SUBSCRIPTION with multiple subs in so is a minor concern. + */ + dest_ids = _mosquitto_realloc(stored->dest_ids, sizeof(char *)*(stored->dest_id_count+1)); + if(dest_ids){ + stored->dest_ids = dest_ids; + stored->dest_id_count++; + stored->dest_ids[stored->dest_id_count-1] = _mosquitto_strdup(context->id); + if(!stored->dest_ids[stored->dest_id_count-1]){ + return MOSQ_ERR_NOMEM; + } + }else{ + return MOSQ_ERR_NOMEM; + } + } +#ifdef WITH_BRIDGE + if(context->bridge && context->bridge->start_type == bst_lazy + && context->sock == INVALID_SOCKET + && context->msg_count >= context->bridge->threshold){ + + context->bridge->lazy_reconnect = true; + } +#endif + + return rc; +} + +int mqtt3_db_message_update(struct mosquitto *context, uint16_t mid, enum mosquitto_msg_direction dir, enum mosquitto_msg_state state) +{ + struct mosquitto_client_msg *tail; + + tail = context->msgs; + while(tail){ + if(tail->mid == mid && tail->direction == dir){ + tail->state = state; + tail->timestamp = mosquitto_time(); + return MOSQ_ERR_SUCCESS; + } + tail = tail->next; + } + return 1; +} + +int mqtt3_db_messages_delete(struct mosquitto *context) +{ + struct mosquitto_client_msg *tail, *next; + + if(!context) return MOSQ_ERR_INVAL; + + tail = context->msgs; + while(tail){ + /* FIXME - it would be nice to be able to remove the stored message here if rec_count==0 */ + tail->store->ref_count--; + next = tail->next; + _mosquitto_free(tail); + tail = next; + } + context->msgs = NULL; + context->last_msg = NULL; + context->msg_count = 0; + context->msg_count12 = 0; + + return MOSQ_ERR_SUCCESS; +} + +int mqtt3_db_messages_easy_queue(struct mosquitto_db *db, struct mosquitto *context, const char *topic, int qos, uint32_t payloadlen, const void *payload, int retain) +{ + struct mosquitto_msg_store *stored; + char *source_id; + + assert(db); + + if(!topic) return MOSQ_ERR_INVAL; + + if(context){ + source_id = context->id; + }else{ + source_id = ""; + } + if(mqtt3_db_message_store(db, source_id, 0, topic, qos, payloadlen, payload, retain, &stored, 0)) return 1; + + return mqtt3_db_messages_queue(db, source_id, topic, qos, retain, stored); +} + +int mqtt3_db_message_store(struct mosquitto_db *db, const char *source, uint16_t source_mid, const char *topic, int qos, uint32_t payloadlen, const void *payload, int retain, struct mosquitto_msg_store **stored, dbid_t store_id) +{ + struct mosquitto_msg_store *temp; + + assert(db); + assert(stored); + + temp = _mosquitto_malloc(sizeof(struct mosquitto_msg_store)); + if(!temp) return MOSQ_ERR_NOMEM; + + temp->next = db->msg_store; + temp->ref_count = 0; + if(source){ + temp->source_id = _mosquitto_strdup(source); + }else{ + temp->source_id = _mosquitto_strdup(""); + } + if(!temp->source_id){ + _mosquitto_free(temp); + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory."); + return MOSQ_ERR_NOMEM; + } + temp->source_mid = source_mid; + temp->msg.mid = 0; + temp->msg.qos = qos; + temp->msg.retain = retain; + if(topic){ + temp->msg.topic = _mosquitto_strdup(topic); + if(!temp->msg.topic){ + _mosquitto_free(temp->source_id); + _mosquitto_free(temp); + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory."); + return MOSQ_ERR_NOMEM; + } + }else{ + temp->msg.topic = NULL; + } + temp->msg.payloadlen = payloadlen; + if(payloadlen){ + temp->msg.payload = _mosquitto_malloc(sizeof(char)*payloadlen); + if(!temp->msg.payload){ + if(temp->source_id) _mosquitto_free(temp->source_id); + if(temp->msg.topic) _mosquitto_free(temp->msg.topic); + if(temp->msg.payload) _mosquitto_free(temp->msg.payload); + _mosquitto_free(temp); + return MOSQ_ERR_NOMEM; + } + memcpy(temp->msg.payload, payload, sizeof(char)*payloadlen); + }else{ + temp->msg.payload = NULL; + } + + if(!temp->source_id || (payloadlen && !temp->msg.payload)){ + if(temp->source_id) _mosquitto_free(temp->source_id); + if(temp->msg.topic) _mosquitto_free(temp->msg.topic); + if(temp->msg.payload) _mosquitto_free(temp->msg.payload); + _mosquitto_free(temp); + return 1; + } + temp->dest_ids = NULL; + temp->dest_id_count = 0; + db->msg_store_count++; + db->msg_store = temp; + (*stored) = temp; + + if(!store_id){ + temp->db_id = ++db->last_db_id; + }else{ + temp->db_id = store_id; + } + + return MOSQ_ERR_SUCCESS; +} + +int mqtt3_db_message_store_find(struct mosquitto *context, uint16_t mid, struct mosquitto_msg_store **stored) +{ + struct mosquitto_client_msg *tail; + + if(!context) return MOSQ_ERR_INVAL; + + *stored = NULL; + tail = context->msgs; + while(tail){ + if(tail->store->source_mid == mid && tail->direction == mosq_md_in){ + *stored = tail->store; + return MOSQ_ERR_SUCCESS; + } + tail = tail->next; + } + + return 1; +} + +/* Called on reconnect to set outgoing messages to a sensible state and force a + * retry, and to set incoming messages to expect an appropriate retry. */ +int mqtt3_db_message_reconnect_reset(struct mosquitto *context) +{ + struct mosquitto_client_msg *msg; + struct mosquitto_client_msg *prev = NULL; + int count; + + msg = context->msgs; + context->msg_count = 0; + context->msg_count12 = 0; + while(msg){ + context->last_msg = msg; + + context->msg_count++; + if(msg->qos > 0){ + context->msg_count12++; + } + + if(msg->direction == mosq_md_out){ + if(msg->state != mosq_ms_queued){ + switch(msg->qos){ + case 0: + msg->state = mosq_ms_publish_qos0; + break; + case 1: + msg->state = mosq_ms_publish_qos1; + break; + case 2: + if(msg->state == mosq_ms_wait_for_pubcomp){ + msg->state = mosq_ms_resend_pubrel; + }else{ + msg->state = mosq_ms_publish_qos2; + } + break; + } + } + }else{ + if(msg->qos != 2){ + /* Anything next; + } + /* Messages received when the client was disconnected are put + * in the mosq_ms_queued state. If we don't change them to the + * appropriate "publish" state, then the queued messages won't + * get sent until the client next receives a message - and they + * will be sent out of order. + */ + if(context->msgs){ + count = 0; + msg = context->msgs; + while(msg && (max_inflight == 0 || count < max_inflight)){ + if(msg->state == mosq_ms_queued){ + switch(msg->qos){ + case 0: + msg->state = mosq_ms_publish_qos0; + break; + case 1: + msg->state = mosq_ms_publish_qos1; + break; + case 2: + msg->state = mosq_ms_publish_qos2; + break; + } + } + msg = msg->next; + count++; + } + } + + return MOSQ_ERR_SUCCESS; +} + +int mqtt3_db_message_timeout_check(struct mosquitto_db *db, unsigned int timeout) +{ + int i; + time_t threshold; + enum mosquitto_msg_state new_state = mosq_ms_invalid; + struct mosquitto *context; + struct mosquitto_client_msg *msg; + + threshold = mosquitto_time() - timeout; + + for(i=0; icontext_count; i++){ + context = db->contexts[i]; + if(!context) continue; + + msg = context->msgs; + while(msg){ + if(msg->timestamp < threshold && msg->state != mosq_ms_queued){ + switch(msg->state){ + case mosq_ms_wait_for_puback: + new_state = mosq_ms_publish_qos1; + break; + case mosq_ms_wait_for_pubrec: + new_state = mosq_ms_publish_qos2; + break; + case mosq_ms_wait_for_pubrel: + new_state = mosq_ms_send_pubrec; + break; + case mosq_ms_wait_for_pubcomp: + new_state = mosq_ms_resend_pubrel; + break; + default: + break; + } + if(new_state != mosq_ms_invalid){ + msg->timestamp = mosquitto_time(); + msg->state = new_state; + msg->dup = true; + } + } + msg = msg->next; + } + } + + return MOSQ_ERR_SUCCESS; +} + +int mqtt3_db_message_release(struct mosquitto_db *db, struct mosquitto *context, uint16_t mid, enum mosquitto_msg_direction dir) +{ + struct mosquitto_client_msg *tail, *last = NULL; + int qos; + int retain; + char *topic; + char *source_id; + int msg_index = 0; + bool deleted = false; + + if(!context) return MOSQ_ERR_INVAL; + + tail = context->msgs; + while(tail){ + msg_index++; + if(tail->state == mosq_ms_queued && msg_index <= max_inflight){ + tail->timestamp = mosquitto_time(); + if(tail->direction == mosq_md_out){ + switch(tail->qos){ + case 0: + tail->state = mosq_ms_publish_qos0; + break; + case 1: + tail->state = mosq_ms_publish_qos1; + break; + case 2: + tail->state = mosq_ms_publish_qos2; + break; + } + }else{ + if(tail->qos == 2){ + _mosquitto_send_pubrec(context, tail->mid); + tail->state = mosq_ms_wait_for_pubrel; + } + } + } + if(tail->mid == mid && tail->direction == dir){ + qos = tail->store->msg.qos; + topic = tail->store->msg.topic; + retain = tail->retain; + source_id = tail->store->source_id; + + /* topic==NULL should be a QoS 2 message that was + * denied/dropped and is being processed so the client doesn't + * keep resending it. That means we don't send it to other + * clients. */ + if(!topic || !mqtt3_db_messages_queue(db, source_id, topic, qos, retain, tail->store)){ + _message_remove(context, &tail, last); + deleted = true; + }else{ + return 1; + } + }else{ + last = tail; + tail = tail->next; + } + if(msg_index > max_inflight && deleted){ + return MOSQ_ERR_SUCCESS; + } + } + if(deleted){ + return MOSQ_ERR_SUCCESS; + }else{ + return 1; + } +} + +int mqtt3_db_message_write(struct mosquitto *context) +{ + int rc; + struct mosquitto_client_msg *tail, *last = NULL; + uint16_t mid; + int retries; + int retain; + const char *topic; + int qos; + uint32_t payloadlen; + const void *payload; + int msg_count = 0; + + if(!context || context->sock == -1 + || (context->state == mosq_cs_connected && !context->id)){ + return MOSQ_ERR_INVAL; + } + + tail = context->msgs; + while(tail){ + if(tail->direction == mosq_md_in){ + msg_count++; + } + if(tail->state != mosq_ms_queued){ + mid = tail->mid; + retries = tail->dup; + retain = tail->retain; + topic = tail->store->msg.topic; + qos = tail->qos; + payloadlen = tail->store->msg.payloadlen; + payload = tail->store->msg.payload; + + switch(tail->state){ + case mosq_ms_publish_qos0: + rc = _mosquitto_send_publish(context, mid, topic, payloadlen, payload, qos, retain, retries); + if(!rc){ + _message_remove(context, &tail, last); + }else{ + return rc; + } + break; + + case mosq_ms_publish_qos1: + rc = _mosquitto_send_publish(context, mid, topic, payloadlen, payload, qos, retain, retries); + if(!rc){ + tail->timestamp = mosquitto_time(); + tail->dup = 1; /* Any retry attempts are a duplicate. */ + tail->state = mosq_ms_wait_for_puback; + }else{ + return rc; + } + last = tail; + tail = tail->next; + break; + + case mosq_ms_publish_qos2: + rc = _mosquitto_send_publish(context, mid, topic, payloadlen, payload, qos, retain, retries); + if(!rc){ + tail->timestamp = mosquitto_time(); + tail->dup = 1; /* Any retry attempts are a duplicate. */ + tail->state = mosq_ms_wait_for_pubrec; + }else{ + return rc; + } + last = tail; + tail = tail->next; + break; + + case mosq_ms_send_pubrec: + rc = _mosquitto_send_pubrec(context, mid); + if(!rc){ + tail->state = mosq_ms_wait_for_pubrel; + }else{ + return rc; + } + last = tail; + tail = tail->next; + break; + + case mosq_ms_resend_pubrel: + rc = _mosquitto_send_pubrel(context, mid, true); + if(!rc){ + tail->state = mosq_ms_wait_for_pubcomp; + }else{ + return rc; + } + last = tail; + tail = tail->next; + break; + + case mosq_ms_resend_pubcomp: + rc = _mosquitto_send_pubcomp(context, mid); + if(!rc){ + tail->state = mosq_ms_wait_for_pubrel; + }else{ + return rc; + } + last = tail; + tail = tail->next; + break; + + default: + last = tail; + tail = tail->next; + break; + } + }else{ + /* state == mosq_ms_queued */ + if(tail->direction == mosq_md_in && (max_inflight == 0 || msg_count < max_inflight)){ + if(tail->qos == 2){ + tail->state = mosq_ms_send_pubrec; + } + }else{ + last = tail; + tail = tail->next; + } + } + } + + return MOSQ_ERR_SUCCESS; +} + +void mqtt3_db_store_clean(struct mosquitto_db *db) +{ + /* FIXME - this may not be necessary if checks are made when messages are removed. */ + struct mosquitto_msg_store *tail, *last = NULL; + int i; + assert(db); + + tail = db->msg_store; + while(tail){ + if(tail->ref_count == 0){ + if(tail->source_id) _mosquitto_free(tail->source_id); + if(tail->dest_ids){ + for(i=0; idest_id_count; i++){ + if(tail->dest_ids[i]) _mosquitto_free(tail->dest_ids[i]); + } + _mosquitto_free(tail->dest_ids); + } + if(tail->msg.topic) _mosquitto_free(tail->msg.topic); + if(tail->msg.payload) _mosquitto_free(tail->msg.payload); + if(last){ + last->next = tail->next; + _mosquitto_free(tail); + tail = last->next; + }else{ + db->msg_store = tail->next; + _mosquitto_free(tail); + tail = db->msg_store; + } + db->msg_store_count--; + }else{ + last = tail; + tail = tail->next; + } + } +} + +void mqtt3_db_limits_set(int inflight, int queued) +{ + max_inflight = inflight; + max_queued = queued; +} + +void mqtt3_db_vacuum(void) +{ + /* FIXME - reimplement? */ +} + diff --git a/src/db_dump/Makefile b/src/db_dump/Makefile new file mode 100644 index 0000000000..4317c9abe7 --- /dev/null +++ b/src/db_dump/Makefile @@ -0,0 +1,16 @@ +include ../../config.mk + +CFLAGS_FINAL=${CFLAGS} -I.. -I../../lib -I../.. + +.PHONY: all clean reallyclean + +all : mosquitto_db_dump + +mosquitto_db_dump : db_dump.o + ${CC} $^ -o $@ ${LDFLAGS} ${LIBS} + +db_dump.o : db_dump.c ../persist.h + ${CC} $(CFLAGS_FINAL) -c $< -o $@ + +clean : + -rm -f *.o mosquitto_db_dump diff --git a/src/db_dump/db_dump.c b/src/db_dump/db_dump.c new file mode 100644 index 0000000000..3228a8194e --- /dev/null +++ b/src/db_dump/db_dump.c @@ -0,0 +1,399 @@ +/* +Copyright (c) 2010-2012 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +static uint32_t db_version; + +static int _db_client_chunk_restore(struct mosquitto_db *db, FILE *db_fd) +{ + uint16_t i16temp, slen, last_mid; + char *client_id = NULL; + int rc = 0; + time_t disconnect_t; + + read_e(db_fd, &i16temp, sizeof(uint16_t)); + slen = ntohs(i16temp); + if(!slen){ + fprintf(stderr, "Error: Corrupt persistent database."); + fclose(db_fd); + return 1; + } + client_id = calloc(slen+1, sizeof(char)); + if(!client_id){ + fclose(db_fd); + fprintf(stderr, "Error: Out of memory."); + return 1; + } + read_e(db_fd, client_id, slen); + printf("\tClient ID: %s\n", client_id); + + read_e(db_fd, &i16temp, sizeof(uint16_t)); + last_mid = ntohs(i16temp); + printf("\tLast MID: %d\n", last_mid); + + if(db_version == 2){ + disconnect_t = time(NULL); + }else{ + read_e(db_fd, &disconnect_t, sizeof(time_t)); + printf("\tDisconnect time: %ld\n", disconnect_t); + } + + free(client_id); + + return rc; +error: + fprintf(stderr, "Error: %s.", strerror(errno)); + if(db_fd >= 0) fclose(db_fd); + if(client_id) free(client_id); + return 1; +} + +static int _db_client_msg_chunk_restore(struct mosquitto_db *db, FILE *db_fd) +{ + dbid_t i64temp, store_id; + uint16_t i16temp, slen, mid; + uint8_t qos, retain, direction, state, dup; + char *client_id = NULL; + + read_e(db_fd, &i16temp, sizeof(uint16_t)); + slen = ntohs(i16temp); + if(!slen){ + fprintf(stderr, "Error: Corrupt persistent database."); + fclose(db_fd); + return 1; + } + client_id = calloc(slen+1, sizeof(char)); + if(!client_id){ + fclose(db_fd); + fprintf(stderr, "Error: Out of memory."); + return 1; + } + read_e(db_fd, client_id, slen); + printf("\tClient ID: %s\n", client_id); + + read_e(db_fd, &i64temp, sizeof(dbid_t)); + store_id = i64temp; + printf("\tStore ID: %ld\n", (long )store_id); + + read_e(db_fd, &i16temp, sizeof(uint16_t)); + mid = ntohs(i16temp); + printf("\tMID: %d\n", mid); + + read_e(db_fd, &qos, sizeof(uint8_t)); + printf("\tQoS: %d\n", qos); + read_e(db_fd, &retain, sizeof(uint8_t)); + printf("\tRetain: %d\n", retain); + read_e(db_fd, &direction, sizeof(uint8_t)); + printf("\tDirection: %d\n", direction); + read_e(db_fd, &state, sizeof(uint8_t)); + printf("\tState: %d\n", state); + read_e(db_fd, &dup, sizeof(uint8_t)); + printf("\tDup: %d\n", dup); + + free(client_id); + + return 0; +error: + fprintf(stderr, "Error: %s.", strerror(errno)); + if(db_fd >= 0) fclose(db_fd); + if(client_id) free(client_id); + return 1; +} + +static int _db_msg_store_chunk_restore(struct mosquitto_db *db, FILE *db_fd) +{ + dbid_t i64temp, store_id; + uint32_t i32temp, payloadlen; + uint16_t i16temp, slen, source_mid, mid; + uint8_t qos, retain, *payload = NULL; + char *source_id = NULL; + char *topic = NULL; + int rc = 0; + bool binary; + int i; + + read_e(db_fd, &i64temp, sizeof(dbid_t)); + store_id = i64temp; + printf("\tStore ID: %ld\n", (long)store_id); + + read_e(db_fd, &i16temp, sizeof(uint16_t)); + slen = ntohs(i16temp); + if(slen){ + source_id = calloc(slen+1, sizeof(char)); + if(!source_id){ + fclose(db_fd); + fprintf(stderr, "Error: Out of memory."); + return 1; + } + if(fread(source_id, 1, slen, db_fd) != slen){ + fprintf(stderr, "Error: %s.", strerror(errno)); + fclose(db_fd); + free(source_id); + return 1; + } + printf("\tSource ID: %s\n", source_id); + free(source_id); + } + read_e(db_fd, &i16temp, sizeof(uint16_t)); + source_mid = ntohs(i16temp); + printf("\tSource MID: %d\n", source_mid); + + read_e(db_fd, &i16temp, sizeof(uint16_t)); + mid = ntohs(i16temp); + printf("\tMID: %d\n", mid); + + read_e(db_fd, &i16temp, sizeof(uint16_t)); + slen = ntohs(i16temp); + if(slen){ + topic = calloc(slen+1, sizeof(char)); + if(!topic){ + fclose(db_fd); + free(source_id); + fprintf(stderr, "Error: Out of memory."); + return 1; + } + if(fread(topic, 1, slen, db_fd) != slen){ + fprintf(stderr, "Error: %s.", strerror(errno)); + fclose(db_fd); + free(source_id); + free(topic); + return 1; + } + printf("\tTopic: %s\n", topic); + free(topic); + }else{ + fprintf(stderr, "Error: Invalid msg_store chunk when restoring persistent database."); + fclose(db_fd); + free(source_id); + return 1; + } + read_e(db_fd, &qos, sizeof(uint8_t)); + printf("\tQoS: %d\n", qos); + read_e(db_fd, &retain, sizeof(uint8_t)); + printf("\tRetain: %d\n", retain); + + read_e(db_fd, &i32temp, sizeof(uint32_t)); + payloadlen = ntohl(i32temp); + printf("\tPayload Length: %d\n", payloadlen); + + if(payloadlen){ + payload = malloc(payloadlen+1); + if(!payload){ + fclose(db_fd); + free(source_id); + free(topic); + fprintf(stderr, "Error: Out of memory."); + return 1; + } + memset(payload, 0, payloadlen+1); + if(fread(payload, 1, payloadlen, db_fd) != payloadlen){ + fprintf(stderr, "Error: %s.", strerror(errno)); + fclose(db_fd); + free(source_id); + free(topic); + free(payload); + return 1; + } + binary = false; + for(i=0; i= 0) fclose(db_fd); + if(source_id) free(source_id); + if(topic) free(topic); + return 1; +} + +static int _db_retain_chunk_restore(struct mosquitto_db *db, FILE *db_fd) +{ + dbid_t i64temp, store_id; + + if(fread(&i64temp, sizeof(dbid_t), 1, db_fd) != 1){ + fprintf(stderr, "Error: %s.", strerror(errno)); + fclose(db_fd); + return 1; + } + store_id = i64temp; + printf("\tStore ID: %ld\n", (long int)store_id); + return 0; +} + +static int _db_sub_chunk_restore(struct mosquitto_db *db, FILE *db_fd) +{ + uint16_t i16temp, slen; + uint8_t qos; + char *client_id; + char *topic; + int rc = 0; + + read_e(db_fd, &i16temp, sizeof(uint16_t)); + slen = ntohs(i16temp); + client_id = calloc(slen+1, sizeof(char)); + if(!client_id){ + fclose(db_fd); + fprintf(stderr, "Error: Out of memory."); + return 1; + } + read_e(db_fd, client_id, slen); + printf("\tClient ID: %s\n", client_id); + read_e(db_fd, &i16temp, sizeof(uint16_t)); + slen = ntohs(i16temp); + topic = calloc(slen+1, sizeof(char)); + if(!topic){ + fclose(db_fd); + fprintf(stderr, "Error: Out of memory."); + free(client_id); + return 1; + } + read_e(db_fd, topic, slen); + printf("\tTopic: %s\n", topic); + read_e(db_fd, &qos, sizeof(uint8_t)); + printf("\tQoS: %d\n", qos); + free(client_id); + free(topic); + + return rc; +error: + fprintf(stderr, "Error: %s.", strerror(errno)); + if(db_fd >= 0) fclose(db_fd); + return 1; +} + +int main(int argc, char *argv[]) +{ + FILE *fd; + char header[15]; + int rc = 0; + uint32_t crc; + dbid_t i64temp; + uint32_t i32temp, length; + uint16_t i16temp, chunk; + uint8_t i8temp; + ssize_t rlen; + struct mosquitto_db db; + + if(argc != 2){ + fprintf(stderr, "Usage: db_dump \n"); + return 1; + } + memset(&db, 0, sizeof(struct mosquitto_db)); + fd = fopen(argv[1], "rb"); + if(!fd) return 0; + read_e(fd, &header, 15); + if(!memcmp(header, magic, 15)){ + printf("Mosquitto DB dump\n"); + // Restore DB as normal + read_e(fd, &crc, sizeof(uint32_t)); + printf("CRC: %d\n", crc); + read_e(fd, &i32temp, sizeof(uint32_t)); + db_version = ntohl(i32temp); + printf("DB version: %d\n", db_version); + + while(rlen = fread(&i16temp, sizeof(uint16_t), 1, fd), rlen == 1){ + chunk = ntohs(i16temp); + read_e(fd, &i32temp, sizeof(uint32_t)); + length = ntohl(i32temp); + switch(chunk){ + case DB_CHUNK_CFG: + printf("DB_CHUNK_CFG:\n"); + printf("\tLength: %d\n", length); + read_e(fd, &i8temp, sizeof(uint8_t)); // shutdown + printf("\tShutdown: %d\n", i8temp); + read_e(fd, &i8temp, sizeof(uint8_t)); // sizeof(dbid_t) + printf("\tDB ID size: %d\n", i8temp); + if(i8temp != sizeof(dbid_t)){ + fprintf(stderr, "Error: Incompatible database configuration (dbid size is %d bytes, expected %ld)", + i8temp, sizeof(dbid_t)); + fclose(fd); + return 1; + } + read_e(fd, &i64temp, sizeof(dbid_t)); + printf("\tLast DB ID: %ld\n", (long)i64temp); + break; + + case DB_CHUNK_MSG_STORE: + printf("DB_CHUNK_MSG_STORE:\n"); + printf("\tLength: %d\n", length); + if(_db_msg_store_chunk_restore(&db, fd)) return 1; + break; + + case DB_CHUNK_CLIENT_MSG: + printf("DB_CHUNK_CLIENT_MSG:\n"); + printf("\tLength: %d\n", length); + if(_db_client_msg_chunk_restore(&db, fd)) return 1; + break; + + case DB_CHUNK_RETAIN: + printf("DB_CHUNK_RETAIN:\n"); + printf("\tLength: %d\n", length); + if(_db_retain_chunk_restore(&db, fd)) return 1; + break; + + case DB_CHUNK_SUB: + printf("DB_CHUNK_SUB:\n"); + printf("\tLength: %d\n", length); + if(_db_sub_chunk_restore(&db, fd)) return 1; + break; + + case DB_CHUNK_CLIENT: + printf("DB_CHUNK_CLIENT:\n"); + printf("\tLength: %d\n", length); + if(_db_client_chunk_restore(&db, fd)) return 1; + break; + + default: + fprintf(stderr, "Warning: Unsupported chunk \"%d\" in persistent database file. Ignoring.", chunk); + fseek(fd, length, SEEK_CUR); + break; + } + } + if(rlen < 0) goto error; + }else{ + fprintf(stderr, "Error: Unrecognised file format."); + rc = 1; + } + + fclose(fd); + + return rc; +error: + fprintf(stderr, "Error: %s.", strerror(errno)); + if(fd >= 0) fclose(fd); + return 1; +} + diff --git a/src/lib_load.h b/src/lib_load.h new file mode 100644 index 0000000000..76fb8aada8 --- /dev/null +++ b/src/lib_load.h @@ -0,0 +1,37 @@ +/* +Copyright (c) 2012-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#ifndef LIB_LOAD_H +#define LIB_LOAD_H + +#ifdef WIN32 +# include +#else +# include +#endif + +#ifdef WIN32 +# define LIB_LOAD(A) LoadLibrary(A) +# define LIB_CLOSE(A) FreeLibrary(A) +# define LIB_SYM(HANDLE, SYM) GetProcAddress(HANDLE, SYM) +#else +# define LIB_LOAD(A) dlopen(A, RTLD_NOW|RTLD_GLOBAL) +# define LIB_CLOSE(A) dlclose(A) +# define LIB_SYM(HANDLE, SYM) dlsym(HANDLE, SYM) +#endif + +#define LIB_SYM_EASY(MEMBER, HANDLE, SYM) if(!(MEMBER = LIB_SYM(HANDLE, SYM)) return 1 +#endif diff --git a/src/logging.c b/src/logging.c new file mode 100644 index 0000000000..56f37e0202 --- /dev/null +++ b/src/logging.c @@ -0,0 +1,222 @@ +/* +Copyright (c) 2009-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ +#include +#include +#include +#ifndef WIN32 +#include +#endif + +#ifndef CMAKE +#include +#endif + +#include +#include + +extern struct mosquitto_db int_db; + +#ifdef WIN32 +HANDLE syslog_h; +#endif + +/* Options for logging should be: + * + * A combination of: + * Via syslog + * To a file + * To stdout/stderr + * To topics + */ + +/* Give option of logging timestamp. + * Logging pid. + */ +static int log_destinations = MQTT3_LOG_STDERR; +static int log_priorities = MOSQ_LOG_ERR | MOSQ_LOG_WARNING | MOSQ_LOG_NOTICE | MOSQ_LOG_INFO; + +int mqtt3_log_init(int priorities, int destinations) +{ + int rc = 0; + + log_priorities = priorities; + log_destinations = destinations; + + if(log_destinations & MQTT3_LOG_SYSLOG){ +#ifndef WIN32 + openlog("mosquitto", LOG_PID, LOG_DAEMON); +#else + syslog_h = OpenEventLog(NULL, "mosquitto"); +#endif + } + + return rc; +} + +int mqtt3_log_close(void) +{ + if(log_destinations & MQTT3_LOG_SYSLOG){ +#ifndef WIN32 + closelog(); +#else + CloseEventLog(syslog_h); +#endif + } + /* FIXME - do something for all destinations! */ + + return MOSQ_ERR_SUCCESS; +} + +int _mosquitto_log_printf(struct mosquitto *mosq, int priority, const char *fmt, ...) +{ + va_list va; + char *s; + char *st; + int len; +#ifdef WIN32 + char *sp; +#endif + const char *topic; + int syslog_priority; + time_t now = time(NULL); + + if((log_priorities & priority) && log_destinations != MQTT3_LOG_NONE){ + switch(priority){ + case MOSQ_LOG_SUBSCRIBE: + topic = "$SYS/broker/log/M/subscribe"; +#ifndef WIN32 + syslog_priority = LOG_NOTICE; +#else + syslog_priority = EVENTLOG_INFORMATION_TYPE; +#endif + break; + case MOSQ_LOG_UNSUBSCRIBE: + topic = "$SYS/broker/log/M/unsubscribe"; +#ifndef WIN32 + syslog_priority = LOG_NOTICE; +#else + syslog_priority = EVENTLOG_INFORMATION_TYPE; +#endif + break; + case MOSQ_LOG_DEBUG: + topic = "$SYS/broker/log/D"; +#ifndef WIN32 + syslog_priority = LOG_DEBUG; +#else + syslog_priority = EVENTLOG_INFORMATION_TYPE; +#endif + break; + case MOSQ_LOG_ERR: + topic = "$SYS/broker/log/E"; +#ifndef WIN32 + syslog_priority = LOG_ERR; +#else + syslog_priority = EVENTLOG_ERROR_TYPE; +#endif + break; + case MOSQ_LOG_WARNING: + topic = "$SYS/broker/log/W"; +#ifndef WIN32 + syslog_priority = LOG_WARNING; +#else + syslog_priority = EVENTLOG_WARNING_TYPE; +#endif + break; + case MOSQ_LOG_NOTICE: + topic = "$SYS/broker/log/N"; +#ifndef WIN32 + syslog_priority = LOG_NOTICE; +#else + syslog_priority = EVENTLOG_INFORMATION_TYPE; +#endif + break; + case MOSQ_LOG_INFO: + topic = "$SYS/broker/log/I"; +#ifndef WIN32 + syslog_priority = LOG_INFO; +#else + syslog_priority = EVENTLOG_INFORMATION_TYPE; +#endif + break; + default: + topic = "$SYS/broker/log/E"; +#ifndef WIN32 + syslog_priority = LOG_ERR; +#else + syslog_priority = EVENTLOG_ERROR_TYPE; +#endif + } + len = strlen(fmt) + 500; + s = _mosquitto_malloc(len*sizeof(char)); + if(!s) return MOSQ_ERR_NOMEM; + + va_start(va, fmt); + vsnprintf(s, len, fmt, va); + va_end(va); + s[len-1] = '\0'; /* Ensure string is null terminated. */ + + if(log_destinations & MQTT3_LOG_STDOUT){ + if(int_db.config && int_db.config->log_timestamp){ + fprintf(stdout, "%d: %s\n", (int)now, s); + }else{ + fprintf(stdout, "%s\n", s); + } + fflush(stdout); + } + if(log_destinations & MQTT3_LOG_STDERR){ + if(int_db.config && int_db.config->log_timestamp){ + fprintf(stderr, "%d: %s\n", (int)now, s); + }else{ + fprintf(stderr, "%s\n", s); + } + fflush(stderr); + } + if(log_destinations & MQTT3_LOG_FILE && int_db.config->log_fptr){ + if(int_db.config && int_db.config->log_timestamp){ + fprintf(int_db.config->log_fptr, "%d: %s\n", (int)now, s); + }else{ + fprintf(int_db.config->log_fptr, "%s\n", s); + } + } + if(log_destinations & MQTT3_LOG_SYSLOG){ +#ifndef WIN32 + syslog(syslog_priority, "%s", s); +#else + sp = (char *)s; + ReportEvent(syslog_h, syslog_priority, 0, 0, NULL, 1, 0, &sp, NULL); +#endif + } + if(log_destinations & MQTT3_LOG_TOPIC && priority != MOSQ_LOG_DEBUG){ + if(int_db.config && int_db.config->log_timestamp){ + len += 30; + st = _mosquitto_malloc(len*sizeof(char)); + if(!st){ + _mosquitto_free(s); + return MOSQ_ERR_NOMEM; + } + snprintf(st, len, "%d: %s", (int)now, s); + mqtt3_db_messages_easy_queue(&int_db, NULL, topic, 2, strlen(st), st, 0); + _mosquitto_free(st); + }else{ + mqtt3_db_messages_easy_queue(&int_db, NULL, topic, 2, strlen(s), s, 0); + } + } + _mosquitto_free(s); + } + + return MOSQ_ERR_SUCCESS; +} + diff --git a/src/loop.c b/src/loop.c new file mode 100644 index 0000000000..cfe17f211e --- /dev/null +++ b/src/loop.c @@ -0,0 +1,363 @@ +/* +Copyright (c) 2009-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#define _GNU_SOURCE + +#include + +#include +#ifndef WIN32 +#include +#else +#include +#include +#include +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include + +extern bool flag_reload; +#ifdef WITH_PERSISTENCE +extern bool flag_db_backup; +#endif +extern bool flag_tree_print; +extern int run; +#ifdef WITH_SYS_TREE +extern int g_clients_expired; +#endif + +static void loop_handle_errors(struct mosquitto_db *db, struct pollfd *pollfds); +static void loop_handle_reads_writes(struct mosquitto_db *db, struct pollfd *pollfds); + +int mosquitto_main_loop(struct mosquitto_db *db, int *listensock, int listensock_count, int listener_max) +{ + time_t start_time = mosquitto_time(); + time_t last_backup = mosquitto_time(); + time_t last_store_clean = mosquitto_time(); + time_t now; + int time_count; + int fdcount; +#ifndef WIN32 + sigset_t sigblock, origsig; +#endif + int i; + struct pollfd *pollfds = NULL; + int pollfd_count = 0; + int pollfd_index; +#ifdef WITH_BRIDGE + int bridge_sock; + int rc; +#endif + +#ifndef WIN32 + sigemptyset(&sigblock); + sigaddset(&sigblock, SIGINT); +#endif + + while(run){ +#ifdef WITH_SYS_TREE + if(db->config->sys_interval > 0){ + mqtt3_db_sys_update(db, db->config->sys_interval, start_time); + } +#endif + + if(listensock_count + db->context_count > pollfd_count || !pollfds){ + pollfd_count = listensock_count + db->context_count; + pollfds = _mosquitto_realloc(pollfds, sizeof(struct pollfd)*pollfd_count); + if(!pollfds){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory."); + return MOSQ_ERR_NOMEM; + } + } + + memset(pollfds, -1, sizeof(struct pollfd)*pollfd_count); + + pollfd_index = 0; + for(i=0; icontext_count; i++){ + if(db->contexts[i]){ + if(time_count > 0){ + time_count--; + }else{ + time_count = 1000; + now = mosquitto_time(); + } + db->contexts[i]->pollfd_index = -1; + + if(db->contexts[i]->sock != INVALID_SOCKET){ +#ifdef WITH_BRIDGE + if(db->contexts[i]->bridge){ + _mosquitto_check_keepalive(db->contexts[i]); + if(db->contexts[i]->bridge->round_robin == false + && db->contexts[i]->bridge->cur_address != 0 + && now > db->contexts[i]->bridge->primary_retry){ + + /* FIXME - this should be non-blocking */ + if(_mosquitto_try_connect(db->contexts[i]->bridge->addresses[0].address, db->contexts[i]->bridge->addresses[0].port, &bridge_sock, NULL, true) == MOSQ_ERR_SUCCESS){ + COMPAT_CLOSE(bridge_sock); + _mosquitto_socket_close(db->contexts[i]); + db->contexts[i]->bridge->cur_address = db->contexts[i]->bridge->address_count-1; + } + } + } +#endif + + /* Local bridges never time out in this fashion. */ + if(!(db->contexts[i]->keepalive) + || db->contexts[i]->bridge + || now - db->contexts[i]->last_msg_in < (time_t)(db->contexts[i]->keepalive)*3/2){ + + if(mqtt3_db_message_write(db->contexts[i]) == MOSQ_ERR_SUCCESS){ + pollfds[pollfd_index].fd = db->contexts[i]->sock; + pollfds[pollfd_index].events = POLLIN; + pollfds[pollfd_index].revents = 0; + if(db->contexts[i]->current_out_packet){ + pollfds[pollfd_index].events |= POLLOUT; + } + db->contexts[i]->pollfd_index = pollfd_index; + pollfd_index++; + }else{ + mqtt3_context_disconnect(db, db->contexts[i]); + } + }else{ + if(db->config->connection_messages == true){ + _mosquitto_log_printf(NULL, MOSQ_LOG_NOTICE, "Client %s has exceeded timeout, disconnecting.", db->contexts[i]->id); + } + /* Client has exceeded keepalive*1.5 */ + mqtt3_context_disconnect(db, db->contexts[i]); + } + }else{ +#ifdef WITH_BRIDGE + if(db->contexts[i]->bridge){ + /* Want to try to restart the bridge connection */ + if(!db->contexts[i]->bridge->restart_t){ + db->contexts[i]->bridge->restart_t = now+db->contexts[i]->bridge->restart_timeout; + db->contexts[i]->bridge->cur_address++; + if(db->contexts[i]->bridge->cur_address == db->contexts[i]->bridge->address_count){ + db->contexts[i]->bridge->cur_address = 0; + } + if(db->contexts[i]->bridge->round_robin == false && db->contexts[i]->bridge->cur_address != 0){ + db->contexts[i]->bridge->primary_retry = now + 5; + } + }else{ + if(db->contexts[i]->bridge->start_type == bst_lazy && db->contexts[i]->bridge->lazy_reconnect){ + rc = mqtt3_bridge_connect(db, db->contexts[i]); + if(rc){ + db->contexts[i]->bridge->cur_address++; + if(db->contexts[i]->bridge->cur_address == db->contexts[i]->bridge->address_count){ + db->contexts[i]->bridge->cur_address = 0; + } + } + } + if(db->contexts[i]->bridge->start_type == bst_automatic && now > db->contexts[i]->bridge->restart_t){ + db->contexts[i]->bridge->restart_t = 0; + rc = mqtt3_bridge_connect(db, db->contexts[i]); + if(rc == MOSQ_ERR_SUCCESS){ + pollfds[pollfd_index].fd = db->contexts[i]->sock; + pollfds[pollfd_index].events = POLLIN; + pollfds[pollfd_index].revents = 0; + if(db->contexts[i]->current_out_packet){ + pollfds[pollfd_index].events |= POLLOUT; + } + db->contexts[i]->pollfd_index = pollfd_index; + pollfd_index++; + }else{ + /* Retry later. */ + db->contexts[i]->bridge->restart_t = now+db->contexts[i]->bridge->restart_timeout; + + db->contexts[i]->bridge->cur_address++; + if(db->contexts[i]->bridge->cur_address == db->contexts[i]->bridge->address_count){ + db->contexts[i]->bridge->cur_address = 0; + } + } + } + } + }else{ +#endif + if(db->contexts[i]->clean_session == true){ + mqtt3_context_cleanup(db, db->contexts[i], true); + db->contexts[i] = NULL; + }else if(db->config->persistent_client_expiration > 0){ + /* This is a persistent client, check to see if the + * last time it connected was longer than + * persistent_client_expiration seconds ago. If so, + * expire it and clean up. + */ + if(now > db->contexts[i]->disconnect_t+db->config->persistent_client_expiration){ + _mosquitto_log_printf(NULL, MOSQ_LOG_NOTICE, "Expiring persistent client %s due to timeout.", db->contexts[i]->id); +#ifdef WITH_SYS_TREE + g_clients_expired++; +#endif + db->contexts[i]->clean_session = true; + mqtt3_context_cleanup(db, db->contexts[i], true); + db->contexts[i] = NULL; + } + } +#ifdef WITH_BRIDGE + } +#endif + } + } + } + + mqtt3_db_message_timeout_check(db, db->config->retry_interval); + +#ifndef WIN32 + sigprocmask(SIG_SETMASK, &sigblock, &origsig); + fdcount = poll(pollfds, pollfd_index, 100); + sigprocmask(SIG_SETMASK, &origsig, NULL); +#else + fdcount = WSAPoll(pollfds, pollfd_index, 100); +#endif + if(fdcount == -1){ + loop_handle_errors(db, pollfds); + }else{ + loop_handle_reads_writes(db, pollfds); + + for(i=0; iconfig->persistence && db->config->autosave_interval){ + if(db->config->autosave_on_changes){ + if(db->persistence_changes > db->config->autosave_interval){ + mqtt3_db_backup(db, false, false); + db->persistence_changes = 0; + } + }else{ + if(last_backup + db->config->autosave_interval < mosquitto_time()){ + mqtt3_db_backup(db, false, false); + last_backup = mosquitto_time(); + } + } + } +#endif + if(!db->config->store_clean_interval || last_store_clean + db->config->store_clean_interval < mosquitto_time()){ + mqtt3_db_store_clean(db); + last_store_clean = mosquitto_time(); + } +#ifdef WITH_PERSISTENCE + if(flag_db_backup){ + mqtt3_db_backup(db, false, false); + flag_db_backup = false; + } +#endif + if(flag_reload){ + _mosquitto_log_printf(NULL, MOSQ_LOG_INFO, "Reloading config."); + mqtt3_config_read(db->config, true); + mosquitto_security_cleanup(db, true); + mosquitto_security_init(db, true); + mosquitto_security_apply(db); + mqtt3_log_init(db->config->log_type, db->config->log_dest); + flag_reload = false; + } + if(flag_tree_print){ + mqtt3_sub_tree_print(&db->subs, 0); + flag_tree_print = false; + } + } + + if(pollfds) _mosquitto_free(pollfds); + return MOSQ_ERR_SUCCESS; +} + +static void do_disconnect(struct mosquitto_db *db, int context_index) +{ + if(db->config->connection_messages == true){ + if(db->contexts[context_index]->state != mosq_cs_disconnecting){ + _mosquitto_log_printf(NULL, MOSQ_LOG_NOTICE, "Socket error on client %s, disconnecting.", db->contexts[context_index]->id); + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_NOTICE, "Client %s disconnected.", db->contexts[context_index]->id); + } + } + mqtt3_context_disconnect(db, db->contexts[context_index]); +} + +/* Error ocurred, probably an fd has been closed. + * Loop through and check them all. + */ +static void loop_handle_errors(struct mosquitto_db *db, struct pollfd *pollfds) +{ + int i; + + for(i=0; icontext_count; i++){ + if(db->contexts[i] && db->contexts[i]->sock != INVALID_SOCKET){ + if(pollfds[db->contexts[i]->pollfd_index].revents & (POLLERR | POLLNVAL)){ + do_disconnect(db, i); + } + } + } +} + +static void loop_handle_reads_writes(struct mosquitto_db *db, struct pollfd *pollfds) +{ + int i; + + for(i=0; icontext_count; i++){ + if(db->contexts[i] && db->contexts[i]->sock != INVALID_SOCKET){ + assert(pollfds[db->contexts[i]->pollfd_index].fd == db->contexts[i]->sock); +#ifdef WITH_TLS + if(pollfds[db->contexts[i]->pollfd_index].revents & POLLOUT || + db->contexts[i]->want_write || + (db->contexts[i]->ssl && db->contexts[i]->state == mosq_cs_new)){ +#else + if(pollfds[db->contexts[i]->pollfd_index].revents & POLLOUT){ +#endif + if(_mosquitto_packet_write(db->contexts[i])){ + do_disconnect(db, i); + } + } + } + if(db->contexts[i] && db->contexts[i]->sock != INVALID_SOCKET){ + assert(pollfds[db->contexts[i]->pollfd_index].fd == db->contexts[i]->sock); +#ifdef WITH_TLS + if(pollfds[db->contexts[i]->pollfd_index].revents & POLLIN || + (db->contexts[i]->ssl && db->contexts[i]->state == mosq_cs_new)){ +#else + if(pollfds[db->contexts[i]->pollfd_index].revents & POLLIN){ +#endif + if(_mosquitto_packet_read(db, db->contexts[i])){ + do_disconnect(db, i); + } + } + } + if(db->contexts[i] && db->contexts[i]->sock != INVALID_SOCKET){ + if(pollfds[db->contexts[i]->pollfd_index].revents & (POLLERR | POLLNVAL)){ + do_disconnect(db, i); + } + } + } +} + diff --git a/src/mosquitto.c b/src/mosquitto.c new file mode 100644 index 0000000000..916fe515b4 --- /dev/null +++ b/src/mosquitto.c @@ -0,0 +1,389 @@ +/* +Copyright (c) 2009-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#include + +#ifndef WIN32 +/* For initgroups() */ +# define _BSD_SOURCE +# include +# include +#endif + +#ifndef WIN32 +#include +#else +#include +#include +#include +#endif + +#include +#include +#include +#include +#ifdef WITH_WRAP +#include +#endif + +#include +#include +#include "util_mosq.h" + +struct mosquitto_db int_db; + +bool flag_reload = false; +#ifdef WITH_PERSISTENCE +bool flag_db_backup = false; +#endif +bool flag_tree_print = false; +int run; +#ifdef WITH_WRAP +#include +int allow_severity = LOG_INFO; +int deny_severity = LOG_INFO; +#endif + +int drop_privileges(struct mqtt3_config *config); +void handle_sigint(int signal); +void handle_sigusr1(int signal); +void handle_sigusr2(int signal); + +struct mosquitto_db *_mosquitto_get_db(void) +{ + return &int_db; +} + +/* mosquitto shouldn't run as root. + * This function will attempt to change to an unprivileged user and group if + * running as root. The user is given in config->user. + * Returns 1 on failure (unknown user, setuid/setgid failure) + * Returns 0 on success. + * Note that setting config->user to "root" does not produce an error, but it + * strongly discouraged. + */ +int drop_privileges(struct mqtt3_config *config) +{ +#if !defined(__CYGWIN__) && !defined(WIN32) + struct passwd *pwd; + char err[256]; + + if(geteuid() == 0){ + if(config->user && strcmp(config->user, "root")){ + pwd = getpwnam(config->user); + if(!pwd){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid user '%s'.", config->user); + return 1; + } + if(initgroups(config->user, pwd->pw_gid) == -1){ + strerror_r(errno, err, 256); + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error setting groups whilst dropping privileges: %s.", err); + return 1; + } + if(setgid(pwd->pw_gid) == -1){ + strerror_r(errno, err, 256); + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error setting gid whilst dropping privileges: %s.", err); + return 1; + } + if(setuid(pwd->pw_uid) == -1){ + strerror_r(errno, err, 256); + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error setting uid whilst dropping privileges: %s.", err); + return 1; + } + } + if(geteuid() == 0 || getegid() == 0){ + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: Mosquitto should not be run as root/administrator."); + } + } +#endif + return MOSQ_ERR_SUCCESS; +} + +#ifdef SIGHUP +/* Signal handler for SIGHUP - flag a config reload. */ +void handle_sighup(int signal) +{ + flag_reload = true; +} +#endif + +/* Signal handler for SIGINT and SIGTERM - just stop gracefully. */ +void handle_sigint(int signal) +{ + run = 0; +} + +/* Signal handler for SIGUSR1 - backup the db. */ +void handle_sigusr1(int signal) +{ +#ifdef WITH_PERSISTENCE + flag_db_backup = true; +#endif +} + +/* Signal handler for SIGUSR2 - vacuum the db. */ +void handle_sigusr2(int signal) +{ + flag_tree_print = true; +} + +int main(int argc, char *argv[]) +{ + int *listensock = NULL; + int listensock_count = 0; + int listensock_index = 0; + struct mqtt3_config config; + char buf[1024]; + int i, j; + FILE *pid; + int listener_max; + int rc; + char err[256]; +#ifdef WIN32 + SYSTEMTIME st; +#else + struct timeval tv; +#endif + +#if defined(WIN32) || defined(__CYGWIN__) + if(argc == 2){ + if(!strcmp(argv[1], "run")){ + service_run(); + return 0; + }else if(!strcmp(argv[1], "install")){ + service_install(); + return 0; + }else if(!strcmp(argv[1], "uninstall")){ + service_uninstall(); + return 0; + } + } +#endif + + +#ifdef WIN32 + GetSystemTime(&st); + srand(st.wSecond + st.wMilliseconds); +#else + gettimeofday(&tv, NULL); + srand(tv.tv_sec + tv.tv_usec); +#endif + + memset(&int_db, 0, sizeof(struct mosquitto_db)); + + _mosquitto_net_init(); + + mqtt3_config_init(&config); + rc = mqtt3_config_parse_args(&config, argc, argv); + if(rc != MOSQ_ERR_SUCCESS) return rc; + int_db.config = &config; + + if(config.daemon){ +#ifndef WIN32 + switch(fork()){ + case 0: + break; + case -1: + strerror_r(errno, err, 256); + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error in fork: %s", err); + return 1; + default: + return MOSQ_ERR_SUCCESS; + } +#else + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: Can't start in daemon mode in Windows."); +#endif + } + + if(config.daemon && config.pid_file){ + pid = _mosquitto_fopen(config.pid_file, "wt"); + if(pid){ + fprintf(pid, "%d", getpid()); + fclose(pid); + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Unable to write pid file."); + return 1; + } + } + rc = drop_privileges(&config); + if(rc != MOSQ_ERR_SUCCESS) return rc; + + rc = mqtt3_db_open(&config, &int_db); + if(rc != MOSQ_ERR_SUCCESS){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Couldn't open database."); + return rc; + } + + /* Initialise logging only after initialising the database in case we're + * logging to topics */ + mqtt3_log_init(config.log_type, config.log_dest); + _mosquitto_log_printf(NULL, MOSQ_LOG_INFO, "mosquitto version %s (build date %s) starting", VERSION, TIMESTAMP); + if(config.config_file){ + _mosquitto_log_printf(NULL, MOSQ_LOG_INFO, "Config loaded from %s.", config.config_file); + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_INFO, "Using default config."); + } + + rc = mosquitto_security_module_init(&int_db); + if(rc) return rc; + rc = mosquitto_security_init(&int_db, false); + if(rc) return rc; + +#ifdef WITH_SYS_TREE + if(config.sys_interval > 0){ + /* Set static $SYS messages */ + snprintf(buf, 1024, "mosquitto version %s", VERSION); + mqtt3_db_messages_easy_queue(&int_db, NULL, "$SYS/broker/version", 2, strlen(buf), buf, 1); + snprintf(buf, 1024, "%s", TIMESTAMP); + mqtt3_db_messages_easy_queue(&int_db, NULL, "$SYS/broker/timestamp", 2, strlen(buf), buf, 1); +#ifdef CHANGESET + snprintf(buf, 1024, "%s", CHANGESET); + mqtt3_db_messages_easy_queue(&int_db, NULL, "$SYS/broker/changeset", 2, strlen(buf), buf, 1); +#endif + } +#endif + + listener_max = -1; + listensock_index = 0; + for(i=0; i listener_max){ + listener_max = listensock[listensock_index]; + } + listensock_index++; + } + } + + signal(SIGINT, handle_sigint); + signal(SIGTERM, handle_sigint); +#ifdef SIGHUP + signal(SIGHUP, handle_sighup); +#endif +#ifndef WIN32 + signal(SIGUSR1, handle_sigusr1); + signal(SIGUSR2, handle_sigusr2); + signal(SIGPIPE, SIG_IGN); +#endif + +#ifdef WITH_BRIDGE + for(i=0; i8Zx5C_}NgF3?W@mTrj-e&B!j94^OxWe% zj?8hu*~aO|?`Qu28)IV(m<&!BV=%!sCSzk8Z~#osncu6b?p8mkTQ$4O=jVSspS#)l zO5NXj_3G8D^s1`kEggkY^-4WgtCfUQqJ#Z-J3R2mW>4R?9^;eFj*ia9228Y5uT+z{BNIHAf!w4M~H(qE;ZxjkF30`i>rsrX!M+^ryvy=2vtz&aYR7vjlmsaAu} zQ}v2w{d#didG=myynx(xoRn8rQ}=8)v|&njAmCV$*f)#GIhOL6AHOC;33Kd zH5p1wh@rye^^62xRBKR1knqV$4hkYE$Pqz`4T&QBCma^xN03D+1P>-ES}|qj$~Rvs z2y@zsUaTf7r8JRLFOuT1(a5qBjVUxMO(w|Yti^ABELQU?`H=6CM>@a^JC5}8%&>2* z5X_Z>*=1_RTV*L0h=c-R_#LARM>m@c%({K|#4pB!iArf%uLR5WL_Sx|1{3Qwy&8n_ z(ZLu~B;k&3S(alFN)g~1ZI|PB)zgKivfih$Z!YdIoKkIS!xbUU1C5E)BGI!c{j6BoErq!y;T3Ls_jxeHE^0}f= zFQ)X0Fs2oA(90Ro!1pCexq@Ky8LGmfmMQ9$gH_=aH28WYMH=@?y_gA5kuu-liL|d( zBn5=4XjHC)qa`q%j8KG+1ivs>s%CS-Nxf2pw!8!%oQ6Iabk~+?Vvl~Em)L>U#Hz_s zEe~uoxnsjZwG2HNEiYI-6(I>7BIrESh#ZT#d_JcOQ+ggcQ;_MX2wj#$u2RdUv~`x{ z{MUF=IFy1ylb6nxvPI!kMOzm#P)JpwRLrjnwbhaU{UPW83D9E`vh{*i6e_t)wpL{+ zE_{`j;(nBGt(wz{MICr6th=!rYx$y9t?Cu3(vzR&QSU_5Dlsmam#wNm{ubh8=#^5g z^-oBMe z&rg&8jZgBDcPJN$=H=nhte#vJO7)shgL;zJpqo`Q1rk)Jm-$;<>6S-Pd(BR)hGLS3<>dUMGvi9NpCvHGGnM!vn^jX3BgehVYLar)Ik`WAp zH0O_hoX33!Dnzi0dP3?2iVpRoSORSk)uq99OR3{jp0gk0$%9LxF(m8Q*3@sOd@uhfkMDMrKWd~^ zVwcs1r#U}CthG|lB-X*_k(?z<;L3_HyjT~~6&Nm%q90z#t!TnbMK9za-&wFatqR^? zHK|o$R49-^q%Z*v8D3x1s%4{sj=zg1;DKBv8T8i^^yn|SprOz@0E-` zn(}6oiuJBqJE#S1Emx=)XbaIJDNW)kz-<1j4HuWkrH`WGkxrr zHpZbu!!c16qpAvuYvy=zV;oElNmN5Ixmk`AFXeIcfw!11mDyB6$M)0Erk22PN=X>- zKubamcl;$hmR)*&RSu>L)u5Kw!Ct{2SWC4zzM88gvrFJ~C{_5yJgVM8=?d{%RGq!V z_nTsbpWwaQSINK#6Wj|c)O4B*tZBOEU&MQNZ&Zacg>jNU79Ae*hm}~!9~%xa-{txx zvAn?JJZ`TXQm9Du5Ax1$ypZ=!f8k0E<~HgnJ*d^w^zGy|UyaW4z<4eyhC|Uv3c%I*AWDpem8c3&X-SwW@rB8s2@NJehJ1?X zWAgyN`U0MG0}bhj2cY6DA@SsJNC{iwLAvy(opiQmm~3K0G!T=C_PXcuMDsO71I1dokm(njHWg8gwv*S;rWz*iwQ5r}MSk>oJdw8$GcV_0 zLPTNx+{tT#%a70SeDid=Oz;gSmtj^guctD)&-s-{V2Tj3JMvtf6#HT1Ue&>?>%o*x z8r5nLW(RAfBC7+2Ya4ma4fC*Giih7SA}q|0(hQcK#UmUnXq78<9s2)EqoZPUTnvekkTl--G!{KM9#fPNd3bb`+Q_ZW z-wmz94^4F9Ffu=7fCnfF6b*Vz05tzW8B-s2+p$M!FMqa z&PcUt%HlotOdjvfb#0}R)nJx6rAtzf%+OLLbX7{F0?qA_XYe?7VUCnm)0jmY)^p^M z8(={8cjW0ez+hM&YYUECy8#A~s=~0{V^^<}0n9hY8bmL64XVpy>pYI#SeM0mAz^sh zPRW?9S>tisf;qHQsse^c^8#%VljP_skEO{nNb|xdB$;*Ej;&l5g=jOb1We*HJ2bpMgJE1oaa_4hhK6xk5x-T_3uS6v59E0?NRn3TdSLnb36OC;aQXTP zP?LEicl`v+v^tIX>Z94~V`!L`7iQpDHO=rSP&Kuimf<~*r+Ey>I-Lz`NavAYtFY|K z5MnrWJ@4BbRv&R#WWdd=D#@dr zM_{um$(S8K@>Cv$-KvnoGI|!rALcRGt*XggnT_(1hj=7*tHR!jP4mYdxAEvojh}9WHfVb zba-}r_-a(quf~V<;DwBqoLNlAq z!llKF^||2UvT{CBA6*shYV6YanU#_s4h+9lLbuaD8SaUX3gy4qqsZlxAzm+4D1N^XJd!&W}#SDzQ^*_4SJrXUAu1Crg*l zhGx}ME49lrQDt~4ydEzo&f(rzg^qvc5c~omy89>sMyZUrx=aFlWDhF%!}< z>!-)_=f$L&DkkQ`^5JQ9Y(}YFxO{Q=vT}Ag9KTYZO`M+Aa>HZmE0->0;!5n&)Yw>D zUy#aksmqhI(y7_ABg%AcZhcKWmx-*N85^D*9-$VLSmc$}zLv6rKA~PrvOb+pThVe5 zCIQog5VKILSBK6BT0wxhT3A;Y&E~ZNo6UdKd2TvMU4<&5Wnf~fsIRVQ`MMq)9fuWr zWB5%&GbAxA!}!^V2br0Kswl#sW$`q82os!SUSfHX1TNHTWWEBXKEg^Q5>ZJ|1*L=! zL(zaLk%^2v%x@Z4G>6~6y}%Q6wZV2V3dE=Fe-Bz5x4)KWvrdy$Z#5X@YXNfuL& zGyuiWp`;iyF5x&&wEmHi5wNKs%tAoa02R`3%CR}#yW1P@t`o<$YJWXC%X@uW4(v}HX=*$$l?J~Oc}H!d8SD`jAfae}U2 z9DxWSy>dc>$<0zJP^x5(-AAk6v1wkSTgc)Zti3>;$kPP2tilJz`~c;cnBs997+;cL zPEid9XPf_^l$+;Zj(lQ#3>KEC1OKf_9^*g~77U9u>3Dk{%E%~qD5H%Qd`I{Uk90E`8VAWz3mZ>#k;MV( zf0Wa_cel`YNu(mo43gFy{wLj6=@jqHuB2wnzjs6E!iq*;TRh2ot#@+0F~@m`&T$S3 zFl(Mq1%&x5EGEN$Gg`JJj2=ELELOBKtoPovnUYVB^T>r1%zBdne{faT$dYgn+;d(J zCNx-2g%}jpN$-V#L0KpyDCx`?kMt=nq$z#1T*^U+91HPc zi3}+!OXk62ykub6#nBEeeHn#|6&5oUrXS@cFu2hKERF4mQH{ZXP8MKb7>XJyJHktf zTQ+81Oq~tL3elMYI#Y%Wm7RNlm&{!*IVzV{^-9BRJBQgC(bUil6GG26mn5MitRBSc z#Ufh*nvU@}-TX@hVi_Q~rpBsPuP77>!7K}CnvC*D-EvMzYo(gTI+tC9m74j(5gw)F zqDpNH&0b~ z^zsJiO~W=Ku$fnr!U5*u9}Mx*aPtCo6`5%u9kJ+_OENEkjg*0zKto9o+)K^qsS=Oh z&D=>qC`>89d_3tf>>(3I5ETIb2?-i|)rf&REArB~&8;9H@RN-|d*U@+OR*|C8RYT# zT=-U@m0ltnP&78#<}H-7R1NU}kMiCY%EerfY#4zKMN`+JCl(8vJ&OnZJmN#`h*Qli zHtnhzwU~4OGUM;7z9s73$oMLDl)r;N7$wu2_YGsv)GCrNwj(ulbdgXmedefHO#Rg zXs5#3_~}hN$~M-)D9LYR#+@JREJ92o^kIY55`>Bo%Ff4*9A7Ks1!FJd zQJ)kLeaDY&K0*REj%^l%BW7UYu{e})7J3Bm=8>TFDS4z`hNtz^G2-9+V$3hg^KxiN zRAH~XUuP)BwClo-iKH z)FCF^{o&Y=AbG-g*z#278!4SkI#+$ifYkbu<}hdqbfl`&JcD302*5YrBwKRHi{QAG zBSG?wkp`(`=0mWfBblp{r^10KJOy7da)a}TmaDXVEfSC$uYm;AK%p0JCQu;TL|~SK zBq7R?Bg}%~0hWwGPqILZ*hnj-SChx8g;ELjlq9u$L4W}BA_Sj9=p^id&F9MySPuFi zPm!Vt+Fw7CE2c}w<{?C#`~kfx62VS_@W})YnGga^q=bW7)t{>#6zJvzh;N5reh4GZ zk}&;DU4v<-ny!;reF(aQeY?r^z!4ai0PkiSAZZ~GaeWICQ-YBPl2(A5|f}!^$n5-AW9cL z41}Vh7>h*6XEfqhAnRVL8Vkc368Vrun+iU#<^qLzN+zk1fEtU%A`r){#zGNu1#GUH zC-`k-8ZejzO$YTg2s*C9+D1@I!uoV@js*)0T(ee*KuES|^>7!DcfTtyRnBd&bDbmt z%U5&u^}JF#Z6p!#@{;JXg2lJkLLX^?y`+=(@-3+=^?EJ^zwN)MY_m-tZU9Qz4nmkq z1>;j}S%yTv=#`S9#-bsC%5>7hlWE^H9S8+88Q7mzFJ!J{vsYFMD@8Vi)uRx5F?O(a z@VK@cTqj77wiVFXqlyvOwm{_>`FR%%#>kJ`W{KSfM>bQulBr$EWDAw7(aI@dQ8fB% zr+&uc+21V7mBPxEE0toMwQW71BuQZt(9=KVk=@ly=Bh0z5@n>JpYTX~XU4`)24N>G z8J~8&JoYzA&2+PIZNQG`BU0MYn3zOA;z_j4Zigv}%>^yA7$*1% zMcAlBF3(Yke))$yiLj1TLlml`1z5>6YDx^O`(_0)sxp=9#Xq=CxePg|Tr&|hBL9M< zp~|Boz2f^kk?yw_9;k*clw@F!Mk$!dRkUCzy zHQZi>ZhNAXT8DVOQWWG^IqOrs#k>4Dr~@pafbk68~aZmz3;bQ`GPOM;nMeC{ko&j@!+`^zAXKf*#MP^4CA`*7F}d z^#1pL{v#jQ{>6^hy*8nI=<(+(Km5vPe*Ea++-E=c?X87pz5aPm%skQg;PIC|bLvTb z=LVT8Sy5|n^J2U`2vq*|9E^n2Q`|+GSaBNNYo*ga{Ti=p4*&x z>~WQ>Z#VWi9@VYRRJIsP#&rB&c}#m6OfWS;^3_W6FV{gr#tKd(!~e`9+1HQ+Hb$li zi(NHrlz8H^Jes|&G`1WumZSf~W7*wcfig$M%`|)BA9)luK@19ec8v1*43EWDJ_d^| zH;m-yr+Fl{+^nswq1+g1{S=SFmK%e@o*Tw;{F6KuTW$;%TW%Q1#3y(pcN=AMDVJPZ zhbfrgJhW1AIA{bW#T-rV<2**2=@Bj$)B6~Y#-<~arX_nvKgwg-VKY702a_wZfc!2= zGByMq{RbY!PCEtVFuJ+0I!K+~(U0&bY$nqjJ8o2fzvofd)L~HAO~$AIf5&67nT)|= z)2UGb{+375st~4BuVfyH_F*1{T_G{2VEPju;!)TY!YCXR@}hFgISeEDYaWYT5XJ(X zXV&XB6!KmkgIj=$oO*yRX2D&##p3cErWg}r#0Ajkib$70V7V`rBz94vE%nBz!0+U-*mc5Kn(NIwcog>BFbZ34m>fsm&Li2?X!S(Np_6m` zZ9E2>7>uE{WRAU+M{-NE7%f>*-@@bQgL#D9<=`?~80a1z866v^4fl~Zb6;gg09V<1 zRO=DI({JK2448)q2Vq$mPTeE(4*SIjY&(SkD;$jMhr?Z(&J|9*kw?0-x(<Anvxt-TvJN?5ih0frq0Zi+|voQOf6VB(q6|)XIoP`?#ak8*X&&SLtZjw!L(6V zn}V5pEstL?vH|Dml}o0|g0Sf`NDc@$8Id2H^bu&!%tjT*K{89Q}O{mzZkLA;X@cs4xAwDzhSC(FW2Dy>c) zW^ET<$>Zu_T63*~uqoKB4sQDJD|qa;t*)*Hv`VH_^H)>LWMeEukS5>^JMs}pbJGhu zpFe-F;|@=^N9gFT!%nEE$Gd5FUtibWzJ2=-bP654^%eM3W*^_iKK9<((FHM6ny0%< z@OI`>9&cxVXSd+(P39p!ORoUl*3lz)w`vJktAU`BrGi%Wc>A__2H@%bu3o|0XMW}J z_HOfR@Avfg^a&k3^=Q48hP1ZsBp-RY7v@j;qniY87YUc}cz12<9O%weO7(Jo*8u&j zzi%_7YXrA>yt~N*1?_UF((i#kaz*mT7QwqYrKfYSfCB+ekYI0j_wL@Fty|%9eT{}; z(a$&a_Vy2K+rDGxu71J0siLPL5(+lrd%RuSJdmE?^}=Rvk9Q}eVWd6KMIxN~w{C+B zRdqPcj6QB;XzO;t+pSgLU|!>$YVNAuzjdICoPN^3bqBno7gzLrsSHU_QvAS9`arUj zg4t`2cMnOGr8WTXv5))vc0nE>N~>7QrE@wI#17E$7831M$-$Y=rDA{IZqOc_ZBg)e zBip*fA)9o6ZZ6i5$O=eL``GAu&1wHtmfblXkt6tIt~bX zHYIWy*ew8u4AW0;754Pl9=Ht{&Ww+bN7OL=`u00JHXAYNaMY@&`wrpuE^-tM{qjx# z%wDJrn25Y z3ZB+PykPInHN8vl;2d{v9=NMp@HCX&BY0f1@9ppFwOmD?&}+*st%aNR?b*9?lhBL& z#%5q|F1;;VgkIxAn+t%dM9a2+tI%tBA;<}KBHR17Gbb_t8E+Jm$J;3XEsgTuwhe@F zawgkNXR>{}lb_rn^fuT3?fpAlD_b#Eg*_ZSiwR`7op|{!hY`sP3 zZS^EO4NtOj52W7YPPg~(-7ECE_}{JjK%4F^fj9~561)S*C2a5CzhCHeatXKIcKaRo z+BTS~th3b(D}?Y~DjV0U5nLXPb&taE2aw`Y@Qb4M3x zl0BO`2RuE=QXYB`o{j@Myt}+GtlmAa3z|DPNdx}qO2CONy9c}jp3SiRX32c?)*W5D zJZtd1XPx}I3cn1R-JM+no?h}SoG9+;*t)~h_@=wTQZE!e9eW_b28%_|GvL`m2(B){ z#^a*y>DafUb61}kQfp7D*LZS``(&T-0V= zXDc~v8@9bOhuP7)!*k2-f!$lT40yWorBd0`u@}Vcro&p0wa<9Ex_k3hP>g{*9h>*` zdAoZ1db&4l>4!2X!}d2FF0|PV?|R{Itlc|yna}RtIxx`aCGCS=(ye`9@!JN#=Ro@= zLEE%x-}dcYw{C||w-4C+PW$?9V-N2j51YN5JBhpl%t6u4=ltWmE*%^cP=&LON^q+J z?KJwva+T#F!G3C#G$ciaWH>cyym4w&$M6ez%)5tAjgJjajV+E(Oa!G+B)E8HVuGg8 zJq+OmY`5337jS9lspQ~9YimvCk#`yarK8X1aqOoY5DgD~qhhiUY9V6F=%df&5$$6{ zS{?TKmElqx5_yXbNM~`k!_VOn?PWxDxQGgF6lmh<46${Vv1jvW*pcn94}x3_kp)X@ zW@7O|9Y>$VW7#Ojt6K2mA~duo}8P5)*;f=99P;4>xb#8 zSX7kZ3?`ejekBA~rAX0;0>?-rX;p}9h|%bNBak1?Q4P6C8-FHG+Py)#Yav$+va{jH zg*YoEW6#j;2VpPLpa6UJ%wLkkHu0Z(29Nl5i?{}ti;yiAuyGacT!S8c-PpC>1INyT z=+u>*dW1)Kpp~!;XLSU%l?oL3c!bTfPb9$XeiWw^#;X$S+Eaua5*%A>hAI0-I)c`Zn$fR|>@jE3ak(o#mR1&#SC&ANT2 zJFVrR2uN6IvBKjGn7r}%$&h>!7OR&QhUeyio6L+*j|E%X*gbXgYiwb0-!*}Z{RKEW zeNZ5$xWHeMFetDMJjE-&$Ta6RlxDunOLMS&K4Ak`O=fq^Z7`Q5UWy#ek`$#N>Aj6t zDy0gu&Gwqj7kO!JAeR!$eo0DX4XDOcde|~nDTJ(*_Mhm* zQsjagV)V>@Gs`>)?rbl?o2mB`taL>yS@JsBSNoxxN2QbNlI7sxhW`dfkxr{vCxdAMnm9ntf2+jS(cc@i$; zBPWEyQ?T3c2n3f#Bgs%wNX5jor0Ow2OObz*Ng=8yqiP}%79u*FRSP{(AtA;zF%nJ* zS{N>93Wsu0i48=ik!~7aBE5`UoF9*qPwb{V2*)2)lS5DqGa1;*PO6Y;->+NZ z$tJXujqLxY&Ovt)j(m%xJ2^2uJntgiLGrY5C=hY-d!7Sk*IkCr*85B%r`=N;?AGgZ=mWOz9-_=gxiFz*2 zdj5GQ$?4Zf_V_%wUq=f*I0g|7^aKW)0k9HsGPD49yTM@^L#w8g2~U=A2}tUn2oWrl%*| zRLrt&T>ok6@yUxk0i7G?TCyC3Zcq-ciiNYKlSZc+H_k23AW_>j&W~T3OcuyJ|fHzSW}BraAYTC)@zmuK##<#D$nL!RrgqIb{ocr z9I-jLysnqekZ~tmFhF%wK`yiPA+DZm%_<4+AjCofu84*OOM#sSRW&;?q%&Z;)VI&@ zvT?{g8-~@*&{aA%Q;}`W2Y!m15qQAp9uO@EYzeSRS}NkY*;w3^{`J#5>F;%y9&YwX z*+oyZif()4M&%|HSm<|6@r1t5U1+j`0sSN-_~ll?n;yGS;Y}KxftO73GSIAbIxp)y z4?El{yltv>1A?<&?Mo(p0Wz5mqK!<~<076s$CIhKp<#NW^8_u^s<^w-^2~-c^~718 zRLz}HB$e6RQm^GOa&Jt{Zd{@>H!o3}*$qP?cw)m6J$9NWQS%sy>8LRrrs^3&w%zy= z$>BIwW=8$&Q#U1sIaE91B*q4ZYD|z9pX3SRY%@ld$w1T^?&8YT^aRq3(|{M6IVREz z#(5$&kH1k7*~Z^6>uZ~&GX&c3_?t=dzA>IO&CMv1re!92)k!A%Q!R>Wo;sxc0hrXS zlDnZcHi2!b_Bo@x%ruV!QDz$Bzz3Xiv9WO=Q|sIaPZDZ(FmG;5#xg-@_sQ0Z(8}0D zf)G>|;XgNg^Sowb(IZ7<*$6tBKEdOqEn~C1EzX<@+zD5#ll9NJ<2+J2GH@Y9{<>FE zlxQfz0^;MxZkpHhx_uJt9FpWPJALmtM|s3|kPc3>zzzRMvwuvAz-fGPScS>)K;WTj zCHc@KbO;ws3F$a?| z9yy(4bs=xmYTMHMg(#2G(KMT!K9!X!S%jb<2mlh9lBXg(_NLmzmSS3FawxxX@7lRA zkCQeV4JFt)(J0#_RBwV14UR28O@@Sh}95bUP7k7&cE2cw2obJyg zmr7RPU}tuT#9}iEE;L{eja0!=SMlTd;Sm< zOeh*<&N+S`kG7pbp_U33@S{o;F1=*EheJGKItXnxq}CZIS}0u*Tg7Z?<{*!^si|Um zpuBiS2)Ti$@K~EVd^T3wWDTW+b91FonE9*o_wsmY|J+dv<|K%Hu846nO-$Uw<94)p z+UaYoD>E$^jCbEOqv^HGHo(VYbZi3*8JhyU7-#RJckwv4>P5YhS%;nHaGE{IVDE(S z3EXl3QS8Br4hQ(bYG3efazeddFfq9(2fl~pAG!cLMOV{cLi1E5mts$5Yqj!FFi7@P z!V#CS)~4k%@MQo*4c3=~<9V1XtmKm9E;U%K(}HtmN@FZ#mVm4Tvb6$ZRN;vBB1n*i z<6P7OKO!wso0_x_A$)H!Ez&sK%x&bS#1h2waA{PJ55)sL5Nef zRAPjdR$<*hhI2g0j<%r6VwZzCXgnC@&1l4w0kVsPh9zFMT1v?wR=O%2TF#74%L1&r z3vi#Yr7R5|gg1-K&swmUEJML*G#7 zWmu{hg6qLu)6}YhCfrMEF$C&0%;bPd1nod9Xs z%f&1zutZw|M*V=cxmt&NN1)~ijWTmH+V&|yMM%_Z0yRI#E=V%DNbahr!M~d~8_I|4 zEl7|J4aT;7npsSx3iZ6nWTpoC^@HatByzA*SxBxY^SY3MoIwuBMot; zo;;Z>Y4qP>9cZ))`H0+0LjEmR;TK732Gjs4#o-)N;)hPJrz$1#K%rdCuBYIEsZ!Bj zc6kV{0MRKeEXJiPgzhBSz@8Q8s-MvTB5d$|u9AoE&s1_%$FE6t{lXZWrqT2@?cbde zc&+eltYqQ-3gXX#U{z#+aFuxl+U@YFA=dFYc_)wTb|*5B(8gwTIZoekLtLN?8<$sB z*qqY%?L0QeY^EzVGP5sq8;@#NOD2s|w0E%+Ok_Wt#UqPDArXc?v=q+V%HzD%;B4;s zkj?<>uEM?dVvJ4AojJgxGjrQaM;ivp*A3U4#Mng2{C*zYei!MS^R1H0hG@Ry`*_TU z4CY3=3rF-D?Ip8Aqm5yfXkMl`yO&4&6fQCKJBERq^6uf$9&AHPTtv_=ZdWWIvjN8J zEj;3TdBhEIyTYo(^gFZrW@yV*B&}C5wnRlthQwaj|Hwwh+i!|Cmv0s~#`cKKY~%6X(?+``;B5%o1$%#u znl`{=ZD+ZpWo#DLIRARKpGVxzaw)N0UQuE$Y;h}(_Wm|nFxq7#ayK07Z_Jj=Z{d-* zvo(|4CN@ktvvy`-GmrVcc4E`60g@WbSTWXHHu0$2m0*!k+hmqRmAUtYJ|1y9nZXH@ z_8Yl1keRu)crTB-oy-O`lG$*?3q3qyj?AQ=fMgZ}A!Bi`n@7!&xw#9XvKlzw#p7*f zbf639j39}@oSDa%^YWP6IXK2_Q?hK%(am@Ac-v`$^pTOk-Le`mF6)@{@R-{<4a(dQ zxLb-u!bT&~!Q*W!akH0$K2X41^8L9N)>6rj+jU-TB__-^iD6mNSn{3x8IRYPrU_<< z<6u(~Ru9P5U4!%c4N<3m%A+)U;ZBrBm(4hA54x(%`%M3YN9t>JY-EH8<^_WVHl$@8 zOBt#fBc1v&kJOn`M2)jo&GM5 z(>?dizS|-__Z=Rod+xE-@b7|aM!kscgMm3nJuYIeTzrxuFj@5z^fXU$4`Be z$LVhMXpq~jgrz9!%S?ZR$LXR?)@8P5UWMB@jm&?YN9vOK)*ilBGK_xeYdlVu%s2P# zyCo40exS8;=BqqT7defNmOb}ja(gJN^Izeyx>&uziaefd+abZF^_ziS?bN&!|x=f21zyu($*hMR|BLCEMw zP5md2(8Z4Ez}}u~C~;$WH1!`mPM5-Ioh%^Nl2I#~`gb0uizX>&BkORPwkpD{oFqzY z_TPA%ZkEZWKvAK`qGtAIzrdq)=r)l;HwRgEi|>LCBrE#a&+};AYNsKsO$joL-Hgby zsn79fT?~NsNbLp?ftUiO*r|Wzak>~lOaHkm1WgDtfPdj}x)?xH&$&mAsgg0tckZ8W zkdw?f*s^Xl#uK0A5xOXo5Za5~Vm$p%JVqCvgeOIewteCsd2DX-k_7yq?;yIIwP#&Onr*S=aOl%gksNhH=Gz|y3J4WI9;+$IqfCg1ul+R z_~a*eoG$sMoQ>Mu9gE2dqdfg_9;G=cZW|U6S7a;dE^@6nt%7Gh#$z*Q?3>scL$;o% z1UF7I(>ec99^F3Yw#*o=?XgxC!fa*1PuCZys1yId<8~->YnjKEd&?>(Kf+^lC~=F? zUg8$znZM^zIuy7?*(h*xk>>AsbgmLMjo7+ku>hM~nEG2Dr$eK|M06OhvHCdiVIHSz z+2G-tHx^@3PJW2T=$do4vD(xJd6cdhcO9yo{Q!^CHRFyWwaLG^VLm)iBjwG;Ka=n0 zQMzifb(qFF0~7D#QMy_=8l`nf;MZv7p8RVbqq81c#%NtJF<=CGzn4epoNvbw8k=H1 z_g6eh=X^I0(7fTWF_3x>kI-30htXLCPFXbOIsTGI>TKuEgEKbkJMnHFr*rwVjLp1} z2rKb(f5BsP81{f0Y95${0>(K1?4R=p9lBMF5V>+#aHA%9`pqyoz?xGQ0qG%g;&Ao<4eXy0PGdWmPh=9${|1k`UHxb`!khbb9&@|ma~S7^S>et8 z8jshZvu=5}=FuL6N3dCqnP276I;_apX`8$}Q}5Kvc&rY?6+3IALGr?eb~>ht{|b+@ zosl_>|6**?E&j_q;&vtGFbY(RRruLo;t{vgZOd3tm6(G#|0IvLt?jms2)oIM(3p6B zDUaJB&eAG4=YgTQFg^bg9(UX7>^MAR!|nKsdCcuhqh*j7jRcItGU8A0h})Tl<4}>6 z;oOUO#O+L@WxxpMPqW#ena6L2*KOE1`$8UTJN>#29w%SGqiyHX9EXsz&*xFLmDFt@ zIrlssZ96+~A5Km^m&e=A44em*@#pY3+o_;+XsMckO7Um&h}$W_VSp(bGwZX@;t{vg zLh~>a&TMDB_Nm8sv<^dI%a@_SW@E1KQ68tmh~46}4>_&vG#8%9qjZ=bwJ3R8YI4uu zu{${3WW7))XAZCE!A2{@PGsB{l5#fWd2dxV#qLs2#-XWdd1 zf(kw_gV%iIQbv>t zJO$gX$!E}Agnh#Wz48zV>R^Auw^i6nxa9O2+~<_hm&&;mc~_JY6*00FxLnS_JLYAD zz9GrD_+ZF41807hitr_D>?E(m5~`eti3xu!sz&{a5)SzjvY7HmbhtYyDkc?(aPvJf zXo+kpa>vYt)1$Sb?btBavzT1|xgDOpbvc~!rb(sjQqhZCw4O(fIF z6q45}v5o7uQOYgCuB?hJ!iL<1)l-@j6+?a{76D-;CF3ig1 zD0?_9fZS(hTGga33}-ZQkCiaFUe)tyqBuB`%QsZ4=kw%wxC%G5L`$%EHB`y0zyo6 zo?rtHCMrN|Wp*C!GlCihA7$(CB^=<2IV3{R!eI)|ET2h{mMwDgV=<9hNv--Gq36?d zzG98%C2nc*67$EdNQ8rGF`NcX2yQS~b+E#-%sVkCb`RMM&}B@+piT+w2878SbVD~nh zOEQ^T6Hbu09l@Tn7Q4(>3mLcv(aA2Qn3GLmyBrxA6Q?6B^v%2u+|n{vk+!j2*a{Ck zrtQW{MWV!p<;o#wq}saDhV6{Noezt;4i`cZr_gGrwdL|v_`<<|N*nN>!*Efs4#k{+ zduNHeZ?Q~gV{f(0+H!^D5nHB|Dy&9|hGllWe1&J3jt(?LOtb5$Hq$(%B`P^RFPwxn zMTbO9>!cXYUzpH@5po1MvBDzjv9vnVRx7NQ%FwoyfY9b>Sy7vvr#c)&a=M<$>O@zr zrpKNb6z1S@c+N0BJ(5&pHJ$LMbS33i(y_EZmXyMNRZdHxgs$kR7|jg1WXkGmk=~Ob zX{nNfrWx)Zhu$5zxTFLp17TTNA5KzbTwm0+q)K&grAu&oDX}GkW&%b4aAP=W zI-vJXDtv=SxdBJCvbF-Z2b+$F*>b}TK}amu4>c?v&hpFaFg#hP(@=`tHLQVnxmUAg<(3jx+w>BwJef83wBUqq?QOr z&8G8>b)JK|qp9hvkPZeMf0hJ~VBAdDxoq=Pq(3*JYxP<#UC#?~I9e?WGMN5q9JIEc zD5ch+gEFt>mvhB~@a;xil~s@6d7HDM4&X|DrHJc|vlI=>f-a-I@-zuNNP4A^j_Jnq zq!Q8-NqU+{T%SYcg+p)|Rwl2LZUbvv zeWyl-XXoj$m@>Ho5RYAog`~~8eW#}H62{KNN8|9I&B#^Wumv8+<_kmNVnT@!@9Na7 z65HjNZ#)YZOkFO)z@)OQ5uVm|HR{BI+f_pjG90&?Z5j3vU0$icHHd6r^6DDTY#rNG znRfYdHftR&7IV4)=l&H-YmlaCrf@$c(GT>UN3(D-7kqQmCYlkm*hHI>cC}3=+@L_8 z2T2U04Cx_J(-SGbsHzdal1#(6CJh}sh&@rYw5Fyaa)RUtx2+C?=R>1oLcGZcorcXT z>Q&O~GJAMus^GPWW*T-UD-li~Z@4JHA#-3EaA=fafaWQKaA>PufIgW}E$c}bwE#0V zQ5drpi?Oo1hvk~+*;1Ak_aZnRNN7^m;rzTn08(nTRmTAjwp9@5a(dlibRx-xL>?}! zXNSCANb_tpcu)LdJea7!6{xVOm)#^ojs^q^1P7@HDq22J&;zwXiC(&^lk3qtj61ef z-DD>pc68if=PE;c3o~=G6;RcjCeRMPlFdl#afyb<@8M^i705h0xa#`WQl=KtD6*D%D6*E7 zD6-bAD6&?@D6*E{D6&@hD6$q4DYBL;DY6ztDY8~xDYBM!DYDj&DYDk6DY90=DY6#a zDYDk}DYD`RD6%3ID6(QAD6#@CD6--^D6+yxD6#@oD6)cKD6%4MD6-;uD6-;*D6#^T zD6*oVD6)dCD6)dPD6&GxD6%5eD6%5rD6*pND6*paD6)bGDY9Y}DY9ZBDYAkuDY9Zb zDYC*yDYAl9DYBwsDY61?DYAljDY9aPDYAl*DYBxSDY7D|DY7E9DY62=DY9bBDY8P? zDSET%peT9^LT^RrZ3w*`p?4tkPK4ft(4QdmrwIKSLVu3XUm)~ug#Hqt_aO9F2)!4f zzeebN2)!Snzd`5&2z?Nt4-02=tW(-3vd)5|$U1b6BI|5BimXHQD6-D#qsTfGkRt2wL5i$% z4k@w@Q>4f`vymd}Tu6$n($FaatP?>gvW^|4$U5DWBI^iMima1b zDY8z4rN}yNmLlscTZ*jHaw)Qo;ibqr;+G=p@L-Ar@|*lnWZe}&k#!pZMb>Qx6x~C9 zlOKw#dmAXSZik@Ax`Bcs>z)gWtm`x=vhM7l=rH+BekihTETPD{A%&sp-F_M5Sm74 z2BBGm<`9Y_G>^~%Lgx`$MCbxS7ZJLI(1Qp)gwVqXJr$uPgr0_whEM{bBtj{KbcE6f zWf00Dltbt;Ldyu{5h@^5M5u&N8KEl(RS>EoR70qa&tf7I)wfRq1Pky287;-P(PYIcoX{W%?P~(p|>LRHiX`e&^r)% zCqnN+=uZ&(Q-uBup+86HFA#b+LVtgg%JShY;he?aJ?2z?Bpk0bO6gg%MTrx5xyLZ3nC9})T|gg%SVKO^)n2>mNU zpF`;L2z>#ee?#cs5&92={u7}uBJ?GMzKqa+A@mi5zKYP-5c)bo-$3Y_2z?8oZzJ>_ zguaW=_YnF%LO($0hY0-$p&ujk6NG+>(9aP1IYJ%C?|KmGM97O!7ed_#^&r%XP#;2@ z5Za8;7KFAU)Q`{rLfa79j?fN-b|SP3p@Rq=Lg+q(?nme_LW2nT5egs_L`XzPLP$m^ zgph)eiclD#2trYWVh9Z(^Z-If5ITy`F@%mIbONDaghmh=MQ9A6afD7HbPA!<2%SOb zEJEiHnm}k0p(%u>5t>107NI$W;t0(nw1CiggccFHfY3#RE+O + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#ifndef MQTT3_H +#define MQTT3_H + +#include +#include + +#include +#include +#include +#include "tls_mosq.h" +#include "uthash.h" + +#ifndef __GNUC__ +#define __attribute__(attrib) +#endif + +/* Log destinations */ +#define MQTT3_LOG_NONE 0x00 +#define MQTT3_LOG_SYSLOG 0x01 +#define MQTT3_LOG_FILE 0x02 +#define MQTT3_LOG_STDOUT 0x04 +#define MQTT3_LOG_STDERR 0x08 +#define MQTT3_LOG_TOPIC 0x10 +#define MQTT3_LOG_ALL 0xFF + +typedef uint64_t dbid_t; + +struct _mqtt3_listener { + int fd; + char *host; + uint16_t port; + int max_connections; + char *mount_point; + int *socks; + int sock_count; + int client_count; +#ifdef WITH_TLS + char *cafile; + char *capath; + char *certfile; + char *keyfile; + char *ciphers; + char *psk_hint; + bool require_certificate; + SSL_CTX *ssl_ctx; + char *crlfile; + bool use_identity_as_username; + char *tls_version; +#endif +}; + +struct mqtt3_config { + char *config_file; + char *acl_file; + bool allow_anonymous; + bool allow_duplicate_messages; + bool allow_zero_length_clientid; + char *auto_id_prefix; + int auto_id_prefix_len; + int autosave_interval; + bool autosave_on_changes; + char *clientid_prefixes; + bool connection_messages; + bool daemon; + struct _mqtt3_listener default_listener; + struct _mqtt3_listener *listeners; + int listener_count; + int log_dest; + int log_type; + bool log_timestamp; + char *log_file; + FILE *log_fptr; + int message_size_limit; + char *password_file; + bool persistence; + char *persistence_location; + char *persistence_file; + char *persistence_filepath; + time_t persistent_client_expiration; + char *pid_file; + char *psk_file; + bool queue_qos0_messages; + int retry_interval; + int store_clean_interval; + int sys_interval; + bool upgrade_outgoing_qos; + char *user; + bool verbose; +#ifdef WITH_BRIDGE + struct _mqtt3_bridge *bridges; + int bridge_count; +#endif + char *auth_plugin; + struct mosquitto_auth_opt *auth_options; + int auth_option_count; +}; + +struct _mosquitto_subleaf { + struct _mosquitto_subleaf *prev; + struct _mosquitto_subleaf *next; + struct mosquitto *context; + int qos; +}; + +struct _mosquitto_subhier { + struct _mosquitto_subhier *children; + struct _mosquitto_subhier *next; + struct _mosquitto_subleaf *subs; + char *topic; + struct mosquitto_msg_store *retained; +}; + +struct mosquitto_msg_store{ + struct mosquitto_msg_store *next; + dbid_t db_id; + int ref_count; + char *source_id; + char **dest_ids; + int dest_id_count; + uint16_t source_mid; + struct mosquitto_message msg; +}; + +struct mosquitto_client_msg{ + struct mosquitto_client_msg *next; + struct mosquitto_msg_store *store; + uint16_t mid; + int qos; + bool retain; + time_t timestamp; + enum mosquitto_msg_direction direction; + enum mosquitto_msg_state state; + bool dup; +}; + +struct _mosquitto_unpwd{ + char *username; + char *password; +#ifdef WITH_TLS + unsigned int password_len; + unsigned char *salt; + unsigned int salt_len; +#endif + UT_hash_handle hh; +}; + +struct _mosquitto_acl{ + struct _mosquitto_acl *next; + char *topic; + int access; +}; + +struct _mosquitto_acl_user{ + struct _mosquitto_acl_user *next; + char *username; + struct _mosquitto_acl *acl; +}; + +struct _mosquitto_auth_plugin{ + void *lib; + void *user_data; + int (*plugin_version)(void); + int (*plugin_init)(void **user_data, struct mosquitto_auth_opt *auth_opts, int auth_opt_count); + int (*plugin_cleanup)(void *user_data, struct mosquitto_auth_opt *auth_opts, int auth_opt_count); + int (*security_init)(void *user_data, struct mosquitto_auth_opt *auth_opts, int auth_opt_count, bool reload); + int (*security_cleanup)(void *user_data, struct mosquitto_auth_opt *auth_opts, int auth_opt_count, bool reload); + int (*acl_check)(void *user_data, const char *clientid, const char *username, const char *topic, int access); + int (*unpwd_check)(void *user_data, const char *username, const char *password); + int (*psk_key_get)(void *user_data, const char *hint, const char *identity, char *key, int max_key_len); +}; + +struct _clientid_index_hash{ + /* this is the key */ + char *id; + /* this is the index where the client ID exists in the db->contexts array */ + int db_context_index; + UT_hash_handle hh; +}; + +struct mosquitto_db{ + dbid_t last_db_id; + struct _mosquitto_subhier subs; + struct _mosquitto_unpwd *unpwd; + struct _mosquitto_acl_user *acl_list; + struct _mosquitto_acl *acl_patterns; + struct _mosquitto_unpwd *psk_id; + struct mosquitto **contexts; + struct _clientid_index_hash *clientid_index_hash; + int context_count; + struct mosquitto_msg_store *msg_store; + int msg_store_count; + struct mqtt3_config *config; + int persistence_changes; + struct _mosquitto_auth_plugin auth_plugin; + int subscription_count; + int retained_count; +}; + +enum mqtt3_bridge_direction{ + bd_out = 0, + bd_in = 1, + bd_both = 2 +}; + +enum mosquitto_bridge_start_type{ + bst_automatic = 0, + bst_lazy = 1, + bst_manual = 2, + bst_once = 3 +}; + +struct _mqtt3_bridge_topic{ + char *topic; + int qos; + enum mqtt3_bridge_direction direction; + char *local_prefix; + char *remote_prefix; + char *local_topic; /* topic prefixed with local_prefix */ + char *remote_topic; /* topic prefixed with remote_prefix */ +}; + +struct bridge_address{ + char *address; + int port; +}; + +struct _mqtt3_bridge{ + char *name; + struct bridge_address *addresses; + int cur_address; + int address_count; + time_t primary_retry; + bool round_robin; + char *clientid; + int keepalive; + bool clean_session; + struct _mqtt3_bridge_topic *topics; + int topic_count; + bool topic_remapping; + time_t restart_t; + char *username; + char *password; + bool notifications; + char *notification_topic; + enum mosquitto_bridge_start_type start_type; + int idle_timeout; + int restart_timeout; + int threshold; + bool lazy_reconnect; + bool try_private; + bool try_private_accepted; +#ifdef WITH_TLS + char *tls_cafile; + char *tls_capath; + char *tls_certfile; + char *tls_keyfile; + bool tls_insecure; + char *tls_version; +# ifdef REAL_WITH_TLS_PSK + char *tls_psk_identity; + char *tls_psk; +# endif +#endif +}; + +#include + +/* ============================================================ + * Main functions + * ============================================================ */ +int mosquitto_main_loop(struct mosquitto_db *db, int *listensock, int listensock_count, int listener_max); +struct mosquitto_db *_mosquitto_get_db(void); + +/* ============================================================ + * Config functions + * ============================================================ */ +/* Initialise config struct to default values. */ +void mqtt3_config_init(struct mqtt3_config *config); +/* Parse command line options into config. */ +int mqtt3_config_parse_args(struct mqtt3_config *config, int argc, char *argv[]); +/* Read configuration data from config->config_file into config. + * If reload is true, don't process config options that shouldn't be reloaded (listeners etc) + * Returns 0 on success, 1 if there is a configuration error or if a file cannot be opened. + */ +int mqtt3_config_read(struct mqtt3_config *config, bool reload); +/* Free all config data. */ +void mqtt3_config_cleanup(struct mqtt3_config *config); + +/* ============================================================ + * Server send functions + * ============================================================ */ +int _mosquitto_send_connack(struct mosquitto *context, int result); +int _mosquitto_send_suback(struct mosquitto *context, uint16_t mid, uint32_t payloadlen, const void *payload); + +/* ============================================================ + * Network functions + * ============================================================ */ +int mqtt3_socket_accept(struct mosquitto_db *db, int listensock); +int mqtt3_socket_listen(struct _mqtt3_listener *listener); +int _mosquitto_socket_get_address(int sock, char *buf, int len); + +/* ============================================================ + * Read handling functions + * ============================================================ */ +int mqtt3_packet_handle(struct mosquitto_db *db, struct mosquitto *context); +int mqtt3_handle_connack(struct mosquitto_db *db, struct mosquitto *context); +int mqtt3_handle_connect(struct mosquitto_db *db, struct mosquitto *context); +int mqtt3_handle_disconnect(struct mosquitto_db *db, struct mosquitto *context); +int mqtt3_handle_publish(struct mosquitto_db *db, struct mosquitto *context); +int mqtt3_handle_subscribe(struct mosquitto_db *db, struct mosquitto *context); +int mqtt3_handle_unsubscribe(struct mosquitto_db *db, struct mosquitto *context); + +/* ============================================================ + * Database handling + * ============================================================ */ +int mqtt3_db_open(struct mqtt3_config *config, struct mosquitto_db *db); +int mqtt3_db_close(struct mosquitto_db *db); +#ifdef WITH_PERSISTENCE +int mqtt3_db_backup(struct mosquitto_db *db, bool cleanup, bool shutdown); +int mqtt3_db_restore(struct mosquitto_db *db); +#endif +int mqtt3_db_client_count(struct mosquitto_db *db, unsigned int *count, unsigned int *inactive_count); +void mqtt3_db_limits_set(int inflight, int queued); +/* Return the number of in-flight messages in count. */ +int mqtt3_db_message_count(int *count); +int mqtt3_db_message_delete(struct mosquitto *context, uint16_t mid, enum mosquitto_msg_direction dir); +int mqtt3_db_message_insert(struct mosquitto_db *db, struct mosquitto *context, uint16_t mid, enum mosquitto_msg_direction dir, int qos, bool retain, struct mosquitto_msg_store *stored); +int mqtt3_db_message_release(struct mosquitto_db *db, struct mosquitto *context, uint16_t mid, enum mosquitto_msg_direction dir); +int mqtt3_db_message_update(struct mosquitto *context, uint16_t mid, enum mosquitto_msg_direction dir, enum mosquitto_msg_state state); +int mqtt3_db_message_write(struct mosquitto *context); +int mqtt3_db_messages_delete(struct mosquitto *context); +int mqtt3_db_messages_easy_queue(struct mosquitto_db *db, struct mosquitto *context, const char *topic, int qos, uint32_t payloadlen, const void *payload, int retain); +int mqtt3_db_messages_queue(struct mosquitto_db *db, const char *source_id, const char *topic, int qos, int retain, struct mosquitto_msg_store *stored); +int mqtt3_db_message_store(struct mosquitto_db *db, const char *source, uint16_t source_mid, const char *topic, int qos, uint32_t payloadlen, const void *payload, int retain, struct mosquitto_msg_store **stored, dbid_t store_id); +int mqtt3_db_message_store_find(struct mosquitto *context, uint16_t mid, struct mosquitto_msg_store **stored); +/* Check all messages waiting on a client reply and resend if timeout has been exceeded. */ +int mqtt3_db_message_timeout_check(struct mosquitto_db *db, unsigned int timeout); +int mqtt3_db_message_reconnect_reset(struct mosquitto *context); +int mqtt3_retain_queue(struct mosquitto_db *db, struct mosquitto *context, const char *sub, int sub_qos); +void mqtt3_db_store_clean(struct mosquitto_db *db); +void mqtt3_db_sys_update(struct mosquitto_db *db, int interval, time_t start_time); +void mqtt3_db_vacuum(void); + +/* ============================================================ + * Subscription functions + * ============================================================ */ +int mqtt3_sub_add(struct mosquitto_db *db, struct mosquitto *context, const char *sub, int qos, struct _mosquitto_subhier *root); +int mqtt3_sub_remove(struct mosquitto_db *db, struct mosquitto *context, const char *sub, struct _mosquitto_subhier *root); +int mqtt3_sub_search(struct mosquitto_db *db, struct _mosquitto_subhier *root, const char *source_id, const char *topic, int qos, int retain, struct mosquitto_msg_store *stored); +void mqtt3_sub_tree_print(struct _mosquitto_subhier *root, int level); +int mqtt3_subs_clean_session(struct mosquitto_db *db, struct mosquitto *context, struct _mosquitto_subhier *root); + +/* ============================================================ + * Context functions + * ============================================================ */ +struct mosquitto *mqtt3_context_init(int sock); +void mqtt3_context_cleanup(struct mosquitto_db *db, struct mosquitto *context, bool do_free); +void mqtt3_context_disconnect(struct mosquitto_db *db, struct mosquitto *context); + +/* ============================================================ + * Logging functions + * ============================================================ */ +int mqtt3_log_init(int level, int destinations); +int mqtt3_log_close(void); +int _mosquitto_log_printf(struct mosquitto *mosq, int level, const char *fmt, ...) __attribute__((format(printf, 3, 4))); + +/* ============================================================ + * Bridge functions + * ============================================================ */ +#ifdef WITH_BRIDGE +int mqtt3_bridge_new(struct mosquitto_db *db, struct _mqtt3_bridge *bridge); +int mqtt3_bridge_connect(struct mosquitto_db *db, struct mosquitto *context); +void mqtt3_bridge_packet_cleanup(struct mosquitto *context); +#endif + +/* ============================================================ + * Security related functions + * ============================================================ */ +int mosquitto_security_module_init(struct mosquitto_db *db); +int mosquitto_security_module_cleanup(struct mosquitto_db *db); + +int mosquitto_security_init(struct mosquitto_db *db, bool reload); +int mosquitto_security_apply(struct mosquitto_db *db); +int mosquitto_security_cleanup(struct mosquitto_db *db, bool reload); +int mosquitto_acl_check(struct mosquitto_db *db, struct mosquitto *context, const char *topic, int access); +int mosquitto_unpwd_check(struct mosquitto_db *db, const char *username, const char *password); +int mosquitto_psk_key_get(struct mosquitto_db *db, const char *hint, const char *identity, char *key, int max_key_len); + +int mosquitto_security_init_default(struct mosquitto_db *db, bool reload); +int mosquitto_security_apply_default(struct mosquitto_db *db); +int mosquitto_security_cleanup_default(struct mosquitto_db *db, bool reload); +int mosquitto_acl_check_default(struct mosquitto_db *db, struct mosquitto *context, const char *topic, int access); +int mosquitto_unpwd_check_default(struct mosquitto_db *db, const char *username, const char *password); +int mosquitto_psk_key_get_default(struct mosquitto_db *db, const char *hint, const char *identity, char *key, int max_key_len); + +/* ============================================================ + * Window service related functions + * ============================================================ */ +#if defined(WIN32) || defined(__CYGWIN__) +void service_install(void); +void service_uninstall(void); +void service_run(void); +#endif + +#endif diff --git a/src/mosquitto_passwd.c b/src/mosquitto_passwd.c new file mode 100644 index 0000000000..196f9f4328 --- /dev/null +++ b/src/mosquitto_passwd.c @@ -0,0 +1,458 @@ +/* +Copyright (c) 2012-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef WIN32 +# include +# ifndef __cplusplus +# define bool char +# define true 1 +# define false 0 +# endif +# define snprintf sprintf_s +# include +#else +# include +# include +# include +#endif + +#define MAX_BUFFER_LEN 1024 +#define SALT_LEN 12 + +int base64_encode(unsigned char *in, unsigned int in_len, char **encoded) +{ + BIO *bmem, *b64; + BUF_MEM *bptr; + + b64 = BIO_new(BIO_f_base64()); + BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); + bmem = BIO_new(BIO_s_mem()); + b64 = BIO_push(b64, bmem); + BIO_write(b64, in, in_len); + if(BIO_flush(b64) != 1){ + BIO_free_all(b64); + return 1; + } + BIO_get_mem_ptr(b64, &bptr); + *encoded = calloc(bptr->length+1, 1); + if(!(*encoded)){ + BIO_free_all(b64); + return 1; + } + memcpy(*encoded, bptr->data, bptr->length); + (*encoded)[bptr->length] = '\0'; + BIO_free_all(b64); + + return 0; +} + + +void print_usage(void) +{ + printf("mosquitto_passwd is a tool for managing password files for mosquitto.\n\n"); + printf("Usage: mosquitto_passwd [-c | -D] passwordfile username\n"); + printf(" mosquitto_passwd -U passwordfile\n"); + printf(" -c : create a new password file. This will overwrite existing files.\n"); + printf(" -D : delete the username rather than adding/updating its password.\n"); + printf(" -U : update a plain text password file to use hashed passwords.\n"); + printf("\nSee http://mosquitto.org/ for more information.\n\n"); +} + +int output_new_password(FILE *fptr, const char *username, const char *password) +{ + int rc; + unsigned char salt[SALT_LEN]; + char *salt64 = NULL, *hash64 = NULL; + unsigned char hash[EVP_MAX_MD_SIZE]; + unsigned int hash_len; + const EVP_MD *digest; + EVP_MD_CTX context; + + rc = RAND_bytes(salt, SALT_LEN); + if(!rc){ + fprintf(stderr, "Error: Insufficient entropy available to perform password generation.\n"); + return 1; + } + + rc = base64_encode(salt, SALT_LEN, &salt64); + if(rc){ + if(salt64) free(salt64); + fprintf(stderr, "Error: Unable to encode salt.\n"); + return 1; + } + + + digest = EVP_get_digestbyname("sha512"); + if(!digest){ + fprintf(stderr, "Error: Unable to create openssl digest.\n"); + return 1; + } + + EVP_MD_CTX_init(&context); + EVP_DigestInit_ex(&context, digest, NULL); + EVP_DigestUpdate(&context, password, strlen(password)); + EVP_DigestUpdate(&context, salt, SALT_LEN); + EVP_DigestFinal_ex(&context, hash, &hash_len); + EVP_MD_CTX_cleanup(&context); + + rc = base64_encode(hash, hash_len, &hash64); + if(rc){ + if(salt64) free(salt64); + if(hash64) free(hash64); + fprintf(stderr, "Error: Unable to encode hash.\n"); + return 1; + } + + fprintf(fptr, "%s:$6$%s$%s\n", username, salt64, hash64); + free(salt64); + free(hash64); + + return 0; +} + +int delete_pwuser(FILE *fptr, FILE *ftmp, const char *username) +{ + char buf[MAX_BUFFER_LEN]; + char lbuf[MAX_BUFFER_LEN], *token; + bool found = false; + + while(!feof(fptr) && fgets(buf, MAX_BUFFER_LEN, fptr)){ + memcpy(lbuf, buf, MAX_BUFFER_LEN); + token = strtok(lbuf, ":"); + if(strcmp(username, token)){ + fprintf(ftmp, "%s", buf); + }else{ + found = true; + } + } + if(!found){ + fprintf(stderr, "Warning: User %s not found in password file.\n", username); + } + return 0; +} + +int update_file(FILE *fptr, FILE *ftmp) +{ + char buf[MAX_BUFFER_LEN]; + char lbuf[MAX_BUFFER_LEN]; + char *username, *password; + int rc; + int len; + + while(!feof(fptr) && fgets(buf, MAX_BUFFER_LEN, fptr)){ + memcpy(lbuf, buf, MAX_BUFFER_LEN); + username = strtok(lbuf, ":"); + password = strtok(NULL, ":"); + if(password){ + len = strlen(password); + while(len && (password[len-1] == '\n' || password[len-1] == '\r')){ + password[len-1] = '\0'; + len = strlen(password); + } + rc = output_new_password(ftmp, username, password); + if(rc) return rc; + }else{ + fprintf(ftmp, "%s", username); + } + } + return 0; +} + +int update_pwuser(FILE *fptr, FILE *ftmp, const char *username, const char *password) +{ + char buf[MAX_BUFFER_LEN]; + char lbuf[MAX_BUFFER_LEN], *token; + bool found = false; + int rc = 1; + + while(!feof(fptr) && fgets(buf, MAX_BUFFER_LEN, fptr)){ + memcpy(lbuf, buf, MAX_BUFFER_LEN); + token = strtok(lbuf, ":"); + if(strcmp(username, token)){ + fprintf(ftmp, "%s", buf); + }else{ + rc = output_new_password(ftmp, username, password); + found = true; + } + } + if(found){ + return rc; + }else{ + return output_new_password(ftmp, username, password); + } +} + +int gets_quiet(char *s, int len) +{ +#ifdef WIN32 + HANDLE h; + DWORD con_orig, con_quiet; + DWORD read_len = 0; + + memset(s, 0, len); + h = GetStdHandle(STD_INPUT_HANDLE); + GetConsoleMode(h, &con_orig); + con_quiet &= ~ENABLE_ECHO_INPUT; + con_quiet |= ENABLE_LINE_INPUT; + SetConsoleMode(h, con_quiet); + if(!ReadConsole(h, s, len, &read_len, NULL)){ + SetConsoleMode(h, con_orig); + return 1; + } + while(s[strlen(s)-1] == 10 || s[strlen(s)-1] == 13){ + s[strlen(s)-1] = 0; + } + if(strlen(s) == 0){ + return 1; + } + SetConsoleMode(h, con_orig); + + return 0; +#else + struct termios ts_quiet, ts_orig; + char *rs; + + memset(s, 0, len); + tcgetattr(0, &ts_orig); + ts_quiet = ts_orig; + ts_quiet.c_lflag &= ~(ECHO | ICANON); + tcsetattr(0, TCSANOW, &ts_quiet); + + rs = fgets(s, len, stdin); + tcsetattr(0, TCSANOW, &ts_orig); + + if(!rs){ + return 1; + }else{ + while(s[strlen(s)-1] == 10 || s[strlen(s)-1] == 13){ + s[strlen(s)-1] = 0; + } + if(strlen(s) == 0){ + return 1; + } + } + return 0; +#endif +} + +int get_password(char *password, int len) +{ + char pw1[MAX_BUFFER_LEN], pw2[MAX_BUFFER_LEN]; + + printf("Password: "); + if(gets_quiet(pw1, MAX_BUFFER_LEN)){ + fprintf(stderr, "Error: Empty password.\n"); + return 1; + } + printf("\n"); + + printf("Reenter password: "); + if(gets_quiet(pw2, MAX_BUFFER_LEN)){ + fprintf(stderr, "Error: Empty password.\n"); + return 1; + } + printf("\n"); + + if(strcmp(pw1, pw2)){ + fprintf(stderr, "Error: Passwords do not match.\n"); + return 1; + } + + strncpy(password, pw1, len); + return 0; +} + +int copy_contents(FILE *src, FILE *dest) +{ + char buf[MAX_BUFFER_LEN]; + int len; + + rewind(src); + rewind(dest); + +#ifdef WIN32 + _chsize(fileno(dest), 0); +#else + if(ftruncate(fileno(dest), 0)) return 1; +#endif + + while(!feof(src)){ + len = fread(buf, 1, MAX_BUFFER_LEN, src); + if(len > 0){ + if(fwrite(buf, 1, len, dest) != len){ + return 1; + } + }else{ + return !feof(src); + } + } + return 0; +} + +int create_backup(const char *backup_file, FILE *fptr) +{ + FILE *fbackup; + + fbackup = fopen(backup_file, "wt"); + if(!fbackup){ + fprintf(stderr, "Error creating backup password file \"%s\", not continuing.\n", backup_file); + return 1; + } + if(copy_contents(fptr, fbackup)){ + fprintf(stderr, "Error copying data to backup password file \"%s\", not continuing.\n", backup_file); + fclose(fbackup); + return 1; + } + fclose(fbackup); + rewind(fptr); + return 0; +} +void handle_sigint(int signal) +{ +#ifndef WIN32 + struct termios ts; + + tcgetattr(0, &ts); + ts.c_lflag |= ECHO | ICANON; + tcsetattr(0, TCSANOW, &ts); +#endif + exit(0); +} + +int main(int argc, char *argv[]) +{ + char *password_file = NULL; + char *username = NULL; + bool create_new = false; + bool delete_user = false; + FILE *fptr, *ftmp; + char password[MAX_BUFFER_LEN]; + int rc; + bool do_update_file = false; + char *backup_file; + + signal(SIGINT, handle_sigint); + signal(SIGTERM, handle_sigint); + + OpenSSL_add_all_digests(); + + if(argc == 4){ + if(!strcmp(argv[1], "-c")){ + create_new = true; + }else if(!strcmp(argv[1], "-D")){ + delete_user = true; + } + password_file = argv[2]; + username = argv[3]; + }else if(argc == 3){ + if(!strcmp(argv[1], "-U")){ + do_update_file = true; + password_file = argv[2]; + }else{ + password_file = argv[1]; + username = argv[2]; + } + }else{ + print_usage(); + return 1; + } + + if(create_new){ + rc = get_password(password, 1024); + if(rc) return rc; + fptr = fopen(password_file, "wt"); + if(!fptr){ + fprintf(stderr, "Error: Unable to open file %s for writing. %s.\n", password_file, strerror(errno)); + return 1; + } + rc = output_new_password(fptr, username, password); + fclose(fptr); + return rc; + }else{ + fptr = fopen(password_file, "r+t"); + if(!fptr){ + fprintf(stderr, "Error: Unable to open password file %s. %s.\n", password_file, strerror(errno)); + return 1; + } + + backup_file = malloc(strlen(password_file)+5); + snprintf(backup_file, strlen(password_file)+5, "%s.tmp", password_file); + + if(create_backup(backup_file, fptr)){ + fclose(fptr); + free(backup_file); + return 1; + } + + ftmp = tmpfile(); + if(!ftmp){ + fprintf(stderr, "Error: Unable to open temporary file. %s.\n", strerror(errno)); + fclose(fptr); + free(backup_file); + return 1; + } + if(delete_user){ + rc = delete_pwuser(fptr, ftmp, username); + }else if(do_update_file){ + rc = update_file(fptr, ftmp); + }else{ + rc = get_password(password, 1024); + if(rc){ + fclose(fptr); + fclose(ftmp); + unlink(backup_file); + free(backup_file); + return rc; + } + /* Update password for individual user */ + rc = update_pwuser(fptr, ftmp, username, password); + } + if(rc){ + fclose(fptr); + fclose(ftmp); + unlink(backup_file); + free(backup_file); + return rc; + } + + if(copy_contents(ftmp, fptr)){ + fclose(fptr); + fclose(ftmp); + fprintf(stderr, "Error occurred updating password file.\n"); + fprintf(stderr, "Password file may be corrupt, check the backup file: %s.\n", backup_file); + free(backup_file); + return 1; + } + fclose(fptr); + fclose(ftmp); + + /* Everything was ok so backup no longer needed. May contain old + * passwords so shouldn't be kept around. */ + unlink(backup_file); + free(backup_file); + } + + return 0; +} diff --git a/src/mosquitto_plugin.h b/src/mosquitto_plugin.h new file mode 100644 index 0000000000..59bc4f4217 --- /dev/null +++ b/src/mosquitto_plugin.h @@ -0,0 +1,181 @@ +/* +Copyright (c) 2012-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#ifndef MOSQUITTO_PLUGIN_H +#define MOSQUITTO_PLUGIN_H + +#define MOSQ_AUTH_PLUGIN_VERSION 2 + +#define MOSQ_ACL_NONE 0x00 +#define MOSQ_ACL_READ 0x01 +#define MOSQ_ACL_WRITE 0x02 + +struct mosquitto_auth_opt { + char *key; + char *value; +}; + +/* + * To create an authentication plugin you must include this file then implement + * the functions listed below. The resulting code should then be compiled as a + * shared library. Using gcc this can be achieved as follows: + * + * gcc -I -fPIC -shared plugin.c -o plugin.so + */ + +/* + * Function: mosquitto_auth_plugin_version + * + * The broker will call this function immediately after loading the plugin to + * check it is a supported plugin version. Your code must simply return + * MOSQ_AUTH_PLUGIN_VERSION. + */ +int mosquitto_auth_plugin_version(void); + +/* + * Function: mosquitto_auth_plugin_init + * + * Called after the plugin has been loaded and + * has been called. This will only ever be called once and can be used to + * initialise the plugin. + * + * Parameters: + * + * user_data : The pointer set here will be passed to the other plugin + * functions. Use to hold connection information for example. + * auth_opts : Pointer to an array of struct mosquitto_auth_opt, which + * provides the plugin options defined in the configuration file. + * auth_opt_count : The number of elements in the auth_opts array. + * + * Return value: + * Return 0 on success + * Return >0 on failure. + */ +int mosquitto_auth_plugin_init(void **user_data, struct mosquitto_auth_opt *auth_opts, int auth_opt_count); + +/* + * Function: mosquitto_auth_plugin_cleanup + * + * Called when the broker is shutting down. This will only ever be called once. + * Note that will be called directly before + * this function. + * + * Parameters: + * + * user_data : The pointer provided in . + * auth_opts : Pointer to an array of struct mosquitto_auth_opt, which + * provides the plugin options defined in the configuration file. + * auth_opt_count : The number of elements in the auth_opts array. + * + * Return value: + * Return 0 on success + * Return >0 on failure. + */ +int mosquitto_auth_plugin_cleanup(void *user_data, struct mosquitto_auth_opt *auth_opts, int auth_opt_count); + +/* + * Function: mosquitto_auth_security_init + * + * Called when the broker initialises the security functions when it starts up. + * If the broker is requested to reload its configuration whilst running, + * will be called, followed by this function. + * In this situation, the reload parameter will be true. + * + * Parameters: + * + * user_data : The pointer provided in . + * auth_opts : Pointer to an array of struct mosquitto_auth_opt, which + * provides the plugin options defined in the configuration file. + * auth_opt_count : The number of elements in the auth_opts array. + * reload : If set to false, this is the first time the function has + * been called. If true, the broker has received a signal + * asking to reload its configuration. + * + * Return value: + * Return 0 on success + * Return >0 on failure. + */ +int mosquitto_auth_security_init(void *user_data, struct mosquitto_auth_opt *auth_opts, int auth_opt_count, bool reload); + +/* + * Function: mosquitto_auth_security_cleanup + * + * Called when the broker cleans up the security functions when it shuts down. + * If the broker is requested to reload its configuration whilst running, + * this function will be called, followed by . + * In this situation, the reload parameter will be true. + * + * Parameters: + * + * user_data : The pointer provided in . + * auth_opts : Pointer to an array of struct mosquitto_auth_opt, which + * provides the plugin options defined in the configuration file. + * auth_opt_count : The number of elements in the auth_opts array. + * reload : If set to false, this is the first time the function has + * been called. If true, the broker has received a signal + * asking to reload its configuration. + * + * Return value: + * Return 0 on success + * Return >0 on failure. + */ +int mosquitto_auth_security_cleanup(void *user_data, struct mosquitto_auth_opt *auth_opts, int auth_opt_count, bool reload); + +/* + * Function: mosquitto_auth_acl_check + * + * Called by the broker when topic access must be checked. access will be one + * of MOSQ_ACL_READ (for subscriptions) or MOSQ_ACL_WRITE (for publish). Return + * MOSQ_ERR_SUCCESS if access was granted, MOSQ_ERR_ACL_DENIED if access was + * not granted, or MOSQ_ERR_UNKNOWN for an application specific error. + */ +int mosquitto_auth_acl_check(void *user_data, const char *clientid, const char *username, const char *topic, int access); + +/* + * Function: mosquitto_auth_unpwd_check + * + * Called by the broker when a username/password must be checked. Return + * MOSQ_ERR_SUCCESS if the user is authenticated, MOSQ_ERR_AUTH if + * authentication failed, or MOSQ_ERR_UNKNOWN for an application specific + * error. + */ +int mosquitto_auth_unpwd_check(void *user_data, const char *username, const char *password); + +/* + * Function: mosquitto_psk_key_get + * + * Called by the broker when a client connects to a listener using TLS/PSK. + * This is used to retrieve the pre-shared-key associated with a client + * identity. + * + * Examine hint and identity to determine the required PSK (which must be a + * hexadecimal string with no leading "0x") and copy this string into key. + * + * Parameters: + * user_data : the pointer provided in . + * hint : the psk_hint for the listener the client is connecting to. + * identity : the identity string provided by the client + * key : a string where the hex PSK should be copied + * max_key_len : the size of key + * + * Return value: + * Return 0 on success. + * Return >0 on failure. + * Return >0 if this function is not required. + */ +int mosquitto_auth_psk_key_get(void *user_data, const char *hint, const char *identity, char *key, int max_key_len); + +#endif diff --git a/src/net.c b/src/net.c new file mode 100644 index 0000000000..0db4c9a570 --- /dev/null +++ b/src/net.c @@ -0,0 +1,521 @@ +/* +Copyright (c) 2009-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#include + +#ifndef WIN32 +#include +#include +#include +#include +#else +#include +#include +#endif + +#include +#include +#include +#include +#include +#ifdef WITH_WRAP +#include +#endif + +#ifdef __FreeBSD__ +# include +# include +#endif + +#ifdef __QNX__ +#include +#include +#include +#endif + +#include +#include +#include +#include +#include + +#ifdef WITH_TLS +#include "tls_mosq.h" +#include +static int tls_ex_index_context = -1; +static int tls_ex_index_listener = -1; +#endif + +#ifdef WITH_SYS_TREE +extern unsigned int g_socket_connections; +#endif + +int mqtt3_socket_accept(struct mosquitto_db *db, int listensock) +{ + int i; + int j; + int new_sock = -1; + struct mosquitto **tmp_contexts = NULL; + struct mosquitto *new_context; +#ifdef WITH_TLS + BIO *bio; + int rc; + char ebuf[256]; + unsigned long e; +#endif +#ifdef WITH_WRAP + struct request_info wrap_req; + char address[1024]; +#endif + + new_sock = accept(listensock, NULL, 0); + if(new_sock == INVALID_SOCKET) return -1; + +#ifdef WITH_SYS_TREE + g_socket_connections++; +#endif + + if(_mosquitto_socket_nonblock(new_sock)){ + return INVALID_SOCKET; + } + +#ifdef WITH_WRAP + /* Use tcpd / libwrap to determine whether a connection is allowed. */ + request_init(&wrap_req, RQ_FILE, new_sock, RQ_DAEMON, "mosquitto", 0); + fromhost(&wrap_req); + if(!hosts_access(&wrap_req)){ + /* Access is denied */ + if(!_mosquitto_socket_get_address(new_sock, address, 1024)){ + _mosquitto_log_printf(NULL, MOSQ_LOG_NOTICE, "Client connection from %s denied access by tcpd.", address); + } + COMPAT_CLOSE(new_sock); + return -1; + }else{ +#endif + new_context = mqtt3_context_init(new_sock); + if(!new_context){ + COMPAT_CLOSE(new_sock); + return -1; + } + for(i=0; iconfig->listener_count; i++){ + for(j=0; jconfig->listeners[i].sock_count; j++){ + if(db->config->listeners[i].socks[j] == listensock){ + new_context->listener = &db->config->listeners[i]; + new_context->listener->client_count++; + break; + } + } + } + if(!new_context->listener){ + mqtt3_context_cleanup(NULL, new_context, true); + return -1; + } + + if(new_context->listener->max_connections > 0 && new_context->listener->client_count > new_context->listener->max_connections){ + _mosquitto_log_printf(NULL, MOSQ_LOG_NOTICE, "Client connection from %s denied: max_connections exceeded.", new_context->address); + mqtt3_context_cleanup(NULL, new_context, true); + return -1; + } + +#ifdef WITH_TLS + /* TLS init */ + for(i=0; iconfig->listener_count; i++){ + for(j=0; jconfig->listeners[i].sock_count; j++){ + if(db->config->listeners[i].socks[j] == listensock){ + if(db->config->listeners[i].ssl_ctx){ + new_context->ssl = SSL_new(db->config->listeners[i].ssl_ctx); + if(!new_context->ssl){ + mqtt3_context_cleanup(NULL, new_context, true); + return -1; + } + SSL_set_ex_data(new_context->ssl, tls_ex_index_context, new_context); + SSL_set_ex_data(new_context->ssl, tls_ex_index_listener, &db->config->listeners[i]); + new_context->want_write = true; + bio = BIO_new_socket(new_sock, BIO_NOCLOSE); + SSL_set_bio(new_context->ssl, bio, bio); + rc = SSL_accept(new_context->ssl); + if(rc != 1){ + rc = SSL_get_error(new_context->ssl, rc); + if(rc == SSL_ERROR_WANT_READ){ + /* We always want to read. */ + }else if(rc == SSL_ERROR_WANT_WRITE){ + new_context->want_write = true; + }else{ + e = ERR_get_error(); + while(e){ + _mosquitto_log_printf(NULL, MOSQ_LOG_NOTICE, + "Client connection from %s failed: %s.", + new_context->address, ERR_error_string(e, ebuf)); + e = ERR_get_error(); + } + mqtt3_context_cleanup(NULL, new_context, true); + return -1; + } + } + } + } + } + } +#endif + + _mosquitto_log_printf(NULL, MOSQ_LOG_NOTICE, "New connection from %s on port %d.", new_context->address, new_context->listener->port); + for(i=0; icontext_count; i++){ + if(db->contexts[i] == NULL){ + db->contexts[i] = new_context; + break; + } + } + if(i==db->context_count){ + tmp_contexts = _mosquitto_realloc(db->contexts, sizeof(struct mosquitto*)*(db->context_count+1)); + if(tmp_contexts){ + db->context_count++; + db->contexts = tmp_contexts; + db->contexts[i] = new_context; + }else{ + // Out of memory + mqtt3_context_cleanup(NULL, new_context, true); + return -1; + } + } + // If we got here then the context's DB index is "i" regardless of how we got here + new_context->db_index = i; + +#ifdef WITH_WRAP + } +#endif + return new_sock; +} + +#ifdef WITH_TLS +static int client_certificate_verify(int preverify_ok, X509_STORE_CTX *ctx) +{ + /* Preverify should check expiry, revocation. */ + return preverify_ok; +} +#endif + +#ifdef REAL_WITH_TLS_PSK +static unsigned int psk_server_callback(SSL *ssl, const char *identity, unsigned char *psk, unsigned int max_psk_len) +{ + struct mosquitto_db *db; + struct mosquitto *context; + struct _mqtt3_listener *listener; + char *psk_key = NULL; + int len; + const char *psk_hint; + + if(!identity) return 0; + + db = _mosquitto_get_db(); + + context = SSL_get_ex_data(ssl, tls_ex_index_context); + if(!context) return 0; + + listener = SSL_get_ex_data(ssl, tls_ex_index_listener); + if(!listener) return 0; + + psk_hint = listener->psk_hint; + + /* The hex to BN conversion results in the length halving, so we can pass + * max_psk_len*2 as the max hex key here. */ + psk_key = _mosquitto_calloc(1, max_psk_len*2 + 1); + if(!psk_key) return 0; + + if(mosquitto_psk_key_get(db, psk_hint, identity, psk_key, max_psk_len*2) != MOSQ_ERR_SUCCESS){ + return 0; + } + + len = _mosquitto_hex2bin(psk_key, psk, max_psk_len); + if (len < 0) return 0; + + if(listener->use_identity_as_username){ + context->username = _mosquitto_strdup(identity); + if(!context->username) return 0; + } + + return len; +} +#endif + +/* Creates a socket and listens on port 'port'. + * Returns 1 on failure + * Returns 0 on success. + */ +int mqtt3_socket_listen(struct _mqtt3_listener *listener) +{ + int sock = -1; + struct addrinfo hints; + struct addrinfo *ainfo, *rp; + char service[10]; +#ifndef WIN32 + int ss_opt = 1; +#else + char ss_opt = 1; +#endif +#ifdef WITH_TLS + int rc; + X509_STORE *store; + X509_LOOKUP *lookup; + int ssl_options = 0; +#endif + char buf[256]; + + if(!listener) return MOSQ_ERR_INVAL; + + snprintf(service, 10, "%d", listener->port); + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = PF_UNSPEC; + hints.ai_flags = AI_PASSIVE; + hints.ai_socktype = SOCK_STREAM; + + if(getaddrinfo(listener->host, service, &hints, &ainfo)) return INVALID_SOCKET; + + listener->sock_count = 0; + listener->socks = NULL; + + for(rp = ainfo; rp; rp = rp->ai_next){ + if(rp->ai_family == AF_INET){ + _mosquitto_log_printf(NULL, MOSQ_LOG_INFO, "Opening ipv4 listen socket on port %d.", ntohs(((struct sockaddr_in *)rp->ai_addr)->sin_port)); + }else if(rp->ai_family == AF_INET6){ + _mosquitto_log_printf(NULL, MOSQ_LOG_INFO, "Opening ipv6 listen socket on port %d.", ntohs(((struct sockaddr_in6 *)rp->ai_addr)->sin6_port)); + }else{ + continue; + } + + sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if(sock == -1){ + strerror_r(errno, buf, 256); + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: %s", buf); + continue; + } + listener->sock_count++; + listener->socks = _mosquitto_realloc(listener->socks, sizeof(int)*listener->sock_count); + if(!listener->socks){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory."); + return MOSQ_ERR_NOMEM; + } + listener->socks[listener->sock_count-1] = sock; + +#ifndef WIN32 + ss_opt = 1; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &ss_opt, sizeof(ss_opt)); +#endif + ss_opt = 1; + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &ss_opt, sizeof(ss_opt)); + + if(_mosquitto_socket_nonblock(sock)){ + return 1; + } + + if(bind(sock, rp->ai_addr, rp->ai_addrlen) == -1){ + strerror_r(errno, buf, 256); + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: %s", buf); + COMPAT_CLOSE(sock); + return 1; + } + + if(listen(sock, 100) == -1){ + strerror_r(errno, buf, 256); + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: %s", buf); + COMPAT_CLOSE(sock); + return 1; + } + } + freeaddrinfo(ainfo); + + /* We need to have at least one working socket. */ + if(listener->sock_count > 0){ +#ifdef WITH_TLS + if((listener->cafile || listener->capath) && listener->certfile && listener->keyfile){ +#if OPENSSL_VERSION_NUMBER >= 0x10001000L + if(listener->tls_version == NULL){ + listener->ssl_ctx = SSL_CTX_new(TLSv1_2_server_method()); + }else if(!strcmp(listener->tls_version, "tlsv1.2")){ + listener->ssl_ctx = SSL_CTX_new(TLSv1_2_server_method()); + }else if(!strcmp(listener->tls_version, "tlsv1.1")){ + listener->ssl_ctx = SSL_CTX_new(TLSv1_1_server_method()); + }else if(!strcmp(listener->tls_version, "tlsv1")){ + listener->ssl_ctx = SSL_CTX_new(SSLv23_server_method()); + } +#else + listener->ssl_ctx = SSL_CTX_new(SSLv23_server_method()); +#endif + if(!listener->ssl_ctx){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Unable to create TLS context."); + COMPAT_CLOSE(sock); + return 1; + } + + /* Don't accept SSLv2 or SSLv3 */ + ssl_options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3; +#ifdef SSL_OP_NO_COMPRESSION + /* Disable compression */ + ssl_options |= SSL_OP_NO_COMPRESSION; +#endif +#ifdef SSL_OP_CIPHER_SERVER_PREFERENCE + /* Server chooses cipher */ + ssl_options |= SSL_OP_CIPHER_SERVER_PREFERENCE; +#endif + SSL_CTX_set_options(listener->ssl_ctx, ssl_options); + +#ifdef SSL_MODE_RELEASE_BUFFERS + /* Use even less memory per SSL connection. */ + SSL_CTX_set_mode(listener->ssl_ctx, SSL_MODE_RELEASE_BUFFERS); +#endif + snprintf(buf, 256, "mosquitto-%d", listener->port); + SSL_CTX_set_session_id_context(listener->ssl_ctx, (unsigned char *)buf, strlen(buf)); + + if(listener->ciphers){ + rc = SSL_CTX_set_cipher_list(listener->ssl_ctx, listener->ciphers); + if(rc == 0){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Unable to set TLS ciphers. Check cipher list \"%s\".", listener->ciphers); + COMPAT_CLOSE(sock); + return 1; + } + }else{ + rc = SSL_CTX_set_cipher_list(listener->ssl_ctx, "DEFAULT:!aNULL:!eNULL:!LOW:!EXPORT:!SSLv2:@STRENGTH"); + if(rc == 0){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Unable to set TLS ciphers. Check cipher list \"%s\".", listener->ciphers); + COMPAT_CLOSE(sock); + return 1; + } + } + rc = SSL_CTX_load_verify_locations(listener->ssl_ctx, listener->cafile, listener->capath); + if(rc == 0){ + if(listener->cafile && listener->capath){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Unable to load CA certificates. Check cafile \"%s\" and capath \"%s\".", listener->cafile, listener->capath); + }else if(listener->cafile){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Unable to load CA certificates. Check cafile \"%s\".", listener->cafile); + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Unable to load CA certificates. Check capath \"%s\".", listener->capath); + } + COMPAT_CLOSE(sock); + return 1; + } + /* FIXME user data? */ + if(listener->require_certificate){ + SSL_CTX_set_verify(listener->ssl_ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, client_certificate_verify); + }else{ + SSL_CTX_set_verify(listener->ssl_ctx, SSL_VERIFY_PEER, client_certificate_verify); + } + rc = SSL_CTX_use_certificate_chain_file(listener->ssl_ctx, listener->certfile); + if(rc != 1){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Unable to load server certificate \"%s\". Check certfile.", listener->certfile); + COMPAT_CLOSE(sock); + return 1; + } + rc = SSL_CTX_use_PrivateKey_file(listener->ssl_ctx, listener->keyfile, SSL_FILETYPE_PEM); + if(rc != 1){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Unable to load server key file \"%s\". Check keyfile.", listener->keyfile); + COMPAT_CLOSE(sock); + return 1; + } + rc = SSL_CTX_check_private_key(listener->ssl_ctx); + if(rc != 1){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Server certificate/key are inconsistent."); + COMPAT_CLOSE(sock); + return 1; + } + /* Load CRLs if they exist. */ + if(listener->crlfile){ + store = SSL_CTX_get_cert_store(listener->ssl_ctx); + if(!store){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Unable to obtain TLS store."); + COMPAT_CLOSE(sock); + return 1; + } + lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file()); + rc = X509_load_crl_file(lookup, listener->crlfile, X509_FILETYPE_PEM); + if(rc != 1){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Unable to load certificate revocation file \"%s\". Check crlfile.", listener->crlfile); + COMPAT_CLOSE(sock); + return 1; + } + X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK); + } + +# ifdef REAL_WITH_TLS_PSK + }else if(listener->psk_hint){ + if(tls_ex_index_context == -1){ + tls_ex_index_context = SSL_get_ex_new_index(0, "client context", NULL, NULL, NULL); + } + if(tls_ex_index_listener == -1){ + tls_ex_index_listener = SSL_get_ex_new_index(0, "listener", NULL, NULL, NULL); + } + +#if OPENSSL_VERSION_NUMBER >= 0x10001000L + if(listener->tls_version == NULL){ + listener->ssl_ctx = SSL_CTX_new(TLSv1_2_server_method()); + }else if(!strcmp(listener->tls_version, "tlsv1.2")){ + listener->ssl_ctx = SSL_CTX_new(TLSv1_2_server_method()); + }else if(!strcmp(listener->tls_version, "tlsv1.1")){ + listener->ssl_ctx = SSL_CTX_new(TLSv1_1_server_method()); + }else if(!strcmp(listener->tls_version, "tlsv1")){ + listener->ssl_ctx = SSL_CTX_new(TLSv1_server_method()); + } +#else + listener->ssl_ctx = SSL_CTX_new(TLSv1_server_method()); +#endif + if(!listener->ssl_ctx){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Unable to create TLS context."); + COMPAT_CLOSE(sock); + return 1; + } + SSL_CTX_set_psk_server_callback(listener->ssl_ctx, psk_server_callback); + if(listener->psk_hint){ + rc = SSL_CTX_use_psk_identity_hint(listener->ssl_ctx, listener->psk_hint); + if(rc == 0){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Unable to set TLS PSK hint."); + COMPAT_CLOSE(sock); + return 1; + } + } + if(listener->ciphers){ + rc = SSL_CTX_set_cipher_list(listener->ssl_ctx, listener->ciphers); + if(rc == 0){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Unable to set TLS ciphers. Check cipher list \"%s\".", listener->ciphers); + COMPAT_CLOSE(sock); + return 1; + } + } +# endif /* REAL_WITH_TLS_PSK */ + } +#endif /* WITH_TLS */ + return 0; + }else{ + return 1; + } +} + +int _mosquitto_socket_get_address(int sock, char *buf, int len) +{ + struct sockaddr_storage addr; + socklen_t addrlen; + + addrlen = sizeof(addr); + if(!getpeername(sock, (struct sockaddr *)&addr, &addrlen)){ + if(addr.ss_family == AF_INET){ + if(inet_ntop(AF_INET, &((struct sockaddr_in *)&addr)->sin_addr.s_addr, buf, len)){ + return 0; + } + }else if(addr.ss_family == AF_INET6){ + if(inet_ntop(AF_INET6, &((struct sockaddr_in6 *)&addr)->sin6_addr.s6_addr, buf, len)){ + return 0; + } + } + } + return 1; +} diff --git a/src/persist.c b/src/persist.c new file mode 100644 index 0000000000..01866060a7 --- /dev/null +++ b/src/persist.c @@ -0,0 +1,839 @@ +/* +Copyright (c) 2010-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#include + +#ifdef WITH_PERSISTENCE + +#ifndef WIN32 +#include +#endif +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "util_mosq.h" + +static uint32_t db_version; + + +static int _db_restore_sub(struct mosquitto_db *db, const char *client_id, const char *sub, int qos); + +static struct mosquitto *_db_find_or_add_context(struct mosquitto_db *db, const char *client_id, uint16_t last_mid) +{ + struct mosquitto *context; + struct mosquitto **tmp_contexts; + int i; + + context = NULL; + for(i=0; icontext_count; i++){ + if(db->contexts[i] && !strcmp(db->contexts[i]->id, client_id)){ + context = db->contexts[i]; + break; + } + } + if(!context){ + context = mqtt3_context_init(-1); + if(!context) return NULL; + + context->clean_session = false; + + for(i=0; icontext_count; i++){ + if(!db->contexts[i]){ + db->contexts[i] = context; + break; + } + } + if(i==db->context_count){ + db->context_count++; + tmp_contexts = _mosquitto_realloc(db->contexts, sizeof(struct mosquitto*)*db->context_count); + if(tmp_contexts){ + db->contexts = tmp_contexts; + db->contexts[db->context_count-1] = context; + }else{ + mqtt3_context_cleanup(db, context, true); + return NULL; + } + } + context->id = _mosquitto_strdup(client_id); + context->db_index = i; + } + if(last_mid){ + context->last_mid = last_mid; + } + return context; +} + +static int mqtt3_db_client_messages_write(struct mosquitto_db *db, FILE *db_fptr, struct mosquitto *context) +{ + uint32_t length; + dbid_t i64temp; + uint16_t i16temp, slen; + uint8_t i8temp; + struct mosquitto_client_msg *cmsg; + + assert(db); + assert(db_fptr); + assert(context); + + cmsg = context->msgs; + while(cmsg){ + slen = strlen(context->id); + + length = htonl(sizeof(dbid_t) + sizeof(uint16_t) + sizeof(uint8_t) + + sizeof(uint8_t) + sizeof(uint8_t) + sizeof(uint8_t) + + sizeof(uint8_t) + 2+slen); + + i16temp = htons(DB_CHUNK_CLIENT_MSG); + write_e(db_fptr, &i16temp, sizeof(uint16_t)); + write_e(db_fptr, &length, sizeof(uint32_t)); + + i16temp = htons(slen); + write_e(db_fptr, &i16temp, sizeof(uint16_t)); + write_e(db_fptr, context->id, slen); + + i64temp = cmsg->store->db_id; + write_e(db_fptr, &i64temp, sizeof(dbid_t)); + + i16temp = htons(cmsg->mid); + write_e(db_fptr, &i16temp, sizeof(uint16_t)); + + i8temp = (uint8_t )cmsg->qos; + write_e(db_fptr, &i8temp, sizeof(uint8_t)); + + i8temp = (uint8_t )cmsg->retain; + write_e(db_fptr, &i8temp, sizeof(uint8_t)); + + i8temp = (uint8_t )cmsg->direction; + write_e(db_fptr, &i8temp, sizeof(uint8_t)); + + i8temp = (uint8_t )cmsg->state; + write_e(db_fptr, &i8temp, sizeof(uint8_t)); + + i8temp = (uint8_t )cmsg->dup; + write_e(db_fptr, &i8temp, sizeof(uint8_t)); + + cmsg = cmsg->next; + } + + return MOSQ_ERR_SUCCESS; +error: + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: %s.", strerror(errno)); + return 1; +} + + +static int mqtt3_db_message_store_write(struct mosquitto_db *db, FILE *db_fptr) +{ + uint32_t length; + dbid_t i64temp; + uint32_t i32temp; + uint16_t i16temp, slen; + uint8_t i8temp; + struct mosquitto_msg_store *stored; + bool force_no_retain; + + assert(db); + assert(db_fptr); + + stored = db->msg_store; + while(stored){ + if(!strncmp(stored->msg.topic, "$SYS", 4)){ + /* Don't save $SYS messages as retained otherwise they can give + * misleading information when reloaded. They should still be saved + * because a disconnected durable client may have them in their + * queue. */ + force_no_retain = true; + }else{ + force_no_retain = false; + } + length = htonl(sizeof(dbid_t) + 2+strlen(stored->source_id) + + sizeof(uint16_t) + sizeof(uint16_t) + + 2+strlen(stored->msg.topic) + sizeof(uint32_t) + + stored->msg.payloadlen + sizeof(uint8_t) + sizeof(uint8_t)); + + i16temp = htons(DB_CHUNK_MSG_STORE); + write_e(db_fptr, &i16temp, sizeof(uint16_t)); + write_e(db_fptr, &length, sizeof(uint32_t)); + + i64temp = stored->db_id; + write_e(db_fptr, &i64temp, sizeof(dbid_t)); + + slen = strlen(stored->source_id); + i16temp = htons(slen); + write_e(db_fptr, &i16temp, sizeof(uint16_t)); + if(slen){ + write_e(db_fptr, stored->source_id, slen); + } + + i16temp = htons(stored->source_mid); + write_e(db_fptr, &i16temp, sizeof(uint16_t)); + + i16temp = htons(stored->msg.mid); + write_e(db_fptr, &i16temp, sizeof(uint16_t)); + + slen = strlen(stored->msg.topic); + i16temp = htons(slen); + write_e(db_fptr, &i16temp, sizeof(uint16_t)); + write_e(db_fptr, stored->msg.topic, slen); + + i8temp = (uint8_t )stored->msg.qos; + write_e(db_fptr, &i8temp, sizeof(uint8_t)); + + if(force_no_retain == false){ + i8temp = (uint8_t )stored->msg.retain; + }else{ + i8temp = 0; + } + write_e(db_fptr, &i8temp, sizeof(uint8_t)); + + i32temp = htonl(stored->msg.payloadlen); + write_e(db_fptr, &i32temp, sizeof(uint32_t)); + if(stored->msg.payloadlen){ + write_e(db_fptr, stored->msg.payload, (unsigned int)stored->msg.payloadlen); + } + + stored = stored->next; + } + + return MOSQ_ERR_SUCCESS; +error: + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: %s.", strerror(errno)); + return 1; +} + +static int mqtt3_db_client_write(struct mosquitto_db *db, FILE *db_fptr) +{ + int i; + struct mosquitto *context; + uint16_t i16temp, slen; + uint32_t length; + + assert(db); + assert(db_fptr); + + for(i=0; icontext_count; i++){ + context = db->contexts[i]; + if(context && context->clean_session == false){ + length = htonl(2+strlen(context->id) + sizeof(uint16_t) + sizeof(time_t)); + + i16temp = htons(DB_CHUNK_CLIENT); + write_e(db_fptr, &i16temp, sizeof(uint16_t)); + write_e(db_fptr, &length, sizeof(uint32_t)); + + slen = strlen(context->id); + i16temp = htons(slen); + write_e(db_fptr, &i16temp, sizeof(uint16_t)); + write_e(db_fptr, context->id, slen); + i16temp = htons(context->last_mid); + write_e(db_fptr, &i16temp, sizeof(uint16_t)); + write_e(db_fptr, &(context->disconnect_t), sizeof(time_t)); + + if(mqtt3_db_client_messages_write(db, db_fptr, context)) return 1; + } + } + + return MOSQ_ERR_SUCCESS; +error: + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: %s.", strerror(errno)); + return 1; +} + +static int _db_subs_retain_write(struct mosquitto_db *db, FILE *db_fptr, struct _mosquitto_subhier *node, const char *topic) +{ + struct _mosquitto_subhier *subhier; + struct _mosquitto_subleaf *sub; + char *thistopic; + uint32_t length; + uint16_t i16temp; + dbid_t i64temp; + size_t slen; + + slen = strlen(topic) + strlen(node->topic) + 2; + thistopic = _mosquitto_malloc(sizeof(char)*slen); + if(!thistopic) return MOSQ_ERR_NOMEM; + if(strlen(topic)){ + snprintf(thistopic, slen, "%s/%s", topic, node->topic); + }else{ + snprintf(thistopic, slen, "%s", node->topic); + } + + sub = node->subs; + while(sub){ + if(sub->context->clean_session == false){ + length = htonl(2+strlen(sub->context->id) + 2+strlen(thistopic) + sizeof(uint8_t)); + + i16temp = htons(DB_CHUNK_SUB); + write_e(db_fptr, &i16temp, sizeof(uint16_t)); + write_e(db_fptr, &length, sizeof(uint32_t)); + + slen = strlen(sub->context->id); + i16temp = htons(slen); + write_e(db_fptr, &i16temp, sizeof(uint16_t)); + write_e(db_fptr, sub->context->id, slen); + + slen = strlen(thistopic); + i16temp = htons(slen); + write_e(db_fptr, &i16temp, sizeof(uint16_t)); + write_e(db_fptr, thistopic, slen); + + write_e(db_fptr, &sub->qos, sizeof(uint8_t)); + } + sub = sub->next; + } + if(node->retained){ + if(strncmp(node->retained->msg.topic, "$SYS", 4)){ + /* Don't save $SYS messages. */ + length = htonl(sizeof(dbid_t)); + + i16temp = htons(DB_CHUNK_RETAIN); + write_e(db_fptr, &i16temp, sizeof(uint16_t)); + write_e(db_fptr, &length, sizeof(uint32_t)); + + i64temp = node->retained->db_id; + write_e(db_fptr, &i64temp, sizeof(dbid_t)); + } + } + + subhier = node->children; + while(subhier){ + _db_subs_retain_write(db, db_fptr, subhier, thistopic); + subhier = subhier->next; + } + _mosquitto_free(thistopic); + return MOSQ_ERR_SUCCESS; +error: + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: %s.", strerror(errno)); + return 1; +} + +static int mqtt3_db_subs_retain_write(struct mosquitto_db *db, FILE *db_fptr) +{ + struct _mosquitto_subhier *subhier; + + subhier = db->subs.children; + while(subhier){ + _db_subs_retain_write(db, db_fptr, subhier, ""); + subhier = subhier->next; + } + + return MOSQ_ERR_SUCCESS; +} + +int mqtt3_db_backup(struct mosquitto_db *db, bool cleanup, bool shutdown) +{ + int rc = 0; + FILE *db_fptr = NULL; + uint32_t db_version_w = htonl(MOSQ_DB_VERSION); + uint32_t crc = htonl(0); + dbid_t i64temp; + uint32_t i32temp; + uint16_t i16temp; + uint8_t i8temp; + char err[256]; + char *outfile = NULL; + int len; + + if(!db || !db->config || !db->config->persistence_filepath) return MOSQ_ERR_INVAL; + _mosquitto_log_printf(NULL, MOSQ_LOG_INFO, "Saving in-memory database to %s.", db->config->persistence_filepath); + if(cleanup){ + mqtt3_db_store_clean(db); + } + + len = strlen(db->config->persistence_filepath)+5; + outfile = _mosquitto_calloc(len+1, 1); + if(!outfile){ + _mosquitto_log_printf(NULL, MOSQ_LOG_INFO, "Error saving in-memory database, out of memory."); + return MOSQ_ERR_NOMEM; + } + snprintf(outfile, len, "%s.new", db->config->persistence_filepath); + db_fptr = _mosquitto_fopen(outfile, "wb"); + if(db_fptr == NULL){ + _mosquitto_log_printf(NULL, MOSQ_LOG_INFO, "Error saving in-memory database, unable to open %s for writing.", outfile); + goto error; + } + + /* Header */ + write_e(db_fptr, magic, 15); + write_e(db_fptr, &crc, sizeof(uint32_t)); + write_e(db_fptr, &db_version_w, sizeof(uint32_t)); + + /* DB config */ + i16temp = htons(DB_CHUNK_CFG); + write_e(db_fptr, &i16temp, sizeof(uint16_t)); + /* chunk length */ + i32temp = htonl(sizeof(dbid_t) + sizeof(uint8_t) + sizeof(uint8_t)); + write_e(db_fptr, &i32temp, sizeof(uint32_t)); + /* db written at broker shutdown or not */ + i8temp = shutdown; + write_e(db_fptr, &i8temp, sizeof(uint8_t)); + i8temp = sizeof(dbid_t); + write_e(db_fptr, &i8temp, sizeof(uint8_t)); + /* last db mid */ + i64temp = db->last_db_id; + write_e(db_fptr, &i64temp, sizeof(dbid_t)); + + if(mqtt3_db_message_store_write(db, db_fptr)){ + goto error; + } + + mqtt3_db_client_write(db, db_fptr); + mqtt3_db_subs_retain_write(db, db_fptr); + + fclose(db_fptr); + + if(rename(outfile, db->config->persistence_filepath) != 0){ + goto error; + } + _mosquitto_free(outfile); + outfile = NULL; + return rc; +error: + if(outfile) _mosquitto_free(outfile); + strerror_r(errno, err, 256); + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: %s.", err); + if(db_fptr) fclose(db_fptr); + return 1; +} + +static int _db_client_msg_restore(struct mosquitto_db *db, const char *client_id, uint16_t mid, uint8_t qos, uint8_t retain, uint8_t direction, uint8_t state, uint8_t dup, uint64_t store_id) +{ + struct mosquitto_client_msg *cmsg; + struct mosquitto_msg_store *store; + struct mosquitto *context; + + cmsg = _mosquitto_calloc(1, sizeof(struct mosquitto_client_msg)); + if(!cmsg){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory."); + return MOSQ_ERR_NOMEM; + } + + cmsg->store = NULL; + cmsg->mid = mid; + cmsg->qos = qos; + cmsg->retain = retain; + cmsg->direction = direction; + cmsg->state = state; + cmsg->dup = dup; + + store = db->msg_store; + while(store){ + if(store->db_id == store_id){ + cmsg->store = store; + cmsg->store->ref_count++; + break; + } + store = store->next; + } + if(!cmsg->store){ + _mosquitto_free(cmsg); + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error restoring persistent database, message store corrupt."); + return 1; + } + context = _db_find_or_add_context(db, client_id, 0); + if(!context){ + _mosquitto_free(cmsg); + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error restoring persistent database, message store corrupt."); + return 1; + } + if(context->msgs){ + context->last_msg->next = cmsg; + }else{ + context->msgs = cmsg; + } + cmsg->next = NULL; + context->last_msg = cmsg; + + return MOSQ_ERR_SUCCESS; +} + +static int _db_client_chunk_restore(struct mosquitto_db *db, FILE *db_fptr) +{ + uint16_t i16temp, slen, last_mid; + char *client_id = NULL; + int rc = 0; + struct mosquitto *context; + time_t disconnect_t; + struct _clientid_index_hash *new_cih; + + read_e(db_fptr, &i16temp, sizeof(uint16_t)); + slen = ntohs(i16temp); + if(!slen){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Corrupt persistent database."); + fclose(db_fptr); + return 1; + } + client_id = _mosquitto_calloc(slen+1, sizeof(char)); + if(!client_id){ + fclose(db_fptr); + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory."); + return MOSQ_ERR_NOMEM; + } + read_e(db_fptr, client_id, slen); + + read_e(db_fptr, &i16temp, sizeof(uint16_t)); + last_mid = ntohs(i16temp); + + if(db_version == 2){ + disconnect_t = mosquitto_time(); + }else{ + read_e(db_fptr, &disconnect_t, sizeof(time_t)); + } + + context = _db_find_or_add_context(db, client_id, last_mid); + if(context){ + context->disconnect_t = disconnect_t; + }else{ + rc = 1; + } + + _mosquitto_free(client_id); + + if(!rc){ + new_cih = _mosquitto_malloc(sizeof(struct _clientid_index_hash)); + if(!new_cih){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory."); + return MOSQ_ERR_NOMEM; + } + new_cih->id = context->id; + new_cih->db_context_index = context->db_index; + HASH_ADD_KEYPTR(hh, db->clientid_index_hash, context->id, strlen(context->id), new_cih); + } + return rc; +error: + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: %s.", strerror(errno)); + fclose(db_fptr); + if(client_id) _mosquitto_free(client_id); + return 1; +} + +static int _db_client_msg_chunk_restore(struct mosquitto_db *db, FILE *db_fptr) +{ + dbid_t i64temp, store_id; + uint16_t i16temp, slen, mid; + uint8_t qos, retain, direction, state, dup; + char *client_id = NULL; + int rc; + char err[256]; + + read_e(db_fptr, &i16temp, sizeof(uint16_t)); + slen = ntohs(i16temp); + if(!slen){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Corrupt persistent database."); + fclose(db_fptr); + return 1; + } + client_id = _mosquitto_calloc(slen+1, sizeof(char)); + if(!client_id){ + fclose(db_fptr); + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory."); + return MOSQ_ERR_NOMEM; + } + read_e(db_fptr, client_id, slen); + + read_e(db_fptr, &i64temp, sizeof(dbid_t)); + store_id = i64temp; + + read_e(db_fptr, &i16temp, sizeof(uint16_t)); + mid = ntohs(i16temp); + + read_e(db_fptr, &qos, sizeof(uint8_t)); + read_e(db_fptr, &retain, sizeof(uint8_t)); + read_e(db_fptr, &direction, sizeof(uint8_t)); + read_e(db_fptr, &state, sizeof(uint8_t)); + read_e(db_fptr, &dup, sizeof(uint8_t)); + + rc = _db_client_msg_restore(db, client_id, mid, qos, retain, direction, state, dup, store_id); + _mosquitto_free(client_id); + + return rc; +error: + strerror_r(errno, err, 256); + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: %s.", err); + fclose(db_fptr); + if(client_id) _mosquitto_free(client_id); + return 1; +} + +static int _db_msg_store_chunk_restore(struct mosquitto_db *db, FILE *db_fptr) +{ + dbid_t i64temp, store_id; + uint32_t i32temp, payloadlen; + uint16_t i16temp, slen, source_mid; + uint8_t qos, retain, *payload = NULL; + char *source_id = NULL; + char *topic = NULL; + int rc = 0; + struct mosquitto_msg_store *stored = NULL; + char err[256]; + + read_e(db_fptr, &i64temp, sizeof(dbid_t)); + store_id = i64temp; + + read_e(db_fptr, &i16temp, sizeof(uint16_t)); + slen = ntohs(i16temp); + if(slen){ + source_id = _mosquitto_calloc(slen+1, sizeof(char)); + if(!source_id){ + fclose(db_fptr); + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory."); + return MOSQ_ERR_NOMEM; + } + read_e(db_fptr, source_id, slen); + } + read_e(db_fptr, &i16temp, sizeof(uint16_t)); + source_mid = ntohs(i16temp); + + /* This is the mid - don't need it */ + read_e(db_fptr, &i16temp, sizeof(uint16_t)); + + read_e(db_fptr, &i16temp, sizeof(uint16_t)); + slen = ntohs(i16temp); + if(slen){ + topic = _mosquitto_calloc(slen+1, sizeof(char)); + if(!topic){ + fclose(db_fptr); + if(source_id) _mosquitto_free(source_id); + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory."); + return MOSQ_ERR_NOMEM; + } + read_e(db_fptr, topic, slen); + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid msg_store chunk when restoring persistent database."); + fclose(db_fptr); + if(source_id) _mosquitto_free(source_id); + return 1; + } + read_e(db_fptr, &qos, sizeof(uint8_t)); + read_e(db_fptr, &retain, sizeof(uint8_t)); + + read_e(db_fptr, &i32temp, sizeof(uint32_t)); + payloadlen = ntohl(i32temp); + + if(payloadlen){ + payload = _mosquitto_malloc(payloadlen); + if(!payload){ + fclose(db_fptr); + if(source_id) _mosquitto_free(source_id); + _mosquitto_free(topic); + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory."); + return MOSQ_ERR_NOMEM; + } + read_e(db_fptr, payload, payloadlen); + } + + rc = mqtt3_db_message_store(db, source_id, source_mid, topic, qos, payloadlen, payload, retain, &stored, store_id); + if(source_id) _mosquitto_free(source_id); + _mosquitto_free(topic); + _mosquitto_free(payload); + + return rc; +error: + strerror_r(errno, err, 256); + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: %s.", err); + fclose(db_fptr); + if(source_id) _mosquitto_free(source_id); + if(topic) _mosquitto_free(topic); + if(payload) _mosquitto_free(payload); + return 1; +} + +static int _db_retain_chunk_restore(struct mosquitto_db *db, FILE *db_fptr) +{ + dbid_t i64temp, store_id; + struct mosquitto_msg_store *store; + char err[256]; + + if(fread(&i64temp, sizeof(dbid_t), 1, db_fptr) != 1){ + strerror_r(errno, err, 256); + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: %s.", err); + fclose(db_fptr); + return 1; + } + store_id = i64temp; + store = db->msg_store; + while(store){ + if(store->db_id == store_id){ + mqtt3_db_messages_queue(db, NULL, store->msg.topic, store->msg.qos, store->msg.retain, store); + break; + } + store = store->next; + } + return MOSQ_ERR_SUCCESS; +} + +static int _db_sub_chunk_restore(struct mosquitto_db *db, FILE *db_fptr) +{ + uint16_t i16temp, slen; + uint8_t qos; + char *client_id; + char *topic; + int rc = 0; + char err[256]; + + read_e(db_fptr, &i16temp, sizeof(uint16_t)); + slen = ntohs(i16temp); + client_id = _mosquitto_calloc(slen+1, sizeof(char)); + if(!client_id){ + fclose(db_fptr); + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory."); + return MOSQ_ERR_NOMEM; + } + read_e(db_fptr, client_id, slen); + read_e(db_fptr, &i16temp, sizeof(uint16_t)); + slen = ntohs(i16temp); + topic = _mosquitto_calloc(slen+1, sizeof(char)); + if(!topic){ + fclose(db_fptr); + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory."); + _mosquitto_free(client_id); + return MOSQ_ERR_NOMEM; + } + read_e(db_fptr, topic, slen); + read_e(db_fptr, &qos, sizeof(uint8_t)); + if(_db_restore_sub(db, client_id, topic, qos)){ + rc = 1; + } + _mosquitto_free(client_id); + _mosquitto_free(topic); + + return rc; +error: + strerror_r(errno, err, 256); + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: %s.", err); + fclose(db_fptr); + return 1; +} + +int mqtt3_db_restore(struct mosquitto_db *db) +{ + FILE *fptr; + char header[15]; + int rc = 0; + uint32_t crc; + dbid_t i64temp; + uint32_t i32temp, length; + uint16_t i16temp, chunk; + uint8_t i8temp; + ssize_t rlen; + char err[256]; + + assert(db); + assert(db->config); + assert(db->config->persistence_filepath); + + fptr = _mosquitto_fopen(db->config->persistence_filepath, "rb"); + if(fptr == NULL) return MOSQ_ERR_SUCCESS; + read_e(fptr, &header, 15); + if(!memcmp(header, magic, 15)){ + // Restore DB as normal + read_e(fptr, &crc, sizeof(uint32_t)); + read_e(fptr, &i32temp, sizeof(uint32_t)); + db_version = ntohl(i32temp); + /* IMPORTANT - this is where compatibility checks are made. + * Is your DB change still compatible with previous versions? + */ + if(db_version > MOSQ_DB_VERSION && db_version != 0){ + if(db_version == 2){ + /* Addition of disconnect_t to client chunk in v3. */ + }else{ + fclose(fptr); + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Unsupported persistent database format version %d (need version %d).", db_version, MOSQ_DB_VERSION); + return 1; + } + } + + while(rlen = fread(&i16temp, sizeof(uint16_t), 1, fptr), rlen == 1){ + chunk = ntohs(i16temp); + read_e(fptr, &i32temp, sizeof(uint32_t)); + length = ntohl(i32temp); + switch(chunk){ + case DB_CHUNK_CFG: + read_e(fptr, &i8temp, sizeof(uint8_t)); // shutdown + read_e(fptr, &i8temp, sizeof(uint8_t)); // sizeof(dbid_t) + if(i8temp != sizeof(dbid_t)){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Incompatible database configuration (dbid size is %d bytes, expected %lu)", + i8temp, (unsigned long)sizeof(dbid_t)); + fclose(fptr); + return 1; + } + read_e(fptr, &i64temp, sizeof(dbid_t)); + db->last_db_id = i64temp; + break; + + case DB_CHUNK_MSG_STORE: + if(_db_msg_store_chunk_restore(db, fptr)) return 1; + break; + + case DB_CHUNK_CLIENT_MSG: + if(_db_client_msg_chunk_restore(db, fptr)) return 1; + break; + + case DB_CHUNK_RETAIN: + if(_db_retain_chunk_restore(db, fptr)) return 1; + break; + + case DB_CHUNK_SUB: + if(_db_sub_chunk_restore(db, fptr)) return 1; + break; + + case DB_CHUNK_CLIENT: + if(_db_client_chunk_restore(db, fptr)) return 1; + break; + + default: + _mosquitto_log_printf(NULL, MOSQ_LOG_WARNING, "Warning: Unsupported chunk \"%d\" in persistent database file. Ignoring.", chunk); + fseek(fptr, length, SEEK_CUR); + break; + } + } + if(rlen < 0) goto error; + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Unable to restore persistent database. Unrecognised file format."); + rc = 1; + } + + fclose(fptr); + + return rc; +error: + strerror_r(errno, err, 256); + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: %s.", err); + if(fptr) fclose(fptr); + return 1; +} + +static int _db_restore_sub(struct mosquitto_db *db, const char *client_id, const char *sub, int qos) +{ + struct mosquitto *context; + + assert(db); + assert(client_id); + assert(sub); + + context = _db_find_or_add_context(db, client_id, 0); + if(!context) return 1; + return mqtt3_sub_add(db, context, sub, qos, &db->subs); +} + +#endif diff --git a/src/persist.h b/src/persist.h new file mode 100644 index 0000000000..808b05fd3d --- /dev/null +++ b/src/persist.h @@ -0,0 +1,35 @@ +/* +Copyright (c) 2010-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#ifndef PERSIST_H +#define PERSIST_H + +#define MOSQ_DB_VERSION 3 + +/* DB read/write */ +const unsigned char magic[15] = {0x00, 0xB5, 0x00, 'm','o','s','q','u','i','t','t','o',' ','d','b'}; +#define DB_CHUNK_CFG 1 +#define DB_CHUNK_MSG_STORE 2 +#define DB_CHUNK_CLIENT_MSG 3 +#define DB_CHUNK_RETAIN 4 +#define DB_CHUNK_SUB 5 +#define DB_CHUNK_CLIENT 6 +/* End DB read/write */ + +#define read_e(f, b, c) if(fread(b, 1, c, f) != c){ goto error; } +#define write_e(f, b, c) if(fwrite(b, 1, c, f) != c){ goto error; } + +#endif diff --git a/src/read_handle.c b/src/read_handle.c new file mode 100644 index 0000000000..f7b21942e5 --- /dev/null +++ b/src/read_handle.c @@ -0,0 +1,283 @@ +/* +Copyright (c) 2009-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#ifdef WITH_SYS_TREE +extern uint64_t g_pub_bytes_received; +#endif + +int mqtt3_packet_handle(struct mosquitto_db *db, struct mosquitto *context) +{ + if(!context) return MOSQ_ERR_INVAL; + + switch((context->in_packet.command)&0xF0){ + case PINGREQ: + return _mosquitto_handle_pingreq(context); + case PINGRESP: + return _mosquitto_handle_pingresp(context); + case PUBACK: + return _mosquitto_handle_pubackcomp(context, "PUBACK"); + case PUBCOMP: + return _mosquitto_handle_pubackcomp(context, "PUBCOMP"); + case PUBLISH: + return mqtt3_handle_publish(db, context); + case PUBREC: + return _mosquitto_handle_pubrec(context); + case PUBREL: + return _mosquitto_handle_pubrel(db, context); + case CONNECT: + return mqtt3_handle_connect(db, context); + case DISCONNECT: + return mqtt3_handle_disconnect(db, context); + case SUBSCRIBE: + return mqtt3_handle_subscribe(db, context); + case UNSUBSCRIBE: + return mqtt3_handle_unsubscribe(db, context); +#ifdef WITH_BRIDGE + case CONNACK: + return mqtt3_handle_connack(db, context); + case SUBACK: + return _mosquitto_handle_suback(context); + case UNSUBACK: + return _mosquitto_handle_unsuback(context); +#endif + default: + /* If we don't recognise the command, return an error straight away. */ + return MOSQ_ERR_PROTOCOL; + } +} + +int mqtt3_handle_publish(struct mosquitto_db *db, struct mosquitto *context) +{ + char *topic; + void *payload = NULL; + uint32_t payloadlen; + uint8_t dup, qos, retain; + uint16_t mid = 0; + int rc = 0; + uint8_t header = context->in_packet.command; + int res = 0; + struct mosquitto_msg_store *stored = NULL; + int len; + char *topic_mount; +#ifdef WITH_BRIDGE + char *topic_temp; + int i; + struct _mqtt3_bridge_topic *cur_topic; + bool match; +#endif + + dup = (header & 0x08)>>3; + qos = (header & 0x06)>>1; + if(qos == 3){ + _mosquitto_log_printf(NULL, MOSQ_LOG_INFO, + "Invalid QoS in PUBLISH from %s, disconnecting.", context->id); + return 1; + } + retain = (header & 0x01); + + if(_mosquitto_read_string(&context->in_packet, &topic)) return 1; + if(strlen(topic) == 0){ + /* Invalid publish topic, disconnect client. */ + _mosquitto_free(topic); + return 1; + } + if(!strlen(topic)){ + _mosquitto_free(topic); + return 1; + } +#ifdef WITH_BRIDGE + if(context->bridge && context->bridge->topics && context->bridge->topic_remapping){ + for(i=0; ibridge->topic_count; i++){ + cur_topic = &context->bridge->topics[i]; + if((cur_topic->direction == bd_both || cur_topic->direction == bd_in) + && (cur_topic->remote_prefix || cur_topic->local_prefix)){ + + /* Topic mapping required on this topic if the message matches */ + + rc = mosquitto_topic_matches_sub(cur_topic->remote_topic, topic, &match); + if(rc){ + _mosquitto_free(topic); + return rc; + } + if(match){ + if(cur_topic->remote_prefix){ + /* This prefix needs removing. */ + if(!strncmp(cur_topic->remote_prefix, topic, strlen(cur_topic->remote_prefix))){ + topic_temp = _mosquitto_strdup(topic+strlen(cur_topic->remote_prefix)); + if(!topic_temp){ + _mosquitto_free(topic); + return MOSQ_ERR_NOMEM; + } + _mosquitto_free(topic); + topic = topic_temp; + } + } + + if(cur_topic->local_prefix){ + /* This prefix needs adding. */ + len = strlen(topic) + strlen(cur_topic->local_prefix)+1; + topic_temp = _mosquitto_calloc(len+1, sizeof(char)); + if(!topic_temp){ + _mosquitto_free(topic); + return MOSQ_ERR_NOMEM; + } + snprintf(topic_temp, len, "%s%s", cur_topic->local_prefix, topic); + _mosquitto_free(topic); + topic = topic_temp; + } + break; + } + } + } + } +#endif + if(_mosquitto_topic_wildcard_len_check(topic) != MOSQ_ERR_SUCCESS){ + /* Invalid publish topic, just swallow it. */ + _mosquitto_free(topic); + return 1; + } + + if(qos > 0){ + if(_mosquitto_read_uint16(&context->in_packet, &mid)){ + _mosquitto_free(topic); + return 1; + } + } + + payloadlen = context->in_packet.remaining_length - context->in_packet.pos; +#ifdef WITH_SYS_TREE + g_pub_bytes_received += payloadlen; +#endif + if(context->listener && context->listener->mount_point){ + len = strlen(context->listener->mount_point) + strlen(topic) + 1; + topic_mount = _mosquitto_calloc(len, sizeof(char)); + if(!topic_mount){ + _mosquitto_free(topic); + return MOSQ_ERR_NOMEM; + } + snprintf(topic_mount, len, "%s%s", context->listener->mount_point, topic); + _mosquitto_free(topic); + topic = topic_mount; + } + + if(payloadlen){ + if(db->config->message_size_limit && payloadlen > db->config->message_size_limit){ + _mosquitto_log_printf(NULL, MOSQ_LOG_DEBUG, "Dropped too large PUBLISH from %s (d%d, q%d, r%d, m%d, '%s', ... (%ld bytes))", context->id, dup, qos, retain, mid, topic, (long)payloadlen); + goto process_bad_message; + } + payload = _mosquitto_calloc(payloadlen+1, sizeof(uint8_t)); + if(!payload){ + _mosquitto_free(topic); + return 1; + } + if(_mosquitto_read_bytes(&context->in_packet, payload, payloadlen)){ + _mosquitto_free(topic); + _mosquitto_free(payload); + return 1; + } + } + + /* Check for topic access */ + rc = mosquitto_acl_check(db, context, topic, MOSQ_ACL_WRITE); + if(rc == MOSQ_ERR_ACL_DENIED){ + _mosquitto_log_printf(NULL, MOSQ_LOG_DEBUG, "Denied PUBLISH from %s (d%d, q%d, r%d, m%d, '%s', ... (%ld bytes))", context->id, dup, qos, retain, mid, topic, (long)payloadlen); + goto process_bad_message; + }else if(rc != MOSQ_ERR_SUCCESS){ + _mosquitto_free(topic); + if(payload) _mosquitto_free(payload); + return rc; + } + + _mosquitto_log_printf(NULL, MOSQ_LOG_DEBUG, "Received PUBLISH from %s (d%d, q%d, r%d, m%d, '%s', ... (%ld bytes))", context->id, dup, qos, retain, mid, topic, (long)payloadlen); + if(qos > 0){ + mqtt3_db_message_store_find(context, mid, &stored); + } + if(!stored){ + dup = 0; + if(mqtt3_db_message_store(db, context->id, mid, topic, qos, payloadlen, payload, retain, &stored, 0)){ + _mosquitto_free(topic); + if(payload) _mosquitto_free(payload); + return 1; + } + }else{ + dup = 1; + } + switch(qos){ + case 0: + if(mqtt3_db_messages_queue(db, context->id, topic, qos, retain, stored)) rc = 1; + break; + case 1: + if(mqtt3_db_messages_queue(db, context->id, topic, qos, retain, stored)) rc = 1; + if(_mosquitto_send_puback(context, mid)) rc = 1; + break; + case 2: + if(!dup){ + res = mqtt3_db_message_insert(db, context, mid, mosq_md_in, qos, retain, stored); + }else{ + res = 0; + } + /* mqtt3_db_message_insert() returns 2 to indicate dropped message + * due to queue. This isn't an error so don't disconnect them. */ + if(!res){ + if(_mosquitto_send_pubrec(context, mid)) rc = 1; + }else if(res == 1){ + rc = 1; + } + break; + } + _mosquitto_free(topic); + if(payload) _mosquitto_free(payload); + + return rc; +process_bad_message: + _mosquitto_free(topic); + if(payload) _mosquitto_free(payload); + switch(qos){ + case 0: + return MOSQ_ERR_SUCCESS; + case 1: + return _mosquitto_send_puback(context, mid); + case 2: + mqtt3_db_message_store_find(context, mid, &stored); + if(!stored){ + if(mqtt3_db_message_store(db, context->id, mid, NULL, qos, 0, NULL, false, &stored, 0)){ + return 1; + } + res = mqtt3_db_message_insert(db, context, mid, mosq_md_in, qos, false, stored); + }else{ + res = 0; + } + if(!res){ + res = _mosquitto_send_pubrec(context, mid); + } + return res; + } + return 1; +} + diff --git a/src/read_handle_client.c b/src/read_handle_client.c new file mode 100644 index 0000000000..76a1ed284c --- /dev/null +++ b/src/read_handle_client.c @@ -0,0 +1,117 @@ +/* +Copyright (c) 2009-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#include +#include +#include + +#include +#include +#include +#include +#include + +int mqtt3_handle_connack(struct mosquitto_db *db, struct mosquitto *context) +{ + uint8_t byte; + uint8_t rc; + int i; + char *notification_topic; + int notification_topic_len; + char notification_payload; + + if(!context){ + return MOSQ_ERR_INVAL; + } +#ifdef WITH_STRICT_PROTOCOL + if(context->in_packet.remaining_length != 2){ + return MOSQ_ERR_PROTOCOL; + } +#endif + _mosquitto_log_printf(NULL, MOSQ_LOG_DEBUG, "Received CONNACK on connection %s.", context->id); + if(_mosquitto_read_byte(&context->in_packet, &byte)) return 1; // Reserved byte, not used + if(_mosquitto_read_byte(&context->in_packet, &rc)) return 1; + switch(rc){ + case CONNACK_ACCEPTED: + if(context->bridge){ + if(context->bridge->notifications){ + notification_payload = '1'; + if(context->bridge->notification_topic){ + if(_mosquitto_send_real_publish(context, _mosquitto_mid_generate(context), + context->bridge->notification_topic, 1, ¬ification_payload, 1, true, 0)){ + + return 1; + } + mqtt3_db_messages_easy_queue(db, context, context->bridge->notification_topic, 1, 1, ¬ification_payload, 1); + }else{ + notification_topic_len = strlen(context->id)+strlen("$SYS/broker/connection//state"); + notification_topic = _mosquitto_malloc(sizeof(char)*(notification_topic_len+1)); + if(!notification_topic) return MOSQ_ERR_NOMEM; + + snprintf(notification_topic, notification_topic_len+1, "$SYS/broker/connection/%s/state", context->id); + notification_payload = '1'; + if(_mosquitto_send_real_publish(context, _mosquitto_mid_generate(context), + notification_topic, 1, ¬ification_payload, 1, true, 0)){ + + _mosquitto_free(notification_topic); + return 1; + } + mqtt3_db_messages_easy_queue(db, context, notification_topic, 1, 1, ¬ification_payload, 1); + _mosquitto_free(notification_topic); + } + } + for(i=0; ibridge->topic_count; i++){ + if(context->bridge->topics[i].direction == bd_in || context->bridge->topics[i].direction == bd_both){ + if(_mosquitto_send_subscribe(context, NULL, false, context->bridge->topics[i].remote_topic, context->bridge->topics[i].qos)){ + return 1; + } + }else{ + if(_mosquitto_send_unsubscribe(context, NULL, false, context->bridge->topics[i].remote_topic)){ + /* direction = inwards only. This means we should not be subscribed + * to the topic. It is possible that we used to be subscribed to + * this topic so unsubscribe. */ + return 1; + } + } + } + } + context->state = mosq_cs_connected; + return MOSQ_ERR_SUCCESS; + case CONNACK_REFUSED_PROTOCOL_VERSION: + if(context->bridge){ + context->bridge->try_private_accepted = false; + } + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Connection Refused: unacceptable protocol version"); + return 1; + case CONNACK_REFUSED_IDENTIFIER_REJECTED: + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Connection Refused: identifier rejected"); + return 1; + case CONNACK_REFUSED_SERVER_UNAVAILABLE: + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Connection Refused: broker unavailable"); + return 1; + case CONNACK_REFUSED_BAD_USERNAME_PASSWORD: + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Connection Refused: broker unavailable"); + return 1; + case CONNACK_REFUSED_NOT_AUTHORIZED: + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Connection Refused: not authorised"); + return 1; + default: + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Connection Refused: unknown reason"); + return 1; + } + return 1; +} + diff --git a/src/read_handle_server.c b/src/read_handle_server.c new file mode 100644 index 0000000000..5765007e43 --- /dev/null +++ b/src/read_handle_server.c @@ -0,0 +1,695 @@ +/* +Copyright (c) 2009-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#ifdef WITH_SYS_TREE +extern unsigned int g_connection_count; +#endif + +int mqtt3_handle_connect(struct mosquitto_db *db, struct mosquitto *context) +{ + char *protocol_name = NULL; + uint8_t protocol_version; + uint8_t connect_flags; + char *client_id = NULL; + char *will_payload = NULL, *will_topic = NULL; + uint16_t will_payloadlen; + struct mosquitto_message *will_struct = NULL; + uint8_t will, will_retain, will_qos, clean_session; + uint8_t username_flag, password_flag; + char *username = NULL, *password = NULL; + int i; + int rc; + struct _mosquitto_acl_user *acl_tail; + int slen; +#ifdef WITH_TLS + X509 *client_cert; + X509_NAME *name; + X509_NAME_ENTRY *name_entry; +#endif + struct _clientid_index_hash *find_cih; + struct _clientid_index_hash *new_cih; + +#ifdef WITH_SYS_TREE + g_connection_count++; +#endif + + /* Don't accept multiple CONNECT commands. */ + if(context->state != mosq_cs_new){ + mqtt3_context_disconnect(db, context); + return MOSQ_ERR_PROTOCOL; + } + + if(_mosquitto_read_string(&context->in_packet, &protocol_name)){ + mqtt3_context_disconnect(db, context); + return 1; + } + if(!protocol_name){ + mqtt3_context_disconnect(db, context); + return 3; + } + if(_mosquitto_read_byte(&context->in_packet, &protocol_version)){ + mqtt3_context_disconnect(db, context); + return 1; + } + if(!strcmp(protocol_name, PROTOCOL_NAME_v31)){ + if((protocol_version&0x7F) != PROTOCOL_VERSION_v31){ + if(db->config->connection_messages == true){ + _mosquitto_log_printf(NULL, MOSQ_LOG_INFO, "Invalid protocol version %d in CONNECT from %s.", + protocol_version, context->address); + } + _mosquitto_send_connack(context, CONNACK_REFUSED_PROTOCOL_VERSION); + mqtt3_context_disconnect(db, context); + _mosquitto_free(protocol_name); + return MOSQ_ERR_PROTOCOL; + } + context->protocol = mosq_p_mqtt31; + }else if(!strcmp(protocol_name, PROTOCOL_NAME_v311)){ + if((protocol_version&0x7F) != PROTOCOL_VERSION_v311){ + if(db->config->connection_messages == true){ + _mosquitto_log_printf(NULL, MOSQ_LOG_INFO, "Invalid protocol version %d in CONNECT from %s.", + protocol_version, context->address); + } + _mosquitto_send_connack(context, CONNACK_REFUSED_PROTOCOL_VERSION); + mqtt3_context_disconnect(db, context); + _mosquitto_free(protocol_name); + return MOSQ_ERR_PROTOCOL; + } + if((context->in_packet.command&0x0F) != 0x00){ + /* Reserved flags not set to 0, must disconnect. */ + mqtt3_context_disconnect(db, context); + _mosquitto_free(protocol_name); + return MOSQ_ERR_PROTOCOL; + } + context->protocol = mosq_p_mqtt311; + }else{ + if(db->config->connection_messages == true){ + _mosquitto_log_printf(NULL, MOSQ_LOG_INFO, "Invalid protocol \"%s\" in CONNECT from %s.", + protocol_name, context->address); + } + _mosquitto_free(protocol_name); + mqtt3_context_disconnect(db, context); + return MOSQ_ERR_PROTOCOL; + } + _mosquitto_free(protocol_name); + + if(_mosquitto_read_byte(&context->in_packet, &connect_flags)){ + mqtt3_context_disconnect(db, context); + return 1; + } + clean_session = connect_flags & 0x02; + will = connect_flags & 0x04; + will_qos = (connect_flags & 0x18) >> 3; + if(will_qos == 3){ + _mosquitto_log_printf(NULL, MOSQ_LOG_INFO, "Invalid Will QoS in CONNECT from %s.", + context->address); + mqtt3_context_disconnect(db, context); + return MOSQ_ERR_PROTOCOL; + } + will_retain = connect_flags & 0x20; + password_flag = connect_flags & 0x40; + username_flag = connect_flags & 0x80; + + if(_mosquitto_read_uint16(&context->in_packet, &(context->keepalive))){ + mqtt3_context_disconnect(db, context); + return 1; + } + + if(_mosquitto_read_string(&context->in_packet, &client_id)){ + mqtt3_context_disconnect(db, context); + return 1; + } + + slen = strlen(client_id); +#ifdef WITH_STRICT_PROTOCOL + if(slen > 23 || slen == 0){ +#else + if(slen == 0){ +#endif + if(context->protocol == mosq_p_mqtt31){ + _mosquitto_free(client_id); + _mosquitto_send_connack(context, CONNACK_REFUSED_IDENTIFIER_REJECTED); + mqtt3_context_disconnect(db, context); + return MOSQ_ERR_PROTOCOL; + }else{ /* mqtt311 */ + _mosquitto_free(client_id); + + if(clean_session == 0){ + _mosquitto_send_connack(context, CONNACK_REFUSED_IDENTIFIER_REJECTED); + mqtt3_context_disconnect(db, context); + return MOSQ_ERR_PROTOCOL; + } + if(db->config->allow_zero_length_clientid == true){ + client_id = (char *)_mosquitto_calloc(65 + db->config->auto_id_prefix_len, sizeof(char)); + if(!client_id){ + mqtt3_context_disconnect(db, context); + return MOSQ_ERR_NOMEM; + } + memcpy(client_id, db->config->auto_id_prefix, db->config->auto_id_prefix_len); + for(i=0; i<64; i++){ + client_id[i+db->config->auto_id_prefix_len] = (rand()%73)+48; + } + client_id[i] = '\0'; + }else{ + _mosquitto_send_connack(context, CONNACK_REFUSED_IDENTIFIER_REJECTED); + mqtt3_context_disconnect(db, context); + return MOSQ_ERR_PROTOCOL; + } + } + } + + /* clientid_prefixes check */ + if(db->config->clientid_prefixes){ + if(strncmp(db->config->clientid_prefixes, client_id, strlen(db->config->clientid_prefixes))){ + _mosquitto_free(client_id); + _mosquitto_send_connack(context, CONNACK_REFUSED_NOT_AUTHORIZED); + mqtt3_context_disconnect(db, context); + return MOSQ_ERR_SUCCESS; + } + } + + if(will){ + will_struct = _mosquitto_calloc(1, sizeof(struct mosquitto_message)); + if(!will_struct){ + mqtt3_context_disconnect(db, context); + rc = MOSQ_ERR_NOMEM; + goto handle_connect_error; + } + if(_mosquitto_read_string(&context->in_packet, &will_topic)){ + mqtt3_context_disconnect(db, context); + rc = 1; + goto handle_connect_error; + } + if(strlen(will_topic) == 0){ + mqtt3_context_disconnect(db, context); + rc = 1; + goto handle_connect_error; + } + if(_mosquitto_topic_wildcard_pos_check(will_topic)){ + mqtt3_context_disconnect(db, context); + rc = 1; + goto handle_connect_error; + } + + if(_mosquitto_read_uint16(&context->in_packet, &will_payloadlen)){ + mqtt3_context_disconnect(db, context); + rc = 1; + goto handle_connect_error; + } + if(will_payloadlen > 0){ + will_payload = _mosquitto_malloc(will_payloadlen); + if(!will_payload){ + mqtt3_context_disconnect(db, context); + rc = 1; + goto handle_connect_error; + } + + rc = _mosquitto_read_bytes(&context->in_packet, will_payload, will_payloadlen); + if(rc){ + mqtt3_context_disconnect(db, context); + rc = 1; + goto handle_connect_error; + } + } + }else{ + if(context->protocol == mosq_p_mqtt311){ + if(will_qos != 0 || will_retain != 0){ + mqtt3_context_disconnect(db, context); + rc = MOSQ_ERR_PROTOCOL; + goto handle_connect_error; + } + } + } + + if(username_flag){ + rc = _mosquitto_read_string(&context->in_packet, &username); + if(rc == MOSQ_ERR_SUCCESS){ + if(password_flag){ + rc = _mosquitto_read_string(&context->in_packet, &password); + if(rc == MOSQ_ERR_NOMEM){ + rc = MOSQ_ERR_NOMEM; + goto handle_connect_error; + }else if(rc == MOSQ_ERR_PROTOCOL){ + if(context->protocol == mosq_p_mqtt31){ + /* Password flag given, but no password. Ignore. */ + password_flag = 0; + }else if(context->protocol == mosq_p_mqtt311){ + mqtt3_context_disconnect(db, context); + rc = MOSQ_ERR_PROTOCOL; + goto handle_connect_error; + } + } + } + }else if(rc == MOSQ_ERR_NOMEM){ + rc = MOSQ_ERR_NOMEM; + goto handle_connect_error; + }else{ + if(context->protocol == mosq_p_mqtt31){ + /* Username flag given, but no username. Ignore. */ + username_flag = 0; + }else if(context->protocol == mosq_p_mqtt311){ + mqtt3_context_disconnect(db, context); + rc = MOSQ_ERR_PROTOCOL; + goto handle_connect_error; + } + } + }else{ + if(context->protocol == mosq_p_mqtt311){ + if(password_flag){ + /* username_flag == 0 && password_flag == 1 is forbidden */ + mqtt3_context_disconnect(db, context); + rc = MOSQ_ERR_PROTOCOL; + goto handle_connect_error; + } + } + } + +#ifdef WITH_TLS + if(context->listener->use_identity_as_username){ + if(!context->ssl){ + _mosquitto_send_connack(context, CONNACK_REFUSED_BAD_USERNAME_PASSWORD); + mqtt3_context_disconnect(db, context); + rc = MOSQ_ERR_SUCCESS; + goto handle_connect_error; + } +#ifdef REAL_WITH_TLS_PSK + if(context->listener->psk_hint){ + /* Client should have provided an identity to get this far. */ + if(!context->username){ + _mosquitto_send_connack(context, CONNACK_REFUSED_BAD_USERNAME_PASSWORD); + mqtt3_context_disconnect(db, context); + rc = MOSQ_ERR_SUCCESS; + goto handle_connect_error; + } + }else{ +#endif /* REAL_WITH_TLS_PSK */ + client_cert = SSL_get_peer_certificate(context->ssl); + if(!client_cert){ + _mosquitto_send_connack(context, CONNACK_REFUSED_BAD_USERNAME_PASSWORD); + mqtt3_context_disconnect(db, context); + rc = MOSQ_ERR_SUCCESS; + goto handle_connect_error; + } + name = X509_get_subject_name(client_cert); + if(!name){ + _mosquitto_send_connack(context, CONNACK_REFUSED_BAD_USERNAME_PASSWORD); + mqtt3_context_disconnect(db, context); + rc = MOSQ_ERR_SUCCESS; + goto handle_connect_error; + } + + i = X509_NAME_get_index_by_NID(name, NID_commonName, -1); + if(i == -1){ + _mosquitto_send_connack(context, CONNACK_REFUSED_BAD_USERNAME_PASSWORD); + mqtt3_context_disconnect(db, context); + rc = MOSQ_ERR_SUCCESS; + goto handle_connect_error; + } + name_entry = X509_NAME_get_entry(name, i); + context->username = _mosquitto_strdup((char *)ASN1_STRING_data(name_entry->value)); + if(!context->username){ + rc = MOSQ_ERR_SUCCESS; + goto handle_connect_error; + } +#ifdef REAL_WITH_TLS_PSK + } +#endif /* REAL_WITH_TLS_PSK */ + }else{ +#endif /* WITH_TLS */ + if(username_flag){ + rc = mosquitto_unpwd_check(db, username, password); + if(rc == MOSQ_ERR_AUTH){ + _mosquitto_send_connack(context, CONNACK_REFUSED_BAD_USERNAME_PASSWORD); + mqtt3_context_disconnect(db, context); + rc = MOSQ_ERR_SUCCESS; + goto handle_connect_error; + }else if(rc == MOSQ_ERR_INVAL){ + goto handle_connect_error; + } + context->username = username; + context->password = password; + username = NULL; /* Avoid free() in error: below. */ + password = NULL; + } + + if(!username_flag && db->config->allow_anonymous == false){ + _mosquitto_send_connack(context, CONNACK_REFUSED_NOT_AUTHORIZED); + mqtt3_context_disconnect(db, context); + rc = MOSQ_ERR_SUCCESS; + goto handle_connect_error; + } +#ifdef WITH_TLS + } +#endif + + /* Find if this client already has an entry. This must be done *after* any security checks. */ + HASH_FIND_STR(db->clientid_index_hash, client_id, find_cih); + if(find_cih){ + i = find_cih->db_context_index; + /* Found a matching client */ + if(db->contexts[i]->sock == -1){ + /* Client is reconnecting after a disconnect */ + /* FIXME - does anything else need to be done here? */ + }else{ + /* Client is already connected, disconnect old version */ + if(db->config->connection_messages == true){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Client %s already connected, closing old connection.", client_id); + } + } + db->contexts[i]->clean_session = clean_session; + mqtt3_context_cleanup(db, db->contexts[i], false); + db->contexts[i]->state = mosq_cs_connected; + db->contexts[i]->address = _mosquitto_strdup(context->address); + db->contexts[i]->sock = context->sock; + db->contexts[i]->listener = context->listener; + db->contexts[i]->last_msg_in = mosquitto_time(); + db->contexts[i]->last_msg_out = mosquitto_time(); + db->contexts[i]->keepalive = context->keepalive; + db->contexts[i]->pollfd_index = context->pollfd_index; +#ifdef WITH_TLS + db->contexts[i]->ssl = context->ssl; +#endif + if(context->username){ + db->contexts[i]->username = _mosquitto_strdup(context->username); + } + context->sock = -1; +#ifdef WITH_TLS + context->ssl = NULL; +#endif + context->state = mosq_cs_disconnecting; + context = db->contexts[i]; + if(context->msgs){ + mqtt3_db_message_reconnect_reset(context); + } + } + + context->id = client_id; + client_id = NULL; + context->clean_session = clean_session; + context->ping_t = 0; + context->is_dropping = false; + if((protocol_version&0x80) == 0x80){ + context->is_bridge = true; + } + + // Add the client ID to the DB hash table here + new_cih = _mosquitto_malloc(sizeof(struct _clientid_index_hash)); + if(!new_cih){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory."); + mqtt3_context_disconnect(db, context); + rc = MOSQ_ERR_NOMEM; + goto handle_connect_error; + } + new_cih->id = context->id; + new_cih->db_context_index = context->db_index; + HASH_ADD_KEYPTR(hh, db->clientid_index_hash, context->id, strlen(context->id), new_cih); + +#ifdef WITH_PERSISTENCE + if(!clean_session){ + db->persistence_changes++; + } +#endif + /* Associate user with its ACL, assuming we have ACLs loaded. */ + if(db->acl_list){ + acl_tail = db->acl_list; + while(acl_tail){ + if(context->username){ + if(acl_tail->username && !strcmp(context->username, acl_tail->username)){ + context->acl_list = acl_tail; + break; + } + }else{ + if(acl_tail->username == NULL){ + context->acl_list = acl_tail; + break; + } + } + acl_tail = acl_tail->next; + } + }else{ + context->acl_list = NULL; + } + + if(will_struct){ + if(mosquitto_acl_check(db, context, will_topic, MOSQ_ACL_WRITE) != MOSQ_ERR_SUCCESS){ + _mosquitto_send_connack(context, CONNACK_REFUSED_NOT_AUTHORIZED); + mqtt3_context_disconnect(db, context); + rc = MOSQ_ERR_SUCCESS; + goto handle_connect_error; + } + context->will = will_struct; + context->will->topic = will_topic; + if(will_payload){ + context->will->payload = will_payload; + context->will->payloadlen = will_payloadlen; + }else{ + context->will->payload = NULL; + context->will->payloadlen = 0; + } + context->will->qos = will_qos; + context->will->retain = will_retain; + } + + if(db->config->connection_messages == true){ + if(context->is_bridge){ + if(context->username){ + _mosquitto_log_printf(NULL, MOSQ_LOG_NOTICE, "New bridge connected from %s as %s (c%d, k%d, u%s).", context->address, context->id, context->clean_session, context->keepalive, context->username); + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_NOTICE, "New bridge connected from %s as %s (c%d, k%d).", context->address, context->id, context->clean_session, context->keepalive); + } + }else{ + if(context->username){ + _mosquitto_log_printf(NULL, MOSQ_LOG_NOTICE, "New client connected from %s as %s (c%d, k%d, u%s).", context->address, context->id, context->clean_session, context->keepalive, context->username); + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_NOTICE, "New client connected from %s as %s (c%d, k%d).", context->address, context->id, context->clean_session, context->keepalive); + } + } + } + + context->state = mosq_cs_connected; + return _mosquitto_send_connack(context, CONNACK_ACCEPTED); + +handle_connect_error: + if(client_id) _mosquitto_free(client_id); + if(username) _mosquitto_free(username); + if(password) _mosquitto_free(password); + if(will_payload) _mosquitto_free(will_payload); + if(will_topic) _mosquitto_free(will_topic); + if(will_struct) _mosquitto_free(will_struct); + return rc; +} + +int mqtt3_handle_disconnect(struct mosquitto_db *db, struct mosquitto *context) +{ + if(!context){ + return MOSQ_ERR_INVAL; + } + if(context->in_packet.remaining_length != 0){ + return MOSQ_ERR_PROTOCOL; + } + _mosquitto_log_printf(NULL, MOSQ_LOG_DEBUG, "Received DISCONNECT from %s", context->id); + if(context->protocol == mosq_p_mqtt311){ + if((context->in_packet.command&0x0F) != 0x00){ + mqtt3_context_disconnect(db, context); + return MOSQ_ERR_PROTOCOL; + } + } + context->state = mosq_cs_disconnecting; + mqtt3_context_disconnect(db, context); + return MOSQ_ERR_SUCCESS; +} + + +int mqtt3_handle_subscribe(struct mosquitto_db *db, struct mosquitto *context) +{ + int rc = 0; + int rc2; + uint16_t mid; + char *sub; + uint8_t qos; + uint8_t *payload = NULL, *tmp_payload; + uint32_t payloadlen = 0; + int len; + char *sub_mount; + + if(!context) return MOSQ_ERR_INVAL; + _mosquitto_log_printf(NULL, MOSQ_LOG_DEBUG, "Received SUBSCRIBE from %s", context->id); + /* FIXME - plenty of potential for memory leaks here */ + + if(context->protocol == mosq_p_mqtt311){ + if((context->in_packet.command&0x0F) != 0x02){ + return MOSQ_ERR_PROTOCOL; + } + } + if(_mosquitto_read_uint16(&context->in_packet, &mid)) return 1; + + while(context->in_packet.pos < context->in_packet.remaining_length){ + sub = NULL; + if(_mosquitto_read_string(&context->in_packet, &sub)){ + if(payload) _mosquitto_free(payload); + return 1; + } + + if(sub){ + if(!strlen(sub)){ + _mosquitto_log_printf(NULL, MOSQ_LOG_INFO, "Empty subscription string from %s, disconnecting.", + context->address); + _mosquitto_free(sub); + if(payload) _mosquitto_free(payload); + return 1; + } + if(_mosquitto_topic_wildcard_pos_check(sub)){ + _mosquitto_log_printf(NULL, MOSQ_LOG_INFO, "Invalid subscription string from %s, disconnecting.", + context->address); + _mosquitto_free(sub); + if(payload) _mosquitto_free(payload); + return 1; + } + + if(_mosquitto_read_byte(&context->in_packet, &qos)){ + _mosquitto_free(sub); + if(payload) _mosquitto_free(payload); + return 1; + } + if(qos > 2){ + _mosquitto_log_printf(NULL, MOSQ_LOG_INFO, "Invalid QoS in subscription command from %s, disconnecting.", + context->address); + _mosquitto_free(sub); + if(payload) _mosquitto_free(payload); + return 1; + } + if(context->listener && context->listener->mount_point){ + len = strlen(context->listener->mount_point) + strlen(sub) + 1; + sub_mount = _mosquitto_calloc(len, sizeof(char)); + if(!sub_mount){ + _mosquitto_free(sub); + if(payload) _mosquitto_free(payload); + return MOSQ_ERR_NOMEM; + } + snprintf(sub_mount, len, "%s%s", context->listener->mount_point, sub); + _mosquitto_free(sub); + sub = sub_mount; + + } + _mosquitto_log_printf(NULL, MOSQ_LOG_DEBUG, "\t%s (QoS %d)", sub, qos); + + if(context->protocol == mosq_p_mqtt311){ + rc = mosquitto_acl_check(db, context, sub, MOSQ_ACL_READ); + if(rc == MOSQ_ERR_ACL_DENIED){ + qos = 0x80; + } + } + + if(qos != 0x80){ + rc2 = mqtt3_sub_add(db, context, sub, qos, &db->subs); + if(rc2 == MOSQ_ERR_SUCCESS){ + if(mqtt3_retain_queue(db, context, sub, qos)) rc = 1; + }else if(rc2 != -1){ + rc = rc2; + } + _mosquitto_log_printf(NULL, MOSQ_LOG_SUBSCRIBE, "%s %d %s", context->id, qos, sub); + } + _mosquitto_free(sub); + + tmp_payload = _mosquitto_realloc(payload, payloadlen + 1); + if(tmp_payload){ + payload = tmp_payload; + payload[payloadlen] = qos; + payloadlen++; + }else{ + if(payload) _mosquitto_free(payload); + + return MOSQ_ERR_NOMEM; + } + } + } + + if(context->protocol == mosq_p_mqtt311){ + if(payloadlen == 0){ + /* No subscriptions specified, protocol error. */ + return MOSQ_ERR_PROTOCOL; + } + } + if(_mosquitto_send_suback(context, mid, payloadlen, payload)) rc = 1; + _mosquitto_free(payload); + +#ifdef WITH_PERSISTENCE + db->persistence_changes++; +#endif + + return rc; +} + +int mqtt3_handle_unsubscribe(struct mosquitto_db *db, struct mosquitto *context) +{ + uint16_t mid; + char *sub; + + if(!context) return MOSQ_ERR_INVAL; + _mosquitto_log_printf(NULL, MOSQ_LOG_DEBUG, "Received UNSUBSCRIBE from %s", context->id); + + if(context->protocol == mosq_p_mqtt311){ + if((context->in_packet.command&0x0F) != 0x02){ + return MOSQ_ERR_PROTOCOL; + } + } + if(_mosquitto_read_uint16(&context->in_packet, &mid)) return 1; + + while(context->in_packet.pos < context->in_packet.remaining_length){ + sub = NULL; + if(_mosquitto_read_string(&context->in_packet, &sub)){ + return 1; + } + + if(sub){ + if(!strlen(sub)){ + _mosquitto_log_printf(NULL, MOSQ_LOG_INFO, "Empty unsubscription string from %s, disconnecting.", + context->id); + _mosquitto_free(sub); + return 1; + } + if(_mosquitto_topic_wildcard_pos_check(sub)){ + _mosquitto_log_printf(NULL, MOSQ_LOG_INFO, "Invalid unsubscription string from %s, disconnecting.", + context->id); + _mosquitto_free(sub); + return 1; + } + + _mosquitto_log_printf(NULL, MOSQ_LOG_DEBUG, "\t%s", sub); + mqtt3_sub_remove(db, context, sub, &db->subs); + _mosquitto_log_printf(NULL, MOSQ_LOG_UNSUBSCRIBE, "%s %s", context->id, sub); + _mosquitto_free(sub); + } + } +#ifdef WITH_PERSISTENCE + db->persistence_changes++; +#endif + + return _mosquitto_send_command_with_mid(context, UNSUBACK, mid, false); +} + diff --git a/src/security.c b/src/security.c new file mode 100644 index 0000000000..802c188bd5 --- /dev/null +++ b/src/security.c @@ -0,0 +1,221 @@ +/* +Copyright (c) 2011-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#include + +#include +#include + +#include +#include "mosquitto_plugin.h" +#include +#include "lib_load.h" + +typedef int (*FUNC_auth_plugin_version)(void); +typedef int (*FUNC_auth_plugin_init)(void **, struct mosquitto_auth_opt *, int); +typedef int (*FUNC_auth_plugin_cleanup)(void *, struct mosquitto_auth_opt *, int); +typedef int (*FUNC_auth_plugin_security_init)(void *, struct mosquitto_auth_opt *, int, bool); +typedef int (*FUNC_auth_plugin_security_cleanup)(void *, struct mosquitto_auth_opt *, int, bool); +typedef int (*FUNC_auth_plugin_acl_check)(void *, const char *, const char *, const char *, int); +typedef int (*FUNC_auth_plugin_unpwd_check)(void *, const char *, const char *); +typedef int (*FUNC_auth_plugin_psk_key_get)(void *, const char *, const char *, char *, int); + +int mosquitto_security_module_init(struct mosquitto_db *db) +{ + void *lib; + int (*plugin_version)(void) = NULL; + int version; + int rc; + if(db->config->auth_plugin){ + lib = LIB_LOAD(db->config->auth_plugin); + if(!lib){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, + "Error: Unable to load auth plugin \"%s\".", db->config->auth_plugin); + return 1; + } + + db->auth_plugin.lib = NULL; + if(!(plugin_version = (FUNC_auth_plugin_version)LIB_SYM(lib, "mosquitto_auth_plugin_version"))){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, + "Error: Unable to load auth plugin function mosquitto_auth_plugin_version()."); + LIB_CLOSE(lib); + return 1; + } + version = plugin_version(); + if(version != MOSQ_AUTH_PLUGIN_VERSION){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, + "Error: Incorrect auth plugin version (got %d, expected %d).", + version, MOSQ_AUTH_PLUGIN_VERSION); + + LIB_CLOSE(lib); + return 1; + } + if(!(db->auth_plugin.plugin_init = (FUNC_auth_plugin_init)LIB_SYM(lib, "mosquitto_auth_plugin_init"))){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, + "Error: Unable to load auth plugin function mosquitto_auth_plugin_init()."); + LIB_CLOSE(lib); + return 1; + } + if(!(db->auth_plugin.plugin_cleanup = (FUNC_auth_plugin_cleanup)LIB_SYM(lib, "mosquitto_auth_plugin_cleanup"))){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, + "Error: Unable to load auth plugin function mosquitto_auth_plugin_cleanup()."); + LIB_CLOSE(lib); + return 1; + } + + if(!(db->auth_plugin.security_init = (FUNC_auth_plugin_security_init)LIB_SYM(lib, "mosquitto_auth_security_init"))){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, + "Error: Unable to load auth plugin function mosquitto_auth_security_init()."); + LIB_CLOSE(lib); + return 1; + } + + if(!(db->auth_plugin.security_cleanup = (FUNC_auth_plugin_security_cleanup)LIB_SYM(lib, "mosquitto_auth_security_cleanup"))){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, + "Error: Unable to load auth plugin function mosquitto_auth_security_cleanup()."); + LIB_CLOSE(lib); + return 1; + } + + if(!(db->auth_plugin.acl_check = (FUNC_auth_plugin_acl_check)LIB_SYM(lib, "mosquitto_auth_acl_check"))){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, + "Error: Unable to load auth plugin function mosquitto_auth_acl_check()."); + LIB_CLOSE(lib); + return 1; + } + + if(!(db->auth_plugin.unpwd_check = (FUNC_auth_plugin_unpwd_check)LIB_SYM(lib, "mosquitto_auth_unpwd_check"))){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, + "Error: Unable to load auth plugin function mosquitto_auth_unpwd_check()."); + LIB_CLOSE(lib); + return 1; + } + + if(!(db->auth_plugin.psk_key_get = (FUNC_auth_plugin_psk_key_get)LIB_SYM(lib, "mosquitto_auth_psk_key_get"))){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, + "Error: Unable to load auth plugin function mosquitto_auth_psk_key_get()."); + LIB_CLOSE(lib); + return 1; + } + + db->auth_plugin.lib = lib; + db->auth_plugin.user_data = NULL; + if(db->auth_plugin.plugin_init){ + rc = db->auth_plugin.plugin_init(&db->auth_plugin.user_data, db->config->auth_options, db->config->auth_option_count); + if(rc){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, + "Error: Authentication plugin returned %d when initialising.", rc); + } + return rc; + } + }else{ + db->auth_plugin.lib = NULL; + db->auth_plugin.plugin_init = NULL; + db->auth_plugin.plugin_cleanup = NULL; + db->auth_plugin.security_init = NULL; + db->auth_plugin.security_cleanup = NULL; + db->auth_plugin.acl_check = NULL; + db->auth_plugin.unpwd_check = NULL; + db->auth_plugin.psk_key_get = NULL; + } + + return MOSQ_ERR_SUCCESS; +} + +int mosquitto_security_module_cleanup(struct mosquitto_db *db) +{ + mosquitto_security_cleanup(db, false); + + if(db->auth_plugin.plugin_cleanup){ + db->auth_plugin.plugin_cleanup(db->auth_plugin.user_data, db->config->auth_options, db->config->auth_option_count); + } + + if(db->config->auth_plugin){ + if(db->auth_plugin.lib){ + LIB_CLOSE(db->auth_plugin.lib); + } + } + db->auth_plugin.lib = NULL; + db->auth_plugin.plugin_init = NULL; + db->auth_plugin.plugin_cleanup = NULL; + db->auth_plugin.security_init = NULL; + db->auth_plugin.security_cleanup = NULL; + db->auth_plugin.acl_check = NULL; + db->auth_plugin.unpwd_check = NULL; + db->auth_plugin.psk_key_get = NULL; + + return MOSQ_ERR_SUCCESS; +} + +int mosquitto_security_init(struct mosquitto_db *db, bool reload) +{ + if(!db->auth_plugin.lib){ + return mosquitto_security_init_default(db, reload); + }else{ + return db->auth_plugin.security_init(db->auth_plugin.user_data, db->config->auth_options, db->config->auth_option_count, reload); + } +} + +/* Apply security settings after a reload. + * Includes: + * - Disconnecting anonymous users if appropriate + * - Disconnecting users with invalid passwords + * - Reapplying ACLs + */ +int mosquitto_security_apply(struct mosquitto_db *db) +{ + if(!db->auth_plugin.lib){ + return mosquitto_security_apply_default(db); + } + return MOSQ_ERR_SUCCESS; +} + +int mosquitto_security_cleanup(struct mosquitto_db *db, bool reload) +{ + if(!db->auth_plugin.lib){ + return mosquitto_security_cleanup_default(db, reload); + }else{ + return db->auth_plugin.security_cleanup(db->auth_plugin.user_data, db->config->auth_options, db->config->auth_option_count, reload); + } +} + +int mosquitto_acl_check(struct mosquitto_db *db, struct mosquitto *context, const char *topic, int access) +{ + if(!db->auth_plugin.lib){ + return mosquitto_acl_check_default(db, context, topic, access); + }else{ + return db->auth_plugin.acl_check(db->auth_plugin.user_data, context->id, context->username, topic, access); + } +} + +int mosquitto_unpwd_check(struct mosquitto_db *db, const char *username, const char *password) +{ + if(!db->auth_plugin.lib){ + return mosquitto_unpwd_check_default(db, username, password); + }else{ + return db->auth_plugin.unpwd_check(db->auth_plugin.user_data, username, password); + } +} + +int mosquitto_psk_key_get(struct mosquitto_db *db, const char *hint, const char *identity, char *key, int max_key_len) +{ + if(!db->auth_plugin.lib){ + return mosquitto_psk_key_get_default(db, hint, identity, key, max_key_len); + }else{ + return db->auth_plugin.psk_key_get(db->auth_plugin.user_data, hint, identity, key, max_key_len); + } +} + diff --git a/src/security_default.c b/src/security_default.c new file mode 100644 index 0000000000..866821708c --- /dev/null +++ b/src/security_default.c @@ -0,0 +1,796 @@ +/* +Copyright (c) 2011-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#include + +#include +#include + +#include +#include +#include "util_mosq.h" + +static int _aclfile_parse(struct mosquitto_db *db); +static int _unpwd_file_parse(struct mosquitto_db *db); +static int _acl_cleanup(struct mosquitto_db *db, bool reload); +static int _unpwd_cleanup(struct _mosquitto_unpwd **unpwd, bool reload); +static int _psk_file_parse(struct mosquitto_db *db); +#ifdef WITH_TLS +static int _pw_digest(const char *password, const unsigned char *salt, unsigned int salt_len, unsigned char *hash, unsigned int *hash_len); +static int _base64_decode(char *in, unsigned char **decoded, unsigned int *decoded_len); +#endif + +int mosquitto_security_init_default(struct mosquitto_db *db, bool reload) +{ + int rc; + + /* Load username/password data if required. */ + if(db->config->password_file){ + rc = _unpwd_file_parse(db); + if(rc){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error opening password file \"%s\".", db->config->password_file); + return rc; + } + } + + /* Load acl data if required. */ + if(db->config->acl_file){ + rc = _aclfile_parse(db); + if(rc){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error opening acl file \"%s\".", db->config->acl_file); + return rc; + } + } + + /* Load psk data if required. */ + if(db->config->psk_file){ + rc = _psk_file_parse(db); + if(rc){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error opening psk file \"%s\".", db->config->psk_file); + return rc; + } + } + + return MOSQ_ERR_SUCCESS; +} + +int mosquitto_security_cleanup_default(struct mosquitto_db *db, bool reload) +{ + int rc; + rc = _acl_cleanup(db, reload); + if(rc != MOSQ_ERR_SUCCESS) return rc; + rc = _unpwd_cleanup(&db->unpwd, reload); + if(rc != MOSQ_ERR_SUCCESS) return rc; + return _unpwd_cleanup(&db->psk_id, reload); +} + + +int _add_acl(struct mosquitto_db *db, const char *user, const char *topic, int access) +{ + struct _mosquitto_acl_user *acl_user=NULL, *user_tail; + struct _mosquitto_acl *acl, *acl_tail; + char *local_topic; + bool new_user = false; + + if(!db || !topic) return MOSQ_ERR_INVAL; + + local_topic = _mosquitto_strdup(topic); + if(!local_topic){ + return MOSQ_ERR_NOMEM; + } + + if(db->acl_list){ + user_tail = db->acl_list; + while(user_tail){ + if(user == NULL){ + if(user_tail->username == NULL){ + acl_user = user_tail; + break; + } + }else if(user_tail->username && !strcmp(user_tail->username, user)){ + acl_user = user_tail; + break; + } + user_tail = user_tail->next; + } + } + if(!acl_user){ + acl_user = _mosquitto_malloc(sizeof(struct _mosquitto_acl_user)); + if(!acl_user){ + _mosquitto_free(local_topic); + return MOSQ_ERR_NOMEM; + } + new_user = true; + if(user){ + acl_user->username = _mosquitto_strdup(user); + if(!acl_user->username){ + _mosquitto_free(local_topic); + _mosquitto_free(acl_user); + return MOSQ_ERR_NOMEM; + } + }else{ + acl_user->username = NULL; + } + acl_user->next = NULL; + acl_user->acl = NULL; + } + + acl= _mosquitto_malloc(sizeof(struct _mosquitto_acl)); + if(!acl) return MOSQ_ERR_NOMEM; + acl->access = access; + acl->topic = local_topic; + acl->next = NULL; + + /* Add acl to user acl list */ + if(acl_user->acl){ + acl_tail = acl_user->acl; + while(acl_tail->next){ + acl_tail = acl_tail->next; + } + acl_tail->next = acl; + }else{ + acl_user->acl = acl; + } + + if(new_user){ + /* Add to end of list */ + if(db->acl_list){ + user_tail = db->acl_list; + while(user_tail->next){ + user_tail = user_tail->next; + } + user_tail->next = acl_user; + }else{ + db->acl_list = acl_user; + } + } + + return MOSQ_ERR_SUCCESS; +} + +int _add_acl_pattern(struct mosquitto_db *db, const char *topic, int access) +{ + struct _mosquitto_acl *acl, *acl_tail; + char *local_topic; + + if(!db || !topic) return MOSQ_ERR_INVAL; + + local_topic = _mosquitto_strdup(topic); + if(!local_topic){ + return MOSQ_ERR_NOMEM; + } + + acl = _mosquitto_malloc(sizeof(struct _mosquitto_acl)); + if(!acl) return MOSQ_ERR_NOMEM; + acl->access = access; + acl->topic = local_topic; + acl->next = NULL; + + if(db->acl_patterns){ + acl_tail = db->acl_patterns; + while(acl_tail->next){ + acl_tail = acl_tail->next; + } + acl_tail->next = acl; + }else{ + db->acl_patterns = acl; + } + + return MOSQ_ERR_SUCCESS; +} + +int mosquitto_acl_check_default(struct mosquitto_db *db, struct mosquitto *context, const char *topic, int access) +{ + char *local_acl; + struct _mosquitto_acl *acl_root; + bool result; + int i; + int len, tlen, clen, ulen; + int ccount, ucount; + char *s; + + if(!db || !context || !topic) return MOSQ_ERR_INVAL; + if(!db->acl_list && !db->acl_patterns) return MOSQ_ERR_SUCCESS; + if(context->bridge) return MOSQ_ERR_SUCCESS; + if(!context->acl_list && !db->acl_patterns) return MOSQ_ERR_ACL_DENIED; + + if(context->acl_list){ + acl_root = context->acl_list->acl; + }else{ + acl_root = NULL; + } + + /* Loop through all ACLs for this client. */ + while(acl_root){ + /* Loop through the topic looking for matches to this ACL. */ + + /* If subscription starts with $, acl_root->topic must also start with $. */ + if(topic[0] == '$' && acl_root->topic[0] != '$'){ + acl_root = acl_root->next; + continue; + } + mosquitto_topic_matches_sub(acl_root->topic, topic, &result); + if(result){ + if(access & acl_root->access){ + /* And access is allowed. */ + return MOSQ_ERR_SUCCESS; + } + } + acl_root = acl_root->next; + } + + acl_root = db->acl_patterns; + /* Loop through all pattern ACLs. */ + clen = strlen(context->id); + while(acl_root){ + tlen = strlen(acl_root->topic); + + ccount = 0; + s = acl_root->topic; + while(s){ + s = strstr(s, "%c"); + if(s){ + ccount++; + s+=2; + } + } + + ucount = 0; + s = acl_root->topic; + while(s){ + s = strstr(s, "%u"); + if(s){ + ucount++; + s+=2; + } + } + + if(ucount && !context->username){ + continue; + } + + ulen = strlen(context->username); + len = tlen + ccount*(clen-2) + ucount*(ulen-2); + local_acl = malloc(len+1); + if(!local_acl) return 1; // FIXME + s = local_acl; + for(i=0; itopic[i] == '%'){ + if(acl_root->topic[i+1] == 'c'){ + i++; + strncpy(s, context->id, clen); + s+=clen; + continue; + }else if(acl_root->topic[i+1] == 'u'){ + i++; + strncpy(s, context->username, ulen); + s+=ulen; + continue; + } + } + s[0] = acl_root->topic[i]; + s++; + } + local_acl[len] = '\0'; + + mosquitto_topic_matches_sub(local_acl, topic, &result); + if(result){ + if(access & acl_root->access){ + /* And access is allowed. */ + return MOSQ_ERR_SUCCESS; + } + } + + _mosquitto_free(local_acl); + + acl_root = acl_root->next; + } + + return MOSQ_ERR_ACL_DENIED; +} + +static int _aclfile_parse(struct mosquitto_db *db) +{ + FILE *aclfile; + char buf[1024]; + char *token; + char *user = NULL; + char *topic; + char *access_s; + int access; + int rc; + int slen; + int topic_pattern; + char *saveptr = NULL; + + if(!db || !db->config) return MOSQ_ERR_INVAL; + if(!db->config->acl_file) return MOSQ_ERR_SUCCESS; + + aclfile = _mosquitto_fopen(db->config->acl_file, "rt"); + if(!aclfile){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Unable to open acl_file \"%s\".", db->config->acl_file); + return 1; + } + + // topic [read|write] + // user + + while(fgets(buf, 1024, aclfile)){ + slen = strlen(buf); + while(slen > 0 && (buf[slen-1] == 10 || buf[slen-1] == 13)){ + buf[slen-1] = '\0'; + slen = strlen(buf); + } + if(buf[0] == '#'){ + continue; + } + token = strtok_r(buf, " ", &saveptr); + if(token){ + if(!strcmp(token, "topic") || !strcmp(token, "pattern")){ + if(!strcmp(token, "topic")){ + topic_pattern = 0; + }else{ + topic_pattern = 1; + } + + access_s = strtok_r(NULL, " ", &saveptr); + if(!access_s){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Empty topic in acl_file."); + if(user) _mosquitto_free(user); + fclose(aclfile); + return MOSQ_ERR_INVAL; + } + token = strtok_r(NULL, " ", &saveptr); + if(token){ + topic = token; + }else{ + topic = access_s; + access_s = NULL; + } + if(access_s){ + if(!strcmp(access_s, "read")){ + access = MOSQ_ACL_READ; + }else if(!strcmp(access_s, "write")){ + access = MOSQ_ACL_WRITE; + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Empty invalid topic access type in acl_file."); + if(user) _mosquitto_free(user); + fclose(aclfile); + return MOSQ_ERR_INVAL; + } + }else{ + access = MOSQ_ACL_READ | MOSQ_ACL_WRITE; + } + if(topic_pattern == 0){ + rc = _add_acl(db, user, topic, access); + }else{ + rc = _add_acl_pattern(db, topic, access); + } + if(rc){ + fclose(aclfile); + return rc; + } + }else if(!strcmp(token, "user")){ + token = strtok_r(NULL, " ", &saveptr); + if(token){ + if(user) _mosquitto_free(user); + user = _mosquitto_strdup(token); + if(!user){ + fclose(aclfile); + return MOSQ_ERR_NOMEM; + } + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Missing username in acl_file."); + if(user) _mosquitto_free(user); + fclose(aclfile); + return 1; + } + } + } + } + + if(user) _mosquitto_free(user); + fclose(aclfile); + + return MOSQ_ERR_SUCCESS; +} + +static void _free_acl(struct _mosquitto_acl *acl) +{ + if(!acl) return; + + if(acl->next){ + _free_acl(acl->next); + } + if(acl->topic){ + _mosquitto_free(acl->topic); + } + _mosquitto_free(acl); +} + +static int _acl_cleanup(struct mosquitto_db *db, bool reload) +{ + int i; + struct _mosquitto_acl_user *user_tail; + + if(!db) return MOSQ_ERR_INVAL; + if(!db->acl_list) return MOSQ_ERR_SUCCESS; + + /* As we're freeing ACLs, we must clear context->acl_list to ensure no + * invalid memory accesses take place later. + * This *requires* the ACLs to be reapplied after _acl_cleanup() + * is called if we are reloading the config. If this is not done, all + * access will be denied to currently connected clients. + */ + if(db->contexts){ + for(i=0; icontext_count; i++){ + if(db->contexts[i] && db->contexts[i]->acl_list){ + db->contexts[i]->acl_list = NULL; + } + } + } + + while(db->acl_list){ + user_tail = db->acl_list->next; + + _free_acl(db->acl_list->acl); + if(db->acl_list->username){ + _mosquitto_free(db->acl_list->username); + } + _mosquitto_free(db->acl_list); + + db->acl_list = user_tail; + } + + if(db->acl_patterns){ + _free_acl(db->acl_patterns); + db->acl_patterns = NULL; + } + return MOSQ_ERR_SUCCESS; +} + +static int _pwfile_parse(const char *file, struct _mosquitto_unpwd **root) +{ + FILE *pwfile; + struct _mosquitto_unpwd *unpwd; + char buf[256]; + char *username, *password; + int len; + char *saveptr = NULL; + + pwfile = _mosquitto_fopen(file, "rt"); + if(!pwfile){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Unable to open pwfile \"%s\".", file); + return 1; + } + + while(!feof(pwfile)){ + if(fgets(buf, 256, pwfile)){ + username = strtok_r(buf, ":", &saveptr); + if(username){ + unpwd = _mosquitto_calloc(1, sizeof(struct _mosquitto_unpwd)); + if(!unpwd){ + fclose(pwfile); + return MOSQ_ERR_NOMEM; + } + unpwd->username = _mosquitto_strdup(username); + if(!unpwd->username){ + _mosquitto_free(unpwd); + fclose(pwfile); + return MOSQ_ERR_NOMEM; + } + len = strlen(unpwd->username); + while(unpwd->username[len-1] == 10 || unpwd->username[len-1] == 13){ + unpwd->username[len-1] = '\0'; + len = strlen(unpwd->username); + } + password = strtok_r(NULL, ":", &saveptr); + if(password){ + unpwd->password = _mosquitto_strdup(password); + if(!unpwd->password){ + fclose(pwfile); + _mosquitto_free(unpwd->username); + _mosquitto_free(unpwd); + return MOSQ_ERR_NOMEM; + } + len = strlen(unpwd->password); + while(len && (unpwd->password[len-1] == 10 || unpwd->password[len-1] == 13)){ + unpwd->password[len-1] = '\0'; + len = strlen(unpwd->password); + } + } + HASH_ADD_KEYPTR(hh, *root, unpwd->username, strlen(unpwd->username), unpwd); + } + } + } + fclose(pwfile); + + return MOSQ_ERR_SUCCESS; +} + +static int _unpwd_file_parse(struct mosquitto_db *db) +{ + int rc; +#ifdef WITH_TLS + struct _mosquitto_unpwd *u, *tmp; + char *token; + unsigned char *salt; + unsigned int salt_len; + unsigned char *password; + unsigned int password_len; +#endif + + if(!db || !db->config) return MOSQ_ERR_INVAL; + + if(!db->config->password_file) return MOSQ_ERR_SUCCESS; + + rc = _pwfile_parse(db->config->password_file, &db->unpwd); +#ifdef WITH_TLS + if(rc) return rc; + + HASH_ITER(hh, db->unpwd, u, tmp){ + /* Need to decode password into hashed data + salt. */ + if(u->password){ + token = strtok(u->password, "$"); + if(token && !strcmp(token, "6")){ + token = strtok(NULL, "$"); + if(token){ + rc = _base64_decode(token, &salt, &salt_len); + if(rc){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Unable to decode password salt for user %s.", u->username); + return MOSQ_ERR_INVAL; + } + u->salt = salt; + u->salt_len = salt_len; + token = strtok(NULL, "$"); + if(token){ + rc = _base64_decode(token, &password, &password_len); + if(rc){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Unable to decode password for user %s.", u->username); + return MOSQ_ERR_INVAL; + } + _mosquitto_free(u->password); + u->password = (char *)password; + u->password_len = password_len; + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid password hash for user %s.", u->username); + return MOSQ_ERR_INVAL; + } + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid password hash for user %s.", u->username); + return MOSQ_ERR_INVAL; + } + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Invalid password hash for user %s.", u->username); + return MOSQ_ERR_INVAL; + } + } + } +#endif + return rc; +} + +static int _psk_file_parse(struct mosquitto_db *db) +{ + int rc; + struct _mosquitto_unpwd *u, *tmp; + + if(!db || !db->config) return MOSQ_ERR_INVAL; + + /* We haven't been asked to parse a psk file. */ + if(!db->config->psk_file) return MOSQ_ERR_SUCCESS; + + rc = _pwfile_parse(db->config->psk_file, &db->psk_id); + if(rc) return rc; + + HASH_ITER(hh, db->psk_id, u, tmp){ + /* Check for hex only digits */ + if(!u->password){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Empty psk for identity \"%s\".", u->username); + return MOSQ_ERR_INVAL; + } + if(strspn(u->password, "0123456789abcdefABCDEF") < strlen(u->password)){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: psk for identity \"%s\" contains non-hexadecimal characters.", u->username); + return MOSQ_ERR_INVAL; + } + } + return MOSQ_ERR_SUCCESS; +} + +int mosquitto_unpwd_check_default(struct mosquitto_db *db, const char *username, const char *password) +{ + struct _mosquitto_unpwd *u, *tmp; +#ifdef WITH_TLS + unsigned char hash[EVP_MAX_MD_SIZE]; + unsigned int hash_len; + int rc; +#endif + + if(!db || !username) return MOSQ_ERR_INVAL; + if(!db->unpwd) return MOSQ_ERR_SUCCESS; + + HASH_ITER(hh, db->unpwd, u, tmp){ + if(!strcmp(u->username, username)){ + if(u->password){ + if(password){ +#ifdef WITH_TLS + rc = _pw_digest(password, u->salt, u->salt_len, hash, &hash_len); + if(rc == MOSQ_ERR_SUCCESS){ + if(hash_len == u->password_len && !memcmp(u->password, hash, hash_len)){ + return MOSQ_ERR_SUCCESS; + }else{ + return MOSQ_ERR_AUTH; + } + }else{ + return rc; + } +#else + if(!strcmp(u->password, password)){ + return MOSQ_ERR_SUCCESS; + } +#endif + }else{ + return MOSQ_ERR_AUTH; + } + }else{ + return MOSQ_ERR_SUCCESS; + } + } + } + + return MOSQ_ERR_AUTH; +} + +static int _unpwd_cleanup(struct _mosquitto_unpwd **root, bool reload) +{ + struct _mosquitto_unpwd *u, *tmp; + + if(!root) return MOSQ_ERR_INVAL; + + HASH_ITER(hh, *root, u, tmp){ + HASH_DEL(*root, u); + if(u->password) _mosquitto_free(u->password); + if(u->username) _mosquitto_free(u->username); +#ifdef WITH_TLS + if(u->salt) _mosquitto_free(u->salt); +#endif + _mosquitto_free(u); + } + + *root = NULL; + + return MOSQ_ERR_SUCCESS; +} + +/* Apply security settings after a reload. + * Includes: + * - Disconnecting anonymous users if appropriate + * - Disconnecting users with invalid passwords + * - Reapplying ACLs + */ +int mosquitto_security_apply_default(struct mosquitto_db *db) +{ + struct _mosquitto_acl_user *acl_user_tail; + bool allow_anonymous; + int i; + + if(!db) return MOSQ_ERR_INVAL; + + allow_anonymous = db->config->allow_anonymous; + + if(db->contexts){ + for(i=0; icontext_count; i++){ + if(db->contexts[i]){ + /* Check for anonymous clients when allow_anonymous is false */ + if(!allow_anonymous && !db->contexts[i]->username){ + db->contexts[i]->state = mosq_cs_disconnecting; + _mosquitto_socket_close(db->contexts[i]); + continue; + } + /* Check for connected clients that are no longer authorised */ + if(mosquitto_unpwd_check_default(db, db->contexts[i]->username, db->contexts[i]->password) != MOSQ_ERR_SUCCESS){ + db->contexts[i]->state = mosq_cs_disconnecting; + _mosquitto_socket_close(db->contexts[i]); + continue; + } + /* Check for ACLs and apply to user. */ + if(db->acl_list){ + acl_user_tail = db->acl_list; + while(acl_user_tail){ + if(acl_user_tail->username){ + if(db->contexts[i]->username){ + if(!strcmp(acl_user_tail->username, db->contexts[i]->username)){ + db->contexts[i]->acl_list = acl_user_tail; + break; + } + } + }else{ + if(!db->contexts[i]->username){ + db->contexts[i]->acl_list = acl_user_tail; + break; + } + } + acl_user_tail = acl_user_tail->next; + } + } + } + } + } + return MOSQ_ERR_SUCCESS; +} + +int mosquitto_psk_key_get_default(struct mosquitto_db *db, const char *hint, const char *identity, char *key, int max_key_len) +{ + struct _mosquitto_unpwd *u, *tmp; + + if(!db || !hint || !identity || !key) return MOSQ_ERR_INVAL; + if(!db->psk_id) return MOSQ_ERR_AUTH; + + HASH_ITER(hh, db->psk_id, u, tmp){ + if(!strcmp(u->username, identity)){ + strncpy(key, u->password, max_key_len); + return MOSQ_ERR_SUCCESS; + } + } + + return MOSQ_ERR_AUTH; +} + +#ifdef WITH_TLS +int _pw_digest(const char *password, const unsigned char *salt, unsigned int salt_len, unsigned char *hash, unsigned int *hash_len) +{ + const EVP_MD *digest; + EVP_MD_CTX context; + + digest = EVP_get_digestbyname("sha512"); + if(!digest){ + // FIXME fprintf(stderr, "Error: Unable to create openssl digest.\n"); + return 1; + } + + EVP_MD_CTX_init(&context); + EVP_DigestInit_ex(&context, digest, NULL); + EVP_DigestUpdate(&context, password, strlen(password)); + EVP_DigestUpdate(&context, salt, salt_len); + /* hash is assumed to be EVP_MAX_MD_SIZE bytes long. */ + EVP_DigestFinal_ex(&context, hash, hash_len); + EVP_MD_CTX_cleanup(&context); + + return MOSQ_ERR_SUCCESS; +} + +int _base64_decode(char *in, unsigned char **decoded, unsigned int *decoded_len) +{ + BIO *bmem, *b64; + + b64 = BIO_new(BIO_f_base64()); + BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); + bmem = BIO_new(BIO_s_mem()); + b64 = BIO_push(b64, bmem); + BIO_write(bmem, in, strlen(in)); + + if(BIO_flush(bmem) != 1){ + BIO_free_all(bmem); + return 1; + } + *decoded = calloc(strlen(in), 1); + *decoded_len = BIO_read(b64, *decoded, strlen(in)); + BIO_free_all(bmem); + + return 0; +} + +#endif diff --git a/src/send_server.c b/src/send_server.c new file mode 100644 index 0000000000..fedf9e390f --- /dev/null +++ b/src/send_server.c @@ -0,0 +1,76 @@ +/* +Copyright (c) 2009-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#include + +#include +#include +#include +#include + +int _mosquitto_send_connack(struct mosquitto *context, int result) +{ + struct _mosquitto_packet *packet = NULL; + int rc; + + if(context){ + if(context->id){ + _mosquitto_log_printf(NULL, MOSQ_LOG_DEBUG, "Sending CONNACK to %s (%d)", context->id, result); + }else{ + _mosquitto_log_printf(NULL, MOSQ_LOG_DEBUG, "Sending CONNACK to %s (%d)", context->address, result); + } + } + + packet = _mosquitto_calloc(1, sizeof(struct _mosquitto_packet)); + if(!packet) return MOSQ_ERR_NOMEM; + + packet->command = CONNACK; + packet->remaining_length = 2; + rc = _mosquitto_packet_alloc(packet); + if(rc){ + _mosquitto_free(packet); + return rc; + } + packet->payload[packet->pos+0] = 0; + packet->payload[packet->pos+1] = result; + + return _mosquitto_packet_queue(context, packet); +} + +int _mosquitto_send_suback(struct mosquitto *context, uint16_t mid, uint32_t payloadlen, const void *payload) +{ + struct _mosquitto_packet *packet = NULL; + int rc; + + _mosquitto_log_printf(NULL, MOSQ_LOG_DEBUG, "Sending SUBACK to %s", context->id); + + packet = _mosquitto_calloc(1, sizeof(struct _mosquitto_packet)); + if(!packet) return MOSQ_ERR_NOMEM; + + packet->command = SUBACK; + packet->remaining_length = 2+payloadlen; + rc = _mosquitto_packet_alloc(packet); + if(rc){ + _mosquitto_free(packet); + return rc; + } + _mosquitto_write_uint16(packet, mid); + if(payloadlen){ + _mosquitto_write_bytes(packet, payload, payloadlen); + } + + return _mosquitto_packet_queue(context, packet); +} diff --git a/src/service.c b/src/service.c new file mode 100644 index 0000000000..ffe9ffe657 --- /dev/null +++ b/src/service.c @@ -0,0 +1,143 @@ +/* +Copyright (c) 2011-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#if defined(WIN32) || defined(__CYGWIN__) + +#include + +#include + +extern int run; +SERVICE_STATUS_HANDLE service_handle = 0; +static SERVICE_STATUS service_status; +int main(int argc, char *argv[]); + +/* Service control callback */ +void __stdcall service_handler(DWORD fdwControl) +{ + switch(fdwControl){ + case SERVICE_CONTROL_CONTINUE: + /* Continue from Paused state. */ + break; + case SERVICE_CONTROL_PAUSE: + /* Pause service. */ + break; + case SERVICE_CONTROL_SHUTDOWN: + /* System is shutting down. */ + case SERVICE_CONTROL_STOP: + /* Service should stop. */ + service_status.dwCurrentState = SERVICE_STOP_PENDING; + SetServiceStatus(service_handle, &service_status); + run = 0; + break; + } +} + +/* Function called when started as a service. */ +void __stdcall service_main(DWORD dwArgc, LPTSTR *lpszArgv) +{ + char **argv; + int argc = 1; + char conf_path[MAX_PATH + 20]; + int rc; + + service_handle = RegisterServiceCtrlHandler("mosquitto", service_handler); + if(service_handle){ + rc = GetEnvironmentVariable("MOSQUITTO_DIR", conf_path, MAX_PATH); + if(!rc || rc == MAX_PATH){ + service_status.dwCurrentState = SERVICE_STOPPED; + SetServiceStatus(service_handle, &service_status); + return; + } + strcat(conf_path, "/mosquitto.conf"); + + argv = _mosquitto_malloc(sizeof(char *)*3); + argv[0] = "mosquitto"; + argv[1] = "-c"; + argv[2] = conf_path; + argc = 3; + + service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + service_status.dwCurrentState = SERVICE_RUNNING; + service_status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP; + service_status.dwWin32ExitCode = NO_ERROR; + service_status.dwCheckPoint = 0; + SetServiceStatus(service_handle, &service_status); + + main(argc, argv); + _mosquitto_free(argv); + + service_status.dwCurrentState = SERVICE_STOPPED; + SetServiceStatus(service_handle, &service_status); + } +} + +void service_install(void) +{ + SC_HANDLE sc_manager, svc_handle; + char exe_path[MAX_PATH + 5]; + SERVICE_DESCRIPTION svc_desc; + + GetModuleFileName(NULL, exe_path, MAX_PATH); + strcat(exe_path, " run"); + + sc_manager = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE); + if(sc_manager){ + svc_handle = CreateService(sc_manager, "mosquitto", "Mosquitto Broker", + SERVICE_START | SERVICE_STOP | SERVICE_CHANGE_CONFIG, + SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, + exe_path, NULL, NULL, NULL, NULL, NULL); + + if(svc_handle){ + svc_desc.lpDescription = "MQTT v3.1 broker"; + ChangeServiceConfig2(svc_handle, SERVICE_CONFIG_DESCRIPTION, &svc_desc); + CloseServiceHandle(svc_handle); + } + CloseServiceHandle(sc_manager); + } +} + +void service_uninstall(void) +{ + SC_HANDLE sc_manager, svc_handle; + SERVICE_STATUS status; + + sc_manager = OpenSCManager(NULL, SERVICES_ACTIVE_DATABASE, SC_MANAGER_CONNECT); + if(sc_manager){ + svc_handle = OpenService(sc_manager, "mosquitto", SERVICE_QUERY_STATUS | DELETE); + if(svc_handle){ + if(QueryServiceStatus(svc_handle, &status)){ + if(status.dwCurrentState == SERVICE_STOPPED){ + DeleteService(svc_handle); + } + } + CloseServiceHandle(svc_handle); + } + CloseServiceHandle(sc_manager); + } +} + +void service_run(void) +{ + SERVICE_TABLE_ENTRY ste[] = { + { "mosquitto", service_main }, + { NULL, NULL } + }; + + StartServiceCtrlDispatcher(ste); +} + +#endif diff --git a/src/subs.c b/src/subs.c new file mode 100644 index 0000000000..a58cf680b9 --- /dev/null +++ b/src/subs.c @@ -0,0 +1,662 @@ +/* +Copyright (c) 2010-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +/* A note on matching topic subscriptions. + * + * Topics can be up to 32767 characters in length. The / character is used as a + * hierarchy delimiter. Messages are published to a particular topic. + * Clients may subscribe to particular topics directly, but may also use + * wildcards in subscriptions. The + and # characters are used as wildcards. + * The # wildcard can be used at the end of a subscription only, and is a + * wildcard for the level of hierarchy at which it is placed and all subsequent + * levels. + * The + wildcard may be used at any point within the subscription and is a + * wildcard for only the level of hierarchy at which it is placed. + * Neither wildcard may be used as part of a substring. + * Valid: + * a/b/+ + * a/+/c + * a/# + * a/b/# + * # + * +/b/c + * +/+/+ + * Invalid: + * a/#/c + * a+/b/c + * Valid but non-matching: + * a/b + * a/+ + * +/b + * b/c/a + * a/b/d + */ + +#include + +#include +#include +#include + +#include +#include +#include + +struct _sub_token { + struct _sub_token *next; + char *topic; +}; + +static int _subs_process(struct mosquitto_db *db, struct _mosquitto_subhier *hier, const char *source_id, const char *topic, int qos, int retain, struct mosquitto_msg_store *stored, bool set_retain) +{ + int rc = 0; + int rc2; + int client_qos, msg_qos; + uint16_t mid; + struct _mosquitto_subleaf *leaf; + bool client_retain; + + leaf = hier->subs; + + if(retain && set_retain){ +#ifdef WITH_PERSISTENCE + if(strncmp(topic, "$SYS", 4)){ + /* Retained messages count as a persistence change, but only if + * they aren't for $SYS. */ + db->persistence_changes++; + } +#endif + if(hier->retained){ + hier->retained->ref_count--; + /* FIXME - it would be nice to be able to remove the message from the store at this point if ref_count == 0 */ + db->retained_count--; + } + if(stored->msg.payloadlen){ + hier->retained = stored; + hier->retained->ref_count++; + db->retained_count++; + }else{ + hier->retained = NULL; + } + } + while(source_id && leaf){ + if(leaf->context->is_bridge && !strcmp(leaf->context->id, source_id)){ + leaf = leaf->next; + continue; + } + /* Check for ACL topic access. */ + rc2 = mosquitto_acl_check(db, leaf->context, topic, MOSQ_ACL_READ); + if(rc2 == MOSQ_ERR_ACL_DENIED){ + leaf = leaf->next; + continue; + }else if(rc2 == MOSQ_ERR_SUCCESS){ + client_qos = leaf->qos; + + if(db->config->upgrade_outgoing_qos){ + msg_qos = client_qos; + }else{ + if(qos > client_qos){ + msg_qos = client_qos; + }else{ + msg_qos = qos; + } + } + if(msg_qos){ + mid = _mosquitto_mid_generate(leaf->context); + }else{ + mid = 0; + } + if(leaf->context->is_bridge){ + /* If we know the client is a bridge then we should set retain + * even if the message is fresh. If we don't do this, retained + * messages won't be propagated. */ + client_retain = retain; + }else{ + /* Client is not a bridge and this isn't a stale message so + * retain should be false. */ + client_retain = false; + } + if(mqtt3_db_message_insert(db, leaf->context, mid, mosq_md_out, msg_qos, client_retain, stored) == 1) rc = 1; + }else{ + rc = 1; + } + leaf = leaf->next; + } + return rc; +} + +static int _sub_topic_tokenise(const char *subtopic, struct _sub_token **topics) +{ + struct _sub_token *new_topic, *tail = NULL; + int len; + int start, stop, tlen; + int i; + + assert(subtopic); + assert(topics); + + if(subtopic[0] != '$'){ + new_topic = _mosquitto_malloc(sizeof(struct _sub_token)); + if(!new_topic) goto cleanup; + new_topic->next = NULL; + new_topic->topic = _mosquitto_strdup(""); + if(!new_topic->topic) goto cleanup; + + *topics = new_topic; + tail = new_topic; + } + + len = strlen(subtopic); + + if(subtopic[0] == '/'){ + new_topic = _mosquitto_malloc(sizeof(struct _sub_token)); + if(!new_topic) goto cleanup; + new_topic->next = NULL; + new_topic->topic = _mosquitto_strdup(""); + if(!new_topic->topic) goto cleanup; + + *topics = new_topic; + tail = new_topic; + + start = 1; + }else{ + start = 0; + } + + stop = 0; + for(i=start; inext = NULL; + + if(start != stop){ + tlen = stop-start + 1; + + new_topic->topic = _mosquitto_calloc(tlen, sizeof(char)); + if(!new_topic->topic) goto cleanup; + memcpy(new_topic->topic, &subtopic[start], tlen-1); + }else{ + new_topic->topic = _mosquitto_strdup(""); + if(!new_topic->topic) goto cleanup; + } + if(tail){ + tail->next = new_topic; + tail = tail->next; + }else{ + tail = new_topic; + *topics = tail; + } + start = i+1; + } + } + + return MOSQ_ERR_SUCCESS; + +cleanup: + tail = *topics; + *topics = NULL; + while(tail){ + if(tail->topic) _mosquitto_free(tail->topic); + new_topic = tail->next; + _mosquitto_free(tail); + tail = new_topic; + } + return 1; +} + +static int _sub_add(struct mosquitto_db *db, struct mosquitto *context, int qos, struct _mosquitto_subhier *subhier, struct _sub_token *tokens) +{ + struct _mosquitto_subhier *branch, *last = NULL; + struct _mosquitto_subleaf *leaf, *last_leaf; + + if(!tokens){ + if(context){ + leaf = subhier->subs; + last_leaf = NULL; + while(leaf){ + last_leaf = leaf; + leaf = leaf->next; + } + leaf = _mosquitto_malloc(sizeof(struct _mosquitto_subleaf)); + if(!leaf) return MOSQ_ERR_NOMEM; + leaf->next = NULL; + leaf->context = context; + leaf->qos = qos; + if(last_leaf){ + last_leaf->next = leaf; + leaf->prev = last_leaf; + }else{ + subhier->subs = leaf; + leaf->prev = NULL; + } + db->subscription_count++; + } + return MOSQ_ERR_SUCCESS; + } + + branch = subhier->children; + while(branch){ + if(!strcmp(branch->topic, tokens->topic)){ + return _sub_add(db, context, qos, branch, tokens->next); + } + last = branch; + branch = branch->next; + } + /* Not found */ + branch = _mosquitto_calloc(1, sizeof(struct _mosquitto_subhier)); + if(!branch) return MOSQ_ERR_NOMEM; + branch->topic = _mosquitto_strdup(tokens->topic); + if(!branch->topic){ + _mosquitto_free(branch); + return MOSQ_ERR_NOMEM; + } + if(!last){ + subhier->children = branch; + }else{ + last->next = branch; + } + return _sub_add(db, context, qos, branch, tokens->next); +} + +static int _sub_remove(struct mosquitto_db *db, struct mosquitto *context, struct _mosquitto_subhier *subhier, struct _sub_token *tokens) +{ + struct _mosquitto_subhier *branch, *last = NULL; + struct _mosquitto_subleaf *leaf; + + if(!tokens){ + leaf = subhier->subs; + while(leaf){ + if(leaf->context==context){ + db->subscription_count--; + if(leaf->prev){ + leaf->prev->next = leaf->next; + }else{ + subhier->subs = leaf->next; + } + if(leaf->next){ + leaf->next->prev = leaf->prev; + } + _mosquitto_free(leaf); + return MOSQ_ERR_SUCCESS; + } + leaf = leaf->next; + } + return MOSQ_ERR_SUCCESS; + } + + branch = subhier->children; + while(branch){ + if(!strcmp(branch->topic, tokens->topic)){ + _sub_remove(db, context, branch, tokens->next); + if(!branch->children && !branch->subs && !branch->retained){ + if(last){ + last->next = branch->next; + }else{ + subhier->children = branch->next; + } + _mosquitto_free(branch->topic); + _mosquitto_free(branch); + } + return MOSQ_ERR_SUCCESS; + } + last = branch; + branch = branch->next; + } + return MOSQ_ERR_SUCCESS; +} + +static void _sub_search(struct mosquitto_db *db, struct _mosquitto_subhier *subhier, struct _sub_token *tokens, const char *source_id, const char *topic, int qos, int retain, struct mosquitto_msg_store *stored, bool set_retain) +{ + /* FIXME - need to take into account source_id if the client is a bridge */ + struct _mosquitto_subhier *branch; + bool sr; + + branch = subhier->children; + while(branch){ + sr = set_retain; + + if(tokens && tokens->topic && (!strcmp(branch->topic, tokens->topic) || !strcmp(branch->topic, "+"))){ + /* The topic matches this subscription. + * Doesn't include # wildcards */ + if(!strcmp(branch->topic, "+")){ + /* Don't set a retained message where + is in the hierarchy. */ + sr = false; + } + _sub_search(db, branch, tokens->next, source_id, topic, qos, retain, stored, sr); + if(!tokens->next){ + _subs_process(db, branch, source_id, topic, qos, retain, stored, sr); + } + }else if(!strcmp(branch->topic, "#") && !branch->children){ + /* The topic matches due to a # wildcard - process the + * subscriptions but *don't* return. Although this branch has ended + * there may still be other subscriptions to deal with. + */ + _subs_process(db, branch, source_id, topic, qos, retain, stored, false); + } + branch = branch->next; + } +} + +int mqtt3_sub_add(struct mosquitto_db *db, struct mosquitto *context, const char *sub, int qos, struct _mosquitto_subhier *root) +{ + int rc = 0; + struct _mosquitto_subhier *subhier, *child; + struct _sub_token *tokens = NULL, *tail; + + assert(root); + assert(sub); + + if(_sub_topic_tokenise(sub, &tokens)) return 1; + + subhier = root->children; + while(subhier){ + if(!strcmp(subhier->topic, tokens->topic)){ + rc = _sub_add(db, context, qos, subhier, tokens); + break; + } + subhier = subhier->next; + } + if(!subhier){ + child = _mosquitto_malloc(sizeof(struct _mosquitto_subhier)); + if(!child){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory."); + return MOSQ_ERR_NOMEM; + } + child->next = NULL; + child->topic = _mosquitto_strdup(tokens->topic); + if(!child->topic){ + _mosquitto_log_printf(NULL, MOSQ_LOG_ERR, "Error: Out of memory."); + return MOSQ_ERR_NOMEM; + } + child->subs = NULL; + child->children = NULL; + child->retained = NULL; + db->subs.children = child; + + rc = _sub_add(db, context, qos, child, tokens); + } + + while(tokens){ + tail = tokens->next; + _mosquitto_free(tokens->topic); + _mosquitto_free(tokens); + tokens = tail; + } + /* We aren't worried about -1 (already subscribed) return codes. */ + if(rc == -1) rc = MOSQ_ERR_SUCCESS; + return rc; +} + +int mqtt3_sub_remove(struct mosquitto_db *db, struct mosquitto *context, const char *sub, struct _mosquitto_subhier *root) +{ + int rc = 0; + struct _mosquitto_subhier *subhier; + struct _sub_token *tokens = NULL, *tail; + + assert(root); + assert(sub); + + if(_sub_topic_tokenise(sub, &tokens)) return 1; + + subhier = root->children; + while(subhier){ + if(!strcmp(subhier->topic, tokens->topic)){ + rc = _sub_remove(db, context, subhier, tokens); + break; + } + subhier = subhier->next; + } + + while(tokens){ + tail = tokens->next; + _mosquitto_free(tokens->topic); + _mosquitto_free(tokens); + tokens = tail; + } + + return rc; +} + +int mqtt3_db_messages_queue(struct mosquitto_db *db, const char *source_id, const char *topic, int qos, int retain, struct mosquitto_msg_store *stored) +{ + int rc = 0; + struct _mosquitto_subhier *subhier; + struct _sub_token *tokens = NULL, *tail; + + assert(db); + assert(topic); + + if(_sub_topic_tokenise(topic, &tokens)) return 1; + + subhier = db->subs.children; + while(subhier){ + if(!strcmp(subhier->topic, tokens->topic)){ + if(retain){ + /* We have a message that needs to be retained, so ensure that the subscription + * tree for its topic exists. + */ + _sub_add(db, NULL, 0, subhier, tokens); + } + _sub_search(db, subhier, tokens, source_id, topic, qos, retain, stored, true); + } + subhier = subhier->next; + } + while(tokens){ + tail = tokens->next; + _mosquitto_free(tokens->topic); + _mosquitto_free(tokens); + tokens = tail; + } + + return rc; +} + +static int _subs_clean_session(struct mosquitto_db *db, struct mosquitto *context, struct _mosquitto_subhier *root) +{ + int rc = 0; + struct _mosquitto_subhier *child, *last = NULL; + struct _mosquitto_subleaf *leaf, *next; + + if(!root) return MOSQ_ERR_SUCCESS; + + leaf = root->subs; + while(leaf){ + if(leaf->context == context){ + db->subscription_count--; + if(leaf->prev){ + leaf->prev->next = leaf->next; + }else{ + root->subs = leaf->next; + } + if(leaf->next){ + leaf->next->prev = leaf->prev; + } + next = leaf->next; + _mosquitto_free(leaf); + leaf = next; + }else{ + leaf = leaf->next; + } + } + + child = root->children; + while(child){ + _subs_clean_session(db, context, child); + if(!child->children && !child->subs && !child->retained){ + if(last){ + last->next = child->next; + }else{ + root->children = child->next; + } + _mosquitto_free(child->topic); + _mosquitto_free(child); + if(last){ + child = last->next; + }else{ + child = root->children; + } + }else{ + last = child; + child = child->next; + } + } + return rc; +} + +/* Remove all subscriptions for a client. + */ +int mqtt3_subs_clean_session(struct mosquitto_db *db, struct mosquitto *context, struct _mosquitto_subhier *root) +{ + struct _mosquitto_subhier *child; + + child = root->children; + while(child){ + _subs_clean_session(db, context, child); + child = child->next; + } + + return MOSQ_ERR_SUCCESS; +} + +void mqtt3_sub_tree_print(struct _mosquitto_subhier *root, int level) +{ + int i; + struct _mosquitto_subhier *branch; + struct _mosquitto_subleaf *leaf; + + for(i=0; itopic); + leaf = root->subs; + while(leaf){ + if(leaf->context){ + printf(" (%s, %d)", leaf->context->id, leaf->qos); + }else{ + printf(" (%s, %d)", "", leaf->qos); + } + leaf = leaf->next; + } + if(root->retained){ + printf(" (r)"); + } + printf("\n"); + + branch = root->children; + while(branch){ + mqtt3_sub_tree_print(branch, level+1); + branch = branch->next; + } +} + +static int _retain_process(struct mosquitto_db *db, struct mosquitto_msg_store *retained, struct mosquitto *context, const char *sub, int sub_qos) +{ + int rc = 0; + int qos; + uint16_t mid; + + rc = mosquitto_acl_check(db, context, retained->msg.topic, MOSQ_ACL_READ); + if(rc == MOSQ_ERR_ACL_DENIED){ + return MOSQ_ERR_SUCCESS; + }else if(rc != MOSQ_ERR_SUCCESS){ + return rc; + } + + qos = retained->msg.qos; + + if(qos > sub_qos) qos = sub_qos; + if(qos > 0){ + mid = _mosquitto_mid_generate(context); + }else{ + mid = 0; + } + return mqtt3_db_message_insert(db, context, mid, mosq_md_out, qos, true, retained); +} + +static int _retain_search(struct mosquitto_db *db, struct _mosquitto_subhier *subhier, struct _sub_token *tokens, struct mosquitto *context, const char *sub, int sub_qos, int level) +{ + struct _mosquitto_subhier *branch; + int flag = 0; + + branch = subhier->children; + while(branch){ + /* Subscriptions with wildcards in aren't really valid topics to publish to + * so they can't have retained messages. + */ + if(!strcmp(tokens->topic, "#") && !tokens->next){ + /* Set flag to indicate that we should check for retained messages + * on "foo" when we are subscribing to e.g. "foo/#" and then exit + * this function and return to an earlier _retain_search(). + */ + flag = -1; + if(branch->retained){ + _retain_process(db, branch->retained, context, sub, sub_qos); + } + if(branch->children){ + _retain_search(db, branch, tokens, context, sub, sub_qos, level+1); + } + }else if(strcmp(branch->topic, "+") && (!strcmp(branch->topic, tokens->topic) || !strcmp(branch->topic, "+"))){ + if(tokens->next){ + if(_retain_search(db, branch, tokens->next, context, sub, sub_qos, level+1) == -1 + || (!branch->next && tokens->next && !strcmp(tokens->next->topic, "#") && level>0)){ + + if(branch->retained){ + _retain_process(db, branch->retained, context, sub, sub_qos); + } + } + }else{ + if(branch->retained){ + _retain_process(db, branch->retained, context, sub, sub_qos); + } + } + } + + branch = branch->next; + } + return flag; +} + +int mqtt3_retain_queue(struct mosquitto_db *db, struct mosquitto *context, const char *sub, int sub_qos) +{ + struct _mosquitto_subhier *subhier; + struct _sub_token *tokens = NULL, *tail; + + assert(db); + assert(context); + assert(sub); + + if(_sub_topic_tokenise(sub, &tokens)) return 1; + + subhier = db->subs.children; + while(subhier){ + if(!strcmp(subhier->topic, tokens->topic)){ + _retain_search(db, subhier, tokens, context, sub, sub_qos, 0); + break; + } + subhier = subhier->next; + } + while(tokens){ + tail = tokens->next; + _mosquitto_free(tokens->topic); + _mosquitto_free(tokens); + tokens = tail; + } + + return MOSQ_ERR_SUCCESS; +} + diff --git a/src/sys_tree.c b/src/sys_tree.c new file mode 100644 index 0000000000..918a5faead --- /dev/null +++ b/src/sys_tree.c @@ -0,0 +1,330 @@ +/* +Copyright (c) 2009-2014 Roger Light + +All rights reserved. This program and the accompanying materials +are made available under the terms of the Eclipse Public License v1.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + http://www.eclipse.org/legal/epl-v10.html +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +Contributors: + Roger Light - initial implementation and documentation. +*/ + +#ifdef WITH_SYS_TREE + +#include +#include + +#include + +#include +#include +#include + +#define BUFLEN 100 + +uint64_t g_bytes_received = 0; +uint64_t g_bytes_sent = 0; +uint64_t g_pub_bytes_received = 0; +uint64_t g_pub_bytes_sent = 0; +unsigned long g_msgs_received = 0; +unsigned long g_msgs_sent = 0; +unsigned long g_pub_msgs_received = 0; +unsigned long g_pub_msgs_sent = 0; +unsigned long g_msgs_dropped = 0; +int g_clients_expired = 0; +unsigned int g_socket_connections = 0; +unsigned int g_connection_count = 0; + +static void _sys_update_clients(struct mosquitto_db *db, char *buf) +{ + static unsigned int client_count = -1; + static int clients_expired = -1; + static unsigned int client_max = -1; + static unsigned int inactive_count = -1; + static unsigned int active_count = -1; + unsigned int value; + unsigned int inactive; + unsigned int active; + + if(!mqtt3_db_client_count(db, &value, &inactive)){ + if(client_count != value){ + client_count = value; + snprintf(buf, BUFLEN, "%d", client_count); + mqtt3_db_messages_easy_queue(db, NULL, "$SYS/broker/clients/total", 2, strlen(buf), buf, 1); + } + if(inactive_count != inactive){ + inactive_count = inactive; + snprintf(buf, BUFLEN, "%d", inactive_count); + mqtt3_db_messages_easy_queue(db, NULL, "$SYS/broker/clients/inactive", 2, strlen(buf), buf, 1); + } + active = client_count - inactive; + if(active_count != active){ + active_count = active; + snprintf(buf, BUFLEN, "%d", active_count); + mqtt3_db_messages_easy_queue(db, NULL, "$SYS/broker/clients/active", 2, strlen(buf), buf, 1); + } + if(value != client_max){ + client_max = value; + snprintf(buf, BUFLEN, "%d", client_max); + mqtt3_db_messages_easy_queue(db, NULL, "$SYS/broker/clients/maximum", 2, strlen(buf), buf, 1); + } + } + if(g_clients_expired != clients_expired){ + clients_expired = g_clients_expired; + snprintf(buf, BUFLEN, "%d", clients_expired); + mqtt3_db_messages_easy_queue(db, NULL, "$SYS/broker/clients/expired", 2, strlen(buf), buf, 1); + } +} + +#ifdef WITH_MEMORY_TRACKING +static void _sys_update_memory(struct mosquitto_db *db, char *buf) +{ + static unsigned long current_heap = -1; + static unsigned long max_heap = -1; + unsigned long value_ul; + + value_ul = _mosquitto_memory_used(); + if(current_heap != value_ul){ + current_heap = value_ul; + snprintf(buf, BUFLEN, "%lu", current_heap); + mqtt3_db_messages_easy_queue(db, NULL, "$SYS/broker/heap/current", 2, strlen(buf), buf, 1); + } + value_ul =_mosquitto_max_memory_used(); + if(max_heap != value_ul){ + max_heap = value_ul; + snprintf(buf, BUFLEN, "%lu", max_heap); + mqtt3_db_messages_easy_queue(db, NULL, "$SYS/broker/heap/maximum", 2, strlen(buf), buf, 1); + } +} +#endif + +static void calc_load(struct mosquitto_db *db, char *buf, const char *topic, double exponent, double interval, double *current) +{ + double new_value; + + new_value = interval + exponent*((*current) - interval); + if(fabs(new_value - (*current)) >= 0.01){ + snprintf(buf, BUFLEN, "%.2f", new_value); + mqtt3_db_messages_easy_queue(db, NULL, topic, 2, strlen(buf), buf, 1); + } + (*current) = new_value; +} + +/* Send messages for the $SYS hierarchy if the last update is longer than + * 'interval' seconds ago. + * 'interval' is the amount of seconds between updates. If 0, then no periodic + * messages are sent for the $SYS hierarchy. + * 'start_time' is the result of time() that the broker was started at. + */ +void mqtt3_db_sys_update(struct mosquitto_db *db, int interval, time_t start_time) +{ + static time_t last_update = 0; + time_t now; + time_t uptime; + char buf[BUFLEN]; + + static int msg_store_count = -1; + static unsigned long msgs_received = -1; + static unsigned long msgs_sent = -1; + static unsigned long publish_dropped = -1; + static unsigned long pub_msgs_received = -1; + static unsigned long pub_msgs_sent = -1; + static unsigned long long bytes_received = -1; + static unsigned long long bytes_sent = -1; + static unsigned long long pub_bytes_received = -1; + static unsigned long long pub_bytes_sent = -1; + static int subscription_count = -1; + static int retained_count = -1; + + static double msgs_received_load1 = 0; + static double msgs_received_load5 = 0; + static double msgs_received_load15 = 0; + static double msgs_sent_load1 = 0; + static double msgs_sent_load5 = 0; + static double msgs_sent_load15 = 0; + static double publish_dropped_load1 = 0; + static double publish_dropped_load5 = 0; + static double publish_dropped_load15 = 0; + double msgs_received_interval, msgs_sent_interval, publish_dropped_interval; + + static double publish_received_load1 = 0; + static double publish_received_load5 = 0; + static double publish_received_load15 = 0; + static double publish_sent_load1 = 0; + static double publish_sent_load5 = 0; + static double publish_sent_load15 = 0; + double publish_received_interval, publish_sent_interval; + + static double bytes_received_load1 = 0; + static double bytes_received_load5 = 0; + static double bytes_received_load15 = 0; + static double bytes_sent_load1 = 0; + static double bytes_sent_load5 = 0; + static double bytes_sent_load15 = 0; + double bytes_received_interval, bytes_sent_interval; + + static double socket_load1 = 0; + static double socket_load5 = 0; + static double socket_load15 = 0; + double socket_interval; + + static double connection_load1 = 0; + static double connection_load5 = 0; + static double connection_load15 = 0; + double connection_interval; + + double exponent; + double i_mult; + + now = mosquitto_time(); + + if(interval && now - interval > last_update){ + uptime = now - start_time; + snprintf(buf, BUFLEN, "%d seconds", (int)uptime); + mqtt3_db_messages_easy_queue(db, NULL, "$SYS/broker/uptime", 2, strlen(buf), buf, 1); + + _sys_update_clients(db, buf); + if(last_update > 0){ + i_mult = 60.0/(double)(now-last_update); + + msgs_received_interval = (g_msgs_received - msgs_received)*i_mult; + msgs_sent_interval = (g_msgs_sent - msgs_sent)*i_mult; + publish_dropped_interval = (g_msgs_dropped - publish_dropped)*i_mult; + + publish_received_interval = (g_pub_msgs_received - pub_msgs_received)*i_mult; + publish_sent_interval = (g_pub_msgs_sent - pub_msgs_sent)*i_mult; + + bytes_received_interval = (g_bytes_received - bytes_received)*i_mult; + bytes_sent_interval = (g_bytes_sent - bytes_sent)*i_mult; + + socket_interval = g_socket_connections*i_mult; + g_socket_connections = 0; + connection_interval = g_connection_count*i_mult; + g_connection_count = 0; + + /* 1 minute load */ + exponent = exp(-1.0*(now-last_update)/60.0); + + calc_load(db, buf, "$SYS/broker/load/messages/received/1min", exponent, msgs_received_interval, &msgs_received_load1); + calc_load(db, buf, "$SYS/broker/load/messages/sent/1min", exponent, msgs_sent_interval, &msgs_sent_load1); + calc_load(db, buf, "$SYS/broker/load/publish/dropped/1min", exponent, publish_dropped_interval, &publish_dropped_load1); + calc_load(db, buf, "$SYS/broker/load/publish/received/1min", exponent, publish_received_interval, &publish_received_load1); + calc_load(db, buf, "$SYS/broker/load/publish/sent/1min", exponent, publish_sent_interval, &publish_sent_load1); + calc_load(db, buf, "$SYS/broker/load/bytes/received/1min", exponent, bytes_received_interval, &bytes_received_load1); + calc_load(db, buf, "$SYS/broker/load/bytes/sent/1min", exponent, bytes_sent_interval, &bytes_sent_load1); + calc_load(db, buf, "$SYS/broker/load/sockets/1min", exponent, socket_interval, &socket_load1); + calc_load(db, buf, "$SYS/broker/load/connections/1min", exponent, connection_interval, &connection_load1); + + /* 5 minute load */ + exponent = exp(-1.0*(now-last_update)/300.0); + + calc_load(db, buf, "$SYS/broker/load/messages/received/5min", exponent, msgs_received_interval, &msgs_received_load5); + calc_load(db, buf, "$SYS/broker/load/messages/sent/5min", exponent, msgs_sent_interval, &msgs_sent_load5); + calc_load(db, buf, "$SYS/broker/load/publish/dropped/5min", exponent, publish_dropped_interval, &publish_dropped_load5); + calc_load(db, buf, "$SYS/broker/load/publish/received/5min", exponent, publish_received_interval, &publish_received_load5); + calc_load(db, buf, "$SYS/broker/load/publish/sent/5min", exponent, publish_sent_interval, &publish_sent_load5); + calc_load(db, buf, "$SYS/broker/load/bytes/received/5min", exponent, bytes_received_interval, &bytes_received_load5); + calc_load(db, buf, "$SYS/broker/load/bytes/sent/5min", exponent, bytes_sent_interval, &bytes_sent_load5); + calc_load(db, buf, "$SYS/broker/load/sockets/5min", exponent, socket_interval, &socket_load5); + calc_load(db, buf, "$SYS/broker/load/connections/5min", exponent, connection_interval, &connection_load5); + + /* 15 minute load */ + exponent = exp(-1.0*(now-last_update)/900.0); + + calc_load(db, buf, "$SYS/broker/load/messages/received/15min", exponent, msgs_received_interval, &msgs_received_load15); + calc_load(db, buf, "$SYS/broker/load/messages/sent/15min", exponent, msgs_sent_interval, &msgs_sent_load15); + calc_load(db, buf, "$SYS/broker/load/publish/dropped/15min", exponent, publish_dropped_interval, &publish_dropped_load15); + calc_load(db, buf, "$SYS/broker/load/publish/received/15min", exponent, publish_received_interval, &publish_received_load15); + calc_load(db, buf, "$SYS/broker/load/publish/sent/15min", exponent, publish_sent_interval, &publish_sent_load15); + calc_load(db, buf, "$SYS/broker/load/bytes/received/15min", exponent, bytes_received_interval, &bytes_received_load15); + calc_load(db, buf, "$SYS/broker/load/bytes/sent/15min", exponent, bytes_sent_interval, &bytes_sent_load15); + calc_load(db, buf, "$SYS/broker/load/sockets/15min", exponent, socket_interval, &socket_load15); + calc_load(db, buf, "$SYS/broker/load/connections/15min", exponent, connection_interval, &connection_load15); + } + + if(db->msg_store_count != msg_store_count){ + msg_store_count = db->msg_store_count; + snprintf(buf, BUFLEN, "%d", msg_store_count); + mqtt3_db_messages_easy_queue(db, NULL, "$SYS/broker/messages/stored", 2, strlen(buf), buf, 1); + } + + if(db->subscription_count != subscription_count){ + subscription_count = db->subscription_count; + snprintf(buf, BUFLEN, "%d", subscription_count); + mqtt3_db_messages_easy_queue(db, NULL, "$SYS/broker/subscriptions/count", 2, strlen(buf), buf, 1); + } + + if(db->retained_count != retained_count){ + retained_count = db->retained_count; + snprintf(buf, BUFLEN, "%d", retained_count); + mqtt3_db_messages_easy_queue(db, NULL, "$SYS/broker/retained messages/count", 2, strlen(buf), buf, 1); + } + +#ifdef WITH_MEMORY_TRACKING + _sys_update_memory(db, buf); +#endif + + if(msgs_received != g_msgs_received){ + msgs_received = g_msgs_received; + snprintf(buf, BUFLEN, "%lu", msgs_received); + mqtt3_db_messages_easy_queue(db, NULL, "$SYS/broker/messages/received", 2, strlen(buf), buf, 1); + } + + if(msgs_sent != g_msgs_sent){ + msgs_sent = g_msgs_sent; + snprintf(buf, BUFLEN, "%lu", msgs_sent); + mqtt3_db_messages_easy_queue(db, NULL, "$SYS/broker/messages/sent", 2, strlen(buf), buf, 1); + } + + if(publish_dropped != g_msgs_dropped){ + publish_dropped = g_msgs_dropped; + snprintf(buf, BUFLEN, "%lu", publish_dropped); + mqtt3_db_messages_easy_queue(db, NULL, "$SYS/broker/publish/messages/dropped", 2, strlen(buf), buf, 1); + } + + if(pub_msgs_received != g_pub_msgs_received){ + pub_msgs_received = g_pub_msgs_received; + snprintf(buf, BUFLEN, "%lu", pub_msgs_received); + mqtt3_db_messages_easy_queue(db, NULL, "$SYS/broker/publish/messages/received", 2, strlen(buf), buf, 1); + } + + if(pub_msgs_sent != g_pub_msgs_sent){ + pub_msgs_sent = g_pub_msgs_sent; + snprintf(buf, BUFLEN, "%lu", pub_msgs_sent); + mqtt3_db_messages_easy_queue(db, NULL, "$SYS/broker/publish/messages/sent", 2, strlen(buf), buf, 1); + } + + if(bytes_received != g_bytes_received){ + bytes_received = g_bytes_received; + snprintf(buf, BUFLEN, "%llu", bytes_received); + mqtt3_db_messages_easy_queue(db, NULL, "$SYS/broker/bytes/received", 2, strlen(buf), buf, 1); + } + + if(bytes_sent != g_bytes_sent){ + bytes_sent = g_bytes_sent; + snprintf(buf, BUFLEN, "%llu", bytes_sent); + mqtt3_db_messages_easy_queue(db, NULL, "$SYS/broker/bytes/sent", 2, strlen(buf), buf, 1); + } + + if(pub_bytes_received != g_pub_bytes_received){ + pub_bytes_received = g_pub_bytes_received; + snprintf(buf, BUFLEN, "%llu", pub_bytes_received); + mqtt3_db_messages_easy_queue(db, NULL, "$SYS/broker/publish/bytes/received", 2, strlen(buf), buf, 1); + } + + if(pub_bytes_sent != g_pub_bytes_sent){ + pub_bytes_sent = g_pub_bytes_sent; + snprintf(buf, BUFLEN, "%llu", pub_bytes_sent); + mqtt3_db_messages_easy_queue(db, NULL, "$SYS/broker/publish/bytes/sent", 2, strlen(buf), buf, 1); + } + + last_update = mosquitto_time(); + } +} + +#endif diff --git a/src/uthash.h b/src/uthash.h new file mode 100644 index 0000000000..915a8254ee --- /dev/null +++ b/src/uthash.h @@ -0,0 +1,948 @@ +/* +Copyright (c) 2003-2013, Troy D. Hanson http://troydhanson.github.com/uthash/ +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef UTHASH_H +#define UTHASH_H + +#include /* memcmp,strlen */ +#include /* ptrdiff_t */ +#include /* exit() */ + +/* These macros use decltype or the earlier __typeof GNU extension. + As decltype is only available in newer compilers (VS2010 or gcc 4.3+ + when compiling c++ source) this code uses whatever method is needed + or, for VS2008 where neither is available, uses casting workarounds. */ +#ifdef _MSC_VER /* MS compiler */ +#if _MSC_VER >= 1600 && defined(__cplusplus) /* VS2010 or newer in C++ mode */ +#define DECLTYPE(x) (decltype(x)) +#else /* VS2008 or older (or VS2010 in C mode) */ +#define NO_DECLTYPE +#define DECLTYPE(x) +#endif +#else /* GNU, Sun and other compilers */ +#define DECLTYPE(x) (__typeof(x)) +#endif + +#ifdef NO_DECLTYPE +#define DECLTYPE_ASSIGN(dst,src) \ +do { \ + char **_da_dst = (char**)(&(dst)); \ + *_da_dst = (char*)(src); \ +} while(0) +#else +#define DECLTYPE_ASSIGN(dst,src) \ +do { \ + (dst) = DECLTYPE(dst)(src); \ +} while(0) +#endif + +/* a number of the hash function use uint32_t which isn't defined on win32 */ +#ifdef _MSC_VER +typedef unsigned int uint32_t; +typedef unsigned char uint8_t; +#else +#include /* uint32_t */ +#endif + +#define UTHASH_VERSION 1.9.8 + +#ifndef uthash_fatal +#define uthash_fatal(msg) exit(-1) /* fatal error (out of memory,etc) */ +#endif +#ifndef uthash_malloc +#define uthash_malloc(sz) malloc(sz) /* malloc fcn */ +#endif +#ifndef uthash_free +#define uthash_free(ptr,sz) free(ptr) /* free fcn */ +#endif + +#ifndef uthash_noexpand_fyi +#define uthash_noexpand_fyi(tbl) /* can be defined to log noexpand */ +#endif +#ifndef uthash_expand_fyi +#define uthash_expand_fyi(tbl) /* can be defined to log expands */ +#endif + +/* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS 32 /* initial number of buckets */ +#define HASH_INITIAL_NUM_BUCKETS_LOG2 5 /* lg2 of initial number of buckets */ +#define HASH_BKT_CAPACITY_THRESH 10 /* expand when bucket count reaches */ + +/* calculate the element whose hash handle address is hhe */ +#define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho))) + +#define HASH_FIND(hh,head,keyptr,keylen,out) \ +do { \ + unsigned _hf_bkt,_hf_hashv; \ + out=NULL; \ + if (head) { \ + HASH_FCN(keyptr,keylen, (head)->hh.tbl->num_buckets, _hf_hashv, _hf_bkt); \ + if (HASH_BLOOM_TEST((head)->hh.tbl, _hf_hashv)) { \ + HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], \ + keyptr,keylen,out); \ + } \ + } \ +} while (0) + +#ifdef HASH_BLOOM +#define HASH_BLOOM_BITLEN (1ULL << HASH_BLOOM) +#define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8) + ((HASH_BLOOM_BITLEN%8) ? 1:0) +#define HASH_BLOOM_MAKE(tbl) \ +do { \ + (tbl)->bloom_nbits = HASH_BLOOM; \ + (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN); \ + if (!((tbl)->bloom_bv)) { uthash_fatal( "out of memory"); } \ + memset((tbl)->bloom_bv, 0, HASH_BLOOM_BYTELEN); \ + (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE; \ +} while (0) + +#define HASH_BLOOM_FREE(tbl) \ +do { \ + uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN); \ +} while (0) + +#define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8] |= (1U << ((idx)%8))) +#define HASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8] & (1U << ((idx)%8))) + +#define HASH_BLOOM_ADD(tbl,hashv) \ + HASH_BLOOM_BITSET((tbl)->bloom_bv, (hashv & (uint32_t)((1ULL << (tbl)->bloom_nbits) - 1))) + +#define HASH_BLOOM_TEST(tbl,hashv) \ + HASH_BLOOM_BITTEST((tbl)->bloom_bv, (hashv & (uint32_t)((1ULL << (tbl)->bloom_nbits) - 1))) + +#else +#define HASH_BLOOM_MAKE(tbl) +#define HASH_BLOOM_FREE(tbl) +#define HASH_BLOOM_ADD(tbl,hashv) +#define HASH_BLOOM_TEST(tbl,hashv) (1) +#define HASH_BLOOM_BYTELEN 0 +#endif + +#define HASH_MAKE_TABLE(hh,head) \ +do { \ + (head)->hh.tbl = (UT_hash_table*)uthash_malloc( \ + sizeof(UT_hash_table)); \ + if (!((head)->hh.tbl)) { uthash_fatal( "out of memory"); } \ + memset((head)->hh.tbl, 0, sizeof(UT_hash_table)); \ + (head)->hh.tbl->tail = &((head)->hh); \ + (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS; \ + (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2; \ + (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head); \ + (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc( \ + HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ + if (! (head)->hh.tbl->buckets) { uthash_fatal( "out of memory"); } \ + memset((head)->hh.tbl->buckets, 0, \ + HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket)); \ + HASH_BLOOM_MAKE((head)->hh.tbl); \ + (head)->hh.tbl->signature = HASH_SIGNATURE; \ +} while(0) + +#define HASH_ADD(hh,head,fieldname,keylen_in,add) \ + HASH_ADD_KEYPTR(hh,head,&((add)->fieldname),keylen_in,add) + +#define HASH_REPLACE(hh,head,fieldname,keylen_in,add,replaced) \ +do { \ + replaced=NULL; \ + HASH_FIND(hh,head,&((add)->fieldname),keylen_in,replaced); \ + if (replaced!=NULL) { \ + HASH_DELETE(hh,head,replaced); \ + }; \ + HASH_ADD(hh,head,fieldname,keylen_in,add); \ +} while(0) + +#define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add) \ +do { \ + unsigned _ha_bkt; \ + (add)->hh.next = NULL; \ + (add)->hh.key = (char*)keyptr; \ + (add)->hh.keylen = (unsigned)keylen_in; \ + if (!(head)) { \ + head = (add); \ + (head)->hh.prev = NULL; \ + HASH_MAKE_TABLE(hh,head); \ + } else { \ + (head)->hh.tbl->tail->next = (add); \ + (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail); \ + (head)->hh.tbl->tail = &((add)->hh); \ + } \ + (head)->hh.tbl->num_items++; \ + (add)->hh.tbl = (head)->hh.tbl; \ + HASH_FCN(keyptr,keylen_in, (head)->hh.tbl->num_buckets, \ + (add)->hh.hashv, _ha_bkt); \ + HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt],&(add)->hh); \ + HASH_BLOOM_ADD((head)->hh.tbl,(add)->hh.hashv); \ + HASH_EMIT_KEY(hh,head,keyptr,keylen_in); \ + HASH_FSCK(hh,head); \ +} while(0) + +#define HASH_TO_BKT( hashv, num_bkts, bkt ) \ +do { \ + bkt = ((hashv) & ((num_bkts) - 1)); \ +} while(0) + +/* delete "delptr" from the hash table. + * "the usual" patch-up process for the app-order doubly-linked-list. + * The use of _hd_hh_del below deserves special explanation. + * These used to be expressed using (delptr) but that led to a bug + * if someone used the same symbol for the head and deletee, like + * HASH_DELETE(hh,users,users); + * We want that to work, but by changing the head (users) below + * we were forfeiting our ability to further refer to the deletee (users) + * in the patch-up process. Solution: use scratch space to + * copy the deletee pointer, then the latter references are via that + * scratch pointer rather than through the repointed (users) symbol. + */ +#define HASH_DELETE(hh,head,delptr) \ +do { \ + unsigned _hd_bkt; \ + struct UT_hash_handle *_hd_hh_del; \ + if ( ((delptr)->hh.prev == NULL) && ((delptr)->hh.next == NULL) ) { \ + uthash_free((head)->hh.tbl->buckets, \ + (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket) ); \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + head = NULL; \ + } else { \ + _hd_hh_del = &((delptr)->hh); \ + if ((delptr) == ELMT_FROM_HH((head)->hh.tbl,(head)->hh.tbl->tail)) { \ + (head)->hh.tbl->tail = \ + (UT_hash_handle*)((ptrdiff_t)((delptr)->hh.prev) + \ + (head)->hh.tbl->hho); \ + } \ + if ((delptr)->hh.prev) { \ + ((UT_hash_handle*)((ptrdiff_t)((delptr)->hh.prev) + \ + (head)->hh.tbl->hho))->next = (delptr)->hh.next; \ + } else { \ + DECLTYPE_ASSIGN(head,(delptr)->hh.next); \ + } \ + if (_hd_hh_del->next) { \ + ((UT_hash_handle*)((ptrdiff_t)_hd_hh_del->next + \ + (head)->hh.tbl->hho))->prev = \ + _hd_hh_del->prev; \ + } \ + HASH_TO_BKT( _hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt); \ + HASH_DEL_IN_BKT(hh,(head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del); \ + (head)->hh.tbl->num_items--; \ + } \ + HASH_FSCK(hh,head); \ +} while (0) + + +/* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */ +#define HASH_FIND_STR(head,findstr,out) \ + HASH_FIND(hh,head,findstr,strlen(findstr),out) +#define HASH_ADD_STR(head,strfield,add) \ + HASH_ADD(hh,head,strfield,strlen(add->strfield),add) +#define HASH_REPLACE_STR(head,strfield,add,replaced) \ + HASH_REPLACE(hh,head,strfield,strlen(add->strfield),add,replaced) +#define HASH_FIND_INT(head,findint,out) \ + HASH_FIND(hh,head,findint,sizeof(int),out) +#define HASH_ADD_INT(head,intfield,add) \ + HASH_ADD(hh,head,intfield,sizeof(int),add) +#define HASH_REPLACE_INT(head,intfield,add,replaced) \ + HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced) +#define HASH_FIND_PTR(head,findptr,out) \ + HASH_FIND(hh,head,findptr,sizeof(void *),out) +#define HASH_ADD_PTR(head,ptrfield,add) \ + HASH_ADD(hh,head,ptrfield,sizeof(void *),add) +#define HASH_REPLACE_PTR(head,ptrfield,add) \ + HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced) +#define HASH_DEL(head,delptr) \ + HASH_DELETE(hh,head,delptr) + +/* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined. + * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined. + */ +#ifdef HASH_DEBUG +#define HASH_OOPS(...) do { fprintf(stderr,__VA_ARGS__); exit(-1); } while (0) +#define HASH_FSCK(hh,head) \ +do { \ + unsigned _bkt_i; \ + unsigned _count, _bkt_count; \ + char *_prev; \ + struct UT_hash_handle *_thh; \ + if (head) { \ + _count = 0; \ + for( _bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; _bkt_i++) { \ + _bkt_count = 0; \ + _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head; \ + _prev = NULL; \ + while (_thh) { \ + if (_prev != (char*)(_thh->hh_prev)) { \ + HASH_OOPS("invalid hh_prev %p, actual %p\n", \ + _thh->hh_prev, _prev ); \ + } \ + _bkt_count++; \ + _prev = (char*)(_thh); \ + _thh = _thh->hh_next; \ + } \ + _count += _bkt_count; \ + if ((head)->hh.tbl->buckets[_bkt_i].count != _bkt_count) { \ + HASH_OOPS("invalid bucket count %d, actual %d\n", \ + (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count); \ + } \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("invalid hh item count %d, actual %d\n", \ + (head)->hh.tbl->num_items, _count ); \ + } \ + /* traverse hh in app order; check next/prev integrity, count */ \ + _count = 0; \ + _prev = NULL; \ + _thh = &(head)->hh; \ + while (_thh) { \ + _count++; \ + if (_prev !=(char*)(_thh->prev)) { \ + HASH_OOPS("invalid prev %p, actual %p\n", \ + _thh->prev, _prev ); \ + } \ + _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh); \ + _thh = ( _thh->next ? (UT_hash_handle*)((char*)(_thh->next) + \ + (head)->hh.tbl->hho) : NULL ); \ + } \ + if (_count != (head)->hh.tbl->num_items) { \ + HASH_OOPS("invalid app item count %d, actual %d\n", \ + (head)->hh.tbl->num_items, _count ); \ + } \ + } \ +} while (0) +#else +#define HASH_FSCK(hh,head) +#endif + +/* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to + * the descriptor to which this macro is defined for tuning the hash function. + * The app can #include to get the prototype for write(2). */ +#ifdef HASH_EMIT_KEYS +#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) \ +do { \ + unsigned _klen = fieldlen; \ + write(HASH_EMIT_KEYS, &_klen, sizeof(_klen)); \ + write(HASH_EMIT_KEYS, keyptr, fieldlen); \ +} while (0) +#else +#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen) +#endif + +/* default to Jenkin's hash unless overridden e.g. DHASH_FUNCTION=HASH_SAX */ +#ifdef HASH_FUNCTION +#define HASH_FCN HASH_FUNCTION +#else +#define HASH_FCN HASH_JEN +#endif + +/* The Bernstein hash function, used in Perl prior to v5.6 */ +#define HASH_BER(key,keylen,num_bkts,hashv,bkt) \ +do { \ + unsigned _hb_keylen=keylen; \ + char *_hb_key=(char*)(key); \ + (hashv) = 0; \ + while (_hb_keylen--) { (hashv) = ((hashv) * 33) + *_hb_key++; } \ + bkt = (hashv) & (num_bkts-1); \ +} while (0) + + +/* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at + * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx */ +#define HASH_SAX(key,keylen,num_bkts,hashv,bkt) \ +do { \ + unsigned _sx_i; \ + char *_hs_key=(char*)(key); \ + hashv = 0; \ + for(_sx_i=0; _sx_i < keylen; _sx_i++) \ + hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i]; \ + bkt = hashv & (num_bkts-1); \ +} while (0) + +#define HASH_FNV(key,keylen,num_bkts,hashv,bkt) \ +do { \ + unsigned _fn_i; \ + char *_hf_key=(char*)(key); \ + hashv = 2166136261UL; \ + for(_fn_i=0; _fn_i < keylen; _fn_i++) \ + hashv = (hashv * 16777619) ^ _hf_key[_fn_i]; \ + bkt = hashv & (num_bkts-1); \ +} while(0) + +#define HASH_OAT(key,keylen,num_bkts,hashv,bkt) \ +do { \ + unsigned _ho_i; \ + char *_ho_key=(char*)(key); \ + hashv = 0; \ + for(_ho_i=0; _ho_i < keylen; _ho_i++) { \ + hashv += _ho_key[_ho_i]; \ + hashv += (hashv << 10); \ + hashv ^= (hashv >> 6); \ + } \ + hashv += (hashv << 3); \ + hashv ^= (hashv >> 11); \ + hashv += (hashv << 15); \ + bkt = hashv & (num_bkts-1); \ +} while(0) + +#define HASH_JEN_MIX(a,b,c) \ +do { \ + a -= b; a -= c; a ^= ( c >> 13 ); \ + b -= c; b -= a; b ^= ( a << 8 ); \ + c -= a; c -= b; c ^= ( b >> 13 ); \ + a -= b; a -= c; a ^= ( c >> 12 ); \ + b -= c; b -= a; b ^= ( a << 16 ); \ + c -= a; c -= b; c ^= ( b >> 5 ); \ + a -= b; a -= c; a ^= ( c >> 3 ); \ + b -= c; b -= a; b ^= ( a << 10 ); \ + c -= a; c -= b; c ^= ( b >> 15 ); \ +} while (0) + +#define HASH_JEN(key,keylen,num_bkts,hashv,bkt) \ +do { \ + unsigned _hj_i,_hj_j,_hj_k; \ + unsigned char *_hj_key=(unsigned char*)(key); \ + hashv = 0xfeedbeef; \ + _hj_i = _hj_j = 0x9e3779b9; \ + _hj_k = (unsigned)keylen; \ + while (_hj_k >= 12) { \ + _hj_i += (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 ) \ + + ( (unsigned)_hj_key[2] << 16 ) \ + + ( (unsigned)_hj_key[3] << 24 ) ); \ + _hj_j += (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 ) \ + + ( (unsigned)_hj_key[6] << 16 ) \ + + ( (unsigned)_hj_key[7] << 24 ) ); \ + hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 ) \ + + ( (unsigned)_hj_key[10] << 16 ) \ + + ( (unsigned)_hj_key[11] << 24 ) ); \ + \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ + \ + _hj_key += 12; \ + _hj_k -= 12; \ + } \ + hashv += keylen; \ + switch ( _hj_k ) { \ + case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); \ + case 10: hashv += ( (unsigned)_hj_key[9] << 16 ); \ + case 9: hashv += ( (unsigned)_hj_key[8] << 8 ); \ + case 8: _hj_j += ( (unsigned)_hj_key[7] << 24 ); \ + case 7: _hj_j += ( (unsigned)_hj_key[6] << 16 ); \ + case 6: _hj_j += ( (unsigned)_hj_key[5] << 8 ); \ + case 5: _hj_j += _hj_key[4]; \ + case 4: _hj_i += ( (unsigned)_hj_key[3] << 24 ); \ + case 3: _hj_i += ( (unsigned)_hj_key[2] << 16 ); \ + case 2: _hj_i += ( (unsigned)_hj_key[1] << 8 ); \ + case 1: _hj_i += _hj_key[0]; \ + } \ + HASH_JEN_MIX(_hj_i, _hj_j, hashv); \ + bkt = hashv & (num_bkts-1); \ +} while(0) + +/* The Paul Hsieh hash function */ +#undef get16bits +#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \ + || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__) +#define get16bits(d) (*((const uint16_t *) (d))) +#endif + +#if !defined (get16bits) +#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8) \ + +(uint32_t)(((const uint8_t *)(d))[0]) ) +#endif +#define HASH_SFH(key,keylen,num_bkts,hashv,bkt) \ +do { \ + unsigned char *_sfh_key=(unsigned char*)(key); \ + uint32_t _sfh_tmp, _sfh_len = keylen; \ + \ + int _sfh_rem = _sfh_len & 3; \ + _sfh_len >>= 2; \ + hashv = 0xcafebabe; \ + \ + /* Main loop */ \ + for (;_sfh_len > 0; _sfh_len--) { \ + hashv += get16bits (_sfh_key); \ + _sfh_tmp = (uint32_t)(get16bits (_sfh_key+2)) << 11 ^ hashv; \ + hashv = (hashv << 16) ^ _sfh_tmp; \ + _sfh_key += 2*sizeof (uint16_t); \ + hashv += hashv >> 11; \ + } \ + \ + /* Handle end cases */ \ + switch (_sfh_rem) { \ + case 3: hashv += get16bits (_sfh_key); \ + hashv ^= hashv << 16; \ + hashv ^= (uint32_t)(_sfh_key[sizeof (uint16_t)] << 18); \ + hashv += hashv >> 11; \ + break; \ + case 2: hashv += get16bits (_sfh_key); \ + hashv ^= hashv << 11; \ + hashv += hashv >> 17; \ + break; \ + case 1: hashv += *_sfh_key; \ + hashv ^= hashv << 10; \ + hashv += hashv >> 1; \ + } \ + \ + /* Force "avalanching" of final 127 bits */ \ + hashv ^= hashv << 3; \ + hashv += hashv >> 5; \ + hashv ^= hashv << 4; \ + hashv += hashv >> 17; \ + hashv ^= hashv << 25; \ + hashv += hashv >> 6; \ + bkt = hashv & (num_bkts-1); \ +} while(0) + +#ifdef HASH_USING_NO_STRICT_ALIASING +/* The MurmurHash exploits some CPU's (x86,x86_64) tolerance for unaligned reads. + * For other types of CPU's (e.g. Sparc) an unaligned read causes a bus error. + * MurmurHash uses the faster approach only on CPU's where we know it's safe. + * + * Note the preprocessor built-in defines can be emitted using: + * + * gcc -m64 -dM -E - < /dev/null (on gcc) + * cc -## a.c (where a.c is a simple test file) (Sun Studio) + */ +#if (defined(__i386__) || defined(__x86_64__) || defined(_M_IX86)) +#define MUR_GETBLOCK(p,i) p[i] +#else /* non intel */ +#define MUR_PLUS0_ALIGNED(p) (((unsigned long)p & 0x3) == 0) +#define MUR_PLUS1_ALIGNED(p) (((unsigned long)p & 0x3) == 1) +#define MUR_PLUS2_ALIGNED(p) (((unsigned long)p & 0x3) == 2) +#define MUR_PLUS3_ALIGNED(p) (((unsigned long)p & 0x3) == 3) +#define WP(p) ((uint32_t*)((unsigned long)(p) & ~3UL)) +#if (defined(__BIG_ENDIAN__) || defined(SPARC) || defined(__ppc__) || defined(__ppc64__)) +#define MUR_THREE_ONE(p) ((((*WP(p))&0x00ffffff) << 8) | (((*(WP(p)+1))&0xff000000) >> 24)) +#define MUR_TWO_TWO(p) ((((*WP(p))&0x0000ffff) <<16) | (((*(WP(p)+1))&0xffff0000) >> 16)) +#define MUR_ONE_THREE(p) ((((*WP(p))&0x000000ff) <<24) | (((*(WP(p)+1))&0xffffff00) >> 8)) +#else /* assume little endian non-intel */ +#define MUR_THREE_ONE(p) ((((*WP(p))&0xffffff00) >> 8) | (((*(WP(p)+1))&0x000000ff) << 24)) +#define MUR_TWO_TWO(p) ((((*WP(p))&0xffff0000) >>16) | (((*(WP(p)+1))&0x0000ffff) << 16)) +#define MUR_ONE_THREE(p) ((((*WP(p))&0xff000000) >>24) | (((*(WP(p)+1))&0x00ffffff) << 8)) +#endif +#define MUR_GETBLOCK(p,i) (MUR_PLUS0_ALIGNED(p) ? ((p)[i]) : \ + (MUR_PLUS1_ALIGNED(p) ? MUR_THREE_ONE(p) : \ + (MUR_PLUS2_ALIGNED(p) ? MUR_TWO_TWO(p) : \ + MUR_ONE_THREE(p)))) +#endif +#define MUR_ROTL32(x,r) (((x) << (r)) | ((x) >> (32 - (r)))) +#define MUR_FMIX(_h) \ +do { \ + _h ^= _h >> 16; \ + _h *= 0x85ebca6b; \ + _h ^= _h >> 13; \ + _h *= 0xc2b2ae35l; \ + _h ^= _h >> 16; \ +} while(0) + +#define HASH_MUR(key,keylen,num_bkts,hashv,bkt) \ +do { \ + const uint8_t *_mur_data = (const uint8_t*)(key); \ + const int _mur_nblocks = (keylen) / 4; \ + uint32_t _mur_h1 = 0xf88D5353; \ + uint32_t _mur_c1 = 0xcc9e2d51; \ + uint32_t _mur_c2 = 0x1b873593; \ + uint32_t _mur_k1 = 0; \ + const uint8_t *_mur_tail; \ + const uint32_t *_mur_blocks = (const uint32_t*)(_mur_data+_mur_nblocks*4); \ + int _mur_i; \ + for(_mur_i = -_mur_nblocks; _mur_i; _mur_i++) { \ + _mur_k1 = MUR_GETBLOCK(_mur_blocks,_mur_i); \ + _mur_k1 *= _mur_c1; \ + _mur_k1 = MUR_ROTL32(_mur_k1,15); \ + _mur_k1 *= _mur_c2; \ + \ + _mur_h1 ^= _mur_k1; \ + _mur_h1 = MUR_ROTL32(_mur_h1,13); \ + _mur_h1 = _mur_h1*5+0xe6546b64; \ + } \ + _mur_tail = (const uint8_t*)(_mur_data + _mur_nblocks*4); \ + _mur_k1=0; \ + switch((keylen) & 3) { \ + case 3: _mur_k1 ^= _mur_tail[2] << 16; \ + case 2: _mur_k1 ^= _mur_tail[1] << 8; \ + case 1: _mur_k1 ^= _mur_tail[0]; \ + _mur_k1 *= _mur_c1; \ + _mur_k1 = MUR_ROTL32(_mur_k1,15); \ + _mur_k1 *= _mur_c2; \ + _mur_h1 ^= _mur_k1; \ + } \ + _mur_h1 ^= (keylen); \ + MUR_FMIX(_mur_h1); \ + hashv = _mur_h1; \ + bkt = hashv & (num_bkts-1); \ +} while(0) +#endif /* HASH_USING_NO_STRICT_ALIASING */ + +/* key comparison function; return 0 if keys equal */ +#define HASH_KEYCMP(a,b,len) memcmp(a,b,len) + +/* iterate over items in a known bucket to find desired item */ +#define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,out) \ +do { \ + if (head.hh_head) DECLTYPE_ASSIGN(out,ELMT_FROM_HH(tbl,head.hh_head)); \ + else out=NULL; \ + while (out) { \ + if ((out)->hh.keylen == keylen_in) { \ + if ((HASH_KEYCMP((out)->hh.key,keyptr,keylen_in)) == 0) break; \ + } \ + if ((out)->hh.hh_next) DECLTYPE_ASSIGN(out,ELMT_FROM_HH(tbl,(out)->hh.hh_next)); \ + else out = NULL; \ + } \ +} while(0) + +/* add an item to a bucket */ +#define HASH_ADD_TO_BKT(head,addhh) \ +do { \ + head.count++; \ + (addhh)->hh_next = head.hh_head; \ + (addhh)->hh_prev = NULL; \ + if (head.hh_head) { (head).hh_head->hh_prev = (addhh); } \ + (head).hh_head=addhh; \ + if (head.count >= ((head.expand_mult+1) * HASH_BKT_CAPACITY_THRESH) \ + && (addhh)->tbl->noexpand != 1) { \ + HASH_EXPAND_BUCKETS((addhh)->tbl); \ + } \ +} while(0) + +/* remove an item from a given bucket */ +#define HASH_DEL_IN_BKT(hh,head,hh_del) \ + (head).count--; \ + if ((head).hh_head == hh_del) { \ + (head).hh_head = hh_del->hh_next; \ + } \ + if (hh_del->hh_prev) { \ + hh_del->hh_prev->hh_next = hh_del->hh_next; \ + } \ + if (hh_del->hh_next) { \ + hh_del->hh_next->hh_prev = hh_del->hh_prev; \ + } + +/* Bucket expansion has the effect of doubling the number of buckets + * and redistributing the items into the new buckets. Ideally the + * items will distribute more or less evenly into the new buckets + * (the extent to which this is true is a measure of the quality of + * the hash function as it applies to the key domain). + * + * With the items distributed into more buckets, the chain length + * (item count) in each bucket is reduced. Thus by expanding buckets + * the hash keeps a bound on the chain length. This bounded chain + * length is the essence of how a hash provides constant time lookup. + * + * The calculation of tbl->ideal_chain_maxlen below deserves some + * explanation. First, keep in mind that we're calculating the ideal + * maximum chain length based on the *new* (doubled) bucket count. + * In fractions this is just n/b (n=number of items,b=new num buckets). + * Since the ideal chain length is an integer, we want to calculate + * ceil(n/b). We don't depend on floating point arithmetic in this + * hash, so to calculate ceil(n/b) with integers we could write + * + * ceil(n/b) = (n/b) + ((n%b)?1:0) + * + * and in fact a previous version of this hash did just that. + * But now we have improved things a bit by recognizing that b is + * always a power of two. We keep its base 2 log handy (call it lb), + * so now we can write this with a bit shift and logical AND: + * + * ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0) + * + */ +#define HASH_EXPAND_BUCKETS(tbl) \ +do { \ + unsigned _he_bkt; \ + unsigned _he_bkt_i; \ + struct UT_hash_handle *_he_thh, *_he_hh_nxt; \ + UT_hash_bucket *_he_new_buckets, *_he_newbkt; \ + _he_new_buckets = (UT_hash_bucket*)uthash_malloc( \ + 2 * tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ + if (!_he_new_buckets) { uthash_fatal( "out of memory"); } \ + memset(_he_new_buckets, 0, \ + 2 * tbl->num_buckets * sizeof(struct UT_hash_bucket)); \ + tbl->ideal_chain_maxlen = \ + (tbl->num_items >> (tbl->log2_num_buckets+1)) + \ + ((tbl->num_items & ((tbl->num_buckets*2)-1)) ? 1 : 0); \ + tbl->nonideal_items = 0; \ + for(_he_bkt_i = 0; _he_bkt_i < tbl->num_buckets; _he_bkt_i++) \ + { \ + _he_thh = tbl->buckets[ _he_bkt_i ].hh_head; \ + while (_he_thh) { \ + _he_hh_nxt = _he_thh->hh_next; \ + HASH_TO_BKT( _he_thh->hashv, tbl->num_buckets*2, _he_bkt); \ + _he_newbkt = &(_he_new_buckets[ _he_bkt ]); \ + if (++(_he_newbkt->count) > tbl->ideal_chain_maxlen) { \ + tbl->nonideal_items++; \ + _he_newbkt->expand_mult = _he_newbkt->count / \ + tbl->ideal_chain_maxlen; \ + } \ + _he_thh->hh_prev = NULL; \ + _he_thh->hh_next = _he_newbkt->hh_head; \ + if (_he_newbkt->hh_head) _he_newbkt->hh_head->hh_prev = \ + _he_thh; \ + _he_newbkt->hh_head = _he_thh; \ + _he_thh = _he_hh_nxt; \ + } \ + } \ + uthash_free( tbl->buckets, tbl->num_buckets*sizeof(struct UT_hash_bucket) ); \ + tbl->num_buckets *= 2; \ + tbl->log2_num_buckets++; \ + tbl->buckets = _he_new_buckets; \ + tbl->ineff_expands = (tbl->nonideal_items > (tbl->num_items >> 1)) ? \ + (tbl->ineff_expands+1) : 0; \ + if (tbl->ineff_expands > 1) { \ + tbl->noexpand=1; \ + uthash_noexpand_fyi(tbl); \ + } \ + uthash_expand_fyi(tbl); \ +} while(0) + + +/* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */ +/* Note that HASH_SORT assumes the hash handle name to be hh. + * HASH_SRT was added to allow the hash handle name to be passed in. */ +#define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn) +#define HASH_SRT(hh,head,cmpfcn) \ +do { \ + unsigned _hs_i; \ + unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize; \ + struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail; \ + if (head) { \ + _hs_insize = 1; \ + _hs_looping = 1; \ + _hs_list = &((head)->hh); \ + while (_hs_looping) { \ + _hs_p = _hs_list; \ + _hs_list = NULL; \ + _hs_tail = NULL; \ + _hs_nmerges = 0; \ + while (_hs_p) { \ + _hs_nmerges++; \ + _hs_q = _hs_p; \ + _hs_psize = 0; \ + for ( _hs_i = 0; _hs_i < _hs_insize; _hs_i++ ) { \ + _hs_psize++; \ + _hs_q = (UT_hash_handle*)((_hs_q->next) ? \ + ((void*)((char*)(_hs_q->next) + \ + (head)->hh.tbl->hho)) : NULL); \ + if (! (_hs_q) ) break; \ + } \ + _hs_qsize = _hs_insize; \ + while ((_hs_psize > 0) || ((_hs_qsize > 0) && _hs_q )) { \ + if (_hs_psize == 0) { \ + _hs_e = _hs_q; \ + _hs_q = (UT_hash_handle*)((_hs_q->next) ? \ + ((void*)((char*)(_hs_q->next) + \ + (head)->hh.tbl->hho)) : NULL); \ + _hs_qsize--; \ + } else if ( (_hs_qsize == 0) || !(_hs_q) ) { \ + _hs_e = _hs_p; \ + if (_hs_p){ \ + _hs_p = (UT_hash_handle*)((_hs_p->next) ? \ + ((void*)((char*)(_hs_p->next) + \ + (head)->hh.tbl->hho)) : NULL); \ + } \ + _hs_psize--; \ + } else if (( \ + cmpfcn(DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl,_hs_p)), \ + DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl,_hs_q))) \ + ) <= 0) { \ + _hs_e = _hs_p; \ + if (_hs_p){ \ + _hs_p = (UT_hash_handle*)((_hs_p->next) ? \ + ((void*)((char*)(_hs_p->next) + \ + (head)->hh.tbl->hho)) : NULL); \ + } \ + _hs_psize--; \ + } else { \ + _hs_e = _hs_q; \ + _hs_q = (UT_hash_handle*)((_hs_q->next) ? \ + ((void*)((char*)(_hs_q->next) + \ + (head)->hh.tbl->hho)) : NULL); \ + _hs_qsize--; \ + } \ + if ( _hs_tail ) { \ + _hs_tail->next = ((_hs_e) ? \ + ELMT_FROM_HH((head)->hh.tbl,_hs_e) : NULL); \ + } else { \ + _hs_list = _hs_e; \ + } \ + if (_hs_e) { \ + _hs_e->prev = ((_hs_tail) ? \ + ELMT_FROM_HH((head)->hh.tbl,_hs_tail) : NULL); \ + } \ + _hs_tail = _hs_e; \ + } \ + _hs_p = _hs_q; \ + } \ + if (_hs_tail){ \ + _hs_tail->next = NULL; \ + } \ + if ( _hs_nmerges <= 1 ) { \ + _hs_looping=0; \ + (head)->hh.tbl->tail = _hs_tail; \ + DECLTYPE_ASSIGN(head,ELMT_FROM_HH((head)->hh.tbl, _hs_list)); \ + } \ + _hs_insize *= 2; \ + } \ + HASH_FSCK(hh,head); \ + } \ +} while (0) + +/* This function selects items from one hash into another hash. + * The end result is that the selected items have dual presence + * in both hashes. There is no copy of the items made; rather + * they are added into the new hash through a secondary hash + * hash handle that must be present in the structure. */ +#define HASH_SELECT(hh_dst, dst, hh_src, src, cond) \ +do { \ + unsigned _src_bkt, _dst_bkt; \ + void *_last_elt=NULL, *_elt; \ + UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL; \ + ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst)); \ + if (src) { \ + for(_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) { \ + for(_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head; \ + _src_hh; \ + _src_hh = _src_hh->hh_next) { \ + _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh); \ + if (cond(_elt)) { \ + _dst_hh = (UT_hash_handle*)(((char*)_elt) + _dst_hho); \ + _dst_hh->key = _src_hh->key; \ + _dst_hh->keylen = _src_hh->keylen; \ + _dst_hh->hashv = _src_hh->hashv; \ + _dst_hh->prev = _last_elt; \ + _dst_hh->next = NULL; \ + if (_last_elt_hh) { _last_elt_hh->next = _elt; } \ + if (!dst) { \ + DECLTYPE_ASSIGN(dst,_elt); \ + HASH_MAKE_TABLE(hh_dst,dst); \ + } else { \ + _dst_hh->tbl = (dst)->hh_dst.tbl; \ + } \ + HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt); \ + HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt],_dst_hh); \ + (dst)->hh_dst.tbl->num_items++; \ + _last_elt = _elt; \ + _last_elt_hh = _dst_hh; \ + } \ + } \ + } \ + } \ + HASH_FSCK(hh_dst,dst); \ +} while (0) + +#define HASH_CLEAR(hh,head) \ +do { \ + if (head) { \ + uthash_free((head)->hh.tbl->buckets, \ + (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket)); \ + HASH_BLOOM_FREE((head)->hh.tbl); \ + uthash_free((head)->hh.tbl, sizeof(UT_hash_table)); \ + (head)=NULL; \ + } \ +} while(0) + +#define HASH_OVERHEAD(hh,head) \ + (size_t)((((head)->hh.tbl->num_items * sizeof(UT_hash_handle)) + \ + ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket)) + \ + (sizeof(UT_hash_table)) + \ + (HASH_BLOOM_BYTELEN))) + +#ifdef NO_DECLTYPE +#define HASH_ITER(hh,head,el,tmp) \ +for((el)=(head), (*(char**)(&(tmp)))=(char*)((head)?(head)->hh.next:NULL); \ + el; (el)=(tmp),(*(char**)(&(tmp)))=(char*)((tmp)?(tmp)->hh.next:NULL)) +#else +#define HASH_ITER(hh,head,el,tmp) \ +for((el)=(head),(tmp)=DECLTYPE(el)((head)?(head)->hh.next:NULL); \ + el; (el)=(tmp),(tmp)=DECLTYPE(el)((tmp)?(tmp)->hh.next:NULL)) +#endif + +/* obtain a count of items in the hash */ +#define HASH_COUNT(head) HASH_CNT(hh,head) +#define HASH_CNT(hh,head) ((head)?((head)->hh.tbl->num_items):0) + +typedef struct UT_hash_bucket { + struct UT_hash_handle *hh_head; + unsigned count; + + /* expand_mult is normally set to 0. In this situation, the max chain length + * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If + * the bucket's chain exceeds this length, bucket expansion is triggered). + * However, setting expand_mult to a non-zero value delays bucket expansion + * (that would be triggered by additions to this particular bucket) + * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH. + * (The multiplier is simply expand_mult+1). The whole idea of this + * multiplier is to reduce bucket expansions, since they are expensive, in + * situations where we know that a particular bucket tends to be overused. + * It is better to let its chain length grow to a longer yet-still-bounded + * value, than to do an O(n) bucket expansion too often. + */ + unsigned expand_mult; + +} UT_hash_bucket; + +/* random signature used only to find hash tables in external analysis */ +#define HASH_SIGNATURE 0xa0111fe1 +#define HASH_BLOOM_SIGNATURE 0xb12220f2 + +typedef struct UT_hash_table { + UT_hash_bucket *buckets; + unsigned num_buckets, log2_num_buckets; + unsigned num_items; + struct UT_hash_handle *tail; /* tail hh in app order, for fast append */ + ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */ + + /* in an ideal situation (all buckets used equally), no bucket would have + * more than ceil(#items/#buckets) items. that's the ideal chain length. */ + unsigned ideal_chain_maxlen; + + /* nonideal_items is the number of items in the hash whose chain position + * exceeds the ideal chain maxlen. these items pay the penalty for an uneven + * hash distribution; reaching them in a chain traversal takes >ideal steps */ + unsigned nonideal_items; + + /* ineffective expands occur when a bucket doubling was performed, but + * afterward, more than half the items in the hash had nonideal chain + * positions. If this happens on two consecutive expansions we inhibit any + * further expansion, as it's not helping; this happens when the hash + * function isn't a good fit for the key domain. When expansion is inhibited + * the hash will still work, albeit no longer in constant time. */ + unsigned ineff_expands, noexpand; + + uint32_t signature; /* used only to find hash tables in external analysis */ +#ifdef HASH_BLOOM + uint32_t bloom_sig; /* used only to test bloom exists in external analysis */ + uint8_t *bloom_bv; + char bloom_nbits; +#endif + +} UT_hash_table; + +typedef struct UT_hash_handle { + struct UT_hash_table *tbl; + void *prev; /* prev element in app order */ + void *next; /* next element in app order */ + struct UT_hash_handle *hh_prev; /* previous hh in bucket order */ + struct UT_hash_handle *hh_next; /* next hh in bucket order */ + void *key; /* ptr to enclosing struct's key */ + unsigned keylen; /* enclosing struct's key len */ + unsigned hashv; /* result of hash-fcn(key) */ +} UT_hash_handle; + +#endif /* UTHASH_H */ diff --git a/test/Makefile b/test/Makefile new file mode 100644 index 0000000000..8c9a44d034 --- /dev/null +++ b/test/Makefile @@ -0,0 +1,91 @@ +include ../config.mk + +CC=cc +CFLAGS=-I../src -I../lib -I. -I.. -Wall -ggdb -DDEBUG -DWITH_CLIENT +LDFLAGS= +OBJS=context.o database.o logging.o memory.o net.o raw_send.o raw_send_client.o read_handle.o read_handle_client.o util.o +SOVERSION=1 + +.PHONY: all test clean reallyclean + +all : fake_user msgsps_pub msgsps_sub +#packet-gen qos + +test : + $(MAKE) -C broker test + $(MAKE) -C lib test + +fake_user : fake_user.o + ${CC} $^ -o $@ ../lib/libmosquitto.so.${SOVERSION} + #${CC} $^ -o $@ -lmosquitto + +fake_user.o : fake_user.c + ${CC} $(CFLAGS) -c $< -o $@ + +msgsps_pub : msgsps_pub.o + ${CC} $^ -o $@ ../lib/libmosquitto.so.${SOVERSION} + +msgsps_pub.o : msgsps_pub.c msgsps_common.h + ${CC} $(CFLAGS) -c $< -o $@ + +msgsps_sub : msgsps_sub.o + ${CC} $^ -o $@ ../lib/libmosquitto.so.${SOVERSION} + +msgsps_sub.o : msgsps_sub.c msgsps_common.h + ${CC} $(CFLAGS) -c $< -o $@ + +packet-gen : packet-gen.o + ${CC} $^ -o $@ ../lib/libmosquitto.so.${SOVERSION} + +packet-gen.o : packet-gen.c + ${CC} $(CFLAGS) -c $< -o $@ + +qos : qos.o + ${CC} $^ -o $@ ../lib/libmosquitto.so.${SOVERSION} + +qos.o : qos.c + ${CC} $(CFLAGS) -c $< -o $@ + +random_client : random_client.o ${OBJS} + ${CC} $^ -o $@ ${LDFLAGS} + +random_client.o : random_client.c ../src/mqtt3.h + ${CC} $(CFLAGS) -c $< -o $@ + +context.o : ../src/context.c ../src/mqtt3.h + ${CC} $(CFLAGS) -c $< -o $@ + +database.o : ../src/database.c ../src/mqtt3.h + ${CC} $(CFLAGS) -c $< -o $@ + +logging.o : ../src/logging.c ../src/mqtt3.h + ${CC} $(CFLAGS) -c $< -o $@ + +memory.o : ../src/memory.c ../src/mqtt3.h + ${CC} $(CFLAGS) -c $< -o $@ + +net.o : ../src/net.c ../src/mqtt3.h + ${CC} $(CFLAGS) -c $< -o $@ + +raw_send.o : ../src/raw_send.c ../src/mqtt3.h + ${CC} $(CFLAGS) -c $< -o $@ + +raw_send_client.o : ../src/raw_send_client.c ../src/mqtt3.h + ${CC} $(CFLAGS) -c $< -o $@ + +read_handle.o : ../src/read_handle.c ../src/mqtt3.h + ${CC} $(CFLAGS) -c $< -o $@ + +read_handle_client.o : ../src/read_handle_client.c ../src/mqtt3.h + ${CC} $(CFLAGS) -c $< -o $@ + +util.o : ../src/util.c ../src/mqtt3.h + ${CC} $(CFLAGS) -c $< -o $@ + +reallyclean : clean + -rm -f *.orig + +clean : + -rm -f *.o random_client qos msgsps_pub msgsps_sub fake_user test_client *.pyc + $(MAKE) -C lib clean + $(MAKE) -C broker clean diff --git a/test/broker/01-connect-anon-denied.conf b/test/broker/01-connect-anon-denied.conf new file mode 100644 index 0000000000..02567f0deb --- /dev/null +++ b/test/broker/01-connect-anon-denied.conf @@ -0,0 +1,3 @@ +port 1888 +password_file 01-connect-anon-denied.pwfile +allow_anonymous false diff --git a/test/broker/01-connect-anon-denied.pwfile b/test/broker/01-connect-anon-denied.pwfile new file mode 100644 index 0000000000..0fec1e9628 --- /dev/null +++ b/test/broker/01-connect-anon-denied.pwfile @@ -0,0 +1 @@ +user:$6$kyuI0x+unN8lbv9U$b6c3O8U/3fCJLEg7/qDHnE9oOE6gu8JqwBXNLAPBQInJuHhpB3teOaSxb3Lx9O+ukglIRPOI0NCENcincSPCvQ== diff --git a/test/broker/01-connect-anon-denied.py b/test/broker/01-connect-anon-denied.py new file mode 100755 index 0000000000..e83bedf0f0 --- /dev/null +++ b/test/broker/01-connect-anon-denied.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python + +# Test whether an anonymous connection is correctly denied. + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 10 +connect_packet = mosq_test.gen_connect("connect-anon-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=5) + +broker = subprocess.Popen(['../../src/mosquitto', '-c', '01-connect-anon-denied.conf'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet) + sock.close() + rc = 0 +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/01-connect-invalid-id-0-311.py b/test/broker/01-connect-invalid-id-0-311.py new file mode 100755 index 0000000000..1ace1c3d95 --- /dev/null +++ b/test/broker/01-connect-invalid-id-0-311.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python + +# Test whether a CONNECT with a zero length client id results in the correct CONNACK packet. + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 10 +connect_packet = mosq_test.gen_connect("", keepalive=keepalive, proto_ver=4) +connack_packet = mosq_test.gen_connack(rc=0) + +broker = subprocess.Popen(['../../src/mosquitto', '-p', '1888'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet) + sock.close() + rc = 0 +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) diff --git a/test/broker/01-connect-invalid-id-0.py b/test/broker/01-connect-invalid-id-0.py new file mode 100755 index 0000000000..972d3ab6f0 --- /dev/null +++ b/test/broker/01-connect-invalid-id-0.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python + +# Test whether a CONNECT with a zero length client id results in the correct CONNACK packet. + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 10 +connect_packet = mosq_test.gen_connect("", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=2) + +broker = subprocess.Popen(['../../src/mosquitto', '-p', '1888'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet) + sock.close() + rc = 0 +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) diff --git a/test/broker/01-connect-invalid-id-24.py b/test/broker/01-connect-invalid-id-24.py new file mode 100755 index 0000000000..9ddad4c59c --- /dev/null +++ b/test/broker/01-connect-invalid-id-24.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python + +# Test whether a CONNECT with a too-long client id results in the correct CONNACK packet. + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 10 +connect_packet = mosq_test.gen_connect("connect-invalid-id-test-", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=2) + +broker = subprocess.Popen(['../../src/mosquitto', '-p', '1888'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet) + sock.close() + rc = 0 + +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/01-connect-invalid-id-missing.py b/test/broker/01-connect-invalid-id-missing.py new file mode 100755 index 0000000000..55e46f151a --- /dev/null +++ b/test/broker/01-connect-invalid-id-missing.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python + +# Test whether a CONNECT with a zero length client id results in the correct CONNACK packet. + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 10 +connect_packet = mosq_test.gen_connect(None, keepalive=keepalive) + +broker = subprocess.Popen(['../../src/mosquitto', '-p', '1888'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, "") + sock.close() + rc = 0 +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) diff --git a/test/broker/01-connect-invalid-protonum.py b/test/broker/01-connect-invalid-protonum.py new file mode 100755 index 0000000000..4a2c5fb3ab --- /dev/null +++ b/test/broker/01-connect-invalid-protonum.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python + +# Test whether a CONNECT with an invalid protocol number results in the correct CONNACK packet. + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 10 +connect_packet = mosq_test.gen_connect("connect-invalid-test", keepalive=keepalive, proto_ver=0) +connack_packet = mosq_test.gen_connack(rc=1) + +broker = subprocess.Popen(['../../src/mosquitto', '-p', '1888'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet) + sock.close() + rc = 0 + +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/01-connect-success.py b/test/broker/01-connect-success.py new file mode 100755 index 0000000000..05b43ed851 --- /dev/null +++ b/test/broker/01-connect-success.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python + +# Test whether a valid CONNECT results in the correct CONNACK packet. + +import inspect, os, sys +import os +import subprocess +import socket +import sys +import time + +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 10 +connect_packet = mosq_test.gen_connect("connect-success-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +broker = subprocess.Popen(['../../src/mosquitto', '-p', '1888'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet) + sock.close() + rc = 0 +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/01-connect-uname-no-password-denied.conf b/test/broker/01-connect-uname-no-password-denied.conf new file mode 100644 index 0000000000..98732736e0 --- /dev/null +++ b/test/broker/01-connect-uname-no-password-denied.conf @@ -0,0 +1,3 @@ +port 1888 +password_file 01-connect-uname-no-password-denied.pwfile +allow_anonymous false diff --git a/test/broker/01-connect-uname-no-password-denied.pwfile b/test/broker/01-connect-uname-no-password-denied.pwfile new file mode 100644 index 0000000000..fd4ac0a23f --- /dev/null +++ b/test/broker/01-connect-uname-no-password-denied.pwfile @@ -0,0 +1 @@ +user:$6$Ut1cUS9PG8+gC3vn$tOjCfSJJDe1Alu9HktxxyyzwN4+6mAMSWGRAF9gmMN8pzcGTPVEYYMAZpCEp96Oz2ZRRz5YKM6lPMf1tUbb6zA== diff --git a/test/broker/01-connect-uname-no-password-denied.py b/test/broker/01-connect-uname-no-password-denied.py new file mode 100755 index 0000000000..845b10da0f --- /dev/null +++ b/test/broker/01-connect-uname-no-password-denied.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python + +# Test whether a connection is denied if it provides just a username when it +# needs a username and password. + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 10 +connect_packet = mosq_test.gen_connect("connect-uname-test", keepalive=keepalive, username="user") +connack_packet = mosq_test.gen_connack(rc=4) + +broker = subprocess.Popen(['../../src/mosquitto', '-c', '01-connect-uname-no-password-denied.conf'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet) + sock.close() + rc = 0 +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/01-connect-uname-password-denied.conf b/test/broker/01-connect-uname-password-denied.conf new file mode 100644 index 0000000000..2d66a6a0e0 --- /dev/null +++ b/test/broker/01-connect-uname-password-denied.conf @@ -0,0 +1,3 @@ +port 1888 +password_file 01-connect-uname-password-denied.pwfile +allow_anonymous false diff --git a/test/broker/01-connect-uname-password-denied.pwfile b/test/broker/01-connect-uname-password-denied.pwfile new file mode 100644 index 0000000000..913477da5a --- /dev/null +++ b/test/broker/01-connect-uname-password-denied.pwfile @@ -0,0 +1 @@ +user:$6$vZY4TS+/HBxHw38S$vvjVFECzb8dyuu/mruD2QKTfdFn0WmKxbc+1TsdB0L8EdHk3v9JRmfjHd56+VaTnUcSZOZ/hzkdvWCtxlX7AUQ== diff --git a/test/broker/01-connect-uname-password-denied.py b/test/broker/01-connect-uname-password-denied.py new file mode 100755 index 0000000000..618907922e --- /dev/null +++ b/test/broker/01-connect-uname-password-denied.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python + +# Test whether a connection is denied if it provides a correct username but +# incorrect password. + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 10 +connect_packet = mosq_test.gen_connect("connect-uname-pwd-test", keepalive=keepalive, username="user", password="password9") +connack_packet = mosq_test.gen_connack(rc=4) + +broker = subprocess.Popen(['../../src/mosquitto', '-c', '01-connect-uname-password-denied.conf'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet) + sock.close() + rc = 0 + +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/01-connect-uname-password-success.conf b/test/broker/01-connect-uname-password-success.conf new file mode 100644 index 0000000000..99bc880183 --- /dev/null +++ b/test/broker/01-connect-uname-password-success.conf @@ -0,0 +1,3 @@ +port 1888 +password_file 01-connect-uname-password-success.pwfile +allow_anonymous false diff --git a/test/broker/01-connect-uname-password-success.pwfile b/test/broker/01-connect-uname-password-success.pwfile new file mode 100644 index 0000000000..e516eb7776 --- /dev/null +++ b/test/broker/01-connect-uname-password-success.pwfile @@ -0,0 +1 @@ +user:$6$LIg/OiUz2yPftClP$dQu0vVNqRHOcMOzDLuqv4e+5rTFW83DFm3s+C8fy9F7Ip73cdIGUlsNGBs4MtKWNjtMl8LnT+pIQZ7ic1ZttyQ== diff --git a/test/broker/01-connect-uname-password-success.py b/test/broker/01-connect-uname-password-success.py new file mode 100755 index 0000000000..ca2d6b4f4d --- /dev/null +++ b/test/broker/01-connect-uname-password-success.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python + +# Test whether a connection is denied if it provides a correct username but +# incorrect password. + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 10 +connect_packet = mosq_test.gen_connect("connect-uname-pwd-test", keepalive=keepalive, username="user", password="password") +connack_packet = mosq_test.gen_connack(rc=0) + +broker = subprocess.Popen(['../../src/mosquitto', '-c', '01-connect-uname-password-success.conf'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet) + sock.close() + rc = 0 + +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/02-subpub-qos0.py b/test/broker/02-subpub-qos0.py new file mode 100755 index 0000000000..b5ff606cdd --- /dev/null +++ b/test/broker/02-subpub-qos0.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python + +# Test whether a client subscribed to a topic receives its own message sent to that topic. + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +mid = 53 +keepalive = 60 +connect_packet = mosq_test.gen_connect("subpub-qos0-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +subscribe_packet = mosq_test.gen_subscribe(mid, "subpub/qos0", 0) +suback_packet = mosq_test.gen_suback(mid, 0) + +publish_packet = mosq_test.gen_publish("subpub/qos0", qos=0, payload="message") + +broker = subprocess.Popen(['../../src/mosquitto', '-p', '1888'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet, timeout=20) + sock.send(subscribe_packet) + + if mosq_test.expect_packet(sock, "suback", suback_packet): + sock.send(publish_packet) + + if mosq_test.expect_packet(sock, "publish", publish_packet): + rc = 0 + + sock.close() +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/02-subpub-qos1.py b/test/broker/02-subpub-qos1.py new file mode 100755 index 0000000000..c58a455aa6 --- /dev/null +++ b/test/broker/02-subpub-qos1.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python + +# Test whether a client subscribed to a topic receives its own message sent to that topic. + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +mid = 530 +keepalive = 60 +connect_packet = mosq_test.gen_connect("subpub-qos1-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +subscribe_packet = mosq_test.gen_subscribe(mid, "subpub/qos1", 1) +suback_packet = mosq_test.gen_suback(mid, 1) + +mid = 300 +publish_packet = mosq_test.gen_publish("subpub/qos1", qos=1, mid=mid, payload="message") +puback_packet = mosq_test.gen_puback(mid) + +mid = 1 +publish_packet2 = mosq_test.gen_publish("subpub/qos1", qos=1, mid=mid, payload="message") + +broker = subprocess.Popen(['../../src/mosquitto', '-p', '1888'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet, timeout=20) + sock.send(subscribe_packet) + + if mosq_test.expect_packet(sock, "suback", suback_packet): + sock.send(publish_packet) + + if mosq_test.expect_packet(sock, "puback", puback_packet): + + if mosq_test.expect_packet(sock, "publish2", publish_packet2): + rc = 0 + + sock.close() +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/02-subpub-qos2.py b/test/broker/02-subpub-qos2.py new file mode 100755 index 0000000000..11f2ef7c6a --- /dev/null +++ b/test/broker/02-subpub-qos2.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python + +# Test whether a client subscribed to a topic receives its own message sent to that topic. + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +mid = 530 +keepalive = 60 +connect_packet = mosq_test.gen_connect("subpub-qos2-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +subscribe_packet = mosq_test.gen_subscribe(mid, "subpub/qos2", 2) +suback_packet = mosq_test.gen_suback(mid, 2) + +mid = 301 +publish_packet = mosq_test.gen_publish("subpub/qos2", qos=2, mid=mid, payload="message") +pubrec_packet = mosq_test.gen_pubrec(mid) +pubrel_packet = mosq_test.gen_pubrel(mid) +pubcomp_packet = mosq_test.gen_pubcomp(mid) + +mid = 1 +publish_packet2 = mosq_test.gen_publish("subpub/qos2", qos=2, mid=mid, payload="message") +pubrec_packet2 = mosq_test.gen_pubrec(mid) +pubrel_packet2 = mosq_test.gen_pubrel(mid) +pubcomp_packet2 = mosq_test.gen_pubcomp(mid) + +broker = subprocess.Popen(['../../src/mosquitto', '-p', '1888'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet, timeout=20) + sock.send(subscribe_packet) + + if mosq_test.expect_packet(sock, "suback", suback_packet): + sock.send(publish_packet) + + if mosq_test.expect_packet(sock, "pubrec", pubrec_packet): + sock.send(pubrel_packet) + + if mosq_test.expect_packet(sock, "pubcomp", pubcomp_packet): + if mosq_test.expect_packet(sock, "publish2", publish_packet2): + sock.send(pubrec_packet2) + + if mosq_test.expect_packet(sock, "pubrel2", pubrel_packet2): + # Broker side of flow complete so can quit here. + rc = 0 + + sock.close() +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/02-subscribe-qos0.py b/test/broker/02-subscribe-qos0.py new file mode 100755 index 0000000000..34b62e411d --- /dev/null +++ b/test/broker/02-subscribe-qos0.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python + +# Test whether a SUBSCRIBE to a topic with QoS 0 results in the correct SUBACK packet. + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +mid = 53 +keepalive = 60 +connect_packet = mosq_test.gen_connect("subscribe-qos0-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +subscribe_packet = mosq_test.gen_subscribe(mid, "qos0/test", 0) +suback_packet = mosq_test.gen_suback(mid, 0) + +broker = subprocess.Popen(['../../src/mosquitto', '-p', '1888'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet) + sock.send(subscribe_packet) + + if mosq_test.expect_packet(sock, "suback", suback_packet): + rc = 0 + + sock.close() +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/02-subscribe-qos1.py b/test/broker/02-subscribe-qos1.py new file mode 100755 index 0000000000..644b03cef5 --- /dev/null +++ b/test/broker/02-subscribe-qos1.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python + +# Test whether a SUBSCRIBE to a topic with QoS 1 results in the correct SUBACK packet. + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +mid = 79 +keepalive = 60 +connect_packet = mosq_test.gen_connect("subscribe-qos1-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +subscribe_packet = mosq_test.gen_subscribe(mid, "qos1/test", 1) +suback_packet = mosq_test.gen_suback(mid, 1) + +broker = subprocess.Popen(['../../src/mosquitto', '-p', '1888'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet) + sock.send(subscribe_packet) + + if mosq_test.expect_packet(sock, "suback", suback_packet): + rc = 0 + + sock.close() +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/02-subscribe-qos2.py b/test/broker/02-subscribe-qos2.py new file mode 100755 index 0000000000..9aba219d1d --- /dev/null +++ b/test/broker/02-subscribe-qos2.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python + +# Test whether a SUBSCRIBE to a topic with QoS 2 results in the correct SUBACK packet. + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +mid = 3 +keepalive = 60 +connect_packet = mosq_test.gen_connect("subscribe-qos2-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +subscribe_packet = mosq_test.gen_subscribe(mid, "qos2/test", 2) +suback_packet = mosq_test.gen_suback(mid, 2) + +broker = subprocess.Popen(['../../src/mosquitto', '-p', '1888'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet) + sock.send(subscribe_packet) + + if mosq_test.expect_packet(sock, "suback", suback_packet): + rc = 0 + + sock.close() +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/02-unsubscribe-qos0.py b/test/broker/02-unsubscribe-qos0.py new file mode 100755 index 0000000000..8b544ad6c3 --- /dev/null +++ b/test/broker/02-unsubscribe-qos0.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python + +# Test whether a UNSUBSCRIBE to a topic with QoS 0 results in the correct UNSUBACK packet. +# This doesn't assume a subscription exists. + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +mid = 53 +keepalive = 60 +connect_packet = mosq_test.gen_connect("unsubscribe-qos0-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +unsubscribe_packet = mosq_test.gen_unsubscribe(mid, "qos0/test") +unsuback_packet = mosq_test.gen_unsuback(mid) + +broker = subprocess.Popen(['../../src/mosquitto', '-p', '1888'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet) + sock.send(unsubscribe_packet) + + if mosq_test.expect_packet(sock, "unsuback", unsuback_packet): + rc = 0 + + sock.close() +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/02-unsubscribe-qos1.py b/test/broker/02-unsubscribe-qos1.py new file mode 100755 index 0000000000..ea8cabef65 --- /dev/null +++ b/test/broker/02-unsubscribe-qos1.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python + +# Test whether a SUBSCRIBE to a topic with QoS 1 results in the correct SUBACK packet. + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +mid = 79 +keepalive = 60 +connect_packet = mosq_test.gen_connect("unsubscribe-qos1-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +unsubscribe_packet = mosq_test.gen_unsubscribe(mid, "qos1/test") +unsuback_packet = mosq_test.gen_unsuback(mid) + +broker = subprocess.Popen(['../../src/mosquitto', '-p', '1888'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet) + sock.send(unsubscribe_packet) + + if mosq_test.expect_packet(sock, "unsuback", unsuback_packet): + rc = 0 + + sock.close() +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/02-unsubscribe-qos2.py b/test/broker/02-unsubscribe-qos2.py new file mode 100755 index 0000000000..95b7d03e77 --- /dev/null +++ b/test/broker/02-unsubscribe-qos2.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python + +# Test whether a SUBSCRIBE to a topic with QoS 2 results in the correct SUBACK packet. + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +mid = 3 +keepalive = 60 +connect_packet = mosq_test.gen_connect("unsubscribe-qos2-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +unsubscribe_packet = mosq_test.gen_unsubscribe(mid, "qos2/test") +unsuback_packet = mosq_test.gen_unsuback(mid) + +broker = subprocess.Popen(['../../src/mosquitto', '-p', '1888'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet) + sock.send(unsubscribe_packet) + + if mosq_test.expect_packet(sock, "unsuback", unsuback_packet): + rc = 0 + + sock.close() +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/03-pattern-matching-helper.py b/test/broker/03-pattern-matching-helper.py new file mode 100755 index 0000000000..b247a34fdf --- /dev/null +++ b/test/broker/03-pattern-matching-helper.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +connect_packet = mosq_test.gen_connect("test-helper", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +publish_packet = mosq_test.gen_publish(sys.argv[1], qos=0, retain=True, payload="message") + +sock = mosq_test.do_client_connect(connect_packet, connack_packet, connack_error="helper connack") +sock.send(publish_packet) +rc = 0 +sock.close() + +exit(rc) + diff --git a/test/broker/03-pattern-matching.py b/test/broker/03-pattern-matching.py new file mode 100755 index 0000000000..a5d38aba87 --- /dev/null +++ b/test/broker/03-pattern-matching.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +def pattern_test(sub_topic, pub_topic): + rc = 1 + keepalive = 60 + connect_packet = mosq_test.gen_connect("pattern-sub-test", keepalive=keepalive) + connack_packet = mosq_test.gen_connack(rc=0) + + publish_packet = mosq_test.gen_publish(pub_topic, qos=0, payload="message") + publish_retained_packet = mosq_test.gen_publish(pub_topic, qos=0, retain=True, payload="message") + + mid = 312 + subscribe_packet = mosq_test.gen_subscribe(mid, sub_topic, 0) + suback_packet = mosq_test.gen_suback(mid, 0) + + mid = 234; + unsubscribe_packet = mosq_test.gen_unsubscribe(mid, sub_topic) + unsuback_packet = mosq_test.gen_unsuback(mid) + + broker = subprocess.Popen(['../../src/mosquitto', '-p', '1888'], stderr=subprocess.PIPE) + + try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet, timeout=20) + sock.send(subscribe_packet) + + if mosq_test.expect_packet(sock, "suback", suback_packet): + pub = subprocess.Popen(['./03-pattern-matching-helper.py', pub_topic]) + pub.wait() + + if mosq_test.expect_packet(sock, "publish", publish_packet): + sock.send(unsubscribe_packet) + + if mosq_test.expect_packet(sock, "unsuback", unsuback_packet): + sock.send(subscribe_packet) + + if mosq_test.expect_packet(sock, "suback", suback_packet): + if mosq_test.expect_packet(sock, "publish retained", publish_retained_packet): + rc = 0 + + sock.close() + finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + raise + + return rc + +pattern_test("#", "test/topic") +pattern_test("#", "/test/topic") +pattern_test("foo/#", "foo/bar/baz") +pattern_test("foo/+/baz", "foo/bar/baz") +pattern_test("foo/+/baz/#", "foo/bar/baz") +pattern_test("foo/+/baz/#", "foo/bar/baz/bar") +pattern_test("foo/foo/baz/#", "foo/foo/baz/bar") +pattern_test("foo/#", "foo") +pattern_test("/#", "/foo") +pattern_test("test/topic/", "test/topic/") +pattern_test("test/topic/+", "test/topic/") +pattern_test("+/+/+/+/+/+/+/+/+/+/test", "one/two/three/four/five/six/seven/eight/nine/ten/test") + +pattern_test("#", "test////a//topic") +pattern_test("#", "/test////a//topic") +pattern_test("foo/#", "foo//bar///baz") +pattern_test("foo/+/baz", "foo//baz") +pattern_test("foo/+/baz//", "foo//baz//") +pattern_test("foo/+/baz/#", "foo//baz") +pattern_test("foo/+/baz/#", "foo//baz/bar") +pattern_test("foo//baz/#", "foo//baz/bar") +pattern_test("foo/foo/baz/#", "foo/foo/baz/bar") +pattern_test("/#", "////foo///bar") + +exit(0) + diff --git a/test/broker/03-publish-b2c-disconnect-qos1-helper.py b/test/broker/03-publish-b2c-disconnect-qos1-helper.py new file mode 100755 index 0000000000..8c322297aa --- /dev/null +++ b/test/broker/03-publish-b2c-disconnect-qos1-helper.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +connect_packet = mosq_test.gen_connect("test-helper", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +mid = 128 +publish_packet = mosq_test.gen_publish("qos1/disconnect/test", qos=1, mid=mid, payload="disconnect-message") +puback_packet = mosq_test.gen_puback(mid) + +sock = mosq_test.do_client_connect(connect_packet, connack_packet, connack_error="helper connack") +sock.send(publish_packet) + +if mosq_test.expect_packet(sock, "helper puback", puback_packet): + rc = 0 + +sock.close() + +exit(rc) + diff --git a/test/broker/03-publish-b2c-disconnect-qos1.conf b/test/broker/03-publish-b2c-disconnect-qos1.conf new file mode 100644 index 0000000000..425d667978 --- /dev/null +++ b/test/broker/03-publish-b2c-disconnect-qos1.conf @@ -0,0 +1,3 @@ +retry_interval 10 +port 1888 +log_type debug diff --git a/test/broker/03-publish-b2c-disconnect-qos1.py b/test/broker/03-publish-b2c-disconnect-qos1.py new file mode 100755 index 0000000000..50fdf874c9 --- /dev/null +++ b/test/broker/03-publish-b2c-disconnect-qos1.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python + +import subprocess +import socket +import time +from os import environ + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +mid = 3265 +keepalive = 60 +connect_packet = mosq_test.gen_connect("pub-qos1-disco-test", keepalive=keepalive, clean_session=False) +connack_packet = mosq_test.gen_connack(rc=0) + +subscribe_packet = mosq_test.gen_subscribe(mid, "qos1/disconnect/test", 1) +suback_packet = mosq_test.gen_suback(mid, 1) + +mid = 1 +publish_packet = mosq_test.gen_publish("qos1/disconnect/test", qos=1, mid=mid, payload="disconnect-message") +publish_dup_packet = mosq_test.gen_publish("qos1/disconnect/test", qos=1, mid=mid, payload="disconnect-message", dup=True) +puback_packet = mosq_test.gen_puback(mid) + +mid = 3266 +publish2_packet = mosq_test.gen_publish("qos1/outgoing", qos=1, mid=mid, payload="outgoing-message") +puback2_packet = mosq_test.gen_puback(mid) + +broker = subprocess.Popen(['../../src/mosquitto', '-c', '03-publish-b2c-disconnect-qos1.conf'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet) + sock.send(subscribe_packet) + + if mosq_test.expect_packet(sock, "suback", suback_packet): + pub = subprocess.Popen(['./03-publish-b2c-disconnect-qos1-helper.py']) + pub.wait() + # Should have now received a publish command + + if mosq_test.expect_packet(sock, "publish", publish_packet): + # Send our outgoing message. When we disconnect the broker + # should get rid of it and assume we're going to retry. + sock.send(publish2_packet) + sock.close() + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(60) # 60 seconds timeout is much longer than 5 seconds message retry. + sock.connect(("localhost", 1888)) + sock.send(connect_packet) + + if mosq_test.expect_packet(sock, "connack", connack_packet): + + if mosq_test.expect_packet(sock, "dup publish", publish_dup_packet): + sock.send(puback_packet) + rc = 0 + + sock.close() +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/03-publish-b2c-disconnect-qos2-helper.py b/test/broker/03-publish-b2c-disconnect-qos2-helper.py new file mode 100755 index 0000000000..23ca09131b --- /dev/null +++ b/test/broker/03-publish-b2c-disconnect-qos2-helper.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python + +# Test whether a PUBLISH to a topic with QoS 2 results in the correct packet flow. + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +connect_packet = mosq_test.gen_connect("test-helper", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +mid = 312 +publish_packet = mosq_test.gen_publish("qos2/disconnect/test", qos=2, mid=mid, payload="disconnect-message") +pubrec_packet = mosq_test.gen_pubrec(mid) +pubrel_packet = mosq_test.gen_pubrel(mid) +pubcomp_packet = mosq_test.gen_pubcomp(mid) + +sock = mosq_test.do_client_connect(connect_packet, connack_packet, connack_error="helper connack") +sock.send(publish_packet) + +if mosq_test.expect_packet(sock, "helper pubrec", pubrec_packet): + sock.send(pubrel_packet) + + if mosq_test.expect_packet(sock, "helper pubcomp", pubcomp_packet): + rc = 0 + +sock.close() + +exit(rc) + diff --git a/test/broker/03-publish-b2c-disconnect-qos2.conf b/test/broker/03-publish-b2c-disconnect-qos2.conf new file mode 100644 index 0000000000..425d667978 --- /dev/null +++ b/test/broker/03-publish-b2c-disconnect-qos2.conf @@ -0,0 +1,3 @@ +retry_interval 10 +port 1888 +log_type debug diff --git a/test/broker/03-publish-b2c-disconnect-qos2.py b/test/broker/03-publish-b2c-disconnect-qos2.py new file mode 100755 index 0000000000..a932bc7369 --- /dev/null +++ b/test/broker/03-publish-b2c-disconnect-qos2.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python + +import subprocess +import socket +import time +from os import environ + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +mid = 3265 +keepalive = 60 +connect_packet = mosq_test.gen_connect("pub-qos2-disco-test", keepalive=keepalive, clean_session=False) +connack_packet = mosq_test.gen_connack(rc=0) + +subscribe_packet = mosq_test.gen_subscribe(mid, "qos2/disconnect/test", 2) +suback_packet = mosq_test.gen_suback(mid, 2) + +mid = 1 +publish_packet = mosq_test.gen_publish("qos2/disconnect/test", qos=2, mid=mid, payload="disconnect-message") +publish_dup_packet = mosq_test.gen_publish("qos2/disconnect/test", qos=2, mid=mid, payload="disconnect-message", dup=True) +pubrec_packet = mosq_test.gen_pubrec(mid) +pubrel_packet = mosq_test.gen_pubrel(mid) +pubrel_dup_packet = mosq_test.gen_pubrel(mid, dup=True) +pubcomp_packet = mosq_test.gen_pubcomp(mid) + +mid = 3266 +publish2_packet = mosq_test.gen_publish("qos1/outgoing", qos=1, mid=mid, payload="outgoing-message") +puback2_packet = mosq_test.gen_puback(mid) + +broker = subprocess.Popen(['../../src/mosquitto', '-c', '03-publish-b2c-disconnect-qos2.conf'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet) + sock.send(subscribe_packet) + + if mosq_test.expect_packet(sock, "suback", suback_packet): + pub = subprocess.Popen(['./03-publish-b2c-disconnect-qos2-helper.py']) + hrc = pub.wait() + if hrc: + exit(hrc) + # Should have now received a publish command + + if mosq_test.expect_packet(sock, "publish", publish_packet): + # Send our outgoing message. When we disconnect the broker + # should get rid of it and assume we're going to retry. + sock.send(publish2_packet) + sock.close() + + sock = mosq_test.do_client_connect(connect_packet, connack_packet) + if mosq_test.expect_packet(sock, "dup publish", publish_dup_packet): + sock.send(pubrec_packet) + + if mosq_test.expect_packet(sock, "pubrel", pubrel_packet): + sock.close() + + sock = mosq_test.do_client_connect(connect_packet, connack_packet) + if mosq_test.expect_packet(sock, "dup pubrel", pubrel_dup_packet): + sock.send(pubcomp_packet) + rc = 0 + sock.close() + +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/03-publish-b2c-timeout-qos1-helper.py b/test/broker/03-publish-b2c-timeout-qos1-helper.py new file mode 100755 index 0000000000..6d78356809 --- /dev/null +++ b/test/broker/03-publish-b2c-timeout-qos1-helper.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python + +# Test whether a PUBLISH to a topic with QoS 2 results in the correct packet flow. + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +connect_packet = mosq_test.gen_connect("test-helper", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +mid = 128 +publish_packet = mosq_test.gen_publish("qos1/timeout/test", qos=1, mid=mid, payload="timeout-message") +puback_packet = mosq_test.gen_puback(mid) + +sock = mosq_test.do_client_connect(connect_packet, connack_packet, connack_error="helper connack") +sock.send(publish_packet) + +if mosq_test.expect_packet(sock, "helper puback", puback_packet): + rc = 0 + +sock.close() + +exit(rc) + diff --git a/test/broker/03-publish-b2c-timeout-qos1.conf b/test/broker/03-publish-b2c-timeout-qos1.conf new file mode 100644 index 0000000000..425d667978 --- /dev/null +++ b/test/broker/03-publish-b2c-timeout-qos1.conf @@ -0,0 +1,3 @@ +retry_interval 10 +port 1888 +log_type debug diff --git a/test/broker/03-publish-b2c-timeout-qos1.py b/test/broker/03-publish-b2c-timeout-qos1.py new file mode 100755 index 0000000000..1608642352 --- /dev/null +++ b/test/broker/03-publish-b2c-timeout-qos1.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python + +# Test whether a SUBSCRIBE to a topic with QoS 2 results in the correct SUBACK packet. + +import subprocess +import socket +import time +from os import environ + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +mid = 3265 +keepalive = 60 +connect_packet = mosq_test.gen_connect("pub-qos1-timeout-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +subscribe_packet = mosq_test.gen_subscribe(mid, "qos1/timeout/test", 1) +suback_packet = mosq_test.gen_suback(mid, 1) + +mid = 1 +publish_packet = mosq_test.gen_publish("qos1/timeout/test", qos=1, mid=mid, payload="timeout-message") +publish_dup_packet = mosq_test.gen_publish("qos1/timeout/test", qos=1, mid=mid, payload="timeout-message", dup=True) +puback_packet = mosq_test.gen_puback(mid) + +broker = subprocess.Popen(['../../src/mosquitto', '-c', '03-publish-b2c-timeout-qos1.conf'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet) + sock.send(subscribe_packet) + + if mosq_test.expect_packet(sock, "suback", suback_packet): + pub = subprocess.Popen(['./03-publish-b2c-timeout-qos1-helper.py']) + pub.wait() + # Should have now received a publish command + + if mosq_test.expect_packet(sock, "publish", publish_packet): + # Wait for longer than 5 seconds to get republish with dup set + # This is covered by the 8 second timeout + + if mosq_test.expect_packet(sock, "dup publish", publish_dup_packet): + sock.send(puback_packet) + rc = 0 + + sock.close() +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/03-publish-b2c-timeout-qos2-helper.py b/test/broker/03-publish-b2c-timeout-qos2-helper.py new file mode 100755 index 0000000000..9ccd5e72c8 --- /dev/null +++ b/test/broker/03-publish-b2c-timeout-qos2-helper.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python + +# Test whether a PUBLISH to a topic with QoS 2 results in the correct packet flow. + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +connect_packet = mosq_test.gen_connect("test-helper", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +mid = 312 +publish_packet = mosq_test.gen_publish("qos2/timeout/test", qos=2, mid=mid, payload="timeout-message") +pubrec_packet = mosq_test.gen_pubrec(mid) +pubrel_packet = mosq_test.gen_pubrel(mid) +pubcomp_packet = mosq_test.gen_pubcomp(mid) + +sock = mosq_test.do_client_connect(connect_packet, connack_packet, connack_error="helper connack") +sock.send(publish_packet) + +if mosq_test.expect_packet(sock, "helper pubrec", pubrec_packet): + sock.send(pubrel_packet) + + if mosq_test.expect_packet(sock, "helper pubcomp", pubcomp_packet): + rc = 0 + +sock.close() + +exit(rc) + diff --git a/test/broker/03-publish-b2c-timeout-qos2.conf b/test/broker/03-publish-b2c-timeout-qos2.conf new file mode 100644 index 0000000000..425d667978 --- /dev/null +++ b/test/broker/03-publish-b2c-timeout-qos2.conf @@ -0,0 +1,3 @@ +retry_interval 10 +port 1888 +log_type debug diff --git a/test/broker/03-publish-b2c-timeout-qos2.py b/test/broker/03-publish-b2c-timeout-qos2.py new file mode 100755 index 0000000000..4b9c09a7f4 --- /dev/null +++ b/test/broker/03-publish-b2c-timeout-qos2.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python + +# Test whether a SUBSCRIBE to a topic with QoS 2 results in the correct SUBACK packet. + +import subprocess +import socket +import time +from os import environ + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +mid = 3265 +keepalive = 60 +connect_packet = mosq_test.gen_connect("pub-qo2-timeout-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +subscribe_packet = mosq_test.gen_subscribe(mid, "qos2/timeout/test", 2) +suback_packet = mosq_test.gen_suback(mid, 2) + +mid = 1 +publish_packet = mosq_test.gen_publish("qos2/timeout/test", qos=2, mid=mid, payload="timeout-message") +publish_dup_packet = mosq_test.gen_publish("qos2/timeout/test", qos=2, mid=mid, payload="timeout-message", dup=True) +pubrec_packet = mosq_test.gen_pubrec(mid) +pubrel_packet = mosq_test.gen_pubrel(mid) +pubrel_dup_packet = mosq_test.gen_pubrel(mid, dup=True) +pubcomp_packet = mosq_test.gen_pubcomp(mid) + +broker = subprocess.Popen(['../../src/mosquitto', '-c', '03-publish-b2c-timeout-qos2.conf'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet) + sock.send(subscribe_packet) + + if mosq_test.expect_packet(sock, "suback", suback_packet): + pub = subprocess.Popen(['./03-publish-b2c-timeout-qos2-helper.py']) + pub.wait() + # Should have now received a publish command + + if mosq_test.expect_packet(sock, "publish", publish_packet): + # Wait for longer than 5 seconds to get republish with dup set + # This is covered by the 8 second timeout + + if mosq_test.expect_packet(sock, "dup publish", publish_dup_packet): + sock.send(pubrec_packet) + + if mosq_test.expect_packet(sock, "pubrel", pubrel_packet): + # Wait for longer than 5 seconds to get republish with dup set + # This is covered by the 8 second timeout + + if mosq_test.expect_packet(sock, "dup pubrel", pubrel_dup_packet): + sock.send(pubcomp_packet) + rc = 0 + + sock.close() +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/03-publish-c2b-disconnect-qos2.conf b/test/broker/03-publish-c2b-disconnect-qos2.conf new file mode 100644 index 0000000000..a427f24404 --- /dev/null +++ b/test/broker/03-publish-c2b-disconnect-qos2.conf @@ -0,0 +1,2 @@ +retry_interval 10 +port 1888 diff --git a/test/broker/03-publish-c2b-disconnect-qos2.py b/test/broker/03-publish-c2b-disconnect-qos2.py new file mode 100755 index 0000000000..56a17f152d --- /dev/null +++ b/test/broker/03-publish-c2b-disconnect-qos2.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python + +import subprocess +import socket +import time +from os import environ + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +mid = 3265 +keepalive = 60 +connect_packet = mosq_test.gen_connect("pub-qos2-disco-test", keepalive=keepalive, clean_session=False) +connack_packet = mosq_test.gen_connack(rc=0) + +subscribe_packet = mosq_test.gen_subscribe(mid, "qos2/disconnect/test", 2) +suback_packet = mosq_test.gen_suback(mid, 2) + +mid = 1 +publish_packet = mosq_test.gen_publish("qos2/disconnect/test", qos=2, mid=mid, payload="disconnect-message") +publish_dup_packet = mosq_test.gen_publish("qos2/disconnect/test", qos=2, mid=mid, payload="disconnect-message", dup=True) +pubrec_packet = mosq_test.gen_pubrec(mid) +pubrel_packet = mosq_test.gen_pubrel(mid) +pubrel_dup_packet = mosq_test.gen_pubrel(mid, dup=True) +pubcomp_packet = mosq_test.gen_pubcomp(mid) + +mid = 3266 +publish2_packet = mosq_test.gen_publish("qos1/outgoing", qos=1, mid=mid, payload="outgoing-message") +puback2_packet = mosq_test.gen_puback(mid) + +broker = subprocess.Popen(['../../src/mosquitto', '-c', '03-publish-c2b-disconnect-qos2.conf'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet) + #sock.send(subscribe_packet) + + #if mosq_test.expect_packet(sock, "suback", suback_packet): + + sock.send(publish_packet) + if mosq_test.expect_packet(sock, "pubrec", pubrec_packet): + # We're now going to disconnect and pretend we didn't receive the pubrec. + sock.close() + + sock = mosq_test.do_client_connect(connect_packet, connack_packet) + sock.send(publish_dup_packet) + + if mosq_test.expect_packet(sock, "pubrec", pubrec_packet): + sock.send(pubrel_packet) + + if mosq_test.expect_packet(sock, "pubcomp", pubcomp_packet): + # Again, pretend we didn't receive this pubcomp + sock.close() + + sock = mosq_test.do_client_connect(connect_packet, connack_packet) + sock.send(pubrel_dup_packet) + + if mosq_test.expect_packet(sock, "pubcomp", pubcomp_packet): + rc = 0 + + sock.close() +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/03-publish-c2b-timeout-qos2.conf b/test/broker/03-publish-c2b-timeout-qos2.conf new file mode 100644 index 0000000000..a427f24404 --- /dev/null +++ b/test/broker/03-publish-c2b-timeout-qos2.conf @@ -0,0 +1,2 @@ +retry_interval 10 +port 1888 diff --git a/test/broker/03-publish-c2b-timeout-qos2.py b/test/broker/03-publish-c2b-timeout-qos2.py new file mode 100755 index 0000000000..6100864661 --- /dev/null +++ b/test/broker/03-publish-c2b-timeout-qos2.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python + +# Test whether a PUBLISH to a topic with QoS 2 results in the correct packet +# flow. This test introduces delays into the flow in order to force the broker +# to send duplicate PUBREC and PUBCOMP messages. + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 600 +connect_packet = mosq_test.gen_connect("pub-qos2-timeout-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +mid = 1926 +publish_packet = mosq_test.gen_publish("pub/qos2/test", qos=2, mid=mid, payload="timeout-message") +pubrec_packet = mosq_test.gen_pubrec(mid) +pubrel_packet = mosq_test.gen_pubrel(mid) +pubcomp_packet = mosq_test.gen_pubcomp(mid) + +broker = subprocess.Popen(['../../src/mosquitto', '-c', '03-publish-c2b-timeout-qos2.conf'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet) + sock.send(publish_packet) + + if mosq_test.expect_packet(sock, "pubrec", pubrec_packet): + # Timeout is 8 seconds which means the broker should repeat the PUBREC. + + if mosq_test.expect_packet(sock, "pubrec", pubrec_packet): + sock.send(pubrel_packet) + + if mosq_test.expect_packet(sock, "pubcomp", pubcomp_packet): + rc = 0 + + sock.close() +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/03-publish-qos1.py b/test/broker/03-publish-qos1.py new file mode 100755 index 0000000000..bb8a949651 --- /dev/null +++ b/test/broker/03-publish-qos1.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python + +# Test whether a PUBLISH to a topic with QoS 1 results in the correct PUBACK packet. + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +mid = 19 +keepalive = 60 +connect_packet = mosq_test.gen_connect("pub-qos1-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +publish_packet = mosq_test.gen_publish("pub/qos1/test", qos=1, mid=mid, payload="message") +puback_packet = mosq_test.gen_puback(mid) + +broker = subprocess.Popen(['../../src/mosquitto', '-p', '1888'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet) + sock.send(publish_packet) + + if mosq_test.expect_packet(sock, "puback", puback_packet): + rc = 0 + + sock.close() +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/03-publish-qos2.py b/test/broker/03-publish-qos2.py new file mode 100755 index 0000000000..46e5ab5313 --- /dev/null +++ b/test/broker/03-publish-qos2.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python + +# Test whether a PUBLISH to a topic with QoS 2 results in the correct packet flow. + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +connect_packet = mosq_test.gen_connect("pub-qos2-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +mid = 312 +publish_packet = mosq_test.gen_publish("pub/qos2/test", qos=2, mid=mid, payload="message") +pubrec_packet = mosq_test.gen_pubrec(mid) +pubrel_packet = mosq_test.gen_pubrel(mid) +pubcomp_packet = mosq_test.gen_pubcomp(mid) + +broker = subprocess.Popen(['../../src/mosquitto', '-p', '1888'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet) + sock.send(publish_packet) + + if mosq_test.expect_packet(sock, "pubrec", pubrec_packet): + sock.send(pubrel_packet) + + if mosq_test.expect_packet(sock, "pubcomp", pubcomp_packet): + rc = 0 + + sock.close() +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/04-retain-qos0-clear.py b/test/broker/04-retain-qos0-clear.py new file mode 100755 index 0000000000..fac1acba02 --- /dev/null +++ b/test/broker/04-retain-qos0-clear.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python + +# Test whether a retained PUBLISH is cleared when a zero length retained +# message is published to a topic. + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +connect_packet = mosq_test.gen_connect("retain-clear-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +publish_packet = mosq_test.gen_publish("retain/clear/test", qos=0, payload="retained message", retain=True) +retain_clear_packet = mosq_test.gen_publish("retain/clear/test", qos=0, payload=None, retain=True) +mid_sub = 592 +subscribe_packet = mosq_test.gen_subscribe(mid_sub, "retain/clear/test", 0) +suback_packet = mosq_test.gen_suback(mid_sub, 0) + +mid_unsub = 593 +unsubscribe_packet = mosq_test.gen_unsubscribe(mid_unsub, "retain/clear/test") +unsuback_packet = mosq_test.gen_unsuback(mid_unsub) + +broker = subprocess.Popen(['../../src/mosquitto', '-p', '1888'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet, timeout=4) + # Send retained message + sock.send(publish_packet) + # Subscribe to topic, we should get the retained message back. + sock.send(subscribe_packet) + + if mosq_test.expect_packet(sock, "suback", suback_packet): + if mosq_test.expect_packet(sock, "publish", publish_packet): + # Now unsubscribe from the topic before we clear the retained + # message. + sock.send(unsubscribe_packet) + + if mosq_test.expect_packet(sock, "unsuback", unsuback_packet): + # Now clear the retained message. + sock.send(retain_clear_packet) + + # Subscribe to topic, we shouldn't get anything back apart + # from the SUBACK. + sock.send(subscribe_packet) + if mosq_test.expect_packet(sock, "suback", suback_packet): + try: + retain_clear = sock.recv(256) + except socket.timeout: + # This is the expected event + rc = 0 + else: + print("FAIL: Received unexpected message.") + + sock.close() +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/04-retain-qos0-fresh.py b/test/broker/04-retain-qos0-fresh.py new file mode 100755 index 0000000000..f7f0f391d8 --- /dev/null +++ b/test/broker/04-retain-qos0-fresh.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python + +# Test whether a retained PUBLISH to a topic with QoS 0 is sent with +# retain=false to an already subscribed client. + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +mid = 16 +connect_packet = mosq_test.gen_connect("retain-qos0-fresh-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +publish_packet = mosq_test.gen_publish("retain/qos0/test", qos=0, payload="retained message", retain=True) +publish_fresh_packet = mosq_test.gen_publish("retain/qos0/test", qos=0, payload="retained message") +subscribe_packet = mosq_test.gen_subscribe(mid, "retain/qos0/test", 0) +suback_packet = mosq_test.gen_suback(mid, 0) + +broker = subprocess.Popen(['../../src/mosquitto', '-p', '1888'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet) + sock.send(subscribe_packet) + + if mosq_test.expect_packet(sock, "suback", suback_packet): + sock.send(publish_packet) + + if mosq_test.expect_packet(sock, "publish", publish_fresh_packet): + rc = 0 + sock.close() +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/04-retain-qos0-repeated.py b/test/broker/04-retain-qos0-repeated.py new file mode 100755 index 0000000000..de992065ae --- /dev/null +++ b/test/broker/04-retain-qos0-repeated.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python + +# Test whether a retained PUBLISH to a topic with QoS 0 is actually retained +# and delivered when multiple sub/unsub operations are carried out. + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +mid = 16 +connect_packet = mosq_test.gen_connect("retain-qos0-rep-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +publish_packet = mosq_test.gen_publish("retain/qos0/test", qos=0, payload="retained message", retain=True) +subscribe_packet = mosq_test.gen_subscribe(mid, "retain/qos0/test", 0) +suback_packet = mosq_test.gen_suback(mid, 0) + +unsub_mid = 13 +unsubscribe_packet = mosq_test.gen_unsubscribe(unsub_mid, "retain/qos0/test/") +unsuback_packet = mosq_test.gen_unsuback(unsub_mid) + +broker = subprocess.Popen(['../../src/mosquitto', '-p', '1888'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet, timeout=20) + sock.send(publish_packet) + sock.send(subscribe_packet) + + if mosq_test.expect_packet(sock, "suback", suback_packet): + if mosq_test.expect_packet(sock, "publish", publish_packet): + sock.send(unsubscribe_packet) + + if mosq_test.expect_packet(sock, "unsuback", unsuback_packet): + sock.send(subscribe_packet) + + if mosq_test.expect_packet(sock, "suback", suback_packet): + if mosq_test.expect_packet(sock, "publish", publish_packet): + rc = 0 + sock.close() +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/04-retain-qos0.py b/test/broker/04-retain-qos0.py new file mode 100755 index 0000000000..6b48232854 --- /dev/null +++ b/test/broker/04-retain-qos0.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python + +# Test whether a retained PUBLISH to a topic with QoS 0 is actually retained. + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +mid = 16 +connect_packet = mosq_test.gen_connect("retain-qos0-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +publish_packet = mosq_test.gen_publish("retain/qos0/test", qos=0, payload="retained message", retain=True) +subscribe_packet = mosq_test.gen_subscribe(mid, "retain/qos0/test", 0) +suback_packet = mosq_test.gen_suback(mid, 0) + +broker = subprocess.Popen(['../../src/mosquitto', '-p', '1888'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet) + sock.send(publish_packet) + sock.send(subscribe_packet) + + if mosq_test.expect_packet(sock, "suback", suback_packet): + if mosq_test.expect_packet(sock, "publish", publish_packet): + rc = 0 + sock.close() +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/04-retain-qos1-qos0.py b/test/broker/04-retain-qos1-qos0.py new file mode 100755 index 0000000000..deb35aec14 --- /dev/null +++ b/test/broker/04-retain-qos1-qos0.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python + +# Test whether a retained PUBLISH to a topic with QoS 1 is retained. +# Subscription is made with QoS 0 so the retained message should also have QoS +# 0. + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +connect_packet = mosq_test.gen_connect("retain-qos1-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +mid = 6 +publish_packet = mosq_test.gen_publish("retain/qos1/test", qos=1, mid=mid, payload="retained message", retain=True) +puback_packet = mosq_test.gen_puback(mid) +mid = 18 +subscribe_packet = mosq_test.gen_subscribe(mid, "retain/qos1/test", 0) +suback_packet = mosq_test.gen_suback(mid, 0) +publish0_packet = mosq_test.gen_publish("retain/qos1/test", qos=0, payload="retained message", retain=True) + +broker = subprocess.Popen(['../../src/mosquitto', '-p', '1888'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet) + sock.send(publish_packet) + + if mosq_test.expect_packet(sock, "puback", puback_packet): + sock.send(subscribe_packet) + + if mosq_test.expect_packet(sock, "suback", suback_packet): + if mosq_test.expect_packet(sock, "publish0", publish0_packet): + rc = 0 + sock.close() +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/05-clean-session-qos1-helper.py b/test/broker/05-clean-session-qos1-helper.py new file mode 100755 index 0000000000..620099c64b --- /dev/null +++ b/test/broker/05-clean-session-qos1-helper.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python + +# Test whether a clean session client has a QoS 1 message queued for it. + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +connect_packet = mosq_test.gen_connect("test-helper", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +mid = 128 +publish_packet = mosq_test.gen_publish("qos1/clean_session/test", qos=1, mid=mid, payload="clean-session-message") +puback_packet = mosq_test.gen_puback(mid) + +sock = mosq_test.do_client_connect(connect_packet, connack_packet) +sock.send(publish_packet) + +if mosq_test.expect_packet(sock, "puback", puback_packet): + rc = 0 + +sock.close() + +exit(rc) + diff --git a/test/broker/05-clean-session-qos1.py b/test/broker/05-clean-session-qos1.py new file mode 100755 index 0000000000..771b22dead --- /dev/null +++ b/test/broker/05-clean-session-qos1.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python + +# Test whether a clean session client has a QoS 1 message queued for it. + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +mid = 109 +keepalive = 60 +connect_packet = mosq_test.gen_connect("clean-qos2-test", keepalive=keepalive, clean_session=False) +connack_packet = mosq_test.gen_connack(rc=0) + +disconnect_packet = mosq_test.gen_disconnect() + +subscribe_packet = mosq_test.gen_subscribe(mid, "qos1/clean_session/test", 1) +suback_packet = mosq_test.gen_suback(mid, 1) + +mid = 1 +publish_packet = mosq_test.gen_publish("qos1/clean_session/test", qos=1, mid=mid, payload="clean-session-message") +puback_packet = mosq_test.gen_puback(mid) + +broker = subprocess.Popen(['../../src/mosquitto', '-p', '1888'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet) + sock.send(subscribe_packet) + + if mosq_test.expect_packet(sock, "suback", suback_packet): + sock.send(disconnect_packet) + sock.close() + + pub = subprocess.Popen(['./05-clean-session-qos1-helper.py']) + pub.wait() + + # Now reconnect and expect a publish message. + sock = mosq_test.do_client_connect(connect_packet, connack_packet, timeout=30) + if mosq_test.expect_packet(sock, "publish", publish_packet): + sock.send(puback_packet) + rc = 0 + + sock.close() +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/06-bridge-b2br-disconnect-qos1.conf b/test/broker/06-bridge-b2br-disconnect-qos1.conf new file mode 100644 index 0000000000..7561186922 --- /dev/null +++ b/test/broker/06-bridge-b2br-disconnect-qos1.conf @@ -0,0 +1,9 @@ +port 1889 + +retry_interval 10 + +connection bridge_sample +address 127.0.0.1:1888 +topic bridge/# both 1 +notifications false +restart_timeout 5 diff --git a/test/broker/06-bridge-b2br-disconnect-qos1.py b/test/broker/06-bridge-b2br-disconnect-qos1.py new file mode 100755 index 0000000000..afa49d58c4 --- /dev/null +++ b/test/broker/06-bridge-b2br-disconnect-qos1.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python + +# Does a bridge resend a QoS=1 message correctly after a disconnect? + +import os +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +client_id = socket.gethostname()+".bridge_sample" +connect_packet = mosq_test.gen_connect(client_id, keepalive=keepalive, clean_session=False, proto_ver=128+3) +connack_packet = mosq_test.gen_connack(rc=0) + +mid = 1 +subscribe_packet = mosq_test.gen_subscribe(mid, "bridge/#", 1) +suback_packet = mosq_test.gen_suback(mid, 1) + +mid = 2 +subscribe2_packet = mosq_test.gen_subscribe(mid, "bridge/#", 1) +suback2_packet = mosq_test.gen_suback(mid, 1) + +mid = 3 +publish_packet = mosq_test.gen_publish("bridge/disconnect/test", qos=1, mid=mid, payload="disconnect-message") +publish_dup_packet = mosq_test.gen_publish("bridge/disconnect/test", qos=1, mid=mid, payload="disconnect-message", dup=True) +puback_packet = mosq_test.gen_puback(mid) + +mid = 20 +publish2_packet = mosq_test.gen_publish("bridge/disconnect/test", qos=1, mid=mid, payload="disconnect-message") +puback2_packet = mosq_test.gen_puback(mid) + +ssock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +ssock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +ssock.settimeout(40) +ssock.bind(('', 1888)) +ssock.listen(5) + +broker = subprocess.Popen(['../../src/mosquitto', '-c', '06-bridge-b2br-disconnect-qos1.conf'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + (bridge, address) = ssock.accept() + bridge.settimeout(20) + + if mosq_test.expect_packet(bridge, "connect", connect_packet): + bridge.send(connack_packet) + + if mosq_test.expect_packet(bridge, "subscribe", subscribe_packet): + bridge.send(suback_packet) + + bridge.send(publish_packet) + # Bridge doesn't have time to respond but should expect us to retry + # and so remove PUBACK. + bridge.close() + + (bridge, address) = ssock.accept() + bridge.settimeout(20) + + if mosq_test.expect_packet(bridge, "connect", connect_packet): + bridge.send(connack_packet) + + if mosq_test.expect_packet(bridge, "2nd subscribe", subscribe2_packet): + bridge.send(suback2_packet) + + # Send a different publish message to make sure the response isn't to the old one. + bridge.send(publish2_packet) + if mosq_test.expect_packet(bridge, "puback", puback2_packet): + rc = 0 + + bridge.close() +finally: + try: + bridge.close() + except NameError: + pass + + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + ssock.close() + +exit(rc) + diff --git a/test/broker/06-bridge-b2br-disconnect-qos2.conf b/test/broker/06-bridge-b2br-disconnect-qos2.conf new file mode 100644 index 0000000000..ed5fa9f522 --- /dev/null +++ b/test/broker/06-bridge-b2br-disconnect-qos2.conf @@ -0,0 +1,11 @@ +port 1889 + +retry_interval 10 + +connection bridge_sample +address 127.0.0.1:1888 +topic bridge/# both 2 +notifications false +restart_timeout 5 + +log_type debug diff --git a/test/broker/06-bridge-b2br-disconnect-qos2.py b/test/broker/06-bridge-b2br-disconnect-qos2.py new file mode 100755 index 0000000000..94e31ea5ad --- /dev/null +++ b/test/broker/06-bridge-b2br-disconnect-qos2.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python + +# Does a bridge resend a QoS=1 message correctly after a disconnect? + +import os +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +client_id = socket.gethostname()+".bridge_sample" +connect_packet = mosq_test.gen_connect(client_id, keepalive=keepalive, clean_session=False, proto_ver=128+3) +connack_packet = mosq_test.gen_connack(rc=0) + +mid = 1 +subscribe_packet = mosq_test.gen_subscribe(mid, "bridge/#", 2) +suback_packet = mosq_test.gen_suback(mid, 2) + +mid = 2 +subscribe2_packet = mosq_test.gen_subscribe(mid, "bridge/#", 2) +suback2_packet = mosq_test.gen_suback(mid, 2) + +mid = 3 +subscribe3_packet = mosq_test.gen_subscribe(mid, "bridge/#", 2) +suback3_packet = mosq_test.gen_suback(mid, 2) + +mid = 5 +publish_packet = mosq_test.gen_publish("bridge/disconnect/test", qos=2, mid=mid, payload="disconnect-message") +publish_dup_packet = mosq_test.gen_publish("bridge/disconnect/test", qos=2, mid=mid, payload="disconnect-message", dup=True) +pubrec_packet = mosq_test.gen_pubrec(mid) +pubrel_packet = mosq_test.gen_pubrel(mid) +pubcomp_packet = mosq_test.gen_pubcomp(mid) + +ssock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +ssock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +ssock.settimeout(40) +ssock.bind(('', 1888)) +ssock.listen(5) + +broker = subprocess.Popen(['../../src/mosquitto', '-c', '06-bridge-b2br-disconnect-qos2.conf'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + (bridge, address) = ssock.accept() + bridge.settimeout(20) + + if mosq_test.expect_packet(bridge, "connect", connect_packet): + bridge.send(connack_packet) + + if mosq_test.expect_packet(bridge, "subscribe", subscribe_packet): + bridge.send(suback_packet) + + bridge.send(publish_packet) + bridge.close() + + (bridge, address) = ssock.accept() + bridge.settimeout(20) + + if mosq_test.expect_packet(bridge, "connect", connect_packet): + bridge.send(connack_packet) + + if mosq_test.expect_packet(bridge, "2nd subscribe", subscribe2_packet): + bridge.send(suback2_packet) + bridge.send(publish_dup_packet) + + if mosq_test.expect_packet(bridge, "pubrec", pubrec_packet): + bridge.send(pubrel_packet) + bridge.close() + + (bridge, address) = ssock.accept() + bridge.settimeout(20) + + if mosq_test.expect_packet(bridge, "connect", connect_packet): + bridge.send(connack_packet) + + if mosq_test.expect_packet(bridge, "3rd subscribe", subscribe3_packet): + bridge.send(suback3_packet) + + bridge.send(publish_dup_packet) + + if mosq_test.expect_packet(bridge, "2nd pubrec", pubrec_packet): + bridge.send(pubrel_packet) + + if mosq_test.expect_packet(bridge, "pubcomp", pubcomp_packet): + rc = 0 + + bridge.close() +finally: + try: + bridge.close() + except NameError: + pass + + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + ssock.close() + +exit(rc) + diff --git a/test/broker/06-bridge-br2b-disconnect-qos1-helper.py b/test/broker/06-bridge-br2b-disconnect-qos1-helper.py new file mode 100755 index 0000000000..06c67450ba --- /dev/null +++ b/test/broker/06-bridge-br2b-disconnect-qos1-helper.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +connect_packet = mosq_test.gen_connect("test-helper", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +mid = 128 +publish_packet = mosq_test.gen_publish("bridge/disconnect/test", qos=1, mid=mid, payload="disconnect-message") +puback_packet = mosq_test.gen_puback(mid) + +sock = mosq_test.do_client_connect(connect_packet, connack_packet, port=1889, connack_error="helper connack") +sock.send(publish_packet) + +if mosq_test.expect_packet(sock, "helper puback", puback_packet): + rc = 0 + +sock.close() + +exit(rc) + diff --git a/test/broker/06-bridge-br2b-disconnect-qos1.conf b/test/broker/06-bridge-br2b-disconnect-qos1.conf new file mode 100644 index 0000000000..dccb952717 --- /dev/null +++ b/test/broker/06-bridge-br2b-disconnect-qos1.conf @@ -0,0 +1,10 @@ +port 1889 + +retry_interval 10 + +connection bridge_sample +address 127.0.0.1:1888 +topic bridge/# both 1 +notifications false +restart_timeout 5 +try_private true diff --git a/test/broker/06-bridge-br2b-disconnect-qos1.py b/test/broker/06-bridge-br2b-disconnect-qos1.py new file mode 100755 index 0000000000..8a21e10c40 --- /dev/null +++ b/test/broker/06-bridge-br2b-disconnect-qos1.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python + +# Does a bridge resend a QoS=1 message correctly after a disconnect? + +import os +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +client_id = socket.gethostname()+".bridge_sample" +connect_packet = mosq_test.gen_connect(client_id, keepalive=keepalive, clean_session=False, proto_ver=128+3) +connack_packet = mosq_test.gen_connack(rc=0) + +mid = 1 +subscribe_packet = mosq_test.gen_subscribe(mid, "bridge/#", 1) +suback_packet = mosq_test.gen_suback(mid, 1) + +mid = 3 +subscribe2_packet = mosq_test.gen_subscribe(mid, "bridge/#", 1) +suback2_packet = mosq_test.gen_suback(mid, 1) + +mid = 2 +publish_packet = mosq_test.gen_publish("bridge/disconnect/test", qos=1, mid=mid, payload="disconnect-message") +publish_dup_packet = mosq_test.gen_publish("bridge/disconnect/test", qos=1, mid=mid, payload="disconnect-message", dup=True) +puback_packet = mosq_test.gen_puback(mid) + +ssock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +ssock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +ssock.settimeout(40) +ssock.bind(('', 1888)) +ssock.listen(5) + +broker = subprocess.Popen(['../../src/mosquitto', '-c', '06-bridge-br2b-disconnect-qos1.conf'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + (bridge, address) = ssock.accept() + bridge.settimeout(20) + + if mosq_test.expect_packet(bridge, "connect", connect_packet): + bridge.send(connack_packet) + + if mosq_test.expect_packet(bridge, "subscribe", subscribe_packet): + bridge.send(suback_packet) + + pub = subprocess.Popen(['./06-bridge-br2b-disconnect-qos1-helper.py']) + if pub.wait(): + exit(1) + + if mosq_test.expect_packet(bridge, "publish", publish_packet): + bridge.close() + + (bridge, address) = ssock.accept() + bridge.settimeout(20) + + if mosq_test.expect_packet(bridge, "2nd connect", connect_packet): + bridge.send(connack_packet) + + if mosq_test.expect_packet(bridge, "2nd subscribe", subscribe2_packet): + bridge.send(suback2_packet) + + if mosq_test.expect_packet(bridge, "2nd publish", publish_dup_packet): + rc = 0 + + bridge.close() +finally: + try: + bridge.close() + except NameError: + pass + + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + ssock.close() + +exit(rc) + diff --git a/test/broker/06-bridge-br2b-disconnect-qos2-helper.py b/test/broker/06-bridge-br2b-disconnect-qos2-helper.py new file mode 100755 index 0000000000..7515e5a2c8 --- /dev/null +++ b/test/broker/06-bridge-br2b-disconnect-qos2-helper.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +connect_packet = mosq_test.gen_connect("test-helper", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +mid = 312 +publish_packet = mosq_test.gen_publish("bridge/disconnect/test", qos=2, mid=mid, payload="disconnect-message") +pubrec_packet = mosq_test.gen_pubrec(mid) +pubrel_packet = mosq_test.gen_pubrel(mid) +pubcomp_packet = mosq_test.gen_pubcomp(mid) + +sock = mosq_test.do_client_connect(connect_packet, connack_packet, port=1889, connack_error="helper connack") +sock.send(publish_packet) + +if mosq_test.expect_packet(sock, "helper pubrec", pubrec_packet): + sock.send(pubrel_packet) + + if mosq_test.expect_packet(sock, "helper pubcomp", pubcomp_packet): + rc = 0 + +sock.close() + +exit(rc) + diff --git a/test/broker/06-bridge-br2b-disconnect-qos2.conf b/test/broker/06-bridge-br2b-disconnect-qos2.conf new file mode 100644 index 0000000000..ed5fa9f522 --- /dev/null +++ b/test/broker/06-bridge-br2b-disconnect-qos2.conf @@ -0,0 +1,11 @@ +port 1889 + +retry_interval 10 + +connection bridge_sample +address 127.0.0.1:1888 +topic bridge/# both 2 +notifications false +restart_timeout 5 + +log_type debug diff --git a/test/broker/06-bridge-br2b-disconnect-qos2.py b/test/broker/06-bridge-br2b-disconnect-qos2.py new file mode 100755 index 0000000000..e5bb91568e --- /dev/null +++ b/test/broker/06-bridge-br2b-disconnect-qos2.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python + +# Does a bridge resend a QoS=1 message correctly after a disconnect? + +import os +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +client_id = socket.gethostname()+".bridge_sample" +connect_packet = mosq_test.gen_connect(client_id, keepalive=keepalive, clean_session=False, proto_ver=128+3) +connack_packet = mosq_test.gen_connack(rc=0) + +mid = 1 +subscribe_packet = mosq_test.gen_subscribe(mid, "bridge/#", 2) +suback_packet = mosq_test.gen_suback(mid, 2) + +mid = 3 +subscribe2_packet = mosq_test.gen_subscribe(mid, "bridge/#", 2) +suback2_packet = mosq_test.gen_suback(mid, 2) + +mid = 4 +subscribe3_packet = mosq_test.gen_subscribe(mid, "bridge/#", 2) +suback3_packet = mosq_test.gen_suback(mid, 2) + +mid = 2 +publish_packet = mosq_test.gen_publish("bridge/disconnect/test", qos=2, mid=mid, payload="disconnect-message") +publish_dup_packet = mosq_test.gen_publish("bridge/disconnect/test", qos=2, mid=mid, payload="disconnect-message", dup=True) +pubrec_packet = mosq_test.gen_pubrec(mid) +pubrel_packet = mosq_test.gen_pubrel(mid) +pubrel_dup_packet = mosq_test.gen_pubrel(mid, True) +pubcomp_packet = mosq_test.gen_pubcomp(mid) + +ssock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +ssock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +ssock.settimeout(40) +ssock.bind(('', 1888)) +ssock.listen(5) + +broker = subprocess.Popen(['../../src/mosquitto', '-c', '06-bridge-br2b-disconnect-qos2.conf'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + (bridge, address) = ssock.accept() + bridge.settimeout(20) + + if mosq_test.expect_packet(bridge, "connect", connect_packet): + bridge.send(connack_packet) + + if mosq_test.expect_packet(bridge, "subscribe", subscribe_packet): + bridge.send(suback_packet) + + pub = subprocess.Popen(['./06-bridge-br2b-disconnect-qos2-helper.py']) + if pub.wait(): + exit(1) + + if mosq_test.expect_packet(bridge, "publish", publish_packet): + bridge.close() + + (bridge, address) = ssock.accept() + bridge.settimeout(20) + + if mosq_test.expect_packet(bridge, "connect", connect_packet): + bridge.send(connack_packet) + + if mosq_test.expect_packet(bridge, "2nd subscribe", subscribe2_packet): + bridge.send(suback2_packet) + + if mosq_test.expect_packet(bridge, "2nd publish", publish_dup_packet): + bridge.send(pubrec_packet) + + if mosq_test.expect_packet(bridge, "pubrel", pubrel_packet): + bridge.close() + + (bridge, address) = ssock.accept() + bridge.settimeout(20) + + if mosq_test.expect_packet(bridge, "connect", connect_packet): + bridge.send(connack_packet) + + if mosq_test.expect_packet(bridge, "3rd subscribe", subscribe3_packet): + bridge.send(suback3_packet) + + if mosq_test.expect_packet(bridge, "2nd pubrel", pubrel_dup_packet): + bridge.send(pubcomp_packet) + rc = 0 + + bridge.close() +finally: + try: + bridge.close() + except NameError: + pass + + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + ssock.close() + +exit(rc) + diff --git a/test/broker/06-bridge-reconnect-local-out-helper.py b/test/broker/06-bridge-reconnect-local-out-helper.py new file mode 100755 index 0000000000..7222acd708 --- /dev/null +++ b/test/broker/06-bridge-reconnect-local-out-helper.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +connect_packet = mosq_test.gen_connect("test-helper", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +publish_packet = mosq_test.gen_publish("bridge/reconnect", qos=1, mid=1, payload="bridge-reconnect-message") +puback_packet = mosq_test.gen_puback(mid=1) + +disconnect_packet = mosq_test.gen_disconnect() + +sock = mosq_test.do_client_connect(connect_packet, connack_packet, port=1889, connack_error="helper connack") +sock.send(publish_packet) + +if mosq_test.expect_packet(sock, "puback", puback_packet): + sock.send(disconnect_packet) + rc = 0 + +sock.close() + +exit(rc) + diff --git a/test/broker/06-bridge-reconnect-local-out.conf b/test/broker/06-bridge-reconnect-local-out.conf new file mode 100644 index 0000000000..6f3bc6e436 --- /dev/null +++ b/test/broker/06-bridge-reconnect-local-out.conf @@ -0,0 +1,9 @@ +port 1889 + +persistence true + +retry_interval 10 + +connection bridge_sample +address 127.0.0.1:1888 +topic bridge/# out diff --git a/test/broker/06-bridge-reconnect-local-out.py b/test/broker/06-bridge-reconnect-local-out.py new file mode 100755 index 0000000000..8383636a7a --- /dev/null +++ b/test/broker/06-bridge-reconnect-local-out.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python + +# Test whether a bridge topics work correctly after reconnection. +# Important point here is that persistence is enabled. + +import os +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +connect_packet = mosq_test.gen_connect("bridge-reconnect-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +mid = 180 +subscribe_packet = mosq_test.gen_subscribe(mid, "bridge/#", 0) +suback_packet = mosq_test.gen_suback(mid, 0) +publish_packet = mosq_test.gen_publish("bridge/reconnect", qos=0, payload="bridge-reconnect-message") + +try: + os.remove('mosquitto.db') +except OSError: + pass + +broker = subprocess.Popen(['../../src/mosquitto', '-p', '1888'], stderr=subprocess.PIPE) +time.sleep(0.5) +local_broker = subprocess.Popen(['../../src/mosquitto', '-c', '06-bridge-reconnect-local-out.conf'], stderr=subprocess.PIPE) +time.sleep(0.5) +local_broker.terminate() +local_broker.wait() +local_broker = subprocess.Popen(['../../src/mosquitto', '-c', '06-bridge-reconnect-local-out.conf'], stderr=subprocess.PIPE) + +pub = None +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet) + sock.send(subscribe_packet) + + if mosq_test.expect_packet(sock, "suback", suback_packet): + sock.send(subscribe_packet) + + if mosq_test.expect_packet(sock, "suback", suback_packet): + pub = subprocess.Popen(['./06-bridge-reconnect-local-out-helper.py'], stdout=subprocess.PIPE) + pub.wait() + # Should have now received a publish command + + if mosq_test.expect_packet(sock, "publish", publish_packet): + rc = 0 + sock.close() +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + local_broker.terminate() + local_broker.wait() + if rc: + (stdo, stde) = local_broker.communicate() + print(stde) + if pub: + (stdo, stde) = pub.communicate() + print(stdo) + + try: + os.remove('mosquitto.db') + except OSError: + pass + +exit(rc) + diff --git a/test/broker/07-will-acl-denied.acl b/test/broker/07-will-acl-denied.acl new file mode 100644 index 0000000000..06f0bc8704 --- /dev/null +++ b/test/broker/07-will-acl-denied.acl @@ -0,0 +1,2 @@ +topic read will/acl/test +topic write ok diff --git a/test/broker/07-will-acl-denied.conf b/test/broker/07-will-acl-denied.conf new file mode 100644 index 0000000000..41301be98f --- /dev/null +++ b/test/broker/07-will-acl-denied.conf @@ -0,0 +1,2 @@ +port 1888 +acl_file 07-will-acl-denied.acl diff --git a/test/broker/07-will-acl-denied.py b/test/broker/07-will-acl-denied.py new file mode 100755 index 0000000000..fe7d3c2ca1 --- /dev/null +++ b/test/broker/07-will-acl-denied.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +mid = 53 +keepalive = 60 +connect_packet_ok = mosq_test.gen_connect("will-acl-test", keepalive=keepalive, will_topic="ok", will_payload="should be ok") +connack_packet_ok = mosq_test.gen_connack(rc=0) + +connect_packet = mosq_test.gen_connect("will-acl-test", keepalive=keepalive, will_topic="will/acl/test", will_payload="should be denied") +connack_packet = mosq_test.gen_connack(rc=5) + +broker = subprocess.Popen(['../../src/mosquitto', '-c', '07-will-acl-denied.conf'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock_ok = mosq_test.do_client_connect(connect_packet_ok, connack_packet_ok, timeout=5, connack_error="connack ok") + sock = mosq_test.do_client_connect(connect_packet, connack_packet) + rc = 0 + sock.close() + sock_ok.close() +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/07-will-null-helper.py b/test/broker/07-will-null-helper.py new file mode 100755 index 0000000000..458ef58969 --- /dev/null +++ b/test/broker/07-will-null-helper.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python + +# Connect a client with a will, then disconnect without DISCONNECT. + +import struct +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +connect_packet = mosq_test.gen_connect("test-helper", keepalive=keepalive, will_topic="will/null/test") +connack_packet = mosq_test.gen_connack(rc=0) + +sock = mosq_test.do_client_connect(connect_packet, connack_packet) +rc = 0 +sock.close() + +exit(rc) + diff --git a/test/broker/07-will-null-topic.py b/test/broker/07-will-null-topic.py new file mode 100755 index 0000000000..b6d2fbb953 --- /dev/null +++ b/test/broker/07-will-null-topic.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python + +import struct +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +connect_packet = mosq_test.gen_connect("will-null-topic", keepalive=keepalive, will_topic="", will_payload=struct.pack("!4sB7s", "will", 0, "message")) +connack_packet = mosq_test.gen_connack(rc=2) + +broker = subprocess.Popen(['../../src/mosquitto', '-p', '1888'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, "", timeout=30) + rc = 0 + sock.close() +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/07-will-null.py b/test/broker/07-will-null.py new file mode 100755 index 0000000000..fff733c6db --- /dev/null +++ b/test/broker/07-will-null.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python + +# Test whether a client will is transmitted correctly with a null character in the middle. + +import struct +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +mid = 53 +keepalive = 60 +connect_packet = mosq_test.gen_connect("will-qos0-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +subscribe_packet = mosq_test.gen_subscribe(mid, "will/null/test", 0) +suback_packet = mosq_test.gen_suback(mid, 0) + +publish_packet = mosq_test.gen_publish("will/null/test", qos=0) + +broker = subprocess.Popen(['../../src/mosquitto', '-p', '1888'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet, timeout=30) + sock.send(subscribe_packet) + + if mosq_test.expect_packet(sock, "suback", suback_packet): + will = subprocess.Popen(['./07-will-null-helper.py']) + will.wait() + + if mosq_test.expect_packet(sock, "publish", publish_packet): + rc = 0 + + sock.close() +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/07-will-qos0-helper.py b/test/broker/07-will-qos0-helper.py new file mode 100755 index 0000000000..a112f0a87b --- /dev/null +++ b/test/broker/07-will-qos0-helper.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python + +# Connect a client with a will, then disconnect without DISCONNECT. + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +connect_packet = mosq_test.gen_connect("test-helper", keepalive=keepalive, will_topic="will/qos0/test", will_payload="will-message") +connack_packet = mosq_test.gen_connack(rc=0) + +sock = mosq_test.do_client_connect(connect_packet, connack_packet) +rc = 0 +sock.close() + +exit(rc) + diff --git a/test/broker/07-will-qos0.py b/test/broker/07-will-qos0.py new file mode 100755 index 0000000000..552450b807 --- /dev/null +++ b/test/broker/07-will-qos0.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python + +# Test whether a client will is transmitted correctly. + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +mid = 53 +keepalive = 60 +connect_packet = mosq_test.gen_connect("will-qos0-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +subscribe_packet = mosq_test.gen_subscribe(mid, "will/qos0/test", 0) +suback_packet = mosq_test.gen_suback(mid, 0) + +publish_packet = mosq_test.gen_publish("will/qos0/test", qos=0, payload="will-message") + +broker = subprocess.Popen(['../../src/mosquitto', '-p', '1888'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet, timeout=30) + sock.send(subscribe_packet) + + if mosq_test.expect_packet(sock, "suback", suback_packet): + will = subprocess.Popen(['./07-will-qos0-helper.py']) + will.wait() + + if mosq_test.expect_packet(sock, "publish", publish_packet): + rc = 0 + + sock.close() +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/08-ssl-bridge-helper.py b/test/broker/08-ssl-bridge-helper.py new file mode 100755 index 0000000000..9d68101f47 --- /dev/null +++ b/test/broker/08-ssl-bridge-helper.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +connect_packet = mosq_test.gen_connect("test-helper", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +publish_packet = mosq_test.gen_publish("bridge/ssl/test", qos=0, payload="message") + +disconnect_packet = mosq_test.gen_disconnect() + +sock = mosq_test.do_client_connect(connect_packet, connack_packet, port=1889, connack_error="helper connack") +sock.send(publish_packet) +sock.send(disconnect_packet) +sock.close() + +exit(0) diff --git a/test/broker/08-ssl-bridge.conf b/test/broker/08-ssl-bridge.conf new file mode 100644 index 0000000000..fc2c17b550 --- /dev/null +++ b/test/broker/08-ssl-bridge.conf @@ -0,0 +1,12 @@ +port 1889 + +connection bridge_test +address localhost:1888 +topic bridge/# both 0 +notifications false + +#bridge_cafile ../ssl/test-root-ca.crt +bridge_cafile ../ssl/all-ca.crt +bridge_insecure true + +bridge_tls_version tlsv1 diff --git a/test/broker/08-ssl-bridge.py b/test/broker/08-ssl-bridge.py new file mode 100755 index 0000000000..fc84be682e --- /dev/null +++ b/test/broker/08-ssl-bridge.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python + +import os +import subprocess +import socket +import ssl +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +client_id = socket.gethostname()+".bridge_test" +connect_packet = mosq_test.gen_connect(client_id, keepalive=keepalive, clean_session=False, proto_ver=128+3) +connack_packet = mosq_test.gen_connack(rc=0) + +mid = 1 +subscribe_packet = mosq_test.gen_subscribe(mid, "bridge/#", 0) +suback_packet = mosq_test.gen_suback(mid, 0) + +publish_packet = mosq_test.gen_publish("bridge/ssl/test", qos=0, payload="message") + +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +ssock = ssl.wrap_socket(sock, ca_certs="../ssl/all-ca.crt", keyfile="../ssl/server.key", certfile="../ssl/server.crt", server_side=True, ssl_version=ssl.PROTOCOL_TLSv1) +ssock.settimeout(20) +ssock.bind(('', 1888)) +ssock.listen(5) + +broker = subprocess.Popen(['../../src/mosquitto', '-v', '-c', '08-ssl-bridge.conf'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + (bridge, address) = ssock.accept() + bridge.settimeout(20) + + if mosq_test.expect_packet(bridge, "connect", connect_packet): + bridge.send(connack_packet) + + if mosq_test.expect_packet(bridge, "subscribe", subscribe_packet): + bridge.send(suback_packet) + + pub = subprocess.Popen(['./08-ssl-bridge-helper.py'], stdout=subprocess.PIPE) + pub.wait() + + if mosq_test.expect_packet(bridge, "publish", publish_packet): + rc = 0 + + bridge.close() +finally: + try: + bridge.close() + except NameError: + pass + + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + ssock.close() + +exit(rc) + diff --git a/test/broker/08-ssl-connect-cert-auth-crl.conf b/test/broker/08-ssl-connect-cert-auth-crl.conf new file mode 100644 index 0000000000..fdac064a12 --- /dev/null +++ b/test/broker/08-ssl-connect-cert-auth-crl.conf @@ -0,0 +1,9 @@ +port 1888 + +cafile ../ssl/all-ca.crt +certfile ../ssl/server.crt +keyfile ../ssl/server.key +require_certificate true +crlfile ../ssl/crl.pem + +tls_version tlsv1 diff --git a/test/broker/08-ssl-connect-cert-auth-crl.py b/test/broker/08-ssl-connect-cert-auth-crl.py new file mode 100755 index 0000000000..fd65c442c4 --- /dev/null +++ b/test/broker/08-ssl-connect-cert-auth-crl.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python + +import subprocess +import socket +import ssl +import sys +import time + +if sys.version < '2.7': + print("WARNING: SSL not supported on Python 2.6") + exit(0) + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 10 +connect_packet = mosq_test.gen_connect("connect-success-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +broker = subprocess.Popen(['../../src/mosquitto', '-c', '08-ssl-connect-cert-auth-crl.conf'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + ssock = ssl.wrap_socket(sock, ca_certs="../ssl/test-root-ca.crt", certfile="../ssl/client.crt", keyfile="../ssl/client.key", cert_reqs=ssl.CERT_REQUIRED) + ssock.settimeout(20) + ssock.connect(("localhost", 1888)) + ssock.send(connect_packet) + + if mosq_test.expect_packet(ssock, "connack", connack_packet): + rc = 0 + + ssock.close() +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/08-ssl-connect-cert-auth-expired.conf b/test/broker/08-ssl-connect-cert-auth-expired.conf new file mode 100644 index 0000000000..5cb0664c71 --- /dev/null +++ b/test/broker/08-ssl-connect-cert-auth-expired.conf @@ -0,0 +1,8 @@ +port 1888 + +cafile ../ssl/all-ca.crt +certfile ../ssl/server.crt +keyfile ../ssl/server.key +require_certificate true + +tls_version tlsv1 diff --git a/test/broker/08-ssl-connect-cert-auth-expired.py b/test/broker/08-ssl-connect-cert-auth-expired.py new file mode 100755 index 0000000000..595079210d --- /dev/null +++ b/test/broker/08-ssl-connect-cert-auth-expired.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python + +# Test whether a valid CONNECT results in the correct CONNACK packet using an +# SSL connection with client certificates required. + +import subprocess +import socket +import ssl +import sys +import time + +if sys.version < '2.7': + print("WARNING: SSL not supported on Python 2.6") + exit(0) + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 10 +connect_packet = mosq_test.gen_connect("connect-success-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +broker = subprocess.Popen(['../../src/mosquitto', '-v', '-c', '08-ssl-connect-cert-auth.conf'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + ssock = ssl.wrap_socket(sock, ca_certs="../ssl/test-root-ca.crt", certfile="../ssl/client-expired.crt", keyfile="../ssl/client.key", cert_reqs=ssl.CERT_REQUIRED) + ssock.settimeout(20) + try: + ssock.connect(("localhost", 1888)) + except ssl.SSLError as err: + if err.errno == 1: + rc = 0 + else: + broker.terminate() + raise ValueError(err.errno) +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/08-ssl-connect-cert-auth-revoked.conf b/test/broker/08-ssl-connect-cert-auth-revoked.conf new file mode 100644 index 0000000000..fdac064a12 --- /dev/null +++ b/test/broker/08-ssl-connect-cert-auth-revoked.conf @@ -0,0 +1,9 @@ +port 1888 + +cafile ../ssl/all-ca.crt +certfile ../ssl/server.crt +keyfile ../ssl/server.key +require_certificate true +crlfile ../ssl/crl.pem + +tls_version tlsv1 diff --git a/test/broker/08-ssl-connect-cert-auth-revoked.py b/test/broker/08-ssl-connect-cert-auth-revoked.py new file mode 100755 index 0000000000..b712171cd7 --- /dev/null +++ b/test/broker/08-ssl-connect-cert-auth-revoked.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python + +import subprocess +import socket +import ssl +import sys +import time + +if sys.version < '2.7': + print("WARNING: SSL not supported on Python 2.6") + exit(0) + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 10 +connect_packet = mosq_test.gen_connect("connect-revoked-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +broker = subprocess.Popen(['../../src/mosquitto', '-c', '08-ssl-connect-cert-auth-revoked.conf'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + ssock = ssl.wrap_socket(sock, ca_certs="../ssl/test-root-ca.crt", certfile="../ssl/client-revoked.crt", keyfile="../ssl/client-revoked.key", cert_reqs=ssl.CERT_REQUIRED) + ssock.settimeout(20) + try: + ssock.connect(("localhost", 1888)) + except ssl.SSLError as err: + if err.errno == 1 and "certificate revoked" in err.strerror: + rc = 0 + else: + broker.terminate() + print(err.strerror) + raise ValueError(err.errno) + +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/08-ssl-connect-cert-auth-without.conf b/test/broker/08-ssl-connect-cert-auth-without.conf new file mode 100644 index 0000000000..5cb0664c71 --- /dev/null +++ b/test/broker/08-ssl-connect-cert-auth-without.conf @@ -0,0 +1,8 @@ +port 1888 + +cafile ../ssl/all-ca.crt +certfile ../ssl/server.crt +keyfile ../ssl/server.key +require_certificate true + +tls_version tlsv1 diff --git a/test/broker/08-ssl-connect-cert-auth-without.py b/test/broker/08-ssl-connect-cert-auth-without.py new file mode 100755 index 0000000000..5eda97ce5b --- /dev/null +++ b/test/broker/08-ssl-connect-cert-auth-without.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python + +# Test whether a client can connect without an SSL certificate if one is required. +# + +import errno +import subprocess +import socket +import ssl +import sys +import time + +if sys.version < '2.7': + print("WARNING: SSL not supported on Python 2.6") + exit(0) + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 10 +connect_packet = mosq_test.gen_connect("connect-cert-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +broker = subprocess.Popen(['../../src/mosquitto', '-c', '08-ssl-connect-cert-auth-without.conf'], stderr=subprocess.PIPE) + +time.sleep(0.5) + +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +ssock = ssl.wrap_socket(sock, ca_certs="../ssl/test-root-ca.crt", cert_reqs=ssl.CERT_REQUIRED) +ssock.settimeout(20) +try: + ssock.connect(("localhost", 1888)) +except ssl.SSLError as err: + if err.errno == 1: + rc = 0 +except socket.error as err: + if err.errno == errno.ECONNRESET: + rc = 0 + +broker.terminate() +broker.wait() +if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/08-ssl-connect-cert-auth.conf b/test/broker/08-ssl-connect-cert-auth.conf new file mode 100644 index 0000000000..5cb0664c71 --- /dev/null +++ b/test/broker/08-ssl-connect-cert-auth.conf @@ -0,0 +1,8 @@ +port 1888 + +cafile ../ssl/all-ca.crt +certfile ../ssl/server.crt +keyfile ../ssl/server.key +require_certificate true + +tls_version tlsv1 diff --git a/test/broker/08-ssl-connect-cert-auth.py b/test/broker/08-ssl-connect-cert-auth.py new file mode 100755 index 0000000000..d06a8d5203 --- /dev/null +++ b/test/broker/08-ssl-connect-cert-auth.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python + +# Test whether a valid CONNECT results in the correct CONNACK packet using an SSL connection. + +import subprocess +import socket +import ssl +import sys +import time + +if sys.version < '2.7': + print("WARNING: SSL not supported on Python 2.6") + exit(0) + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 10 +connect_packet = mosq_test.gen_connect("connect-success-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +broker = subprocess.Popen(['../../src/mosquitto', '-c', '08-ssl-connect-cert-auth.conf'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + ssock = ssl.wrap_socket(sock, ca_certs="../ssl/test-root-ca.crt", certfile="../ssl/client.crt", keyfile="../ssl/client.key", cert_reqs=ssl.CERT_REQUIRED) + ssock.settimeout(20) + ssock.connect(("localhost", 1888)) + ssock.send(connect_packet) + + if mosq_test.expect_packet(ssock, "connack", connack_packet): + rc = 0 + + ssock.close() +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/08-ssl-connect-identity.conf b/test/broker/08-ssl-connect-identity.conf new file mode 100644 index 0000000000..2b2640a818 --- /dev/null +++ b/test/broker/08-ssl-connect-identity.conf @@ -0,0 +1,9 @@ +port 1888 + +cafile ../ssl/all-ca.crt +certfile ../ssl/server.crt +keyfile ../ssl/server.key + +use_identity_as_username true + +tls_version tlsv1 diff --git a/test/broker/08-ssl-connect-identity.py b/test/broker/08-ssl-connect-identity.py new file mode 100755 index 0000000000..560e675296 --- /dev/null +++ b/test/broker/08-ssl-connect-identity.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python + +# Client connects with a certificate to a server that has use_identity_as_username=true. Shouldn't be rejected. +import subprocess +import socket +import ssl +import sys +import time + +if sys.version < '2.7': + print("WARNING: SSL not supported on Python 2.6") + exit(0) + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 10 +connect_packet = mosq_test.gen_connect("connect-identity-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +broker = subprocess.Popen(['../../src/mosquitto', '-c', '08-ssl-connect-identity.conf'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + ssock = ssl.wrap_socket(sock, ca_certs="../ssl/test-root-ca.crt", certfile="../ssl/client.crt", keyfile="../ssl/client.key", cert_reqs=ssl.CERT_REQUIRED) + ssock.settimeout(20) + ssock.connect(("localhost", 1888)) + ssock.send(connect_packet) + + if mosq_test.expect_packet(ssock, "connack", connack_packet): + rc = 0 + + ssock.close() +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/08-ssl-connect-no-auth-wrong-ca.conf b/test/broker/08-ssl-connect-no-auth-wrong-ca.conf new file mode 100644 index 0000000000..14513d4320 --- /dev/null +++ b/test/broker/08-ssl-connect-no-auth-wrong-ca.conf @@ -0,0 +1,7 @@ +port 1888 + +cafile ../ssl/all-ca.crt +certfile ../ssl/server.crt +keyfile ../ssl/server.key + +tls_version tlsv1 diff --git a/test/broker/08-ssl-connect-no-auth-wrong-ca.py b/test/broker/08-ssl-connect-no-auth-wrong-ca.py new file mode 100755 index 0000000000..978e144ef4 --- /dev/null +++ b/test/broker/08-ssl-connect-no-auth-wrong-ca.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python + +# Test whether a valid CONNECT results in the correct CONNACK packet using an SSL connection. + +import subprocess +import socket +import ssl +import sys +import time + +if sys.version < '2.7': + print("WARNING: SSL not supported on Python 2.6") + exit(0) + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 10 +connect_packet = mosq_test.gen_connect("connect-success-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +broker = subprocess.Popen(['../../src/mosquitto', '-c', '08-ssl-connect-no-auth-wrong-ca.conf'], stderr=subprocess.PIPE) + +time.sleep(0.5) + +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +ssock = ssl.wrap_socket(sock, ca_certs="../ssl/test-alt-ca.crt", cert_reqs=ssl.CERT_REQUIRED) +ssock.settimeout(20) +try: + ssock.connect(("localhost", 1888)) +except ssl.SSLError as err: + if err.errno == 1: + rc = 0 +finally: + ssock.close() + +broker.terminate() +broker.wait +if rc: + (stdo, stde) = broker.communicate() + print(stde) +exit(rc) + diff --git a/test/broker/08-ssl-connect-no-auth.conf b/test/broker/08-ssl-connect-no-auth.conf new file mode 100644 index 0000000000..14513d4320 --- /dev/null +++ b/test/broker/08-ssl-connect-no-auth.conf @@ -0,0 +1,7 @@ +port 1888 + +cafile ../ssl/all-ca.crt +certfile ../ssl/server.crt +keyfile ../ssl/server.key + +tls_version tlsv1 diff --git a/test/broker/08-ssl-connect-no-auth.py b/test/broker/08-ssl-connect-no-auth.py new file mode 100755 index 0000000000..611766d4f5 --- /dev/null +++ b/test/broker/08-ssl-connect-no-auth.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python + +# Test whether a valid CONNECT results in the correct CONNACK packet using an SSL connection. + +import subprocess +import socket +import ssl +import sys +import time + +if sys.version < '2.7': + print("WARNING: SSL not supported on Python 2.6") + exit(0) + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 10 +connect_packet = mosq_test.gen_connect("connect-success-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +broker = subprocess.Popen(['../../src/mosquitto', '-c', '08-ssl-connect-no-auth.conf'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + ssock = ssl.wrap_socket(sock, ca_certs="../ssl/test-root-ca.crt", cert_reqs=ssl.CERT_REQUIRED, ssl_version=ssl.PROTOCOL_TLSv1) + ssock.settimeout(20) + ssock.connect(("localhost", 1888)) + ssock.send(connect_packet) + + if mosq_test.expect_packet(ssock, "connack", connack_packet): + rc = 0 + + ssock.close() +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/08-ssl-connect-no-identity.conf b/test/broker/08-ssl-connect-no-identity.conf new file mode 100644 index 0000000000..2b2640a818 --- /dev/null +++ b/test/broker/08-ssl-connect-no-identity.conf @@ -0,0 +1,9 @@ +port 1888 + +cafile ../ssl/all-ca.crt +certfile ../ssl/server.crt +keyfile ../ssl/server.key + +use_identity_as_username true + +tls_version tlsv1 diff --git a/test/broker/08-ssl-connect-no-identity.py b/test/broker/08-ssl-connect-no-identity.py new file mode 100755 index 0000000000..7c76b71330 --- /dev/null +++ b/test/broker/08-ssl-connect-no-identity.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python + +# Client connects without a certificate to a server that has use_identity_as_username=true. Should be rejected. +import subprocess +import socket +import ssl +import sys +import time + +if sys.version < '2.7': + print("WARNING: SSL not supported on Python 2.6") + exit(0) + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 10 +connect_packet = mosq_test.gen_connect("connect-no-identity-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=4) + +broker = subprocess.Popen(['../../src/mosquitto', '-c', '08-ssl-connect-no-identity.conf'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + ssock = ssl.wrap_socket(sock, ca_certs="../ssl/test-root-ca.crt", cert_reqs=ssl.CERT_REQUIRED) + ssock.settimeout(20) + ssock.connect(("localhost", 1888)) + ssock.send(connect_packet) + + if mosq_test.expect_packet(ssock, "connack", connack_packet): + rc = 0 + + ssock.close() +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/08-tls-psk-bridge.conf b/test/broker/08-tls-psk-bridge.conf new file mode 100644 index 0000000000..1a63d24d80 --- /dev/null +++ b/test/broker/08-tls-psk-bridge.conf @@ -0,0 +1,14 @@ +psk_file 08-tls-psk-bridge.psk + +port 1888 + +listener 1889 +psk_hint hint + +log_type debug +log_type error +log_type warning +log_type information +log_type notice + +tls_version tlsv1 diff --git a/test/broker/08-tls-psk-bridge.conf2 b/test/broker/08-tls-psk-bridge.conf2 new file mode 100644 index 0000000000..b28214258d --- /dev/null +++ b/test/broker/08-tls-psk-bridge.conf2 @@ -0,0 +1,14 @@ +port 1890 + +connection bridge-psk +address localhost:1889 +topic psk/test out +bridge_identity psk-test +bridge_psk deadbeef +bridge_tls_version tlsv1 + +log_type debug +log_type error +log_type warning +log_type information +log_type notice diff --git a/test/broker/08-tls-psk-bridge.psk b/test/broker/08-tls-psk-bridge.psk new file mode 100644 index 0000000000..a12d69009e --- /dev/null +++ b/test/broker/08-tls-psk-bridge.psk @@ -0,0 +1 @@ +psk-test:deadbeef diff --git a/test/broker/08-tls-psk-bridge.py b/test/broker/08-tls-psk-bridge.py new file mode 100755 index 0000000000..dd827e956f --- /dev/null +++ b/test/broker/08-tls-psk-bridge.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python + +import subprocess +import socket +import ssl +import sys +import time + +if sys.version < '2.7': + print("WARNING: SSL not supported on Python 2.6") + exit(0) + +if ssl.OPENSSL_VERSION_NUMBER < 0x10000000: + print("WARNING: TLS-PSK not supported on OpenSSL < 1.0") + exit(0) + + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +env = dict(os.environ) +env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp' +try: + pp = env['PYTHONPATH'] +except KeyError: + pp = '' +env['PYTHONPATH'] = '../../lib/python:'+pp + + +rc = 1 +keepalive = 60 +connect_packet = mosq_test.gen_connect("no-psk-test-client", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +mid = 1 +subscribe_packet = mosq_test.gen_subscribe(mid, "psk/test", 0) +suback_packet = mosq_test.gen_suback(mid, 0) + +publish_packet = mosq_test.gen_publish(topic="psk/test", payload="message", qos=0) + +broker = subprocess.Popen(['../../src/mosquitto', '-c', '08-tls-psk-bridge.conf'], stderr=subprocess.PIPE) +bridge = subprocess.Popen(['../../src/mosquitto', '-c', '08-tls-psk-bridge.conf2'], stderr=subprocess.PIPE) + +pub = None +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet, timeout=30) + sock.send(subscribe_packet) + + if mosq_test.expect_packet(sock, "suback", suback_packet): + pub = subprocess.Popen(['./c/08-tls-psk-bridge.test'], env=env, stdout=subprocess.PIPE) + if pub.wait(): + raise ValueError + exit(1) + + if mosq_test.expect_packet(sock, "publish", publish_packet): + rc = 0 + sock.close() +finally: + broker.terminate() + broker.wait() + bridge.terminate() + bridge.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + (stdo, stde) = bridge.communicate() + print(stde) + if pub: + (stdo, stde) = pub.communicate() + print(stdo) + + +exit(rc) + diff --git a/test/broker/08-tls-psk-pub.conf b/test/broker/08-tls-psk-pub.conf new file mode 100644 index 0000000000..252fba3ca7 --- /dev/null +++ b/test/broker/08-tls-psk-pub.conf @@ -0,0 +1,15 @@ +psk_file 08-tls-psk-pub.psk + +port 1888 +psk_hint hint +tls_version tlsv1 + +listener 1889 + + +log_type debug +log_type error +log_type warning +log_type information +log_type notice + diff --git a/test/broker/08-tls-psk-pub.psk b/test/broker/08-tls-psk-pub.psk new file mode 100644 index 0000000000..6e98667cc7 --- /dev/null +++ b/test/broker/08-tls-psk-pub.psk @@ -0,0 +1 @@ +psk-id:deadbeef diff --git a/test/broker/08-tls-psk-pub.py b/test/broker/08-tls-psk-pub.py new file mode 100755 index 0000000000..9bdc1ca061 --- /dev/null +++ b/test/broker/08-tls-psk-pub.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python + +import subprocess +import socket +import ssl +import sys +import time + +if sys.version < '2.7': + print("WARNING: SSL not supported on Python 2.6") + exit(0) + +if ssl.OPENSSL_VERSION_NUMBER < 0x10000000: + print("WARNING: TLS-PSK not supported on OpenSSL < 1.0") + exit(0) + + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +env = dict(os.environ) +env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp' +try: + pp = env['PYTHONPATH'] +except KeyError: + pp = '' +env['PYTHONPATH'] = '../../lib/python:'+pp + + +rc = 1 +keepalive = 10 +connect_packet = mosq_test.gen_connect("no-psk-test-client", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +mid = 1 +subscribe_packet = mosq_test.gen_subscribe(mid, "psk/test", 0) +suback_packet = mosq_test.gen_suback(mid, 0) + +publish_packet = mosq_test.gen_publish(topic="psk/test", payload="message", qos=0) + +broker = subprocess.Popen(['../../src/mosquitto', '-c', '08-tls-psk-pub.conf'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet, port=1889, timeout=20) + sock.send(subscribe_packet) + + if mosq_test.expect_packet(sock, "suback", suback_packet): + pub = subprocess.Popen(['./c/08-tls-psk-pub.test'], env=env) + if pub.wait(): + raise ValueError + exit(1) + + if mosq_test.expect_packet(sock, "publish", publish_packet): + rc = 0 + sock.close() +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/09-plugin-auth-unpwd-fail.conf b/test/broker/09-plugin-auth-unpwd-fail.conf new file mode 100644 index 0000000000..fca627630d --- /dev/null +++ b/test/broker/09-plugin-auth-unpwd-fail.conf @@ -0,0 +1,3 @@ +port 1888 +allow_anonymous false +auth_plugin c/auth_plugin.so diff --git a/test/broker/09-plugin-auth-unpwd-fail.py b/test/broker/09-plugin-auth-unpwd-fail.py new file mode 100755 index 0000000000..19923c98d0 --- /dev/null +++ b/test/broker/09-plugin-auth-unpwd-fail.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python + +# Test whether a connection is successful with correct username and password +# when using a simple auth_plugin. + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 10 +connect_packet = mosq_test.gen_connect("connect-uname-pwd-test", keepalive=keepalive, username="test-username", password="wrong") +connack_packet = mosq_test.gen_connack(rc=4) + +broker = subprocess.Popen(['../../src/mosquitto', '-c', '09-plugin-auth-unpwd-fail.conf'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet, timeout=20) + rc = 0 + + sock.close() +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/09-plugin-auth-unpwd-success.conf b/test/broker/09-plugin-auth-unpwd-success.conf new file mode 100644 index 0000000000..fca627630d --- /dev/null +++ b/test/broker/09-plugin-auth-unpwd-success.conf @@ -0,0 +1,3 @@ +port 1888 +allow_anonymous false +auth_plugin c/auth_plugin.so diff --git a/test/broker/09-plugin-auth-unpwd-success.py b/test/broker/09-plugin-auth-unpwd-success.py new file mode 100755 index 0000000000..4e9f889f72 --- /dev/null +++ b/test/broker/09-plugin-auth-unpwd-success.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python + +# Test whether a connection is successful with correct username and password +# when using a simple auth_plugin. + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 10 +connect_packet = mosq_test.gen_connect("connect-uname-pwd-test", keepalive=keepalive, username="test-username", password="cnwTICONIURW") +connack_packet = mosq_test.gen_connack(rc=0) + +broker = subprocess.Popen(['../../src/mosquitto', '-c', '09-plugin-auth-unpwd-success.conf'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet, timeout=20) + rc = 0 + sock.close() +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + + +exit(rc) + diff --git a/test/broker/10-listener-mount-point-helper.py b/test/broker/10-listener-mount-point-helper.py new file mode 100755 index 0000000000..bacbc4edf0 --- /dev/null +++ b/test/broker/10-listener-mount-point-helper.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python + +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +connect_packet = mosq_test.gen_connect("test-helper", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +publish_packet = mosq_test.gen_publish("test", qos=0, payload="mount point") + +sock = mosq_test.do_client_connect(connect_packet, connack_packet, port=1889, connack_error="helper connack") +sock.send(publish_packet) +rc = 0 +sock.close() + +exit(rc) + diff --git a/test/broker/10-listener-mount-point.conf b/test/broker/10-listener-mount-point.conf new file mode 100644 index 0000000000..19438646cd --- /dev/null +++ b/test/broker/10-listener-mount-point.conf @@ -0,0 +1,7 @@ +port 1888 + +listener 1889 +mount_point mount/ + +log_type debug + diff --git a/test/broker/10-listener-mount-point.py b/test/broker/10-listener-mount-point.py new file mode 100755 index 0000000000..8bae43f14f --- /dev/null +++ b/test/broker/10-listener-mount-point.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python + +import os +import subprocess +import socket +import time + +import inspect, os, sys +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +connect_packet = mosq_test.gen_connect("test2", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +mid = 1 +subscribe_packet = mosq_test.gen_subscribe(mid, "#", 0) +suback_packet = mosq_test.gen_suback(mid, 0) + +publish_packet = mosq_test.gen_publish("mount/test", qos=0, payload="mount point") + +broker = subprocess.Popen(['../../src/mosquitto', '-c', '10-listener-mount-point.conf'], stderr=subprocess.PIPE) + +try: + time.sleep(0.5) + + sock = mosq_test.do_client_connect(connect_packet, connack_packet, timeout=20) + sock.send(subscribe_packet) + + if mosq_test.expect_packet(sock, "suback", suback_packet): + pub = subprocess.Popen(['./10-listener-mount-point-helper.py'], stdout=subprocess.PIPE) + pub.wait() + # Should have now received a publish command + + if mosq_test.expect_packet(sock, "publish", publish_packet): + rc = 0 + + sock.close() +finally: + broker.terminate() + broker.wait() + if rc: + (stdo, stde) = broker.communicate() + print(stde) + +exit(rc) + diff --git a/test/broker/Makefile b/test/broker/Makefile new file mode 100644 index 0000000000..a2b10e9f5f --- /dev/null +++ b/test/broker/Makefile @@ -0,0 +1,93 @@ +.PHONY: all clean test +.NOTPARALLEL: + +all : + +clean : + $(MAKE) -C c clean + +test-compile : + $(MAKE) -C c + +test : test-compile 01 02 03 04 05 06 07 08 09 10 + +01 : + ./01-connect-success.py + ./01-connect-invalid-protonum.py + ./01-connect-invalid-id-0.py + ./01-connect-invalid-id-0-311.py + ./01-connect-invalid-id-missing.py + ./01-connect-anon-denied.py + ./01-connect-uname-no-password-denied.py + ./01-connect-uname-password-denied.py + ./01-connect-uname-password-success.py + +02 : + ./02-subscribe-qos0.py + ./02-subscribe-qos1.py + ./02-subscribe-qos2.py + ./02-subpub-qos0.py + ./02-subpub-qos1.py + ./02-subpub-qos2.py + ./02-unsubscribe-qos0.py + ./02-unsubscribe-qos1.py + ./02-unsubscribe-qos2.py + +03 : + ./03-publish-qos1.py + ./03-publish-qos2.py + ./03-publish-b2c-timeout-qos1.py + ./03-publish-b2c-disconnect-qos1.py + ./03-publish-c2b-timeout-qos2.py + ./03-publish-c2b-disconnect-qos2.py + ./03-publish-b2c-timeout-qos2.py + ./03-publish-b2c-disconnect-qos2.py + ./03-pattern-matching.py + +04 : + ./04-retain-qos0.py + ./04-retain-qos0-fresh.py + ./04-retain-qos0-repeated.py + ./04-retain-qos1-qos0.py + ./04-retain-qos0-clear.py + +05 : + ./05-clean-session-qos1.py + +06 : + ./06-bridge-reconnect-local-out.py + ./06-bridge-br2b-disconnect-qos1.py + ./06-bridge-br2b-disconnect-qos2.py + ./06-bridge-b2br-disconnect-qos1.py + ./06-bridge-b2br-disconnect-qos2.py + +07 : + ./07-will-qos0.py + ./07-will-null.py + ./07-will-acl-denied.py + ./07-will-null-topic.py + +08 : + ./08-ssl-connect-no-auth.py + ./08-ssl-connect-no-auth-wrong-ca.py + ./08-ssl-connect-cert-auth.py + ./08-ssl-connect-cert-auth-without.py + ./08-ssl-connect-cert-auth-expired.py + ./08-ssl-connect-cert-auth-revoked.py + ./08-ssl-connect-cert-auth-crl.py + ./08-ssl-connect-identity.py + ./08-ssl-connect-no-identity.py + ./08-ssl-bridge.py + ./08-tls-psk-pub.py + ./08-tls-psk-bridge.py + +09 : + ./09-plugin-auth-unpwd-success.py + ./09-plugin-auth-unpwd-fail.py + +10 : + ./10-listener-mount-point.py + +# Tests for with WITH_STRICT_PROTOCOL defined +strict-test : + ./01-connect-invalid-id-24.py diff --git a/test/broker/c/08-tls-psk-bridge.c b/test/broker/c/08-tls-psk-bridge.c new file mode 100644 index 0000000000..c4d0286e7f --- /dev/null +++ b/test/broker/c/08-tls-psk-bridge.c @@ -0,0 +1,63 @@ +#include +#include +#include +#include +#include +#include + +static int run = -1; +static int sent_mid; + +void on_log(struct mosquitto *mosq, void *obj, int level, const char *str) +{ + printf("%s\n", str); +} + +void on_connect(struct mosquitto *mosq, void *obj, int rc) +{ + if(rc){ + exit(1); + }else{ + mosquitto_publish(mosq, &sent_mid, "psk/test", strlen("message"), "message", 1, false); + } +} + +void on_publish(struct mosquitto *mosq, void *obj, int mid) +{ + if(mid == sent_mid){ + mosquitto_disconnect(mosq); + run = 0; + }else{ + exit(1); + } +} + +void on_disconnect(struct mosquitto *mosq, void *obj, int rc) +{ + run = rc; +} + +int main(int argc, char *argv[]) +{ + int rc; + struct mosquitto *mosq; + + mosquitto_lib_init(); + + mosq = mosquitto_new("08-tls-psk-bridge", true, NULL); + mosquitto_tls_opts_set(mosq, 1, "tlsv1", NULL); + mosquitto_connect_callback_set(mosq, on_connect); + mosquitto_disconnect_callback_set(mosq, on_disconnect); + mosquitto_publish_callback_set(mosq, on_publish); + mosquitto_log_callback_set(mosq, on_log); + + rc = mosquitto_connect(mosq, "localhost", 1890, 60); + if(rc) return rc; + + while(run == -1){ + mosquitto_loop(mosq, -1, 1); + } + + mosquitto_lib_cleanup(); + return run; +} diff --git a/test/broker/c/08-tls-psk-pub.c b/test/broker/c/08-tls-psk-pub.c new file mode 100644 index 0000000000..26fefeb3d1 --- /dev/null +++ b/test/broker/c/08-tls-psk-pub.c @@ -0,0 +1,67 @@ +#include +#include +#include +#include +#include +#include + +static int run = -1; +static int sent_mid; + +void on_connect(struct mosquitto *mosq, void *obj, int rc) +{ + if(rc){ + exit(1); + }else{ + mosquitto_publish(mosq, &sent_mid, "psk/test", strlen("message"), "message", 0, false); + } +} + +void on_publish(struct mosquitto *mosq, void *obj, int mid) +{ + if(mid == sent_mid){ + mosquitto_disconnect(mosq); + run = 0; + }else{ + exit(1); + } +} + +void on_disconnect(struct mosquitto *mosq, void *obj, int rc) +{ + run = rc; +} + +int main(int argc, char *argv[]) +{ + int rc; + struct mosquitto *mosq; + + mosquitto_lib_init(); + + mosq = mosquitto_new("08-tls-psk-pub", true, NULL); + mosquitto_tls_opts_set(mosq, 1, "tlsv1", NULL); + rc = mosquitto_tls_psk_set(mosq, "deadbeef", "psk-id", NULL); + if(rc){ + mosquitto_destroy(mosq); + return rc; + } + mosquitto_connect_callback_set(mosq, on_connect); + mosquitto_disconnect_callback_set(mosq, on_disconnect); + mosquitto_publish_callback_set(mosq, on_publish); + + rc = mosquitto_connect(mosq, "localhost", 1888, 60); + if(rc){ + mosquitto_destroy(mosq); + return rc; + } + + while(run == -1){ + mosquitto_loop(mosq, -1, 1); + } + + mosquitto_destroy(mosq); + + mosquitto_lib_cleanup(); + return run; +} diff --git a/test/broker/c/Makefile b/test/broker/c/Makefile new file mode 100644 index 0000000000..4e43637c9d --- /dev/null +++ b/test/broker/c/Makefile @@ -0,0 +1,23 @@ +.PHONY: all test clean reallyclean 08 + +CFLAGS=-I../../../lib -I../../../src -Wall -Werror + +all : auth_plugin.so 08 + +08 : 08-tls-psk-pub.test 08-tls-psk-bridge.test + +auth_plugin.so : auth_plugin.c + $(CC) ${CFLAGS} -fPIC -shared $^ -o $@ + +08-tls-psk-pub.test : 08-tls-psk-pub.c + $(CC) ${CFLAGS} $^ -o $@ ../../../lib/libmosquitto.so.1 + +08-tls-psk-bridge.test : 08-tls-psk-bridge.c + $(CC) ${CFLAGS} $^ -o $@ ../../../lib/libmosquitto.so.1 + + +reallyclean : clean + -rm -f *.orig + +clean : + rm -f *.so *.test diff --git a/test/broker/c/auth_plugin.c b/test/broker/c/auth_plugin.c new file mode 100644 index 0000000000..81d2f41c63 --- /dev/null +++ b/test/broker/c/auth_plugin.c @@ -0,0 +1,55 @@ +#include +#include +#include +#include + +int mosquitto_auth_plugin_version(void) +{ + return MOSQ_AUTH_PLUGIN_VERSION; +} + +int mosquitto_auth_plugin_init(void **user_data, struct mosquitto_auth_opt *auth_opts, int auth_opt_count) +{ + return MOSQ_ERR_SUCCESS; +} + +int mosquitto_auth_plugin_cleanup(void *user_data, struct mosquitto_auth_opt *auth_opts, int auth_opt_count) +{ + return MOSQ_ERR_SUCCESS; +} + +int mosquitto_auth_security_init(void *user_data, struct mosquitto_auth_opt *auth_opts, int auth_opt_count, bool reload) +{ + return MOSQ_ERR_SUCCESS; +} + +int mosquitto_auth_security_cleanup(void *user_data, struct mosquitto_auth_opt *auth_opts, int auth_opt_count, bool reload) +{ + return MOSQ_ERR_SUCCESS; +} + +int mosquitto_auth_acl_check(void *user_data, const char *clientid, const char *username, const char *topic, int access) +{ + if(!strcmp(username, "readonly") && access == MOSQ_ACL_READ){ + return MOSQ_ERR_SUCCESS; + }else{ + return MOSQ_ERR_ACL_DENIED; + } +} + +int mosquitto_auth_unpwd_check(void *user_data, const char *username, const char *password) +{ + if(!strcmp(username, "test-username") && password && !strcmp(password, "cnwTICONIURW")){ + return MOSQ_ERR_SUCCESS; + }else if(!strcmp(username, "readonly")){ + return MOSQ_ERR_SUCCESS; + }else{ + return MOSQ_ERR_AUTH; + } +} + +int mosquitto_auth_psk_key_get(void *user_data, const char *hint, const char *identity, char *key, int max_key_len) +{ + return MOSQ_ERR_AUTH; +} + diff --git a/test/broker/readme.txt b/test/broker/readme.txt new file mode 100644 index 0000000000..82eff27704 --- /dev/null +++ b/test/broker/readme.txt @@ -0,0 +1,14 @@ +----- Broker Tests ----- + +This folder contains a number of tests to exercise the functionality of the +broker. Feel free to add more. + +Numbering is as follows: + +01: Connection tests +02: Subscribe/unsubscribe tests +03: Publish tests +04: Retained message tests +05: Clean session tests +06: Bridge tests +07: Will tests diff --git a/test/fake_user.c b/test/fake_user.c new file mode 100644 index 0000000000..cbbb57aba1 --- /dev/null +++ b/test/fake_user.c @@ -0,0 +1,114 @@ +/* +Copyright (c) 2009,2010, Roger Light +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of mosquitto nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include +#include +#ifndef WIN32 +#include +#else +#include +#define snprintf sprintf_s +#endif + +#include + +void my_connect_callback(struct mosquitto *mosq, void *obj, int result) +{ + char topic[100]; + + if(!result){ + snprintf(topic, 100, "fake/%d", getpid()%100); + mosquitto_subscribe(mosq, NULL, topic, rand()%3); + } +} + +int main(int argc, char *argv[]) +{ + char id[30]; + char *host = "localhost"; + int port = 1883; + int keepalive = 60; + bool clean_session = false; + struct mosquitto *mosq = NULL; + + void *will_payload = NULL; + long will_payloadlen = 0; + int will_qos = 0; + bool will_retain = false; + char will_topic[100], topic[100]; + int pid; + + pid = getpid(); + + srand(pid); + snprintf(id, 30, "fake_user_%d", pid); + + mosquitto_lib_init(); + mosq = mosquitto_new(id, clean_session, NULL); + if(!mosq){ + fprintf(stderr, "Error: Out of memory.\n"); + return 1; + } + + if(rand()%5 == 0){ + snprintf(will_topic, 100, "fake/wills/%d", rand()%100); + if(mosquitto_will_set(mosq, will_topic, will_payloadlen, will_payload, will_qos, will_retain)){ + fprintf(stderr, "Error: Problem setting will.\n"); + return 1; + } + } + mosquitto_connect_callback_set(mosq, my_connect_callback); + while(1){ + clean_session = rand()%10==0?false:true; + + if(mosquitto_connect(mosq, host, port, keepalive)){ + fprintf(stderr, "Unable to connect.\n"); + return 1; + } + mosquitto_subscribe(mosq, NULL, "#", 0); + + while(!mosquitto_loop(mosq, -1, 5)){ + if(rand()%100==0){ + snprintf(topic, 100, "fake/%d", rand()%100); + mosquitto_publish(mosq, NULL, topic, 10, "0123456789", rand()%3, rand()%2); + } + if(rand()%50==0){ + mosquitto_disconnect(mosq); + } + } + sleep(10); + } + mosquitto_destroy(mosq); + mosquitto_lib_cleanup(); + + return 0; +} + diff --git a/test/lib/01-con-discon-success.py b/test/lib/01-con-discon-success.py new file mode 100755 index 0000000000..f3c0835d66 --- /dev/null +++ b/test/lib/01-con-discon-success.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python + +# Test whether a client produces a correct connect and subsequent disconnect. + +# The client should connect to port 1888 with keepalive=60, clean session set, +# and client id 01-con-discon-success +# The test will send a CONNACK message to the client with rc=0. Upon receiving +# the CONNACK and verifying that rc=0, the client should send a DISCONNECT +# message. If rc!=0, the client should exit with an error. + +import inspect +import os +import subprocess +import socket +import sys +import time + +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +connect_packet = mosq_test.gen_connect("01-con-discon-success", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +disconnect_packet = mosq_test.gen_disconnect() + +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +sock.settimeout(10) +sock.bind(('', 1888)) +sock.listen(5) + +client_args = sys.argv[1:] +env = dict(os.environ) +env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp' +try: + pp = env['PYTHONPATH'] +except KeyError: + pp = '' +env['PYTHONPATH'] = '../../lib/python:'+pp +client = subprocess.Popen(client_args, env=env) + +try: + (conn, address) = sock.accept() + conn.settimeout(10) + + if mosq_test.expect_packet(conn, "connect", connect_packet): + conn.send(connack_packet) + + if mosq_test.expect_packet(conn, "disconnect", disconnect_packet): + rc = 0 + + conn.close() +finally: + client.terminate() + client.wait() + sock.close() + +exit(rc) + diff --git a/test/lib/01-keepalive-pingreq.py b/test/lib/01-keepalive-pingreq.py new file mode 100755 index 0000000000..6048316671 --- /dev/null +++ b/test/lib/01-keepalive-pingreq.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python + +# Test whether a client sends a pingreq after the keepalive time + +# The client should connect to port 1888 with keepalive=4, clean session set, +# and client id 01-keepalive-pingreq +# The client should send a PINGREQ message after the appropriate amount of time +# (4 seconds after no traffic). + +import inspect +import os +import subprocess +import socket +import sys +import time + +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 4 +connect_packet = mosq_test.gen_connect("01-keepalive-pingreq", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +pingreq_packet = mosq_test.gen_pingreq() +pingresp_packet = mosq_test.gen_pingresp() + +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +sock.settimeout(10) +sock.bind(('', 1888)) +sock.listen(5) + +client_args = sys.argv[1:] +env = dict(os.environ) +env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp' +try: + pp = env['PYTHONPATH'] +except KeyError: + pp = '' +env['PYTHONPATH'] = '../../lib/python:'+pp +client = subprocess.Popen(client_args, env=env) + +try: + (conn, address) = sock.accept() + conn.settimeout(keepalive+10) + + if mosq_test.expect_packet(conn, "connect", connect_packet): + conn.send(connack_packet) + + if mosq_test.expect_packet(conn, "pingreq", pingreq_packet): + time.sleep(1.0) + conn.send(pingresp_packet) + + if mosq_test.expect_packet(conn, "pingreq", pingreq_packet): + rc = 0 + + conn.close() +finally: + client.terminate() + client.wait() + sock.close() + +exit(rc) + diff --git a/test/lib/01-no-clean-session.py b/test/lib/01-no-clean-session.py new file mode 100755 index 0000000000..af9ce20210 --- /dev/null +++ b/test/lib/01-no-clean-session.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python + +# Test whether a client produces a correct connect with clean session not set. + +# The client should connect to port 1888 with keepalive=60, clean session not +# set, and client id 01-no-clean-session. + +import inspect +import os +import subprocess +import socket +import sys +import time + +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +connect_packet = mosq_test.gen_connect("01-no-clean-session", clean_session=False, keepalive=keepalive) + +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +sock.settimeout(10) +sock.bind(('', 1888)) +sock.listen(5) + +client_args = sys.argv[1:] +env = dict(os.environ) +env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp' +try: + pp = env['PYTHONPATH'] +except KeyError: + pp = '' +env['PYTHONPATH'] = '../../lib/python:'+pp +client = subprocess.Popen(client_args, env=env) + +try: + (conn, address) = sock.accept() + conn.settimeout(10) + + if mosq_test.expect_packet(conn, "connect", connect_packet): + rc = 0 + + conn.close() +finally: + client.terminate() + client.wait() + sock.close() + +exit(rc) + diff --git a/test/lib/01-unpwd-set.py b/test/lib/01-unpwd-set.py new file mode 100755 index 0000000000..2b05458c96 --- /dev/null +++ b/test/lib/01-unpwd-set.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python + +# Test whether a client produces a correct connect with a username and password. + +# The client should connect to port 1888 with keepalive=60, clean session set, +# client id 01-unpwd-set, username set to uname and password set to ;'[08gn=# + +import inspect +import os +import subprocess +import socket +import sys +import time + +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +connect_packet = mosq_test.gen_connect("01-unpwd-set", keepalive=keepalive, username="uname", password=";'[08gn=#") + +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +sock.settimeout(10) +sock.bind(('', 1888)) +sock.listen(5) + +client_args = sys.argv[1:] +env = dict(os.environ) +env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp' +try: + pp = env['PYTHONPATH'] +except KeyError: + pp = '' +env['PYTHONPATH'] = '../../lib/python:'+pp +client = subprocess.Popen(client_args, env=env) + +try: + (conn, address) = sock.accept() + conn.settimeout(10) + + if mosq_test.expect_packet(conn, "connect", connect_packet): + rc = 0 + + conn.close() +finally: + client.terminate() + client.wait() + sock.close() + +exit(rc) + diff --git a/test/lib/01-will-set.py b/test/lib/01-will-set.py new file mode 100755 index 0000000000..5d2ce23e93 --- /dev/null +++ b/test/lib/01-will-set.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python + +# Test whether a client produces a correct connect with a will. +# Will QoS=1, will retain=1. + +# The client should connect to port 1888 with keepalive=60, clean session set, +# client id 01-will-set will topic set to topic/on/unexpected/disconnect , will +# payload set to "will message", will qos set to 1 and will retain set. + +import inspect +import os +import subprocess +import socket +import sys +import time + +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +connect_packet = mosq_test.gen_connect("01-will-set", keepalive=keepalive, will_topic="topic/on/unexpected/disconnect", will_qos=1, will_retain=True, will_payload="will message") + +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +sock.settimeout(10) +sock.bind(('', 1888)) +sock.listen(5) + +client_args = sys.argv[1:] +env = dict(os.environ) +env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp' +try: + pp = env['PYTHONPATH'] +except KeyError: + pp = '' +env['PYTHONPATH'] = '../../lib/python:'+pp +client = subprocess.Popen(client_args, env=env) + +try: + (conn, address) = sock.accept() + conn.settimeout(10) + + if mosq_test.expect_packet(conn, "connect", connect_packet): + rc = 0 + + conn.close() +finally: + client.terminate() + client.wait() + sock.close() + +exit(rc) + diff --git a/test/lib/01-will-unpwd-set.py b/test/lib/01-will-unpwd-set.py new file mode 100755 index 0000000000..3edb4a314b --- /dev/null +++ b/test/lib/01-will-unpwd-set.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python + +# Test whether a client produces a correct connect with a will, username and password. + +# The client should connect to port 1888 with keepalive=60, clean session set, +# client id 01-will-unpwd-set , will topic set to "will-topic", will payload +# set to "will message", will qos=2, will retain not set, username set to +# "oibvvwqw" and password set to "#'^2hg9a&nm38*us". + +import inspect +import os +import subprocess +import socket +import sys +import time + +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +connect_packet = mosq_test.gen_connect("01-will-unpwd-set", + keepalive=keepalive, username="oibvvwqw", password="#'^2hg9a&nm38*us", + will_topic="will-topic", will_qos=2, will_payload="will message") + +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +sock.settimeout(10) +sock.bind(('', 1888)) +sock.listen(5) + +client_args = sys.argv[1:] +env = dict(os.environ) +env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp' +try: + pp = env['PYTHONPATH'] +except KeyError: + pp = '' +env['PYTHONPATH'] = '../../lib/python:'+pp +client = subprocess.Popen(client_args, env=env) + +try: + (conn, address) = sock.accept() + conn.settimeout(10) + + if mosq_test.expect_packet(conn, "connect", connect_packet): + rc = 0 + + conn.close() +finally: + client.terminate() + client.wait() + sock.close() + +exit(rc) + diff --git a/test/lib/02-subscribe-qos0.py b/test/lib/02-subscribe-qos0.py new file mode 100755 index 0000000000..d671b4ef2b --- /dev/null +++ b/test/lib/02-subscribe-qos0.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python + +# Test whether a client sends a correct SUBSCRIBE to a topic with QoS 0. + +# The client should connect to port 1888 with keepalive=60, clean session set, +# and client id subscribe-qos0-test +# The test will send a CONNACK message to the client with rc=0. Upon receiving +# the CONNACK and verifying that rc=0, the client should send a SUBSCRIBE +# message to subscribe to topic "qos0/test" with QoS=0. If rc!=0, the client +# should exit with an error. +# Upon receiving the correct SUBSCRIBE message, the test will reply with a +# SUBACK message with the accepted QoS set to 0. On receiving the SUBACK +# message, the client should send a DISCONNECT message. + +import inspect +import os +import subprocess +import socket +import sys +import time + +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +connect_packet = mosq_test.gen_connect("subscribe-qos0-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +disconnect_packet = mosq_test.gen_disconnect() + +mid = 1 +subscribe_packet = mosq_test.gen_subscribe(mid, "qos0/test", 0) +suback_packet = mosq_test.gen_suback(mid, 0) + +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +sock.settimeout(10) +sock.bind(('', 1888)) +sock.listen(5) + +client_args = sys.argv[1:] +env = dict(os.environ) +env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp' +try: + pp = env['PYTHONPATH'] +except KeyError: + pp = '' +env['PYTHONPATH'] = '../../lib/python:'+pp +client = subprocess.Popen(client_args, env=env) + +try: + (conn, address) = sock.accept() + conn.settimeout(10) + + if mosq_test.expect_packet(conn, "connect", connect_packet): + conn.send(connack_packet) + + if mosq_test.expect_packet(conn, "subscribe", subscribe_packet): + conn.send(suback_packet) + + if mosq_test.expect_packet(conn, "disconnect", disconnect_packet): + rc = 0 + + conn.close() +finally: + client.terminate() + client.wait() + sock.close() + +exit(rc) diff --git a/test/lib/02-subscribe-qos1.py b/test/lib/02-subscribe-qos1.py new file mode 100755 index 0000000000..f96b813f02 --- /dev/null +++ b/test/lib/02-subscribe-qos1.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python + +# Test whether a client sends a correct SUBSCRIBE to a topic with QoS 1. + +# The client should connect to port 1888 with keepalive=60, clean session set, +# and client id subscribe-qos1-test +# The test will send a CONNACK message to the client with rc=0. Upon receiving +# the CONNACK and verifying that rc=0, the client should send a SUBSCRIBE +# message to subscribe to topic "qos1/test" with QoS=1. If rc!=0, the client +# should exit with an error. +# Upon receiving the correct SUBSCRIBE message, the test will reply with a +# SUBACK message with the accepted QoS set to 1. On receiving the SUBACK +# message, the client should send a DISCONNECT message. + +import inspect +import os +import subprocess +import socket +import sys +import time + +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +connect_packet = mosq_test.gen_connect("subscribe-qos1-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +disconnect_packet = mosq_test.gen_disconnect() + +mid = 1 +subscribe_packet = mosq_test.gen_subscribe(mid, "qos1/test", 1) +suback_packet = mosq_test.gen_suback(mid, 1) + +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +sock.settimeout(10) +sock.bind(('', 1888)) +sock.listen(5) + +client_args = sys.argv[1:] +env = dict(os.environ) +env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp' +try: + pp = env['PYTHONPATH'] +except KeyError: + pp = '' +env['PYTHONPATH'] = '../../lib/python:'+pp +client = subprocess.Popen(client_args, env=env) + +try: + (conn, address) = sock.accept() + conn.settimeout(10) + + if mosq_test.expect_packet(conn, "connect", connect_packet): + conn.send(connack_packet) + + if mosq_test.expect_packet(conn, "subscribe", subscribe_packet): + conn.send(suback_packet) + + if mosq_test.expect_packet(conn, "disconnect", disconnect_packet): + rc = 0 + + conn.close() +finally: + client.terminate() + client.wait() + sock.close() + +exit(rc) diff --git a/test/lib/02-subscribe-qos2.py b/test/lib/02-subscribe-qos2.py new file mode 100755 index 0000000000..f9c42c8b1c --- /dev/null +++ b/test/lib/02-subscribe-qos2.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python + +# Test whether a client sends a correct SUBSCRIBE to a topic with QoS 2. + +# The client should connect to port 1888 with keepalive=60, clean session set, +# and client id subscribe-qos2-test +# The test will send a CONNACK message to the client with rc=0. Upon receiving +# the CONNACK and verifying that rc=0, the client should send a SUBSCRIBE +# message to subscribe to topic "qos2/test" with QoS=2. If rc!=0, the client +# should exit with an error. +# Upon receiving the correct SUBSCRIBE message, the test will reply with a +# SUBACK message with the accepted QoS set to 2. On receiving the SUBACK +# message, the client should send a DISCONNECT message. + +import inspect +import os +import subprocess +import socket +import sys +import time + +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +connect_packet = mosq_test.gen_connect("subscribe-qos2-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +disconnect_packet = mosq_test.gen_disconnect() + +mid = 1 +subscribe_packet = mosq_test.gen_subscribe(mid, "qos2/test", 2) +suback_packet = mosq_test.gen_suback(mid, 2) + +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +sock.settimeout(10) +sock.bind(('', 1888)) +sock.listen(5) + +client_args = sys.argv[1:] +env = dict(os.environ) +env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp' +try: + pp = env['PYTHONPATH'] +except KeyError: + pp = '' +env['PYTHONPATH'] = '../../lib/python:'+pp +client = subprocess.Popen(client_args, env=env) + +try: + (conn, address) = sock.accept() + conn.settimeout(10) + + if mosq_test.expect_packet(conn, "connect", connect_packet): + conn.send(connack_packet) + + if mosq_test.expect_packet(conn, "subscribe", subscribe_packet): + conn.send(suback_packet) + + if mosq_test.expect_packet(conn, "disconnect", disconnect_packet): + rc = 0 + + conn.close() +finally: + client.terminate() + client.wait() + sock.close() + +exit(rc) diff --git a/test/lib/02-unsubscribe.py b/test/lib/02-unsubscribe.py new file mode 100755 index 0000000000..1485bf9678 --- /dev/null +++ b/test/lib/02-unsubscribe.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python + +# Test whether a client sends a correct UNSUBSCRIBE packet. + +import inspect +import os +import subprocess +import socket +import sys +import time + +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +connect_packet = mosq_test.gen_connect("unsubscribe-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +disconnect_packet = mosq_test.gen_disconnect() + +mid = 1 +unsubscribe_packet = mosq_test.gen_unsubscribe(mid, "unsubscribe/test") +unsuback_packet = mosq_test.gen_unsuback(mid) + +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +sock.settimeout(10) +sock.bind(('', 1888)) +sock.listen(5) + +client_args = sys.argv[1:] +env = dict(os.environ) +env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp' +try: + pp = env['PYTHONPATH'] +except KeyError: + pp = '' +env['PYTHONPATH'] = '../../lib/python:'+pp +client = subprocess.Popen(client_args, env=env) + +try: + (conn, address) = sock.accept() + conn.settimeout(10) + + if mosq_test.expect_packet(conn, "connect", connect_packet): + conn.send(connack_packet) + + if mosq_test.expect_packet(conn, "unsubscribe", unsubscribe_packet): + conn.send(unsuback_packet) + + if mosq_test.expect_packet(conn, "disconnect", disconnect_packet): + rc = 0 + + conn.close() +finally: + client.terminate() + client.wait() + sock.close() + +exit(rc) diff --git a/test/lib/03-publish-b2c-qos1.py b/test/lib/03-publish-b2c-qos1.py new file mode 100755 index 0000000000..993f077d3d --- /dev/null +++ b/test/lib/03-publish-b2c-qos1.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python + +# Test whether a client responds correctly to a PUBLISH with QoS 1. + +# The client should connect to port 1888 with keepalive=60, clean session set, +# and client id publish-qos1-test +# The test will send a CONNACK message to the client with rc=0. Upon receiving +# the CONNACK the client should verify that rc==0. +# The test will send the client a PUBLISH message with topic +# "pub/qos1/receive", payload of "message", QoS=1 and mid=123. The client +# should handle this as per the spec by sending a PUBACK message. +# The client should then exit with return code==0. + +import inspect +import os +import subprocess +import socket +import sys +import time + +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +connect_packet = mosq_test.gen_connect("publish-qos1-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +disconnect_packet = mosq_test.gen_disconnect() + +mid = 123 +publish_packet = mosq_test.gen_publish("pub/qos1/receive", qos=1, mid=mid, payload="message") +puback_packet = mosq_test.gen_puback(mid) + +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +sock.settimeout(10) +sock.bind(('', 1888)) +sock.listen(5) + +client_args = sys.argv[1:] +env = dict(os.environ) +env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp' +try: + pp = env['PYTHONPATH'] +except KeyError: + pp = '' +env['PYTHONPATH'] = '../../lib/python:'+pp +client = subprocess.Popen(client_args, env=env) + +try: + (conn, address) = sock.accept() + conn.settimeout(10) + + if mosq_test.expect_packet(conn, "connect", connect_packet): + conn.send(connack_packet) + conn.send(publish_packet) + + if mosq_test.expect_packet(conn, "puback", puback_packet): + rc = 0 + + conn.close() +finally: + for i in range(0, 5): + if client.returncode != None: + break + time.sleep(0.1) + + try: + client.terminate() + except OSError: + pass + client.wait() + sock.close() + if client.returncode != 0: + exit(1) + +exit(rc) diff --git a/test/lib/03-publish-b2c-qos2.py b/test/lib/03-publish-b2c-qos2.py new file mode 100755 index 0000000000..658103b613 --- /dev/null +++ b/test/lib/03-publish-b2c-qos2.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python + +# Test whether a client responds correctly to a PUBLISH with QoS 1. + +# The client should connect to port 1888 with keepalive=60, clean session set, +# and client id publish-qos2-test +# The test will send a CONNACK message to the client with rc=0. Upon receiving +# the CONNACK the client should verify that rc==0. +# The test will send the client a PUBLISH message with topic +# "pub/qos2/receive", payload of "message", QoS=2 and mid=13423. The client +# should handle this as per the spec by sending a PUBREC message. +# The test will not respond to the first PUBREC message, so the client must +# resend the PUBREC message with dup=1. Note that to keep test durations low, a +# message retry timeout of less than 10 seconds is required for this test. +# On receiving the second PUBREC with dup==1, the test will send the correct +# PUBREL message. The client should respond to this with the correct PUBCOMP +# message and then exit with return code=0. + +import inspect +import os +import subprocess +import socket +import sys +import time + +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +connect_packet = mosq_test.gen_connect("publish-qos2-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +disconnect_packet = mosq_test.gen_disconnect() + +mid = 13423 +publish_packet = mosq_test.gen_publish("pub/qos2/receive", qos=2, mid=mid, payload="message") +pubrec_packet = mosq_test.gen_pubrec(mid) +pubrel_packet = mosq_test.gen_pubrel(mid) +pubcomp_packet = mosq_test.gen_pubcomp(mid) + +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +sock.settimeout(10) +sock.bind(('', 1888)) +sock.listen(5) + +client_args = sys.argv[1:] +env = dict(os.environ) +env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp' +try: + pp = env['PYTHONPATH'] +except KeyError: + pp = '' +env['PYTHONPATH'] = '../../lib/python:'+pp +client = subprocess.Popen(client_args, env=env) + +try: + (conn, address) = sock.accept() + conn.settimeout(10) + + if mosq_test.expect_packet(conn, "connect", connect_packet): + conn.send(connack_packet) + conn.send(publish_packet) + + if mosq_test.expect_packet(conn, "pubrec", pubrec_packet): + # Should be repeated due to timeout + if mosq_test.expect_packet(conn, "pubrec", pubrec_packet): + conn.send(pubrel_packet) + + if mosq_test.expect_packet(conn, "pubcomp", pubcomp_packet): + rc = 0 + + conn.close() +finally: + for i in range(0, 5): + if client.returncode != None: + break + time.sleep(0.1) + + try: + client.terminate() + except OSError: + pass + + client.wait() + sock.close() + if client.returncode != 0: + exit(1) + +exit(rc) diff --git a/test/lib/03-publish-c2b-qos1-disconnect.py b/test/lib/03-publish-c2b-qos1-disconnect.py new file mode 100755 index 0000000000..6ce0a8e942 --- /dev/null +++ b/test/lib/03-publish-c2b-qos1-disconnect.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python + +# Test whether a client sends a correct PUBLISH to a topic with QoS 1, then responds correctly to a disconnect. + +import inspect +import os +import subprocess +import socket +import sys +import time + +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +connect_packet = mosq_test.gen_connect("publish-qos1-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +disconnect_packet = mosq_test.gen_disconnect() + +mid = 1 +publish_packet = mosq_test.gen_publish("pub/qos1/test", qos=1, mid=mid, payload="message") +publish_packet_dup = mosq_test.gen_publish("pub/qos1/test", qos=1, mid=mid, payload="message", dup=True) +puback_packet = mosq_test.gen_puback(mid) + +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +sock.settimeout(10) +sock.bind(('', 1888)) +sock.listen(5) + +client_args = sys.argv[1:] +env = dict(os.environ) +env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp' +try: + pp = env['PYTHONPATH'] +except KeyError: + pp = '' +env['PYTHONPATH'] = '../../lib/python:'+pp + +client = subprocess.Popen(client_args, env=env) + +try: + (conn, address) = sock.accept() + conn.settimeout(15) + + if mosq_test.expect_packet(conn, "connect", connect_packet): + conn.send(connack_packet) + + if mosq_test.expect_packet(conn, "publish", publish_packet): + # Disconnect client. It should reconnect. + conn.close() + + (conn, address) = sock.accept() + conn.settimeout(15) + + if mosq_test.expect_packet(conn, "connect", connect_packet): + conn.send(connack_packet) + + if mosq_test.expect_packet(conn, "retried publish", publish_packet_dup): + conn.send(puback_packet) + + if mosq_test.expect_packet(conn, "disconnect", disconnect_packet): + rc = 0 + + conn.close() +finally: + client.terminate() + client.wait() + sock.close() + +exit(rc) diff --git a/test/lib/03-publish-c2b-qos1-timeout.py b/test/lib/03-publish-c2b-qos1-timeout.py new file mode 100755 index 0000000000..de59699f81 --- /dev/null +++ b/test/lib/03-publish-c2b-qos1-timeout.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python + +# Test whether a client sends a correct PUBLISH to a topic with QoS 1 and responds to a delay. + +# The client should connect to port 1888 with keepalive=60, clean session set, +# and client id publish-qos1-test +# The test will send a CONNACK message to the client with rc=0. Upon receiving +# the CONNACK the client should verify that rc==0. If not, it should exit with +# return code=1. +# On a successful CONNACK, the client should send a PUBLISH message with topic +# "pub/qos1/test", payload "message" and QoS=1. +# The test will not respond to the first PUBLISH message, so the client must +# resend the PUBLISH message with dup=1. Note that to keep test durations low, a +# message retry timeout of less than 10 seconds is required for this test. +# On receiving the second PUBLISH message, the test will send the correct +# PUBACK response. On receiving the correct PUBACK response, the client should +# send a DISCONNECT message. + +import inspect +import os +import subprocess +import socket +import sys +import time + +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +connect_packet = mosq_test.gen_connect("publish-qos1-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +disconnect_packet = mosq_test.gen_disconnect() + +mid = 1 +publish_packet = mosq_test.gen_publish("pub/qos1/test", qos=1, mid=mid, payload="message") +publish_packet_dup = mosq_test.gen_publish("pub/qos1/test", qos=1, mid=mid, payload="message", dup=True) +puback_packet = mosq_test.gen_puback(mid) + +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +sock.settimeout(10) +sock.bind(('', 1888)) +sock.listen(5) + +client_args = sys.argv[1:] +env = dict(os.environ) +env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp' +try: + pp = env['PYTHONPATH'] +except KeyError: + pp = '' +env['PYTHONPATH'] = '../../lib/python:'+pp +client = subprocess.Popen(client_args, env=env) + +try: + (conn, address) = sock.accept() + conn.settimeout(10) + + if mosq_test.expect_packet(conn, "connect", connect_packet): + conn.send(connack_packet) + + if mosq_test.expect_packet(conn, "publish", publish_packet): + # Delay for > 3 seconds (message retry time) + + if mosq_test.expect_packet(conn, "dup publish", publish_packet_dup): + conn.send(puback_packet) + + if mosq_test.expect_packet(conn, "disconnect", disconnect_packet): + rc = 0 + + conn.close() +finally: + client.terminate() + client.wait() + sock.close() + +exit(rc) diff --git a/test/lib/03-publish-c2b-qos2-disconnect.py b/test/lib/03-publish-c2b-qos2-disconnect.py new file mode 100755 index 0000000000..ce638bffeb --- /dev/null +++ b/test/lib/03-publish-c2b-qos2-disconnect.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python + +# Test whether a client sends a correct PUBLISH to a topic with QoS 2 and responds to a disconnect. + +import inspect +import os +import subprocess +import socket +import sys +import time + +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +connect_packet = mosq_test.gen_connect("publish-qos2-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +disconnect_packet = mosq_test.gen_disconnect() + +mid = 1 +publish_packet = mosq_test.gen_publish("pub/qos2/test", qos=2, mid=mid, payload="message") +publish_dup_packet = mosq_test.gen_publish("pub/qos2/test", qos=2, mid=mid, payload="message", dup=True) +pubrec_packet = mosq_test.gen_pubrec(mid) +pubrel_packet = mosq_test.gen_pubrel(mid) +pubrel_dup_packet = mosq_test.gen_pubrel(mid, dup=True) +pubcomp_packet = mosq_test.gen_pubcomp(mid) + +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +sock.settimeout(10) +sock.bind(('', 1888)) +sock.listen(5) + +client_args = sys.argv[1:] +env = dict(os.environ) +env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp' +try: + pp = env['PYTHONPATH'] +except KeyError: + pp = '' +env['PYTHONPATH'] = '../../lib/python:'+pp +client = subprocess.Popen(client_args, env=env) + +try: + (conn, address) = sock.accept() + conn.settimeout(10) + + if mosq_test.expect_packet(conn, "connect", connect_packet): + conn.send(connack_packet) + + if mosq_test.expect_packet(conn, "publish", publish_packet): + # Disconnect client. It should reconnect. + conn.close() + + (conn, address) = sock.accept() + conn.settimeout(15) + + if mosq_test.expect_packet(conn, "connect", connect_packet): + conn.send(connack_packet) + + if mosq_test.expect_packet(conn, "retried publish", publish_dup_packet): + conn.send(pubrec_packet) + + if mosq_test.expect_packet(conn, "pubrel", pubrel_packet): + # Disconnect client. It should reconnect. + conn.close() + + (conn, address) = sock.accept() + conn.settimeout(15) + + # Complete connection and message flow. + if mosq_test.expect_packet(conn, "connect", connect_packet): + conn.send(connack_packet) + + if mosq_test.expect_packet(conn, "retried pubrel", pubrel_dup_packet): + conn.send(pubcomp_packet) + + if mosq_test.expect_packet(conn, "disconnect", disconnect_packet): + rc = 0 + + conn.close() +finally: + client.terminate() + client.wait() + sock.close() + +exit(rc) diff --git a/test/lib/03-publish-c2b-qos2-timeout.py b/test/lib/03-publish-c2b-qos2-timeout.py new file mode 100755 index 0000000000..3a78af7c46 --- /dev/null +++ b/test/lib/03-publish-c2b-qos2-timeout.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python + +# Test whether a client sends a correct PUBLISH to a topic with QoS 1 and responds to a delay. + +# The client should connect to port 1888 with keepalive=60, clean session set, +# and client id publish-qos2-test +# The test will send a CONNACK message to the client with rc=0. Upon receiving +# the CONNACK the client should verify that rc==0. If not, it should exit with +# return code=1. +# On a successful CONNACK, the client should send a PUBLISH message with topic +# "pub/qos2/test", payload "message" and QoS=2. +# The test will not respond to the first PUBLISH message, so the client must +# resend the PUBLISH message with dup=1. Note that to keep test durations low, a +# message retry timeout of less than 10 seconds is required for this test. +# On receiving the second PUBLISH message, the test will send the correct +# PUBREC response. On receiving the correct PUBREC response, the client should +# send a PUBREL message. +# The test will not respond to the first PUBREL message, so the client must +# resend the PUBREL message with dup=1. On receiving the second PUBREL message, +# the test will send the correct PUBCOMP response. On receiving the correct +# PUBCOMP response, the client should send a DISCONNECT message. + +import inspect +import os +import subprocess +import socket +import sys +import time + +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +connect_packet = mosq_test.gen_connect("publish-qos2-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +disconnect_packet = mosq_test.gen_disconnect() + +mid = 1 +publish_packet = mosq_test.gen_publish("pub/qos2/test", qos=2, mid=mid, payload="message") +publish_dup_packet = mosq_test.gen_publish("pub/qos2/test", qos=2, mid=mid, payload="message", dup=True) +pubrec_packet = mosq_test.gen_pubrec(mid) +pubrel_packet = mosq_test.gen_pubrel(mid) +pubrel_dup_packet = mosq_test.gen_pubrel(mid, dup=True) +pubcomp_packet = mosq_test.gen_pubcomp(mid) + +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +sock.settimeout(10) +sock.bind(('', 1888)) +sock.listen(5) + +client_args = sys.argv[1:] +env = dict(os.environ) +env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp' +try: + pp = env['PYTHONPATH'] +except KeyError: + pp = '' +env['PYTHONPATH'] = '../../lib/python:'+pp +client = subprocess.Popen(client_args, env=env) + +try: + (conn, address) = sock.accept() + conn.settimeout(10) + + if mosq_test.expect_packet(conn, "connect", connect_packet): + conn.send(connack_packet) + + if mosq_test.expect_packet(conn, "publish", publish_packet): + # Delay for > 3 seconds (message retry time) + + if mosq_test.expect_packet(conn, "dup publish", publish_dup_packet): + conn.send(pubrec_packet) + + if mosq_test.expect_packet(conn, "pubrel", pubrel_packet): + if mosq_test.expect_packet(conn, "dup pubrel", pubrel_dup_packet): + conn.send(pubcomp_packet) + + if mosq_test.expect_packet(conn, "disconnect", disconnect_packet): + rc = 0 + + conn.close() +finally: + client.terminate() + client.wait() + sock.close() + +exit(rc) diff --git a/test/lib/03-publish-c2b-qos2.py b/test/lib/03-publish-c2b-qos2.py new file mode 100755 index 0000000000..e486ea11ff --- /dev/null +++ b/test/lib/03-publish-c2b-qos2.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python + +# Test whether a client sends a correct PUBLISH to a topic with QoS 2. + +# The client should connect to port 1888 with keepalive=60, clean session set, +# and client id publish-qos2-test +# The test will send a CONNACK message to the client with rc=0. Upon receiving +# the CONNACK the client should verify that rc==0. If not, it should exit with +# return code=1. +# On a successful CONNACK, the client should send a PUBLISH message with topic +# "pub/qos2/test", payload "message" and QoS=2. +# The test will not respond to the first PUBLISH message, so the client must +# resend the PUBLISH message with dup=1. Note that to keep test durations low, a +# message retry timeout of less than 10 seconds is required for this test. +# On receiving the second PUBLISH message, the test will send the correct +# PUBREC response. On receiving the correct PUBREC response, the client should +# send a PUBREL message. +# The test will not respond to the first PUBREL message, so the client must +# resend the PUBREL message with dup=1. On receiving the second PUBREL message, +# the test will send the correct PUBCOMP response. On receiving the correct +# PUBCOMP response, the client should send a DISCONNECT message. + +import inspect +import os +import subprocess +import socket +import sys +import time + +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +connect_packet = mosq_test.gen_connect("publish-qos2-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +disconnect_packet = mosq_test.gen_disconnect() + +mid = 1 +publish_packet = mosq_test.gen_publish("pub/qos2/test", qos=2, mid=mid, payload="message") +publish_dup_packet = mosq_test.gen_publish("pub/qos2/test", qos=2, mid=mid, payload="message", dup=True) +pubrec_packet = mosq_test.gen_pubrec(mid) +pubrel_packet = mosq_test.gen_pubrel(mid) +pubcomp_packet = mosq_test.gen_pubcomp(mid) + +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +sock.settimeout(10) +sock.bind(('', 1888)) +sock.listen(5) + +client_args = sys.argv[1:] +env = dict(os.environ) +env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp' +try: + pp = env['PYTHONPATH'] +except KeyError: + pp = '' +env['PYTHONPATH'] = '../../lib/python:'+pp +client = subprocess.Popen(client_args, env=env) + +try: + (conn, address) = sock.accept() + conn.settimeout(10) + + if mosq_test.expect_packet(conn, "connect", connect_packet): + conn.send(connack_packet) + + if mosq_test.expect_packet(conn, "publish", publish_packet): + conn.send(pubrec_packet) + + if mosq_test.expect_packet(conn, "pubrel", pubrel_packet): + conn.send(pubcomp_packet) + + if mosq_test.expect_packet(conn, "disconnect", disconnect_packet): + rc = 0 + + conn.close() +finally: + client.terminate() + client.wait() + sock.close() + +exit(rc) diff --git a/test/lib/03-publish-qos0-no-payload.py b/test/lib/03-publish-qos0-no-payload.py new file mode 100755 index 0000000000..58cf1bf5e0 --- /dev/null +++ b/test/lib/03-publish-qos0-no-payload.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python + +# Test whether a client sends a correct PUBLISH to a topic with QoS 0 and no payload. + +# The client should connect to port 1888 with keepalive=60, clean session set, +# and client id publish-qos0-test-np +# The test will send a CONNACK message to the client with rc=0. Upon receiving +# the CONNACK and verifying that rc=0, the client should send a PUBLISH message +# to topic "pub/qos0/no-payload/test" with zero length payload and QoS=0. If +# rc!=0, the client should exit with an error. +# After sending the PUBLISH message, the client should send a DISCONNECT message. + +import inspect +import os +import subprocess +import socket +import sys +import time + +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +connect_packet = mosq_test.gen_connect("publish-qos0-test-np", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +publish_packet = mosq_test.gen_publish("pub/qos0/no-payload/test", qos=0) + +disconnect_packet = mosq_test.gen_disconnect() + +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +sock.settimeout(10) +sock.bind(('', 1888)) +sock.listen(5) + +client_args = sys.argv[1:] +env = dict(os.environ) +env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp' +try: + pp = env['PYTHONPATH'] +except KeyError: + pp = '' +env['PYTHONPATH'] = '../../lib/python:'+pp +client = subprocess.Popen(client_args, env=env) + +try: + (conn, address) = sock.accept() + conn.settimeout(10) + + if mosq_test.expect_packet(conn, "connect", connect_packet): + conn.send(connack_packet) + + if mosq_test.expect_packet(conn, "publish", publish_packet): + if mosq_test.expect_packet(conn, "disconnect", disconnect_packet): + rc = 0 + + conn.close() +finally: + client.terminate() + client.wait() + sock.close() + +exit(rc) diff --git a/test/lib/03-publish-qos0.py b/test/lib/03-publish-qos0.py new file mode 100755 index 0000000000..5a6b5e585f --- /dev/null +++ b/test/lib/03-publish-qos0.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python + +# Test whether a client sends a correct PUBLISH to a topic with QoS 0. + +# The client should connect to port 1888 with keepalive=60, clean session set, +# and client id publish-qos0-test +# The test will send a CONNACK message to the client with rc=0. Upon receiving +# the CONNACK and verifying that rc=0, the client should send a PUBLISH message +# to topic "pub/qos0/test" with payload "message" and QoS=0. If rc!=0, the +# client should exit with an error. +# After sending the PUBLISH message, the client should send a DISCONNECT message. + +import inspect +import os +import subprocess +import socket +import sys +import time + +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +connect_packet = mosq_test.gen_connect("publish-qos0-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +publish_packet = mosq_test.gen_publish("pub/qos0/test", qos=0, payload="message") + +disconnect_packet = mosq_test.gen_disconnect() + +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +sock.settimeout(10) +sock.bind(('', 1888)) +sock.listen(5) + +client_args = sys.argv[1:] +env = dict(os.environ) +env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp' +try: + pp = env['PYTHONPATH'] +except KeyError: + pp = '' +env['PYTHONPATH'] = '../../lib/python:'+pp +client = subprocess.Popen(client_args, env=env) + +try: + (conn, address) = sock.accept() + conn.settimeout(10) + + if mosq_test.expect_packet(conn, "connect", connect_packet): + conn.send(connack_packet) + + if mosq_test.expect_packet(conn, "publish", publish_packet): + if mosq_test.expect_packet(conn, "disconnect", disconnect_packet): + rc = 0 + + conn.close() +finally: + client.terminate() + client.wait() + sock.close() + +exit(rc) diff --git a/test/lib/04-retain-qos0.py b/test/lib/04-retain-qos0.py new file mode 100755 index 0000000000..70d7882b77 --- /dev/null +++ b/test/lib/04-retain-qos0.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python + +# Test whether a client sends a correct retained PUBLISH to a topic with QoS 0. + +import inspect +import os +import subprocess +import socket +import sys +import time + +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +rc = 1 +keepalive = 60 +mid = 16 +connect_packet = mosq_test.gen_connect("retain-qos0-test", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) + +publish_packet = mosq_test.gen_publish("retain/qos0/test", qos=0, payload="retained message", retain=True) + +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +sock.settimeout(10) +sock.bind(('', 1888)) +sock.listen(5) + +client_args = sys.argv[1:] +env = dict(os.environ) +env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp' +try: + pp = env['PYTHONPATH'] +except KeyError: + pp = '' +env['PYTHONPATH'] = '../../lib/python:'+pp +client = subprocess.Popen(client_args, env=env) + +try: + (conn, address) = sock.accept() + conn.settimeout(10) + + if mosq_test.expect_packet(conn, "connect", connect_packet): + conn.send(connack_packet) + + if mosq_test.expect_packet(conn, "publish", publish_packet): + rc = 0 + + conn.close() +finally: + client.terminate() + client.wait() + sock.close() + +exit(rc) diff --git a/test/lib/08-ssl-bad-cacert.py b/test/lib/08-ssl-bad-cacert.py new file mode 100755 index 0000000000..cf2861f86c --- /dev/null +++ b/test/lib/08-ssl-bad-cacert.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python + +import inspect +import os +import subprocess +import socket +import ssl +import sys +import time + +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +if sys.version < '2.7': + print("WARNING: SSL not supported on Python 2.6") + exit(0) + +rc = 1 + +client_args = sys.argv[1:] +env = dict(os.environ) +env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp' +try: + pp = env['PYTHONPATH'] +except KeyError: + pp = '' +env['PYTHONPATH'] = '../../lib/python:'+pp + +client = subprocess.Popen(client_args, env=env) +client.wait() + +rc = client.returncode + +exit(rc) diff --git a/test/lib/08-ssl-connect-cert-auth.py b/test/lib/08-ssl-connect-cert-auth.py new file mode 100755 index 0000000000..bab0792727 --- /dev/null +++ b/test/lib/08-ssl-connect-cert-auth.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python + +# Test whether a client produces a correct connect and subsequent disconnect when using SSL. +# Client must provide a certificate. + +# The client should connect to port 1888 with keepalive=60, clean session set, +# and client id 08-ssl-connect-crt-auth +# It should use the CA certificate ssl/test-root-ca.crt for verifying the server. +# The test will send a CONNACK message to the client with rc=0. Upon receiving +# the CONNACK and verifying that rc=0, the client should send a DISCONNECT +# message. If rc!=0, the client should exit with an error. + +import inspect +import os +import subprocess +import socket +import ssl +import sys +import time + +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +if sys.version < '2.7': + print("WARNING: SSL not supported on Python 2.6") + exit(0) + +rc = 1 +keepalive = 60 +connect_packet = mosq_test.gen_connect("08-ssl-connect-crt-auth", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) +disconnect_packet = mosq_test.gen_disconnect() + +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +ssock = ssl.wrap_socket(sock, ca_certs="../ssl/all-ca.crt", + keyfile="../ssl/server.key", certfile="../ssl/server.crt", + server_side=True, ssl_version=ssl.PROTOCOL_TLSv1, cert_reqs=ssl.CERT_REQUIRED) +ssock.settimeout(10) +ssock.bind(('', 1888)) +ssock.listen(5) + +client_args = sys.argv[1:] +env = dict(os.environ) +env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp' +try: + pp = env['PYTHONPATH'] +except KeyError: + pp = '' +env['PYTHONPATH'] = '../../lib/python:'+pp +client = subprocess.Popen(client_args, env=env) + +try: + (conn, address) = ssock.accept() + conn.settimeout(10) + + if mosq_test.expect_packet(conn, "connect", connect_packet): + conn.send(connack_packet) + + if mosq_test.expect_packet(conn, "disconnect", disconnect_packet): + rc = 0 + + conn.close() +finally: + client.terminate() + client.wait() + ssock.close() + +exit(rc) diff --git a/test/lib/08-ssl-connect-no-auth.py b/test/lib/08-ssl-connect-no-auth.py new file mode 100755 index 0000000000..b05c61ca3d --- /dev/null +++ b/test/lib/08-ssl-connect-no-auth.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python + +# Test whether a client produces a correct connect and subsequent disconnect when using SSL. + +# The client should connect to port 1888 with keepalive=60, clean session set, +# and client id 08-ssl-connect-no-auth +# It should use the CA certificate ssl/test-ca.crt for verifying the server. +# The test will send a CONNACK message to the client with rc=0. Upon receiving +# the CONNACK and verifying that rc=0, the client should send a DISCONNECT +# message. If rc!=0, the client should exit with an error. + +import inspect +import os +import subprocess +import socket +import ssl +import sys +import time + +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +if sys.version < '2.7': + print("WARNING: SSL not supported on Python 2.6") + exit(0) + +rc = 1 +keepalive = 60 +connect_packet = mosq_test.gen_connect("08-ssl-connect-no-auth", keepalive=keepalive) +connack_packet = mosq_test.gen_connack(rc=0) +disconnect_packet = mosq_test.gen_disconnect() + +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +ssock = ssl.wrap_socket(sock, ca_certs="../ssl/all-ca.crt", keyfile="../ssl/server.key", certfile="../ssl/server.crt", server_side=True, ssl_version=ssl.PROTOCOL_TLSv1) +ssock.settimeout(10) +ssock.bind(('', 1888)) +ssock.listen(5) + +client_args = sys.argv[1:] +env = dict(os.environ) +env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp' +try: + pp = env['PYTHONPATH'] +except KeyError: + pp = '' +env['PYTHONPATH'] = '../../lib/python:'+pp +client = subprocess.Popen(client_args, env=env) + +try: + (conn, address) = ssock.accept() + conn.settimeout(100) + + if mosq_test.expect_packet(conn, "connect", connect_packet): + conn.send(connack_packet) + + if mosq_test.expect_packet(conn, "disconnect", disconnect_packet): + rc = 0 + + conn.close() +finally: + client.terminate() + client.wait() + ssock.close() + +exit(rc) diff --git a/test/lib/08-ssl-fake-cacert.py b/test/lib/08-ssl-fake-cacert.py new file mode 100755 index 0000000000..275b57e64c --- /dev/null +++ b/test/lib/08-ssl-fake-cacert.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python + +import inspect +import os +import subprocess +import socket +import ssl +import sys +import time + +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +import mosq_test + +if sys.version < '2.7': + print("WARNING: SSL not supported on Python 2.6") + exit(0) + +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +ssock = ssl.wrap_socket(sock, ca_certs="../ssl/all-ca.crt", + keyfile="../ssl/server.key", certfile="../ssl/server.crt", + server_side=True, ssl_version=ssl.PROTOCOL_TLSv1, cert_reqs=ssl.CERT_REQUIRED) +ssock.settimeout(10) +ssock.bind(('', 1888)) +ssock.listen(5) + +client_args = sys.argv[1:] +env = dict(os.environ) +env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp' +try: + pp = env['PYTHONPATH'] +except KeyError: + pp = '' +env['PYTHONPATH'] = '../../lib/python:'+pp +client = subprocess.Popen(client_args, env=env) + +try: + (conn, address) = ssock.accept() + + conn.close() +except ssl.SSLError: + # Expected error due to ca certs not matching. + pass +finally: + time.sleep(1.0) + try: + client.terminate() + except OSError: + pass + client.wait() + ssock.close() + +if client.returncode == 0: + exit(0) +else: + exit(1) + diff --git a/test/lib/09-util-topic-matching.py b/test/lib/09-util-topic-matching.py new file mode 100755 index 0000000000..b678bc6cc3 --- /dev/null +++ b/test/lib/09-util-topic-matching.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python + +import inspect +import os +import subprocess +import sys +import time + +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +rc = 1 + +client_args = sys.argv[1:] +env = dict(os.environ) +env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp' +try: + pp = env['PYTHONPATH'] +except KeyError: + pp = '' +env['PYTHONPATH'] = '../../lib/python:'+pp + +client = subprocess.Popen(client_args, env=env) +client.wait() +if client.returncode: + (stdo, stde) = client.communicate() + print(stdo) +exit(client.returncode) diff --git a/test/lib/09-util-topic-tokenise.py b/test/lib/09-util-topic-tokenise.py new file mode 100755 index 0000000000..0625e0286d --- /dev/null +++ b/test/lib/09-util-topic-tokenise.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python + +import inspect +import os +import subprocess +import sys +import time + +# From http://stackoverflow.com/questions/279237/python-import-a-module-from-a-folder +cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],".."))) +if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + +rc = 1 + +client_args = sys.argv[1:] +env = dict(os.environ) +env['LD_LIBRARY_PATH'] = '../../lib:../../lib/cpp' +try: + pp = env['PYTHONPATH'] +except KeyError: + pp = '' +env['PYTHONPATH'] = '../../lib/python:'+pp + +client = subprocess.Popen(client_args, env=env) +client.wait() +exit(client.returncode) diff --git a/test/lib/Makefile b/test/lib/Makefile new file mode 100644 index 0000000000..187901bf51 --- /dev/null +++ b/test/lib/Makefile @@ -0,0 +1,48 @@ +.PHONY: all test test-compile test-compile-c test-compile-cpp c cpp +.NOTPARALLEL: + +LD_LIBRARY_PATH=../../lib + +all : + +test : c cpp + +test-compile : test-compile-c test-compile-cpp + +test-compile-c : + $(MAKE) -C c + +test-compile-cpp : + $(MAKE) -C cpp + +c cpp : test-compile + ./01-con-discon-success.py $@/01-con-discon-success.test + ./01-will-set.py $@/01-will-set.test + ./01-unpwd-set.py $@/01-unpwd-set.test + ./01-will-unpwd-set.py $@/01-will-unpwd-set.test + ./01-no-clean-session.py $@/01-no-clean-session.test + ./01-keepalive-pingreq.py $@/01-keepalive-pingreq.test + ./02-subscribe-qos0.py $@/02-subscribe-qos0.test + ./02-subscribe-qos1.py $@/02-subscribe-qos1.test + ./02-subscribe-qos2.py $@/02-subscribe-qos2.test + ./02-unsubscribe.py $@/02-unsubscribe.test + ./03-publish-qos0.py $@/03-publish-qos0.test + ./03-publish-qos0-no-payload.py $@/03-publish-qos0-no-payload.test + ./03-publish-c2b-qos1-timeout.py $@/03-publish-c2b-qos1-timeout.test + ./03-publish-c2b-qos1-disconnect.py $@/03-publish-c2b-qos1-disconnect.test + ./03-publish-c2b-qos2.py $@/03-publish-c2b-qos2.test + ./03-publish-c2b-qos2-timeout.py $@/03-publish-c2b-qos2-timeout.test + ./03-publish-c2b-qos2-disconnect.py $@/03-publish-c2b-qos2-disconnect.test + ./03-publish-b2c-qos1.py $@/03-publish-b2c-qos1.test + ./03-publish-b2c-qos2.py $@/03-publish-b2c-qos2.test + ./04-retain-qos0.py $@/04-retain-qos0.test + ./08-ssl-connect-no-auth.py $@/08-ssl-connect-no-auth.test + ./08-ssl-connect-cert-auth.py $@/08-ssl-connect-cert-auth.test + ./08-ssl-bad-cacert.py $@/08-ssl-bad-cacert.test + ./08-ssl-fake-cacert.py $@/08-ssl-fake-cacert.test + ./09-util-topic-matching.py $@/09-util-topic-matching.test + ./09-util-topic-tokenise.py $@/09-util-topic-tokenise.test + +clean : + $(MAKE) -C c clean + $(MAKE) -C cpp clean diff --git a/test/lib/c/01-con-discon-success.c b/test/lib/c/01-con-discon-success.c new file mode 100644 index 0000000000..9fac2f694c --- /dev/null +++ b/test/lib/c/01-con-discon-success.c @@ -0,0 +1,41 @@ +#include +#include +#include +#include + +static int run = -1; + +void on_connect(struct mosquitto *mosq, void *obj, int rc) +{ + if(rc){ + exit(1); + }else{ + mosquitto_disconnect(mosq); + } +} + +void on_disconnect(struct mosquitto *mosq, void *obj, int rc) +{ + run = rc; +} + +int main(int argc, char *argv[]) +{ + int rc; + struct mosquitto *mosq; + + mosquitto_lib_init(); + + mosq = mosquitto_new("01-con-discon-success", true, NULL); + mosquitto_connect_callback_set(mosq, on_connect); + mosquitto_disconnect_callback_set(mosq, on_disconnect); + + rc = mosquitto_connect(mosq, "localhost", 1888, 60); + + while(run == -1){ + mosquitto_loop(mosq, -1, 1); + } + + mosquitto_lib_cleanup(); + return run; +} diff --git a/test/lib/c/01-keepalive-pingreq.c b/test/lib/c/01-keepalive-pingreq.c new file mode 100644 index 0000000000..427a535110 --- /dev/null +++ b/test/lib/c/01-keepalive-pingreq.c @@ -0,0 +1,33 @@ +#include +#include +#include +#include + +static int run = -1; + +void on_connect(struct mosquitto *mosq, void *obj, int rc) +{ + if(rc){ + exit(1); + } +} + +int main(int argc, char *argv[]) +{ + int rc; + struct mosquitto *mosq; + + mosquitto_lib_init(); + + mosq = mosquitto_new("01-keepalive-pingreq", true, NULL); + mosquitto_connect_callback_set(mosq, on_connect); + + rc = mosquitto_connect(mosq, "localhost", 1888, 4); + + while(run == -1){ + mosquitto_loop(mosq, -1, 1); + } + + mosquitto_lib_cleanup(); + return run; +} diff --git a/test/lib/c/01-no-clean-session.c b/test/lib/c/01-no-clean-session.c new file mode 100644 index 0000000000..43f7951915 --- /dev/null +++ b/test/lib/c/01-no-clean-session.c @@ -0,0 +1,25 @@ +#include +#include +#include +#include +#include + +static int run = -1; +int main(int argc, char *argv[]) +{ + int rc; + struct mosquitto *mosq; + + mosquitto_lib_init(); + + mosq = mosquitto_new("01-no-clean-session", false, NULL); + + rc = mosquitto_connect(mosq, "localhost", 1888, 60); + + while(run == -1){ + mosquitto_loop(mosq, -1, 1); + } + + mosquitto_lib_cleanup(); + return run; +} diff --git a/test/lib/c/01-unpwd-set.c b/test/lib/c/01-unpwd-set.c new file mode 100644 index 0000000000..bec71640c2 --- /dev/null +++ b/test/lib/c/01-unpwd-set.c @@ -0,0 +1,26 @@ +#include +#include +#include +#include +#include + +static int run = -1; +int main(int argc, char *argv[]) +{ + int rc; + struct mosquitto *mosq; + + mosquitto_lib_init(); + + mosq = mosquitto_new("01-unpwd-set", true, NULL); + mosquitto_username_pw_set(mosq, "uname", ";'[08gn=#"); + + rc = mosquitto_connect(mosq, "localhost", 1888, 60); + + while(run == -1){ + mosquitto_loop(mosq, -1, 1); + } + + mosquitto_lib_cleanup(); + return run; +} diff --git a/test/lib/c/01-will-set.c b/test/lib/c/01-will-set.c new file mode 100644 index 0000000000..041eef790a --- /dev/null +++ b/test/lib/c/01-will-set.c @@ -0,0 +1,26 @@ +#include +#include +#include +#include +#include + +static int run = -1; +int main(int argc, char *argv[]) +{ + int rc; + struct mosquitto *mosq; + + mosquitto_lib_init(); + + mosq = mosquitto_new("01-will-set", true, NULL); + mosquitto_will_set(mosq, "topic/on/unexpected/disconnect", strlen("will message"), "will message", 1, true); + + rc = mosquitto_connect(mosq, "localhost", 1888, 60); + + while(run == -1){ + mosquitto_loop(mosq, -1, 1); + } + + mosquitto_lib_cleanup(); + return run; +} diff --git a/test/lib/c/01-will-unpwd-set.c b/test/lib/c/01-will-unpwd-set.c new file mode 100644 index 0000000000..af812f0d05 --- /dev/null +++ b/test/lib/c/01-will-unpwd-set.c @@ -0,0 +1,27 @@ +#include +#include +#include +#include +#include + +static int run = -1; +int main(int argc, char *argv[]) +{ + int rc; + struct mosquitto *mosq; + + mosquitto_lib_init(); + + mosq = mosquitto_new("01-will-unpwd-set", true, NULL); + mosquitto_username_pw_set(mosq, "oibvvwqw", "#'^2hg9a&nm38*us"); + mosquitto_will_set(mosq, "will-topic", strlen("will message"), "will message", 2, false); + + rc = mosquitto_connect(mosq, "localhost", 1888, 60); + + while(run == -1){ + mosquitto_loop(mosq, -1, 1); + } + + mosquitto_lib_cleanup(); + return run; +} diff --git a/test/lib/c/02-subscribe-qos0.c b/test/lib/c/02-subscribe-qos0.c new file mode 100644 index 0000000000..9763f6aa8b --- /dev/null +++ b/test/lib/c/02-subscribe-qos0.c @@ -0,0 +1,47 @@ +#include +#include +#include +#include + +static int run = -1; + +void on_connect(struct mosquitto *mosq, void *obj, int rc) +{ + if(rc){ + exit(1); + }else{ + mosquitto_subscribe(mosq, NULL, "qos0/test", 0); + } +} + +void on_disconnect(struct mosquitto *mosq, void *obj, int rc) +{ + run = rc; +} + +void on_subscribe(struct mosquitto *mosq, void *obj, int mid, int qos_count, const int *granted_qos) +{ + mosquitto_disconnect(mosq); +} + +int main(int argc, char *argv[]) +{ + int rc; + struct mosquitto *mosq; + + mosquitto_lib_init(); + + mosq = mosquitto_new("subscribe-qos0-test", true, NULL); + mosquitto_connect_callback_set(mosq, on_connect); + mosquitto_disconnect_callback_set(mosq, on_disconnect); + mosquitto_subscribe_callback_set(mosq, on_subscribe); + + rc = mosquitto_connect(mosq, "localhost", 1888, 60); + + while(run == -1){ + mosquitto_loop(mosq, -1, 1); + } + + mosquitto_lib_cleanup(); + return run; +} diff --git a/test/lib/c/02-subscribe-qos1.c b/test/lib/c/02-subscribe-qos1.c new file mode 100644 index 0000000000..1f4677a897 --- /dev/null +++ b/test/lib/c/02-subscribe-qos1.c @@ -0,0 +1,47 @@ +#include +#include +#include +#include + +static int run = -1; + +void on_connect(struct mosquitto *mosq, void *obj, int rc) +{ + if(rc){ + exit(1); + }else{ + mosquitto_subscribe(mosq, NULL, "qos1/test", 1); + } +} + +void on_disconnect(struct mosquitto *mosq, void *obj, int rc) +{ + run = rc; +} + +void on_subscribe(struct mosquitto *mosq, void *obj, int mid, int qos_count, const int *granted_qos) +{ + mosquitto_disconnect(mosq); +} + +int main(int argc, char *argv[]) +{ + int rc; + struct mosquitto *mosq; + + mosquitto_lib_init(); + + mosq = mosquitto_new("subscribe-qos1-test", true, NULL); + mosquitto_connect_callback_set(mosq, on_connect); + mosquitto_disconnect_callback_set(mosq, on_disconnect); + mosquitto_subscribe_callback_set(mosq, on_subscribe); + + rc = mosquitto_connect(mosq, "localhost", 1888, 60); + + while(run == -1){ + mosquitto_loop(mosq, -1, 1); + } + + mosquitto_lib_cleanup(); + return run; +} diff --git a/test/lib/c/02-subscribe-qos2.c b/test/lib/c/02-subscribe-qos2.c new file mode 100644 index 0000000000..7832e8b64a --- /dev/null +++ b/test/lib/c/02-subscribe-qos2.c @@ -0,0 +1,47 @@ +#include +#include +#include +#include + +static int run = -1; + +void on_connect(struct mosquitto *mosq, void *obj, int rc) +{ + if(rc){ + exit(1); + }else{ + mosquitto_subscribe(mosq, NULL, "qos2/test", 2); + } +} + +void on_disconnect(struct mosquitto *mosq, void *obj, int rc) +{ + run = rc; +} + +void on_subscribe(struct mosquitto *mosq, void *obj, int mid, int qos_count, const int *granted_qos) +{ + mosquitto_disconnect(mosq); +} + +int main(int argc, char *argv[]) +{ + int rc; + struct mosquitto *mosq; + + mosquitto_lib_init(); + + mosq = mosquitto_new("subscribe-qos2-test", true, NULL); + mosquitto_connect_callback_set(mosq, on_connect); + mosquitto_disconnect_callback_set(mosq, on_disconnect); + mosquitto_subscribe_callback_set(mosq, on_subscribe); + + rc = mosquitto_connect(mosq, "localhost", 1888, 60); + + while(run == -1){ + mosquitto_loop(mosq, -1, 1); + } + + mosquitto_lib_cleanup(); + return run; +} diff --git a/test/lib/c/02-unsubscribe.c b/test/lib/c/02-unsubscribe.c new file mode 100644 index 0000000000..406401fec0 --- /dev/null +++ b/test/lib/c/02-unsubscribe.c @@ -0,0 +1,47 @@ +#include +#include +#include +#include + +static int run = -1; + +void on_connect(struct mosquitto *mosq, void *obj, int rc) +{ + if(rc){ + exit(1); + }else{ + mosquitto_unsubscribe(mosq, NULL, "unsubscribe/test"); + } +} + +void on_disconnect(struct mosquitto *mosq, void *obj, int rc) +{ + run = rc; +} + +void on_unsubscribe(struct mosquitto *mosq, void *obj, int mid) +{ + mosquitto_disconnect(mosq); +} + +int main(int argc, char *argv[]) +{ + int rc; + struct mosquitto *mosq; + + mosquitto_lib_init(); + + mosq = mosquitto_new("unsubscribe-test", true, NULL); + mosquitto_connect_callback_set(mosq, on_connect); + mosquitto_disconnect_callback_set(mosq, on_disconnect); + mosquitto_unsubscribe_callback_set(mosq, on_unsubscribe); + + rc = mosquitto_connect(mosq, "localhost", 1888, 60); + + while(run == -1){ + mosquitto_loop(mosq, -1, 1); + } + + mosquitto_lib_cleanup(); + return run; +} diff --git a/test/lib/c/03-publish-b2c-qos1.c b/test/lib/c/03-publish-b2c-qos1.c new file mode 100644 index 0000000000..64a06ea0ad --- /dev/null +++ b/test/lib/c/03-publish-b2c-qos1.c @@ -0,0 +1,64 @@ +#include +#include +#include +#include +#include + +void on_connect(struct mosquitto *mosq, void *obj, int rc) +{ + if(rc){ + exit(1); + } +} + +void on_message(struct mosquitto *mosq, void *obj, const struct mosquitto_message *msg) +{ + if(msg->mid != 123){ + printf("Invalid mid (%d)\n", msg->mid); + exit(1); + } + if(msg->qos != 1){ + printf("Invalid qos (%d)\n", msg->qos); + exit(1); + } + if(strcmp(msg->topic, "pub/qos1/receive")){ + printf("Invalid topic (%s)\n", msg->topic); + exit(1); + } + if(strcmp(msg->payload, "message")){ + printf("Invalid payload (%s)\n", (char *)msg->payload); + exit(1); + } + if(msg->payloadlen != 7){ + printf("Invalid payloadlen (%d)\n", msg->payloadlen); + exit(1); + } + if(msg->retain != false){ + printf("Invalid retain (%d)\n", msg->retain); + exit(1); + } + + exit(0); +} + +int main(int argc, char *argv[]) +{ + int rc; + struct mosquitto *mosq; + + mosquitto_lib_init(); + + mosq = mosquitto_new("publish-qos1-test", true, NULL); + mosquitto_connect_callback_set(mosq, on_connect); + mosquitto_message_callback_set(mosq, on_message); + mosquitto_message_retry_set(mosq, 3); + + rc = mosquitto_connect(mosq, "localhost", 1888, 60); + + while(1){ + mosquitto_loop(mosq, 300, 1); + } + + mosquitto_lib_cleanup(); + return 1; +} diff --git a/test/lib/c/03-publish-b2c-qos2.c b/test/lib/c/03-publish-b2c-qos2.c new file mode 100644 index 0000000000..9e1a0cc85e --- /dev/null +++ b/test/lib/c/03-publish-b2c-qos2.c @@ -0,0 +1,66 @@ +#include +#include +#include +#include +#include + +static int run = -1; + +void on_connect(struct mosquitto *mosq, void *obj, int rc) +{ + if(rc){ + exit(1); + } +} + +void on_message(struct mosquitto *mosq, void *obj, const struct mosquitto_message *msg) +{ + if(msg->mid != 13423){ + printf("Invalid mid (%d)\n", msg->mid); + exit(1); + } + if(msg->qos != 2){ + printf("Invalid qos (%d)\n", msg->qos); + exit(1); + } + if(strcmp(msg->topic, "pub/qos2/receive")){ + printf("Invalid topic (%s)\n", msg->topic); + exit(1); + } + if(strcmp(msg->payload, "message")){ + printf("Invalid payload (%s)\n", (char *)msg->payload); + exit(1); + } + if(msg->payloadlen != 7){ + printf("Invalid payloadlen (%d)\n", msg->payloadlen); + exit(1); + } + if(msg->retain != false){ + printf("Invalid retain (%d)\n", msg->retain); + exit(1); + } + + run = 0; +} + +int main(int argc, char *argv[]) +{ + int rc; + struct mosquitto *mosq; + + mosquitto_lib_init(); + + mosq = mosquitto_new("publish-qos2-test", true, &run); + mosquitto_connect_callback_set(mosq, on_connect); + mosquitto_message_callback_set(mosq, on_message); + mosquitto_message_retry_set(mosq, 5); + + rc = mosquitto_connect(mosq, "localhost", 1888, 60); + + while(run == -1){ + mosquitto_loop(mosq, 300, 1); + } + + mosquitto_lib_cleanup(); + return run; +} diff --git a/test/lib/c/03-publish-c2b-qos1-disconnect.c b/test/lib/c/03-publish-c2b-qos1-disconnect.c new file mode 100644 index 0000000000..51b76a6041 --- /dev/null +++ b/test/lib/c/03-publish-c2b-qos1-disconnect.c @@ -0,0 +1,57 @@ +#include +#include +#include +#include +#include + +static int run = -1; +static int first_connection = 1; + +void on_connect(struct mosquitto *mosq, void *obj, int rc) +{ + if(rc){ + exit(1); + }else{ + if(first_connection == 1){ + mosquitto_publish(mosq, NULL, "pub/qos1/test", strlen("message"), "message", 1, false); + first_connection = 0; + } + } +} + +void on_publish(struct mosquitto *mosq, void *obj, int mid) +{ + mosquitto_disconnect(mosq); +} + +void on_disconnect(struct mosquitto *mosq, void *obj, int rc) +{ + if(rc){ + mosquitto_reconnect(mosq); + }else{ + run = 0; + } +} + +int main(int argc, char *argv[]) +{ + int rc; + struct mosquitto *mosq; + + mosquitto_lib_init(); + + mosq = mosquitto_new("publish-qos1-test", true, NULL); + mosquitto_connect_callback_set(mosq, on_connect); + mosquitto_disconnect_callback_set(mosq, on_disconnect); + mosquitto_publish_callback_set(mosq, on_publish); + mosquitto_message_retry_set(mosq, 3); + + rc = mosquitto_connect(mosq, "localhost", 1888, 60); + + while(run == -1){ + mosquitto_loop(mosq, 300, 1); + } + + mosquitto_lib_cleanup(); + return run; +} diff --git a/test/lib/c/03-publish-c2b-qos1-timeout.c b/test/lib/c/03-publish-c2b-qos1-timeout.c new file mode 100644 index 0000000000..b01473f1a9 --- /dev/null +++ b/test/lib/c/03-publish-c2b-qos1-timeout.c @@ -0,0 +1,49 @@ +#include +#include +#include +#include +#include + +static int run = -1; + +void on_connect(struct mosquitto *mosq, void *obj, int rc) +{ + if(rc){ + exit(1); + }else{ + mosquitto_publish(mosq, NULL, "pub/qos1/test", strlen("message"), "message", 1, false); + } +} + +void on_publish(struct mosquitto *mosq, void *obj, int mid) +{ + mosquitto_disconnect(mosq); +} + +void on_disconnect(struct mosquitto *mosq, void *obj, int rc) +{ + run = 0; +} + +int main(int argc, char *argv[]) +{ + int rc; + struct mosquitto *mosq; + + mosquitto_lib_init(); + + mosq = mosquitto_new("publish-qos1-test", true, NULL); + mosquitto_connect_callback_set(mosq, on_connect); + mosquitto_disconnect_callback_set(mosq, on_disconnect); + mosquitto_publish_callback_set(mosq, on_publish); + mosquitto_message_retry_set(mosq, 3); + + rc = mosquitto_connect(mosq, "localhost", 1888, 60); + + while(run == -1){ + mosquitto_loop(mosq, 300, 1); + } + + mosquitto_lib_cleanup(); + return run; +} diff --git a/test/lib/c/03-publish-c2b-qos2-disconnect.c b/test/lib/c/03-publish-c2b-qos2-disconnect.c new file mode 100644 index 0000000000..c9575263d3 --- /dev/null +++ b/test/lib/c/03-publish-c2b-qos2-disconnect.c @@ -0,0 +1,57 @@ +#include +#include +#include +#include +#include + +static int run = -1; +static int first_connection = 1; + +void on_connect(struct mosquitto *mosq, void *obj, int rc) +{ + if(rc){ + exit(1); + }else{ + if(first_connection == 1){ + mosquitto_publish(mosq, NULL, "pub/qos2/test", strlen("message"), "message", 2, false); + first_connection = 0; + } + } +} + +void on_publish(struct mosquitto *mosq, void *obj, int mid) +{ + mosquitto_disconnect(mosq); +} + +void on_disconnect(struct mosquitto *mosq, void *obj, int rc) +{ + if(rc){ + mosquitto_reconnect(mosq); + }else{ + run = 0; + } +} + +int main(int argc, char *argv[]) +{ + int rc; + struct mosquitto *mosq; + + mosquitto_lib_init(); + + mosq = mosquitto_new("publish-qos2-test", true, NULL); + mosquitto_connect_callback_set(mosq, on_connect); + mosquitto_disconnect_callback_set(mosq, on_disconnect); + mosquitto_publish_callback_set(mosq, on_publish); + mosquitto_message_retry_set(mosq, 3); + + rc = mosquitto_connect(mosq, "localhost", 1888, 60); + + while(run == -1){ + mosquitto_loop(mosq, 300, 1); + } + + mosquitto_lib_cleanup(); + return run; +} diff --git a/test/lib/c/03-publish-c2b-qos2-timeout.c b/test/lib/c/03-publish-c2b-qos2-timeout.c new file mode 100644 index 0000000000..f4c379e9fa --- /dev/null +++ b/test/lib/c/03-publish-c2b-qos2-timeout.c @@ -0,0 +1,49 @@ +#include +#include +#include +#include +#include + +static int run = -1; + +void on_connect(struct mosquitto *mosq, void *obj, int rc) +{ + if(rc){ + exit(1); + }else{ + mosquitto_publish(mosq, NULL, "pub/qos2/test", strlen("message"), "message", 2, false); + } +} + +void on_publish(struct mosquitto *mosq, void *obj, int mid) +{ + mosquitto_disconnect(mosq); +} + +void on_disconnect(struct mosquitto *mosq, void *obj, int rc) +{ + run = 0; +} + +int main(int argc, char *argv[]) +{ + int rc; + struct mosquitto *mosq; + + mosquitto_lib_init(); + + mosq = mosquitto_new("publish-qos2-test", true, NULL); + mosquitto_connect_callback_set(mosq, on_connect); + mosquitto_disconnect_callback_set(mosq, on_disconnect); + mosquitto_publish_callback_set(mosq, on_publish); + mosquitto_message_retry_set(mosq, 3); + + rc = mosquitto_connect(mosq, "localhost", 1888, 60); + + while(run == -1){ + mosquitto_loop(mosq, 300, 1); + } + + mosquitto_lib_cleanup(); + return run; +} diff --git a/test/lib/c/03-publish-c2b-qos2.c b/test/lib/c/03-publish-c2b-qos2.c new file mode 100644 index 0000000000..2c14818fbb --- /dev/null +++ b/test/lib/c/03-publish-c2b-qos2.c @@ -0,0 +1,48 @@ +#include +#include +#include +#include +#include + +static int run = -1; + +void on_connect(struct mosquitto *mosq, void *obj, int rc) +{ + if(rc){ + exit(1); + }else{ + mosquitto_publish(mosq, NULL, "pub/qos2/test", strlen("message"), "message", 2, false); + } +} + +void on_publish(struct mosquitto *mosq, void *obj, int mid) +{ + mosquitto_disconnect(mosq); +} + +void on_disconnect(struct mosquitto *mosq, void *obj, int rc) +{ + run = 0; +} + +int main(int argc, char *argv[]) +{ + int rc; + struct mosquitto *mosq; + + mosquitto_lib_init(); + + mosq = mosquitto_new("publish-qos2-test", true, NULL); + mosquitto_connect_callback_set(mosq, on_connect); + mosquitto_disconnect_callback_set(mosq, on_disconnect); + mosquitto_publish_callback_set(mosq, on_publish); + + rc = mosquitto_connect(mosq, "localhost", 1888, 60); + + while(run == -1){ + mosquitto_loop(mosq, 300, 1); + } + + mosquitto_lib_cleanup(); + return run; +} diff --git a/test/lib/c/03-publish-qos0-no-payload.c b/test/lib/c/03-publish-qos0-no-payload.c new file mode 100644 index 0000000000..8757790958 --- /dev/null +++ b/test/lib/c/03-publish-qos0-no-payload.c @@ -0,0 +1,48 @@ +#include +#include +#include +#include +#include + +static int run = -1; +static int sent_mid = -1; + +void on_connect(struct mosquitto *mosq, void *obj, int rc) +{ + if(rc){ + exit(1); + }else{ + mosquitto_publish(mosq, &sent_mid, "pub/qos0/no-payload/test", 0, NULL, 0, false); + } +} + +void on_publish(struct mosquitto *mosq, void *obj, int mid) +{ + if(mid == sent_mid){ + mosquitto_disconnect(mosq); + run = 0; + }else{ + exit(1); + } +} + +int main(int argc, char *argv[]) +{ + int rc; + struct mosquitto *mosq; + + mosquitto_lib_init(); + + mosq = mosquitto_new("publish-qos0-test-np", true, NULL); + mosquitto_connect_callback_set(mosq, on_connect); + mosquitto_publish_callback_set(mosq, on_publish); + + rc = mosquitto_connect(mosq, "localhost", 1888, 60); + + while(run == -1){ + mosquitto_loop(mosq, -1, 1); + } + + mosquitto_lib_cleanup(); + return run; +} diff --git a/test/lib/c/03-publish-qos0.c b/test/lib/c/03-publish-qos0.c new file mode 100644 index 0000000000..0bb769212b --- /dev/null +++ b/test/lib/c/03-publish-qos0.c @@ -0,0 +1,48 @@ +#include +#include +#include +#include +#include + +static int run = -1; +static int sent_mid = -1; + +void on_connect(struct mosquitto *mosq, void *obj, int rc) +{ + if(rc){ + exit(1); + }else{ + mosquitto_publish(mosq, &sent_mid, "pub/qos0/test", strlen("message"), "message", 0, false); + } +} + +void on_publish(struct mosquitto *mosq, void *obj, int mid) +{ + if(mid == sent_mid){ + mosquitto_disconnect(mosq); + run = 0; + }else{ + exit(1); + } +} + +int main(int argc, char *argv[]) +{ + int rc; + struct mosquitto *mosq; + + mosquitto_lib_init(); + + mosq = mosquitto_new("publish-qos0-test", true, NULL); + mosquitto_connect_callback_set(mosq, on_connect); + mosquitto_publish_callback_set(mosq, on_publish); + + rc = mosquitto_connect(mosq, "localhost", 1888, 60); + + while(run == -1){ + mosquitto_loop(mosq, -1, 1); + } + + mosquitto_lib_cleanup(); + return run; +} diff --git a/test/lib/c/04-retain-qos0.c b/test/lib/c/04-retain-qos0.c new file mode 100644 index 0000000000..c2a96784be --- /dev/null +++ b/test/lib/c/04-retain-qos0.c @@ -0,0 +1,36 @@ +#include +#include +#include +#include +#include + +static int run = -1; + +void on_connect(struct mosquitto *mosq, void *obj, int rc) +{ + if(rc){ + exit(1); + }else{ + mosquitto_publish(mosq, NULL, "retain/qos0/test", strlen("retained message"), "retained message", 0, true); + } +} + +int main(int argc, char *argv[]) +{ + int rc; + struct mosquitto *mosq; + + mosquitto_lib_init(); + + mosq = mosquitto_new("retain-qos0-test", true, NULL); + mosquitto_connect_callback_set(mosq, on_connect); + + rc = mosquitto_connect(mosq, "localhost", 1888, 60); + + while(run == -1){ + mosquitto_loop(mosq, -1, 1); + } + + mosquitto_lib_cleanup(); + return run; +} diff --git a/test/lib/c/08-ssl-bad-cacert.c b/test/lib/c/08-ssl-bad-cacert.c new file mode 100644 index 0000000000..fc39a96cd0 --- /dev/null +++ b/test/lib/c/08-ssl-bad-cacert.c @@ -0,0 +1,21 @@ +#include +#include +#include +#include +#include + +int main(int argc, char *argv[]) +{ + int rc = 1; + struct mosquitto *mosq; + + mosquitto_lib_init(); + + mosq = mosquitto_new("08-ssl-bad-cacert", true, NULL); + mosquitto_tls_opts_set(mosq, 1, "tlsv1", NULL); + if(mosquitto_tls_set(mosq, "this/file/doesnt/exist", NULL, NULL, NULL, NULL) == MOSQ_ERR_INVAL){ + rc = 0; + } + mosquitto_lib_cleanup(); + return rc; +} diff --git a/test/lib/c/08-ssl-connect-cert-auth.c b/test/lib/c/08-ssl-connect-cert-auth.c new file mode 100644 index 0000000000..3adc7729dd --- /dev/null +++ b/test/lib/c/08-ssl-connect-cert-auth.c @@ -0,0 +1,44 @@ +#include +#include +#include +#include +#include + +static int run = -1; + +void on_connect(struct mosquitto *mosq, void *obj, int rc) +{ + if(rc){ + exit(1); + }else{ + mosquitto_disconnect(mosq); + } +} + +void on_disconnect(struct mosquitto *mosq, void *obj, int rc) +{ + run = rc; +} + +int main(int argc, char *argv[]) +{ + int rc; + struct mosquitto *mosq; + + mosquitto_lib_init(); + + mosq = mosquitto_new("08-ssl-connect-crt-auth", true, NULL); + mosquitto_tls_opts_set(mosq, 1, "tlsv1", NULL); + mosquitto_tls_set(mosq, "../ssl/test-root-ca.crt", "../ssl/certs", "../ssl/client.crt", "../ssl/client.key", NULL); + mosquitto_connect_callback_set(mosq, on_connect); + mosquitto_disconnect_callback_set(mosq, on_disconnect); + + rc = mosquitto_connect(mosq, "localhost", 1888, 60); + + while(run == -1){ + mosquitto_loop(mosq, -1, 1); + } + + mosquitto_lib_cleanup(); + return run; +} diff --git a/test/lib/c/08-ssl-connect-no-auth.c b/test/lib/c/08-ssl-connect-no-auth.c new file mode 100644 index 0000000000..84fe8e7380 --- /dev/null +++ b/test/lib/c/08-ssl-connect-no-auth.c @@ -0,0 +1,45 @@ +#include +#include +#include +#include +#include + +static int run = -1; + +void on_connect(struct mosquitto *mosq, void *obj, int rc) +{ + if(rc){ + exit(1); + }else{ + mosquitto_disconnect(mosq); + } +} + +void on_disconnect(struct mosquitto *mosq, void *obj, int rc) +{ + run = rc; +} + +int main(int argc, char *argv[]) +{ + int rc; + struct mosquitto *mosq; + + mosquitto_lib_init(); + + mosq = mosquitto_new("08-ssl-connect-no-auth", true, NULL); + mosquitto_tls_opts_set(mosq, 1, "tlsv1", NULL); + //mosquitto_tls_set(mosq, "../ssl/test-root-ca.crt", NULL, NULL, NULL, NULL); + mosquitto_tls_set(mosq, "../ssl/all-ca.crt", NULL, NULL, NULL, NULL); + mosquitto_connect_callback_set(mosq, on_connect); + mosquitto_disconnect_callback_set(mosq, on_disconnect); + + rc = mosquitto_connect(mosq, "localhost", 1888, 60); + + while(run == -1){ + mosquitto_loop(mosq, -1, 1); + } + + mosquitto_lib_cleanup(); + return run; +} diff --git a/test/lib/c/08-ssl-fake-cacert.c b/test/lib/c/08-ssl-fake-cacert.c new file mode 100644 index 0000000000..8bf0699947 --- /dev/null +++ b/test/lib/c/08-ssl-fake-cacert.c @@ -0,0 +1,35 @@ +#include +#include +#include +#include +#include + +static int run = -1; + +void on_connect(struct mosquitto *mosq, void *obj, int rc) +{ + exit(1); +} + +int main(int argc, char *argv[]) +{ + int rc; + struct mosquitto *mosq; + + mosquitto_lib_init(); + + mosq = mosquitto_new("08-ssl-connect-crt-auth", true, NULL); + mosquitto_tls_opts_set(mosq, 1, "tlsv1", NULL); + mosquitto_tls_set(mosq, "../ssl/test-fake-root-ca.crt", NULL, "../ssl/client.crt", "../ssl/client.key", NULL); + mosquitto_connect_callback_set(mosq, on_connect); + + rc = mosquitto_connect(mosq, "localhost", 1888, 60); + + rc = mosquitto_loop_forever(mosq, -1, 1); + if(rc == MOSQ_ERR_ERRNO && errno == EPROTO){ + return 0; + }else{ + return 1; + } +} + diff --git a/test/lib/c/09-util-topic-matching.c b/test/lib/c/09-util-topic-matching.c new file mode 100644 index 0000000000..8ffaed2b87 --- /dev/null +++ b/test/lib/c/09-util-topic-matching.c @@ -0,0 +1,48 @@ +#include +#include +#include + +void do_check(const char *sub, const char *topic, bool bad_res) +{ + bool match; + + mosquitto_topic_matches_sub(sub, topic, &match); + + if(match == bad_res){ + printf("s: %s t: %s\n", sub, topic); + exit(1); + } +} + +int main(int argc, char *argv[]) +{ + do_check("foo/bar", "foo/bar", false); + do_check("foo/+", "foo/bar", false); + do_check("foo/+/baz", "foo/bar/baz", false); + + do_check("foo/+/#", "foo/bar/baz", false); + do_check("#", "foo/bar/baz", false); + + do_check("foo/bar", "foo", true); + do_check("foo/+", "foo/bar/baz", true); + do_check("foo/+/baz", "foo/bar/bar", true); + + do_check("foo/+/#", "fo2/bar/baz", true); + + do_check("#", "/foo/bar", false); + do_check("/#", "/foo/bar", false); + do_check("/#", "foo/bar", true); + + + do_check("foo//bar", "foo//bar", false); + do_check("foo//+", "foo//bar", false); + do_check("foo/+/+/baz", "foo///baz", false); + do_check("foo/bar/+", "foo/bar/", false); + + do_check("$SYS/bar", "$SYS/bar", false); + do_check("#", "$SYS/bar", true); + do_check("$BOB/bar", "$SYS/bar", true); + + return 0; +} + diff --git a/test/lib/c/09-util-topic-tokenise.c b/test/lib/c/09-util-topic-tokenise.c new file mode 100644 index 0000000000..24baf07fa2 --- /dev/null +++ b/test/lib/c/09-util-topic-tokenise.c @@ -0,0 +1,130 @@ +#include +#include +#include + +void print_error(const char *topic, char **topics, int topic_count) +{ + int i; + printf("TOPIC: %s\n", topic); + printf("TOKENS: "); + for(i=0; i +//#include +//#include +#include + +static int run = -1; + +class mosquittopp_test : public mosqpp::mosquittopp +{ + public: + mosquittopp_test(const char *id); + + void on_connect(int rc); + void on_disconnect(int rc); +}; + +mosquittopp_test::mosquittopp_test(const char *id) : mosqpp::mosquittopp(id) +{ +} + +void mosquittopp_test::on_connect(int rc) +{ + if(rc){ + exit(1); + }else{ + disconnect(); + } +} + +void mosquittopp_test::on_disconnect(int rc) +{ + run = rc; +} + + +int main(int argc, char *argv[]) +{ + struct mosquittopp_test *mosq; + + mosqpp::lib_init(); + + mosq = new mosquittopp_test("01-con-discon-success"); + + mosq->connect("localhost", 1888, 60); + + while(run == -1){ + mosq->loop(); + } + + mosqpp::lib_cleanup(); + + return run; +} diff --git a/test/lib/cpp/01-keepalive-pingreq.cpp b/test/lib/cpp/01-keepalive-pingreq.cpp new file mode 100644 index 0000000000..fda159210a --- /dev/null +++ b/test/lib/cpp/01-keepalive-pingreq.cpp @@ -0,0 +1,41 @@ +#include + +static int run = -1; + +class mosquittopp_test : public mosqpp::mosquittopp +{ + public: + mosquittopp_test(const char *id); + + void on_connect(int rc); +}; + +mosquittopp_test::mosquittopp_test(const char *id) : mosqpp::mosquittopp(id) +{ +} + +void mosquittopp_test::on_connect(int rc) +{ + if(rc){ + exit(1); + } +} + +int main(int argc, char *argv[]) +{ + struct mosquittopp_test *mosq; + + mosqpp::lib_init(); + + mosq = new mosquittopp_test("01-keepalive-pingreq"); + + mosq->connect("localhost", 1888, 4); + + while(run == -1){ + mosq->loop(); + } + + mosqpp::lib_cleanup(); + + return run; +} diff --git a/test/lib/cpp/01-no-clean-session.cpp b/test/lib/cpp/01-no-clean-session.cpp new file mode 100644 index 0000000000..508f05da96 --- /dev/null +++ b/test/lib/cpp/01-no-clean-session.cpp @@ -0,0 +1,33 @@ +#include +#include + +static int run = -1; + +class mosquittopp_test : public mosqpp::mosquittopp +{ + public: + mosquittopp_test(const char *id, bool clean_session); +}; + +mosquittopp_test::mosquittopp_test(const char *id, bool clean_session) : mosqpp::mosquittopp(id, clean_session) +{ +} + +int main(int argc, char *argv[]) +{ + struct mosquittopp_test *mosq; + + mosqpp::lib_init(); + + mosq = new mosquittopp_test("01-no-clean-session", false); + + mosq->connect("localhost", 1888, 60); + + while(run == -1){ + mosq->loop(); + } + + mosqpp::lib_cleanup(); + + return run; +} diff --git a/test/lib/cpp/01-unpwd-set.cpp b/test/lib/cpp/01-unpwd-set.cpp new file mode 100644 index 0000000000..39017a0006 --- /dev/null +++ b/test/lib/cpp/01-unpwd-set.cpp @@ -0,0 +1,34 @@ +#include +#include + +static int run = -1; + +class mosquittopp_test : public mosqpp::mosquittopp +{ + public: + mosquittopp_test(const char *id); +}; + +mosquittopp_test::mosquittopp_test(const char *id) : mosqpp::mosquittopp(id) +{ +} + +int main(int argc, char *argv[]) +{ + struct mosquittopp_test *mosq; + + mosqpp::lib_init(); + + mosq = new mosquittopp_test("01-unpwd-set"); + mosq->username_pw_set("uname", ";'[08gn=#"); + + mosq->connect("localhost", 1888, 60); + + while(run == -1){ + mosq->loop(); + } + + mosqpp::lib_cleanup(); + + return run; +} diff --git a/test/lib/cpp/01-will-set.cpp b/test/lib/cpp/01-will-set.cpp new file mode 100644 index 0000000000..4b6f337c1d --- /dev/null +++ b/test/lib/cpp/01-will-set.cpp @@ -0,0 +1,37 @@ +//#include +//#include +//#include +#include +#include + +static int run = -1; + +class mosquittopp_test : public mosqpp::mosquittopp +{ + public: + mosquittopp_test(const char *id); +}; + +mosquittopp_test::mosquittopp_test(const char *id) : mosqpp::mosquittopp(id) +{ +} + +int main(int argc, char *argv[]) +{ + struct mosquittopp_test *mosq; + + mosqpp::lib_init(); + + mosq = new mosquittopp_test("01-will-set"); + mosq->will_set("topic/on/unexpected/disconnect", strlen("will message"), "will message", 1, true); + + mosq->connect("localhost", 1888, 60); + + while(run == -1){ + mosq->loop(); + } + + mosqpp::lib_cleanup(); + + return run; +} diff --git a/test/lib/cpp/01-will-unpwd-set.cpp b/test/lib/cpp/01-will-unpwd-set.cpp new file mode 100644 index 0000000000..421016242f --- /dev/null +++ b/test/lib/cpp/01-will-unpwd-set.cpp @@ -0,0 +1,35 @@ +#include +#include + +static int run = -1; + +class mosquittopp_test : public mosqpp::mosquittopp +{ + public: + mosquittopp_test(const char *id); +}; + +mosquittopp_test::mosquittopp_test(const char *id) : mosqpp::mosquittopp(id) +{ +} + +int main(int argc, char *argv[]) +{ + struct mosquittopp_test *mosq; + + mosqpp::lib_init(); + + mosq = new mosquittopp_test("01-will-unpwd-set"); + mosq->username_pw_set("oibvvwqw", "#'^2hg9a&nm38*us"); + mosq->will_set("will-topic", strlen("will message"), "will message", 2, false); + + mosq->connect("localhost", 1888, 60); + + while(run == -1){ + mosq->loop(); + } + + mosqpp::lib_cleanup(); + + return run; +} diff --git a/test/lib/cpp/02-subscribe-qos0.cpp b/test/lib/cpp/02-subscribe-qos0.cpp new file mode 100644 index 0000000000..9ef54d1957 --- /dev/null +++ b/test/lib/cpp/02-subscribe-qos0.cpp @@ -0,0 +1,55 @@ +#include + +static int run = -1; + +class mosquittopp_test : public mosqpp::mosquittopp +{ + public: + mosquittopp_test(const char *id); + + void on_connect(int rc); + void on_disconnect(int rc); + void on_subscribe(int mid, int qos_count, const int *granted_qos); +}; + +mosquittopp_test::mosquittopp_test(const char *id) : mosqpp::mosquittopp(id) +{ +} + +void mosquittopp_test::on_connect(int rc) +{ + if(rc){ + exit(1); + }else{ + subscribe(NULL, "qos0/test", 0); + } +} + +void mosquittopp_test::on_disconnect(int rc) +{ + run = rc; +} + +void mosquittopp_test::on_subscribe(int mid, int qos_count, const int *granted_qos) +{ + disconnect(); +} + +int main(int argc, char *argv[]) +{ + struct mosquittopp_test *mosq; + + mosqpp::lib_init(); + + mosq = new mosquittopp_test("subscribe-qos0-test"); + + mosq->connect("localhost", 1888, 60); + + while(run == -1){ + mosq->loop(); + } + + mosqpp::lib_cleanup(); + + return run; +} diff --git a/test/lib/cpp/02-subscribe-qos1.cpp b/test/lib/cpp/02-subscribe-qos1.cpp new file mode 100644 index 0000000000..7bbcde0b30 --- /dev/null +++ b/test/lib/cpp/02-subscribe-qos1.cpp @@ -0,0 +1,56 @@ +#include + +static int run = -1; + +class mosquittopp_test : public mosqpp::mosquittopp +{ + public: + mosquittopp_test(const char *id); + + void on_connect(int rc); + void on_disconnect(int rc); + void on_subscribe(int mid, int qos_count, const int *granted_qos); +}; + +mosquittopp_test::mosquittopp_test(const char *id) : mosqpp::mosquittopp(id) +{ +} + +void mosquittopp_test::on_connect(int rc) +{ + if(rc){ + exit(1); + }else{ + subscribe(NULL, "qos1/test", 1); + } +} + +void mosquittopp_test::on_disconnect(int rc) +{ + run = rc; +} + +void mosquittopp_test::on_subscribe(int mid, int qos_count, const int *granted_qos) +{ + disconnect(); +} + + +int main(int argc, char *argv[]) +{ + struct mosquittopp_test *mosq; + + mosqpp::lib_init(); + + mosq = new mosquittopp_test("subscribe-qos1-test"); + + mosq->connect("localhost", 1888, 60); + + while(run == -1){ + mosq->loop(); + } + + mosqpp::lib_cleanup(); + + return run; +} diff --git a/test/lib/cpp/02-subscribe-qos2.cpp b/test/lib/cpp/02-subscribe-qos2.cpp new file mode 100644 index 0000000000..362fad73e1 --- /dev/null +++ b/test/lib/cpp/02-subscribe-qos2.cpp @@ -0,0 +1,56 @@ +#include + +static int run = -1; + +class mosquittopp_test : public mosqpp::mosquittopp +{ + public: + mosquittopp_test(const char *id); + + void on_connect(int rc); + void on_disconnect(int rc); + void on_subscribe(int mid, int qos_count, const int *granted_qos); +}; + +mosquittopp_test::mosquittopp_test(const char *id) : mosqpp::mosquittopp(id) +{ +} + +void mosquittopp_test::on_connect(int rc) +{ + if(rc){ + exit(1); + }else{ + subscribe(NULL, "qos2/test", 2); + } +} + +void mosquittopp_test::on_disconnect(int rc) +{ + run = rc; +} + +void mosquittopp_test::on_subscribe(int mid, int qos_count, const int *granted_qos) +{ + disconnect(); +} + + +int main(int argc, char *argv[]) +{ + struct mosquittopp_test *mosq; + + mosqpp::lib_init(); + + mosq = new mosquittopp_test("subscribe-qos2-test"); + + mosq->connect("localhost", 1888, 60); + + while(run == -1){ + mosq->loop(); + } + + mosqpp::lib_cleanup(); + + return run; +} diff --git a/test/lib/cpp/02-unsubscribe.cpp b/test/lib/cpp/02-unsubscribe.cpp new file mode 100644 index 0000000000..5d07476935 --- /dev/null +++ b/test/lib/cpp/02-unsubscribe.cpp @@ -0,0 +1,55 @@ +#include + +static int run = -1; + +class mosquittopp_test : public mosqpp::mosquittopp +{ + public: + mosquittopp_test(const char *id); + + void on_connect(int rc); + void on_disconnect(int rc); + void on_unsubscribe(int mid); +}; + +mosquittopp_test::mosquittopp_test(const char *id) : mosqpp::mosquittopp(id) +{ +} + +void mosquittopp_test::on_connect(int rc) +{ + if(rc){ + exit(1); + }else{ + unsubscribe(NULL, "unsubscribe/test"); + } +} + +void mosquittopp_test::on_disconnect(int rc) +{ + run = rc; +} + +void mosquittopp_test::on_unsubscribe(int mid) +{ + disconnect(); +} + +int main(int argc, char *argv[]) +{ + struct mosquittopp_test *mosq; + + mosqpp::lib_init(); + + mosq = new mosquittopp_test("unsubscribe-test"); + + mosq->connect("localhost", 1888, 60); + + while(run == -1){ + mosq->loop(); + } + + mosqpp::lib_cleanup(); + + return run; +} diff --git a/test/lib/cpp/03-publish-b2c-qos1.cpp b/test/lib/cpp/03-publish-b2c-qos1.cpp new file mode 100644 index 0000000000..e3950bb90c --- /dev/null +++ b/test/lib/cpp/03-publish-b2c-qos1.cpp @@ -0,0 +1,76 @@ +#include +#include +#include + +#include + +class mosquittopp_test : public mosqpp::mosquittopp +{ + public: + mosquittopp_test(const char *id); + + void on_connect(int rc); + void on_message(const struct mosquitto_message *msg); +}; + +mosquittopp_test::mosquittopp_test(const char *id) : mosqpp::mosquittopp(id) +{ +} + +void mosquittopp_test::on_connect(int rc) +{ + if(rc){ + exit(1); + } +} + +void mosquittopp_test::on_message(const struct mosquitto_message *msg) +{ + if(msg->mid != 123){ + printf("Invalid mid (%d)\n", msg->mid); + exit(1); + } + if(msg->qos != 1){ + printf("Invalid qos (%d)\n", msg->qos); + exit(1); + } + if(strcmp(msg->topic, "pub/qos1/receive")){ + printf("Invalid topic (%s)\n", msg->topic); + exit(1); + } + if(strcmp((char *)msg->payload, "message")){ + printf("Invalid payload (%s)\n", (char *)msg->payload); + exit(1); + } + if(msg->payloadlen != 7){ + printf("Invalid payloadlen (%d)\n", msg->payloadlen); + exit(1); + } + if(msg->retain != false){ + printf("Invalid retain (%d)\n", msg->retain); + exit(1); + } + + exit(0); +} + +int main(int argc, char *argv[]) +{ + struct mosquittopp_test *mosq; + + mosqpp::lib_init(); + + mosq = new mosquittopp_test("publish-qos1-test"); + mosq->message_retry_set(3); + + mosq->connect("localhost", 1888, 60); + + while(1){ + mosq->loop(); + } + + mosqpp::lib_cleanup(); + + return 1; +} + diff --git a/test/lib/cpp/03-publish-b2c-qos2.cpp b/test/lib/cpp/03-publish-b2c-qos2.cpp new file mode 100644 index 0000000000..b8a12226db --- /dev/null +++ b/test/lib/cpp/03-publish-b2c-qos2.cpp @@ -0,0 +1,78 @@ +#include +#include +#include + +#include + +static int run = -1; + +class mosquittopp_test : public mosqpp::mosquittopp +{ + public: + mosquittopp_test(const char *id); + + void on_connect(int rc); + void on_message(const struct mosquitto_message *msg); +}; + +mosquittopp_test::mosquittopp_test(const char *id) : mosqpp::mosquittopp(id) +{ +} + +void mosquittopp_test::on_connect(int rc) +{ + if(rc){ + exit(1); + } +} + +void mosquittopp_test::on_message(const struct mosquitto_message *msg) +{ + if(msg->mid != 13423){ + printf("Invalid mid (%d)\n", msg->mid); + exit(1); + } + if(msg->qos != 2){ + printf("Invalid qos (%d)\n", msg->qos); + exit(1); + } + if(strcmp(msg->topic, "pub/qos2/receive")){ + printf("Invalid topic (%s)\n", msg->topic); + exit(1); + } + if(strcmp((char *)msg->payload, "message")){ + printf("Invalid payload (%s)\n", (char *)msg->payload); + exit(1); + } + if(msg->payloadlen != 7){ + printf("Invalid payloadlen (%d)\n", msg->payloadlen); + exit(1); + } + if(msg->retain != false){ + printf("Invalid retain (%d)\n", msg->retain); + exit(1); + } + + run = 0; +} + +int main(int argc, char *argv[]) +{ + struct mosquittopp_test *mosq; + + mosqpp::lib_init(); + + mosq = new mosquittopp_test("publish-qos2-test"); + mosq->message_retry_set(3); + + mosq->connect("localhost", 1888, 60); + + while(run == -1){ + mosq->loop(); + } + + mosqpp::lib_cleanup(); + + return run; +} + diff --git a/test/lib/cpp/03-publish-c2b-qos1-disconnect.cpp b/test/lib/cpp/03-publish-c2b-qos1-disconnect.cpp new file mode 100644 index 0000000000..dde6f266e3 --- /dev/null +++ b/test/lib/cpp/03-publish-c2b-qos1-disconnect.cpp @@ -0,0 +1,68 @@ +#include +#include + +#include + +static int run = -1; +static int first_connection = 1; + +class mosquittopp_test : public mosqpp::mosquittopp +{ + public: + mosquittopp_test(const char *id); + + void on_connect(int rc); + void on_disconnect(int rc); + void on_publish(int mid); +}; + +mosquittopp_test::mosquittopp_test(const char *id) : mosqpp::mosquittopp(id) +{ +} + +void mosquittopp_test::on_connect(int rc) +{ + if(rc){ + exit(1); + }else{ + if(first_connection == 1){ + publish(NULL, "pub/qos1/test", strlen("message"), "message", 1, false); + first_connection = 0; + } + } +} + +void mosquittopp_test::on_disconnect(int rc) +{ + if(rc){ + reconnect(); + }else{ + run = 0; + } +} + +void mosquittopp_test::on_publish(int mid) +{ + disconnect(); +} + +int main(int argc, char *argv[]) +{ + struct mosquittopp_test *mosq; + + mosqpp::lib_init(); + + mosq = new mosquittopp_test("publish-qos1-test"); + mosq->message_retry_set(3); + + mosq->connect("localhost", 1888, 60); + + while(run == -1){ + mosq->loop(); + } + + mosqpp::lib_cleanup(); + + return run; +} + diff --git a/test/lib/cpp/03-publish-c2b-qos1-timeout.cpp b/test/lib/cpp/03-publish-c2b-qos1-timeout.cpp new file mode 100644 index 0000000000..417c84c683 --- /dev/null +++ b/test/lib/cpp/03-publish-c2b-qos1-timeout.cpp @@ -0,0 +1,60 @@ +#include +#include + +#include + +static int run = -1; + +class mosquittopp_test : public mosqpp::mosquittopp +{ + public: + mosquittopp_test(const char *id); + + void on_connect(int rc); + void on_disconnect(int rc); + void on_publish(int mid); +}; + +mosquittopp_test::mosquittopp_test(const char *id) : mosqpp::mosquittopp(id) +{ +} + +void mosquittopp_test::on_connect(int rc) +{ + if(rc){ + exit(1); + }else{ + publish(NULL, "pub/qos1/test", strlen("message"), "message", 1, false); + } +} + +void mosquittopp_test::on_disconnect(int rc) +{ + run = 0; +} + +void mosquittopp_test::on_publish(int mid) +{ + disconnect(); +} + +int main(int argc, char *argv[]) +{ + struct mosquittopp_test *mosq; + + mosqpp::lib_init(); + + mosq = new mosquittopp_test("publish-qos1-test"); + mosq->message_retry_set(3); + + mosq->connect("localhost", 1888, 60); + + while(run == -1){ + mosq->loop(); + } + + mosqpp::lib_cleanup(); + + return run; +} + diff --git a/test/lib/cpp/03-publish-c2b-qos2-disconnect.cpp b/test/lib/cpp/03-publish-c2b-qos2-disconnect.cpp new file mode 100644 index 0000000000..16c7cc0be2 --- /dev/null +++ b/test/lib/cpp/03-publish-c2b-qos2-disconnect.cpp @@ -0,0 +1,68 @@ +#include +#include + +#include + +static int run = -1; +static int first_connection = 1; + +class mosquittopp_test : public mosqpp::mosquittopp +{ + public: + mosquittopp_test(const char *id); + + void on_connect(int rc); + void on_disconnect(int rc); + void on_publish(int mid); +}; + +mosquittopp_test::mosquittopp_test(const char *id) : mosqpp::mosquittopp(id) +{ +} + +void mosquittopp_test::on_connect(int rc) +{ + if(rc){ + exit(1); + }else{ + if(first_connection == 1){ + publish(NULL, "pub/qos2/test", strlen("message"), "message", 2, false); + first_connection = 0; + } + } +} + +void mosquittopp_test::on_disconnect(int rc) +{ + if(rc){ + reconnect(); + }else{ + run = 0; + } +} + +void mosquittopp_test::on_publish(int mid) +{ + disconnect(); +} + +int main(int argc, char *argv[]) +{ + struct mosquittopp_test *mosq; + + mosqpp::lib_init(); + + mosq = new mosquittopp_test("publish-qos2-test"); + mosq->message_retry_set(3); + + mosq->connect("localhost", 1888, 60); + + while(run == -1){ + mosq->loop(); + } + + mosqpp::lib_cleanup(); + + return run; +} + diff --git a/test/lib/cpp/03-publish-c2b-qos2-timeout.cpp b/test/lib/cpp/03-publish-c2b-qos2-timeout.cpp new file mode 100644 index 0000000000..6f53e5a42d --- /dev/null +++ b/test/lib/cpp/03-publish-c2b-qos2-timeout.cpp @@ -0,0 +1,60 @@ +#include +#include + +#include + +static int run = -1; + +class mosquittopp_test : public mosqpp::mosquittopp +{ + public: + mosquittopp_test(const char *id); + + void on_connect(int rc); + void on_disconnect(int rc); + void on_publish(int mid); +}; + +mosquittopp_test::mosquittopp_test(const char *id) : mosqpp::mosquittopp(id) +{ +} + +void mosquittopp_test::on_connect(int rc) +{ + if(rc){ + exit(1); + }else{ + publish(NULL, "pub/qos2/test", strlen("message"), "message", 2, false); + } +} + +void mosquittopp_test::on_disconnect(int rc) +{ + run = 0; +} + +void mosquittopp_test::on_publish(int mid) +{ + disconnect(); +} + +int main(int argc, char *argv[]) +{ + struct mosquittopp_test *mosq; + + mosqpp::lib_init(); + + mosq = new mosquittopp_test("publish-qos2-test"); + mosq->message_retry_set(3); + + mosq->connect("localhost", 1888, 60); + + while(run == -1){ + mosq->loop(); + } + + mosqpp::lib_cleanup(); + + return run; +} + diff --git a/test/lib/cpp/03-publish-c2b-qos2.cpp b/test/lib/cpp/03-publish-c2b-qos2.cpp new file mode 100644 index 0000000000..25295762a9 --- /dev/null +++ b/test/lib/cpp/03-publish-c2b-qos2.cpp @@ -0,0 +1,59 @@ +#include +#include + +#include + +static int run = -1; + +class mosquittopp_test : public mosqpp::mosquittopp +{ + public: + mosquittopp_test(const char *id); + + void on_connect(int rc); + void on_disconnect(int rc); + void on_publish(int mid); +}; + +mosquittopp_test::mosquittopp_test(const char *id) : mosqpp::mosquittopp(id) +{ +} + +void mosquittopp_test::on_connect(int rc) +{ + if(rc){ + exit(1); + }else{ + publish(NULL, "pub/qos2/test", strlen("message"), "message", 2, false); + } +} + +void mosquittopp_test::on_disconnect(int rc) +{ + run = 0; +} + +void mosquittopp_test::on_publish(int mid) +{ + disconnect(); +} + +int main(int argc, char *argv[]) +{ + struct mosquittopp_test *mosq; + + mosqpp::lib_init(); + + mosq = new mosquittopp_test("publish-qos2-test"); + + mosq->connect("localhost", 1888, 60); + + while(run == -1){ + mosq->loop(); + } + + mosqpp::lib_cleanup(); + + return run; +} + diff --git a/test/lib/cpp/03-publish-qos0-no-payload.cpp b/test/lib/cpp/03-publish-qos0-no-payload.cpp new file mode 100644 index 0000000000..e13e2609aa --- /dev/null +++ b/test/lib/cpp/03-publish-qos0-no-payload.cpp @@ -0,0 +1,56 @@ +#include + +#include + +static int run = -1; +static int sent_mid = -1; + +class mosquittopp_test : public mosqpp::mosquittopp +{ + public: + mosquittopp_test(const char *id); + + void on_connect(int rc); + void on_publish(int mid); +}; + +mosquittopp_test::mosquittopp_test(const char *id) : mosqpp::mosquittopp(id) +{ +} + +void mosquittopp_test::on_connect(int rc) +{ + if(rc){ + exit(1); + }else{ + publish(&sent_mid, "pub/qos0/no-payload/test", 0, NULL, 0, false); + } +} + +void mosquittopp_test::on_publish(int mid) +{ + if(sent_mid == mid){ + disconnect(); + }else{ + exit(1); + } +} + +int main(int argc, char *argv[]) +{ + struct mosquittopp_test *mosq; + + mosqpp::lib_init(); + + mosq = new mosquittopp_test("publish-qos0-test-np"); + + mosq->connect("localhost", 1888, 60); + + while(run == -1){ + mosq->loop(); + } + + mosqpp::lib_cleanup(); + + return run; +} diff --git a/test/lib/cpp/03-publish-qos0.cpp b/test/lib/cpp/03-publish-qos0.cpp new file mode 100644 index 0000000000..58ece0a8e2 --- /dev/null +++ b/test/lib/cpp/03-publish-qos0.cpp @@ -0,0 +1,56 @@ +#include + +#include + +static int run = -1; +static int sent_mid = -1; + +class mosquittopp_test : public mosqpp::mosquittopp +{ + public: + mosquittopp_test(const char *id); + + void on_connect(int rc); + void on_publish(int mid); +}; + +mosquittopp_test::mosquittopp_test(const char *id) : mosqpp::mosquittopp(id) +{ +} + +void mosquittopp_test::on_connect(int rc) +{ + if(rc){ + exit(1); + }else{ + publish(&sent_mid, "pub/qos0/test", strlen("message"), "message", 0, false); + } +} + +void mosquittopp_test::on_publish(int mid) +{ + if(sent_mid == mid){ + disconnect(); + }else{ + exit(1); + } +} + +int main(int argc, char *argv[]) +{ + struct mosquittopp_test *mosq; + + mosqpp::lib_init(); + + mosq = new mosquittopp_test("publish-qos0-test"); + + mosq->connect("localhost", 1888, 60); + + while(run == -1){ + mosq->loop(); + } + + mosqpp::lib_cleanup(); + + return run; +} diff --git a/test/lib/cpp/04-retain-qos0.cpp b/test/lib/cpp/04-retain-qos0.cpp new file mode 100644 index 0000000000..beca2bc42e --- /dev/null +++ b/test/lib/cpp/04-retain-qos0.cpp @@ -0,0 +1,45 @@ +#include + +#include + +static int run = -1; + +class mosquittopp_test : public mosqpp::mosquittopp +{ + public: + mosquittopp_test(const char *id); + + void on_connect(int rc); +}; + +mosquittopp_test::mosquittopp_test(const char *id) : mosqpp::mosquittopp(id) +{ +} + +void mosquittopp_test::on_connect(int rc) +{ + if(rc){ + exit(1); + }else{ + publish(NULL, "retain/qos0/test", strlen("retained message"), "retained message", 0, true); + } +} + +int main(int argc, char *argv[]) +{ + struct mosquittopp_test *mosq; + + mosqpp::lib_init(); + + mosq = new mosquittopp_test("retain-qos0-test"); + + mosq->connect("localhost", 1888, 60); + + while(run == -1){ + mosq->loop(); + } + + mosqpp::lib_cleanup(); + + return run; +} diff --git a/test/lib/cpp/08-ssl-bad-cacert.cpp b/test/lib/cpp/08-ssl-bad-cacert.cpp new file mode 100644 index 0000000000..b54d4fc5c6 --- /dev/null +++ b/test/lib/cpp/08-ssl-bad-cacert.cpp @@ -0,0 +1,31 @@ +#include + +static int run = -1; + +class mosquittopp_test : public mosqpp::mosquittopp +{ + public: + mosquittopp_test(const char *id); +}; + +mosquittopp_test::mosquittopp_test(const char *id) : mosqpp::mosquittopp(id) +{ +} + +int main(int argc, char *argv[]) +{ + struct mosquittopp_test *mosq; + int rc = 1; + + mosqpp::lib_init(); + + mosq = new mosquittopp_test("08-ssl-bad-cacert"); + + mosq->tls_opts_set(1, "tlsv1", NULL); + if(mosq->tls_set("this/file/doesnt/exist") == MOSQ_ERR_INVAL){ + rc = 0; + } + mosqpp::lib_cleanup(); + + return rc; +} diff --git a/test/lib/cpp/08-ssl-connect-cert-auth.cpp b/test/lib/cpp/08-ssl-connect-cert-auth.cpp new file mode 100644 index 0000000000..d47f4e5647 --- /dev/null +++ b/test/lib/cpp/08-ssl-connect-cert-auth.cpp @@ -0,0 +1,53 @@ +#include + +static int run = -1; + +class mosquittopp_test : public mosqpp::mosquittopp +{ + public: + mosquittopp_test(const char *id); + + void on_connect(int rc); + void on_disconnect(int rc); +}; + +mosquittopp_test::mosquittopp_test(const char *id) : mosqpp::mosquittopp(id) +{ +} + +void mosquittopp_test::on_connect(int rc) +{ + if(rc){ + exit(1); + }else{ + disconnect(); + } +} + +void mosquittopp_test::on_disconnect(int rc) +{ + run = rc; +} + + +int main(int argc, char *argv[]) +{ + struct mosquittopp_test *mosq; + + mosqpp::lib_init(); + + mosq = new mosquittopp_test("08-ssl-connect-crt-auth"); + + mosq->tls_opts_set(1, "tlsv1", NULL); + //mosq->tls_set("../ssl/test-ca.crt", NULL, "../ssl/client.crt", "../ssl/client.key"); + mosq->tls_set("../ssl/all-ca.crt", NULL, "../ssl/client.crt", "../ssl/client.key"); + mosq->connect("localhost", 1888, 60); + + while(run == -1){ + mosq->loop(); + } + + mosqpp::lib_cleanup(); + + return run; +} diff --git a/test/lib/cpp/08-ssl-connect-no-auth.cpp b/test/lib/cpp/08-ssl-connect-no-auth.cpp new file mode 100644 index 0000000000..79b86f8acc --- /dev/null +++ b/test/lib/cpp/08-ssl-connect-no-auth.cpp @@ -0,0 +1,53 @@ +#include + +static int run = -1; + +class mosquittopp_test : public mosqpp::mosquittopp +{ + public: + mosquittopp_test(const char *id); + + void on_connect(int rc); + void on_disconnect(int rc); +}; + +mosquittopp_test::mosquittopp_test(const char *id) : mosqpp::mosquittopp(id) +{ +} + +void mosquittopp_test::on_connect(int rc) +{ + if(rc){ + exit(1); + }else{ + disconnect(); + } +} + +void mosquittopp_test::on_disconnect(int rc) +{ + run = rc; +} + + +int main(int argc, char *argv[]) +{ + struct mosquittopp_test *mosq; + + mosqpp::lib_init(); + + mosq = new mosquittopp_test("08-ssl-connect-no-auth"); + + mosq->tls_opts_set(1, "tlsv1", NULL); + //mosq->tls_set("../ssl/test-root-ca.crt"); + mosq->tls_set("../ssl/all-ca.crt"); + mosq->connect("localhost", 1888, 60); + + while(run == -1){ + mosq->loop(); + } + + mosqpp::lib_cleanup(); + + return run; +} diff --git a/test/lib/cpp/08-ssl-fake-cacert.cpp b/test/lib/cpp/08-ssl-fake-cacert.cpp new file mode 100644 index 0000000000..615ea7cd73 --- /dev/null +++ b/test/lib/cpp/08-ssl-fake-cacert.cpp @@ -0,0 +1,42 @@ +#include +#include + +static int run = -1; + +class mosquittopp_test : public mosqpp::mosquittopp +{ + public: + mosquittopp_test(const char *id); + + void on_connect(int rc); +}; + +mosquittopp_test::mosquittopp_test(const char *id) : mosqpp::mosquittopp(id) +{ +} + +void mosquittopp_test::on_connect(int rc) +{ + exit(1); +} + +int main(int argc, char *argv[]) +{ + struct mosquittopp_test *mosq; + int rc; + + mosqpp::lib_init(); + + mosq = new mosquittopp_test("08-ssl-fake-cacert"); + + mosq->tls_opts_set(1, "tlsv1", NULL); + mosq->tls_set("../ssl/test-fake-root-ca.crt", NULL, "../ssl/client.crt", "../ssl/client.key"); + mosq->connect("localhost", 1888, 60); + + rc = mosq->loop_forever(); + if(rc == MOSQ_ERR_ERRNO && errno == EPROTO){ + return 0; + }else{ + return 1; + } +} diff --git a/test/lib/cpp/09-util-topic-matching.cpp b/test/lib/cpp/09-util-topic-matching.cpp new file mode 100644 index 0000000000..4b1776aa84 --- /dev/null +++ b/test/lib/cpp/09-util-topic-matching.cpp @@ -0,0 +1,47 @@ +#include +#include + +void do_check(const char *sub, const char *topic, bool bad_res) +{ + bool match; + + mosqpp::topic_matches_sub(sub, topic, &match); + + if(match == bad_res){ + printf("s: %s t: %s\n", sub, topic); + exit(1); + } +} + +int main(int argc, char *argv[]) +{ + do_check("foo/bar", "foo/bar", false); + do_check("foo/+", "foo/bar", false); + do_check("foo/+/baz", "foo/bar/baz", false); + + do_check("foo/+/#", "foo/bar/baz", false); + do_check("#", "foo/bar/baz", false); + + do_check("foo/bar", "foo", true); + do_check("foo/+", "foo/bar/baz", true); + do_check("foo/+/baz", "foo/bar/bar", true); + + do_check("foo/+/#", "fo2/bar/baz", true); + + do_check("#", "/foo/bar", false); + do_check("/#", "/foo/bar", false); + do_check("/#", "foo/bar", true); + + + do_check("foo//bar", "foo//bar", false); + do_check("foo//+", "foo//bar", false); + do_check("foo/+/+/baz", "foo///baz", false); + do_check("foo/bar/+", "foo/bar/", false); + + do_check("$SYS/bar", "$SYS/bar", false); + do_check("#", "$SYS/bar", true); + do_check("$BOB/bar", "$SYS/bar", true); + + return 0; +} + diff --git a/test/lib/cpp/09-util-topic-tokenise.cpp b/test/lib/cpp/09-util-topic-tokenise.cpp new file mode 100644 index 0000000000..3094d29dad --- /dev/null +++ b/test/lib/cpp/09-util-topic-tokenise.cpp @@ -0,0 +1,129 @@ +#include +#include +#include + +void print_error(const char *topic, char **topics, int topic_count) +{ + int i; + printf("TOPIC: %s\n", topic); + printf("TOKENS: "); + for(i=0; i 0: + rlen = len(expected) + else: + rlen = 1 + + packet_recvd = sock.recv(rlen) + return packet_matches(name, packet_recvd, expected) + +def packet_matches(name, recvd, expected): + if recvd != expected: + print("FAIL: Received incorrect "+name+".") + try: + print("Received: "+to_string(recvd)) + except struct.error: + print("Received (not decoded): "+recvd) + try: + print("Expected: "+to_string(expected)) + except struct.error: + print("Expected (not decoded): "+expected) + + return 0 + else: + return 1 + +def do_client_connect(connect_packet, connack_packet, hostname="localhost", port=1888, timeout=60, connack_error="connack"): + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(timeout) + sock.connect((hostname, port)) + sock.send(connect_packet) + + if expect_packet(sock, connack_error, connack_packet): + return sock + else: + sock.close() + raise ValueError + +def remaining_length(packet): + l = min(5, len(packet)) + all_bytes = struct.unpack("!"+"B"*l, packet[:l]) + mult = 1 + rl = 0 + for i in range(1,l-1): + byte = all_bytes[i] + + rl += (byte & 127) * mult + mult *= 128 + if byte & 128 == 0: + packet = packet[i+1:] + break + + return (packet, rl) + + +def to_string(packet): + if len(packet) == 0: + return "" + + packet0 = struct.unpack("!B", packet[0]) + packet0 = packet0[0] + cmd = packet0 & 0xF0 + if cmd == 0x00: + # Reserved + return "0x00" + elif cmd == 0x10: + # CONNECT + (packet, rl) = remaining_length(packet) + pack_format = "!H" + str(len(packet)-2) + 's' + (slen, packet) = struct.unpack(pack_format, packet) + pack_format = "!" + str(slen)+'sBBH' + str(len(packet)-slen-4) + 's' + (protocol, proto_ver, flags, keepalive, packet) = struct.unpack(pack_format, packet) + s = "CONNECT, proto="+protocol+str(proto_ver)+", keepalive="+str(keepalive) + if flags&2: + s = s+", clean-session" + else: + s = s+", durable" + + pack_format = "!H" + str(len(packet)-2) + 's' + (slen, packet) = struct.unpack(pack_format, packet) + pack_format = "!" + str(slen)+'s' + str(len(packet)-slen) + 's' + (client_id, packet) = struct.unpack(pack_format, packet) + s = s+", id="+client_id + + if flags&4: + pack_format = "!H" + str(len(packet)-2) + 's' + (slen, packet) = struct.unpack(pack_format, packet) + pack_format = "!" + str(slen)+'s' + str(len(packet)-slen) + 's' + (will_topic, packet) = struct.unpack(pack_format, packet) + s = s+", will-topic="+will_topic + + pack_format = "!H" + str(len(packet)-2) + 's' + (slen, packet) = struct.unpack(pack_format, packet) + pack_format = "!" + str(slen)+'s' + str(len(packet)-slen) + 's' + (will_message, packet) = struct.unpack(pack_format, packet) + s = s+", will-message="+will_message + + s = s+", will-qos="+str((flags&24)>>3) + s = s+", will-retain="+str((flags&32)>>5) + + if flags&128: + pack_format = "!H" + str(len(packet)-2) + 's' + (slen, packet) = struct.unpack(pack_format, packet) + pack_format = "!" + str(slen)+'s' + str(len(packet)-slen) + 's' + (username, packet) = struct.unpack(pack_format, packet) + s = s+", username="+username + + if flags&64: + pack_format = "!H" + str(len(packet)-2) + 's' + (slen, packet) = struct.unpack(pack_format, packet) + pack_format = "!" + str(slen)+'s' + str(len(packet)-slen) + 's' + (password, packet) = struct.unpack(pack_format, packet) + s = s+", password="+password + + return s + elif cmd == 0x20: + # CONNACK + (cmd, rl, resv, rc) = struct.unpack('!BBBB', packet) + return "CONNACK, rl="+str(rl)+", res="+str(resv)+", rc="+str(rc) + elif cmd == 0x30: + # PUBLISH + dup = (packet0 & 0x08)>>3 + qos = (packet0 & 0x06)>>1 + retain = (packet0 & 0x01) + (packet, rl) = remaining_length(packet) + pack_format = "!H" + str(len(packet)-2) + 's' + (tlen, packet) = struct.unpack(pack_format, packet) + pack_format = "!" + str(tlen)+'s' + str(len(packet)-tlen) + 's' + (topic, packet) = struct.unpack(pack_format, packet) + s = "PUBLISH, rl="+str(rl)+", topic="+topic+", qos="+str(qos)+", retain="+str(retain)+", dup="+str(dup) + if qos > 0: + pack_format = "!H" + str(len(packet)-2) + 's' + (mid, packet) = struct.unpack(pack_format, packet) + s = s + ", mid="+str(mid) + + s = s + ", payload="+packet + return s + elif cmd == 0x40: + # PUBACK + (cmd, rl, mid) = struct.unpack('!BBH', packet) + return "PUBACK, rl="+str(rl)+", mid="+str(mid) + elif cmd == 0x50: + # PUBREC + (cmd, rl, mid) = struct.unpack('!BBH', packet) + return "PUBREC, rl="+str(rl)+", mid="+str(mid) + elif cmd == 0x60: + # PUBREL + dup = (packet0 & 0x08)>>3 + (cmd, rl, mid) = struct.unpack('!BBH', packet) + return "PUBREL, rl="+str(rl)+", mid="+str(mid)+", dup="+str(dup) + elif cmd == 0x70: + # PUBCOMP + (cmd, rl, mid) = struct.unpack('!BBH', packet) + return "PUBCOMP, rl="+str(rl)+", mid="+str(mid) + elif cmd == 0x80: + # SUBSCRIBE + (packet, rl) = remaining_length(packet) + pack_format = "!H" + str(len(packet)-2) + 's' + (mid, packet) = struct.unpack(pack_format, packet) + s = "SUBSCRIBE, rl="+str(rl)+", mid="+str(mid) + topic_index = 0 + while len(packet) > 0: + pack_format = "!H" + str(len(packet)-2) + 's' + (tlen, packet) = struct.unpack(pack_format, packet) + pack_format = "!" + str(tlen)+'sB' + str(len(packet)-tlen-1) + 's' + (topic, qos, packet) = struct.unpack(pack_format, packet) + s = s + ", topic"+str(topic_index)+"="+topic+","+str(qos) + return s + elif cmd == 0x90: + # SUBACK + (packet, rl) = remaining_length(packet) + pack_format = "!H" + str(len(packet)-2) + 's' + (mid, packet) = struct.unpack(pack_format, packet) + pack_format = "!" + "B"*len(packet) + granted_qos = struct.unpack(pack_format, packet) + + s = "SUBACK, rl="+str(rl)+", mid="+str(mid)+", granted_qos="+str(granted_qos[0]) + for i in range(1, len(granted_qos)-1): + s = s+", "+str(granted_qos[i]) + return s + elif cmd == 0xA0: + # UNSUBSCRIBE + (packet, rl) = remaining_length(packet) + pack_format = "!H" + str(len(packet)-2) + 's' + (mid, packet) = struct.unpack(pack_format, packet) + s = "UNSUBSCRIBE, rl="+str(rl)+", mid="+str(mid) + topic_index = 0 + while len(packet) > 0: + pack_format = "!H" + str(len(packet)-2) + 's' + (tlen, packet) = struct.unpack(pack_format, packet) + pack_format = "!" + str(tlen)+'s' + str(len(packet)-tlen) + 's' + (topic, packet) = struct.unpack(pack_format, packet) + s = s + ", topic"+str(topic_index)+"="+topic + return s + elif cmd == 0xB0: + # UNSUBACK + (cmd, rl, mid) = struct.unpack('!BBH', packet) + return "UNSUBACK, rl="+str(rl)+", mid="+str(mid) + elif cmd == 0xC0: + # PINGREQ + (cmd, rl) = struct.unpack('!BB', packet) + return "PINGREQ, rl="+str(rl) + elif cmd == 0xD0: + # PINGRESP + (cmd, rl) = struct.unpack('!BB', packet) + return "PINGRESP, rl="+str(rl) + elif cmd == 0xE0: + # DISCONNECT + (cmd, rl) = struct.unpack('!BB', packet) + return "DISCONNECT, rl="+str(rl) + elif cmd == 0xF0: + # Reserved + return "0xF0" + +def gen_connect(client_id, clean_session=True, keepalive=60, username=None, password=None, will_topic=None, will_qos=0, will_retain=False, will_payload="", proto_ver=3): + if (proto_ver&0x7F) == 3 or proto_ver == 0: + remaining_length = 12 + elif (proto_ver&0x7F) == 4: + remaining_length = 10 + else: + raise ValueError + + if client_id != None: + remaining_length = remaining_length + 2+len(client_id) + + connect_flags = 0 + if clean_session: + connect_flags = connect_flags | 0x02 + + if will_topic != None: + remaining_length = remaining_length + 2+len(will_topic) + 2+len(will_payload) + connect_flags = connect_flags | 0x04 | ((will_qos&0x03) << 3) + if will_retain: + connect_flags = connect_flags | 32 + + if username != None: + remaining_length = remaining_length + 2+len(username) + connect_flags = connect_flags | 0x80 + if password != None: + connect_flags = connect_flags | 0x40 + remaining_length = remaining_length + 2+len(password) + + rl = pack_remaining_length(remaining_length) + packet = struct.pack("!B"+str(len(rl))+"s", 0x10, rl) + if (proto_ver&0x7F) == 3 or proto_ver == 0: + packet = packet + struct.pack("!H6sBBH", len("MQIsdp"), "MQIsdp", proto_ver, connect_flags, keepalive) + elif (proto_ver&0x7F) == 4: + packet = packet + struct.pack("!H4sBBH", len("MQTT"), "MQTT", proto_ver, connect_flags, keepalive) + + if client_id != None: + packet = packet + struct.pack("!H"+str(len(client_id))+"s", len(client_id), client_id) + + if will_topic != None: + packet = packet + struct.pack("!H"+str(len(will_topic))+"s", len(will_topic), will_topic) + if len(will_payload) > 0: + packet = packet + struct.pack("!H"+str(len(will_payload))+"s", len(will_payload), will_payload) + else: + packet = packet + struct.pack("!H", 0) + + if username != None: + packet = packet + struct.pack("!H"+str(len(username))+"s", len(username), username) + if password != None: + packet = packet + struct.pack("!H"+str(len(password))+"s", len(password), password) + return packet + +def gen_connack(resv=0, rc=0): + return struct.pack('!BBBB', 32, 2, resv, rc); + +def gen_publish(topic, qos, payload=None, retain=False, dup=False, mid=0): + rl = 2+len(topic) + pack_format = "!BBH"+str(len(topic))+"s" + if qos > 0: + rl = rl + 2 + pack_format = pack_format + "H" + if payload != None: + rl = rl + len(payload) + pack_format = pack_format + str(len(payload))+"s" + else: + payload = "" + pack_format = pack_format + "0s" + + cmd = 48 | (qos<<1) + if retain: + cmd = cmd + 1 + if dup: + cmd = cmd + 8 + + if qos > 0: + return struct.pack(pack_format, cmd, rl, len(topic), topic, mid, payload) + else: + return struct.pack(pack_format, cmd, rl, len(topic), topic, payload) + +def gen_puback(mid): + return struct.pack('!BBH', 64, 2, mid) + +def gen_pubrec(mid): + return struct.pack('!BBH', 80, 2, mid) + +def gen_pubrel(mid, dup=False): + if dup: + cmd = 96+8+2 + else: + cmd = 96+2 + return struct.pack('!BBH', cmd, 2, mid) + +def gen_pubcomp(mid): + return struct.pack('!BBH', 112, 2, mid) + +def gen_subscribe(mid, topic, qos): + pack_format = "!BBHH"+str(len(topic))+"sB" + return struct.pack(pack_format, 130, 2+2+len(topic)+1, mid, len(topic), topic, qos) + +def gen_suback(mid, qos): + return struct.pack('!BBHB', 144, 2+1, mid, qos) + +def gen_unsubscribe(mid, topic): + pack_format = "!BBHH"+str(len(topic))+"s" + return struct.pack(pack_format, 162, 2+2+len(topic), mid, len(topic), topic) + +def gen_unsuback(mid): + return struct.pack('!BBH', 176, 2, mid) + +def gen_pingreq(): + return struct.pack('!BB', 192, 0) + +def gen_pingresp(): + return struct.pack('!BB', 208, 0) + +def gen_disconnect(): + return struct.pack('!BB', 224, 0) + +def pack_remaining_length(remaining_length): + s = "" + while True: + byte = remaining_length % 128 + remaining_length = remaining_length // 128 + # If there are more digits to encode, set the top bit of this digit + if remaining_length > 0: + byte = byte | 0x80 + + s = s + struct.pack("!B", byte) + if remaining_length == 0: + return s diff --git a/test/msgsps_common.h b/test/msgsps_common.h new file mode 100644 index 0000000000..8925f88655 --- /dev/null +++ b/test/msgsps_common.h @@ -0,0 +1,3 @@ +#define MESSAGE_COUNT 100000L +#define MESSAGE_SIZE 1024L + diff --git a/test/msgsps_pub.c b/test/msgsps_pub.c new file mode 100644 index 0000000000..b2ac62565b --- /dev/null +++ b/test/msgsps_pub.c @@ -0,0 +1,135 @@ +/* This provides a crude manner of testing the performance of a broker in messages/s. */ + +#include +#include +#include +#include +#include +#include + +#include + +static bool run = true; +static int message_count = 0; +static struct timeval start, stop; + +void my_connect_callback(struct mosquitto *mosq, void *obj, int rc) +{ + printf("rc: %d\n", rc); + gettimeofday(&start, NULL); +} + +void my_disconnect_callback(struct mosquitto *mosq, void *obj, int result) +{ + run = false; +} + +void my_publish_callback(struct mosquitto *mosq, void *obj, int mid) +{ + message_count++; + //printf("%d ", message_count); + if(message_count == MESSAGE_COUNT){ + gettimeofday(&stop, NULL); + mosquitto_disconnect((struct mosquitto *)obj); + } +} + +int create_data(void) +{ + int i; + FILE *fptr, *rnd; + int rc = 0; + char buf[MESSAGE_SIZE]; + + fptr = fopen("msgsps_pub.dat", "rb"); + if(fptr){ + fseek(fptr, 0, SEEK_END); + if(ftell(fptr) >= MESSAGE_SIZE*MESSAGE_COUNT){ + fclose(fptr); + return 0; + } + fclose(fptr); + } + + fptr = fopen("msgsps_pub.dat", "wb"); + if(!fptr) return 1; + rnd = fopen("/dev/urandom", "rb"); + if(!rnd){ + fclose(fptr); + return 1; + } + + for(i=0; i +#include +#include +#include +#include +#include + +#include + +static bool run = true; +static int message_count = 0; +static struct timeval start, stop; +FILE *fptr = NULL; + + +void my_connect_callback(struct mosquitto *mosq, void *obj, int rc) +{ + printf("rc: %d\n", rc); +} + +void my_disconnect_callback(struct mosquitto *mosq, void *obj, int result) +{ + run = false; +} + +void my_message_callback(struct mosquitto *mosq, void *obj, const struct mosquitto_message *msg) +{ + if(message_count == 0){ + gettimeofday(&start, NULL); + } + fwrite(msg->payload, sizeof(uint8_t), msg->payloadlen, fptr); + message_count++; + if(message_count == MESSAGE_COUNT){ + gettimeofday(&stop, NULL); + mosquitto_disconnect((struct mosquitto *)obj); + } +} + +int main(int argc, char *argv[]) +{ + struct mosquitto *mosq; + double dstart, dstop, diff; + int mid = 0; + char id[50]; + int rc; + + start.tv_sec = 0; + start.tv_usec = 0; + stop.tv_sec = 0; + stop.tv_usec = 0; + + fptr = fopen("msgsps_sub.dat", "wb"); + if(!fptr){ + printf("Error: Unable to write to msgsps_sub.dat.\n"); + return 1; + } + mosquitto_lib_init(); + + snprintf(id, 50, "msgps_sub_%d", getpid()); + mosq = mosquitto_new(id, true, NULL); + mosquitto_connect_callback_set(mosq, my_connect_callback); + mosquitto_disconnect_callback_set(mosq, my_disconnect_callback); + mosquitto_message_callback_set(mosq, my_message_callback); + + mosquitto_connect(mosq, "127.0.0.1", 1884, 600); + mosquitto_subscribe(mosq, &mid, "perf/test", 0); + + do{ + rc = mosquitto_loop(mosq, 1, 10); + }while(rc == MOSQ_ERR_SUCCESS && run); + printf("rc: %d\n", rc); + + dstart = (double)start.tv_sec*1.0e6 + (double)start.tv_usec; + dstop = (double)stop.tv_sec*1.0e6 + (double)stop.tv_usec; + diff = (dstop-dstart)/1.0e6; + + printf("Start: %g\nStop: %g\nDiff: %g\nMessages/s: %g\n", dstart, dstop, diff, (double)MESSAGE_COUNT/diff); + + mosquitto_destroy(mosq); + mosquitto_lib_cleanup(); + fclose(fptr); + + return 0; +} diff --git a/test/packet-gen.c b/test/packet-gen.c new file mode 100644 index 0000000000..30af3570fd --- /dev/null +++ b/test/packet-gen.c @@ -0,0 +1,53 @@ +/* Fudge a file description into a client instead of a socket connection so + * that we can write out packets to a file. + * See http://answers.launchpad.net/mosquitto/+question/123594 + * also http://answers.launchpad.net/mosquitto/+question/136821 + */ +#include +#include +#include + +#include +#include +#include + +int main(int argc, char *argv[]) +{ + struct mosquitto *mosq; + int fd; + bool clean_session = true; + int keepalive = 60; + + mosq = mosquitto_new("packetgen", NULL); + if(!mosq){ + fprintf(stderr, "Error: Out of memory.\n"); + return 1; + } + + /* CONNECT */ + fd = open("mqtt.connect", O_CREAT|O_WRONLY, 00644); + if(fd<0){ + fprintf(stderr, "Error: Unable to open mqtt.connect for writing.\n"); + return 1; + } + mosq->core.sock = fd; + printf("_mosquitto_send_connect(): %d\n", _mosquitto_send_connect(mosq, keepalive, clean_session)); + printf("loop: %d\n", mosquitto_loop_write(mosq)); + close(fd); + + /* SUBSCRIBE */ + fd = open("mqtt.subscribe", O_CREAT|O_WRONLY, 00644); + if(fd<0){ + fprintf(stderr, "Error: Unable to open mqtt.subscribe for writing.\n"); + return 1; + } + mosq->core.sock = fd; + printf("_mosquitto_send_subscribe(): %d\n", _mosquitto_send_subscribe(mosq, NULL, false, "subscribe/topic", 2)); + printf("loop: %d\n", mosquitto_loop_write(mosq)); + close(fd); + + mosquitto_destroy(mosq); + + return 0; +} + diff --git a/test/qos.c b/test/qos.c new file mode 100644 index 0000000000..d1ca41ad76 --- /dev/null +++ b/test/qos.c @@ -0,0 +1,186 @@ +#include +#include +#include +#include +#include +#include + +#include + +struct msg_list{ + struct msg_list *next; + struct mosquitto_message msg; + bool sent; +}; + +struct sub{ + uint16_t mid; + char *topic; + int qos; + bool complete; +}; + +struct sub subs[3]; +struct msg_list *messages_received = NULL; +struct msg_list *messages_sent = NULL; +int sent_count = 0; +int received_count = 0; + +void on_message(void *obj, const struct mosquitto_message *msg) +{ + struct msg_list *tail, *new_list; + + received_count++; + + new_list = malloc(sizeof(struct msg_list)); + if(!new_list){ + fprintf(stderr, "Error allocating list memory.\n"); + return; + } + new_list->next = NULL; + if(!mosquitto_message_copy(&new_list->msg, msg)){ + if(messages_received){ + tail = messages_received; + while(tail->next){ + tail = tail->next; + } + tail->next = new_list; + }else{ + messages_received = new_list; + } + }else{ + free(new_list); + return; + } +} + +void on_publish(void *obj, uint16_t mid) +{ + struct msg_list *tail = messages_sent; + + sent_count++; + + while(tail){ + if(tail->msg.mid == mid){ + tail->sent = true; + return; + } + tail = tail->next; + } + + fprintf(stderr, "ERROR: Invalid on_publish() callback for mid %d\n", mid); +} + +void on_subscribe(void *obj, uint16_t mid, int qos_count, const uint8_t *granted_qos) +{ + int i; + for(i=0; i<3; i++){ + if(subs[i].mid == mid){ + if(subs[i].complete){ + fprintf(stderr, "WARNING: Duplicate on_subscribe() callback for mid %d\n", mid); + } + subs[i].complete = true; + return; + } + } + fprintf(stderr, "ERROR: Invalid on_subscribe() callback for mid %d\n", mid); +} + +void on_disconnect(void *obj) +{ + printf("Disconnected cleanly.\n"); +} + +void rand_publish(struct mosquitto *mosq, const char *topic, int qos) +{ + int fd = open("/dev/urandom", O_RDONLY); + uint8_t buf[100]; + uint16_t mid; + struct msg_list *new_list, *tail; + + if(fd >= 0){ + if(read(fd, buf, 100) == 100){ + if(!mosquitto_publish(mosq, &mid, topic, 100, buf, qos, false)){ + new_list = malloc(sizeof(struct msg_list)); + if(new_list){ + new_list->msg.mid = mid; + new_list->msg.topic = strdup(topic); + new_list->msg.payloadlen = 100; + new_list->msg.payload = malloc(100); + memcpy(new_list->msg.payload, buf, 100); + new_list->msg.retain = false; + new_list->next = NULL; + new_list->sent = false; + + if(messages_sent){ + tail = messages_sent; + while(tail->next){ + tail = tail->next; + } + tail->next = new_list; + }else{ + messages_sent = new_list; + } + } + } + } + close(fd); + } +} + +int main(int argc, char *argv[]) +{ + struct mosquitto *mosq; + int i; + time_t start; + + mosquitto_lib_init(); + + mosq = mosquitto_new("qos-test", NULL); + mosquitto_log_init(mosq, MOSQ_LOG_ALL, MOSQ_LOG_STDOUT); + mosquitto_message_callback_set(mosq, on_message); + mosquitto_publish_callback_set(mosq, on_publish); + mosquitto_subscribe_callback_set(mosq, on_subscribe); + mosquitto_disconnect_callback_set(mosq, on_disconnect); + + mosquitto_connect(mosq, "127.0.0.1", 1883, 60, true); + subs[0].topic = "qos-test/0"; + subs[0].qos = 0; + subs[0].complete = false; + subs[1].topic = "qos-test/1"; + subs[1].qos = 1; + subs[1].complete = false; + subs[2].topic = "qos-test/2"; + subs[2].qos = 2; + subs[2].complete = false; + mosquitto_subscribe(mosq, &subs[0].mid, subs[0].topic, subs[0].qos); + mosquitto_subscribe(mosq, &subs[1].mid, subs[1].topic, subs[1].qos); + mosquitto_subscribe(mosq, &subs[2].mid, subs[2].topic, subs[2].qos); + + for(i=0; i<1; i++){ + rand_publish(mosq, "qos-test/0", 0); + rand_publish(mosq, "qos-test/0", 1); + rand_publish(mosq, "qos-test/0", 2); + rand_publish(mosq, "qos-test/1", 0); + rand_publish(mosq, "qos-test/1", 1); + rand_publish(mosq, "qos-test/1", 2); + rand_publish(mosq, "qos-test/2", 0); + rand_publish(mosq, "qos-test/2", 1); + rand_publish(mosq, "qos-test/2", 2); + } + start = time(NULL); + while(!mosquitto_loop(mosq, -1)){ + if(time(NULL)-start > 20){ + mosquitto_disconnect(mosq); + } + } + + mosquitto_destroy(mosq); + + mosquitto_lib_cleanup(); + + printf("Sent messages: %d\n", sent_count); + printf("Received messages: %d\n", received_count); + return 0; +} + diff --git a/test/random_client.c b/test/random_client.c new file mode 100644 index 0000000000..f56060c876 --- /dev/null +++ b/test/random_client.c @@ -0,0 +1,198 @@ +/* +Copyright (c) 2009, Roger Light +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of mosquitto nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +typedef enum { + stStart, + stSocketOpened, + stConnSent, + stConnAckd, + stSubSent, + stSubAckd, + stPause +} stateType; + +static stateType state = stStart; + +int handle_read(mqtt3_context *context) +{ + uint8_t buf; + int rc; + + rc = read(context->sock, &buf, 1); + printf("rc: %d\n", rc); + if(rc == -1){ + printf("Error: %s\n", strerror(errno)); + return 1; + }else if(rc == 0){ + return 2; + } + + switch(buf&0xF0){ + case CONNACK: + if(mqtt3_handle_connack(context)) return 3; + state = stConnAckd; + break; + case SUBACK: + if(mqtt3_handle_suback(context)) return 3; + state = stSubAckd; + break; + case PINGREQ: + if(mqtt3_handle_pingreq(context)) return 3; + break; + case PINGRESP: + if(mqtt3_handle_pingresp(context)) return 3; + break; + case PUBACK: + if(mqtt3_handle_puback(context)) return 3; + break; + case PUBCOMP: + if(mqtt3_handle_pubcomp(context)) return 3; + break; + case PUBLISH: + if(mqtt3_handle_publish(context)) return 0; + break; + case PUBREC: + if(mqtt3_handle_pubrec(context)) return 3; + break; + case UNSUBACK: + if(mqtt3_handle_unsuback(context)) return 3; + break; + default: + printf("Unknown command: %s (%d)\n", mqtt3_command_to_string(buf&0xF0), buf&0xF0); + break; + } + return 0; +} + +void send_random(mqtt3_context *context, int length) +{ + int fd = open("/dev/urandom", O_RDONLY); + uint8_t buf[length]; + + if(fd >= 0){ + if(read(fd, buf, length) == length){ + mqtt3_write_bytes(context, buf, length); + } + close(fd); + } +} + +/* pselect loop test */ +int main(int argc, char *argv[]) +{ + struct timespec timeout; + fd_set readfds, writefds; + int fdcount; + int run = 1; + mqtt3_context context; + char id[30]; + + if(argc == 2){ + sprintf(id, "test%s", argv[1]); + }else{ + sprintf(id, "test"); + } + context.sock = mqtt3_socket_connect("127.0.0.1", 1883); + if(context.sock == -1){ + return 1; + } + state = stSocketOpened; + + while(run){ + FD_ZERO(&readfds); + FD_SET(context.sock, &readfds); + FD_ZERO(&writefds); + //FD_SET(0, &writefds); + timeout.tv_sec = 1; + timeout.tv_nsec = 0; + + fdcount = pselect(context.sock+1, &readfds, &writefds, NULL, &timeout, NULL); + if(fdcount == -1){ + fprintf(stderr, "Error in pselect: %s\n", strerror(errno)); + run = 0; + }else if(fdcount == 0){ + switch(state){ + case stSocketOpened: + mqtt3_raw_connect(&context, id, true, 2, true, "will", "aargh", 60, true); + state = stConnSent; + break; + case stConnSent: + printf("Waiting for CONNACK\n"); + break; + case stConnAckd: + // printf("CONNACK received\n"); + // mqtt3_raw_subscribe(&context, false, "a/b/c", 0); + // state = stSubSent; + send_random(&context, 100); + break; + case stSubSent: + printf("Waiting for SUBACK\n"); + break; + case stSubAckd: + printf("SUBACK received\n"); + mqtt3_raw_publish(&context, 0, 0, 0, 1, "a/b/c", 5, (uint8_t *)"Roger"); + state = stPause; + break; + case stPause: + //mqtt3_raw_disconnect(&context); + printf("Pause\n"); + break; + default: + fprintf(stderr, "Error: Unknown state\n"); + break; + } + }else{ + printf("fdcount=%d\n", fdcount); + + if(FD_ISSET(context.sock, &readfds)){ + if(handle_read(&context)){ + fprintf(stderr, "Socket closed on remote side\n"); + mqtt3_socket_close(&context); + run = 0; + } + } + } + } + return 0; +} + diff --git a/test/ssl/all-ca.crt b/test/ssl/all-ca.crt new file mode 100644 index 0000000000..ba0d2f9f28 --- /dev/null +++ b/test/ssl/all-ca.crt @@ -0,0 +1,75 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1 (0x1) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=GB, ST=Derbyshire, L=Derby, O=Mosquitto Project, OU=Testing, CN=Root CA + Validity + Not Before: Aug 30 22:03:18 2013 GMT + Not After : Aug 29 22:03:18 2018 GMT + Subject: C=GB, ST=Derbyshire, O=Mosquitto Project, OU=Testing, CN=Signing CA + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:a4:b5:b9:31:d8:b4:d6:de:49:c0:cc:15:3f:b8: + 50:8b:be:4a:f4:d3:94:a9:dd:53:2a:e9:df:aa:0d: + 3c:08:7b:a7:51:6d:b9:44:98:b7:8d:03:ab:67:9e: + e1:c4:23:4d:33:8d:0a:90:9f:c6:de:82:14:4c:f6: + 75:5d:a4:e1:a3:ea:fc:9b:79:dd:cb:36:20:87:a3: + 9d:eb:e6:5b:0c:53:34:73:cb:dd:a8:e4:0e:7f:f0: + 5f:8a:3c:d8:8f:01:ff:66:31:16:41:1b:e3:7a:61: + 2c:3d:44:a5:a9:dd:1d:42:e5:5a:a1:df:29:35:dc: + 91:5e:9d:82:60:0d:7a:08:db + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + 40:43:50:14:D1:63:7E:0B:7C:97:14:20:63:E5:8A:95:96:9F:D4:AB + X509v3 Authority Key Identifier: + keyid:28:8D:BF:F8:DE:D1:F5:BB:26:37:A4:4D:27:FD:37:91:EC:6B:0C:DD + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + 8a:b1:49:b4:53:eb:bb:9d:5e:20:f4:d7:8d:b8:24:a1:28:95: + 56:72:03:ed:15:ef:f0:ff:65:b5:6e:34:cf:27:83:7b:57:40: + a7:93:61:f0:93:ff:02:b4:74:e0:43:dc:65:0c:e8:a6:20:f9: + 8c:88:82:8f:0e:8d:33:4d:ba:bb:28:ff:29:5f:a8:96:60:31: + f5:13:15:19:60:a4:00:0e:fc:a7:79:b6:10:95:0b:7b:88:75: + 03:ec:7d:94:63:9e:67:2e:2e:9c:fe:79:89:61:93:75:52:f2: + 36:48:a6:2d:c0:b2:a7:36:c2:36:50:53:b3:cd:e7:07:1d:e5: + 6a:1d +-----BEGIN CERTIFICATE----- +MIICnTCCAgagAwIBAgIBATANBgkqhkiG9w0BAQUFADByMQswCQYDVQQGEwJHQjET +MBEGA1UECAwKRGVyYnlzaGlyZTEOMAwGA1UEBwwFRGVyYnkxGjAYBgNVBAoMEU1v +c3F1aXR0byBQcm9qZWN0MRAwDgYDVQQLDAdUZXN0aW5nMRAwDgYDVQQDDAdSb290 +IENBMB4XDTEzMDgzMDIyMDMxOFoXDTE4MDgyOTIyMDMxOFowZTELMAkGA1UEBhMC +R0IxEzARBgNVBAgMCkRlcmJ5c2hpcmUxGjAYBgNVBAoMEU1vc3F1aXR0byBQcm9q +ZWN0MRAwDgYDVQQLDAdUZXN0aW5nMRMwEQYDVQQDDApTaWduaW5nIENBMIGfMA0G +CSqGSIb3DQEBAQUAA4GNADCBiQKBgQCktbkx2LTW3knAzBU/uFCLvkr005Sp3VMq +6d+qDTwIe6dRbblEmLeNA6tnnuHEI00zjQqQn8beghRM9nVdpOGj6vybed3LNiCH +o53r5lsMUzRzy92o5A5/8F+KPNiPAf9mMRZBG+N6YSw9RKWp3R1C5Vqh3yk13JFe +nYJgDXoI2wIDAQABo1AwTjAdBgNVHQ4EFgQUQENQFNFjfgt8lxQgY+WKlZaf1Ksw +HwYDVR0jBBgwFoAUKI2/+N7R9bsmN6RNJ/03kexrDN0wDAYDVR0TBAUwAwEB/zAN +BgkqhkiG9w0BAQUFAAOBgQCKsUm0U+u7nV4g9NeNuCShKJVWcgPtFe/w/2W1bjTP +J4N7V0Cnk2Hwk/8CtHTgQ9xlDOimIPmMiIKPDo0zTbq7KP8pX6iWYDH1ExUZYKQA +DvynebYQlQt7iHUD7H2UY55nLi6c/nmJYZN1UvI2SKYtwLKnNsI2UFOzzecHHeVq +HQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICsjCCAhugAwIBAgIJAPTHt3psLAUTMA0GCSqGSIb3DQEBBQUAMHIxCzAJBgNV +BAYTAkdCMRMwEQYDVQQIDApEZXJieXNoaXJlMQ4wDAYDVQQHDAVEZXJieTEaMBgG +A1UECgwRTW9zcXVpdHRvIFByb2plY3QxEDAOBgNVBAsMB1Rlc3RpbmcxEDAOBgNV +BAMMB1Jvb3QgQ0EwHhcNMTMwODMwMjIwMzE2WhcNMjMwODI4MjIwMzE2WjByMQsw +CQYDVQQGEwJHQjETMBEGA1UECAwKRGVyYnlzaGlyZTEOMAwGA1UEBwwFRGVyYnkx +GjAYBgNVBAoMEU1vc3F1aXR0byBQcm9qZWN0MRAwDgYDVQQLDAdUZXN0aW5nMRAw +DgYDVQQDDAdSb290IENBMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDB3KGu +pkiSYbDAaH0ewiCb44CLsAdV5PdYgZHH0jlH8oXkNH0MU3qs7Se2UWrnPQb1VbdI +K2DpSTk+3XuWO0BOqQ+/JuRFN/omwrucyKcRNm4MQP1aY2Tm04zsP0Muy4aSyMIk +F6jxQzAmIgj8VgkQ/y/knS5tbQ2kkoWKRn1RCQIDAQABo1AwTjAdBgNVHQ4EFgQU +KI2/+N7R9bsmN6RNJ/03kexrDN0wHwYDVR0jBBgwFoAUKI2/+N7R9bsmN6RNJ/03 +kexrDN0wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQCn2WxbxDd5ar2U +UvttJW4I+/V1h3iAQCXVDAegOGzsYp3cfIdd2oZY++Q9FhzHh8nP18D+CeC9MMu2 +H2iLULUV08cGSaDLlpo1eq2oJc5ygLOEt/XK7/aIMRwrlP/CoSrI2GPkeA8rka96 +G0WtyGRkzqBKHpt6CnseA2evP5NVcQ== +-----END CERTIFICATE----- diff --git a/test/ssl/client-expired.crt b/test/ssl/client-expired.crt new file mode 100644 index 0000000000..7ba84c644a --- /dev/null +++ b/test/ssl/client-expired.crt @@ -0,0 +1,61 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 3 (0x3) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=GB, ST=Derbyshire, O=Mosquitto Project, OU=Testing, CN=Signing CA + Validity + Not Before: Aug 20 00:00:00 2012 GMT + Not After : Aug 21 00:00:00 2012 GMT + Subject: C=GB, ST=Nottinghamshire, L=Nottingham, O=Server, OU=Production, CN=test client expired + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:9a:f0:be:71:57:51:38:4e:1a:de:35:1d:3c:37: + 66:6a:d6:5a:77:17:7d:f9:66:55:2f:c5:b8:17:04: + 3c:59:e6:8f:aa:ae:16:b9:c1:64:a1:a0:3b:ca:0c: + ed:35:e9:2a:85:e9:b6:36:65:d6:ae:62:71:d1:89: + 14:e6:3a:18:c1:0b:28:c8:77:c8:26:e2:fc:f9:51: + 76:6e:21:70:42:28:4e:32:80:9c:5e:a6:58:26:b2: + 6c:40:b9:af:97:23:c1:fe:4b:c1:7f:b6:05:d2:8e: + f5:90:34:cc:0a:28:ed:31:d7:71:5b:dc:6d:2f:ff: + 43:6b:78:1a:c5:6f:42:03:1f + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + CC:E1:DD:22:B5:A1:24:98:8F:47:1E:FF:4F:AE:88:7E:E5:40:56:DB + X509v3 Authority Key Identifier: + keyid:40:43:50:14:D1:63:7E:0B:7C:97:14:20:63:E5:8A:95:96:9F:D4:AB + + Signature Algorithm: sha1WithRSAEncryption + 05:13:6e:9f:27:8b:1e:7b:3c:83:d4:be:d7:d9:3d:95:85:3e: + 3f:d2:56:05:01:fa:3c:1f:4d:f5:b1:39:2e:af:cb:fe:39:4d: + 3b:11:54:68:3e:c1:a9:e2:8b:6f:40:78:65:f5:d3:ec:04:de: + 53:a9:c1:44:64:24:46:69:66:5e:33:41:02:d1:b5:d6:77:de: + 8f:cb:cd:46:97:4a:d2:8c:af:b4:7d:fe:72:48:38:40:d9:75: + 93:2c:a1:4c:70:e3:7d:cb:92:30:93:96:0e:92:9f:05:21:6e: + 39:2d:cb:ec:83:fc:a4:34:ee:d3:ef:89:a7:11:ff:48:fa:1b: + 12:e5 +-----BEGIN CERTIFICATE----- +MIIC1zCCAkCgAwIBAgIBAzANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJHQjET +MBEGA1UECAwKRGVyYnlzaGlyZTEaMBgGA1UECgwRTW9zcXVpdHRvIFByb2plY3Qx +EDAOBgNVBAsMB1Rlc3RpbmcxEzARBgNVBAMMClNpZ25pbmcgQ0EwHhcNMTIwODIw +MDAwMDAwWhcNMTIwODIxMDAwMDAwWjCBgDELMAkGA1UEBhMCR0IxGDAWBgNVBAgM +D05vdHRpbmdoYW1zaGlyZTETMBEGA1UEBwwKTm90dGluZ2hhbTEPMA0GA1UECgwG +U2VydmVyMRMwEQYDVQQLDApQcm9kdWN0aW9uMRwwGgYDVQQDDBN0ZXN0IGNsaWVu +dCBleHBpcmVkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCa8L5xV1E4Thre +NR08N2Zq1lp3F335ZlUvxbgXBDxZ5o+qrha5wWShoDvKDO016SqF6bY2ZdauYnHR +iRTmOhjBCyjId8gm4vz5UXZuIXBCKE4ygJxeplgmsmxAua+XI8H+S8F/tgXSjvWQ +NMwKKO0x13Fb3G0v/0NreBrFb0IDHwIDAQABo3sweTAJBgNVHRMEAjAAMCwGCWCG +SAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4E +FgQUzOHdIrWhJJiPRx7/T66IfuVAVtswHwYDVR0jBBgwFoAUQENQFNFjfgt8lxQg +Y+WKlZaf1KswDQYJKoZIhvcNAQEFBQADgYEABRNunyeLHns8g9S+19k9lYU+P9JW +BQH6PB9N9bE5Lq/L/jlNOxFUaD7BqeKLb0B4ZfXT7ATeU6nBRGQkRmlmXjNBAtG1 +1nfej8vNRpdK0oyvtH3+ckg4QNl1kyyhTHDjfcuSMJOWDpKfBSFuOS3L7IP8pDTu +0++JpxH/SPobEuU= +-----END CERTIFICATE----- diff --git a/test/ssl/client-revoked.crt b/test/ssl/client-revoked.crt new file mode 100644 index 0000000000..31f2764d14 --- /dev/null +++ b/test/ssl/client-revoked.crt @@ -0,0 +1,61 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 4 (0x4) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=GB, ST=Derbyshire, O=Mosquitto Project, OU=Testing, CN=Signing CA + Validity + Not Before: Aug 30 22:03:34 2013 GMT + Not After : Aug 29 22:03:34 2018 GMT + Subject: C=GB, ST=Nottinghamshire, L=Nottingham, O=Server, OU=Production, CN=test client revoked + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:ce:19:b3:0b:d1:87:97:06:48:44:84:77:65:bc: + a7:25:fd:ec:49:16:0b:73:c9:2f:7a:9c:14:16:af: + cd:d3:3e:9a:2a:18:1c:90:f1:1a:5b:6d:31:d5:fd: + 6c:04:2b:87:e2:fe:2b:a8:01:ad:00:64:50:c7:ec: + d1:4f:ec:76:7f:4c:a3:f4:98:82:bf:53:af:06:e3: + 26:87:3e:44:e3:6b:bb:b8:9c:9d:ef:a2:fe:59:3e: + bd:9a:31:c0:3c:77:a9:69:4c:3a:1a:aa:c4:3f:68: + 4c:7f:e2:05:ea:38:98:d6:be:93:27:26:fc:ac:a3: + d0:b4:9c:65:a9:10:e6:5d:b7 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + 96:E5:2B:FD:A2:61:F5:32:36:92:3F:CC:BA:28:A7:E2:4C:6C:A5:91 + X509v3 Authority Key Identifier: + keyid:40:43:50:14:D1:63:7E:0B:7C:97:14:20:63:E5:8A:95:96:9F:D4:AB + + Signature Algorithm: sha1WithRSAEncryption + 22:82:2d:16:57:95:84:10:a5:5b:5b:0f:20:1a:5b:db:59:f5: + 5c:d8:42:24:72:42:80:a8:30:77:82:b2:9c:ee:3e:61:3e:af: + d0:4d:75:32:ee:cc:04:fc:d6:96:57:46:35:34:d6:7e:42:51: + 41:fa:a3:2a:a5:02:3a:50:39:a6:5c:16:a3:8f:dc:2b:45:93: + d6:a0:fd:cf:5c:db:fc:5d:ae:f7:5c:e1:2e:36:de:ee:82:38: + de:db:76:af:fa:04:f2:a6:bc:14:56:2a:66:b9:9c:dc:88:41: + 2d:e7:4e:2c:4d:a9:ae:22:ba:7c:29:65:15:48:85:e4:45:c5: + 32:21 +-----BEGIN CERTIFICATE----- +MIIC1zCCAkCgAwIBAgIBBDANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJHQjET +MBEGA1UECAwKRGVyYnlzaGlyZTEaMBgGA1UECgwRTW9zcXVpdHRvIFByb2plY3Qx +EDAOBgNVBAsMB1Rlc3RpbmcxEzARBgNVBAMMClNpZ25pbmcgQ0EwHhcNMTMwODMw +MjIwMzM0WhcNMTgwODI5MjIwMzM0WjCBgDELMAkGA1UEBhMCR0IxGDAWBgNVBAgM +D05vdHRpbmdoYW1zaGlyZTETMBEGA1UEBwwKTm90dGluZ2hhbTEPMA0GA1UECgwG +U2VydmVyMRMwEQYDVQQLDApQcm9kdWN0aW9uMRwwGgYDVQQDDBN0ZXN0IGNsaWVu +dCByZXZva2VkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOGbML0YeXBkhE +hHdlvKcl/exJFgtzyS96nBQWr83TPpoqGByQ8RpbbTHV/WwEK4fi/iuoAa0AZFDH +7NFP7HZ/TKP0mIK/U68G4yaHPkTja7u4nJ3vov5ZPr2aMcA8d6lpTDoaqsQ/aEx/ +4gXqOJjWvpMnJvyso9C0nGWpEOZdtwIDAQABo3sweTAJBgNVHRMEAjAAMCwGCWCG +SAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4E +FgQUluUr/aJh9TI2kj/Muiin4kxspZEwHwYDVR0jBBgwFoAUQENQFNFjfgt8lxQg +Y+WKlZaf1KswDQYJKoZIhvcNAQEFBQADgYEAIoItFleVhBClW1sPIBpb21n1XNhC +JHJCgKgwd4KynO4+YT6v0E11Mu7MBPzWlldGNTTWfkJRQfqjKqUCOlA5plwWo4/c +K0WT1qD9z1zb/F2u91zhLjbe7oI43tt2r/oE8qa8FFYqZrmc3IhBLedOLE2priK6 +fCllFUiF5EXFMiE= +-----END CERTIFICATE----- diff --git a/test/ssl/client-revoked.csr b/test/ssl/client-revoked.csr new file mode 100644 index 0000000000..9c897d5b4d --- /dev/null +++ b/test/ssl/client-revoked.csr @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBwTCCASoCAQAwgYAxCzAJBgNVBAYTAkdCMRgwFgYDVQQIDA9Ob3R0aW5naGFt +c2hpcmUxEzARBgNVBAcMCk5vdHRpbmdoYW0xDzANBgNVBAoMBlNlcnZlcjETMBEG +A1UECwwKUHJvZHVjdGlvbjEcMBoGA1UEAwwTdGVzdCBjbGllbnQgcmV2b2tlZDCB +nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAzhmzC9GHlwZIRIR3ZbynJf3sSRYL +c8kvepwUFq/N0z6aKhgckPEaW20x1f1sBCuH4v4rqAGtAGRQx+zRT+x2f0yj9JiC +v1OvBuMmhz5E42u7uJyd76L+WT69mjHAPHepaUw6GqrEP2hMf+IF6jiY1r6TJyb8 +rKPQtJxlqRDmXbcCAwEAAaAAMA0GCSqGSIb3DQEBBQUAA4GBABqc8X/e5amA7jA3 +cBEICNfQmwXl7KHkLN3vkoa6bm+gGkYWRQYKVk2lQ1zoWuuVSSRcHZhFAJEayQFq +xLF+lr72707ncc+yUAwnw4/TTmsDizmDcYj3GwjF+u20CSxnbSgLQfpp5xgSNluc +07XSxkm6Zeolt9GyKliqTJ1kojLY +-----END CERTIFICATE REQUEST----- diff --git a/test/ssl/client-revoked.key b/test/ssl/client-revoked.key new file mode 100644 index 0000000000..d7cdcc751a --- /dev/null +++ b/test/ssl/client-revoked.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICWwIBAAKBgQDOGbML0YeXBkhEhHdlvKcl/exJFgtzyS96nBQWr83TPpoqGByQ +8RpbbTHV/WwEK4fi/iuoAa0AZFDH7NFP7HZ/TKP0mIK/U68G4yaHPkTja7u4nJ3v +ov5ZPr2aMcA8d6lpTDoaqsQ/aEx/4gXqOJjWvpMnJvyso9C0nGWpEOZdtwIDAQAB +AoGAWOgPK6b8dbK5FA2Mr+98r0/lUPXYhN8hwyN3Kv41rM3RlR0HnaLUOuJU4DnN +EdNxcAMy8+udJJEho8zN0ktwJd3Mi/LHVRAZx5EwuZ1m5kSbM/n4iD5TMpDIoFD4 +hkq/sxl6EcPBjwDAoykWiYYMcatAyjlxQzs4/FxP9VsgM3kCQQDnUz0K+3zSgE12 +MNx5+mynN6Ugt9wp731sNNirkPrLkp7AG6VF5nX5j4SMqROMOfGSPZ2sKwXnyFUz +/Aj4KXWbAkEA5BWm8VB1hI1vklGdkCfEcE6lIrND62mQ1hmoF3oaxL8XwnLgzv2U +9r3jWUJWZE9AFx0VHj457oN5GpbU/xaoFQJAEr02e7ZFtVO5crKgma0EskMauFzM +lAUXlvVs+/EBsA4PmCZlLBVwRyguJ6rmr3xeKmedZz4Q+2bKKCzpmRjaswJAEJuT +AFc/d1tlGF5g/rIml5biZ1huRaH2LeDIYI0/jbvsWvhKbkgApMbG2yT9bWhn3kb7 +1qvpQ/jGxKze7YQU0QJALPCnF5/cvmnvOgsCbtLvD4yobKpeYTOUz8BESqDWzKKA +L9WyvcvAGneKR55UzIGNeo3c51WWGovlh66TMrXfmA== +-----END RSA PRIVATE KEY----- diff --git a/test/ssl/client.crt b/test/ssl/client.crt new file mode 100644 index 0000000000..d29c5161c4 --- /dev/null +++ b/test/ssl/client.crt @@ -0,0 +1,61 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 2 (0x2) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=GB, ST=Derbyshire, O=Mosquitto Project, OU=Testing, CN=Signing CA + Validity + Not Before: Aug 30 22:03:31 2013 GMT + Not After : Aug 29 22:03:31 2018 GMT + Subject: C=GB, ST=Nottinghamshire, L=Nottingham, O=Server, OU=Production, CN=test client + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:9a:f0:be:71:57:51:38:4e:1a:de:35:1d:3c:37: + 66:6a:d6:5a:77:17:7d:f9:66:55:2f:c5:b8:17:04: + 3c:59:e6:8f:aa:ae:16:b9:c1:64:a1:a0:3b:ca:0c: + ed:35:e9:2a:85:e9:b6:36:65:d6:ae:62:71:d1:89: + 14:e6:3a:18:c1:0b:28:c8:77:c8:26:e2:fc:f9:51: + 76:6e:21:70:42:28:4e:32:80:9c:5e:a6:58:26:b2: + 6c:40:b9:af:97:23:c1:fe:4b:c1:7f:b6:05:d2:8e: + f5:90:34:cc:0a:28:ed:31:d7:71:5b:dc:6d:2f:ff: + 43:6b:78:1a:c5:6f:42:03:1f + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + CC:E1:DD:22:B5:A1:24:98:8F:47:1E:FF:4F:AE:88:7E:E5:40:56:DB + X509v3 Authority Key Identifier: + keyid:40:43:50:14:D1:63:7E:0B:7C:97:14:20:63:E5:8A:95:96:9F:D4:AB + + Signature Algorithm: sha1WithRSAEncryption + 0f:0c:fa:e2:7d:c6:64:58:70:0b:f1:22:1b:bc:ef:ba:60:17: + d8:29:9b:51:bf:a7:6f:cd:89:7c:bd:b7:02:b8:3c:4e:f2:22: + 24:31:3d:4a:54:4d:14:98:ce:37:14:3a:74:23:31:bd:50:53: + b2:aa:d1:9e:d0:b0:a8:1d:e2:b5:be:7e:6f:26:20:d8:b2:5b: + 5c:c4:9d:5d:f1:c3:6f:e1:3b:c1:ea:eb:18:39:79:d9:78:96: + 44:c7:88:65:68:41:05:58:40:83:99:8e:fc:11:64:1b:cf:96: + fe:62:df:68:a8:a7:cb:fe:f1:cc:bf:a6:cb:8a:74:94:14:dd: + 69:12 +-----BEGIN CERTIFICATE----- +MIICzjCCAjegAwIBAgIBAjANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJHQjET +MBEGA1UECAwKRGVyYnlzaGlyZTEaMBgGA1UECgwRTW9zcXVpdHRvIFByb2plY3Qx +EDAOBgNVBAsMB1Rlc3RpbmcxEzARBgNVBAMMClNpZ25pbmcgQ0EwHhcNMTMwODMw +MjIwMzMxWhcNMTgwODI5MjIwMzMxWjB4MQswCQYDVQQGEwJHQjEYMBYGA1UECAwP +Tm90dGluZ2hhbXNoaXJlMRMwEQYDVQQHDApOb3R0aW5naGFtMQ8wDQYDVQQKDAZT +ZXJ2ZXIxEzARBgNVBAsMClByb2R1Y3Rpb24xFDASBgNVBAMMC3Rlc3QgY2xpZW50 +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCa8L5xV1E4ThreNR08N2Zq1lp3 +F335ZlUvxbgXBDxZ5o+qrha5wWShoDvKDO016SqF6bY2ZdauYnHRiRTmOhjBCyjI +d8gm4vz5UXZuIXBCKE4ygJxeplgmsmxAua+XI8H+S8F/tgXSjvWQNMwKKO0x13Fb +3G0v/0NreBrFb0IDHwIDAQABo3sweTAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQf +Fh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUzOHdIrWh +JJiPRx7/T66IfuVAVtswHwYDVR0jBBgwFoAUQENQFNFjfgt8lxQgY+WKlZaf1Ksw +DQYJKoZIhvcNAQEFBQADgYEADwz64n3GZFhwC/EiG7zvumAX2CmbUb+nb82JfL23 +Arg8TvIiJDE9SlRNFJjONxQ6dCMxvVBTsqrRntCwqB3itb5+byYg2LJbXMSdXfHD +b+E7werrGDl52XiWRMeIZWhBBVhAg5mO/BFkG8+W/mLfaKiny/7xzL+my4p0lBTd +aRI= +-----END CERTIFICATE----- diff --git a/test/ssl/client.csr b/test/ssl/client.csr new file mode 100644 index 0000000000..f3e8c626c1 --- /dev/null +++ b/test/ssl/client.csr @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBuDCCASECAQAweDELMAkGA1UEBhMCR0IxGDAWBgNVBAgMD05vdHRpbmdoYW1z +aGlyZTETMBEGA1UEBwwKTm90dGluZ2hhbTEPMA0GA1UECgwGU2VydmVyMRMwEQYD +VQQLDApQcm9kdWN0aW9uMRQwEgYDVQQDDAt0ZXN0IGNsaWVudDCBnzANBgkqhkiG +9w0BAQEFAAOBjQAwgYkCgYEAmvC+cVdROE4a3jUdPDdmatZadxd9+WZVL8W4FwQ8 +WeaPqq4WucFkoaA7ygztNekqhem2NmXWrmJx0YkU5joYwQsoyHfIJuL8+VF2biFw +QihOMoCcXqZYJrJsQLmvlyPB/kvBf7YF0o71kDTMCijtMddxW9xtL/9Da3gaxW9C +Ax8CAwEAAaAAMA0GCSqGSIb3DQEBBQUAA4GBAH5l2eVGP+//MBFAT+ne3/KQvoRQ +yF4xlDjvKUlK3LHjT+js/fxGQJWmXqea5jRmEZjAxNnjDcjf828jaFkaQGsoajym +ebNL5RvrPykwaXjdhHgavDiM/LCRR6bDCUYzS5akjZx2ENQ1TM7BVThOJQ2W+KPn +xdxeRH8KxKGJ3wp0 +-----END CERTIFICATE REQUEST----- diff --git a/test/ssl/client.key b/test/ssl/client.key new file mode 100644 index 0000000000..f94f069c2d --- /dev/null +++ b/test/ssl/client.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICWwIBAAKBgQCa8L5xV1E4ThreNR08N2Zq1lp3F335ZlUvxbgXBDxZ5o+qrha5 +wWShoDvKDO016SqF6bY2ZdauYnHRiRTmOhjBCyjId8gm4vz5UXZuIXBCKE4ygJxe +plgmsmxAua+XI8H+S8F/tgXSjvWQNMwKKO0x13Fb3G0v/0NreBrFb0IDHwIDAQAB +AoGAH3DpBHD2n1liJGNc2mJXmyiCVRZkTt7QPJB/ydPnN0sNLlKDdBBljlLIrziu +TjlRkrkZa7KAvQRnGmEZ55o0eW8bMRkY7vrje+dr3btPet7driZbmkOJDmzPZucV +5IObA5j4sUAd7MOkvLfrK2wtn14PEwEznzZQeZO5NiSp9fECQQDOiCUEjg4/xwG4 +OBKcT7G0zZnYlaqgus8JffC7NBp4nUi4Ol42Zkf3I6j83cUU7RwvhmpmX2IWzmjX +jGDN8EV5AkEAwA0uz7hy6+Nj+boST35r8oUF/j/wgFzqNZwuGv6zIp2EAkjG+LMZ +6hU7MRR+L1V3FYkYr7uZyAv8mSYyn0bfVwJAagcw4ea/3/QdqOJ4g3DSbVzD55Hm +d/+PfHMAXEsCb/tnMtUcOtdFiNXw0mhT3ktgFfHuu8GqDMVIw6fYpsD8GQJAVYTJ +RogM7ItqFmbMBof2C50+iPPx5Ub6p/qu8Shfnldj1BySNWaTcJAZtoY4ll1JVNai +noY8OT9VMOE4g4JsqwJAdZhegiH2/UGh2+81xQZNh8R0dBuK8SVu+FvMvK7np36Q +OEuaW2NZMujP+j/GnNJ2OfzIWIv1LNAP8JhApyCCDg== +-----END RSA PRIVATE KEY----- diff --git a/test/ssl/crl.pem b/test/ssl/crl.pem new file mode 100644 index 0000000000..80283407c4 --- /dev/null +++ b/test/ssl/crl.pem @@ -0,0 +1,10 @@ +-----BEGIN X509 CRL----- +MIIBVTCBvwIBATANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJHQjETMBEGA1UE +CAwKRGVyYnlzaGlyZTEaMBgGA1UECgwRTW9zcXVpdHRvIFByb2plY3QxEDAOBgNV +BAsMB1Rlc3RpbmcxEzARBgNVBAMMClNpZ25pbmcgQ0EXDTEzMDgzMDIyMDMzNVoY +DzIwOTUxMDE5MjIwMzM1WjAUMBICAQQXDTEzMDgzMDIyMDMzNVqgDjAMMAoGA1Ud +FAQDAgEBMA0GCSqGSIb3DQEBBQUAA4GBAHq0ebJDiawBBbMDohyfoFlmtCvJDUuS +79x239ublxRGg8vB9eALiru16YGL2/x3AUYDjr9Xh4cm4BvA5+F6vdebzVcSH/Xe +qxa1YZTvmuZko2Fp7kHMs1bn5diFoGCSXD4OqGFJJwtIOHLXXwtcGaAaGSLtWT8M +2+/Fn+oFhax/ +-----END X509 CRL----- diff --git a/test/ssl/demoCA/crlnumber b/test/ssl/demoCA/crlnumber new file mode 100644 index 0000000000..eeee65ec41 --- /dev/null +++ b/test/ssl/demoCA/crlnumber @@ -0,0 +1 @@ +05 diff --git a/test/ssl/demoCA/index.txt b/test/ssl/demoCA/index.txt new file mode 100644 index 0000000000..ced7051c4e --- /dev/null +++ b/test/ssl/demoCA/index.txt @@ -0,0 +1 @@ +R 391118144000Z 120703155846Z CDAE0E564A2891A7 unknown /C=GB/ST=United Kingdom/L=Derby/O=Mosquitto Test Suite/OU=Broker Test/CN=localhost-client-test diff --git a/test/ssl/demoCA/index.txt.attr b/test/ssl/demoCA/index.txt.attr new file mode 100644 index 0000000000..3a7e39e6ee --- /dev/null +++ b/test/ssl/demoCA/index.txt.attr @@ -0,0 +1 @@ +unique_subject = no diff --git a/test/ssl/demoCA/serial b/test/ssl/demoCA/serial new file mode 100644 index 0000000000..8a0f05e166 --- /dev/null +++ b/test/ssl/demoCA/serial @@ -0,0 +1 @@ +01 diff --git a/test/ssl/gen.sh b/test/ssl/gen.sh new file mode 100755 index 0000000000..047a141853 --- /dev/null +++ b/test/ssl/gen.sh @@ -0,0 +1,70 @@ +# This file generates the keys and certificates used for testing mosquitto. +# None of the keys are encrypted, so do not just use this script to generate +# files for your own use. + +rm -f *.crt *.key *.csr +for a in root signing; do + rm -rf ${a}CA/ + mkdir -p ${a}CA/newcerts + touch ${a}CA/index.txt + echo 01 > ${a}CA/serial + echo 01 > ${a}CA/crlnumber +done +rm -rf certs + +BASESUBJ="/C=GB/ST=Derbyshire/L=Derby/O=Mosquitto Project/OU=Testing" +SBASESUBJ="/C=GB/ST=Nottinghamshire/L=Nottingham/O=Server/OU=Production" +BBASESUBJ="/C=GB/ST=Nottinghamshire/L=Nottingham/O=Server/OU=Bridge" + +# The root CA +openssl genrsa -out test-root-ca.key 1024 +openssl req -new -x509 -days 3650 -key test-root-ca.key -out test-root-ca.crt -config openssl.cnf -subj "${BASESUBJ}/CN=Root CA/" + +# Another root CA that doesn't sign anything +openssl genrsa -out test-bad-root-ca.key 1024 +openssl req -new -x509 -days 3650 -key test-bad-root-ca.key -out test-bad-root-ca.crt -config openssl.cnf -subj "${BASESUBJ}/CN=Bad Root CA/" + +# This is a root CA that has the exact same details as the real root CA, but is a different key and certificate. Effectively a "fake" CA. +openssl genrsa -out test-fake-root-ca.key 1024 +openssl req -new -x509 -days 3650 -key test-fake-root-ca.key -out test-fake-root-ca.crt -config openssl.cnf -subj "${BASESUBJ}/CN=Root CA/" + +# An intermediate CA, signed by the root CA, used to sign server/client csrs. +openssl genrsa -out test-signing-ca.key 1024 +openssl req -out test-signing-ca.csr -key test-signing-ca.key -new -config openssl.cnf -subj "${BASESUBJ}/CN=Signing CA/" +openssl ca -config openssl.cnf -name CA_root -extensions v3_ca -out test-signing-ca.crt -infiles test-signing-ca.csr + +# An alternative intermediate CA, signed by the root CA, not used to sign anything. +openssl genrsa -out test-alt-ca.key 1024 +openssl req -out test-alt-ca.csr -key test-alt-ca.key -new -config openssl.cnf -subj "${BASESUBJ}/CN=Alternative Signing CA/" +openssl ca -config openssl.cnf -name CA_root -extensions v3_ca -out test-alt-ca.crt -infiles test-alt-ca.csr + +# Valid server key and certificate. +openssl genrsa -out server.key 1024 +openssl req -new -key server.key -out server.csr -config openssl.cnf -subj "${SBASESUBJ}/CN=localhost/" +openssl ca -config openssl.cnf -name CA_signing -out server.crt -infiles server.csr + +# Expired server certificate, based on the above server key. +openssl req -new -days 1 -key server.key -out server-expired.csr -config openssl.cnf -subj "${SBASESUBJ}/CN=localhost/" +openssl ca -config openssl.cnf -name CA_signing -days 1 -startdate 120820000000Z -enddate 120821000000Z -out server-expired.crt -infiles server-expired.csr + +# Valid client key and certificate. +openssl genrsa -out client.key 1024 +openssl req -new -key client.key -out client.csr -config openssl.cnf -subj "${SBASESUBJ}/CN=test client/" +openssl ca -config openssl.cnf -name CA_signing -out client.crt -infiles client.csr + +# Expired client certificate, based on the above client key. +openssl req -new -days 1 -key client.key -out client-expired.csr -config openssl.cnf -subj "${SBASESUBJ}/CN=test client expired/" +openssl ca -config openssl.cnf -name CA_signing -days 1 -startdate 120820000000Z -enddate 120821000000Z -out client-expired.crt -infiles client-expired.csr + +# Revoked client certificate, based on a new client key. +openssl genrsa -out client-revoked.key 1024 +openssl req -new -days 1 -key client-revoked.key -out client-revoked.csr -config openssl.cnf -subj "${SBASESUBJ}/CN=test client revoked/" +openssl ca -config openssl.cnf -name CA_signing -out client-revoked.crt -infiles client-revoked.csr +openssl ca -config openssl.cnf -name CA_signing -revoke client-revoked.crt +openssl ca -config openssl.cnf -name CA_signing -gencrl -out crl.pem + +cat test-signing-ca.crt test-root-ca.crt > all-ca.crt +#mkdir certs +#cp test-signing-ca.crt certs/test-signing-ca.pem +#cp test-root-ca.crt certs/test-root.ca.pem +c_rehash certs diff --git a/test/ssl/openssl.cnf b/test/ssl/openssl.cnf new file mode 100644 index 0000000000..0612f44bd7 --- /dev/null +++ b/test/ssl/openssl.cnf @@ -0,0 +1,406 @@ +# +# OpenSSL example configuration file. +# This is mostly being used for generation of certificate requests. +# + +# This definition stops the following lines choking if HOME isn't +# defined. +HOME = . +RANDFILE = $ENV::HOME/.rnd + +# Extra OBJECT IDENTIFIER info: +#oid_file = $ENV::HOME/.oid +oid_section = new_oids + +# To use this configuration file with the "-extfile" option of the +# "openssl x509" utility, name here the section containing the +# X.509v3 extensions to use: +# extensions = +# (Alternatively, use a configuration file that has only +# X.509v3 extensions in its main [= default] section.) + +[ new_oids ] + +# We can add new OIDs in here for use by 'ca', 'req' and 'ts'. +# Add a simple OID like this: +# testoid1=1.2.3.4 +# Or use config file substitution like this: +# testoid2=${testoid1}.5.6 + +# Policies used by the TSA examples. +tsa_policy1 = 1.2.3.4.1 +tsa_policy2 = 1.2.3.4.5.6 +tsa_policy3 = 1.2.3.4.5.7 + +#################################################################### +[ ca ] +default_ca = CA_default # The default ca section + +#################################################################### +[ CA_signing ] + +dir = ./signingCA # Where everything is kept +certs = $dir/certs # Where the issued certs are kept +crl_dir = $dir/crl # Where the issued crl are kept +database = $dir/index.txt # database index file. +#unique_subject = no # Set to 'no' to allow creation of + # several ctificates with same subject. +new_certs_dir = $dir/newcerts # default place for new certs. + +certificate = test-signing-ca.crt # The CA certificate +serial = $dir/serial # The current serial number +crlnumber = $dir/crlnumber # the current crl number + # must be commented out to leave a V1 CRL +crl = $dir/crl.pem # The current CRL +private_key = test-signing-ca.key # The private key +RANDFILE = $dir/.rand # private random number file + +x509_extensions = usr_cert # The extentions to add to the cert + +# Comment out the following two lines for the "traditional" +# (and highly broken) format. +name_opt = ca_default # Subject Name options +cert_opt = ca_default # Certificate field options + +# Extension copying option: use with caution. +# copy_extensions = copy + +# Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs +# so this is commented out by default to leave a V1 CRL. +# crlnumber must also be commented out to leave a V1 CRL. +# crl_extensions = crl_ext + +default_days = 1825 # how long to certify for +default_crl_days= 30000 # how long before next CRL +default_md = default # use public key default MD +preserve = no # keep passed DN ordering + +# A few difference way of specifying how similar the request should look +# For type CA, the listed attributes must be the same, and the optional +# and supplied fields are just that :-) +policy = policy_anything + +[ CA_inter ] +dir = ./interCA +certs = $dir/certs +crl_dir = $dir/crl +database = $dir/index.txt +new_certs_dir = $dir/newcerts + +certificate = test-inter-ca.crt +serial = $dir/serial +crlnumber = $dir/crlnumber +crl = $dir/crl.pem +private_key = test-inter-ca.key +RANDFILE = $dir/.rand + +#x509_extensions = v3_ca +x509_extensions = usr_cert + +name_opt = ca_default +cert_opt = ca_default + +default_days = 1825 +default_crl_days = 30 +default_md = default +preserve = no + +policy = policy_match +unique_subject = yes + +[ CA_root ] +dir = ./rootCA +certs = $dir/certs +crl_dir = $dir/crl +database = $dir/index.txt +new_certs_dir = $dir/newcerts + +certificate = test-root-ca.crt +serial = $dir/serial +crlnumber = $dir/crlnumber +crl = $dir/crl.pem +private_key = test-root-ca.key +RANDFILE = $dir/.rand + +x509_extensions = v3_ca + +name_opt = ca_default +cert_opt = ca_default + +default_days = 1825 +default_crl_days = 30 +default_md = default +preserve = no + +policy = policy_match +unique_subject = yes + +# For the CA policy +[ policy_match ] +countryName = match +stateOrProvinceName = match +organizationName = match +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +# For the 'anything' policy +# At this point in time, you must list all acceptable 'object' +# types. +[ policy_anything ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +#################################################################### +[ req ] +default_bits = 2048 +default_keyfile = privkey.pem +distinguished_name = req_distinguished_name +attributes = req_attributes +x509_extensions = v3_ca # The extentions to add to the self signed cert + +# Passwords for private keys if not present they will be prompted for +# input_password = secret +# output_password = secret + +# This sets a mask for permitted string types. There are several options. +# default: PrintableString, T61String, BMPString. +# pkix : PrintableString, BMPString (PKIX recommendation before 2004) +# utf8only: only UTF8Strings (PKIX recommendation after 2004). +# nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings). +# MASK:XXXX a literal mask value. +# WARNING: ancient versions of Netscape crash on BMPStrings or UTF8Strings. +string_mask = utf8only + +# req_extensions = v3_req # The extensions to add to a certificate request + +[ req_distinguished_name ] +countryName = Country Name (2 letter code) +countryName_default = GB +countryName_min = 2 +countryName_max = 2 + +stateOrProvinceName = State or Province Name (full name) +stateOrProvinceName_default = Derbyshire + +localityName = Locality Name (eg, city) +localityName_default = Derby + +0.organizationName = Organization Name (eg, company) +0.organizationName_default = Mosquitto Project + +# we can do this but it is not needed normally :-) +#1.organizationName = Second Organization Name (eg, company) +#1.organizationName_default = World Wide Web Pty Ltd + +organizationalUnitName = Organizational Unit Name (eg, section) +organizationalUnitName_default = Testing + +commonName = Common Name (e.g. server FQDN or YOUR name) +commonName_max = 64 + +emailAddress = Email Address +emailAddress_max = 64 + +# SET-ex3 = SET extension number 3 + +[ req_attributes ] +challengePassword = A challenge password +challengePassword_min = 4 +challengePassword_max = 20 + +unstructuredName = An optional company name + +[ usr_cert ] + +# These extensions are added when 'ca' signs a request. + +# This goes against PKIX guidelines but some CAs do it and some software +# requires this to avoid interpreting an end user certificate as a CA. + +basicConstraints=CA:FALSE + +# Here are some examples of the usage of nsCertType. If it is omitted +# the certificate can be used for anything *except* object signing. + +# This is OK for an SSL server. +# nsCertType = server + +# For an object signing certificate this would be used. +# nsCertType = objsign + +# For normal client use this is typical +# nsCertType = client, email + +# and for everything including object signing: +# nsCertType = client, email, objsign + +# This is typical in keyUsage for a client certificate. +# keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +# This will be displayed in Netscape's comment listbox. +nsComment = "OpenSSL Generated Certificate" + +# PKIX recommendations harmless if included in all certificates. +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid,issuer + +# This stuff is for subjectAltName and issuerAltname. +# Import the email address. +# subjectAltName=email:copy +# An alternative to produce certificates that aren't +# deprecated according to PKIX. +# subjectAltName=email:move + +# Copy subject details +# issuerAltName=issuer:copy + +#nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem +#nsBaseUrl +#nsRevocationUrl +#nsRenewalUrl +#nsCaPolicyUrl +#nsSslServerName + +# This is required for TSA certificates. +# extendedKeyUsage = critical,timeStamping + +[ v3_req ] + +# Extensions to add to a certificate request + +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +[ v3_ca ] + + +# Extensions for a typical CA + + +# PKIX recommendation. + +subjectKeyIdentifier=hash + +authorityKeyIdentifier=keyid:always,issuer + +# This is what PKIX recommends but some broken software chokes on critical +# extensions. +#basicConstraints = critical,CA:true +# So we do this instead. +basicConstraints = CA:true + +# Key usage: this is typical for a CA certificate. However since it will +# prevent it being used as an test self-signed certificate it is best +# left out by default. +# keyUsage = cRLSign, keyCertSign + +# Some might want this also +# nsCertType = sslCA, emailCA + +# Include email address in subject alt name: another PKIX recommendation +# subjectAltName=email:copy +# Copy issuer details +# issuerAltName=issuer:copy + +# DER hex encoding of an extension: beware experts only! +# obj=DER:02:03 +# Where 'obj' is a standard or added object +# You can even override a supported extension: +# basicConstraints= critical, DER:30:03:01:01:FF + +[ crl_ext ] + +# CRL extensions. +# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL. + +# issuerAltName=issuer:copy +authorityKeyIdentifier=keyid:always + +[ proxy_cert_ext ] +# These extensions should be added when creating a proxy certificate + +# This goes against PKIX guidelines but some CAs do it and some software +# requires this to avoid interpreting an end user certificate as a CA. + +basicConstraints=CA:FALSE + +# Here are some examples of the usage of nsCertType. If it is omitted +# the certificate can be used for anything *except* object signing. + +# This is OK for an SSL server. +# nsCertType = server + +# For an object signing certificate this would be used. +# nsCertType = objsign + +# For normal client use this is typical +# nsCertType = client, email + +# and for everything including object signing: +# nsCertType = client, email, objsign + +# This is typical in keyUsage for a client certificate. +# keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +# This will be displayed in Netscape's comment listbox. +nsComment = "OpenSSL Generated Certificate" + +# PKIX recommendations harmless if included in all certificates. +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid,issuer + +# This stuff is for subjectAltName and issuerAltname. +# Import the email address. +# subjectAltName=email:copy +# An alternative to produce certificates that aren't +# deprecated according to PKIX. +# subjectAltName=email:move + +# Copy subject details +# issuerAltName=issuer:copy + +#nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem +#nsBaseUrl +#nsRevocationUrl +#nsRenewalUrl +#nsCaPolicyUrl +#nsSslServerName + +# This really needs to be in place for it to be a proxy certificate. +proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo + +#################################################################### +[ tsa ] + +default_tsa = tsa_config1 # the default TSA section + +[ tsa_config1 ] + +# These are used by the TSA reply generation only. +dir = ./demoCA # TSA root directory +serial = $dir/tsaserial # The current serial number (mandatory) +crypto_device = builtin # OpenSSL engine to use for signing +signer_cert = $dir/tsacert.pem # The TSA signing certificate + # (optional) +certs = $dir/cacert.pem # Certificate chain to include in reply + # (optional) +signer_key = $dir/private/tsakey.pem # The TSA private key (optional) + +default_policy = tsa_policy1 # Policy if request did not specify it + # (optional) +other_policies = tsa_policy2, tsa_policy3 # acceptable policies (optional) +digests = md5, sha1 # Acceptable message digests (mandatory) +accuracy = secs:1, millisecs:500, microsecs:100 # (optional) +clock_precision_digits = 0 # number of digits after dot. (optional) +ordering = yes # Is ordering defined for timestamps? + # (optional, default: no) +tsa_name = yes # Must the TSA name be included in the reply? + # (optional, default: no) +ess_cert_id_chain = no # Must the ESS cert id chain be included? + # (optional, default: no) diff --git a/test/ssl/readme.txt b/test/ssl/readme.txt new file mode 100644 index 0000000000..bbb6ad3a91 --- /dev/null +++ b/test/ssl/readme.txt @@ -0,0 +1,2 @@ +This directory contains certificates and keys required for SSL testing. +The CA key has password "password". diff --git a/test/ssl/rootCA/crlnumber b/test/ssl/rootCA/crlnumber new file mode 100644 index 0000000000..8a0f05e166 --- /dev/null +++ b/test/ssl/rootCA/crlnumber @@ -0,0 +1 @@ +01 diff --git a/test/ssl/rootCA/index.txt b/test/ssl/rootCA/index.txt new file mode 100644 index 0000000000..faf7b4b274 --- /dev/null +++ b/test/ssl/rootCA/index.txt @@ -0,0 +1,2 @@ +V 180829220318Z 01 unknown /C=GB/ST=Derbyshire/O=Mosquitto Project/OU=Testing/CN=Signing CA +V 180829220327Z 02 unknown /C=GB/ST=Derbyshire/O=Mosquitto Project/OU=Testing/CN=Alternative Signing CA diff --git a/test/ssl/rootCA/index.txt.attr b/test/ssl/rootCA/index.txt.attr new file mode 100644 index 0000000000..8f7e63a347 --- /dev/null +++ b/test/ssl/rootCA/index.txt.attr @@ -0,0 +1 @@ +unique_subject = yes diff --git a/test/ssl/rootCA/serial b/test/ssl/rootCA/serial new file mode 100644 index 0000000000..75016ea362 --- /dev/null +++ b/test/ssl/rootCA/serial @@ -0,0 +1 @@ +03 diff --git a/test/ssl/server-expired.crt b/test/ssl/server-expired.crt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/ssl/server.crt b/test/ssl/server.crt new file mode 100644 index 0000000000..e4b1bd1835 --- /dev/null +++ b/test/ssl/server.crt @@ -0,0 +1,60 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1 (0x1) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=GB, ST=Derbyshire, O=Mosquitto Project, OU=Testing, CN=Signing CA + Validity + Not Before: Aug 30 22:03:29 2013 GMT + Not After : Aug 29 22:03:29 2018 GMT + Subject: C=GB, ST=Nottinghamshire, L=Nottingham, O=Server, OU=Production, CN=localhost + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:ab:8d:98:97:5f:97:fa:82:fa:56:01:6d:f1:6e: + ab:ef:47:a6:24:6c:1f:f1:9a:e5:80:0d:58:71:2f: + be:08:25:87:81:12:0b:a2:aa:ea:19:ee:75:8c:66: + 88:5b:35:ac:79:a6:ff:e4:e0:1b:97:19:da:8d:28: + 50:57:71:c1:ff:44:bb:be:4f:e7:e8:e7:54:bf:14: + cf:12:91:b1:0d:24:9b:24:1c:84:36:a8:99:9e:1e: + 87:18:19:f1:83:c8:ae:fd:a2:af:5e:29:ba:ac:ac: + 5b:56:1c:1c:0d:64:c3:80:d1:4c:c5:21:a8:6e:b8: + b2:f3:03:7a:1b:35:e3:9f:0f + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + 07:C5:AF:95:28:37:45:F4:2C:F5:BA:6A:60:79:DC:0F:A2:46:99:72 + X509v3 Authority Key Identifier: + keyid:40:43:50:14:D1:63:7E:0B:7C:97:14:20:63:E5:8A:95:96:9F:D4:AB + + Signature Algorithm: sha1WithRSAEncryption + 90:dd:85:cb:9f:4a:89:78:2b:26:c4:82:b9:34:ea:39:5a:8b: + d9:3b:56:5c:78:df:69:ab:4a:f7:c6:10:8a:a3:9a:1a:5d:c5: + be:55:a1:36:df:36:d6:ea:3a:ec:be:99:38:9f:34:19:50:c4: + 30:6a:18:2a:42:9f:45:a0:d2:57:bf:37:47:b7:2c:b0:1e:f4: + 2e:95:1a:9a:90:2d:41:95:00:e8:23:3c:c1:99:ea:39:56:b1: + ea:8f:2d:db:e9:2c:ea:c8:5b:e7:90:8e:98:2e:ff:13:aa:73: + c2:da:fa:af:ee:aa:86:b6:1d:dc:91:4e:24:df:19:4d:aa:3f: + 1b:d7 +-----BEGIN CERTIFICATE----- +MIICzDCCAjWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJHQjET +MBEGA1UECAwKRGVyYnlzaGlyZTEaMBgGA1UECgwRTW9zcXVpdHRvIFByb2plY3Qx +EDAOBgNVBAsMB1Rlc3RpbmcxEzARBgNVBAMMClNpZ25pbmcgQ0EwHhcNMTMwODMw +MjIwMzI5WhcNMTgwODI5MjIwMzI5WjB2MQswCQYDVQQGEwJHQjEYMBYGA1UECAwP +Tm90dGluZ2hhbXNoaXJlMRMwEQYDVQQHDApOb3R0aW5naGFtMQ8wDQYDVQQKDAZT +ZXJ2ZXIxEzARBgNVBAsMClByb2R1Y3Rpb24xEjAQBgNVBAMMCWxvY2FsaG9zdDCB +nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAq42Yl1+X+oL6VgFt8W6r70emJGwf +8ZrlgA1YcS++CCWHgRILoqrqGe51jGaIWzWseab/5OAblxnajShQV3HB/0S7vk/n +6OdUvxTPEpGxDSSbJByENqiZnh6HGBnxg8iu/aKvXim6rKxbVhwcDWTDgNFMxSGo +briy8wN6GzXjnw8CAwEAAaN7MHkwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYd +T3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFAfFr5UoN0X0 +LPW6amB53A+iRplyMB8GA1UdIwQYMBaAFEBDUBTRY34LfJcUIGPlipWWn9SrMA0G +CSqGSIb3DQEBBQUAA4GBAJDdhcufSol4KybEgrk06jlai9k7Vlx432mrSvfGEIqj +mhpdxb5VoTbfNtbqOuy+mTifNBlQxDBqGCpCn0Wg0le/N0e3LLAe9C6VGpqQLUGV +AOgjPMGZ6jlWseqPLdvpLOrIW+eQjpgu/xOqc8La+q/uqoa2HdyRTiTfGU2qPxvX +-----END CERTIFICATE----- diff --git a/test/ssl/server.csr b/test/ssl/server.csr new file mode 100644 index 0000000000..d8d36b26ac --- /dev/null +++ b/test/ssl/server.csr @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBtjCCAR8CAQAwdjELMAkGA1UEBhMCR0IxGDAWBgNVBAgMD05vdHRpbmdoYW1z +aGlyZTETMBEGA1UEBwwKTm90dGluZ2hhbTEPMA0GA1UECgwGU2VydmVyMRMwEQYD +VQQLDApQcm9kdWN0aW9uMRIwEAYDVQQDDAlsb2NhbGhvc3QwgZ8wDQYJKoZIhvcN +AQEBBQADgY0AMIGJAoGBAKuNmJdfl/qC+lYBbfFuq+9HpiRsH/Ga5YANWHEvvggl +h4ESC6Kq6hnudYxmiFs1rHmm/+TgG5cZ2o0oUFdxwf9Eu75P5+jnVL8UzxKRsQ0k +myQchDaomZ4ehxgZ8YPIrv2ir14puqysW1YcHA1kw4DRTMUhqG64svMDehs1458P +AgMBAAGgADANBgkqhkiG9w0BAQUFAAOBgQBKfRvwCj6N1SlwGLwJ7NWrasIYE4qP +L1+K5l0xnchICmB4r2kGMN7uoYZGf+rbufQXV6R4DrnsNQVLZGB0OIs0qH1dOIr4 +dr9+VZwSKkig+EGSkefKCsqaS9IzlosT+tsOc2AAl4xpradpVbt7Ln6GlpNfNP+x +ry3A9QBKB3zdMw== +-----END CERTIFICATE REQUEST----- diff --git a/test/ssl/server.key b/test/ssl/server.key new file mode 100644 index 0000000000..e2d0097534 --- /dev/null +++ b/test/ssl/server.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQCrjZiXX5f6gvpWAW3xbqvvR6YkbB/xmuWADVhxL74IJYeBEgui +quoZ7nWMZohbNax5pv/k4BuXGdqNKFBXccH/RLu+T+fo51S/FM8SkbENJJskHIQ2 +qJmeHocYGfGDyK79oq9eKbqsrFtWHBwNZMOA0UzFIahuuLLzA3obNeOfDwIDAQAB +AoGAIbMdCI9kwXc9SevZ9xVwfP6sKnd7BvEQqEj22LUyNVN5/ObYlknQ1us6+Cuk +GZa/nN4rYoCLqvEPN691qNfV7cbiIJcGEMXkBnjaM/ISh6Iv0eGNsX+D7PZOchLK +dlVg7wdzRsuOlbGkWAOPpCLL8JazHKp89+RjiPajB1IEQaECQQDaI/ZZVbiDvTBY +ZI57XJR7eSrn5WcN+LGhOv8G+3HXNDh7hcTAlvfQkZxXHIcc/SgWnkfKBEaC7P5W +T4ImQHqZAkEAyVO+tq/w6rcCK8x0LHjyQ2lmMCKCL/oJ/oWjQCEeZPyII1anZKhk +9Lkbzf432FZn8s2aOS9D5x6TjfJxgkNX5wJAK2CLVChfkJLGUk1sp8s5G3R0u7g6 +TeTuLYl1vQWzFYAk2ys2fLWIgcjytb/Ofk0484Z18A35l39Y9ADLeJ/JwQJAZf02 +r/WRZlYvk2CPubfLgrryOZBBw2w3g+jPOr2MWDxV+xD629My0Ya0vzX5tG6RWj8t +0apQC9VBirc3KXZUIQJBAJ+y07xmUN5a2wpDu3UzmeZn3HdzJO7fBAPi4h8xnLZQ +N5Qu629DQq+X/TzVv2GjBWQHePjezL0NPfch9VzKrMM= +-----END RSA PRIVATE KEY----- diff --git a/test/ssl/signingCA/crlnumber b/test/ssl/signingCA/crlnumber new file mode 100644 index 0000000000..9e22bcb8e3 --- /dev/null +++ b/test/ssl/signingCA/crlnumber @@ -0,0 +1 @@ +02 diff --git a/test/ssl/signingCA/index.txt b/test/ssl/signingCA/index.txt new file mode 100644 index 0000000000..7ca764eff7 --- /dev/null +++ b/test/ssl/signingCA/index.txt @@ -0,0 +1,4 @@ +V 180829220329Z 01 unknown /C=GB/ST=Nottinghamshire/L=Nottingham/O=Server/OU=Production/CN=localhost +V 180829220331Z 02 unknown /C=GB/ST=Nottinghamshire/L=Nottingham/O=Server/OU=Production/CN=test client +V 120821000000Z 03 unknown /C=GB/ST=Nottinghamshire/L=Nottingham/O=Server/OU=Production/CN=test client expired +R 180829220334Z 130830220335Z 04 unknown /C=GB/ST=Nottinghamshire/L=Nottingham/O=Server/OU=Production/CN=test client revoked diff --git a/test/ssl/signingCA/index.txt.attr b/test/ssl/signingCA/index.txt.attr new file mode 100644 index 0000000000..8f7e63a347 --- /dev/null +++ b/test/ssl/signingCA/index.txt.attr @@ -0,0 +1 @@ +unique_subject = yes diff --git a/test/ssl/signingCA/serial b/test/ssl/signingCA/serial new file mode 100644 index 0000000000..eeee65ec41 --- /dev/null +++ b/test/ssl/signingCA/serial @@ -0,0 +1 @@ +05 diff --git a/test/ssl/test-alt-ca.crt b/test/ssl/test-alt-ca.crt new file mode 100644 index 0000000000..902b3ffd2c --- /dev/null +++ b/test/ssl/test-alt-ca.crt @@ -0,0 +1,58 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 2 (0x2) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=GB, ST=Derbyshire, L=Derby, O=Mosquitto Project, OU=Testing, CN=Root CA + Validity + Not Before: Aug 30 22:03:27 2013 GMT + Not After : Aug 29 22:03:27 2018 GMT + Subject: C=GB, ST=Derbyshire, O=Mosquitto Project, OU=Testing, CN=Alternative Signing CA + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:d4:04:e6:69:13:5c:2d:56:c5:0e:10:f3:5b:34: + b8:f0:db:00:4f:e4:c4:e3:2d:a4:32:0b:d6:ab:53: + a1:a8:31:e9:e8:3d:6f:fb:8d:d4:f7:09:ad:54:5c: + 47:b3:27:4a:c8:d1:95:1e:43:2a:c9:4b:b6:c2:81: + 8a:4b:84:56:f2:88:43:b9:53:5a:e2:f8:91:6b:2f: + 26:1e:87:62:73:eb:c1:45:9e:7a:97:3f:f8:db:d2: + bf:d6:44:20:a7:84:fb:11:eb:e9:cb:83:5f:74:39: + a7:95:85:4c:0f:07:c0:01:50:01:ff:34:b4:2c:8f: + 50:d8:ee:61:cd:35:40:2c:05 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + 9A:86:EB:20:AE:18:31:4F:8D:E2:0D:B9:FA:63:31:EA:DF:A4:8C:35 + X509v3 Authority Key Identifier: + keyid:28:8D:BF:F8:DE:D1:F5:BB:26:37:A4:4D:27:FD:37:91:EC:6B:0C:DD + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + af:8e:86:a9:b0:74:70:1b:46:4f:85:3c:7d:4e:6d:a0:de:f4: + 45:e2:34:d8:3f:a1:c6:18:35:ed:1b:f2:19:88:79:b5:da:a5: + df:e8:82:a1:e8:72:c0:da:68:3c:3b:83:fa:23:2d:85:d6:97: + b0:70:02:22:39:10:40:de:e6:45:86:a8:ee:85:a9:04:f2:51: + 99:82:a2:e3:8f:b6:fd:8a:29:e8:3a:47:92:56:a6:98:cf:b7: + 39:5c:4f:83:80:a9:9f:89:f6:a6:33:95:d1:f3:5d:65:30:aa: + ad:89:40:c0:fd:d1:24:6a:f5:b2:c8:50:71:9b:01:c6:cc:8c: + af:35 +-----BEGIN CERTIFICATE----- +MIICqTCCAhKgAwIBAgIBAjANBgkqhkiG9w0BAQUFADByMQswCQYDVQQGEwJHQjET +MBEGA1UECAwKRGVyYnlzaGlyZTEOMAwGA1UEBwwFRGVyYnkxGjAYBgNVBAoMEU1v +c3F1aXR0byBQcm9qZWN0MRAwDgYDVQQLDAdUZXN0aW5nMRAwDgYDVQQDDAdSb290 +IENBMB4XDTEzMDgzMDIyMDMyN1oXDTE4MDgyOTIyMDMyN1owcTELMAkGA1UEBhMC +R0IxEzARBgNVBAgMCkRlcmJ5c2hpcmUxGjAYBgNVBAoMEU1vc3F1aXR0byBQcm9q +ZWN0MRAwDgYDVQQLDAdUZXN0aW5nMR8wHQYDVQQDDBZBbHRlcm5hdGl2ZSBTaWdu +aW5nIENBMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDUBOZpE1wtVsUOEPNb +NLjw2wBP5MTjLaQyC9arU6GoMenoPW/7jdT3Ca1UXEezJ0rI0ZUeQyrJS7bCgYpL +hFbyiEO5U1ri+JFrLyYeh2Jz68FFnnqXP/jb0r/WRCCnhPsR6+nLg190OaeVhUwP +B8ABUAH/NLQsj1DY7mHNNUAsBQIDAQABo1AwTjAdBgNVHQ4EFgQUmobrIK4YMU+N +4g25+mMx6t+kjDUwHwYDVR0jBBgwFoAUKI2/+N7R9bsmN6RNJ/03kexrDN0wDAYD +VR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQCvjoapsHRwG0ZPhTx9Tm2g3vRF +4jTYP6HGGDXtG/IZiHm12qXf6IKh6HLA2mg8O4P6Iy2F1pewcAIiORBA3uZFhqju +hakE8lGZgqLjj7b9iinoOkeSVqaYz7c5XE+DgKmfifamM5XR811lMKqtiUDA/dEk +avWyyFBxmwHGzIyvNQ== +-----END CERTIFICATE----- diff --git a/test/ssl/test-alt-ca.key b/test/ssl/test-alt-ca.key new file mode 100644 index 0000000000..a9f2716c03 --- /dev/null +++ b/test/ssl/test-alt-ca.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDUBOZpE1wtVsUOEPNbNLjw2wBP5MTjLaQyC9arU6GoMenoPW/7 +jdT3Ca1UXEezJ0rI0ZUeQyrJS7bCgYpLhFbyiEO5U1ri+JFrLyYeh2Jz68FFnnqX +P/jb0r/WRCCnhPsR6+nLg190OaeVhUwPB8ABUAH/NLQsj1DY7mHNNUAsBQIDAQAB +AoGACeLwm5W7hqG1LqK7tlUPCqwrp44TYESQk4TZzcNoll89eQbkYeaLN7nLy1NC +RKhgZFzhhze6lwhgzVEdEchqBW9qtznz/D2rxKfuRrlKylG7WzOIPHjIWFpkuRcm +7rTJnqMTBndH8zfGd8c+q7YVRxgA4r4UG8NMq9Mqrp0LmgECQQD7eisZIbsbgVpW +cM3zusTYcud+eky0TJhHuZWRFoIrvNk9iHEcI47J+0t4bTlXxuU9oarL3bvMmNBb +HMceWwnpAkEA19UP2MgM6yKioYJ+2pCYlNdWLR3HHAX4QY1VJk4C2+V5Sw7Ld3NP +WBOH5XYK5tfWmmt+C8g2ga1iY9BEb9cjvQJAIAGDfLZbTvvemIPQ4oVRyk6Ngf5k +xsm809wd2hJoTNLDP16fLrqj0Lcn+tLD6pUI1hg+WaYF4dtNIVt/SDDECQJBAIpi +TbrM6ZuJpYSwyu0QcQRd3R8oTJWnLjm5iLL6qdKcG10Iq2R3RpROUX/KY8sG8M4p +xbOAN5KFvOQKkRa0dnECQQDkz6bXTDHQlerNZ5B0MFFL5VrOC/n4qyVvtJ4jasK9 +3GF3X27zr4XyMfKgL+WPLJMG5nmv62MV1vhUtbvM+GqN +-----END RSA PRIVATE KEY----- diff --git a/test/ssl/test-bad-root-ca.crt b/test/ssl/test-bad-root-ca.crt new file mode 100644 index 0000000000..e8abde9c9a --- /dev/null +++ b/test/ssl/test-bad-root-ca.crt @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICujCCAiOgAwIBAgIJANMI717jaH+OMA0GCSqGSIb3DQEBBQUAMHYxCzAJBgNV +BAYTAkdCMRMwEQYDVQQIDApEZXJieXNoaXJlMQ4wDAYDVQQHDAVEZXJieTEaMBgG +A1UECgwRTW9zcXVpdHRvIFByb2plY3QxEDAOBgNVBAsMB1Rlc3RpbmcxFDASBgNV +BAMMC0JhZCBSb290IENBMB4XDTEzMDgzMDIyMDMxN1oXDTIzMDgyODIyMDMxN1ow +djELMAkGA1UEBhMCR0IxEzARBgNVBAgMCkRlcmJ5c2hpcmUxDjAMBgNVBAcMBURl +cmJ5MRowGAYDVQQKDBFNb3NxdWl0dG8gUHJvamVjdDEQMA4GA1UECwwHVGVzdGlu +ZzEUMBIGA1UEAwwLQmFkIFJvb3QgQ0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJ +AoGBANgcLofeUGl23VLK8ZMgoc8/shrVQFRgBAk4/0S6+HHNZg15Nm3ECl9voC4q +fZY0g0ZCGvWA9QPim1lR7RjzWk4GCjyyJrGAPaQ96Dr1t6xq9eO3l0QyAgyuV3UX +IYhWhuf3q+HtHmZJfof1RSLjNf5JFRCxWoYKGmOP+nDVchCnAgMBAAGjUDBOMB0G +A1UdDgQWBBTeznI4RKjkyJl7N+BvRGCBZAIO2jAfBgNVHSMEGDAWgBTeznI4RKjk +yJl7N+BvRGCBZAIO2jAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAKnY +Fco0xDWuqeJsGJzMiHqWVy6NAfZyMt1UJAojry+jDQXgW+zPscpwAd+24xQLPPOR +R+Cp666oAr1oksaU93Lo5hUmc+1dkaFQZspZ4H29ItZ701OptgSABNTmj2nvdQEG +t8HBAF1tzN8Vxrvy4Mtzs51E6M0oVIV+TegQgXSJ +-----END CERTIFICATE----- diff --git a/test/ssl/test-bad-root-ca.key b/test/ssl/test-bad-root-ca.key new file mode 100644 index 0000000000..37d6483c99 --- /dev/null +++ b/test/ssl/test-bad-root-ca.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICWwIBAAKBgQDYHC6H3lBpdt1SyvGTIKHPP7Ia1UBUYAQJOP9EuvhxzWYNeTZt +xApfb6AuKn2WNINGQhr1gPUD4ptZUe0Y81pOBgo8siaxgD2kPeg69besavXjt5dE +MgIMrld1FyGIVobn96vh7R5mSX6H9UUi4zX+SRUQsVqGChpjj/pw1XIQpwIDAQAB +AoGAMo+dX1JnE9WogGdUz6xRzzBC1j5QV61DJHk+V/E6kT2SA9L5JgM4vg1at5Jf +YZYVpIlwz0GFkYwh9mrRgwXkeXB4LfA31VTOw5l3NzRGyHERiFlhnf5W5pEJOaWm +gaOm7/5M5MBrQqgdNHGNhr1hgggXnSXbrhoOu8LmItcGxrECQQD5HBsQSlE+AJg7 +ayAfugbmRD+P9OyCDPx19GHL3D0FWc9xGLn5XQ9qFyGgY8vKkRUUsAn4TyDLjyBa +zWsX0chvAkEA3hZpfDtZuEtdhA2H5xq8WCH3DU4a2Qf/isB+r1PMV1xZ+FfmqmBE +d6g83NpjyWreZG9bafERCLO8mAjhQdknSQJAVref/DXCvlC6rcSG9ERv7mzHq7dZ +NZSLtgwSl0LdwyUWf4paAyKQISBYRls3MBb9PaxibBwvkG0MmE91/l665QJAfwGk +K6apZYq8HTO7v797bI9oAJTlJ666RjhVeqDaoC8xSKPERzUskp2EyOyf2mUib597 +ULfK/QYE2ZFieMzd+QJAIYYxEYBb1LJ4PPDsV5JQRmaMb6r5ElOMl0sJs878o0L+ +oOOeyn/8cbKHTtJLfm21YfNUO1TsRJZ3bOlhAPrT/g== +-----END RSA PRIVATE KEY----- diff --git a/test/ssl/test-ca.srl b/test/ssl/test-ca.srl new file mode 100644 index 0000000000..53b55300c2 --- /dev/null +++ b/test/ssl/test-ca.srl @@ -0,0 +1 @@ +CDAE0E564A2891AA diff --git a/test/ssl/test-fake-root-ca.crt b/test/ssl/test-fake-root-ca.crt new file mode 100644 index 0000000000..ac128d7a46 --- /dev/null +++ b/test/ssl/test-fake-root-ca.crt @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICsjCCAhugAwIBAgIJAOOjGsO7TBrTMA0GCSqGSIb3DQEBBQUAMHIxCzAJBgNV +BAYTAkdCMRMwEQYDVQQIDApEZXJieXNoaXJlMQ4wDAYDVQQHDAVEZXJieTEaMBgG +A1UECgwRTW9zcXVpdHRvIFByb2plY3QxEDAOBgNVBAsMB1Rlc3RpbmcxEDAOBgNV +BAMMB1Jvb3QgQ0EwHhcNMTMwODMwMjIwMzE3WhcNMjMwODI4MjIwMzE3WjByMQsw +CQYDVQQGEwJHQjETMBEGA1UECAwKRGVyYnlzaGlyZTEOMAwGA1UEBwwFRGVyYnkx +GjAYBgNVBAoMEU1vc3F1aXR0byBQcm9qZWN0MRAwDgYDVQQLDAdUZXN0aW5nMRAw +DgYDVQQDDAdSb290IENBMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCih0Ux +pn7wdxufnDagJtW/mf4at3n1TKGVNirCIh8hoU+EdIqLarNt9ayWnJc3h8cHvG9F +21ic2zbM+I5L9Iavqkpb9hChLm3Ft+HIxKliXnB48Fr5r1J/rt3jIHHwE02HcPm1 +TqLKejHpjngZuMjRV/A5CVJ/iAQZy9ABRjEnRQIDAQABo1AwTjAdBgNVHQ4EFgQU +8YIrNiwFO8c97RWwfMUGokdbxU0wHwYDVR0jBBgwFoAU8YIrNiwFO8c97RWwfMUG +okdbxU0wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQCI9QpvF3fdWO1g +W+zOZzBPspqIXqoRou8P135lNTLWHTixFAscWNdPOZn19zzmPGRKMMmtzOqoRMAx +XDORn9n7ZhyIn2kjw5nTfwrO21TsgYaUOGQSCay5GPFryAEX+1kWkqOoVbJ3F99Q +wU8uq/pogwQ/VTSQJqgUCEvN1aiyLw== +-----END CERTIFICATE----- diff --git a/test/ssl/test-fake-root-ca.key b/test/ssl/test-fake-root-ca.key new file mode 100644 index 0000000000..f95f5580de --- /dev/null +++ b/test/ssl/test-fake-root-ca.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXgIBAAKBgQCih0Uxpn7wdxufnDagJtW/mf4at3n1TKGVNirCIh8hoU+EdIqL +arNt9ayWnJc3h8cHvG9F21ic2zbM+I5L9Iavqkpb9hChLm3Ft+HIxKliXnB48Fr5 +r1J/rt3jIHHwE02HcPm1TqLKejHpjngZuMjRV/A5CVJ/iAQZy9ABRjEnRQIDAQAB +AoGBAJiL7l4Tr8FzifHdZUgcKzOTDfV1kHq0WlT6alecPywJg+EGoaMJmy/yDvOu +NiBgyGZybt5aammPN3hbMvQHpwFqswU6H09YjNYGHgA1sqvZhgczLL6l0PM8dwTl +LDL72SQL5sxM8podBaKVqbXgbGHugvV4cG3l/YzqIednfk0BAkEA1f9L3Fx9nN6B +jUeS9QY80wk7CjAuARHxvlarmTMX08UZgmo4DgwFK0yP5mLiz+X+2xyyClOHOnU+ +8Gcw/pBAeQJBAMJt0o0VOBQW2L8lFnc6mJedTwpohAVJAb957UO4VcR+RmyARd5G +gIYQzQp7pXikOBb97X7BSFDW/dnIbbCD4i0CQQC9MAWOHIrEq4XXNCa8zjXp0KhM +eonBUm7o+lCckSoIg6DoxiUmbgQH4pj5cgTZDZmBdt4D+RJ9YPgyqtgKxdbpAkEA +hOJ6nWJ7SX+z9DBtAmBSGo2xj/OPB+21/CBhQX+jXwDPMSkal6in/vlMqnWHysSy +cURsJc4ElvvZ1BdgoNwCoQJAFceaHLS/G6PKZ+ASdjSUthYIPfXXh8eg4K082uUp +TLN1/csizBLn5Z74T0gGBDD/w1K9/xZ2cUNO+wLkNT9JJg== +-----END RSA PRIVATE KEY----- diff --git a/test/ssl/test-root-ca.crt b/test/ssl/test-root-ca.crt new file mode 100644 index 0000000000..2050feb905 --- /dev/null +++ b/test/ssl/test-root-ca.crt @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICsjCCAhugAwIBAgIJAPTHt3psLAUTMA0GCSqGSIb3DQEBBQUAMHIxCzAJBgNV +BAYTAkdCMRMwEQYDVQQIDApEZXJieXNoaXJlMQ4wDAYDVQQHDAVEZXJieTEaMBgG +A1UECgwRTW9zcXVpdHRvIFByb2plY3QxEDAOBgNVBAsMB1Rlc3RpbmcxEDAOBgNV +BAMMB1Jvb3QgQ0EwHhcNMTMwODMwMjIwMzE2WhcNMjMwODI4MjIwMzE2WjByMQsw +CQYDVQQGEwJHQjETMBEGA1UECAwKRGVyYnlzaGlyZTEOMAwGA1UEBwwFRGVyYnkx +GjAYBgNVBAoMEU1vc3F1aXR0byBQcm9qZWN0MRAwDgYDVQQLDAdUZXN0aW5nMRAw +DgYDVQQDDAdSb290IENBMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDB3KGu +pkiSYbDAaH0ewiCb44CLsAdV5PdYgZHH0jlH8oXkNH0MU3qs7Se2UWrnPQb1VbdI +K2DpSTk+3XuWO0BOqQ+/JuRFN/omwrucyKcRNm4MQP1aY2Tm04zsP0Muy4aSyMIk +F6jxQzAmIgj8VgkQ/y/knS5tbQ2kkoWKRn1RCQIDAQABo1AwTjAdBgNVHQ4EFgQU +KI2/+N7R9bsmN6RNJ/03kexrDN0wHwYDVR0jBBgwFoAUKI2/+N7R9bsmN6RNJ/03 +kexrDN0wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQCn2WxbxDd5ar2U +UvttJW4I+/V1h3iAQCXVDAegOGzsYp3cfIdd2oZY++Q9FhzHh8nP18D+CeC9MMu2 +H2iLULUV08cGSaDLlpo1eq2oJc5ygLOEt/XK7/aIMRwrlP/CoSrI2GPkeA8rka96 +G0WtyGRkzqBKHpt6CnseA2evP5NVcQ== +-----END CERTIFICATE----- diff --git a/test/ssl/test-root-ca.key b/test/ssl/test-root-ca.key new file mode 100644 index 0000000000..f11dca8455 --- /dev/null +++ b/test/ssl/test-root-ca.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQDB3KGupkiSYbDAaH0ewiCb44CLsAdV5PdYgZHH0jlH8oXkNH0M +U3qs7Se2UWrnPQb1VbdIK2DpSTk+3XuWO0BOqQ+/JuRFN/omwrucyKcRNm4MQP1a +Y2Tm04zsP0Muy4aSyMIkF6jxQzAmIgj8VgkQ/y/knS5tbQ2kkoWKRn1RCQIDAQAB +AoGAJJUM0ZdBVJYos3ZEPhyl6KTaqgFysOu/HS1+I/XwpzoFuBWLj1rlaGJsPwRI +JxCmEn+1UWIWLI+LxOgonSXbMWg+G+un/UxgsPGIPeskDuqPXe97tdnd5c1HDwNf +Sy6fzzd0PyyFHrUnxCDl5Y6oJ3O9ZJ1vfELIVyXDoXyCKi0CQQD3U1SM7YhgczaO +9EwHJI8DBeab09DZ3gUT/4zeNVeHGjRtYhZuxJWeINsj0gUJtG/yY6+CaHm3TGPj +WOToqq0/AkEAyKlD6uIJ0YuBozmSTkBUJpEaQ1xkDszgUPlxS+73IGz+LZjstbML +l1irYV5OyWMDdg/JUmnXl+8gOV+1UtFHtwJAfmDGQ3zcwuwcZM/QSZYUvaa2P8ns +XmdkkON0R9dZ8l8hiwMkE1XAXhzL3XHjwSHCUkk91ZUtHMyb/f/eeEU+YQJBAJxD +3QVlBFpwNwPDCOHxjNb/9yDwKWexOxs0Nnv42/EfkA44YlbZ2TQCtGw+QkLo3cAq +aRDKJkBG06R6mT2mhx8CQEhJ5VEhTuM88SQ9mEUup4XDc9wSPK5VK7HLR0Ip22fU +Lh1L/oAsWDIFo3zBQ9aSpiTWzAS/D7gyZPZz17dsJZk= +-----END RSA PRIVATE KEY----- diff --git a/test/ssl/test-signing-ca.crt b/test/ssl/test-signing-ca.crt new file mode 100644 index 0000000000..43a0da06d7 --- /dev/null +++ b/test/ssl/test-signing-ca.crt @@ -0,0 +1,58 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1 (0x1) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=GB, ST=Derbyshire, L=Derby, O=Mosquitto Project, OU=Testing, CN=Root CA + Validity + Not Before: Aug 30 22:03:18 2013 GMT + Not After : Aug 29 22:03:18 2018 GMT + Subject: C=GB, ST=Derbyshire, O=Mosquitto Project, OU=Testing, CN=Signing CA + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (1024 bit) + Modulus: + 00:a4:b5:b9:31:d8:b4:d6:de:49:c0:cc:15:3f:b8: + 50:8b:be:4a:f4:d3:94:a9:dd:53:2a:e9:df:aa:0d: + 3c:08:7b:a7:51:6d:b9:44:98:b7:8d:03:ab:67:9e: + e1:c4:23:4d:33:8d:0a:90:9f:c6:de:82:14:4c:f6: + 75:5d:a4:e1:a3:ea:fc:9b:79:dd:cb:36:20:87:a3: + 9d:eb:e6:5b:0c:53:34:73:cb:dd:a8:e4:0e:7f:f0: + 5f:8a:3c:d8:8f:01:ff:66:31:16:41:1b:e3:7a:61: + 2c:3d:44:a5:a9:dd:1d:42:e5:5a:a1:df:29:35:dc: + 91:5e:9d:82:60:0d:7a:08:db + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + 40:43:50:14:D1:63:7E:0B:7C:97:14:20:63:E5:8A:95:96:9F:D4:AB + X509v3 Authority Key Identifier: + keyid:28:8D:BF:F8:DE:D1:F5:BB:26:37:A4:4D:27:FD:37:91:EC:6B:0C:DD + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + 8a:b1:49:b4:53:eb:bb:9d:5e:20:f4:d7:8d:b8:24:a1:28:95: + 56:72:03:ed:15:ef:f0:ff:65:b5:6e:34:cf:27:83:7b:57:40: + a7:93:61:f0:93:ff:02:b4:74:e0:43:dc:65:0c:e8:a6:20:f9: + 8c:88:82:8f:0e:8d:33:4d:ba:bb:28:ff:29:5f:a8:96:60:31: + f5:13:15:19:60:a4:00:0e:fc:a7:79:b6:10:95:0b:7b:88:75: + 03:ec:7d:94:63:9e:67:2e:2e:9c:fe:79:89:61:93:75:52:f2: + 36:48:a6:2d:c0:b2:a7:36:c2:36:50:53:b3:cd:e7:07:1d:e5: + 6a:1d +-----BEGIN CERTIFICATE----- +MIICnTCCAgagAwIBAgIBATANBgkqhkiG9w0BAQUFADByMQswCQYDVQQGEwJHQjET +MBEGA1UECAwKRGVyYnlzaGlyZTEOMAwGA1UEBwwFRGVyYnkxGjAYBgNVBAoMEU1v +c3F1aXR0byBQcm9qZWN0MRAwDgYDVQQLDAdUZXN0aW5nMRAwDgYDVQQDDAdSb290 +IENBMB4XDTEzMDgzMDIyMDMxOFoXDTE4MDgyOTIyMDMxOFowZTELMAkGA1UEBhMC +R0IxEzARBgNVBAgMCkRlcmJ5c2hpcmUxGjAYBgNVBAoMEU1vc3F1aXR0byBQcm9q +ZWN0MRAwDgYDVQQLDAdUZXN0aW5nMRMwEQYDVQQDDApTaWduaW5nIENBMIGfMA0G +CSqGSIb3DQEBAQUAA4GNADCBiQKBgQCktbkx2LTW3knAzBU/uFCLvkr005Sp3VMq +6d+qDTwIe6dRbblEmLeNA6tnnuHEI00zjQqQn8beghRM9nVdpOGj6vybed3LNiCH +o53r5lsMUzRzy92o5A5/8F+KPNiPAf9mMRZBG+N6YSw9RKWp3R1C5Vqh3yk13JFe +nYJgDXoI2wIDAQABo1AwTjAdBgNVHQ4EFgQUQENQFNFjfgt8lxQgY+WKlZaf1Ksw +HwYDVR0jBBgwFoAUKI2/+N7R9bsmN6RNJ/03kexrDN0wDAYDVR0TBAUwAwEB/zAN +BgkqhkiG9w0BAQUFAAOBgQCKsUm0U+u7nV4g9NeNuCShKJVWcgPtFe/w/2W1bjTP +J4N7V0Cnk2Hwk/8CtHTgQ9xlDOimIPmMiIKPDo0zTbq7KP8pX6iWYDH1ExUZYKQA +DvynebYQlQt7iHUD7H2UY55nLi6c/nmJYZN1UvI2SKYtwLKnNsI2UFOzzecHHeVq +HQ== +-----END CERTIFICATE----- diff --git a/test/ssl/test-signing-ca.key b/test/ssl/test-signing-ca.key new file mode 100644 index 0000000000..7b0b0af67a --- /dev/null +++ b/test/ssl/test-signing-ca.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQCktbkx2LTW3knAzBU/uFCLvkr005Sp3VMq6d+qDTwIe6dRbblE +mLeNA6tnnuHEI00zjQqQn8beghRM9nVdpOGj6vybed3LNiCHo53r5lsMUzRzy92o +5A5/8F+KPNiPAf9mMRZBG+N6YSw9RKWp3R1C5Vqh3yk13JFenYJgDXoI2wIDAQAB +AoGAHx3Jn9Ydy93wtwCXHxOV++B2TqxOEI0kch3+yCR56+xYXrTI5GGpg3VnA0tr +wV8d7Zg+n7XfnxeZ+DQzVf6ZNc24mf7J7gM881GA1zmrUOyolpo5sgc9PW4mbyC1 +rvMsLyEGP+fP93MDJ0CYhQjxa4eGNsiLTXtHOsg9y4a1cgkCQQDXgy5ajpo4vzvK +2zMPINIk2QdRQ6jIKwnKUtBmoNsCPcwIW1yhJUc6g1C3qGpi0p7edeLlOaAfugbm +5m1M70L3AkEAw6dAg6fWTDNxt6IO6GtdJWoEJbzV7fWvRUuT6Zh/m14OwGMJvOQN +vz4U0FFZK2EbUBL+Za5enzJRyj2AUOiMPQJBAMo3pukF4aPZnIstvu01CLnWgs03 +xUl9SMR1jGKgEKA7yBUXVQVH61v2F2kdOCXeJ3/p8arQtXTPouZJ1MlZv+UCQA80 +XzIcB+5SDSNNJ8VuGoX+0CWyoBlm/2DuN6dun3QOgiz3RVl1i4/yHiH2QGy7lijJ +4RU70MSkX3DNCLzA5a0CQEb3xQSj+YJW7AHAQI8/9vSO5f2yYuyrtMy5aU8k7hKB +Sopu/XLwoWt27pl596Gur0adYnBAZMYYueY8oCN5DNU= +-----END RSA PRIVATE KEY----- diff --git a/test/to-test b/test/to-test new file mode 100644 index 0000000000..0edb848e85 --- /dev/null +++ b/test/to-test @@ -0,0 +1,7 @@ +message delivery 0, 1, 2 +retained message delivery +queued message delivery for 1, 2 +wills +bridge connections +password authentication +ACLs