コード例 #1
0
    def rx_packet(self, packet):
        global socketio
        # LOG.debug("Got packet back {}".format(packet))
        resp = packet.get("response", None)
        if resp == "ack":
            ack_num = packet.get("msgNo")
            LOG.info(f"We got ack for our sent message {ack_num}")
            messaging.log_packet(packet)
            SentMessages().ack(self.msg.id)
            socketio.emit(
                "ack",
                SentMessages().get(self.msg.id),
                namespace="/sendmsg",
            )
            stats.APRSDStats().ack_rx_inc()
            self.got_ack = True
            if self.request["wait_reply"] == "0" or self.got_reply:
                # We aren't waiting for a reply, so we can bail
                self.stop()
                self.thread_stop = self.aprs_client.thread_stop = True
        else:
            packets.PacketList().add(packet)
            stats.APRSDStats().msgs_rx_inc()
            message = packet.get("message_text", None)
            fromcall = packet["from"]
            msg_number = packet.get("msgNo", "0")
            messaging.log_message(
                "Received Message",
                packet["raw"],
                message,
                fromcall=fromcall,
                ack=msg_number,
            )
            SentMessages().reply(self.msg.id, packet)
            SentMessages().set_status(self.msg.id, "Got Reply")
            socketio.emit(
                "reply",
                SentMessages().get(self.msg.id),
                namespace="/sendmsg",
            )

            # Send the ack back?
            ack = messaging.AckMessage(
                self.request["from"],
                fromcall,
                msg_id=msg_number,
            )
            ack.send_direct()
            SentMessages().set_status(self.msg.id, "Ack Sent")

            # Now we can exit, since we are done.
            self.got_reply = True
            if self.got_ack:
                self.stop()
                self.thread_stop = self.aprs_client.thread_stop = True
コード例 #2
0
ファイル: listen.py プロジェクト: craigerl/aprsd
def signal_handler(sig, frame):
    threads.APRSDThreadList().stop_all()
    if "subprocess" not in str(frame):
        LOG.info(
            "Ctrl+C, Sending all threads exit! Can take up to 10 seconds {}".
            format(datetime.datetime.now(), ), )
        time.sleep(5)
        LOG.info(stats.APRSDStats())
コード例 #3
0
ファイル: messaging.py プロジェクト: wa9ecj/aprsd
    def loop(self):
        """Loop until a message is acked or it gets delayed.

        We only sleep for 5 seconds between each loop run, so
        that CTRL-C can exit the app in a short period.  Each sleep
        means the app quitting is blocked until sleep is done.
        So we keep track of the last send attempt and only send if the
        last send attempt is old enough.

        """
        cl = client.get_client()
        tracker = MsgTrack()
        # lets see if the message is still in the tracking queue
        msg = tracker.get(self.msg.id)
        if not msg:
            # The message has been removed from the tracking queue
            # So it got acked and we are done.
            LOG.info("Message Send Complete via Ack.")
            return False
        else:
            send_now = False
            if msg.last_send_attempt == msg.retry_count:
                # we reached the send limit, don't send again
                # TODO(hemna) - Need to put this in a delayed queue?
                LOG.info("Message Send Complete. Max attempts reached.")
                return False

            # Message is still outstanding and needs to be acked.
            if msg.last_send_time:
                # Message has a last send time tracking
                now = datetime.datetime.now()
                sleeptime = (msg.last_send_attempt + 1) * 31
                delta = now - msg.last_send_time
                if delta > datetime.timedelta(seconds=sleeptime):
                    # It's time to try to send it again
                    send_now = True
            else:
                send_now = True

            if send_now:
                # no attempt time, so lets send it, and start
                # tracking the time.
                log_message(
                    "Sending Message",
                    str(msg).rstrip("\n"),
                    msg.message,
                    tocall=self.msg.tocall,
                    retry_number=msg.last_send_attempt,
                    msg_num=msg.id,
                )
                cl.sendall(str(msg))
                stats.APRSDStats().msgs_tx_inc()
                msg.last_send_time = datetime.datetime.now()
                msg.last_send_attempt += 1

            time.sleep(5)
            # Make sure we get called again.
            return True
