2
0
Эх сурвалжийг харах

[SOGo] use python bootstrapper to start SOGo container

FreddleSpl0it 3 сар өмнө
parent
commit
1d482ed425

+ 13 - 12
data/Dockerfiles/sogo/Dockerfile

@@ -27,6 +27,7 @@ RUN echo "Building from repository $SOGO_DEBIAN_REPOSITORY" \
   psmisc \
   wget \
   patch \
+  python3 python3-pip \
   && dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')" \
   && wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch" \
   && chmod +x /usr/local/bin/gosu \
@@ -42,18 +43,18 @@ RUN echo "Building from repository $SOGO_DEBIAN_REPOSITORY" \
   && rm -rf /var/lib/apt/lists/* \
   && touch /etc/default/locale
 
-COPY ./bootstrap-sogo.sh /bootstrap-sogo.sh
-COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.conf
-COPY syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng-redis_slave.conf
-COPY supervisord.conf /etc/supervisor/supervisord.conf
-COPY acl.diff /acl.diff
-COPY navMailcowBtns.diff /navMailcowBtns.diff
-COPY stop-supervisor.sh /usr/local/sbin/stop-supervisor.sh
-COPY docker-entrypoint.sh /
+RUN pip install  --break-system-packages \
+  mysql-connector-python \
+  jinja2
 
-RUN chmod +x /bootstrap-sogo.sh \
-  /usr/local/sbin/stop-supervisor.sh
 
-ENTRYPOINT ["/docker-entrypoint.sh"]
+COPY data/Dockerfiles/bootstrap /bootstrap
+COPY data/Dockerfiles/sogo/syslog-ng.conf /etc/syslog-ng/syslog-ng.conf
+COPY data/Dockerfiles/sogo/syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng-redis_slave.conf
+COPY data/Dockerfiles/sogo/supervisord.conf /etc/supervisor/supervisord.conf
+COPY data/Dockerfiles/sogo/stop-supervisor.sh /usr/local/sbin/stop-supervisor.sh
+COPY data/Dockerfiles/sogo/docker-entrypoint.sh /
 
-CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"]
+RUN chmod +x /usr/local/sbin/stop-supervisor.sh
+
+CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"]

+ 0 - 11
data/Dockerfiles/sogo/acl.diff

@@ -1,11 +0,0 @@
---- /usr/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox    2018-08-17 18:29:57.987504204 +0200
-+++ /usr/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox    2018-08-17 18:29:35.918291298 +0200
-@@ -46,7 +46,7 @@
-           </md-item-template>
-         </md-autocomplete>
-       </div>
--      <md-card ng-repeat="user in acl.users | orderBy:['userClass', 'cn']"
-+      <md-card ng-repeat="user in acl.users | filter:{ userClass: 'normal' } | orderBy:['cn']"
-                class="sg-collapsed"
-                ng-class="{ 'sg-expanded': user.uid == acl.selectedUid }">
-         <a class="md-flex md-button" ng-click="acl.selectUser(user, $event)">

+ 0 - 153
data/Dockerfiles/sogo/bootstrap-sogo.sh

