Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial commit for Autoconfig #343

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
/.php_cs.cache
/.idea
/composer.lock
**/*.bbprojectd
1,284 changes: 1,284 additions & 0 deletions AUTOCONFIG/AutoconfigHandler.php

Large diffs are not rendered by default.

157 changes: 157 additions & 0 deletions AUTOCONFIG/INSTALL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
Installation procedure for Autodiscovery configuration tool
============================================================
* Author: [Jacques Deguest](mailto:jack@deguest.jp)
* Created: 2020-03-10
* License: Same as Postfix Admin itself

## Quick background & overview

Autodiscovery is a somewhat standardised feature that makes it possible for mail client to find out the proper configuration for a mail account, and to prevent the every day user from guessing the right parameters.

Let's take the example of joe@example.com.

When creating an account on Thurderbird and other who use the same configuration, the mail client will make a http query to <https://www.example.com/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress=joe%2540example.com>

See this page from Mozilla for more information: <https://wiki.mozilla.org/Thunderbird:Autoconfiguration:ConfigFileFormat>

If a DNS record exist

For Outlook, the mail client will attempt a POST request to: <https://www.example.com/autodiscover/autodiscover.xml> and submit a XML-based request

For Mac mail and iOS, the user needs to download a `mobileconfig` file, which is basically an XML file, that can be signed.

Unfortunately, there is no auto discovery system for Mac/iOS mail, so you need to redirect your users to the `autoconfig.pl` cgi script under the Postfix Admin web root. You need to pass a `emailaddress` parameter for example <https://www.example.com/postfixadmin/autoconfig.pl?mac_mail=1&emailaddress=joe@example.com>

## Installation

### Dependencies

#### SQL

You need to activate the `uuid-ossp` PostgreSQL extension to use the UUID_V4. You can do that, as an admin logged on PostgreSQL, with `CREATE EXTENSION IF NOT EXISTS "uuid-ossp";`

If you cannot or do not want to do that, edit the SQL script for PostgreSQL and comment line 9 and uncomment line 11, comment line 72 and uncoment line 74, comment line 84 and uncomment line 86, comment line 107 and uncomment line 109

#### Perl

The following perl modules are required. Most are standard core modules.

* strict
* IO::File
* CGI v4.44
* Email::Valid v1.202
* Email::Address v1.912
* XML::LibXML v2.0201
* XML::LibXML::PrettyPrint v0.006
* Data::Dumper v2.174
* Scalar::Util v1.50
* Data::UUID v1.224
* File::Basename v2.85
* Cwd v3.78
* File::Temp v0.2309
* File::Spec v3.78
* File::Which v1.23
* JSON v2.34
* DBI v1.642
* TryCatch v1.003002
* Devel::StackTrace v2.04

For PostgreSQL you need `DBD::Pg`. I use version 3.8.1.

For MySQL you need `DBD::mysql` any recent version should do.

For SQLite, you need `DBD::SQLite`. I used version 1.62.

You can install those module using `cpanm` (https://metacpan.org/pod/App::cpanminus) like:

`cpanm --interactive IO::File CGI Email::Valid Email::Address XML::LibXML XML::LibXML::PrettyPrint Data::Dumper Scalar::Util Data::UUID File::Basename Cwd File::Temp File::Spec File::Which JSON DBI TryCatch Devel::StackTrace`

#### Web

* jQuery v3.3.1 (loaded automatically from within the template by calling <https://code.jquery.com/jquery-3.3.1.min.js>)

#### Signature of mobileconfig files for Mac/iOS

You need to have `openssl` installed. I used version 1.0.2g. You would also need ssl certificates installed for server wide or per domain. I recommend using Let's Encrypt <https://letsencrypt.org/> by installing their command line too `certbot`

### SQL

Load the SQL script `autoconfig.sql` into your Postfix Admin database. For exaple:

* PostgreSQL : `psql -U postfixadmin postfixadmin < autoconfig.sql`

* MySQL : `mysql -u postfixadmin postfixadmin < autoconfig.sql`

* SQLite : `sqlite3 /path/to/database.sqlite < autoconfig.sql`

This will create 4 new standalone tables, and does not alter other areas of the PostfixAdmin database.

### PHP, perl and other web files

Move `AutoconfigHandler.php` under the `model` sub directory in the Postfix Admin root folder, and `autoconfig.php`, `autoconfig.pl`, `autoconfig.css`, `autoconfig.js` and `sprintf.js` under the Postfix Admin web root `public`:

```
mv ./AUTOCONFIG/autoconfig.{css,js,php,pl} ./public/
mv ./AUTOCONFIG/{autoconfig_languages.php,sprintf.js} ./public/
mv ./AUTOCONFIG/AutoconfigHandler.php ./model
mv ./AUTOCONFIG/*.tpl ./templates/
```

#### Additional notes :

`autoconfig.js` is a small file containing event handlers to make the use of the admin interface smooth, and also makes use of Ajax with jQuery 3.3.1. jQuery 3.3.1 is used, and not the latest 3.3.2, because the pseudo selector `:first` and `:last` have been deprecated and are needed here, at least until I can find an alternative solution. If you have one, please let me know!

The general use of Javascript is light and only to support workflow, nothing more. Pure CSS is used whenever possible (such as the switch button). No other framework is used to keep things light.

FontAwesome version 5.12.1 is loaded as import in the CSS file

`autoconfig.pl` will guess the location of the `config.inc.php` based on the file path. You can change that, such as by specifiying `config.local.php` instead by editing the perl script and change the line `our $POSTFIXADMIN_CONF_FILE = File::Basename::dirname( __FILE__ ) . '/../config.inc.php';` for example into `our $POSTFIXADMIN_CONF_FILE = '/var/www/postfix/config.inc.php';`

`autoconfig.pl` will read the php file by converting it into a json file and save that conversion into a temporary file on the server until the modification time of `config.inc.php` changes.

### DNS

Not required, but to take full advaantage of the idea of auto discovery, you should set up the following DNS records in your domain name zones:

```bind
_submission._tcp IN SRV 0 1 587 mail.example.com.
_imap._tcp IN SRV 0 0 143 mail.example.com.
_imaps._tcp IN SRV 0 0 993 mail.example.com.
_pop3._tcp IN SRV 0 0 110 mail.example.com.
```

If you want to use a dedicated autodiscover sub domain, you could set up your DNS zone with the following record:

```bind
_autodiscover._tcp IN SRV 0 10 443 autoconfig.example.com.
```

### Apache

Add the following to the general config file or to the relevant Vitual Hosts. You can also add it as a conf file under `/etc/apache2/conf-available` if it exists and then issue `a2enconf autoconfig.conf` to activate it (assuming the file name was `autoconfig.conf`)

(Here I presumed Postfix Admin is installed under /var/www/postfixadmin)

```conf
Alias /autoconfig /var/www/postfixadmin/public

<Directory "/var/www/postfixadmin/public/">
AllowOverride None
Options Indexes FollowSymLinks ExecCGI
Require all granted
Allow from all
AddHandler cgi-script .cgi .pl
</Directory>

RewriteEngine On
# For Thunderbird and others
RewriteRule "^/.well-known/autoconfig/mail/config-v1.1.xml" "/autoconfig/autoconfig.pl" [PT,L]

# For Outlook; POST request
RewriteRule "^/autodiscover/autodiscover.xml" "/autoconfig/autoconfig.pl?outlook=1" [PT,L]

# For autodiscovery settings in DNS
RewriteRule "^/mail/config-v1\.1\.xml(.*)$" "/autoconfig/autoconfig.pl" [PT,L]
```


91 changes: 91 additions & 0 deletions AUTOCONFIG/autoconfig-host-settings.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<tr class="{if $server.type == 'imap' || $server.type == 'pop3'}autoconfig-incoming{else}autoconfig-outgoing{/if}">
<td colspan="3">
<table class="server" width="100%">
<tr>
<td class="label" width="20%"><label>{$PALANG.pAutoconfig_type}:</label></td>
{if $server.type == "imap" || $server.type == "pop3"}
<td width="60%"><select name="type[]" class="host_type">
<option value="imap" {if !empty($server.type) && $server.type == "imap"}selected{/if}>imap</option>
<option value="pop3" {if !empty($server.type) && $server.type == "pop3"}selected{/if}>pop3</option></select></td>
{else}
<td width="20%"><em>smtp</em><input type="hidden" name="type[]" value="smtp" /></td>
{/if}
<td rowspan="2"><button class="autoconfig-command ripple autoconfig-server-add {if $server.type == 'imap' || $server.type == 'pop3'} autoconfig-incoming-server{else}autoconfig-outgoing-server{/if}" title="{$PALANG.pAutoconfig_add_new_host}"><i class="fas fa-plus fa-2x"></i></button><button class="autoconfig-command ripple autoconfig-server-remove {if $server.type == 'imap' || $server.type == 'pop3'} autoconfig-incoming-server{else}autoconfig-outgoing-server{/if}" title="{$PALANG.pAutoconfig_remove_host}"><i class="fas fa-minus fa-2x"></i></button><button class="autoconfig-command ripple autoconfig-move-up" title="{$PALANG.pAutoconfig_move_up_host}"><i class="fas fa-arrow-up fa-2x"></i></button><button class="autoconfig-command ripple autoconfig-move-down" title="{$PALANG.pAutoconfig_move_down_host}"><i class="fas fa-arrow-down fa-2x"></i></button></td>
</tr>
<tr>
<td class="label"><label>{$PALANG.pAutoconfig_hostname}:</label></td>
<td><input type="hidden" name="host_id[]" value="{$server.host_id}" />
<input type="text" size="40" class="flat" name="hostname[]" maxlength="255" value="{$server.hostname}" /></td>
<!-- <td>&nbsp;</td> -->
</tr>
<tr>
<td class="label"><label>{$PALANG.pAutoconfig_port}:</label></td>
<td><input type="number" class="flat" name="port[]" maxlength="255" value="{$server.port}" list="defaultPorts" /></td>
<td>&nbsp;</td>
</tr>
<tr>
<td class="label"><label>{$PALANG.pAutoconfig_socket_type}:</label></td>
<td><select name="socket_type[]">
<option value="" {if empty($server.socket_type) || $server.socket_type == ""}selected{/if}>{$PALANG.pAutoconfig_no_selection}</option>
<option value="SSL" {if !empty($server.socket_type) && $server.socket_type == "SSL"}selected{/if}>SSL</option>
<option value="STARTTLS" {if !empty($server.socket_type) && $server.socket_type == "STARTTLS"}selected{/if}>STARTTLS</option>
<option value="TLS" {if !empty($server.socket_type) && $server.socket_type == "TLS"}selected{/if}>TLS</option>
</select></td>
<td>&nbsp;</td>
</tr>
<tr>
<td class="label"><label>{$PALANG.pAutoconfig_auth}:</label></td>
<td><select name="auth[]">
<option value="password-cleartext" {if !empty($server.auth) && $server.auth == "password-cleartext"}selected{/if}>{$PALANG.pAutoconfig_password_cleartext}</option>
<option value="password-encrypted" {if !empty($server.auth) && $server.auth == "password-encrypted"}selected{/if}>{$PALANG.pAutoconfig_password_encrypted}</option>
<option value="NTLM" {if !empty($server.auth) && $server.auth == "NTLM"}selected{/if}>NTLM</option>
<option value="GSSAPI" {if !empty($server.auth) && $server.auth == "GSSAPI"}selected{/if}>GSSAPI</option>
<option value="client-IP-address" {if !empty($server.auth) && $server.auth == "client-IP-address"}selected{/if}>{$PALANG.pAutoconfig_client_ip_address}</option>
<option value="TLS-client-cert" {if !empty($server.auth) && $server.auth == "TLS-client-cert"}selected{/if}>{$PALANG.pAutoconfig_tls_client_cert}</option>
<option value="smtp-after-pop" {if !empty($server.auth) && $server.auth == "smtp-after-pop"}selected{/if}>{$PALANG.pAutoconfig_smtp_after_pop}</option>
<option value="ouath2" {if !empty($server.auth) && $server.auth == "ouath2"}selected{/if}>OAuth2</option>
</select></td>
<td>&nbsp;</td>
</tr>
<tr>
<td class="label"><label>{$PALANG.pAutoconfig_username}:</label></td>
<td><input type="text" class="flat" name="username[]" maxlength="255" value="{$server.username}" placeholder="%EMAILADDRESS%" /> {$PALANG.pAutoconfig_username_template}<select name="username_template" class="username_template">
<option value="">{$PALANG.pAutoconfig_no_selection}</option>
<option value="%EMAILADDRESS%">%EMAILADDRESS%</option>
<option value="%EMAILLOCALPART%">%EMAILLOCALPART%</option>
<option value="%EMAILDOMAIN%">%EMAILDOMAIN%</option>
</select></td>
<td>&nbsp;</td>
</tr>
{if $server.type == "pop3" || $server.type == "imap"}
<!-- if incoming server is a pop3 -->
{if isset( $server.host_id )}
{assign var=host_unique_id value=$server.host_id}
{else}
{assign var=host_unique_id value=10|mt_rand:20}
{/if}
<tr class="host_pop3">
<td class="label"><label for="autoconfig_leave_messages_on_server_{$host_unique_id}">{$PALANG.pAutoconfig_leave_messages_on_server}:</label></td>
<td><input type="checkbox" class="flat" name="leave_messages_on_server[]" id="autoconfig_leave_messages_on_server_{$host_unique_id}" value="1" {if !empty($server.leave_messages_on_server) && $server.leave_messages_on_server == 1}checked="checked"{/if} /></td>
<td>&nbsp;</td>
</tr>
<tr class="host_pop3">
<td class="label"><label for="autoconfig_download_on_biff_{$host_unique_id}">{$PALANG.pAutoconfig_download_on_biff}:</label></td>
<td><input type="checkbox" class="flat" name="download_on_biff[]" id="autoconfig_download_on_biff_{$host_unique_id}" value="1" {if !empty($server.download_on_biff) && $server.download_on_biff == 1}checked="checked"{/if} /></td>
<td>&nbsp;</td>
</tr>
<tr class="host_pop3">
<td class="label"><label for="autoconfig_days_to_leave_messages_on_server_{$host_unique_id}">{$PALANG.pAutoconfig_days_to_leave_messages_on_server}:</label></td>
<td><input type="number" class="flat" name="days_to_leave_messages_on_server[]" id="autoconfig_days_to_leave_messages_on_server_{$host_unique_id}" min="0" max="365" value="{$server.days_to_leave_messages_on_server}" /></td>
<td>&nbsp;</td>
</tr>
<tr class="host_pop3">
<td class="label"><label for="autoconfig_check_interval_{$host_unique_id}">{$PALANG.pAutoconfig_check_interval}:</label></td>
<td><input type="number" class="flat" name="check_interval[]" id="autoconfig_check_interval_{$host_unique_id}" value="{$server.check_interval}" /></td>
<td>&nbsp;</td>
</tr>
{/if}
</table>
</td>
</tr>
<!-- end if incoming server is a pop3 -->
116 changes: 116 additions & 0 deletions AUTOCONFIG/autoconfig-mysql.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
Created on 2020-03-07
Copyright 2020 Jacques Deguest
Distributed under the same licence as Postfix Admin
*/
CREATE TABLE IF NOT EXISTS autoconfig (
id SERIAL
-- https://stackoverflow.com/questions/43056220/store-uuid-v4-in-mysql
,config_id CHAR(36) NOT NULL
-- If you prefer you can also use CHAR(36)
-- ,config_id CHAR(36) NOT NULL
,encoding VARCHAR(12)
,provider_id VARCHAR(255) NOT NULL
-- Nice feature but not enough standard across other db. Instead, we'll use a separate table
-- ,provider_domain VARCHAR(255)[]
,provider_name VARCHAR(255)
,provider_short VARCHAR(120)
-- enable section
,enable_status BOOLEAN
,enable_url VARCHAR(2048)
-- documentation section
,documentation_status BOOLEAN
,documentation_url VARCHAR(2048)
,webmail_login_page VARCHAR(2048)
-- webmail login page info
,lp_info_url VARCHAR(2048)
,lp_info_username VARCHAR(255)
,lp_info_username_field_id VARCHAR(255)
,lp_info_username_field_name VARCHAR(255)
,lp_info_password_field VARCHAR(255)
,lp_info_login_button_id VARCHAR(255)
,lp_info_login_button_name VARCHAR(255)
-- Mac Mail specific fields
,account_name VARCHAR(255)
-- Typically 'email'
,account_type VARCHAR(42)
-- could be empty or could be a placeholder like %EMAILADDRESS%
,email VARCHAR(255)
-- If not explicitly set, this will be guessed from host socket_type
,ssl_enabled BOOLEAN
-- Will be empty obviously unless the user enters it in the form
-- password may be provided by the user on the web interface, but not stored here
-- Used for payload_description
,description TEXT
,organisation VARCHAR(255)
-- payload type : regular account, or Microsoft Exchange, e.g. com.apple.mail.managed for mail account or com.apple.eas.account for exchange server
,payload_type VARCHAR(100)
,prevent_app_sheet BOOLEAN
,prevent_move BOOLEAN
,smime_enabled BOOLEAN
,payload_remove_ok BOOLEAN
-- Outlook specific fields
-- domain_required -> Not sure this should be an option; false by default
,spa BOOLEAN
-- payload_enabled
,active BOOLEAN
-- For signing of the Mac/iOS mobileconfig settings
-- none, local or global
-- none: do not sign
-- local: use this configuration's certificate information
-- global: use the server wide one in config.inc.php
,sign_option VARCHAR(7)
,cert_filepath VARCHAR(1024)
,privkey_filepath VARCHAR(1024)
,chain_filepath VARCHAR(1024)
,created TIMESTAMP DEFAULT CURRENT_TIMESTAMP
,modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP
,CONSTRAINT pk_autoconfig PRIMARY KEY (id)
,CONSTRAINT idx_autoconfig UNIQUE (config_id)
) ENGINE = InnoDB;

