diff --git a/pykolab/cli/cmd_user_info.py b/pykolab/cli/cmd_user_info.py
index e8a5766..beaeb40 100644
--- a/pykolab/cli/cmd_user_info.py
+++ b/pykolab/cli/cmd_user_info.py
@@ -1,62 +1,62 @@
 # -*- coding: utf-8 -*-
 # Copyright 2010-2012 Kolab Systems AG (http://www.kolabsys.com)
 #
 # Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen a kolabsys.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
 # (at your option) any later version.
 
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU General Public License for more details.
 
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 
 from __future__ import print_function
 
 import sys
 
 import commands
 
 import pykolab
 
 from pykolab import utils
 from pykolab.translate import _
 
 log = pykolab.getLogger('pykolab.cli')
 conf = pykolab.getConf()
 
 def __init__():
     commands.register('user_info', execute, description="Display user information.")
 
 def execute(*args, **kw):
     from pykolab import wap_client
 
     try:
         user = conf.cli_args.pop(0)
     except IndexError:
         user = utils.ask_question(_("Email address"))
 
     result = wap_client.authenticate(username=conf.get("ldap", "bind_dn"), password=conf.get("ldap", "bind_pw"))
 
     if len(user.split('@')) > 1:
         wap_client.system_select_domain(user.split('@')[1])
 
     user_info = wap_client.user_find({'mail':user})
 
     if user_info == None or not user_info:
         print(_("No such user %s") % (user), file=sys.stderr)
         sys.exit(0)
 
     unic_attrs = ['displayname', 'givenname', 'cn', 'sn', 'ou', 'entrydn']
 
-    for (k,v) in user_info.iteritems():
+    for (k,v) in user_info.items():
         if k in unic_attrs:
             print("%s: %s" % (k,v))
         else:
             print("%s: %r" % (k,v))
diff --git a/pykolab/wap_client/__init__.py b/pykolab/wap_client/__init__.py
index bfb2e2c..f3cf1d7 100644
--- a/pykolab/wap_client/__init__.py
+++ b/pykolab/wap_client/__init__.py
@@ -1,661 +1,661 @@
 
 import json
 import httplib
 import urllib
 import sys
 from urlparse import urlparse
 
 import pykolab
 
 from pykolab import utils
 from pykolab.translate import _
 
 log = pykolab.getLogger('pykolab.wap_client')
 conf = pykolab.getConf()
 
 if not hasattr(conf, 'defaults'):
     conf.finalize_conf()
 
 API_HOSTNAME = "localhost"
 API_SCHEME = "http"
 API_PORT = 80
 API_SSL = False
 API_BASE = "/kolab-webadmin/api/"
 
 kolab_wap_url = conf.get('kolab_wap', 'api_url')
 
 if not kolab_wap_url == None:
     result = urlparse(kolab_wap_url)
 else:
     result = None
 
 if hasattr(result, 'scheme') and result.scheme == 'https':
     API_SSL = True
     API_PORT = 443
 
 if hasattr(result, 'hostname'):
     API_HOSTNAME = result.hostname
 
 if hasattr(result, 'port'):
     API_PORT = result.port
 
 if hasattr(result, 'path'):
     API_BASE = result.path
 
 session_id = None
 
 conn = None
 
 def authenticate(username=None, password=None, domain=None):
     global session_id
 
     if username == None:
         username = conf.get('ldap', 'bind_dn')
     if password == None:
         password = conf.get('ldap', 'bind_pw')
 
     if domain == None:
         domain = conf.get('kolab', 'primary_domain')
 
     post = json.dumps(
             {
                     'username': username,
                     'password': password,
                     'domain': domain
                 }
         )
 
     response = request('POST', "system.authenticate", post=post)
 
     if not response:
         return False
 
     if 'session_token' in response:
         session_id = response['session_token']
         return True
 
 def connect(uri=None):
     global conn, API_SSL, API_PORT, API_HOSTNAME, API_BASE
 
     if not uri == None:
         result = urlparse(uri)
 
         if hasattr(result, 'scheme') and result.scheme == 'https':
             API_SSL = True
             API_PORT = 443
 
         if hasattr(result, 'hostname'):
             API_HOSTNAME = result.hostname
 
         if hasattr(result, 'port'):
             API_PORT = result.port
 
         if hasattr(result, 'path'):
             API_BASE = result.path
 
     if conn == None:
         if API_SSL:
             conn = httplib.HTTPSConnection(API_HOSTNAME, API_PORT)
         else:
             conn = httplib.HTTPConnection(API_HOSTNAME, API_PORT)
 
         conn.connect()
 
     return conn
 
 def disconnect(quit=False):
     global conn, session_id
 
     if quit and session_id:
         request('GET', 'system.quit')
         session_id = None
 
     if conn:
         conn.close()
         conn = None
 
 def domain_add(domain, aliases=[]):
     dna = conf.get('ldap', 'domain_name_attribute')
 
     post = json.dumps({
             dna: [ domain ] + aliases
         })
 
     return request('POST', 'domain.add', post=post)
 
 def domain_delete(domain, force=False):
     domain_id, domain_attrs = domain_find(domain).popitem()
 
     param = {}
     param['id'] = domain_id
 
     if force:
         param['force'] = force
     post = json.dumps(param)
 
     return request('POST', 'domain.delete', post=post)
 
 def domain_find(domain):
     dna = conf.get('ldap', 'domain_name_attribute')
 
     get = { dna: domain }
 
     return request('GET', 'domain.find', get=get)
 
 def domain_info(domain):
     domain_id, domain_attrs = domain_find(domain)
 
     get = { 'id': domain_id }
 
     return request('GET', 'domain.info', get=get)
 
 def domains_capabilities():
     return request('GET', 'domains.capabilities')
 
 def domains_list():
     return request('GET', 'domains.list')
 
 def form_value_generate(params):
     post = json.dumps(params)
 
     return request('POST', 'form_value.generate', post=post)
 
 def form_value_generate_password(*args, **kw):
     return request('GET', 'form_value.generate_password')
 
 def form_value_list_options(object_type, object_type_id, attribute):
     post = json.dumps(
             {
                     'object_type': object_type,
                     'type_id': object_type_id,
                     'attribute': attribute
                 }
         )
 
     return request('POST', 'form_value.list_options', post=post)
 
 def form_value_select_options(object_type, object_type_id, attribute):
     post = json.dumps(
             {
                     'object_type': object_type,
                     'type_id': object_type_id,
                     'attributes': [ attribute ]
                 }
         )
 
     return request('POST', 'form_value.select_options', post=post)
 
 def get_group_input():
     group_types = group_types_list()
 
     if len(group_types) > 1:
         for key in group_types:
             if not key == "status":
                 print("%s) %s" % (key,group_types[key]['name']))
 
         group_type_id = utils.ask_question("Please select the group type")
 
     elif len(group_types) > 0:
         print("Automatically selected the only group type available")
         group_type_id = group_types.keys()[0]
 
     else:
         print("No group types available")
         sys.exit(1)
 
     if group_type_id in group_types:
         group_type_info = group_types[group_type_id]['attributes']
     else:
         print("No such group type")
         sys.exit(1)
 
     params = {
             'group_type_id': group_type_id
         }
 
     for attribute in group_type_info['form_fields']:
         params[attribute] = utils.ask_question(attribute)
 
     for attribute in group_type_info['auto_form_fields']:
         exec("retval = group_form_value_generate_%s(params)" % (attribute))
         params[attribute] = retval[attribute]
 
     return params
 
 def get_user_input():
     user_types = user_types_list()
 
     if user_types['count'] > 1:
         print("")
         for key in user_types['list']:
             if not key == "status":
                 print("%s) %s" % (key,user_types['list'][key]['name']))
 
         print("")
         user_type_id = utils.ask_question("Please select the user type")
 
     elif user_types['count'] > 0:
         print("Automatically selected the only user type available")
         user_type_id = user_types['list'].keys()[0]
 
     else:
         print("No user types available")
         sys.exit(1)
 
     if user_type_id in user_types['list']:
         user_type_info = user_types['list'][user_type_id]['attributes']
     else:
         print("No such user type")
         sys.exit(1)
 
     params = {
             'object_type': 'user',
             'type_id': user_type_id
         }
 
     must_attrs = []
     may_attrs = []
 
     for attribute in user_type_info['form_fields']:
         if isinstance(user_type_info['form_fields'][attribute], dict):
             if 'optional' in user_type_info['form_fields'][attribute] and user_type_info['form_fields'][attribute]['optional']:
                 may_attrs.append(attribute)
             else:
                 must_attrs.append(attribute)
         else:
             must_attrs.append(attribute)
 
     for attribute in must_attrs:
         if isinstance(user_type_info['form_fields'][attribute], dict) and \
                 'type' in user_type_info['form_fields'][attribute]:
 
             if user_type_info['form_fields'][attribute]['type'] == 'select':
                 if 'values' not in user_type_info['form_fields'][attribute]:
                     attribute_values = form_value_select_options('user', user_type_id, attribute)
 
                     default = ''
                     if 'default' in attribute_values[attribute]:
                         default = attribute_values[attribute]['default']
 
                     params[attribute] = utils.ask_menu(
                             "Choose the %s value" % (attribute),
                             attribute_values[attribute]['list'],
                             default=default
                         )
 
                 else:
                     default = ''
                     if 'default' in user_type_info['form_fields'][attribute]:
                         default = user_type_info['form_fields'][attribute]['default']
 
                     params[attribute] = utils.ask_menu(
                             "Choose the %s value" % (attribute),
                             user_type_info['form_fields'][attribute]['values'],
                             default=default
                         )
 
             else:
                 params[attribute] = utils.ask_question(attribute)
 
         else:
             params[attribute] = utils.ask_question(attribute)
 
     for attribute in user_type_info['fields']:
         params[attribute] = user_type_info['fields'][attribute]
 
     exec("retval = user_form_value_generate(params)")
     print(retval)
 
     return params
 
 def group_add(params=None):
     if params == None:
         params = get_group_input()
 
     post = json.dumps(params)
 
     return request('POST', 'group.add', post=post)
 
 def group_delete(params=None):
     if params == None:
         params = {
                 'id': utils.ask_question("Name of group to delete", "group")
             }
 
     post = json.dumps(params)
 
     return request('POST', 'group.delete', post=post)
 
 def group_form_value_generate_mail(params=None):
     if params == None:
         params = get_user_input()
 
     params = json.dumps(params)
 
     return request('POST', 'group_form_value.generate_mail', params)
 
 def group_find(params=None):
     post = { 'search': { 'params': {} } }
 
-    for (k,v) in params.iteritems():
+    for (k,v) in params.items():
         post['search']['params'][k] = { 'value': v, 'type': 'exact' }
 
     return request('POST', 'group.find', post=json.dumps(post))
 
 def group_info(group=None):
     if group == None:
         group = utils.ask_question("group DN")
     return request('GET', 'group.info', get={ 'id': group })
 
 def group_members_list(group=None):
     if group == None:
         group = utils.ask_question("Group email address")
     group = request('GET', 'group.members_list?group=%s' % (group))
     return group
 
 def group_types_list():
     return request('GET', 'group_types.list')
 
 def groups_list(params={}):
     return request('POST', 'groups.list', post=json.dumps(params))
 
 def ou_add(params={}):
     return request('POST', 'ou.add', post=json.dumps(params))
 
 def ou_delete(params={}):
     return request('POST', 'ou.delete', post=json.dumps(params))
 
 def ou_edit(params={}):
     return request('POST', 'ou.edit', post=json.dumps(params))
 
 def ou_find(params=None):
     post = { 'search': { 'params': {} } }
 
-    for (k,v) in params.iteritems():
+    for (k,v) in params.items():
         post['search']['params'][k] = { 'value': v, 'type': 'exact' }
 
     return request('POST', 'ou.find', post=json.dumps(post))
 
 def ou_info(ou):
     _params = { 'id': ou }
 
     ou = request('GET', 'ou.info', get=_params)
 
     return ou
 
 def ous_list(params={}):
     return request('POST', 'ous.list', post=json.dumps(params))
 
 def request(method, api_uri, get=None, post=None, headers={}):
     response_data = request_raw(method, api_uri, get, post, headers)
 
     if response_data['status'] == "OK":
         del response_data['status']
         return response_data['result']
     else:
         print("%s: %s (code %s)" % (response_data['status'], response_data['reason'], response_data['code']))
         return False
 
 def request_raw(method, api_uri, get=None, post=None, headers={}, isretry=False):
     global session_id
 
     if not session_id == None:
         headers["X-Session-Token"] = session_id
 
     reconnect = False
     conn = connect()
 
     if conf.debuglevel > 8:
         conn.set_debuglevel(9)
 
     if not get == None:
         _get = "?%s" % (urllib.urlencode(get))
     else:
         _get = ""
 
     log.debug(_("Requesting %r with params %r") % ("%s/%s" % (API_BASE,api_uri), (get, post)), level=8)
 
     try:
         conn.request(method.upper(), "%s/%s%s" % (API_BASE, api_uri, _get), post, headers)
 
         response = conn.getresponse()
         data = response.read()
 
         log.debug(_("Got response: %r") % (data), level=8)
 
     except (httplib.BadStatusLine, httplib.CannotSendRequest) as e:
         if isretry:
             raise e
         log.info(_("Connection error: %r; re-connecting..."), e)
         reconnect = True
 
     # retry with a new connection
     if reconnect:
         disconnect()
         return request_raw(method, api_uri, get, post, headers, True)
 
     try:
         response_data = json.loads(data)
     except ValueError:
         # Some data is not JSON
         log.error(_("Response data is not JSON"))
 
     return response_data
 
 def resource_add(params=None):
     if params == None:
         params = get_user_input()
 
     return request('POST', 'resource.add', post=json.dumps(params))
 
 def resource_delete(params=None):
     if params == None:
         params = {
             'id': utils.ask_question("Resource DN to delete", "resource")
         }
 
     return request('POST', 'resource.delete', post=json.dumps(params))
 
 def resource_find(params=None):
     post = { 'search': { 'params': {} } }
 
-    for (k,v) in params.iteritems():
+    for (k,v) in params.items():
         post['search']['params'][k] = { 'value': v, 'type': 'exact' }
 
     return request('POST', 'resource.find', post=json.dumps(post))
 
 def resource_info(resource=None):
     if resource == None:
         resource = utils.ask_question("Resource DN")
     return request('GET', 'resource.info', get={ 'id': resource })
 
 def resource_types_list():
     return request('GET', 'resource_types.list')
 
 def resources_list(params={}):
     return request('POST', 'resources.list', post=json.dumps(params))
 
 def role_add(params=None):
     if params == None:
         role_name = utils.ask_question("Role name")
         params = {
                 'cn': role_name
             }
 
     params = json.dumps(params)
 
     return request('POST', 'role.add', params)
 
 def role_capabilities():
     return request('GET', 'role.capabilities')
 
 def role_delete(params=None):
     if params == None:
         role_name = utils.ask_question("Role name")
         role = role_find_by_attribute({'cn': role_name})
         params = {
                 'role': role.keys()[0]
             }
 
     if 'role' not in params:
         role = role_find_by_attribute(params)
         params = {
                 'role': role.keys()[0]
             }
 
     post = json.dumps(params)
 
     return request('POST', 'role.delete', post=post)
 
 def role_find_by_attribute(params=None):
     if params == None:
         role_name = utils.ask_question("Role name")
     else:
         role_name = params['cn']
 
     get = { 'cn': role_name }
     role = request('GET', 'role.find_by_attribute', get=get)
 
     return role
 
 def role_info(role_name):
     role = role_find_by_attribute({'cn': role_name})
 
     get = { 'role': role['id'] }
 
     role = request('GET', 'role.info', get=get)
 
     return role
 
 def roles_list():
     return request('GET', 'roles.list')
 
 def sharedfolder_add(params=None):
     if params == None:
         params = get_user_input()
 
     return request('POST', 'sharedfolder.add', post=json.dumps(params))
 
 def sharedfolder_delete(params=None):
     if params == None:
         params = {
             'id': utils.ask_question("Shared Folder DN to delete", "sharedfolder")
         }
 
     return request('POST', 'sharedfolder.delete', post=json.dumps(params))
 
 def sharedfolders_list(params={}):
     return request('POST', 'sharedfolders.list', post=json.dumps(params))
 
 def system_capabilities(domain=None):
     return request('GET', 'system.capabilities', get={'domain':domain})
 
 def system_get_domain():
     return request('GET', 'system.get_domain')
 
 def system_select_domain(domain=None):
     if domain == None:
         domain = utils.ask_question("Domain name")
 
     get = { 'domain': domain }
 
     return request('GET', 'system.select_domain', get=get)
 
 def user_add(params=None):
     if params == None:
         params = get_user_input()
 
     params = json.dumps(params)
 
     return request('POST', 'user.add', post=params)
 
 def user_delete(params=None):
     if params == None:
         params = {
                 'id': utils.ask_question("Username for user to delete", "user")
             }
 
     post = json.dumps(params)
 
     return request('POST', 'user.delete', post=post)
 
 def user_edit(user = None, attributes={}):
     if user == None:
         get = {
                 'id': utils.ask_question("Username for user to edit", "user")
             }
     else:
         get = {
                 'id': user
             }
 
     user_info = request('GET', 'user.info', get=get)
 
     for attribute in attributes:
         user_info[attribute] = attributes[attribute]
 
     post = json.dumps(user_info)
 
     user_edit = request('POST', 'user.edit', get=get, post=post)
 
     return user_edit
 
 def user_find(attribs=None):
     if attribs == None:
         post = {
                 'search': {
                         'params': {
                                 utils.ask_question("Attribute") : {
                                         'value': utils.ask_question("value"),
                                         'type': 'exact'
                                     }
                             }
                     }
             }
     else:
         post = { 'search': { 'params': {} } }
 
-        for (k,v) in attribs.iteritems():
+        for (k,v) in attribs.items():
             post['search']['params'][k] = { 'value': v, 'type': 'exact' }
 
     post = json.dumps(post)
 
     user = request('POST', 'user.find', post=post)
 
     return user
 
 def user_form_value_generate(params=None):
     if params == None:
         params = get_user_input()
 
     post = json.dumps(params)
 
     return request('POST', 'form_value.generate', post=post)
 
 def user_form_value_generate_uid(params=None):
     if params == None:
         params = get_user_input()
 
     params = json.dumps(params)
 
     return request('POST', 'form_value.generate_uid', params)
 
 def user_form_value_generate_userpassword(*args, **kw):
     result = form_value_generate_password()
     return { 'userpassword': result['password'] }
 
 def user_info(user=None):
     if user == None:
         user = utils.ask_question("User email address")
 
     _params = { 'id': user }
 
     user = request('GET', 'user.info', get=_params)
 
     return user
 
 def users_list(params={}):
     return request('POST', 'users.list', post=json.dumps(params))
 
 def user_types_list():
     return request('GET', 'user_types.list')
diff --git a/pykolab/xml/attendee.py b/pykolab/xml/attendee.py
index f82f96e..050a1d5 100644
--- a/pykolab/xml/attendee.py
+++ b/pykolab/xml/attendee.py
@@ -1,280 +1,280 @@
 import kolabformat
 
 from pykolab.translate import _
 from pykolab.translate import N_
 
 from contact_reference import ContactReference
 
 participant_status_labels = {
         "NEEDS-ACTION": N_("Needs Action"),
         "ACCEPTED": N_("Accepted"),
         "DECLINED": N_("Declined"),
         "TENTATIVE": N_("Tentatively Accepted"),
         "DELEGATED": N_("Delegated"),
         "IN-PROCESS": N_("Started"),
         "COMPLETED": N_("Completed"),
         "PENDING": N_("Pending"),
         # support integer values, too
         kolabformat.PartNeedsAction: N_("Needs Action"),
         kolabformat.PartAccepted: N_("Accepted"),
         kolabformat.PartDeclined: N_("Declined"),
         kolabformat.PartTentative: N_("Tentatively Accepted"),
         kolabformat.PartDelegated: N_("Delegated"),
         kolabformat.PartInProcess: N_("Started"),
         kolabformat.PartCompleted: N_("Completed"),
     }
 
 def participant_status_label(status):
     return _(participant_status_labels[status]) if status in participant_status_labels else _(status)
 
 
 class Attendee(kolabformat.Attendee):
     cutype_map = {
             "INDIVIDUAL": kolabformat.CutypeIndividual,
             "RESOURCE": kolabformat.CutypeResource,
             "GROUP": kolabformat.CutypeGroup,
             "ROOM": kolabformat.CutypeRoom,
             "UNKNOWN": kolabformat.CutypeUnknown,
         }
 
     participant_status_map = {
             "NEEDS-ACTION": kolabformat.PartNeedsAction,
             "ACCEPTED": kolabformat.PartAccepted,
             "DECLINED": kolabformat.PartDeclined,
             "TENTATIVE": kolabformat.PartTentative,
             "DELEGATED": kolabformat.PartDelegated,
             "IN-PROCESS": kolabformat.PartInProcess,
             "COMPLETED": kolabformat.PartCompleted,
         }
 
     # See RFC 2445, 5445
     role_map = {
             "CHAIR": kolabformat.Chair,
             "REQ-PARTICIPANT": kolabformat.Required,
             "OPT-PARTICIPANT": kolabformat.Optional,
             "NON-PARTICIPANT": kolabformat.NonParticipant,
         }
 
     rsvp_map = {
             "TRUE": True,
             "FALSE": False,
         }
 
     properties_map = {
             'role': 'get_role',
             'rsvp':  'rsvp',
             'partstat':  'get_participant_status',
             'cutype':   'get_cutype',
             'delegated-to': 'get_delegated_to',
             'delegated-from': 'get_delegated_from',
         }
 
     def __init__(
             self,
             email,
             name=None,
             rsvp=False,
             role=None,
             participant_status=None,
             cutype=None,
             ical_params=None
         ):
 
         self.email = email
 
         self.contactreference = ContactReference(email)
 
         if not name == None:
             self.contactreference.set_name(name)
 
         kolabformat.Attendee.__init__(self, self.contactreference)
 
         if isinstance(rsvp, bool):
             self.setRSVP(rsvp)
         else:
             if rsvp in self.rsvp_map:
                 self.setRSVP(self.rsvp_map[rsvp])
 
         if not role == None:
             self.set_role(role)
 
         if not cutype == None:
             self.set_cutype(cutype)
 
         if ical_params and 'DELEGATED-FROM' in ical_params:
             self.delegate_from(Attendee(str(ical_params['DELEGATED-FROM']), role=self.get_role(), cutype=self.get_cutype()))
 
         if ical_params and 'DELEGATED-TO' in ical_params:
             self.delegate_to(Attendee(str(ical_params['DELEGATED-TO'])))
 
         if not participant_status == None:
             self.set_participant_status(participant_status)
 
     def copy_from(self, obj):
         if isinstance(obj, kolabformat.Attendee):
             self.contactreference = ContactReference(obj.contact())
             self.email = self.contactreference.get_email()
             self.setContact(self.contactreference)
 
             # manually copy all properities, copy constructor doesn't work :-(
             self.setRSVP(obj.rsvp())
             self.setRole(obj.role())
             self.setCutype(obj.cutype())
             self.setPartStat(obj.partStat())
             self.setDelegatedTo(obj.delegatedTo())
             self.setDelegatedFrom(obj.delegatedFrom())
 
     def delegate_from(self, delegators):
         crefs = []
 
         if not isinstance(delegators, list):
             delegators = [delegators]
 
         for delegator in delegators:
             if not isinstance(delegator, Attendee):
                 raise ValueError(_("Not a valid attendee"))
             else:
                 self.set_role(delegator.get_role())
                 self.set_cutype(delegator.get_cutype())
                 crefs.append(delegator.contactreference)
 
         if len(crefs) == 0:
             raise ValueError(_("No valid delegator references found"))
         else:
             crefs += self.get_delegated_from()
 
         self.setDelegatedFrom(list(set(crefs)))
 
     def delegate_to(self, delegatees):
         self.set_participant_status("DELEGATED")
 
         crefs = []
         if not isinstance(delegatees, list):
             delegatees = [delegatees]
 
         for delegatee in delegatees:
             if not isinstance(delegatee, Attendee):
                 raise ValueError(_("Not a valid attendee"))
             else:
                 crefs.append(delegatee.contactreference)
 
         if len(crefs) == 0:
             raise ValueError(_("No valid delegatee references found"))
         else:
             crefs += self.get_delegated_to()
 
         self.setDelegatedTo(list(set(crefs)))
 
     def get_cutype(self, translated=False):
         cutype = self.cutype()
         if translated:
             return self._translate_value(cutype, self.cutype_map)
         return cutype
 
     def get_delegated_from(self, translated=False):
         delegators = []
         for cr in self.delegatedFrom():
             delegators.append(cr.email() if translated else ContactReference(cr))
         return delegators
 
     def get_delegated_to(self, translated=False):
         delegatees = []
         for cr in self.delegatedTo():
             delegatees.append(cr.email() if translated else ContactReference(cr))
         return delegatees
 
     def get_email(self):
         return self.contactreference.get_email()
 
     def get_name(self):
         return self.contactreference.get_name()
 
     def get_displayname(self):
         name = self.contactreference.get_name()
         email = self.contactreference.get_email()
         return "%s <%s>" % (name, email) if not name == "" else email
 
     def get_participant_status(self, translated=False):
         partstat = self.partStat()
         if translated:
             return self._translate_value(partstat, self.participant_status_map)
         return partstat
 
     def get_role(self, translated=False):
         role = self.role()
         if translated:
             return self._translate_value(role, self.role_map)
         return role
 
     def get_rsvp(self):
         return self.rsvp()
 
     def _translate_value(self, val, map):
