Esempio n. 1
0
    def setUp(self):
        host = socket.socket()
        host.bind(("127.0.0.1", 0))
        host.listen(1)

        self.client_sock = cs = socket.socket()
        cs.setblocking(False)
        cs.connect_ex(host.getsockname())
        self.serverSock, self.clientEndpoint = host.accept()
        host.close()

        self.loop = pyev.Loop()
        _prepare_cert()
        s = Storage("security", "private")
        self.certfile = s.get_path("cert.pem")
        self.keyfile = s.get_path("sslkey.pem")
Esempio n. 2
0
    def update_mbfw(cls):
        cls.reset_mainboard()
        sleep(1.0)

        storage = Storage("update_fw")
        tty = cls.get_mainboard_port()

        if not storage.exists("mainboard.bin"):
            raise RuntimeWarning("mainboard.bin not found")

        if os.system("stty -F %s 1200" % tty) != 0:
            raise RuntimeWarning("stty exec failed")

        sleep(2.0)

        fw_path = storage.get_path("mainboard.bin")
        if os.system("bossac -p %s -e -w -v -b %s" %
                     (tty.split("/")[-1], fw_path)) != 0:
            raise RuntimeWarning("bossac exec failed")

        os.rename(fw_path, fw_path + ".updated")
        os.system("sync")
        sleep(0.75)
        cls.reset_mainboard()
        sleep(0.75)
Esempio n. 3
0
    def __init__(self, loop, taskfile, terminated_callback=None):
        storage = Storage("run")

        oldpid = load_pid(storage.get_path("fluxplayerd.pid"))
        if oldpid is not None:
            try:
                os.kill(oldpid, SIGKILL)
                logger.error("Kill old player process: %i", oldpid)
            except Exception:
                logger.exception("Error while kill old process: %i", oldpid)

        s = create_mainboard_socket()
        try:
            s.send("\n@DISABLE_LINECHECK\nX5S115\n")

            if os.path.exists(PLAY_ENDPOINT):
                os.unlink(PLAY_ENDPOINT)
            ff = FCodeFile(taskfile)

            self.playinfo = ff.metadata, ff.image_buf

            cmd = [
                "fluxplayer", "-c", PLAY_ENDPOINT, "--task", taskfile, "--log",
                storage.get_path("fluxplayerd.log"), "--pid",
                storage.get_path("fluxplayerd.pid")
            ]
            if logger.getEffectiveLevel() <= 10:
                cmd += ["--debug"]

            f = open(storage.get_path("fluxplayerd.err.log"), "a")
            proc = Popen(cmd, stdin=PIPE, stderr=f.fileno())
            child_watcher = loop.child(proc.pid, False, self.on_process_dead,
                                       terminated_callback)
            child_watcher.start()
            metadata.update_device_status(1, 0, "N/A", err_label="")

            self.child_watcher = child_watcher
            self.proc = proc

        except FCodeError as e:
            s.send("X5S0\n")
            raise RuntimeError(FILE_BROKEN, *e.args)
        except Exception as e:
            s.send("X5S0\n")
            raise RuntimeError(UNKNOWN_ERROR)
        finally:
            s.close()
Esempio n. 4
0
def get_cert():
    from binascii import b2a_base64 as to_base64
    from OpenSSL import crypto
    from fluxmonitor.storage import Storage
    s = Storage("security", "private")

    try:
        key = crypto.load_privatekey(crypto.FILETYPE_PEM, s["sslkey.pem"])
    except (crypto.Error, TypeError):
        pkey = get_private_key()
        s["sslkey.pem"] = pem = pkey.export_pem()
        key = crypto.load_privatekey(crypto.FILETYPE_PEM, pem)

    try:
        cert = crypto.load_certificate(crypto.FILETYPE_PEM, s["cert2.pem"])
    except (crypto.Error, TypeError):
        issuersubj = crypto.X509Name(crypto.X509().get_subject())
        issuersubj.C = "TW"
        issuersubj.L = "Taipei"
        issuersubj.O = "FLUX Inc."

        cert = crypto.X509()
        subj = cert.get_subject()

        subj.O = "FLUX 3D Delta Printer"
        subj.CN = (get_uuid() + ":" + get_serial() + ":")
        ext = crypto.X509Extension("nsComment", True,
                                   to_base64(get_identify()))
        cert.add_extensions((ext, ))

        cert.set_serial_number(1001)
        cert.gmtime_adj_notBefore(0)
        cert.gmtime_adj_notAfter(60 * 365 * 24 * 60 * 60)
        cert.set_issuer(cert.get_subject())
        cert.set_pubkey(key)
        cert.sign(key, 'sha512')
        s["cert2.pem"] = crypto.dump_certificate(crypto.FILETYPE_PEM, cert)

    return s.get_path("cert2.pem"), s.get_path("sslkey.pem")
