diff --git a/debian/changelog b/debian/changelog index 8f307b5..442d417 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,13 @@ +python-discuss (1.3-0debathena1) unstable; urgency=low + + * Release version 1.3. + * Add Python 3 support + * Update Debian packaging for Python 3 support + * Drop Python 2 support + * Add additional functionality for pergamon + + -- Alex Dehnert Fri, 21 Feb 2025 02:21:24 -0400 + python-discuss (1.2-0debathena3) unstable; urgency=low * Avoid building for Python 2.6 on distributions which support it diff --git a/debian/compat b/debian/compat index 7f8f011..f599e28 100644 --- a/debian/compat +++ b/debian/compat @@ -1 +1 @@ -7 +10 diff --git a/debian/control b/debian/control index 7002fee..3ba994c 100644 --- a/debian/control +++ b/debian/control @@ -1,14 +1,13 @@ Source: python-discuss Section: python -Priority: extra +Priority: optional Maintainer: Debathena Project -Build-Depends: debhelper (>= 7), python (>= 2.7) -X-Python-Version: 2.7 +Build-Depends: debhelper (>= 10), dh-python, python3-all Standards-Version: 3.9.4 -Package: python-discuss +Package: python3-discuss Architecture: all -Depends: ${shlibs:Depends}, ${misc:Depends}, ${python:Depends}, python (>= 2.7), python-kerberos +Depends: ${shlibs:Depends}, ${misc:Depends}, ${python3:Depends}, python3-kerberos Description: Python client for Project Athena forum system Pydiscuss provides a pure-Python implementation of discuss -- the forum protocol developed in late 80's and still used around MIT. diff --git a/debian/python3-discuss.install b/debian/python3-discuss.install new file mode 100644 index 0000000..9b58b6f --- /dev/null +++ b/debian/python3-discuss.install @@ -0,0 +1,2 @@ +etc/servers etc/discuss +etc/meetings.default etc/discuss diff --git a/debian/rules b/debian/rules index 2ebce13..4429223 100755 --- a/debian/rules +++ b/debian/rules @@ -9,5 +9,7 @@ # Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 +export PYBUILD_NAME=discuss + %: - dh $@ --with python2 + dh $@ --with python3 --buildsystem=pybuild diff --git a/discuss/client.py b/discuss/client.py index ed2077e..855413a 100644 --- a/discuss/client.py +++ b/discuss/client.py @@ -43,7 +43,7 @@ def autoreconnect(self, *args, **kwargs): class Client(object): """Discuss client.""" - def __init__(self, server, port = 2100, auth = True, timeout = None): + def __init__(self, server, port = 2100, auth = True, timeout = None, RPCClient=RPCClient): self.rpc = RPCClient(server, port, auth, timeout) if auth and self.who_am_i().startswith("???@"): raise ProtocolError("Authentication to server failed") @@ -65,6 +65,17 @@ def who_am_i(self): reply = self.rpc.request(request) return reply.read_string() + @autoreconnects + def create_mtg(self, location, long_mtg_name, public): + request = USPBlock(constants.CREATE_MTG) + request.put_string(location) + request.put_string(long_mtg_name) + request.put_boolean(public) + reply = self.rpc.request(request) + result = reply.read_long_integer() + if result != 0: + raise DiscussError(result) + def close(self): """Disconnect from the server.""" @@ -300,6 +311,15 @@ def set_access(self, principal, modes): if result != 0: raise DiscussError(result) + def ensure_access(self, principal, modes): + current = self.get_access(principal) + self.set_access(principal, current+modes) + + def remove_access(self, principal, modes): + current = self.get_access(principal) + new_modes = ''.join(c for c in current if not c in modes) + self.set_access(principal, new_modes) + @autoreconnects def undelete_transaction(self, trn_number): """Undelete the transaction by its number.""" @@ -340,7 +360,7 @@ def get_text(self): if result != 0: raise DiscussError(result) - return tfile.buffer + return tfile.buffer.decode() @autoreconnects def delete(self): diff --git a/discuss/locator.py b/discuss/locator.py index f2a3045..9a7f9c1 100644 --- a/discuss/locator.py +++ b/discuss/locator.py @@ -28,7 +28,7 @@ def _read_server_list(filename): lines = map(remove_comments, lines) # comments lines = map(str.strip, lines) # whitespace - lines = filter(lambda x: x, lines) # empty lines + lines = [x for x in lines if x] # empty lines return lines except IOError as err: diff --git a/discuss/rpc.py b/discuss/rpc.py index 7c1532f..51b4919 100644 --- a/discuss/rpc.py +++ b/discuss/rpc.py @@ -42,8 +42,10 @@ # import errno +import fcntl import socket from struct import pack, unpack, calcsize +import subprocess from functools import partial from . import constants @@ -91,9 +93,9 @@ def _get_krb5_ap_req(service, server): # bindings myself, but this is the yak I am not ready to shave at the # moment. - body_start = token_gssapi.find( chr(0x01) + chr(0x00) ) # 01 00 indicates that this is AP_REQ - if token_gssapi[0] != chr(0x60) or \ - not (token_gssapi[2] == chr(0x06) or token_gssapi[4] == chr(0x06)) or \ + body_start = token_gssapi.find(b'\x01\x00') # 01 00 indicates that this is AP_REQ + if token_gssapi[0:1] != b'\x60' or \ + not (token_gssapi[2:3] == b'\x06' or token_gssapi[4:5] == b'\x06') or \ body_start == -1 or body_start < 8 or body_start > 64: raise ProtocolError("Invalid GSSAPI token provided by Python's Kerberos API") @@ -135,13 +137,13 @@ def put_string(self, s): # technical reasons from 1980s I do not really want to know. This works # out because input is null-terminated and wire format is has length # specified. - encoded = s.replace("\r", "\r\0").replace("\n", "\r\n") + encoded = s.encode().replace(b"\r", b"\r\0").replace(b"\n", b"\r\n") self.put_cardinal(len(encoded)) self.buffer += encoded # Padding if len(encoded) % 2 == 1: - self.buffer += "\0" + self.buffer += b"\0" def send(self, sock): """Sends the block over a socket.""" @@ -193,7 +195,7 @@ def read_string(self): omit = size + 1 if size % 2 ==1 else size # due to padding encoded, self.buffer = self.buffer[0:size], self.buffer[omit:] - return encoded.replace("\r\n", "\n").replace("\r\0", "\r") + return encoded.replace(b"\r\n", b"\n").replace(b"\r\0", b"\r").decode() @staticmethod def receive(sock): @@ -272,7 +274,9 @@ def connect(self): auth_block.put_cardinal(len(authenticator)) for byte in authenticator: - auth_block.put_cardinal(ord(byte)) + if str == bytes: + byte = ord(byte) + auth_block.put_cardinal(byte) else: auth_block.put_cardinal(0) @@ -314,3 +318,22 @@ def request(self, block): raise ProtocolError("Transport-level error") return reply +class RPCLocalClient(RPCClient): + # Args are for compatibility with the remote RPC; most aren't used + def __init__(self, server, port, auth, timeout): + # Used as the id field on meeting objects, so copy it in + self.server = server + # port 2100 is the default port -> use the binary + if port == 2100: + port = '/usr/sbin/disserve' + self.cmd = port + + self.connect() + self.make_wrapper() + + def connect(self): + pair = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM) + subprocess.Popen([self.cmd], stdin=pair[1], close_fds=True) + pair[1].close() + fcntl.fcntl(pair[0].fileno(), fcntl.F_SETFD, fcntl.FD_CLOEXEC) + self.socket = pair[0] diff --git a/setup.py b/setup.py index afb85b8..7005451 100644 --- a/setup.py +++ b/setup.py @@ -1,9 +1,9 @@ #!/usr/bin/python -from distutils.core import setup +from setuptools import setup setup(name='discuss', - version='1.2', + version='1.3', description='Python client for Project Athena forum system', author='Victor Vasiliev', maintainer='Debathena Project', diff --git a/tools/constants_gen.py b/tools/constants_gen.py index e325fd0..1516ed0 100644 --- a/tools/constants_gen.py +++ b/tools/constants_gen.py @@ -1,5 +1,5 @@ #!/usr/bin/python - +from __future__ import print_function # This file generates the constants from discuss sources. # The first argument is path to those sources. # @@ -16,7 +16,7 @@ basepath = sys.argv[1] if not os.path.isdir(basepath): - print "ERROR: the specified path is not a directory" + print("ERROR: the specified path is not a directory") exit() with open(basepath + "/ets/dsc_et.et", "r") as et_file_handler: @@ -28,35 +28,35 @@ header_match_entries = re.findall( r'#define ([A-Z0-9_]+)\s+(".+"|[0-9x\-]+)', header_file ) if not et_match_entries: - print "ERROR: unable to parse dsc_et file correctly" + print("ERROR: unable to parse dsc_et file correctly") exit() if not header_match_entries: - print "ERROR: unable to parse rpc.h file correctly" + print("ERROR: unable to parse rpc.h file correctly") exit() ##### Code file header ##### -print "# Discuss status codes and other constants, generated from discuss sources" -print "#" -print "# NOTE: this file was autogenerated from the following files:" -print "" +print("# Discuss status codes and other constants, generated from discuss sources") +print("#") +print("# NOTE: this file was autogenerated from the following files:") +print("") ##### Discuss error codes ##### -print "# Error codes" +print("# Error codes") cur_code = et_base for match in et_match_entries: - print '%s = %s' % (match[0], repr(cur_code)) + print('%s = %s' % (match[0], repr(cur_code))) cur_code += 1 -print "" +print("") -print "# Error code descriptions" -print "errors = {" +print("# Error code descriptions") +print("errors = {") for match in et_match_entries: - print ' %s : "%s",' % match -print "}" -print "" + print(' %s : "%s",' % match) +print("}") +print("") ##### Constatns from rpc.h ##### -print "# Definitions from rpc.h" +print("# Definitions from rpc.h") for match in header_match_entries: - print '%s = %s' % match -print "" + print('%s = %s' % match) +print("")