コード例 #4
0
ファイル: aprsis.py プロジェクト: craigerl/aprsd
    def _send_login(self):
        """
        Sends login string to server
        """
        login_str = "user {0} pass {1} vers github.com/craigerl/aprsd {3}{2}\r\n"
        login_str = login_str.format(
            self.callsign,
            self.passwd,
            (" filter " + self.filter) if self.filter != "" else "",
            aprsd.__version__,
        )

        self.logger.info("Sending login information")

        try:
            self._sendall(login_str)
            self.sock.settimeout(5)
            test = self.sock.recv(len(login_str) + 100)
            if is_py3:
                test = test.decode("latin-1")
            test = test.rstrip()

            self.logger.debug("Server: %s", test)

            a, b, callsign, status, e = test.split(" ", 4)
            s = e.split(",")
            if len(s):
                server_string = s[0].replace("server ", "")
            else:
                server_string = e.replace("server ", "")

            self.logger.info(f"Connected to {server_string}")
            self.server_string = server_string
            stats.APRSDStats().set_aprsis_server(server_string)

            if callsign == "":
                raise LoginError("Server responded with empty callsign???")
            if callsign != self.callsign:
                raise LoginError(f"Server: {test}")
            if status != "verified," and self.passwd != "-1":
                raise LoginError("Password is incorrect")

            if self.passwd == "-1":
                self.logger.info("Login successful (receive only)")
            else:
                self.logger.info("Login successful")

        except LoginError as e:
            self.logger.error(str(e))
            self.close()
            raise
        except Exception as e:
            self.close()
            self.logger.error(f"Failed to login '{e}'")
            raise LoginError("Failed to login")
コード例 #5
0
    def stats(self):
        stats_obj = stats.APRSDStats()
        track = messaging.MsgTrack()

        result = {
            "version": aprsd.__version__,
            "uptime": stats_obj.uptime,
            "size_tracker": len(track),
            "stats": stats_obj.stats(),
        }
        return json.dumps(result)
コード例 #6
0
ファイル: version.py プロジェクト: craigerl/aprsd
 def process(self, packet):
     LOG.info("Version COMMAND")
     # fromcall = packet.get("from")
     # message = packet.get("message_text", None)
     # ack = packet.get("msgNo", "0")
     stats_obj = stats.APRSDStats()
     s = stats_obj.stats()
     return "APRSD ver:{} uptime:{}".format(
         aprsd.__version__,
         s["aprsd"]["uptime"],
     )
コード例 #7
0
ファイル: messaging.py プロジェクト: wa9ecj/aprsd
 def send_direct(self):
     """Send a message without a separate thread."""
     cl = client.get_client()
     log_message(
         "Sending Message Direct",
         str(self).rstrip("\n"),
         self.message,
         tocall=self.tocall,
         fromcall=self.fromcall,
     )
     cl.sendall(str(self))
     stats.APRSDStats().msgs_tx_inc()
コード例 #8
0
ファイル: messaging.py プロジェクト: craigerl/aprsd
 def send_direct(self, aprsis_client=None):
     """Send a message without a separate thread."""
     cl = client.factory.create().client
     log_message(
         "Sending Message Direct",
         str(self),
         self.message,
         tocall=self.tocall,
         fromcall=self.fromcall,
     )
     cl.send(self)
     stats.APRSDStats().msgs_tx_inc()
コード例 #9
0
ファイル: threads.py プロジェクト: craigerl/aprsd
 def process_ack_packet(self, packet):
     ack_num = packet.get("msgNo")
     LOG.info(f"Got ack for message {ack_num}")
     messaging.log_message(
         "RXACK",
         packet["raw"],
         None,
         ack=ack_num,
         fromcall=packet["from"],
     )
     tracker = messaging.MsgTrack()
     tracker.remove(ack_num)
     stats.APRSDStats().ack_rx_inc()
     return
コード例 #10
0
ファイル: test_plugin.py プロジェクト: craigerl/aprsd
    def config_and_init(self, config=None):
        if not config:
            self.config = aprsd_config.Config(aprsd_config.DEFAULT_CONFIG_DICT)
            self.config["ham"]["callsign"] = self.fromcall
            self.config["aprs"]["login"] = fake.FAKE_TO_CALLSIGN
            self.config["services"]["aprs.fi"]["apiKey"] = "something"
        else:
            self.config = config

        # Inintialize the stats object with the config
        stats.APRSDStats(self.config)
        packets.WatchList(config=self.config)
        packets.SeenList(config=self.config)
        messaging.MsgTrack(config=self.config)
コード例 #11
0
ファイル: main.py プロジェクト: wa9ecj/aprsd
def signal_handler(sig, frame):
    global flask_enabled

    threads.APRSDThreadList().stop_all()
    if "subprocess" not in str(frame):
        LOG.info(
            "Ctrl+C, Sending all threads exit! Can take up to 10 seconds {}".
            format(datetime.datetime.now(), ), )
        time.sleep(5)
        tracker = messaging.MsgTrack()
        tracker.save()
        LOG.info(stats.APRSDStats())
        # signal.signal(signal.SIGTERM, sys.exit(0))
        # sys.exit(0)
    if flask_enabled:
        signal.signal(signal.SIGTERM, sys.exit(0))
コード例 #12
0
ファイル: messaging.py プロジェクト: wa9ecj/aprsd
 def send_thread(self):
     """Separate thread to send acks with retries."""
     cl = client.get_client()
     for i in range(self.retry_count, 0, -1):
         log_message(
             "Sending ack",
             str(self).rstrip("\n"),
             None,
             ack=self.id,
             tocall=self.tocall,
             retry_number=i,
         )
         cl.sendall(str(self))
         stats.APRSDStats().ack_tx_inc()
         # aprs duplicate detection is 30 secs?
         # (21 only sends first, 28 skips middle)
         time.sleep(31)
