«Java Script» и «Почтовый сервер на Debian 9 полная установка: dbmail & postgresql & postfix & stunnel & postgrey& spamassassin»: разница между страницами

Материал из support.qbpro.ru
(Различия между страницами)
imported>Supportadmin
 
imported>Vix
Нет описания правки
 
Строка 1: Строка 1:
==Как бы учебник==
'''Руководство для быстрого развертывания собственного сервера почты.'''<br>
*[[Операторы JavaScript]]
* ''Данная статья появилась тут в связи с тем, что я столкнулся с проблемой переноса почтового сервера на обычной файловой системе.''
*[[Массивы JavaScript]]
''В первую очередь с тем, что почта была организована на уже устаревшем ПО и перенос ее на новую платформу без потерь стал практически не возможен.
*[[Объекты JavaScript]]
А вот хранение почты в базе данных, дает огромные преимущества при обновлении или доступе к информации, а так же восстановлении. В частности у меня база данных находится на другом хосте, что сильно облегчает ее обслуживание, при этом все конфигурационные файлы самой почты можно легко повторить если понадобится на новом хосте для создания почтового сервера заново.''<br>
*[[Особенности функций в JavaScript]]
**[[Возвратные функции]]
*<span style="color:darkgred; background:yellow">[[Базовые Namespace паттерны JavaScript]]</span>
*<span style="color:darkgred; background:yellow">[[Шаблоны проектирования JavaScript]]</span>
*[[ООП JavaScript - наследование]]
*[[Область видимости переменной в Javascript]]
*[[Клиентский (браузерный) JavaScript]]
**[[Javascript синхронный и асинхронный запрос]]
*[[JavaScript Strict Mode]]


----
=='''1. Порядок установки dbmail'''==
*[[Масштабируемые JavaScript приложения]]
* '''''Система Debian Stretch {9}'''''
*[[10 несуразностей и секретов JavaScript]]
* Используемый source.list
*[[Используем console на полную]]
#
----
deb http://mirror.mephi.ru/debian/ stretch main
*[[Java Script модули]]
deb-src http://mirror.mephi.ru/debian/ stretch main
deb http://security.debian.org/debian-security stretch/updates main
deb-src http://security.debian.org/debian-security stretch/updates main
# stretch-updates, previously known as 'volatile'
deb http://mirror.mephi.ru/debian/ stretch-updates main
deb-src http://mirror.mephi.ru/debian/ stretch-updates main
###### Debian Main Repos
deb http://deb.debian.org/debian/ stable main contrib non-free
deb-src http://deb.debian.org/debian/ stable main contrib non-free
deb http://deb.debian.org/debian/ stable-updates main contrib non-free
deb-src http://deb.debian.org/debian/ stable-updates main contrib non-free
deb http://deb.debian.org/debian-security stable/updates main contrib non-free
deb-src http://deb.debian.org/debian-security stable/updates main contrib non-free
deb http://ftp.debian.org/debian stretch-backports main contrib non-free
deb-src http://ftp.debian.org/debian stretch-backports main contrib non-free
1.1 ''Устанавливаем необходимые пакеты:''
apt-get install pkg-config libglib2.0-dev libgmime-2.6-dev libmhash-dev libevent-dev libssl-dev libzdb-dev\
autoconf automake libtool autotools-dev dpkg-dev fakeroot debhelper dh-make libldap2-dev libsieve2-dev ascidoc\
libcrypto++6 libcrypto++-utils libcrypto++-dev xmlto xmltoman libarchive-tools lrzip binutils-multiarch\
arch-test libpgf-dev libsasl2-modules-db libsasl2-modules curl libcroco3 libsasl2-2 procmail libsasl2-modules-sql\
libpcre32-3 zlib1g-dev libmhash-dev libpcrecpp0v5


