diff --git a/discuss/client.py b/discuss/client.py
index ed2077e..aaafb4f 100644
--- a/discuss/client.py
+++ b/discuss/client.py
@@ -340,7 +340,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..43369f0 100644
--- a/discuss/rpc.py
+++ b/discuss/rpc.py
@@ -91,9 +91,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 +135,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 +193,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 +272,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)
 
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("")