コード例 #13
0
ファイル: messaging.py プロジェクト: craigerl/aprsd
    def loop(self):
        """Separate thread to send acks with retries."""
        send_now = False
        if self.ack.last_send_attempt == self.ack.retry_count:
            # we reached the send limit, don't send again
            # TODO(hemna) - Need to put this in a delayed queue?
            LOG.info("Ack Send Complete. Max attempts reached.")
            return False

        if self.ack.last_send_time:
            # Message has a last send time tracking
            now = datetime.datetime.now()

            # aprs duplicate detection is 30 secs?
            # (21 only sends first, 28 skips middle)
            sleeptime = 31
            delta = now - self.ack.last_send_time
            if delta > datetime.timedelta(seconds=sleeptime):
                # It's time to try to send it again
                send_now = True
            else:
                LOG.debug(f"Still wating. {delta}")
        else:
            send_now = True

        if send_now:
            cl = client.factory.create().client
            log_message(
                "Sending ack",
                str(self.ack).rstrip("\n"),
                None,
                ack=self.ack.id,
                tocall=self.ack.tocall,
                retry_number=self.ack.last_send_attempt,
            )
            cl.send(self.ack)
            stats.APRSDStats().ack_tx_inc()
            packets.PacketList().add(self.ack.dict())
            self.ack.last_send_attempt += 1
            self.ack.last_send_time = datetime.datetime.now()
        time.sleep(5)
        return True
コード例 #14
0
def send_email(to_addr, content):
    global check_email_delay

    shortcuts = CONFIG["aprsd"]["email"]["shortcuts"]
    email_address = get_email_from_shortcut(to_addr)
    LOG.info("Sending Email_________________")

    if to_addr in shortcuts:
        LOG.info("To          : " + to_addr)
        to_addr = email_address
        LOG.info(" (" + to_addr + ")")
    subject = CONFIG["ham"]["callsign"]
    # content = content + "\n\n(NOTE: reply with one line)"
    LOG.info("Subject     : " + subject)
    LOG.info("Body        : " + content)

    # check email more often since there's activity right now
    check_email_delay = 60

    msg = MIMEText(content)
    msg["Subject"] = subject
    msg["From"] = CONFIG["aprsd"]["email"]["smtp"]["login"]
    msg["To"] = to_addr
    server = _smtp_connect()
    if server:
        try:
            server.sendmail(
                CONFIG["aprsd"]["email"]["smtp"]["login"],
                [to_addr],
                msg.as_string(),
            )
            stats.APRSDStats().email_tx_inc()
        except Exception as e:
            msg = getattr(e, "message", repr(e))
            LOG.error("Sendmail Error!!!! '{}'", msg)
            server.quit()
            return -1
        server.quit()
        return 0
コード例 #15
0
    def _stats(self):
        stats_obj = stats.APRSDStats()
        track = messaging.MsgTrack()
        now = datetime.datetime.now()

        time_format = "%m-%d-%Y %H:%M:%S"

        stats_dict = stats_obj.stats()

        # Convert the watch_list entries to age
        wl = packets.WatchList()
        new_list = {}
        for call in wl.get_all():
            # call_date = datetime.datetime.strptime(
            #    str(wl.last_seen(call)),
            #    "%Y-%m-%d %H:%M:%S.%f",
            # )
            new_list[call] = {
                "last": wl.age(call),
                "packets": wl.get(call)["packets"].get(),
            }

        stats_dict["aprsd"]["watch_list"] = new_list
        packet_list = packets.PacketList()
        rx = packet_list.total_received()
        tx = packet_list.total_sent()
        stats_dict["packets"] = {
            "sent": tx,
            "received": rx,
        }

        result = {
            "time": now.strftime(time_format),
            "size_tracker": len(track),
            "stats": stats_dict,
        }

        return result
コード例 #16
0
ファイル: email.py プロジェクト: craigerl/aprsd
def send_email(config, to_addr, content):
    shortcuts = config["aprsd"]["email"]["shortcuts"]
    email_address = get_email_from_shortcut(config, to_addr)
    LOG.info("Sending Email_________________")

    if to_addr in shortcuts:
        LOG.info(f"To          : {to_addr}")
        to_addr = email_address
        LOG.info(f" ({to_addr})")
    subject = config["ham"]["callsign"]
    # content = content + "\n\n(NOTE: reply with one line)"
    LOG.info(f"Subject     : {subject}")
    LOG.info(f"Body        : {content}")

    # check email more often since there's activity right now
    EmailInfo().delay = 60

    msg = MIMEText(content)
    msg["Subject"] = subject
    msg["From"] = config["aprsd"]["email"]["smtp"]["login"]
    msg["To"] = to_addr
    server = _smtp_connect(config)
    if server:
        try:
            server.sendmail(
                config["aprsd"]["email"]["smtp"]["login"],
                [to_addr],
                msg.as_string(),
            )
            stats.APRSDStats().email_tx_inc()
        except Exception:
            LOG.exception("Sendmail Error!!!!")
            server.quit()
            return -1
        server.quit()
        return 0
