def communicate(self, raw: str, wait=True) -> str: eof = False response = '' data = raw.strip() + '\n' num_lines = data.count('\n') try: self._ssl_sock.sendall(data.encode('utf-8')) if not wait: return None while not eof: response += self._ssl_sock.recv().decode('utf-8') eof = response.count("\n") == num_lines or not response return response except socket.error as e: if 'read operation timed out' in str(e): raise Error("Timed out waiting for CLI response. " "Perhaps the tunnel endpoint is incorrect, " "or the LMS CLI is down?") else: print_d("Couldn't communicate with Squeezebox ({error!r})", error=e) self.failures += 1 if self.failures >= self._MAX_FAILURES: self.is_connected = False self._ssl_sock.close() raise Error("Too many Squeezebox failures. Disconnecting") return None
def connected(client, userdata, flags, rc): print_d("Connected to {client}. Subscribing to {topic}", client=self.client, topic=self.resp_topic) result, mid = self.client.subscribe(self.resp_topic, qos=1) if result != MQTT_ERR_SUCCESS: raise Error("Couldn't subscribe to '{topic}'", self.resp_topic)
def _conf_file_of(self, rel_glob: str) -> str: full_glob = os.path.join(self.settings.cert_dir, rel_glob) results = glob(full_glob) try: return results[0] except IndexError: raise Error("Can't find {glob} within dir {base}".format( base=self.settings.cert_dir, glob=rel_glob))
def _request(self, lines, raw=False, wait=True) -> List[str]: """ Send multiple pipelined requests to the server, if connected, and return their responses, assuming order is maintained (which seems safe). """ if not (lines and len(lines)): return [] lines = [l.rstrip() for l in lines] match = RESPONSE_CMD_REGEX.match(lines[0]) # If we can't match, then take the first two words (for debugging) first_word = (match.group(2) if match else ' '.join(lines[0].split()[:2])) if not (self.transport.is_connected or first_word == 'login'): try: print_w("Transport wasn't connected - trying to restart") self.transport.start() except Exception: raise SqueezeboxException( "Can't do '{cmd}', {transport} is not connected".format( cmd=first_word, transport=self.transport)) if self._debug: print_d("<<<< " + "\n..<< ".join(lines)) request = "\n".join(lines) raw_response = self.transport.communicate(request, wait=wait) if not wait: return [] if not raw_response: raise SqueezeboxException( "No further response from %s. Login problem?" % self) raw_response = raw_response.rstrip("\n") response = raw_response if raw else self._unquote(raw_response) if self._debug: print_d(">>>> " + "\n..>> ".join(response.splitlines())) def start_point(text): if first_word == 'login': return 6 delta = -1 if text.endswith('?') else 1 return len(self._unquote(text) if raw else text) + delta resp_lines = response.splitlines() if len(lines) != len(resp_lines): print_d("Got mismatched response: {lines} vs {resp_lines}", lines=lines, resp_lines=resp_lines) raise Error("Transport response problem: got %d lines, not %d" % (len(resp_lines), len(lines))) return [ resp_line[start_point(line):] for line, resp_line in zip(lines, resp_lines) ]
def connect(self, host=None, port=None, keepalive=30, bind_address=""): host = host or self.settings.hostname port = port or self.settings.port check_listening(host, port, msg="check your MQTT settings") ret = super().connect(host=host, port=port, keepalive=keepalive, bind_address=bind_address) if MQTT_ERR_SUCCESS == ret: print_d("Connecting to {}", self.settings) return ret raise Error("Couldn't connect to {}".format(self.settings))
def connect(self, host=None, port=None, keepalive=30, bind_address=""): print_d("Connecting {client}...", client=self) host = host or self._host port = port or self._port check_listening(host, port, msg="check your MQTT settings") print_d("Remote socket is listening, let's continue.") try: ret = super().connect(host=host, port=port, keepalive=keepalive, bind_address=bind_address) except ssl.SSLError as e: if 'SSLV3_ALERT_CERTIFICATE_UNKNOWN' in str(e): raise Error("Certificate problem with MQTT. " "Is the certificate enabled in AWS?") else: if ret == MQTT_ERR_SUCCESS: print_d("Connected to {settings}", settings=self.settings) self.connected = True return ret raise Error( "Couldn't connect to {settings}".format(settings=self.settings))
def communicate(self, raw: str, wait=True) -> str: data = raw.strip() + '\n' num_lines = data.count('\n') self._clear() ret = self.client.publish(self.req_topic, data.encode('utf-8'), qos=1 if wait else 0) if not wait: return None ret.wait_for_publish() if ret.rc != MQTT_ERR_SUCCESS: raise Error("Error publishing message: {}", error_string(ret.rc)) print_d("Published to {topic} OK. Waiting for {num} line(s)...", topic=self.req_topic, num=num_lines) wait_for(lambda s: len(s.response_lines) >= num_lines, context=self, what="response from mqtt-squeeze", timeout=5) return "\n".join(m.decode('utf-8') for m in self.response_lines)
def communicate(self, raw: str, wait=True) -> str: eof = False response = '' data = raw.strip() + '\n' num_lines = data.count('\n') try: self._ssl_sock.sendall(data.encode('utf-8')) if not wait: return None while not eof: response += self._ssl_sock.recv().decode('utf-8') eof = response.count("\n") == num_lines or not response return response except socket.error as e: print_d("Couldn't communicate with Squeezebox ({!r})", e) self.failures += 1 if self.failures >= self._MAX_FAILURES: self.is_connected = False self._ssl_sock.close() raise Error("Too many Squeezebox failures. Disconnecting") return None
def __init__(self, hostname, port=9090, ca_file=None, cert_file=None, verify_hostname=False, timeout=5): super().__init__() self.hostname = hostname self.port = port self.timeout = timeout self.failures = 0 context = ssl.SSLContext(ssl.PROTOCOL_TLSv1) self.__harden_context(context) try: if ca_file: context.load_verify_locations(ca_file) if cert_file: context.verify_mode = ssl.CERT_REQUIRED context.check_hostname = verify_hostname context.load_cert_chain(cert_file) except ssl.SSLError as e: raise Error( "Problem with Cert / CA (+key) files ({} / {}). " "Does it include the private key?".format(cert_file, ca_file), e) except IOError as e: if 'No such file or directory' in e.strerror: self._die("Can't find cert '{cert_file}' or CA '{ca_file}'. " "Check CERT_FILE / CA_FILE_PATH in settings".format( ca_file=ca_file, cert_file=cert_file)) self._die( "could be mismatched certificate files, " "or wrong hostname in cert." "Check CERT_FILE and certs on server too.", e) sock = socket.socket() sock.settimeout(self.timeout) self._ssl_sock = context.wrap_socket(sock, server_hostname=hostname) print_d("Connecting to port {port} on {hostname}", port=port, hostname=hostname or '(localhost)') try: self._ssl_sock.connect((hostname, port)) except socket.gaierror as e: if "Name or service not know" in e.strerror: self._die( "unknown host ({}) - check SERVER_HOSTNAME".format( hostname), e) self._die("Couldn't connect to %s with TLS" % (self, ), e) except IOError as e: err_str = e.strerror or str(e) if 'Connection refused' in err_str: self._die("nothing listening on {}. " "Check settings, or (re)start server.".format(self)) elif 'WRONG_VERSION_NUMBER' in err_str: self._die( 'probably not TLS on port {} - ' 'wrong SERVER_PORT maybe?'.format(port), e) elif 'Connection reset by peer' in err_str: self._die("server killed the connection - handshake error? " "Check the SSL tunnel logs") elif 'CERTIFICATE_VERIFY_FAILED' in err_str: self._die( "Cert not trusted by / from server. " "Is your CA correct? Is the cert expired? " "Is the cert for the right hostname ({})?".format( hostname), e) elif 'timed out' in err_str: msg = ("Couldn't connect to port {port} on {host} - " "check the server setup and the firewall.").format( host=self.hostname, port=self.port) self._die(msg) self._die("Connection problem ({}: {})".format( type(e).__name__, err_str)) peer_cert = self._ssl_sock.getpeercert() if peer_cert is None: self._die("No certificate configured at {}".format(self)) elif not peer_cert: print_w("Unvalidated server cert at {}", self) else: subject_data = peer_cert['subject'] try: data = {k: v for d in subject_data for k, v in d} except Exception: data = subject_data print_d("Validated cert for {}", data) self.is_connected = True
def _die(self, msg, e=None): raise Error(msg, e)
def disconnect(self): ret = super().disconnect() self.connected = False if ret != MQTT_ERR_SUCCESS and ret != MQTT_ERR_NO_CONN: raise Error("Failed to disconnect (%s)" % error_string(ret)) return ret
def _die(self, msg, err=None, **kwargs): raise Error(msg.format(**kwargs), err)