/mandos/trunk

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/trunk

« back to all changes in this revision

Viewing changes to mandos

  • Committer: Teddy Hogeborn
  • Date: 2016-03-09 21:23:21 UTC
  • Revision ID: teddy@recompile.se-20160309212321-2qlkzj9tecepc8xq
Server: Add Python 3 compatibility

Add Python 3 compatibility by not using the python-avahi module.  Also
fix miscellaneous things which differs in Python 3.  Especially hard
to fix is loading and saving clients data between Python 3 and 2,
since pickle formats have problems with strings.

* INSTALL: Remove python-avahi (and change python-gobject to
  python-gi, which is preferred now).
* debian/control (Source: mandos/Build-Depends-Indep): Remove
  "python-avahi".
* mandos: Wrap future_builtins import in try-except clause.  Do not
  import avahi module.  Use codecs.decode(..., "base64) instead of
  .decode("base64).  Use .keys(), .values(), and .items() instead of
  .iterkeys(), .itervalues(), and .iteritems().
  (alternate_dbus_interfaces/wrapper): Python 3 still requires the
  "black magic", but luckily it still works.  The Python 3 type()
  constructor requires first argument to be a string, not a byte
  string.
  (copy_function): New.  Use throughout.
  (Avahi, avahi): New class and global variable.
  (GnuTLS._need_version): Changed to be a byte string.
  (main): Decode byte strings loaded from pickle file.
  (main/cleanup): Dump using pickle prototoc 2 which Python 2 can
  read.

Show diffs side-by-side

added added

removed removed

Lines of Context:
34
34
from __future__ import (division, absolute_import, print_function,
35
35
                        unicode_literals)
36
36
 
37
 
from future_builtins import *
 
37
try:
 
38
    from future_builtins import *
 
39
except ImportError:
 
40
    pass
38
41
 
39
42
try:
40
43
    import SocketServer as socketserver
80
83
    from gi.repository import GObject
81
84
except ImportError:
82
85
    import gobject as GObject
83
 
import avahi
84
86
from dbus.mainloop.glib import DBusGMainLoop
85
87
import ctypes
86
88
import ctypes.util
119
121
        return interface_index
120
122
 
121
123
 
 
124
def copy_function(func):
 
125
    """Make a copy of a function"""
 
126
    if sys.version_info.major == 2:
 
127
        return types.FunctionType(func.func_code,
 
128
                                  func.func_globals,
 
129
                                  func.func_name,
 
130
                                  func.func_defaults,
 
131
                                  func.func_closure)
 
132
    else:
 
133
        return types.FunctionType(func.__code__,
 
134
                                  func.__globals__,
 
135
                                  func.__name__,
 
136
                                  func.__defaults__,
 
137
                                  func.__closure__)
 
138
 
 
139
 
122
140
def initlogger(debug, level=logging.WARNING):
123
141
    """init logger and add loglevel"""
124
142
    
155
173
        try:
156
174
            output = subprocess.check_output(["gpgconf"])
157
175
            for line in output.splitlines():
158
 
                name, text, path = line.split(":")
 
176
                name, text, path = line.split(b":")
159
177
                if name == "gpg":
160
178
                    self.gpg = path
161
179
                    break
238
256
            raise PGPError(err)
239
257
        return decrypted_plaintext
240
258
 
 
259
# Pretend that we have an Avahi module
 
260
class Avahi(object):
 
261
    """This isn't so much a class as it is a module-like namespace.
 
262
    It is instantiated once, and simulates having an Avahi module."""
 
263
    IF_UNSPEC = -1              # avahi-common/address.h
 
264
    PROTO_UNSPEC = -1           # avahi-common/address.h
 
265
    PROTO_INET = 0              # avahi-common/address.h
 
266
    PROTO_INET6 = 1             # avahi-common/address.h
 
267
    DBUS_NAME = "org.freedesktop.Avahi"
 
268
    DBUS_INTERFACE_ENTRY_GROUP = DBUS_NAME + ".EntryGroup"
 
269
    DBUS_INTERFACE_SERVER = DBUS_NAME + ".Server"
 
270
    DBUS_PATH_SERVER = "/"
 
271
    def string_array_to_txt_array(self, t):
 
272
        return dbus.Array((dbus.ByteArray(s.encode("utf-8"))
 
273
                           for s in t), signature="ay")
 
274
    ENTRY_GROUP_ESTABLISHED = 2 # avahi-common/defs.h
 
275
    ENTRY_GROUP_COLLISION = 3   # avahi-common/defs.h
 
276
    ENTRY_GROUP_FAILURE = 4     # avahi-common/defs.h
 
277
    SERVER_INVALID = 0          # avahi-common/defs.h
 
278
    SERVER_REGISTERING = 1      # avahi-common/defs.h
 
279
    SERVER_RUNNING = 2          # avahi-common/defs.h
 
280
    SERVER_COLLISION = 3        # avahi-common/defs.h
 
281
    SERVER_FAILURE = 4          # avahi-common/defs.h
 
282
avahi = Avahi()
241
283
 
242
284
class AvahiError(Exception):
243
285
    def __init__(self, value, *args, **kwargs):
447
489
    
448
490
    _library = ctypes.cdll.LoadLibrary(
449
491
        ctypes.util.find_library("gnutls"))
450
 
    _need_version = "3.3.0"
 
492
    _need_version = b"3.3.0"
451
493
    def __init__(self):
452
494
        # Need to use class name "GnuTLS" here, since this method is
453
495
        # called before the assignment to the "gnutls" global variable
794
836
            client["fingerprint"] = (section["fingerprint"].upper()
795
837
                                     .replace(" ", ""))
796
838
            if "secret" in section:
797
 
                client["secret"] = section["secret"].decode("base64")
 
839
                client["secret"] = codecs.decode(section["secret"]
 
840
                                                 .encode("utf-8"),
 
841
                                                 "base64")
798
842
            elif "secfile" in section:
799
843
                with open(os.path.expanduser(os.path.expandvars
800
844
                                             (section["secfile"])),
853
897
        self.changedstate = multiprocessing_manager.Condition(
854
898
            multiprocessing_manager.Lock())
855
899
        self.client_structure = [attr
856
 
                                 for attr in self.__dict__.iterkeys()
 
900
                                 for attr in self.__dict__.keys()
857
901
                                 if not attr.startswith("_")]
858
902
        self.client_structure.append("client_structure")
859
903
        
1507
1551
                interface_names.add(alt_interface)
1508
1552
                # Is this a D-Bus signal?
1509
1553
                if getattr(attribute, "_dbus_is_signal", False):
 
1554
                    # Extract the original non-method undecorated
 
1555
                    # function by black magic
1510
1556
                    if sys.version_info.major == 2:
1511
 
                        # Extract the original non-method undecorated
1512
 
                        # function by black magic
1513
1557
                        nonmethod_func = (dict(
1514
1558
                            zip(attribute.func_code.co_freevars,
1515
1559
                                attribute.__closure__))
1516
1560
                                          ["func"].cell_contents)
1517
1561
                    else:
1518
 
                        nonmethod_func = attribute
 
1562
                        nonmethod_func = (dict(
 
1563
                            zip(attribute.__code__.co_freevars,
 
1564
                                attribute.__closure__))
 
1565
                                          ["func"].cell_contents)
1519
1566
                    # Create a new, but exactly alike, function
1520
1567
                    # object, and decorate it to be a new D-Bus signal
1521
1568
                    # with the alternate D-Bus interface name
1522
 
                    if sys.version_info.major == 2:
1523
 
                        new_function = types.FunctionType(
1524
 
                            nonmethod_func.func_code,
1525
 
                            nonmethod_func.func_globals,
1526
 
                            nonmethod_func.func_name,
1527
 
                            nonmethod_func.func_defaults,
1528
 
                            nonmethod_func.func_closure)
1529
 
                    else:
1530
 
                        new_function = types.FunctionType(
1531
 
                            nonmethod_func.__code__,
1532
 
                            nonmethod_func.__globals__,
1533
 
                            nonmethod_func.__name__,
1534
 
                            nonmethod_func.__defaults__,
1535
 
                            nonmethod_func.__closure__)
 
1569
                    new_function = copy_function(nonmethod_func)
1536
1570
                    new_function = (dbus.service.signal(
1537
1571
                        alt_interface,
1538
1572
                        attribute._dbus_signature)(new_function))
1577
1611
                            alt_interface,
1578
1612
                            attribute._dbus_in_signature,
1579
1613
                            attribute._dbus_out_signature)
1580
 
                        (types.FunctionType(attribute.func_code,
1581
 
                                            attribute.func_globals,
1582
 
                                            attribute.func_name,
1583
 
                                            attribute.func_defaults,
1584
 
                                            attribute.func_closure)))
 
1614
                        (copy_function(attribute)))
