diff --git a/README.rst b/README.rst
index b5cfb6f7cb852048ef56ce33f11158535d9b24e7..cab317ccfd5fe64f084fd58234fe869ab1965fc6 100644
--- a/README.rst
+++ b/README.rst
@@ -17,6 +17,7 @@ Licence
 ::
 
   Copyright © 2020 Tom Teichler <tom.teichler@teckids.org>
+  Copyright © 2020 Dominik George <dominik.george@teckids.org>
 
   Licenced under the EUPL, version 1.2 or later
 
diff --git a/aleksis/apps/ldap/apps.py b/aleksis/apps/ldap/apps.py
index 88f49092f03fdfe5f69e5e873d3199145a6271d5..66bec2ebbbaf64bf823505a8dbb27b16c7d9e9df 100644
--- a/aleksis/apps/ldap/apps.py
+++ b/aleksis/apps/ldap/apps.py
@@ -3,7 +3,7 @@ from django.db.models.signals import post_save
 
 from aleksis.core.util.apps import AppConfig
 
-from .util.ldap_sync import ldap_sync_from_user
+from .util.ldap_sync import ldap_sync_from_user, update_constance_config_fields
 
 class LDAPConfig(AppConfig):
     name = "aleksis.apps.ldap"
@@ -11,5 +11,8 @@ class LDAPConfig(AppConfig):
 
     def ready(self) -> None:
         super().ready()
+
+        update_constance_config_fields()
+
         User = get_user_model()
         post_save.connect(ldap_sync_from_user, sender=User)
diff --git a/aleksis/apps/ldap/settings.py b/aleksis/apps/ldap/settings.py
index ad56c32b134c878cac9915cb9bd3d054d839b6f0..5bc325fd0805cfbd36d4d7d95523eaca7f8c85b2 100644
--- a/aleksis/apps/ldap/settings.py
+++ b/aleksis/apps/ldap/settings.py
@@ -1,3 +1,4 @@
+from django.apps import apps
 from django.utils.translation import gettext_lazy as _
 
 CONSTANCE_ADDITIONAL_FIELDS = {
@@ -24,7 +25,6 @@ CONSTANCE_CONFIG = {
         "matching-fields-select",
     ),
     "ENABLE_LDAP_GROUP_SYNC": (True, _("Enable ldap group sync"), bool),
-    "LDAP_SYNC_CREATE_GROUPS": (True, _("Create non-existing groups"), bool),
     "LDAP_GROUP_SYNC_FIELD_SHORT_NAME": ("cn", _("Field for short name of group"), str),
     "LDAP_GROUP_SYNC_FIELD_NAME": ("cn", _("Field for name of group"), str),
 }
@@ -34,7 +34,6 @@ CONSTANCE_CONFIG_FIELDSETS = {
         "LDAP_SYNC_ON_UPDATE",
         "LDAP_MATCHING_FIELDS",
         "ENABLE_LDAP_GROUP_SYNC",
-        "LDAP_SYNC_CREATE_GROUPS",
         "LDAP_GROUP_SYNC_FIELD_SHORT_NAME",
         "LDAP_GROUP_SYNC_FIELD_NAME",
     ),
diff --git a/aleksis/apps/ldap/util/ldap_sync.py b/aleksis/apps/ldap/util/ldap_sync.py
index 5312dafbf6ca7975f61ecfc7ceefe3f03a16c24d..b0866bbdd1c33c2a918202c77f279c263019858a 100644
--- a/aleksis/apps/ldap/util/ldap_sync.py
+++ b/aleksis/apps/ldap/util/ldap_sync.py
@@ -1,18 +1,77 @@
 from django.apps import apps
+from django.conf import settings
 from django.contrib.auth import get_user_model
+from django.db.models import fields
 
 from constance import config
 
 
