yannstatic/static/2019/10/21/Annuaire-LDAP-xoyize.xyz-(exPC2).html

6213 lines
371 KiB
HTML
Raw Normal View History

2024-10-31 20:18:37 +01:00
<!DOCTYPE html><html lang="fr">
<head><meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"><title>OpenLDAP installation et configuration annuaire xoyize.xyz - YannStatic</title>
<meta name="description" content="OpenLDAP Debian">
<link rel="canonical" href="https://static.rnmkcy.eu/2019/10/21/Annuaire-LDAP-xoyize.xyz-(exPC2).html"><link rel="alternate" type="application/rss+xml" title="YannStatic" href="/feed.xml">
<!-- - include head/favicon.html - -->
<link rel="shortcut icon" type="image/png" href="/assets/favicon/favicon.png"><link rel="stylesheet" href="/assets/css/main.css"><link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.13/css/all.css" ><!-- start custom head snippets --><link rel="stylesheet" href="/assets/css/expand.css">
<!-- end custom head snippets --><script>(function() {
window.isArray = function(val) {
return Object.prototype.toString.call(val) === '[object Array]';
};
window.isString = function(val) {
return typeof val === 'string';
};
window.hasEvent = function(event) {
return 'on'.concat(event) in window.document;
};
window.isOverallScroller = function(node) {
return node === document.documentElement || node === document.body || node === window;
};
window.isFormElement = function(node) {
var tagName = node.tagName;
return tagName === 'INPUT' || tagName === 'SELECT' || tagName === 'TEXTAREA';
};
window.pageLoad = (function () {
var loaded = false, cbs = [];
window.addEventListener('load', function () {
var i;
loaded = true;
if (cbs.length > 0) {
for (i = 0; i < cbs.length; i++) {
cbs[i]();
}
}
});
return {
then: function(cb) {
cb && (loaded ? cb() : (cbs.push(cb)));
}
};
})();
})();
(function() {
window.throttle = function(func, wait) {
var args, result, thisArg, timeoutId, lastCalled = 0;
function trailingCall() {
lastCalled = new Date;
timeoutId = null;
result = func.apply(thisArg, args);
}
return function() {
var now = new Date,
remaining = wait - (now - lastCalled);
args = arguments;
thisArg = this;
if (remaining <= 0) {
clearTimeout(timeoutId);
timeoutId = null;
lastCalled = now;
result = func.apply(thisArg, args);
} else if (!timeoutId) {
timeoutId = setTimeout(trailingCall, remaining);
}
return result;
};
};
})();
(function() {
var Set = (function() {
var add = function(item) {
var i, data = this._data;
for (i = 0; i < data.length; i++) {
if (data[i] === item) {
return;
}
}
this.size ++;
data.push(item);
return data;
};
var Set = function(data) {
this.size = 0;
this._data = [];
var i;
if (data.length > 0) {
for (i = 0; i < data.length; i++) {
add.call(this, data[i]);
}
}
};
Set.prototype.add = add;
Set.prototype.get = function(index) { return this._data[index]; };
Set.prototype.has = function(item) {
var i, data = this._data;
for (i = 0; i < data.length; i++) {
if (this.get(i) === item) {
return true;
}
}
return false;
};
Set.prototype.is = function(map) {
if (map._data.length !== this._data.length) { return false; }
var i, j, flag, tData = this._data, mData = map._data;
for (i = 0; i < tData.length; i++) {
for (flag = false, j = 0; j < mData.length; j++) {
if (tData[i] === mData[j]) {
flag = true;
break;
}
}
if (!flag) { return false; }
}
return true;
};
Set.prototype.values = function() {
return this._data;
};
return Set;
})();
window.Lazyload = (function(doc) {
var queue = {js: [], css: []}, sources = {js: {}, css: {}}, context = this;
var createNode = function(name, attrs) {
var node = doc.createElement(name), attr;
for (attr in attrs) {
if (attrs.hasOwnProperty(attr)) {
node.setAttribute(attr, attrs[attr]);
}
}
return node;
};
var end = function(type, url) {
var s, q, qi, cbs, i, j, cur, val, flag;
if (type === 'js' || type ==='css') {
s = sources[type], q = queue[type];
s[url] = true;
for (i = 0; i < q.length; i++) {
cur = q[i];
if (cur.urls.has(url)) {
qi = cur, val = qi.urls.values();
qi && (cbs = qi.callbacks);
for (flag = true, j = 0; j < val.length; j++) {
cur = val[j];
if (!s[cur]) {
flag = false;
}
}
if (flag && cbs && cbs.length > 0) {
for (j = 0; j < cbs.length; j++) {
cbs[j].call(context);
}
qi.load = true;
}
}
}
}
};
var load = function(type, urls, callback) {
var s, q, qi, node, i, cur,
_urls = typeof urls === 'string' ? new Set([urls]) : new Set(urls), val, url;
if (type === 'js' || type ==='css') {
s = sources[type], q = queue[type];
for (i = 0; i < q.length; i++) {
cur = q[i];
if (_urls.is(cur.urls)) {
qi = cur;
break;
}
}
val = _urls.values();
if (qi) {
callback && (qi.load || qi.callbacks.push(callback));
callback && (qi.load && callback());
} else {
q.push({
urls: _urls,
callbacks: callback ? [callback] : [],
load: false
});
for (i = 0; i < val.length; i++) {
node = null, url = val[i];
if (s[url] === undefined) {
(type === 'js' ) && (node = createNode('script', { src: url }));
(type === 'css') && (node = createNode('link', { rel: 'stylesheet', href: url }));
if (node) {
node.onload = (function(type, url) {
return function() {
end(type, url);
};
})(type, url);
(doc.head || doc.body).appendChild(node);
s[url] = false;
}
}
}
}
}
};
return {
js: function(url, callback) {
load('js', url, callback);
},
css: function(url, callback) {
load('css', url, callback);
}
};
})(this.document);
})();
</script><script>
(function() {
var TEXT_VARIABLES = {
version: '2.2.6',
sources: {
font_awesome: 'https://use.fontawesome.com/releases/v5.0.13/css/all.css',
jquery: '/assets/js/jquery.min.js',
leancloud_js_sdk: '//cdn.jsdelivr.net/npm/leancloud-storage@3.13.2/dist/av-min.js',
chart: 'https://cdn.bootcss.com/Chart.js/2.7.2/Chart.bundle.min.js',
gitalk: {
js: 'https://cdn.bootcss.com/gitalk/1.2.2/gitalk.min.js',
css: 'https://cdn.bootcss.com/gitalk/1.2.2/gitalk.min.css'
},
valine: 'https://unpkg.com/valine/dist/Valine.min.js'
},
site: {
toc: {
selectors: 'h1,h2,h3'
}
},
paths: {
search_js: '/assets/search.js'
}
};
window.TEXT_VARIABLES = TEXT_VARIABLES;
})();
</script>
</head>
<body>
<div class="root" data-is-touch="false">
<div class="layout--page js-page-root"><!----><div class="page__main js-page-main page__viewport hide-footer has-aside has-aside cell cell--auto">
<div class="page__main-inner"><div class="page__header d-print-none"><header class="header"><div class="main">
<div class="header__title">
<div class="header__brand"><svg id="svg" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" height="478.9473684210526" viewBox="0, 0, 400,478.9473684210526"><g id="svgg"><path id="path0" d="M308.400 56.805 C 306.970 56.966,303.280 57.385,300.200 57.738 C 290.906 58.803,278.299 59.676,269.200 59.887 L 260.600 60.085 259.400 61.171 C 258.010 62.428,256.198 63.600,255.645 63.600 C 255.070 63.600,252.887 65.897,252.598 66.806 C 252.460 67.243,252.206 67.600,252.034 67.600 C 251.397 67.600,247.206 71.509,247.202 72.107 C 247.201 72.275,246.390 73.190,245.400 74.138 C 243.961 75.517,243.598 76.137,243.592 77.231 C 243.579 79.293,241.785 83.966,240.470 85.364 C 239.176 86.740,238.522 88.365,237.991 91.521 C 237.631 93.665,236.114 97.200,235.554 97.200 C 234.938 97.200,232.737 102.354,232.450 104.472 C 232.158 106.625,230.879 109.226,229.535 110.400 C 228.933 110.926,228.171 113.162,226.434 119.500 C 226.178 120.435,225.795 121.200,225.584 121.200 C 225.373 121.200,225.200 121.476,225.200 121.813 C 225.200 122.149,224.885 122.541,224.500 122.683 C 223.606 123.013,223.214 123.593,223.204 124.600 C 223.183 126.555,220.763 132.911,219.410 134.562 C 218.443 135.742,217.876 136.956,217.599 138.440 C 217.041 141.424,215.177 146.434,214.532 146.681 C 214.240 146.794,214.000 147.055,214.000 147.261 C 214.000 147.467,213.550 148.086,213.000 148.636 C 212.450 149.186,212.000 149.893,212.000 150.208 C 212.000 151.386,208.441 154.450,207.597 153.998 C 206.319 153.315,204.913 150.379,204.633 147.811 C 204.365 145.357,202.848 142.147,201.759 141.729 C 200.967 141.425,199.200 137.451,199.200 135.974 C 199.200 134.629,198.435 133.224,196.660 131.311 C 195.363 129.913,194.572 128.123,193.870 125.000 C 193.623 123.900,193.236 122.793,193.010 122.540 C 190.863 120.133,190.147 118.880,188.978 115.481 C 188.100 112.928,187.151 111.003,186.254 109.955 C 185.358 108.908,184.518 107.204,183.847 105.073 C 183.280 103.273,182.497 101.329,182.108 100.753 C 181.719 100.177,180.904 98.997,180.298 98.131 C 179.693 97.265,178.939 95.576,178.624 94.378 C 178.041 92.159,177.125 90.326,175.023 87.168 C 174.375 86.196,173.619 84.539,173.342 83.486 C 172.800 81.429,171.529 79.567,170.131 78.785 C 169.654 78.517,168.697 77.511,168.006 76.549 C 167.316 75.587,166.594 74.800,166.402 74.800 C 166.210 74.800,164.869 73.633,163.421 72.206 C 160.103 68.936,161.107 69.109,146.550 69.301 C 133.437 69.474,128.581 70.162,126.618 72.124 C 126.248 72.495,125.462 72.904,124.872 73.033 C 124.282 73.163,123.088 73.536,122.219 73.863 C 121.349 74.191,119.028 74.638,117.061 74.858 C 113.514 75.254,109.970 76.350,108.782 77.419 C 107.652 78.436,100.146 80.400,97.388 80.400 C 95.775 80.400,93.167 81.360,91.200 82.679 C 90.430 83.195,89.113 83.804,88.274 84.031 C 85.875 84.681,78.799 90.910,74.400 96.243 L 73.400 97.456 73.455 106.028 C 73.526 117.055,74.527 121.238,77.820 124.263 C 78.919 125.273,80.400 127.902,80.400 128.842 C 80.400 129.202,81.075 130.256,81.900 131.186 C 83.563 133.059,85.497 136.346,86.039 138.216 C 86.233 138.886,87.203 140.207,88.196 141.153 C 89.188 142.098,90.000 143.104,90.000 143.388 C 90.000 144.337,92.129 148.594,92.869 149.123 C 93.271 149.410,93.600 149.831,93.600 150.059 C 93.600 150.286,93.932 150.771,94.337 151.136 C 94.743 151.501,95.598 153.004,96.237 154.475 C 96.877 155.947,97.760 157.351,98.200 157.596 C 98.640 157.841,99.900 159.943,101.000 162.267 C 102.207 164.817,103.327 166.644,103.825 166.876 C 104.278 167.087,105.065 168.101,105.573 169.130 C 107.658 173.348,108.097 174.093,110.006 176.647 C 111.103 178.114,112.000 179.725,112.000 180.227 C 112.000 181.048,113.425 183.163,114.678 184.200 C 115.295 184.711,117.396 188.733,117.720 190.022 C 117.855 190.562,118.603 191.633,119.381 192.402 C 120.160 193.171,121.496 195.258,122.351 197.039 C 123.206 198.820,124.167 200.378,124.487 200.501 C 124.807 200.624,125.953 202.496,127.034 204.662 C 128.114 206.828,129.676 209.299,130.505 210.153 C 131.333 211.007,132.124 212.177,132.262 212.753 C 132.618 214.239,134.291 217.048,136.288 219.5
" href="/">YannStatic</a></div><!--<button class="button button--secondary button--circle search-button js-search-toggle"><i class="fas fa-search"></i></button>--><!-- <li><button class="button button--secondary button--circle search-button js-search-toggle"><i class="fas fa-search"></i></button></li> -->
<!-- Champ de recherche -->
<div id="searchbox" class="search search--dark" style="visibility: visible">
<div class="main">
<div class="search__header"></div>
<div class="search-bar">
<div class="search-box js-search-box">
<div class="search-box__icon-search"><i class="fas fa-search"></i></div>
<input id="search-input" type="text" />
<!-- <div class="search-box__icon-clear js-icon-clear">
<a><i class="fas fa-times"></i></a>
</div> -->
</div>
</div>
</div>
</div>
<!-- Script pointing to search-script.js -->
<script>/*!
* Simple-Jekyll-Search
* Copyright 2015-2020, Christian Fei
* Licensed under the MIT License.
*/
(function(){
'use strict'
var _$Templater_7 = {
compile: compile,
setOptions: setOptions
}
const options = {}
options.pattern = /\{(.*?)\}/g
options.template = ''
options.middleware = function () {}
function setOptions (_options) {
options.pattern = _options.pattern || options.pattern
options.template = _options.template || options.template
if (typeof _options.middleware === 'function') {
options.middleware = _options.middleware
}
}
function compile (data) {
return options.template.replace(options.pattern, function (match, prop) {
const value = options.middleware(prop, data[prop], options.template)
if (typeof value !== 'undefined') {
return value
}
return data[prop] || match
})
}
'use strict';
function fuzzysearch (needle, haystack) {
var tlen = haystack.length;
var qlen = needle.length;
if (qlen > tlen) {
return false;
}
if (qlen === tlen) {
return needle === haystack;
}
outer: for (var i = 0, j = 0; i < qlen; i++) {
var nch = needle.charCodeAt(i);
while (j < tlen) {
if (haystack.charCodeAt(j++) === nch) {
continue outer;
}
}
return false;
}
return true;
}
var _$fuzzysearch_1 = fuzzysearch;
'use strict'
/* removed: const _$fuzzysearch_1 = require('fuzzysearch') */;
var _$FuzzySearchStrategy_5 = new FuzzySearchStrategy()
function FuzzySearchStrategy () {
this.matches = function (string, crit) {
return _$fuzzysearch_1(crit.toLowerCase(), string.toLowerCase())
}
}
'use strict'
var _$LiteralSearchStrategy_6 = new LiteralSearchStrategy()
function LiteralSearchStrategy () {
this.matches = function (str, crit) {
if (!str) return false
str = str.trim().toLowerCase()
crit = crit.trim().toLowerCase()
return crit.split(' ').filter(function (word) {
return str.indexOf(word) >= 0
}).length === crit.split(' ').length
}
}
'use strict'
var _$Repository_4 = {
put: put,
clear: clear,
search: search,
setOptions: __setOptions_4
}
/* removed: const _$FuzzySearchStrategy_5 = require('./SearchStrategies/FuzzySearchStrategy') */;
/* removed: const _$LiteralSearchStrategy_6 = require('./SearchStrategies/LiteralSearchStrategy') */;
function NoSort () {
return 0
}
const data = []
let opt = {}
opt.fuzzy = false
opt.limit = 10
opt.searchStrategy = opt.fuzzy ? _$FuzzySearchStrategy_5 : _$LiteralSearchStrategy_6
opt.sort = NoSort
opt.exclude = []
function put (data) {
if (isObject(data)) {
return addObject(data)
}
if (isArray(data)) {
return addArray(data)
}
return undefined
}
function clear () {
data.length = 0
return data
}
function isObject (obj) {
return Boolean(obj) && Object.prototype.toString.call(obj) === '[object Object]'
}
function isArray (obj) {
return Boolean(obj) && Object.prototype.toString.call(obj) === '[object Array]'
}
function addObject (_data) {
data.push(_data)
return data
}
function addArray (_data) {
const added = []
clear()
for (let i = 0, len = _data.length; i < len; i++) {
if (isObject(_data[i])) {
added.push(addObject(_data[i]))
}
}
return added
}
function search (crit) {
if (!crit) {
return []
}
return findMatches(data, crit, opt.searchStrategy, opt).sort(opt.sort)
}
function __setOptions_4 (_opt) {
opt = _opt || {}
opt.fuzzy = _opt.fuzzy || false
opt.limit = _opt.limit || 10
opt.searchStrategy = _opt.fuzzy ? _$FuzzySearchStrategy_5 : _$LiteralSearchStrategy_6
opt.sort = _opt.sort || NoSort
opt.exclude = _opt.exclude || []
}
function findMatches (data, crit, strategy, opt) {
const matches = []
for (let i = 0; i < data.length && matches.length < opt.limit; i++) {
const match = findMatchesInObject(data[i], crit, strategy, opt)
if (match) {
matches.push(match)
}
}
return matches
}
function findMatchesInObject (obj, crit, strategy, opt) {
for (const key in obj) {
if (!isExcluded(obj[key], opt.exclude) && strategy.matches(obj[key], crit)) {
return obj
}
}
}
function isExcluded (term, excludedTerms) {
for (let i = 0, len = excludedTerms.length; i < len; i++) {
const excludedTerm = excludedTerms[i]
if (new RegExp(excludedTerm).test(term)) {
return true
}
}
return false
}
/* globals ActiveXObject:false */
'use strict'
var _$JSONLoader_2 = {
load: load
}
function load (location, callback) {
const xhr = getXHR()
xhr.open('GET', location, true)
xhr.onreadystatechange = createStateChangeListener(xhr, callback)
xhr.send()
}
function createStateChangeListener (xhr, callback) {
return function () {
if (xhr.readyState === 4 && xhr.status === 200) {
try {
callback(null, JSON.parse(xhr.responseText))
} catch (err) {
callback(err, null)
}
}
}
}
function getXHR () {
return window.XMLHttpRequest ? new window.XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP')
}
'use strict'
var _$OptionsValidator_3 = function OptionsValidator (params) {
if (!validateParams(params)) {
throw new Error('-- OptionsValidator: required options missing')
}
if (!(this instanceof OptionsValidator)) {
return new OptionsValidator(params)
}
const requiredOptions = params.required
this.getRequiredOptions = function () {
return requiredOptions
}
this.validate = function (parameters) {
const errors = []
requiredOptions.forEach(function (requiredOptionName) {
if (typeof parameters[requiredOptionName] === 'undefined') {
errors.push(requiredOptionName)
}
})
return errors
}
function validateParams (params) {
if (!params) {
return false
}
return typeof params.required !== 'undefined' && params.required instanceof Array
}
}
'use strict'
var _$utils_9 = {
merge: merge,
isJSON: isJSON
}
function merge (defaultParams, mergeParams) {
const mergedOptions = {}
for (const option in defaultParams) {
mergedOptions[option] = defaultParams[option]
if (typeof mergeParams[option] !== 'undefined') {
mergedOptions[option] = mergeParams[option]
}
}
return mergedOptions
}
function isJSON (json) {
try {
if (json instanceof Object && JSON.parse(JSON.stringify(json))) {
return true
}
return false
} catch (err) {
return false
}
}
var _$src_8 = {};
(function (window) {
'use strict'
let options = {
searchInput: null,
resultsContainer: null,
json: [],
success: Function.prototype,
searchResultTemplate: '<li><a href="{url}" title="{desc}">{title}</a></li>',
templateMiddleware: Function.prototype,
sortMiddleware: function () {
return 0
},
noResultsText: 'No results found',
limit: 10,
fuzzy: false,
debounceTime: null,
exclude: []
}
let debounceTimerHandle
const debounce = function (func, delayMillis) {
if (delayMillis) {
clearTimeout(debounceTimerHandle)
debounceTimerHandle = setTimeout(func, delayMillis)
} else {
func.call()
}
}
const requiredOptions = ['searchInput', 'resultsContainer', 'json']
/* removed: const _$Templater_7 = require('./Templater') */;
/* removed: const _$Repository_4 = require('./Repository') */;
/* removed: const _$JSONLoader_2 = require('./JSONLoader') */;
const optionsValidator = _$OptionsValidator_3({
required: requiredOptions
})
/* removed: const _$utils_9 = require('./utils') */;
window.SimpleJekyllSearch = function (_options) {
const errors = optionsValidator.validate(_options)
if (errors.length > 0) {
throwError('You must specify the following required options: ' + requiredOptions)
}
options = _$utils_9.merge(options, _options)
_$Templater_7.setOptions({
template: options.searchResultTemplate,
middleware: options.templateMiddleware
})
_$Repository_4.setOptions({
fuzzy: options.fuzzy,
limit: options.limit,
sort: options.sortMiddleware,
exclude: options.exclude
})
if (_$utils_9.isJSON(options.json)) {
initWithJSON(options.json)
} else {
initWithURL(options.json)
}
const rv = {
search: search
}
typeof options.success === 'function' && options.success.call(rv)
return rv
}
function initWithJSON (json) {
_$Repository_4.put(json)
registerInput()
}
function initWithURL (url) {
_$JSONLoader_2.load(url, function (err, json) {
if (err) {
throwError('failed to get JSON (' + url + ')')
}
initWithJSON(json)
})
}
function emptyResultsContainer () {
options.resultsContainer.innerHTML = ''
}
function appendToResultsContainer (text) {
options.resultsContainer.innerHTML += text
}
function registerInput () {
options.searchInput.addEventListener('input', function (e) {
if (isWhitelistedKey(e.which)) {
emptyResultsContainer()
debounce(function () { search(e.target.value) }, options.debounceTime)
}
})
}
function search (query) {
if (isValidQuery(query)) {
emptyResultsContainer()
render(_$Repository_4.search(query), query)
}
}
function render (results, query) {
const len = results.length
if (len === 0) {
return appendToResultsContainer(options.noResultsText)
}
for (let i = 0; i < len; i++) {
results[i].query = query
appendToResultsContainer(_$Templater_7.compile(results[i]))
}
}
function isValidQuery (query) {
return query && query.length > 0
}
function isWhitelistedKey (key) {
return [13, 16, 20, 37, 38, 39, 40, 91].indexOf(key) === -1
}
function throwError (message) {
throw new Error('SimpleJekyllSearch --- ' + message)
}
})(window)
}());
</script>
<!-- Configuration -->
<script>
SimpleJekyllSearch({
searchInput: document.getElementById('search-input'),
resultsContainer: document.getElementById('results-container'),
json: '/search.json',
//searchResultTemplate: '<li><a href="https://static.rnmkcy.eu{url}">{date}&nbsp;{title}</a></li>'
searchResultTemplate: '<li><a href="{url}">{date}&nbsp;{title}</a></li>'
})
</script>
<!-- Fin déclaration champ de recherche --></div><nav class="navigation">
2024-11-28 11:42:23 +01:00
<ul><li class="navigation__item"><a href="/archive.html">Etiquettes</a></li><li class="navigation__item"><a href="/htmldoc.html">Documents</a></li><li class="navigation__item"><a href="/liens_ttrss.html">Liens</a></li><li class="navigation__item"><a href="/syntaxe-markdown.html">Aide</a></li></ul>
2024-10-31 20:18:37 +01:00
</nav></div>
</header>
</div><div class="page__content"><div class ="main"><div class="grid grid--reverse">
<div class="col-main cell cell--auto"><!-- start custom main top snippet --><div id="results-container" class="search-result js-search-result"></div><!-- end custom main top snippet -->
<article itemscope itemtype="http://schema.org/Article"><div class="article__header"><header><h1 style="color:Tomato;">OpenLDAP installation et configuration annuaire xoyize.xyz</h1></header></div><meta itemprop="headline" content="OpenLDAP installation et configuration annuaire xoyize.xyz"><div class="article__info clearfix"><ul class="left-col menu"><li>
2024-11-08 14:10:33 +01:00
<a class="button button--secondary button--pill button--sm" style="color:#00FFFF" href="/archive.html?tag=debian">debian</a>
2024-10-31 20:18:37 +01:00
</li></ul><ul class="right-col menu"><li>
<i class="far fa-calendar-alt"></i>&nbsp;<span title="Création" style="color:#FF00FF">21&nbsp;oct.&nbsp;&nbsp;2019</span>
<span title="Modification" style="color:#00FF7F">22&nbsp;oct.&nbsp;&nbsp;2019</span></li></ul></div><meta itemprop="datePublished" content="2019-10-22T00:00:00+02:00">
<meta itemprop="keywords" content="debian"><div class="js-article-content">
<div class="layout--article"><!-- start custom article top snippet -->
<style>
#myBtn {
display: none;
position: fixed;
bottom: 10px;
right: 10px;
z-index: 99;
font-size: 12px;
font-weight: bold;
border: none;
outline: none;
background-color: white;
color: black;
cursor: pointer;
padding: 5px;
border-radius: 4px;
}
#myBtn:hover {
background-color: #555;
}
</style>
<button onclick="topFunction()" id="myBtn" title="Haut de page">&#8679;</button>
<script>
//Get the button
var mybutton = document.getElementById("myBtn");
// When the user scrolls down 20px from the top of the document, show the button
window.onscroll = function() {scrollFunction()};
function scrollFunction() {
if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) {
mybutton.style.display = "block";
} else {
mybutton.style.display = "none";
}
}
// When the user clicks on the button, scroll to the top of the document
function topFunction() {
document.body.scrollTop = 0;
document.documentElement.scrollTop = 0;
}
</script>
<!-- end custom article top snippet -->
<div class="article__content" itemprop="articleBody"><details>
<summary><b>Afficher/cacher Sommaire</b></summary>
<!-- affichage sommaire -->
<div class="toc-aside js-toc-root"></div>
</details><h1 id="openldap-debian">OpenLDAP Debian</h1>
<p><img src="/images/OpenLDAP-logo.png" alt="" /></p>
<h2 id="liens">Liens</h2>
<ul>
<li><a href="https://www.vennedey.net/resources/0-Getting-started-with-OpenLDAP-on-Debian-8">Getting started with OpenLDAP on Debian </a></li>
<li><a href="https://www.vennedey.net/resources/2-LDAP-managed-mail-server-with-Postfix-and-Dovecot-for-multiple-domains">LDAP managed mail server with Postfix and Dovecot for multiple domains</a></li>
<li><a href="https://wiki.gandi.net/fr/hosting/using-linux/tutorials/debian/mail-server-ldap">Installation dun serveur mail avec backend OpenLDAP</a></li>
<li><a href="https://computingforgeeks.com/how-to-install-and-configure-openldap-server-on-debian/">How To Install and Configure OpenLDAP Server on Debian 10 (Buster)</a></li>
<li>
<p><a href="http://idum.fr/spip.php?article326">Mise en place de OpenLDAP</a> (<em>Le but de cet article est de vous faire découvrir OpenLDAP. En commençant par vous expliquer comment il fonctionne. Puis comment linstaller. Et enfin comment ladministrer.<br />
Pour nous aider dans cette dernière partie, jutiliserai linterface web phpldapadmin.</em>)<br />
<a href="/files/Mise en place de OpenLDAP.pdf">Mise en place de OpenLDAP</a> (Document au format PDF)</p>
</li>
<li><a href="https://blog.debugo.fr/openldap-serie/">OpenLDAP : La série de Tutos</a></li>
<li><a href="https://blog.debugo.fr/serveur-messagerie-complet-postfix-dovecot-ldap-rspamd/">Serveur Messagerie complet : La série de Tutos</a></li>
</ul>
<h2 id="openldap-serveur">OpenLDAP Serveur</h2>
<p><em>Les tests sont effectués sur le serveur xoyize.xyz</em></p>
<ul>
<li><a href="404.html">OVH VPS SSD 1 KVM OpenStack (1 vCore/2GoRam/20GoSSD) domaine xoyize.xyz + compilation OpenResty</a></li>
</ul>
<p>Avant de commencer linstallation, configurez le nom dhôte FQDN de votre serveur et ajoutez un enregistrement au fichier <strong>/etc/hosts</strong><br />
Relever ladresse ip de votre serveur <code class="language-plaintext highlighter-rouge">ip a</code> (ici notre adresse ip 192.168.0.45)</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>echo "192.168.0.45 ldap.xoyize.xyz" | sudo tee -a /etc/hosts
sudo hostnamectl set-hostname ldap.xoyize.xyz --static
</code></pre></div></div>
<h3 id="installation-et-configuration">Installation et configuration</h3>
<blockquote>
<p>en mode su ou sudo</p>
</blockquote>
<p>Linstallation dOpenLDAP sur Debian est faite à laide de la gestion des paquets APT.<br />
Le premier élément précise que linstallation du paquet <strong>slapd</strong> se fait en mode non interactif et <code class="language-plaintext highlighter-rouge">-yq</code> ajoute linstallation en mode silencieux.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>DEBIAN_FRONTEND=noninteractive apt-get -yq install slapd ldap-utils net-tools
</code></pre></div></div>
<p>Pendant linstallation, vous devrez choisir un mot de passe de ladministrateur pour le compte racine LDAP. rhTJH8f97dkS65</p>
<h4 id="configuration-initiale">Configuration initiale</h4>
<p>Créer un squelette de configuration pour <strong>slapd</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dpkg-reconfigure slapd
</code></pre></div></div>
<p>Voulez-vous omettre la configuration dOpenLDAP ? Non<br />
Nom de domaine : xoyize.xyz<br />
Nom dentité (« organization ») : srvxo<br />
Mot de passe de ladministrateur : f43z7C9TBwxX3h<br />
Module de base de données à utiliser : MDB<br />
Faut-il supprimer la base de données lors de la purge du paquet ? Non<br />
Faut-il déplacer lancienne base de données ? Oui</p>
<h4 id="tests-fonctionnels">Tests fonctionnels</h4>
<p>Activer au démarrage et lancer <strong>slapd</strong> .</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl enable slapd
systemctl start slapd
</code></pre></div></div>
<p>Sur quel port du réseau écoute slapd</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>netstat -laputn | grep slapd
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tcp 0 0 0.0.0.0:389 0.0.0.0:* LISTEN 10204/slapd
tcp6 0 0 :::389 :::* LISTEN 10204/slapd
</code></pre></div></div>
<p>Comme on peut le voir, <strong>slapd</strong> attend les requêtes ldap sur le port TCP 389.<br />
Avec quels arguments sexécute le service.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat /var/run/slapd/slapd.args
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/usr/sbin/slapd -h ldap:/// ldapi:/// -g openldap -u openldap -F /etc/ldap/slapd.d
</code></pre></div></div>
<p><strong>-h</strong> → les modalités daccès à slapd : ldap et ldapi (ldap via TCP 389 et ldapi via socket unix)<br />
<strong>-g</strong> et <strong>-u</strong> → identité groupe et utilisateur sur lequel “tourne” le processus slapd<br />
<strong>-F</strong> → emplacement du ficher de configuration de slapd</p>
<p>Afficher les données de lannuaire</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -x -H ldap://cinay.ldap.xoyize.xyz -b 'dc=xoyize,dc=xyz'
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># extended LDIF
#
# LDAPv3
# base &lt;dc=xoyize,dc=xyz&gt; with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#
# xoyize.xyz
dn: dc=xoyize,dc=xyz
objectClass: top
objectClass: dcObject
objectClass: organization
o: srvxo
dc: xoyize
# admin, xoyize.xyz
dn: cn=admin,dc=xoyize,dc=xyz
objectClass: simpleSecurityObject
objectClass: organizationalRole
cn: admin
description: LDAP administrator
# search result
search: 2
result: 0 Success
# numResponses: 3
# numEntries: 2
</code></pre></div></div>
<p><strong>-x</strong> → nous utilisons un compte défini à lintérieur de la base pour accéder à lannuaire<br />
<strong>-H</strong> → accès en ldap sur la machine cinay.ldap.xoyize.xyz<br />
<strong>-b</strong> → la racine à parcourir , ici dc=ldap,dc=xoyize,dc=xyz</p>
<p>ou</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -Y external -H ldapi:/// -b dc=xoyize,dc=xyz -LLL
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SASL/EXTERNAL authentication started
SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
SASL SSF: 0
dn: dc=xoyize,dc=xyz
objectClass: top
objectClass: dcObject
objectClass: organization
o: srvxo
dc: xoyize
dn: cn=admin,dc=xoyize,dc=xyz
objectClass: simpleSecurityObject
objectClass: organizationalRole
cn: admin
description: LDAP administrator
</code></pre></div></div>
<p>Pour avoir dautres informations sur le serveur, vous pouvez utiliser les commandes suivantes :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>slapd -V
ldapsearch -Y external -H ldapi:/// -b cn=config "(objectClass=olcGlobal)" -LLL
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SASL/EXTERNAL authentication started
SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
SASL SSF: 0
dn: cn=config
objectClass: olcGlobal
cn: config
olcArgsFile: /var/run/slapd/slapd.args
olcLogLevel: none
olcPidFile: /var/run/slapd/slapd.pid
olcToolThreads: 1
</code></pre></div></div>
<blockquote>
<p>-LLL : Permet de ne pas afficher certains informations superflues dans la sortie</p>
</blockquote>
<p>La configuration nest plus dans un fichier unique, mais sous forme de DIT (cn=config). On peut en avoir un aperçu avec la commande</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> tree /etc/ldap/slapd.d/
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/etc/ldap/slapd.d/
├── cn=config
│   ├── cn=module{0}.ldif
│   ├── cn=schema
│   │   ├── cn={0}core.ldif
│   │   ├── cn={1}cosine.ldif
│   │   ├── cn={2}nis.ldif
│   │   └── cn={3}inetorgperson.ldif
│   ├── cn=schema.ldif
│   ├── olcBackend={0}mdb.ldif
│   ├── olcDatabase={0}config.ldif
│   ├── olcDatabase={-1}frontend.ldif
│   └── olcDatabase={1}mdb.ldif
└── cn=config.ldif
</code></pre></div></div>
<h3 id="explications">Explications</h3>
<h4 id="les-schémas">Les Schémas</h4>
<p>Si vous regardez le résultat de la commande :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tree /etc/ldap/slapd.d/
</code></pre></div></div>
<p>Vous voyez plusieurs entrées sous la branche cn=schema. En loccurrence, core, cosine, nis et inetorgperson.</p>
<p>Ce sont en quelque sorte les modèles quutiliseront vos futurs enregistrement (quel attribut, de quel type, etc..). Ceux déjà intégrés sont les plus couramment utilisés, mais il en existe dautres, soit déjà disponibles dans /etc/ldap/schema :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ls /etc/ldap/schema/*.ldif
</code></pre></div></div>
<p>Soit disponibles sur Internet.</p>
<p>Il est aussi tout à fait possible de créer vos propres schémas, afin que votre annuaire réponde précisément à vos besoins.</p>
<h4 id="les-commandes">Les commandes</h4>
<p>Pour modifier la configuration du serveur nous utiliserons principalement les commandes ldapadd, ldapmodify, ldapdelete (installées avec le paquet ldap-utils) Ces dernières prennent en option un fichier (option -f) de format ldif (Ldap Data Interchange Format).</p>
<p>Nous allons rapidement voir de quoi il sagit.</p>
<p>Pour effectuer des requêtes, nous utiliserons la commande ldapsearch.</p>
<p>Celle ci peut interroger le serveur LDAP de plusieurs façons.</p>
<p>Sur le socket interne :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -Q -Y external -H ldapi:/// -b cn=config
</code></pre></div></div>
<p>Loption -Q cache les infos SASL, inutile à laffichage.</p>
<p>Ou sur un socket réseau :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -x -H ldap://localhost -D cn=admin,dc=xoyize,dc=xyz -W -b cn=config
</code></pre></div></div>
<p>Sur le socket réseau, nous indiquons les options :</p>
<p>-x : authentification simple.</p>
<p>-H indique lhôte (en loccurrence, localhost).</p>
<p>-D est le compte qui va se connecter.</p>
<p>-W sert à demander le mot de passe.</p>
<p>-b ou basedn est lendroit ou nous voulons faire notre recherche.</p>
<p>La commande fonctionne mais ne retourne rien. Le compte admin na pas accès à la configuration en passant par le socket réseau (nous allons y remédier après).</p>
<p>Au passage, pourquoi donc sembêter alors quon ne pourrait quutiliser le socket interne ?</p>
<p>Lintérêt du socket réseau est quon peut lancer les commandes à distance (dans ce cas remplacer localhost par le nom de la machine distante).
C BaseDn, Filtres, Attributs</p>
<p>Je reviens sur la commande :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -Q -LLL -Y external -H ldapi:/// -b cn=config
</code></pre></div></div>
<p>Celle ci affiche donc lintégralité de larbre cn=config (le baseDN de notre requête).</p>
<p>Au passage, on peut lécrire sous la forme :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -QLLLY EXTERNAL -H ldapi:/// -b "cn=config"
</code></pre></div></div>
<p>Si lon veut nafficher que les attributs dn des résultats, on fera alors :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -QLLLY EXTERNAL -H ldapi:/// -b "cn=config" dn
</code></pre></div></div>
<p>Si par exemple, je veux afficher dautres attributs :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -QLLLY EXTERNAL -H ldapi:/// -b "cn=config" "(objectClass=olcModuleList)" dn olcModuleLoad
</code></pre></div></div>
<p>Dans la commande précédente, jutilise un filtre. Pour les utiliser, il suffit de les indiquer après loption -b et avant ce que lon demande par ex :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -QLLLY EXTERNAL -H ldapi:/// -b "cn=config" "(&amp;(objectClass=olcDatabaseConfig))" dn
</code></pre></div></div>
<p>Cette commande naffichera que les attributs dn des objectClass olcDatabaseConfig. Le &amp; est facultatif. Jaurais très bien pu mettre :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -QLLLY EXTERNAL -H ldapi:/// -b "cn=config" "(objectClass=olcDatabaseConfig)" dn
</code></pre></div></div>
<p>Si lon ne veut pas que les attributs dn, mais toutes les infos :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -QLLLY EXTERNAL -H ldapi:/// -b "cn=config" "(&amp;(objectClass=olcDatabaseConfig))"
</code></pre></div></div>
<table>
<tbody>
<tr>
<td>Et si je voulais les attributs dn des objectClass étant des olcDatabaseConfig ou ceux étant des olcModuleList, je vais utiliser le</td>
<td>pour indiquer un OU (le ET étant spécifié par &amp;)</td>
</tr>
</tbody>
</table>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -QLLLY EXTERNAL -H ldapi:/// -b "cn=config" "(|(objectClass=olcDatabaseConfig)(objectClass=olcModuleList))" dn
</code></pre></div></div>
<p>Petite explication sur lutilisation des filtres. Ceux ci utilisent un principe qui rappelle la notation polonaise inversée, lopérateur étant ici devant.</p>
<p>Exemple :</p>
<p>Si je veux A et B :</p>
<p>( &amp; (A) (B) )</p>
<p>Si je veux A ou B</p>
<table>
<tbody>
<tr>
<td>(</td>
<td>(A) (B) )</td>
</tr>
</tbody>
</table>
<p>Si je veux A et B ou A et C :</p>
<table>
<tbody>
<tr>
<td>(</td>
<td>( &amp; (A) (B) ) ( &amp; (A) (C) ) )</td>
</tr>
</tbody>
</table>
<p>ou, en simplifiant :</p>
<table>
<tbody>
<tr>
<td>( &amp; (A) (</td>
<td>(B) (C) ))</td>
</tr>
</tbody>
</table>
<p>On peut aussi inverser avec le !.</p>
<p>Si lon veut par ex ce qui est A et ce qui nest pas B :</p>
<p>( &amp; (A) (! B) )</p>
<p>Au niveau des filtres, nous pouvons utiliser les opérateurs suivants :</p>
<ul>
<li>&lt; : plus petit que</li>
<li>&lt;= : plus petit ou égal</li>
<li>= : égal</li>
<li>
<blockquote>
<p>: plus grand</p>
</blockquote>
</li>
<li>
<blockquote>
<p>= : plus grand ou égal</p>
</blockquote>
</li>
<li>=* : présence de (retourne les entrées ou lattribut est présent)</li>
<li>~= : approximation ( par ex, ~=Nocolas pourra retourner Nicolas)</li>
</ul>
<p>On peut utiliser un joker, par ex, = <em>toto</em> sortira tout ce qui contient toto comme sous chaîne.</p>
<p>Il existe également la notion de filtres étendus, utilisé ainsi : attribut:dn:=valeur.</p>
<p>Par ex :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -xLLL -H ldap://localhost -D cn=admin,dc=xoyize,dc=xyz -w password -b "dc=xoyize,dc=xyz" ou=people dn
</code></pre></div></div>
<p>me donne :</p>
<p>dn: ou=people,dc=xoyize,dc=xyz</p>
<p>Et :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -xLLL -H ldap://localhost -D cn=admin,dc=xoyize,dc=xyz -w password -b "dc=xoyize,dc=xyz" ou:dn:=people dn
</code></pre></div></div>
<p>me donne :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: ou=people,dc=xoyize,dc=xyz
dn: ou=client,ou=people,dc=xoyize,dc=xyz
dn: ou=xoyize,ou=people,dc=xoyize,dc=xyz
dn: uid=niko,ou=xoyize,ou=people,dc=xoyize,dc=xyz
dn: uid=ldap1,ou=xoyize,ou=people,dc=xoyize,dc=xyz
dn: uid=ldap2,ou=xoyize,ou=people,dc=xoyize,dc=xyz
</code></pre></div></div>
<p>Ils permettent donc de considérer les éléments du DN comme faisant partie de lentrée elle même.</p>
<p>On peut également utiliser les filtres étendus pour modifier les opérateurs.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -xLLL -H ldap://localhost -D cn=admin,dc=xoyize,dc=xyz -w password -b "dc=xoyize,dc=xyz" title:2.5.13.5:=Admin dn
</code></pre></div></div>
<p>ne me sortira que les entrées ou lattribut title est égal à Admin (et non pas admin, ADMIN, etc…). Par défaut, lopération nest pas sensible à la casse.</p>
<p>Pour connaitre lensemble des matchingRules disponibles :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -xLLL -D cn=admin,dc=xoyize,dc=xyz -w password -b "cn=subschema" -s base matchingRuleUse | grep "matchingRule" | cut -d ' ' -f3,5
</code></pre></div></div>
<h3 id="configuration">Configuration</h3>
<p>Nous allons créer un répertoire /root/ldap qui va centraliser tous nos fichiers de configuration :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo -s
mkdir -p /root/ldap
cd /root/ldap
</code></pre></div></div>
<h4 id="compte-admin">Compte admin</h4>
<p>1 Mdp Admin dans un fichier</p>
<p>Afin déviter davoir à toujours retaper le mot de passe admin (mot de passe que nous avons créé lors de la configuration du paquet) lors des commandes, nous allons lenregistrer dans un fichier.</p>
<p>On va créer un fichier /root/pwdldap et mettre le mot de passe dedans :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>echo -n "mdpadmin" &gt; /root/pwdldap
chmod 600 /root/pwdldap
</code></pre></div></div>
<p>On test :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -x -H ldap://localhost -D cn=admin,dc=xoyize,dc=xyz -y /root/pwdldap -b dc=xoyize,dc=xyz
</code></pre></div></div>
<p>2 Droits daccès à la configuration du serveur</p>
<p>Par défaut, laccès à la configuration nest pas possible en passant par le socket réseau avec le compte admin :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -xLLL -H ldap://localhost -D cn=admin,dc=xoyize,dc=xyz -y /root/pwdldap -b cn=config
</code></pre></div></div>
<p>nous donne :</p>
<p>No such object (32)</p>
<p>Créez le fichier LDIF /root/ldap/acces-conf-admin.ldif, et insérez :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat &gt; /root/ldap/acces-conf-admin.ldif &lt;&lt; EOF
dn: olcDatabase={0}config,cn=config
changeType: modify
add: olcAccess
olcAccess: to * by dn.exact=cn=admin,dc=xoyize,dc=xyz manage by * break
EOF
</code></pre></div></div>
<p>Injectez :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapmodify -Y external -H ldapi:/// -f acces-conf-admin.ldif
</code></pre></div></div>
<p>Et restestez :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -x -H ldap://localhost -D cn=admin,dc=xoyize,dc=xyz -y /root/pwdldap -b dc=xoyize,dc=xyz
</code></pre></div></div>
<h3 id="les-overlays">Les Overlays</h3>
<ul>
<li>accesslog : Enregistrement des accès. Attention, réduit drastiquement les performances.</li>
<li><strong>auditlog</strong> : Enregistrement des modifications.</li>
<li>chain : Liaison de plusieurs serveurs LDAP au niveau des recherches.</li>
<li>collect : Implémentation de la RFC 3671. Les attributs collectifs partagent des valeurs communes entre lensemble des membres héritant dune entrée commune.</li>
<li>constraint : Permet de forcer des contraintes sur des attributs.</li>
<li><strong>memberof</strong> : Permet de connaître les groupes auxquels appartient un utilisateur</li>
<li><strong>ppolicy</strong> : Password Policy. Permet la mise en place de contrôles sur les mots de passe (longueur, durée de validité, etc…).</li>
<li><strong>refint</strong> :Referential Integrity. Permet de sassurer de la cohérence de lannuaire lors de suppression dentrées.</li>
<li>syncprov :Syncrepl Provider. Permet la réplication syncrepl, incluant la fonctionnalité de recherche persistante. On linstallera plus tard.</li>
<li>translucent : Translucent Proxy. Cet overlay peut être utilisé avec un backend local pour créer un proxy transparent. Le contenu des entrées récupérées à partir dun serveur LDAP distant peut être en partie réécrit/modifé/completé par la base locale.</li>
<li><strong>unique</strong> : permet de sassurer de lunicité dattributs.</li>
<li>valsort : Permet de forcer lordre pour les valeurs dattributs lorsquils sont retournées suite à une recherche.</li>
</ul>
<blockquote>
<p>En gras, ceux dont nous nous servirons.</p>
</blockquote>
<h4 id="memberof">MemberOf</h4>
<p>Loverlay memberof permet de savoir dans quels groupes se trouve un utilisateur en une seule requête au lieu de deux.<br />
Créez un fichier memberof_act.ldif et insérez le contenu suivant :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat &gt; /root/ldap/memberof_act.ldif &lt;&lt; EOF
dn: cn=module,cn=config
cn:module
objectclass: olcModuleList
objectclass: top
olcmoduleload: memberof.la
olcmodulepath: /usr/lib/ldap
EOF
</code></pre></div></div>
<p>Créez un fichier memberof_conf.ldif et insérez le contenu suivant :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat &gt; /root/ldap/memberof_conf.ldif &lt;&lt; EOF
dn: olcOverlay=memberof,olcDatabase={1}mdb,cn=config
changetype: add
objectClass: olcMemberOf
objectClass: olcOverlayConfig
objectClass: olcConfig
objectClass: top
olcOverlay: memberof
olcMemberOfDangling: ignore
olcMemberOfRefInt: TRUE
olcMemberOfGroupOC: groupOfNames
olcMemberOfMemberAD: member
olcMemberOfMemberOfAD: memberOf
EOF
</code></pre></div></div>
<p>On injecte :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapadd -Y EXTERNAL -H ldapi:/// -f memberof_act.ldif
ldapadd -Y EXTERNAL -H ldapi:/// -f memberof_conf.ldif
</code></pre></div></div>
<p>Pour vérifier :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -QLLLY EXTERNAL -H ldapi:/// -b "cn=config" "Objectclass=olcModuleList"
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: cn=module{0},cn=config
objectClass: olcModuleList
cn: module{0}
olcModulePath: /usr/lib/ldap
olcModuleLoad: {0}back_mdb
dn: cn=module{1},cn=config
objectClass: olcModuleList
objectClass: top
cn: module{1}
olcModulePath: /usr/lib/ldap
olcModuleLoad: {0}memberof.la
</code></pre></div></div>
<p>Ou bien encore :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tree /etc/ldap/slapd.d/
</code></pre></div></div>
<p>On doit trouver trace dans la liste des modules.<br />
On peut vérifier la configuration avec :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -QLLLY EXTERNAL -H ldapi:/// -b "cn=config" "Objectclass=olcmemberOf"
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: olcOverlay={0}memberof,olcDatabase={1}mdb,cn=config
objectClass: olcMemberOf
objectClass: olcOverlayConfig
objectClass: olcConfig
objectClass: top
olcOverlay: {0}memberof
olcMemberOfDangling: ignore
olcMemberOfRefInt: TRUE
olcMemberOfGroupOC: groupOfNames
olcMemberOfMemberAD: member
olcMemberOfMemberOfAD: memberOf
</code></pre></div></div>
<h4 id="intégrité-référentielle">Intégrité Référentielle</h4>
<p>Cet overlay permet de supprimer un utilisateur dun groupe quand on supprime lutilisateur. Au passage, si un groupe se retrouve vide, ladmin sera automatiquement ajouté (un groupe vide créé une erreur dans OpenLdap)</p>
<p>Créez un fichier refint_act.ldif et insérez le contenu suivant :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat &gt; /root/ldap/refint_act.ldif &lt;&lt; EOF
dn: cn=module,cn=config
cn: module
objectclass: olcModuleList
objectclass: top
olcmoduleload: refint.la
olcmodulepath: /usr/lib/ldap
EOF
</code></pre></div></div>
<p>Créez un fichier refint_conf.ldif et insérez le contenu suivant :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat &gt; /root/ldap/refint_conf.ldif &lt;&lt; EOF
dn: olcOverlay=refint,olcDatabase={1}mdb,cn=config
objectClass: olcConfig
objectClass: olcOverlayConfig
objectClass: olcRefintConfig
objectClass: top
olcOverlay: refint
olcRefintAttribute: memberof member manager owner
olcRefintNothing: cn=admin,dc=xoyize,dc=xyz
EOF
</code></pre></div></div>
<p>On injecte :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapadd -Q -Y EXTERNAL -H ldapi:/// -f refint_act.ldif
ldapadd -Q -Y EXTERNAL -H ldapi:/// -f refint_conf.ldif
</code></pre></div></div>
<p>On vérifie :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -QLLLY EXTERNAL -H ldapi:/// -b "cn=config" "Objectclass=olcModuleList"
</code></pre></div></div>
<p>On peut vérifier la configuration avec :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -QLLLY EXTERNAL -H ldapi:/// -b "cn=config" "Objectclass=olcRefintConfig"
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: olcOverlay={1}refint,olcDatabase={1}mdb,cn=config
objectClass: olcConfig
objectClass: olcOverlayConfig
objectClass: olcRefintConfig
objectClass: top
olcOverlay: {1}refint
olcRefintAttribute: memberof member manager owner
olcRefintNothing: cn=admin,dc=xoyize,dc=xyz
</code></pre></div></div>
<h4 id="overlay-audit-log">Overlay Audit Log</h4>
<p>Cet overlay sert à auditer chaque modification au sein de lannuaire. Dans notre cas, cela sera inscrit dans le fichier : /var/log/openldap/audit.ldif</p>
<p>Créez le fichier auditlog_act.ldif pour y insérer :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat &gt; /root/ldap/auditlog_act.ldif &lt;&lt; EOF
dn: cn=module,cn=config
cn: module
objectclass: olcModuleList
objectclass: top
olcModuleLoad: auditlog.la
olcmodulepath: /usr/lib/ldap
EOF
</code></pre></div></div>
<p>Créez le fichier auditlog_conf.ldif pour y insérer :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat &gt; /root/ldap/auditlog_conf.ldif &lt;&lt; EOF
dn: olcOverlay=auditlog,olcDatabase={1}mdb,cn=config
objectClass: olcOverlayConfig
objectClass: olcAuditLogConfig
olcOverlay: auditlog
olcAuditlogFile: /var/log/openldap/auditlog.ldif
EOF
</code></pre></div></div>
<p>Injectez :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapadd -Q -Y EXTERNAL -H ldapi:/// -f auditlog_act.ldif
ldapadd -Q -Y EXTERNAL -H ldapi:/// -f auditlog_conf.ldif
</code></pre></div></div>
<p>On vérifie :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -QLLLY EXTERNAL -H ldapi:/// -b "cn=config" "Objectclass=olcModuleList"
</code></pre></div></div>
<p>On peut vérifier la configuration avec :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -QLLLY EXTERNAL -H ldapi:/// -b "cn=config" "Objectclass=olcAuditLogConfig"
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: olcOverlay={2}auditlog,olcDatabase={1}mdb,cn=config
objectClass: olcOverlayConfig
objectClass: olcAuditlogConfig
olcOverlay: {2}auditlog
olcAuditlogFile: /var/log/openldap/auditlog.ldif
</code></pre></div></div>
<p>Ensuite, nous allons créer le fichier :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkdir -p /var/log/openldap
chmod 755 /var/log/openldap
chown openldap:openldap /var/log/openldap
touch /var/log/openldap/auditlog.ldif
chmod 755 /var/log/openldap/auditlog.ldif
chown openldap:openldap /var/log/openldap/auditlog.ldif
</code></pre></div></div>
<h4 id="overlay-unique">Overlay Unique</h4>
<p>Cet overlay permet de nous assurer lunicité des attributs que lon spécifie.</p>
<p>Créez un fichier unique_act.ldif pour y insérer :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat &gt; /root/ldap/unique_act.ldif &lt;&lt; EOF
dn: cn=module,cn=config
cn: module
objectclass: olcModuleList
objectclass: top
olcModuleLoad: unique.la
olcmodulepath: /usr/lib/ldap
EOF
</code></pre></div></div>
<p>Créez un fichier unique_conf.ldif pour y insérer :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat &gt; /root/ldap/unique_conf.ldif &lt;&lt; EOF
dn: olcOverlay=unique,olcDatabase={1}mdb,cn=config
objectClass: olcOverlayConfig
objectClass: olcUniqueConfig
olcOverlay: unique
olcUniqueUri: ldap:///ou=people,dc=xoyize,dc=xyz?uid?sub
olcUniqueUri: ldap:///ou=people,dc=xoyize,dc=xyz?mail?sub
olcUniqueUri: ldap:///ou=people,dc=xoyize,dc=xyz?uidNumber?sub
olcUniqueUri: ldap:///ou=groups,dc=xoyize,dc=xyz?cn?sub
EOF
</code></pre></div></div>
<p>Nous demandons ici à ce que les attributs ui, mail et uidNumber dans lou people soient uniques. Et que lattribut cn dans lou groups soit lui aussi unique.</p>
<p>Injectez :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapadd -Q -Y EXTERNAL -H ldapi:/// -f unique_act.ldif
ldapadd -Q -Y EXTERNAL -H ldapi:/// -f unique_conf.ldif
</code></pre></div></div>
<p>On vérifie :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -QLLLY EXTERNAL -H ldapi:/// -b "cn=config" "Objectclass=olcModuleList"
</code></pre></div></div>
<p>On peut vérifier la configuration avec :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -QLLLY EXTERNAL -H ldapi:/// -b "cn=config" "Objectclass=olcUniqueConfig"
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: olcOverlay={3}unique,olcDatabase={1}mdb,cn=config
objectClass: olcOverlayConfig
objectClass: olcUniqueConfig
olcOverlay: {3}unique
olcUniqueURI: ldap:///ou=people,dc=xoyize,dc=xyz?uid?sub
olcUniqueURI: ldap:///ou=people,dc=xoyize,dc=xyz?mail?sub
olcUniqueURI: ldap:///ou=people,dc=xoyize,dc=xyz?uidNumber?sub
olcUniqueURI: ldap:///ou=groups,dc=xoyize,dc=xyz?cn?sub
</code></pre></div></div>
<h4 id="overlay-ppolicy">Overlay Ppolicy</h4>
<p>Cet overlay va nous permettre de spécifier une politique de mot de passe.</p>
<p>Un peu particulier, il faut ajouter son schéma dans lannuaire :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapadd -Q -Y EXTERNAL -H ldapi:/// -f /etc/ldap/schema/ppolicy.ldif
</code></pre></div></div>
<p>On peut voir que cest bon avec :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -QLLLY EXTERNAL -H ldapi:/// -b "cn=schema,cn=config" cn
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: cn=schema,cn=config
cn: schema
dn: cn={0}core,cn=schema,cn=config
cn: {0}core
dn: cn={1}cosine,cn=schema,cn=config
cn: {1}cosine
dn: cn={2}nis,cn=schema,cn=config
cn: {2}nis
dn: cn={3}inetorgperson,cn=schema,cn=config
cn: {3}inetorgperson
dn: cn={4}ppolicy,cn=schema,cn=config
cn: {4}ppolicy
</code></pre></div></div>
<p>Ou bien encore :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tree /etc/ldap/slapd.d/
</code></pre></div></div>
<p>Dans la branche cn=schema, on doit voir le schéma ppolicy qui sest ajouté à ceux présent par défaut (core, cosine, nis et inetorgperson).</p>
<p>Créez un fichier ppolicy_act.ldif pour y insérer :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat &gt; /root/ldap/ppolicy_act.ldif &lt;&lt; EOF
dn: cn=module,cn=config
cn: module
objectclass: olcModuleList
objectclass: top
olcModuleLoad: ppolicy.la
olcmodulepath: /usr/lib/ldap
EOF
</code></pre></div></div>
<p>Créez un fichier ppolicy_conf.ldif pour y insérer :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat &gt; /root/ldap/ppolicy_conf.ldif &lt;&lt; EOF
dn: olcOverlay=ppolicy,olcDatabase={1}mdb,cn=config
objectClass: olcOverlayConfig
objectClass: olcPpolicyConfig
olcOverlay: ppolicy
olcPPolicyDefault: cn=ppolicy,dc=xoyize,dc=xyz
olcPPolicyHashCleartext: TRUE
olcPPolicyUseLockout: FALSE
EOF
</code></pre></div></div>
<p>olcPPolicyDefault : Indique le DN de configuration utilisé par défaut (nous le définirons apres).</p>
<p>olcPPolicyHashCleartext : Indique si les mots de passe doivent être cryptés.</p>
<p>olcPPolicyUseLockout : Si TRUE, le message derreur retourné en cas de connexion à un compte vérouillé indiquera quil sagit dun compte vérouillé. Si FALSE, ce sera un message déchec stantard.</p>
<p>On injecte</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapadd -Q -Y EXTERNAL -H ldapi:/// -f ppolicy_act.ldif
ldapadd -Q -Y EXTERNAL -H ldapi:/// -f ppolicy_conf.ldif
</code></pre></div></div>
<p>On vérifie :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -QLLLY EXTERNAL -H ldapi:/// -b "cn=config" "Objectclass=olcPpolicyConfig" -LLL
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: olcOverlay={4}ppolicy,olcDatabase={1}mdb,cn=config
objectClass: olcOverlayConfig
objectClass: olcPPolicyConfig
olcOverlay: {4}ppolicy
olcPPolicyDefault: cn=ppolicy,dc=xoyize,dc=xyz
olcPPolicyHashCleartext: TRUE
olcPPolicyUseLockout: FALSE
</code></pre></div></div>
<p>On va maintenant créer la politique par défaut.</p>
<p>Créez un fichier ppolicy_def.ldif pour y insérer :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat &gt; /root/ldap/ppolicy_def.ldif &lt;&lt; EOF
dn: cn=ppolicy,dc=xoyize,dc=xyz
objectClass: top
objectClass: device
objectClass: pwdPolicy
cn: ppolicy
pwdAllowUserChange: TRUE
pwdAttribute: userPassword
pwdCheckQuality: 1
pwdExpireWarning: 0
pwdFailureCountInterval: 30
pwdGraceAuthNLimit: 5
pwdInHistory: 5
pwdLockout: TRUE
pwdLockoutDuration: 60
pwdMaxAge: 0
pwdMaxFailure: 5
pwdMinAge: 0
pwdMinLength: 5
pwdMustChange: FALSE
pwdSafeModify: FALSE
EOF
</code></pre></div></div>
<p>La signification des attributs est :</p>
<p>pwdAllowUserChange : indique si lutilisateur peut changer son mot de passe.</p>
<p>pwdCheckQuality : indique si OpenLDAP renvoie une erreur si le mot de passe nest pas conforme</p>
<p>pwdExpireWarning : avertissement dexpiration.</p>
<p>pwdFailureCountInterval : Intervalle de temps entre deux tentatives infructueuses pour quelles soient considérées comme « à la suite ».</p>
<p>pwdGraceAuthNLimit : période de grâce suite à lexpiration du mot de passe.</p>
<p>pwdInHistory : nombre de mots de passe dans lhistorique.</p>
<p>pwdLockout : indique si on bloque le compte au bout de X échecs.</p>
<p>pwdLockoutDuration : durée du blocage du compte (en secondes).</p>
<p>pwdMaxAge : age maximal du mot de passe (en secondes).</p>
<p>pwdMaxFailure : nombre déchecs de saisie du mot de passe maximal (avant blocage).</p>
<p>pwdMinAge : age minimal du mot de passe (en secondes).</p>
<p>pwdMinLength : longueur minimale du mot de passe.</p>
<p>pwdMustChange : indique si lutilisateur doit changer son mot de passe.</p>
<p>pwdSafeModify : indique si il faut envoyer lancien mot de passe avec le nouveau pour modification.</p>
<p>On injecte :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapadd -H ldap://localhost -D cn=admin,dc=xoyize,dc=xyz -y /root/pwdldap -f ppolicy_def.ldif
</code></pre></div></div>
<p>On vérifie :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -QLLLY EXTERNAL -H ldapi:/// -b "dc=xoyize,dc=xyz" "Objectclass=pwdPolicy"
</code></pre></div></div>
<p>On peut avoir plusieurs politique (une adapté pour des comptes clients, par exemple).</p>
<p>Par ex, fichier ppolicy-client.ldif :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat &gt; /root/ldap/ppolicy-client.ldif &lt;&lt; EOF
dn: cn=ppolicy-client,dc=xoyize,dc=xyz
objectClass: top
objectClass: device
objectClass: pwdPolicy
cn: ppolicy-client
pwdAllowUserChange: TRUE
pwdAttribute: userPassword
pwdCheckQuality: 1
pwdExpireWarning: 0
pwdFailureCountInterval: 30
pwdGraceAuthNLimit: 5
pwdInHistory: 10
pwdLockout: TRUE
pwdLockoutDuration: 60
pwdMaxAge: 0
pwdMaxFailure: 3
pwdMinAge: 0
pwdMinLength: 8
pwdMustChange: FALSE
pwdSafeModify: TRUE
EOF
</code></pre></div></div>
<p>Quon injecte :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapadd -H ldap://localhost -D cn=admin,dc=xoyize,dc=xyz -y /root/pwdldap -f ppolicy-client.ldif
</code></pre></div></div>
<hr />
<p>Et quon affectera avec un fichier changeppolicy.ldif :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat &gt; /root/ldap/ppolicy-client.ldif &lt;&lt; EOF
dn: uid=toto,ou=client,ou=people,dc=xoyize,dc=xyz
changetype: modify
replace: pwdPolicySubEntry
pwdPolicySubEntry: cn=ppolicy-client,dc=xoyize,dc=xyz
EOF
</code></pre></div></div>
<p>Quon injectera avec :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapadd -H ldap://localhost -D cn=admin,dc=xoyize,dc=xyz -y /root/pwdldap -f changeppolicy.ldif
</code></pre></div></div>
<p>On pourra aussi spécifier cet attribut à la création de lutilisateur.</p>
<h3 id="peuplement-de-lannuaire">Peuplement de lannuaire</h3>
<h4 id="les-ous">Les OUs</h4>
<p>Nous avions créé un répertoire /root/ldap/conf pour y mettre la configuration. Créons un répertoire pour y mettre les fichiers ldif contenant les données.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkdir -p /root/ldap/data
cd /root/ldap/data
</code></pre></div></div>
<p>Pour rappel, les OUs sont des conteneurs qui permettent de ranger les données dans lannuaire, de les hiérarchiser. Il ne faut pas les confondre avec les Groupes.</p>
<p>Créez un fichier OU.ldif et remplissez le de la sorte :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat &gt; /root/ldap/data/OU.ldif &lt;&lt; EOF
dn: ou=people,dc=xoyize,dc=xyz
ou: people
objectClass: organizationalUnit
dn: ou=group,dc=xoyize,dc=xyz
ou: group
objectClass: organizationalUnit
dn: ou=system,dc=xoyize,dc=xyz
ou: system
objectClass: organizationalUnit
dn: ou=xoyize,ou=people,dc=xoyize,dc=xyz
ou: xoyize
objectClass: organizationalUnit
dn: ou=client,ou=people,dc=xoyize,dc=xyz
ou: client
objectClass: organizationalUnit
dn: ou=sysgroup,ou=group,dc=xoyize,dc=xyz
ou: sysgroup
objectClass: organizationalUnit
dn: ou=workgroup,ou=group,dc=xoyize,dc=xyz
ou: workgroup
objectClass: organizationalUnit
EOF
</code></pre></div></div>
<p>On injecte :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapadd -cxWD cn=admin,dc=xoyize,dc=xyz -y /root/pwdldap -f OU.ldif
</code></pre></div></div>
<h4 id="les-utilisateurs">Les utilisateurs</h4>
<p>On va créer notre premier utilisateur.(remplacer password par le vrai mot de passe)</p>
<p>Fichier User_yannick.ldif :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat &gt; /root/ldap/data/User_yannick.ldif &lt;&lt; EOF
dn: uid=yannick,ou=xoyize,ou=people,dc=xoyize,dc=xyz
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
uid: yannick
sn: yannick
givenName: Yannick
cn: Yannick
displayName: Yannick
userPassword: kfhd59erty54
mail: yannick@xoyize.xyz
title: Admin
initials: Y
EOF
</code></pre></div></div>
<p>On injecte :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapadd -cxWD cn=admin,dc=xoyize,dc=xyz -y /root/pwdldap -f User_yannick.ldif
</code></pre></div></div>
<p>Profitons pour faire une explication sur ces lignes :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
</code></pre></div></div>
<p>Notre utilisateur héritera des attributs de ces trois classes. Mais quest ce donc ?</p>
<p>Voyons un peu les schémas :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -xLLL -H ldap://localhost -D cn=admin,dc=xoyize,dc=xyz -y /root/pwdldap -b "cn=schema,cn=config"
</code></pre></div></div>
<p>Beaucoup dinfos, pour sen sortir affichons les dn des schémas :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -xLLL -H ldap://localhost -D cn=admin,dc=xoyize,dc=xyz -y /root/pwdldap -b "cn=schema,cn=config" dn
</code></pre></div></div>
<p>Ce qui nous affiche :</p>
<p>dn: cn=schema,cn=config
dn: cn={0}core,cn=schema,cn=config
dn: cn={1}cosine,cn=schema,cn=config
dn: cn={2}nis,cn=schema,cn=config
dn: cn={3}inetorgperson,cn=schema,cn=config
dn: cn={4}ppolicy,cn=schema,cn=config</p>
<p>Concentrons nous sur le schéma core :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -xLLL -H ldap://localhost -D cn=admin,dc=xoyize,dc=xyz -y /root/pwdldap -b "cn=schema,cn=config" "cn={0}core"
</code></pre></div></div>
<p>La vous devez voir deux grands types de données : les <strong>olcObjectClasses</strong> et les <strong>olcAttributeTypes</strong>:</p>
<p>Les AttributeTypes sont les attributs que peuvent avoir les entrées de lannuaire. Les ObjectClasses sont les regroupement de ces attributs.</p>
<p>Regardons les différents ObjectClasses qui composent notre schéma core :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -xLLL -H ldap://localhost -D cn=admin,dc=xoyize,dc=xyz -y /root/pwdldap -b "cn=schema,cn=config" "cn={0}core" olcObjectClasses
</code></pre></div></div>
<p>En cherchant dedans, on va trouver nos objectclasses person et organizationalPerson :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>olcObjectClasses: {4}( 2.5.6.6 NAME 'person' DESC 'RFC2256: a person' SUP top
STRUCTURAL MUST ( sn $ cn ) MAY ( userPassword $ telephoneNumber $ seeAlso $
description ) )
olcObjectClasses: {5}( 2.5.6.7 NAME 'organizationalPerson' DESC 'RFC2256: an o
rganizational person' SUP person STRUCTURAL MAY ( title $ x121Address $ regis
teredAddress $ destinationIndicator $ preferredDeliveryMethod $ telexNumber $
teletexTerminalIdentifier $ telephoneNumber $ internationaliSDNNumber $ fac
simileTelephoneNumber $ street $ postOfficeBox $ postalCode $ postalAddress $
physicalDeliveryOfficeName $ ou $ st $ l ) )
</code></pre></div></div>
<p>Ce quil faut comprendre :</p>
<p>LObjectClasses person hérite de top.</p>
<p>Must spécifie les AttributeTypes obligatoires : sn et cn.</p>
<p>May définie les AttributeTypes facultatives</p>
<p>Les AttributeTypes sont définis sur un modèle similaire, par exemple :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -xLLL -H ldap://localhost -D cn=admin,dc=xoyize,dc=xyz -y /root/pwdldap -b "cn=schema,cn=config" "cn={0}core" olcAttributeTypes
</code></pre></div></div>
<p>Jen prend un au hasard :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>olcAttributeTypes: {6}( 2.5.4.9 NAME ( 'street' 'streetAddress' ) DESC 'RFC225
6: street address of this object' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreS
ubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} )
</code></pre></div></div>
<p>On trouve le nom de lattribut, une description et son type (SYNTAX suivi dun OID, je reviendrais sur ce point dans le chapitre consacré à la création dun schéma).</p>
<p>Lobjet que lon créé hérite des attributs des classes person, organizationalPerson et inetOrgPerson.</p>
<p>Voila donc pour les explications sur les ObjectClasses et les AttributeTypes:</p>
<p>Au passage, on va vérifier quelques points.</p>
<p>Regardons comment est stocké le mdp dans la base :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -xLLL -H ldap://localhost -D cn=admin,dc=xoyize,dc=xyz -y /root/pwdldap -b "dc=xoyize,dc=xyz" "uid=yannick"
</code></pre></div></div>
<p>On voit une ligne :</p>
<p>userPassword:: e1NTSEF9TklxSkd5YVFBdVcwMkpOVmplcjArOGYwRmNNMjNBVXg=</p>
<p>Il sagit dun codage en base64. Pour le décoder :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>echo e1NTSEF9TklxSkd5YVFBdVcwMkpOVmplcjArOGYwRmNNMjNBVXg= | base64 -d
</code></pre></div></div>
<p>Et la on le résultat suivant :</p>
<p>{SSHA}NIqJGyaQAuW02JNVjer0+8f0FcM23AUx</p>
<p>Le mot de passe est bien crypté comme configuré avec loverlay ppolicy.</p>
<p>Et pour tester la connexion avec ce compte :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -xLLLH ldap://localhost -D uid=yannick,ou=xoyize,ou=people,dc=xoyize,dc=xyz -W -b "dc=xoyize,dc=xyz" "uid=yannick"
</code></pre></div></div>
<p>Et si vous tapez le bon mot de passe, cela vous affiche votre propre fiche.</p>
<h4 id="les-groupes">Les groupes</h4>
<p>A noter, il existe deux types de groupes : les posixgroup et les groupofnames.
Les posixgroup sont similaires au groupes Unix, et les groupofnames ressemblent plus à des groupes AD.
Pour faire simple, lavantage des groupofnames est quavec un filtre sur un utilisateur, on peut connaitre ses groupes (avec loverlay memberof). Chose impossible avec les posixgroups.
Par contre, il est impératif davoir au moins un utilisateur dans un groupe de type groupofnames. Vous pouvez être tenté de vous mettre par défaut, mais le jour ou vous aller modifier votre schéma, vous allez devoir supprimer les utilisateurs, et un groupe sans utilisateur garde luid du dernier membre à lavoir quitté et si vous refaite un compte avec le même uid, impossible de vous remettre dedans sans supprimer le groupe. Bref, perso, je met ladmin par défaut, comme ça pas de risques. De plus notre overlay refint configuré au préalable est raccord avec cela.</p>
<p>On créé un fichier Group.ldif :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat &gt; /root/ldap/data/Group.ldif &lt;&lt; EOF
dn: cn=cloud,ou=sysgroup,ou=group,dc=xoyize,dc=xyz
cn: cloud
description: Cloud
objectClass: groupOfNames
member: cn=admin,dc=xoyize,dc=xyz
dn: cn=xoyize ,ou=workgroup,ou=group,dc=xoyize,dc=xyz
cn: xoyize
description: xoyize
objectClass: groupOfNames
member: cn=admin,dc=xoyize,dc=xyz
EOF
</code></pre></div></div>
<p>Jai mis dedans les groupes dont jai besoin, à vous de faire en fonction, etc…</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapadd -cxWD cn=admin,dc=xoyize,dc=xyz -y /root/pwdldap -f Group.ldif
</code></pre></div></div>
<p>Au passage, on peut tester notre overlay memberof :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -xLLLH ldap://localhost -D cn=admin,dc=xoyize,dc=xyz -y /root/pwdldap -b "dc=xoyize,dc=xyz" "cn=admin" memberof
</code></pre></div></div>
<p>Doit nous retourner quelque chose qui ressemble à :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dc=xoyize,dc=xyz" "cn=admin" memberof
dn: cn=admin,dc=xoyize,dc=xyz
memberOf: cn=cloud,ou=sysgroup,ou=group,dc=xoyize,dc=xyz
memberOf: cn=xoyize,ou=workgroup,ou=group,dc=xoyize,dc=xyz
</code></pre></div></div>
<p>A la question, si je veux rajouter un utilisateur dans un groupe, comment faire ?</p>
<p>Je vous répond quavec OpenLdap, tout passe par des fichiers ldif, du coup, créez un fichier addusertogroup.ldif pour y mettre :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: cn=cloud,ou=sysgroup,ou=group,dc=xoyize,dc=xyz
changetype: modify
add: member
member: uid=yannick,ou=xoyize,ou=people,dc=xoyize,dc=xyz
</code></pre></div></div>
<p>Puis on injecte :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapmodify -cxWD cn=admin,dc=xoyize,dc=xyz -y /root/pwdldap -f addusertogroup.ldif
</code></pre></div></div>
<p>Et la, si on requête cela :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -xLLLH ldap://localhost -D cn=admin,dc=xoyize,dc=xyz -y /root/pwdldap -b "dc=xoyize,dc=xyz" "uid=yannick" memberof
</code></pre></div></div>
<p>On doit avoir ceci :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: uid=yannick,ou=xoyize,ou=people,dc=xoyize,dc=xyz
memberOf: cn=cloud,ou=sysgroup,ou=group,dc=xoyize,dc=xyz
</code></pre></div></div>
<p>Nous verrons plus en détail les ajouts, modifications, suppressions dans la partie suivante.</p>
<h4 id="comptes-système-et-acl">Comptes Système et ACL</h4>
<p>Par défaut, laccès à lannuaire est assez ouvert :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -xLLL
</code></pre></div></div>
<p>On voit ce que laccès anonyme nous affiche…</p>
<p>On va devoir filtrer un peu.</p>
<p>Tout dabord, nous allons créer deux compte systèmes. Un viewer (qui aura des droits de lecture uniquement, utilisé par les applications pour se connecter à lannuaire et vérifier des droits, au lieu de passe par le bind anonyme) et un Writer (qui lui aura des droits décriture…)</p>
<p>Créez le fichier viewer.ldif :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat &gt; /root/ldap/data/viewer.ldif &lt;&lt; EOF
dn: cn=viewer,ou=system,dc=xoyize,dc=xyz
objectClass: simpleSecurityObject
objectClass: organizationalRole
cn: viewer
description: LDAP viewer
userPassword: passview
EOF
</code></pre></div></div>
<p>Créez le fichier writer.ldif :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat &gt; /root/ldap/data/writer.ldif &lt;&lt; EOF
dn: cn=writer,ou=system,dc=xoyize,dc=xyz
objectClass: simpleSecurityObject
objectClass: organizationalRole
cn: writer
description: LDAP Writer
userPassword: passwrite
EOF
</code></pre></div></div>
<p>Injectez :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapadd -cxWD cn=admin,dc=xoyize,dc=xyz -y /root/pwdldap -f viewer.ldif
ldapadd -cxWD cn=admin,dc=xoyize,dc=xyz -y /root/pwdldap -f writer.ldif
</code></pre></div></div>
<p>Voyons les ACLs par défaut :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -x -H ldap://localhost -D cn=admin,dc=xoyize,dc=xyz -y /root/pwdldap -b "cn=config" "olcDatabase={1}mdb" olcaccess
</code></pre></div></div>
<p>Modifions les pour empêcher les connexions anonymes.</p>
<p>Créez un fichier acl.ldif :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat &gt; /root/ldap/data/acl.ldif &lt;&lt; EOF
dn: olcDatabase={1}mdb,cn=config
changetype: modify
replace: olcAccess
olcAccess: to attrs=userPassword by self write by anonymous auth by dn="cn=writer,ou=system,dc=xoyize,dc=xyz" write by dn="cn=viewer,ou=system,dc=xoyize,dc=xyz" read by dn="cn=admin,dc=xoyize,dc=xyz" write by * none
olcAccess: to dn.base="dc=xoyize,dc=xyz" by users read
olcAccess: to * by self write by dn="cn=admin,dc=xoyize,dc=xyz" write by * read by anonymous none
EOF
</code></pre></div></div>
<p>Jexpliquerais plus précisément le fonctionnement des ACLs dans un autre article. Nous modifions la deuxieme ACL qui par défaut donne accès en lecture à tout le monde. La, nous réduisons la lecture aux utilisateurs authentifiés.</p>
<p>On injecte :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f acl.ldif
</code></pre></div></div>
<p>On test de manière anonyme :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -xLLL
</code></pre></div></div>
<p>Ça ne passe plus.</p>
<p>On teste avec notre compte viewer :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -xLLL -H ldap://localhost -D cn=viewer,ou=system,dc=xoyize,dc=xyz -w passview -b "dc=xoyize,dc=xyz" "uid=yannick"
</code></pre></div></div>
<h3 id="rootbashrc">/root/.bashrc</h3>
<p>Nous allons utiliser énormément les commande <strong>ldapsearch</strong> et <strong>ldapmodify</strong></p>
<p>Du coup, afin daller plus vite nous allons créer des alias.</p>
<p>Éditez le fichier /root/.bashrc pour y ajouter :</p>
<p>alias lmodif=ldapmodify -cxWD cn=admin,dc=xoyize,dc=xyz -y /root/pwdldap
alias lsearch=ldapsearch -xLLL -H ldap://localhost -D cn=admin,dc=xoyize,dc=xyz -y /root/pwdldap
alias ladd=ldapadd -cxWD cn=admin,dc=xoyize,dc=xyz -y /root/pwdldap
alias ldel=ldapdelete -cxWD cn=admin,dc=xoyize,dc=xyz -y /root/pwdldap</p>
<p>Puis pour recharger le fichier :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>source ~/.bashrc
</code></pre></div></div>
<p>Dorénavant, il ny aura plus tout à saisir…</p>
<h3 id="modification-des-données">Modification des données</h3>
<h4 id="ajout-dune-ou">Ajout dune OU</h4>
<p>Tout dabord, listons ce qui est présent</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lsearch -b "dc=xoyize,dc=xyz" "objectClass=organizationalUnit"
</code></pre></div></div>
<p>Je vais profiter de cette partie sur les OU pour expliquer les trois commandes à notre disposition pour modifier les données de lannuaire : ldapadd, ldapmodify et ldapdelete.</p>
<p>ldapadd est un alias de ldapmodify -a, le commutateur -a indiquant un ajout. Ce commutateur peut être remplacé par la ligne changetype: add dans le fichier ldif.</p>
<p>Ainsi, pour ajouter une OU par ex avec le fichier addou.ldif :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: ou=test,ou=people,dc=xoyize,dc=xyz
ou: test
objectClass: organizationalUnit
</code></pre></div></div>
<p>Injection :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapadd -cxWD cn=admin,dc=xoyize,dc=xyz -y /root/pwdldap -f addou.ldif
</code></pre></div></div>
<p>ou</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapmodify -cxWD cn=admin,dc=xoyize,dc=xyz -y /root/pwdldap -a -f addou.ldif
</code></pre></div></div>
<p>Autre méthode avec le ficher addou.ldif :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: ou=test,ou=people,dc=xoyize,dc=xyz
changetype: add
ou: test
objectClass: organizationalUnit
</code></pre></div></div>
<p>Injection</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapmodify -cxWD cn=admin,dc=xoyize,dc=xyz -y /root/pwdldap -f addou.ldif
</code></pre></div></div>
<p>Ces trois façons de faire donnent le même résultat.</p>
<p>Lavantage de tout faire par ex avec ldapmodify est que lon peut effectuer plusieurs opérations en un fichier, le tout étant de laisser une ligne avec entre chaque opération.</p>
<p>Exemple avec le fichier test.ldif :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: ou=test,ou=people,dc=xoyize,dc=xyz
changetype: add
ou: test
objectClass: organizationalUnit
-
dn: uid=toto,ou=xoyize,ou=people,dc=xoyize,dc=xyz
changetype: delete
</code></pre></div></div>
<p>Dans ce fichier, nous ajoutons une OU puis nous supprimons un utilisateur. Il ny a pas de limite dopérations que lon peut effectuer en une fois.</p>
<h4 id="modification-dune-ou">Modification dune OU</h4>
<p>Nous renommons lOU test en newtest avec le fichier chgou.ldif :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: ou=test,ou=people,dc=xoyize,dc=xyz
changetype: modrdn
newrdn: ou=newtest
deleteoldrdn: 1
</code></pre></div></div>
<p>Injection :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lmodif -f chgou.ldif
</code></pre></div></div>
<h4 id="déplacement-dune-ou">Déplacement dune OU</h4>
<p>Nous déplaçons lOU newtest de la branche ou=people,dc=xoyize,dc=xyz vers la branche ou=workgroup,ou=group,dc=xoyize,dc=xyz avec le fichier mvou.ldif :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: ou=newtest,ou=people,dc=xoyize,dc=xyz
changetype: modrdn
newrdn: ou=newtest
deleteoldrdn: 1
newsuperior: ou=workgroup,ou=group,dc=xoyize,dc=xyz
</code></pre></div></div>
<p>Injection :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lmodif -f mvou.ldif
</code></pre></div></div>
<h4 id="suppression-dune-ou">Suppression dune OU</h4>
<p>Il est nécessaire quelle soit vide.</p>
<p>Supprimons lOU newtest avec le fichier deleteou.ldif :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: ou=newtest,ou=workgroup,ou=group,dc=xoyize,dc=xyz
changetype: delete
</code></pre></div></div>
<p>Injection :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lmodif -f deleteou.ldif
</code></pre></div></div>
<p>Ou bien un fichier deleteou.ldif :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ou=newtest,ou=workgroup,ou=group,dc=xoyize,dc=xyz
</code></pre></div></div>
<p>Injection :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldel -f deleteou.ldif
</code></pre></div></div>
<h3 id="modification-dentrées">Modification dentrées</h3>
<p>Dans notre cas, les entrées sont de deux types, les users et les groupes.</p>
<p>Listons les groupes :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lsearch -b "dc=xoyize,dc=xyz" "objectClass=groupOfNames"
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: cn=cloud,ou=sysgroup,ou=group,dc=xoyize,dc=xyz
cn: cloud
description: Cloud
objectClass: groupOfNames
member: cn=admin,dc=xoyize,dc=xyz
dn: cn=xoyize,ou=workgroup,ou=group,dc=xoyize,dc=xyz
cn:: eG95aXplIA==
description:: eG95aXplIA==
objectClass: groupOfNames
member: cn=admin,dc=xoyize,dc=xyz
</code></pre></div></div>
<p>Listons les users :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lsearch -b "dc=xoyize,dc=xyz" "objectClass=InetOrgPerson"
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: uid=yannick,ou=xoyize,ou=people,dc=xoyize,dc=xyz
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
uid: yannick
sn: yannick
givenName: Yannick
cn: Yannick
displayName: Yannick
userPassword:: e1NTSEF9NW5UbDVjNWJXazBvdXZZYU9lTkROVU41OXZQWDdUZHM=
mail: yannick@xoyize.xyz
title: Admin
initials: Y
</code></pre></div></div>
<h4 id="ajout-dun-utilisateur">Ajout dun utilisateur</h4>
<p>Ajoutons un utilisateur toto avec le fichier adduser.ldif :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: uid=toto,ou=xoyize,ou=people,dc=xoyize,dc=xyz
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
uid: toto
sn: toto
givenName: toto
cn: toto
displayName: toto
userPassword: password
</code></pre></div></div>
<p>Injection :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ladd -f adduser.ldif
</code></pre></div></div>
<h4 id="ajout-dun-groupe">Ajout dun groupe</h4>
<p>Ajoutons un groupe test avec le fichier addgrp.ldif :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: cn=test,ou=workgroup,ou=group,dc=xoyize,dc=xyz
cn: test
description: test
objectClass: groupOfNames
member: cn=admin,dc=xoyize,dc=xyz
</code></pre></div></div>
<p>Injection :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ladd -f addgrp.ldif
</code></pre></div></div>
<h4 id="déplacement-dun-utilisateur">Déplacement dun utilisateur</h4>
<p>Déplaçons toto de ou=xoyize,ou=people,dc=xoyize,dc=xyz vers ou=client,ou=people,dc=xoyize,dc=xyz avec le fichier mvuser.ldif :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: uid=toto,ou=xoyize,ou=people,dc=xoyize,dc=xyz
changetype: modrdn
newrdn: uid=toto
deleteoldrdn: 1
newsuperior: ou=client,ou=people,dc=xoyize,dc=xyz
</code></pre></div></div>
<p>Injection :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lmodif -f mvuser.ldif
</code></pre></div></div>
<h4 id="déplacement-dun-groupe">Déplacement dun groupe</h4>
<p>Déplaçons le groupe test de ou=workgroup,ou=group,dc=xoyize,dc=xyz vers ou=sysgroup,ou=group,dc=xoyize,dc=xyz avec le fichier mvgrp.ldif :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: cn=test,ou=workgroup,ou=group,dc=xoyize,dc=xyz
changetype: modrdn
newrdn: cn=test
deleteoldrdn: 1
newsuperior: ou=sysgroup,ou=group,dc=xoyize,dc=xyz
</code></pre></div></div>
<p>Injection :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lmodif -f mvgrp.ldif
</code></pre></div></div>
<h4 id="renommer-un-utilisateur">Renommer un utilisateur</h4>
<p>Renommons toto en tata avec le fichier renameuser.ldif :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: uid=toto,ou=client,ou=people,dc=xoyize,dc=xyz
changetype: modrdn
newrdn: uid=tata
deleteoldrdn: 1
</code></pre></div></div>
<p>Injection</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lmodif -f renameuser.ldif
</code></pre></div></div>
<p>Ceci ne change que le nom de lentrée dn: uid=tata,ou=client,ou=people,dc=xoyize,dc=xyz. Les autres attributs (cn, sn, etc..) sils doivent être renommés seront modifiés par une autre opération (voir plus bas).</p>
<h4 id="renommer-un-groupe">Renommer un groupe</h4>
<p>Renommons le groupe test en newtest avec le fichier renamegrp.ldif :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: cn=test,ou=sysgroup,ou=group,dc=xoyize,dc=xyz
changetype: modrdn
newrdn: cn=newtest
deleteoldrdn: 1
</code></pre></div></div>
<p>Injection</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lmodif -f renamegrp.ldif
</code></pre></div></div>
<h4 id="supprimer-un-utilisateur">Supprimer un utilisateur</h4>
<p>Supprimons lutilisateur tata à laide dun fichier deluser.ldif :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: uid=tata,ou=client,ou=people,dc=xoyize,dc=xyz
changetype: delete
</code></pre></div></div>
<p>Injection</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lmodif -f deluser.ldif
</code></pre></div></div>
<p>On aurait pu aussi directement faire :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldel uid=tata,ou=client,ou=people,dc=xoyize,dc=xyz
</code></pre></div></div>
<h4 id="supprimer-un-groupe">Supprimer un groupe</h4>
<p>Supprimons le groupe newtest à laide dun fichier delgrp.ldif :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: cn=newtest,ou=sysgroup,ou=group,dc=xoyize,dc=xyz
changetype: delete
</code></pre></div></div>
<p>Injection</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lmodif -f delgrp.ldif
</code></pre></div></div>
<p>Ou bien :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldel cn=newtest,ou=sysgroup,ou=group,dc=xoyize,dc=xyz
</code></pre></div></div>
<h3 id="modification-dattributs">Modification dattributs</h3>
<p>Nous allons réinsérer un utilisateur test et un groupe test avant tout :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: uid=toto,ou=xoyize,ou=people,dc=xoyize,dc=xyz
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
uid: toto
sn: toto
givenName: toto
cn: toto
displayName: toto
userPassword: password
</code></pre></div></div>
<p>Injection :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ladd -f adduser.ldif
</code></pre></div></div>
<p>Puis :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: cn=test,ou=workgroup,ou=group,dc=xoyize,dc=xyz
cn: test
description: test
objectClass: groupOfNames
member: cn=admin,dc=xoyize,dc=xyz
</code></pre></div></div>
<p>Injection :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ladd -f addgrp.ldif
</code></pre></div></div>
<h4 id="ajouter-un-attribut-à-un-utilisateur">Ajouter un attribut à un utilisateur</h4>
<p>Listons les attributs à notre disposition :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lsearch -b "cn=schema,cn=config" olcAttributeTypes
</code></pre></div></div>
<p>Ou plus précises ment du schéma core :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lsearch -b "cn={0}core,cn=schema,cn=config" olcAttributeTypes
</code></pre></div></div>
<p>Ajoutons un attribut description à notre utilisateur toto avec un fichier addattribute.ldif :</p>
<p>dn: uid=toto,ou=xoyize,ou=people,dc=xoyize,dc=xyz
changetype: modify
add: description
description: Sa tete est egale a zero plus zero !</p>
<p>Injection :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lmodif -f addattribute.ldif
</code></pre></div></div>
<p>Et on peut vérifier lajout :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lsearch -b "dc=xoyize,dc=xyz" uid=toto
</code></pre></div></div>
<h4 id="ajouter-un-utilisateur-à-un-groupe">Ajouter un utilisateur à un groupe</h4>
<p>Regardons ce que propose la classe groupOfNames (dans le schéma core)</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lsearch -b "cn={0}core,cn=schema,cn=config" olcObjectClasses
</code></pre></div></div>
<p>On va trouver :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>olcObjectClasses: {7}( 2.5.6.9 NAME 'groupOfNames' DESC 'RFC2256: a group of n
ames (DNs)' SUP top STRUCTURAL MUST ( member $ cn ) MAY ( businessCategory $
seeAlso $ owner $ ou $ o $ description ) )
</code></pre></div></div>
<p>Pour un groupe, ce qui est le plus fréquent dajouter comme attribut, cest un utilisateur (ajout dun membre dans un groupe…)</p>
<p>Bref, ajoutons donc notre utilisateur test dans le groupe test avec un fichier addusertogroup.ldif :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: cn=test,ou=workgroup,ou=group,dc=xoyize,dc=xyz
changetype: modify
add: member
member: uid=toto,ou=client,ou=people,dc=xoyize,dc=xyz
</code></pre></div></div>
<p>Injection :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lmodif -f addusertogroup.ldif
</code></pre></div></div>
<p>On peut vérifier :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lsearch -b "dc=xoyize,dc=xyz" "(&amp;(objectclass=inetOrgPerson)(memberof=cn=test,ou=workgroup,ou=group,dc=xoyize,dc=xyz))" dn
</code></pre></div></div>
<p>Si on regarde la fiche de toto :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lsearch -b "dc=xoyize,dc=xyz" "uid=toto"
</code></pre></div></div>
<p>On constater quon ne voit pas lattribut memberof (overlay que nous avons pourtant configuré. En effet, il faut le demander spécifiquement :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lsearch -b "dc=xoyize,dc=xyz" "uid=toto" memberof
</code></pre></div></div>
<p>Ou alors, nous pouvons le voir avec cette commande :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lsearch -b "dc=xoyize,dc=xyz" "uid=toto" +
</code></pre></div></div>
<h4 id="modifier-un-attribut-utilisateur">Modifier un attribut utilisateur</h4>
<p>Un attribut qui peut etre modifié fréquement sur un utilisateur, cest le mot de passe. Modifions donc le mot de passe de toto avec un fichier chgpwd.ldif :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: uid=toto,ou=xoyize,ou=people,dc=xoyize,dc=xyz
changetype: modify
replace: UserPassword
UserPassword: newpass
</code></pre></div></div>
<p>Injection :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lmodif -f chgpwd.ldif
</code></pre></div></div>
<p>Ceci dit, pour modifier un mot de passe, nous avons la commande ldappassword consacré à cet usage :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldappasswd -H ldap://localhost -D "uid=yannick,ou=xoyize,ou=people,dc=xoyize,dc=xyz" -W -S
</code></pre></div></div>
<p>-W pour la demande de lancien mot de passe.</p>
<p>-S pour la demande dun nouveau mot de passe (sinon, le nouveau mot de passe est généré aléatoirement)</p>
<p>Cette commande peut également servir à changer le mot de passe dun autre utilisateur, par exemple, le compte admin demande à changer le mot de passe de lutilisateur yannick :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldappasswd -D "cn=admin,dc=xoyize,dc=xyz" -w passsadmin -x "uid=yannick,ou=xoyize,ou=people,dc=xoyize,dc=xyz" -S
</code></pre></div></div>
<h4 id="modifier-un-attribut-groupe">Modifier un attribut groupe</h4>
<p>Modifions la description de notre groupe test avec un fichier chgattr.ldif :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: cn=test,ou=workgroup,ou=group,dc=xoyize,dc=xyz
changetype: modify
replace: description
description: Nouvelle description
</code></pre></div></div>
<p>Injection :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lmodif -f chgattr.ldif
</code></pre></div></div>
<h4 id="supprimer-un-attribut-à-un-utilisateur">Supprimer un attribut à un utilisateur</h4>
<p>Supprimons lattribut description de toto avec un fichier delattr.ldif :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: uid=toto,ou=xoyize,ou=people,dc=xoyize,dc=xyz
changetype: modify
delete: description
</code></pre></div></div>
<p>Injection :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lmodif -f delattr.ldif
</code></pre></div></div>
<h4 id="supprimer-un-attribut-à-un-groupe">Supprimer un attribut à un groupe</h4>
<p>Pour un groupe, vous vous en doutez, un attribut qui peut etre amené à etre supprimé, cest un membre. Supprimons donc toto du groupe test avec un ficher</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: cn=test,ou=workgroup,ou=group,dc=xoyize,dc=xyz
changetype: modify
delete: member
member: uid=toto,ou=xoyize,ou=people,dc=xoyize,dc=xyz
</code></pre></div></div>
<p>Injection</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lmodif -f deluserfromgroup.ldif
</code></pre></div></div>
<p>Vérification :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lsearch -b "dc=xoyize,dc=xyz" "(&amp;(objectclass=groupOfNames)(cn=test))" member
</code></pre></div></div>
<h2 id="phpldapadmin">PhpLdapAdmin</h2>
<h3 id="pré-requis">Pré-requis</h3>
<p>Pour pouvoir utiliser phpLDAPadmin, vous aurez besoin des éléments suivants :</p>
<ul>
<li>Un serveur LDAP en cours dexécution de votre choix, quelque part dans votre réseau.
phpLDAPadmin a été développé pour gérer un serveur OpenLDAP, mais il devrait également fonctionner avec dautres serveurs LDAP. Si vous rencontrez des problèmes avec PLA et votre serveur LDAP, contactez les développeurs et ils se feront un plaisir dapporter les modifications nécessaires pour que cela fonctionne.</li>
</ul>
<p>Votre serveur LDAP doit également fournir laccès au schéma en utilisant un lien anonyme. Veuillez vous assurer que vous pouvez voir votre schéma sans avoir à vous lier au serveur LDAP. Un moyen de tester cela pourrait être aussi simple que :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -xh HOST -b ''' -s base subschemaSubentry
</code></pre></div></div>
<ul>
<li>Un serveur web (Nginx, Apache, IIS, etc.).</li>
<li>Votre serveur web configuré pour utiliser PHP v5 ou + . PHP doit être configuré avec :
<ul>
<li>Support PCRE</li>
<li>Prise en charge des SESSION</li>
<li>Support GETTEXT</li>
<li>Support LDAP</li>
<li>Support XML</li>
</ul>
</li>
</ul>
<h3 id="installation">Installation</h3>
<ol>
<li>Installez et configurez votre serveur LDAP favori quelque part sur votre réseau. (Ou, obtenez vos détails LDAP auprès de votre administrateur LDAP.)</li>
<li>Assurez-vous davoir toutes les conditions préalables requises</li>
<li>git phpldapadmin version 1.25</li>
<li>Décompressez larchive à un endroit pratique.</li>
<li>Placez le répertoirephpldapadmin résultant quelque part dans votre webroot.</li>
<li>Copiezconfig.php.example dansconfig.php et éditez selon votre goût. <br />
<code class="language-plaintext highlighter-rouge">sudo cp /var/www/phpldapadmin/config/config.php.example /var/www/phpldapadmin/config/config.php</code></li>
<li>Ensuite, pointez votre navigateur vers le répertoire phpldapadmin → https://lm.xoyize.xyz</li>
</ol>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/leenooks/phpLDAPadmin.git
sudo mv phpLDAPadmin /var/www/phpldapadmin
sudo chown www-data:www-data -R /var/www/phpldapadmin/
sudo cp /var/www/phpldapadmin/config/config.php.example /var/www/phpldapadmin/config/config.php
</code></pre></div></div>
<h3 id="configuration-nginx">configuration nginx</h3>
<p>Fichier de configuration nginx <strong>/etc/nginx/conf.d/lm.xoyize.xyz.conf</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo nano /etc/nginx/conf.d/lm.xoyize.xyz.conf
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>server {
listen 80;
listen [::]:80;
## redirect http to https ##
server_name lm.xoyize.xyz;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name lm.xoyize.xyz;
include ssl_params;
include header_params;
# Diffie-Hellmann
# Uncomment the following directive after DH generation
# &gt; openssl dhparam -out /etc/ssl/private/dh2048.pem -outform PEM -2 2048
# ssl_dhparam /etc/ssl/private/dh2048.pem;
root /var/www/phpldapadmin;
index index.php;
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/run/php/php7.3-fpm.sock; # PHP7.3
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $request_filename;
}
access_log /var/log/nginx/lm.xoyize.xyz-access.log;
error_log /var/log/nginx/lm.xoyize.xyz-error.log;
}
</code></pre></div></div>
<p>Vérification et rechargement nginx</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo nginx -t
sudo systemctl reload nginx
</code></pre></div></div>
<p>Accès https://lm.xoyize.xyz</p>
<p><img src="/images/phpldapadmin01.png" alt="" width="400" /></p>
<p>Accès par le lien https://lm.xoyize.xyz</p>
<h1 id="xoyizexyz---serveur-messagerie-complet-ldap">xoyize.xyz - Serveur Messagerie complet (LDAP)</h1>
<ul>
<li>Gestion des adresses mails.</li>
<li>Gestion de plusieurs domaines.</li>
<li>Gestion des mails</li>
<li>Traitement Spam.</li>
<li>Utilisation du référentiel utilisateur du serveur LDAP.</li>
<li>Filtrage Sieve (sur le serveur directement).</li>
</ul>
<p>Les briques qui vont constituer larchitecture</p>
<ul>
<li>Postfix : Le point central de larchitecture, il assure la réception des mails des serveurs smtps extérieurs, lenvoi de mails vers eux et la soumission de message depuis les clients connectés.</li>
<li>Dovecot :
<ul>
<li>Première fonction : range les mails reçus depuis Postfix via lmtp dans les bals physiques des utilisateurs. Peut les trier dans des sous-répertoires en fonction de règles Sieve.</li>
<li>Deuxième fonction : permet aux utilisateurs de consulter leur boites aux lettres en IMAP à laide du client de leur choix (ou dun webmail). Ici, je ne gère pas le pop car cest le mal (plus sérieusement, tellement davantages avec lImap que je ne vois pas lutilité de proposer du pop.</li>
<li>Troisième fonction : fournit le support AUTH à Postfix (pour lenvoi de message).</li>
</ul>
</li>
<li>Rspamd : Autre changement par rapport à ce que jutilisais jusque la (Spamassassin). Ce dernier sassure donc de traiter le spam pour les mails venant de lextérieur, et de la signature DKIM pour les mails sortants.</li>
<li>Le serveur Openldap</li>
</ul>
<p>Pas de traitement antivirus, cest lourd en terme de ressource, à voir ?</p>
<h2 id="ldap-_-messagerie">Ldap _ messagerie</h2>
<h3 id="création-utilisateurs">Création utilisateurs</h3>
<p>Premier utilisateur dans un fichier userym.ldif :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: uid=ym,ou=people,dc=xoyize,dc=xyz
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
uid: ym
sn: ym
givenName: ym
cn: ym
displayName: ym
userPassword: mot-de-passe-ym
mail: ym@xoyize.xyz
</code></pre></div></div>
<p>Second dans un fichier usercm.ldif :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: uid=cm,ou=people,dc=xoyize,dc=xyz
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
uid: cm
sn: cm
givenName: cm
cn: cm
displayName: cm
userPassword: mot-de-passe-cm
mail: cm@xoyize.xyz
</code></pre></div></div>
<p>Pour terminer en injectant les deux :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ladd -f userym.ldif
ladd -f usercm.ldif
</code></pre></div></div>
<h3 id="explication-de-lorganisation-ldap">Explication de lorganisation LDAP</h3>
<p>Jusque la, je stocke mes comptes utilisateur. Ceci dit, il me manque des attributs, que je rajouterais via un schéma personnel.</p>
<p>Au niveau domaine, jen ai deux : xoyize.fr, ouestline.net et je gère deux compte mails toto@xoyize.fr et tata@xoyize.fr. Toutes les autres adresses de xoyize et ouestline (par ex ccc@xoyize.fr, aaa@ouestline.net, bbb@ouestline.net, etc.. seront renvoyés soit vers toto@xoyize.fr soit vers tata@xoyize.fr</p>
<p>Pour la gestion domaines et alias, jai donc choisi de faire de la sorte :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dc=xoyize,dc=xyz
ou=people,dc=xoyize,dc=xyz
"Stockage de mes utilisateurs"
ou=mail,dc=xoyize,dc=xyz
ou=xoyize.fr,
cn=alias1@xoyize.fr,ou=xoyize.fr,ou=mail,dc=xoyize,dc=xyz
attr: mailfrom: alias@xoyize.fr
attr: mailto: toto@xoyize.fr
etc...
ou=ouestline.net,
etc...
</code></pre></div></div>
<p>Chaque domaine sera une OU dans une nouvelle OU créée pour loccasion.</p>
<p>Et dans chaque OU, je crée des entrées correspondants aux alias, la aussi avec laide de schéma supplémentaire</p>
<p>Mes domaines auraient très bien pu être non pas des OU mais des entrées, au niveau de lOU mail et les alias définies par des attributs soit dans ces entrées soit dans les entrées des users. Je vous le disais, on peut vraiment faire comme on veut.</p>
<h3 id="mise-en-place">Mise en place</h3>
<h4 id="le-schéma">Le Schéma</h4>
<p>On va créer un fichier <strong>schema.ldif</strong> :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: cn=mailxoyize,cn=schema,cn=config
objectClass: olcSchemaConfig
cn: mailxoyize
olcAttributeTypes: ( 1.3.6.1.4.1.99999.2.2.20 NAME 'mailaccountquota' DESC 'Quota Mail' EQUALITY caseExactMatch SINGLE-VALUE SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: ( 1.3.6.1.4.1.99999.2.2.21 NAME 'mailaccountactif' DESC 'Mail Actif' EQUALITY caseExactMatch SINGLE-VALUE SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: ( 1.3.6.1.4.1.99999.2.2.40 NAME 'mailaliasfrom' DESC 'Mail From' EQUALITY caseExactMatch SINGLE-VALUE SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: ( 1.3.6.1.4.1.99999.2.2.41 NAME 'mailaliasto' DESC 'Mail To' EQUALITY caseExactMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: ( 1.3.6.1.4.1.99999.2.2.42 NAME 'mailaliasactif' DESC 'Alias Actif' EQUALITY caseExactMatch SINGLE-VALUE SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: ( 1.3.6.1.4.1.99999.2.2.60 NAME 'maildomain' DESC 'Domaine' EQUALITY caseExactMatch SINGLE-VALUE SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: ( 1.3.6.1.4.1.99999.2.2.61 NAME 'maildomainactif' DESC 'Domaine Actif' EQUALITY caseExactMatch SINGLE-VALUE SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcObjectClasses: ( 1.3.6.1.4.1.99999.2.1.20 NAME 'mailaccountxoyize' SUP TOP AUXILIARY MUST ( mailaccountquota $ mailaccountactif))
olcObjectClasses: ( 1.3.6.1.4.1.99999.2.1.40 NAME 'mailaliasxoyize' SUP TOP STRUCTURAL MUST ( cn $ mailaliasfrom $ mailaliasto $ mailaliasactif))
olcObjectClasses: ( 1.3.6.1.4.1.99999.2.1.60 NAME 'maildomainxoyize' SUP TOP AUXILIARY MUST ( maildomain $ maildomainactif))
</code></pre></div></div>
<p>Vous voyez trois séries de chiffres dans le fichier.<br />
LOID 1.3.6.1.4.1.99999.2.2.x correspond à la hiérarchie de mes attributs (la branche 1.3.6.1.4.1 est la branche dédiée aux OID privés : voir ici).<br />
Le 99999 devrait être dans lidéal remplacé par le PEN que vous pouvez obtenir sur cette page. Si vous ne destinez pas votre schéma a être public, ça na pas trop dimportance, mais attention à ne pas prendre un numéro déjà existant si un jour vous importez un schéma avec ce numéro, bref, vous voyez le topo. Jai fais la demande, jai eu mon PEN en 72h je crois…<br />
LOID 1.3.6.1.4.1.99999.2.1.x est sur le même modèle mais définit un objet.<br />
LOID 1.3.6.1.4.1.1466.115.121.1.15 correspond à la définition dune directory string (chaîne de caractère), je fais simple et prend ce type de donnée pour mes nouveau attributs.</p>
<p>Ce schéma est au final assez simple, je rajoute trois nouvelles classe dobjets : mailaccountxoyize, maildomainxoyize et mailaliasxoyize (qui à la particularité dêtre structural, cest à dire que ce pourra être une entrée sans ajout dautre classe (par ex, inetogperson, etc…).<br />
Chaque classe possède des attributs obligatoire (MUST).</p>
<p>Lattribut mailaliasto est le seule à ne pas avoir SINGLE-VALUE, en effet, un alias peut renvoyer vers plusieurs bals.</p>
<p>On va ajouter notre schéma :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ladd -f schema.ldif
</code></pre></div></div>
<h4 id="les-ous-pour-nos-domaines">Les OUs pour nos domaines</h4>
<p>Ensuite, on va créer nos nouvelles OUs dans un fichier <strong>oumail.ldif</strong> :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: ou=mail,dc=xoyize,dc=xyz
ou: people
objectClass: organizationalUnit
dn: ou=xoyize.fr,ou=mail,dc=xoyize,dc=xyz
ou: xoyize.fr
objectClass: organizationalUnit
objectClass: maildomainxoyize
description: Domaine mail primaire
maildomain: xoyize.fr
maildomainactif: YES
dn: ou=ouestline.net,ou=mail,dc=xoyize,dc=xyz
ou: ouestline.net
objectClass: organizationalUnit
objectClass: maildomainxoyize
description: Domaine mail secondaire
maildomain: ouestline.net
maildomainactif: YES
</code></pre></div></div>
<p>On injecte :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ladd -f oumail.ldif
</code></pre></div></div>
<h4 id="les-alias">Les Alias</h4>
<p>Puis on passe à la création des entrées pour les alias dans un fichier alias.ldif :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: cn=postmaster@xoyize.fr,ou=xoyize.fr,ou=mail,dc=xoyize,dc=xyz
objectclass: mailaliasxoyize
cn: postmaster@xoyize.fr
mailaliasfrom: postmaster@xoyize.fr
mailaliasto: ym@xoyize.fr
mailaliasactif: YES
dn: cn=postmaster@ouestline.net,ou=ouestline.net,ou=mail,dc=xoyize,dc=xyz
objectclass: mailaliasxoyize
cn: postmaster@ouestline.net
mailaliasfrom: postmaster@ouestline.net
mailaliasto: ym@xoyize.fr
mailaliasactif: YES
dn: cn=testalias@xoyize.fr,ou=xoyize.fr,ou=mail,dc=xoyize,dc=xyz
objectclass: mailaliasxoyize
cn: testalias@xoyize.fr
mailaliasfrom: testalias@xoyize.fr
mailaliasto: ym@xoyize.fr
mailaliasto: cm@xoyize.fr
mailaliasactif: YES
</code></pre></div></div>
<p>A vous bien sur de faire vos propres alias en fonction de vos besoins.</p>
<p>On injecte :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ladd -f alias.ldif
</code></pre></div></div>
<h4 id="les-nouveaux-attributs-des-utilisateurs">Les nouveaux attributs des utilisateurs</h4>
<p>Et pour finir, on va rajouter les attributs de la classe mailaccountxoyize à nos utilisateurs.</p>
<p>Fichier attrym.ldif :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: uid=ym,ou=people,dc=xoyize,dc=xyz
changetype: modify
add: objectclass
objectclass: mailaccountxoyize
-
add: mailaccountquota
mailaccountquota: 0
-
add: mailaccountactif
mailaccountactif: YES
</code></pre></div></div>
<p>Fichier attrcm.ldif :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: uid=cm,ou=people,dc=xoyize,dc=xyz
changetype: modify
add: objectclass
objectclass: mailaccountxoyize
-
add: mailaccountquota
mailaccountquota: 0
-
add: mailaccountactif
mailaccountactif: YES
</code></pre></div></div>
<p>On injecte ces deux fichiers :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ladd -f attrym.ldif
ladd -f attrcm.ldif
</code></pre></div></div>
<p>Par la suite, pour ajouter un nouvel utilisateur, on pourra bien évidemment tout faire en un bloc :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: uid=new,ou=people,dc=xoyize,dc=xyz
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
objectclass: mailaccountxoyize
uid: new
sn: new
givenName: new
cn: new
displayName: new
userPassword: {SSHA}.....
mail: new@xoyize.fr
mailaccountquota: 0
mailaccountactif: YES
</code></pre></div></div>
<h4 id="tests">Tests</h4>
<p>On peut déjà tester en listant par exemple les domaines gérés :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lsearch -b "ou=mail,dc=xoyize,dc=xyz" "(&amp;(objectClass=maildomainxoyize))" ou
</code></pre></div></div>
<p>Ou encore, pour savoir par exemple vers quelle bal renvoie lalias postmaster@xoyize.fr :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lsearch -b "ou=mail,dc=xoyize,dc=xyz" "(&amp;(objectClass=mailaliasxoyize)(mailaliasfrom=postmaster@xoyize.fr))" mailaliasto
</code></pre></div></div>
<p>Voila qui termine la partie LDAP pour lutilisation avec un serveur de messagerie.</p>
<h3 id="postfix">Postfix</h3>
<h4 id="reverse-dns">Reverse DNS</h4>
<p>Il est nécessaire que son reverse dns soit correctement configuré : si on demande ladresse de mail.domaine1.fr et quon a XXX.XXX.XXX.XXX, il faut quen demandant le reverse de XXX.XXX.XX.XXX on obtienne mail.domaine1.fr</p>
<p>dig xoyize.xyz</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>;; ANSWER SECTION:
xoyize.xyz. 3356 IN A 78.235.240.223
</code></pre></div></div>
<p>dig -x 78.235.240.223</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>;; ANSWER SECTION:
223.240.235.78.in-addr.arpa. 86400 IN PTR xoyize.xyz.
</code></pre></div></div>
<h3 id="dns">DNS</h3>
<p>OVH</p>
<p>Test</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dig xoyize.xyz MX +short
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>10 xoyize.xyz.
</code></pre></div></div>
<h3 id="routeur---iptables">Routeur - iptables</h3>
<p>Modifier le fichier <strong>/sbin/iptables-firewall.sh</strong></p>
<p>Ajouter les lignes</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Postfix SMTP,SMTPS, SUBMISSION
#iptables -A INPUT -p tcp --dport 25 -j ACCEPT
#iptables -A OUTPUT -p tcp --dport 25 -j ACCEPT
iptables -A INPUT -p tcp --dport 465 -j ACCEPT
iptables -A OUTPUT -p tcp --dport 465 -j ACCEPT
iptables -A INPUT -p tcp --dport 587 -j ACCEPT
iptables -A OUTPUT -p tcp --dport 587 -j ACCEPT
# Imap and ImapS
#iptables -A INPUT -p tcp --dport 143 -j ACCEPT
#iptables -A OUTPUT -p tcp --dport 143 -j ACCEPT
iptables -A INPUT -p tcp --dport 993 -j ACCEPT
iptables -A OUTPUT -p tcp --dport 993 -j ACCEPT
# Managesieve
iptables -A INPUT -p tcp --dport 4190 -j ACCEPT
iptables -A OUTPUT -p tcp --dport 4190 -j ACCEPT
</code></pre></div></div>
<p>Redémarrer le parefeu</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo systemctl restart iptables-firewall
</code></pre></div></div>
<p>Les définitions des ports dans le domaine du mail</p>
<ul>
<li>25 -&gt; SMTP utilisé pour lenvoi de mail vers le serveur depuis dautres serveurs.</li>
<li>465 -&gt; SMTPS (chiffrement SSL/TLS), utilisé par les logiciels clients pour envoyer des mails vers le serveur.</li>
<li>587 -&gt; SUBMISSION (chiffrement STARTLS), lui aussi utilisé par les logiciels clients pour envoyer des mails vers le serveur.</li>
<li>143 -&gt; IMAP permet de consulter sa messagerie, depuis un logiciel client, un webmail, un smartphone, etc…</li>
<li>993 -&gt; IMAPS (chiffrement SSL/TLS)</li>
<li>4190 -&gt; Managesieve</li>
</ul>
<h3 id="installation-postfix--dovecot">Installation postfix + dovecot</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt-get install postfix postfix-ldap ca-certificates
</code></pre></div></div>
<p>Lors de linstallation de Postfix, répondez :</p>
<p>Site Internet<br />
xoyize.fr</p>
<p>Pour ca-certificates, il est toujours bon de lavoir, et il nous sera utile pour linstallation de Rspamd plus tard.</p>
<p>on installe Dovecot</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt-get install dovecot-core dovecot-imapd dovecot-ldap dovecot-managesieved dovecot-sieve dovecot-lmtpd
</code></pre></div></div>
<blockquote>
<p>le script de mise à jour du certificat redémarre les services postfix et dovecot , donc autant éviter des erreurs dès le début…</p>
</blockquote>
<h3 id="certificats">Certificats</h3>
<p>gérer le certificat avec acme.sh (client pour Lets Encrypt, plus dinformations dans mon article) :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt-get install git socat
</code></pre></div></div>
<p>Installons maintenant acme.sh :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd
mkdir sources
cd sources/
git clone https://github.com/Neilpang/acme.sh.git
cd ./acme.sh
./acme.sh --install
</code></pre></div></div>
<p>On recharge le bash :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>source /root/.bashrc
</code></pre></div></div>
<p>Et on lance la création du certificat :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>acme.sh --issue -k 4096 --standalone -d xoyize.fr --log
</code></pre></div></div>
<p>On installe le certificat :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkdir /etc/ssl/private/xoyize.fr
acme.sh --installcert -d xoyize.fr --cert-file /etc/ssl/private/xoyize.fr/cert.pem --key-file /etc/ssl/private/xoyize.fr/key.pem --ca-file /etc/ssl/private/xoyize.fr/ca.pem --fullchain-file /etc/ssl/private/xoyize.fr/fullcert.pem --reloadCmd 'service postfix reload &amp;&amp; service dovecot reload'
</code></pre></div></div>
<p>On génère nos clés DH :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>openssl dhparam -out /etc/ssl/private/dh512.pem 512
openssl dhparam -out /etc/ssl/private/dh2048.pem 2048
chmod 644 /etc/ssl/private/dh{512,2048}.pem
</code></pre></div></div>
<p>La commande installcert met également en place une tache cron pour le renouvellement automatique de votre certificat qui du coup, renouvelle, copie ou il faut et relance, parfait !</p>
<p>Les certificatts sous xoyize.xyz</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> ls /etc/ssl/private/
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dh2048.pem ouestline.net-chain.pem ssl-cert-snakeoil.key xoyize.xyz-fullchain.pem
dh512.pem ouestline.net-fullchain.pem xoyize.xyz-ca.pem xoyize.xyz-key.pem
ouestline.net-ca.pem ouestline.net-key.pem xoyize.xyz-chain.pem
</code></pre></div></div>
<h3 id="postfix---la-base">Postfix - La base</h3>
<p>On va recréer le fichier <strong>/etc/postfix/main.cf</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU)
biff = no
# appending .domain is the MUA's job.
append_dot_mydomain = yes
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
#mynetworks = 10.0.0.0/8
inet_interfaces = all
inet_protocols = ipv4
readme_directory = no
compatibility_level = 2
notify_classes = bounce, delay, policy, protocol, resource, software
myhostname = xoyize.xyz
mydestination = $myhostname, mail, localhost.localdomain, localhost
myorigin = $myhostname
disable_vrfy_command = yes
strict_rfc821_envelopes = yes
show_user_unknown_table_name = no
message_size_limit = 0
mailbox_size_limit = 0
allow_percent_hack = no
swap_bangpath = no
recipient_delimiter = +
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
broken_sasl_auth_clients=yes
smtp_tls_security_level = may
smtp_tls_session_cache_database = btree:${data_directory}/smtp_tlscache
smtpd_tls_loglevel = 1
smtpd_tls_security_level = may
smtpd_tls_auth_only = yes
smtpd_tls_key_file = /etc/ssl/private/xoyize.xyz-key.pem
smtpd_tls_cert_file = /etc/ssl/private/xoyize.xyz-ca.pem
smtpd_tls_CAfile = /etc/ssl/private/xoyize.xyz-fullchain.pem
smtpd_tls_protocols = !SSLv2 !SSLv3
smtpd_tls_mandatory_protocols = !SSLv2 !SSLv3
smtpd_tls_mandatory_ciphers = high
smtpd_tls_eecdh_grade = strong
smtpd_tls_dh512_param_file = /etc/ssl/private/dh512.pem
smtpd_tls_dh1024_param_file = /etc/ssl/private/dh2048.pem
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_tlscache
smtpd_tls_session_cache_timeout = 3600s
smtpd_tls_received_header = yes
smtpd_sasl_auth_enable = yes
smtpd_sasl_path = private/auth
smtpd_sasl_type = dovecot
smtpd_sasl_security_options = noanonymous, noplaintext
smtpd_sasl_tls_security_options = noanonymous
tls_preempt_cipherlist = yes
tls_high_cipherlist = ALL EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA RC4 !aNULL !eNULL !LOW !MEDIUM !3DES !MD5 !EXP !PSK !SRP !DSS !RC4
tls_ssl_options = no_ticket, no_compression
smtpd_helo_required = yes
smtpd_client_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
reject_unknown_reverse_client_hostname,
reject_unauth_pipelining
smtpd_helo_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
reject_invalid_helo_hostname,
reject_non_fqdn_helo_hostname,
reject_unauth_pipelining
smtpd_sender_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
reject_non_fqdn_sender,
reject_unknown_sender_domain,
reject_unauth_pipelining
smtpd_relay_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
reject_unauth_destination
smtpd_recipient_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
reject_non_fqdn_recipient,
reject_unknown_recipient_domain,
reject_unauth_pipelining
smtpd_data_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
reject_multi_recipient_bounce,
reject_unauth_pipelining
virtual_transport = lmtp:unix:private/dovecot-lmtp
virtual_mailbox_domains = ldap:/etc/postfix/ldap/virtual_domains.cf
virtual_mailbox_maps = ldap:/etc/postfix/ldap/virtual_mailbox.cf
virtual_alias_maps = ldap:/etc/postfix/ldap/virtual_alias.cf
</code></pre></div></div>
<p>Quelques explications :</p>
<p>append_dot_mydomain = yes</p>
<p>ajoute le domaine au mail locaux qui sont envoyés (si un mail local est envoyé depuis un service sur le serveur, genre cron, etc… il sera de la forme root@xoyize.xyz au lieu de root@mail).</p>
<p>Centralisant tous mes mails, éventuellement dautres serveur, cest une info dont jai besoin (on peut aussi réécrire lexpéditeur, etc..).</p>
<p>notify_classes = bounce, delay, policy, protocol, resource, software</p>
<p>Définit les messages derreurs que recoit le postmaster. A affiner selon ce que vous voulez. Voir la doc de Postfix pour plus de détails.</p>
<p>myhostname = xoyize.xyz
mydestination = $myhostname, mail, localhost.localdomain, localhost</p>
<p>Le nom de votre serveur de mail. Peut être différent, mais dans mon cas, vu ma config Dns, de lextérieur ou de linterieur, ca reste le meme nom.
Ensuite, les destinations quil accepte. On ne liste ici aucun domaine quon gère, ceux ci sont déclarés dans les alias virtuels.</p>
<p>Pour la partie SSL/TLS/SALS on remarque deux groupes doptions : smtp_* et smtpd_*</p>
<p>Smtp concerne la patie client de postfix, cest à dire celle qui envoie les mails aux autres serveurs SMTP (aussi appelés MX).
Smtpd concerne la partie serveur, celle qui recoit les mails, soit des clients, soit des autres MX.</p>
<p>smtp_tls_security_level = may</p>
<p>indique que le client SMTP de postfix, quand il se connecte à un autre MX, supporte le TLS.</p>
<p>smtpd_tls_security_level = may</p>
<p>indique que le serveur SMTP de Postfix, quand il reçoit une connexion dun client (mx ou soumis), supporte le TLS.</p>
<p>On le laisse à may pour indiquer que cest possible sans être obligatoire. On le surchargera dans le fichier master.cf</p>
<p>Ne pas confondre aussi <em>sasl</em> et <em>tls</em> ! <em>sasl</em> concerne lauthentification de nos utilisateusr (via Dovecot) et <em>tls</em> le chiffrement des communications.</p>
<p>smtpd_tls_auth_only = yes</p>
<p>indique que si lauthentification est utilisé, on doit forcement être en SSL (ou TLS, ca revient au meme.)</p>
<p>On va ensuite modifier le fichier <strong>/etc/postfix/master.cf</strong> au début</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>submission inet n - y - - smtpd
-o smtpd_tls_security_level=encrypt
</code></pre></div></div>
<p>On active submission, et on surchage (-o le parametre smtpd_tls_security_level à encrypt pour forcer le tls.</p>
<p>Un peu plus bas, on va activer le smtps :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>smtps inet n - y - - smtpd
-o smtpd_tls_security_level=encrypt
-o smtpd_tls_wrappermode=yes
</code></pre></div></div>
<p>Le reste ne change pas.</p>
<p>Pourquoi Postfix utilise deux fichiers de configuration ?</p>
<p>Le fichier main.cf définit les options générales de Postfix. Le fichier master.cf, lui sert à gérer les sous process de Postfix et permet de modifier certains paramètres du fichier main.cf en les surchargeant (option -o).</p>
<h3 id="postfix---ldap">Postfix - Ldap</h3>
<p>On va créer nos fichiers chargé de faire la liaison avec le serveur ldap et les ranger :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkdir /etc/postfix/ldap
cd /etc/postfix/ldap
</code></pre></div></div>
<p>Fichier virtual_domains.cf :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>server_host = ldap://127.0.0.1
version = 3
bind = yes
bind_dn = cn=viewer,ou=system,dc=xoyize,dc=xyz
bind_pw = passview
search_base = ou=mail,dc=xoyize,dc=xyz
scope = sub
query_filter = (&amp;(maildomain=%s)(objectClass=maildomainxoyize)(maildomainactif=YES))
result_attribute = maildomain
</code></pre></div></div>
<p>Fichier virtual_mailbox.cf :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>server_host = ldap://127.0.0.1
version = 3
bind = yes
bind_dn = cn=viewer,ou=system,dc=xoyize,dc=xyz
bind_pw = passview
search_base = ou=people,dc=xoyize,dc=xyz
scope = sub
query_filter = (&amp;(mail=%s)(objectClass=mailaccountxoyize)(mailaccountactif=YES))
result_attribute = mail
</code></pre></div></div>
<p>Et le fichier virtual_alias.cf :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>server_host = ldap://127.0.0.1
version = 3
bind = yes
bind_dn = cn=viewer,ou=system,dc=xoyize,dc=xyz
bind_pw = passview
search_base = ou=mail,dc=xoyize,dc=xyz
scope = sub
query_filter = (&amp;(mailaliasfrom=%s)(objectClass=mailaliasxoyize)(mailaliasactif=YES))
result_attribute = mailaliasto
</code></pre></div></div>
<p>Pour des explications sur les filtres, vous en aurez dans la partie suivante, consacrée à Dovecot.</p>
<p>On sécurise :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>chmod 640 /etc/postfix/ldap/
chown :postfix /etc/postfix/ldap/*
</code></pre></div></div>
<p>Puis on recharge Postfix :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>postfix reload
</code></pre></div></div>
<p>Et on peut tester les liens avec le ldap :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>postmap -q xoyize.xyz ldap:/etc/postfix/ldap/virtual_domains.cf
</code></pre></div></div>
<p>nous retourne le domaine sil existe.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>postmap -q postmaster@xoyize.xyz ldap:/etc/postfix/ldap/virtual_alias.cf
</code></pre></div></div>
<p>donne le compte vers le lequel est envoyé lalias.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>postmap -q ym@xoyize.xyz ldap:/etc/postfix/ldap/virtual_mailbox.cf
</code></pre></div></div>
<p>nous retourne le mail sil existe.</p>
<h4 id="alias-locaux">Alias locaux</h4>
<p>Il reste un dernier petit point, les alias locaux de la machine.</p>
<p>Comme je lai dis avant, je veux quà terme, ce serveur mail gère également les mails de mes autres vm pour tout centraliser.</p>
<p>Et je veux tout rapatrier sur mon adresse mail ym@xoyize.xyz.</p>
<p>On édite le fichier /etc/aliases pour y mettre :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>postmaster: postmaster@xoyize.xyz
root: ym@xoyize.xyz
</code></pre></div></div>
<p>Pour mémoire, postmaster@xoyize.xyz est défini comme un alias de ym@xoyize.xyz dans le ldap.</p>
<p>On exécute la commande :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>newaliases
</code></pre></div></div>
<p>pour convertir cela en fichier un fichier compréhensible de postfix et on le recharge :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>postfix reload
</code></pre></div></div>
<h4 id="postfix---conclusion">Postfix - Conclusion</h4>
<p>Postfix est opérationnel. Vous voyez, ce nest pas si terrible ! On ne fait queffleurer la configuration, la documentation de Postfix est très complète et on peut configurer plein de scénarios…</p>
<p>Maintenant pour le tester, comme nous navons pas encore Dovecot derriere, on va se contenter de juste tester la communication dun MX vers le nôtre :</p>
<p>Le site https://www.checktls.com/TestReceiver permet de faire cette vérification, et lon voit de suite si le TLS est bon. On peut aussi classiquement en telnet, mais cest… long…</p>
<p>Le site https://mxtoolbox.com/ est très pratique également.</p>
<p>Ha oui, au passage, le fichier clé pour debug, cest bien évidement le fidèle :</p>
<p>/var/log/mail.log</p>
<h2 id="dovecot">Dovecot</h2>
<h3 id="dovecot---la-base">Dovecot - La base</h3>
<h4 id="utilisateur-vmail">Utilisateur Vmail</h4>
<p>Nous allons créer lutilisateur virtuel vmail ainsi que le répertoire qui stockera les mails :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>groupadd -g 11000 vmail
useradd -g vmail -u 11000 vmail -d /var/vmail -m
chown vmail: /var/vmail -R
chmod 770 /var/vmail
</code></pre></div></div>
<h4 id="du-ménage">Du ménage</h4>
<p>Concernant la configuration de Dovecot, celui ci à la mauvaise idée de tout éclater dans une foultitude de fichiers.
Avant tout, faisons donc du tri…</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd /etc/dovecot/conf.d
rm 10-director* 10-tcpwrapper* 90-acl* auth-*
</code></pre></div></div>
<p>et dans le répertoire /etc/dovecot :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd /etc/dovecot
rm dovecot-dict-* dovecot-sql.conf.ext dovecot-ldap.conf.ext
</code></pre></div></div>
<h4 id="de-la-config">De la config</h4>
<p>On passe maintenant à la configuration. On va éditer le fichier /etc/dovecot/dovecot.conf et tout remplacer par :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>protocols = imap lmtp
!include conf.d/*.conf
</code></pre></div></div>
<p>Oui, cest concis, le reste sera dans le sous répertoire conf.d :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd /etc/dovecot/conf.d
</code></pre></div></div>
<p>Et la pour chaque fichier, remplacez tout son contenu par ce que je vous donne.</p>
<p>1 Fichier 10-auth.conf</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat &gt; 10-auth.conf &lt;&lt; EOF
auth_cache_size = 0
auth_cache_ttl = 1 hour
auth_cache_negative_ttl = 1 hour
auth_mechanisms = plain
passdb {
driver = ldap
args = /etc/dovecot/dovecot-ldap-pass.conf.ext
}
userdb {
driver = prefetch
}
userdb {
driver = ldap
args = /etc/dovecot/dovecot-ldap-user.conf.ext
}
EOF
</code></pre></div></div>
<p>Dans ce fichier, on déclare la façon dont Dovecot récupère les infos. On reviendra plus en détail dessus après…</p>
<p>2 Fichier 10-logging.conf</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat &gt; 10-logging.conf &lt;&lt; EOF
#log_path = syslog
#debug_log_path =
#syslog_facility = mail
#auth_verbose = no
#auth_verbose_passwords = no
#auth_debug = no
#auth_debug_passwords = no
#mail_debug = no
#verbose_ssl = no
plugin {
#mail_log_events = delete undelete expunge copy mailbox_delete mailbox_rename
# Available fields: uid, box, msgid, from, subject, size, vsize, flags
# size and vsize are available only for expunge and copy events.
#mail_log_fields = uid box msgid size
}
#log_timestamp = "%b %d %H:%M:%S "
#login_log_format_elements = user=&lt;%u&gt; method=%m rip=%r lip=%l mpid=%e %c
#login_log_format = %$: %s
#mail_log_prefix = "%s(%u): "
# %$ - Delivery status message (e.g. "saved to INBOX")
# %m - Message-ID
# %s - Subject
# %f - From address
# %p - Physical size
# %w - Virtual size
#deliver_log_format = msgid=%m: %$
EOF
</code></pre></div></div>
<p>Ici, auth_verbose et auth_debug sont bien pratiques en cas de soucis avec les connexions au LDAP, mail_debug pour les filtres Sieves (et autres, quota, etc…).</p>
<p>3 Fichier 10-mail.conf</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat &gt; 10-mail.conf &lt;&lt; EOF
mail_home = /var/vmail/%d/%n
mail_location = maildir:~/mailbox
namespace inbox {
separator = /
inbox = yes
}
mail_uid = 11000
mail_gid = 11000
mail_privileged_group = vmail
first_valid_uid = 11000
last_valid_uid = 11000
first_valid_gid = 11000
last_valid_gid = 11000
mail_plugins = $mail_plugins
EOF
</code></pre></div></div>
<p>Emplacement des mails que je range en fonction du domaine et du nom. Ensuite,uig/gid de vmail… Rien de sorcier.</p>
<p>4 Fichier 10-master.conf</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat &gt; 10-master.conf &lt;&lt; EOF
mail_fsync = never
service imap-login {
# inet_listener imap {
# port = 143
# }
inet_listener imaps {
port = 993
ssl = yes
}
}
service imap {
service_count = 64
process_min_avail = 1
}
service lmtp {
unix_listener /var/spool/postfix/private/dovecot-lmtp {
group = postfix
mode = 0600
user = postfix
}
}
service auth {
unix_listener /var/spool/postfix/private/auth {
mode = 0660
user = postfix
group = postfix
}
}
service auth-worker {
user = vmail
}
EOF
</code></pre></div></div>
<p>Dans ce fichier, on déclare nos services : imap-login, imap, lmtp (utilisé pour le transfert de mails entre Postfix et Dovecot) et le service auth, qui service à Postfix pour authentifier les utilisateurs en SMTPS.</p>
<p>5 Fichier 10-ssl.conf</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat &gt; 10-ssl.conf &lt;&lt; EOF
ssl = required
ssl_cert = &lt;/etc/ssl/private/xoyize.xyz-chain.pem
ssl_key = &lt;/etc/ssl/private/xoyize.xyz-key.pem
ssl_dh = &lt;/etc/ssl/private/dh2048.pem
ssl_min_protocol = !TLSv1.2
ssl_cipher_list = EECDH+AES:EDH+AES+aRSA
ssl_prefer_server_ciphers = yes
verbose_ssl = yes
EOF
</code></pre></div></div>
<p>Le SSL, classique…</p>
<p>6 Fichier 15-mailboxes.conf</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat &gt; 15-mailboxes.conf &lt;&lt; EOF
namespace inbox {
separator = /
mailbox Drafts {
auto = subscribe
special_use = \Drafts
}
mailbox Junk {
auto = subscribe
special_use = \Junk
}
mailbox Trash {
auto = subscribe
special_use = \Trash
}
mailbox Sent {
auto = subscribe
special_use = \Sent
}
mailbox Archive {
auto = subscribe
special_use = \Archive
}
}
EOF
</code></pre></div></div>
<p>La, on définit larchitecture de base de nos boites aux lettres. Création des répertoires spéciaux et auto souscription pour que lutilisateur les voit de suite.</p>
<p>7 Fichier 20-imap.conf</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat &gt; 20-imap.conf &lt;&lt; EOF
imap_idle_notify_interval = 30 mins
protocol imap {
mail_max_userip_connections = 50
mail_plugins = $mail_plugins imap_sieve
postmaster_address = postmaster@xoyize.xyz
}
EOF
</code></pre></div></div>
<p>Configuration du protocole IMAP.</p>
<p>8 Fichier 20-lmtp.conf</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat &gt; 20-lmtp.conf &lt;&lt; EOF
protocol lmtp {
mail_fsync = optimized
mail_plugins = $mail_plugins sieve
postmaster_address = postmaster@xoyize.xyz
}
EOF
</code></pre></div></div>
<p>Configuration du protocole LMTP.</p>
<p>9 Fichier 20-managesieve.conf</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat &gt; 20-managesieve.conf &lt;&lt; EOF
protocols = $protocols sieve
service managesieve-login {
inet_listener sieve {
port = 4190
}
service_count = 1
#process_min_avail = 0
#vsz_limit = 64M
}
EOF
</code></pre></div></div>
<p>Configuration de ManageSieve.
10 Fichier 90-sieve.conf</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat &gt; 90-sieve.conf &lt;&lt; EOF
plugin {
sieve = file:~/sieve;active=~/.dovecot.sieve
sieve_before = /etc/dovecot/sieve-global
recipient_delimiter = +
sieve_quota_max_storage = 50M
}
EOF
</code></pre></div></div>
<p>Configuration de Sieve.</p>
<h3 id="fichiers-de-mapping-ldap">Fichiers de mapping Ldap</h3>
<p>A Selon la Doc de Dovecot</p>
<p>Avant de faire nos fichiers pour le Ldap, on va regarder à quoi devrait ressembler un dovecot-ldap.conf.ext quon peut rencontrer sur le grand internet, un exemple souvent cité (bon, les filtres sont les miens, mais cest lidée) :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>uris = ldap://ip.interne
dn = cn=viewer,ou=system,dc=xoyize,dc=xyz
dnpass = passview
debug_level = 0
auth_bind = no
ldap_version = 3
base = ou=people,dc=xoyize,dc=xyz
scope = subtree
user_attrs = mailaccountquota=quota_rule=*:bytes=%$
user_filter = (&amp;(uid=%u)(objectClass=mailaccountxoyize)(mailaccountactif=YES))
pass_attrs = mail=user,userPassword=password
pass_filter = (&amp;(uid=%u)(objectClass=mailaccountxoyize)(mailaccountactif=YES))
</code></pre></div></div>
<p>Cest la que le <code class="language-plaintext highlighter-rouge">auth_debug</code> et <code class="language-plaintext highlighter-rouge">mail_debug</code> sont utiles</p>
<p>B Passdb et Userdb</p>
<p>Dovecot utilise deux bases : passdb et userdb, déclarées dans cond.f/10-auth.conf ou jutilise une petite astuce, le prefetch, pour éviter les doubles requêtes…</p>
<ul>
<li>Lorsquun utilisateur se connecte en IMAP, passdb est utilisé pour vérifier lutilisateur (login et mdp), puis userdb est appelé pour connaitre des infos supplémentaires sur luser (doù le prefetch).</li>
<li>Lorsquun mail arrive de Postfix à Dovecot via le protocole LMTP, cest userdb qui est appelé pour connaitre lutilisateur à qui remettre le mail doù le second userdb car le premier, prefetch, ne contient rien pour le coup.</li>
</ul>
<p>Il faut aussi savoir que Dovecot utilise des variables. Celles qui nous intéressent sont :</p>
<ul>
<li>%u qui est le nom utilisateur complet fourni. Par exemple ym@xoyize.xyz via un appel en Imtp (mail entrant) ou ym via lImap (client qui se connecte)</li>
<li>%n qui correspond à la partie utilisateur, ici : ym</li>
<li>%d qui correspond au domaine, ici : xoyize.xyz</li>
</ul>
<p>Ici, nous nutiliserons que %u.</p>
<p>Au niveau du résultat ldap, lordre est :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>attribut_ldap:attribut_dovecot
</code></pre></div></div>
<p>Et donc :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pass_attrs = mail=user,userPassword=password
</code></pre></div></div>
<p>indique que le champ mail de mon ldap correspond à lutilisateur, du coup jai la bonne association.</p>
<p>C Bind Ldap</p>
<p>Ensuite, au niveau du bind avec Ldap (la connexion avec Ldap), celui ci peut se faire de deux façons.</p>
<p>1 auth_bind = yes</p>
<p>Cela permet de faire en sorte que le test du mot de passe se fasse directement avec le compte de lutilisateur. Dovecot dans ce cas na pas besoin de lire le pass avec le compte viewer.</p>
<p>En contre partie, cest plus lent, Dovecot attendant que la connexion avec le Ldap soit terminée avant de passer à la suivante.</p>
<p>2 auth_bind = no</p>
<p>La, il y a besoin que le compte viewer puisse lire le mot de passe (doù les acls mises en place dans la partie LDAP). Ayant de toute façon besoin de ce compte pour dautres applications, ça ne me dérange pas.</p>
<p>De plus, cest asynchrone, Dovecot peut lancer plusieurs requêtes concomitantes sans attendre apres.</p>
<p>Bref, on part sur le second choix.</p>
<p>D Paf</p>
<p>Cependant, cette config me pose encore un problème… Lorsque lon se connecte en IMAP on se présente donc avec un simple ym par contre, lors du LMTP, le %u présenté par Postfix est le mail complet, ym@xoyize.xyz et la, bah ça foire au niveau du filtre ldap, qui est pour mémoire :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(&amp;(uid=%u)(objectClass=mailaccountxoyize)(mailaccountactif=YES))
</code></pre></div></div>
<p>Que faire ? Refaire nos utilisateurs pour que leur uid soit leur mail ? Heu, cest pas lidée à la base…</p>
<p>Donc, pour ça, javais trouvé une première solution :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>user_filter = (&amp;(|(mail=%u)(uid=%u))(objectClass=mailaccountxoyize)(mailaccountactif=YES))
</code></pre></div></div>
<table>
<tbody>
<tr>
<td>Ha, les filtres LDAP et leur notation préfixée. Si vous avez eu une calculette Sharp dans les 90s, vous devez connaître (et être vieux 😉 ). Bon, elles utilisaient la notaion polonaise inverse où lopérateur est à la fin, mais cest la même idée… Si vous avez fait de lalgèbre booléenne ou bien joué avec les AND, OR, XOR, et NAND (du genre, comment faire un OR avec que des NAND, tables de vérités, etc..), et bien le + (ou) est remplacé par</td>
<td>et le . (et) par &amp;.</td>
</tr>
</tbody>
</table>
<p>On peut résumer :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(&amp;(Bloc1)(Bloc2)(Bloc3)) -&gt; On veut Bloc1 ET Bloc2 ET Bloc3
Bloc1=(|(A)(B)) -&gt; A OU B
</code></pre></div></div>
<p>Cest beau hein ! Bah cest balo car on en va pas lutiliser. En effet, cela amène un effet de bord : je peux désormais me connecter à ma bal avec comme login mon mail….
Après, on aime ou on naime pas. Perso, je ne préfère pas laisser cette possibilité, pour éviter les vilains qui tenteraient de se connecter en connaissant déjà le login…</p>
<p>Apres tout, jaurais pu en rester la et me dire tant pis, mais au final cétait ne pas vraiment comprendre Dovecot…</p>
<p>E Solution</p>
<p>Il faut faire en sorte que les appels userdb et passdb soient distincts…</p>
<p>Pour la configuration, on aura donc deux fichiers :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/etc/dovecot/dovecot-ldap-pass.conf.ext
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>uris = ldap://127.0.0.1
dn = cn=viewer,ou=system,dc=xoyize,dc=xyz
dnpass = passview
debug_level = 0
auth_bind = no
ldap_version = 3
base = ou=people,dc=xoyize,dc=xyz
scope = subtree
pass_attrs = mail=user,userPassword=password,mailaccountquota=userdb_quota_rule=*:bytes=%$
pass_filter = (&amp;(uid=%u)(objectClass=mailaccountxoyize)(mailaccountactif=YES))
</code></pre></div></div>
<p>La, on demande aussi au passage un attribut quota avec userdb <code class="language-plaintext highlighter-rouge">userdb_quota_rule=*:bytes=%$</code> et qui sera utilisé dans le prefetch pour éviter de refaire une requête LDAP.</p>
<p>Et le fichier</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/etc/dovecot/dovecot-ldap-user.conf.ext
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>uris = ldap://127.0.0.1
dn = cn=viewer,ou=system,dc=xoyize,dc=xyz
dnpass = passview
debug_level = 0
auth_bind = no
ldap_version = 3
base = ou=people,dc=xoyize,dc=xyz
scope = subtree
user_attrs = mailaccountquota=quota_rule=*:bytes=%$
user_filter = (&amp;(mail=%u)(objectClass=mailaccountxoyize)(mailaccountactif=YES))
</code></pre></div></div>
<p>La, on filtre sur le champ mail pour retrouver le compte correspondant.</p>
<p>On sécurise :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>chmod 600 /etc/dovecot/dovecot-ldap*
</code></pre></div></div>
<p>On va recharger Dovecot et regarder les logs voir si tout démarre bien :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>service dovecot restart
tail /var/log/mail.log -n 100
</code></pre></div></div>
<h3 id="sieve">Sieve</h3>
<h4 id="la-base">La base</h4>
<p>Dovecot permet donc de filtrer les messages en utilisant le protocole Sieve. Il les range à leur arrivée selon les règles, global et utilisateur.
Pour le global, on aura besoin que dune seule règle : les messages marqués comme Spam sont dirigés dans le répertoire Spam.
Par la suite, chaque utilisateur pourra ajouter ses règles afin de filtrer comme il lentend.</p>
<p>On a déjà activé Sieve au préalable. Il ne reste plus quà faire quelques opérations.</p>
<p>Pour le global :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkdir /etc/dovecot/sieve-global
chown vmail /etc/dovecot/sieve-global
</code></pre></div></div>
<p>Nous rangerons nos scripts globaux dans ce répertoire, qui sexécutera avant les filtres utilisateurs.</p>
<p>Si vous voulez faire les vérifications global apres, dans le fichier 90-sieve.conf, il faudra indiquer :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sieve_after = /etc/dovecot/sieve-global
</code></pre></div></div>
<p>Et on peut bien sûr combiner.</p>
<p>Nous rangerons nos scripts globaux dans ce répertoire, qui sexécutera avant les filtres utilisateurs.</p>
<p>Si vous voulez faire les vérifications global apres, dans le fichier 90-sieve.conf, il faudra indiquer :</p>
<p>sieve_after = /etc/dovecot/sieve-global</p>
<p>Et on peut bien sûr combiner.</p>
<p>Créez ensuite le fichier /etc/dovecot/sieve-global/global.sieve avec le contenu qui suit :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>require ["variables", "envelope", "fileinto", "mailbox", "regex", "subaddress", "body"];
if header :contains "X-Spam" "Yes" {
fileinto "Junk";
stop;
}
</code></pre></div></div>
<p>Tout bête, si le header contient X-spam à Yes, on le déplace dans les indésirables ( Le X-spam sera rajouté par Rspamd).</p>
<p>Le stop indique darrêter le traitement. En effet, si dautres filtres (users ou globaux) matchent également, le message sera dupliqué.</p>
<p>Puis on change luser et les droits :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>chown vmail: /etc/dovecot/sieve-global/global.sieve
chmod 750 /etc/dovecot/sieve-global/global.sieve
</code></pre></div></div>
<p>On peut le compiler :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sievec /etc/dovecot/sieve-global/global.sieve
</code></pre></div></div>
<p>qui donnera un fichier /etc/dovecot/sieve-global/global.svbin, version compilée de nos règles.</p>
<p>Cette opération est cependant facultative et elle sera de toute façon exécutée par Dovecot à la première occasion si vous le navez pas fait.</p>
<p>Si on le fait, on pense à modifier les droits :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>chown vmail /etc/dovecot/sieve-globa/global.svbin
</code></pre></div></div>
<p>Je serais même tenté de dire que la première fois, cest mieux de laisser Dovecot le faire, en regardant les logs et avec loption mail_debug = yes on voit très rapidement où se trouve le problème sil y en a un (et généralement, ça chouine pour des histoires de permissions). Pour les filtres utilisateurs, cest la même tambouille, Dovecot les compilant au moment ou il les charge, sil nexistent pas ou sont obsolètes.</p>
<h4 id="niveau-utilisateur">Niveau Utilisateur</h4>
<p>Pour modifier les filtres dun utilisateur, on peut utiliser un webmail préalablement configuré. Pour le moment, je ne couvre pas cette partie (ce sera pour plus tard), donc pour le moment, on va plutôt passer par un client tel que Thunderbird.</p>
<p>Par défaut, Thunderbird ne les gère pas, mais il suffit de rajouter ce module complémentaire.
Ensuite menu Outils/Paramètres Sieve et activez la gestion de Sieve. Les paramètres sont identiques à lIMAP, mais sur le port 4190.</p>
<p>Et ensuite dans le menu Outils/Filtres Sieve (M). Faites nouveau pour créer un nouveau fichier de règles.</p>
<p>Voila un exemple de règles :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>require ["variables", "envelope", "fileinto", "mailbox", "regex", "subaddress", "body"];
if header :contains "subject" "Postfix SMTP server" {
fileinto "Serveur";
}
</code></pre></div></div>
<p>En cas de fautes, le plugin vous le signale. Il ne reste qua enregistrer. A noter : Sieve permet davoir plusieurs fichiers de filtrage, mais un seul peut être actif à la fois.</p>
<p>Bien sur, Sieve permet de faire beaucoup de choses, avec gestion de conditions, variables, etc… Ce sera loccasion de faire un article complet (ou presque) sur Sieve un peu plus tard.</p>
<h4 id="tests-1">Tests</h4>
<p>Comme je lai dis, loption <code class="language-plaintext highlighter-rouge">mail_debug = yes</code> permet de voir de suite dans le fichier de log /var/log/mail.log c e qui ne va pas.</p>
<h3 id="quota">Quota</h3>
<h4 id="configuration-1">Configuration</h4>
<p>Pour rajouter la gestion du quota, il suffit de quelques manipulations. Pour rappel, si le champ dans le ldap est égal à zéro, cela veut dire pas de quota. Notez également que le champ est exprimé en bytes.</p>
<p>Dans le fichier /etc/dovecot/conf.d/10-mail.conf on va modifier :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[...]
mail_plugins = $mail_plugins quota
[...]
</code></pre></div></div>
<p>Dans le fichier /etc/dovecot/conf.d/20-imap.conf :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[...]
mail_plugins = $mail_plugins imap_sieve imap_quota
[...]
</code></pre></div></div>
<p>Puis dans le fichier 20-lmtp.conf :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[...]
mail_plugins = $mail_plugins sieve quota
[...]
</code></pre></div></div>
<p>Et pour terminer on remplace le contenu du fichier /etc/dovecot/conf.d/90-quota.conf par ce qui suit :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>plugin {
quota = maildir:User quota
quota_warning = storage=90%% quota-warning 90 %u
}
service quota-warning {
executable = script /etc/dovecot/quota.sh
user = vmail
unix_listener quota-warning {
user = vmail
}
}
</code></pre></div></div>
<p>Voila, cest en place.</p>
<h4 id="avertissement-automatique">Avertissement automatique</h4>
<p>On va rajouter le petit script qui va envoyer le mail dalerte.</p>
<p>Dans un fichier /etc/dovecot/quota.sh mettez cela :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#!/usr/bin/env bash
PERCENT=${1}
USER=${2}
cat &lt;&lt; EOF | /usr/lib/dovecot/dovecot-lda -d $USER -o "plugin/quota=maildir:User quota:noenforcing"
From: no-reply@xoyize.xyz
Subject: HOHOHO: Votre BAL est pleine a ${PERCENT}
Content-Type: text/plain; charset="utf-8"
HOHOHO
Votre BAL est pleine a ${PERCENT}. Faut faire du menage mon coco !
EOF
</code></pre></div></div>
<p>Rien de bien compliqué.</p>
<p>On soccupe des permissions :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>chmod +x /etc/dovecot/quota.sh
chown vmail /etc/dovecot/quota.sh
</code></pre></div></div>
<p>Lœil attentif remarquera que lon fait appel à dovecot-lda pour ce mail. Jai tenté avec le lmtp, où pourtant -d a la même utilité, mais je me mange une erreur. Tant pis, au final, lda est toujours dispo, et nest invoqué quau besoin. Autant dire que ça ne va pas beaucoup tourner</p>
<p>On édite le fichier /etc/dovecot/conf.d/15-lda.conf pour tout remplacer par :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>protocol lda {
info_log_path =
log_path =
mail_plugins = sieve quota
postmaster_address = postmaster@xoyize.xyz
quota_full_tempfail = yes
}
</code></pre></div></div>
<p>On recharge Dovecot :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dovecot reload
</code></pre></div></div>
<h4 id="tests-2">Tests</h4>
<p>Si vous voulez ajouter un quota à un utilisateur pour tester, sur le ldap, un fichier mod_quota.ldif :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: uid=cm,ou=people,dc=xoyize,dc=xyz
changetype: modify
replace: mailaccountquota
mailaccountquota: 2147483648
</code></pre></div></div>
<p>Que vous injectez :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ladd -f mod_quota.ldif
</code></pre></div></div>
<p>La, il est réglé à 2Go.</p>
<p>Ensuite, de retour sur le serveur mail, on va déja regarder si le quota est fonctionnel :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>doveadm quota get -u cm@xoyize.xyz
</code></pre></div></div>
<p>doit retourner quelque chose qui ressemble à ca :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Quota name Type Value Limit %
User quota STORAGE 0 2097152 0
User quota MESSAGE 0 - 0
</code></pre></div></div>
<p>On va aussi tester le script :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./quota.sh 90 cm@xoyize.xyz
</code></pre></div></div>
<p>Et pour tester en conditions réelles, attention, il faut savoir que le mail nest envoyé quune seule fois, au moment où le quota est dépassé.</p>
<p>Par exemple, si vous activez le quota avec une bal deja hors quota vous naurez jamais le mail. Du coup, pour tester, cest pas évident… faut jouer avec une toute petite bal. Mais si les deux commandes au préalable fonctionnent sans problemes, ca doit marcher en production.</p>
<p>Pour voir le quota avec Thunderbird, je conseille <a href="https://addons.thunderbird.net/fr/thunderbird/addon/display-quota/">Display Quota</a>.</p>
<h4 id="conclusion">Conclusion</h4>
<p>Voila pour cette troisième partie où nous avons fait connaissance avec Dovecot. Je pourrais vous parler en profondeur de la commande doveadm qui permet de faire pas mal de choses mais ce sera pour un tuto annexe… En attendant, rien ne vous empêche de vous documenter.</p>
<p>Vous voulez la bonne nouvelle ? Notre serveur de messagerie est opérationnel.</p>
<p>Pour tester, rien de tel quun client lourd. Thunderbird pour Windows, Evolution ou autres pour Linux.</p>
<p>Vous devez pouvoir recevoir et envoyer des mails.</p>
<p>Attention cependant, aucune protection contre le spam nest encore en place à ce niveau. De la meme façon, vos mails envoyés vers certains domaines (gmail, hotmail) risquent fort de se retrouver classés comme spams (DKIM, SPF et DMARC pas encore en place).</p>
<p>Et oui, jai dis opérationnel, pas terminé ! Mais ne ne perdez pas espoir, on a fait le plus dur.</p>
<h2 id="optimisation-de-postfix">Optimisation de Postfix</h2>
<h3 id="sécurisation-smtps-et-submission">Sécurisation SMTPS et Submission</h3>
<p>La partie consacrée à Postfix laisse dans notre configuration une potentielle future faille.</p>
<p>Vous le savez, un SMTP externe communiquera avec le votre via le port 25, mais imaginons quun instant, un serveur se connecte à vos ports 465 ou bien 587…
Et bien, il ne sera pas embêté. Au final, pour le moment, ce nest pas bien méchant car il suivra les restrictions quil aurait rencontré via le port 25, mais si nous mettons en place des restrictions différentes selon les services (et cest ce que nous ferons plus tard), et bien, on risque davoir des surprises.</p>
<p>Autant y remédier de suite en bloquant la possibilité à un serveur SMTP de se connecter la ou il ne doit pas.</p>
<p>On édite le fichier /etc/postfix/master.cf :</p>
<p>[…]
submission inet n - y - - smtpd
-o smtpd_tls_security_level=encrypt
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
smtps inet n - y - - smtpd
-o smtpd_tls_security_level=encrypt
-o smtpd_tls_wrappermode=yes
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
[…]</p>
<p>Pour ces deux services, la directive <code class="language-plaintext highlighter-rouge">smtpd_client_restriction</code> du <strong>main.cf</strong> est remplacée :<br />
on permet les utilisateurs connectés et on rejette le reste.</p>
<h3 id="antiforge">Antiforge</h3>
<h4 id="explication">Explication</h4>
<p>Derrière ce terme, se cache quelque de tout simple : le fait de pouvoir modifier (forger) sans vergogne ladresse de lexpéditeur. Je mexplique :</p>
<ul>
<li>Depuis votre logiciel de messagerie, si vous modifiez ladresse dexpéditeur par nimportequoi@xoyize.xyz, le mail est expédié sans aucun soucis.</li>
<li>De la même façon, un MX distant peut tout à fait présenter un mail auprès de notre serveur avec un FROM TO : ym@xoyize.xyz , sans que cela ne gène le moins du monde.</li>
</ul>
<p>On va donc rajouter une restriction au moment de la vérification du sender afin de limiter tout cela.</p>
<p>On édite le fichier <strong>/etc/postfix/main.cf</strong> et on ajoute dans la directive <code class="language-plaintext highlighter-rouge">smtpd_sender_resctriction</code> :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>smtpd_sender_restrictions =
reject_sender_login_mismatch,
permit_mynetworks,
[...]
</code></pre></div></div>
<p>Cette nouvelle directive sappuie sur une <code class="language-plaintext highlighter-rouge">sender_login_maps</code>, quon va déclarer plus bas :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>smtpd_sender_login_maps = ldap:/etc/postfix/ldap/virtual_senders.cf
</code></pre></div></div>
<p>On va créer le fichier qui va faire le lien avec le ldap :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/etc/postfix/ldap/virtual_senders.cf
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>server_host = ldap://127.0.0.1
version = 3
bind = yes
bind_dn = cn=viewer,ou=system,dc=xoyize,dc=xyz
bind_pw = passview
search_base = dc=xoyize,dc=xyz
scope = sub
query_filter = (|(&amp;(mailaliasfrom=%s)(objectClass=mailaliasxoyize)(mailaliasactif=YES))(&amp;(mail=%s)(objectClass=mailaccountxoyize)(mailaccountactif=YES)))
result_attribute = mail mailaliasto
</code></pre></div></div>
<p>Simple, cela retourne le propriétaire de la boite demandée si il y en a un.</p>
<p>Ensuite, deux cas de figures :</p>
<ul>
<li>Si le client est SASL, il faut quil soit le propriétaire du mail pour que le mail soit accepté.</li>
<li>Si le client nes pas en SASL et que la boite a un propriétaire, le mail ne sera pas accepté.</li>
</ul>
<p>Vite fait, je reviens bien sur la distinction TLS et SASL :</p>
<ul>
<li>Client SASL veut dire que le client est authentifié : un utilisateur.</li>
<li>Client Non SASL veut dire que cest un client non authentifié, un serveur MX.</li>
</ul>
<p>A ne pas confondre avec TLS (ou SSL) qui est la sécurité appliquée à la connexion.</p>
<p>On recharge Postfix :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>postfix reload
</code></pre></div></div>
<p><strong>Pour le fun</strong><br />
Imaginons que dans notre schéma LDAP, nous ayons un attribut mailaliassend qui stipule si un alias a le droit ou non denvoyer des mails. Après tout, pourquoi pas…</p>
<p>Notre fichier /etc/postfix/ldap/virtual_senders.cf pourrait ressembler à cela :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>server_host = ldap://127.0.0.1
version = 3
bind = yes
bind_dn = cn=viewer,ou=system,dc=xoyize,dc=xyz
bind_pw = passview
search_base = dc=xoyize,dc=xyz
scope = sub
query_filter = (|(&amp;(mailaliasfrom=%s)(objectClass=mailaliasxoyize)(mailaliasactif=YES)(mailaliassend=YES))(&amp;(mail=%s)(objectClass=mailaccountxoyize)(mailaccountactif=YES)))
result_attribute = mail mailaliasto
</code></pre></div></div>
<p>La puissance du LDAP, tout simplement…</p>
<h3 id="contrôle-daccès-facultatif">Contrôle daccès (Facultatif)</h3>
<p>Dans sa grande générosité au niveau de ses possibilités, Postfix permet également deffectuer des vérifications daccès à laide de quatre contrôles :</p>
<ul>
<li>check_client_access</li>
<li>check_helo_access</li>
<li>check_sender_access</li>
<li>check_recipient_access</li>
</ul>
<p>Dans le cadre du renforcement de notre serveur Postfix, seul check_sender_access sera vraiment obligatoire, les autres, cest au cas pas cas.</p>
<p>Ces vérification sappuient sur des données formatées de la sorte :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>111.111.111.111 REJECT
// ou bien
ym@xoyize.xyz OK
</code></pre></div></div>
<p>Bien que le OK soit implicite (si pas dentrée, cest OK par défaut), il peut être nécessaire dans certains cas de lexpliciter.</p>
<p>Au niveau des actions possible, nous trouvons entre autres :</p>
<ul>
<li>DUNNO: On ne fait rien (sort du contrôle en cours et permet déviter un éventuel match avec une règle plus bas…)</li>
<li>HOLD: le mail reste dans la queue de Postfix.</li>
<li>REDIRECT: redirection du mail vers une autre adresse.</li>
<li>etc…</li>
</ul>
<p>Plus de détails ici : http://www.postfix.org/access.5.html</p>
<p>Si vous utilisez des fichiers statiques, après chaque modification, il faudra invoquer :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>postmap /etc/postfix/nomdufichier
</code></pre></div></div>
<p>Et pour rappel, vu notre configuration actuelle, ces quatre contrôles se font dans un sens (mail entrant) et dans lautre (mail sortant).</p>
<p>Chacun de ces contrôles prendra place ensuite dans son bloc smtpd_*_restrictions correspondant.</p>
<h4 id="check_client_access">check_client_access</h4>
<p>Ici, on peut autoriser ou interdire des IPs ou des domaines spécifiques.</p>
<p>Exemple :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>111.222.111.222 REJECT
domaineabloquer.com REJECT
</code></pre></div></div>
<p>Et cela se place de la sorte :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>smtpd_client_restrictions =
check_client_access hash:/etc/postfix/acces_client,
[...]
</code></pre></div></div>
<p>Pour filtrer des lourds (spammeurs, bots, etc…), ce nest pas forcement la meilleure solution. Dune, il faut que la la liste daccès soit maintenue à jour, et cest alors un travail quotidien, ou presque…</p>
<p>Seconde « limitation », même en cas de Reject, la session SMTP ira jusquau RCPT ou bien sur, elle sera terminée, mais on a gaspillé de la ressource à aller si loin.</p>
<p>On peut demander à Postfix darrêter la communication dès quune restriction sapplique (en mettant loption smtpd_delay_reject = no) mais pour trouver un éventuel soucis par la suite, ce peut être gênant. Autre problématique, certains clients gèrent mal une connexion coupée de la sorte…</p>
<p>Je conseille donc de laisser comme cest par défaut.</p>
<p>Et donc pour bloquer des IPS spécifiques, je passe soit par mon firewall, soit par Postscreen (quon verra plus tard).</p>
<h4 id="check_helo_access">check_helo_access</h4>
<p>Ici est effectué un contrôle sur le helo. Comme pour le client, cest vite compliqué de maintenir quelque chose à jour.</p>
<p>Cependant, on peut quand même effectuer un petit contrôle de routine pour éviter le helo qui serait le notre, ça ne mange pas de pain…</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>smtpd_helo_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
check_helo_access ldap:/etc/postfix/ldap/check_helo_domains_reject.cf,
[...]
</code></pre></div></div>
<p>Et le fichier <strong>/etc/postfix/ldap/check_helo_domains_reject.cf</strong> correspondant :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>server_host = ldap://127.0.0.1
version = 3
bind = yes
bind_dn = cn=viewer,ou=system,dc=xoyize,dc=xyz
bind_pw = passview
search_base = ou=mail,dc=xoyize,dc=xyz
scope = sub
query_filter = (&amp;(maildomain=%s)(objectClass=maildomainxoyize)(maildomainactif=YES))
result_attribute = maildomain
result_filter = REJECT Menteur
</code></pre></div></div>
<p>Rien de sorcier…</p>
<p>On pense à recharger Postfix :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>postfix reload
</code></pre></div></div>
<h4 id="check_recipient_access">check_recipient_access</h4>
<p>La, on peut contrôler le destinataire :</p>
<p>Par exemple :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>spam@domaine.xyz REJECT
</code></pre></div></div>
<p>Pour bloquer les mails destiné a mon utilisateur ym. Oui, cest pas franchement utile…</p>
<p>Ou bien encore :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>adresseextene@gmail.com REJECT
</code></pre></div></div>
<p>Pour bloquer les mails à destination de cette adresse. Lintérêt la aussi, dans notre cas de figure, est faible, mais ça existe et ça peut servir.</p>
<h4 id="check_sender_access">check_sender_access</h4>
<p>Ici, on va effectuer un contrôle au niveau de lexpéditeur et on va revenir sur lantiforge dont je parlais avant pour sintéresser à un cas que je nai pas traité :</p>
<p>Imaginons un serveur SMTP qui nous envoie un mail avec comme FROM TO un mail du genre userbidon@xoyize.xyz, la bal nappartenant à personne, le <code class="language-plaintext highlighter-rouge">reject_sender_login_mismatch</code> quon a vu plus haut ne bloquera rien, le mail passera.</p>
<p>On va donc y remédier. Lidée étant dautoriser en FROM TO nos mails qui existent et de rejeter le reste.</p>
<p>Dans le bloc smtpd_sender_resctrictions, nos usagers sont déjà autorisés via permit_sasl_authenticated, on va donc ajouter juste après la restriction pour nos domaines.</p>
<p>On aurait alors besoin dun fichier ressemblant à ça :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@xoyize.xyz REJECT
@ouestline.net REJECT
</code></pre></div></div>
<p>Et comme on a le Ldap pour alimenter cela, on va donc passer par un fichier nommé</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/etc/postfix/ldap/check_sender_domains_reject.cf
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>server_host = ldap://127.0.0.1
version = 3
bind = yes
bind_dn = cn=viewer,ou=system,dc=xoyize,dc=xyz
bind_pw = passview
search_base = ou=mail,dc=xoyize,dc=xyz
scope = sub
query_filter = (&amp;(maildomain=%s)(objectClass=maildomainxoyize)(maildomainactif=YES))
result_attribute = maildomain
result_filter = REJECT Ho le vilain....
</code></pre></div></div>
<p>Et cela se place de la sorte dans le fichier</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/etc/postfix/main.cf
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>smtpd_sender_restrictions =
reject_sender_login_mismatch,
permit_mynetworks,
permit_sasl_authenticated,
check_sender_access ldap://etc/postfix/ldap/check_sender_domains_reject.cf,
[...]
</code></pre></div></div>
<p>Lordre est important. Si le check_sender_access est positionné avant le permit_sasl_authenticated, nos propres utilisateurs ne pourraient pas envoyer de mails.</p>
<p>On recharge :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>postfix reload
</code></pre></div></div>
<p>Et voila lantiforge amélioré.</p>
<h3 id="les--header-checks-">Les « Header Checks »</h3>
<p>Dernière série de contrôle que lon peut effectuer ou lon va regarder un peu plus profondément dans le mail.</p>
<p>Postfix nous propose plusieurs types de contrôles sur les headers :</p>
<ul>
<li>header_check : contrôle dans le header (format ASCII)</li>
<li>mime_header_check : contrôle dans le header (format MIME)</li>
<li>body_header_check : contrôle dans corps.</li>
<li>etc…</li>
</ul>
<p>Plus de détails ici : http://www.postfix.org/header_checks.5.html</p>
<p>Pour les utiliser, il faut tout dabord installer le module pcre de Postfix :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt-get install postfix-pcre
</code></pre></div></div>
<p>On recharge Postfix :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>postfix reload
</code></pre></div></div>
<p>A partir de maintenant, on va pouvoir inspecter, via des REGEX, ce quil y a dans les mails.</p>
<p>La syntaxe est la suivante :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/^Subject:.*viagra*/ REJECT Pas besoin, merci
/^From: *ym*/ REJECT On connait la blague
</code></pre></div></div>
<p>De mon avis et expérience, pour filtrer le spam, ce nest plus vraiment la meilleure solution sauf cas particulier. Compliqué de maintenir un truc à jour, risque dun regex un peu foireux qui pourrait être trop restrictif…</p>
<p>Pour information, il est possible de tester vos regex de la sorte :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>postmap -q "Subject: viagra" pcre:/etc/postfix/header_checks
</code></pre></div></div>
<p>Cela doit vous renvoyer la règle qui sapplique.</p>
<p>Si vous voulez tester avec un mail sauvegardé dans un fichier :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>postmap -q - pcre:/etc/postfix/header_checks &lt; mail.txt
</code></pre></div></div>
<p>Le second après le -q est important.</p>
<p>Donc pour le traitement du spam, on fait mieux, cependant, je me sers du <strong>header_check</strong> pour deux choses.</p>
<p>Créons dabord le répertoire qui va accueillir nos fichiers :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkdir /etc/postfix/check/
</code></pre></div></div>
<h4 id="filtrage-des-fichiers-à-risque">Filtrage des fichiers à risque</h4>
<p>Au niveau filtrage en entrée, pour la démonstration, je ne montre quun exemple simple. A vous après de faire vos propres règles.</p>
<p>Créons un fichier <strong>/etc/postfix/check/header_checks_in</strong> avec ce qui suit :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/^s*Content­.(Disposition|Type).*names*=s*"?(.+.(bat|exe|com|scr|vbs))"?s*$/ PREPEND X-xoyize:WARN
</code></pre></div></div>
<p>Ici, un header perso X-xoyize: WARN est ajouté si le mail contient un fichier avec les extensions bat, exe, com, etc… Celui ci pourra me servir à effectuer un filtre avec Sieve.</p>
<p>Au niveau des actions, on retrouve peu ou prou ce quon avait pour les contrôles daccès. On peut donc aussi utiliser REJECT pour le rejeter, REDIRECT xxx@xoyize.xyz , etc…</p>
<p>Voila pour les contrôles en entrée.</p>
<h4 id="supprimer-les-informations-sensibles">Supprimer les informations sensibles</h4>
<p>Lautre utilité est de pouvoir masquer certaines informations de vos mails sortants, tel que votre IP denvoi, votre client mail… Par défaut, les headers ajoutés par votre client de messagerie sont un peu trop causant…</p>
<p>Si on regarde ces fameux headers dun mail qui partirait de chez vous vers un destinataire externe, le premier Received ressemblerait à cela :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Received: from [monip] (monreverse[monip])
(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits))
(No client certificate requested)
by mail.xoyize.xyz (Postfix) with ESMTPSA id 3AB4D22938
for &lt;destinataire@destination.com&gt;; Mon, 21 Jul 1969 02:56:20 +0000 (UTC)
</code></pre></div></div>
<p>Au passage, pour lire les headers dun mail, il faut commencer par le bas. Le premier Received que lon voit en haut est en fait le dernier SMTP rencontré, celui du destinataire.</p>
<p>Et donc notre destinataire, sil est un peu curieux, peut connaitre notre IP. Puis sil descend un peu, il en apprendra davantage sur votre OS, etc…</p>
<p>Hum, ce nest pas terrible…</p>
<p>Allez, nettoyons tout cela !</p>
<p>Dans un fichier <strong>/etc/postfix/check/header_checks_out</strong> :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/^\s*Received: from \S+ \(\S+ \[\S+\]\)(.*)/ REPLACE Received: from [127.0.0.1] (localhost [127.0.0.1])$1
/^X-Originating-IP:/ IGNORE
/^X-Mailer:/ IGNORE
/^Mime-Version:/ IGNORE
/^User-Agent:/ IGNORE
</code></pre></div></div>
<p>Au passage, on rencontre souvent sur internet ce regex :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/^Received:.*with ESMTPSA/ IGNORE
</code></pre></div></div>
<p>En lieu et place de celui que je vous ai indiqué. Celui ci est un peu trop violent à mon gout, vu quil supprime complétement le Received from vous concernant. Il peut toujours être utile dindiquer que vous étiez en tls, le serveur qui a reçu le mail, etc… Je préfère donc juste masquer lip dorigine.</p>
<h4 id="oui-mais">Oui mais…</h4>
<p>Si on indique ces checks dans le main.cf avec :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>header_checks = pcre:/etc/postfix/check/header_checks_in,pcre:/etc/postfix/check/header_checks_out
mime_header_checks = pcre:/etc/postfix/check/header_checks_in,pcre:/etc/postfix/check/header_checks_out
</code></pre></div></div>
<p>Ils vont se faire sur les mails qui entrent et qui sortent. Et on ne veut pas effacer les headers des mails qui nous arrivent… ni ajouter un éventuel header au mail que lon envoie.</p>
<p>Que faire…</p>
<p>La solution élégante est de faire appel au process cleanup et den faire deux « sous process ». Chacun en charge dun traitement, et affecté comme il faut.</p>
<p>Pour se faire, cest assez simple :</p>
<p>On édite le fichier <strong>/etc/postfix/master.cf</strong> pour y ajouter pour chaque service un nouveau service cleanup :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>smtp pass - - y - - smtpd
-o cleanup_service_name=subcleanin
[...]
submission inet n - y - - smtpd
-o smtpd_tls_security_level=encrypt
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
-o cleanup_service_name=subcleanout
smtps inet n - y - - smtpd
-o smtpd_tls_security_level=encrypt
-o smtpd_tls_wrappermode=yes
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
-o cleanup_service_name=subcleanout
</code></pre></div></div>
<p>Et plus bas, en dessous du cleanup existant, on ajoute :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cleanup unix n - y - 0 cleanup
subcleanout unix n - - - 0 cleanup
-o header_checks=pcre:/etc/postfix/check/header_checks_out
-o mime_header_checks=pcre:/etc/postfix/check/header_checks_out
subcleanin unix n - - - 0 cleanup
-o header_checks=pcre:/etc/postfix/check/header_checks_in
-o mime_header_checks=pcre:/etc/postfix/check/header_checks_in
</code></pre></div></div>
<p>Précisez bien <strong>header_checks</strong> et <strong>mime_header_checks</strong> : les headers pouvant être dans le format MIME ou ASCII, il est important de bien traiter les deux cas.</p>
<p>On recharge Postfix :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>postfix reload
</code></pre></div></div>
<p>Je vous invite bien évidement à consulter la <a href="http://www.postfix.org/documentation.html">doc officielle</a> qui vous permettra de trouver réponses à vos cas particuliers.</p>
<h2 id="policyd-spf">Policyd SPF</h2>
<p>SPF (Sender Policy Framework) est un mécanisme simple qui permet de savoir si un SMTP à lorigine dun mail est bien légitime .</p>
<p>Cela sappuie sur un enregistrement TXT dans le DNS qui ressemble à ça :</p>
<p>“v=spf1 ip4:ip.legitime mx -all”</p>
<p>Cet enregistrement stipule quel MX est autorisé à envoyer depuis le domaine.</p>
<p>Ici, on va mettre en place la vérification pour les mails entrants, la création dans notre DNS de notre propre SPF se fera dans la partie VII.</p>
<p>On commence en installant le module SPF python (il existe aussi une version perl, mais celle en python est mieux maintenue et nécessite moins de dépendances, puis le python, cest la vie !)</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt-get install postfix-policyd-spf-python
</code></pre></div></div>
<p>Il se configure dans le fichier <strong>/etc/postfix-policyd-spf-python/policyd-spf.conf</strong> ou vous indiquez ceci :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>debugLevel = 1
HELO_reject = Fail
Mail_From_reject = Fail
PermError_reject = False
TempError_Defer = False
skip_addresses = 127.0.0.0/8,::ffff:127.0.0.0/104,::1
</code></pre></div></div>
<p>Les mails qui ne respectent pas les SPF seront rejetés. Par contre, sil ny a pas de SPF définis, on accepte (les refuser ici est une mauvaise idée, nombre de domaines légitimes nont pas de SPF en place…)</p>
<p>Ensuite, dans le fichier <strong>/etc/postfix/master.cf</strong> on va ajouter en bas :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>policyd-spf unix - n n - - spawn
user=nobody argv=/usr/bin/policyd-spf
</code></pre></div></div>
<p>Et dans le <strong>/etc/postfix/main.cf</strong>, on ajoute tout dabord une ligne :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>policyd-spf_time_limit = 3600s
</code></pre></div></div>
<p>puis en dessous, dans le bloc <code class="language-plaintext highlighter-rouge">smtpd_sender_restrictions</code>, on ajoute :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>smtpd_sender_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
check_sender_access ldap://etc/postfix/ldap/check_sender_domains_reject.cf,
check_policy_service unix:private/policyd-spf,
[...]
</code></pre></div></div>
<p>On recharge :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>postfix reload
</code></pre></div></div>
<p>Dorénavant, chaque mail entrant subira une vérification SPF. On pourra lobserver en regardant les headers dun mail quon reçoit :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Received: from mail.xoyize.xyz
by mail (Dovecot) with LMTP id N59BECWohFzNVwAAZU03Dg
for &lt;ym@xoyize.xyz&gt;; Mon, 21 Jul 1969 02:56:20 +0000
Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=blablabla...
</code></pre></div></div>
<p>Voila pour le contrôle SPF qui va permettre décrémer un peu plus.</p>
<h2 id="postcreen-a-voir">Postcreen (A voir)</h2>
<p>On arrive à la chose la plus efficace pour lutter contre le spam bête et méchant. Sil ne devait en rester quun, ce serait lui je pense. Pourtant il ne réinvente rien et utilise des principes déjà connus, mais il le fait bien et simplement.</p>
<p>Il est le bouclier contre les zombies/bots, etc… que je résume en « les lourds »…</p>
<p>Il est… Postscreen !</p>
<h3 id="activation">Activation</h3>
<p>Postscreen étant intégré dans Postfix, rien à installer, il suffit de lactiver.</p>
<p>Dans le fichier /etc/postfix/master.cf, commentez la ligne :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>smtp inet n - y - - smtpd
</code></pre></div></div>
<p>et décommentez juste en dessous :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>smtp inet n - y - 1 postscreen
smtpd pass - - y - - smtpd
dnsblog unix - - y - 0 dnsblog
tlsproxy unix - - y - 0 tlsproxy
</code></pre></div></div>
<p>Le reste de la configuration se passera dans le fichier /etc/postfix/main.cf</p>
<h3 id="configuration-2">Configuration</h3>
<p>Postcreen peut faire deux types de contrôles :</p>
<ul>
<li>des contrôles simples</li>
<li>des contrôles profonds</li>
</ul>
<p>Les contrôles simples se fond avant de passer la main à Postfix. Si tout est Ok, le mail suit son chemin.</p>
<p>Au contraire, les contrôles profonds prennent la main sur lensemble du dialogue et introduisent de par ce fait, un greylisting. Jy reviens plus bas.</p>
<p>Pour chaque contrôle, on peut définir une action :</p>
<ul>
<li>ignore : on ne fait rien. Peut servir en cas de debug.</li>
<li>enforced : le blocage est actif, mais la coupure se fera au moment du RCPT TO: avec une réponse 550 5.3.2 Service currently unavailable</li>
<li>drop : efficace, on coupe court avec une réponse 521 5.3.2 Service currently unavailable</li>
</ul>
<p>Un serveur qui sera accepté sera mis en liste blanche pour les prochaines fois afin de ne pas mobiliser de la ressource pour rien.</p>
<h4 id="contrôles-simples">Contrôles simples</h4>
<p>Simples ne veut pas dire inefficaces. Personnellement, je nutilise queux, naimant pas le principe du greylisting.</p>
<p>a Access list</p>
<p>Simplissime, ce contrôle va permettre de bloquer des IPs et contrairement au blocage dans Postfix, ici avec laction sur drop, cest immédiat. Ciao !</p>
<p>Pour lactiver, ajoutez ces lignes dans le fichier /etc/postfix/main.cf :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>postscreen_access_list = permit_mynetworks, cidr:/etc/postfix/postscreen_access.cidr
postscreen_blacklist_action = drop
</code></pre></div></div>
<p>Le fichier /etc/postfix/postscreen_access.cidr doit ressembler à cela :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>xxx.xxx.xxx.xxx reject
[...]
</code></pre></div></div>
<p>b Greet banner</p>
<p>Ce contrôle joue sur une subtilité du protocole SMTP. En effet, si le serveur répond avec « 250-On attend un pneu…« , le tiret après le 250 indique quil y a plusieurs lignes.</p>
<p>Et il attend le temps indiqué avant denvoyer le « 250 mail.xoyize.xyz ESMTP mail (Debian/GNU)« .</p>
<p>Du coup, un zombie trop rapide se fera avoir, et hop, dehors !</p>
<p>Pour lactiver, ajoutez ces lignes dans le fichier /etc/postfix/main.cf :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>postscreen_greet_wait = 3s
postscreen_greet_banner = On attend un pneu...
postscreen_greet_action = drop
</code></pre></div></div>
<p>Le temps dattente sert également à Postscreen pour consulter les Dnsbl quon voit juste en dessous.</p>
<p>c Dnsbl</p>
<p>Ici, on va interroger des RBL.</p>
<p>Le principe est simple. On passe par une interrogation DNS pour savoir si un MX distant est légitime ou non.</p>
<p>Une réponse du genre 127.0.0.X indique quil ne faut pas accepter le mail (le type de réponse dépend de la liste dans la quelle se trouve lIP). On peut éventuellement ne prendre que certaines réponses.</p>
<p>Pour lactiver, ajoutez ces lignes dans le fichier /etc/postfix/main.cf :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>postscreen_dnsbl_sites =
zen.spamhaus.org*2,
bl.spamcop.net,
b.barracudacentral.org*2
postscreen_dnsbl_threshold = 3
postscreen_dnsbl_action = drop
</code></pre></div></div>
<p>Jutilise trois listes. En mettre de trop nest pas forcement une bonne idée, temps dinterrogation plus long, trouver comment bien pondérer chacune…</p>
<p>Sachez cependant quil en existe plein. Vous en trouverez ici par exemple : https://www.dnsbl.info/dnsbl-list.php</p>
<p>Pour faire une interrogation à la main, si votre IP est AAA.BBB.CCC.DDD :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dig +short DDD.CCC.BBB.AAA.b.barracudacentral.org
</code></pre></div></div>
<p>On passe par un PTR record, ce qui fait quon peut connaitre « létat » dune IP en interrogeant nimporte quel DNS, ce dernier ira faire la requête au bon endroit.</p>
<p>Si pas de retour, lIP nest pas blacklisté, si retour, IP blacklisté donc.</p>
<h4 id="contrôle-profond">Contrôle profond</h4>
<p>Très efficace contre les « lourds » qui ne respecte pas le protocole SMTP, ces vérifications ont cependant un inconvénient comme je lexpliquais :</p>
<p>Ils introduisent un Greylisting. Pour effectuer ces vérifications, Postscreen prend en charge tout la communication SMTP, mais le bougre à la fin nest pas capable de repasser la main à Postfix.</p>
<p>Postscreen règle le problème très « simplement » en répondant à la fin : 450 4.3.2 Service currently unavailable et en mettant au passage le serveur en liste blanche.</p>
<p>Le voila le Greylisting … Un serveur SMTP bien configuré se doit de retenter lenvoi et comme il sera reconnu par Postscreen, ce dernier passera la main à Postfix.</p>
<p>Ça, cest la théorie… Les trucs configurés avec les pieds, on en voit partout et il est tout a fait possible que le SMTP distant, bien que légitime, ne retente jamais. Cest pourquoi je naime pas le principe du Greylisting.</p>
<p>A vous de voir comme vous le sentez …</p>
<p>a Pipelining</p>
<p>Postscreen ne gérant pas le pipelining (du full duplex en quelque sorte), il lindique durant la communication. Un client « correct » le prendra alors en compte. Un « lourd », certainement que non, et bam !</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>postscreen_pipelining_enable = yes
postscreen_pipelining_action = enforce
</code></pre></div></div>
<p>b Non SMTP Command</p>
<p>Contrôle sur déventuelles commandes CONNECT, GET et POST utilisées par les « lourds » passant par des proxys.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>postscreen_non_smtp_command_enable = yes
postscreen_non_smtp_command_action = enforce
</code></pre></div></div>
<p>c Bare Newline</p>
<p>La norme SMTP impose que chaque ligne se terminer par <CR><LF>. Les « lourds », souvent, nutilisent que <LF>.</LF></LF></CR></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>postscreen_bare_newline_enable = yes
postscreen_bare_newline_action = enforce
</code></pre></div></div>
<hr />
<h2 id="rspamd">Rspamd</h2>
<p>Sixième partie de ma série darticles sur la mise en place dun serveur mail complet. Courage, on arrive au bout ! Nous allons installer et configurer le digne remplaçant du vénérable Spamassassin, jai nommé Rspamd.</p>
<h3 id="installation-1">Installation</h3>
<p>Le paquet Rspamd disponible dans les dépôts de Debian nétant plus maintenu, on va passer par les dépôts de Rspamd</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wget -O- https://rspamd.com/apt-stable/gpg.key | apt-key add -
</code></pre></div></div>
<p>La, si un message évoque un problème de certificats, cest que le paquet ca-certificates nest pas présent.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>echo "deb [arch=amd64] http://rspamd.com/apt-stable/ stretch main" &gt; /etc/apt/sources.list.d/rspamd.list
apt-get update
apt-get install rspamd
</code></pre></div></div>
<p>Pour fonctionner, Rspamd a besoin dun serveur redis. Perso, jai une VM dédiée à cela, mais ici, on va linstaller sur le serveur mail.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt-get install redis-server
</code></pre></div></div>
<p>Nous verrons dans un article plus tard linstallation dun serveur redis « général ».</p>
<h3 id="configuration-3">Configuration</h3>
<h4 id="assistant">Assistant</h4>
<p>Pour la configuration initiale, un assistant est disponible, ne nous en privons pas :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rspamadm configwizard
</code></pre></div></div>
<p>Puis répondez :</p>
<p>Do you wish to continue?[Y/n]: -&gt; Yes
Controller password is not set, do you want to set one?[Y/n]: -&gt; Yes (et vous le renseignez)
Do you wish to set Redis servers?[Y/n]: -&gt; Yes
Input read only servers separated by <code class="language-plaintext highlighter-rouge">,</code> [default: localhost]: (si server Redis sur un autre serveur, vous indiquez son ip.)
Input write only servers separated by <code class="language-plaintext highlighter-rouge">,</code> [default: localhost]: (idem)
Do you have any password set for your Redis?[y/N]: si ya un pass, vous lindiquez
Do you have any specific database for your Redis?[y/N]: sil faut spécifier une base (dans le cas dun redis central), vous la spécifiez aussi
Do you want to setup dkim signing feature?[y/N]: -&gt; No (on le fera après)
Expire time for new tokens [100d]: on laisse par défaut.
Reset previous data?[y/N]: -&gt; No, vu quil ny en a pas
Do you wish to convert them to Redis?[Y/n]: -&gt; Yes</p>
<p>Et on termine en rechargeant :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>service rspamd reload
</code></pre></div></div>
<h4 id="quelques-ajustements">Quelques ajustements</h4>
<p>Au niveau de la configuration, tout se passe dans /etc/rspamd/, cependant, modifier ces fichiers nest pas une bonne idée car comme indiqué dedans, ils se retrouveraient écrasés en cas dupdate. On va donc utiliser le répertoire <strong>/etc/rspamd/local.d</strong> pour y indiquer nos modifications.</p>
<p>1 classifier-bayes.conf</p>
<p>Ce fichier est créé par lassistant, il suffit de rajouter la première ligne</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>autolearn = true;
backend = "redis";
new_schema = true;
expire = 8640000;
</code></pre></div></div>
<p>2 worker-controller.inc</p>
<p>Fichier également créé par lassistant. Ici, on rajoute les deux lignes pour les sockets. Le socket sur le port 11334 sera pour linterface Web (cest pour cela quon le bind sur lip interne de la machine et non sur localhost). Le second socket servira pour lapprentissage des spams.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>password = "$2$b51na53x357bjrz5khmewri4o7um4s8i$mzegmbea3osb4yuzx93o4qzjonft8h87i9pxu9gwgyx9wkqqizab"
bind_socket = "127.0.0.1:11334";
bind_socket = "/var/run/rspamd/rspamd.sock mode=0666 owner=nobody";
</code></pre></div></div>
<p>3 metrics.conf</p>
<p>Fichier à créer. Il permet dindiquer vos valeurs pour les différentes actions.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>actions {
add_header = 5;
greylist = 25;
reject = 50;
}
</code></pre></div></div>
<p>A vous de bien régler ces paramètres. Ici, le reject est volontairement haut, dans un but de test et pour pouvoir le baisser au fur et à mesure.</p>
<p>4 milter_headers.conf</p>
<p>Ce fichier est à créer. Il indique dajouter des entêtes dans les mails. Grace à eux, vous pourrez voir directement dans votre logiciel ce qui a provoqué ou non le marquage en spam. Mettez simplement :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>extended_spam_headers = true;
</code></pre></div></div>
<p>5 rspamd_update.conf</p>
<p>Dernier fichier à créer. Permet à Rspamd de se mettre à jour automatiquement au niveau des règles. On lactive, tout simplement :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>enabled = true;
</code></pre></div></div>
<p>Et voila pour la configuration de Rspamd. Il existe dautres modules, mais nous en parlerons dans un futur article ou nous approfondirons le sujet sur Rspamd.</p>
<h3 id="liaison-avec-postfix">Liaison avec Postfix</h3>
<p>Pour dire à Postfix de passer le mail à Rspamd, cest le protocole milter que lon va utiliser en indiquant en bas de notre fichier <strong>/etc/postfix/main.cf</strong> :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>milter_protocol = 6
milter_default_action = accept
smtpd_milters = inet:localhost:11332
non_smtpd_milters=inet:localhost:11332
milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen}
</code></pre></div></div>
<p>Le milter_default_action spécifie laction par défaut au cas ou Rspamd ne serait pas disponible. On recharge :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>postfix reload
service rspamd restart
</code></pre></div></div>
<h3 id="apprentissage">Apprentissage</h3>
<p>Pour lutilisation de Rspamd au quotidien, pas grand chose à faire. Cest automatique, même pour les mises à jour. Cependant, si vous avez déjà un corpus de spam et de ham à lui apprendre, ce nest pas une mauvaise chose de commencer par la, le filtre bayésien ne fonctionnant pas à moins de 200 mails appris par catégorie. Pour se faire, jai toujours deux dossiers récents de spams et de hams que je garde sous le coude. Je les déplace dans ma bal dans deux répertoires. Puis pour le spam :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rspamc -h /var/run/rspamd/rspamd.sock learn_spam /var/vmail/xoyize.xyz/ym/mailbox/.Spamtolearn/
</code></pre></div></div>
<p>et pour le ham :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rspamc -h /var/run/rspamd/rspamd.sock learn_ham /var/vmail/xoyize.xyz/ym/mailbox/.Hamtolearn/
</code></pre></div></div>
<p>Pour regarder ensuite les statistiques et voir à combien on en est :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rspamc -h /var/run/rspamd/rspamd.sock stat
</code></pre></div></div>
<p>Pour un bon apprentissage, il faut lui apprendre des spams certes, mais également des hams, cest important.</p>
<h3 id="apprentissage-par-déplacement">Apprentissage par déplacement.</h3>
<p>Couplé avec Dovecot, Rspamd nous propose de pouvoir apprendre également en fonction des actions des utilisateurs. Si un mail est déplacé vers le répertoire spam, il sera appris comme tel et au contraire, sil est sorti du répertoire Spam vers autre chose que la corbeille, il sera appris comme Ham. Dans le fichier <strong>/etc/dovecot/conf.d/90-sieve-extprograms.conf</strong>, mettez cela :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>plugin {
sieve_plugins = sieve_imapsieve sieve_extprograms
imapsieve_mailbox1_name = Junk
imapsieve_mailbox1_causes = COPY
imapsieve_mailbox1_before = file:/etc/dovecot/sieve/report-spam.sieve
imapsieve_mailbox2_name = *
imapsieve_mailbox2_from = Junk
imapsieve_mailbox2_causes = COPY
imapsieve_mailbox2_before = file:/etc/dovecot/sieve/report-ham.sieve
sieve_pipe_bin_dir = /etc/dovecot/sieve
sieve_global_extensions = +vnd.dovecot.pipe
}
</code></pre></div></div>
<p>On recharge Dovecot :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dovecot reload
</code></pre></div></div>
<p>Quand un mail sera déplacé dans le répertoire Junk (Spam), le filtre report-spam.sieve sera appelé. Quand un mail sera déplacé depuis le répertoire Junk vers un autre répertoire (autre que la Corbeille), le filtre report-ham.sieve sera appelé. On va créer les filtre sieves. On créer un répertoire :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkdir /etc/dovecot/sieve/
</code></pre></div></div>
<p>Puis un fichier <strong>/etc/dovecot/sieve/report-ham.sieve</strong> :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];
if environment :matches "imap.email" "*" {
set "email" "${1}";
}
pipe :copy "train-spam.sh" [ "${email}" ];
</code></pre></div></div>
<p>puis, <strong>/etc/dovecot/sieve/report-spam.sieve</strong> :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];
if environment :matches "imap.mailbox" "*" {
set "mailbox" "${1}";
}
if string "${mailbox}" "Trash" {
stop;
}
if environment :matches "imap.email" "*" {
set "email" "${1}";
}
pipe :copy "train-ham.sh" [ "${email}" ];
</code></pre></div></div>
<p>On les compile :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sievec /etc/dovecot/sieve/report-ham.sieve
sievec /etc/dovecot/sieve/report-spam.sieve
</code></pre></div></div>
<p>Puis on change luser :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>chown vmail:vmail /etc/dovecot/sieve/report-*
</code></pre></div></div>
<p>On va passer à la création de nos deux petits scripts : <br />
Tout dabord</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/etc/dovecot/sieve/train-ham.sh
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>exec /usr/bin/rspamc -h /var/run/rspamd/rspamd.sock learn_ham
</code></pre></div></div>
<p>et</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/etc/dovecot/sieve/train-spam.sh
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>exec /usr/bin/rspamc -h /var/run/rspamd/rspamd.sock learn_spam
</code></pre></div></div>
<p>Et on leur donne les droits :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>chown vmail:vmail /etc/dovecot/sieve/train-*
chmod +x /etc/dovecot/sieve/train-*
</code></pre></div></div>
<p>On recharge Dovecot :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dovecot reload
</code></pre></div></div>
<p>Et voila, vous pouvez vérifier en déplaçant un spam ou un ham et voir dans les fichiers de logs Fichier /var/log/mail.log :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mail dovecot: imap(ym@xoyize.xyz): sieve: pipe action: piped message to program `train-spam.sh'
</code></pre></div></div>
<p>Fichier /var/log/rspamd/rspamd.log :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#9159(controller) &lt;e6896a&gt;; csession; rspamd_controller_check_password: allow unauthorized connection from a unix socket
#9159(controller) &lt;e6896a&gt;; csession; rspamd_message_parse: loaded message; id: &lt;004801d4dc01$02b7a725$5d7532ac$@lourd.com&gt;; queue-id: &lt;undef&gt;; size: 1732; checksum: &lt;b32bcf8d811d92610d2808ce822930dc&gt;
#9159(controller) &lt;e6896a&gt;; csession; rspamd_mime_text_part_utf8_convert: converted from IBM852 to UTF-8 inlen: 104, outlen: 104 (104 UTF16 chars)
#9159(controller) &lt;e6896a&gt;; csession; rspamd_controller_learn_fin_task: &lt;/var/run/rspamd/rspamd.sock&gt; learned message as spam: 004801d4dc01$02b7a725$5d7532ac$@lourd.com
</code></pre></div></div>
<h2 id="signature-dkim">Signature DKIM</h2>
<h3 id="configuration-4">Configuration</h3>
<p>On va tout dabord créer le répertoire qui va accueillir nos clés :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkdir /var/lib/rspamd/dkim
</code></pre></div></div>
<p>Créez un fichier <strong>/etc/rspamd/local.d/dkim_signing.conf</strong> et ajoutez :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>path = "/var/lib/rspamd/dkim/dkim.$domain.key";
allow_username_mismatch = true;
</code></pre></div></div>
<h3 id="signature-dun-domaine">Signature dun domaine</h3>
<p>Pour signer un nouveau domaine :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rspamadm dkim_keygen -b 2048 -s dkim -d xoyize.xyz -k /var/lib/rspamd/dkim/dkim.xoyize.xyz.key | tee -a /var/lib/rspamd/dkim/dkim.xoyize.xyz.pub
</code></pre></div></div>
<ul>
<li>-b indique que lon veut une clé de 2048 bits.</li>
<li>-s est le sélecteur.</li>
<li>-d indique le domaine.</li>
<li>-k pour dire ou lon veut stocker la clé privée.</li>
<li>et pour terminer, on enregistre la clé publique, qui par défaut est simplement affichée, dans un fichier.</li>
</ul>
<p>Gardez la clé publique sous le coude, on la rajoutera dans le DNS.</p>
<p>Pour info, il est tout a fait possible davoir des sélecteurs différents, soit en fonction des domaines, soit pour versionner, etc… Dans ce cas, il faudrait renseigner le fichier /etc/rspamd/local.d/dkim_signing.conf de la sorte :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>path = "/var/lib/rspamd/dkim/$selector.$domain.key";
selector_map = "/etc/rspamd/dkim_selectors.map";
</code></pre></div></div>
<p>Et le fichier /etc/rspamd/dkim_selectors.map devra contenir :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>domaine.fr selecteur
ouestline.net autre_selecteur
[...]
</code></pre></div></div>
<p>Puis changeons les droits :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>chmod u=rw,g=r,o= /var/lib/rspamd/dkim/*
chown _rspamd /var/lib/rspamd/dkim/*
</code></pre></div></div>
<p>On recharge :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>service rspamd reload
</code></pre></div></div>
<h3 id="interface-web">Interface Web</h3>
<p>Rspamd propose une interface Web. Avant de la tester, je pensais que cétait un gadget, mais après avoir vu la chose, force est de constater quelle est plutôt bien fichue et permet de voir lhistorique des messages, ce qui a provoqué ou non leur tag, rejet… On peut aussi y modifier les valeurs de metrics, les symboles (ce qui sert à détecter un spam par rapport à un critère donné). Bref, très pratique, je vous conseille de vous en servir. Cest déjà en écoute sur le port 11334, mais on ne va pas faire le goret et ouvrir cela sur le routeur. Servez vous dun reverse proxy (Nginx est juste parfait) et indiquez dans la configuration de votre serveur.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>location /rspamd/ {
proxy_pass http://ip.interne.mail:11334/;
proxy_http_version 1.1;
}
</code></pre></div></div>
<p>Pour sy connecter ensuite : https://www.xoyize.xyz/rspamd (par exemple) et le mot de passe est celui quon a renseigné grâce à lassistant.</p>
<p>Configurer Nginx pour linterface web de Rspamd</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nano /etc/nginx/conf.d/spam.xoyize.xyz.conf
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> ##
# Virtual Host spam.xoyize.xyz (Rspamd)
##
server {
listen 80;
listen [::]:80;
## redirect http to https ##
server_name spam.xoyize.xyz;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name spam.xoyize.xyz;
include ssl_dh_headers_ocsp;
location / {
proxy_pass http://127.0.0.1:11334/;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
</code></pre></div></div>
<p><strong>Conclusion</strong><br />
Voila, Rspamd est installé et opérationnel. La configuration reste simple, mais comme déjà dit, il existe dautres modules que lon peut utiliser. Ce sera pour un autre article, hors de la série sur le serveur de messagerie. La configuration présentée ici est bien suffisante dans un premier temps. En attendant, si vous voulez en savoir plus, la documentation officielle est ici : https://rspamd.com/doc/ On va passer à la finalisation de notre architecture mail avec, dans la partie VII, la mise en place des enregistrements SPF, DKIM et DMARC dans notre DNS.</p>
<h2 id="dkim-spf-et-dmarc">DKIM, SPF et DMARC</h2>
<p>Dans cette avant dernière partie de ma série darticles sur la mise en place dun serveur mail, (la dernière sera la conclusion), nous allons mettre le coup final à notre configuration en mettant à jour nos enregistrements DNS.</p>
<p>Dans lidéal, vous gérez vous même votre DNS, sinon, faites les modifications indiquées dans linterface de votre registar.</p>
<p>Ces trois enregistrements sappuient sur de simples champss TXT, afin que tous les resolvers les comprennent.</p>
<p>Il fut un temps ou on trouvait un champ SPF mais celui ci a été abandonné car pas vraiment de RFC pour cela. Il est conseillé maintenant de ne faire quun enregistrement TXT.</p>
<p>Bref, cest tout simple, mais cest vital, du moins, pour votre système de messagerie !</p>
<h3 id="spf">SPF</h3>
<p>Cet enregistrement va stipuler une ou plusieurs IP(s) que lont autorise à envoyer des mails en notre nom.</p>
<p>Dans votre fichier de zone, indiquez :</p>
<p>xoyize.xyz. IN TXT “v=spf1 a mx ip4:78.235.240.223 ip6:2a01:e34:eebf:df3::1 -all”</p>
<p>On indique daccepter les mails venant de notre IP et de refuser strictement les autres.</p>
<h3 id="dkim">DKIM</h3>
<p>Cet enregistrement indique la clé publique correspondant à la clé privée utilisée par Rspamd pour signer les mails.</p>
<p>Un serveur distant pourra alors vérifier lauthenticité du mail en vérifiant si la clé privée utilisée pour la signature correspond à la clé publique publiée dans le DNS.</p>
<p>Reprenez la clé publique que vous aviez gardé sous le coude dans la partie VI :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dkim._domainkey IN TXT ( "v=DKIM1;k=rsa;p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwCY7hcaoWyU0i7ydFiJFjnOG+Hk8UWMA+7y+RTL1BicoHm5KLwNhqO72VHW5wirk0WqEoTcNjnAIhvI1Ur07VfWh/CdACavzt2+DGKB3eiXPAbLhqI5Y72DiW1gaYUMVR2xmPtDA6aptrA/vCX5LGpyDPNP+YpTqch9eLBv/Gn9ciO3L+kgKLsDV6VyV5O" "6e5+n3PTsrXvjKJgHEDYp3QD1clLS9X9tw9Tb+BPV+qxc286niKcz5qgBhihN8GIKXSIQmf4SuBlffgAfX4Mgu9MaXbmyxryF4BGKP1ktLKuJOynla6QrkATNJmMy78ECUrUTZ8i0PVVgyeTQCkPmhAwIDAQAB;" )
</code></pre></div></div>
<p>Insérez tout cela dans votre fichier de zone, tout simplement.</p>
<h3 id="dmarc">DMARC</h3>
<p>Ce nest pas à proprement parlé un système de protection, mais plus une façon de dire ce quil faut faire avec des mails qui ne passe pas votre politique SPF et/ou DKIM.<br />
Une consigne pour le SMTP qui reçoit en quelque sorte. A ne pas négliger, car labsence de ce champ peut compromettre la livraison de vos mails.</p>
<p>Insérez une ligne semblable à la suivante :</p>
<p>_dmarc.xoyize.xyz. IN TXT “v=DMARC1; p=none; rua=mailto:postmaster@xoyize.xyz;ruf=mailto:postmaster@xoyize.xyz”</p>
<p>le p indique la politique à appliquer chez le SMTP si un mail reçu en votre nom (de domaine) ne passe ni SPF, ni DKIM.</p>
<p>Il est possible de spécifier :</p>
<ul>
<li>none, ne rien faire, mais juste le consigner dans les rapports envoyés à postmaster@xoyize.xyz</li>
<li>quarantine : mettre le mail en spam.</li>
<li>reject : le rejeter</li>
<li>rua indique à quelle adresse recevoir les reports agrégés</li>
<li>ruf indique où recevoir les reports détaillés (envoyés à chaque fois quun mail en votre nom est refusé)</li>
</ul>
<p>Pour plus dinformations : https://dmarc.org/wiki</p>
<p>Au début, disons le premier mois, utilisez un p=none et une fois quon voit que tout est OK (les mails refusés le sont à juste titre et non pas du à une boulette de votre côté), vous pouvez passer sur p=quarantine, ou p=reject.</p>
<h2 id="tests-avec-dig">Tests avec dig</h2>
<p>Pour tester, on va utiliser dig et au passage, linterrogation se fera auprès du DNS de Google pour être sur que la propagation DNS est bonne.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dig xoyize.xyz TXT @8.8.4.4 +short
</code></pre></div></div>
<p>Va vous renvoyer les enregistrements TXT, dont le SPF.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dig dkim._domainkey.xoyize.xyz TXT @8.8.4.4 +short
</code></pre></div></div>
<p>Va vous renvoyer votre DKIM.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dig _dmarc.xoyize.xyz TXT @8.8.4.4 +short
</code></pre></div></div>
<p>Bah, le Dmarc hein…</p>
<p>On peut aussi utiliser des outils en ligne : http://www.appmaildev.com, https://mxtoolbox.com, etc…</p>
<h2 id="dnsbl---liste-noire">DNSBL - liste noire</h2>
<p>Ensuite, il peut être utile de vérifier de temps à autres si vous nêtes pas en liste noire sur une des nombreuses DNSBL.</p>
<p>Il existe de nombreux sites ou script qui permettent de faire cette vérification.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>https://mxtoolbox.com/blacklists.aspx
http://multirbl.valli.org
http://www.anti-abuse.org/multi-rbl-check/
etc…
</code></pre></div></div>
<p>Si vous vous retrouvez listé, chaque liste gérant la chose à sa façon, à vous daller voir sur leur site pour vous faire délister.</p>
<h2 id="liens-1">Liens</h2>
<p>Voila quelques liens pour tester un peu votre serveur :</p>
<p>https://www.mail-tester.com : très pratique, permet de voir si vous nauriez pas louper une étape en vous donnant une note.</p>
<p>https://www.emailsecuritycheck.net : teste la protection de votre système en envoyant 7 mails douteux. Le 3 ne doit même pas arriver, les autres doivent se retrouver en spam.</p>
<p>Plus généralement, le site https://mxtoolbox.com est très complet car il couvre de nombreuses vérifications (et pas quau sujet de la messagerie).</p>
<h2 id="erreurs">ERREURS</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Oct 22 10:55:10 xoyize postfix/lmtp[15186]: E7AFA63841: to=&lt;ym@xoyize.fr&gt;, orig_to=&lt;root@xoyize.xyz&gt;, relay=none, delay=583, delays=583/0.03/0/0, dsn=4.4.1, status=deferred (connect to xoyize.xyz[private/dovecot-lmtp]: No such file or directory)
</code></pre></div></div>
<p>mv /var/spool/postfix/private/lmtp /var/spool/postfix/private/dovecot-lmtp</p>
<p>main.cf</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Configure Root CA certificates
# (for example, avoids getting "Untrusted TLS connection established to" messages in logs)
smtpd_tls_CAfile = /etc/ssl/certs/ca-certificates.crt
smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt
mydomain = cinay.xyz
mydestination = localhost
relayhost =
</code></pre></div></div>
</div>
<div class="d-print-none"><footer class="article__footer"><meta itemprop="dateModified" content="2019-10-21T00:00:00+02:00"><!-- start custom article footer snippet -->
<!-- end custom article footer snippet -->
<!--
<div align="right"><a type="application/rss+xml" href="/feed.xml" title="S'abonner"><i class="fa fa-rss fa-2x"></i></a>
&emsp;</div>
-->
</footer>
<div class="article__section-navigator clearfix"><div class="previous"><span>PRÉCÉDENT</span><a href="/2019/10/17/Linux-gestion-des-utilisateurs-et-permissions-serveur-web.html">Linux, gestion des utilisateurs ,permissions des fichiers et répertoires dun serveur web</a></div><div class="next"><span>SUIVANT</span><a href="/2019/12/11/Trousseau-de-Cles-Gnome-Keyring.html">XFCE/GNOME Porte-clés ou trousseau (gnome-keyring)</a></div></div></div>
</div>
<script>(function() {
var SOURCES = window.TEXT_VARIABLES.sources;
window.Lazyload.js(SOURCES.jquery, function() {
$(function() {
var $this ,$scroll;
var $articleContent = $('.js-article-content');
var hasSidebar = $('.js-page-root').hasClass('layout--page--sidebar');
var scroll = hasSidebar ? '.js-page-main' : 'html, body';
$scroll = $(scroll);
$articleContent.find('.highlight').each(function() {
$this = $(this);
$this.attr('data-lang', $this.find('code').attr('data-lang'));
});
$articleContent.find('h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]').each(function() {
$this = $(this);
$this.append($('<a class="anchor d-print-none" aria-hidden="true"></a>').html('<i class="fas fa-anchor"></i>'));
});
$articleContent.on('click', '.anchor', function() {
$scroll.scrollToAnchor('#' + $(this).parent().attr('id'), 400);
});
});
});
})();
</script>
</div><section class="page__comments d-print-none"></section></article><!-- start custom main bottom snippet -->
<!-- end custom main bottom snippet -->
</div>
</div></div></div></div>
</div><script>(function() {
var SOURCES = window.TEXT_VARIABLES.sources;
window.Lazyload.js(SOURCES.jquery, function() {
var $body = $('body'), $window = $(window);
var $pageRoot = $('.js-page-root'), $pageMain = $('.js-page-main');
var activeCount = 0;
function modal(options) {
var $root = this, visible, onChange, hideWhenWindowScroll = false;
var scrollTop;
function setOptions(options) {
var _options = options || {};
visible = _options.initialVisible === undefined ? false : show;
onChange = _options.onChange;
hideWhenWindowScroll = _options.hideWhenWindowScroll;
}
function init() {
setState(visible);
}
function setState(isShow) {
if (isShow === visible) {
return;
}
visible = isShow;
if (visible) {
activeCount++;
scrollTop = $(window).scrollTop() || $pageMain.scrollTop();
$root.addClass('modal--show');
$pageMain.scrollTop(scrollTop);
activeCount === 1 && ($pageRoot.addClass('show-modal'), $body.addClass('of-hidden'));
hideWhenWindowScroll && window.hasEvent('touchstart') && $window.on('scroll', hide);
$window.on('keyup', handleKeyup);
} else {
activeCount > 0 && activeCount--;
$root.removeClass('modal--show');
$window.scrollTop(scrollTop);
activeCount === 0 && ($pageRoot.removeClass('show-modal'), $body.removeClass('of-hidden'));
hideWhenWindowScroll && window.hasEvent('touchstart') && $window.off('scroll', hide);
$window.off('keyup', handleKeyup);
}
onChange && onChange(visible);
}
function show() {
setState(true);
}
function hide() {
setState(false);
}
function handleKeyup(e) {
// Char Code: 27 ESC
if (e.which === 27) {
hide();
}
}
setOptions(options);
init();
return {
show: show,
hide: hide,
$el: $root
};
}
$.fn.modal = modal;
});
})();
</script><div class="modal modal--overflow page__search-modal d-print-none js-page-search-modal"><script>
(function () {
var SOURCES = window.TEXT_VARIABLES.sources;
window.Lazyload.js(SOURCES.jquery, function() {
// search panel
var search = (window.search || (window.search = {}));
var useDefaultSearchBox = window.useDefaultSearchBox === undefined ?
true : window.useDefaultSearchBox ;
var $searchModal = $('.js-page-search-modal');
var $searchToggle = $('.js-search-toggle');
var searchModal = $searchModal.modal({ onChange: handleModalChange, hideWhenWindowScroll: true });
var modalVisible = false;
search.searchModal = searchModal;
var $searchBox = null;
var $searchInput = null;
var $searchClear = null;
function getModalVisible() {
return modalVisible;
}
search.getModalVisible = getModalVisible;
function handleModalChange(visible) {
modalVisible = visible;
if (visible) {
search.onShow && search.onShow();
useDefaultSearchBox && $searchInput[0] && $searchInput[0].focus();
} else {
search.onShow && search.onHide();
useDefaultSearchBox && $searchInput[0] && $searchInput[0].blur();
setTimeout(function() {
useDefaultSearchBox && ($searchInput.val(''), $searchBox.removeClass('not-empty'));
search.clear && search.clear();
window.pageAsideAffix && window.pageAsideAffix.refresh();
}, 400);
}
}
$searchToggle.on('click', function() {
modalVisible ? searchModal.hide() : searchModal.show();
});
// Char Code: 83 S, 191 /
$(window).on('keyup', function(e) {
if (!modalVisible && !window.isFormElement(e.target || e.srcElement) && (e.which === 83 || e.which === 191)) {
modalVisible || searchModal.show();
}
});
if (useDefaultSearchBox) {
$searchBox = $('.js-search-box');
$searchInput = $searchBox.children('input');
$searchClear = $searchBox.children('.js-icon-clear');
search.getSearchInput = function() {
return $searchInput.get(0);
};
search.getVal = function() {
return $searchInput.val();
};
search.setVal = function(val) {
$searchInput.val(val);
};
$searchInput.on('focus', function() {
$(this).addClass('focus');
});
$searchInput.on('blur', function() {
$(this).removeClass('focus');
});
$searchInput.on('input', window.throttle(function() {
var val = $(this).val();
if (val === '' || typeof val !== 'string') {
search.clear && search.clear();
} else {
$searchBox.addClass('not-empty');
search.onInputNotEmpty && search.onInputNotEmpty(val);
}
}, 400));
$searchClear.on('click', function() {
$searchInput.val(''); $searchBox.removeClass('not-empty');
search.clear && search.clear();
});
}
});
})();
</script><div class="search search--dark">
<div class="main">
<div class="search__header">Recherche</div>
<div class="search-bar">
<div class="search-box js-search-box">
<div class="search-box__icon-search"><i class="fas fa-search"></i></div>
<input id="search-input" type="text" />
<div class="search-box__icon-clear js-icon-clear">
<a><i class="fas fa-times"></i></a>
</div>
</div>
<button class="button button--theme-dark button--pill search__cancel js-search-toggle">
Annuler</button>
</div>
<div id="results-container" class="search-result js-search-result"></div>
</div>
</div>
<!-- Script pointing to search-script.js -->
<script>/*!
* Simple-Jekyll-Search
* Copyright 2015-2020, Christian Fei
* Licensed under the MIT License.
*/
(function(){
'use strict'
var _$Templater_7 = {
compile: compile,
setOptions: setOptions
}
const options = {}
options.pattern = /\{(.*?)\}/g
options.template = ''
options.middleware = function () {}
function setOptions (_options) {
options.pattern = _options.pattern || options.pattern
options.template = _options.template || options.template
if (typeof _options.middleware === 'function') {
options.middleware = _options.middleware
}
}
function compile (data) {
return options.template.replace(options.pattern, function (match, prop) {
const value = options.middleware(prop, data[prop], options.template)
if (typeof value !== 'undefined') {
return value
}
return data[prop] || match
})
}
'use strict';
function fuzzysearch (needle, haystack) {
var tlen = haystack.length;
var qlen = needle.length;
if (qlen > tlen) {
return false;
}
if (qlen === tlen) {
return needle === haystack;
}
outer: for (var i = 0, j = 0; i < qlen; i++) {
var nch = needle.charCodeAt(i);
while (j < tlen) {
if (haystack.charCodeAt(j++) === nch) {
continue outer;
}
}
return false;
}
return true;
}
var _$fuzzysearch_1 = fuzzysearch;
'use strict'
/* removed: const _$fuzzysearch_1 = require('fuzzysearch') */;
var _$FuzzySearchStrategy_5 = new FuzzySearchStrategy()
function FuzzySearchStrategy () {
this.matches = function (string, crit) {
return _$fuzzysearch_1(crit.toLowerCase(), string.toLowerCase())
}
}
'use strict'
var _$LiteralSearchStrategy_6 = new LiteralSearchStrategy()
function LiteralSearchStrategy () {
this.matches = function (str, crit) {
if (!str) return false
str = str.trim().toLowerCase()
crit = crit.trim().toLowerCase()
return crit.split(' ').filter(function (word) {
return str.indexOf(word) >= 0
}).length === crit.split(' ').length
}
}
'use strict'
var _$Repository_4 = {
put: put,
clear: clear,
search: search,
setOptions: __setOptions_4
}
/* removed: const _$FuzzySearchStrategy_5 = require('./SearchStrategies/FuzzySearchStrategy') */;
/* removed: const _$LiteralSearchStrategy_6 = require('./SearchStrategies/LiteralSearchStrategy') */;
function NoSort () {
return 0
}
const data = []
let opt = {}
opt.fuzzy = false
opt.limit = 10
opt.searchStrategy = opt.fuzzy ? _$FuzzySearchStrategy_5 : _$LiteralSearchStrategy_6
opt.sort = NoSort
opt.exclude = []
function put (data) {
if (isObject(data)) {
return addObject(data)
}
if (isArray(data)) {
return addArray(data)
}
return undefined
}
function clear () {
data.length = 0
return data
}
function isObject (obj) {
return Boolean(obj) && Object.prototype.toString.call(obj) === '[object Object]'
}
function isArray (obj) {
return Boolean(obj) && Object.prototype.toString.call(obj) === '[object Array]'
}
function addObject (_data) {
data.push(_data)
return data
}
function addArray (_data) {
const added = []
clear()
for (let i = 0, len = _data.length; i < len; i++) {
if (isObject(_data[i])) {
added.push(addObject(_data[i]))
}
}
return added
}
function search (crit) {
if (!crit) {
return []
}
return findMatches(data, crit, opt.searchStrategy, opt).sort(opt.sort)
}
function __setOptions_4 (_opt) {
opt = _opt || {}
opt.fuzzy = _opt.fuzzy || false
opt.limit = _opt.limit || 10
opt.searchStrategy = _opt.fuzzy ? _$FuzzySearchStrategy_5 : _$LiteralSearchStrategy_6
opt.sort = _opt.sort || NoSort
opt.exclude = _opt.exclude || []
}
function findMatches (data, crit, strategy, opt) {
const matches = []
for (let i = 0; i < data.length && matches.length < opt.limit; i++) {
const match = findMatchesInObject(data[i], crit, strategy, opt)
if (match) {
matches.push(match)
}
}
return matches
}
function findMatchesInObject (obj, crit, strategy, opt) {
for (const key in obj) {
if (!isExcluded(obj[key], opt.exclude) && strategy.matches(obj[key], crit)) {
return obj
}
}
}
function isExcluded (term, excludedTerms) {
for (let i = 0, len = excludedTerms.length; i < len; i++) {
const excludedTerm = excludedTerms[i]
if (new RegExp(excludedTerm).test(term)) {
return true
}
}
return false
}
/* globals ActiveXObject:false */
'use strict'
var _$JSONLoader_2 = {
load: load
}
function load (location, callback) {
const xhr = getXHR()
xhr.open('GET', location, true)
xhr.onreadystatechange = createStateChangeListener(xhr, callback)
xhr.send()
}
function createStateChangeListener (xhr, callback) {
return function () {
if (xhr.readyState === 4 && xhr.status === 200) {
try {
callback(null, JSON.parse(xhr.responseText))
} catch (err) {
callback(err, null)
}
}
}
}
function getXHR () {
return window.XMLHttpRequest ? new window.XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP')
}
'use strict'
var _$OptionsValidator_3 = function OptionsValidator (params) {
if (!validateParams(params)) {
throw new Error('-- OptionsValidator: required options missing')
}
if (!(this instanceof OptionsValidator)) {
return new OptionsValidator(params)
}
const requiredOptions = params.required
this.getRequiredOptions = function () {
return requiredOptions
}
this.validate = function (parameters) {
const errors = []
requiredOptions.forEach(function (requiredOptionName) {
if (typeof parameters[requiredOptionName] === 'undefined') {
errors.push(requiredOptionName)
}
})
return errors
}
function validateParams (params) {
if (!params) {
return false
}
return typeof params.required !== 'undefined' && params.required instanceof Array
}
}
'use strict'
var _$utils_9 = {
merge: merge,
isJSON: isJSON
}
function merge (defaultParams, mergeParams) {
const mergedOptions = {}
for (const option in defaultParams) {
mergedOptions[option] = defaultParams[option]
if (typeof mergeParams[option] !== 'undefined') {
mergedOptions[option] = mergeParams[option]
}
}
return mergedOptions
}
function isJSON (json) {
try {
if (json instanceof Object && JSON.parse(JSON.stringify(json))) {
return true
}
return false
} catch (err) {
return false
}
}
var _$src_8 = {};
(function (window) {
'use strict'
let options = {
searchInput: null,
resultsContainer: null,
json: [],
success: Function.prototype,
searchResultTemplate: '<li><a href="{url}" title="{desc}">{title}</a></li>',
templateMiddleware: Function.prototype,
sortMiddleware: function () {
return 0
},
noResultsText: 'No results found',
limit: 10,
fuzzy: false,
debounceTime: null,
exclude: []
}
let debounceTimerHandle
const debounce = function (func, delayMillis) {
if (delayMillis) {
clearTimeout(debounceTimerHandle)
debounceTimerHandle = setTimeout(func, delayMillis)
} else {
func.call()
}
}
const requiredOptions = ['searchInput', 'resultsContainer', 'json']
/* removed: const _$Templater_7 = require('./Templater') */;
/* removed: const _$Repository_4 = require('./Repository') */;
/* removed: const _$JSONLoader_2 = require('./JSONLoader') */;
const optionsValidator = _$OptionsValidator_3({
required: requiredOptions
})
/* removed: const _$utils_9 = require('./utils') */;
window.SimpleJekyllSearch = function (_options) {
const errors = optionsValidator.validate(_options)
if (errors.length > 0) {
throwError('You must specify the following required options: ' + requiredOptions)
}
options = _$utils_9.merge(options, _options)
_$Templater_7.setOptions({
template: options.searchResultTemplate,
middleware: options.templateMiddleware
})
_$Repository_4.setOptions({
fuzzy: options.fuzzy,
limit: options.limit,
sort: options.sortMiddleware,
exclude: options.exclude
})
if (_$utils_9.isJSON(options.json)) {
initWithJSON(options.json)
} else {
initWithURL(options.json)
}
const rv = {
search: search
}
typeof options.success === 'function' && options.success.call(rv)
return rv
}
function initWithJSON (json) {
_$Repository_4.put(json)
registerInput()
}
function initWithURL (url) {
_$JSONLoader_2.load(url, function (err, json) {
if (err) {
throwError('failed to get JSON (' + url + ')')
}
initWithJSON(json)
})
}
function emptyResultsContainer () {
options.resultsContainer.innerHTML = ''
}
function appendToResultsContainer (text) {
options.resultsContainer.innerHTML += text
}
function registerInput () {
options.searchInput.addEventListener('input', function (e) {
if (isWhitelistedKey(e.which)) {
emptyResultsContainer()
debounce(function () { search(e.target.value) }, options.debounceTime)
}
})
}
function search (query) {
if (isValidQuery(query)) {
emptyResultsContainer()
render(_$Repository_4.search(query), query)
}
}
function render (results, query) {
const len = results.length
if (len === 0) {
return appendToResultsContainer(options.noResultsText)
}
for (let i = 0; i < len; i++) {
results[i].query = query
appendToResultsContainer(_$Templater_7.compile(results[i]))
}
}
function isValidQuery (query) {
return query && query.length > 0
}
function isWhitelistedKey (key) {
return [13, 16, 20, 37, 38, 39, 40, 91].indexOf(key) === -1
}
function throwError (message) {
throw new Error('SimpleJekyllSearch --- ' + message)
}
})(window)
}());
</script>
<!-- Configuration -->
<script>
SimpleJekyllSearch({
searchInput: document.getElementById('search-input'),
resultsContainer: document.getElementById('results-container'),
noResultsText: '<p>Aucun résultat!</p>',
json: '/search.json',
searchResultTemplate: '<li><a href="{url}">{date}&nbsp;{title}</a>&nbsp;(Création {create})</li>'
})
</script>
</div></div>
<script>(function() {
var SOURCES = window.TEXT_VARIABLES.sources;
window.Lazyload.js(SOURCES.jquery, function() {
function scrollToAnchor(anchor, duration, callback) {
var $root = this;
$root.animate({ scrollTop: $(anchor).position().top }, duration, function() {
window.history.replaceState(null, '', window.location.href.split('#')[0] + anchor);
callback && callback();
});
}
$.fn.scrollToAnchor = scrollToAnchor;
});
})();
(function() {
var SOURCES = window.TEXT_VARIABLES.sources;
window.Lazyload.js(SOURCES.jquery, function() {
function affix(options) {
var $root = this, $window = $(window), $scrollTarget, $scroll,
offsetBottom = 0, scrollTarget = window, scroll = window.document, disabled = false, isOverallScroller = true,
rootTop, rootLeft, rootHeight, scrollBottom, rootBottomTop,
hasInit = false, curState;
function setOptions(options) {
var _options = options || {};
_options.offsetBottom && (offsetBottom = _options.offsetBottom);
_options.scrollTarget && (scrollTarget = _options.scrollTarget);
_options.scroll && (scroll = _options.scroll);
_options.disabled !== undefined && (disabled = _options.disabled);
$scrollTarget = $(scrollTarget);
isOverallScroller = window.isOverallScroller($scrollTarget[0]);
$scroll = $(scroll);
}
function preCalc() {
top();
rootHeight = $root.outerHeight();
rootTop = $root.offset().top + (isOverallScroller ? 0 : $scrollTarget.scrollTop());
rootLeft = $root.offset().left;
}
function calc(needPreCalc) {
needPreCalc && preCalc();
scrollBottom = $scroll.outerHeight() - offsetBottom - rootHeight;
rootBottomTop = scrollBottom - rootTop;
}
function top() {
if (curState !== 'top') {
$root.removeClass('fixed').css({
left: 0,
top: 0
});
curState = 'top';
}
}
function fixed() {
if (curState !== 'fixed') {
$root.addClass('fixed').css({
left: rootLeft + 'px',
top: 0
});
curState = 'fixed';
}
}
function bottom() {
if (curState !== 'bottom') {
$root.removeClass('fixed').css({
left: 0,
top: rootBottomTop + 'px'
});
curState = 'bottom';
}
}
function setState() {
var scrollTop = $scrollTarget.scrollTop();
if (scrollTop >= rootTop && scrollTop <= scrollBottom) {
fixed();
} else if (scrollTop < rootTop) {
top();
} else {
bottom();
}
}
function init() {
if(!hasInit) {
var interval, timeout;
calc(true); setState();
// run calc every 100 millisecond
interval = setInterval(function() {
calc();
}, 100);
timeout = setTimeout(function() {
clearInterval(interval);
}, 45000);
window.pageLoad.then(function() {
setTimeout(function() {
clearInterval(interval);
clearTimeout(timeout);
}, 3000);
});
$scrollTarget.on('scroll', function() {
disabled || setState();
});
$window.on('resize', function() {
disabled || (calc(true), setState());
});
hasInit = true;
}
}
setOptions(options);
if (!disabled) {
init();
}
$window.on('resize', window.throttle(function() {
init();
}, 200));
return {
setOptions: setOptions,
refresh: function() {
calc(true, { animation: false }); setState();
}
};
}
$.fn.affix = affix;
});
})();
(function() {
var SOURCES = window.TEXT_VARIABLES.sources;
window.Lazyload.js(SOURCES.jquery, function() {
function toc(options) {
var $root = this, $window = $(window), $scrollTarget, $scroller, $tocUl = $('<ul class="toc toc--ellipsis"></ul>'), $tocLi, $headings, $activeLast, $activeCur,
selectors = 'h1,h2,h3', container = 'body', scrollTarget = window, scroller = 'html, body', disabled = false,
headingsPos, scrolling = false, hasRendered = false, hasInit = false;
function setOptions(options) {
var _options = options || {};
_options.selectors && (selectors = _options.selectors);
_options.container && (container = _options.container);
_options.scrollTarget && (scrollTarget = _options.scrollTarget);
_options.scroller && (scroller = _options.scroller);
_options.disabled !== undefined && (disabled = _options.disabled);
$headings = $(container).find(selectors).filter('[id]');
$scrollTarget = $(scrollTarget);
$scroller = $(scroller);
}
function calc() {
headingsPos = [];
$headings.each(function() {
headingsPos.push(Math.floor($(this).position().top));
});
}
function setState(element, disabled) {
var scrollTop = $scrollTarget.scrollTop(), i;
if (disabled || !headingsPos || headingsPos.length < 1) { return; }
if (element) {
$activeCur = element;
} else {
for (i = 0; i < headingsPos.length; i++) {
if (scrollTop >= headingsPos[i]) {
$activeCur = $tocLi.eq(i);
} else {
$activeCur || ($activeCur = $tocLi.eq(i));
break;
}
}
}
$activeLast && $activeLast.removeClass('active');
($activeLast = $activeCur).addClass('active');
}
function render() {
if(!hasRendered) {
$root.append($tocUl);
$headings.each(function() {
var $this = $(this);
$tocUl.append($('<li></li>').addClass('toc-' + $this.prop('tagName').toLowerCase())
.append($('<a></a>').text($this.text()).attr('href', '#' + $this.prop('id'))));
});
$tocLi = $tocUl.children('li');
$tocUl.on('click', 'a', function(e) {
e.preventDefault();
var $this = $(this);
scrolling = true;
setState($this.parent());
$scroller.scrollToAnchor($this.attr('href'), 400, function() {
scrolling = false;
});
});
}
hasRendered = true;
}
function init() {
var interval, timeout;
if(!hasInit) {
render(); calc(); setState(null, scrolling);
// run calc every 100 millisecond
interval = setInterval(function() {
calc();
}, 100);
timeout = setTimeout(function() {
clearInterval(interval);
}, 45000);
window.pageLoad.then(function() {
setTimeout(function() {
clearInterval(interval);
clearTimeout(timeout);
}, 3000);
});
$scrollTarget.on('scroll', function() {
disabled || setState(null, scrolling);
});
$window.on('resize', window.throttle(function() {
if (!disabled) {
render(); calc(); setState(null, scrolling);
}
}, 100));
}
hasInit = true;
}
setOptions(options);
if (!disabled) {
init();
}
$window.on('resize', window.throttle(function() {
init();
}, 200));
return {
setOptions: setOptions
};
}
$.fn.toc = toc;
});
})();
/*(function () {
})();*/
</script><script>
/* toc must before affix, since affix need to konw toc' height. */(function() {
var SOURCES = window.TEXT_VARIABLES.sources;
var TOC_SELECTOR = window.TEXT_VARIABLES.site.toc.selectors;
window.Lazyload.js(SOURCES.jquery, function() {
var $window = $(window);
var $articleContent = $('.js-article-content');
var $tocRoot = $('.js-toc-root'), $col2 = $('.js-col-aside');
var toc;
var tocDisabled = false;
var hasSidebar = $('.js-page-root').hasClass('layout--page--sidebar');
var hasToc = $articleContent.find(TOC_SELECTOR).length > 0;
function disabled() {
return $col2.css('display') === 'none' || !hasToc;
}
tocDisabled = disabled();
toc = $tocRoot.toc({
selectors: TOC_SELECTOR,
container: $articleContent,
scrollTarget: hasSidebar ? '.js-page-main' : null,
scroller: hasSidebar ? '.js-page-main' : null,
disabled: tocDisabled
});
$window.on('resize', window.throttle(function() {
tocDisabled = disabled();
toc && toc.setOptions({
disabled: tocDisabled
});
}, 100));
});
})();
(function() {
var SOURCES = window.TEXT_VARIABLES.sources;
window.Lazyload.js(SOURCES.jquery, function() {
var $window = $(window), $pageFooter = $('.js-page-footer');
var $pageAside = $('.js-page-aside');
var affix;
var tocDisabled = false;
var hasSidebar = $('.js-page-root').hasClass('layout--page--sidebar');
affix = $pageAside.affix({
offsetBottom: $pageFooter.outerHeight(),
scrollTarget: hasSidebar ? '.js-page-main' : null,
scroller: hasSidebar ? '.js-page-main' : null,
scroll: hasSidebar ? $('.js-page-main').children() : null,
disabled: tocDisabled
});
$window.on('resize', window.throttle(function() {
affix && affix.setOptions({
disabled: tocDisabled
});
}, 100));
window.pageAsideAffix = affix;
});
})();
</script><!---->
</div>
<script>(function () {
var $root = document.getElementsByClassName('root')[0];
if (window.hasEvent('touchstart')) {
$root.dataset.isTouch = true;
document.addEventListener('touchstart', function(){}, false);
}
})();
</script>
</body>
</html>