-        name_map = dict([(v, k) for (k, v) in map.iteritems()])
+        name_map = dict([(v, k) for (k, v) in map.items()])
         return name_map[val] if val in name_map else 'UNKNOWN'
 
     def set_cutype(self, cutype):
         if cutype in self.cutype_map:
             self.setCutype(self.cutype_map[cutype])
         elif cutype in self.cutype_map.values():
             self.setCutype(cutype)
         else:
             raise InvalidAttendeeCutypeError(_("Invalid cutype %r") % (cutype))
 
     def set_name(self, name):
         self.contactreference.set_name(name)
         self.setContact(self.contactreference)
 
     def set_participant_status(self, participant_status):
         if participant_status in self.participant_status_map:
             self.setPartStat(self.participant_status_map[participant_status])
         elif participant_status in self.participant_status_map.values():
             self.setPartStat(participant_status)
         else:
             raise InvalidAttendeeParticipantStatusError(_("Invalid participant status %r") % (participant_status))
 
     def set_role(self, role):
         if role in self.role_map:
             self.setRole(self.role_map[role])
         elif role in self.role_map.values():
             self.setRole(role)
         else:
             raise InvalidAttendeeRoleError(_("Invalid role %r") % (role))
 
     def set_rsvp(self, rsvp):
         self.setRSVP(rsvp)
 
     def to_dict(self):
         data = self.contactreference.to_dict()
         data.pop('type', None)
 
-        for p, getter in self.properties_map.iteritems():
+        for p, getter in self.properties_map.items():
             val = None
             args = {}
             if hasattr(self, getter):
                 if getter.startswith('get_'):
                     args = dict(translated=True)
                 val = getattr(self, getter)(**args)
             if val is not None:
                 data[p] = val
 
         return data
 
     def __str__(self):
         return self.email
 
 class AttendeeIntegrityError(Exception):
     def __init__(self, message):
         Exception.__init__(self, message)
 
 class InvalidAttendeeCutypeError(Exception):
     def __init__(self, message):
         Exception.__init__(self, message)
 
 class InvalidAttendeeParticipantStatusError(Exception):
     def __init__(self, message):
         Exception.__init__(self, message)
 
 class InvalidAttendeeRoleError(Exception):
     def __init__(self, message):
         Exception.__init__(self, message)
diff --git a/pykolab/xml/contact.py b/pykolab/xml/contact.py
index f5053e6..7b0a2a7 100644
--- a/pykolab/xml/contact.py
+++ b/pykolab/xml/contact.py
@@ -1,336 +1,336 @@
 import kolabformat
 import datetime
 import pytz
 import base64
 
 from pykolab.xml import utils as xmlutils
 from pykolab.xml.utils import ustr
 
 def contact_from_vcard(string):
     # TODO: implement this
     pass
 
 def contact_from_string(string):
     _xml = kolabformat.readContact(string, False)
     return Contact(_xml)
 
 def contact_from_message(message):
     contact = None
     if message.is_multipart():
         for part in message.walk():
             if part.get_content_type() == "application/vcard+xml":
                 payload = part.get_payload(decode=True)
                 contact = contact_from_string(payload)
 
             # append attachment parts to Contact object
             elif contact and 'Content-ID' in part:
                 contact._attachment_parts.append(part)
 
     return contact
 
 
 class Contact(kolabformat.Contact):
     type = 'contact'
 
     related_map = {
         'manager': kolabformat.Related.Manager,
         'assistant': kolabformat.Related.Assistant,
         'spouse': kolabformat.Related.Spouse,
         'children': kolabformat.Related.Child,
         None: kolabformat.Related.NoRelation,
     }
 
     addresstype_map = {
         'home': kolabformat.Address.Home,
         'work': kolabformat.Address.Work,
     }
 
     phonetype_map = {
         'home':    kolabformat.Telephone.Home,
         'work':    kolabformat.Telephone.Work,
         'text':    kolabformat.Telephone.Text,
         'main':    kolabformat.Telephone.Voice,
         'homefax': kolabformat.Telephone.Fax,
         'workfax': kolabformat.Telephone.Fax,
         'mobile':  kolabformat.Telephone.Cell,
         'video':   kolabformat.Telephone.Video,
         'pager':   kolabformat.Telephone.Pager,
         'car':     kolabformat.Telephone.Car,
         'other':   kolabformat.Telephone.Textphone,
     }
 
     emailtype_map = {
         'home': kolabformat.Email.Home,
         'work': kolabformat.Email.Work,
         'other': kolabformat.Email.Work,
     }
 
     urltype_map = {
         'homepage': kolabformat.Url.NoType,
         'blog': kolabformat.Url.Blog,
     }
 
     keytype_map = {
         'pgp': kolabformat.Key.PGP,
         'pkcs7': kolabformat.Key.PKCS7_MIME,
         None: kolabformat.Key.Invalid,
     }
 
     gender_map = {
         'female': kolabformat.Contact.Female,
         'male': kolabformat.Contact.Male,
         None: kolabformat.Contact.NotSet,
     }
 
     properties_map = {
         'uid':               'get_uid',
         'lastmodified-date': 'get_lastmodified',
         'fn':                'name',
         'nickname':          'nickNames',
         'title':             'titles',
         'email':             'emailAddresses',
         'tel':               'telephones',
         'url':               'urls',
         'im':                'imAddresses',
         'address':           'addresses',
         'note':              'note',
         'freebusyurl':       'freeBusyUrl',
         'birthday':          'bDay',
         'anniversary':       'anniversary',
         'categories':        'categories',
         'lang':              'languages',
         'gender':            'get_gender',
         'gpspos':            'gpsPos',
         'key':               'keys',
     }
 
     def __init__(self, *args, **kw):
         self._attachment_parts = []
         kolabformat.Contact.__init__(self, *args, **kw)
 
     def get_uid(self):
         uid = self.uid()
         if not uid == '':
             return uid
         else:
             self.__str__()
             return kolabformat.getSerializedUID()
 
     def get_lastmodified(self):
         try:
             _datetime = self.lastModified()
             if _datetime == None or not _datetime.isValid():
                 self.__str__()
         except:
             return datetime.datetime.now(pytz.utc)
 
         return xmlutils.from_cdatetime(self.lastModified(), True)
 
     def get_email(self, preferred=True):
         if preferred:
             return self.emailAddresses()[self.emailAddressPreferredIndex()]
         else:
             return [x for x in self.emailAddresses()]
 
     def set_email(self, email, preferred_index=0):
         if isinstance(email, basestring):
             self.setEmailAddresses([email], preferred_index)
         else:
             self.setEmailAddresses(email, preferred_index)
 
     def add_email(self, email):
         if isinstance(email, basestring):
             self.add_emails([email])
         elif isinstance(email, list):
             self.add_emails(email)
 
     def add_emails(self, emails):
         preferred_email = self.get_email()
         emails = [x for x in set(self.get_email(preferred=False) + emails)]
         preferred_email_index = emails.index(preferred_email)
         self.setEmailAddresses(emails, preferred_email_index)
 
     def set_name(self, name):
         self.setName(ustr(name))
 
     def get_gender(self, translated=True):
         _gender = self.gender()
         if translated:
             return self._translate_value(_gender, self.gender_map)
         return _gender
 
     def _translate_value(self, val, map):
-        name_map = dict([(v, k) for (k, v) in map.iteritems()])
+        name_map = dict([(v, k) for (k, v) in map.items()])
         return name_map[val] if val in name_map else 'UNKNOWN'
 
     def to_dict(self):
         if not self.isValid():
             return None
 
         data = self._names2dict(self.nameComponents())
 
-        for p, getter in self.properties_map.iteritems():
+        for p, getter in self.properties_map.items():
             val = None
             if hasattr(self, getter):
                 val = getattr(self, getter)()
             if isinstance(val, kolabformat.cDateTime):
                 val = xmlutils.from_cdatetime(val, True)
             elif isinstance(val, kolabformat.vectori):
                 val = [int(x) for x in val]
             elif isinstance(val, kolabformat.vectors):
                 val = [str(x) for x in val]
             elif isinstance(val, kolabformat.vectortelephone):
                 val = [self._struct2dict(x, 'number', self.phonetype_map) for x in val]
             elif isinstance(val, kolabformat.vectoremail):
                 val = [self._struct2dict(x, 'address', self.emailtype_map) for x in val]
             elif isinstance(val, kolabformat.vectorurl):
                 val = [self._struct2dict(x, 'url', self.urltype_map) for x in val]
             elif isinstance(val, kolabformat.vectorkey):
                 val = [self._struct2dict(x, 'key', self.keytype_map) for x in val]
             elif isinstance(val, kolabformat.vectoraddress):
                 val = [self._address2dict(x) for x in val]
             elif isinstance(val, kolabformat.vectorgeo):
                 val = [[x.latitude, x.longitude] for x in val]
 
             if val is not None:
                 data[p] = val
 
         affiliations = self.affiliations()
         if len(affiliations) > 0:
             _affiliation = self._affiliation2dict(affiliations[0])
             if 'address' in _affiliation:
                 data['address'].extend(_affiliation['address'])
                 _affiliation.pop('address', None)
             data.update(_affiliation)
 
         data.update(self._relateds2dict(self.relateds()))
 
         if self.photoMimetype():
             data['photo'] = dict(mimetype=self.photoMimetype(), base64=base64.b64encode(self.photo()))
         elif self.photo():
             data['photo'] = dict(uri=self.photo())
 
         return data
 
     def _names2dict(self, namecomp):
         names_map = {
             'surname':    'surnames',
             'given':      'given',
             'additional': 'additional',
             'prefix':     'prefixes',
             'suffix':     'suffixes',
         }
 
         data = dict()
 
-        for p, getter in names_map.iteritems():
+        for p, getter in names_map.items():
             val = None
             if hasattr(namecomp, getter):
                 val = getattr(namecomp, getter)()
             if isinstance(val, kolabformat.vectors):
                 val = [str(x) for x in val][0] if len(val) > 0 else None
             if val is not None:
                 data[p] = val
 
         return data
 
     def _affiliation2dict(self, affiliation):
         props_map = {
             'organization': 'organisation',
             'department':   'organisationalUnits',
             'role':         'roles',
         }
 
         data = dict()
 
-        for p, getter in props_map.iteritems():
+        for p, getter in props_map.items():
             val = None
             if hasattr(affiliation, getter):
                 val = getattr(affiliation, getter)()
             if isinstance(val, kolabformat.vectors):
                 val = [str(x) for x in val][0] if len(val) > 0 else None
             if val is not None:
                 data[p] = val
 
         data.update(self._relateds2dict(affiliation.relateds(), True))
 
         addresses = affiliation.addresses()
         if len(addresses):
             data['address'] = [self._address2dict(adr, 'office') for adr in addresses]
 
         return data
 
     def _address2dict(self, adr, adrtype=None):
         props_map = {
             'label':    'label',
             'street':   'street',
             'locality': 'locality',
             'region':   'region',
             'code':     'code',
             'country':  'country',
         }
-        addresstype_map = dict([(v, k) for (k, v) in self.addresstype_map.iteritems()])
+        addresstype_map = dict([(v, k) for (k, v) in self.addresstype_map.items()])
 
         data = dict()
 
         if adrtype is None:
             adrtype = addresstype_map.get(adr.types(), None)
 
         if adrtype is not None:
             data['type'] = adrtype
 
-        for p, getter in props_map.iteritems():
+        for p, getter in props_map.items():
             val = None
             if hasattr(adr, getter):
                 val = getattr(adr, getter)()
             if isinstance(val, kolabformat.vectors):
                 val = [str(x) for x in val][0] if len(val) > 0 else None
             if val is not None:
                 data[p] = val
 
         return data
 
     def _relateds2dict(self, relateds, aslist=True):
         data = dict()
 
-        related_map = dict([(v, k) for (k, v) in self.related_map.iteritems()])
+        related_map = dict([(v, k) for (k, v) in self.related_map.items()])
         for rel in relateds:
             reltype = related_map.get(rel.relationTypes(), None)
             val = rel.uri() if rel.type() == kolabformat.Related.Uid else rel.text()
             if reltype and val is not None:
                 if aslist:
                     if reltype not in data:
                         data[reltype] = []
                     data[reltype].append(val)
                 else:
                     data[reltype] = val
 
         return data
 
     def _struct2dict(self, struct, propname, map):
-        type_map = dict([(v, k) for (k, v) in map.iteritems()])
+        type_map = dict([(v, k) for (k, v) in map.items()])
         result = dict()
 
         if hasattr(struct, 'types'):
             result['type'] = type_map.get(struct.types(), None)
         elif hasattr(struct, 'type'):
             result['type'] = type_map.get(struct.type(), None)
 
         if hasattr(struct, propname):
             result[propname] = getattr(struct, propname)()
 
         return result
 
     def __str__(self):
         xml = kolabformat.writeContact(self)
         error = kolabformat.error()
 
         if error == None or not error:
             return xml
         else:
             raise ContactIntegrityError(kolabformat.errorMessage())
 
 
 class ContactIntegrityError(Exception):
     def __init__(self, message):
         Exception.__init__(self, message)
diff --git a/pykolab/xml/contact_reference.py b/pykolab/xml/contact_reference.py
index 42d9919..6b40817 100644
--- a/pykolab/xml/contact_reference.py
+++ b/pykolab/xml/contact_reference.py
@@ -1,54 +1,54 @@
 import kolabformat
 
 """
     def __eq__(self, *args): return _kolabformat.ContactReference___eq__(self, *args)
     def isValid(self): return _kolabformat.ContactReference_isValid(self)
     def setName(self, *args): return _kolabformat.ContactReference_setName(self, *args)
     def email(self): return _kolabformat.ContactReference_email(self)
     def uid(self): return _kolabformat.ContactReference_uid(self)
     def name(self): return _kolabformat.ContactReference_name(self)
     def type(self): return _kolabformat.ContactReference_type(self)
 """
 
 class ContactReference(kolabformat.ContactReference):
     properties_map = {
         'email': 'email',
         'name':  'name',
         'type':  'type',
         'uid':   'uid',
     }
 
     def __init__(self, email=None, name=""):
         if email == None:
             kolabformat.ContactReference.__init__(self)
         elif isinstance(email, kolabformat.ContactReference):
             kolabformat.ContactReference.__init__(self, email.email(), email.name(), email.uid())
         else:
             kolabformat.ContactReference.__init__(self, email, name)
 
     def get_email(self):
         return self.email()
 
     def get_name(self):
         return self.name()
 
     def set_cn(self, value):
         self.setName(value)
 
     def set_email(self, email):
         kolabformat.ContactReference.__init__(self, email, self.name(), self.uid())
 
     def set_name(self, name):
         self.setName(name)
 
     def to_dict(self):
         data = dict()
 
-        for p, getter in self.properties_map.iteritems():
+        for p, getter in self.properties_map.items():
             val = None
             if hasattr(self, getter):
                 val = getattr(self, getter)()
             if val is not None:
                 data[p] = val
 
         return data