1585
1615
                    # Copy annotations, if any
1586
1616
                    try:
1587
1617
                        attr[attrname]._dbus_annotations = dict(
1599
1629
                        attribute._dbus_access,
1600
1630
                        attribute._dbus_get_args_options
1601
1631
                        ["byte_arrays"])
1602
 
                                      (types.FunctionType(
1603
 
                                          attribute.func_code,
1604
 
                                          attribute.func_globals,
1605
 
                                          attribute.func_name,
1606
 
                                          attribute.func_defaults,
1607
 
                                          attribute.func_closure)))
 
1632
                                      (copy_function(attribute)))
1608
1633
                    # Copy annotations, if any
1609
1634
                    try:
1610
1635
                        attr[attrname]._dbus_annotations = dict(
1619
1644
                    # to the class.
1620
1645
                    attr[attrname] = (
1621
1646
                        dbus_interface_annotations(alt_interface)
1622
 
                        (types.FunctionType(attribute.func_code,
1623
 
                                            attribute.func_globals,
1624
 
                                            attribute.func_name,
1625
 
                                            attribute.func_defaults,
1626
 
                                            attribute.func_closure)))
 
1647
                        (copy_function(attribute)))
1627
1648
            if deprecate:
1628
1649
                # Deprecate all alternate interfaces