CREATE TABLE IF NOT EXISTS autoconfig_domains (
id SERIAL
,config_id CHAR(36) NOT NULL
-- COLLATE here is crucial for the foreign key to work. It must be the same as the target
,domain VARCHAR(255) NOT NULL COLLATE latin1_general_ci
,CONSTRAINT pk_autoconfig_domains PRIMARY KEY (id)
,CONSTRAINT idx_autoconfig_domains UNIQUE (config_id, domain)
,CONSTRAINT fk_autoconfig_domains_domain FOREIGN KEY (domain) REFERENCES domain(domain) ON DELETE CASCADE
,CONSTRAINT fk_autoconfig_domains_config_id FOREIGN KEY (config_id) REFERENCES autoconfig(config_id) ON DELETE CASCADE
) ENGINE = InnoDB;

CREATE TABLE IF NOT EXISTS autoconfig_hosts (
id SERIAL
,config_id CHAR(36) NOT NULL
-- imap, smtp, pop3
,type VARCHAR(12) NOT NULL
,hostname VARCHAR(255) NOT NULL
,port INTEGER NOT NULL
,socket_type VARCHAR(42)
,auth VARCHAR(42) DEFAULT 'none' NOT NULL
-- possibly to contain some placeholder like %EMAILADDRESS%
,username VARCHAR(255)
,leave_messages_on_server BOOLEAN DEFAULT FALSE
,download_on_biff BOOLEAN DEFAULT FALSE
,days_to_leave_messages_on_server INTEGER
,check_interval INTEGER
,priority INTEGER
,CONSTRAINT pk_autoconfig_hosts PRIMARY KEY (id)
,CONSTRAINT idx_autoconfig_hosts UNIQUE (config_id, type, hostname, port)
,CONSTRAINT fk_autoconfig_hosts_config_id FOREIGN KEY (config_id) REFERENCES autoconfig(config_id) ON DELETE CASCADE
) ENGINE = InnoDB;

CREATE TABLE IF NOT EXISTS autoconfig_text (
id SERIAL
,config_id CHAR(36) NOT NULL
-- instruction or documentation
,type VARCHAR(17) NOT NULL
-- iso 639 2-letters code
,lang CHAR(2) NOT NULL
,phrase TEXT
,CONSTRAINT pk_autoconfig_text PRIMARY KEY (id)
,CONSTRAINT idx_autoconfig_text UNIQUE (config_id, type, lang)
,CONSTRAINT fk_autoconfig_text_config_id FOREIGN KEY (config_id) REFERENCES autoconfig(config_id) ON DELETE CASCADE
) ENGINE = InnoDB;