def _handle_tls_args(self): """Make sure TLS options are valid """ if self._tls_args: # If _any_ options are specified, first assume we DO want it enabled self._enable_tls = True else: self._enable_tls = False return if "enable" in self._tls_args: if not self._tls_args.pop("enable"): # if enable=false, return immediately self._enable_tls = False return if "keyfile" in self._tls_args and "certfile" not in self._tls_args: raise exceptions.MQTTTLSError( "If specifying a TLS keyfile, a certfile also needs to be specified" ) def check_file_exists(key): try: with open(self._tls_args[key], "r"): pass except LoadError as e: raise_from( exceptions.MQTTTLSError( "Couldn't load '{}' from '{}'".format( key, self._tls_args[key])), e) except KeyError: pass # could be moved to schema validation stage check_file_exists("cert_reqs") check_file_exists("certfile") check_file_exists("keyfile") # This shouldn't raise an AttributeError because it's enumerated try: self._tls_args["cert_reqs"] = getattr(ssl, self._tls_args["cert_reqs"]) except KeyError: pass try: self._tls_args["tls_version"] = getattr( ssl, self._tls_args["tls_version"]) except AttributeError as e: raise_from( exceptions.MQTTTLSError( "Error getting TLS version from " "ssl module - ssl module had no attribute '{}'. Check the " "documentation for the version of python you're using to see " "if this a valid option.".format( self._tls_args["tls_version"])), e) except KeyError: pass
def _handle_tls_args(tls_args): """Make sure TLS options are valid """ if not tls_args: return None if "enable" in tls_args: if not tls_args.pop("enable"): # if enable=false, return immediately return None if "keyfile" in tls_args and "certfile" not in tls_args: raise exceptions.MQTTTLSError( "If specifying a TLS keyfile, a certfile also needs to be specified" ) def check_file_exists(key): try: with open(tls_args[key], "r", encoding="utf-8"): pass except IOError as e: raise exceptions.MQTTTLSError( "Couldn't load '{}' from '{}'".format(key, tls_args[key]) ) from e except KeyError: pass # could be moved to schema validation stage check_file_exists("cert_reqs") check_file_exists("certfile") check_file_exists("keyfile") # This shouldn't raise an AttributeError because it's enumerated try: tls_args["cert_reqs"] = getattr(ssl, tls_args["cert_reqs"]) except KeyError: pass try: tls_args["tls_version"] = getattr(ssl, tls_args["tls_version"]) except AttributeError as e: raise exceptions.MQTTTLSError( "Error getting TLS version from " "ssl module - ssl module had no attribute '{}'. Check the " "documentation for the version of python you're using to see " "if this a valid option.".format(tls_args["tls_version"]) ) from e except KeyError: pass return tls_args
def check_file_exists(key): try: with open(self._tls_args[key], "r"): pass except LoadError as e: raise_from(exceptions.MQTTTLSError("Couldn't load '{}' from '{}'".format( key, self._tls_args[key])), e) except KeyError: pass
def check_file_exists(key): try: with open(tls_args[key], "r", encoding="utf-8"): pass except IOError as e: raise exceptions.MQTTTLSError( "Couldn't load '{}' from '{}'".format(key, tls_args[key])) from e except KeyError: pass
def __init__(self, **kwargs): expected_blocks = { "client": { "client_id", "clean_session", # Can't really use this easily... # "userdata", # Force mqttv311 - fix if this becomes an issue # "protocol", "transport", }, "connect": { "host", "port", "keepalive", "timeout", }, "tls": { "enable", "ca_certs", "cert_reqs", "certfile", "keyfile", "tls_version", "ciphers", }, "auth": { "username", "password", }, } logger.debug("Initialising MQTT client with %s", kwargs) # check main block first check_expected_keys(expected_blocks.keys(), kwargs) # then check constructor/connect/tls_set args self._client_args = kwargs.pop("client", {}) check_expected_keys(expected_blocks["client"], self._client_args) self._connect_args = kwargs.pop("connect", {}) check_expected_keys(expected_blocks["connect"], self._connect_args) self._auth_args = kwargs.pop("auth", {}) check_expected_keys(expected_blocks["auth"], self._auth_args) if "host" not in self._connect_args: msg = "Need 'host' in 'connect' block for mqtt" logger.error(msg) raise exceptions.MissingKeysError(msg) self._connect_timeout = self._connect_args.pop("timeout", 3) # If there is any tls kwarg (including 'enable'), enable tls self._tls_args = kwargs.pop("tls", {}) check_expected_keys(expected_blocks["tls"], self._tls_args) self._handle_tls_args() logger.debug("TLS is %s", "enabled" if self._enable_tls else "disabled") logger.debug("Paho client args: %s", self._client_args) self._client = paho.Client(**self._client_args) self._client.enable_logger() if self._auth_args: self._client.username_pw_set(**self._auth_args) self._client.on_message = self._on_message if self._enable_tls: try: self._client.tls_set(**self._tls_args) except ValueError as e: # tls_set only raises ValueErrors directly raise_from( exceptions.MQTTTLSError("Unexpected error enabling TLS", e)) except ssl.SSLError as e: # incorrect cipher, etc. raise_from( exceptions.MQTTTLSError( "Unexpected SSL error enabling TLS", e)) # Arbitrary number, could just be 1 and only accept 1 message per stages # but we might want to raise an error if more than 1 message is received # during a test stage. self._message_queue = Queue(maxsize=10) self._userdata = { "queue": self._message_queue, } self._client.user_data_set(self._userdata)