@@ -1,153 +0,0 @@
-#!/bin/bash
-
-# Wait for MySQL to warm-up
-while ! mariadb-admin status --ssl=false --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do
-  echo "Waiting for database to come up..."
-  sleep 2
-done
-
-# Wait until port becomes free and send sig
-until ! nc -z sogo-mailcow 20000;
-do
-  killall -TERM sogod
-  sleep 3
-done
-
-# Wait for updated schema
-DBV_NOW=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT version FROM versions WHERE application = 'db_schema';" -BN)
-DBV_NEW=$(grep -oE '\$db_version = .*;' init_db.inc.php | sed 's/$db_version = //g;s/;//g' | cut -d \" -f2)
-while [[ "${DBV_NOW}" != "${DBV_NEW}" ]]; do
-  echo "Waiting for schema update..."
-  DBV_NOW=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT version FROM versions WHERE application = 'db_schema';" -BN)
-  DBV_NEW=$(grep -oE '\$db_version = .*;' init_db.inc.php | sed 's/$db_version = //g;s/;//g' | cut -d \" -f2)
-  sleep 5
-done
-echo "DB schema is ${DBV_NOW}"
-
-# cat /dev/urandom seems to hang here occasionally and is not recommended anyway, better use openssl
-RAND_PASS=$(openssl rand -base64 16 | tr -dc _A-Z-a-z-0-9)
-
-# Generate plist header with timezone data
-mkdir -p /var/lib/sogo/GNUstep/Defaults/
-cat <<EOF > /var/lib/sogo/GNUstep/Defaults/sogod.plist
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//GNUstep//DTD plist 0.9//EN" "http://www.gnustep.org/plist-0_9.xml">
-<plist version="0.9">
-<dict>
-    <key>OCSAclURL</key>
-    <string>mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_acl</string>
-    <key>SOGoIMAPServer</key>
-    <string>imap://${IPV4_NETWORK}.250:143/?TLS=YES&amp;tlsVerifyMode=none</string>
-    <key>SOGoSieveServer</key>
-    <string>sieve://${IPV4_NETWORK}.250:4190/?TLS=YES&amp;tlsVerifyMode=none</string>
-    <key>SOGoSMTPServer</key>
-    <string>smtp://${IPV4_NETWORK}.253:588/?TLS=YES&amp;tlsVerifyMode=none</string>
-    <key>SOGoTrustProxyAuthentication</key>
-    <string>YES</string>
-    <key>SOGoEncryptionKey</key>
-    <string>${RAND_PASS}</string>
-    <key>OCSAdminURL</key>
-    <string>mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_admin</string>
-    <key>OCSCacheFolderURL</key>
-    <string>mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_cache_folder</string>
-    <key>OCSEMailAlarmsFolderURL</key>
-    <string>mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_alarms_folder</string>
-    <key>OCSFolderInfoURL</key>
-    <string>mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_folder_info</string>
-    <key>OCSSessionsFolderURL</key>
-    <string>mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_sessions_folder</string>
-    <key>OCSStoreURL</key>
-    <string>mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_store</string>
-    <key>SOGoProfileURL</key>
-    <string>mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_user_profile</string>
-    <key>SOGoTimeZone</key>
-    <string>${TZ}</string>
-    <key>domains</key>
-    <dict>
-EOF
-
-# Generate multi-domain setup
-while read -r line gal
-  do
-  echo "        <key>${line}</key>
-        <dict>
-            <key>SOGoMailDomain</key>
-            <string>${line}</string>
-            <key>SOGoUserSources</key>
-            <array>
-                <dict>
-                    <key>MailFieldNames</key>
-                    <array>
-                        <string>aliases</string>
-                        <string>ad_aliases</string>
-                        <string>ext_acl</string>
-                    </array>
-                    <key>KindFieldName</key>
-                    <string>kind</string>
-                    <key>DomainFieldName</key>
-                    <string>domain</string>
-                    <key>MultipleBookingsFieldName</key>
-                    <string>multiple_bookings</string>
-                    <key>listRequiresDot</key>
-                    <string>NO</string>
-                    <key>canAuthenticate</key>
-                    <string>YES</string>
-                    <key>displayName</key>
-                    <string>GAL ${line}</string>
-                    <key>id</key>
-                    <string>${line}</string>
-                    <key>isAddressBook</key>
-                    <string>${gal}</string>
-                    <key>type</key>
-                    <string>sql</string>
-                    <key>userPasswordAlgorithm</key>
-                    <string>${MAILCOW_PASS_SCHEME}</string>
-                    <key>prependPasswordScheme</key>
-                    <string>YES</string>
-                    <key>viewURL</key>
-                    <string>mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/_sogo_static_view</string>
-                </dict>" >> /var/lib/sogo/GNUstep/Defaults/sogod.plist
-  # Generate alternative LDAP authentication dict, when SQL authentication fails
-  # This will nevertheless read attributes from LDAP
-  /etc/sogo/plist_ldap.sh ${line} ${gal} >> /var/lib/sogo/GNUstep/Defaults/sogod.plist
-  echo "            </array>
-        </dict>" >> /var/lib/sogo/GNUstep/Defaults/sogod.plist
-done < <(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain, CASE gal WHEN '1' THEN 'YES' ELSE 'NO' END AS gal FROM domain;" -B -N)
-
-# Generate footer
-echo '    </dict>
-</dict>
-</plist>' >> /var/lib/sogo/GNUstep/Defaults/sogod.plist
-
-# Fix permissions
-chown sogo:sogo -R /var/lib/sogo/
-chmod 600 /var/lib/sogo/GNUstep/Defaults/sogod.plist
-
-# Patch ACLs
-#if [[ ${ACL_ANYONE} == 'allow' ]]; then
-#  #enable any or authenticated targets for ACL
-#  if patch -R -sfN --dry-run /usr/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox < /acl.diff > /dev/null; then
-#    patch -R /usr/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox < /acl.diff;
-#  fi
-#else
-#  #disable any or authenticated targets for ACL
-#  if patch -sfN --dry-run /usr/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox < /acl.diff > /dev/null; then
-#    patch /usr/lib/GNUstep/SOGo/Templates/UIxAclEditor.wox < /acl.diff;
-#  fi
-#fi
-
-if patch -R -sfN --dry-run /usr/lib/GNUstep/SOGo/Templates/UIxTopnavToolbar.wox < /navMailcowBtns.diff > /dev/null; then
-  patch -R /usr/lib/GNUstep/SOGo/Templates/UIxTopnavToolbar.wox < /navMailcowBtns.diff;
-fi
-
-# Rename custom logo, if any
-[[ -f /etc/sogo/sogo-full.svg ]] && mv /etc/sogo/sogo-full.svg /etc/sogo/custom-fulllogo.svg
-
-# Rsync web content
-echo "Syncing web content with named volume"
-rsync -a /usr/lib/GNUstep/SOGo/. /sogo_web/
-
-# Chown backup path
-chown -R sogo:sogo /sogo_backup
-
-exec gosu sogo /usr/sbin/sogod

+ 10 - 13
data/Dockerfiles/sogo/docker-entrypoint.sh

@@ -1,17 +1,5 @@
 #!/bin/bash
 
-if [[ "${SKIP_SOGO}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
-  echo "SKIP_SOGO=y, skipping SOGo..."
-  sleep 365d
-  exit 0
-fi
-
-if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then
-  cp /etc/syslog-ng/syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng.conf
-fi
-
-echo "$TZ" > /etc/timezone
-
 # Run hooks
 for file in /hooks/*; do
   if [ -x "${file}" ]; then
@@ -20,4 +8,13 @@ for file in /hooks/*; do
   fi
 done
 
-exec "$@"
+python3 /bootstrap/main.py
+BOOTSTRAP_EXIT_CODE=$?
+
+if [ $BOOTSTRAP_EXIT_CODE -ne 0 ]; then
+  echo "Bootstrap failed with exit code $BOOTSTRAP_EXIT_CODE. Not starting SOGo."
+  exit $BOOTSTRAP_EXIT_CODE
+fi
+
+echo "Bootstrap succeeded. Starting SOGo..."
+exec gosu sogo /usr/sbin/sogod

+ 0 - 15
data/Dockerfiles/sogo/navMailcowBtns.diff

@@ -1,15 +0,0 @@
-60,65d58
-<                var:ng-click="navButtonClick"
-<                ng-href="/user">
-<       <md-icon>build</md-icon>
-<       <md-tooltip>mailcow <var:string label:value="Preferences"/></md-tooltip>
-<     </md-button>
-<     <md-button class="md-icon-button"
-83c76
-<                onclick="mc_logout();"
----
->                ng-show="::activeUser.path.logoff.length"
-85c78
-<                ng-href="#">
----
->                ng-href="{{::activeUser.path.logoff}}">

+ 1 - 1
data/Dockerfiles/sogo/supervisord.conf

@@ -12,7 +12,7 @@ autostart=true
 priority=1
 
 [program:bootstrap-sogo]
-command=/bootstrap-sogo.sh
+command=/docker-entrypoint.sh
 stdout_logfile=/dev/stdout
 stdout_logfile_maxbytes=0
 stderr_logfile=/dev/stderr

+ 102 - 0
data/conf/sogo/config_templates/UIxTopnavToolbar.wox.j2

@@ -0,0 +1,102 @@
+<?xml version='1.0' standalone='yes'?>
+<container
+    xmlns="http://www.w3.org/1999/xhtml"
+    xmlns:var="http://www.skyrix.com/od/binding"
+    xmlns:const="http://www.skyrix.com/od/constant"
+    xmlns:label="OGo:label">
+
+  <div class="md-toolbar-tools sg-toolbar-group-1" layout="row">
+    <md-button ng-click="toggleLeft()"
+               class="md-icon-button hide show-gt-md"
+               label:aria-label="Toggle Menu">
+      {% raw %}
+      <md-tooltip ng-if="leftIsClose" md-direction="bottom">{{ ::'Reduce' | loc }}</md-tooltip>
+      <md-tooltip ng-else="leftIsClose" md-direction="bottom">{{ ::'Expand' | loc }}</md-tooltip>
+      <md-icon>{{ leftIsClose ? 'fullscreen_exit' : 'fullscreen' }}</md-icon>
+      {% endraw %}
+    </md-button>
+    <md-button ng-click="toggleLeft()"
+               class="md-icon-button hide-gt-md"
+               label:aria-label="Toggle Menu">
+      <md-icon>menu</md-icon>
+    </md-button>
+    <div class="sg-date-group sg-padded hide show-gt-md" layout="column" layout-align="space-between end">
+      <p class="sg-day" ng-bind="currentDay.weekday"><!-- weekday --></p>
+      <p class="sg-month" ng-bind="currentDay.month"><!-- month --></p>
+      <p class="sg-year" ng-bind="currentDay.year"><!-- year --></p>
+    </div>
+    <p class="sg-md-display-3 sg-date-today hide show-gt-md" ng-bind="currentDay.day"><!-- day --></p>
+  </div>
+  <div class="md-toolbar-tools sg-toolbar-group-last" layout="row" layout-align="end center">
+    <md-button class="md-icon-button"
+               ng-show="::activeUser.path.calendar.length"
+               ng-disabled="::baseURL.endsWith('/Calendar/')"
+               var:ng-click="navButtonClick"
+               {% raw %}
+               ng-href="{{::activeUser.path.calendar}}">
+               {% endraw %}
+      <md-tooltip><var:string label:value="Calendar"/></md-tooltip>
+      <md-icon>event</md-icon>
+    </md-button>
+    <md-button class="md-icon-button"
+               ng-disabled="::baseURL.endsWith('/Contacts/')"
+               var:ng-click="navButtonClick"
+               {% raw %}
+               ng-href="{{::activeUser.path.contacts}}">
+               {% endraw %}
+      <md-icon>contacts</md-icon>
+      <md-tooltip><var:string label:value="Address Book"/></md-tooltip>
+    </md-button>
+    <md-button class="md-icon-button"
+               ng-show="::activeUser.path.mail.length"
+               ng-disabled="baseURL.endsWith('/Mail/')"
+               var:ng-click="navButtonClick"
+               {% raw %}
+               ng-href="{{::activeUser.path.mail}}">
+               {% endraw %}
+      <md-icon>email</md-icon>
+      <var:if condition="userHasVacationDisabled">
+        <md-tooltip><var:string label:value="Mail"/></md-tooltip>
+      </var:if>
+      <var:if condition="userHasVacationEnabled">
+        <md-icon class="md-default-theme md-warn md-bg sg-icon--badge" label:aria-label="Vacation message is enabled">forward</md-icon>
+        <md-tooltip><var:string label:value="Vacation message is enabled"/></md-tooltip>
+      </var:if>
+    </md-button>
+    <md-button class="md-icon-button"
+               var:ng-click="navButtonClick"
+               ng-href="/user">
+      <md-icon>build</md-icon>
+      <md-tooltip>mailcow <var:string label:value="Preferences"/></md-tooltip>
+    </md-button>
+    <md-button class="md-icon-button"
+               ng-disabled="::baseURL.endsWith('/Administration')"
+               ng-show="::activeUser.isSuperUser"
+               var:ng-click="navButtonClick"
+               {% raw %}
+               ng-href="{{::activeUser.path.administration}}">
+               {% endraw %}
+      <md-icon>settings_applications</md-icon>
+      <md-tooltip><var:string label:value="Administration"/></md-tooltip>
+    </md-button>
+    <div class="hide-xs" style="width: 40px"
+         ng-show="::activeUser.path.logoff.length"><!-- divider --></div>
+    <md-button class="md-icon-button"
+               ng-show="::activeUser.path.help.length"
+               {% raw %}
+               ng-href="{{::activeUser.path.help}}"
+               {% endraw %}
+               target="_blank">
+      <md-icon>help_outline</md-icon>
+      <md-tooltip><var:string label:value="Help"/></md-tooltip>
+    </md-button>
+    <md-button class="md-icon-button"
+               onclick="mc_logout();"
+               var:ng-click="navButtonClick"
+               ng-href="#">
+      <md-icon>settings_power</md-icon>
+      <md-tooltip><var:string label:value="Disconnect"/></md-tooltip>
+    </md-button>
+  </div>
+
+</container>

+ 107 - 0
data/conf/sogo/config_templates/sogod.plist.j2

@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//GNUstep//DTD plist 0.9//EN" "http://www.gnustep.org/plist-0_9.xml">
+<plist version="0.9">
+<dict>
+    <key>OCSAclURL</key>
+    <string>mysql://{{DBUSER}}:{{DBPASS}}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/{{DBNAME}}/sogo_acl</string>
+    <key>SOGoIMAPServer</key>
+    <string>imap://{{IPV4_NETWORK}}.250:143/?TLS=YES&amp;tlsVerifyMode=none</string>
+    <key>SOGoSieveServer</key>
+    <string>sieve://{{IPV4_NETWORK}}.250:4190/?TLS=YES&amp;tlsVerifyMode=none</string>
+    <key>SOGoSMTPServer</key>
+    <string>smtp://{{IPV4_NETWORK}}.253:588/?TLS=YES&amp;tlsVerifyMode=none</string>
+    <key>SOGoTrustProxyAuthentication</key>
+    <string>YES</string>
+    <key>SOGoEncryptionKey</key>
+    <string>{{RAND_PASS}}</string>
+    <key>OCSAdminURL</key>
+    <string>mysql://{{DBUSER}}:{{DBPASS}}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/{{DBNAME}}/sogo_admin</string>
+    <key>OCSCacheFolderURL</key>
+    <string>mysql://{{DBUSER}}:{{DBPASS}}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/{{DBNAME}}/sogo_cache_folder</string>
+    <key>OCSEMailAlarmsFolderURL</key>
+    <string>mysql://{{DBUSER}}:{{DBPASS}}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/{{DBNAME}}/sogo_alarms_folder</string>
+    <key>OCSFolderInfoURL</key>
+    <string>mysql://{{DBUSER}}:{{DBPASS}}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/{{DBNAME}}/sogo_folder_info</string>
+    <key>OCSSessionsFolderURL</key>
+    <string>mysql://{{DBUSER}}:{{DBPASS}}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/{{DBNAME}}/sogo_sessions_folder</string>
+    <key>OCSStoreURL</key>
+    <string>mysql://{{DBUSER}}:{{DBPASS}}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/{{DBNAME}}/sogo_store</string>
+    <key>SOGoProfileURL</key>
+    <string>mysql://{{DBUSER}}:{{DBPASS}}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/{{DBNAME}}/sogo_user_profile</string>
+    <key>SOGoTimeZone</key>
+    <string>{{TZ}}</string>
+    <key>domains</key>
+  <dict>
+  {% for domain in SQL_DOMAINS %}
+    <key>{{ domain.domain }}</key>
+    <dict>
+      <key>SOGoMailDomain</key>
+      <string>{{ domain.domain }}</string>
+      <key>SOGoUserSources</key>
+      <array>
+        <dict>
+          <key>MailFieldNames</key>
+          <array>
+            <string>aliases</string>
+            <string>ad_aliases</string>
+            <string>ext_acl</string>
+          </array>
+          <key>KindFieldName</key>
+          <string>kind</string>
+          <key>DomainFieldName</key>
+          <string>domain</string>
+          <key>MultipleBookingsFieldName</key>
+          <string>multiple_bookings</string>
+          <key>listRequiresDot</key>
+          <string>NO</string>
+          <key>canAuthenticate</key>
+          <string>YES</string>
+          <key>displayName</key>
+          <string>GAL {{ domain.domain }}</string>
+          <key>id</key>
+          <string>{{ domain.domain }}</string>
+          <key>isAddressBook</key>
+          <string>{{ domain.gal_status }}</string>
+          <key>type</key>
+          <string>sql</string>
+          <key>userPasswordAlgorithm</key>
+          <string>{{ MAILCOW_PASS_SCHEME }}</string>
+          <key>prependPasswordScheme</key>
+          <string>YES</string>
+          <key>viewURL</key>
+          <string>mysql://{{ DBUSER }}:{{ DBPASS }}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/{{ DBNAME }}/_sogo_static_view</string>
+        </dict>
+        {% if IAM_SETTINGS.authsource == "ldap" and domain.ldap_gal %}
+        <dict>
+            <key>canAuthenticate</key>
+            <string>YES</string>
+            <key>id</key>
+            <string>{{ domain.domain }}_ldap</string>
+            <key>isAddressBook</key>
+            <string>"{{ domain.gal_status }}"</string>
+            <key>IDFieldName</key>
+            <string>mail</string>
+            <key>UIDFieldName</key>
+            <string>uid</string>
+            <key>bindFields</key>
+            <array>
+                <string>mail</string>
+            </array>
+            <key>type</key>
+            <string>ldap</string>
+            <key>bindDN</key>
+            <string>{{ IAM_SETTINGS.binddn }}</string>
+            <key>bindPassword</key>
+            <string>{{ IAM_SETTINGS.bindpass }}</string>
+            <key>baseDN</key>
+            <string>{{ IAM_SETTINGS.basedn }}</string>
+            <key>hostname</key>
+            <string>{{ IAM_SETTINGS.ldap_url }}</string>
+        </dict>
+        {% endif%}
+      </array>
+    </dict>
+  {% endfor %}
+  </dict>
+</dict>
+</plist>

+ 17 - 4
data/web/inc/functions.mailbox.inc.php

@@ -538,6 +538,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
           $relay_unknown_only = (isset($_data['relay_unknown_only'])) ? intval($_data['relay_unknown_only']) : $DOMAIN_DEFAULT_ATTRIBUTES['relay_unknown_only'];
           $backupmx = (isset($_data['backupmx'])) ? intval($_data['backupmx']) : $DOMAIN_DEFAULT_ATTRIBUTES['backupmx'];
           $gal = (isset($_data['gal'])) ? intval($_data['gal']) : $DOMAIN_DEFAULT_ATTRIBUTES['gal'];
+          $ldap_gal = (isset($_data['gal'])) ? intval($_data['ldap_gal']) : $DOMAIN_DEFAULT_ATTRIBUTES['ldap_gal'];
           if ($relay_all_recipients == 1) {
             $backupmx = '1';
           }
@@ -593,8 +594,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
             ':domain' => '%@' . $domain
           ));
           // save domain
-          $stmt = $pdo->prepare("INSERT INTO `domain` (`domain`, `description`, `aliases`, `mailboxes`, `defquota`, `maxquota`, `quota`, `backupmx`, `gal`, `active`, `relay_unknown_only`, `relay_all_recipients`)
-            VALUES (:domain, :description, :aliases, :mailboxes, :defquota, :maxquota, :quota, :backupmx, :gal, :active, :relay_unknown_only, :relay_all_recipients)");
+          $stmt = $pdo->prepare("INSERT INTO `domain` (`domain`, `description`, `aliases`, `mailboxes`, `defquota`, `maxquota`, `quota`, `backupmx`, `gal`, `ldap_gal`, `active`, `relay_unknown_only`, `relay_all_recipients`)
+            VALUES (:domain, :description, :aliases, :mailboxes, :defquota, :maxquota, :quota, :backupmx, :gal, :ldap_gal, :active, :relay_unknown_only, :relay_all_recipients)");
           $stmt->execute(array(
             ':domain' => $domain,
             ':description' => $description,
@@ -605,6 +606,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
             ':quota' => $quota,
             ':backupmx' => $backupmx,
             ':gal' => $gal,
+            ':ldap_gal' => $ldap_gal,
             ':active' => $active,
             ':relay_unknown_only' => $relay_unknown_only,
             ':relay_all_recipients' => $relay_all_recipients
@@ -1551,9 +1553,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
           $attr['rl_value']                   = (!empty($_data['rl_value'])) ? $_data['rl_value'] : "";
           $attr['active']                     = isset($_data['active']) ? intval($_data['active']) : 1;
           $attr['gal']                        = (isset($_data['gal'])) ? intval($_data['gal']) : 1;
+          $attr['ldap_gal']                   = (isset($_data['ldap_gal'])) ? intval($_data['ldap_gal']) : 1;
           $attr['backupmx']                   = (isset($_data['backupmx'])) ? intval($_data['backupmx']) : 0;
           $attr['relay_all_recipients']       = (isset($_data['relay_all_recipients'])) ? intval($_data['relay_all_recipients']) : 0;
-          $attr['relay_unknown_only']          = (isset($_data['relay_unknown_only'])) ? intval($_data['relay_unknown_only']) : 0;
+          $attr['relay_unknown_only']         = (isset($_data['relay_unknown_only'])) ? intval($_data['relay_unknown_only']) : 0;
           $attr['dkim_selector']              = (isset($_data['dkim_selector'])) ? $_data['dkim_selector'] : "dkim";
           $attr['key_size']                   = isset($_data['key_size']) ? intval($_data['key_size']) : 2048;
 
@@ -2626,6 +2629,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
               $is_now = mailbox('get', 'domain_details', $domain);
               if (!empty($is_now)) {
                 $gal                  = (isset($_data['gal'])) ? intval($_data['gal']) : $is_now['gal'];
+                $ldap_gal             = (isset($_data['ldap_gal'])) ? intval($_data['ldap_gal']) : $is_now['ldap_gal'];
                 $description          = (!empty($_data['description']) && isset($_SESSION['acl']['domain_desc']) && $_SESSION['acl']['domain_desc'] == "1") ? $_data['description'] : $is_now['description'];
                 (int)$relayhost       = (isset($_data['relayhost']) && isset($_SESSION['acl']['domain_relayhost']) && $_SESSION['acl']['domain_relayhost'] == "1") ? intval($_data['relayhost']) : intval($is_now['relayhost']);
                 $tags                 = (is_array($_data['tags']) ? $_data['tags'] : array());
@@ -2642,10 +2646,12 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
               $stmt = $pdo->prepare("UPDATE `domain` SET
               `description` = :description,
               `gal` = :gal
+              `ldap_gal` = :ldap_gal
                 WHERE `domain` = :domain");
               $stmt->execute(array(
                 ':description' => $description,
                 ':gal' => $gal,
+                ':ldap_gal' => $ldap_gal,
                 ':domain' => $domain
               ));
               // save tags
@@ -2678,6 +2684,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
                 $active               = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active'];
                 $backupmx             = (isset($_data['backupmx'])) ? intval($_data['backupmx']) : $is_now['backupmx'];
                 $gal                  = (isset($_data['gal'])) ? intval($_data['gal']) : $is_now['gal'];
+                $ldap_gal             = (isset($_data['ldap_gal'])) ? intval($_data['ldap_gal']) : $is_now['ldap_gal'];
                 $relay_all_recipients = (isset($_data['relay_all_recipients'])) ? intval($_data['relay_all_recipients']) : $is_now['relay_all_recipients'];
                 $relay_unknown_only   = (isset($_data['relay_unknown_only'])) ? intval($_data['relay_unknown_only']) : $is_now['relay_unknown_only'];
                 $relayhost            = (isset($_data['relayhost'])) ? intval($_data['relayhost']) : $is_now['relayhost'];
@@ -2792,6 +2799,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
               `relay_unknown_only` = :relay_unknown_only,
               `backupmx` = :backupmx,
               `gal` = :gal,
+              `ldap_gal` = :ldap_gal,
               `active` = :active,
               `quota` = :quota,
               `defquota` = :defquota,
@@ -2806,6 +2814,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
                 ':relay_unknown_only' => $relay_unknown_only,
                 ':backupmx' => $backupmx,
                 ':gal' => $gal,
+                ':ldap_gal' => $ldap_gal,
                 ':active' => $active,
                 ':quota' => $quota,
                 ':defquota' => $defquota,
@@ -2890,9 +2899,10 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
             $attr['rl_value']                   = (!empty($_data['rl_value'])) ? $_data['rl_value'] : "";
             $attr['active']                     = isset($_data['active']) ? intval($_data['active']) : 1;
             $attr['gal']                        = (isset($_data['gal'])) ? intval($_data['gal']) : 1;
+            $attr['ldap_gal']                   = (isset($_data['ldap_gal'])) ? intval($_data['ldap_gal']) : 0;
             $attr['backupmx']                   = (isset($_data['backupmx'])) ? intval($_data['backupmx']) : 0;
             $attr['relay_all_recipients']       = (isset($_data['relay_all_recipients'])) ? intval($_data['relay_all_recipients']) : 0;
-            $attr['relay_unknown_only']          = (isset($_data['relay_unknown_only'])) ? intval($_data['relay_unknown_only']) : 0;
+            $attr['relay_unknown_only']         = (isset($_data['relay_unknown_only'])) ? intval($_data['relay_unknown_only']) : 0;
             $attr['dkim_selector']              = (isset($_data['dkim_selector'])) ? $_data['dkim_selector'] : "dkim";
             $attr['key_size']                   = isset($_data['key_size']) ? intval($_data['key_size']) : 2048;
 
@@ -4673,6 +4683,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
               `relay_unknown_only`,
               `backupmx`,
               `gal`,
+              `ldap_gal`,
               `active`
                 FROM `domain` WHERE `domain`= :domain");
           $stmt->execute(array(
@@ -4733,6 +4744,8 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
           $domaindata['backupmx_int'] = $row['backupmx'];
           $domaindata['gal'] = $row['gal'];
           $domaindata['gal_int'] = $row['gal'];
+          $domaindata['ldap_gal'] = $row['ldap_gal'];
+          $domaindata['ldap_gal_int'] = $row['ldap_gal'];
           $domaindata['rl'] = $rl;
           $domaindata['active'] = $row['active'];
           $domaindata['active_int'] = $row['active'];

+ 17 - 1
data/web/inc/init_db.inc.php

@@ -4,7 +4,7 @@ function init_db_schema()
   try {
     global $pdo;
 
-    $db_version = "27012025_1555";
+    $db_version = "16052025_1245";
 
     $stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
     $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
@@ -259,6 +259,7 @@ function init_db_schema()
           "relayhost" => "VARCHAR(255) NOT NULL DEFAULT '0'",
           "backupmx" => "TINYINT(1) NOT NULL DEFAULT '0'",
           "gal" => "TINYINT(1) NOT NULL DEFAULT '1'",
+          "ldap_gal" => "TINYINT(1) NOT NULL DEFAULT '0'",
           "relay_all_recipients" => "TINYINT(1) NOT NULL DEFAULT '0'",
           "relay_unknown_only" => "TINYINT(1) NOT NULL DEFAULT '0'",
           "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
@@ -560,6 +561,21 @@ function init_db_schema()
         ),
         "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
       ),
+      "service_settings" => array(
+        "cols" => array(
+          "key" => "VARCHAR(255) NOT NULL",
+          "value" => "LONGTEXT NOT NULL",
+          "type" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
+          "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
+          "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP",
+        ),
+        "keys" => array(
+          "primary" => array(
+            "" => array("key")
+          )
+        ),
+        "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
+      ),
       "settingsmap" => array(
         "cols" => array(
           "id" => "INT NOT NULL AUTO_INCREMENT",

+ 4 - 0
data/web/lang/lang.en-gb.json

@@ -65,6 +65,8 @@
         "full_name": "Full name",
         "gal": "Global Address List",
         "gal_info": "The GAL contains all objects of a domain and cannot be edited by any user. Free/busy information in SOGo is missing, if disabled! <b>Restart SOGo to apply changes.</b>",
+        "ldap_gal": "Global LDAP Address List",
+        "ldap_gal_info": "",
         "generate": "generate",
         "goto_ham": "Learn as <span class=\"text-success\"><b>ham</b></span>",
         "goto_null": "Silently discard mail",
@@ -681,6 +683,8 @@
         "full_name": "Full name",
         "gal": "Global Address List",
         "gal_info": "The GAL contains all objects of a domain and cannot be edited by any user. Free/busy information in SOGo is missing, if disabled! <b>Restart SOGo to apply changes.</b>",
+        "ldap_gal": "Global LDAP Address List",
+        "ldap_gal_info": "",
         "generate": "generate",
         "grant_types": "Grant types",
         "hostname": "Hostname",

+ 9 - 0
data/web/templates/edit/domain.twig

@@ -24,6 +24,7 @@
                   <input type="hidden" value="0" name="active">
                   <input type="hidden" value="0" name="backupmx">
                   <input type="hidden" value="0" name="gal">
+                  <input type="hidden" value="0" name="ldap_gal">
                   <input type="hidden" value="0" name="relay_all_recipients">
                   <input type="hidden" value="0" name="relay_unknown_only">
                   <div class="row mb-4">
@@ -130,6 +131,14 @@
                       </div>
                     </div>
                   </div>
+                  <div class="row">
+                    <div class="offset-sm-2 col-sm-10">
+                      <div class="form-check">
+                        <label><input type="checkbox" class="form-check-input" value="1" name="ldap_gal"{% if result.ldap_gal == '1' %} checked{% endif %}> {{ lang.edit.ldap_gal }}</label>
+                        <small class="text-muted">{{ lang.edit.ldap_gal_info|raw }}</small>
+                      </div>
+                    </div>
+                  </div>
                   <hr>
                   <div class="row mb-2">
                     <div class="offset-sm-2 col-sm-10">

+ 17 - 0
data/web/templates/modals/mailbox.twig

@@ -387,6 +387,7 @@
       <div class="modal-body">
         <form class="form-horizontal" data-cached-form="true" data-id="add_domain" role="form">
           <input type="hidden" value="0" name="gal">
+          <input type="hidden" value="0" name="ldap_gal">
           <input type="hidden" value="0" name="active">
           <input type="hidden" value="0" name="backupmx">
           <input type="hidden" value="0" name="relay_all_recipients">
@@ -460,6 +461,14 @@
               </div>
             </div>
           </div>
+          <div class="row mb-2">
+            <div class="offset-sm-2 col-sm-10">
+              <div class="form-check">
+                <label><input type="checkbox" class="form-check-input" id="addDomain_ldap_gal" value="1" name="ldap_gal"> {{ lang.edit.ldap_gal }}</label>
+                <small class="text-muted">{{ lang.edit.ldap_gal_info|raw }}</small>
+              </div>
+            </div>
+          </div>
           {% endif %}
           <div class="row mb-4">
             <div class="offset-sm-2 col-sm-10">
@@ -600,6 +609,14 @@
               </div>
             </div>
           </div>
+          <div class="row">
+            <div class="offset-sm-2 col-sm-10">
+              <div class="form-check">
+                <label><input type="checkbox" class="form-check-input" value="1" name="ldap_gal"> {{ lang.add.ldap_gal }}</label>
+                <small class="text-muted">{{ lang.add.ldap_gal_info|raw }}</small>
+              </div>
+            </div>
+          </div>
           <div class="row mb-2">
             <div class="offset-sm-2 col-sm-10">
               <div class="form-check">