1629
1650
                iname="_AlternateDBusNames_interface_annotation{}"
1642
1663
            if interface_names:
1643
1664
                # Replace the class with a new subclass of it with
1644
1665
                # methods, signals, etc. as created above.
1645
 
                cls = type(b"{}Alternate".format(cls.__name__),
1646
 
                           (cls, ), attr)
 
1666
                if sys.version_info.major == 2:
 
1667
                    cls = type(b"{}Alternate".format(cls.__name__),
 
1668
                               (cls, ), attr)
 
1669
                else:
 
1670
                    cls = type("{}Alternate".format(cls.__name__),
 
1671
                               (cls, ), attr)
1647
1672
        return cls
1648
1673
    
1649
1674
    return wrapper
2518
2543
            fpr = request[1]
2519
2544
            address = request[2]
2520
2545
            
2521
 
            for c in self.clients.itervalues():
 
2546
            for c in self.clients.values():
2522
2547
                if c.fingerprint == fpr:
2523
2548
                    client = c
2524
2549
                    break
3027
3052
    if server_settings["restore"]:
3028
3053
        try:
3029
3054
            with open(stored_state_path, "rb") as stored_state:
3030
 
                clients_data, old_client_settings = pickle.load(
3031
 
                    stored_state)
 
3055
                if sys.version_info.major == 2:                
 
3056
                    clients_data, old_client_settings = pickle.load(
 
3057
                        stored_state)
 
3058
                else:
 
3059
                    bytes_clients_data, bytes_old_client_settings = (
 
3060
                        pickle.load(stored_state, encoding = "bytes"))
 
3061
                    ### Fix bytes to strings
 
3062
                    ## clients_data
 
3063
                    # .keys()
 