==Крайне полезная информация==
1.2 ''Скачиваем с [http://www.dbmail.org/index.php?page=download dbmail.org] исходники:''
wget -c -t 0 -T 8 http://www.dbmail.org/download/3.1/dbmail-3.1.17.tar.gz


Это надо обработать
1.3 ''Распаковываем и компилируем:''
*[http://jsperf.com/array-prototype-push-apply-vs-concat/20 Тесты производительности циклов, работы с массивами и т.д.]
cp dbmail-3.1.17.tar.gz /usr/local/src
*'''[http://bonsaiden.github.com/JavaScript-Garden/ru/ JavaScript Гарден]'''
tar -xf dbmail-3.1.17.tar.gz /usr/local/src.dbmail-3.1.7
*[http://habrahabr.ru/post/117069/ Модульный подход в JavaScript]
cp dbmail-3.1.17.tar.gz /usr/local/src/dbmail_3.1.7.orig.tar.gz
*[http://canegor.urc.ac.ru/gui_for_script/articles/random_sort.htm Случайное перемешивание массива]
* '''[!]''' - ''не знаю, может так у меня получилось, но когда применяешь комменты, версия которая высвечивается именно'' '''3.1.7'''!!
*[http://habrahabr.ru/post/131714/ Javascript наследование для чайников]
* '''[!]''' - ''именно поэтому все, что тут распаковываем и создаем имеет версию'' - 3.1.7 ...
*[http://javascript.ru/basic/operators Операторы, их особенности в JS]
*[http://www.w3schools.com/js/js_comparisons.asp JavaScript Comparison and Logical Operators]
*'''[http://habrahabr.ru/post/138773/  Путь асинхронного самурая]'''
*[http://habrahabr.ru/post/137818/ «Лапша» из callback-ов — будьте проще]
*[http://habrahabr.ru/post/97042/ Как избавиться от пристрастия к синхронности]
*[http://habrahabr.ru/post/137778/ Спагетти в последовательном вызове асинхронных функций. Теория и практика]
*'''[http://hashcode.ru/questions/87809/javascript-xmlhttprequest-%D0%B2-%D1%86%D0%B8%D0%BA%D0%BB%D0%B5 javascript - XMLHttpRequest в цикле]'''  
*[[javascript - XMLHttpRequest подробный анализ примера]]
*[http://javascript.ru/blog/tenshi/mnogopotochnyi-yavaskript Многопоточный яваскрипт]
* [http://javascript.ru/tutorial/object/inheritance работаем с объектами в примерах]
* '''[http://javascript.ru/unsorted/top-10-functions 10 лучших функций на JavaScript]''' - в комментах тьма полезностей
* [http://webo.in/articles/habrahabr/28-chain-javascript-calls-optimization/ ОПТИМИЗИРУЕМ JS]
* http://pyramidin.narod.ru/clientref13/ -очень хорошо описаны функции, их стандартные методы, свойства и использование
* [http://wiki.dieg.info/doku.php/pobitovye_ili_porazrjadnye_operacii Побитовые или поразрядные операции] в конце для js
* [http://ruseller.com/lessons.php?rub=28&id=1445 Прототипы в JavaScript]
* [http://habrahabr.ru/post/49245/ Стыкуем компоненты в JavaScript]
* [http://webo.in/articles/habrahabr/77-coupling-async-scripts/ Стыкуем асинхронные скрипты]
* [http://learn.javascript.ru/gcc-closure-library GCC: интеграция с Google Closure Library]
* [http://creativejs.com/resources/requestanimationframe/ 3d -javascipt]
* [http://stepansuvorov.com/blog/2012/07/%D0%BF%D0%B8%D1%88%D0%B5%D0%BC-%D1%81%D0%B2%D0%BE%D0%B9-uploader-%D1%81-%D0%BD%D1%83%D0%BB%D1%8F-%D0%BD%D0%B0-javascript-%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D1%83%D1%8F-fileapi-%D1%87%D0%B0-3/ свой Uploader с нуля]
* [http://alljs.ru/articles/document-write document-write]
* [http://www.jaguar-developers.com/category/articles/javascript_dom/ создаем элементы DOM]
* [http://www.jstoolbox.com/skripty/rabota-s-massivami/zapolnenie-massiva-odinakovymi-znacheniyami/ Заполнение массива одинаковыми значениями]
* [http://webo.in/articles/habrahabr/78-javascript-constructions-performance/ Производительность простых и сложных конструкций в JavaScript]


==обработка событий в примерах==
''Готовим пакет к сборке:''
*[http://on-line-teaching.com/js/js.events.keyboard.htm События клавиатуры и мыши]
cd /usr/local/src/dbmail-3.1.7
*[http://javascript.ru/tutorial/events/properties  Свойства объекта событие]
./configure --prefix=/usr
*[http://www.tigir.com/javascript.htm разные примеры...]
 
*[https://developer.mozilla.org/ru/docs/JavaScript/Guide/Regular_Expressions регулярные выражения - шаблоны]
dpkg-source --commit
==Контекст==
даем имя, что-то: '''pgsql.commit'''<br>
*[http://habrahabr.ru/post/149516/ Ключевое слово this в javascript — учимся определять контекст на практике]
выходим по '''ESC'''<br>
*[http://habrahabr.ru/post/149581/ Привязка контекста (this) к функции в javascript и частичное применение функций]
должно быть так:<br>
*[http://habrahabr.ru/post/103760/ Правильный захват контекста в Javascript]
...
*[http://habrahabr.ru/qa/5846/ JavaScript: что делает Function.call.apply(…)?]
dpkg-source: инфо: локальные изменения были записаны в новую заплату: dbmail-3.1.7/debian/patches/pgsql.commit
*[http://ru.wikipedia.org/wiki/%D0%A1%D1%82%D1%80%D0%B0%D1%82%D0%B5%D0%B3%D0%B8%D1%8F_(%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F) Стратегия (шаблон проектирования)]
[http://javascript.ru/ecma/part10 Контексты исполнения]
*[http://habrahabr.ru/post/112069/ предсказание элемента формы]


==Библиотеки==
далее:
*[http://qooxdoo.org/ JavaScript-фреймворк qooxdoo 1.6] - новое слово в вебинтерфейсах
cd /usr/local/src/
:*[http://www.opennet.ru/opennews/art.shtml?num=32573 Новость про qooxdoo 1.6]
dpkg-source -b dbmail-3.1.7
*[http://www.leemon.com/crypto/BigInt.js JS библиотека для работы с большими числами][http://www.leemon.com/crypto/BigInt.html Демо]


cd /usr/local/src/dbmail-3.1.7
dpkg-buildpackage -d


*[http://learn.javascript.ru/lib Мини-библиотека функций]
* '''[!]''' - если у вас появилось сообщение типа:
===Несколько интересных проектов с помощью которых можно динамически создавать векторные рисунки.===
...
* [http://y3x.ru/2010/12/artisanjs/ Статья - Пробуем ремесло рисования с помощью ArtisanJS]
debian/rules:138: *** missing separator (did you mean TAB instead of 8 spaces?). Останов.
* [http://artisanjs.com/ Страница разработчика ArtisanJS]
dpkg-buildpackage: ошибка: debian/rules clean возвратил код ошибки 2
* [http://jcscript.com/ Высокопроизводительная библиотека динамического рисования Html5 - полностью самодостаточная]
* [http://raphaeljs.com Кросс-браузерное решение]
* [http://kangax.github.com/fabric.js/kitchensink/ Поддерживается как свободное рисование так и работа с векторными формами]


==Полезные трюки==
* '''[!]''' - то необходимо исправить ошибку в файле '''dbmail-3.1.7/debian/rules'''
http://habrahabr.ru/post/155093/
строка 138:  
'''''........make -f debian/rules binary-common $* DH_OPTIONS=-p$*'''''
      ^^^
    здесь 8 пробелов!! - а должно быть 2 табуляции, что и вызывает ошибку...


==Как работает Array.prototype.slice.call==
* после того как соберется пакет, дожно быть так:
Для того, чтобы из аргументов JavaScript функции "отрезать" первые Х значений, используется метод:
# ls -n /usr/local/src
итого 3668
drwxrwxr-x 13 0  0    4096 ноя  2 00:19 dbmail-3.1.7
-rw-r--r--  1 0 50    7597 ноя  2 00:19 dbmail_3.1.7-1_amd64.buildinfo
-rw-r--r--  1 0 50    1957 ноя  2 00:19 dbmail_3.1.7-1_amd64.changes
-rw-r--r--  1 0 50  349256 ноя  2 00:19 dbmail_3.1.7-1_amd64.deb
-rw-r--r--  1 0 50  148008 ноя  2 00:14 dbmail_3.1.7-1.debian.tar.xz
-rw-r--r--  1 0 50    1045 ноя  2 00:14 dbmail_3.1.7-1.dsc
-rw-r--r--  1 0  0 2391054 июл 27  2014 dbmail_3.1.7.orig.tar.gz
-rw-r--r--  1 0 50  838508 ноя  2 00:19 dbmail-dbgsym_3.1.7-1_amd64.deb


Array.prototype.slice.call(arguments, X);
* копируем себе в архив и ставим пакет.
dpkg -i dbmail_3.1.7-1_amd64.deb


Например такая функция вернет "3,4":
* правим файл конфигурации:
editor /etc/dbmail/dbmail.conf


<nowiki>(function(){
* пример рабочего конфигурационного файла:
  var args = Array.prototype.slice.call(arguments, 2);
  alert(args); // Returns: 3,4
})(1, 2, 3, 4);</nowiki>


# (c) 2000-2006 IC&S, The Netherlands
#
# Configuration file for DBMAIL
[DBMAIL]
#
# Database settings
#
# database connection URI
'''#dburi                = sqlite:///var/tmp/dbmail.db'''
'''dburi                = postgresql://dbmail:dbmailpass@10.0.5.2:5432/mailbasename'''
#
# Supported drivers are sql, ldap.
#
'''authdriver          = sql'''
#
#
# following fields are now DEPRECATED!
'''driver              = postgresql'''
'''host                = 10.0.5.2'''
'''sqlport              = 5432'''
'''#sqlsocket            ='''             
'''user                = dbmail'''
'''pass                = dbmailpass'''
'''db                  = mailbasename'''
#
# Number of database connections per threaded daemon
# This also determines the size of the worker threadpool
#
# Do NOT increase this without proper consideration. A
# very large database/worker pool will not only increase
# the connection pressure on the database, but will more
# significantly cause unnecessary context-switching in
# your CPUs.
#
#max_db_connections  = 10
#
# Table prefix. Defaults to "dbmail_" if not specified.
#
'''table_prefix        = dbmail_''' 
#
# encoding must match the database/table encoding.
# i.e. latin1, utf8
encoding            = utf8
#
# messages with unknown encoding will be assumed to have
# default_msg_encoding
# i.e. iso8859-1, utf8
default_msg_encoding = utf8
#
# Postmaster's email address for use in bounce messages.
#
#postmaster          = DBMAIL-MAILER     
#
# Sendmail executable for forwards, replies, notifies, vacations.
# You may use pipes (|) in this command, for example:
# dos2unix|/usr/sbin/sendmail  works well with Qmail.
# You may use quotes (") for executables with unusual names.
#
sendmail              = /usr/sbin/sendmail   
#
#
# The following items can be overridden in the service-specific sections.
#
#
#
# Logging via stderr/log file and syslog
#
# Logging is broken up into 8 logging levels and each level can be indivually turned on or off.
# The Stderr/log file logs all entries to stderr or the log file.
# Syslog logging uses the facility mail and the logging level of the event for logging.
# Syslog can then be configured to log data according to the levels.
#
# Set the log level to the sum of the values next to the levels you want to record.
#  1 = Emergency
#  2 = Alert
#  4 = Critical
#  8 = Error
#  16 = Warning
#  32 = Notice
#  64 = Info
# 128 = Debug
# 256 = Database -> Logs at debug level
#
# Examples:  0 = Nothing
#            31 = Emergency + Alert + Critical + Error + Warning
#          511 = Everything
#
file_logging_levels      = 7
#
syslog_logging_levels    = 31
#
# Generate a log entry for database queries for the log level at number of seconds of query execution time.
#
query_time_info      = 10
query_time_notice    = 20
query_time_warning    = 30
#
# Throw an exception is the query takes longer than query_timeout seconds
query_timeout        = 300
#
# Root privs are used to open a port, then privs
# are dropped down to the user/group specified here.
#
'''effective_user        = dbmail'''
'''effective_group      = mail'''
#
# The IPv4 and/or IPv6 addresses the services will bind to.
# Use * for all local interfaces.
# Use 127.0.0.1 for localhost only.
# Separate multiple entries with spaces ( ) or commas (,).
#
'''bindip                = 0.0.0.0        # IPv4 only - all IP's'''
#bindip                = ::            # IPv4 and IPv6 - all IP's (linux)
#bindip                = ::            # IPv6 only - all IP's (BSD)
#bindip                = 0.0.0.0,::    # IPv4 and IPv6 - all IP's (BSD)
#
# The maximum length of the queue of pending connections. See
# listen(2) for more information
#
# backlog              = 128
#
# Idle time allowed before a connection is shut off.
#
timeout              = 300           
#
# Idle time allowed before a connection is shut off if you have not logged in yet.
#
login_timeout        = 60
#
# If yes, resolves IP addresses to DNS names when logging.
#
resolve_ip            = yes
#
# If yes, keep statistics in the authlog table for connecting users
#
authlog              = no
#
# logfile for stdout messages
#
logfile              = /var/log/dbmail.log       
#
# logfile for stderr messages
#
errorlog              = /var/log/dbmail.err       
#
# directory for storing PID files
#
pid_directory        = /var/run/dbmail
#
# directory for locating libraries (normally has a sane default compiled-in)
#
library_directory      = /usr/lib/dbmail
#
# SSL/TLS certificates
#
# A file containing a list of CAs in PEM format
tls_cafile            =
# A file containing a PEM format certificate
tls_cert              =
# A file containing a PEM format RSA or DSA key
tls_key              =
# A cipher list string in the format given in ciphers(1)
tls_ciphers          =
# hashing algorithm. You can select your favorite hash type
# for generating unique ids for message parts.
#
# for valid values check mhash(3) but minus the MHASH_ prefix.
# if you ever change this value run 'dbmail-util --rehash' to
# update the hash for all mimeparts.
#
# examples: MD5, SHA1, SHA256, SHA512, TIGER, WHIRLPOOL
#
# hash_algorithm = SHA1
# header_cache tuning
#
# set header_cache_readonly to 'yes' to prevent new
# unknown header-names from being cached.
#
# header_cache_readonly = yes
[LMTP]
'''bindip = 127.0.0.1'''
port                  = 24               
#tls_port              =
[POP]
port                  = 110
#tls_port              = 995
# You can set an alternate banner to display when connecting to the service
# banner = DBMAIL pop3 server ready to rock
#
# If yes, allows SMTP access from the host IP connecting by POP3.
# This requires addition configuration of your MTA
#
pop_before_smtp      = no     
[HTTP]
port                  = 41380
#
# the httpd daemon provides full access to all users, mailboxes
# and messages. Be very careful with this one!
'''bindip                = 127.0.0.1'''
admin                = admin:secret
[IMAP]
# You can set an alternate banner to display when connecting to the service
# banner = imap 4r1 server (dbmail 2.3.x)
#
# Port to bind to.
#
port                  = 143               
##tls_port              = 993
#
# IMAP prefers a longer timeout than other services.
#
timeout              = 4000           
#
# If yes, allows SMTP access from the host IP connecting by IMAP.
# This requires addition configuration of your MTA
#
imap_before_smtp      = no
#
# during IDLE, how many seconds between checking the mailbox
# status (default: 30)
#
# idle_timeout          = 30
# during IDLE, how often should the server send an '* OK' still
# here message (default: 10)
#
# the time between such a message is idle_timeout * idle_interval
# seconds
#
# idle_interval        = 10
#
# If TLS is enabled, login before starttls is normally
# not allowed. Use login_disabled=no to change this
#
# login_disabled        = yes
#
# Provide a CAPABILITY to override the default
#
# capability  = IMAP4 IMAP4rev1 AUTH=LOGIN ACL RIGHTS=texk NAMESPACE CHILDREN SORT QUOTA THREAD=ORDEREDSUBJECT UNSELECT IDLE
# max message size. You can specify the maximum message size
# accepted by the IMAP daemon during APPEND commands.
#
# Supported formats:
#  decimal: 1000000   
#  octal:  03777777
#  hex:    0xfffff
#
# max_message_size      =
[SIEVE]
#
# Port to bind to.
#
port                  = 2000             
tls_port              =
[LDAP]
port                  = 389
version              = 3
hostname              = ldap
base_dn              = ou=People,dc=mydomain,dc=com
#
# If your LDAP library supports ldap_initialize(), then you can use the
# alternative LDAP server DSN like following.
#
# URI                = ldap://127.0.0.1:389
# URI                = ldapi://%2fvar%2frun%2fopenldap%2fldapi/
#
# Leave blank for anonymous bind.
# example: cn=admin,dc=mydomain,dc=com   
#
bind_dn              =
#
# Leave blank for anonymous bind.
#
bind_pw              =
scope                = SubTree
# AD users may want to set this to 'no' to disable
# ldap referrals if you are seeing 'Operations errors'
# in your logs
#
referrals            = yes
user_objectclass      = top,account,dbmailUser
forw_objectclass      = top,account,dbmailForwardingAddress
cn_string            = uid
field_passwd          = userPassword
field_uid            = uid
field_nid            = uidNumber
min_nid              = 10000
max_nid              = 15000
field_cid            = gidNumber
min_cid              = 10000
max_cid              = 15000
# a comma-separated list of attributes to match when searching
# for users or forwards that match a delivery address. A match
# on any of them is a hit.
field_mail            = mail
# field that holds the mail-quota size for a user.
field_quota          = mailQuota
# field that holds the forwarding address.
field_fwdtarget      = mailForwardingAddress
# override the query string used to search for users
# or forwards with a delivery address.
# query_string          = (mail=%s)
[DELIVERY]
#
# Run Sieve scripts as messages are delivered.
#
SIEVE                = yes             
#
# Use 'user+mailbox@domain' format to deliver to a mailbox.
#
SUBADDRESS            = yes         
#
# Turn on/off the Sieve Vacation extension.
#
SIEVE_VACATION        = yes     
#
# Turn on/off the Sieve Notify extension
#
SIEVE_NOTIFY          = yes
#
# Turn on/off additional Sieve debugging.
#
SIEVE_DEBUG          = no         
# Use the auto_notify table to send email notifications.
#
AUTO_NOTIFY          = no
 
#
# Use the auto_reply table to send away messages.
#
AUTO_REPLY            = no
#
# Defaults to "NEW MAIL NOTIFICATION"
#
#AUTO_NOTIFY_SUBJECT        =   
#
# Defaults to POSTMASTER from the DBMAIL section.
#
#AUTO_NOTIFY_SENDER        = 
# If you set this to 'yes' dbmail will check for duplicate
# messages in the relevant mailbox during delivery using
# the Message-ID header
#
suppress_duplicates    = no
#
# Soft or hard bounce on over-quota delivery
#
quota_failure          = hard
# end of configuration file


Таким образом, такой подход используется когда нужно срезать входящие параметры функции JavaScript.
* правим default конфигурационный файл - /etc/default/dbmail
==Array.prototype.concat.apply==
Для объединения более двух массивов используется concat().


  <nowiki>var a = [1, 2], b = ["x", "y"], c = [true, false];
  # debian specific configuration for dbmail
var d = a.concat(b, c);
console.log(d); // [1, 2, "x", "y", true, false];</nowiki>
# work-around for linux/epoll bug in libevent
export EVENT_NOEPOLL=yes
# comment out to disable the pop3 server
'''START_POP3D=true'''
# comment out to disable the imapd server
'''START_IMAPD=true'''
# uncomment to enable the lmtpd server
'''START_LMTPD=true'''
# uncomment to enable the timsieved server
#START_SIEVE=true
# comment out to enable the stunnel SSL wrapper
'''START_SSL=true'''
# specify the filename for the pem file as
# it resides in /etc/ssl/certs
'''PEMFILE="/etc/ssl/serts/dbmail.pem"'''


For concatenating just two arrays, using push.apply() can be used instead for the case of adding elements from one array to the end of another without producing a new array. With slice() it can also be used instead of concat() but there appears to be no performance advantage from doing this.
* создаем сертификат для dbmail:
cd /etc/ssl/certs
openssl req -new -x509 -nodes -out dbmail.pem -keyout smtpd.pem -days 3650
* перезапуск службы:
systemctl restart dbmail


  <nowiki>var a = [1, 2], b = ["x", "y"];
* Краткое пояснение:
a.push.apply(a, b);
  1. Предназначенные для доставки сообщений от MTA в хранилище.<br>
console.log(a); // [1, 2, "x", "y"];</nowiki>
2. Предназначенные для доставки MUA из хранилища.<br>


==Современный метод bind==
* К первым относятся:<br>
'''dbmail-lmtpd''' – UNIX-демон, принимающий клиентские подключения через UNIX-сокет или TCP-сокет. Для приема почтовых сообщений используется протокол LMTP. На каждое входящее сообщение MTA создает только клиентский сокет, необходимое количество процессов и подключений к БД создается заранее.<br>
Таким образом, этот вариант обеспечивает лучшую производительность при высокой нагрузке, но при низкой он потребляет больше системных ресурсов, чем необходимо.<br>


В современном JavaScript для привязки функций есть метод bind. Он поддерживается большинством современных браузеров, за исключением IE<9, но легко эмулируется.
* Ко вторым относятся:<br>
'''dbmail-pop3d''' – демон для доступа по протоколу POP3.<br>
'''dbmail-imapd''' – демон для доступа по протоколу IMAP.<br>


Этот метод позволяет привязать функцию к нужному контексту, и даже к аргументам.
* Кроме того, в состав DBMail входят следующие вспомогательные утилиты:<br>
'''dbmail-users''' – инструмент для управления пользователями и их псевдонимами (возможно, многим из вас будет привычнее термин alias).<br>
'''dbmail-util''' – инструмент для очистки, оптимизации и проверки корректности БД.<br>


Синтаксис bind:
* С установкой '''dbmail''' пока окончено, следующий этап установка '''postgesql''' и настройка для будущей работы.


<nowiki>var wrapper = func.bind(context[, arg1, arg2...])</nowiki>
*'''func''' Произвольная функция
*'''wrapper''' Функция-обёртка, которую возвращает вызов bind. Она вызывает func, фиксируя контекст и, если указаны, первые аргументы.
*'''context''' Обертка wrapper будет вызывать функцию с контекстом this = context.
*'''arg1, arg2, …''' Если указаны аргументы arg1, arg2... — они будут прибавлены к каждому вызову новой функции, причем встанут перед теми, которые указаны при вызове.


Простейший пример, фиксируем только this:
=='''2. [[Настройка PostgreSQL]]'''==


<nowiki>function f() {
2.1. После того как мы настроили базу данных '''postgresql''', создаем пользователя '''dbmail''' и базу '''dbmail'''<br>
  alert(this.name);
* Создаем пользователя для работы с почтовой базой
}
createuser -U postgres -P dbmail


var user = { name: "Вася" };
* '''[!]''' - Ни в коем случае не используйте спецсимволы в пароле, кроме #! (авторизация может не проходить)


var f2 = f.bind(user);
* Создаем базу
createdb -U postgres --owner dbmail dbmail


f2(); // выполнит f с this = user
* Вместе с '''dbmail''' идут заготовки базы, распаковываем и заливаем:
</nowiki>
  bunzip2 /usr/share/doc/dbmail-2.2.10/create_tables.pgsql.bz2
Использование для привязки метода sayHi к новому объекту User:
psql -U dbmail -d dbmail < /usr/share/doc/dbmail-2.2.10/create_tables.pgsql
  <nowiki>
function User() {
  this.id = 1;
  this.sayHi = function() {
    alert(this.id);
  }.bind(this);
}
var user = new User();
setTimeout(user.sayHi, 1000); // выведет "1"</nowiki>


==Object.defineProperty или как сделать код капельку лучше==
или так:
[http://habrahabr.ru/post/150571/ оригинал статьи] [http://msdn.microsoft.com/ru-ru/library/ie/dd548687(v=vs.94).aspx тоже ребята старались]
zcat /usr/share/doc/dbmail/examples/create_tables.pgsql.gz|psql -h 127.0.0.1 dbmail dbmailadmin


Этот краткий пост-заметку или температурный бред (в Одессе похолодало, да) хочу посвятить такой прекрасной функции, как Object.defineProperty (и Object.defineProperties). Активно использую её уже около двух месяцев, так как поддержка старых браузеров (в том числе и IE8) в проекте, который я сейчас реализую, не требуется (завидуйте).
или так:
psql -U dbmail -h localhost maildb < create_tables.pgsql


Как положено статье на хабре, приведу краткое описание того, что она делает. Object.defineProperty добавляет новое свойство, обладающее неким нестандартным для обычного свойства поведением, и принимает три аргумента:
*Объект, который мы модифицируем, добавляя новое свойство
*Свойство (строка), которое, собственно, хотим добавить
*Дескриптор: объект, содержащий «настройки» нового свойства, например аццессоры (геттер, сеттер)
Дескриптор может содержать следующие свойства:
*value (любое значение: строка, функция...) — значение, которое получит определяемое свойство объекта (геттер и сеттер в данном случае определить нельзя)
*writable (true/false) — можно ли перезаписать значение свойства (аццессоры тоже не доступны)
*get (функция) — геттер (value и writable определить нельзя)
*set (функция) — сеттер (value и writable определить нельзя)
*configurable (true/false) — можно ли переопределить дескриптор (использовать Object.defineProperty над тем же свойством)
*enumerable (true/false) — будет ли свойство перечисляться через for..in и доступно в Object.keys (плохая формулировка)
<nowiki>var o = {};
Object.defineProperty(o, "a", {value : 37,
                              writable : true,
                              enumerable : true,
                              configurable : true});


* В этом дампе нет таблицы для работы с виртуальными доменами, создадим ее:
  CREATE TYPE dtype AS ENUM (
  'LOCAL',
  'VIRTUAL',
  'RELAY'
);
ALTER TYPE public.dtype OWNER TO dbmail;
SET default_with_oids = true;
CREATE TABLE dbmail_domains (
  uid integer NOT NULL,
  domain character varying(128) NOT NULL,
  type dtype NOT NULL
);
   
   
var bValue;
INSERT INTO dbmail_domains (uid, domain, type) VALUES (1, 'example.com', 'LOCAL');
Object.defineProperty(o, "b", {get : function(){ return bValue; },
                              set : function(newValue){ bValue = newValue; },
                              enumerable : true,
                              configurable : true});</nowiki>


Лучше меня объяснит MDN [https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/defineProperty Object/defineProperty]. Благо, даже английский знать не надо, и так всё понятно.
'''База готова.'''


Если нужно определить сразу несколько свойств, можно использовать Object.defineProperties, который принимает два аргумента: объект, требующий изменений и объект с определяемыми ключами.
* добавляем обработку базы в /etc/crontab
MDN: [https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/defineProperties Object/defineProperties].
...
0 3 * * * root /usr/sbin/dbmail-util -cturpd -l 24h -qq
...


<nowiki>
* проверяем работу '''dbmail''' c базой:
// Код сперт с MDN
Object.defineProperties(obj, {
  "property1": {
    value: true,
    writable: true
  },
  "property2": {
    value: "Hello",
    writable: false
  }
  // etc. etc.
});</nowiki>


dbmail-util -av


если есть ошибки, исправляем не забывая проверить файл конфигурации...<br>
.. если все ок, приступаем к настройке '''postfix'''


Теперь соль. Чего я вообще решил это запостить?
=='''3. Настройка Postfix'''==


Так как в упомянутом выше проекте мне приходится использовать defineProperty не просто активно, а очень активно, код стал, мягко говоря, некрасивым. Пришла в голову простейшая идея (как я до этого раньше-то не додумался?), расширить прототип Object, сделав код сильно компактнее. Плохой тон, скажете вы, засерать прототип Object новыми методами.
apt-get install postfix postfix-pgsql postfix-sqlite procmail libsasl2-2 libsasl2-modules libsasl2-modules-db\
libsasl2-modules-sql sqlite3 mutt postfix-pcre postfix-ldap postfix-lmdb sasl2-bin ufw


Откуда вообще взялось это мнение? Потому что все объекты унаследуют это свойство, которое, при обычной модификации прототипа, становится перечисляемым в for..in. На душе становится тепло, когда вспоминаешь о том, что я описал выше, а именно, о свойстве дескриптора enumerable. Действительно, расширив прототип таким образом:  
* вносим необходимые изменения в файлы конфигурации - пример рабочей версии '''main.cf''':


  <nowiki>Object.defineProperty( Object.prototype, 'logOk' {
  # See /usr/share/postfix/main.cf.dist for a commented, more complete version
    value: function() { console.log('ok') },
    enumerable: false
});</nowiki>
# Debian specific:  Specifying a file name will cause the first
# line of that file to be used as the name.  The Debian default
# is /etc/mailname.
#myorigin = /etc/mailname
smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU)
biff = no
# appending .domain is the MUA's job.
append_dot_mydomain = no
# Uncomment the next line to generate "delayed mail" warnings
#delay_warning_time = 4h
readme_directory = no
# See http://www.postfix.org/COMPATIBILITY_README.html -- default to 2 on
# fresh installs.
compatibility_level = 2
# TLS parameters
'''#smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem'''
'''#smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key'''
'''smtpd_tls_cert_file=/etc/postfix/ssl/smtpd.pem'''
'''smtpd_tls_key_file=/etc/postfix/ssl/smtpd.key'''
smtpd_use_tls=yes
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
# See /usr/share/doc/postfix/TLS_README.gz in the postfix-doc package for
# information on enabling SSL in the smtp client.
'''smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination'''
'''myhostname = mymail.home.local'''
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
myorigin = /etc/mailname
'''mydestination = $myhostname, mymail.ru, mymail.home.local, localhost.home.local, localhost'''
relayhost =
'''#mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128'''
'''######################### вторым ip указываем хост где база данных postgresql'''
'''mynetworks = 127.0.0.0/8 10.0.5.2'''
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = all
inet_protocols = all
'''############################## - указываем способ использования postgresql'''
'''local_recipient_maps = pgsql:/etc/postfix/dbmail-mailboxes.cf $alias_maps'''
'''mailbox_transport = dbmail-lmtp:127.0.0.1:24'''
'''#################### - подключаем авторизацию через sasl, установка ниже в статье.'''
'''broken_sasl_auth_clients = yes'''
'''smtpd_sasl_auth_enable = yes'''
'''smtpd_sasl_local_domain ='''
'''############################### - подключаем наш сертификат созданный как описано ниже.'''
'''smtpd_tls_auth_only = no'''
'''smtpd_tls_loglevel = 1'''
'''smtpd_tls_received_header = yes'''
'''smtpd_tls_session_cache_timeout = 3600s'''
'''tls_random_source = dev:/dev/urandom'''


все объекты получат этот метод, но, при этом, он будет неперечисляемым (не нужно каждый раз использовать hasOwnProperty для проверки, есть ли такое свойство):
 
* вносим необходимые изменения в файлы конфигурации - пример рабочей версии '''master.cf''':
#
# Postfix master process configuration file.  For details on the format
# of the file, see the master(5) manual page (command: "man 5 master" or
# on-line: http://www.postfix.org/master.5.html).
#
# Do not forget to execute "postfix reload" after editing this file.
#
# ==========================================================================
# service type  private unpriv  chroot  wakeup  maxproc command + args
#              (yes)  (yes)  (no)    (never) (100)
# ==========================================================================
smtp      inet  n      -      y      -      -      smtpd
#smtp      inet  n      -      y      -      1      postscreen
#smtpd    pass  -      -      y      -      -      smtpd
#dnsblog  unix  -      -      y      -      0      dnsblog
#tlsproxy  unix  -      -      y      -      0      tlsproxy
#submission inet n      -      y      -      -      smtpd
#  -o syslog_name=postfix/submission
#  -o smtpd_tls_security_level=encrypt
#  -o smtpd_sasl_auth_enable=yes
#  -o smtpd_reject_unlisted_recipient=no
#  -o smtpd_client_restrictions=$mua_client_restrictions
#  -o smtpd_helo_restrictions=$mua_helo_restrictions
#  -o smtpd_sender_restrictions=$mua_sender_restrictions
#  -o smtpd_recipient_restrictions=
#  -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
#  -o milter_macro_daemon_name=ORIGINATING
#smtps    inet  n      -      y      -      -      smtpd
#  -o syslog_name=postfix/smtps
#  -o smtpd_tls_wrappermode=yes
#  -o smtpd_sasl_auth_enable=yes
#  -o smtpd_reject_unlisted_recipient=no
#  -o smtpd_client_restrictions=$mua_client_restrictions
#  -o smtpd_helo_restrictions=$mua_helo_restrictions
#  -o smtpd_sender_restrictions=$mua_sender_restrictions
#  -o smtpd_recipient_restrictions=
#  -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
#  -o milter_macro_daemon_name=ORIGINATING
#628      inet  n      -      y      -      -      qmqpd
pickup    unix  n      -      y      60      1      pickup
cleanup  unix  n      -      y      -      0      cleanup
qmgr      unix  n      -      n      300    1      qmgr
#qmgr    unix  n      -      n      300    1      oqmgr
tlsmgr    unix  -      -      y      1000?  1      tlsmgr
rewrite  unix  -      -      y      -      -      trivial-rewrite
bounce    unix  -      -      y      -      0      bounce
defer    unix  -      -      y      -      0      bounce
trace    unix  -      -      y      -      0      bounce
verify    unix  -      -      y      -      1      verify
flush    unix  n      -      y      1000?  0      flush
proxymap  unix  -      -      n      -      -      proxymap
proxywrite unix -      -      n      -      1      proxymap
smtp      unix  -      -      y      -      -      smtp
relay    unix  -      -      y      -      -      smtp
#      -o smtp_helo_timeout=5 -o smtp_connect_timeout=5
showq    unix  n      -      y      -      -      showq
error    unix  -      -      y      -      -      error
retry    unix  -      -      y      -      -      error
discard  unix  -      -      y      -      -      discard
local    unix  -      n      n      -      -      local
virtual  unix  -      n      n      -      -      virtual
lmtp      unix  -      -      y      -      -      lmtp
anvil    unix  -      -      y      -      1      anvil
scache    unix  -      -      y      -      1      scache
#
# ====================================================================
# Interfaces to non-Postfix software. Be sure to examine the manual
# pages of the non-Postfix software to find out what options it wants.
#
# Many of the following services use the Postfix pipe(8) delivery
# agent.  See the pipe(8) man page for information about ${recipient}
# and other message envelope options.
# ====================================================================
#
# maildrop. See the Postfix MAILDROP_README file for details.
# Also specify in main.cf: maildrop_destination_recipient_limit=1
#
maildrop  unix  -      n      n      -      -      pipe
  flags=DRhu user=vmail argv=/usr/bin/maildrop -d ${recipient}
#
# ====================================================================
#
# Recent Cyrus versions can use the existing "lmtp" master.cf entry.
#
# Specify in cyrus.conf:
#  lmtp    cmd="lmtpd -a" listen="localhost:lmtp" proto=tcp4
#
# Specify in main.cf one or more of the following:
#  mailbox_transport = lmtp:inet:localhost
#  virtual_transport = lmtp:inet:localhost
#
# ====================================================================
#
# Cyrus 2.1.5 (Amos Gouaux)
# Also specify in main.cf: cyrus_destination_recipient_limit=1
#
#cyrus    unix  -      n      n      -      -      pipe
#  user=cyrus argv=/cyrus/bin/deliver -e -r ${sender} -m ${extension} ${user}
#
# ====================================================================
# Old example of delivery via Cyrus.
#
#old-cyrus unix  -      n      n      -      -      pipe
#  flags=R user=cyrus argv=/cyrus/bin/deliver -e -m ${extension} ${user}
#
# ====================================================================
#
# See the Postfix UUCP_README file for configuration details.
#
uucp      unix  -      n      n      -      -      pipe
  flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient)
#
# Other external delivery methods.
#
ifmail    unix  -      n      n      -      -      pipe
  flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient)
bsmtp    unix  -      n      n      -      -      pipe
  flags=Fq. user=bsmtp argv=/usr/lib/bsmtp/bsmtp -t$nexthop -f$sender $recipient
scalemail-backend unix  -      n      n      -      2      pipe
  flags=R user=scalemail argv=/usr/lib/scalemail/bin/scalemail-store ${nexthop} ${user} ${extension}
mailman  unix  -      n      n      -      -      pipe
  flags=FR user=list argv=/usr/lib/mailman/bin/postfix-to-mailman.py
  ${nexthop} ${user}
'''######'''
'''dbmail-lmtp    unix    -      -      n      -      -      lmtp'''
        '''-o disable_dns_lookups=yes'''
* создаем файл настройки подключения к базе postgresql - '''dbmail-mailboxes.cf''':


  <nowiki>var o = {a: 1, b: 2}
  user = dbmail
for( var i in o ) console.log( i, o[ i ] );
password = userpass
> a 1
hosts = 10.0.5.2
> b 2
dbname = mailbasename
o.logOk();
table = dbmail_aliases
> ok</nowiki>
select_field = alias
where_field = alias


* Так как почтовый сервер изначально не рассматсривается как релей, то доступ к '''SMTP''' только по авторизации и для этого используем '''SASL'''.
* в каталоге настроек postfix создаем файл настроек для '''sasl''':
mkdir -p /etc/postfix/sasl


Собственно то, ради чего я тут графоманю.
* создаем файл конфигурации - '''smtpd.conf''':
echo > /etc/postfix/sasl/smtpd.conf
* вносим содержимое файла:
edit /etc/postfix/sasl/smtpd.conf


Во-первых определим метод define, чтоб каждый раз не вызывать перегруженную, на мой взгляд, конструкцию. Во-вторых определим метод extendNotEnum, который расширяет объект неперечисляемыми свойствами.
pwcheck_method: auxprop
auxprop_plugin: sql
mech_list: digest-md5 cram-md5 login plain
sql_engine: pgsql
sql_user: dbmail
sql_passwd: userpass
sql_hostnames: 10.0.5.2
sql_database: mailbasename
sql_statement: select passwd from dbmail_users where userid='%u@%r'
sql_verbose: yes


  <nowiki>Object.defineProperties( Object.prototype, {
* создаем свой recipient_bcc
    define: {
  *@mainhost.local admin@mailbox.ru
        value: function( key, descriptor ) {
*@slavehost.local admin@mailbox.ru
            if( descriptor ) {
*@otherhost.local admin@mailbox.ru
                Object.defineProperty( this, key, descriptor );
            } else {
                Object.defineProperties( this, key );
            }
            return this;
        },
        enumerable: false
    },
    extendNotEnum: {
        value: function( key, property ) {
            if( property ) {
                this.define( key, {
                    value: property,
                    enumerable: false,
                    configurable: true
                });
            } else {
                for( var prop in key ) if( key.hasOwnProperty( prop ) ){
                    this.extendNotEnum( prop, key[ prop ] );
                }
            }
        },
        enumerable: false
    }
});
</nowiki>


Использование:
* генерируем базу
  <nowiki>var o = { a: 1 };
  postmap recipient_bcc
o.define( 'randomInt', {
    get: function() {
        return 42;
    }
});


o.extendNotEnum({
* генерируем свой сертификат tls:
    b: 2;
mkdir -p /etc/postfix/ssl
});
cd /etc/postfix/ssl
openssl req -new -x509 -days 3650 -nodes -out smtpd.pem -keyout smtpd.key


for( var i in o ) console.log( i, o[ i ] );
* перезапускаем '''postfix''':
> a 1
systemctl  restart postfix
> randomInt 42
или
/etc/init.d/postfix restart


console.log( o.b );
* проверяем работу '''postfix''':
> 2
# telnet mymail.ru 25
</nowiki>
Trying mymail.ru...
Connected to mymail.ru.
Escape character is '^]'.
220 mx.kscom.ru ESMTP Postfix
EHLO example.com
250-mx.kscom.ru
250-PIPELINING
250-SIZE 10240000
250-VRFY
250-ETRN
250-STARTTLS
250-ENHANCEDSTATUSCODES
250-8BITMIME
250 DSN
QUIT
221 2.0.0 Bye
Connection closed by foreign host.
- должно быть - 250-STARTTLS
- все работает..


И пошла… Еще два удобных метода:
=='''4. Настройка Stunnel'''==
* Данный пакет позволяет организовать защищенное соединение как для почты так и для других программ.<br>
* Далее будет описание, как создать защищенный вход на почтовый сервер.<br>


<nowiki>Object.prototype.extendNotEnum({
* Устанавливаем пакет:
    extend: function() {
apt-get install stunnel4
        var args = Array.prototype.slice.call( arguments );
        args.unshift( this );
        return $.extend.apply( null, args ); // если jQuery надоест, можно просто переписать под себя
    },
   
    each: function( callback ) {
        return $.each( this, callback ); // аналогично
    }
});


* в каталоге /etc/stunnel - сразу создаем себе скрипт для генерации сертификата, чтобы если понадобится снова не вспоминать как это...
echo > /etc/stunnel/create-sert
editor /etc/stunnel/create-sert


o.extend({c: 3}); // тупо добавляет новые свойства в объект
* вносим содержимое:
o.each(function( key, value ) {
#!/bin/sh
    // просто повторяет механизм $.each, перебирая все ключи и свойства
# каталог сертификатов SSL в системе
});</nowiki>
cd /etc/ssl/certs
# имя сертификата на свое усмотрение...
PEMFILE="servername.mymail.ru.pem"
# генерация сертификата
openssl req -new -x509 -nodes -days 3650 -out $PEMFILE -keyout $PEMFILE
chmod 600 $PEMFILE
[ -e temp_file ] && rm -f temp_file
dd if=/dev/urandom of=temp_file count=2
openssl dhparam -rand temp_file 512 >> $PEMFILE
ln -sf $PEMFILE `openssl x509 -noout -hash < $PEMFILE`.0
 
* даем права на исполнение - только для root:
chmod 0700 /etc/stunnel/create-sert


* запускаем скрипт и отвечаем на вопросы..
/etc/stunnel/create-sert


Добавлять новые свойства можно до бесконечности, никто вам за это руку не откусит.
* создаем каталог в котором будет файл запуска .pid
mkdir -p /var/run/stunnel4/


Вывод
* копируем из примера будущий конфигурационный файл для stunnel4
cp /usr/share/doc/stunnel4/examples/stunnel.conf-sample /etc/stunnel/stunnel.conf


Играйте в Денди, пишите на Javascript, который становится всё лучше и лучше.
* приводим его в такой вариант (рабочий пример):
; Sample stunnel configuration file for Unix by Michal Trojnara 2002-2015
; Some options used here may be inadequate for your particular configuration
; This sample file does *not* represent stunnel.conf defaults
; Please consult the manual for detailed description of available options
; **************************************************************************
; * Global options                                                        *
; **************************************************************************
; It is recommended to drop root privileges if stunnel is started by root
;setuid = stunnel4
;setgid = stunnel4
; PID file is created inside the chroot jail (if enabled)
pid = /var/run/stunnel4/stunnel.pid
; Debugging stuff (may be useful for troubleshooting)
;foreground = yes
;debug = info
output = /var/log/stunnel.log
; Enable FIPS 140-2 mode if needed for compliance
;fips = yes
fips = no
; **************************************************************************
; * Service defaults may also be specified in individual service sections  *
; **************************************************************************
; Enable support for the insecure SSLv3 protocol
options = -NO_SSLv3
sslVersion = TLSv1.2
; These options provide additional security at some performance degradation
;options = SINGLE_ECDH_USE
;options = SINGLE_DH_USE
; **************************************************************************
; * Include all configuration file fragments from the specified folder    *
; **************************************************************************
;include = /etc/stunnel/conf.d
; **************************************************************************
; * Service definitions (remove all services for inetd mode)              *
; **************************************************************************
; ***************************************** Example TLS client mode services
; The following examples use /etc/ssl/certs, which is the common location
; of a hashed directory containing trusted CA certificates.  This is not
; a hardcoded path of the stunnel package, as it is not related to the
; stunnel configuration in /etc/stunnel/.
;[mymail-pop3]
;client = yes
;accept = 127.0.0.1:110
;connect = pop3.mymail.ru:995
;verifyChain = yes
;CApath = @sysconfdir/ssl/certs
;checkHost = pop3s.mymail.ru
;OCSPaia = yes
;[mymail-imap]
;client = yes
;accept = 127.0.0.1:143
;connect = imap.mymail.ru:993
;verifyChain = yes
;CApath = @sysconfdir/ssl/certs
;checkHost = imaps.mymail.ru
;OCSPaia = yes
;[mymail-smtp]
;client = yes
;accept = 127.0.0.1:25
;connect = smtp.mymail.ru:465
;verifyChain = yes
;CApath = @sysconfdir/ssl/certs
;checkHost = smtps.mymail.ru
;OCSPaia = yes
; ***************************************** Example TLS server mode services
[pop3s]
accept  = 995
connect = 110
cert = /etc/ssl/certs/servername.mymail.ru.pem
[imaps]
accept  = 993
connect = 143
cert = /etc/ssl/certs/servername.mymail.ru.pem
[smtps]
accept  = 465
connect = 25
cert = /etc/ssl/certs/servername.mymail.ru.pem
; TLS front-end to a web server
;[https]
;accept  = 443
;connect = 80
;cert = /etc/stunnel/stunnel.pem
; "TIMEOUTclose = 0" is a workaround for a design flaw in Microsoft SChannel
; Microsoft implementations do not use TLS close-notify alert and thus they
; are vulnerable to truncation attacks
;TIMEOUTclose = 0
; Remote shell protected with PSK-authenticated TLS
; Create "/etc/stunnel/secrets.txt" containing IDENTITY:KEY pairs
;[shell]
;accept = 1337
;exec = /bin/sh
;execArgs = sh -i
;ciphers = PSK
;PSKsecrets = /etc/stunnel/secrets.txt
; Non-standard MySQL-over-TLS encapsulation connecting the Unix socket
;[mysql]
;cert = /etc/stunnel/stunnel.pem
;accept = 3307
;connect = /run/mysqld/mysqld.sock
; vim:ft=dosini


комментарии
* корректируем конфигурационный файл запуска по умолчанию:
# /etc/default/stunnel
# Julien LEMOINE <speedblue@debian.org>
# September 2003
# Change to one to enable stunnel automatic startup
ENABLED=1
FILES="/etc/stunnel/*.conf"
OPTIONS=""
# Change to one to enable ppp restart scripts
PPP_RESTART=0
# Change to enable the setting of limits on the stunnel instances
# For example, to set a large limit on file descriptors (to enable
# more simultaneous client connections), set RLIMITS="-n 4096"
# More than one resource limit may be modified at the same time,
# e.g. RLIMITS="-n 4096 -d unlimited"
RLIMITS=""


* перезапуск stunnel
/etc/init.d/stunnel4 restart


Еще один финт ушами для тех, кто определяет свойства прототипа только однажды, после определения функции-конструктора:
* после этого проверяем наличие нужных нам портов:
<nowiki>// запускается в консоли
nmap -v mymail.ru
Object.defineProperty( Object.prototype, 'awesomeprototype', {
...
    set: function( object ) {
PORT    STATE SERVICE
        for( var prop in object ) {
22/tcp  open  ssh
            Object.defineProperty( this.prototype, prop, {
25/tcp  open  smtp
                value: object[ prop ],
110/tcp open  pop3
                enumerable: false
143/tcp open  imap
            });
465/tcp open  smtps
        }
993/tcp open  imaps
    }
995/tcp open  pop3s
});


var X = function() {}
* проверяем работу с почтой по '''SSL\TLS''' - зашифрованный пароль на портах '''465,993,995'''
X.awesomeprototype = {
* если все в порядке, рекомендую закрыть обычные порты через '''iptables''' ('''110,143'''),
    method: function() { alert( 'ok' ) }
* а оставить только '''25''' (некоторые серверы для доставки вам почты требуют именно его)
};


var x = new X
=='''5. Установка антиспама Spamassassin'''==


x.method()
* установка пакета:
</nowiki>
aptitude install spamassassin


'''Стоит отметить что в литералах объектов свойства можно задавать через геттер и сеттер:'''
* запуск по умолчанию в /etc/default/spamassassin
  <nowiki>var o = {
  ...
    __someProperty : 42,
ENABLED=1
    get someProperty() { return this.__someProperty; },
...
    set someProperty(v) { this.__someProperty = v; }
};


o.someProperty; // 42
* Приводим файл конфигурации антиспама /etc/spamassassin/local.cf  к такому:
o.someProperty = 56;
o.someProperty; // 56
</nowiki>


Ну это так, к слову :)
# This is the right place to customize your installation of SpamAssassin.
 
#
==deferred==
# See 'perldoc Mail::SpamAssassin::Conf' for details of what can be
*[http://habrahabr.ru/post/175947/ Асинхронные API и объект Deferred в деталях][https://github.com/StreetStrider/utils/tree/master/doc GitHub]
# tweaked.
*[http://javascript.ru/unsorted/async/deferred Объект Deferred.]
#
*[http://www.smira.ru/2009/02/10/deferred-async-programming/ Асинхронное программирование: концепция Deferred]
# Only a small subset of options are listed below
*[http://www.smira.ru/2009/02/24/more-about-deferred/ Deferred: все подробности]
#
==Шаблонизация в JavaScript==
###########################################################################
*[http://learn.javascript.ru/templates Шаблонизация в JavaScript]
==DOM==
#  Add *****SPAM***** to the Subject header of spam e-mails
===dom2json===
#
http://coderepos.org/share/browser/lang/javascript/dom2json/dom2json.js?rev=30780
rewrite_header Subject *****SPAM*****
skips empty attributes that IE implicitly adds
  <nowiki>/*
  #  Save spam messages as a message/rfc822 MIME attachment instead of
  * $Id: dom2json.js,v 0.2 2008/06/15 07:04:10 dankogai Exp dankogai $
  #  modifying the original message (0: off, 2: use text/plain instead)
  */
  #
(function(){
report_safe 0
 
dom2json = function(dom){
    var type = dom.nodeType;
#  Set which networks or hosts are considered 'trusted' by your mail
    if (type == 3) return dom.nodeValue;
#  server (i.e. not spammers)
    if (type == 1){
#
        var json = [];
# trusted_networks 212.17.35.
        json.push(dom.nodeName);
trusted_networks 10.0.5.
        var attrs = dom.attributes;
        if (attrs.length){
            var attr = {};
#  Set file-locking method (flock is not safe over NFS, but is faster)
            for (var i = 0, l = attrs.length; i < l; i++){
#
                attr[attrs[i].name] = attrs[i].value;
# lock_method flock
            }
            if (0 /*@cc_on +1@*/){
                attr['style'] = dom.style;
#  Set the threshold at which a message is considered spam (default: 5.0)
            }
#
            json.push(attr);
required_score 5.0
        }
        if (! dom.hasChildNodes()) return json;
        var kids = dom.childNodes;
#  Use Bayesian classifier (default: 1)
        for (var i = 0, l = kids.length; i < l; i++){
#
            var kjson = arguments.callee(kids[i]);
use_bayes 1
            if (kjson) json.push(kjson);
        }
        return json;
#  Bayesian classifier auto-learning (default: 1)
    }
#
};
bayes_auto_learn 1
json2dom = function(json){
    var i = 0;
#  Set headers which may provide inappropriate cues to the Bayesian
    var tag = json[i++]
#  classifier
    var dom = document.createElement(tag);
#
    if (json[i].constructor == Object){
bayes_ignore_header X-Bogosity
        var attr = json[i++];
bayes_ignore_header X-Spam-Flag
        for (var name in attr){
bayes_ignore_header X-Spam-Status
            dom.setAttribute(name, attr[name]);
        }
    }
#  Whether to decode non- UTF-8 and non-ASCII textual parts and recode
    for (var l = json.length; i < l; i++){
#  them to UTF-8 before the text is given over to rules processing.
        dom.appendChild(
#
            json[i].constructor == Array
# normalize_charset 1
                ? arguments.callee(json[i])
                : document.createTextNode(json[i])
#  Some shortcircuiting, if the plugin is enabled
            );           
#
    }
ifplugin Mail::SpamAssassin::Plugin::Shortcircuit
    return dom;
  #
}
  #  default: strongly-whitelisted mails are *really* whitelisted now, if the
  #  shortcircuiting plugin is active, causing early exit to save CPU load.
})();</nowiki>
  #  Uncomment to turn this on
===обход дерева DOM===
  #
  <nowiki>/**
  # shortcircuit USER_IN_WHITELIST      on
  * Рекурсивное перечисление дочерних элементов
# shortcircuit USER_IN_DEF_WHITELIST  on
  *
# shortcircuit USER_IN_ALL_SPAM_TO     on
* @param DomNode node
# shortcircuit SUBJECT_IN_WHITELIST    on
  * Родительский элемент, чьи дочерние узлы нужно перечислять.
  *
#  the opposite; blacklisted mails can also save CPU
  * @return void
#
  */
# shortcircuit USER_IN_BLACKLIST      on
function enumChildNodes(node) {
# shortcircuit USER_IN_BLACKLIST_TO    on
     // если нам передали элемент
# shortcircuit SUBJECT_IN_BLACKLIST    on
    if (node && 1 == node.nodeType) {
        // берем его первый дочерний узел
#  if you have taken the time to correctly specify your "trusted_networks",
        var child = node.firstChild;
#  this is another good way to save CPU
        // пока узлы не закончились
#
        while (child) {
# shortcircuit ALL_TRUSTED             on
            // если этот узел является элементом
            if (1 == child.nodeType) {
                // что-то делаем с найденным элементом
                alert('элемент ' + child.tagName);
                // рекурсивно перечисляем дочерние узлы
                enumChildNodes(child);
             };
            // переходим к следующему узлу
            child = child.nextSibling;
        };
    };
};
   
   
// перечисляем содержимое body
  #   and a well-trained bayes DB can save running rules, too
enumChildNodes(document.body);</nowiki>
  #
 
  # shortcircuit BAYES_99                spam
===Обход child nodes (потомков) определнного элемента в Javascript===
  # shortcircuit BAYES_00                ham
JavaScript на заметку Комментировать
  whitelist_from @mymail.ru
Речь о том как корректно обойти элементы потомки DOM для данного элемента,
простите за тафтологию
 
  var object = document.getElementById('el');
for (var childItem in object.childNodes) {
if (object.childNodes[childItem].nodeType == 1)
object.childNodes[childItem].style.color = '#FF0000';
 
В данном примере мы берем некоторый элемент с id = 'el' и проходим по массиву
его потомков (childNodes), при этом проверяем nodeType потомка, чтобы он был
элементом страницы и если так, то окрашиваем его в КРАСНЫЙ цвет.
 
В принципе на базе этой конструкции можно организовать рекурсивный обход дерева
всех потомков заданного элемента, если конечно возникнет такая необходимость, но
пока я представляю только упрощенный вариант.
 
Здесь следует обратить внимание на проверку свойства nodeType == 1, дело в том
что без этой проверки в обработку попадут и разрывы строк, т.е. символы "\n",
которые тоже воспинимаются как ноды. Т.е. например конструкция вида:
 
так быстрее и меньше кода
  var object = document.getElementById(‘el’).getElementsByTagName(‘*’);
  for(var i=0;object[i];i++){
  object[i].style.color = ‘#FF0000′;
 
 
Sun, getElementsByTagName не поддерживает ИЕ, смысл его использовать?
 
К сожалению заметил, что данная конструкция в Opera выполняется некорректно
(цикл for проходится два раза), никто с таким не встречался? Где-то всё же
закралась моя ошибка или это ошибка браузера (проверял в 9.20, 9.50, 10.20),
замечу, что в остальных браузерах всё выполняется верно.
 
var object = document.getElementById(‘el’);
  for (var childItem in object.childNodes) {
   
   
  }
  endif # Mail::SpamAssassin::Plugin::Shortcircuit
   
   
var object=document.getElementById(‘el’);
* Стартуем spamassasin:
for(var i=0;i<object.childNodes.length;i++) {
if (object.childNodes[i].nodeType == 1) {
// тут что-либо делаем
}
}
==Functional JavaScript==
[http://functionaljavascript.blogspot.ru/2013/03/introduction-to-functional-javascript.html Источник]
===Introduction to Functional JavaScript===
JavaScript was a functional programming language even before it got its name! Back in 1995 Netscape Communications the makers of the Netscape Browser realized that their browser needed a scripting language embedded in HTML. They hired Brendan Eich to embed the Scheme programming language in HTML. Scheme was a full fledged functional programming language. To his dismay “the powers that be” encouraged Brendan to come up with a more traditional programming language. His first cut was a language called “Mocha” in May 1995, and it retained that name till December 1995. It was first renamed “LiveScript” and soon after that Netscape and Sun did a license agreement and it was called “JavaScript”. But by then Brendan Eich had sneaked in some “functional programming” features into JavaScript.
 
The story gets more complicated, and we will delve into it. Because this story will tell you why JavaScript is what it is today. Brendan Eich claims that hiring him to embed the Scheme language, might actually have been a “bait and switch operation”. But at that point in time Netscape was also negotiating with Sun to embed Java in the browser. Note that JavaScript was for embedding in HTML while Java was for embedding in the Browser. The idea was that Java would be used for component development while JavaScript would be used for lightweight scripting within HTML.
 
We don't know what actually transpired, but the orders from above to Brendan where clear. The new scripting language must “look like Java” and must be “object based”. Any hopes Brendan might have harboured for Scheme, where now out of the window. We will see why.
 
Programming Paradigms
We can only speculate on what “look like Java” meant. However we can be certain that it had to be a “curly brace” language. Curly brace languages define statement blocks using curly braces, the “{“ and “}” characters. Indeed Java syntax was fashioned on another curly brace language “C++”, which in turn was fashioned on “C”. Here is how a for-loop looks in Java.
 
<nowiki>for (int i = 0; i < 10; i++) {
    System.out.println("Hello");
    System.out.println("World");
}</nowiki>
The same for-loop in C.
<nowiki>int i;
for (i = 0; i < 10; i++) {
    printf("Hello\n");
    printf("World\n");
}</nowiki>
And the for-loop in JavaScript.
<nowiki>for (var i = 0; i < 10; i++) {
    console.log("Hello");
    console.log("World");
}</nowiki>
Indeed JavaScript does look like Java. And C too. However curly braces where not the only implications for a language to “look like Java”. Java is an imperative/object oriented style programming language. JavaScript also had to be an imperative/object oriented style language.
 
Programming languages are made up of operators, conditional statements, loop statements and functions. Having conditional statements and loop statements are hallmarks of an “imperative” style programming language. Functional style languages tend to support operators and functions only.
 
It is interesting that none of the three languages, Java, C++ and C, were functional programming languages. While C was an imperative programming language, C++ and Java were Imperative/Objected Oriented programming languages. By now you would have guessed that the three programming paradigms (styles) were imperative, object oriented and functional. There is one more, the declarative paradigm.
 
The differences between these paradigms are because of the foundations on which they were based. Imperative and object oriented programming are based on the “turing machine”. Functional programming is based on “lambda calculus” and declarative programming is based on “first order logic”. In this post we will look at the differences between, imperative, object oriented and functional programming at a more practical level.
 
An imperative programming language is one in which program state change is achieved by executing a series of statements, and does flow control primarily using conditional statements, loop statements and function calls. The program given below is a simple implementation of the JavaScript Array.join method in an imperative manner.
 
<nowiki>function simpleJoin(stringArray) {
    var accumulator = '';
    for (var i=0, l=stringArray.length; i < l; i++) {
        accumulator = accumulator + stringArray[i];
    }
    return accumulator;
}</nowiki>
The code above is straight forward. We iterate through an array and add each string element to the accumulator and return the accumulator. We will now rewrite this function in an object oriented manner. Since JavaScript has an Array class, we will add this method to the Array class, so that every instance of this class has access to this function. JavaScript use prototypal inheritance and so we add this function to the Array prototype.
 
<nowiki>Array.prototype.simpleJoin = function() {
    var accumulator = "";
    for (var i=0, l=this.length; i < l; i++) {
        accumulator = accumulator + this[i];
    }
    return accumulator;
}</nowiki>
As we can see, the object oriented version is quite like the imperative version, except that the function (method) is now a method of the class. Object oriented languages tend to be imperative languages also.
 
Now let us write the functional version of this function.
<nowiki>function simpleJoin(stringArray, i, accumulator) {
    if (i === stringArray.length) {
    return accumulator;
    } else {
    return simpleJoin(stringArray, i+1, accumulator+stringArray[i])
    }
}</nowiki>
The first thing to note is that we are not using the for loop here for iteration. Instead we use recursion for iteration. Recursion happens when the function calls itself from within itself. Indeed this is one of the characteristics of a functional programming language. eg. Scheme does not have any loop statements. Instead it uses recursion for iteration. The function is called for the first time with the given array in stringArray, i set to 0, and accumulator set to "". The second time around the function is called from within itself with the same stringArray, i set to i + 1, and accumulator set to accumulator + stringArray[i]. And we continue the same way until i === stringArray.length when we return the accumulator. We will discuss recursion in detail later in a later post. Just remember we used recursion for doing iteration here.
 
There is still something imperative about this function. The if statement. Functional languages tend to use expressions that evaluate to some value, instead of statements that don't evaluate to anything. So let us rewrite the function, to make it as functional as possible in JavaScript.
 
<nowiki>function simpleJoin(stringArray, i, accumulator) {
    return (i === stringArray.length) ? accumulator :
        simpleJoin(stringArray, i + 1, accumulator + stringArray[i])
}</nowiki>
Now this as functional as you can get with JavaScript. Instead of the if statement we return the value evaluated by the conditional operator ?. The conditional operator ? Takes a conditional expression and returns the value of one of the two expressions based the condition being true or false. The value of the first expression is returned if true and the second if false.
 
We can see that the functional version is concise. Indeed one of the advantages of functional programming is that it lends itself to lesser code to accomplish the same thing, leading to better readability and maintainability.
 
However in the case of JavaScript, as of now you cannot use recursion for doing iteration. You should continue to use the imperative or object oriented method for iteration. This is because JavaScript does not (yet) support “tail call optimization”. For doing proper recursion, tail call optimization is required. We will discuss tail recursion, and tail call optimization, and how to get around this problem in a future post. As of writing this post tail call optimization is expected in ecmascript 6.
 
So is JavaScript an imperative language, or an object oriented language, or a functional language? It is a multi paradigm language. It does not have all the functional features implemented. But it is slowly getting there. This is also true of most other languages. Most languages (other than functional languages to begin with) have added functional features to various degrees over the years. A good example of the multi paradigm nature of JavaScript is the Array.forEach method. Here is a possible simple implementation. Note that all modern browsers have already implemented this.
 
<nowiki>Array.prototype.forEach = function(callback) {
    for (var i = 0, len = this.length; i < len; ++i) {
        callback(this[i], i, this);
    }
}</nowiki>
In the code above the for-loop part of the code is imperative. Adding to the array prototype and usage of this is object oriented. Passing a function as an argument to another function (callback) is functional and is a feature of functional programming known as “higher order function”. In JavaScript, we take this for granted, passing functions as an argument. Surprisingly this was not a feature found in the most popular languages until recently. eg. You cannot pass functions as arguments in Java, though you can do it indirectly via Interfaces. Same is the case with C, though you can do it indirectly using pointers.
 
Programming Abstractions
What if JavaScript did not have the “passing functions as arguments feature”? The answer to this question is crucial to understanding what functional programming brings to the table. For one, we would not be able to write the Array.forEach function above. And even worse, every time we had to iterate over an array, we would have to write the same for-loop again and again. If we had Array.foreach we need to think only about writing the callback. We need to think only of what to do with each element of the array, and need not concern ourselves with iterating over the array. In other words we have “abstracted” away the iteration part of the problem, and freed ourselves to concentrate on each element of the array.
 
Abstractions are about hiding away implementation details, thereby reducing and factoring out details, so that programmers can focus on the problem at a higher level. Abstractions can be code abstractions, as we saw in the example above, and data abstractions. Objected oriented programming is about combining code and data abstractions into one abstraction called the class. In functional programming we use functional features like first class functions, nested functions, anonymous functions and closures to abstract away code and sometimes even data. Monads are an esoteric feature of functional programming that can even abstract away program structure!
 
Macro's are another feature that allows code abstraction. However macros are not a feature of functional programming. But they are found in all Lisp like functional languages like Lisp, Scheme, Clojure etc. There are attempts to bring Macro's to JavaScript and at the moment it is very much early days.
 
A good example of the power of functional abstractions is jQuery. jQuery is the mother of all functional abstractions. It has abstracted away the JavaScript language itself! So much so that you wouldn't be surprised, if you found a jQuery programmer who knows very little or no JavaScript. As of Feb 2013 there was one publisher who had 32 jQuery books listed, and not a single JavaScript book! And jQuery has achieved so much mostly using only two functional features, functions as arguments and closures.
 
First Class Functions and Closures
The functional programming feature that Brendan Eich implemented in JavaScript was “first class functions”. Functions are first class if they are treated as “first class citizens” of that language. Which implies functions are treated just like all other variables. ie. You can pass them as arguments to functions, you can return them as values from other functions, or you can assign them to variables or data structures. We have seen “passing functions are arguments” earlier. Here is an example of assigning a function to a variable.
 
<nowiki>function greet(name) {
    console.log("Hello " + name);
}
 
greet("John"); // "Hello John"
 
var sayHello = greet;
sayHello("Alex"); // "Hello Alex"</nowiki>
Some programming language theorists consider “anonymous functions” as first class functions. Not to be outdone, Brendan Eich threw anonymous functions into the mix. This is like letting the cat among the pigeons so to speak. But not for Brendan Eich, he knew the solution to the problem. Here is an anonymous function in JavaScript.
 
<nowiki>function(name) {
    console.log(“Hello “ + name);
}</nowiki>
If you noticed we did not give this function a name. After all, it is an anonymous function. If you try to run the code above, you will get an error. Something to the effect “you cannot run the code in this context”. And rightly so. They can only be assigned to something, or passed as arguments to a function.
 
<nowiki>var sayHello = function(name) {
    console.log(“Hello “ + name);
}
sayHello("Jane"); // "Hello Jane"</nowiki>
What if we wanted to change the greeting? Sometimes we would like to say “Hi” instead of “Hello”. We could create a generic “createGreeting” function, which would in turn “compose” another function for you, and return the new composed function. So if we wanted to sat “Hi” it would return a function, and if we wanted to say “Hello” it would return another function that says “Hello”. We can do all that because JavaScript supports first class functions, and we can return functions from other functions. Here is the code.
<nowiki>
function createGreeting(greeting) {
    return function(name) {
        console.log(greeting + " " + name);
    }
}
var sayHi = createGreeting("Hi");
sayHi("Jack"); // "Hi Jack"
var sayHello = createGreeting("Hello");
sayHello("Jack"); // "Hello Jack"</nowiki>
 
The createGreeting function takes a greeting as its argument. The function returns a newly created anonymous function. However the newly created anonymous function was created inside another function createGreeting. So it is also a nested function now. Now since our language supports anonymous functions it will also have to support nested functions. And when we return nested functions from our function we run into another problem. We will look at that in more detail.
 
The anonymous function takes a name argument and prints to the console greeting + name. The variable name is an argument to the anonymous function, and behaves just like any other variable defined within the function. In other words name is “local” to the anonymous function. But this is not true of the variable greeting. It is defined in another function called createGreeting and hence is “non local” to the anonymous function. However the anonymous function can access the variable greeting due to something called lexical scoping.
 
The “scope” of a variable is its “visibility” within a program. “Lexical scope” means that visibility is limited to all the text (code). So when we say “local variables are lexically scoped” within a function, it means that the function's local variables are visible to all the text (code) in the function, even if the code is within another nested function. This also means that when you run the nested function outside the lexically scoped environment, the nested functions non local variable will not be visible. And there lies the problem of returning nested functions from another function. And indeed thats what we are doing here.
 
<nowiki>var sayHi = createGreeting("Hi");</nowiki>
In the line above we assign the returned anonymous function to variable sayHi. And call the function in the next line.
 
<nowiki>sayHi(“Jack”)</nowiki>
We are calling sayHi outside of createGreeting. And the greeting variable is not available outside of createGreeting. The variables it may access in the scope where it was defined, may not be available in the scope where it was actually called. Thats why languages like C don't support nested functions. For this to work the language needs to support another functional programming feature called “closures”. JavaScript supports closures. As matter of fact it has to support closures. Any language that supports first class functions and nested functions has to support closures.
 
A function's closure is a reference to all its non local variables. In the previous example greeting was the non local variable and name was the local variable. A closure is a table of references to all of a functions non local variables. This allows the function to continue to refer to the non local variable even when the function is out of the scope of the variables.
 
 
===Implementing Monads in JavaScript===
UPDATE: This post has been updated to a new post. All the code has been refactored and redone in the new post. http://functionaljavascript.blogspot.in/2013/07/monads.html
 
Consider the problem of doing a series of computations (calling a series of functions), and you want each successive computation to have access to the results of the previous computations. We will write a function called doComputations, and here is how a call to doComputations would look like.
<nowiki>
var result = doComputations(
    "a", function(scope) {
            return 2;
        },
    "b", function(scope) {
            with (scope) {
                return a * 3;
            }
        },
    function(scope) {
        with(scope) {
            return a + b;
        }
    }
);</nowiki>
The arguments to doComputaions are one or more "string - function" pairs and the last argument is just a "result" function. The string is a variable name which will be assigned the value returned from calling the function. So in the first case "a" will be assigned the value 2. What is interesting is that "a" is visible inside the next function whose value gets assigned to "b". And both "a" and "b" are visible inside the last function. Every function is called with a "scope" object which carries key value pairs corresponding to the previous computations carried out. Here we use the "with" statement to scope the "scope" object within the function. If you don't want to use the "with" statement you could access the variable from the scope object directly eg. scope.a, scope.b. The value returned by doComputations is the value returned by the last "result" function, in this case the final value is 8. And here is the definition of doComputations.
<nowiki>
function doComputations() {
    var args = arguments;
    var scope = {};
    function iterator(i) {
        if (args.length === i + 1) {return args[i](scope);}
        var varName = args[i];
        var func = args[i + 1];
        var value = func(scope);
        scope[varName] = value;
        return iterator(i + 2);
    }
    return iterator(0);
}</nowiki>
Inside doComputations we define an iterator function, which recursively iterates over the arguments array of doComputations. In the first line of the iterator function we check to see if we have reached the last "result function", if yes we call it with scope and return the result. In the next three lines we create three variables initialised to the variable name, function, and value returned by calling the function with the scope. In the next line we attach the key-value to scope. And finally we make a recursive call to the iterator to do the next computation. In the last line of doComputations we start the iterator with initial values 0 for the index.
 
Copy the two code fragments above into a file, add the a final line:
<nowiki>console.log(result);</nowiki>
and run it. You should get the result as 8.
 
All this looks like lots of work just to add and multiply a couple of integers, but we have done something useful. For one we have abstracted away the nitty gritty of iterating over computations, with visibility of previous results, into a function called doComputations.
 
Computations are not always so simple and straight forward as the one above. What if we wanted to abort the computations midway for some reason? eg. If any of the functions returns "null" we want to abort the computations. There are many other types of computations and to write a version of doComputations for each type is not a good idea. Instead we could make doComputations call another function between computations so that any thing different, we want to do, is done in this function. This function is passed to doComputations as its first argument. We will call this function "mBind". Now all we have to do is write a version of mBind for every type of computation. For every computation, doComputations will call mBind which in turn will call the next computation. First we write the mBind function to handle null values returned by any computation.
<nowiki>var mBind = function(mValue, mFunction) {
    if (mValue === null)
        return null;
    else
        return mFunction(i + 2);
}</nowiki>
Now the iterator function will call mBind, which is passed as an argument to doComputations, which in turn will recursively call the iterator.
 
<nowiki>function doComputations(mBind) {
    var args = arguments;
    var scope = {};
    function iterator(i) {
        if (args.length === i + 1) {return args[i](scope);}
        var varName = args[i];
        var func = args[i + 1];
        var value = func(scope);
        return mBind(value, function() {
            scope[varName] = value;
            return iterator(i + 2);
        });
    }
    return iterator(1);
}</nowiki>
Below we call doComputations whose first argument is the mBind function. Also we want to abort the computations in case the browser does not support the console.log function.
<nowiki>
var result = doComputations(mBind,
    "a", function(scope) {
        if (typeof console === "object" && console.log)
            return 2;
        else
            return null;
        },
    "b", function(scope) {
          with (scope) {
                return a * 3;
            }
        },
    function(scope) {
        with(scope) {
            return a + b;
        }
    }
);</nowiki>
We can now use doComputations for various types of computations by simply changing the mBind function passed to it. It would be even better if we could predefine the mBind function for various types of computations. And that is what we will do below. We will also change the name of doComputations to doMonad. And we will add mBind as the property of an object called "monad".
 
<nowiki>var maybeMonad = {
    mBind: function(mValue, mFunction) {
        if (mValue === null)
            return null;
        else
            return mFunction(mValue);
    }
};
 
function doMonad(monad) {
    var args = arguments;
    var scope = {};
    function iterator(i) {
        if (args.length === i + 1) {return args[i](scope);}
        var varName = args[i];
        var func = args[i + 1];
        var value = func(scope);
        return monad.mBind(value, function() {
            scope[varName] = value;
            return iterator(i + 2);
        });
    }
    return iterator(1);
}</nowiki>
Compare the above code to the previous listing. It is pretty much the same, except that we have renamed doComputations, and the mBind function is now passed as the property of an object, and this object is called a monad, and in this specific case we called the monad the "maybeMonad". Because "maybe" the computations are carried out, or "maybe" they won't be.
 
A monad MUST have two properties defined for it to be a proper monad. "mBind" and "mResult". We have not seen mResult so far. mResult is a wrapper function for the "result" function. So we add support for mResult in doMonad below. Also we define a new monad called the arrayMonad below and we do some computations with the it.
<nowiki>
function doMonad(monad) {
    var args = arguments, scope = {};
    function iterator(i) {
        if (args.length === i + 1) {
            return monad.mResult(args[i](scope));
        }
        var varName = args[i];
        var func = args[i + 1];
        var value = func(scope);
        return monad.mBind(value, function(value) {
            scope[varName] = value;
            return iterator(i + 2);
        });
    }
    return iterator(1);
}
 
var arrayMonad = {
    mBind: function(mValue, mFunc) {
        var accum = [];
        mValue.forEach(function(elem){
            accum = accum.concat(mFunc(elem));
        });
        return accum;
    },
    mResult: function(value) {
        return [value];
    }
}
 
var result = doMonad(arrayMonad,
    "a", function() {
            return [1, 2];
        },
    "b", function() {
            return [3, 4];
        },
    function(scope) {
        with(scope) {
            return a + b;
        }
    }
);
 
console.log(result);</nowiki>
Running the code above will yield a result of [ 4, 5, 5, 6 ]. The computations using the arrayMonad each return an array. The final result function is called with values a and b, for each element of both arrays. ie it will be called with (1,3), (1,4), (2,3), (2,4). And the addition of each of the elements yields the returned array of [ 4, 5, 5, 6 ].
 
Using the arrayMonad let us implement a two dimensional iterator function in JavaScript called forEach2D. It will take 3 arguments, an iArray, a jArray, and a callback. The callback is called for each value of i and j. Here is the code below.
 
<nowiki>function forEach2D(iArray, jArray, callback) {
    return doMonad(arrayMonad,
        "i", function() {
                return iArray;
            },
        "j", function() {
                return jArray;
            },
        function(scope) {
            with(scope) {
                return callback(i, j);
            }
        }
    );
}
 
var result = forEach2D([1, 2, 3], [4, 5, 6], function(i, j) {
    return [i, j];
});
 
console.log(result);</nowiki>
Running the code above will yield result:[ [1, 4],[1, 5],[1, 6],[2, 4],[2, 5],[2, 6],[3, 4],[3, 5],[3, 6] ]
 
How about a function for iterating over three arrays? A forEach3D function. Easy!
 
<nowiki>function forEach3D(iArray, jArray, kArray, callback) {
    return doMonad(arrayMonad,
        "i", function() {
                return iArray;
            },
        "j", function() {
                return jArray;
            },
        "k", function() {
                return kArray;
            },
        function(scope) {
            with(scope) {
                return callback(i, j, k);
            }
        }
    );
}
 
var result = forEach3D([1, 2], [3, 4], [5, 6], function(i, j, k) {
    return [i, j, k];
});
 
console.log(result);</nowiki>
And running this code will print out: [ [1, 3, 5], [1, 3, 6], [1, 4, 5], [1, 4, 6], [2, 3, 5], [2, 3, 6], [2, 4, 5], [2, 4, 6] ]
 
You can begin to see the power of monads here. They abstract away the complicated part of your code and simplify the problem at hand. Monads are a hard concept to understand, and I hope that I have simplified its understanding here. If there is any part not clear enough please let me know. In the next post I hope to take a more in depth look and monads with some interesting examples.
 
==The monad laws and state monad in JavaScript==
UPDATE: This post has been updated to a new post. All the code has been refactored and redone in the new post. http://functionaljavascript.blogspot.in/2013/07/monads.html
 
In the previous post (please read the previous post before reading this post) we implemented three monads, the identity monad, maybe monad and array monad. However we did not get into the details of monads. We will do that in this post. Also we will implement the state monad. Here is an identity monad example.
 
<nowiki>var identityMonad = {
    mBind: function(mValue, mFunction) {
        return mFunction(mValue);
    },
    mResult: function(value) {
        return value;
    }
};
 
var result = doMonad(identityMonad,
    "a", function(scope) {
            return 2;
        },
    "b", function(scope) {
            with (scope) {
                return a * 3;
            }
        },
    function(scope) {
        with(scope) {
            return a + b;
        }
    }
);
 
console.log(result);</nowiki>
First we define the identityMonad that we use when calling doMonad. Each computation in doMonad MUST return a monadic value. eg. the first computation returns 2 a monadic value. However what gets assigned to "a" is not the monadic value but the value. This distinction is important. However in the case of the identity monad both the monadic value and value are the same.
 
The mBind function takes a monadic value and a monadic function as its arguments. mBind then extracts the "value" out of the "monadic value" and calls the monadic function with the value. In this case no extraction is done because both are equal for the identity monad. The monadic function takes a "value" and returns a "monadic value".
 
In the next computation "a" is available as the value and not the monadic value. In the result computation both "a" and "b" are available and we return "value a + b". Since we return a value and not a monadic value in the final computation, the transformation from "value" to "monadic value" is done by the mResult function which takes a value and returns a monadic value. In this case it returns the argument itself because the value and monadic value are the same. So mResult is the identity function, and thats why it is called the identity monad.
 
We will look at a case where the "value" and "monadic value" are not the same. Using the array monad we will write a map function that doubles each element of an array.
<nowiki>
var arrayMonad = {
    mBind: function(mValue, mFunc) {
        var accum = [];
        mValue.forEach(function(elem){
            accum = accum.concat(mFunc(elem));
        });
        return accum;
    },
    mResult: function(value) {
        return [value];
    }
};
 
var result = doMonad(arrayMonad,
    "i", function() {
            return [1, 2, 3];
        },
    function(scope) {
        with(scope) {
            return i * 2;
        }
    }
);</nowiki>
Running the above code will print the result [ 2, 4, 6 ]. The first function in doMonad returns a monadic value which is an array. However the "value" in a monadic value of array type is the value of each element. It is mBinds job to extract each value, and call the monadic function for each value, and thats what it does exactly.
 
The result function returns i * 2 which is of type integer. However all monadic functions of a given monad MUST return monadic values of the same type. It is mResults job to convert the result type from integer to array. And that is what it does exactly.
 
The monad laws
 
To write our own monads, our monads must obay the monad laws.
 
# mBind(mResult(x), mFunction) is equal to mFunction(x).
# mBind(mValue, mResult) is equal to mValue.
# mBind(mBind(mValue, mFunction), mFunction2) is equal to mBind(mValue, function(x){return mbind(mFunction(x), mFunction2)}).
Now let us check to see if our array monad follows the monad laws.
<nowiki>
var mBind = arrayMonad.mBind;
var mResult = arrayMonad.mResult;
var mValue = [1, 2, 3];
var x = 4;
var mFunction = function(x) {
    return [x * 2];
};
var mFunction2 = function(x) {
    return [x * 3];
};
 
// Check first law
console.log(mBind(mResult(x), mFunction));  // [ 8 ]
console.log(mFunction(x));  // [ 8 ]
 
//Check second Law
console.log(mBind(mValue, mResult));  // [ 1, 2, 3 ]
console.log(mValue);  // [ 1, 2, 3 ]
 
//Check third Law
console.log(mBind(mBind(mValue, mFunction), mFunction2));  // [ 6, 12, 18 ]
console.log(mBind(mValue, function(x){return mBind(mFunction(x), mFunction2)}));  // [ 6, 12, 18 ]</nowiki>


===The State Monad===
/etc/init.d/spamassassin start


So far we saw monadic values of types integer and array. But monadic values can also be functions! After all JavaScript supports first class functions, that can be treated as values. The state monad is just such a monad. The monadic value is a function. However it is important to differentiate between a monadic function and a monadic value of type function.
* Редактируем файл постфикса /etc/postfix/master.cf
- Строку:
..
smtp      inet  n      -      -      -      -      smtpd
..
- Заменяем на:
..
smtp      inet  n      -      -      -      -      smtpd -o content_filter=spamassassin
..


The monadic function in a state monad, just like all monadic functions takes a value and returns a monadic value. It so happens that this monadic value is a function.
- Перед:
..
dbmail-lmtp    unix    -      -      n      -      -      lmtp
        -o disable_dns_lookups=yes
..


The monadic value in a state monad is a function that takes a state, and returns a two element array, with a value and the new state respectively. The state can be of any type. it can be a an integer, string, array object or any other valid type.
- Добавляем:
..
spamassassin unix  -  n  n  -  -  pipe  user=debian-spamd argv=/usr/bin/spamc -s 5120000 -f -e /usr/sbin/sendmail -oi -f
${sender}${recipient}
..


In the next example we maintain an immutable stack array over a set of computations. We define two monadic functions "push" and "pop".
* Перезапускаем '''postfix''':
   
   
  <nowiki>
  /etc/init.d/postfix restart
var stateMonad = {
    mBind: function(mValue, mFunc) {
        return function(state) {
            var compute = mValue(state);
            var value = compute[0];
            var newState = compute[1];
            return mFunc(value)(newState);
        };
    },
    mResult: function(value) {
        return function(state) {
            return [value, state];
        };
    }
};
 
var push = function(value) {
    return function(state) {
        var newstate = [value];
        return [undefined, newstate.concat(state)];
    };
};
 
var pop = function() {
    return function(state) {
        var newstate = state.slice(1);
        return [state[0], newstate];
    };
};
 
var result = doMonad(stateMonad,
    "a", function(scope) {
            return push(5);
        },
    "b", function(scope) {
            with (scope) {
                return push(10);
            }
        },
    "c", function(scope) {
            with (scope) {
                return push(20);
            }
        },
    "d", function(scope) {
            with (scope) {
                return pop();
            }
        },
    function(scope) {
        with(scope) {
            return d;
        }
    }
);
 
console.log(result([]));</nowiki>
 
Running the code above will print [ 20, [ 10, 5 ] ].
 
20 is the value of the last "pop" computation, and the second value is the final state of the stack.
 
First we will look at mResult. mResult is a monadic function that takes a value and returns a monadic value, which is a function that takes a state and returns an array with value and state.
 
mBind returns a monadic value which is a function. So the result of doMonad is a function which you must call with an initial value for the stack. Which is [] in our case. Remember mBind is called with a monadic value and a monadic function. It has to extract the value out of the monadic value and call the monadic function with the value. Which it does in the three lines inside the returned monadic function. Notice that mFunc is called with the extracted value, and since its return value is a monadic value of type function, the function is called immediately with the new state.
 
All the code in the last two posts are available as a monad library on github.
 
==The Promise Monad in JavaScript==
UPDATE: This post has been updated to a new post. All the code has been refactored and redone in the new post. http://functionaljavascript.blogspot.in/2013/07/monads.html
 
If you find it difficult to understand whats going on below, read the following posts.
Implementing Monads in JavaScript
The monad laws and state monad in JavaScript.
 
We will go through an example of the promise monad in this post. The promise monad is available now in the monadjs library. The best way is too look at an example. We will write a nodejs command line program that will copy an input file into an output file asynchronously. We can run the program like this.
 
<nowiki>$ node copy.js infile outfile</nowiki>
The program has to do the following.
 
Check the command line for infile and verify it exists, if not print an error and halt computations.
Check if outfile is given in the command line otherwise print error and halt.
Read infile content into memory and halt if error.
Write content to outfile or print error to console.
Halting of program in case of error to be done without throwing errors.
Computations (1) (3) and (4) are asynchronous. (2) is synchronous because we are only checking process.argv.
 
The monadic values of the promise monad are functions that take a continuation and promise to call the continuation either asynchronously or synchronously. The continuation is called with a value which is the result of the last computation. If the continuation is called with "null" the computations are halted.
 
All the computations have access to the results of the previous computations via the "scope" variable. The results are stored in the variable names you give.
Here is the source code of copy.js
<nowiki>
var monads = require("monadjs");
var fs = require("fs");
 
monads.doMonad(monads.promiseMonad,
    "infile",
        function(scope) {
            return function(continuation) {
                var fname = process.argv[2] || "";
                fs.exists(fname, function (exists) {
                    if (exists) {
                        continuation(fname);
                    } else {
                        console.log("File does not exist: " + fname);
                        continuation(null);
                    }
                });
            }
        },
    "outfile",
        function(scope) {
            return function(continuation) {
                var fname = process.argv[3];
                if (fname) {
                    continuation(fname);
                } else {
                    console.log("Output File Name is Required");
                    continuation(null);
                }
            }
        },
    "contents",
        function(scope) {
            return function(continuation) {
                fs.readFile(scope.infile, function (err, data) {
                    if (err) {
                        console.log("Error reading File: " + scope.infile);
                        continuation(null);
                    } else {
                        continuation(data);
                    }
                });
            }
        },
    function(scope) {
        fs.writeFile(scope.outfile, scope.contents, function (err) {
            if (err) {
                console.log("Error writing File: " + scope.outfile);
            }
        });
    }
);</nowiki>
 
I don't think the promise monad obeys the monad laws. But it works. It works only for sequential asynchronous calls though. What is interesting is that it allows you to break the program structure into bite size pieces and call them sequentially. Notice also the promise monad is implemented purely functionally, and no timing loops used.
 
However you don't have to actually use the promise monad from this library. I have refactored and simplified everything in a simple promise library you can find here.
<nowiki>/*
    dopromise
    Promise Library for JavaScript
    Copyright (c) 2013 Santosh Rajan
    License - MIT - https://github.com/santoshrajan/dopromise/blob/master/LICENSE
*/
 
(function(exports){
 
    var serial = function() {
        var args = arguments, scope = {}
        function iterator(i) {
            var func = args[i]
            if (args.length === i + 1) {
                func.call(scope)
            } else {
                func.call(scope, function() {
                    iterator(i + 1)
                })
            }
        }
        iterator(0);
    }
 
    var parallel = function() {
        var args = Array.prototype.slice.call(arguments),
            last = args.pop()
            counter = args.length
            scope = {}
            done = function() {
                --counter
                if (counter === 0) {
                    last.call(scope)
                }
            }
 
        args.forEach(function(f) {
            f.call(scope, done)
        })
    }
 
    var loop = function(f) {
        var scope = {}
        function iterator() {
            f.call(scope, iterator)
        }
        iterator()
    }
 
    exports.version = "0.0.4"
    exports.doPromise = serial  // for backward compatability
    exports.serial = serial
    exports.parallel = parallel
    exports.loop = loop
 
})(typeof exports === 'undefined'? this.dopromise={}: exports);</nowiki>
 
===Functors===
 
[http://functionaljavascript.blogspot.ru/2013/07/functors.html Источник]
 
Consider the function below.
 
<nowiki>function plus1(value) { 
    return value + 1 
}</nowiki> 
It is just a function that takes an integer and adds one to it. Similarly we could could have another function plus2. We will use these functions later.
 
<nowiki>function plus2(value) { 
    return value + 2 
}</nowiki> 
And we could write a generalised function to use any of these functions as and when required.
 
<nowiki>function F(value, fn) { 
    return fn(value) 
}
 
F(1, plus1) ==>> 2</nowiki>
This function will work fine as long as the value passed is an integer. Try an array.
 
<nowiki>F([1, 2, 3], plus1)  ==>> '1,2,31'</nowiki>
Ouch. We took an array of integers, added an integer and got back a string! Not only did it do the wrong thing, we ended up with a string having started with an array. In other words our program also trashed the structure of the input. We want F to do the "right thing". The right thing is to "maintain structure" through out the operation.
 
So what do we mean by "maintain structure"? Our function must "unwrap" the given array and get its elements. Then call the given function with every element. Then wrap the returned values in a new Array and return it. Fortunately JavaScript just has that function. Its called map.
 
<nowiki>[1, 2, 3].map(plus1)  ==>> [2, 3, 4]</nowiki>
And map is a functor!
 
A functor is a function, given a value and a function, does the right thing.
 
''To be more specific.''
 
A functor is a function, given a value and a function, unwraps the values to get to its inner value(s), calls the given function with the inner value(s), wraps the returned values in a new structure, and returns the new structure.
 
Thing to note here is that depending on the "Type" of the value, the unwrapping may lead to a value or a set of values.
 
Also the returned structure need not be of the same type as the original value. In the case of map both the value and the returned value have the same structure (Array). The returned structure can be any type as long as you can get to the individual elements. So if you had a function that takes and Array and returns value of type Object with all the array indexes as keys, and corresponding values, that will also be a functor.
 
In the case of JavaScript, filter is a functor because it returns an Array, however forEach is not a functor because it returns undefined. ie. forEach does not maintain structure.
 
Functors come from category theory in mathematics, where functors are defined as "homomorphisms between categories". Let's draw some meaning out of those words.
 
homo = same
morphisms = functions that maintain structure
category = type
According to the theory, function F is a functor when for two composable ordinary functions f and g
 
<nowiki>F(f . g) = F(f) . F(g)</nowiki>
where . indicates composition. ie. functors must preserve composition.
 
So given this equation we can prove wether a given function is indeed a functor or not.
 
====Array Functor====
We saw that map is a functor that acts on type Array. Let us prove that the JavaScript Array.map function is a functor.
 
<nowiki>function compose(f, g) {
    return function(x) {return f(g(x))}
}</nowiki>
Composing functions is about calling a set of functions, by calling the next function, with results of the previous function. Note that our compose function above works from right to left. g is called first then f.
 
<nowiki>[1, 2, 3].map(compose(plus1, plus2))  ==>> [ 4, 5, 6 ]
 
[1, 2, 3].map(plus2).map(plus1)        ==>> [ 4, 5, 6 ]</nowiki>
Yes! map is indeed a functor.
 
Lets try some functors. You can write functors for values of any type, as long as you can unwrap the value and return a structure.
 
String Functor
So can we write a functor for type string? Can you unwrap a string? Actually you can, if you think of a string as an array of chars. So it is really about how you look at the value. We also know that chars have char codes which are integers. So we run plus1 on every char charcode, wrap them back to a string and return it.
<nowiki>
function stringFunctor(value, fn) { 
    var chars = value.split("") 
    return chars.map(function(char) { 
        return String.fromCharCode(fn(char.charCodeAt(0))) 
    }).join("") 
}
stringFunctor("ABCD", plus1) ==>> "BCDE" 
</nowiki>
You can begin to see how awesome functors are. You can actually write a parser using the string functor as the basis.
 
====Function Functor====
In JavaScript functions are first class citizens. That means you can treat functions like any other value. So can we write a functor for value of type function? We should be able to! But how do we unwrap a function? You can unwrap a function by calling it and getting its return value. But we straight away run into a problem. To call the function we need its arguments. Remember that the functor only has the function that came in as the value. We can solve this by having the functor return a new function. This function is called with the arguments, and we will in turn call the value function with the argument, and call the original functors function with the value returned!
<nowiki>
function functionFunctor(value, fn) { 
    return function(initial) { 
        return function() { 
            return fn(value(initial)) 
        } 
    } 
}
 
var init = functionFunctor(function(x) {return x * x}, plus1) 
var final = init(2) 
final() ==> 5</nowiki>
 
Our function functor really does nothing much, to say the least. But there a couple things of note here. Nothing happens until you call final. Every thing is in a state of suspended animation until you call final. The function functor forms the basis for more awesome functional stuff like maintaining state, continuation calling and even promises. You can write your own function functors to do these things!
 
====MayBe Functor====
<nowiki>function mayBe(value, fn) {
    return value === null || value === undefined ? value : fn(value)
}</nowiki>
Yes, this is a valid functor.
 
<nowiki>mayBe(undefined, compose(plus1, plus2))    ==>> undefined
mayBe(mayBe(undefined, plus2), plus1)      ==>> undefined
mayBe(1, compose(plus1, plus2))            ==>> 4
mayBe(mayBe(1, plus2), plus1)              ==>> 4</nowiki>
 
So mayBe passes our functor test. There is no need for unrapping or wrapping here. It just returns nothing for nothing. Maybe is useful as a short circuiting function, which you can use as a substitute for code like
 
<nowiki>if (result === null) {
    return null
} else {
    doSomething(result)
}</nowiki>
====Identity Function====
<nowiki>function id(x) {
    return x
}</nowiki>
 
The function above is known as the identity function. It is just a function that returns the value passed to it. It is called so, because it is the identity in composition of functions in mathematics.
 
We learned earlier that functors must preserve composition. However something I did not mention then, is that functors must also preserve identity. ie.
 
<nowiki>F(value, id) = value</nowiki>
Lets try this for map.
 
<nowiki>[1, 2, 3].map(id)    ==>>  [ 1, 2, 3 ]</nowiki>
 
===Type Signature===
The type signature of a function is the type of its argument and return value. So the type signature of our plus1 function is
 
<nowiki>f: int -> int</nowiki>
The type signature of the functor map depends on the type signature of the function argument. So if map is called with plus1 then its type signature is
 
<nowiki>map: [int] -> [int]</nowiki>
However the type signature of the given function need not be the same as above. We could have a function like
 
<nowiki>f: int -> string</nowiki>
in which the type signature of map would be


<nowiki>map: [int] -> [string]</nowiki>
* Проверяем работу почты, все должно работать...  
The only restriction being that the type change does not affect the composability of the functor. So in general a functor's type signature can


<nowiki>F: A -> B</nowiki>
In other words map can take an array of integers and return an array of strings and would still be a functor.


Monads are a special case of Functors whos type signature is
<hr>


<nowiki>M: A -> A</nowiki>
Источники:
<hr>
* [https://www.opennet.ru/docs/RUS/dbmail_postfix/ Почтовый сервер на основе реляционной СУБД.]
* [http://library.mobrien.com/dbmailadministrator/ GUI-конфигуратора DbMail Administrator (DBMA), написанного на Perl]
* [https://habrahabr.ru/post/37195/ Настройка exim+postgresql+dbmail+spamassassin...]
* [https://www.opennet.ru/docs/RUS/dbmail/#dbmail_fs Создание почтовой системы на базе exim, dbmail, amavisd-new и postgresql]
* [https://www.opennet.ru/docs/RUS/dbmail_postfix/ Почтовый сервер на основе реляционной СУБД]
* [https://habrahabr.ru/post/211078/ Почтовый сервер с хранением данных в PostgreSQL]
* [https://www.opennet.ru/base/net/exim_intro.txt.html  Exim (exim mail mta virtual spam virus clamav freebsd imap postgresql)]
* [http://www.linuxcenter.ru/lib/articles/soft/ezh_mailsystem.phtml?style=print Создание почтовой системы на базе exim, dbmail, amavisd-new и postgresql]
* [https://www.lissyara.su/archive/exim+dbmail/ Exim и dbmail]
* [https://vovanys.com/linux/pochtovyj-server-pod-ubuntu-server-svyazka-dbmail-postfix-sasl-spamassassin-clamav/ Почтовый сервер под Ubuntu Server: связка DBmail + Postfix + sasl + spamassassin + clamav]
* [http://samag.ru/archive/article/608 Почтовый сервер на основе реляционной СУБД - переработанное]
* [http://www.wertup.ru/ubuntu/mail-server Почтовый сервер cвязка DBmail + Postfix + sasl + spamassassin + clamav + DBMA + Roundcube webmail]
* [https://www.lissyara.su/articles/freebsd/mail/postfix+dbmail/ Почтовая система Postfix + DBMail + SASL2 + TLS + DSpam + ClamAV + RoundCubeWebMail]
* [http://www.dbmail.org/dokuwiki/doku.php/stunnel How to set up and use encrypted connections with DBmail]
* [https://notessysadmin.com/postfix-perenapravlenie-pochty Postfix. Перенаправление почты]
* [https://toster.ru/q/53106 Postfix пересылка всей входящей почты на другой ящик]
* [https://code.google.com/archive/p/simple-dbmail-admin/downloads web admin dbmail]

Версия от 15:53, 2 мая 2018

Руководство для быстрого развертывания собственного сервера почты.

  • Данная статья появилась тут в связи с тем, что я столкнулся с проблемой переноса почтового сервера на обычной файловой системе.

В первую очередь с тем, что почта была организована на уже устаревшем ПО и перенос ее на новую платформу без потерь стал практически не возможен. А вот хранение почты в базе данных, дает огромные преимущества при обновлении или доступе к информации, а так же восстановлении. В частности у меня база данных находится на другом хосте, что сильно облегчает ее обслуживание, при этом все конфигурационные файлы самой почты можно легко повторить если понадобится на новом хосте для создания почтового сервера заново.

1. Порядок установки dbmail

  • Система Debian Stretch {9}
  • Используемый source.list
# 
deb http://mirror.mephi.ru/debian/ stretch main
deb-src http://mirror.mephi.ru/debian/ stretch main

deb http://security.debian.org/debian-security stretch/updates main
deb-src http://security.debian.org/debian-security stretch/updates main 

# stretch-updates, previously known as 'volatile'
deb http://mirror.mephi.ru/debian/ stretch-updates main
deb-src http://mirror.mephi.ru/debian/ stretch-updates main

###### Debian Main Repos
deb http://deb.debian.org/debian/ stable main contrib non-free
deb-src http://deb.debian.org/debian/ stable main contrib non-free

deb http://deb.debian.org/debian/ stable-updates main contrib non-free
deb-src http://deb.debian.org/debian/ stable-updates main contrib non-free

deb http://deb.debian.org/debian-security stable/updates main contrib non-free
deb-src http://deb.debian.org/debian-security stable/updates main contrib non-free

deb http://ftp.debian.org/debian stretch-backports main contrib non-free
deb-src http://ftp.debian.org/debian stretch-backports main contrib non-free

1.1 Устанавливаем необходимые пакеты:

apt-get install pkg-config libglib2.0-dev libgmime-2.6-dev libmhash-dev libevent-dev libssl-dev libzdb-dev\
autoconf automake libtool autotools-dev dpkg-dev fakeroot debhelper dh-make libldap2-dev libsieve2-dev ascidoc\
libcrypto++6 libcrypto++-utils libcrypto++-dev xmlto xmltoman libarchive-tools lrzip binutils-multiarch\
arch-test libpgf-dev libsasl2-modules-db libsasl2-modules curl libcroco3 libsasl2-2 procmail libsasl2-modules-sql\
libpcre32-3 zlib1g-dev libmhash-dev libpcrecpp0v5 

1.2 Скачиваем с dbmail.org исходники:

wget -c -t 0 -T 8 http://www.dbmail.org/download/3.1/dbmail-3.1.17.tar.gz

1.3 Распаковываем и компилируем:

cp dbmail-3.1.17.tar.gz /usr/local/src
tar -xf dbmail-3.1.17.tar.gz /usr/local/src.dbmail-3.1.7
cp dbmail-3.1.17.tar.gz /usr/local/src/dbmail_3.1.7.orig.tar.gz
  • [!] - не знаю, может так у меня получилось, но когда применяешь комменты, версия которая высвечивается именно 3.1.7!!
  • [!] - именно поэтому все, что тут распаковываем и создаем имеет версию - 3.1.7 ...

Готовим пакет к сборке:

cd /usr/local/src/dbmail-3.1.7
./configure --prefix=/usr
 
dpkg-source --commit

даем имя, что-то: pgsql.commit
выходим по ESC
должно быть так:

...
dpkg-source: инфо: локальные изменения были записаны в новую заплату: dbmail-3.1.7/debian/patches/pgsql.commit

далее:

cd /usr/local/src/
dpkg-source -b dbmail-3.1.7
cd /usr/local/src/dbmail-3.1.7
dpkg-buildpackage -d
  • [!] - если у вас появилось сообщение типа:
...
debian/rules:138: *** missing separator (did you mean TAB instead of 8 spaces?).  Останов.
dpkg-buildpackage: ошибка: debian/rules clean возвратил код ошибки 2
  • [!] - то необходимо исправить ошибку в файле dbmail-3.1.7/debian/rules
строка 138: 
........make -f debian/rules binary-common $* DH_OPTIONS=-p$*
     ^^^
   здесь 8 пробелов!! - а должно быть 2 табуляции, что и вызывает ошибку...
  • после того как соберется пакет, дожно быть так:
# ls -n /usr/local/src
итого 3668
drwxrwxr-x 13 0  0    4096 ноя  2 00:19 dbmail-3.1.7
-rw-r--r--  1 0 50    7597 ноя  2 00:19 dbmail_3.1.7-1_amd64.buildinfo
-rw-r--r--  1 0 50    1957 ноя  2 00:19 dbmail_3.1.7-1_amd64.changes
-rw-r--r--  1 0 50  349256 ноя  2 00:19 dbmail_3.1.7-1_amd64.deb
-rw-r--r--  1 0 50  148008 ноя  2 00:14 dbmail_3.1.7-1.debian.tar.xz
-rw-r--r--  1 0 50    1045 ноя  2 00:14 dbmail_3.1.7-1.dsc
-rw-r--r--  1 0  0 2391054 июл 27  2014 dbmail_3.1.7.orig.tar.gz
-rw-r--r--  1 0 50  838508 ноя  2 00:19 dbmail-dbgsym_3.1.7-1_amd64.deb
  • копируем себе в архив и ставим пакет.
dpkg -i dbmail_3.1.7-1_amd64.deb
  • правим файл конфигурации:
editor /etc/dbmail/dbmail.conf
  • пример рабочего конфигурационного файла:
# (c) 2000-2006 IC&S, The Netherlands 
#
# Configuration file for DBMAIL 

[DBMAIL] 
# 
# Database settings
#
# database connection URI

#dburi                = sqlite:///var/tmp/dbmail.db
dburi                = postgresql://dbmail:dbmailpass@10.0.5.2:5432/mailbasename
# 
# Supported drivers are sql, ldap.
#
authdriver           = sql

# 
# 
# following fields are now DEPRECATED!
driver               = postgresql
host                 = 10.0.5.2
sqlport              = 5432
#sqlsocket            =              
user                 = dbmail
pass                 = dbmailpass
db                   = mailbasename

#
# Number of database connections per threaded daemon
# This also determines the size of the worker threadpool
#
# Do NOT increase this without proper consideration. A
# very large database/worker pool will not only increase
# the connection pressure on the database, but will more
# significantly cause unnecessary context-switching in 
# your CPUs.
#
#max_db_connections   = 10

# 
# Table prefix. Defaults to "dbmail_" if not specified.
#
table_prefix         = dbmail_   

# 
# encoding must match the database/table encoding.
# i.e. latin1, utf8
encoding             = utf8

#
# messages with unknown encoding will be assumed to have 
# default_msg_encoding
# i.e. iso8859-1, utf8
default_msg_encoding = utf8

# 
# Postmaster's email address for use in bounce messages.
#
#postmaster           = DBMAIL-MAILER       

# 
# Sendmail executable for forwards, replies, notifies, vacations.
# You may use pipes (|) in this command, for example:
# dos2unix|/usr/sbin/sendmail  works well with Qmail.
# You may use quotes (") for executables with unusual names.
#
sendmail              = /usr/sbin/sendmail     

#
#
# The following items can be overridden in the service-specific sections.
#
#

#
# Logging via stderr/log file and syslog
#
# Logging is broken up into 8 logging levels and each level can be indivually turned on or off.
# The Stderr/log file logs all entries to stderr or the log file.
# Syslog logging uses the facility mail and the logging level of the event for logging.
# Syslog can then be configured to log data according to the levels.
#
# Set the log level to the sum of the values next to the levels you want to record.
#   1 = Emergency 
#   2 = Alert
#   4 = Critical
#   8 = Error
#  16 = Warning
#  32 = Notice
#  64 = Info
# 128 = Debug
# 256 = Database -> Logs at debug level
#
# Examples:   0 = Nothing
#            31 = Emergency + Alert + Critical + Error + Warning
#           511 = Everything
#
file_logging_levels       = 7
#
syslog_logging_levels     = 31

#
# Generate a log entry for database queries for the log level at number of seconds of query execution time.
#
query_time_info       = 10
query_time_notice     = 20
query_time_warning    = 30

#
# Throw an exception is the query takes longer than query_timeout seconds
query_timeout         = 300 

# 
# Root privs are used to open a port, then privs
# are dropped down to the user/group specified here.
#
effective_user        = dbmail
effective_group       = mail

# 
# The IPv4 and/or IPv6 addresses the services will bind to.
# Use * for all local interfaces.
# Use 127.0.0.1 for localhost only.
# Separate multiple entries with spaces ( ) or commas (,).
#
bindip                = 0.0.0.0         # IPv4 only - all IP's
#bindip                = ::             # IPv4 and IPv6 - all IP's (linux)
#bindip                = ::             # IPv6 only - all IP's (BSD)
#bindip                = 0.0.0.0,::     # IPv4 and IPv6 - all IP's (BSD)


#
# The maximum length of the queue of pending connections. See
# listen(2) for more information
#
# backlog              = 128

# 
# Idle time allowed before a connection is shut off.
#
timeout               = 300             

# 
# Idle time allowed before a connection is shut off if you have not logged in yet.
#
login_timeout         = 60

# 
# If yes, resolves IP addresses to DNS names when logging.
#
resolve_ip            = yes

#
# If yes, keep statistics in the authlog table for connecting users
#
authlog               = no

# 
# logfile for stdout messages
#
logfile               = /var/log/dbmail.log        

# 
# logfile for stderr messages
#
errorlog              = /var/log/dbmail.err        

# 
# directory for storing PID files
#
pid_directory         = /var/run/dbmail

#
# directory for locating libraries (normally has a sane default compiled-in)
#
library_directory       = /usr/lib/dbmail

#
# SSL/TLS certificates
#
# A file containing a list of CAs in PEM format
tls_cafile            =

# A file containing a PEM format certificate
tls_cert              =

# A file containing a PEM format RSA or DSA key
tls_key               =

# A cipher list string in the format given in ciphers(1)
tls_ciphers           =


# hashing algorithm. You can select your favorite hash type
# for generating unique ids for message parts. 
#
# for valid values check mhash(3) but minus the MHASH_ prefix.
#  
# if you ever change this value run 'dbmail-util --rehash' to 
# update the hash for all mimeparts.
#
# examples: MD5, SHA1, SHA256, SHA512, TIGER, WHIRLPOOL
#
# hash_algorithm = SHA1


# header_cache tuning
#
# set header_cache_readonly to 'yes' to prevent new
# unknown header-names from being cached.
#
# header_cache_readonly = yes



[LMTP]
bindip = 127.0.0.1
port                  = 24                 
#tls_port              =


[POP]
port                  = 110
#tls_port              = 995

# You can set an alternate banner to display when connecting to the service
# banner = DBMAIL pop3 server ready to rock

# 
# If yes, allows SMTP access from the host IP connecting by POP3.
# This requires addition configuration of your MTA
#
pop_before_smtp       = no      

[HTTP]
port                  = 41380
#
# the httpd daemon provides full access to all users, mailboxes
# and messages. Be very careful with this one!
bindip                = 127.0.0.1
admin                 = admin:secret

[IMAP]
# You can set an alternate banner to display when connecting to the service
# banner = imap 4r1 server (dbmail 2.3.x)

# 
# Port to bind to.
#
port                  = 143                
##tls_port              = 993

# 
# IMAP prefers a longer timeout than other services.
#
timeout               = 4000            

# 
# If yes, allows SMTP access from the host IP connecting by IMAP.
# This requires addition configuration of your MTA
#
imap_before_smtp      = no

#
# during IDLE, how many seconds between checking the mailbox
# status (default: 30)
#
# idle_timeout          = 30

# during IDLE, how often should the server send an '* OK' still
# here message (default: 10)
#
# the time between such a message is idle_timeout * idle_interval
# seconds
#
# idle_interval         = 10

#
# If TLS is enabled, login before starttls is normally
# not allowed. Use login_disabled=no to change this
#
# login_disabled        = yes

#
# Provide a CAPABILITY to override the default
#
# capability   = IMAP4 IMAP4rev1 AUTH=LOGIN ACL RIGHTS=texk NAMESPACE CHILDREN SORT QUOTA THREAD=ORDEREDSUBJECT UNSELECT IDLE

# max message size. You can specify the maximum message size
# accepted by the IMAP daemon during APPEND commands.
#
# Supported formats:
#  decimal: 1000000    
#  octal:   03777777
#  hex:     0xfffff
#
# max_message_size      =


[SIEVE]
# 
# Port to bind to.
#
port                  = 2000               
tls_port              =


[LDAP]
port                  = 389
version               = 3
hostname              = ldap
base_dn               = ou=People,dc=mydomain,dc=com

# 
# If your LDAP library supports ldap_initialize(), then you can use the
# alternative LDAP server DSN like following.
#
# URI                = ldap://127.0.0.1:389
# URI                = ldapi://%2fvar%2frun%2fopenldap%2fldapi/

# 
# Leave blank for anonymous bind.
# example: cn=admin,dc=mydomain,dc=com     
#
bind_dn               = 

# 
# Leave blank for anonymous bind.
#
bind_pw               = 
scope                 = SubTree

# AD users may want to set this to 'no' to disable
# ldap referrals if you are seeing 'Operations errors' 
# in your logs
#
referrals             = yes

user_objectclass      = top,account,dbmailUser
forw_objectclass      = top,account,dbmailForwardingAddress
cn_string             = uid
field_passwd          = userPassword
field_uid             = uid
field_nid             = uidNumber
min_nid               = 10000
max_nid               = 15000
field_cid             = gidNumber
min_cid               = 10000
max_cid               = 15000

# a comma-separated list of attributes to match when searching
# for users or forwards that match a delivery address. A match
# on any of them is a hit.
field_mail            = mail

# field that holds the mail-quota size for a user.
field_quota           = mailQuota

# field that holds the forwarding address. 
field_fwdtarget       = mailForwardingAddress

# override the query string used to search for users 
# or forwards with a delivery address.
# query_string          = (mail=%s)

[DELIVERY]
# 
# Run Sieve scripts as messages are delivered.
#
SIEVE                 = yes               

# 
# Use 'user+mailbox@domain' format to deliver to a mailbox.
#
SUBADDRESS            = yes          

# 
# Turn on/off the Sieve Vacation extension.
#
SIEVE_VACATION        = yes      

# 
# Turn on/off the Sieve Notify extension
#
SIEVE_NOTIFY          = yes

# 
# Turn on/off additional Sieve debugging.
#
SIEVE_DEBUG           = no          


# Use the auto_notify table to send email notifications.
#
AUTO_NOTIFY           = no
 
# 
# Use the auto_reply table to send away messages.
#
AUTO_REPLY            = no

# 
# Defaults to "NEW MAIL NOTIFICATION"
#
#AUTO_NOTIFY_SUBJECT        =    

# 
# Defaults to POSTMASTER from the DBMAIL section.
#
#AUTO_NOTIFY_SENDER        =   


# If you set this to 'yes' dbmail will check for duplicate
# messages in the relevant mailbox during delivery using 
# the Message-ID header
#
suppress_duplicates     = no

#
# Soft or hard bounce on over-quota delivery
#
quota_failure           = hard


# end of configuration file

  • правим default конфигурационный файл - /etc/default/dbmail
# debian specific configuration for dbmail

# work-around for linux/epoll bug in libevent
export EVENT_NOEPOLL=yes

# comment out to disable the pop3 server
START_POP3D=true

# comment out to disable the imapd server
START_IMAPD=true

# uncomment to enable the lmtpd server
START_LMTPD=true

# uncomment to enable the timsieved server
#START_SIEVE=true

# comment out to enable the stunnel SSL wrapper
START_SSL=true

# specify the filename for the pem file as 
# it resides in /etc/ssl/certs
PEMFILE="/etc/ssl/serts/dbmail.pem"
  • создаем сертификат для dbmail:
cd /etc/ssl/certs
openssl req -new -x509 -nodes -out dbmail.pem -keyout smtpd.pem -days 3650
  • перезапуск службы:
systemctl restart dbmail
  • Краткое пояснение:
1. Предназначенные для доставки сообщений от MTA в хранилище.
2. Предназначенные для доставки MUA из хранилища.
  • К первым относятся:

dbmail-lmtpd – UNIX-демон, принимающий клиентские подключения через UNIX-сокет или TCP-сокет. Для приема почтовых сообщений используется протокол LMTP. На каждое входящее сообщение MTA создает только клиентский сокет, необходимое количество процессов и подключений к БД создается заранее.
Таким образом, этот вариант обеспечивает лучшую производительность при высокой нагрузке, но при низкой он потребляет больше системных ресурсов, чем необходимо.

  • Ко вторым относятся:

dbmail-pop3d – демон для доступа по протоколу POP3.
dbmail-imapd – демон для доступа по протоколу IMAP.

  • Кроме того, в состав DBMail входят следующие вспомогательные утилиты:

dbmail-users – инструмент для управления пользователями и их псевдонимами (возможно, многим из вас будет привычнее термин alias).
dbmail-util – инструмент для очистки, оптимизации и проверки корректности БД.

  • С установкой dbmail пока окончено, следующий этап установка postgesql и настройка для будущей работы.


2. Настройка PostgreSQL

2.1. После того как мы настроили базу данных postgresql, создаем пользователя dbmail и базу dbmail

  • Создаем пользователя для работы с почтовой базой
createuser -U postgres -P dbmail
  • [!] - Ни в коем случае не используйте спецсимволы в пароле, кроме #! (авторизация может не проходить)
  • Создаем базу
createdb -U postgres --owner dbmail dbmail
  • Вместе с dbmail идут заготовки базы, распаковываем и заливаем:
bunzip2 /usr/share/doc/dbmail-2.2.10/create_tables.pgsql.bz2
psql -U dbmail -d dbmail < /usr/share/doc/dbmail-2.2.10/create_tables.pgsql

или так:

zcat /usr/share/doc/dbmail/examples/create_tables.pgsql.gz|psql -h 127.0.0.1 dbmail dbmailadmin

или так:

psql -U dbmail -h localhost maildb < create_tables.pgsql


  • В этом дампе нет таблицы для работы с виртуальными доменами, создадим ее:
 CREATE TYPE dtype AS ENUM ( 
 'LOCAL', 
 'VIRTUAL', 
 'RELAY' 
); 

ALTER TYPE public.dtype OWNER TO dbmail; 

SET default_with_oids = true; 

CREATE TABLE dbmail_domains ( 
 uid integer NOT NULL, 
 domain character varying(128) NOT NULL, 
 type dtype NOT NULL 
);

INSERT INTO dbmail_domains (uid, domain, type) VALUES (1, 'example.com', 'LOCAL');

База готова.

  • добавляем обработку базы в /etc/crontab
...
0 3 * * * root /usr/sbin/dbmail-util -cturpd -l 24h -qq
...
  • проверяем работу dbmail c базой:
dbmail-util -av

если есть ошибки, исправляем не забывая проверить файл конфигурации...
.. если все ок, приступаем к настройке postfix

3. Настройка Postfix

apt-get install postfix postfix-pgsql postfix-sqlite procmail libsasl2-2 libsasl2-modules libsasl2-modules-db\ 
libsasl2-modules-sql sqlite3 mutt postfix-pcre postfix-ldap postfix-lmdb sasl2-bin ufw 
  • вносим необходимые изменения в файлы конфигурации - пример рабочей версии main.cf:
# See /usr/share/postfix/main.cf.dist for a commented, more complete version


# Debian specific:  Specifying a file name will cause the first
# line of that file to be used as the name.  The Debian default
# is /etc/mailname.
#myorigin = /etc/mailname

smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU)
biff = no

# appending .domain is the MUA's job.
append_dot_mydomain = no

# Uncomment the next line to generate "delayed mail" warnings
#delay_warning_time = 4h

readme_directory = no

# See http://www.postfix.org/COMPATIBILITY_README.html -- default to 2 on
# fresh installs.
compatibility_level = 2

# TLS parameters
#smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
#smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
smtpd_tls_cert_file=/etc/postfix/ssl/smtpd.pem
smtpd_tls_key_file=/etc/postfix/ssl/smtpd.key
smtpd_use_tls=yes
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache

# See /usr/share/doc/postfix/TLS_README.gz in the postfix-doc package for
# information on enabling SSL in the smtp client.

smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
myhostname = mymail.home.local
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
myorigin = /etc/mailname
mydestination = $myhostname, mymail.ru, mymail.home.local, localhost.home.local, localhost
relayhost = 
#mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
######################### вторым ip указываем хост где база данных postgresql
mynetworks = 127.0.0.0/8 10.0.5.2
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = all
inet_protocols = all
############################## - указываем способ использования postgresql
local_recipient_maps = pgsql:/etc/postfix/dbmail-mailboxes.cf $alias_maps
mailbox_transport = dbmail-lmtp:127.0.0.1:24

#################### - подключаем авторизацию через sasl, установка ниже в статье.
broken_sasl_auth_clients = yes
smtpd_sasl_auth_enable = yes
smtpd_sasl_local_domain = 
############################### - подключаем наш сертификат созданный как описано ниже.
smtpd_tls_auth_only = no
smtpd_tls_loglevel = 1
smtpd_tls_received_header = yes
smtpd_tls_session_cache_timeout = 3600s
tls_random_source = dev:/dev/urandom


  • вносим необходимые изменения в файлы конфигурации - пример рабочей версии master.cf:
#
# Postfix master process configuration file.  For details on the format
# of the file, see the master(5) manual page (command: "man 5 master" or
# on-line: http://www.postfix.org/master.5.html).
#
# Do not forget to execute "postfix reload" after editing this file.
#
# ==========================================================================
# service type  private unpriv  chroot  wakeup  maxproc command + args
#               (yes)   (yes)   (no)    (never) (100)
# ==========================================================================
smtp      inet  n       -       y       -       -       smtpd
#smtp      inet  n       -       y       -       1       postscreen
#smtpd     pass  -       -       y       -       -       smtpd
#dnsblog   unix  -       -       y       -       0       dnsblog
#tlsproxy  unix  -       -       y       -       0       tlsproxy
#submission inet n       -       y       -       -       smtpd
#  -o syslog_name=postfix/submission
#  -o smtpd_tls_security_level=encrypt
#  -o smtpd_sasl_auth_enable=yes
#  -o smtpd_reject_unlisted_recipient=no
#  -o smtpd_client_restrictions=$mua_client_restrictions
#  -o smtpd_helo_restrictions=$mua_helo_restrictions
#  -o smtpd_sender_restrictions=$mua_sender_restrictions
#  -o smtpd_recipient_restrictions=
#  -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
#  -o milter_macro_daemon_name=ORIGINATING
#smtps     inet  n       -       y       -       -       smtpd
#  -o syslog_name=postfix/smtps
#  -o smtpd_tls_wrappermode=yes
#  -o smtpd_sasl_auth_enable=yes
#  -o smtpd_reject_unlisted_recipient=no
#  -o smtpd_client_restrictions=$mua_client_restrictions
#  -o smtpd_helo_restrictions=$mua_helo_restrictions
#  -o smtpd_sender_restrictions=$mua_sender_restrictions
#  -o smtpd_recipient_restrictions=
#  -o smtpd_relay_restrictions=permit_sasl_authenticated,reject
#  -o milter_macro_daemon_name=ORIGINATING
#628       inet  n       -       y       -       -       qmqpd
pickup    unix  n       -       y       60      1       pickup
cleanup   unix  n       -       y       -       0       cleanup
qmgr      unix  n       -       n       300     1       qmgr
#qmgr     unix  n       -       n       300     1       oqmgr
tlsmgr    unix  -       -       y       1000?   1       tlsmgr
rewrite   unix  -       -       y       -       -       trivial-rewrite
bounce    unix  -       -       y       -       0       bounce
defer     unix  -       -       y       -       0       bounce
trace     unix  -       -       y       -       0       bounce
verify    unix  -       -       y       -       1       verify
flush     unix  n       -       y       1000?   0       flush
proxymap  unix  -       -       n       -       -       proxymap
proxywrite unix -       -       n       -       1       proxymap
smtp      unix  -       -       y       -       -       smtp
relay     unix  -       -       y       -       -       smtp
#       -o smtp_helo_timeout=5 -o smtp_connect_timeout=5
showq     unix  n       -       y       -       -       showq
error     unix  -       -       y       -       -       error
retry     unix  -       -       y       -       -       error
discard   unix  -       -       y       -       -       discard
local     unix  -       n       n       -       -       local
virtual   unix  -       n       n       -       -       virtual
lmtp      unix  -       -       y       -       -       lmtp
anvil     unix  -       -       y       -       1       anvil
scache    unix  -       -       y       -       1       scache
#
# ====================================================================
# Interfaces to non-Postfix software. Be sure to examine the manual
# pages of the non-Postfix software to find out what options it wants.
#
# Many of the following services use the Postfix pipe(8) delivery
# agent.  See the pipe(8) man page for information about ${recipient}
# and other message envelope options.
# ====================================================================
#
# maildrop. See the Postfix MAILDROP_README file for details.
# Also specify in main.cf: maildrop_destination_recipient_limit=1
#
maildrop  unix  -       n       n       -       -       pipe
  flags=DRhu user=vmail argv=/usr/bin/maildrop -d ${recipient}
#
# ====================================================================
#
# Recent Cyrus versions can use the existing "lmtp" master.cf entry.
#
# Specify in cyrus.conf:
#   lmtp    cmd="lmtpd -a" listen="localhost:lmtp" proto=tcp4
#
# Specify in main.cf one or more of the following:
#  mailbox_transport = lmtp:inet:localhost
#  virtual_transport = lmtp:inet:localhost
#
# ====================================================================
#
# Cyrus 2.1.5 (Amos Gouaux)
# Also specify in main.cf: cyrus_destination_recipient_limit=1
#
#cyrus     unix  -       n       n       -       -       pipe
#  user=cyrus argv=/cyrus/bin/deliver -e -r ${sender} -m ${extension} ${user}
#
# ====================================================================
# Old example of delivery via Cyrus.
#
#old-cyrus unix  -       n       n       -       -       pipe
#  flags=R user=cyrus argv=/cyrus/bin/deliver -e -m ${extension} ${user}
#
# ====================================================================
#
# See the Postfix UUCP_README file for configuration details.
#
uucp      unix  -       n       n       -       -       pipe
  flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient)
#
# Other external delivery methods.
#
ifmail    unix  -       n       n       -       -       pipe
  flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient)
bsmtp     unix  -       n       n       -       -       pipe
  flags=Fq. user=bsmtp argv=/usr/lib/bsmtp/bsmtp -t$nexthop -f$sender $recipient
scalemail-backend unix  -       n       n       -       2       pipe
  flags=R user=scalemail argv=/usr/lib/scalemail/bin/scalemail-store ${nexthop} ${user} ${extension}
mailman   unix  -       n       n       -       -       pipe
  flags=FR user=list argv=/usr/lib/mailman/bin/postfix-to-mailman.py
  ${nexthop} ${user}
######
dbmail-lmtp     unix    -       -       n       -       -       lmtp
        -o disable_dns_lookups=yes

  • создаем файл настройки подключения к базе postgresql - dbmail-mailboxes.cf:
user = dbmail
password = userpass
hosts = 10.0.5.2
dbname = mailbasename
table = dbmail_aliases
select_field = alias
where_field = alias
  • Так как почтовый сервер изначально не рассматсривается как релей, то доступ к SMTP только по авторизации и для этого используем SASL.
  • в каталоге настроек postfix создаем файл настроек для sasl:
mkdir -p /etc/postfix/sasl
  • создаем файл конфигурации - smtpd.conf:
echo > /etc/postfix/sasl/smtpd.conf
  • вносим содержимое файла:
edit /etc/postfix/sasl/smtpd.conf
pwcheck_method: auxprop
auxprop_plugin: sql
mech_list: digest-md5 cram-md5 login plain
sql_engine: pgsql
sql_user: dbmail
sql_passwd: userpass
sql_hostnames: 10.0.5.2
sql_database: mailbasename
sql_statement: select passwd from dbmail_users where userid='%u@%r'
sql_verbose: yes
  • создаем свой recipient_bcc
*@mainhost.local admin@mailbox.ru
*@slavehost.local admin@mailbox.ru
*@otherhost.local admin@mailbox.ru
  • генерируем базу
postmap recipient_bcc
  • генерируем свой сертификат tls:
mkdir -p /etc/postfix/ssl
cd /etc/postfix/ssl
openssl req -new -x509 -days 3650 -nodes -out smtpd.pem -keyout smtpd.key
  • перезапускаем postfix:
systemctl  restart postfix

или

/etc/init.d/postfix restart
  • проверяем работу postfix:
# telnet mymail.ru 25
Trying mymail.ru...
Connected to mymail.ru.
Escape character is '^]'.
220 mx.kscom.ru ESMTP Postfix
EHLO example.com
250-mx.kscom.ru
250-PIPELINING
250-SIZE 10240000
250-VRFY
250-ETRN
250-STARTTLS
250-ENHANCEDSTATUSCODES
250-8BITMIME
250 DSN
QUIT
221 2.0.0 Bye
Connection closed by foreign host.

- должно быть - 250-STARTTLS - все работает..

4. Настройка Stunnel

  • Данный пакет позволяет организовать защищенное соединение как для почты так и для других программ.
  • Далее будет описание, как создать защищенный вход на почтовый сервер.
  • Устанавливаем пакет:
apt-get install stunnel4
  • в каталоге /etc/stunnel - сразу создаем себе скрипт для генерации сертификата, чтобы если понадобится снова не вспоминать как это...
echo > /etc/stunnel/create-sert
editor /etc/stunnel/create-sert
  • вносим содержимое:
#!/bin/sh
# каталог сертификатов SSL в системе
cd /etc/ssl/certs
# имя сертификата на свое усмотрение...
PEMFILE="servername.mymail.ru.pem"
# генерация сертификата
openssl req -new -x509 -nodes -days 3650 -out $PEMFILE -keyout $PEMFILE
chmod 600 $PEMFILE
[ -e temp_file ] && rm -f temp_file
dd if=/dev/urandom of=temp_file count=2
openssl dhparam -rand temp_file 512 >> $PEMFILE
ln -sf $PEMFILE `openssl x509 -noout -hash < $PEMFILE`.0
 
  • даем права на исполнение - только для root:
chmod 0700 /etc/stunnel/create-sert
  • запускаем скрипт и отвечаем на вопросы..
/etc/stunnel/create-sert
  • создаем каталог в котором будет файл запуска .pid
mkdir -p /var/run/stunnel4/
  • копируем из примера будущий конфигурационный файл для stunnel4
cp /usr/share/doc/stunnel4/examples/stunnel.conf-sample /etc/stunnel/stunnel.conf
  • приводим его в такой вариант (рабочий пример):
; Sample stunnel configuration file for Unix by Michal Trojnara 2002-2015
; Some options used here may be inadequate for your particular configuration
; This sample file does *not* represent stunnel.conf defaults
; Please consult the manual for detailed description of available options

; **************************************************************************
; * Global options                                                         *
; **************************************************************************

; It is recommended to drop root privileges if stunnel is started by root
;setuid = stunnel4
;setgid = stunnel4

; PID file is created inside the chroot jail (if enabled)
pid = /var/run/stunnel4/stunnel.pid

; Debugging stuff (may be useful for troubleshooting)
;foreground = yes
;debug = info
output = /var/log/stunnel.log

; Enable FIPS 140-2 mode if needed for compliance
;fips = yes
fips = no
; **************************************************************************
; * Service defaults may also be specified in individual service sections  *
; **************************************************************************

; Enable support for the insecure SSLv3 protocol
options = -NO_SSLv3
sslVersion = TLSv1.2

; These options provide additional security at some performance degradation
;options = SINGLE_ECDH_USE
;options = SINGLE_DH_USE

; **************************************************************************
; * Include all configuration file fragments from the specified folder     *
; **************************************************************************

;include = /etc/stunnel/conf.d

; **************************************************************************
; * Service definitions (remove all services for inetd mode)               *
; **************************************************************************

; ***************************************** Example TLS client mode services

; The following examples use /etc/ssl/certs, which is the common location
; of a hashed directory containing trusted CA certificates.  This is not
; a hardcoded path of the stunnel package, as it is not related to the
; stunnel configuration in /etc/stunnel/.

;[mymail-pop3]
;client = yes
;accept = 127.0.0.1:110
;connect = pop3.mymail.ru:995
;verifyChain = yes
;CApath = @sysconfdir/ssl/certs
;checkHost = pop3s.mymail.ru
;OCSPaia = yes

;[mymail-imap]
;client = yes
;accept = 127.0.0.1:143
;connect = imap.mymail.ru:993
;verifyChain = yes
;CApath = @sysconfdir/ssl/certs
;checkHost = imaps.mymail.ru
;OCSPaia = yes

;[mymail-smtp]
;client = yes
;accept = 127.0.0.1:25
;connect = smtp.mymail.ru:465
;verifyChain = yes
;CApath = @sysconfdir/ssl/certs
;checkHost = smtps.mymail.ru
;OCSPaia = yes

; ***************************************** Example TLS server mode services

[pop3s]
accept  = 995
connect = 110
cert = /etc/ssl/certs/servername.mymail.ru.pem

[imaps]
accept  = 993
connect = 143
cert = /etc/ssl/certs/servername.mymail.ru.pem

[smtps]
accept  = 465
connect = 25
cert = /etc/ssl/certs/servername.mymail.ru.pem

; TLS front-end to a web server
;[https]
;accept  = 443
;connect = 80
;cert = /etc/stunnel/stunnel.pem
; "TIMEOUTclose = 0" is a workaround for a design flaw in Microsoft SChannel
; Microsoft implementations do not use TLS close-notify alert and thus they
; are vulnerable to truncation attacks
;TIMEOUTclose = 0

; Remote shell protected with PSK-authenticated TLS
; Create "/etc/stunnel/secrets.txt" containing IDENTITY:KEY pairs
;[shell]
;accept = 1337
;exec = /bin/sh
;execArgs = sh -i
;ciphers = PSK
;PSKsecrets = /etc/stunnel/secrets.txt

; Non-standard MySQL-over-TLS encapsulation connecting the Unix socket
;[mysql]
;cert = /etc/stunnel/stunnel.pem
;accept = 3307
;connect = /run/mysqld/mysqld.sock

; vim:ft=dosini
  • корректируем конфигурационный файл запуска по умолчанию:
# /etc/default/stunnel
# Julien LEMOINE <speedblue@debian.org>
# September 2003

# Change to one to enable stunnel automatic startup
ENABLED=1
FILES="/etc/stunnel/*.conf"
OPTIONS=""

# Change to one to enable ppp restart scripts
PPP_RESTART=0

# Change to enable the setting of limits on the stunnel instances
# For example, to set a large limit on file descriptors (to enable
# more simultaneous client connections), set RLIMITS="-n 4096"
# More than one resource limit may be modified at the same time,
# e.g. RLIMITS="-n 4096 -d unlimited"
RLIMITS=""
  • перезапуск stunnel
/etc/init.d/stunnel4 restart
  • после этого проверяем наличие нужных нам портов:
nmap -v mymail.ru
...
PORT    STATE SERVICE
22/tcp  open  ssh
25/tcp  open  smtp
110/tcp open  pop3
143/tcp open  imap
465/tcp open  smtps
993/tcp open  imaps
995/tcp open  pop3s 
  • проверяем работу с почтой по SSL\TLS - зашифрованный пароль на портах 465,993,995
  • если все в порядке, рекомендую закрыть обычные порты через iptables (110,143),
  • а оставить только 25 (некоторые серверы для доставки вам почты требуют именно его)

5. Установка антиспама Spamassassin

  • установка пакета:
aptitude install spamassassin
  • запуск по умолчанию в /etc/default/spamassassin
...
ENABLED=1
...
  • Приводим файл конфигурации антиспама /etc/spamassassin/local.cf к такому:
# This is the right place to customize your installation of SpamAssassin.
#
# See 'perldoc Mail::SpamAssassin::Conf' for details of what can be
# tweaked.
#
# Only a small subset of options are listed below
#
###########################################################################

#   Add *****SPAM***** to the Subject header of spam e-mails
#
rewrite_header Subject *****SPAM*****


#   Save spam messages as a message/rfc822 MIME attachment instead of
#   modifying the original message (0: off, 2: use text/plain instead)
#
report_safe 0


#   Set which networks or hosts are considered 'trusted' by your mail
#   server (i.e. not spammers)
#
# trusted_networks 212.17.35.
trusted_networks 10.0.5.


#   Set file-locking method (flock is not safe over NFS, but is faster)
#
# lock_method flock


#   Set the threshold at which a message is considered spam (default: 5.0)
#
required_score 5.0


#   Use Bayesian classifier (default: 1)
#
use_bayes 1


#   Bayesian classifier auto-learning (default: 1)
#
bayes_auto_learn 1


#   Set headers which may provide inappropriate cues to the Bayesian
#   classifier
#
bayes_ignore_header X-Bogosity
bayes_ignore_header X-Spam-Flag
bayes_ignore_header X-Spam-Status


#   Whether to decode non- UTF-8 and non-ASCII textual parts and recode
#   them to UTF-8 before the text is given over to rules processing.
#
# normalize_charset 1

#   Some shortcircuiting, if the plugin is enabled
# 
ifplugin Mail::SpamAssassin::Plugin::Shortcircuit
#
#   default: strongly-whitelisted mails are *really* whitelisted now, if the
#   shortcircuiting plugin is active, causing early exit to save CPU load.
#   Uncomment to turn this on
#
# shortcircuit USER_IN_WHITELIST       on
# shortcircuit USER_IN_DEF_WHITELIST   on
# shortcircuit USER_IN_ALL_SPAM_TO     on
# shortcircuit SUBJECT_IN_WHITELIST    on

#   the opposite; blacklisted mails can also save CPU
#
# shortcircuit USER_IN_BLACKLIST       on
# shortcircuit USER_IN_BLACKLIST_TO    on
# shortcircuit SUBJECT_IN_BLACKLIST    on

#   if you have taken the time to correctly specify your "trusted_networks",
#   this is another good way to save CPU
#
# shortcircuit ALL_TRUSTED             on

#   and a well-trained bayes DB can save running rules, too
#
# shortcircuit BAYES_99                spam
# shortcircuit BAYES_00                ham
whitelist_from @mymail.ru

endif # Mail::SpamAssassin::Plugin::Shortcircuit

  • Стартуем spamassasin:
/etc/init.d/spamassassin start 
  • Редактируем файл постфикса /etc/postfix/master.cf

- Строку:

..
smtp      inet  n       -       -       -       -       smtpd
..

- Заменяем на:

..
smtp      inet  n       -       -       -       -       smtpd -o content_filter=spamassassin
..

- Перед:

..
dbmail-lmtp     unix    -       -       n       -       -       lmtp
        -o disable_dns_lookups=yes
..

- Добавляем:

..
spamassassin unix   -   n   n   -   -   pipe  user=debian-spamd argv=/usr/bin/spamc -s 5120000 -f -e /usr/sbin/sendmail -oi -f
${sender}${recipient}
..
  • Перезапускаем postfix:
/etc/init.d/postfix restart
  • Проверяем работу почты, все должно работать...



Источники: