2020-04-02 09:59:28 -04:00
|
|
|
#!/usr/bin/env python3
|
2020-03-23 00:55:21 -04:00
|
|
|
|
2021-05-28 05:00:04 -04:00
|
|
|
import hashlib
|
2017-09-14 05:18:39 -04:00
|
|
|
import json
|
2021-05-28 05:00:04 -04:00
|
|
|
import logging
|
2012-11-19 12:04:23 -05:00
|
|
|
import optparse
|
|
|
|
import os
|
2021-05-28 05:00:04 -04:00
|
|
|
import re
|
|
|
|
import select
|
2012-11-19 12:04:23 -05:00
|
|
|
import signal
|
2021-05-28 05:00:04 -04:00
|
|
|
import subprocess
|
|
|
|
import sys
|
2012-12-20 14:10:12 -05:00
|
|
|
import tempfile
|
2021-05-28 05:00:04 -04:00
|
|
|
import textwrap
|
|
|
|
import time
|
|
|
|
from types import FrameType
|
|
|
|
from typing import IO, Any, Dict, List, NoReturn, Optional, Set, Tuple, Union
|
2012-12-21 11:59:41 -05:00
|
|
|
|
2021-03-10 16:55:13 -05:00
|
|
|
from typing_extensions import Literal, TypedDict
|
|
|
|
|
2020-07-31 21:59:28 -04:00
|
|
|
from zulip import RandomExponentialBackoff
|
|
|
|
|
2013-07-24 17:53:39 -04:00
|
|
|
DEFAULT_SITE = "https://api.zulip.com"
|
2012-11-19 12:04:23 -05:00
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
|
2020-04-09 20:14:01 -04:00
|
|
|
class States:
|
2015-11-01 11:15:05 -05:00
|
|
|
Startup, ZulipToZephyr, ZephyrToZulip, ChildSending = list(range(4))
|
2021-05-28 05:03:46 -04:00
|
|
|
|
|
|
|
|
zmirror: Allow duplicate zmirror processes to die gracefully.
Fixes #602.
I replaced the SIGKILL with a SIGINT, and then catch SIGINT with a
handler. This handler calls cancelSubs if necessary, and can later be
edited to perform other clean-up operations, too. I thought about, in
this same commit, changing the SIGTERM in
maybe_restart_mirroring_script to a SIGINT, but after tracing out the
code paths, I realized that isn't necessary. (The SIGTERM is
necessarily performed on a process that has not subscribed to any
zephyr classes, so cancelSubs is unnecessary. If we do think that we
may want to add additional clean-up operations in the future, though,
then it might be worth investigating changing this SIGTERM.)
(imported from commit 692b295be6cb40b0e4ec2ca0bc58c58056ed9bd9)
2013-01-03 13:21:19 -05:00
|
|
|
CURRENT_STATE = States.Startup
|
|
|
|
|
2021-03-10 16:55:13 -05:00
|
|
|
logger: logging.Logger
|
2016-02-05 14:27:19 -05:00
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
|
2020-04-18 18:59:12 -04:00
|
|
|
def to_zulip_username(zephyr_username: str) -> str:
|
2012-11-19 12:04:23 -05:00
|
|
|
if "@" in zephyr_username:
|
|
|
|
(user, realm) = zephyr_username.split("@")
|
|
|
|
else:
|
|
|
|
(user, realm) = (zephyr_username, "ATHENA.MIT.EDU")
|
|
|
|
if realm.upper() == "ATHENA.MIT.EDU":
|
2013-10-11 16:06:50 -04:00
|
|
|
# Hack to make ctl's fake username setup work :)
|
2021-05-28 05:05:11 -04:00
|
|
|
if user.lower() == "golem":
|
|
|
|
user = "ctl"
|
2012-11-19 12:04:23 -05:00
|
|
|
return user.lower() + "@mit.edu"
|
|
|
|
return user.lower() + "|" + realm.upper() + "@mit.edu"
|
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
|
2020-04-18 18:59:12 -04:00
|
|
|
def to_zephyr_username(zulip_username: str) -> str:
|
2013-08-06 16:25:43 -04:00
|
|
|
(user, realm) = zulip_username.split("@")
|
2012-11-19 12:04:23 -05:00
|
|
|
if "|" not in user:
|
2013-10-15 16:44:19 -04:00
|
|
|
# Hack to make ctl's fake username setup work :)
|
2021-05-28 05:05:11 -04:00
|
|
|
if user.lower() == "ctl":
|
|
|
|
user = "golem"
|
2012-11-19 12:04:23 -05:00
|
|
|
return user.lower() + "@ATHENA.MIT.EDU"
|
2021-05-28 05:05:11 -04:00
|
|
|
match_user = re.match(r"([a-zA-Z0-9_]+)\|(.+)", user)
|
2012-11-19 12:04:23 -05:00
|
|
|
if not match_user:
|
2021-05-28 07:19:40 -04:00
|
|
|
raise Exception(f"Could not parse Zephyr realm for cross-realm user {zulip_username}")
|
2012-11-19 12:04:23 -05:00
|
|
|
return match_user.group(1).lower() + "@" + match_user.group(2).upper()
|
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
|
2012-11-19 12:04:23 -05:00
|
|
|
# Checks whether the pair of adjacent lines would have been
|
|
|
|
# linewrapped together, had they been intended to be parts of the same
|
|
|
|
# paragraph. Our check is whether if you move the first word on the
|
|
|
|
# 2nd line onto the first line, the resulting line is either (1)
|
|
|
|
# significantly shorter than the following line (which, if they were
|
|
|
|
# in the same paragraph, should have been wrapped in a way consistent
|
|
|
|
# with how the previous line was wrapped) or (2) shorter than 60
|
2017-01-12 02:03:07 -05:00
|
|
|
# characters (our assumed minimum linewrapping threshold for Zephyr)
|
2012-11-19 12:04:23 -05:00
|
|
|
# or (3) the first word of the next line is longer than this entire
|
|
|
|
# line.
|
2020-04-18 18:59:12 -04:00
|
|
|
def different_paragraph(line: str, next_line: str) -> bool:
|
2012-11-19 12:04:23 -05:00
|
|
|
words = next_line.split()
|
2020-04-18 20:33:01 -04:00
|
|
|
return (
|
|
|
|
len(line + " " + words[0]) < len(next_line) * 0.8
|
|
|
|
or len(line + " " + words[0]) < 50
|
|
|
|
or len(line) < len(words[0])
|
|
|
|
)
|
2012-11-19 12:04:23 -05:00
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
|
2012-11-19 12:04:23 -05:00
|
|
|
# Linewrapping algorithm based on:
|
2016-08-19 15:03:09 -04:00
|
|
|
# http://gcbenison.wordpress.com/2011/07/03/a-program-to-intelligently-remove-carriage-returns-so-you-can-paste-text-without-having-it-look-awful/ #ignorelongline
|
2020-04-18 18:59:12 -04:00
|
|
|
def unwrap_lines(body: str) -> str:
|
2012-11-19 12:04:23 -05:00
|
|
|
lines = body.split("\n")
|
|
|
|
result = ""
|
|
|
|
previous_line = lines[0]
|
|
|
|
for line in lines[1:]:
|
|
|
|
line = line.rstrip()
|
2021-05-28 05:05:11 -04:00
|
|
|
if re.match(r"^\W", line, flags=re.UNICODE) and re.match(
|
|
|
|
r"^\W", previous_line, flags=re.UNICODE
|
2020-04-18 20:33:01 -04:00
|
|
|
):
|
2012-11-19 12:04:23 -05:00
|
|
|
result += previous_line + "\n"
|
2020-04-18 20:33:01 -04:00
|
|
|
elif (
|
|
|
|
line == ""
|
|
|
|
or previous_line == ""
|
2021-05-28 05:05:11 -04:00
|
|
|
or re.match(r"^\W", line, flags=re.UNICODE)
|
2020-04-18 20:33:01 -04:00
|
|
|
or different_paragraph(previous_line, line)
|
|
|
|
):
|
2012-11-19 12:04:23 -05:00
|
|
|
# Use 2 newlines to separate sections so that we
|
|
|
|
# trigger proper Markdown processing on things like
|
|
|
|
# bulleted lists
|
|
|
|
result += previous_line + "\n\n"
|
|
|
|
else:
|
|
|
|
result += previous_line + " "
|
|
|
|
previous_line = line
|
|
|
|
result += previous_line
|
|
|
|
return result
|
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
|
2021-03-10 16:55:13 -05:00
|
|
|
class ZephyrDict(TypedDict, total=False):
|
|
|
|
type: Literal["private", "stream"]
|
|
|
|
time: str
|
|
|
|
sender: str
|
|
|
|
stream: str
|
|
|
|
subject: str
|
|
|
|
recipient: Union[str, List[str]]
|
|
|
|
content: str
|
|
|
|
zsig: str
|
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
|
2021-03-10 16:55:13 -05:00
|
|
|
def send_zulip(zeph: ZephyrDict) -> Dict[str, Any]:
|
|
|
|
message: Dict[str, Any]
|
2012-11-19 12:04:23 -05:00
|
|
|
message = {}
|
|
|
|
if options.forward_class_messages:
|
|
|
|
message["forged"] = "yes"
|
2021-05-28 05:05:11 -04:00
|
|
|
message["type"] = zeph["type"]
|
|
|
|
message["time"] = zeph["time"]
|
|
|
|
message["sender"] = to_zulip_username(zeph["sender"])
|
2012-11-19 12:04:23 -05:00
|
|
|
if "subject" in zeph:
|
2013-08-06 15:32:15 -04:00
|
|
|
# Truncate the subject to the current limit in Zulip. No
|
2012-11-19 12:04:23 -05:00
|
|
|
# need to do this for stream names, since we're only
|
|
|
|
# subscribed to valid stream names.
|
|
|
|
message["subject"] = zeph["subject"][:60]
|
2021-05-28 05:05:11 -04:00
|
|
|
if zeph["type"] == "stream":
|
2012-11-19 12:04:23 -05:00
|
|
|
# Forward messages sent to -c foo -i bar to stream bar subject "instance"
|
|
|
|
if zeph["stream"] == "message":
|
2021-05-28 05:05:11 -04:00
|
|
|
message["to"] = zeph["subject"].lower()
|
2021-05-28 07:19:40 -04:00
|
|
|
message["subject"] = "instance {}".format(zeph["subject"])
|
2012-11-19 12:04:23 -05:00
|
|
|
elif zeph["stream"] == "tabbott-test5":
|
2021-05-28 05:05:11 -04:00
|
|
|
message["to"] = zeph["subject"].lower()
|
2021-05-28 07:19:40 -04:00
|
|
|
message["subject"] = "test instance {}".format(zeph["subject"])
|
2012-11-19 12:04:23 -05:00
|
|
|
else:
|
|
|
|
message["to"] = zeph["stream"]
|
|
|
|
else:
|
|
|
|
message["to"] = zeph["recipient"]
|
2021-05-28 05:05:11 -04:00
|
|
|
message["content"] = unwrap_lines(zeph["content"])
|
2012-11-19 12:04:23 -05:00
|
|
|
|
|
|
|
if options.test_mode and options.site == DEFAULT_SITE:
|
2021-05-28 07:19:40 -04:00
|
|
|
logger.debug(f"Message is: {str(message)}")
|
2021-05-28 05:05:11 -04:00
|
|
|
return {"result": "success"}
|
2012-11-19 12:04:23 -05:00
|
|
|
|
2013-08-07 11:51:03 -04:00
|
|
|
return zulip_client.send_message(message)
|
2012-11-19 12:04:23 -05:00
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
|
2020-04-18 18:59:12 -04:00
|
|
|
def send_error_zulip(error_msg: str) -> None:
|
2021-05-28 05:03:46 -04:00
|
|
|
message = {
|
|
|
|
"type": "private",
|
|
|
|
"sender": zulip_account_email,
|
|
|
|
"to": zulip_account_email,
|
|
|
|
"content": error_msg,
|
|
|
|
}
|
2013-08-07 11:51:03 -04:00
|
|
|
zulip_client.send_message(message)
|
2012-11-19 12:04:23 -05:00
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
|
2012-11-19 12:04:23 -05:00
|
|
|
current_zephyr_subs = set()
|
2021-05-28 05:03:46 -04:00
|
|
|
|
|
|
|
|
2020-04-18 18:59:12 -04:00
|
|
|
def zephyr_bulk_subscribe(subs: List[Tuple[str, str, str]]) -> None:
|
2012-11-19 12:04:23 -05:00
|
|
|
try:
|
|
|
|
zephyr._z.subAll(subs)
|
2020-04-09 20:14:01 -04:00
|
|
|
except OSError:
|
2012-11-19 12:04:23 -05:00
|
|
|
# Since we haven't added the subscription to
|
|
|
|
# current_zephyr_subs yet, we can just return (so that we'll
|
|
|
|
# continue processing normal messages) and we'll end up
|
|
|
|
# retrying the next time the bot checks its subscriptions are
|
|
|
|
# up to date.
|
|
|
|
logger.exception("Error subscribing to streams (will retry automatically):")
|
2021-05-28 07:19:40 -04:00
|
|
|
logger.warning(f"Streams were: {[cls for cls, instance, recipient in subs]}")
|
2012-11-28 15:09:15 -05:00
|
|
|
return
|
|
|
|
try:
|
|
|
|
actual_zephyr_subs = [cls for (cls, _, _) in zephyr._z.getSubscriptions()]
|
2020-04-09 20:14:01 -04:00
|
|
|
except OSError:
|
2013-01-14 13:08:33 -05:00
|
|
|
logger.exception("Error getting current Zephyr subscriptions")
|
2012-11-28 15:09:15 -05:00
|
|
|
# Don't add anything to current_zephyr_subs so that we'll
|
|
|
|
# retry the next time we check for streams to subscribe to
|
|
|
|
# (within 15 seconds).
|
2012-11-19 12:04:23 -05:00
|
|
|
return
|
|
|
|
for (cls, instance, recipient) in subs:
|
2012-11-29 13:12:18 -05:00
|
|
|
if cls not in actual_zephyr_subs:
|
2021-05-28 07:19:40 -04:00
|
|
|
logger.error(f"Zephyr failed to subscribe us to {cls}; will retry")
|
2012-11-28 15:09:15 -05:00
|
|
|
try:
|
|
|
|
# We'll retry automatically when we next check for
|
|
|
|
# streams to subscribe to (within 15 seconds), but
|
|
|
|
# it's worth doing 1 retry immediately to avoid
|
|
|
|
# missing 15 seconds of messages on the affected
|
|
|
|
# classes
|
|
|
|
zephyr._z.sub(cls, instance, recipient)
|
2020-04-09 20:14:01 -04:00
|
|
|
except OSError:
|
2012-11-28 15:09:15 -05:00
|
|
|
pass
|
|
|
|
else:
|
|
|
|
current_zephyr_subs.add(cls)
|
2012-11-19 12:04:23 -05:00
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
|
2020-04-18 18:59:12 -04:00
|
|
|
def update_subscriptions() -> None:
|
2012-11-19 12:04:23 -05:00
|
|
|
try:
|
2020-04-09 20:14:01 -04:00
|
|
|
f = open(options.stream_file_path)
|
2021-03-10 16:55:13 -05:00
|
|
|
public_streams: List[str] = json.loads(f.read())
|
2013-02-05 10:20:49 -05:00
|
|
|
f.close()
|
2017-01-08 10:42:07 -05:00
|
|
|
except Exception:
|
2013-02-05 10:20:49 -05:00
|
|
|
logger.exception("Error reading public streams:")
|
2012-11-19 12:04:23 -05:00
|
|
|
return
|
2013-02-05 10:20:49 -05:00
|
|
|
|
2012-11-29 13:12:18 -05:00
|
|
|
classes_to_subscribe = set()
|
2013-02-05 10:20:49 -05:00
|
|
|
for stream in public_streams:
|
2021-03-10 16:55:13 -05:00
|
|
|
zephyr_class = stream
|
2021-05-28 05:03:46 -04:00
|
|
|
if options.shard is not None and not hashlib.sha1(
|
|
|
|
zephyr_class.encode("utf-8")
|
|
|
|
).hexdigest().startswith(options.shard):
|
2012-11-27 10:27:02 -05:00
|
|
|
# This stream is being handled by a different zephyr_mirror job.
|
|
|
|
continue
|
2013-02-05 10:20:49 -05:00
|
|
|
if zephyr_class in current_zephyr_subs:
|
|
|
|
continue
|
|
|
|
classes_to_subscribe.add((zephyr_class, "*", "*"))
|
2012-11-27 10:27:02 -05:00
|
|
|
|
2012-11-29 13:12:18 -05:00
|
|
|
if len(classes_to_subscribe) > 0:
|
|
|
|
zephyr_bulk_subscribe(list(classes_to_subscribe))
|
2012-11-19 12:04:23 -05:00
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
|
2020-04-18 18:59:12 -04:00
|
|
|
def maybe_kill_child() -> None:
|
2012-12-21 11:59:41 -05:00
|
|
|
try:
|
|
|
|
if child_pid is not None:
|
|
|
|
os.kill(child_pid, signal.SIGTERM)
|
|
|
|
except OSError:
|
|
|
|
# We don't care if the child process no longer exists, so just log the error
|
|
|
|
logger.exception("")
|
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
|
2020-04-18 18:59:12 -04:00
|
|
|
def maybe_restart_mirroring_script() -> None:
|
2021-05-28 05:03:46 -04:00
|
|
|
if os.stat(
|
|
|
|
os.path.join(options.stamp_path, "stamps", "restart_stamp")
|
|
|
|
).st_mtime > start_time or (
|
2020-04-18 20:33:01 -04:00
|
|
|
(options.user == "tabbott" or options.user == "tabbott/extra")
|
2021-05-28 05:03:46 -04:00
|
|
|
and os.stat(os.path.join(options.stamp_path, "stamps", "tabbott_stamp")).st_mtime
|
|
|
|
> start_time
|
2020-04-18 20:33:01 -04:00
|
|
|
):
|
2012-11-19 12:04:23 -05:00
|
|
|
logger.warning("")
|
|
|
|
logger.warning("zephyr mirroring script has been updated; restarting...")
|
2012-12-21 11:59:41 -05:00
|
|
|
maybe_kill_child()
|
2012-12-21 11:42:54 -05:00
|
|
|
try:
|
|
|
|
zephyr._z.cancelSubs()
|
2020-04-09 20:14:01 -04:00
|
|
|
except OSError:
|
2012-12-21 11:42:54 -05:00
|
|
|
# We don't care whether we failed to cancel subs properly, but we should log it
|
2013-01-14 13:08:33 -05:00
|
|
|
logger.exception("")
|
2020-07-31 21:59:28 -04:00
|
|
|
backoff = RandomExponentialBackoff(
|
|
|
|
maximum_retries=3,
|
|
|
|
)
|
|
|
|
while backoff.keep_going():
|
2012-11-19 12:04:23 -05:00
|
|
|
try:
|
2017-10-05 15:44:27 -04:00
|
|
|
os.execvp(os.path.abspath(__file__), sys.argv)
|
2020-07-31 21:59:28 -04:00
|
|
|
# No need for backoff.succeed, since this can't be reached
|
2012-11-29 08:48:35 -05:00
|
|
|
except Exception:
|
2012-11-19 12:04:23 -05:00
|
|
|
logger.exception("Error restarting mirroring script; trying again... Traceback:")
|
2020-07-31 21:59:28 -04:00
|
|
|
backoff.fail()
|
|
|
|
raise Exception("Failed to reload too many times, aborting!")
|
2012-11-19 12:04:23 -05:00
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
|
2021-03-10 16:55:13 -05:00
|
|
|
def process_loop(log: Optional[IO[str]]) -> NoReturn:
|
2013-04-04 10:21:42 -04:00
|
|
|
restart_check_count = 0
|
2013-05-07 11:08:01 -04:00
|
|
|
last_check_time = time.time()
|
2020-07-31 21:59:28 -04:00
|
|
|
recieve_backoff = RandomExponentialBackoff()
|
2012-11-19 12:04:23 -05:00
|
|
|
while True:
|
2013-05-07 11:08:01 -04:00
|
|
|
select.select([zephyr._z.getFD()], [], [], 15)
|
2012-11-19 12:41:46 -05:00
|
|
|
try:
|
2020-07-31 21:59:28 -04:00
|
|
|
process_backoff = RandomExponentialBackoff()
|
2013-09-25 13:23:11 -04:00
|
|
|
# Fetch notices from the queue until its empty
|
|
|
|
while True:
|
|
|
|
notice = zephyr.receive(block=False)
|
2020-07-31 21:59:28 -04:00
|
|
|
recieve_backoff.succeed()
|
2013-09-25 13:23:11 -04:00
|
|
|
if notice is None:
|
|
|
|
break
|
|
|
|
try:
|
|
|
|
process_notice(notice, log)
|
2020-07-31 21:59:28 -04:00
|
|
|
process_backoff.succeed()
|
2013-09-25 13:23:11 -04:00
|
|
|
except Exception:
|
|
|
|
logger.exception("Error relaying zephyr:")
|
2020-07-31 21:59:28 -04:00
|
|
|
process_backoff.fail()
|
2012-11-19 12:41:46 -05:00
|
|
|
except Exception:
|
|
|
|
logger.exception("Error checking for new zephyrs:")
|
2020-07-31 21:59:28 -04:00
|
|
|
recieve_backoff.fail()
|
2012-11-19 12:41:46 -05:00
|
|
|
continue
|
2012-11-19 12:04:23 -05:00
|
|
|
|
2013-05-07 11:08:01 -04:00
|
|
|
if time.time() - last_check_time > 15:
|
|
|
|
last_check_time = time.time()
|
|
|
|
try:
|
|
|
|
maybe_restart_mirroring_script()
|
|
|
|
if restart_check_count > 0:
|
2013-05-09 13:50:21 -04:00
|
|
|
logger.info("Stopped getting errors checking whether restart is required.")
|
2013-05-07 11:08:01 -04:00
|
|
|
restart_check_count = 0
|
|
|
|
except Exception:
|
|
|
|
if restart_check_count < 5:
|
|
|
|
logger.exception("Error checking whether restart is required:")
|
|
|
|
restart_check_count += 1
|
2012-11-19 12:04:23 -05:00
|
|
|
|
|
|
|
if options.forward_class_messages:
|
2012-11-19 12:41:46 -05:00
|
|
|
try:
|
2013-02-05 10:20:49 -05:00
|
|
|
update_subscriptions()
|
2012-11-19 12:41:46 -05:00
|
|
|
except Exception:
|
2013-07-10 16:58:30 -04:00
|
|
|
logger.exception("Error updating subscriptions from Zulip:")
|
2012-11-19 12:04:23 -05:00
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
|
2020-04-18 18:59:12 -04:00
|
|
|
def parse_zephyr_body(zephyr_data: str, notice_format: str) -> Tuple[str, str]:
|
2012-11-19 12:04:23 -05:00
|
|
|
try:
|
|
|
|
(zsig, body) = zephyr_data.split("\x00", 1)
|
2020-04-18 20:33:01 -04:00
|
|
|
if (
|
2021-05-28 05:05:11 -04:00
|
|
|
notice_format == "New transaction [$1] entered in $2\nFrom: $3 ($5)\nSubject: $4"
|
|
|
|
or notice_format == "New transaction [$1] entered in $2\nFrom: $3\nSubject: $4"
|
2020-04-18 20:33:01 -04:00
|
|
|
):
|
2017-05-10 14:43:16 -04:00
|
|
|
# Logic based off of owl_zephyr_get_message in barnowl
|
2021-05-28 05:05:11 -04:00
|
|
|
fields = body.split("\x00")
|
2017-05-10 14:43:16 -04:00
|
|
|
if len(fields) == 5:
|
2021-05-28 07:19:40 -04:00
|
|
|
body = "New transaction [{}] entered in {}\nFrom: {} ({})\nSubject: {}".format(
|
2021-05-28 05:03:46 -04:00
|
|
|
fields[0],
|
|
|
|
fields[1],
|
|
|
|
fields[2],
|
|
|
|
fields[4],
|
|
|
|
fields[3],
|
|
|
|
)
|
2012-11-19 12:04:23 -05:00
|
|
|
except ValueError:
|
|
|
|
(zsig, body) = ("", zephyr_data)
|
2017-05-10 14:47:00 -04:00
|
|
|
# Clean body of any null characters, since they're invalid in our protocol.
|
2021-05-28 05:05:11 -04:00
|
|
|
body = body.replace("\x00", "")
|
2012-11-19 12:04:23 -05:00
|
|
|
return (zsig, body)
|
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
|
2021-03-10 16:55:13 -05:00
|
|
|
def parse_crypt_table(zephyr_class: str, instance: str) -> Optional[str]:
|
2013-08-14 12:03:03 -04:00
|
|
|
try:
|
2015-10-14 16:31:08 -04:00
|
|
|
crypt_table = open(os.path.join(os.environ["HOME"], ".crypt-table"))
|
2020-04-09 20:14:01 -04:00
|
|
|
except OSError:
|
2013-08-20 15:23:29 -04:00
|
|
|
return None
|
2013-08-14 12:03:03 -04:00
|
|
|
|
|
|
|
for line in crypt_table.readlines():
|
|
|
|
if line.strip() == "":
|
|
|
|
# Ignore blank lines
|
|
|
|
continue
|
2021-05-28 05:03:46 -04:00
|
|
|
match = re.match(
|
|
|
|
r"^crypt-(?P<class>\S+):\s+((?P<algorithm>(AES|DES)):\s+)?(?P<keypath>\S+)$", line
|
|
|
|
)
|
2013-08-14 12:03:03 -04:00
|
|
|
if match is None:
|
|
|
|
# Malformed crypt_table line
|
|
|
|
logger.debug("Invalid crypt_table line!")
|
|
|
|
continue
|
|
|
|
groups = match.groupdict()
|
2021-05-28 05:03:46 -04:00
|
|
|
if (
|
2021-05-28 05:05:11 -04:00
|
|
|
groups["class"].lower() == zephyr_class
|
|
|
|
and "keypath" in groups
|
2021-05-28 05:03:46 -04:00
|
|
|
and groups.get("algorithm") == "AES"
|
|
|
|
):
|
2013-08-20 15:23:29 -04:00
|
|
|
return groups["keypath"]
|
|
|
|
return None
|
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
|
2021-03-10 16:55:13 -05:00
|
|
|
def decrypt_zephyr(zephyr_class: str, instance: str, body: str) -> str:
|
2013-08-20 15:23:29 -04:00
|
|
|
keypath = parse_crypt_table(zephyr_class, instance)
|
|
|
|
if keypath is None:
|
2013-08-14 12:03:03 -04:00
|
|
|
# We can't decrypt it, so we just return the original body
|
|
|
|
return body
|
|
|
|
|
|
|
|
# Enable handling SIGCHLD briefly while we call into
|
|
|
|
# subprocess to avoid http://bugs.python.org/issue9127
|
|
|
|
signal.signal(signal.SIGCHLD, signal.SIG_DFL)
|
|
|
|
|
|
|
|
# decrypt the message!
|
2021-05-28 05:03:46 -04:00
|
|
|
p = subprocess.Popen(
|
|
|
|
[
|
|
|
|
"gpg",
|
|
|
|
"--decrypt",
|
|
|
|
"--no-options",
|
|
|
|
"--no-default-keyring",
|
|
|
|
"--keyring=/dev/null",
|
|
|
|
"--secret-keyring=/dev/null",
|
|
|
|
"--batch",
|
|
|
|
"--quiet",
|
|
|
|
"--no-use-agent",
|
|
|
|
"--passphrase-file",
|
|
|
|
keypath,
|
|
|
|
],
|
|
|
|
stdin=subprocess.PIPE,
|
|
|
|
stdout=subprocess.PIPE,
|
|
|
|
stderr=subprocess.PIPE,
|
|
|
|
universal_newlines=True,
|
|
|
|
errors="replace",
|
|
|
|
)
|
2021-03-10 16:55:13 -05:00
|
|
|
decrypted, _ = p.communicate(input=body)
|
2013-08-14 12:03:03 -04:00
|
|
|
# Restore our ignoring signals
|
|
|
|
signal.signal(signal.SIGCHLD, signal.SIG_IGN)
|
2021-03-10 16:55:13 -05:00
|
|
|
return decrypted
|
2013-08-14 12:03:03 -04:00
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
|
2021-03-10 16:55:13 -05:00
|
|
|
def process_notice(notice: "zephyr.ZNotice", log: Optional[IO[str]]) -> None:
|
|
|
|
assert notice.sender is not None
|
2017-05-10 14:43:16 -04:00
|
|
|
(zsig, body) = parse_zephyr_body(notice.message, notice.format)
|
2012-11-19 12:04:23 -05:00
|
|
|
is_personal = False
|
|
|
|
is_huddle = False
|
|
|
|
|
|
|
|
if notice.opcode == "PING":
|
|
|
|
# skip PING messages
|
|
|
|
return
|
|
|
|
|
2012-11-26 18:54:21 -05:00
|
|
|
zephyr_class = notice.cls.lower()
|
|
|
|
|
2013-08-26 15:43:29 -04:00
|
|
|
if zephyr_class == options.nagios_class:
|
|
|
|
# Mark that we got the message and proceed
|
2015-10-14 16:31:08 -04:00
|
|
|
with open(options.nagios_path, "w") as f:
|
2013-08-26 15:43:29 -04:00
|
|
|
f.write("0\n")
|
|
|
|
return
|
|
|
|
|
2012-11-26 18:55:37 -05:00
|
|
|
if notice.recipient != "":
|
2012-11-26 18:54:21 -05:00
|
|
|
is_personal = True
|
|
|
|
# Drop messages not to the listed subscriptions
|
|
|
|
if is_personal and not options.forward_personals:
|
|
|
|
return
|
2012-11-27 10:17:52 -05:00
|
|
|
if (zephyr_class not in current_zephyr_subs) and not is_personal:
|
2021-05-28 07:19:40 -04:00
|
|
|
logger.debug(f"Skipping ... {zephyr_class}/{notice.instance}/{is_personal}")
|
2012-11-26 18:54:21 -05:00
|
|
|
return
|
2013-02-04 14:19:43 -05:00
|
|
|
if notice.format.startswith("Zephyr error: See") or notice.format.endswith("@(@color(blue))"):
|
2013-07-10 16:58:30 -04:00
|
|
|
logger.debug("Skipping message we got from Zulip!")
|
2012-11-19 12:04:23 -05:00
|
|
|
return
|
2020-04-18 20:33:01 -04:00
|
|
|
if (
|
|
|
|
zephyr_class == "mail"
|
|
|
|
and notice.instance.lower() == "inbox"
|
|
|
|
and is_personal
|
|
|
|
and not options.forward_mail_zephyrs
|
|
|
|
):
|
2013-08-27 15:25:59 -04:00
|
|
|
# Only forward mail zephyrs if forwarding them is enabled.
|
|
|
|
return
|
2012-11-19 12:04:23 -05:00
|
|
|
|
2012-11-26 18:54:21 -05:00
|
|
|
if is_personal:
|
2012-11-19 12:04:23 -05:00
|
|
|
if body.startswith("CC:"):
|
|
|
|
is_huddle = True
|
2015-09-20 03:35:54 -04:00
|
|
|
# Map "CC: user1 user2" => "user1@mit.edu, user2@mit.edu"
|
2021-05-28 05:03:46 -04:00
|
|
|
huddle_recipients = [
|
|
|
|
to_zulip_username(x.strip()) for x in body.split("\n")[0][4:].split()
|
|
|
|
]
|
2012-11-19 12:04:23 -05:00
|
|
|
if notice.sender not in huddle_recipients:
|
2013-08-06 16:25:43 -04:00
|
|
|
huddle_recipients.append(to_zulip_username(notice.sender))
|
2012-11-19 12:04:23 -05:00
|
|
|
body = body.split("\n", 1)[1]
|
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
if (
|
|
|
|
options.forward_class_messages
|
|
|
|
and notice.opcode is not None
|
|
|
|
and notice.opcode.lower() == "crypt"
|
|
|
|
):
|
2013-08-20 15:23:29 -04:00
|
|
|
body = decrypt_zephyr(zephyr_class, notice.instance.lower(), body)
|
2013-08-14 12:03:03 -04:00
|
|
|
|
2021-03-10 16:55:13 -05:00
|
|
|
zeph: ZephyrDict
|
2021-05-28 05:03:46 -04:00
|
|
|
zeph = {
|
2021-05-28 05:05:11 -04:00
|
|
|
"time": str(notice.time),
|
|
|
|
"sender": notice.sender,
|
|
|
|
"zsig": zsig, # logged here but not used by app
|
|
|
|
"content": body,
|
2021-05-28 05:03:46 -04:00
|
|
|
}
|
2012-11-19 12:04:23 -05:00
|
|
|
if is_huddle:
|
2021-05-28 05:05:11 -04:00
|
|
|
zeph["type"] = "private"
|
|
|
|
zeph["recipient"] = huddle_recipients
|
2012-11-19 12:04:23 -05:00
|
|
|
elif is_personal:
|
2021-03-10 16:55:13 -05:00
|
|
|
assert notice.recipient is not None
|
2021-05-28 05:05:11 -04:00
|
|
|
zeph["type"] = "private"
|
|
|
|
zeph["recipient"] = to_zulip_username(notice.recipient)
|
2012-11-19 12:04:23 -05:00
|
|
|
else:
|
2021-05-28 05:05:11 -04:00
|
|
|
zeph["type"] = "stream"
|
|
|
|
zeph["stream"] = zephyr_class
|
2012-11-19 12:04:23 -05:00
|
|
|
if notice.instance.strip() != "":
|
2021-05-28 05:05:11 -04:00
|
|
|
zeph["subject"] = notice.instance
|
2012-11-19 12:04:23 -05:00
|
|
|
else:
|
2021-05-28 07:19:40 -04:00
|
|
|
zeph["subject"] = f'(instance "{notice.instance}")'
|
2012-11-19 12:04:23 -05:00
|
|
|
|
|
|
|
# Add instances in for instanced personals
|
2012-11-27 13:27:41 -05:00
|
|
|
if is_personal:
|
|
|
|
if notice.cls.lower() != "message" and notice.instance.lower != "personal":
|
2021-05-28 07:19:40 -04:00
|
|
|
heading = f"[-c {notice.cls} -i {notice.instance}]\n"
|
2012-11-27 13:27:41 -05:00
|
|
|
elif notice.cls.lower() != "message":
|
2021-05-28 07:19:40 -04:00
|
|
|
heading = f"[-c {notice.cls}]\n"
|
2012-11-27 13:27:41 -05:00
|
|
|
elif notice.instance.lower() != "personal":
|
2021-05-28 07:19:40 -04:00
|
|
|
heading = f"[-i {notice.instance}]\n"
|
2012-11-27 13:27:41 -05:00
|
|
|
else:
|
|
|
|
heading = ""
|
|
|
|
zeph["content"] = heading + zeph["content"]
|
2012-11-19 12:04:23 -05:00
|
|
|
|
2021-05-28 07:19:40 -04:00
|
|
|
logger.info(f"Received a message on {zephyr_class}/{notice.instance} from {notice.sender}...")
|
2012-11-19 12:04:23 -05:00
|
|
|
if log is not None:
|
2021-05-28 05:05:11 -04:00
|
|
|
log.write(json.dumps(zeph) + "\n")
|
2012-11-19 12:04:23 -05:00
|
|
|
log.flush()
|
|
|
|
|
|
|
|
if os.fork() == 0:
|
zmirror: Allow duplicate zmirror processes to die gracefully.
Fixes #602.
I replaced the SIGKILL with a SIGINT, and then catch SIGINT with a
handler. This handler calls cancelSubs if necessary, and can later be
edited to perform other clean-up operations, too. I thought about, in
this same commit, changing the SIGTERM in
maybe_restart_mirroring_script to a SIGINT, but after tracing out the
code paths, I realized that isn't necessary. (The SIGTERM is
necessarily performed on a process that has not subscribed to any
zephyr classes, so cancelSubs is unnecessary. If we do think that we
may want to add additional clean-up operations in the future, though,
then it might be worth investigating changing this SIGTERM.)
(imported from commit 692b295be6cb40b0e4ec2ca0bc58c58056ed9bd9)
2013-01-03 13:21:19 -05:00
|
|
|
global CURRENT_STATE
|
|
|
|
CURRENT_STATE = States.ChildSending
|
2012-11-19 12:04:23 -05:00
|
|
|
# Actually send the message in a child process, to avoid blocking.
|
2012-11-29 08:57:25 -05:00
|
|
|
try:
|
2013-08-06 16:25:43 -04:00
|
|
|
res = send_zulip(zeph)
|
2012-11-29 08:57:25 -05:00
|
|
|
if res.get("result") != "success":
|
2021-05-28 07:19:40 -04:00
|
|
|
logger.error(f"Error relaying zephyr:\n{zeph}\n{res}")
|
2012-11-29 08:57:25 -05:00
|
|
|
except Exception:
|
2013-01-14 13:08:33 -05:00
|
|
|
logger.exception("Error relaying zephyr:")
|
2012-11-29 08:57:25 -05:00
|
|
|
finally:
|
|
|
|
os._exit(0)
|
2012-11-19 12:04:23 -05:00
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
|
2020-04-18 18:59:12 -04:00
|
|
|
def quit_failed_initialization(message: str) -> str:
|
2012-12-21 11:59:41 -05:00
|
|
|
logger.error(message)
|
|
|
|
maybe_kill_child()
|
|
|
|
sys.exit(1)
|
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
|
2020-04-18 18:59:12 -04:00
|
|
|
def zephyr_init_autoretry() -> None:
|
2014-03-11 14:09:15 -04:00
|
|
|
backoff = zulip.RandomExponentialBackoff()
|
2012-12-21 11:59:41 -05:00
|
|
|
while backoff.keep_going():
|
|
|
|
try:
|
|
|
|
# zephyr.init() tries to clear old subscriptions, and thus
|
|
|
|
# sometimes gets a SERVNAK from the server
|
|
|
|
zephyr.init()
|
|
|
|
backoff.succeed()
|
|
|
|
return
|
2020-04-09 20:14:01 -04:00
|
|
|
except OSError:
|
2012-12-21 11:59:41 -05:00
|
|
|
logger.exception("Error initializing Zephyr library (retrying). Traceback:")
|
|
|
|
backoff.fail()
|
|
|
|
|
|
|
|
quit_failed_initialization("Could not initialize Zephyr library, quitting!")
|
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
|
2020-04-18 18:59:12 -04:00
|
|
|
def zephyr_load_session_autoretry(session_path: str) -> None:
|
2014-03-11 14:09:15 -04:00
|
|
|
backoff = zulip.RandomExponentialBackoff()
|
2013-08-23 14:13:26 -04:00
|
|
|
while backoff.keep_going():
|
|
|
|
try:
|
2021-03-10 16:53:50 -05:00
|
|
|
with open(session_path, "rb") as f:
|
|
|
|
session = f.read()
|
2013-08-23 14:13:26 -04:00
|
|
|
zephyr._z.initialize()
|
|
|
|
zephyr._z.load_session(session)
|
|
|
|
zephyr.__inited = True
|
|
|
|
return
|
2020-04-09 20:14:01 -04:00
|
|
|
except OSError:
|
2013-08-23 14:13:26 -04:00
|
|
|
logger.exception("Error loading saved Zephyr session (retrying). Traceback:")
|
|
|
|
backoff.fail()
|
|
|
|
|
|
|
|
quit_failed_initialization("Could not load saved Zephyr session, quitting!")
|
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
|
2020-04-18 18:59:12 -04:00
|
|
|
def zephyr_subscribe_autoretry(sub: Tuple[str, str, str]) -> None:
|
2014-03-11 14:09:15 -04:00
|
|
|
backoff = zulip.RandomExponentialBackoff()
|
2012-12-21 11:59:41 -05:00
|
|
|
while backoff.keep_going():
|
2012-11-19 12:04:23 -05:00
|
|
|
try:
|
|
|
|
zephyr.Subscriptions().add(sub)
|
2012-12-21 11:59:41 -05:00
|
|
|
backoff.succeed()
|
2012-11-19 12:04:23 -05:00
|
|
|
return
|
2020-04-09 20:14:01 -04:00
|
|
|
except OSError:
|
2013-01-14 13:08:33 -05:00
|
|
|
# Probably a SERVNAK from the zephyr server, but log the
|
2012-11-19 12:04:23 -05:00
|
|
|
# traceback just in case it's something else
|
|
|
|
logger.exception("Error subscribing to personals (retrying). Traceback:")
|
2012-12-21 11:59:41 -05:00
|
|
|
backoff.fail()
|
|
|
|
|
|
|
|
quit_failed_initialization("Could not subscribe to personals, quitting!")
|
2012-11-19 12:04:23 -05:00
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
|
2021-03-10 16:55:13 -05:00
|
|
|
def zephyr_to_zulip(options: optparse.Values) -> None:
|
2013-08-23 14:13:26 -04:00
|
|
|
if options.use_sessions and os.path.exists(options.session_path):
|
|
|
|
logger.info("Loading old session")
|
|
|
|
zephyr_load_session_autoretry(options.session_path)
|
|
|
|
else:
|
|
|
|
zephyr_init_autoretry()
|
|
|
|
if options.forward_class_messages:
|
|
|
|
update_subscriptions()
|
|
|
|
if options.forward_personals:
|
|
|
|
# Subscribe to personals; we really can't operate without
|
|
|
|
# those subscriptions, so just retry until it works.
|
|
|
|
zephyr_subscribe_autoretry(("message", "*", "%me%"))
|
2013-08-27 15:25:59 -04:00
|
|
|
zephyr_subscribe_autoretry(("mail", "inbox", "%me%"))
|
2013-08-26 15:43:29 -04:00
|
|
|
if options.nagios_class:
|
|
|
|
zephyr_subscribe_autoretry((options.nagios_class, "*", "*"))
|
2013-08-23 14:13:26 -04:00
|
|
|
if options.use_sessions:
|
2021-03-10 16:53:50 -05:00
|
|
|
with open(options.session_path, "wb") as f:
|
|
|
|
f.write(zephyr._z.dump_session())
|
2012-11-19 12:04:23 -05:00
|
|
|
|
2013-08-27 11:10:13 -04:00
|
|
|
if options.logs_to_resend is not None:
|
2020-04-09 20:14:01 -04:00
|
|
|
with open(options.logs_to_resend) as log:
|
2012-11-19 12:04:23 -05:00
|
|
|
for ln in log:
|
|
|
|
try:
|
2017-09-14 05:18:39 -04:00
|
|
|
zeph = json.loads(ln)
|
2012-11-19 12:04:23 -05:00
|
|
|
# Handle importing older zephyrs in the logs
|
|
|
|
# where it isn't called a "stream" yet
|
|
|
|
if "class" in zeph:
|
|
|
|
zeph["stream"] = zeph["class"]
|
|
|
|
if "instance" in zeph:
|
|
|
|
zeph["subject"] = zeph["instance"]
|
2021-05-28 05:03:46 -04:00
|
|
|
logger.info(
|
|
|
|
"sending saved message to %s from %s..."
|
2021-05-28 05:05:11 -04:00
|
|
|
% (zeph.get("stream", zeph.get("recipient")), zeph["sender"])
|
2021-05-28 05:03:46 -04:00
|
|
|
)
|
2013-08-06 16:25:43 -04:00
|
|
|
send_zulip(zeph)
|
2012-11-29 08:48:35 -05:00
|
|
|
except Exception:
|
2012-11-19 12:04:23 -05:00
|
|
|
logger.exception("Could not send saved zephyr:")
|
|
|
|
time.sleep(2)
|
|
|
|
|
2012-12-21 11:59:41 -05:00
|
|
|
logger.info("Successfully initialized; Starting receive loop.")
|
2012-11-19 12:04:23 -05:00
|
|
|
|
2013-08-27 11:10:13 -04:00
|
|
|
if options.resend_log_path is not None:
|
2021-05-28 05:05:11 -04:00
|
|
|
with open(options.resend_log_path, "a") as log:
|
2012-11-19 12:04:23 -05:00
|
|
|
process_loop(log)
|
|
|
|
else:
|
|
|
|
process_loop(None)
|
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
|
2020-04-18 18:59:12 -04:00
|
|
|
def send_zephyr(zwrite_args: List[str], content: str) -> Tuple[int, str]:
|
2021-05-28 05:03:46 -04:00
|
|
|
p = subprocess.Popen(
|
|
|
|
zwrite_args,
|
|
|
|
stdin=subprocess.PIPE,
|
|
|
|
stdout=subprocess.PIPE,
|
|
|
|
stderr=subprocess.PIPE,
|
|
|
|
universal_newlines=True,
|
|
|
|
)
|
2021-03-10 16:55:13 -05:00
|
|
|
stdout, stderr = p.communicate(input=content)
|
2012-11-19 12:04:23 -05:00
|
|
|
if p.returncode:
|
2021-05-28 05:03:46 -04:00
|
|
|
logger.error(
|
|
|
|
"zwrite command '%s' failed with return code %d:"
|
|
|
|
% (
|
|
|
|
" ".join(zwrite_args),
|
|
|
|
p.returncode,
|
|
|
|
)
|
|
|
|
)
|
2012-11-19 12:04:23 -05:00
|
|
|
if stdout:
|
2021-03-10 16:55:13 -05:00
|
|
|
logger.info("stdout: " + stdout)
|
2012-11-19 12:04:23 -05:00
|
|
|
elif stderr:
|
2021-05-28 05:03:46 -04:00
|
|
|
logger.warning(
|
2021-05-28 07:19:40 -04:00
|
|
|
"zwrite command '{}' printed the following warning:".format(" ".join(zwrite_args))
|
2021-05-28 05:03:46 -04:00
|
|
|
)
|
2012-11-19 12:04:23 -05:00
|
|
|
if stderr:
|
2021-03-10 16:55:13 -05:00
|
|
|
logger.warning("stderr: " + stderr)
|
|
|
|
return (p.returncode, stderr)
|
2012-11-19 12:04:23 -05:00
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
|
2020-04-18 18:59:12 -04:00
|
|
|
def send_authed_zephyr(zwrite_args: List[str], content: str) -> Tuple[int, str]:
|
2012-11-19 12:04:23 -05:00
|
|
|
return send_zephyr(zwrite_args, content)
|
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
|
2020-04-18 18:59:12 -04:00
|
|
|
def send_unauthed_zephyr(zwrite_args: List[str], content: str) -> Tuple[int, str]:
|
2012-11-19 12:04:23 -05:00
|
|
|
return send_zephyr(zwrite_args + ["-d"], content)
|
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
|
2020-04-18 18:59:12 -04:00
|
|
|
def zcrypt_encrypt_content(zephyr_class: str, instance: str, content: str) -> Optional[str]:
|
2013-08-20 15:23:29 -04:00
|
|
|
keypath = parse_crypt_table(zephyr_class, instance)
|
|
|
|
if keypath is None:
|
2013-08-21 16:03:15 -04:00
|
|
|
return None
|
2013-08-20 15:23:29 -04:00
|
|
|
|
|
|
|
# encrypt the message!
|
2021-05-28 05:03:46 -04:00
|
|
|
p = subprocess.Popen(
|
|
|
|
[
|
|
|
|
"gpg",
|
|
|
|
"--symmetric",
|
|
|
|
"--no-options",
|
|
|
|
"--no-default-keyring",
|
|
|
|
"--keyring=/dev/null",
|
|
|
|
"--secret-keyring=/dev/null",
|
|
|
|
"--batch",
|
|
|
|
"--quiet",
|
|
|
|
"--no-use-agent",
|
|
|
|
"--armor",
|
|
|
|
"--cipher-algo",
|
|
|
|
"AES",
|
|
|
|
"--passphrase-file",
|
|
|
|
keypath,
|
|
|
|
],
|
|
|
|
stdin=subprocess.PIPE,
|
|
|
|
stdout=subprocess.PIPE,
|
|
|
|
stderr=subprocess.PIPE,
|
|
|
|
universal_newlines=True,
|
|
|
|
)
|
2021-03-10 16:55:13 -05:00
|
|
|
encrypted, _ = p.communicate(input=content)
|
|
|
|
return encrypted
|
2013-08-20 15:23:29 -04:00
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
|
2020-04-18 18:59:12 -04:00
|
|
|
def forward_to_zephyr(message: Dict[str, Any]) -> None:
|
2016-12-06 14:03:23 -05:00
|
|
|
# 'Any' can be of any type of text
|
2013-08-27 18:01:50 -04:00
|
|
|
support_heading = "Hi there! This is an automated message from Zulip."
|
|
|
|
support_closing = """If you have any questions, please be in touch through the \
|
2020-06-08 17:03:38 -04:00
|
|
|
Feedback button or at support@zulip.com."""
|
2013-08-27 18:01:50 -04:00
|
|
|
|
2012-12-18 10:32:17 -05:00
|
|
|
wrapper = textwrap.TextWrapper(break_long_words=False, break_on_hyphens=False)
|
2021-05-28 05:03:46 -04:00
|
|
|
wrapped_content = "\n".join(
|
|
|
|
"\n".join(wrapper.wrap(line)) for line in message["content"].replace("@", "@@").split("\n")
|
|
|
|
)
|
2012-11-19 12:04:23 -05:00
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
zwrite_args = [
|
|
|
|
"zwrite",
|
|
|
|
"-n",
|
|
|
|
"-s",
|
|
|
|
message["sender_full_name"],
|
|
|
|
"-F",
|
|
|
|
"Zephyr error: See http://zephyr.1ts.org/wiki/df",
|
|
|
|
"-x",
|
|
|
|
"UTF-8",
|
|
|
|
]
|
2013-08-28 15:58:34 -04:00
|
|
|
|
|
|
|
# Hack to make ctl's fake username setup work :)
|
2021-05-28 05:05:11 -04:00
|
|
|
if message["type"] == "stream" and zulip_account_email == "ctl@mit.edu":
|
2013-08-28 15:58:34 -04:00
|
|
|
zwrite_args.extend(["-S", "ctl"])
|
|
|
|
|
2021-05-28 05:05:11 -04:00
|
|
|
if message["type"] == "stream":
|
2012-11-19 12:04:23 -05:00
|
|
|
zephyr_class = message["display_recipient"]
|
|
|
|
instance = message["subject"]
|
|
|
|
|
|
|
|
match_whitespace_instance = re.match(r'^\(instance "(\s*)"\)$', instance)
|
|
|
|
if match_whitespace_instance:
|
|
|
|
# Forward messages sent to '(instance "WHITESPACE")' back to the
|
|
|
|
# appropriate WHITESPACE instance for bidirectional mirroring
|
|
|
|
instance = match_whitespace_instance.group(1)
|
2021-05-28 07:19:40 -04:00
|
|
|
elif instance == f"instance {zephyr_class}" or instance == "test instance {}".format(
|
2021-05-28 05:03:46 -04:00
|
|
|
zephyr_class,
|
2020-04-18 20:33:01 -04:00
|
|
|
):
|
2012-11-19 12:04:23 -05:00
|
|
|
# Forward messages to e.g. -c -i white-magic back from the
|
|
|
|
# place we forward them to
|
|
|
|
if instance.startswith("test"):
|
|
|
|
instance = zephyr_class
|
|
|
|
zephyr_class = "tabbott-test5"
|
|
|
|
else:
|
|
|
|
instance = zephyr_class
|
|
|
|
zephyr_class = "message"
|
2012-11-26 14:47:59 -05:00
|
|
|
zwrite_args.extend(["-c", zephyr_class, "-i", instance])
|
2021-05-28 07:19:40 -04:00
|
|
|
logger.info(f"Forwarding message to class {zephyr_class}, instance {instance}")
|
2021-05-28 05:05:11 -04:00
|
|
|
elif message["type"] == "private":
|
|
|
|
if len(message["display_recipient"]) == 1:
|
2012-12-03 13:49:12 -05:00
|
|
|
recipient = to_zephyr_username(message["display_recipient"][0]["email"])
|
2012-12-10 13:35:31 -05:00
|
|
|
recipients = [recipient]
|
2021-05-28 05:05:11 -04:00
|
|
|
elif len(message["display_recipient"]) == 2:
|
2012-12-08 12:59:57 -05:00
|
|
|
recipient = ""
|
|
|
|
for r in message["display_recipient"]:
|
2013-08-06 16:25:43 -04:00
|
|
|
if r["email"].lower() != zulip_account_email.lower():
|
2012-12-08 12:59:57 -05:00
|
|
|
recipient = to_zephyr_username(r["email"])
|
|
|
|
break
|
2012-12-10 13:35:31 -05:00
|
|
|
recipients = [recipient]
|
2012-12-03 13:49:12 -05:00
|
|
|
else:
|
|
|
|
zwrite_args.extend(["-C"])
|
2012-12-10 13:49:08 -05:00
|
|
|
# We drop the @ATHENA.MIT.EDU here because otherwise the
|
|
|
|
# "CC: user1 user2 ..." output will be unnecessarily verbose.
|
2021-05-28 05:03:46 -04:00
|
|
|
recipients = [
|
|
|
|
to_zephyr_username(user["email"]).replace("@ATHENA.MIT.EDU", "")
|
|
|
|
for user in message["display_recipient"]
|
|
|
|
]
|
2021-05-28 07:19:40 -04:00
|
|
|
logger.info(f"Forwarding message to {recipients}")
|
2012-12-10 13:35:31 -05:00
|
|
|
zwrite_args.extend(recipients)
|
2012-11-19 12:04:23 -05:00
|
|
|
|
2013-08-27 18:01:50 -04:00
|
|
|
if message.get("invite_only_stream"):
|
2013-08-21 16:03:15 -04:00
|
|
|
result = zcrypt_encrypt_content(zephyr_class, instance, wrapped_content)
|
2013-08-27 18:01:50 -04:00
|
|
|
if result is None:
|
2021-05-28 05:03:46 -04:00
|
|
|
send_error_zulip(
|
|
|
|
"""%s
|
2013-08-27 18:01:50 -04:00
|
|
|
|
|
|
|
Your Zulip-Zephyr mirror bot was unable to forward that last message \
|
|
|
|
from Zulip to Zephyr because you were sending to a zcrypted Zephyr \
|
|
|
|
class and your mirroring bot does not have access to the relevant \
|
|
|
|
key (perhaps because your AFS tokens expired). That means that while \
|
|
|
|
Zulip users (like you) received it, Zephyr users did not.
|
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
%s"""
|
|
|
|
% (support_heading, support_closing)
|
|
|
|
)
|
2016-12-06 14:03:23 -05:00
|
|
|
return
|
2013-08-27 18:01:50 -04:00
|
|
|
|
|
|
|
# Proceed with sending a zcrypted message
|
|
|
|
wrapped_content = result
|
|
|
|
zwrite_args.extend(["-O", "crypt"])
|
2013-08-20 15:23:29 -04:00
|
|
|
|
2012-11-19 12:04:23 -05:00
|
|
|
if options.test_mode:
|
2021-05-28 07:19:40 -04:00
|
|
|
logger.debug(f"Would have forwarded: {zwrite_args}\n{wrapped_content}")
|
2012-11-19 12:04:23 -05:00
|
|
|
return
|
|
|
|
|
|
|
|
(code, stderr) = send_authed_zephyr(zwrite_args, wrapped_content)
|
|
|
|
if code == 0 and stderr == "":
|
|
|
|
return
|
|
|
|
elif code == 0:
|
2021-05-28 05:03:46 -04:00
|
|
|
send_error_zulip(
|
|
|
|
"""%s
|
2012-11-19 12:04:23 -05:00
|
|
|
|
|
|
|
Your last message was successfully mirrored to zephyr, but zwrite \
|
|
|
|
returned the following warning:
|
|
|
|
|
|
|
|
%s
|
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
%s"""
|
|
|
|
% (support_heading, stderr, support_closing)
|
|
|
|
)
|
2016-12-06 14:03:23 -05:00
|
|
|
return
|
2020-04-18 20:33:01 -04:00
|
|
|
elif code != 0 and (
|
|
|
|
stderr.startswith("zwrite: Ticket expired while sending notice to ")
|
|
|
|
or stderr.startswith("zwrite: No credentials cache found while sending notice to ")
|
|
|
|
):
|
2012-11-19 12:04:23 -05:00
|
|
|
# Retry sending the message unauthenticated; if that works,
|
|
|
|
# just notify the user that they need to renew their tickets
|
|
|
|
(code, stderr) = send_unauthed_zephyr(zwrite_args, wrapped_content)
|
|
|
|
if code == 0:
|
2013-08-26 14:50:30 -04:00
|
|
|
if options.ignore_expired_tickets:
|
|
|
|
return
|
2021-05-28 05:03:46 -04:00
|
|
|
send_error_zulip(
|
|
|
|
"""%s
|
2012-11-19 12:04:23 -05:00
|
|
|
|
2013-07-10 16:58:30 -04:00
|
|
|
Your last message was forwarded from Zulip to Zephyr unauthenticated, \
|
2012-11-19 12:04:23 -05:00
|
|
|
because your Kerberos tickets have expired. It was sent successfully, \
|
|
|
|
but please renew your Kerberos tickets in the screen session where you \
|
2013-07-10 16:58:30 -04:00
|
|
|
are running the Zulip-Zephyr mirroring bot, so we can send \
|
2012-11-19 12:04:23 -05:00
|
|
|
authenticated Zephyr messages for you again.
|
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
%s"""
|
|
|
|
% (support_heading, support_closing)
|
|
|
|
)
|
2016-12-06 14:03:23 -05:00
|
|
|
return
|
2012-11-19 12:04:23 -05:00
|
|
|
|
|
|
|
# zwrite failed and it wasn't because of expired tickets: This is
|
|
|
|
# probably because the recipient isn't subscribed to personals,
|
|
|
|
# but regardless, we should just notify the user.
|
2021-05-28 05:03:46 -04:00
|
|
|
send_error_zulip(
|
|
|
|
"""%s
|
2012-11-19 12:04:23 -05:00
|
|
|
|
2013-07-10 16:58:30 -04:00
|
|
|
Your Zulip-Zephyr mirror bot was unable to forward that last message \
|
|
|
|
from Zulip to Zephyr. That means that while Zulip users (like you) \
|
2012-11-19 12:04:23 -05:00
|
|
|
received it, Zephyr users did not. The error message from zwrite was:
|
|
|
|
|
|
|
|
%s
|
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
%s"""
|
|
|
|
% (support_heading, stderr, support_closing)
|
|
|
|
)
|
2016-12-06 14:03:23 -05:00
|
|
|
return
|
2012-11-19 12:04:23 -05:00
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
|
2020-04-18 18:59:12 -04:00
|
|
|
def maybe_forward_to_zephyr(message: Dict[str, Any]) -> None:
|
2016-12-06 14:03:23 -05:00
|
|
|
# The key string can be used to direct any type of text.
|
2021-05-28 05:03:46 -04:00
|
|
|
if message["sender_email"] == zulip_account_email:
|
2020-04-18 20:33:01 -04:00
|
|
|
if not (
|
|
|
|
(message["type"] == "stream")
|
|
|
|
or (
|
|
|
|
message["type"] == "private"
|
|
|
|
and False
|
|
|
|
not in [
|
2021-05-28 05:03:46 -04:00
|
|
|
u["email"].lower().endswith("mit.edu") for u in message["display_recipient"]
|
2020-04-18 20:33:01 -04:00
|
|
|
]
|
|
|
|
)
|
|
|
|
):
|
2012-12-03 13:49:12 -05:00
|
|
|
# Don't try forward private messages with non-MIT users
|
2012-11-19 12:04:23 -05:00
|
|
|
# to MIT Zephyr.
|
|
|
|
return
|
2017-02-25 16:11:53 -05:00
|
|
|
timestamp_now = int(time.time())
|
|
|
|
if float(message["timestamp"]) < timestamp_now - 15:
|
2021-05-28 05:03:46 -04:00
|
|
|
logger.warning(
|
2021-05-28 07:19:40 -04:00
|
|
|
"Skipping out of order message: {} < {}".format(message["timestamp"], timestamp_now)
|
2021-05-28 05:03:46 -04:00
|
|
|
)
|
2012-11-19 12:04:23 -05:00
|
|
|
return
|
|
|
|
try:
|
|
|
|
forward_to_zephyr(message)
|
2012-11-29 08:48:35 -05:00
|
|
|
except Exception:
|
2012-11-19 12:04:23 -05:00
|
|
|
# Don't let an exception forwarding one message crash the
|
|
|
|
# whole process
|
|
|
|
logger.exception("Error forwarding message:")
|
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
|
2021-03-10 16:55:13 -05:00
|
|
|
def zulip_to_zephyr(options: optparse.Values) -> NoReturn:
|
2014-02-24 15:27:15 -05:00
|
|
|
# Sync messages from zulip to zephyr
|
2012-11-19 12:04:23 -05:00
|
|
|
logger.info("Starting syncing messages.")
|
2020-07-31 21:59:28 -04:00
|
|
|
backoff = RandomExponentialBackoff(timeout_success_equivalent=120)
|
2012-11-19 12:41:46 -05:00
|
|
|
while True:
|
|
|
|
try:
|
2013-08-07 11:51:03 -04:00
|
|
|
zulip_client.call_on_each_message(maybe_forward_to_zephyr)
|
2012-11-19 12:41:46 -05:00
|
|
|
except Exception:
|
|
|
|
logger.exception("Error syncing messages:")
|
2020-07-31 21:59:28 -04:00
|
|
|
backoff.fail()
|
2012-11-19 12:04:23 -05:00
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
|
2020-04-18 18:59:12 -04:00
|
|
|
def subscribed_to_mail_messages() -> bool:
|
2012-11-19 12:04:23 -05:00
|
|
|
# In case we have lost our AFS tokens and those won't be able to
|
|
|
|
# parse the Zephyr subs file, first try reading in result of this
|
|
|
|
# query from the environment so we can avoid the filesystem read.
|
|
|
|
stored_result = os.environ.get("HUMBUG_FORWARD_MAIL_ZEPHYRS")
|
|
|
|
if stored_result is not None:
|
|
|
|
return stored_result == "True"
|
|
|
|
for (cls, instance, recipient) in parse_zephyr_subs(verbose=False):
|
2021-05-28 05:03:46 -04:00
|
|
|
if cls.lower() == "mail" and instance.lower() == "inbox":
|
2012-11-19 12:04:23 -05:00
|
|
|
os.environ["HUMBUG_FORWARD_MAIL_ZEPHYRS"] = "True"
|
|
|
|
return True
|
|
|
|
os.environ["HUMBUG_FORWARD_MAIL_ZEPHYRS"] = "False"
|
|
|
|
return False
|
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
|
2020-04-18 18:59:12 -04:00
|
|
|
def add_zulip_subscriptions(verbose: bool) -> None:
|
2012-11-19 12:04:23 -05:00
|
|
|
zephyr_subscriptions = set()
|
|
|
|
skipped = set()
|
|
|
|
for (cls, instance, recipient) in parse_zephyr_subs(verbose=verbose):
|
2012-12-03 15:33:33 -05:00
|
|
|
if cls.lower() == "message":
|
2012-11-19 12:04:23 -05:00
|
|
|
if recipient != "*":
|
|
|
|
# We already have a (message, *, you) subscription, so
|
|
|
|
# these are redundant
|
|
|
|
continue
|
|
|
|
# We don't support subscribing to (message, *)
|
|
|
|
if instance == "*":
|
|
|
|
if recipient == "*":
|
2021-05-28 05:03:46 -04:00
|
|
|
skipped.add(
|
|
|
|
(
|
|
|
|
cls,
|
|
|
|
instance,
|
|
|
|
recipient,
|
|
|
|
"subscribing to all of class message is not supported.",
|
|
|
|
)
|
|
|
|
)
|
2012-11-19 12:04:23 -05:00
|
|
|
continue
|
2013-08-06 16:25:43 -04:00
|
|
|
# If you're on -i white-magic on zephyr, get on stream white-magic on zulip
|
|
|
|
# instead of subscribing to stream "message" on zulip
|
2012-11-19 12:04:23 -05:00
|
|
|
zephyr_subscriptions.add(instance)
|
|
|
|
continue
|
2012-12-03 15:33:33 -05:00
|
|
|
elif cls.lower() == "mail" and instance.lower() == "inbox":
|
2013-01-14 13:08:33 -05:00
|
|
|
# We forward mail zephyrs, so no need to log a warning.
|
2012-11-19 12:04:23 -05:00
|
|
|
continue
|
2013-11-10 17:55:31 -05:00
|
|
|
elif len(cls) > 60:
|
|
|
|
skipped.add((cls, instance, recipient, "Class longer than 60 characters"))
|
2012-11-19 12:04:23 -05:00
|
|
|
continue
|
|
|
|
elif instance != "*":
|
|
|
|
skipped.add((cls, instance, recipient, "Unsupported non-* instance"))
|
|
|
|
continue
|
|
|
|
elif recipient != "*":
|
|
|
|
skipped.add((cls, instance, recipient, "Unsupported non-* recipient."))
|
|
|
|
continue
|
|
|
|
zephyr_subscriptions.add(cls)
|
|
|
|
|
|
|
|
if len(zephyr_subscriptions) != 0:
|
2021-05-28 05:03:46 -04:00
|
|
|
res = zulip_client.add_subscriptions(
|
|
|
|
list({"name": stream} for stream in zephyr_subscriptions),
|
|
|
|
authorization_errors_fatal=False,
|
|
|
|
)
|
2012-11-19 12:04:23 -05:00
|
|
|
if res.get("result") != "success":
|
2021-05-28 07:19:40 -04:00
|
|
|
logger.error("Error subscribing to streams:\n{}".format(res["msg"]))
|
2012-11-19 12:04:23 -05:00
|
|
|
return
|
|
|
|
|
|
|
|
already = res.get("already_subscribed")
|
|
|
|
new = res.get("subscribed")
|
2013-08-15 17:38:21 -04:00
|
|
|
unauthorized = res.get("unauthorized")
|
2012-11-19 12:04:23 -05:00
|
|
|
if verbose:
|
|
|
|
if already is not None and len(already) > 0:
|
2021-05-28 07:19:40 -04:00
|
|
|
logger.info(
|
|
|
|
"\nAlready subscribed to: {}".format(", ".join(list(already.values())[0]))
|
|
|
|
)
|
2012-11-19 12:04:23 -05:00
|
|
|
if new is not None and len(new) > 0:
|
2021-05-28 05:03:46 -04:00
|
|
|
logger.info(
|
2021-05-28 07:19:40 -04:00
|
|
|
"\nSuccessfully subscribed to: {}".format(", ".join(list(new.values())[0]))
|
2021-05-28 05:03:46 -04:00
|
|
|
)
|
2013-08-15 17:38:21 -04:00
|
|
|
if unauthorized is not None and len(unauthorized) > 0:
|
2021-05-28 05:03:46 -04:00
|
|
|
logger.info(
|
|
|
|
"\n"
|
|
|
|
+ "\n".join(
|
|
|
|
textwrap.wrap(
|
|
|
|
"""\
|
2013-08-15 17:38:21 -04:00
|
|
|
The following streams you have NOT been subscribed to,
|
|
|
|
because they have been configured in Zulip as invitation-only streams.
|
|
|
|
This was done at the request of users of these Zephyr classes, usually
|
|
|
|
because traffic to those streams is sent within the Zephyr world encrypted
|
|
|
|
via zcrypt (in Zulip, we achieve the same privacy goals through invitation-only streams).
|
|
|
|
If you wish to read these streams in Zulip, you need to contact the people who are
|
|
|
|
on these streams and already use Zulip. They can subscribe you to them via the
|
|
|
|
"streams" page in the Zulip web interface:
|
2021-05-28 05:03:46 -04:00
|
|
|
"""
|
|
|
|
)
|
|
|
|
)
|
2021-05-28 07:19:40 -04:00
|
|
|
+ "\n\n {}".format(", ".join(unauthorized))
|
2021-05-28 05:03:46 -04:00
|
|
|
)
|
2012-11-19 12:04:23 -05:00
|
|
|
|
|
|
|
if len(skipped) > 0:
|
|
|
|
if verbose:
|
2021-05-28 05:03:46 -04:00
|
|
|
logger.info(
|
|
|
|
"\n"
|
|
|
|
+ "\n".join(
|
|
|
|
textwrap.wrap(
|
|
|
|
"""\
|
2012-11-19 12:04:23 -05:00
|
|
|
You have some lines in ~/.zephyr.subs that could not be
|
2013-07-10 16:58:30 -04:00
|
|
|
synced to your Zulip subscriptions because they do not
|
2012-11-19 12:04:23 -05:00
|
|
|
use "*" as both the instance and recipient and not one of
|
|
|
|
the special cases (e.g. personals and mail zephyrs) that
|
2013-07-10 16:58:30 -04:00
|
|
|
Zulip has a mechanism for forwarding. Zulip does not
|
|
|
|
allow subscribing to only some subjects on a Zulip
|
2012-11-19 12:04:23 -05:00
|
|
|
stream, so this tool has not created a corresponding
|
2013-07-10 16:58:30 -04:00
|
|
|
Zulip subscription to these lines in ~/.zephyr.subs:
|
2021-05-28 05:03:46 -04:00
|
|
|
"""
|
|
|
|
)
|
|
|
|
)
|
|
|
|
+ "\n"
|
|
|
|
)
|
2012-11-19 12:04:23 -05:00
|
|
|
|
|
|
|
for (cls, instance, recipient, reason) in skipped:
|
|
|
|
if verbose:
|
|
|
|
if reason != "":
|
2021-05-28 07:19:40 -04:00
|
|
|
logger.info(f" [{cls},{instance},{recipient}] ({reason})")
|
2012-11-19 12:04:23 -05:00
|
|
|
else:
|
2021-05-28 07:19:40 -04:00
|
|
|
logger.info(f" [{cls},{instance},{recipient}]")
|
2012-11-19 12:04:23 -05:00
|
|
|
if len(skipped) > 0:
|
|
|
|
if verbose:
|
2021-05-28 05:03:46 -04:00
|
|
|
logger.info(
|
|
|
|
"\n"
|
|
|
|
+ "\n".join(
|
|
|
|
textwrap.wrap(
|
|
|
|
"""\
|
2013-07-10 16:58:30 -04:00
|
|
|
If you wish to be subscribed to any Zulip streams related
|
|
|
|
to these .zephyrs.subs lines, please do so via the Zulip
|
2012-11-19 12:04:23 -05:00
|
|
|
web interface.
|
2021-05-28 05:03:46 -04:00
|
|
|
"""
|
|
|
|
)
|
|
|
|
)
|
|
|
|
+ "\n"
|
|
|
|
)
|
|
|
|
|
2012-11-19 12:04:23 -05:00
|
|
|
|
2020-04-18 18:59:12 -04:00
|
|
|
def valid_stream_name(name: str) -> bool:
|
2012-11-19 12:04:23 -05:00
|
|
|
return name != ""
|
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
|
2020-04-18 18:59:12 -04:00
|
|
|
def parse_zephyr_subs(verbose: bool = False) -> Set[Tuple[str, str, str]]:
|
2017-06-04 05:35:22 -04:00
|
|
|
zephyr_subscriptions = set() # type: Set[Tuple[str, str, str]]
|
2012-11-19 12:04:23 -05:00
|
|
|
subs_file = os.path.join(os.environ["HOME"], ".zephyr.subs")
|
|
|
|
if not os.path.exists(subs_file):
|
|
|
|
if verbose:
|
2013-01-14 13:08:33 -05:00
|
|
|
logger.error("Couldn't find ~/.zephyr.subs!")
|
2017-05-22 13:51:27 -04:00
|
|
|
return zephyr_subscriptions
|
2012-11-19 12:04:23 -05:00
|
|
|
|
2020-04-09 20:14:01 -04:00
|
|
|
for line in open(subs_file).readlines():
|
2012-11-19 12:04:23 -05:00
|
|
|
line = line.strip()
|
|
|
|
if len(line) == 0:
|
|
|
|
continue
|
|
|
|
try:
|
|
|
|
(cls, instance, recipient) = line.split(",")
|
|
|
|
cls = cls.replace("%me%", options.user)
|
|
|
|
instance = instance.replace("%me%", options.user)
|
|
|
|
recipient = recipient.replace("%me%", options.user)
|
|
|
|
if not valid_stream_name(cls):
|
|
|
|
if verbose:
|
2021-05-28 07:19:40 -04:00
|
|
|
logger.error(f"Skipping subscription to unsupported class name: [{line}]")
|
2012-11-19 12:04:23 -05:00
|
|
|
continue
|
2012-11-29 08:48:35 -05:00
|
|
|
except Exception:
|
2012-11-19 12:04:23 -05:00
|
|
|
if verbose:
|
2021-05-28 07:19:40 -04:00
|
|
|
logger.error(f"Couldn't parse ~/.zephyr.subs line: [{line}]")
|
2012-11-19 12:04:23 -05:00
|
|
|
continue
|
|
|
|
zephyr_subscriptions.add((cls.strip(), instance.strip(), recipient.strip()))
|
|
|
|
return zephyr_subscriptions
|
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
|
2020-04-18 18:59:12 -04:00
|
|
|
def open_logger() -> logging.Logger:
|
2013-08-27 11:10:13 -04:00
|
|
|
if options.log_path is not None:
|
|
|
|
log_file = options.log_path
|
|
|
|
elif options.forward_class_messages:
|
2012-11-19 12:04:23 -05:00
|
|
|
if options.test_mode:
|
2013-09-25 14:18:32 -04:00
|
|
|
log_file = "/var/log/zulip/test-mirror-log"
|
2012-11-19 12:04:23 -05:00
|
|
|
else:
|
2013-09-25 14:18:32 -04:00
|
|
|
log_file = "/var/log/zulip/mirror-log"
|
2012-11-19 12:04:23 -05:00
|
|
|
else:
|
2021-05-28 07:19:40 -04:00
|
|
|
f = tempfile.NamedTemporaryFile(prefix=f"zulip-log.{options.user}.", delete=False)
|
2012-12-20 14:10:12 -05:00
|
|
|
log_file = f.name
|
|
|
|
# Close the file descriptor, since the logging system will
|
|
|
|
# reopen it anyway.
|
|
|
|
f.close()
|
2012-11-19 12:04:23 -05:00
|
|
|
logger = logging.getLogger(__name__)
|
2013-01-14 13:08:33 -05:00
|
|
|
log_format = "%(asctime)s <initial>: %(message)s"
|
2012-11-19 12:04:23 -05:00
|
|
|
formatter = logging.Formatter(log_format)
|
|
|
|
logging.basicConfig(format=log_format)
|
|
|
|
logger.setLevel(logging.DEBUG)
|
|
|
|
file_handler = logging.FileHandler(log_file)
|
|
|
|
file_handler.setFormatter(formatter)
|
|
|
|
logger.addHandler(file_handler)
|
|
|
|
return logger
|
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
|
2020-04-18 18:59:12 -04:00
|
|
|
def configure_logger(logger: logging.Logger, direction_name: Optional[str]) -> None:
|
2013-01-14 13:08:33 -05:00
|
|
|
if direction_name is None:
|
|
|
|
log_format = "%(message)s"
|
|
|
|
else:
|
|
|
|
log_format = "%(asctime)s [" + direction_name + "] %(message)s"
|
|
|
|
formatter = logging.Formatter(log_format)
|
|
|
|
|
|
|
|
# Replace the formatters for the file and stdout loggers
|
|
|
|
for handler in logger.handlers:
|
|
|
|
handler.setFormatter(formatter)
|
|
|
|
root_logger = logging.getLogger()
|
|
|
|
for handler in root_logger.handlers:
|
|
|
|
handler.setFormatter(formatter)
|
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
|
2021-03-10 16:55:13 -05:00
|
|
|
def parse_args() -> Tuple[optparse.Values, List[str]]:
|
2012-11-19 12:04:23 -05:00
|
|
|
parser = optparse.OptionParser()
|
2021-05-28 05:03:46 -04:00
|
|
|
parser.add_option(
|
2021-05-28 05:05:11 -04:00
|
|
|
"--forward-class-messages", default=False, help=optparse.SUPPRESS_HELP, action="store_true"
|
2021-05-28 05:03:46 -04:00
|
|
|
)
|
2021-05-28 05:05:11 -04:00
|
|
|
parser.add_option("--shard", help=optparse.SUPPRESS_HELP)
|
|
|
|
parser.add_option("--noshard", default=False, help=optparse.SUPPRESS_HELP, action="store_true")
|
|
|
|
parser.add_option("--resend-log", dest="logs_to_resend", help=optparse.SUPPRESS_HELP)
|
|
|
|
parser.add_option("--enable-resend-log", dest="resend_log_path", help=optparse.SUPPRESS_HELP)
|
|
|
|
parser.add_option("--log-path", dest="log_path", help=optparse.SUPPRESS_HELP)
|
2021-05-28 05:03:46 -04:00
|
|
|
parser.add_option(
|
2021-05-28 05:05:11 -04:00
|
|
|
"--stream-file-path",
|
|
|
|
dest="stream_file_path",
|
2021-05-28 05:03:46 -04:00
|
|
|
default="/home/zulip/public_streams",
|
|
|
|
help=optparse.SUPPRESS_HELP,
|
|
|
|
)
|
|
|
|
parser.add_option(
|
2021-05-28 05:05:11 -04:00
|
|
|
"--no-forward-personals",
|
|
|
|
dest="forward_personals",
|
2021-05-28 05:03:46 -04:00
|
|
|
help=optparse.SUPPRESS_HELP,
|
|
|
|
default=True,
|
2021-05-28 05:05:11 -04:00
|
|
|
action="store_false",
|
2021-05-28 05:03:46 -04:00
|
|
|
)
|
|
|
|
parser.add_option(
|
2021-05-28 05:05:11 -04:00
|
|
|
"--forward-mail-zephyrs",
|
|
|
|
dest="forward_mail_zephyrs",
|
2021-05-28 05:03:46 -04:00
|
|
|
help=optparse.SUPPRESS_HELP,
|
|
|
|
default=False,
|
2021-05-28 05:05:11 -04:00
|
|
|
action="store_true",
|
2021-05-28 05:03:46 -04:00
|
|
|
)
|
|
|
|
parser.add_option(
|
2021-05-28 05:05:11 -04:00
|
|
|
"--no-forward-from-zulip",
|
2021-05-28 05:03:46 -04:00
|
|
|
default=True,
|
2021-05-28 05:05:11 -04:00
|
|
|
dest="forward_from_zulip",
|
2021-05-28 05:03:46 -04:00
|
|
|
help=optparse.SUPPRESS_HELP,
|
2021-05-28 05:05:11 -04:00
|
|
|
action="store_false",
|
2021-05-28 05:03:46 -04:00
|
|
|
)
|
2021-05-28 05:05:11 -04:00
|
|
|
parser.add_option("--verbose", default=False, help=optparse.SUPPRESS_HELP, action="store_true")
|
|
|
|
parser.add_option("--sync-subscriptions", default=False, action="store_true")
|
|
|
|
parser.add_option("--ignore-expired-tickets", default=False, action="store_true")
|
|
|
|
parser.add_option("--site", default=DEFAULT_SITE, help=optparse.SUPPRESS_HELP)
|
|
|
|
parser.add_option("--on-startup-command", default=None, help=optparse.SUPPRESS_HELP)
|
|
|
|
parser.add_option("--user", default=os.environ["USER"], help=optparse.SUPPRESS_HELP)
|
2021-05-28 05:03:46 -04:00
|
|
|
parser.add_option(
|
2021-05-28 05:05:11 -04:00
|
|
|
"--stamp-path",
|
2021-05-28 05:03:46 -04:00
|
|
|
default="/afs/athena.mit.edu/user/t/a/tabbott/for_friends",
|
|
|
|
help=optparse.SUPPRESS_HELP,
|
|
|
|
)
|
2021-05-28 05:05:11 -04:00
|
|
|
parser.add_option("--session-path", default=None, help=optparse.SUPPRESS_HELP)
|
|
|
|
parser.add_option("--nagios-class", default=None, help=optparse.SUPPRESS_HELP)
|
|
|
|
parser.add_option("--nagios-path", default=None, help=optparse.SUPPRESS_HELP)
|
2021-05-28 05:03:46 -04:00
|
|
|
parser.add_option(
|
2021-05-28 05:05:11 -04:00
|
|
|
"--use-sessions", default=False, action="store_true", help=optparse.SUPPRESS_HELP
|
2021-05-28 05:03:46 -04:00
|
|
|
)
|
|
|
|
parser.add_option(
|
2021-05-28 05:05:11 -04:00
|
|
|
"--test-mode", default=False, help=optparse.SUPPRESS_HELP, action="store_true"
|
2021-05-28 05:03:46 -04:00
|
|
|
)
|
|
|
|
parser.add_option(
|
2021-05-28 05:05:11 -04:00
|
|
|
"--api-key-file", default=os.path.join(os.environ["HOME"], "Private", ".humbug-api-key")
|
2021-05-28 05:03:46 -04:00
|
|
|
)
|
2012-11-19 12:04:23 -05:00
|
|
|
return parser.parse_args()
|
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
|
2020-04-18 18:59:12 -04:00
|
|
|
def die_gracefully(signal: int, frame: FrameType) -> None:
|
2013-08-06 15:34:20 -04:00
|
|
|
if CURRENT_STATE == States.ZulipToZephyr or CURRENT_STATE == States.ChildSending:
|
zmirror: Allow duplicate zmirror processes to die gracefully.
Fixes #602.
I replaced the SIGKILL with a SIGINT, and then catch SIGINT with a
handler. This handler calls cancelSubs if necessary, and can later be
edited to perform other clean-up operations, too. I thought about, in
this same commit, changing the SIGTERM in
maybe_restart_mirroring_script to a SIGINT, but after tracing out the
code paths, I realized that isn't necessary. (The SIGTERM is
necessarily performed on a process that has not subscribed to any
zephyr classes, so cancelSubs is unnecessary. If we do think that we
may want to add additional clean-up operations in the future, though,
then it might be worth investigating changing this SIGTERM.)
(imported from commit 692b295be6cb40b0e4ec2ca0bc58c58056ed9bd9)
2013-01-03 13:21:19 -05:00
|
|
|
# this is a child process, so we want os._exit (no clean-up necessary)
|
|
|
|
os._exit(1)
|
|
|
|
|
2013-08-23 14:13:26 -04:00
|
|
|
if CURRENT_STATE == States.ZephyrToZulip and not options.use_sessions:
|
zmirror: Allow duplicate zmirror processes to die gracefully.
Fixes #602.
I replaced the SIGKILL with a SIGINT, and then catch SIGINT with a
handler. This handler calls cancelSubs if necessary, and can later be
edited to perform other clean-up operations, too. I thought about, in
this same commit, changing the SIGTERM in
maybe_restart_mirroring_script to a SIGINT, but after tracing out the
code paths, I realized that isn't necessary. (The SIGTERM is
necessarily performed on a process that has not subscribed to any
zephyr classes, so cancelSubs is unnecessary. If we do think that we
may want to add additional clean-up operations in the future, though,
then it might be worth investigating changing this SIGTERM.)
(imported from commit 692b295be6cb40b0e4ec2ca0bc58c58056ed9bd9)
2013-01-03 13:21:19 -05:00
|
|
|
try:
|
2013-08-06 16:25:43 -04:00
|
|
|
# zephyr=>zulip processes may have added subs, so run cancelSubs
|
zmirror: Allow duplicate zmirror processes to die gracefully.
Fixes #602.
I replaced the SIGKILL with a SIGINT, and then catch SIGINT with a
handler. This handler calls cancelSubs if necessary, and can later be
edited to perform other clean-up operations, too. I thought about, in
this same commit, changing the SIGTERM in
maybe_restart_mirroring_script to a SIGINT, but after tracing out the
code paths, I realized that isn't necessary. (The SIGTERM is
necessarily performed on a process that has not subscribed to any
zephyr classes, so cancelSubs is unnecessary. If we do think that we
may want to add additional clean-up operations in the future, though,
then it might be worth investigating changing this SIGTERM.)
(imported from commit 692b295be6cb40b0e4ec2ca0bc58c58056ed9bd9)
2013-01-03 13:21:19 -05:00
|
|
|
zephyr._z.cancelSubs()
|
2020-04-09 20:14:01 -04:00
|
|
|
except OSError:
|
zmirror: Allow duplicate zmirror processes to die gracefully.
Fixes #602.
I replaced the SIGKILL with a SIGINT, and then catch SIGINT with a
handler. This handler calls cancelSubs if necessary, and can later be
edited to perform other clean-up operations, too. I thought about, in
this same commit, changing the SIGTERM in
maybe_restart_mirroring_script to a SIGINT, but after tracing out the
code paths, I realized that isn't necessary. (The SIGTERM is
necessarily performed on a process that has not subscribed to any
zephyr classes, so cancelSubs is unnecessary. If we do think that we
may want to add additional clean-up operations in the future, though,
then it might be worth investigating changing this SIGTERM.)
(imported from commit 692b295be6cb40b0e4ec2ca0bc58c58056ed9bd9)
2013-01-03 13:21:19 -05:00
|
|
|
# We don't care whether we failed to cancel subs properly, but we should log it
|
2013-01-14 13:08:33 -05:00
|
|
|
logger.exception("")
|
zmirror: Allow duplicate zmirror processes to die gracefully.
Fixes #602.
I replaced the SIGKILL with a SIGINT, and then catch SIGINT with a
handler. This handler calls cancelSubs if necessary, and can later be
edited to perform other clean-up operations, too. I thought about, in
this same commit, changing the SIGTERM in
maybe_restart_mirroring_script to a SIGINT, but after tracing out the
code paths, I realized that isn't necessary. (The SIGTERM is
necessarily performed on a process that has not subscribed to any
zephyr classes, so cancelSubs is unnecessary. If we do think that we
may want to add additional clean-up operations in the future, though,
then it might be worth investigating changing this SIGTERM.)
(imported from commit 692b295be6cb40b0e4ec2ca0bc58c58056ed9bd9)
2013-01-03 13:21:19 -05:00
|
|
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
2021-05-28 05:03:46 -04:00
|
|
|
|
2012-11-19 12:04:23 -05:00
|
|
|
if __name__ == "__main__":
|
|
|
|
# Set the SIGCHLD handler back to SIG_DFL to prevent these errors
|
|
|
|
# when importing the "requests" module after being restarted using
|
|
|
|
# the restart_stamp functionality:
|
|
|
|
#
|
|
|
|
# close failed in file object destructor:
|
|
|
|
# IOError: [Errno 10] No child processes
|
|
|
|
signal.signal(signal.SIGCHLD, signal.SIG_DFL)
|
|
|
|
|
zmirror: Allow duplicate zmirror processes to die gracefully.
Fixes #602.
I replaced the SIGKILL with a SIGINT, and then catch SIGINT with a
handler. This handler calls cancelSubs if necessary, and can later be
edited to perform other clean-up operations, too. I thought about, in
this same commit, changing the SIGTERM in
maybe_restart_mirroring_script to a SIGINT, but after tracing out the
code paths, I realized that isn't necessary. (The SIGTERM is
necessarily performed on a process that has not subscribed to any
zephyr classes, so cancelSubs is unnecessary. If we do think that we
may want to add additional clean-up operations in the future, though,
then it might be worth investigating changing this SIGTERM.)
(imported from commit 692b295be6cb40b0e4ec2ca0bc58c58056ed9bd9)
2013-01-03 13:21:19 -05:00
|
|
|
signal.signal(signal.SIGINT, die_gracefully)
|
|
|
|
|
2021-03-10 16:55:13 -05:00
|
|
|
(options, args) = parse_args()
|
2012-11-19 12:04:23 -05:00
|
|
|
|
2013-01-14 13:08:33 -05:00
|
|
|
logger = open_logger()
|
|
|
|
configure_logger(logger, "parent")
|
|
|
|
|
2012-11-19 12:04:23 -05:00
|
|
|
# In case this is an automated restart of the mirroring script,
|
|
|
|
# and we have lost AFS tokens, first try reading the API key from
|
|
|
|
# the environment so that we can skip doing a filesystem read.
|
|
|
|
if os.environ.get("HUMBUG_API_KEY") is not None:
|
|
|
|
api_key = os.environ.get("HUMBUG_API_KEY")
|
|
|
|
else:
|
|
|
|
if not os.path.exists(options.api_key_file):
|
2021-05-28 05:03:46 -04:00
|
|
|
logger.error(
|
|
|
|
"\n"
|
|
|
|
+ "\n".join(
|
|
|
|
textwrap.wrap(
|
|
|
|
"""\
|
2012-11-19 12:04:23 -05:00
|
|
|
Could not find API key file.
|
|
|
|
You need to either place your api key file at %s,
|
2021-05-28 05:03:46 -04:00
|
|
|
or specify the --api-key-file option."""
|
|
|
|
% (options.api_key_file,)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
2012-11-19 12:04:23 -05:00
|
|
|
sys.exit(1)
|
2015-10-14 16:31:08 -04:00
|
|
|
api_key = open(options.api_key_file).read().strip()
|
2012-11-19 12:04:23 -05:00
|
|
|
# Store the API key in the environment so that our children
|
|
|
|
# don't need to read it in
|
|
|
|
os.environ["HUMBUG_API_KEY"] = api_key
|
|
|
|
|
2013-08-26 15:43:29 -04:00
|
|
|
if options.nagios_path is None and options.nagios_class is not None:
|
|
|
|
logger.error("\n" + "nagios_path is required with nagios_class\n")
|
|
|
|
sys.exit(1)
|
|
|
|
|
2013-08-06 16:25:43 -04:00
|
|
|
zulip_account_email = options.user + "@mit.edu"
|
2013-08-07 11:51:03 -04:00
|
|
|
import zulip
|
2021-05-28 05:03:46 -04:00
|
|
|
|
2013-08-07 11:51:03 -04:00
|
|
|
zulip_client = zulip.Client(
|
2013-08-06 16:25:43 -04:00
|
|
|
email=zulip_account_email,
|
2012-12-03 12:23:06 -05:00
|
|
|
api_key=api_key,
|
|
|
|
verbose=True,
|
|
|
|
client="zephyr_mirror",
|
2021-05-28 05:03:46 -04:00
|
|
|
site=options.site,
|
|
|
|
)
|
2012-11-19 12:04:23 -05:00
|
|
|
|
|
|
|
start_time = time.time()
|
|
|
|
|
|
|
|
if options.sync_subscriptions:
|
2013-01-14 13:08:33 -05:00
|
|
|
configure_logger(logger, None) # make the output cleaner
|
2013-07-10 16:58:30 -04:00
|
|
|
logger.info("Syncing your ~/.zephyr.subs to your Zulip Subscriptions!")
|
2013-08-06 16:25:43 -04:00
|
|
|
add_zulip_subscriptions(True)
|
2012-11-19 12:04:23 -05:00
|
|
|
sys.exit(0)
|
|
|
|
|
2012-12-21 11:26:36 -05:00
|
|
|
# Kill all zephyr_mirror processes other than this one and its parent.
|
2012-11-19 12:04:23 -05:00
|
|
|
if not options.test_mode:
|
2013-05-16 14:44:35 -04:00
|
|
|
pgrep_query = "python.*zephyr_mirror"
|
2012-11-27 10:27:02 -05:00
|
|
|
if options.shard is not None:
|
2013-08-26 14:29:41 -04:00
|
|
|
# sharded class mirror
|
2021-05-28 07:19:40 -04:00
|
|
|
pgrep_query = f"{pgrep_query}.*--shard={options.shard}"
|
2013-08-26 14:29:41 -04:00
|
|
|
elif options.user is not None:
|
|
|
|
# Personals mirror on behalf of another user.
|
2021-05-28 07:19:40 -04:00
|
|
|
pgrep_query = f"{pgrep_query}.*--user={options.user}"
|
2021-05-28 05:03:46 -04:00
|
|
|
proc = subprocess.Popen(
|
2021-05-28 05:05:11 -04:00
|
|
|
["pgrep", "-U", os.environ["USER"], "-f", pgrep_query],
|
2021-05-28 05:03:46 -04:00
|
|
|
stdout=subprocess.PIPE,
|
|
|
|
stderr=subprocess.PIPE,
|
|
|
|
)
|
2012-11-19 12:04:23 -05:00
|
|
|
out, _err_unused = proc.communicate()
|
2012-12-21 11:26:36 -05:00
|
|
|
for pid in map(int, out.split()):
|
|
|
|
if pid == os.getpid() or pid == os.getppid():
|
|
|
|
continue
|
|
|
|
|
|
|
|
# Another copy of zephyr_mirror.py! Kill it.
|
2021-05-28 07:19:40 -04:00
|
|
|
logger.info(f"Killing duplicate zephyr_mirror process {pid}")
|
2012-12-21 11:26:36 -05:00
|
|
|
try:
|
zmirror: Allow duplicate zmirror processes to die gracefully.
Fixes #602.
I replaced the SIGKILL with a SIGINT, and then catch SIGINT with a
handler. This handler calls cancelSubs if necessary, and can later be
edited to perform other clean-up operations, too. I thought about, in
this same commit, changing the SIGTERM in
maybe_restart_mirroring_script to a SIGINT, but after tracing out the
code paths, I realized that isn't necessary. (The SIGTERM is
necessarily performed on a process that has not subscribed to any
zephyr classes, so cancelSubs is unnecessary. If we do think that we
may want to add additional clean-up operations in the future, though,
then it might be worth investigating changing this SIGTERM.)
(imported from commit 692b295be6cb40b0e4ec2ca0bc58c58056ed9bd9)
2013-01-03 13:21:19 -05:00
|
|
|
os.kill(pid, signal.SIGINT)
|
2012-12-21 11:26:36 -05:00
|
|
|
except OSError:
|
2013-01-14 13:08:33 -05:00
|
|
|
# We don't care if the target process no longer exists, so just log the error
|
|
|
|
logger.exception("")
|
2012-11-19 12:04:23 -05:00
|
|
|
|
2012-11-27 10:27:02 -05:00
|
|
|
if options.shard is not None and set(options.shard) != set("a"):
|
|
|
|
# The shard that is all "a"s is the one that handles personals
|
2013-08-06 16:25:43 -04:00
|
|
|
# forwarding and zulip => zephyr forwarding
|
2012-11-27 10:27:02 -05:00
|
|
|
options.forward_personals = False
|
2013-08-06 16:25:43 -04:00
|
|
|
options.forward_from_zulip = False
|
2013-08-27 15:25:59 -04:00
|
|
|
if options.forward_mail_zephyrs is None:
|
|
|
|
options.forward_mail_zephyrs = subscribed_to_mail_messages()
|
2012-11-27 10:27:02 -05:00
|
|
|
|
2013-08-23 14:13:26 -04:00
|
|
|
if options.session_path is None:
|
2021-05-28 07:19:40 -04:00
|
|
|
options.session_path = f"/var/tmp/{options.user}"
|
2013-08-23 14:13:26 -04:00
|
|
|
|
2013-08-06 16:25:43 -04:00
|
|
|
if options.forward_from_zulip:
|
2018-01-07 12:29:08 -05:00
|
|
|
child_pid = os.fork() # type: Optional[int]
|
2012-11-27 10:25:49 -05:00
|
|
|
if child_pid == 0:
|
2013-08-06 15:34:20 -04:00
|
|
|
CURRENT_STATE = States.ZulipToZephyr
|
2013-08-06 16:25:43 -04:00
|
|
|
# Run the zulip => zephyr mirror in the child
|
|
|
|
configure_logger(logger, "zulip=>zephyr")
|
|
|
|
zulip_to_zephyr(options)
|
2012-11-27 10:25:49 -05:00
|
|
|
else:
|
|
|
|
child_pid = None
|
2013-08-06 15:34:20 -04:00
|
|
|
CURRENT_STATE = States.ZephyrToZulip
|
2012-11-19 12:04:23 -05:00
|
|
|
|
|
|
|
import zephyr
|
2021-05-28 05:03:46 -04:00
|
|
|
|
2013-08-06 16:25:43 -04:00
|
|
|
logger_name = "zephyr=>zulip"
|
2012-11-27 10:27:02 -05:00
|
|
|
if options.shard is not None:
|
2021-05-28 07:19:40 -04:00
|
|
|
logger_name += f"({options.shard})"
|
2013-01-14 13:08:33 -05:00
|
|
|
configure_logger(logger, logger_name)
|
2013-08-06 15:32:15 -04:00
|
|
|
# Have the kernel reap children for when we fork off processes to send Zulips
|
2012-11-19 12:04:23 -05:00
|
|
|
signal.signal(signal.SIGCHLD, signal.SIG_IGN)
|
2013-08-06 16:25:43 -04:00
|
|
|
zephyr_to_zulip(options)
|