+def setting_name_from_field(model, field):
+    """ Generate a constance setting name from a model field """
+
+    return "LDAP_ADDITIONAL_FIELD_%s_%s" % (model._meta.label, field.name)
+
+
+def syncable_fields(model):
+    """ Collect all fields that can be synced on a model """
+
+    return [field for field in model._meta.fields if field.editable and not field.auto_created]
+
+
+def from_ldap(value, field):
+    """ Convert an LDAP value to the Python type of the target field
+
+    This conversion is prone to error because LDAP deliberately breaks
+    standards to cope with ASN.1 limitations.
+    """
+
+    from ldapdb.models.fields import datetime_from_ldap  # noqa
+
+    # Pre-convert DateTimeField and DateField due to ISO 8601 limitations in RFC 4517
+    if type(field) in (fields.DateField, fields.DateTimeField):
+        # Be opportunistic, but keep old value if conversion fails
+        value = datetime_from_ldap(value) or value
+
+    # Finally, use field's conversion method as default
+    return field.to_python(value)
+
+
+def update_constance_config_fields():
+    """ Auto-generate sync field settings from models """
+
+    Person = apps.get_model("core", "Person")
+    for model in (Person,):
+        # Collect fields that are matchable
+        setting_names = []
+        for field in syncable_fields(model):
+            setting_name = setting_name_from_field(model, field)
+            setting_desc = field.verbose_name
+
+            settings.CONSTANCE_CONFIG[setting_name] = ("", setting_desc, str)
+            setting_names.append(setting_name)
+
+        # Add separate constance section if settings were generated
+        if setting_names:
+            fieldset_name = "LDAP-Sync: Additional fields for %s" % model._meta.verbose_name
+            settings.CONSTANCE_CONFIG_FIELDSETS[fieldset_name] = setting_names
+
+
 def ldap_sync_from_user(sender, instance, created, raw, using, update_fields, **kwargs):
     """ Synchronise Person meta-data and groups from ldap_user on User update. """
 
+    # Semaphore to guard recursive saves within this signal
+    if getattr(instance, "_skip_signal", False):
+        return
+    instance._skip_signal = True
+
     Person = apps.get_model("core", "Person")
     Group = apps.get_model("core", "Group")
 
     if config.ENABLE_LDAP_SYNC and (created or config.LDAP_SYNC_ON_UPDATE) and hasattr(instance, "ldap_user"):
         # Check if there is an existing person connected to the user.
-        if not Person.objects.filter(user=instance).exists():
+        if Person.objects.filter(user=instance).exists():
+            person = instance.person
+        else:
             # Build filter criteria depending on config
             matches = {}
             if "-email" in config.LDAP_MATCHING_FIELDS:
@@ -28,7 +87,16 @@ def ldap_sync_from_user(sender, instance, created, raw, using, update_fields, **
                 return
 
             person.user = instance
-            person.save()
+
+        # Synchronise additional fields if enabled
+        for field in syncable_fields(Person):
+            setting_name = setting_name_from_field(Person, field)
+
+            # Try sync if constance setting for this field is non-empty
+            ldap_field = getattr(config, setting_name, "").lower()
+            if ldap_field and ldap_field in instance.ldap_user.attrs.data:
+                setattr(person, field.name,
+                        from_ldap(instance.ldap_user.attrs.data[ldap_field][0], field))
 
         if config.ENABLE_LDAP_GROUP_SYNC:
             # Resolve Group objects from LDAP group objects
@@ -47,4 +115,14 @@ def ldap_sync_from_user(sender, instance, created, raw, using, update_fields, **
                 group_objects.append(group)
 
             # Replace linked groups of logged-in user completely
-            instance.person.member_of.set(group_objects)
+            person.member_of.set(group_objects)
+
+        try:
+            person.save()
+        except Exception:
+            # Exceptions here are silenced because the synchronisation is optional
+            # FIXME throw warning to user instead
+            pass
+
+    # Remove semaphore
+    del instance._skip_signal
diff --git a/pyproject.toml b/pyproject.toml
index 0c61cba4435b19004dd9bdeab90c0f2381f51d20..697302fd5c33c7e01fa7d809626d589003de3131 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -20,6 +20,7 @@ classifiers = [
 
 [tool.poetry.dependencies]
 python = "^3.7"
+django-ldapdb = "^1.4.0"
 AlekSIS = { path = "../../.." }
 
 [build-system]