diff --git a/pykolab/xml/event.py b/pykolab/xml/event.py
index e957a19..a80fe20 100644
--- a/pykolab/xml/event.py
+++ b/pykolab/xml/event.py
@@ -1,1511 +1,1511 @@
 import datetime
 import icalendar
 import kolabformat
 import pytz
 import time
 import uuid
 import base64
 import re
 
 import pykolab
 from pykolab import constants
 from pykolab import utils
 from pykolab.xml import utils as xmlutils
 from pykolab.xml import participant_status_label
 from pykolab.xml.utils import ustr
 from pykolab.translate import _
 
 from os import path
 from attendee import Attendee
 from contact_reference import ContactReference
 from recurrence_rule import RecurrenceRule
 from collections import OrderedDict
 
 log = pykolab.getLogger('pykolab.xml_event')
 
 
 def event_from_ical(ical, string=None):
     return Event(from_ical=ical, from_string=string)
 
 def event_from_string(string):
     return Event(from_string=string)
 
 def event_from_message(message):
     event = None
     if message.is_multipart():
         for part in message.walk():
             if part.get_content_type() == "application/calendar+xml":
                 payload = part.get_payload(decode=True)
                 event = event_from_string(payload)
 
             # append attachment parts to Event object
             elif event and 'Content-ID' in part:
                 event._attachment_parts.append(part)
 
     return event
 
 
 class Event(object):
     type = 'event'
     thisandfuture = False
 
     status_map = {
             None: kolabformat.StatusUndefined,
             "TENTATIVE": kolabformat.StatusTentative,
             "CONFIRMED": kolabformat.StatusConfirmed,
             "CANCELLED": kolabformat.StatusCancelled,
             "COMPLETED":  kolabformat.StatusCompleted,
             "IN-PROCESS": kolabformat.StatusInProcess,
             "NEEDS-ACTION": kolabformat.StatusNeedsAction,
         }
 
     classification_map = {
             "PUBLIC": kolabformat.ClassPublic,
             "PRIVATE": kolabformat.ClassPrivate,
             "CONFIDENTIAL": kolabformat.ClassConfidential,
         }
 
     alarm_type_map = {
             'EMAIL': kolabformat.Alarm.EMailAlarm,
             'DISPLAY': kolabformat.Alarm.DisplayAlarm,
             'AUDIO': kolabformat.Alarm.AudioAlarm
         }
 
     related_map = {
             'START': kolabformat.Start,
             'END': kolabformat.End
         }
 
     properties_map = {
         # property: getter
         "uid": "get_uid",
         "created": "get_created",
         "lastmodified-date": "get_lastmodified",
         "sequence": "sequence",
         "classification": "get_classification",
         "categories": "categories",
         "start": "get_start",
         "end": "get_end",
         "duration": "get_duration",
         "transparency": "transparency",
         "rrule": "recurrenceRule",
         "rdate": "recurrenceDates",
         "exdate": "exceptionDates",
         "recurrence-id": "recurrenceID",
         "summary": "summary",
         "description": "description",
         "priority": "priority",
         "status": "get_ical_status",
         "location": "location",
         "organizer": "organizer",
         "attendee": "get_attendees",
         "attach": "attachments",
         "url": "url",
         "alarm": "alarms",
         "x-custom": "customProperties",
         # TODO: add to_dict() support for these
         # "exception": "exceptions",
     }
 
     def __init__(self, from_ical="", from_string=""):
         self._attendees = []
         self._categories = []
         self._exceptions = []
         self._attachment_parts = []
 
         if isinstance(from_ical, str) and from_ical == "":
             if from_string == "":
                 self.event = kolabformat.Event()
             else:
                 self.event = kolabformat.readEvent(from_string, False)
                 self._load_attendees()
                 self._load_exceptions()
         else:
             self.from_ical(from_ical, from_string)
 
         self.set_created(self.get_created())
         self.uid = self.get_uid()
 
     def _load_attendees(self):
         for a in self.event.attendees():
             att = Attendee(a.contact().email())
             att.copy_from(a)
             self._attendees.append(att)
 
     def _load_exceptions(self):
         for ex in self.event.exceptions():
             exception = Event()
             exception.uid = ex.uid()
             exception.event = ex
             exception._load_attendees()
             self._exceptions.append(exception)
 
     def add_attendee(self, email_or_attendee, name=None, rsvp=False, role=None, participant_status=None, cutype="INDIVIDUAL", params=None):
         if isinstance(email_or_attendee, Attendee):
             attendee = email_or_attendee
         else:
             attendee = Attendee(email_or_attendee, name, rsvp, role, participant_status, cutype, params)
 
         # apply update to self and all exceptions
         self.update_attendees([attendee], True)
 
     def add_category(self, category):
         self._categories.append(ustr(category))
         self.event.setCategories(self._categories)
 
     def add_recurrence_date(self, _datetime):
         valid_datetime = False
         if isinstance(_datetime, datetime.date):
             valid_datetime = True
 
         if isinstance(_datetime, datetime.datetime):
             # If no timezone information is passed on, make it UTC
             if _datetime.tzinfo is None:
                 _datetime = _datetime.replace(tzinfo=pytz.utc)
 
             valid_datetime = True
 
         if not valid_datetime:
             raise InvalidEventDateError(_("Rdate needs datetime.date or datetime.datetime instance, got %r") % (type(_datetime)))
 
         self.event.addRecurrenceDate(xmlutils.to_cdatetime(_datetime, True))
 
     def add_exception_date(self, _datetime):
         valid_datetime = False
         if isinstance(_datetime, datetime.date):
             valid_datetime = True
 
         if isinstance(_datetime, datetime.datetime):
             # If no timezone information is passed on, make it UTC
             if _datetime.tzinfo == None:
                 _datetime = _datetime.replace(tzinfo=pytz.utc)
 
             valid_datetime = True
 
         if not valid_datetime:
             raise InvalidEventDateError(_("Exdate needs datetime.date or datetime.datetime instance, got %r") % (type(_datetime)))
 
         self.event.addExceptionDate(xmlutils.to_cdatetime(_datetime, True))
 
     def add_exception(self, exception):
         recurrence_id = exception.get_recurrence_id()
         if recurrence_id is None:
             raise EventIntegrityError("Recurrence exceptions require a Recurrence-ID property")
 
         # check if an exception with the given recurrence-id already exists
         append = True
         vexceptions = self.event.exceptions()
         for i, ex in enumerate(self._exceptions):
             if ex.get_recurrence_id() == recurrence_id and ex.thisandfuture == exception.thisandfuture:
                 # update the existing exception
                 vexceptions[i] = exception.event
                 self._exceptions[i] = exception
                 append = False
 
         # check if main event matches the given recurrence-id
         if append and self.get_recurrence_id() == recurrence_id:
             self.event = exception.event
             self._load_attendees()
             self._load_exceptions()
             append = False
 
         if append:
             vexceptions.append(exception.event)
             self._exceptions.append(exception)
 
         self.event.setExceptions(vexceptions)
 
     def del_exception(self, exception):
         recurrence_id = exception.get_recurrence_id()
         if recurrence_id is None:
             raise EventIntegrityError("Recurrence exceptions require a Recurrence-ID property")
 
         updated = False
         vexceptions = self.event.exceptions()
         for i, ex in enumerate(self._exceptions):
             if ex.get_recurrence_id() == recurrence_id and ex.thisandfuture == exception.thisandfuture:
                 del vexceptions[i]
                 del self._exceptions[i]
                 updated = True
 
         if updated:
             self.event.setExceptions(vexceptions)
 
 
     def as_string_itip(self, method="REQUEST"):
         cal = icalendar.Calendar()
         cal.add(
                 'prodid',
                 '-//pykolab-%s-%s//kolab.org//' % (
                         constants.__version__,
                         constants.__release__
                     )
             )
 
         cal.add('version', '2.0')
         # TODO: Really?
         cal.add('calscale', 'GREGORIAN')
         # TODO: Not always a request...
         cal.add('method', method)
 
         # TODO: Add timezone information using icalendar.?()
         #       Not sure if there is a class for it.
 
         cal.add_component(self.to_ical())
 
         # add recurrence exceptions
         if len(self._exceptions) > 0 and not method == 'REPLY':
             for exception in self._exceptions:
                 cal.add_component(exception.to_ical())
 
         if hasattr(cal, 'to_ical'):
             return cal.to_ical()
         elif hasattr(cal, 'as_string'):
             return cal.as_string()
 
     def to_ical(self):
         event = icalendar.Event()
 
         # Required
         event['uid'] = self.get_uid()
 
         # NOTE: Make sure to list(set()) or duplicates may arise
         for attr in list(set(event.singletons)):
             _attr = attr.lower().replace('-', '')
             ical_getter = 'get_ical_%s' % (_attr)
             default_getter = 'get_%s' % (_attr)
             retval = None
             if hasattr(self, ical_getter):
                 retval = getattr(self, ical_getter)()
                 if not retval == None and not retval == "":
                     event.add(attr.lower(), retval)
             elif hasattr(self, default_getter):
                 retval = getattr(self, default_getter)()
                 if not retval == None and not retval == "":
                     event.add(attr.lower(), retval, encode=0)
 
         # NOTE: Make sure to list(set()) or duplicates may arise
         for attr in list(set(event.multiple)):
             _attr = attr.lower().replace('-', '')
             ical_getter = 'get_ical_%s' % (_attr)
             default_getter = 'get_%s' % (_attr)
             retval = None
             if hasattr(self, ical_getter):
                 retval = getattr(self, ical_getter)()
             elif hasattr(self, default_getter):
                 retval = getattr(self, default_getter)()
 
             if isinstance(retval, list) and not len(retval) == 0:
                 for _retval in retval:
                     event.add(attr.lower(), _retval, encode=0)
 
         # copy custom properties to iCal
         for cs in self.event.customProperties():
             event.add(cs.identifier, cs.value)
 
         return event
 
     def delegate(self, delegators, delegatees, names=None):
         if not isinstance(delegators, list):
             delegators = [delegators]
 
         if not isinstance(delegatees, list):
             delegatees = [delegatees]
 
         if not isinstance(names, list):
             names = [names]
 
         _delegators = []
         for delegator in delegators:
             _delegators.append(self.get_attendee(delegator))
 
         _delegatees = []
 
         for i,delegatee in enumerate(delegatees):
             try:
                 _delegatees.append(self.get_attendee(delegatee))
             except:
                 # TODO: An iTip needs to be sent out to the new attendee
                 self.add_attendee(delegatee, names[i] if i < len(names) else None)
                 _delegatees.append(self.get_attendee(delegatee))
 
         for delegator in _delegators:
             delegator.delegate_to(_delegatees)
 
         for delegatee in _delegatees:
             delegatee.delegate_from(_delegators)
 
         self.event.setAttendees(self._attendees)
 
     def from_ical(self, ical, raw=None):
         if isinstance(ical, icalendar.Event) or isinstance(ical, icalendar.Calendar):
             ical_event = ical
         elif hasattr(icalendar.Event, 'from_ical'):
             ical_event = icalendar.Event.from_ical(ical)
         elif hasattr(icalendar.Event, 'from_string'):
             ical_event = icalendar.Event.from_string(ical)
 
         # VCALENDAR block was given, find the first VEVENT item
         if isinstance(ical_event, icalendar.Calendar):
             for c in ical_event.walk():
                 if c.name == 'VEVENT':
                     ical_event = c
                     break
 
         # use the libkolab calendaring bindings to load the full iCal data
         if 'RRULE' in ical_event or 'ATTACH' in ical_event \
              or [part for part in ical_event.walk() if part.name == 'VALARM']:
             if raw is None or raw == "":
                 raw = ical if isinstance(ical, str) else ical.to_ical()
             self._xml_from_ical(raw)
         else:
             self.event = kolabformat.Event()
 
         # TODO: Clause the timestamps for zulu suffix causing datetime.datetime
         # to fail substitution.
         for attr in list(set(ical_event.required)):
             if attr in ical_event:
                 self.set_from_ical(attr.lower(), ical_event[attr])
 
         # NOTE: Make sure to list(set()) or duplicates may arise
         # NOTE: Keep the original order e.g. to read DTSTART before RECURRENCE-ID
         for attr in list(OrderedDict.fromkeys(ical_event.singletons)):
             if attr in ical_event:
                 if isinstance(ical_event[attr], list):
                     ical_event[attr] = ical_event[attr][0];
                 self.set_from_ical(attr.lower(), ical_event[attr])
 
         # NOTE: Make sure to list(set()) or duplicates may arise
         for attr in list(set(ical_event.multiple)):
             if attr in ical_event:
                 self.set_from_ical(attr.lower(), ical_event[attr])
 
     def _xml_from_ical(self, ical):
         if not "BEGIN:VCALENDAR" in ical:
             ical = "BEGIN:VCALENDAR\nVERSION:2.0\n" + ical + "\nEND:VCALENDAR"
         from kolab.calendaring import EventCal
         self.event = EventCal()
         success = self.event.fromICal(ical)
         if success:
             self._load_exceptions()
         return success
 
     def get_attendee_participant_status(self, attendee):
         return attendee.get_participant_status()
 
     def get_attendee(self, attendee):
         if isinstance(attendee, basestring):
             if attendee in [x.get_email() for x in self.get_attendees()]:
                 attendee = self.get_attendee_by_email(attendee)
 
             elif attendee in [x.get_name() for x in self.get_attendees()]:
                 attendee = self.get_attendee_by_name(attendee)
 
             else:
                 raise ValueError(_("No attendee with email or name %r") %(attendee))
 
             return attendee
 
         elif isinstance(attendee, Attendee):
             return attendee
 
         else:
             raise ValueError(_("Invalid argument value attendee %r, must be basestring or Attendee") % (attendee))
 
     def find_attendee(self, attendee):
         try:
             return self.get_attendee(attendee)
         except:
             return None
 
     def get_attendee_by_email(self, email):
         if email in [x.get_email() for x in self.get_attendees()]:
             return [x for x in self.get_attendees() if x.get_email() == email][0]
 
         raise ValueError(_("No attendee with email %r") %(email))
 
     def get_attendee_by_name(self, name):
         if name in [x.get_name() for x in self.get_attendees()]:
             return [x for x in self.get_attendees() if x.get_name() == name][0]
 
         raise ValueError(_("No attendee with name %r") %(name))
 
     def get_attendees(self):
         return self._attendees
 
     def get_categories(self):
         return [str(c) for c in self.event.categories()]
 
     def get_classification(self):
         return self.event.classification()
 
     def get_created(self):
         try:
             return xmlutils.from_cdatetime(self.event.created(), True)
         except ValueError:
             return datetime.datetime.now()
 
     def get_description(self):
         return self.event.description()
 
     def get_comment(self):
         if hasattr(self.event, 'comment'):
             return self.event.comment()
         else:
             return None
 
     def get_duration(self):
         duration = self.event.duration()
         if duration and duration.isValid():
             dtd = datetime.timedelta(
                 days=duration.days(),
                 seconds=duration.seconds(),
                 minutes=duration.minutes(),
                 hours=duration.hours(),
                 weeks=duration.weeks()
             )
             return dtd
 
         return None
 
     def get_end(self):
         dt = xmlutils.from_cdatetime(self.event.end(), True)
         if not dt:
             duration = self.get_duration()
             if duration is not None:
                 dt = self.get_start() + duration
         return dt
 
     def get_date_text(self, date_format=None, time_format=None):
         if date_format is None:
             date_format = _("%Y-%m-%d")
         if time_format is None:
             time_format = _("%H:%M (%Z)")
 
         start = self.get_start()
         end = self.get_end()
         all_day = not hasattr(start, 'date')
         start_date = start.date() if not all_day else start
         end_date = end.date() if not all_day else end
 
         if start_date == end_date:
             end_format = time_format
         else:
             end_format = date_format + " " + time_format
 
         if all_day:
             time_format = ''
             if start_date == end_date:
                 return start.strftime(date_format)
 
         return "%s - %s" % (start.strftime(date_format + " " + time_format), end.strftime(end_format))
 
     def get_recurrence_dates(self):
         return map(lambda _: xmlutils.from_cdatetime(_, True), self.event.recurrenceDates())
 
     def get_exception_dates(self):
         return map(lambda _: xmlutils.from_cdatetime(_, True), self.event.exceptionDates())
 
     def get_exceptions(self):
         return self._exceptions
 
     def has_exceptions(self):
         return len(self._exceptions) > 0
 
     def get_attachments(self):
         return self.event.attachments()
 
     def get_attachment_data(self, i):
         vattach = self.event.attachments()
         if i < len(vattach):
             attachment = vattach[i]
             uri = attachment.uri()
             if uri and uri[0:4] == 'cid:':
                 # get data from MIME part with matching content-id
                 cid = '<' + uri[4:] + '>'
                 for p in self._attachment_parts:
                     if p['Content-ID'] == cid:
                         return p.get_payload(decode=True)
             else:
                 return attachment.data()
 
         return None
 
     def get_alarms(self):
         return self.event.alarms()
 
     def get_ical_attendee(self):
         # TODO: Formatting, aye? See also the example snippet:
         #
         # ATTENDEE;RSVP=TRUE;ROLE=REQ-PARTICIPANT;CUTYPE=GROUP:
         # MAILTO:employee-A@host.com
 
         attendees = []
         for attendee in self.get_attendees():
             email = attendee.get_email()
             name = attendee.get_name()
             rsvp = attendee.get_rsvp()
             role = attendee.get_role()
             partstat = attendee.get_participant_status()
             cutype = attendee.get_cutype()
             delegators = attendee.get_delegated_from()
             delegatees = attendee.get_delegated_to()
 
             if rsvp in attendee.rsvp_map:
                 _rsvp = rsvp
             elif rsvp in attendee.rsvp_map.values():
-                _rsvp = [k for k, v in attendee.rsvp_map.iteritems() if v == rsvp][0]
+                _rsvp = [k for k, v in attendee.rsvp_map.items() if v == rsvp][0]
             else:
                 _rsvp = None
 
             if role in attendee.role_map:
                 _role = role
             elif role in attendee.role_map.values():
-                _role = [k for k, v in attendee.role_map.iteritems() if v == role][0]
+                _role = [k for k, v in attendee.role_map.items() if v == role][0]
             else:
                 _role = None
 
             if partstat in attendee.participant_status_map:
                 _partstat = partstat
             elif partstat in attendee.participant_status_map.values():
-                _partstat = [k for k, v in attendee.participant_status_map.iteritems() if v == partstat][0]
+                _partstat = [k for k, v in attendee.participant_status_map.items() if v == partstat][0]
             else:
                 _partstat = None
 
             if cutype in attendee.cutype_map:
                 _cutype = cutype
             elif cutype in attendee.cutype_map.values():
-                _cutype = [k for k, v in attendee.cutype_map.iteritems() if v == cutype][0]
+                _cutype = [k for k, v in attendee.cutype_map.items() if v == cutype][0]
             else:
                 _cutype = None
 
             _attendee = icalendar.vCalAddress("MAILTO:%s" % email)
             if not name == None and not name == "":
                 _attendee.params['CN'] = icalendar.vText(name)
 
             if not _rsvp == None:
                 _attendee.params['RSVP'] = icalendar.vText(_rsvp)
 
             if not _role == None:
                 _attendee.params['ROLE'] = icalendar.vText(_role)
 
             if not _partstat == None:
                 _attendee.params['PARTSTAT'] = icalendar.vText(_partstat)
 
             if not _cutype == None:
                 _attendee.params['CUTYPE'] = icalendar.vText(_cutype)
 
             if not delegators == None and len(delegators) > 0:
                 _attendee.params['DELEGATED-FROM'] = icalendar.vText(delegators[0].email())
 
             if not delegatees == None and len(delegatees) > 0:
                 _attendee.params['DELEGATED-TO'] = icalendar.vText(delegatees[0].email())
 
             attendees.append(_attendee)
 
         return attendees
 
     def get_ical_attendee_participant_status(self, attendee):
         attendee = self.get_attendee(attendee)
 
         if attendee.get_participant_status() in attendee.participant_status_map:
             return attendee.get_participant_status()
         elif attendee.get_participant_status() in attendee.participant_status_map.values():
-            return [k for k, v in attendee.participant_status_map.iteritems() if v == attendee.get_participant_status()][0]
+            return [k for k, v in attendee.participant_status_map.items() if v == attendee.get_participant_status()][0]
         else:
             raise ValueError(_("Invalid participant status"))
 
     def get_ical_created(self):
         return self.get_created()
 
     def get_ical_dtend(self):
         dtend = self.get_end()
         # shift end by one day on all-day events
         if not hasattr(dtend, 'hour'):
             dtend = dtend + datetime.timedelta(days=1)
         return dtend
 
     def get_ical_dtstamp(self):
         try:
             retval = self.get_lastmodified()
             if retval == None or retval == "":
                 return datetime.datetime.now()
         except:
             return datetime.datetime.now()
 
     def get_ical_lastmodified(self):
         return self.get_ical_dtstamp()
 
     def get_ical_dtstart(self):
         return self.get_start()
 
     def get_ical_organizer(self):
         contact = self.get_organizer()
         organizer = icalendar.vCalAddress("MAILTO:%s" % contact.email())
         name = contact.name()
 
         if not name == None and not name == "":
             organizer.params["CN"] = icalendar.vText(name)
 
         return organizer
 
     def get_ical_status(self):
         status = self.event.status()
 
         if status in self.status_map:
             return status
 
         return self._translate_value(status, self.status_map) if status else None
 
     def get_ical_class(self):
         class_ = self.event.classification()
         return self._translate_value(class_, self.classification_map) if class_ else None
 
     def get_ical_sequence(self):
         return str(self.event.sequence()) if self.event.sequence() else None
 
     def get_ical_comment(self):
         comment = self.get_comment()
         if comment is not None:
             return [ comment ]
         return None
 
     def get_ical_recurrenceid(self):
         rid = self.get_recurrence_id()
         if isinstance(rid, datetime.datetime) or isinstance(rid, datetime.date):
             prop = icalendar.vDatetime(rid) if isinstance(rid, datetime.datetime) else icalendar.vDate(rid)
             if self.thisandfuture:
                 prop.params.update({'RANGE':'THISANDFUTURE'})
             return prop
 
         return None
 
     def get_ical_rrule(self):
         result = []
         rrule = self.get_recurrence()
         if rrule.isValid():
             result.append(rrule.to_ical())
         return result
 
     def get_ical_rdate(self):
         rdates = self.get_recurrence_dates()
         for i in range(len(rdates)):
             rdates[i] = icalendar.prop.vDDDLists(rdates[i])
 
         return rdates
 
     def get_location(self):
         return self.event.location()
 
     def get_lastmodified(self):
         try:
             _datetime = self.event.lastModified()
             if _datetime == None or not _datetime.isValid():
                 self.__str__()
         except:
             self.__str__()
 
         return xmlutils.from_cdatetime(self.event.lastModified(), True)
 
     def get_organizer(self):
         organizer = self.event.organizer()
         return organizer
 
     def get_priority(self):
         return str(self.event.priority())
 
     def get_start(self):
         return xmlutils.from_cdatetime(self.event.start(), True)
 
     def get_status(self, translated=False):
         status = self.event.status()
         if translated:
             return self._translate_value(status, self.status_map) if status else None
 
         return status
 
     def get_summary(self):
         return self.event.summary()
 
     def get_uid(self):
         uid = self.event.uid()
         if not uid == '':
             return uid
         else:
             self.set_uid(uuid.uuid4())
             return self.get_uid()
 
     def get_recurrence_id(self):
         self.thisandfuture = self.event.thisAndFuture();
         recurrence_id = xmlutils.from_cdatetime(self.event.recurrenceID(), True)
 
         # fix recurrence-id type if stored as date instead of datetime
         if recurrence_id is not None and isinstance(recurrence_id, datetime.date):
             dtstart = self.get_start()
             if not type(recurrence_id) == type(dtstart):
                 recurrence_id = datetime.datetime.combine(recurrence_id, dtstart.time()).replace(tzinfo=dtstart.tzinfo)
 
         return recurrence_id
 
     def get_thisandfuture(self):
         self.thisandfuture = self.event.thisAndFuture();
         return self.thisandfuture
 
     def get_sequence(self):
         return self.event.sequence()
 
     def get_url(self):
         return self.event.url()
 
     def get_transparency(self):
         return self.event.transparency()
 
     def get_recurrence(self):
         return RecurrenceRule(self.event.recurrenceRule())
 
     def set_attendees(self, _attendees, recursive=False):
         if recursive:
             self._attendees = []
             self.update_attendees(_attendees, True)
         else:
             self._attendees = _attendees
             self.event.setAttendees(self._attendees)
 
     def set_attendee_participant_status(self, attendee, status, rsvp=None):
         """
             Set the participant status of an attendee to status.
 
             As the attendee arg, pass an email address or name, for this
             function to obtain the attendee object by searching the list of
             attendees for this event.
         """
         attendee = self.get_attendee(attendee)
         attendee.set_participant_status(status)
 
         if rsvp is not None:
             attendee.set_rsvp(rsvp)
 
         # apply update to self and all exceptions
         self.update_attendees([attendee], False)
 
     def update_attendees(self, _attendees, append=True):
         self.merge_attendee_data(_attendees, append)
 
         if len(self._exceptions):
             vexceptions = self.event.exceptions()
             for i, exception in enumerate(self._exceptions):
                 exception.merge_attendee_data(_attendees, append)
                 vexceptions[i] = exception.event
             self.event.setExceptions(vexceptions)
 
     def merge_attendee_data(self, _attendees, append=True):
         for attendee in _attendees:
             found = False
 
             for candidate in self._attendees:
                 if candidate.get_email() == attendee.get_email():
                     candidate.copy_from(attendee)
                     found = True
                     break
 
             if not found and append:
                 self._attendees.append(attendee)
 
         self.event.setAttendees(self._attendees)
 
     def set_classification(self, classification):
         if classification in self.classification_map:
             self.event.setClassification(self.classification_map[classification])
         elif classification in self.classification_map.values():
             self.event.setClassification(classification)
         else:
             raise ValueError(_("Invalid classification %r") % (classification))
 
     def set_created(self, _datetime=None):
         if _datetime is None or isinstance(_datetime, datetime.time):
             _datetime = datetime.datetime.utcnow()
 
         self.event.setCreated(xmlutils.to_cdatetime(_datetime, False, True))
 
     def set_description(self, description):
         self.event.setDescription(ustr(description))
 
     def set_comment(self, comment):
         if hasattr(self.event, 'setComment'):
             self.event.setComment(ustr(comment))
 
     def set_dtstamp(self, _datetime):
         self.event.setLastModified(xmlutils.to_cdatetime(_datetime, False, True))
 
     def set_end(self, _datetime):
         valid_datetime = False
         if isinstance(_datetime, datetime.date):
             valid_datetime = True
 
         if isinstance(_datetime, datetime.datetime):
             # If no timezone information is passed on, make it UTC
             if _datetime.tzinfo == None:
                 _datetime = _datetime.replace(tzinfo=pytz.utc)
 
             valid_datetime = True
 
         if not valid_datetime:
             raise InvalidEventDateError(_("Event end needs datetime.date or datetime.datetime instance, got %r") % (type(_datetime)))
 
         self.event.setEnd(xmlutils.to_cdatetime(_datetime, True))
 
     def set_exception_dates(self, _datetimes):
         for _datetime in _datetimes:
             self.add_exception_date(_datetime)
 
     def set_recurrence_dates(self, _datetimes):
         for _datetime in _datetimes:
             self.add_recurrence_date(_datetime)
 
     def add_custom_property(self, name, value):
         if not name.upper().startswith('X-'):
             raise ValueError(_("Invalid custom property name %r") % (name))
 
         props = self.event.customProperties()
         props.append(kolabformat.CustomProperty(name.upper(), value))
         self.event.setCustomProperties(props)
 
     def set_from_ical(self, attr, value):
         attr = attr.replace('-', '')
         ical_setter = 'set_ical_' + attr
         default_setter = 'set_' + attr
         params = value.params if hasattr(value, 'params') else {}
 
         if isinstance(value, icalendar.vDDDTypes) and hasattr(value, 'dt'):
             value = value.dt
 
         if attr == "categories":
             self.add_category(value)
         elif attr == "class":
             if (value and value[:2] not in ['X-', 'x-']):
                 self.set_classification(value)
         elif attr == "recurrenceid":
             self.set_ical_recurrenceid(value, params)
         elif hasattr(self, ical_setter):
             getattr(self, ical_setter)(value)
         elif hasattr(self, default_setter):
             getattr(self, default_setter)(value)
 
     def set_ical_attendee(self, _attendee):
         if isinstance(_attendee, basestring):
             _attendee = [_attendee]
 
         if isinstance(_attendee, list):
             for attendee in _attendee:
                 address = str(attendee).split(':')[-1]
 
                 if hasattr(attendee, 'params'):
                     params = attendee.params
                 else:
                     params = {}
 
                 if 'CN' in params:
                     name = ustr(params['CN'])
                 else:
                     name = None
 
                 if 'ROLE' in params:
                     role = params['ROLE']
                 else:
                     role = None
 
                 if 'PARTSTAT' in params:
                     partstat = params['PARTSTAT']
                 else:
                     partstat = None
 
                 if 'RSVP' in params:
                     rsvp = params['RSVP']
                 else:
                     rsvp = None
 
                 if 'CUTYPE' in params:
                     cutype = params['CUTYPE']
                 else:
                     cutype = kolabformat.CutypeIndividual
 
                 att = self.add_attendee(address, name=name, rsvp=rsvp, role=role, participant_status=partstat, cutype=cutype, params=params)
 
     def set_ical_dtend(self, dtend):
         # shift end by one day on all-day events
         if not hasattr(dtend, 'hour'):
             dtend = dtend - datetime.timedelta(days=1)
         self.set_end(dtend)
 
     def set_ical_dtstamp(self, dtstamp):
         self.set_dtstamp(dtstamp)
 
     def set_ical_dtstart(self, dtstart):
         self.set_start(dtstart)
 
     def set_ical_lastmodified(self, lastmod):
         self.set_lastmodified(lastmod)
 
     def set_ical_duration(self, value):
         if hasattr(value, 'dt'):
             value = value.dt
 
         duration = kolabformat.Duration(value.days, 0, 0, value.seconds, False)
         self.event.setDuration(duration)
 
     def set_ical_organizer(self, organizer):
         address = str(organizer).split(':')[-1]
 
         cn = None
 
         if hasattr(organizer, 'params'):
             params = organizer.params
         else:
             params = {}
 
         if 'CN' in params:
             cn = ustr(params['CN'])
 
         self.set_organizer(str(address), name=cn)
 
     def set_ical_priority(self, priority):
         self.set_priority(priority)
 
     def set_ical_sequence(self, sequence):
         self.set_sequence(sequence)
 
     def set_ical_summary(self, summary):
         self.set_summary(ustr(summary))
 
     def set_ical_uid(self, uid):
         self.set_uid(str(uid))
 
     def set_ical_rdate(self, rdate):
         rdates = []
         # rdate here can be vDDDLists or a list of vDDDLists, why?!
         if isinstance(rdate, icalendar.prop.vDDDLists):
             rdate = [rdate]
 
         for _rdate in rdate:
             if isinstance(_rdate, icalendar.prop.vDDDLists):
                 tzid = None
                 if hasattr(_rdate, 'params') and 'TZID' in _rdate.params:
                     tzid = _rdate.params['TZID']
                 dts = icalendar.prop.vDDDLists.from_ical(_rdate.to_ical(), tzid)
                 for datetime in dts:
                     rdates.append(datetime)
 
         self.set_recurrence_dates(rdates)
 
     def set_ical_recurrenceid(self, value, params):
         try:
             self.thisandfuture = params.get('RANGE', '') == 'THISANDFUTURE'
             self.set_recurrence_id(value, self.thisandfuture)
         except InvalidEventDateError:
             pass
 
     def set_lastmodified(self, _datetime=None):
         valid_datetime = False
         if isinstance(_datetime, datetime.date):
             valid_datetime = True
 
         if isinstance(_datetime, datetime.datetime):
             valid_datetime = True
 
         if _datetime is None or isinstance(_datetime, datetime.time):
             valid_datetime = True
             _datetime = datetime.datetime.utcnow()
 
         if not valid_datetime:
             raise InvalidEventDateError(_("Event last-modified needs datetime.date or datetime.datetime instance, got %r") % (type(_datetime)))
 
         self.event.setLastModified(xmlutils.to_cdatetime(_datetime, False, True))
 
     def set_location(self, location):
         self.event.setLocation(ustr(location))
 
     def set_organizer(self, email, name=None):
         contactreference = ContactReference(email)
         if not name == None:
             contactreference.setName(name)
 
         self.event.setOrganizer(contactreference)
 
     def set_priority(self, priority):
         self.event.setPriority(priority)
 
     def set_sequence(self, sequence):
         self.event.setSequence(int(sequence))
 
     def set_url(self, url):
         self.event.setUrl(ustr(url))
 
     def set_recurrence(self, recurrence):
         self.event.setRecurrenceRule(recurrence)
 
         # reset eventcal instance
         if hasattr(self, 'eventcal'):
             del self.eventcal
 
     def set_start(self, _datetime):
         valid_datetime = False
         if isinstance(_datetime, datetime.date):
             valid_datetime = True
 
         if isinstance(_datetime, datetime.datetime):
             # If no timezone information is passed on, make it UTC
             if _datetime.tzinfo == None:
                 _datetime = _datetime.replace(tzinfo=pytz.utc)
 
             valid_datetime = True
 
         if not valid_datetime:
             raise InvalidEventDateError(_("Event start needs datetime.date or datetime.datetime instance, got %r") % (type(_datetime)))
 
         self.event.setStart(xmlutils.to_cdatetime(_datetime, True))
 
     def set_status(self, status):
         if status in self.status_map:
             self.event.setStatus(self.status_map[status])
         elif status in self.status_map.values():
             self.event.setStatus(status)
         elif not status == kolabformat.StatusUndefined:
             raise InvalidEventStatusError(_("Invalid status set: %r") % (status))
 
     def set_summary(self, summary):
         self.event.setSummary(summary)
 
     def set_uid(self, uid):
         self.uid = uid
         self.event.setUid(str(uid))
 
     def set_recurrence_id(self, _datetime, _thisandfuture=None):
         valid_datetime = False
         if isinstance(_datetime, datetime.date):
             valid_datetime = True
 
         if isinstance(_datetime, datetime.datetime):
             # If no timezone information is passed on, use the one from event start
             if _datetime.tzinfo == None:
                 _start = self.get_start()
                 _datetime = _datetime.replace(tzinfo=_start.tzinfo)
 
             valid_datetime = True
 
         if not valid_datetime:
             raise InvalidEventDateError(_("Event recurrence-id needs datetime.datetime instance"))
 
         if _thisandfuture is None:
             _thisandfuture = self.thisandfuture
 
         self.event.setRecurrenceID(xmlutils.to_cdatetime(_datetime), _thisandfuture)
 
     def set_transparency(self, transp):
         return self.event.setTransparency(transp)
 
     def __str__(self):
         event_xml = kolabformat.writeEvent(self.event)
 
         error = kolabformat.error()
 
         if error == None or not error:
             return event_xml
         else:
             raise EventIntegrityError(kolabformat.errorMessage())
 
     def to_dict(self):
         data = dict()
 