Esempio n. 5
0
class CloudService(ServiceBase):
    config_ts = -1
    error_counter = 0
    config_enable = None
    cloud_netloc = None
    storage = None

    _notify_up_required = False
    _notify_last_st = {"st_id": None}
    _notify_last_ts = 0
    _notify_aggressive = 0
    _notify_retry_counter = 0
    aws_client = None
    postback_url = None

    def __init__(self, options):
        super(CloudService, self).__init__(logger, options)

        mqttlogger = logging.getLogger(
            "AWSIoTPythonSDK.core.protocol.mqttCore")
        if logger.getEffectiveLevel() < logging.INFO:
            mqttlogger.setLevel(logging.DEBUG)
        else:
            mqttlogger.setLevel(logging.WARNING)

        self.storage = Storage("cloud")
        self.uuidhex = security.get_uuid()

        if options.cloud.endswith("/"):
            self.cloud_netloc = options.cloud[:-1]
        else:
            self.cloud_netloc = options.cloud

        self.diagnosis()

    def on_start(self):
        logger.info("Cloud service started (version=%s)", __version__)
        if security.get_serial() == "XXXXXXXXXX":
            logger.error("Serial invalid, cloud deamon will be silenced.")
        else:
            self.timer = self.loop.timer(3., 3., self.on_timer)
            self.timer.start()

    def require_identify(self):
        logger.debug("Require identify")

        url = urlparse("%s/axon/require_identify" % self.cloud_netloc)

        pkey = security.get_private_key()
        publickey_base64 = b2a_base64(pkey.export_pubkey_der())
        serial = security.get_serial()
        model = get_model_id()
        identify_base64 = b2a_base64(security.get_identify())

        try:
            conn = get_connection(url)
            logger.debug("Require identify request sent")
            conn.post_request(
                url.path, {
                    "serial": serial,
                    "uuid": self.uuidhex,
                    "model": model,
                    "version": __version__,
                    "publickey": publickey_base64,
                    "signature": identify_base64
                })
            resp = conn.get_json_response()

            if resp.get("status") == "ok":
                return resp["token"].encode(), resp["subject"]
            elif resp.get("status") == "error":
                raise RuntimeWarning(*resp["error"])
            else:
                logger.error("require_identify response unknown response: %s",
                             resp)
                raise RuntimeWarning("RESPONSE_ERROR")
        except socket.gaierror as e:
            raise RuntimeError("REQUIRE_IDENTIFY", "DNS_ERROR", e)
        except socket.error as e:
            raise RuntimeError("REQUIRE_IDENTIFY", "CONNECTION_ERROR", e)
        except RuntimeWarning as e:
            raise RuntimeError("REQUIRE_IDENTIFY", *e.args)
        except Exception as e:
            logger.exception("Error in require identify")
            raise RuntimeError("REQUIRE_IDENTIFY", "UNKNOWN_ERROR", e)

    def get_identify(self, token, request_asn1):
        logger.debug("Get identify")

        url = urlparse("%s/axon/identify" % self.cloud_netloc)
        uuidhex = security.get_uuid()

        try:
            conn = get_connection(url)
            logger.debug("Get identify request sent")
            conn.post_request(
                url.path, {
                    "uuid": uuidhex,
                    "token": token,
                    "x509_request": request_asn1,
                    "metadata": {
                        "version": __version__
                    }
                })
            resp = conn.get_json_response()

            if resp.get("status") == "ok":
                return resp
            elif resp.get("status") == "error":
                raise RuntimeWarning(*resp["error"])
            else:
                logger.error("get identify response unknown response: %s",
                             resp)
                raise RuntimeWarning("RESPONSE_ERROR")
        except socket.gaierror as e:
            raise RuntimeError("GET_IDENTIFY", "DNS_ERROR", e)
        except socket.error as e:
            raise RuntimeError("GET_IDENTIFY", "CONNECTION_ERROR", e)
        except RuntimeWarning as e:
            raise RuntimeError("GET_IDENTIFY", *e.args)
        except Exception as e:
            logger.exception("Error in get identify")
            raise RuntimeError("GET_IDENTIFY", "UNKNOWN_ERROR", e)

    def generate_certificate_request(self, subject_list):
        from subprocess import Popen, PIPE

        fxkey = security.get_private_key()
        self.storage["key.pem"] = fxkey.export_pem()
        subject_str = "/" + "/".join(("=".join(i) for i in subject_list))

        proc = Popen([
            "openssl", "req", "-new", "-key",
            self.storage.get_path("key.pem"), "-subj", subject_str, "-keyform",
            "pem", "-nodes", "-sha256"
        ],
                     stdin=PIPE,
                     stderr=PIPE,
                     stdout=PIPE)
        stdoutdata, stderrdata = proc.communicate(input="")

        while proc.poll() is None:
            pass
        if proc.returncode > 0:
            error = stdoutdata if stdoutdata else stderrdata
            if error:
                error = error.decode("utf8")
            else:
                error = "Process return %i" % proc.returncode
            raise SystemError(error)
        else:
            return stdoutdata
        # csr = crypto.X509Req()
        # subj = csr.get_subject()
        # for name, value in subject_list:
        #     if name == "CN":
        #         add_result = crypto._lib.X509_NAME_add_entry_by_NID(
        #             subj._name, 13, crypto._lib.MBSTRING_UTF8,
        #             value.encode('utf-8'), -1, -1, 0)
        #         if not add_result:
        #             crypto._raise_current_error()
        #     else:
        #         setattr(subj, name, value)

        # fxkey = security.get_private_key()
        # opensslkey = crypto.load_privatekey(crypto.FILETYPE_ASN1,
        #                                     fxkey.export_der())
        # self.storage["key.pem"] = fxkey.export_pem()
        # csr.set_pubkey(opensslkey)
        # csr.sign(opensslkey, "sha256")
        # return crypto.dump_certificate_request(crypto.FILETYPE_PEM, csr)

    def fetch_identify(self):
        logger.info("Fetch identify")

        token, subject_list = self.require_identify()
        logger.info("Require identify return token=%s, subject=%s", token,
                    subject_list)

        request_asn1 = self.generate_certificate_request(subject_list)
        doc = self.get_identify(token, request_asn1)
        logger.info("Get identify return %s", doc["status"])

        self.storage["token"] = token
        self.storage["endpoint"] = doc["endpoint"]
        self.storage["client_id"] = doc["client_id"]
        self.storage["certificate_reqs.pem"] = doc["certificate_reqs"]
        self.storage["certificate.pem"] = doc["certificate"]

    def notify_up(self, c):
        payload = json.dumps({
            "state": {
                "reported": {
                    "version": __version__,
                    "token": self.storage["token"],
                    "nickname": metadata.nickname
                }
            }
        })
        c.publish(self._notify_topic, payload, 1)

    def postback_status(self, st_id):
        url = Storage("general", "meta")["player_postback_url"]
        if url:
            try:
                if '"' in url or '\\' in url:
                    logger.error("Bad url: %r", url)
                else:
                    url = url % {"st_id": st_id}
                    os.system("curl -s -o /dev/null \"%s\"" % url)
            except Exception:
                logger.exception("Error while post back status, url: %s", url)

    def notify_update(self, new_st, now):
        if metadata.verify_mversion() is False:
            self._notify_up_required = True

        new_st_id = new_st["st_id"]

        if self._notify_last_st["st_id"] == new_st_id:
            if self._notify_last_ts > self._notify_aggressive:
                # notify aggressive is invalid
                if now - self._notify_last_ts < 1200:
                    # update every 1200 seconds
                    return
            else:
                # notify aggressive is valid
                if new_st_id <= 0 and now - self._notify_last_ts < 1200:
                    # update every 1200 seconds if device is idle or occupy
                    return
        elif new_st_id in (48, 64, 128):  # paused, completed, aborted
            self.postback_status(new_st_id)

        c = self.aws_client.getMQTTConnection()

        payload = json.dumps({"state": {"reported": new_st}})

        if self._notify_up_required:
            self.notify_up(c)
            self._notify_up_required = False
        c.publish(self._notify_topic, payload, 0)
        self._notify_last_st = new_st
        self._notify_last_ts = now

    def aws_on_request_callback(self, client, userdata, message):
        # incommint topic format: "device/{token}/request/{action}"
        # response topic format: "device/{token}/response/{action}"

        action = message.topic.split("/", 3)[-1]
        logger.debug("IoT request: %s", action)
        response_topic = "device/%s/response/%s" % (self.aws_token, action)

        try:
            payload = json.loads(message.payload)
        except ValueError:
            logger.error("IoT request payload error: %s", message.payload)
            client.publish(response_topic, message.payload)
            return

        if payload.get("uuid") != self.uuidhex:
            client.publish(
                response_topic,
                json.dumps({
                    "status": "reject",
                    "cmd_index": payload.get("cmd_index")
                }))
            return

        cmd_index = payload.get("cmd_index")

        try:
            if action == "getchu":
                try:
                    current_hash = metadata.cloud_hash
                    access_id, signature = payload.get("validate_message")
                    client_key = security.get_keyobj(access_id=access_id)
                    if client_key.verify(current_hash, a2b_base64(signature)):
                        client.publish(
                            response_topic,
                            json.dumps({
                                "status": "ok",
                                "cmd_index": cmd_index
                            }))
                    else:
                        client.publish(
                            response_topic,
                            json.dumps({
                                "status": "reject",
                                "cmd_index": cmd_index
                            }))
                finally:
                    metadata.cloud_hash = os.urandom(32)
            elif action == "monitor":
                self._notify_aggressive = time() + 180
                self._notify_last_st["st_id"] = None
                client.publish(
                    response_topic,
                    json.dumps({
                        "status": "ok",
                        "cmd_index": cmd_index
                    }))
            elif action == "camera":
                self.require_camera(payload["camera_id"], payload["endpoint"],
                                    payload["token"])
                client.publish(
                    response_topic,
                    json.dumps({
                        "status": "ok",
                        "cmd_index": cmd_index
                    }))
            elif action == "control":
                self.require_control(payload["endpoint"], payload["token"])
                client.publish(
                    response_topic,
                    json.dumps({
                        "status": "ok",
                        "cmd_index": cmd_index
                    }))
            else:
                client.publish(
                    response_topic,
                    json.dumps({
                        "status": "error",
                        "cmd_index": cmd_index
                    }))
        except Exception:
            logger.exception("Handle aws request error")
            client.publish(
                response_topic,
                json.dumps({
                    "status": "error",
                    "cmd_index": cmd_index
                }))

    def begin_session(self):
        logger.info("Begin Session")
        if not self.storage["certificate.pem"]:
            metadata.cloud_status = (False, ("INIT", ))
            self.fetch_identify()

        self.setup_session()
        metadata.cloud_status = (True, ())

    def setup_session(self):
        logger.info("Setup session")
        from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTShadowClient
        from AWSIoTPythonSDK.core.protocol.mqttCore import (
            connectTimeoutException)

        client_id, token = self.storage["client_id"], self.storage["token"]
        if not client_id or not token:
            raise SystemError("client_id or token error")

        try:
            ipaddr, port = self.storage["endpoint"].split(":")
        except (AttributeError, ValueError):
            raise SystemError("endpoint error")

        cafile = self.storage.get_path("certificate_reqs.pem")
        cert = self.storage.get_path("certificate.pem")
        key = self.storage.get_path("key.pem")

        if False in map(lambda p: os.path.exists(p), (cafile, cert, key)):
            raise SystemError("cafile or cert or key not exist")

        client = AWSIoTMQTTShadowClient(client_id)
        client.configureEndpoint(ipaddr, int(port))
        client.configureCredentials(cafile, key, cert)
        client.configureConnectDisconnectTimeout(10)
        client.configureMQTTOperationTimeout(5)

        try:
            client.connect()
        except SSLError as e:
            raise RuntimeError("SESSION", "TLS_ERROR", "%s" % e.reason)
        except (connectTimeoutException, socket.gaierror, socket.error):
            raise RuntimeError("SESSION", "CONNECTION_ERROR")

        conn = client.getMQTTConnection()
        conn.subscribe("device/%s/request/+" % self.storage["token"], 1,
                       self.aws_on_request_callback)

        self.aws_client = client
        self.aws_token = token
        self._notify_topic = "$aws/things/%s/shadow/update" % (
            self.storage["client_id"])
        self.notify_up(conn)
        metadata.cloud_hash = os.urandom(32)
        logger.info("Session ready")

    def teardown_session(self):
        if self.aws_client:
            aws_client = self.aws_client
            self.aws_client = None

            conn = aws_client.getMQTTConnection()
            try:
                if conn._mqttCore._pahoClient._thread.isAlive():
                    aws_client.disconnect()
                else:
                    logger.error("MQTT thread closed, remove directly")

            except Exception:
                logger.exception("AWS panic while disconnect from aws, "
                                 "make a ugly bugfix")
                try:
                    conn._mqttCore._pahoClient._thread_terminate = True
                    if conn._mqttCore._pahoClient._sock:
                        conn._mqttCore._pahoClient._sock.close()
                    return
                except Exception:
                    logger.exception("AWS panic ugly bugfix failed")

    def on_timer(self, watcher, revent):
        try:
            if self.config_ts != metadata.mversion:
                self.error_counter = 0

                if metadata.enable_cloud == "R":
                    logger.warning("Refetch required")
                    if self.aws_client:
                        self.teardown_session()
                    metadata.enable_cloud = "A"
                    metadata.cloud_status = (False, ("INIT", ))
                    self.fetch_identify()

                self.config_ts = metadata.mversion
                self.config_enable = (metadata.enable_cloud == "A")
                if self.config_enable is False:
                    metadata.cloud_status = (False, ("DISABLE", ))

            if self.config_enable:
                if self.aws_client:
                    self.notify_update(metadata.format_device_status, time())
                    self.error_counter = max(self.error_counter - 1, 0)
                else:
                    if self.error_counter in ERROR_COUNTER_MATCH:
                        self.begin_session()
                        self.error_counter = max(self.error_counter - 1, 0)
                    elif self.error_counter > ERROR_COUNTER_MATCH[-1]:
                        self.error_counter = ERROR_COUNTER_MATCH[-2]
                    else:
                        self.error_counter += 1

            else:
                if self.aws_client:
                    self.teardown_session()

            self._notify_retry_counter = 0
        except publishQueueDisabledException:
            metadata.cloud_status = (False, ("SESSION", "CONNECTION_ERROR"))
            self._notify_retry_counter += 1
            logger.exception("publishQueueDisabledException raise in notify")
            if self._notify_retry_counter > 10:
                self.teardown_session()
            self.error_counter += 1
        except RuntimeError as e:
            logger.error(e)
            metadata.cloud_status = (False, e.args)
            self.error_counter += 1

            self.diagnosis()
        except Exception:
            logger.exception("Unhandle error")
            metadata.cloud_status = (False, ("UNKNOWN_ERROR", ))
            self.error_counter += 1

    def on_shutdown(self):
        pass

    def require_camera(self, camera_id, endpoint, token):
        payload = msgpack.packb((0x80, camera_id, endpoint, token))
        s = socket.socket(socket.AF_UNIX)
        s.connect(CAMERA_ENDPOINT)
        s.send(payload)
        rl = select((s, ), (), (), 0.25)[0]
        if rl:
            logger.debug("Require camera return %s",
                         msgpack.unpackb(s.recv(4096)))
        s.close()

    def require_control(self, endpoint, token):
        payload = msgpack.packb((0x80, endpoint, token))
        s = socket.socket(socket.AF_UNIX)
        s.connect(ROBOT_ENDPOINT)
        s.send(payload)
        rl = select((s, ), (), (), 0.25)[0]
        if rl:
            logger.debug("Require robot return %s",
                         msgpack.unpackb(s.recv(4096)))
        s.close()

    def diagnosis(self):
        from fluxmonitor.misc._process import Process
        from time import time as epoch
        logger.info("Diagnosis...")

        try:
            logger.info(
                'ntp dns testing...\n%s\n%s',
                Process.call_with_output('getent', 'hosts',
                                         '0.debian.pool.ntp.org'),
                Process.call_with_output('getent', 'hosts',
                                         '1.debian.pool.ntp.org'))
        except Exception:
            logger.exception('ntp dns testing failed')

        try:
            logger.info('ntp service status...\n%s',
                        Process.call_with_output('service', 'ntp', 'status'))
        except Exception:
            logger.exception('fetch ntp service status failed')

        try:
            if epoch() < 1505720271:
                logger.error(
                    "System time error, fixing...\n%s",
                    Process.call_with_output('ntp-wait', '-s', '1', '-n', '1'))
                if epoch() < 1505720271:
                    logger.error("System time sync failed")

        except Exception:
            logger.exception("cloud diagnosis error")