На хабре уже были статьи по настройке почтового клиента Thunderbird, с подробным разбором его возможностей и деталей настроек, а поиск по сети выдаёт множество блогов с продублированной информацией о его базовых возможностях.
Каждый из авторов решал настройку почты своим путём, используя разные языки и подходы.
Моя цель - попытаться унифицировать это, избавиться от самописных скриптов, предоставив готовый сервер-шаблонизатор для выдачи настроек по запросу почтового клиента.
Обзор
Основой для TACS является файл описания schema.yaml, в котором задаются основные параметры:
# Directory to search for templates # The search is also performed in subdirectories, but without following symbolic links # Required file extension: *.tmpl #templateDir: templates # Block for local processing of user logins. # Executes first because it doesn't require external requests. # If there is a match, subsequent stages are not checked #local: # Default fields and properties #default: # Name of the GO template (*.tmpl), with insert fields - {{define "templateName"}} #template: default # A key-value map (hash table) that is used to fill the template. # Can be overridden at the user level. # If the field is not present at the user level, it will be set to the specified value. # The key is a variable declared in the template - {{ .variableName }} # Value - what will be substituted for the key #fields: # cn: user # mail: mail@example.com # telephoneNumber: # position: no_body # company: "\"Example\"" # List of users to handle requests. # Is a hash table of "username: params", # so each iteration of the key overwrites the previous values #list: #root: # template: root # fields: # company: "" # mail: root@example.com # cn: Administrator # signatureIsHTML: true # signature: 'Sincerely\nroot' # Block for processing users from LDAP. # If there is a match, subsequent stages are not checked #ldap: # A unique field that identifies the user, a filter compiled for this: # (uid=username), example - (sAMAccountName=username) #uid: sAMAccountName # Ldap path where to start the search #searchBase: OU=SystemUsers,OU=Corp,DC=corp,DC=domain,DC=com # Additional filter for user search. Added to the filter with uid: # (&(uid=username)filter) # In this case, search only for enabled users: #filter: (!(userAccountControl:1.2.840.113556.1.4.803:=2))(objectCategory=person)(objectClass=user) # Search in subgroups? # If true - adds the option ":1.2.840.113556.1.4.1941:" to the filter #subgroups: true # Should I generate a response for a user who is not a member of any declared group? # In this case, a default section must be declared #allowWithoutGroups: true # A key-value map (hash table) that is used to fill the template. # Can be overridden at the ldap group level. # If the field is not present at the user level, it will be set to the specified value. # The key is a variable declared in the template - {{ .variableName }} # Value - fields taken from the user's LDAP profile. # Note. # If you prefix an LDAP field name with "raw:", the value will be queried raw and base64 encoded #default: # template: default # fields: # cn: cn # mail: mail # telephoneNumber: telephoneNumber # position: position # company: company # photo: raw:jpegPhoto #list: # The list of groups by which the user's groups are matched. # The check is performed one by one, and the first match # interrupts further processing. #- group: "CN=Domain Users,OU=SystemUsers,OU=Corp,DC=corp,DC=domain,DC=com" # template: default # fields: # cn: cn # mail: mail # telephoneNumber: telephoneNumber # position: position # company: company # photo: raw:jpegPhoto
Для выдачи требуемой конфигурации, составляются списки с привязкой к ним соответствующих шаблонов. Списков два:
Локальный - явно указывается логин пользователя, поля для заполнения шаблона и сам шаблон, который должен быть отдан. Все значения шаблона задаются в самой конфигурации и никаких запросов в иные системы выполняться не будет.
LDAP - указывается группа из ldap, в которой должен состоять пользователь, LDAP-поля пользователя из которых необходимо брать свойства для заполнения шаблона и соответствую��ий шаблон. Если логин не определен в локальном списке, будет выполнен LDAP запрос на поиск пользователя и его свойств.
Рассмотрим более подробно оба вида списков:
local
local: default: template: users fields: cn: user mail: mail@example.com telephoneNumber: position: no_body company: "\"Example\"" list:
local.default - этот раздел используется как значения по умолчанию для всех участников списка, если на самом участнике списка, не переопределено иное.
local.default.template - задаёт имя шаблона, для формирования ответа
local.default.fields - карта соответствия Go-Template переменных и значений, которые должны быть подставлены вместо них. В примере выше видно, что если в шаблоне будут переменные
{{ .cn }}, {{ .mail }}, {{ .position }}
то они будут заменены указанными значениями:
user, mail@example.com, no_body
local.list - карта со списком логинов, на которые будут отдаваться настройки клиента. Здесь можно переопределить значения по умолчанию, заданные в разделе local.default с тем же синтаксисом, например:
local: default: template: default_users fields: cn: user mail: mail@example.com telephoneNumber: position: no_body company: "\"Example\"" list: user1: user2: template: managers user3: template: admins fields: cn: Вася Пупкин position: Сетевой администратор
local.list.user1: шаблон-default_users, поля заполнены по умолчаниюlocal.list.user2: шаблон-managers, поля всё те жеlocal.list.user3: шаблон-admins, поля - индивидуальные для данного пользователя
LDAP
Если логин пользователя не найден в локальном списке, выполняется поиск в LDAP.
Настройка LDAP списка расширяет локальные списки:
ldap: uid: sAMAccountName usersSearchBase: OU=Users,DC=corp,DC=example,DC=com groupsSearchBase: OU=Groups,DC=corp,DC=example,DC=com filter: (!(userAccountControl:1.2.840.113556.1.4.803:=2))(objectCategory=person)(objectClass=user) subgroups: true allowWithoutGroups: true default: list:
ldap.uid- уникальный идентификатор для поиска пользователя в LDAP.ldap.usersSearchBase- начальный каталог, от которого выполнять поиск пользователей в LDAPldap.groupSearchBase- начальный каталог, от которого выполнять поиск групп в LDAPldap.filter- дополнительный фильтр при поиске пользователей. Базовый поиск выполняется по фильтру($UID=$username)или(sAMAccountName=pupkin.v). При указании данного поля, он расширяется через аргумент И -(&($UID=$username)$AddFilter)и получается, например:
(&(sAMAccountName=pupkin.v)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(objectCategory=person)(objectClass=user))
ldap.subgroups: [true|false]- выполнять ли поиск в дочерних LDAP группах или нет. Например у вас пользователи находятся в общей группеDomain Peoples, аtacs_default- группа для выбора шаблона. Можно было быtacs_defaultскриптом навесить на каждого пользователя:
# Условная структура LDAP user1.memberOf: - Domain Peoples - tacs_default user1.memberOf: - Domain Peoples - tacs_default ...
а можно повесить на
Domain Peoples, чтобы через наследование её влияние распространялось ниже:
# Условная структура LDAP # Было: user1.memberOf: - Domain Peoples user2.memberOf: - Domain Peoples # Стало: Domain Peoples.memberOf: - tacs_default
При true, к фильтру добавляется опция :1.2.840.113556.1.4.1941: которая позволяет просмотреть все дочерние подгруппы и найти искомого пользователя, если тот присутствует в дереве дочерних групп.
ldap.allowWithoutGroups: [true|false] - разрешить ли пользователей без группы. Если пользователь не найден в группах LDAP, следует ли применить к нему значения LDAP по умолчанию или выдать 404 ошибку.
ldap.default - так же как и в локальном списке, если не переназначено на группе, применяются данные значения.
ldap.default.template - шаблон по умолчанию для LDAP групп
ldap.default.fields - карта полей вида Go-Template-переменная: ldap-поле-пользователя. Если в локальном списке значение было просто значением, то в LDAP-списке, значения берутся из свойств объекта пользователя. Например:
default: template: ldap-default fields: cn: cn position: description
Имя шаблона по умолчанию -
ldap-defaultПеременная шаблона
{{ .cn }}- соответствует полюcnиз LDAPПеременная шаблона
{{ .position }}- соответствует полюdesccriptionиз LDAP
ldap.default.fields.*: raw:* - если перед именем LDAP-поля указать префикс raw: тогда значение поля будет выгружено в сыром виде и закодировано в base64. Это используется для выгрузки фотографий из поля LDAP jpegPhoto:
default: ... fields: photo: "raw:jpegPhoto"
Получив фото из LDAP, можно составить html-подпись для пользователя:
<img src="data:image/jpeg;base64,{{ .photo }}"/>
ldap.list - список групп с привязкой к ним шаблонов и необязательной карты свойств
list: - group: "CN=tacs-with-photo,OU=tacs,OU=Groups,DC=corp,DC=example,DC=com" template: with-ldap-photo fields: cn: cn mail: mail position: position company: company photo: raw:jpegPhoto address: physicalDeliveryOfficeName telephoneNumber: telephoneNumber
ldap.list.[*].group - полный DN-путь к группе, например: "CN=tacs-with-photo,OU=tacs,OU=Groups,DC=corp,DC=example,DC=com"
ldap.list.[*].template - необязательное переопределение используемого шаблона для указанной группы
ldap.list.[*].fields - карта необязательных переопределений полей, аналогично разделу ldap.default.fields.
Шаблоны
Каталог для поиска шаблонов указывается в scheme.yaml:
templateDir - TACS проходит по всем подкаталогам, кроме символических ссылок, анализируя и загружая в память *.tmpl файлы, которые являются go-template.
Объявление шаблона выполняется блоком
{{define "template_name"}}...{{end}}
Имя шаблона указывается в
scheme.yaml, в свойствеtemplateи используется при генерации страницы конфигурации:
local: default: template: template_name ... list: username: template: template_name ... ldap: default: template: template_name ... list: - group: ... template: template_name
Шаблоны могут вызывать другие шаблоны:
{{define "temp1"}} ... {{end}} {{define "temp2"}} ... {{end}} {{define "temp3"}} {{template "temp1"}} {{template "temp2"}} {{end}}
Имя шаблона должно быть уникальным по сравнению с остальными, иначе шаблоны могут переписать друг-друга.
В шаблоне можно использовать переменные для подстановки значений из
scheme.yaml/LDAP:
# Все переменные хранятся в "точке": {{define "default"}} {{.var_name}} {{end}}
Переменные объявляются в
scheme.yaml, в свойствеfields:
local: default: template_key: value ... list: username: template_key: value ... ldap: default: fields: template_key: ldap_field_with_value template_key: raw:ldap_field_with_binary_value ... list: - group: ... fields: template_key: ldap_field_with_value
Примеры
Примерная конфигурация приведена в репозитории.