-        for p, getter in self.properties_map.iteritems():
+        for p, getter in self.properties_map.items():
             val = None
             if hasattr(self, getter):
                 val = getattr(self, getter)()
             elif hasattr(self.event, getter):
                 val = getattr(self.event, getter)()
 
             if isinstance(val, kolabformat.cDateTime):
                 val = xmlutils.from_cdatetime(val, True)
             elif isinstance(val, kolabformat.vectordatetime):
                 val = [xmlutils.from_cdatetime(x, True) for x in val]
             elif isinstance(val, kolabformat.vectors):
                 val = [str(x) for x in val]
             elif isinstance(val, kolabformat.vectorcs):
                 for x in val:
                     data[x.identifier] = x.value
                 val = None
             elif isinstance(val, kolabformat.ContactReference):
                 val = ContactReference(val).to_dict()
             elif isinstance(val, kolabformat.RecurrenceRule):
                 val = RecurrenceRule(val).to_dict()
             elif isinstance(val, kolabformat.vectorattachment):
                 val = [dict(fmttype=x.mimetype(), label=x.label(), uri=x.uri()) for x in val]
             elif isinstance(val, kolabformat.vectoralarm):
                 val = [self._alarm_to_dict(x) for x in val]
             elif isinstance(val, list):
                 val = [x.to_dict() for x in val if hasattr(x, 'to_dict')]
 
             if val is not None:
                 data[p] = val
 
         return data
 
     def _alarm_to_dict(self, alarm):
         ret = dict(
             action=self._translate_value(alarm.type(), self.alarm_type_map),
             summary=alarm.summary(),
             description=alarm.description(),
             trigger=None
         )
 
         start = alarm.start()
         if start and start.isValid():
             ret['trigger'] = xmlutils.from_cdatetime(start, True)
         else:
             ret['trigger'] = dict(related=self._translate_value(alarm.relativeTo(), self.related_map))
             duration = alarm.relativeStart()
             if duration and duration.isValid():
                 prefix = '-' if duration.isNegative() else '+'
                 value = prefix + "P%dW%dDT%dH%dM%dS" % (
                     duration.weeks(), duration.days(), duration.hours(), duration.minutes(), duration.seconds()
                 )
                 ret['trigger']['value'] = re.sub(r"T$", '', re.sub(r"0[WDHMS]", '', value))
 
         if alarm.type() == kolabformat.Alarm.EMailAlarm:
             ret['attendee'] = [ContactReference(a).to_dict() for a in alarm.attendees()]
 
         return ret
 
     def _translate_value(self, val, map):
-        name_map = dict([(v, k) for (k, v) in map.iteritems()])
+        name_map = dict([(v, k) for (k, v) in map.items()])
         return name_map[val] if val in name_map else 'UNKNOWN'
 
     def to_message(self, creator=None):
         from email.MIMEMultipart import MIMEMultipart
         from email.MIMEBase import MIMEBase
         from email.MIMEText import MIMEText
         from email.Utils import COMMASPACE, formatdate
 
         msg = MIMEMultipart()
         organizer = self.get_organizer()
         email = organizer.email()
         name = organizer.name()
 
         if creator:
             msg['From'] = creator
         elif not name:
             msg['From'] = email
         else:
             msg['From'] = '"%s" <%s>' % (name, email)
 
         msg['To'] = ', '.join([x.__str__() for x in self.get_attendees()])
         msg['Date'] = formatdate(localtime=True)
 
         msg.add_header('X-Kolab-MIME-Version', '3.0')
         msg.add_header('X-Kolab-Type', 'application/x-vnd.kolab.' + self.type)
 
         text = utils.multiline_message("""
                     This is a Kolab Groupware object. To view this object you
                     will need an email client that understands the Kolab
                     Groupware format. For a list of such email clients please
                     visit http://www.kolab.org/
             """)
 
         msg.attach( MIMEText(text) )
 
         part = MIMEBase('application', "calendar+xml")
         part.set_charset('UTF-8')
 
         msg["Subject"] = self.get_uid()
 
         # extract attachment data into separate MIME parts
         vattach = self.event.attachments()
         i = 0
         for attach in vattach:
             if attach.uri():
                 continue
 
             mimetype = attach.mimetype()
             (primary, seconday) = mimetype.split('/')
             name = attach.label()
             if not name:
                 name = 'unknown.x'
 
             (basename, suffix) = path.splitext(name)
             t = datetime.datetime.now()
             cid = "%s.%s.%s%s" % (basename, time.mktime(t.timetuple()), t.microsecond + len(self._attachment_parts), suffix)
 
             p = MIMEBase(primary, seconday)
             p.add_header('Content-Disposition', 'attachment', filename=name)
             p.add_header('Content-Transfer-Encoding', 'base64')
             p.add_header('Content-ID', '<' + cid + '>')
             p.set_payload(base64.b64encode(attach.data()))
 
             self._attachment_parts.append(p)
 
             # modify attachment object
             attach.setData('', mimetype)
             attach.setUri('cid:' + cid, mimetype)
             vattach[i] = attach
             i += 1
 
         self.event.setAttachments(vattach)
 
         part.set_payload(str(self))
 
         part.add_header('Content-Disposition', 'attachment; filename="kolab.xml"')
         part.replace_header('Content-Transfer-Encoding', '8bit')
 
         msg.attach(part)
 
         # append attachment parts
         for p in self._attachment_parts:
             msg.attach(p)
 
         return msg
 
     def to_message_itip(self, from_address, method="REQUEST", participant_status="ACCEPTED", subject=None, message_text=None):
         from email.MIMEMultipart import MIMEMultipart
         from email.MIMEBase import MIMEBase
         from email.MIMEText import MIMEText
         from email.Utils import COMMASPACE, formatdate
 
         # encode unicode strings with quoted-printable
         from email import charset
         charset.add_charset('utf-8', charset.SHORTEST, charset.QP)
 
         msg = MIMEMultipart("alternative")
 
         msg_from = None
         attendees = None
 
         if method == "REPLY":
             # TODO: Make user friendly name <email>
             msg['To'] = self.get_organizer().email()
 
             attendees = self.get_attendees()
             reply_attendees = []
 
             # There's an exception here for delegation (partstat DELEGATED)
             for attendee in attendees:
                 if attendee.get_email() == from_address:
                     # Only the attendee is supposed to be listed in a reply
                     attendee.set_participant_status(participant_status)
                     attendee.set_rsvp(False)
 
                     reply_attendees.append(attendee)
 
                     name = attendee.get_name()
                     email = attendee.get_email()
 
                     if not name:
                         msg_from = email
                     else:
                         msg_from = '"%s" <%s>' % (name, email)
 
                 elif from_address in attendee.get_delegated_from(True):
                     reply_attendees.append(attendee)
 
             # keep only replying (and delegated) attendee(s)
             self._attendees = reply_attendees
             self.event.setAttendees(self._attendees)
 
             if msg_from == None:
                 organizer = self.get_organizer()
                 email = organizer.email()
                 name = organizer.name()
                 if email == from_address:
                     if not name:
                         msg_from = email
                     else:
                         msg_from = '"%s" <%s>' % (name, email)
 
         elif method == "REQUEST":
             organizer = self.get_organizer()
             email = organizer.email()
             name = organizer.name()
             if not name:
                 msg_from = email
             else:
                 msg_from = '"%s" <%s>' % (name, email)
 
         if msg_from == None:
             if from_address == None:
                 log.error(_("No sender specified"))
             else:
                 msg_from = from_address
 
         msg['From'] = utils.str2unicode(msg_from)
 
         msg['Date'] = formatdate(localtime=True)
 
         if subject is None:
             subject = _("Invitation for %s was %s") % (self.get_summary(), participant_status_label(participant_status))
 
         msg['Subject'] = utils.str2unicode(subject)
 
         if message_text is None:
             message_text = _("""This is an automated response to one of your event requests.""")
 
         msg.attach(MIMEText(utils.stripped_message(message_text), _charset='utf-8'))
 
         part = MIMEBase('text', 'calendar', charset='UTF-8', method=method)
         del part['MIME-Version']  # mime parts don't need this
 
         part.set_payload(self.as_string_itip(method=method))
 
         part.add_header('Content-Transfer-Encoding', '8bit')
 
         msg.attach(part)
 
         # restore the original list of attendees
         # attendees being reduced to the replying attendee above
         if attendees is not None:
             self._attendees = attendees
             self.event.setAttendees(self._attendees)
 
         return msg
 
     def is_recurring(self):
         return self.event.recurrenceRule().isValid() or len(self.get_recurrence_dates()) > 0
 
     def to_event_cal(self):
         from kolab.calendaring import EventCal
         return EventCal(self.event)
 
     def get_next_occurence(self, _datetime):
         if not hasattr(self, 'eventcal'):
             self.eventcal = self.to_event_cal()
 
         next_cdatetime = self.eventcal.getNextOccurence(xmlutils.to_cdatetime(_datetime, True))
         next_datetime  = xmlutils.from_cdatetime(next_cdatetime, True) if next_cdatetime is not None else None
 
         # cut infinite recurrence at a reasonable point
         if next_datetime and not self.get_last_occurrence() and next_datetime > xmlutils.to_dt(self._recurrence_end()):
             next_datetime = None
 
         # next_datetime is always a cdatetime, convert to date if necessary
         if next_datetime and not isinstance(self.get_start(), datetime.datetime):
             next_datetime = datetime.date(next_datetime.year, next_datetime.month, next_datetime.day)
 
         return next_datetime
 
     def get_occurence_end_date(self, datetime):
         if not datetime:
             return None
 
         if not hasattr(self, 'eventcal'):
             return None
 
         end_cdatetime = self.eventcal.getOccurenceEndDate(xmlutils.to_cdatetime(datetime, True))
         return xmlutils.from_cdatetime(end_cdatetime, True) if end_cdatetime is not None else None
 
     def get_last_occurrence(self, force=False):
         if not hasattr(self, 'eventcal'):
             self.eventcal = self.to_event_cal()
 
         last = self.eventcal.getLastOccurrence()
         last_datetime = xmlutils.from_cdatetime(last, True) if last is not None else None
 
         # we're forced to return some date
         if last_datetime is None and force:
             last_datetime = self._recurrence_end()
 
         return last_datetime
 
     def get_next_instance(self, datetime):
         next_start = self.get_next_occurence(datetime)
         if next_start:
             instance = Event(from_string=str(self))
             instance.set_start(next_start)
             instance.event.setRecurrenceID(xmlutils.to_cdatetime(next_start), False)
             next_end = self.get_occurence_end_date(next_start)
             if next_end:
                 instance.set_end(next_end)
 
             # unset recurrence rule and exceptions
             instance.set_recurrence(kolabformat.RecurrenceRule())
             instance.event.setExceptions(kolabformat.vectorevent())
             instance.event.setExceptionDates(kolabformat.vectordatetime())
             instance._exceptions = []
             instance._isexception = False
 
             # unset attachments list (only stored in main event)
             instance.event.setAttachments(kolabformat.vectorattachment())
 
             # copy data from matching exception
             # (give precedence to single occurrence exceptions over thisandfuture)
             for exception in self._exceptions:
                 recurrence_id = exception.get_recurrence_id()
                 if recurrence_id == next_start and (not exception.thisandfuture or not instance._isexception):
                     instance = exception
                     instance._isexception = True
                     if not exception.thisandfuture:
                         break
                 elif exception.thisandfuture and next_start > recurrence_id:
                     # TODO: merge exception properties over this instance + adjust start/end with the according offset
                     pass
 
             return instance
 
         return None
 
     def get_instance(self, _datetime):
         # If no timezone information is given, use the one from event start
         if isinstance(_datetime, datetime.datetime) and _datetime.tzinfo == None:
             _start = self.get_start()
             if hasattr(_start, 'tzinfo'):
                 _datetime = _datetime.replace(tzinfo=_start.tzinfo)
 
         if self.is_recurring():
             instance = self.get_next_instance(_datetime - datetime.timedelta(days=1))
             while instance:
                 recurrence_id = instance.get_recurrence_id()
                 if type(recurrence_id) == type(_datetime) and recurrence_id <= _datetime:
                     if xmlutils.dates_equal(recurrence_id, _datetime):
                         return instance
                     instance = self.get_next_instance(instance.get_start())
                 else:
                     break
 
         elif self.has_exceptions():
             for exception in self._exceptions:
                 recurrence_id = exception.get_recurrence_id()
                 if type(recurrence_id) == type(_datetime) and xmlutils.dates_equal(recurrence_id, _datetime):
                     return exception
 
         if self.get_recurrence_id():
             recurrence_id = self.get_recurrence_id()
             if type(recurrence_id) == type(_datetime) and xmlutils.dates_equal(recurrence_id, _datetime):
                 return self
 
         return None
 
     def _recurrence_end(self):
         """
             Determine a reasonable end date for infinitely recurring events
         """
         rrule = self.event.recurrenceRule()
         if rrule.isValid() and rrule.count() < 0 and not rrule.end().isValid():
             now = datetime.datetime.now()
             switch = {
                 kolabformat.RecurrenceRule.Yearly: 100,
                 kolabformat.RecurrenceRule.Monthly: 20
             }
             intvl = switch[rrule.frequency()] if rrule.frequency() in switch else 10
             return self.get_start().replace(year=now.year + intvl)
 
         return xmlutils.from_cdatetime(rrule.end(), True)
 
 
 class EventIntegrityError(Exception):
     def __init__(self, message):
         Exception.__init__(self, message)
 
 class InvalidEventDateError(Exception):
     def __init__(self, message):
         Exception.__init__(self, message)
 
 class InvalidEventStatusError(Exception):
     def __init__(self, message):
         Exception.__init__(self, message)
 
diff --git a/pykolab/xml/note.py b/pykolab/xml/note.py
index 735a098..936315b 100644
--- a/pykolab/xml/note.py
+++ b/pykolab/xml/note.py
@@ -1,138 +1,138 @@
 import pytz
 import datetime
 import kolabformat
 
 from pykolab.translate import _
 from pykolab.xml import utils as xmlutils
 from pykolab.xml.utils import ustr
 
 def note_from_string(string):
     _xml = kolabformat.readNote(string, False)
     return Note(_xml)
 
 def note_from_message(message):
     note = None
     if message.is_multipart():
         for part in message.walk():
             if part.get_content_type() == "application/vnd.kolab+xml":
                 payload = part.get_payload(decode=True)
                 note = note_from_string(payload)
 
             # append attachment parts to Note object
             elif note and 'Content-ID' in part:
                 note._attachment_parts.append(part)
 
     return note
 
 
 class Note(kolabformat.Note):
     type = 'note'
 
     classification_map = {
         'PUBLIC': kolabformat.ClassPublic,
         'PRIVATE': kolabformat.ClassPrivate,
         'CONFIDENTIAL': kolabformat.ClassConfidential,
     }
 
     properties_map = {
         'uid':               'get_uid',
         'summary':           'summary',
         'description':       'description',
         'created':           'get_created',
         'lastmodified-date': 'get_lastmodified',
         'classification':    'get_classification',
         'categories':        'categories',
         'color':             'color',
     }
 
     def __init__(self, *args, **kw):
         self._attachment_parts = []
         kolabformat.Note.__init__(self, *args, **kw)
 
     def get_uid(self):
         uid = self.uid()
         if not uid == '':
             return uid
         else:
             self.__str__()
             return kolabformat.getSerializedUID()
 
     def get_created(self):
         try:
             return xmlutils.from_cdatetime(self.created(), True)
         except ValueError:
             return datetime.datetime.now()
 
     def get_lastmodified(self):
         try:
             _datetime = self.lastModified()
             if _datetime == None or not _datetime.isValid():
                 self.__str__()
         except:
             return datetime.datetime.now(pytz.utc)
 
         return xmlutils.from_cdatetime(self.lastModified(), True)
 
     def set_summary(self, summary):
         self.setSummary(ustr(summary))
 
     def set_description(self, description):
         self.setDescription(ustr(description))
 
     def get_classification(self, translated=True):
         _class = self.classification()
         if translated:
             return self._translate_value(_class, self.classification_map)
         return _class
 
     def set_classification(self, classification):
         if classification in self.classification_map:
             self.setClassification(self.classification_map[classification])
         elif classification in self.classification_map.values():
             self.setClassification(status)
         else:
             raise ValueError(_("Invalid classification %r") % (classification))
 
     def add_category(self, category):
         _categories = self.categories()
         _categories.append(ustr(category))
         self.setCategories(_categories)
 
     def _translate_value(self, val, map):
-        name_map = dict([(v, k) for (k, v) in map.iteritems()])
+        name_map = dict([(v, k) for (k, v) in map.items()])
         return name_map[val] if val in name_map else 'UNKNOWN'
 
     def to_dict(self):
         if not self.isValid():
             return None
 
         data = dict()
 
-        for p, getter in self.properties_map.iteritems():
+        for p, getter in self.properties_map.items():
             val = None
             if hasattr(self, getter):
                 val = getattr(self, getter)()
             if isinstance(val, kolabformat.cDateTime):
                 val = xmlutils.from_cdatetime(val, True)
             elif isinstance(val, kolabformat.vectori):
                 val = [int(x) for x in val]
             elif isinstance(val, kolabformat.vectors):
                 val = [str(x) for x in val]
 
             if val is not None:
                 data[p] = val
 
         return data
 
     def __str__(self):
         xml = kolabformat.writeNote(self)
         error = kolabformat.error()
 
         if error == None or not error:
             return xml
         else:
             raise NoteIntegrityError(kolabformat.errorMessage())
 
 class NoteIntegrityError(Exception):
     def __init__(self, message):
         Exception.__init__(self, message)