コード例 #17
0
ファイル: main.py プロジェクト: wa9ecj/aprsd
def server(
    loglevel,
    quiet,
    disable_validation,
    config_file,
    flush,
):
    """Start the aprsd server process."""
    global flask_enabled
    signal.signal(signal.SIGINT, signal_handler)

    if not quiet:
        click.echo("Load config")
    config = utils.parse_config(config_file)

    # Force setting the config to the modules that need it
    # TODO(Walt): convert these modules to classes that can
    # Accept the config as a constructor param, instead of this
    # hacky global setting
    email.CONFIG = config

    setup_logging(config, loglevel, quiet)
    if config["aprsd"].get("trace", False):
        trace.setup_tracing(["method", "api"])
    LOG.info("APRSD Started version: {}".format(aprsd.__version__))
    stats.APRSDStats(config)

    email_enabled = config["aprsd"]["email"].get("enabled", False)

    if email_enabled:
        # TODO(walt): Make email processing/checking optional?
        # Maybe someone only wants this to process messages with plugins only.
        valid = email.validate_email_config(config, disable_validation)
        if not valid:
            LOG.error("Failed to validate email config options")
            sys.exit(-1)
    else:
        LOG.info("Email services not enabled.")

    # Create the initial PM singleton and Register plugins
    plugin_manager = plugin.PluginManager(config)
    plugin_manager.setup_plugins()
    try:
        cl = client.Client(config)
        cl.client
    except LoginError:
        sys.exit(-1)

    # Now load the msgTrack from disk if any
    if flush:
        LOG.debug("Deleting saved MsgTrack.")
        messaging.MsgTrack().flush()
    else:
        # Try and load saved MsgTrack list
        LOG.debug("Loading saved MsgTrack object.")
        messaging.MsgTrack().load()

    rx_msg_queue = queue.Queue(maxsize=20)
    tx_msg_queue = queue.Queue(maxsize=20)
    msg_queues = {"rx": rx_msg_queue, "tx": tx_msg_queue}

    rx_thread = threads.APRSDRXThread(msg_queues=msg_queues, config=config)
    tx_thread = threads.APRSDTXThread(msg_queues=msg_queues, config=config)

    if email_enabled:
        email_thread = email.APRSDEmailThread(msg_queues=msg_queues,
                                              config=config)
        email_thread.start()

    rx_thread.start()
    tx_thread.start()

    messaging.MsgTrack().restart()

    keepalive = threads.KeepAliveThread()
    keepalive.start()

    try:
        web_enabled = utils.check_config_option(config,
                                                ["aprsd", "web", "enabled"])
    except Exception:
        web_enabled = False

    if web_enabled:
        flask_enabled = True
        app = flask.init_flask(config)
        app.run(
            host=config["aprsd"]["web"]["host"],
            port=config["aprsd"]["web"]["port"],
        )

    # If there are items in the msgTracker, then save them
    LOG.info("APRSD Exiting.")
    return 0
コード例 #18
0
ファイル: messaging.py プロジェクト: wa9ecj/aprsd
 def add(self, msg):
     with self.lock:
         key = int(msg.id)
         self.track[key] = msg
         stats.APRSDStats().msgs_tracked_inc()
         self.total_messages_tracked += 1
コード例 #19
0
ファイル: listen.py プロジェクト: craigerl/aprsd
def listen(
    ctx,
    aprs_login,
    aprs_password,
    filter,
):
    """Listen to packets on the APRS-IS Network based on FILTER.

    FILTER is the APRS Filter to use.\n
     see http://www.aprs-is.net/javAPRSFilter.aspx\n
    r/lat/lon/dist - Range Filter Pass posits and objects within dist km from lat/lon.\n
    p/aa/bb/cc... - Prefix Filter Pass traffic with fromCall that start with aa or bb or cc.\n
    b/call1/call2... - Budlist Filter Pass all traffic from exact call: call1, call2, ... (* wild card allowed) \n
    o/obj1/obj2... - Object Filter Pass all objects with the exact name of obj1, obj2, ... (* wild card allowed)\n

    """
    config = ctx.obj["config"]

    if not aprs_login:
        click.echo(ctx.get_help())
        click.echo("")
        ctx.fail("Must set --aprs_login or APRS_LOGIN")
        ctx.exit()

    if not aprs_password:
        click.echo(ctx.get_help())
        click.echo("")
        ctx.fail("Must set --aprs-password or APRS_PASSWORD")
        ctx.exit()

    config["aprs"]["login"] = aprs_login
    config["aprs"]["password"] = aprs_password

    LOG.info(f"APRSD Listen Started version: {aprsd.__version__}")

    flat_config = utils.flatten_dict(config)
    LOG.info("Using CONFIG values:")
    for x in flat_config:
        if "password" in x or "aprsd.web.users.admin" in x:
            LOG.info(f"{x} = XXXXXXXXXXXXXXXXXXX")
        else:
            LOG.info(f"{x} = {flat_config[x]}")

    stats.APRSDStats(config)

    # Try and load saved MsgTrack list
    LOG.debug("Loading saved MsgTrack object.")
    messaging.MsgTrack(config=config).load()
    packets.WatchList(config=config).load()
    packets.SeenList(config=config).load()

    @trace.trace
    def rx_packet(packet):
        resp = packet.get("response", None)
        if resp == "ack":
            ack_num = packet.get("msgNo")
            console.log(f"We saw an ACK {ack_num} Ignoring")
            messaging.log_packet(packet)
        else:
            message = packet.get("message_text", None)
            fromcall = packet["from"]
            msg_number = packet.get("msgNo", "0")
            messaging.log_message(
                "Received Message",
                packet["raw"],
                message,
                fromcall=fromcall,
                ack=msg_number,
                console=console,
            )

    # Initialize the client factory and create
    # The correct client object ready for use
    client.ClientFactory.setup(config)
    # Make sure we have 1 client transport enabled
    if not client.factory.is_client_enabled():
        LOG.error("No Clients are enabled in config.")
        sys.exit(-1)

    # Creates the client object
    LOG.info("Creating client connection")
    client.factory.create().client
    aprs_client = client.factory.create().client

    LOG.debug(f"Filter by '{filter}'")
    aprs_client.set_filter(filter)

    while True:
        try:
            # This will register a packet consumer with aprslib
            # When new packets come in the consumer will process
            # the packet
            with console.status("Listening for packets"):
                aprs_client.consumer(rx_packet, raw=False)
        except aprslib.exceptions.ConnectionDrop:
            LOG.error("Connection dropped, reconnecting")
            time.sleep(5)
            # Force the deletion of the client object connected to aprs
            # This will cause a reconnect, next time client.get_client()
            # is called
            aprs_client.reset()
        except aprslib.exceptions.UnknownFormat:
            LOG.error("Got a Bad packet")
コード例 #20
0
def test_plugin(
    ctx,
    aprs_login,
    plugin_path,
    load_all,
    number,
    message,
):
    """Test an individual APRSD plugin given a python path."""
    config = ctx.obj["config"]

    if not aprs_login:
        if not config.exists("aprs.login"):
            click.echo("Must set --aprs_login or APRS_LOGIN")
            ctx.exit(-1)
            return
        else:
            fromcall = config.get("aprs.login")
    else:
        fromcall = aprs_login

    if not plugin_path:
        click.echo(ctx.get_help())
        click.echo("")
        ctx.fail("Failed to provide -p option to test a plugin")
        return

    if type(message) is tuple:
        message = " ".join(message)

    if config["aprsd"].get("trace", False):
        trace.setup_tracing(["method", "api"])

    client.Client(config)
    stats.APRSDStats(config)
    messaging.MsgTrack(config=config)
    packets.WatchList(config=config)
    packets.SeenList(config=config)

    pm = plugin.PluginManager(config)
    if load_all:
        pm.setup_plugins()
    else:
        pm._init()
    obj = pm._create_class(plugin_path, plugin.APRSDPluginBase, config=config)
    if not obj:
        click.echo(ctx.get_help())
        click.echo("")
        ctx.fail(f"Failed to create object from plugin path '{plugin_path}'")
        ctx.exit()

    # Register the plugin they wanted tested.
    LOG.info(
        "Testing plugin {} Version {}".format(
            obj.__class__,
            obj.version,
        ), )
    pm._pluggy_pm.register(obj)
    login = config["aprs"]["login"]

    packet = {
        "from": fromcall,
        "addresse": login,
        "message_text": message,
        "format": "message",
        "msgNo": 1,
    }
    LOG.info(f"P'{plugin_path}'  F'{fromcall}'   C'{message}'")

    for x in range(number):
        reply = pm.run(packet)
        # Plugin might have threads, so lets stop them so we can exit.
        # obj.stop_threads()
        LOG.info(f"Result{x} = '{reply}'")
    pm.stop()