3064
                    clients_data = { (key.decode("utf-8")
 
3065
                                      if isinstance(key, bytes)
 
3066
                                      else key): value
 
3067
                                     for key, value in
 
3068
                                     bytes_clients_data.items() }
 
3069
                    for key in clients_data:
 
3070
                        value = { (k.decode("utf-8")
 
3071
                                   if isinstance(k, bytes) else k): v
 
3072
                                  for k, v in
 
3073
                                  clients_data[key].items() }
 
3074
                        clients_data[key] = value
 
3075
                        # .client_structure
 
3076
                        value["client_structure"] = [
 
3077
                            (s.decode("utf-8")
 
3078
                             if isinstance(s, bytes)
 
3079
                             else s) for s in
 
3080
                            value["client_structure"] ]
 
3081
                        # .name & .host
 
3082
                        for k in ("name", "host"):
 
3083
                            if isinstance(value[k], bytes):
 
3084
                                value[k] = value[k].decode("utf-8")
 
3085
                    ## old_client_settings
 
3086
                    # .keys
 
3087
                    old_client_settings = {
 
3088
                        (key.decode("utf-8")
 
3089
                         if isinstance(key, bytes)
 
3090
                         else key): value
 
3091
                        for key, value in
 
3092
                        bytes_old_client_settings.items() }
 
3093
                    # .host
 
3094
                    for value in old_client_settings.values():
 
3095
                        value["host"] = value["host"].decode("utf-8")
3032
3096
            os.remove(stored_state_path)
3033
3097
        except IOError as e:
3034
3098
            if e.errno == errno.ENOENT:
3173
3237
            def GetAllClients(self):
3174
3238
                "D-Bus method"
3175
3239
                return dbus.Array(c.dbus_object_path for c in
3176
 
                                  tcp_server.clients.itervalues())
 
3240
                                  tcp_server.clients.values())
3177
3241
            
3178
3242
            @dbus_annotations({"org.freedesktop.DBus.Deprecated":
3179
3243
                               "true"})
3184
3248
                return dbus.Dictionary(
3185
3249
                    { c.dbus_object_path: c.GetAll(
3186
3250
                        "se.recompile.Mandos.Client")
3187
 
                      for c in tcp_server.clients.itervalues() },
 
3251
                      for c in tcp_server.clients.values() },
3188
3252
                    signature="oa{sv}")
3189
3253
            
3190
3254
            @dbus.service.method(_interface, in_signature="o")
3191
3255
            def RemoveClient(self, object_path):
3192
3256
                "D-Bus method"
3193
 
                for c in tcp_server.clients.itervalues():
 
3257
                for c in tcp_server.clients.values():
3194
3258
                    if c.dbus_object_path == object_path:
3195
3259
                        del tcp_server.clients[c.name]
3196
3260
                        c.remove_from_connection()
3256
3320
        # removed/edited, old secret will thus be unrecovable.
3257
3321
        clients = {}
3258
3322
        with PGPEngine() as pgp:
3259
 
            for client in tcp_server.clients.itervalues():
 
3323
            for client in tcp_server.clients.values():
3260
3324
                key = client_settings[client.name]["secret"]
3261
3325
                client.encrypted_secret = pgp.encrypt(client.secret,
3262
3326
                                                      key)
3286
3350
                    prefix='clients-',
3287
3351
                    dir=os.path.dirname(stored_state_path),
3288
3352
                    delete=False) as stored_state:
3289
 
                pickle.dump((clients, client_settings), stored_state)
 
3353
                pickle.dump((clients, client_settings), stored_state,
 
3354
                            protocol = 2)
3290
3355
                tempname = stored_state.name
3291
3356
            os.rename(tempname, stored_state_path)
3292
3357
        except (IOError, OSError) as e:
3317
3382
    
3318
3383
    atexit.register(cleanup)
3319
3384
    
3320
 
    for client in tcp_server.clients.itervalues():
 
3385
    for client in tcp_server.clients.values():
3321
3386
        if use_dbus:
3322
3387
            # Emit D-Bus signal for adding
3323
3388
            mandos_dbus_service.client_added_signal(client)