diff --git a/pykolab/xml/recurrence_rule.py b/pykolab/xml/recurrence_rule.py
index 52058ca..cbe164d 100644
--- a/pykolab/xml/recurrence_rule.py
+++ b/pykolab/xml/recurrence_rule.py
@@ -1,219 +1,219 @@
 import pytz
 import icalendar
 import datetime
 import kolabformat
 from pykolab.xml import utils as xmlutils
 
 from pykolab.translate import _
 from pykolab.translate import N_
 
 """
     def setFrequency(self, *args): return _kolabformat.RecurrenceRule_setFrequency(self, *args)
     def frequency(self): return _kolabformat.RecurrenceRule_frequency(self)
     def setWeekStart(self, *args): return _kolabformat.RecurrenceRule_setWeekStart(self, *args)
     def weekStart(self): return _kolabformat.RecurrenceRule_weekStart(self)
     def setEnd(self, *args): return _kolabformat.RecurrenceRule_setEnd(self, *args)
     def end(self): return _kolabformat.RecurrenceRule_end(self)
     def setCount(self, *args): return _kolabformat.RecurrenceRule_setCount(self, *args)
     def count(self): return _kolabformat.RecurrenceRule_count(self)
     def setInterval(self, *args): return _kolabformat.RecurrenceRule_setInterval(self, *args)
     def interval(self): return _kolabformat.RecurrenceRule_interval(self)
     def setBysecond(self, *args): return _kolabformat.RecurrenceRule_setBysecond(self, *args)
     def bysecond(self): return _kolabformat.RecurrenceRule_bysecond(self)
     def setByminute(self, *args): return _kolabformat.RecurrenceRule_setByminute(self, *args)
     def byminute(self): return _kolabformat.RecurrenceRule_byminute(self)
     def setByhour(self, *args): return _kolabformat.RecurrenceRule_setByhour(self, *args)
     def byhour(self): return _kolabformat.RecurrenceRule_byhour(self)
     def setByday(self, *args): return _kolabformat.RecurrenceRule_setByday(self, *args)
     def byday(self): return _kolabformat.RecurrenceRule_byday(self)
     def setBymonthday(self, *args): return _kolabformat.RecurrenceRule_setBymonthday(self, *args)
     def bymonthday(self): return _kolabformat.RecurrenceRule_bymonthday(self)
     def setByyearday(self, *args): return _kolabformat.RecurrenceRule_setByyearday(self, *args)
     def byyearday(self): return _kolabformat.RecurrenceRule_byyearday(self)
     def setByweekno(self, *args): return _kolabformat.RecurrenceRule_setByweekno(self, *args)
     def byweekno(self): return _kolabformat.RecurrenceRule_byweekno(self)
     def setBymonth(self, *args): return _kolabformat.RecurrenceRule_setBymonth(self, *args)
     def bymonth(self): return _kolabformat.RecurrenceRule_bymonth(self)
     def isValid(self): return _kolabformat.RecurrenceRule_isValid(self)
 """
 
 frequency_labels = {
     "YEARLY":   N_("Every %d year(s)"),
     "MONTHLY":  N_("Every %d month(s)"),
     "WEEKLY":   N_("Every %d week(s)"),
     "DAILY":    N_("Every %d day(s)"),
     "HOURLY":   N_("Every %d hours"),
     "MINUTELY": N_("Every %d minutes"),
     "SECONDLY": N_("Every %d seconds")
 }
 
 def frequency_label(freq):
     return _(frequency_labels[freq]) if freq in frequency_labels else _(freq)
 
 
 class RecurrenceRule(kolabformat.RecurrenceRule):
     frequency_map = {
         None: kolabformat.RecurrenceRule.FreqNone,
         "YEARLY": kolabformat.RecurrenceRule.Yearly,
         "MONTHLY": kolabformat.RecurrenceRule.Monthly,
         "WEEKLY": kolabformat.RecurrenceRule.Weekly,
         "DAILY": kolabformat.RecurrenceRule.Daily,
         "HOURLY": kolabformat.RecurrenceRule.Hourly,
         "MINUTELY": kolabformat.RecurrenceRule.Minutely,
         "SECONDLY": kolabformat.RecurrenceRule.Secondly
     }
 
     weekday_map = {
         "MO": kolabformat.Monday,
         "TU": kolabformat.Tuesday,
         "WE": kolabformat.Wednesday,
         "TH": kolabformat.Thursday,
         "FR": kolabformat.Friday,
         "SA": kolabformat.Saturday,
         "SU": kolabformat.Sunday
     }
 
     properties_map = {
         'freq': 'get_frequency',
         'interval':  'interval',
         'count':     'count',
         'until':     'end',
         'bymonth':   'bymonth',
         'byday':     'byday',
         'bymonthday':'bymonthday',
         'byyearday': 'byyearday',
         'byweekno':  'byweekno',
         'byhour':    'byhour',
         'byminute':  'byminute',
         'wkst':      'get_weekstart'
     }
 
     def __init__(self, rrule=None):
         if rrule == None:
             kolabformat.RecurrenceRule.__init__(self)
         else:
             kolabformat.RecurrenceRule.__init__(self, rrule)
 
     def from_ical(self, vrecur):
         vectorimap = {
             'BYSECOND': 'setBysecond',
             'BYMINUTE': 'setByminute',
             'BYHOUR': 'setByhour',
             'BYMONTHDAY': 'setBymonthday',
             'BYYEARDAY': 'setByyearday',
             'BYMONTH': 'setBymonth',
         }
 
         settermap = {
             'FREQ': 'set_frequency',
             'INTERVAL': 'set_interval',
             'COUNT': 'set_count',
             'UNTIL': 'set_until',
             'WKST': 'set_weekstart',
             'BYDAY': 'set_byday',
         }
 
         for prop,setter in vectorimap.items():
             if prop in vrecur:
                 getattr(self, setter)([int(v) for v in vrecur[prop]])
 
         for prop,setter in settermap.items():
             if prop in vrecur:
                 getattr(self, setter)(vrecur[prop])
 
     def set_count(self, count):
         if isinstance(count, list):
             count = count[0]
         self.setCount(int(count))
 
     def set_interval(self, val):
         if isinstance(val, list):
             val = val[0]
         self.setInterval(int(val))
 
     def set_frequency(self, freq):
         self._set_map_value(freq, self.frequency_map, 'setFrequency')
 
     def get_frequency(self, translated=False):
         freq = self.frequency()
         if translated:
             return self._translate_value(freq, self.frequency_map)
         return freq
 
     def set_byday(self, bdays):
         daypos = kolabformat.vectordaypos()
         for wday in bdays:
             if isinstance(wday, str):
                 wday = icalendar.vWeekday(wday)
 
             weekday = str(wday)[-2:]
             occurrence = int(wday.relative)
             if str(wday)[0] == '-':
                 occurrence = occurrence * -1
             if weekday in self.weekday_map:
                 daypos.append(kolabformat.DayPos(occurrence, self.weekday_map[weekday]))
         self.setByday(daypos)
 
     def set_weekstart(self, wkst):
         self._set_map_value(wkst, self.weekday_map, 'setWeekStart')
 
     def get_weekstart(self, translated=False):
         wkst = self.weekStart()
         if translated:
             return self._translate_value(wkst, self.weekday_map)
         return wkst
 
     def set_until(self, until):
         if isinstance(until, list):
             until = until[0]
         if isinstance(until, datetime.datetime) or isinstance(until, datetime.date):
             # move into UTC timezone according to RFC 5545
             if isinstance(until, datetime.datetime):
                 until = until.astimezone(pytz.utc)
             self.setEnd(xmlutils.to_cdatetime(until, True))
 
     def _set_map_value(self, val, pmap, setter):
         if isinstance(val, list):
             val = val[0]
         if val in pmap:
             getattr(self, setter)(pmap[val])
         elif val in pmap.values():
             getattr(self, setter)(val)
 
     def _translate_value(self, val, map):
-        name_map = dict([(v, k) for (k, v) in map.iteritems()])
+        name_map = dict([(v, k) for (k, v) in map.items()])
         return name_map[val] if val in name_map else 'UNKNOWN'
 
     def to_ical(self):
         rrule = icalendar.vRecur(dict((k,v) for k,v in self.to_dict(True).items() if not (type(v) == str and v == '' or type(v) == list and len(v) == 0)))
         return rrule
 
     def to_dict(self, raw=False):
         if not self.isValid() or self.frequency() == kolabformat.RecurrenceRule.FreqNone:
             return None
 
         data = dict()
 
-        for p, getter in self.properties_map.iteritems():
+        for p, getter in self.properties_map.items():
             val = None
             args = {}
             if hasattr(self, getter):
                 if getter.startswith('get_'):
                     args = dict(translated=True)
             if hasattr(self, getter):
                 val = getattr(self, getter)(**args)
             if isinstance(val, kolabformat.cDateTime):
                 val = xmlutils.from_cdatetime(val, True)
             elif isinstance(val, kolabformat.vectori):
                 val = [int(x) for x in val]
             elif isinstance(val, kolabformat.vectordaypos):
                 val = ["%s%s" % (str(x.occurence()) if x.occurence() != 0 else '', self._translate_value(x.weekday(), self.weekday_map)) for x in val]
 
             if not raw and isinstance(val, list):
                 val = ",".join(val)
             if val is not None:
                 data[p] = val
 
         return data
 
 
diff --git a/pykolab/xml/utils.py b/pykolab/xml/utils.py
index 3e3fc9e..821295f 100644
--- a/pykolab/xml/utils.py
+++ b/pykolab/xml/utils.py
@@ -1,369 +1,369 @@
 import datetime
 import pytz
 import kolabformat
 from dateutil.tz import tzlocal
 from collections import OrderedDict
 
 from pykolab.translate import _
 from pykolab.translate import N_
 
 
 def to_dt(dt):
     """
         Convert a naive date or datetime to a tz-aware datetime.
     """
 
     if isinstance(dt, datetime.date) and not isinstance(dt, datetime.datetime) or dt is not None and not hasattr(dt, 'hour'):
         dt = datetime.datetime(dt.year, dt.month, dt.day, 0, 0, 0, 0, tzinfo=pytz.utc)
 
     elif isinstance(dt, datetime.datetime):
         if dt.tzinfo == None:
             return dt.replace(tzinfo=pytz.utc)
 
     return dt
 
 
 def from_cdatetime(_cdatetime, with_timezone=True):
     """
         Convert from kolabformat.cDateTime to datetime.date(time)
     """
     if not _cdatetime.isValid():
         return None
 
     (
         year,
         month,
         day,
     ) = (
         _cdatetime.year(),
         _cdatetime.month(),
         _cdatetime.day(),
     )
 
     if _cdatetime.hour() == None or _cdatetime.hour() < 0:
         return datetime.date(year, month, day)
 
     (
         hour,
         minute,
         second
     ) = (
         _cdatetime.hour(),
         _cdatetime.minute(),
         _cdatetime.second()
     )
 
     if with_timezone:
         _timezone = _cdatetime.timezone()
 
         if _timezone == '' or _timezone == None:
             _dt = datetime.datetime(year, month, day, hour, minute, second, tzinfo=pytz.utc)
         else:
             try:
                 # use pytz.timezone.localize() to correctly set DST in tzinfo according to the given date
                 _tz = pytz.timezone(_timezone)
                 _dt = _tz.localize(datetime.datetime(year, month, day, hour, minute, second))
             except:
                 # fall back to local time
                 _dt = datetime.datetime(year, month, day, hour, minute, second)
 
         return _dt
 
     else:
         return datetime.datetime(year, month, day, hour, minute, second)
 
 
 def to_cdatetime(_datetime, with_timezone=True, as_utc=False):
     """
         Convert a datetime.dateime object into a kolabformat.cDateTime instance
     """
     # convert date into UTC timezone
     if as_utc and hasattr(_datetime, "tzinfo"):
         if _datetime.tzinfo is not None:
             _datetime = _datetime.astimezone(pytz.utc)
         else:
             datetime = _datetime.replace(tzinfo=pytz.utc)
         with_timezone = False
 
     # Sometimes we deal with dummy 00000000T000000 values from iCalendar
     # in such cases we end up with datetime.time objects
     if not hasattr(_datetime, 'year'):
         (year, month, day) = (1970, 1, 1)
     else:
         (year, month, day) = (_datetime.year, _datetime.month, _datetime.day)
 
     if hasattr(_datetime, 'hour'):
         (hour, minute, second) = (_datetime.hour, _datetime.minute, _datetime.second)
         _cdatetime = kolabformat.cDateTime(year, month, day, hour, minute, second)
     else:
         _cdatetime = kolabformat.cDateTime(year, month, day)
 
     if with_timezone and hasattr(_datetime, "tzinfo"):
         if _datetime.tzinfo.__str__() in ['UTC','GMT']:
             _cdatetime.setUTC(True)
         else:
             _cdatetime.setTimezone(_datetime.tzinfo.__str__())
 
     if as_utc:
         _cdatetime.setUTC(True)
 
     return _cdatetime
 
 
 def dates_equal(a, b):
     date_format = '%Y%m%d' if isinstance(a, datetime.date) and isinstance(b, datetime.date) else '%Y%m%dT%H%M%S'
     return type(a) == type(b) and a.strftime(date_format) == b.strftime(date_format)
 
 
 def ustr(s):
     """
         Force the given (unicode) string into UTF-8 encoding
     """
     if not isinstance(s, unicode):
         for cs in ['utf-8','latin-1']:
             try:
                 s = unicode(s, cs)
                 break
             except:
                 pass
 
     if isinstance(s, unicode):
         return s.encode('utf-8')
 
     return s
 
 
 property_labels = {
     "name":        N_("Name"),
     "summary":     N_("Summary"),
     "location":    N_("Location"),
     "description": N_("Description"),
     "url":         N_("URL"),
     "status":      N_("Status"),
     "priority":    N_("Priority"),
     "attendee":    N_("Attendee"),
     "start":       N_("Start"),
     "end":         N_("End"),
     "due":         N_("Due"),
     "rrule":       N_("Repeat"),
     "exdate":      N_("Repeat Exception"),
     "organizer":   N_("Organizer"),
     "attach":      N_("Attachment"),
     "alarm":       N_("Alarm"),
     "classification":   N_("Classification"),
     "percent-complete": N_("Progress")
 }
 
 def property_label(propname):
     """
         Return a localized name for the given object property
     """
     return _(property_labels[propname]) if propname in property_labels else _(propname)
 
 
 def property_to_string(propname, value):
     """
         Render a human readable string for the given object property
     """
     date_format = _("%Y-%m-%d")
     time_format = _("%H:%M (%Z)")
     date_time_format = date_format + " " + time_format
     maxlen = 50
 
     if isinstance(value, datetime.datetime):
         return value.strftime(date_time_format)
     elif isinstance(value, datetime.date):
         return value.strftime(date_format)
     elif isinstance(value, int):
         return str(value)
     elif isinstance(value, str):
         if len(value) > maxlen:
             return value[:maxlen].rsplit(' ', 1)[0] + '...'
         return value
     elif isinstance(value, object) and hasattr(value, 'to_dict'):
         value = value.to_dict()
 
     if isinstance(value, dict):
         if propname == 'attendee':
             from . import attendee
             name = value['name'] if 'name' in value and not value['name'] == '' else value['email']
             return "%s, %s" % (name, attendee.participant_status_label(value['partstat']))
 
         elif propname == 'organizer':
             return value['name'] if 'name' in value and not value['name'] == '' else value['email']
 
         elif propname == 'rrule':
             from . import recurrence_rule
             rrule = recurrence_rule.frequency_label(value['freq']) % (value['interval'])
             if 'count' in value and value['count'] > 0:
                 rrule += " " + _("for %d times") % (value['count'])
             elif 'until' in value and (isinstance(value['until'], datetime.datetime) or isinstance(value['until'], datetime.date)):
                 rrule += " " + _("until %s") % (value['until'].strftime(date_format))
             return rrule
 
         elif propname == 'alarm':
             alarm_type_labels = {
                 'DISPLAY': _("Display message"),
                 'EMAIL':   _("Send email"),
                 'AUDIO':   _("Play sound")
             }
             alarm = alarm_type_labels.get(value['action'], "")
             if isinstance(value['trigger'], datetime.datetime):
                 alarm += " @ " + property_to_string('trigger', value['trigger'])
             else:
                 rel = _("%s after") if value['trigger']['related'] == 'END' else _("%s before")
                 offsets = []
                 try:
                     from icalendar import vDuration
                     duration = vDuration.from_ical(value['trigger']['value'].strip('-'))
                 except:
                     return None
 
                 if duration.days:
                     offsets.append(_("%d day(s)") % (duration.days))
                 if duration.seconds:
                     hours = duration.seconds // 3600
                     minutes = duration.seconds % 3600 // 60
                     seconds = duration.seconds % 60
                     if hours:
                         offsets.append(_("%d hour(s)") % (hours))
                     if minutes or (hours and seconds):
                         offsets.append(_("%d minute(s)") % (minutes))
                 if len(offsets):
                     alarm += " " + rel % (", ".join(offsets))
 
             return alarm
 
         elif propname == 'attach':
             return value['label'] if 'label' in value else value['fmttype']
 
     return None
 
 
 def compute_diff(a, b, reduced=False):
     """
         List the differences between two given dicts
     """
     diff = []
 
     properties = a.keys()
     properties.extend([x for x in b if x not in properties])
 
     for prop in properties:
         aa = a[prop] if prop in a else None
         bb = b[prop] if prop in b else None
 
         # compare two lists
         if isinstance(aa, list) or isinstance(bb, list):
             if not isinstance(aa, list):
                 aa = [aa]
             if not isinstance(bb, list):
                 bb = [bb]
 
             (aa, bb) = order_proplists(aa, bb)
             index = 0
             length = max(len(aa), len(bb))
             while index < length:
                 aai = aa[index] if index < len(aa) else None
                 bbi = bb[index] if index < len(bb) else None
                 if not compare_values(aai, bbi):
                     (old, new) = reduce_properties(aai, bbi) if reduced else (aai, bbi)
                     diff.append(OrderedDict([('property', prop), ('index', index), ('old', old), ('new', new)]))
                 index += 1
 
         # the two properties differ
         elif not compare_values(aa, bb):
             if reduced:
                 (old, new) = reduce_properties(aa, bb)
             else:
                 (old, new) = (aa, bb)
             diff.append(OrderedDict([('property', prop), ('old', old), ('new', new)]))
 
     return diff
 
 
 def order_proplists(a, b):
     """
         Orders two lists so that equal entries have the same position
     """
     # nothing to be done here
     if len(a) == 0 and len(b) == 0:
         return (a, b)
 
     base = a
     comp = b
     flip = False
 
     if len(a) > len(b):
         flip = True
         base = b
         comp = a
 
     indices = []
     top = len(comp) + 1
     for bb in comp:
         index = None
 
         # find a matching entry in base
         for j, aa in enumerate(base):
             if compare_values(aa, bb, True):
                 index = j
                 break
 
         # move non-matching items to the end of the list
         if index is None:
             index = top
             top += 1
 
         indices.append(index)
 
     # do sort by indices
     indices, comp = zip(*sorted(zip(indices, comp), key=lambda x: x[0]))
 
     return (comp, base) if flip else (base, comp)
 
 
 def compare_values(aa, bb, partial=False):
     ignore_keys = ['rsvp']
     if not aa.__class__ == bb.__class__:
         return False
 
     if isinstance(aa, dict) and isinstance(bb, dict):
         aa = dict(aa)
         bb = dict(bb)
         # ignore some properties for comparison
         for k in ignore_keys:
             aa.pop(k, None)
             bb.pop(k, None)
 
         # accept partial match
         if partial:
-            for k,v in aa.iteritems():
+            for k,v in aa.items():
                 if k in bb and bb[k] == v:
                     return True
 
             return False
 
     return aa == bb
 
 
 def reduce_properties(aa, bb):
     """
         Compares two given structs and removes equal values in bb
     """
     if not isinstance(aa, dict) or not isinstance(bb, dict):
         return (aa, bb)
 
     properties = aa.keys()
     properties.extend([x for x in bb if x not in properties])
 
     for prop in properties:
         if prop not in aa or prop not in bb:
             continue
         if isinstance(aa[prop], dict) and isinstance(bb[prop], dict):
             (aa[prop], bb[prop]) = reduce_properties(aa[prop], bb[prop])
         if aa[prop] == bb[prop]:
             # del aa[prop]
             del bb[prop]
 
     return (aa, bb)
diff --git a/tests/unit/test-002-attendee.py b/tests/unit/test-002-attendee.py
index 9e7511c..63d6686 100644
--- a/tests/unit/test-002-attendee.py
+++ b/tests/unit/test-002-attendee.py
@@ -1,145 +1,145 @@
 import datetime
 import unittest
 import kolabformat
 
 from pykolab.xml import Attendee
 from pykolab.xml import participant_status_label
 from pykolab.xml.attendee import InvalidAttendeeCutypeError
 
 
 class TestEventXML(unittest.TestCase):
     attendee = Attendee("jane@doe.org")
 
     def assertIsInstance(self, _value, _type):
         if hasattr(unittest.TestCase, 'assertIsInstance'):
             return unittest.TestCase.assertIsInstance(self, _value, _type)
         else:
             if (type(_value)) == _type:
                 return True
             else:
                 raise AssertionError("%s != %s" % (type(_value), _type))
 
     def test_001_minimal(self):
         self.assertIsInstance(self.attendee.__str__(), str)
 
     def test_002_empty_name(self):
         self.assertEqual(self.attendee.get_name(), "")
 
     def test_003_set_name(self):
         name = "Doe, Jane"
         self.attendee.set_name(name)
         self.assertEqual(self.attendee.get_name(), name)
 
     def test_004_default_participant_status(self):
         self.assertEqual(self.attendee.get_participant_status(), 0)
 
     def test_005_participant_status_map_length(self):
         self.assertEqual(len(self.attendee.participant_status_map), 7)
 
     def test_006_participant_status_map_forward_lookup(self):
         # Forward lookups
         self.assertEqual(self.attendee.participant_status_map["NEEDS-ACTION"], 0)
         self.assertEqual(self.attendee.participant_status_map["ACCEPTED"], 1)
         self.assertEqual(self.attendee.participant_status_map["DECLINED"], 2)
         self.assertEqual(self.attendee.participant_status_map["TENTATIVE"], 3)
         self.assertEqual(self.attendee.participant_status_map["DELEGATED"], 4)
         self.assertEqual(self.attendee.participant_status_map["IN-PROCESS"], 5)
         self.assertEqual(self.attendee.participant_status_map["COMPLETED"], 6)
 
     def test_007_participant_status_map_reverse_lookup(self):
         # Reverse lookups