コード例 #21
0
    def run(self):
        global check_email_delay

        LOG.debug("Starting")

        check_email_delay = 60
        past = datetime.datetime.now()
        while not self.thread_stop:
            time.sleep(5)
            stats.APRSDStats().email_thread_update()
            # always sleep for 5 seconds and see if we need to check email
            # This allows CTRL-C to stop the execution of this loop sooner
            # than check_email_delay time
            now = datetime.datetime.now()
            if now - past > datetime.timedelta(seconds=check_email_delay):
                # It's time to check email

                # slowly increase delay every iteration, max out at 300 seconds
                # any send/receive/resend activity will reset this to 60 seconds
                if check_email_delay < 300:
                    check_email_delay += 1
                LOG.debug("check_email_delay is " + str(check_email_delay) +
                          " seconds")

                shortcuts = CONFIG["aprsd"]["email"]["shortcuts"]
                # swap key/value
                shortcuts_inverted = {v: k for k, v in shortcuts.items()}

                date = datetime.datetime.now()
                month = date.strftime("%B")[:3]  # Nov, Mar, Apr
                day = date.day
                year = date.year
                today = "{}-{}-{}".format(day, month, year)

                server = None
                try:
                    server = _imap_connect()
                except Exception as e:
                    LOG.exception("IMAP failed to connect.", e)

                if not server:
                    continue

                try:
                    messages = server.search(["SINCE", today])
                except Exception as e:
                    LOG.exception(
                        "IMAP failed to search for messages since today.", e)
                    continue
                LOG.debug("{} messages received today".format(len(messages)))

                try:
                    _msgs = server.fetch(messages, ["ENVELOPE"])
                except Exception as e:
                    LOG.exception("IMAP failed to fetch/flag messages: ", e)
                    continue

                for msgid, data in _msgs.items():
                    envelope = data[b"ENVELOPE"]
                    LOG.debug(
                        'ID:%d  "%s" (%s)' %
                        (msgid, envelope.subject.decode(), envelope.date), )
                    f = re.search(
                        r"'([[A-a][0-9]_-]+@[[A-a][0-9]_-\.]+)",
                        str(envelope.from_[0]),
                    )
                    if f is not None:
                        from_addr = f.group(1)
                    else:
                        from_addr = "noaddr"

                    # LOG.debug("Message flags/tags:  " + str(server.get_flags(msgid)[msgid]))
                    # if "APRS" not in server.get_flags(msgid)[msgid]:
                    # in python3, imap tags are unicode.  in py2 they're strings. so .decode them to handle both
                    try:
                        taglist = [
                            x.decode(errors="ignore")
                            for x in server.get_flags(msgid)[msgid]
                        ]
                    except Exception as e:
                        LOG.exception("Failed to get flags.", e)
                        break

                    if "APRS" not in taglist:
                        # if msg not flagged as sent via aprs
                        try:
                            server.fetch([msgid], ["RFC822"])
                        except Exception as e:
                            LOG.exception(
                                "Failed single server fetch for RFC822", e)
                            break

                        (body, from_addr) = parse_email(msgid, data, server)
                        # unset seen flag, will stay bold in email client
                        try:
                            server.remove_flags(msgid, [imapclient.SEEN])
                        except Exception as e:
                            LOG.exception("Failed to remove flags SEEN", e)
                            # Not much we can do here, so lets try and
                            # send the aprs message anyway

                        if from_addr in shortcuts_inverted:
                            # reverse lookup of a shortcut
                            from_addr = shortcuts_inverted[from_addr]

                        reply = "-" + from_addr + " " + body.decode(
                            errors="ignore")
                        msg = messaging.TextMessage(
                            self.config["aprs"]["login"],
                            self.config["ham"]["callsign"],
                            reply,
                        )
                        self.msg_queues["tx"].put(msg)
                        # flag message as sent via aprs
                        try:
                            server.add_flags(msgid, ["APRS"])
                            # unset seen flag, will stay bold in email client
                        except Exception as e:
                            LOG.exception("Couldn't add APRS flag to email", e)

                        try:
                            server.remove_flags(msgid, [imapclient.SEEN])
                        except Exception as e:
                            LOG.exception(
                                "Couldn't remove seen flag from email", e)

                        # check email more often since we just received an email
                        check_email_delay = 60

                # reset clock
                LOG.debug("Done looping over Server.fetch, logging out.")
                past = datetime.datetime.now()
                try:
                    server.logout()
                except Exception as e:
                    LOG.exception("IMAP failed to logout: ", e)
                    continue
            else:
                # We haven't hit the email delay yet.
                # LOG.debug("Delta({}) < {}".format(now - past, check_email_delay))
                pass

        # Remove ourselves from the global threads list
        threads.APRSDThreadList().remove(self)
        LOG.info("Exiting")