-        self.assertEqual([k for k, v in self.attendee.participant_status_map.iteritems() if v == 0][0], "NEEDS-ACTION")
-        self.assertEqual([k for k, v in self.attendee.participant_status_map.iteritems() if v == 1][0], "ACCEPTED")
-        self.assertEqual([k for k, v in self.attendee.participant_status_map.iteritems() if v == 2][0], "DECLINED")
-        self.assertEqual([k for k, v in self.attendee.participant_status_map.iteritems() if v == 3][0], "TENTATIVE")
-        self.assertEqual([k for k, v in self.attendee.participant_status_map.iteritems() if v == 4][0], "DELEGATED")
-        self.assertEqual([k for k, v in self.attendee.participant_status_map.iteritems() if v == 5][0], "IN-PROCESS")
-        self.assertEqual([k for k, v in self.attendee.participant_status_map.iteritems() if v == 6][0], "COMPLETED")
+        self.assertEqual([k for k, v in self.attendee.participant_status_map.items() if v == 0][0], "NEEDS-ACTION")
+        self.assertEqual([k for k, v in self.attendee.participant_status_map.items() if v == 1][0], "ACCEPTED")
+        self.assertEqual([k for k, v in self.attendee.participant_status_map.items() if v == 2][0], "DECLINED")
+        self.assertEqual([k for k, v in self.attendee.participant_status_map.items() if v == 3][0], "TENTATIVE")
+        self.assertEqual([k for k, v in self.attendee.participant_status_map.items() if v == 4][0], "DELEGATED")
+        self.assertEqual([k for k, v in self.attendee.participant_status_map.items() if v == 5][0], "IN-PROCESS")
+        self.assertEqual([k for k, v in self.attendee.participant_status_map.items() if v == 6][0], "COMPLETED")
 
     def test_008_default_rsvp(self):
         self.assertEqual(self.attendee.get_rsvp(), 0)
 
     def test_009_rsvp_map_length(self):
         self.assertEqual(len(self.attendee.rsvp_map), 2)
 
     def test_010_rsvp_map_forward_lookup_boolean(self):
         self.assertEqual(self.attendee.rsvp_map["TRUE"], True)
         self.assertEqual(self.attendee.rsvp_map["FALSE"], False)
 
     def test_011_rsvp_map_forward_lookup_integer(self):
         self.assertEqual(self.attendee.rsvp_map["TRUE"], 1)
         self.assertEqual(self.attendee.rsvp_map["FALSE"], 0)
 
     def test_012_rsvp_map_reverse_lookup_boolean(self):
-        self.assertEqual([k for k, v in self.attendee.rsvp_map.iteritems() if v is True][0], "TRUE")
-        self.assertEqual([k for k, v in self.attendee.rsvp_map.iteritems() if v is False][0], "FALSE")
+        self.assertEqual([k for k, v in self.attendee.rsvp_map.items() if v is True][0], "TRUE")
+        self.assertEqual([k for k, v in self.attendee.rsvp_map.items() if v is False][0], "FALSE")
 
     def test_013_rsvp_map_reverse_lookup_integer(self):
-        self.assertEqual([k for k, v in self.attendee.rsvp_map.iteritems() if v == 1][0], "TRUE")
-        self.assertEqual([k for k, v in self.attendee.rsvp_map.iteritems() if v == 0][0], "FALSE")
+        self.assertEqual([k for k, v in self.attendee.rsvp_map.items() if v == 1][0], "TRUE")
+        self.assertEqual([k for k, v in self.attendee.rsvp_map.items() if v == 0][0], "FALSE")
 
     def test_014_default_role(self):
         self.assertEqual(self.attendee.get_role(), 0)
 
     def test_015_role_map_length(self):
         self.assertEqual(len(self.attendee.role_map), 4)
 
     def test_016_role_map_forward_lookup(self):
         self.assertEqual(self.attendee.role_map["REQ-PARTICIPANT"], 0)
         self.assertEqual(self.attendee.role_map["CHAIR"], 1)
         self.assertEqual(self.attendee.role_map["OPT-PARTICIPANT"], 2)
         self.assertEqual(self.attendee.role_map["NON-PARTICIPANT"], 3)
 
     def test_017_role_map_reverse_lookup(self):
-        self.assertEqual([k for k, v in self.attendee.role_map.iteritems() if v == 0][0], "REQ-PARTICIPANT")
-        self.assertEqual([k for k, v in self.attendee.role_map.iteritems() if v == 1][0], "CHAIR")
-        self.assertEqual([k for k, v in self.attendee.role_map.iteritems() if v == 2][0], "OPT-PARTICIPANT")
-        self.assertEqual([k for k, v in self.attendee.role_map.iteritems() if v == 3][0], "NON-PARTICIPANT")
+        self.assertEqual([k for k, v in self.attendee.role_map.items() if v == 0][0], "REQ-PARTICIPANT")
+        self.assertEqual([k for k, v in self.attendee.role_map.items() if v == 1][0], "CHAIR")
+        self.assertEqual([k for k, v in self.attendee.role_map.items() if v == 2][0], "OPT-PARTICIPANT")
+        self.assertEqual([k for k, v in self.attendee.role_map.items() if v == 3][0], "NON-PARTICIPANT")
 
     def test_015_cutype_map_length(self):
         self.assertEqual(len(self.attendee.cutype_map), 5)
 
     def test_016_cutype_map_forward_lookup(self):
         self.assertEqual(self.attendee.cutype_map["GROUP"], kolabformat.CutypeGroup)
         self.assertEqual(self.attendee.cutype_map["INDIVIDUAL"], kolabformat.CutypeIndividual)
         self.assertEqual(self.attendee.cutype_map["RESOURCE"], kolabformat.CutypeResource)
         self.assertEqual(self.attendee.cutype_map["ROOM"], kolabformat.CutypeRoom)
         self.assertEqual(self.attendee.cutype_map["UNKNOWN"], kolabformat.CutypeUnknown)
 
     def test_017_cutype_map_reverse_lookup(self):
-        self.assertEqual([k for k, v in self.attendee.cutype_map.iteritems() if v == kolabformat.CutypeGroup][0], "GROUP")
-        self.assertEqual([k for k, v in self.attendee.cutype_map.iteritems() if v == kolabformat.CutypeIndividual][0], "INDIVIDUAL")
-        self.assertEqual([k for k, v in self.attendee.cutype_map.iteritems() if v == kolabformat.CutypeResource][0], "RESOURCE")
-        self.assertEqual([k for k, v in self.attendee.cutype_map.iteritems() if v == kolabformat.CutypeRoom][0], "ROOM")
-        self.assertEqual([k for k, v in self.attendee.cutype_map.iteritems() if v == kolabformat.CutypeUnknown][0], "UNKNOWN")
+        self.assertEqual([k for k, v in self.attendee.cutype_map.items() if v == kolabformat.CutypeGroup][0], "GROUP")
+        self.assertEqual([k for k, v in self.attendee.cutype_map.items() if v == kolabformat.CutypeIndividual][0], "INDIVIDUAL")
+        self.assertEqual([k for k, v in self.attendee.cutype_map.items() if v == kolabformat.CutypeResource][0], "RESOURCE")
+        self.assertEqual([k for k, v in self.attendee.cutype_map.items() if v == kolabformat.CutypeRoom][0], "ROOM")
+        self.assertEqual([k for k, v in self.attendee.cutype_map.items() if v == kolabformat.CutypeUnknown][0], "UNKNOWN")
 
     def test_018_partstat_label(self):
         self.assertEqual(participant_status_label('NEEDS-ACTION'), "Needs Action")
         self.assertEqual(participant_status_label(kolabformat.PartTentative), "Tentatively Accepted")
         self.assertEqual(participant_status_label('UNKNOWN'), "UNKNOWN")
 
     def test_020_to_dict(self):
         name = "Doe, Jane"
         role = 'OPT-PARTICIPANT'
         cutype = 'RESOURCE'
         partstat = 'ACCEPTED'
         self.attendee.set_name(name)
         self.attendee.set_rsvp(True)
         self.attendee.set_role(role)
         self.attendee.set_cutype(cutype)
         self.attendee.set_participant_status(partstat)
 
         data = self.attendee.to_dict()
         self.assertIsInstance(data, dict)
         self.assertEqual(data['role'], role)
         self.assertEqual(data['cutype'], cutype)
         self.assertEqual(data['partstat'], partstat)
         self.assertEqual(data['name'], name)
         self.assertEqual(data['email'], 'jane@doe.org')
         self.assertTrue(data['rsvp'])
 
     def test_030_to_cutype_exception(self):
         self.assertRaises(InvalidAttendeeCutypeError, self.attendee.set_cutype, "DUMMY")
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/wallace/module_invitationpolicy.py b/wallace/module_invitationpolicy.py
index e1f0b52..63d8879 100644
--- a/wallace/module_invitationpolicy.py
+++ b/wallace/module_invitationpolicy.py
@@ -1,1480 +1,1480 @@
 # -*- coding: utf-8 -*-
 # Copyright 2014 Kolab Systems AG (http://www.kolabsys.com)
 #
 # Thomas Bruederli (Kolab Systems) <bruederli@kolabsys.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
 # (at your option) any later version.
 
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU General Public License for more details.
 
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 
 import datetime
 import os
 import random
 import signal
 import tempfile
 import time
 from urlparse import urlparse
 import urllib
 import hashlib
 import traceback
 import re
 
 from email import message_from_string
 from email.parser import Parser
 from email.utils import formataddr
 from email.utils import getaddresses
 
 import modules
 
 import pykolab
 import kolabformat
 
 from pykolab import utils
 from pykolab.auth import Auth
 from pykolab.conf import Conf
 from pykolab.imap import IMAP
 from pykolab.xml import to_dt
 from pykolab.xml import utils as xmlutils
 from pykolab.xml import todo_from_message
 from pykolab.xml import event_from_message
 from pykolab.xml import participant_status_label
 from pykolab.itip import objects_from_message
 from pykolab.itip import check_event_conflict
 from pykolab.itip import send_reply
 from pykolab.translate import _
 
 # define some contstants used in the code below
 ACT_MANUAL         = 1
 ACT_ACCEPT         = 2
 ACT_DELEGATE       = 4
 ACT_REJECT         = 8
 ACT_UPDATE         = 16
 ACT_CANCEL_DELETE  = 32
 ACT_SAVE_TO_FOLDER = 64
 
 COND_IF_AVAILABLE  = 128
 COND_IF_CONFLICT   = 256
 COND_TENTATIVE     = 512
 COND_NOTIFY        = 1024
 COND_FORWARD       = 2048
 COND_TYPE_EVENT    = 4096
 COND_TYPE_TASK     = 8192
 COND_TYPE_ALL      = COND_TYPE_EVENT + COND_TYPE_TASK
 
 ACT_TENTATIVE         = ACT_ACCEPT + COND_TENTATIVE
 ACT_UPDATE_AND_NOTIFY = ACT_UPDATE + COND_NOTIFY
 ACT_SAVE_AND_FORWARD  = ACT_SAVE_TO_FOLDER + COND_FORWARD
 ACT_CANCEL_DELETE_AND_NOTIFY = ACT_CANCEL_DELETE + COND_NOTIFY
 
 FOLDER_TYPE_ANNOTATION = '/vendor/kolab/folder-type'
 
 MESSAGE_PROCESSED = 1
 MESSAGE_FORWARD   = 2
 
 policy_name_map = {
     # policy values applying to all object types
     'ALL_MANUAL':                     ACT_MANUAL + COND_TYPE_ALL,
     'ALL_ACCEPT':                     ACT_ACCEPT + COND_TYPE_ALL,
     'ALL_REJECT':                     ACT_REJECT + COND_TYPE_ALL,
     'ALL_DELEGATE':                   ACT_DELEGATE + COND_TYPE_ALL,  # not implemented
     'ALL_UPDATE':                     ACT_UPDATE + COND_TYPE_ALL,
     'ALL_UPDATE_AND_NOTIFY':          ACT_UPDATE_AND_NOTIFY + COND_TYPE_ALL,
     'ALL_SAVE_TO_FOLDER':             ACT_SAVE_TO_FOLDER + COND_TYPE_ALL,
     'ALL_SAVE_AND_FORWARD':           ACT_SAVE_AND_FORWARD + COND_TYPE_ALL,
     'ALL_CANCEL_DELETE':              ACT_CANCEL_DELETE + COND_TYPE_ALL,
     'ALL_CANCEL_DELETE_AND_NOTIFY':   ACT_CANCEL_DELETE_AND_NOTIFY + COND_TYPE_ALL,
     # event related policy values
     'EVENT_MANUAL':                   ACT_MANUAL + COND_TYPE_EVENT,
     'EVENT_ACCEPT':                   ACT_ACCEPT + COND_TYPE_EVENT,
     'EVENT_TENTATIVE':                ACT_TENTATIVE + COND_TYPE_EVENT,
     'EVENT_REJECT':                   ACT_REJECT + COND_TYPE_EVENT,
     'EVENT_DELEGATE':                 ACT_DELEGATE + COND_TYPE_EVENT,  # not implemented
     'EVENT_UPDATE':                   ACT_UPDATE + COND_TYPE_EVENT,
     'EVENT_UPDATE_AND_NOTIFY':        ACT_UPDATE_AND_NOTIFY + COND_TYPE_EVENT,
     'EVENT_ACCEPT_IF_NO_CONFLICT':    ACT_ACCEPT + COND_IF_AVAILABLE + COND_TYPE_EVENT,
     'EVENT_TENTATIVE_IF_NO_CONFLICT': ACT_ACCEPT + COND_TENTATIVE + COND_IF_AVAILABLE + COND_TYPE_EVENT,
     'EVENT_DELEGATE_IF_CONFLICT':     ACT_DELEGATE + COND_IF_CONFLICT + COND_TYPE_EVENT,
     'EVENT_REJECT_IF_CONFLICT':       ACT_REJECT + COND_IF_CONFLICT + COND_TYPE_EVENT,
     'EVENT_SAVE_TO_FOLDER':           ACT_SAVE_TO_FOLDER + COND_TYPE_EVENT,
     'EVENT_SAVE_AND_FORWARD':         ACT_SAVE_AND_FORWARD + COND_TYPE_EVENT,
     'EVENT_CANCEL_DELETE':            ACT_CANCEL_DELETE + COND_TYPE_EVENT,
     'EVENT_CANCEL_DELETE_AND_NOTIFY': ACT_CANCEL_DELETE_AND_NOTIFY + COND_TYPE_EVENT,
     # task related policy values
     'TASK_MANUAL':                    ACT_MANUAL + COND_TYPE_TASK,
     'TASK_ACCEPT':                    ACT_ACCEPT + COND_TYPE_TASK,
     'TASK_REJECT':                    ACT_REJECT + COND_TYPE_TASK,
     'TASK_DELEGATE':                  ACT_DELEGATE + COND_TYPE_TASK,  # not implemented
     'TASK_UPDATE':                    ACT_UPDATE + COND_TYPE_TASK,
     'TASK_UPDATE_AND_NOTIFY':         ACT_UPDATE_AND_NOTIFY + COND_TYPE_TASK,
     'TASK_SAVE_TO_FOLDER':            ACT_SAVE_TO_FOLDER + COND_TYPE_TASK,
     'TASK_SAVE_AND_FORWARD':          ACT_SAVE_AND_FORWARD + COND_TYPE_TASK,
     'TASK_CANCEL_DELETE':             ACT_CANCEL_DELETE + COND_TYPE_TASK,
     'TASK_CANCEL_DELETE_AND_NOTIFY':  ACT_CANCEL_DELETE_AND_NOTIFY + COND_TYPE_TASK,
     # legacy values
     'ACT_MANUAL':                     ACT_MANUAL + COND_TYPE_ALL,
     'ACT_ACCEPT':                     ACT_ACCEPT + COND_TYPE_ALL,
     'ACT_ACCEPT_IF_NO_CONFLICT':      ACT_ACCEPT + COND_IF_AVAILABLE + COND_TYPE_EVENT,
     'ACT_TENTATIVE':                  ACT_TENTATIVE + COND_TYPE_EVENT,
     'ACT_TENTATIVE_IF_NO_CONFLICT':   ACT_ACCEPT + COND_TENTATIVE + COND_IF_AVAILABLE + COND_TYPE_EVENT,
     'ACT_DELEGATE':                   ACT_DELEGATE + COND_TYPE_ALL,
     'ACT_DELEGATE_IF_CONFLICT':       ACT_DELEGATE + COND_IF_CONFLICT + COND_TYPE_EVENT,
     'ACT_REJECT':                     ACT_REJECT + COND_TYPE_ALL,
     'ACT_REJECT_IF_CONFLICT':         ACT_REJECT + COND_IF_CONFLICT + COND_TYPE_EVENT,
     'ACT_UPDATE':                     ACT_UPDATE + COND_TYPE_ALL,
     'ACT_UPDATE_AND_NOTIFY':          ACT_UPDATE_AND_NOTIFY + COND_TYPE_ALL,
     'ACT_CANCEL_DELETE':              ACT_CANCEL_DELETE + COND_TYPE_ALL,
     'ACT_CANCEL_DELETE_AND_NOTIFY':   ACT_CANCEL_DELETE_AND_NOTIFY + COND_TYPE_ALL,
     'ACT_SAVE_TO_CALENDAR':           ACT_SAVE_TO_FOLDER + COND_TYPE_EVENT,
     'ACT_SAVE_AND_FORWARD':           ACT_SAVE_AND_FORWARD + COND_TYPE_EVENT,
 }
 