コード例 #22
0
ファイル: server.py プロジェクト: craigerl/aprsd
def server(ctx, flush):
    """Start the aprsd server gateway process."""
    ctx.obj["config_file"]
    loglevel = ctx.obj["loglevel"]
    quiet = ctx.obj["quiet"]
    config = ctx.obj["config"]

    signal.signal(signal.SIGINT, aprsd_main.signal_handler)
    signal.signal(signal.SIGTERM, aprsd_main.signal_handler)

    if not quiet:
        click.echo("Load config")

    level, msg = utils._check_version()
    if level:
        LOG.warning(msg)
    else:
        LOG.info(msg)
    LOG.info(f"APRSD Started version: {aprsd.__version__}")

    flat_config = utils.flatten_dict(config)
    LOG.info("Using CONFIG values:")
    for x in flat_config:
        if "password" in x or "aprsd.web.users.admin" in x:
            LOG.info(f"{x} = XXXXXXXXXXXXXXXXXXX")
        else:
            LOG.info(f"{x} = {flat_config[x]}")

    if config["aprsd"].get("trace", False):
        trace.setup_tracing(["method", "api"])
    stats.APRSDStats(config)

    # Initialize the client factory and create
    # The correct client object ready for use
    client.ClientFactory.setup(config)
    # Make sure we have 1 client transport enabled
    if not client.factory.is_client_enabled():
        LOG.error("No Clients are enabled in config.")
        sys.exit(-1)

    if not client.factory.is_client_configured():
        LOG.error("APRS client is not properly configured in config file.")
        sys.exit(-1)

    # Creates the client object
    LOG.info("Creating client connection")
    client.factory.create().client

    # Now load the msgTrack from disk if any
    packets.PacketList(config=config)
    if flush:
        LOG.debug("Deleting saved MsgTrack.")
        messaging.MsgTrack(config=config).flush()
        packets.WatchList(config=config)
        packets.SeenList(config=config)
    else:
        # Try and load saved MsgTrack list
        LOG.debug("Loading saved MsgTrack object.")
        messaging.MsgTrack(config=config).load()
        packets.WatchList(config=config).load()
        packets.SeenList(config=config).load()

    # Create the initial PM singleton and Register plugins
    LOG.info("Loading Plugin Manager and registering plugins")
    plugin_manager = plugin.PluginManager(config)
    plugin_manager.setup_plugins()

    rx_thread = threads.APRSDRXThread(
        msg_queues=threads.msg_queues,
        config=config,
    )
    rx_thread.start()

    messaging.MsgTrack().restart()

    keepalive = threads.KeepAliveThread(config=config)
    keepalive.start()

    web_enabled = config.get("aprsd.web.enabled", default=False)

    if web_enabled:
        aprsd_main.flask_enabled = True
        (socketio, app) = flask.init_flask(config, loglevel, quiet)
        socketio.run(
            app,
            host=config["aprsd"]["web"]["host"],
            port=config["aprsd"]["web"]["port"],
        )

    # If there are items in the msgTracker, then save them
    LOG.info("APRSD Exiting.")
    return 0
コード例 #23
0
ファイル: threads.py プロジェクト: craigerl/aprsd
    def loop(self):
        """Process a packet recieved from aprs-is server."""
        packet = self.packet
        packets.PacketList().add(packet)

        fromcall = packet["from"]
        tocall = packet.get("addresse", None)
        msg = packet.get("message_text", None)
        msg_id = packet.get("msgNo", "0")
        msg_response = packet.get("response", None)
        # LOG.debug(f"Got packet from '{fromcall}' - {packet}")

        # We don't put ack packets destined for us through the
        # plugins.
        if tocall == self.config["aprs"]["login"] and msg_response == "ack":
            self.process_ack_packet(packet)
        else:
            # It's not an ACK for us, so lets run it through
            # the plugins.
            messaging.log_message(
                "Received Message",
                packet["raw"],
                msg,
                fromcall=fromcall,
                msg_num=msg_id,
            )

            # Only ack messages that were sent directly to us
            if tocall == self.config["aprs"]["login"]:
                stats.APRSDStats().msgs_rx_inc()
                # let any threads do their thing, then ack
                # send an ack last
                ack = messaging.AckMessage(
                    self.config["aprs"]["login"],
                    fromcall,
                    msg_id=msg_id,
                )
                ack.send()

            pm = plugin.PluginManager()
            try:
                results = pm.run(packet)
                wl = packets.WatchList()
                wl.update_seen(packet)
                replied = False
                for reply in results:
                    if isinstance(reply, list):
                        # one of the plugins wants to send multiple messages
                        replied = True
                        for subreply in reply:
                            LOG.debug(f"Sending '{subreply}'")
                            if isinstance(subreply, messaging.Message):
                                subreply.send()
                            else:
                                msg = messaging.TextMessage(
                                    self.config["aprs"]["login"],
                                    fromcall,
                                    subreply,
                                )
                                msg.send()
                    elif isinstance(reply, messaging.Message):
                        # We have a message based object.
                        LOG.debug(f"Sending '{reply}'")
                        reply.send()
                        replied = True
                    else:
                        replied = True
                        # A plugin can return a null message flag which signals
                        # us that they processed the message correctly, but have
                        # nothing to reply with, so we avoid replying with a
                        # usage string
                        if reply is not messaging.NULL_MESSAGE:
                            LOG.debug(f"Sending '{reply}'")

                            msg = messaging.TextMessage(
                                self.config["aprs"]["login"],
                                fromcall,
                                reply,
                            )
                            msg.send()

                # If the message was for us and we didn't have a
                # response, then we send a usage statement.
                if tocall == self.config["aprs"]["login"] and not replied:
                    LOG.warning("Sending help!")
                    msg = messaging.TextMessage(
                        self.config["aprs"]["login"],
                        fromcall,
                        "Unknown command! Send 'help' message for help",
                    )
                    msg.send()
            except Exception as ex:
                LOG.error("Plugin failed!!!")
                LOG.exception(ex)
                # Do we need to send a reply?
                if tocall == self.config["aprs"]["login"]:
                    reply = "A Plugin failed! try again?"
                    msg = messaging.TextMessage(
                        self.config["aprs"]["login"],
                        fromcall,
                        reply,
                    )
                    msg.send()

        LOG.debug("Packet processing complete")
コード例 #24
0
ファイル: aprsis.py プロジェクト: craigerl/aprsd
    def consumer(self, callback, blocking=True, immortal=False, raw=False):
        """
        When a position sentence is received, it will be passed to the callback function

        blocking: if true (default), runs forever, otherwise will return after one sentence
                  You can still exit the loop, by raising StopIteration in the callback function

        immortal: When true, consumer will try to reconnect and stop propagation of Parse exceptions
                  if false (default), consumer will return

        raw: when true, raw packet is passed to callback, otherwise the result from aprs.parse()
        """

        if not self._connected:
            raise ConnectionError("not connected to a server")

        line = b""

        while True and not self.thread_stop:
            try:
                for line in self._socket_readlines(blocking):
                    if line[0:1] != b"#":
                        if raw:
                            callback(line)
                        else:
                            callback(self._parse(line))
                    else:
                        self.logger.debug("Server: %s", line.decode("utf8"))
                        stats.APRSDStats().set_aprsis_keepalive()
            except ParseError as exp:
                self.logger.log(
                    11,
                    "%s\n    Packet: %s",
                    exp,
                    exp.packet,
                )
            except UnknownFormat as exp:
                self.logger.log(
                    9,
                    "%s\n    Packet: %s",
                    exp,
                    exp.packet,
                )
            except LoginError as exp:
                self.logger.error("%s: %s", exp.__class__.__name__, exp)
            except (KeyboardInterrupt, SystemExit):
                raise
            except (ConnectionDrop, ConnectionError):
                self.close()

                if not immortal:
                    raise
                else:
                    self.connect(blocking=blocking)
                    continue
            except GenericError:
                pass
            except StopIteration:
                break
            except Exception:
                self.logger.error("APRS Packet: %s", line)
                raise

            if not blocking:
                break
コード例 #25
0
ファイル: threads.py プロジェクト: craigerl/aprsd
    def loop(self):
        if self.cntr % 60 == 0:
            tracker = messaging.MsgTrack()
            stats_obj = stats.APRSDStats()
            pl = packets.PacketList()
            thread_list = APRSDThreadList()
            now = datetime.datetime.now()
            last_email = stats_obj.email_thread_time
            if last_email:
                email_thread_time = utils.strfdelta(now - last_email)
            else:
                email_thread_time = "N/A"

            last_msg_time = utils.strfdelta(now - stats_obj.aprsis_keepalive)

            current, peak = tracemalloc.get_traced_memory()
            stats_obj.set_memory(current)
            stats_obj.set_memory_peak(peak)

            try:
                login = self.config["aprs"]["login"]
            except KeyError:
                login = self.config["ham"]["callsign"]

            keepalive = (
                "{} - Uptime {} RX:{} TX:{} Tracker:{} Msgs TX:{} RX:{} "
                "Last:{} Email: {} - RAM Current:{} Peak:{} Threads:{}"
            ).format(
                login,
                utils.strfdelta(stats_obj.uptime),
                pl.total_recv,
                pl.total_tx,
                len(tracker),
                stats_obj.msgs_tx,
                stats_obj.msgs_rx,
                last_msg_time,
                email_thread_time,
                utils.human_size(current),
                utils.human_size(peak),
                len(thread_list),
            )
            LOG.info(keepalive)

            # See if we should reset the aprs-is client
            # Due to losing a keepalive from them
            delta_dict = utils.parse_delta_str(last_msg_time)
            delta = datetime.timedelta(**delta_dict)

            if delta > self.max_delta:
                #  We haven't gotten a keepalive from aprs-is in a while
                # reset the connection.a
                if not client.KISSClient.is_enabled(self.config):
                    LOG.warning("Resetting connection to APRS-IS.")
                    client.factory.create().reset()

            # Check version every hour
            delta = now - self.checker_time
            if delta > datetime.timedelta(hours=1):
                self.checker_time = now
                level, msg = utils._check_version()
                if level:
                    LOG.warning(msg)
        self.cntr += 1
        time.sleep(1)
        return True