-policy_value_map = dict([(v &~ COND_TYPE_ALL, k) for (k, v) in policy_name_map.iteritems()])
+policy_value_map = dict([(v &~ COND_TYPE_ALL, k) for (k, v) in policy_name_map.items()])
 
 object_type_conditons = {
     'event': COND_TYPE_EVENT,
     'task':  COND_TYPE_TASK
 }
 
 log = pykolab.getLogger('pykolab.wallace/invitationpolicy')
 extra_log_params = {'qid': '-'}
 log = pykolab.logger.LoggerAdapter(log, extra_log_params)
 
 conf = pykolab.getConf()
 
 mybasepath = '/var/spool/pykolab/wallace/invitationpolicy/'
 
 auth = None
 imap = None
 write_locks = []
 
 def __init__():
     modules.register('invitationpolicy', execute, description=description())
 
 def accept(filepath):
     new_filepath = os.path.join(
         mybasepath,
         'ACCEPT',
         os.path.basename(filepath)
     )
 
     cleanup()
     os.rename(filepath, new_filepath)
     filepath = new_filepath
     exec('modules.cb_action_ACCEPT(%r, %r)' % ('invitationpolicy',filepath))
 
 def reject(filepath):
     new_filepath = os.path.join(
         mybasepath,
         'REJECT',
         os.path.basename(filepath)
     )
 
     os.rename(filepath, new_filepath)
     filepath = new_filepath
     exec('modules.cb_action_REJECT(%r, %r)' % ('invitationpolicy',filepath))
 
 def description():
     return """Invitation policy execution module."""
 
 def cleanup():
     global auth, imap, write_locks, extra_log_params
 
     log.debug("cleanup(): %r, %r" % (auth, imap), level=8)
 
     extra_log_params['qid'] = '-'
 
     auth.disconnect()
     del auth
 
     # Disconnect IMAP or we lock the mailbox almost constantly
     imap.disconnect()
     del imap
 
     # remove remaining write locks
     for key in write_locks:
         remove_write_lock(key, False)
 
 def execute(*args, **kw):
     global auth, imap, extra_log_params
 
     filepath = args[0]
 
     extra_log_params['qid'] = os.path.basename(filepath)
 
     # (re)set language to default
     pykolab.translate.setUserLanguage(conf.get('kolab','default_locale'))
 
     if not os.path.isdir(mybasepath):
         os.makedirs(mybasepath)
 
     for stage in ['incoming', 'ACCEPT', 'REJECT', 'HOLD', 'DEFER', 'locks']:
         if not os.path.isdir(os.path.join(mybasepath, stage)):
             os.makedirs(os.path.join(mybasepath, stage))
 
     log.debug(_("Invitation policy called for %r, %r") % (args, kw), level=8)
 
     auth = Auth()
     imap = IMAP()
 
     # ignore calls on lock files
     if '/locks/' in filepath or 'stage' in kw and kw['stage'] == 'locks':
         return False
 
     log.debug("Invitation policy executing for %r, %r" % (filepath, '/locks/' in filepath), level=8)
 
     if 'stage' in kw:
         log.debug(_("Issuing callback after processing to stage %s") % (kw['stage']), level=8)
 
         log.debug(_("Testing cb_action_%s()") % (kw['stage']), level=8)
         if hasattr(modules, 'cb_action_%s' % (kw['stage'])):
             log.debug(_("Attempting to execute cb_action_%s()") % (kw['stage']), level=8)
 
             exec(
                 'modules.cb_action_%s(%r, %r)' % (
                     kw['stage'],
                     'invitationpolicy',
                     filepath
                 )
             )
 
             return filepath
     else:
         # Move to incoming
         new_filepath = os.path.join(
             mybasepath,
             'incoming',
             os.path.basename(filepath)
         )
 
         if not filepath == new_filepath:
             log.debug("Renaming %r to %r" % (filepath, new_filepath))
             os.rename(filepath, new_filepath)
             filepath = new_filepath
 
     # parse full message
     message = Parser().parse(open(filepath, 'r'))
 
     # invalid message, skip
     if not message.get('X-Kolab-To'):
         return filepath
 
     recipients = [address for displayname,address in getaddresses(message.get_all('X-Kolab-To'))]
     sender_email = [address for displayname,address in getaddresses(message.get_all('X-Kolab-From'))][0]
 
     any_itips = False
     recipient_email = None
     recipient_emails = []
     recipient_user_dn = None
 
     # An iTip message may contain multiple events. Later on, test if the message
     # is an iTip message by checking the length of this list.
     try:
         itip_events = objects_from_message(message, ['VEVENT','VTODO'], ['REQUEST', 'REPLY', 'CANCEL'])
     except Exception as errmsg:
         log.error(_("Failed to parse iTip objects from message: %r" % (errmsg)))
         itip_events = []
 
     if not len(itip_events) > 0:
         log.info(_("Message is not an iTip message or does not contain any (valid) iTip objects."))
 
     else:
         any_itips = True
         log.debug(_("iTip objects attached to this message contain the following information: %r") % (itip_events), level=8)
 
     # See if any iTip actually allocates a user.
     if any_itips and len([x['uid'] for x in itip_events if 'attendees' in x or 'organizer' in x]) > 0:
         auth.connect()
 
         # we're looking at the first itip object
         itip_event = itip_events[0]
 
         for recipient in recipients:
             recipient_user_dn = user_dn_from_email_address(recipient)
             if recipient_user_dn:
                 receiving_user = auth.get_entry_attributes(None, recipient_user_dn, ['*'])
                 recipient_emails = auth.extract_recipient_addresses(receiving_user)
                 recipient_email = recipient
 
                 # extend with addresses from delegators
                 # (only do this lookup for REPLY messages)
                 receiving_user['_delegated_mailboxes'] = []
 
                 if itip_event['method'] == 'REPLY':
                     for _delegator in auth.list_delegators(recipient_user_dn):
                         if not _delegator['_mailbox_basename'] == None:
                             receiving_user['_delegated_mailboxes'].append(
                                     _delegator['_mailbox_basename'].split('@')[0]
                                 )
 
                 log.debug(_("Recipient emails for %s: %r") % (recipient_user_dn, recipient_emails), level=8)
                 break
 
     if not any_itips:
         log.debug(_("No itips, no users, pass along %r") % (filepath), level=5)
         return filepath
     elif recipient_email is None:
         log.debug(_("iTips, but no users, pass along %r") % (filepath), level=5)
         return filepath
 
     # for replies, the organizer is the recipient
     if itip_event['method'] == 'REPLY':
         # Outlook can send iTip replies without an organizer property
         if 'organizer' in itip_event:
             organizer_mailto = str(itip_event['organizer']).split(':')[-1]
             user_attendees = [organizer_mailto] if organizer_mailto in recipient_emails else []
         else:
             user_attendees = [recipient_email]
 
     else:
         # Limit the attendees to the one that is actually invited with the current message.
         attendees = [str(a).split(':')[-1] for a in (itip_event['attendees'] if 'attendees' in itip_event else [])]
         user_attendees = [a for a in attendees if a in recipient_emails]
 
         if 'organizer' in itip_event:
             sender_email = itip_event['xml'].get_organizer().email()
 
     # abort if no attendee matches the envelope recipient
     if len(user_attendees) == 0:
         log.info(_("No user attendee matching envelope recipient %s, skip message") % (recipient_email))
         return filepath
 
     log.debug(_("Receiving user: %r") % (receiving_user), level=8)
 
     # set recipient_email to the matching attendee mailto: address
     recipient_email = user_attendees[0]
 
     # change gettext language to the preferredlanguage setting of the receiving user
     if 'preferredlanguage' in receiving_user:
         pykolab.translate.setUserLanguage(receiving_user['preferredlanguage'])
 
     # find user's kolabInvitationPolicy settings and the matching policy values
     type_condition = object_type_conditons.get(itip_event['type'], COND_TYPE_ALL)
     policies = get_matching_invitation_policies(receiving_user, sender_email, type_condition)
 
     # select a processing function according to the iTip request method
     method_processing_map = {
         'REQUEST': process_itip_request,
         'REPLY':   process_itip_reply,
         'CANCEL':  process_itip_cancel
     }
 
     done = None
     if itip_event['method'] in method_processing_map:
         processor_func = method_processing_map[itip_event['method']]
 
         # connect as cyrus-admin
         imap.connect()
 
         for policy in policies:
             log.debug(_("Apply invitation policy %r for sender %r") % (policy_value_map[policy], sender_email), level=8)
             done = processor_func(itip_event, policy, recipient_email, sender_email, receiving_user)
 
             # matching policy found
             if done is not None:
                 break
 
             # remove possible write lock from this iteration
             remove_write_lock(get_lock_key(receiving_user, itip_event['uid']))
 
     else:
         log.debug(_("Ignoring '%s' iTip method") % (itip_event['method']), level=8)
 
     # message has been processed by the module, remove it
     if done == MESSAGE_PROCESSED:
         log.debug(_("iTip message %r consumed by the invitationpolicy module") % (message.get('Message-ID')), level=5)
         os.unlink(filepath)
         cleanup()
         return None
 
     # accept message into the destination inbox
     accept(filepath)
 
 
 def process_itip_request(itip_event, policy, recipient_email, sender_email, receiving_user):
     """
         Process an iTip REQUEST message according to the given policy
     """
 
     # if invitation policy is set to MANUAL, pass message along
     if policy & ACT_MANUAL:
         log.info(_("Pass invitation for manual processing"))
         return MESSAGE_FORWARD
 
     try:
         receiving_attendee = itip_event['xml'].get_attendee_by_email(recipient_email)
         log.debug(_("Receiving attendee: %r") % (receiving_attendee.to_dict()), level=8)
     except Exception as errmsg:
         log.error("Could not find envelope attendee: %r" % (errmsg))
         return MESSAGE_FORWARD
 
     # process request to participating attendees with RSVP=TRUE or PARTSTAT=NEEDS-ACTION
     is_task = itip_event['type'] == 'task'
     nonpart = receiving_attendee.get_role() == kolabformat.NonParticipant
     partstat = receiving_attendee.get_participant_status()
     save_object = not nonpart or not partstat == kolabformat.PartNeedsAction
     rsvp = receiving_attendee.get_rsvp()
     scheduling_required = rsvp or partstat == kolabformat.PartNeedsAction
     respond_with = receiving_attendee.get_participant_status(True)
     condition_fulfilled = True
 
     # find existing event in user's calendar
     (existing, master) = find_existing_object(itip_event['uid'], itip_event['type'], itip_event['recurrence-id'], receiving_user, True)
 
     # compare sequence number to determine a (re-)scheduling request
     if existing is not None:
         scheduling_required = itip_event['sequence'] > 0 and itip_event['sequence'] > existing.get_sequence()
         log.debug(_("Scheduling required: %r, for existing %s: %s") % (scheduling_required, existing.type, existing.get_uid()), level=8)
         save_object = True
 
     # if scheduling: check availability (skip that for tasks)
     if scheduling_required:
         if not is_task and policy & (COND_IF_AVAILABLE | COND_IF_CONFLICT):
             condition_fulfilled = check_availability(itip_event, receiving_user)
         if not is_task and policy & COND_IF_CONFLICT:
             condition_fulfilled = not condition_fulfilled
 
         log.debug(_("Precondition for object %r fulfilled: %r") % (itip_event['uid'], condition_fulfilled), level=5)
 
         if existing:
             respond_with = None
 
         if policy & ACT_ACCEPT and condition_fulfilled:
             respond_with = 'TENTATIVE' if policy & COND_TENTATIVE else 'ACCEPTED'
 
         elif policy & ACT_REJECT and condition_fulfilled:
             respond_with = 'DECLINED'
             # TODO: only save declined invitation when a certain config option is set?
 
         elif policy & ACT_DELEGATE and condition_fulfilled:
             # TODO: delegate (but to whom?)
             return None
 
     # auto-update changes if enabled for this user
     elif policy & ACT_UPDATE and existing:
         # compare sequence number to avoid outdated updates
         if not itip_event['sequence'] == existing.get_sequence():
             log.info(_("The iTip request sequence (%r) doesn't match the referred object version (%r). Ignoring.") % (
                 itip_event['sequence'], existing.get_sequence()
             ))
             return None
 
         log.debug(_("Auto-updating %s %r on iTip REQUEST (no re-scheduling)") % (existing.type, existing.uid), level=8)
         save_object = True
         rsvp = False
 
         # retain task status and percent-complete properties from my old copy
         if is_task:
             itip_event['xml'].set_status(existing.get_status())
             itip_event['xml'].set_percentcomplete(existing.get_percentcomplete())
 
         if policy & COND_NOTIFY:
             sender = itip_event['xml'].get_organizer()
             comment = itip_event['xml'].get_comment()
             send_update_notification(itip_event['xml'], receiving_user, existing, False,
                                      sender, comment)
 
     # if RSVP, send an iTip REPLY
     if rsvp or scheduling_required:
         # set attendee's CN from LDAP record if yet missing
         if not receiving_attendee.get_name() and 'cn' in receiving_user:
             receiving_attendee.set_name(receiving_user['cn'])
 
         # send iTip reply
         if respond_with is not None and not respond_with == 'NEEDS-ACTION':
             receiving_attendee.set_participant_status(respond_with)
             send_reply(recipient_email, itip_event, invitation_response_text(itip_event['type']),
                 subject=_('"%(summary)s" has been %(status)s'))
 
         elif policy & ACT_SAVE_TO_FOLDER:
             # copy the invitation into the user's default folder with PARTSTAT=NEEDS-ACTION
             itip_event['xml'].set_attendee_participant_status(receiving_attendee, respond_with or 'NEEDS-ACTION')
             save_object = True
 
         else:
             # policy doesn't match, pass on to next one
             return None
 
     if save_object:
         targetfolder = None
 
         # delete old version from IMAP
         if existing:
             targetfolder = existing._imap_folder
             delete_object(existing)
         elif master and hasattr(master, '_imap_folder'):
             targetfolder = master._imap_folder
             delete_object(master)
 
         if not nonpart or existing:
             # save new copy from iTip
             if store_object(itip_event['xml'], receiving_user, targetfolder, master):
                 if policy & COND_FORWARD:
                     log.debug(_("Forward invitation for notification"), level=5)
                     return MESSAGE_FORWARD
                 else:
                     return MESSAGE_PROCESSED
 
     return None
 
 
 def process_itip_reply(itip_event, policy, recipient_email, sender_email, receiving_user):
     """
         Process an iTip REPLY message according to the given policy
     """
 
     # if invitation policy is set to MANUAL, pass message along
     if policy & ACT_MANUAL:
         log.info(_("Pass reply for manual processing"))
         return MESSAGE_FORWARD
 
     # auto-update is enabled for this user
     if policy & ACT_UPDATE:
         try:
             sender_attendee = itip_event['xml'].get_attendee_by_email(sender_email)
             log.debug(_("Sender Attendee: %r") % (sender_attendee), level=8)
         except Exception as errmsg:
             log.error("Could not find envelope sender attendee: %r" % (errmsg))
             return MESSAGE_FORWARD
 
         # find existing event in user's calendar
         # sets/checks lock to avoid concurrent wallace processes trying to update the same event simultaneously
         (existing, master) = find_existing_object(itip_event['uid'], itip_event['type'], itip_event['recurrence-id'], receiving_user, True)
 
         if existing:
             # compare sequence number to avoid outdated replies?
             if not itip_event['sequence'] == existing.get_sequence():
                 log.info(_("The iTip reply sequence (%r) doesn't match the referred object version (%r). Forwarding to Inbox.") % (
                     itip_event['sequence'], existing.get_sequence()
                 ))
                 remove_write_lock(existing._lock_key)
                 return MESSAGE_FORWARD
 
             log.debug(_("Auto-updating %s %r on iTip REPLY") % (existing.type, existing.uid), level=8)
             updated_attendees = []
             try:
                 existing.set_attendee_participant_status(sender_email, sender_attendee.get_participant_status(), rsvp=False)
                 existing_attendee = existing.get_attendee(sender_email)
                 updated_attendees.append(existing_attendee)
             except Exception as errmsg:
                 log.error("Could not find corresponding attende in organizer's copy: %r" % (errmsg))
 
                 # append delegated-from attendee ?
                 if len(sender_attendee.get_delegated_from()) > 0:
                     existing.add_attendee(sender_attendee)
                     updated_attendees.append(sender_attendee)
                 else:
                     # TODO: accept new participant if ACT_ACCEPT ?
                     remove_write_lock(existing._lock_key)
                     return MESSAGE_FORWARD
 
             # append delegated-to attendee
             if len(sender_attendee.get_delegated_to()) > 0:
                 try:
                     delegatee_email = sender_attendee.get_delegated_to(True)[0]
                     sender_delegatee = itip_event['xml'].get_attendee_by_email(delegatee_email)
                     existing_delegatee = existing.find_attendee(delegatee_email)
 
                     if not existing_delegatee:
                         existing.add_attendee(sender_delegatee)
                         log.debug(_("Add delegatee: %r") % (sender_delegatee.to_dict()), level=8)
                     else:
                         existing_delegatee.copy_from(sender_delegatee)
                         log.debug(_("Update existing delegatee: %r") % (existing_delegatee.to_dict()), level=8)
 
                     updated_attendees.append(sender_delegatee)
 
                     # copy all parameters from replying attendee (e.g. delegated-to, role, etc.)
                     existing_attendee.copy_from(sender_attendee)
                     existing.update_attendees([existing_attendee])
                     log.debug(_("Update delegator: %r") % (existing_attendee.to_dict()), level=8)
 
                 except Exception as errmsg:
                     log.error("Could not find delegated-to attendee: %r" % (errmsg))
 
             # update the organizer's copy of the object
             if update_object(existing, receiving_user, master):
                 if policy & COND_NOTIFY:
                     send_update_notification(existing, receiving_user, existing, True,
                                              sender_attendee, itip_event['xml'].get_comment())
 
                 # update all other attendee's copies
                 if conf.get('wallace','invitationpolicy_autoupdate_other_attendees_on_reply'):
                     propagate_changes_to_attendees_accounts(existing, updated_attendees)
 
                 return MESSAGE_PROCESSED
 
         else:
             log.error(_("The object referred by this reply was not found in the user's folders. Forwarding to Inbox."))
             return MESSAGE_FORWARD
 
     return None
 
 
 def process_itip_cancel(itip_event, policy, recipient_email, sender_email, receiving_user):
     """
         Process an iTip CANCEL message according to the given policy
     """
 
     # if invitation policy is set to MANUAL, pass message along
     if policy & ACT_MANUAL:
         log.info(_("Pass cancellation for manual processing"))
         return MESSAGE_FORWARD
 
     # auto-update the local copy
     if policy & ACT_UPDATE or policy & ACT_CANCEL_DELETE:
         # find existing object in user's folders
         (existing, master) = find_existing_object(itip_event['uid'], itip_event['type'], itip_event['recurrence-id'], receiving_user, True)
         remove_object = policy & ACT_CANCEL_DELETE
 
         if existing:
             # on this-and-future cancel requests, set the recurrence until date on the master event
             if itip_event['recurrence-id'] and master and itip_event['xml'].get_thisandfuture():
                 rrule = master.get_recurrence()
                 rrule.set_count(0)
                 rrule.set_until(existing.get_start() + datetime.timedelta(days=-1))
                 master.set_recurrence(rrule)
                 existing.set_recurrence_id(existing.get_recurrence_id(), True)
                 remove_object = False
 
             # delete the local copy
             if remove_object:
                 # remove exception and register an exdate to the main event
                 if master:
                     log.debug(_("Remove cancelled %s instance %s from %r") % (existing.type, itip_event['recurrence-id'], existing.uid), level=8)
                     master.add_exception_date(existing.get_start())
                     master.del_exception(existing)
                     success = update_object(master, receiving_user)
 
                 # delete main event
                 else:
                     success = delete_object(existing)
 
             # update the local copy with STATUS=CANCELLED
             else:
                 log.debug(_("Update cancelled %s %r with STATUS=CANCELLED") % (existing.type, existing.uid), level=8)
                 existing.set_status('CANCELLED')
                 existing.set_transparency(True)
                 success = update_object(existing, receiving_user, master)
 
             if success:
                 # send cancellation notification
                 if policy & COND_NOTIFY:
                     sender = itip_event['xml'].get_organizer()
                     comment = itip_event['xml'].get_comment()
                     send_cancel_notification(existing, receiving_user, remove_object, sender, comment)
 
                 return MESSAGE_PROCESSED
 
         else:
             log.error(_("The object referred by this cancel request was not found in the user's folders. Forwarding to Inbox."))
             return MESSAGE_FORWARD
 
     return None
 
 
 def user_dn_from_email_address(email_address):
     """
         Resolves the given email address to a Kolab user entity
     """
     global auth
 
     if not auth:
         auth = Auth()
         auth.connect()
 
     # return cached value
     if email_address in user_dn_from_email_address.cache:
         return user_dn_from_email_address.cache[email_address]
 
     local_domains = auth.list_domains()
 
     if local_domains is not None:
         local_domains = list(set(local_domains.keys()))
 
     if not email_address.split('@')[1] in local_domains:
         user_dn_from_email_address.cache[email_address] = None
         return None
 
     log.debug(_("Checking if email address %r belongs to a local user") % (email_address), level=8)
 
     user_dn = auth.find_user_dn(email_address, True)
 
     if isinstance(user_dn, basestring):
         log.debug(_("User DN: %r") % (user_dn), level=8)
     else:
         log.debug(_("No user record(s) found for %r") % (email_address), level=8)
 
     # remember this lookup
     user_dn_from_email_address.cache[email_address] = user_dn
 
     return user_dn
 
 user_dn_from_email_address.cache = {}
 
 
 def get_matching_invitation_policies(receiving_user, sender_email, type_condition=COND_TYPE_ALL):
     # get user's kolabInvitationPolicy settings
     policies = receiving_user['kolabinvitationpolicy'] if 'kolabinvitationpolicy' in receiving_user else []
     if policies and not isinstance(policies, list):
         policies = [policies]
 
     if len(policies) == 0:
         policies = conf.get_list('wallace', 'kolab_invitation_policy')
 
     # match policies agains the given sender_email
     matches = []
     for p in policies:
         if ':' in p:
             (value, domain) = p.split(':', 1)
         else:
             value = p
             domain = ''
 
         if domain == '' or domain == '*' or str(sender_email).endswith(domain):
             value = value.upper()
             if value in policy_name_map:
                 val = policy_name_map[value]
                 # append if type condition matches
                 if val & type_condition:
                     matches.append(val &~ COND_TYPE_ALL)
 
     # add manual as default action
     if len(matches) == 0:
         matches.append(ACT_MANUAL)
 
     return matches
 
 
 def imap_proxy_auth(user_rec):
     """
         Perform IMAP login using proxy authentication with admin credentials
     """
     global imap
 
     mail_attribute = conf.get('cyrus-sasl', 'result_attribute')
     if mail_attribute is None:
         mail_attribute = 'mail'
 
     mail_attribute = mail_attribute.lower()
 
     if mail_attribute not in user_rec:
         log.error(_("User record doesn't have the mailbox attribute %r set" % (mail_attribute)))
         return False
 
     # do IMAP prox auth with the given user
     backend = conf.get('kolab', 'imap_backend')
     admin_login = conf.get(backend, 'admin_login')
     admin_password = conf.get(backend, 'admin_password')
 
     try:
         imap.disconnect()
         imap.connect(login=False)
         imap.login_plain(admin_login, admin_password, user_rec[mail_attribute])
     except Exception as errmsg:
         log.error(_("IMAP proxy authentication failed: %r") % (errmsg))
         return False
 
     return True
 
 
 def list_user_folders(user_rec, _type):
     """
         Get a list of the given user's private calendar/tasks folders
     """
     global imap
 
     # return cached list
     if '_imap_folders' in user_rec:
         return user_rec['_imap_folders']
 
     result = []
 
     if not imap_proxy_auth(user_rec):
         return result
 
     folders = imap.get_metadata('*')
 
     log.debug(
         _("List %r folders for user %r: %r") % (
             _type,
             user_rec['mail'],
             folders
         ),
         level=8
     )
 
     (ns_personal, ns_other, ns_shared) = imap.namespaces()
 
     _folders = {}
 
     # Filter the folders by type relevance
     for folder, metadata in folders.items():
         key = '/shared' + FOLDER_TYPE_ANNOTATION
         if key in metadata:
             if metadata[key].startswith(_type):
                 _folders[folder] = metadata
 
         key = '/private' + FOLDER_TYPE_ANNOTATION
         if key in metadata:
             if metadata[key].startswith(_type):
                 _folders[folder] = metadata
 
     for folder, metadata in _folders.items():
         folder_delegated = False
 
         # Exclude shared and other user's namespace
         #
         # First, test if this is another users folder
         if ns_other is not None and folder.startswith(ns_other):
             # If we have no delegated mailboxes, we can skip this entirely
             if '_delegated_mailboxes' not in user_rec:
                 continue
 
             for _m in user_rec['_delegated_mailboxes']:
                 if folder.startswith(ns_other + _m + '/'):
                     folder_delegated = True
 
             if not folder_delegated:
                 continue
 
         # TODO: list shared folders the user has write privileges ?
         if ns_shared is not None:
             if len([_ns for _ns in ns_shared if folder.startswith(_ns)]) > 0:
                 continue
 
         key = '/shared' + FOLDER_TYPE_ANNOTATION
         if key in metadata:
             if metadata[key].startswith(_type):
                 result.append(folder)
 
         key = '/private' + FOLDER_TYPE_ANNOTATION
         if key in metadata:
             if metadata[key].startswith(_type):
                 result.append(folder)
 
             # store default folder in user record
             if metadata[key].endswith('.default'):
                 user_rec['_default_folder'] = folder
                 continue
 
             # store private and confidential folders in user record
             if metadata[key].endswith('.confidential'):
                 if '_confidential_folder' not in user_rec:
                     user_rec['_confidential_folder'] = folder
 
                 continue
 
             if metadata[key].endswith('.private'):
                 if '_private_folder' not in user_rec:
                     user_rec['_private_folder'] = folder
 
                 continue
 
     # cache with user record
     user_rec['_imap_folders'] = result
 
     return result
 
 
 def find_existing_object(uid, type, recurrence_id, user_rec, lock=False):
     """
         Search user's private folders for the given object (by UID+type)
     """
     global imap
 
     lock_key = None
 
     if lock:
         lock_key = get_lock_key(user_rec, uid)
         set_write_lock(lock_key)
 
     event = None
     master = None
     for folder in list_user_folders(user_rec, type):
         log.debug(_("Searching folder %r for %s %r") % (folder, type, uid), level=8)
         imap.imap.m.select(imap.folder_utf7(folder))
 
         res, data = imap.imap.m.search(None, '(UNDELETED HEADER SUBJECT "%s")' % (uid))
         for num in reversed(data[0].split()):
             res, data = imap.imap.m.fetch(num, '(UID RFC822)')
 
             try:
                 msguid = re.search(r"\WUID (\d+)", data[0][0]).group(1)
             except Exception:
                 log.error(_("No UID found in IMAP response: %r") % (data[0][0]))
                 continue
 
             try:
                 if type == 'task':
                     event = todo_from_message(message_from_string(data[0][1]))
                 else:
                     event = event_from_message(message_from_string(data[0][1]))
 
                 # find instance in a recurring series
                 if recurrence_id and (event.is_recurring() or event.has_exceptions() or event.get_recurrence_id()):
                     master = event
                     event = master.get_instance(recurrence_id)
                     setattr(master, '_imap_folder', folder)
                     setattr(master, '_msguid', msguid)
 
                     # return master, even if instance is not found
                     if not event and master.uid == uid:
                         return (event, master)
 
                 if event is not None:
                     setattr(event, '_imap_folder', folder)
                     setattr(event, '_lock_key', lock_key)
                     setattr(event, '_msguid', msguid)
 
             except Exception:
                 log.error(_("Failed to parse %s from message %s/%s: %s") % (type, folder, num, traceback.format_exc()))
                 event = None
                 master = None
                 continue
 
             if event and event.uid == uid:
                 return (event, master)
 
     if lock_key is not None:
         remove_write_lock(lock_key)
 
     return (event, master)
 
 
 def check_availability(itip_event, receiving_user):
     """
         For the receiving user, determine if the event in question is in conflict.
     """
 
     start = time.time()
     num_messages = 0
     conflict = False
 
     # return previously detected conflict
     if '_conflicts' in itip_event:
         return not itip_event['_conflicts']
 
     for folder in list_user_folders(receiving_user, 'event'):
         log.debug(_("Listing events from folder %r") % (folder), level=8)
         imap.imap.m.select(imap.folder_utf7(folder))
 
         res, data = imap.imap.m.search(None, '(UNDELETED HEADER X-Kolab-Type "application/x-vnd.kolab.event")')
         num_messages += len(data[0].split())
 
         for num in reversed(data[0].split()):
             event = None
             res, data = imap.imap.m.fetch(num, '(RFC822)')
 
             try:
                 event = event_from_message(message_from_string(data[0][1]))
             except Exception as errmsg:
                 log.error(_("Failed to parse event from message %s/%s: %r") % (folder, num, errmsg))
                 continue
 
             if event and event.uid:
                 conflict = check_event_conflict(event, itip_event)
                 if conflict:
                     log.info(_("Existing event %r conflicts with invitation %r") % (event.uid, itip_event['uid']))
                     break
 
         if conflict:
             break
 
     end = time.time()
     log.debug(_("start: %r, end: %r, total: %r, messages: %d") % (start, end, (end-start), num_messages), level=8)
 
     # remember the result of this check for further iterations
     itip_event['_conflicts'] = conflict
 
     return not conflict
 
 
 def set_write_lock(key, wait=True):
     """
         Set a write-lock for the given key and wait if such a lock already exists
     """
     if not os.path.isdir(mybasepath):
         os.makedirs(mybasepath)
     if not os.path.isdir(os.path.join(mybasepath, 'locks')):
         os.makedirs(os.path.join(mybasepath, 'locks'))
 
     filename = os.path.join(mybasepath, 'locks', key + '.lock')
     locktime = 0
 
     if os.path.isfile(filename):
         locktime = os.path.getmtime(filename)
 
     # wait if file lock is in place
     while time.time() < locktime + 300:
         if not wait:
             return False
 
         log.debug(_("%r is locked, waiting...") % (key), level=8)
         time.sleep(0.5)
         locktime = os.path.getmtime(filename) if os.path.isfile(filename) else 0
 
     # touch the file
     if os.path.isfile(filename):
         os.utime(filename, None)
     else:
         open(filename, 'w').close()
 
     # register active lock
     write_locks.append(key)
 
     return True
 
 
 def remove_write_lock(key, update=True):
     """
         Remove the lock file for the given key
     """
     global write_locks
 
     if key is not None:
         file = os.path.join(mybasepath, 'locks', key + '.lock')
         if os.path.isfile(file):
             os.remove(file)
             if update:
                 write_locks = [k for k in write_locks if not k == key]
 
 
 def get_lock_key(user, uid):
     return hashlib.md5("%s/%s" % (user['mail'], uid)).hexdigest()
 
 
 def update_object(object, user_rec, master=None):
     """
         Update the given object in IMAP (i.e. delete + append)
     """
     success = False
     saveobj = object
 
     # updating a single instance only: use master event
     if object.get_recurrence_id() and master:
         saveobj = master
 
     if hasattr(saveobj, '_imap_folder'):
         if delete_object(saveobj):
             saveobj.set_lastmodified()  # update last-modified timestamp
             success = store_object(object, user_rec, saveobj._imap_folder, master)
 
         # remove write lock for this event
         if hasattr(saveobj, '_lock_key') and saveobj._lock_key is not None:
             remove_write_lock(saveobj._lock_key)
 
     return success
 
 
 def store_object(object, user_rec, targetfolder=None, master=None):
     """
         Append the given object to the user's default calendar/tasklist
     """
 
     # find calendar folder to save object to if not specified
     if targetfolder is None:
         targetfolders = list_user_folders(user_rec, object.type)
         oc = object.get_classification()
 
         # use *.confidential/private folder for confidential/private invitations
         if oc == kolabformat.ClassConfidential and '_confidential_folder' in user_rec:
             targetfolder = user_rec['_confidential_folder']
         elif oc == kolabformat.ClassPrivate and '_private_folder' in user_rec:
             targetfolder = user_rec['_private_folder']
         # use *.default folder if exists
         elif '_default_folder' in user_rec:
             targetfolder = user_rec['_default_folder']
         # fallback to any existing folder of specified type
         elif targetfolders is not None and len(targetfolders) > 0:
             targetfolder = targetfolders[0]
 
     if targetfolder is None:
         log.error(_("Failed to save %s: no target folder found for user %r") % (object.type, user_rec['mail']))
         return False
 
     saveobj = object
 
     # updating a single instance only: add exception to master event
     if object.get_recurrence_id() and master:
         object.set_lastmodified()  # update last-modified timestamp
         master.add_exception(object)
         saveobj = master
 
     log.debug(_("Save %s %r to user folder %r") % (saveobj.type, saveobj.uid, targetfolder), level=8)
 
     try:
         imap.imap.m.select(imap.folder_utf7(targetfolder))
         result = imap.imap.m.append(
             imap.folder_utf7(targetfolder),
             None,
             None,
             saveobj.to_message(creator="Kolab Server <wallace@localhost>").as_string()
         )
         return result
 
     except Exception as errmsg:
         log.error(_("Failed to save %s to user folder at %r: %r") % (
             saveobj.type, targetfolder, errmsg
         ))
 
     return False
 
 
 def delete_object(existing):
     """
         Removes the IMAP object with the given UID from a user's folder
     """
     targetfolder = existing._imap_folder
     msguid = existing._msguid if hasattr(existing, '_msguid') else None
 
     try:
         imap.imap.m.select(imap.folder_utf7(targetfolder))
 
         # delete by IMAP UID
         if msguid is not None:
             log.debug(_("Delete %s %r in %r by UID: %r") % (
                 existing.type, existing.uid, targetfolder, msguid
             ), level=8)
 
             imap.imap.m.uid('store', msguid, '+FLAGS', '(\\Deleted)')
         else:
             res, data = imap.imap.m.search(None, '(HEADER SUBJECT "%s")' % existing.uid)
 
             log.debug(_("Delete %s %r in %r: %r") % (
                 existing.type, existing.uid, targetfolder, data
             ), level=8)
 
             for num in data[0].split():
                 imap.imap.m.store(num, '+FLAGS', '(\\Deleted)')
 
         imap.imap.m.expunge()
         return True
 
     except Exception as errmsg:
         log.error(_("Failed to delete %s from folder %r: %r") % (
             existing.type, targetfolder, errmsg
         ))
 
     return False
 
 
 def send_update_notification(object, receiving_user, old=None, reply=True, sender=None, comment=None):
     """
         Send a (consolidated) notification about the current participant status to organizer
     """
     global auth
 
     from email.MIMEText import MIMEText
     from email.Utils import formatdate
     from email.header import Header
     from email import charset
 
     # encode unicode strings with quoted-printable
     charset.add_charset('utf-8', charset.SHORTEST, charset.QP)
 
     organizer = object.get_organizer()
     orgemail = organizer.email()
     orgname = organizer.name()
 
     itip_comment = None
     if comment is not None:
         comment = comment.strip()
 
     if sender is not None and not comment == '':
         itip_comment = _("%s commented: %s") % (_attendee_name(sender), comment)
 
     if reply:
         log.debug(_("Compose participation status summary for %s %r to user %r") % (
             object.type, object.uid, receiving_user['mail']
         ), level=8)
 
         auto_replies_expected = 0
         auto_replies_received = 0
         is_manual_reply = True
         partstats = {'ACCEPTED': [], 'TENTATIVE': [], 'DECLINED': [], 'DELEGATED': [], 'IN-PROCESS': [], 'COMPLETED': [], 'PENDING': []}
 
         for attendee in object.get_attendees():
             parstat = attendee.get_participant_status(True)
             if parstat in partstats:
                 partstats[parstat].append(attendee.get_displayname())
             else:
                 partstats['PENDING'].append(attendee.get_displayname())
 
             # look-up kolabinvitationpolicy for this attendee
             if attendee.get_cutype() == kolabformat.CutypeResource:
                 resource_dns = auth.find_resource(attendee.get_email())
                 if isinstance(resource_dns, list):
                     attendee_dn = resource_dns[0] if len(resource_dns) > 0 else None
                 else:
                     attendee_dn = resource_dns
             else:
                 attendee_dn = user_dn_from_email_address(attendee.get_email())
 
             if attendee_dn:
                 attendee_rec = auth.get_entry_attributes(None, attendee_dn, ['kolabinvitationpolicy'])
                 if is_auto_reply(attendee_rec, orgemail, object.type):
                     auto_replies_expected += 1
                     if not parstat == 'NEEDS-ACTION':
                         auto_replies_received += 1
 
                     if sender is not None and sender.get_email() == attendee.get_email():
                         is_manual_reply = False
 
         # skip notification until we got replies from all automatically responding attendees
         if not is_manual_reply and auto_replies_received < auto_replies_expected:
             log.debug(_("Waiting for more automated replies (got %d of %d); skipping notification") % (
                 auto_replies_received, auto_replies_expected
             ), level=8)
             return
 
         # build notification message body
         roundup = ''
 
         if itip_comment is not None:
             roundup += "\n" + itip_comment
 
-        for status,attendees in partstats.iteritems():
+        for status,attendees in partstats.items():
             if len(attendees) > 0:
                 roundup += "\n" + participant_status_label(status) + ":\n\t" + "\n\t".join(attendees) + "\n"
     else:
         # build notification message body
         roundup = ''
 
         if itip_comment is not None:
             roundup += "\n" + itip_comment
 
         roundup += "\n" + _("Changes submitted by %s have been automatically applied.") % (orgname if orgname else orgemail)
 
         # list properties changed from previous version
         if old:
             diff = xmlutils.compute_diff(old.to_dict(), object.to_dict())
             if len(diff) > 1:
                 roundup += "\n"
                 for change in diff:
                     if not change['property'] in ['created','lastmodified-date','sequence']:
                         new_value = xmlutils.property_to_string(change['property'], change['new']) if change['new'] else _("(removed)")
                         if new_value:
                             roundup += "\n- %s: %s" % (xmlutils.property_label(change['property']), new_value)
 
     # compose different notification texts for events/tasks
     if object.type == 'task':
         message_text = _("""
             The assignment for '%(summary)s' has been updated in your tasklist.
             %(roundup)s
         """) % {
             'summary': object.get_summary(),
             'roundup': roundup
         }
     else:
         message_text = _("""
             The event '%(summary)s' at %(start)s has been updated in your calendar.
             %(roundup)s
         """) % {
             'summary': object.get_summary(),
             'start': xmlutils.property_to_string('start', object.get_start()),
             'roundup': roundup
         }
 
     if object.get_recurrence_id():
         message_text += _("NOTE: This update only refers to this single occurrence!") + "\n"
 
     message_text += "\n" + _("*** This is an automated message. Please do not reply. ***")
 
     # compose mime message
     msg = MIMEText(utils.stripped_message(message_text), _charset='utf-8')
 
     msg['To'] = receiving_user['mail']
     msg['Date'] = formatdate(localtime=True)
     msg['Subject'] = utils.str2unicode(_('"%s" has been updated') % (object.get_summary()))
     msg['From'] = Header(utils.str2unicode('%s' % orgname) if orgname else '')
     msg['From'].append("<%s>" % orgemail)
 
     seed = random.randint(0, 6)
     alarm_after = (seed * 10) + 60
     log.debug(_("Set alarm to %s seconds") % (alarm_after), level=8)
     signal.alarm(alarm_after)
 
     result = modules._sendmail(orgemail, receiving_user['mail'], msg.as_string())
     log.debug(_("Sent update notification to %r: %r") % (receiving_user['mail'], result), level=8)
     signal.alarm(0)
 
 def send_cancel_notification(object, receiving_user, deleted=False, sender=None, comment=None):
     """
         Send a notification about event/task cancellation
     """
     from email.MIMEText import MIMEText
     from email.Utils import formatdate
     from email.header import Header
     from email import charset
 
     # encode unicode strings with quoted-printable
     charset.add_charset('utf-8', charset.SHORTEST, charset.QP)
 
     log.debug(_("Send cancellation notification for %s %r to user %r") % (
         object.type, object.uid, receiving_user['mail']
     ), level=8)
 
     organizer = object.get_organizer()
     orgemail = organizer.email()
     orgname = organizer.name()
 
     # compose different notification texts for events/tasks
     if object.type == 'task':
         message_text = _("The assignment for '%(summary)s' has been cancelled by %(organizer)s.") % {
             'summary': object.get_summary(),
             'organizer': orgname if orgname else orgemail
         }
         if deleted:
             message_text += " " + _("The copy in your tasklist has been removed accordingly.")
         else:
             message_text += " " + _("The copy in your tasklist has been marked as cancelled accordingly.")
     else:
         message_text = _("The event '%(summary)s' at %(start)s has been cancelled by %(organizer)s.") % {
             'summary': object.get_summary(),
             'start': xmlutils.property_to_string('start', object.get_start()),
             'organizer': orgname if orgname else orgemail
         }
         if deleted:
             message_text += " " + _("The copy in your calendar has been removed accordingly.")
         else:
             message_text += " " + _("The copy in your calendar has been marked as cancelled accordingly.")
 
     if comment is not None:
         comment = comment.strip()
 
     if sender is not None and not comment == '':
         message_text += "\n" + _("%s commented: %s") % (_attendee_name(sender), comment)
 
     if object.get_recurrence_id():
         message_text += "\n" + _("NOTE: This cancellation only refers to this single occurrence!")
 
     message_text += "\n\n" + _("*** This is an automated message. Please do not reply. ***")
 
     # compose mime message
     msg = MIMEText(utils.stripped_message(message_text), _charset='utf-8')
 
     msg['To'] = receiving_user['mail']
     msg['Date'] = formatdate(localtime=True)
     msg['Subject'] = utils.str2unicode(_('"%s" has been cancelled') % (object.get_summary()))
     msg['From'] = Header(utils.str2unicode('%s' % orgname) if orgname else '')
     msg['From'].append("<%s>" % orgemail)
 
     seed = random.randint(0, 6)
     alarm_after = (seed * 10) + 60
     log.debug(_("Set alarm to %s seconds") % (alarm_after), level=8)
     signal.alarm(alarm_after)
 
     result = modules._sendmail(orgemail, receiving_user['mail'], msg.as_string())
     log.debug(_("Sent cancel notification to %r: %r") % (receiving_user['mail'], result), level=8)
     signal.alarm(0)
 
 def is_auto_reply(user, sender_email, type):
     accept_available = False
     accept_conflicts = False
     for policy in get_matching_invitation_policies(user, sender_email, object_type_conditons.get(type, COND_TYPE_EVENT)):
         if policy & (ACT_ACCEPT | ACT_REJECT | ACT_DELEGATE):
             if check_policy_condition(policy, True):
                 accept_available = True
             if check_policy_condition(policy, False):
                 accept_conflicts = True
 
         # we have both cases covered by a policy
         if accept_available and accept_conflicts:
             return True
 
         # manual action reached
         if policy & (ACT_MANUAL | ACT_SAVE_TO_FOLDER):
             return False
 
     return False
 
 
 def check_policy_condition(policy, available):
     condition_fulfilled = True
     if policy & (COND_IF_AVAILABLE | COND_IF_CONFLICT):
         condition_fulfilled = available
     if policy & COND_IF_CONFLICT:
         condition_fulfilled = not condition_fulfilled
     return condition_fulfilled
 
 
 def propagate_changes_to_attendees_accounts(object, updated_attendees=None):
     """
         Find and update copies of this object in all attendee's personal folders
     """
     recurrence_id = object.get_recurrence_id()
 
     for attendee in object.get_attendees():
         attendee_user_dn = user_dn_from_email_address(attendee.get_email())
         if attendee_user_dn:
             attendee_user = auth.get_entry_attributes(None, attendee_user_dn, ['*'])
             (attendee_object, master_object) = find_existing_object(object.uid, object.type, recurrence_id, attendee_user, True)  # does IMAP authenticate
             if attendee_object:
                 # find attendee's entry by one of its email addresses
                 attendee_emails = auth.extract_recipient_addresses(attendee_user)
                 for attendee_email in attendee_emails:
                     try:
                         attendee_entry = attendee_object.get_attendee_by_email(attendee_email)
                     except:
                         attendee_entry = None
                     if attendee_entry:
                         break
 
                 # copy all attendees from master object (covers additions and removals)
                 new_attendees = []
                 for a in object.get_attendees():
                     # keep my own entry intact
                     if attendee_entry is not None and attendee_entry.get_email() == a.get_email():
                         new_attendees.append(attendee_entry)
                     else:
                         new_attendees.append(a)
 
                 attendee_object.set_attendees(new_attendees)
 
                 if updated_attendees and not recurrence_id:
                     log.debug("Update Attendees %r for %s" % ([a.get_email()+':'+a.get_participant_status(True) for a in updated_attendees], attendee_user['mail']), level=8)
                     attendee_object.update_attendees(updated_attendees, False)
 
                 success = update_object(attendee_object, attendee_user, master_object)
                 log.debug(_("Updated %s's copy of %r: %r") % (attendee_user['mail'], object.uid, success), level=8)
 
             else:
                 log.debug(_("Attendee %s's copy of %r not found") % (attendee_user['mail'], object.uid), level=8)
 
         else:
             log.debug(_("Attendee %r not found in LDAP") % (attendee.get_email()), level=8)
 
 
 def invitation_response_text(type):
     footer = "\n\n" + _("*** This is an automated message. Please do not reply. ***")
 
     if type == 'task':
         return _("%(name)s has %(status)s your assignment for %(summary)s.") + footer
     else:
         return _("%(name)s has %(status)s your invitation for %(summary)s.") + footer
 
 
 def _attendee_name(attendee):
     # attendee here can be Attendee or ContactReference
     try:
         name = attendee.get_name()
     except Exception:
         name = attendee.name()
 
     if name == '':
         try:
             name = attendee.get_email()
         except Exception:
             name = attendee.email()
 
     return name
diff --git a/wallace/module_signature.py b/wallace/module_signature.py
index dda2d92..c483d93 100644
--- a/wallace/module_signature.py
+++ b/wallace/module_signature.py
@@ -1,254 +1,254 @@
 # -*- coding: utf-8 -*-
 # Copyright 2010-2019 Kolab Systems AG (http://www.kolabsys.com)
 #
 # Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen a kolabsys.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
 # (at your option) any later version.
 
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU General Public License for more details.
 
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 
 import json
 import os
 import re
 import tempfile
 
 from email.encoders import encode_quopri
 from email.parser import Parser
 from email.utils import getaddresses
 
 import modules
 import pykolab
 
 from pykolab.auth import Auth
 from pykolab.translate import _
 
 # pylint: disable=invalid-name
 log = pykolab.getLogger('pykolab.wallace/signature')
 extra_log_params = {'qid': '-'}
 log = pykolab.logger.LoggerAdapter(log, extra_log_params)
 conf = pykolab.getConf()
 
 mybasepath = '/var/spool/pykolab/wallace/signature/'
 
 
 def __init__():
     modules.register('signature', execute, description=description())
 
 
 def description():
     return """Append a signature to messages."""
 
 
 def set_part_content(part, content):
     # Reset old encoding and use quoted-printable (#5414)
     del part['Content-Transfer-Encoding']
     part.set_payload(content)
     encode_quopri(part)
 
     return True
 
 
 def attr_resolve(sender_info, attr):
     try:
         attr, attr_val = attr.split(':')
     except ValueError:
         return None
 
     auth = Auth()
     auth.connect()
 
     values = []
 
     if not isinstance(sender_info[attr], list):
         sender_info[attr] = [sender_info[attr]]
 
     for sender_attr_val in sender_info[attr]:
         values.append(auth.get_entry_attribute(None, sender_attr_val, attr_val))
 
     return ", ".join(values)
 
 
 # pylint: disable=too-many-branches,too-many-locals,too-many-statements
 def execute(*args, **kw):  # noqa: C901
     global extra_log_params
 
     # TODO: Test for correct call.
     filepath = args[0]
 
     extra_log_params['qid'] = os.path.basename(filepath)
 
     if not os.path.isdir(mybasepath):
         os.makedirs(mybasepath)
 
     for stage in ['incoming', 'ACCEPT']:
         if not os.path.isdir(os.path.join(mybasepath, stage)):
             os.makedirs(os.path.join(mybasepath, stage))
 
     if 'stage' in kw:
         log.debug(_("Issuing callback after processing to stage %s") % (kw['stage']), level=8)
         log.debug(_("Testing cb_action_%s()") % (kw['stage']), level=8)
         if hasattr(modules, 'cb_action_%s' % (kw['stage'])):
             log.debug(_("Attempting to execute cb_action_%s()") % (kw['stage']), level=8)
             exec('modules.cb_action_%s(%r, %r)' % (kw['stage'], 'signature', filepath))
             return
 
     log.debug(_("Executing module signature for %r, %r") % (args, kw), level=8)
 
     new_filepath = os.path.join(
         '/var/spool/pykolab/wallace/signature/incoming',
         os.path.basename(filepath)
     )
 
     os.rename(filepath, new_filepath)
     filepath = new_filepath
 
     # parse message
     message = Parser().parse(open(filepath, 'r'))
 
     sender_address = [
         address for displayname, address in getaddresses(message.get_all('X-Kolab-From'))
     ][0]
 
     auth = Auth()
     auth.connect()
 
     sender_dn = auth.find_recipient(sender_address)
     if not sender_dn:
         exec('modules.cb_action_%s(%r, %r)' % ('ACCEPT', 'signature', filepath))
         return
 
     sender_info = auth.get_entry_attributes(None, sender_dn, ['*', 'entrydn', 'manager'])
 
     log.debug("Sender info: %r" % (sender_info), level=7)
 
     signature_rules = conf.get_raw('wallace', 'signature_rules')
 
     if signature_rules:
         signature_rules = json.loads(signature_rules)
 
     log.debug("Signature rules: %r" % (signature_rules), level=7)
 
     signature_html = None
     signature_text = None
 
     sig_html_conf = conf.get_raw('wallace', 'signature_file_html')
     sig_text_conf = conf.get_raw('wallace', 'signature_file_text')
 
     if sig_html_conf and sig_text_conf:
         _sig_html_conf = sig_html_conf % sender_info
         _sig_text_conf = sig_text_conf % sender_info
 
         if not os.path.exists(_sig_html_conf):
             _sig_html_conf = '/etc/kolab/signature.d/default.html'
 
         if not os.path.exists(_sig_text_conf):
             _sig_text_conf = '/etc/kolab/signature.d/default.txt'
 
         if os.path.exists(_sig_html_conf):
             signature_html = open(_sig_html_conf, 'r').read()
 
         if os.path.exists(_sig_text_conf):
             signature_text = open(_sig_text_conf, 'r').read()
 
     if not signature_html and not signature_text and signature_rules is not None:
         for signature_rule in signature_rules:
             try:
-                for attr, regex in signature_rule.iteritems():
+                for attr, regex in signature_rule.items():
                     if attr == "html":
                         if not os.path.exists(signature_rule['html']):
                             raise ValueError
                         continue
 
                     if attr == "text":
                         if not os.path.exists(signature_rule['text']):
                             raise ValueError
                         continue
 
                     if attr in sender_info and re.match(regex, sender_info[attr], flags=re.IGNORECASE):
                         success = False
 
                         while not success:
                             try:
                                 signature_html = open(signature_rule['html'], 'r').read() % sender_info
                                 signature_text = open(signature_rule['text'], 'r').read() % sender_info
 
                                 success = True
 
                             except KeyError as errmsg:
                                 sender_info[errmsg] = attr_resolve(sender_info, errmsg)
             except ValueError:
                 continue
 
     if signature_html is None and signature_text is None:
         exec('modules.cb_action_%s(%r, %r)' % ('ACCEPT', 'signature', filepath))
         return
 
     signature_added = False
 
     try:
         _signature_added = message.get("X-Wallace-Signature")
 
     # pylint: disable=broad-except
     except Exception:
         pass
 
     if _signature_added == "YES":
         exec('modules.cb_action_%s(%r, %r)' % ('ACCEPT','signature', filepath))
         return
 
     for part in message.walk():
         disposition = None
 
         try:
             content_type = part.get_content_type()
 
         # pylint: disable=broad-except
         except Exception:
             continue
 
         try:
             disposition = part.get("Content-Disposition")
 
         # pylint: disable=broad-except
         except Exception:
             pass
 
         log.debug("Walking message part: %s; disposition = %r" % (content_type, disposition), level=8)
 
         if disposition is not None:
             continue
 
         if content_type == "text/plain":
             content = part.get_payload(decode=True)
             content += "\n\n-- \n%s" % (signature_text)
             signature_added = set_part_content(part, content)
 
         elif content_type == "text/html":
             content = part.get_payload(decode=True)
             append = "\n<!-- signature appended by Wallace -->\n" + signature_html
             if "</body>" in content:
                 content = content.replace("</body>", append + "</body>")
             else:
                 content = "<html><body>" + content + append + "</body></html>"
             signature_added = set_part_content(part, content)
 
     if signature_added:
         log.debug("Signature attached.", level=8)
         message.add_header("X-Wallace-Signature", "YES")
 
     (fp, new_filepath) = tempfile.mkstemp(dir="/var/spool/pykolab/wallace/signature/ACCEPT")
     os.write(fp, message.as_string())
     os.close(fp)
     os.unlink(filepath)
 
     exec('modules.cb_action_%s(%r, %r)' % ('ACCEPT','signature', new_filepath))