1189 lines
66 KiB
HTML
1189 lines
66 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<meta name="description" content="">
|
|
<meta name="author" content="">
|
|
|
|
|
|
<title>Setting up a mail server</title>
|
|
|
|
<link href="https://captainark.net/rss.xml" type="application/atom+xml" rel="alternate" title="Sysadmining. All day. Every day. Full Atom Feed" />
|
|
|
|
<!-- Bootstrap Core CSS -->
|
|
<link href="https://captainark.net/theme/css/bootstrap.min.css" rel="stylesheet">
|
|
|
|
<!-- Custom CSS -->
|
|
<link href="https://captainark.net/theme/css/clean-blog.min.css" rel="stylesheet">
|
|
|
|
<!-- Code highlight color scheme -->
|
|
<link href="https://captainark.net/theme/css/code_blocks/github.css" rel="stylesheet">
|
|
|
|
|
|
<!-- Custom Fonts -->
|
|
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet" type="text/css">
|
|
<link href='https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic' rel='stylesheet' type='text/css'>
|
|
<link href='https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800' rel='stylesheet' type='text/css'>
|
|
|
|
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
|
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
|
|
<!--[if lt IE 9]>
|
|
<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
|
|
<script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
|
|
<![endif]-->
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<meta property="og:locale" content="">
|
|
<meta property="og:site_name" content="Sysadmining. All day. Every day.">
|
|
|
|
<meta property="og:type" content="article">
|
|
<meta property="article:author" content="">
|
|
<meta property="og:url" content="https://captainark.net/setting-up-a-mail-server.html">
|
|
<meta property="og:title" content="Setting up a mail server">
|
|
<meta property="og:description" content="">
|
|
<meta property="og:image" content="https://captainark.net/">
|
|
<meta property="article:published_time" content="2015-04-24 00:00:00+02:00">
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<!-- Navigation -->
|
|
<nav class="navbar navbar-default navbar-custom navbar-fixed-top">
|
|
<div class="container-fluid">
|
|
<!-- Brand and toggle get grouped for better mobile display -->
|
|
<div class="navbar-header page-scroll">
|
|
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
|
|
<span class="sr-only">Toggle navigation</span>
|
|
<span class="icon-bar"></span>
|
|
<span class="icon-bar"></span>
|
|
<span class="icon-bar"></span>
|
|
</button>
|
|
<a class="navbar-brand" href="https://captainark.net/">Sysadmining. All day. Every day.</a>
|
|
</div>
|
|
|
|
<!-- Collect the nav links, forms, and other content for toggling -->
|
|
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
|
<ul class="nav navbar-nav navbar-right">
|
|
<li><a href="/">Homepage</a></li>
|
|
<li><a href="/rss.xml">RSS</a></li>
|
|
<li><a href="/categories.html">Categories</a></li>
|
|
|
|
<li><a href="https://captainark.net/pages/about.html">About</a></li>
|
|
<li><a href="https://captainark.net/pages/resume.html">Resume</a></li>
|
|
</ul>
|
|
</div>
|
|
<!-- /.navbar-collapse -->
|
|
</div>
|
|
<!-- /.container -->
|
|
</nav>
|
|
|
|
<!-- Page Header -->
|
|
<header class="intro-header" style="background-image: url('/bg.png')">
|
|
<div class="container">
|
|
<div class="row">
|
|
<div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
|
|
<div class="post-heading">
|
|
<h1>Setting up a mail server</h1>
|
|
<span class="meta">Posted by
|
|
<a href="https://captainark.net/author/antoine-joubert.html">Antoine Joubert</a>
|
|
on Fri 24 April 2015
|
|
</span>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Main Content -->
|
|
<div class="container">
|
|
<div class="row">
|
|
<div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
|
|
<!-- Post Content -->
|
|
<article>
|
|
<p>In this first tutorial, I'll explain how I've configured my mail server using the following :</p>
|
|
<ul>
|
|
<li>A server running Linux Debian (jessie) ;</li>
|
|
<li>Postfix ;</li>
|
|
<li>Postfix-policyd-spf-python ;</li>
|
|
<li>Dovecot ;</li>
|
|
<li>Spamassassin ;</li>
|
|
<li>OpenDKIM ;</li>
|
|
<li>OpenDMARC ;</li>
|
|
<li>Monit ;</li>
|
|
<li>Rainloop.</li>
|
|
</ul>
|
|
<p>I'm assuming you have some basic knowledge of Linux and DNS configuration.</p>
|
|
<p>You can host this server at home, but you might have issues with your ISP not allowing outbound traffic on TCP port 25, and your emails might be considered to be spam by other providers if your IP is dynamic and/or you can't configure a reverse DNS record on it.</p>
|
|
<p>The cheapest VMs from <a href="https://www.digitalocean.com/?refcode=1cd69e4c3389">DigitalOcean</a> or <a href="http://www.vultr.com/?ref=6804947">Vultr</a> are powerful enough to have this configuration running smoothly.</p>
|
|
<p>We'll also need a SSL certificate for this configuration. You can create an auto-signed one or get a free valid one from <a href="http://www.startssl.com/">StartSSL</a>. For the purpose of this tutorial, I'll consider you've chosen the latter.</p>
|
|
<p>You'll also need a domain name. I've chosen <a href="http://www.namecheap.com/?aff=85990">Namecheap</a> as a registrar. I won't go into details on how to configure it, but you'll need at the very least a A record on your server's IP as well as a MX record pointing to it.</p>
|
|
<p>I use the captainark.net domain as an example throughout this tutorial. You'll have to use your actual domain for your configuration to work !</p>
|
|
<p><em>Note: links in this section are sponsored.</em></p>
|
|
<h2>Initial configuration</h2>
|
|
<h3>Installing the required packages</h3>
|
|
<p>First thing first, we need to install the packages we'll need for this configuration :</p>
|
|
<div class="highlight"><pre>apt update
|
|
|
|
apt install mysql-server mysql-client postfix postfix-mysql <span class="se">\</span>
|
|
postfix-policyd-spf-python dovecot-core dovecot-imapd dovecot-lmtpd <span class="se">\</span>
|
|
dovecot-mysql dovecot-sieve dovecot-managesieved dovecot-antispam <span class="se">\</span>
|
|
opendkim opendkim-tools monit opendmarc spamassassin spamc
|
|
</pre></div>
|
|
|
|
|
|
<p>During its installation, Postfix will prompt you with configuration questions. Choose "Internet Site", and when asked about your System mail name, provide it with your server's FQDN (it should be the output of the <code>hostname -f</code> command on your server).</p>
|
|
<p>You'll also have to set-up a password for the MySQL root user.</p>
|
|
<h3>Additional configuration</h3>
|
|
<p>The PTR records on your server's IPv4 and/or IPv6 should match your server's FQDN (a <code>dig -x</code> on your server's IP should match a <code>hostname -f</code> on your server).</p>
|
|
<p>You'll have to open the following TCP ports on your server for this configuration to work : 25, 465, 587 and 993.</p>
|
|
<p>If you don't want to have to remember the root user MySQL password, you can create a .my.cnf file in your current user home directory containing the following lines :</p>
|
|
<div class="highlight"><pre><span class="o">[</span>client<span class="o">]</span>
|
|
<span class="nv">host</span> <span class="o">=</span> localhost
|
|
<span class="nv">user</span> <span class="o">=</span> root
|
|
<span class="nv">password</span> <span class="o">=</span> myverysecurepassword
|
|
<span class="nv">socket</span> <span class="o">=</span> /var/run/mysqld/mysqld.sock
|
|
</pre></div>
|
|
|
|
|
|
<p>Once it has been created, change the permissions on the file to make sure no other user can read it :</p>
|
|
<div class="highlight"><pre>chmod <span class="m">600</span> ~/.my.cnf
|
|
</pre></div>
|
|
|
|
|
|
<p>I also like to change the default MySQL shell to see what database I'm using at any given time. Since I use bash, I achieve this the following way :</p>
|
|
<div class="highlight"><pre><span class="nb">echo</span> <span class="s1">'export MYSQL_PS1="[\u@\h] (\d)> "'</span> > ~/.bash_aliases
|
|
</pre></div>
|
|
|
|
|
|
<p>You'll have to logout from the current shell for the modification to be taken into account (if you're using SSH, log out and back into your server).</p>
|
|
<p>You should now be able to log into MySQL without specifying a password, and it should look like this :</p>
|
|
<div class="highlight"><pre>:~$ mysql mysql
|
|
<span class="o">[</span>...<span class="o">]</span>
|
|
<span class="o">[</span>root@localhost<span class="o">]</span> <span class="o">(</span>mysql<span class="o">)</span>>
|
|
</pre></div>
|
|
|
|
|
|
<h2>Configuring the MySQL database</h2>
|
|
<h3>Initial configuration</h3>
|
|
<p>We now need to configure the MySQL database Postfix and Dovecot will be using. In this tutorial, we'll be calling it "mail", but you can name it whatever you want.</p>
|
|
<p>First, in a mysql shell, let's create the MySQL database :</p>
|
|
<div class="highlight"><pre><span class="k">CREATE</span> <span class="k">DATABASE</span> <span class="n">mail</span><span class="p">;</span>
|
|
</pre></div>
|
|
|
|
|
|
<p>Now, we are going to create the user that Postfix and Dovecot will be using to access the database. We will only be granting this user select permission :</p>
|
|
<div class="highlight"><pre><span class="k">GRANT</span> <span class="k">SELECT</span> <span class="k">ON</span> <span class="n">mail</span><span class="p">.</span><span class="o">*</span> <span class="k">TO</span> <span class="s1">'mail'</span><span class="o">@</span><span class="s1">'localhost'</span> <span class="n">IDENTIFIED</span> <span class="k">BY</span> <span class="s1">'mailpassword'</span><span class="p">;</span>
|
|
<span class="n">FLUSH</span> <span class="k">PRIVILEGES</span><span class="p">;</span>
|
|
</pre></div>
|
|
|
|
|
|
<p>We are now going to create the necessary tables for our needs. Let's first use the mail database :</p>
|
|
<div class="highlight"><pre><span class="n">USE</span> <span class="n">mail</span><span class="p">;</span>
|
|
</pre></div>
|
|
|
|
|
|
<p>The first table we are going to create will contain the domains we will be using with our mail server :</p>
|
|
<div class="highlight"><pre><span class="k">CREATE</span> <span class="k">TABLE</span> <span class="o">`</span><span class="n">virtual_domains</span><span class="o">`</span> <span class="p">(</span>
|
|
<span class="o">`</span><span class="n">id</span><span class="o">`</span> <span class="nb">INT</span> <span class="k">NOT</span> <span class="k">NULL</span> <span class="n">AUTO_INCREMENT</span><span class="p">,</span>
|
|
<span class="o">`</span><span class="n">name</span><span class="o">`</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">50</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
|
|
<span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="p">(</span><span class="o">`</span><span class="n">id</span><span class="o">`</span><span class="p">)</span>
|
|
<span class="p">)</span> <span class="n">ENGINE</span><span class="o">=</span><span class="n">InnoDB</span> <span class="k">DEFAULT</span> <span class="n">CHARSET</span><span class="o">=</span><span class="n">utf8</span><span class="p">;</span>
|
|
</pre></div>
|
|
|
|
|
|
<p>Then, we are going to create the table that will contain our users and their password :</p>
|
|
<div class="highlight"><pre><span class="k">CREATE</span> <span class="k">TABLE</span> <span class="o">`</span><span class="n">virtual_users</span><span class="o">`</span> <span class="p">(</span>
|
|
<span class="o">`</span><span class="n">id</span><span class="o">`</span> <span class="nb">INT</span> <span class="k">NOT</span> <span class="k">NULL</span> <span class="n">AUTO_INCREMENT</span><span class="p">,</span>
|
|
<span class="o">`</span><span class="n">domain_id</span><span class="o">`</span> <span class="nb">INT</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
|
|
<span class="o">`</span><span class="n">password</span><span class="o">`</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">106</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
|
|
<span class="o">`</span><span class="n">email</span><span class="o">`</span> <span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">120</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
|
|
<span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="p">(</span><span class="o">`</span><span class="n">id</span><span class="o">`</span><span class="p">),</span>
|
|
<span class="k">UNIQUE</span> <span class="k">KEY</span> <span class="o">`</span><span class="n">email</span><span class="o">`</span> <span class="p">(</span><span class="o">`</span><span class="n">email</span><span class="o">`</span><span class="p">),</span>
|
|
<span class="k">FOREIGN</span> <span class="k">KEY</span> <span class="p">(</span><span class="n">domain_id</span><span class="p">)</span> <span class="k">REFERENCES</span> <span class="n">virtual_domains</span><span class="p">(</span><span class="n">id</span><span class="p">)</span> <span class="k">ON</span> <span class="k">DELETE</span> <span class="k">CASCADE</span>
|
|
<span class="p">)</span> <span class="n">ENGINE</span><span class="o">=</span><span class="n">InnoDB</span> <span class="k">DEFAULT</span> <span class="n">CHARSET</span><span class="o">=</span><span class="n">utf8</span><span class="p">;</span>
|
|
</pre></div>
|
|
|
|
|
|
<p>Finally, the last table we are going to create will contain our mail aliases :</p>
|
|
<div class="highlight"><pre><span class="k">CREATE</span> <span class="k">TABLE</span> <span class="o">`</span><span class="n">virtual_aliases</span><span class="o">`</span> <span class="p">(</span>
|
|
<span class="o">`</span><span class="n">id</span><span class="o">`</span> <span class="nb">INT</span> <span class="k">NOT</span> <span class="k">NULL</span> <span class="n">AUTO_INCREMENT</span><span class="p">,</span>
|
|
<span class="o">`</span><span class="n">domain_id</span><span class="o">`</span> <span class="nb">INT</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
|
|
<span class="o">`</span><span class="k">source</span><span class="o">`</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">100</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
|
|
<span class="o">`</span><span class="n">destination</span><span class="o">`</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">100</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span><span class="p">,</span>
|
|
<span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="p">(</span><span class="o">`</span><span class="n">id</span><span class="o">`</span><span class="p">),</span>
|
|
<span class="k">FOREIGN</span> <span class="k">KEY</span> <span class="p">(</span><span class="n">domain_id</span><span class="p">)</span> <span class="k">REFERENCES</span> <span class="n">virtual_domains</span><span class="p">(</span><span class="n">id</span><span class="p">)</span> <span class="k">ON</span> <span class="k">DELETE</span> <span class="k">CASCADE</span>
|
|
<span class="p">)</span> <span class="n">ENGINE</span><span class="o">=</span><span class="n">InnoDB</span> <span class="k">DEFAULT</span> <span class="n">CHARSET</span><span class="o">=</span><span class="n">utf8</span><span class="p">;</span>
|
|
</pre></div>
|
|
|
|
|
|
<h3>Domains, users and aliases management</h3>
|
|
<p>We are now going to add data to the tables we have created.</p>
|
|
<p>First, let's add a domain to the virtual_domains table :</p>
|
|
<div class="highlight"><pre><span class="k">INSERT</span> <span class="k">INTO</span> <span class="n">virtual_domains</span> <span class="p">(</span><span class="o">`</span><span class="n">name</span><span class="o">`</span><span class="p">)</span> <span class="k">VALUES</span> <span class="p">(</span><span class="s1">'captainark.net'</span><span class="p">);</span>
|
|
</pre></div>
|
|
|
|
|
|
<p>We can now create users associated with this domain in the virtual_users table :</p>
|
|
<div class="highlight"><pre><span class="k">INSERT</span> <span class="k">INTO</span> <span class="n">virtual_users</span> <span class="p">(</span><span class="o">`</span><span class="n">domain_id</span><span class="o">`</span><span class="p">,</span> <span class="o">`</span><span class="n">password</span><span class="o">`</span> <span class="p">,</span> <span class="o">`</span><span class="n">email</span><span class="o">`</span><span class="p">)</span> <span class="k">VALUES</span>
|
|
<span class="p">(</span><span class="s1">'1'</span><span class="p">,</span> <span class="n">ENCRYPT</span><span class="p">(</span><span class="s1">'notanactualpassword'</span><span class="p">,</span> <span class="n">CONCAT</span><span class="p">(</span><span class="s1">'$6$'</span><span class="p">,</span> <span class="k">SUBSTRING</span><span class="p">(</span><span class="n">SHA</span><span class="p">(</span><span class="n">RAND</span><span class="p">()),</span> <span class="o">-</span><span class="mi">16</span><span class="p">))),</span>
|
|
<span class="s1">'example@captainark.net'</span><span class="p">);</span>
|
|
</pre></div>
|
|
|
|
|
|
<p>This is not mandatory, but we can also create our first mail alias :</p>
|
|
<div class="highlight"><pre><span class="k">INSERT</span> <span class="k">INTO</span> <span class="n">virtual_aliases</span> <span class="p">(</span><span class="o">`</span><span class="n">domain_id</span><span class="o">`</span><span class="p">,</span> <span class="o">`</span><span class="k">source</span><span class="o">`</span><span class="p">,</span> <span class="o">`</span><span class="n">destination</span><span class="o">`</span><span class="p">)</span> <span class="k">VALUES</span>
|
|
<span class="p">(</span><span class="s1">'1'</span><span class="p">,</span> <span class="s1">'alias@captainark.net'</span><span class="p">,</span> <span class="s1">'example@captainark.net'</span><span class="p">);</span>
|
|
</pre></div>
|
|
|
|
|
|
<p>Now, all messages sent to alias@captainark.net will be forwarded to example@captainark.net.</p>
|
|
<p>Use the same syntax to create additional domains, users and aliases. If you have more than one domains configured, be sure to associate your users and aliases with the correct domain_id.</p>
|
|
<h2>Configuring Postfix</h2>
|
|
<p>Next, we are going to configure <a href="http://www.postfix.org/">Postfix</a>.</p>
|
|
<h3>Configuration backup</h3>
|
|
<p>First, let's backup the original configuration files :</p>
|
|
<div class="highlight"><pre>cp /etc/postfix/main.cf /etc/postfix/main.cf.orig
|
|
cp /etc/postfix/master.cf /etc/postfix/master.cf.orig
|
|
</pre></div>
|
|
|
|
|
|
<h3>User and group creation</h3>
|
|
<p>We are now going to create a user and group called vmail that will be used by both Postfix and Dovecot :</p>
|
|
<div class="highlight"><pre>groupadd -g <span class="m">5000</span> vmail
|
|
useradd -g vmail -u <span class="m">5000</span> vmail -d /var/mail -m -s /bin/false
|
|
</pre></div>
|
|
|
|
|
|
<h3>SSL certificates</h3>
|
|
<p>Next, we are going to create the folder where we will store the SSL certificates :</p>
|
|
<div class="highlight"><pre>mkdir /etc/postfix/ssl
|
|
chown root: /etc/postfix/ssl <span class="o">&&</span> chmod <span class="m">600</span> /etc/postfix/ssl
|
|
</pre></div>
|
|
|
|
|
|
<p>Purists will probably want to store their certificates in /etc/ssl/private. If you choose to do so, you'll have to adapt the path of those files for the remainder of this tutorial.</p>
|
|
<p>If you've decided to create a certificate with StartSSL, you'll end up with two files, a .crt and a .key. I'll name those files server.crt and server-with-passphrase.key. Put both these files in the folder we've just created.</p>
|
|
<p>Now, let's remove the passphrase from the key :</p>
|
|
<div class="highlight"><pre><span class="nb">cd</span> /etc/postfix/ssl
|
|
openssl rsa -in server-with-passphrase.key -out server.key
|
|
</pre></div>
|
|
|
|
|
|
<p>You'll be prompted for the passphrase you chose during the certificate generation.</p>
|
|
<p>Next, we have to download the appropriate intermediate certificate :</p>
|
|
<div class="highlight"><pre>wget -O /etc/postfix/ssl/sub.class1.server.ca.pem <span class="se">\</span>
|
|
http://www.startssl.com/certs/sub.class1.server.ca.pem
|
|
</pre></div>
|
|
|
|
|
|
<p>We now have to make sure that the permissions on those files are correct :</p>
|
|
<div class="highlight"><pre>chown root: /etc/postfix/ssl/* <span class="o">&&</span> chmod <span class="m">600</span> /etc/postfix/ssl/*
|
|
</pre></div>
|
|
|
|
|
|
<p>The last thing we have to do here is to generate Diffie-Hellman keys for Perfect Forward Secrecy (PFS) :</p>
|
|
<div class="highlight"><pre>openssl gendh -out /etc/postfix/dh_512.pem -2 512
|
|
openssl gendh -out /etc/postfix/dh_1024.pem -2 1024
|
|
</pre></div>
|
|
|
|
|
|
<h3>Postifx configuration</h3>
|
|
<p>First, let's edit the /etc/postfix/main.cf file. It should end up looking something like that :</p>
|
|
<div class="highlight"><pre>smtpd_banner = <span class="nv">$myhostname</span> ESMTP <span class="nv">$mail_name</span> (Debian/GNU)
|
|
biff = no
|
|
|
|
broken_sasl_auth_clients = yes
|
|
config_directory = /etc/postfix
|
|
disable_vrfy_command = yes
|
|
smtpd_data_restrictions = reject_unauth_pipelining, permit
|
|
smtpd_helo_required = yes
|
|
|
|
queue_directory = /var/spool/postfix
|
|
append_dot_mydomain = no
|
|
readme_directory = no
|
|
|
|
smtpd_use_tls=yes
|
|
smtpd_tls_auth_only = yes
|
|
smtpd_tls_cert_file=/etc/postfix/ssl/server.crt
|
|
smtpd_tls_key_file=/etc/postfix/ssl/server.key
|
|
smtpd_tls_CAfile=/etc/postfix/ssl/sub.class1.server.ca.pem
|
|
smtpd_tls_mandatory_protocols=!SSLv2,!SSLv3
|
|
smtpd_tls_protocols=!SSLv2,!SSLv3
|
|
smtpd_tls_mandatory_ciphers=high
|
|
smtpd_tls_dh1024_param_file = /etc/postfix/dh_1024.pem
|
|
smtpd_tls_dh512_param_file = /etc/postfix/dh_512.pem
|
|
smtpd_tls_eecdh_grade = strong
|
|
smtpd_tls_loglevel = 1
|
|
smtpd_tls_received_header = yes
|
|
smtpd_tls_session_cache_database = btree:<span class="cp">${</span><span class="n">data_directory</span><span class="cp">}</span>/smtpd_scache
|
|
|
|
tls_preempt_cipherlist = yes
|
|
tls_random_source = dev:/dev/urandom
|
|
|
|
smtpd_data_restrictions = reject_unauth_pipelining, permit
|
|
smtpd_helo_required = yes
|
|
|
|
smtp_tls_CAfile = <span class="nv">$smtpd_tls_CAfile</span>
|
|
smtp_tls_mandatory_protocols=!SSLv2,!SSLv3
|
|
smtp_tls_protocols=!SSLv2,!SSLv3
|
|
smtp_tls_security_level = may
|
|
smtp_tls_loglevel = 1
|
|
smtp_tls_session_cache_database = btree:<span class="cp">${</span><span class="n">data_directory</span><span class="cp">}</span>/smtp_scache
|
|
|
|
smtpd_milters =
|
|
non_smtpd_milters = <span class="nv">$smtpd_milters</span>
|
|
milter_protocol = 2
|
|
milter_default_action = accept
|
|
|
|
smtpd_recipient_restrictions =
|
|
reject_invalid_hostname,
|
|
reject_non_fqdn_hostname,
|
|
reject_non_fqdn_sender,
|
|
reject_non_fqdn_recipient,
|
|
reject_unknown_sender_domain,
|
|
reject_unknown_recipient_domain,
|
|
permit_mynetworks,
|
|
permit_sasl_authenticated,
|
|
reject_unauth_destination,
|
|
permit
|
|
|
|
smtpd_sasl_auth_enable = yes
|
|
smtpd_sasl_local_domain = <span class="nv">$myhostname</span>
|
|
smtpd_sasl_security_options = noanonymous
|
|
smtpd_sasl_tls_security_options = <span class="nv">$smtpd_sasl_security_options</span>
|
|
smtpd_tls_auth_only = yes
|
|
smtpd_sasl_type = dovecot
|
|
smtpd_sasl_path = private/auth
|
|
|
|
myhostname = myserver.captainark.net ### CHANGE THIS
|
|
alias_maps = hash:/etc/aliases
|
|
alias_database = hash:/etc/aliases
|
|
myorigin = /etc/mailname
|
|
mydestination = localhost, myserver.captainark.net ### CHANGE THIS
|
|
relayhost =
|
|
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
|
|
mailbox_size_limit = 0
|
|
recipient_delimiter = +
|
|
default_transport = smtp
|
|
relay_transport = smtp
|
|
inet_interfaces = all
|
|
inet_protocols = all
|
|
|
|
virtual_transport = lmtp:unix:private/dovecot-lmtp
|
|
virtual_mailbox_domains = mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
|
|
virtual_mailbox_maps = mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
|
|
virtual_alias_maps = mysql:/etc/postfix/mysql-virtual-alias-maps.cf
|
|
</pre></div>
|
|
|
|
|
|
<p>The variable "myhostname" has to be defined to you server's FQDN. The file /etc/mailname should contain your server's FQDN as well.</p>
|
|
<p>Next, we need to edit the /etc/postfix/master.cf file. You need to uncomment the following lines :</p>
|
|
<div class="highlight"><pre>submission inet n - - - - smtpd
|
|
-o syslog_name=postfix/submission
|
|
-o smtpd_tls_security_level=encrypt
|
|
-o tls_preempt_cipherlist=yes
|
|
-o smtpd_sasl_auth_enable=yes
|
|
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
|
|
|
|
smtps inet n - - - - smtpd
|
|
-o syslog_name=postfix/smtps
|
|
-o smtpd_tls_wrappermode=yes
|
|
-o smtpd_sasl_auth_enable=yes
|
|
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
|
|
</pre></div>
|
|
|
|
|
|
<p>You also have to add the following lines at the end of the file :</p>
|
|
<div class="highlight"><pre>dovecot unix - n n - - pipe
|
|
flags=DRhu user=vmail:vmail argv=/usr/lib/dovecot/deliver -f <span class="cp">${</span><span class="n">sender</span><span class="cp">}</span> -d <span class="cp">${</span><span class="n">recipient</span><span class="cp">}</span>
|
|
</pre></div>
|
|
|
|
|
|
<h3>MySQL access for Postfix</h3>
|
|
<p>We now need to allow Postfix to connect to the MySQL database we have created earlier. To that end, we must create three files.</p>
|
|
<p>/etc/postfix/mysql-virtual-mailbox-domains.cf should contain the following lines :</p>
|
|
<div class="highlight"><pre>user = mail
|
|
password = mailpassword
|
|
hosts = 127.0.0.1
|
|
dbname = mail
|
|
query = SELECT 1 FROM virtual_domains WHERE name='%s'
|
|
</pre></div>
|
|
|
|
|
|
<p>/etc/postfix/mysql-virtual-mailbox-maps.cf should contain the following lines :</p>
|
|
<div class="highlight"><pre>user = mail
|
|
password = mailpassword
|
|
hosts = 127.0.0.1
|
|
dbname = mail
|
|
query = SELECT 1 FROM virtual_users WHERE email='%s'
|
|
</pre></div>
|
|
|
|
|
|
<p>/etc/postfix/mysql-virtual-alias-maps.cf should contain the following lines :</p>
|
|
<div class="highlight"><pre>user = mail
|
|
password = mailpassword
|
|
hosts = 127.0.0.1
|
|
dbname = mail
|
|
query = SELECT destination FROM virtual_aliases WHERE source='%s'
|
|
</pre></div>
|
|
|
|
|
|
<p>Since these files contain a password, let's make sure they are not world-readable :</p>
|
|
<div class="highlight"><pre>chown root: /etc/postfix/mysql* <span class="o">&&</span> chmod <span class="m">600</span> /etc/postfix/mysql*
|
|
</pre></div>
|
|
|
|
|
|
<p>You can use the command postmap to confirm that everything is working properly :</p>
|
|
<div class="highlight"><pre>postmap -q captainark.net mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
|
|
|
|
postmap -q example@captainark.net mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
|
|
|
|
postmap -q alias@captainark.net mysql:/etc/postfix/mysql-virtual-alias-maps.cf
|
|
</pre></div>
|
|
|
|
|
|
<p>Let's restart postfix for our modifications to be taken into account :</p>
|
|
<div class="highlight"><pre>systemctl restart postfix
|
|
</pre></div>
|
|
|
|
|
|
<p>That's it for Postfix, for now ; Dovecot is next !</p>
|
|
<h2>Configuring Dovecot</h2>
|
|
<h3>Dovecot global configuration</h3>
|
|
<p>By default, on Debian, <a href="http://www.dovecot.org/">Dovecot</a> uses multiple configuration files in /etc/dovecot/conf.d. I found it annoying to maintain, and I ended up only using the /etc/doveconf.conf file.</p>
|
|
<p>As always, let's start by backing up the original configuration file :</p>
|
|
<div class="highlight"><pre>mv /etc/dovecot/dovecot.conf /etc/dovecot/dovecot.conf.orig
|
|
</pre></div>
|
|
|
|
|
|
<p>Next, we are going to create a new /etc/dovecot/dovecot.conf file. It should contain the following lines :</p>
|
|
<div class="highlight"><pre><span class="sx">!include_try /usr/share/dovecot/protocols.d/*.protocol</span>
|
|
<span class="n">protocols</span> <span class="p">=</span> <span class="n">imap</span> <span class="n">lmtp</span> <span class="n">sieve</span>
|
|
|
|
<span class="n">mail_location</span> <span class="p">=</span> <span class="n">maildir</span><span class="p">:</span><span class="o">/</span><span class="n">var</span><span class="o">/</span><span class="n">mail</span><span class="o">/</span><span class="c">%d/%n</span>
|
|
<span class="n">mail_privileged_group</span> <span class="p">=</span> <span class="n">vmail</span>
|
|
<span class="n">mail_plugin_dir</span> <span class="p">=</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">dovecot</span><span class="o">/</span><span class="n">modules</span>
|
|
<span class="n">mail_plugins</span> <span class="p">=</span>
|
|
|
|
<span class="n">disable_plaintext_auth</span> <span class="p">=</span> <span class="n">yes</span>
|
|
<span class="n">auth_mechanisms</span> <span class="p">=</span> <span class="n">plain</span> <span class="n">login</span>
|
|
|
|
<span class="n">service</span> <span class="n">director</span> <span class="p">{</span>
|
|
<span class="n">unix_listener</span> <span class="n">login</span><span class="o">/</span><span class="n">director</span> <span class="p">{</span>
|
|
<span class="p">}</span>
|
|
<span class="n">fifo_listener</span> <span class="n">login</span><span class="o">/</span><span class="n">proxy</span><span class="o">-</span><span class="n">notify</span> <span class="p">{</span>
|
|
<span class="p">}</span>
|
|
<span class="n">unix_listener</span> <span class="n">director</span><span class="o">-</span><span class="n">userdb</span> <span class="p">{</span>
|
|
<span class="p">}</span>
|
|
<span class="n">inet_listener</span> <span class="p">{</span>
|
|
<span class="p">}</span>
|
|
<span class="p">}</span>
|
|
|
|
<span class="n">namespace</span> <span class="n">inbox</span> <span class="p">{</span>
|
|
<span class="n">inbox</span> <span class="p">=</span> <span class="n">yes</span>
|
|
<span class="n">type</span> <span class="p">=</span> <span class="n">private</span>
|
|
<span class="n">mailbox</span> <span class="n">Drafts</span> <span class="p">{</span>
|
|
<span class="n">auto</span> <span class="p">=</span> <span class="n">subscribe</span>
|
|
<span class="n">special_use</span> <span class="p">=</span> <span class="o">\</span><span class="n">Drafts</span>
|
|
<span class="p">}</span>
|
|
<span class="n">mailbox</span> <span class="n">Junk</span> <span class="p">{</span>
|
|
<span class="n">auto</span> <span class="p">=</span> <span class="n">subscribe</span>
|
|
<span class="n">special_use</span> <span class="p">=</span> <span class="o">\</span><span class="n">Junk</span>
|
|
<span class="p">}</span>
|
|
<span class="n">mailbox</span> <span class="n">Sent</span> <span class="p">{</span>
|
|
<span class="n">auto</span> <span class="p">=</span> <span class="n">subscribe</span>
|
|
<span class="n">special_use</span> <span class="p">=</span> <span class="o">\</span><span class="n">Sent</span>
|
|
<span class="p">}</span>
|
|
<span class="n">mailbox</span> <span class="n">Trash</span> <span class="p">{</span>
|
|
<span class="n">auto</span> <span class="p">=</span> <span class="n">subscribe</span>
|
|
<span class="n">special_use</span> <span class="p">=</span> <span class="o">\</span><span class="n">Trash</span>
|
|
<span class="p">}</span>
|
|
<span class="p">}</span>
|
|
|
|
<span class="n">service</span> <span class="n">imap</span><span class="o">-</span><span class="n">login</span> <span class="p">{</span>
|
|
<span class="n">inet_listener</span> <span class="n">imap</span> <span class="p">{</span>
|
|
<span class="n">port</span> <span class="p">=</span> <span class="mi">0</span>
|
|
<span class="p">}</span>
|
|
<span class="n">inet_listener</span> <span class="n">imaps</span> <span class="p">{</span>
|
|
<span class="n">port</span> <span class="p">=</span> <span class="mi">993</span>
|
|
<span class="n">ssl</span> <span class="p">=</span> <span class="n">yes</span>
|
|
<span class="p">}</span>
|
|
<span class="p">}</span>
|
|
|
|
<span class="n">service</span> <span class="n">pop3</span><span class="o">-</span><span class="n">login</span> <span class="p">{</span>
|
|
<span class="n">inet_listener</span> <span class="n">pop3</span> <span class="p">{</span>
|
|
<span class="n">port</span> <span class="p">=</span> <span class="mi">0</span>
|
|
<span class="p">}</span>
|
|
<span class="n">inet_listener</span> <span class="n">pop3s</span> <span class="p">{</span>
|
|
<span class="n">port</span> <span class="p">=</span> <span class="mi">0</span>
|
|
<span class="p">}</span>
|
|
<span class="p">}</span>
|
|
|
|
<span class="n">service</span> <span class="n">lmtp</span> <span class="p">{</span>
|
|
<span class="n">unix_listener</span> <span class="o">/</span><span class="n">var</span><span class="o">/</span><span class="n">spool</span><span class="o">/</span><span class="n">postfix</span><span class="o">/</span><span class="n">private</span><span class="o">/</span><span class="n">dovecot</span><span class="o">-</span><span class="n">lmtp</span> <span class="p">{</span>
|
|
<span class="n">mode</span> <span class="p">=</span> <span class="mi">0600</span>
|
|
<span class="n">user</span> <span class="p">=</span> <span class="n">postfix</span>
|
|
<span class="n">group</span> <span class="p">=</span> <span class="n">postfix</span>
|
|
<span class="p">}</span>
|
|
<span class="p">}</span>
|
|
|
|
<span class="n">service</span> <span class="n">imap</span> <span class="p">{</span>
|
|
<span class="p">}</span>
|
|
|
|
<span class="n">service</span> <span class="n">pop3</span> <span class="p">{</span>
|
|
<span class="p">}</span>
|
|
|
|
<span class="n">service</span> <span class="n">auth</span> <span class="p">{</span>
|
|
<span class="n">unix_listener</span> <span class="o">/</span><span class="n">var</span><span class="o">/</span><span class="n">spool</span><span class="o">/</span><span class="n">postfix</span><span class="o">/</span><span class="n">private</span><span class="o">/</span><span class="n">auth</span> <span class="p">{</span>
|
|
<span class="n">mode</span> <span class="p">=</span> <span class="mi">0666</span>
|
|
<span class="n">user</span> <span class="p">=</span> <span class="n">postfix</span>
|
|
<span class="n">group</span> <span class="p">=</span> <span class="n">postfix</span>
|
|
<span class="p">}</span>
|
|
<span class="n">unix_listener</span> <span class="n">auth</span><span class="o">-</span><span class="n">userdb</span> <span class="p">{</span>
|
|
<span class="n">mode</span> <span class="p">=</span> <span class="mi">0600</span>
|
|
<span class="n">user</span> <span class="p">=</span> <span class="n">vmail</span>
|
|
<span class="p">}</span>
|
|
<span class="n">user</span> <span class="p">=</span> <span class="n">dovecot</span>
|
|
<span class="p">}</span>
|
|
|
|
<span class="n">service</span> <span class="n">auth</span><span class="o">-</span><span class="n">worker</span> <span class="p">{</span>
|
|
<span class="n">user</span> <span class="p">=</span> <span class="n">vmail</span>
|
|
<span class="p">}</span>
|
|
|
|
<span class="n">service</span> <span class="n">dict</span> <span class="p">{</span>
|
|
<span class="n">unix_listener</span> <span class="n">dict</span> <span class="p">{</span>
|
|
<span class="p">}</span>
|
|
<span class="p">}</span>
|
|
|
|
<span class="n">ssl</span> <span class="p">=</span> <span class="n">required</span>
|
|
<span class="n">ssl_cert</span> <span class="p">=</span> <span class="o"></</span><span class="n">etc</span><span class="o">/</span><span class="n">postfix</span><span class="o">/</span><span class="n">ssl</span><span class="o">/</span><span class="n">server</span><span class="p">.</span><span class="n">crt</span>
|
|
<span class="n">ssl_key</span> <span class="p">=</span> <span class="o"></</span><span class="n">etc</span><span class="o">/</span><span class="n">postfix</span><span class="o">/</span><span class="n">ssl</span><span class="o">/</span><span class="n">server</span><span class="p">.</span><span class="n">key</span>
|
|
<span class="n">ssl_ca</span> <span class="p">=</span> <span class="o"></</span><span class="n">etc</span><span class="o">/</span><span class="n">postfix</span><span class="o">/</span><span class="n">ssl</span><span class="o">/</span><span class="n">sub</span><span class="p">.</span><span class="n">class1</span><span class="p">.</span><span class="n">server</span><span class="p">.</span><span class="n">ca</span><span class="p">.</span><span class="n">pem</span>
|
|
<span class="n">ssl_protocols</span> <span class="p">=</span> !<span class="n">SSLv2</span> !<span class="n">SSLv3</span>
|
|
<span class="n">ssl_cipher_list</span> <span class="p">=</span> <span class="n">AES128</span><span class="o">+</span><span class="n">EECDH</span><span class="p">:</span><span class="n">AES128</span><span class="o">+</span><span class="n">EDH</span><span class="p">:</span>!<span class="n">aNULL</span><span class="p">;</span>
|
|
<span class="n">protocol</span> <span class="n">lda</span> <span class="p">{</span>
|
|
<span class="n">mail_plugins</span> <span class="p">=</span> $<span class="n">mail_plugins</span> <span class="n">sieve</span>
|
|
<span class="p">}</span>
|
|
|
|
<span class="n">protocol</span> <span class="n">imap</span> <span class="p">{</span>
|
|
<span class="n">mail_plugins</span> <span class="p">=</span> $<span class="n">mail_plugins</span>
|
|
<span class="p">}</span>
|
|
<span class="n">protocol</span> <span class="n">lmtp</span> <span class="p">{</span>
|
|
<span class="n">mail_plugins</span> <span class="p">=</span> $<span class="n">mail_plugins</span> <span class="n">sieve</span>
|
|
<span class="p">}</span>
|
|
|
|
<span class="n">plugin</span> <span class="p">{</span>
|
|
<span class="n">sieve</span> <span class="p">=</span> <span class="o">/</span><span class="n">var</span><span class="o">/</span><span class="n">mail</span><span class="o">/</span><span class="n">sieve</span><span class="o">/</span><span class="n">users</span><span class="o">/</span><span class="c">%u.sieve</span>
|
|
<span class="n">sieve_after</span> <span class="p">=</span> <span class="o">/</span><span class="n">var</span><span class="o">/</span><span class="n">mail</span><span class="o">/</span><span class="n">sieve</span><span class="o">/</span><span class="n">after</span>
|
|
<span class="n">sieve_before</span> <span class="p">=</span> <span class="o">/</span><span class="n">var</span><span class="o">/</span><span class="n">mail</span><span class="o">/</span><span class="n">sieve</span><span class="o">/</span><span class="n">before</span>
|
|
<span class="n">sieve_global_dir</span> <span class="p">=</span> <span class="o">/</span><span class="n">var</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">dovecot</span><span class="o">/</span><span class="n">sieve</span><span class="o">/</span>
|
|
<span class="n">sieve_dir</span> <span class="p">=</span> <span class="o">~/</span><span class="n">sieve</span>
|
|
<span class="p">}</span>
|
|
|
|
<span class="n">passdb</span> <span class="p">{</span>
|
|
<span class="n">driver</span> <span class="p">=</span> <span class="n">sql</span>
|
|
<span class="n">args</span> <span class="p">=</span> <span class="o">/</span><span class="n">etc</span><span class="o">/</span><span class="n">dovecot</span><span class="o">/</span><span class="n">sql</span><span class="p">.</span><span class="n">conf</span>
|
|
<span class="p">}</span>
|
|
<span class="n">userdb</span> <span class="p">{</span>
|
|
<span class="n">driver</span> <span class="p">=</span> <span class="n">static</span>
|
|
<span class="n">args</span> <span class="p">=</span> <span class="n">uid</span><span class="p">=</span><span class="n">vmail</span> <span class="n">gid</span><span class="p">=</span><span class="n">vmail</span> <span class="n">home</span><span class="p">=</span><span class="o">/</span><span class="n">var</span><span class="o">/</span><span class="n">mail</span><span class="o">/</span><span class="c">%d/%n</span>
|
|
<span class="p">}</span>
|
|
</pre></div>
|
|
|
|
|
|
<p>Dovecot will use the same SSL certificate as Postfix.</p>
|
|
<p>Using this configuration, your virtual users' emails will be stored in /var/mail/$domain/$user/ and will be owned by the vmail user.</p>
|
|
<p>For this to work, we have to create the domain folder :</p>
|
|
<div class="highlight"><pre>mkdir -p /var/mail/captainark.net
|
|
chown vmail: /var/mail/captainark.net <span class="o">&&</span> chmod <span class="m">770</span> /var/mail/captainark.net
|
|
</pre></div>
|
|
|
|
|
|
<p>Dovecot will create the virtual users' folders automatically.</p>
|
|
<h3>Dovecot access to the MySQL database</h3>
|
|
<p>We now need to allow Dovecot to connect to the mail database we have populated earlier. To do so, we are going to create a /etc/dovecot/sql.conf file with the following content :</p>
|
|
<div class="highlight"><pre>driver = mysql
|
|
connect = host=localhost dbname=mail user=mail password=mailpassword
|
|
default_pass_scheme = SHA512-CRYPT
|
|
password_query = SELECT email as user, password FROM virtual_users WHERE email='%u';
|
|
</pre></div>
|
|
|
|
|
|
<p>You'll have to change the password to the one you have defined earlier. Since this file contains a password, let's make sure it's not world-readable :</p>
|
|
<div class="highlight"><pre>chown root: /etc/dovecot/sql.conf <span class="o">&&</span> chmod <span class="m">600</span> /etc/dovecot/sql.conf
|
|
</pre></div>
|
|
|
|
|
|
<h3>Configuring Sieve</h3>
|
|
<p>The last thing we need to configure here is sieve. The idea is to have all messages flagged as spam automatically moved to the mailbox Junk folder.</p>
|
|
<p>To do so, let's first create the required folders :</p>
|
|
<div class="highlight"><pre>mkdir -p /var/mail/sieve/before
|
|
mkdir /var/mail/sieve/after
|
|
mkdir /var/mail/sieve/users
|
|
chown -R vmail: /var/mail/sieve <span class="o">&&</span> chmod -R <span class="m">770</span> /var/mail/sieve
|
|
</pre></div>
|
|
|
|
|
|
<p>If you want to have sieve rules for a specific user, simply create $user@$domain.sieve file in the users folder (example@captainark.net in my case).</p>
|
|
<p>All .sieve files in the before folder will be used for all your virtual users, before their individual configuration ; the .sieve files in the after folder will be used, well, you guessed it, after.</p>
|
|
<p>Let's create a filter.sieve file in the /var/mail/sieve/before folder with the following content :</p>
|
|
<div class="highlight"><pre>require ["envelope", "fileinto", "imap4flags", "regex"];
|
|
|
|
if not header :regex "message-id" ".*@.*\." {
|
|
fileinto "Junk";
|
|
}
|
|
|
|
if header :contains "X-Spam-Level" "*****" {
|
|
fileinto "Junk";
|
|
}
|
|
</pre></div>
|
|
|
|
|
|
<p>Last thing we have to do is to change the permissions on the newly created file :</p>
|
|
<div class="highlight"><pre>chown vmail: /var/mail/sieve/before/filter.sieve <span class="o">&&</span> <span class="se">\</span>
|
|
chmod <span class="m">660</span> /var/mail/sieve/before/filter.sieve
|
|
</pre></div>
|
|
|
|
|
|
<p>That's all ; now, all email we receive that is flagged as spam by SpamAssassin will be moved to the Junk folder.</p>
|
|
<p>Let's restart dovecot :</p>
|
|
<div class="highlight"><pre>systemctl restart dovecot
|
|
</pre></div>
|
|
|
|
|
|
<p>We now have a working mail server !</p>
|
|
<p>To connect to it and access your mailbox, configure your email client as follow :</p>
|
|
<ul>
|
|
<li>Username: example@captainark.net ;</li>
|
|
<li>Password: the password you chose for your virtual user ;</li>
|
|
<li>IMAP: your server's FQDN, port 993 (SSL/TLS with normal password) ;</li>
|
|
<li>SMTP: your server's FQDN, port 465 (SSL/TLS with normal password).</li>
|
|
</ul>
|
|
<h2>Configuring SpamAssassin</h2>
|
|
<h3>The alternatives</h3>
|
|
<p>Next thing we have to do is to configure the actual anti-spam. I tried a few, but I ended up sticking with <a href="http://spamassassin.apache.org/">SpamAssassin</a>. Here's why :</p>
|
|
<ul>
|
|
<li><a href="http://dspam.nuclearelephant.com/">DSPAM</a> is <a href="http://sourceforge.net/p/dspam/mailman/message/32585111/">no longer maintained</a> and <a href="https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=754810">has been removed from Debian Jessie</a> ;</li>
|
|
<li><a href="https://rspamd.com/">Rspamd</a> is interesting, has been <a href="https://packages.debian.org/source/jessie/rspamd">integrated in Debian Jessie</a>, but is poorly documented at this time ;</li>
|
|
<li><a href="http://bogofilter.sourceforge.net/">Bogofilter</a> does not seem to have the greatest server integration.</li>
|
|
</ul>
|
|
<h3>The actual configuration</h3>
|
|
<p>SpamAssassin's configuration is pretty straightforward. First, let's edit the /etc/default/spamassassin file :</p>
|
|
<div class="highlight"><pre>ENABLED=1
|
|
[...]
|
|
CRON=1
|
|
</pre></div>
|
|
|
|
|
|
<p>Before the cron runs for the first time, we have to manually update SpamAssassin's ruleset :</p>
|
|
<div class="highlight"><pre>sa-learn
|
|
</pre></div>
|
|
|
|
|
|
<p>Next, as usual, let's back up the original configuration file :</p>
|
|
<div class="highlight"><pre>mv /etc/spamassassin/local.cf /etc/spamassassin/local.cf.orig
|
|
</pre></div>
|
|
|
|
|
|
<p>Let's create a new /etc/spamassassin/local.cf file with the following content :</p>
|
|
<div class="highlight"><pre>rewrite_header Subject [SPAM]
|
|
report_safe 0
|
|
required_score 5.0
|
|
use_bayes 1
|
|
bayes_auto_learn 1
|
|
|
|
whitelist_from *@captainark.net
|
|
</pre></div>
|
|
|
|
|
|
<p>Next, to have Postfix send incoming emails through SpamAssassin, we have to edit the /etc/postfix/master.cf file. At the very beginning, we have to add a line under the smtp definition :</p>
|
|
<div class="highlight"><pre>smtp inet n - - - - smtpd
|
|
-o content_filter=spamassassin
|
|
</pre></div>
|
|
|
|
|
|
<p>At the very end of the same file, we have to add the following lines :</p>
|
|
<div class="highlight"><pre>spamassassin unix - n n - - pipe
|
|
user=debian-spamd argv=/usr/bin/spamc -f -e /usr/sbin/sendmail -oi -f <span class="cp">${</span><span class="n">sender</span><span class="cp">}</span> <span class="cp">${</span><span class="n">recipient</span><span class="cp">}</span>
|
|
</pre></div>
|
|
|
|
|
|
<p>Let's restart SpamAssassin and Postfix :</p>
|
|
<div class="highlight"><pre>systemctl restart postfix
|
|
systemctl restart spamassassin
|
|
</pre></div>
|
|
|
|
|
|
<p>That's all for SpamAssassin ! To check if it is working, send yourself an email from another provider. You should see the following headers in it :</p>
|
|
<div class="highlight"><pre>X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on
|
|
myserver.captainark.net
|
|
X-Spam-Level:
|
|
</pre></div>
|
|
|
|
|
|
<h2>Configuring SPF</h2>
|
|
<h3>Allowing your server to send emails for your domain</h3>
|
|
<p><a href="http://www.openspf.org/">SPF</a> (Sender Policy Framework) is a mechanism that confirms that your server's IP is allowed to send emails for your domain. Technically, it is a TXT DNS record which looks something like this :</p>
|
|
<div class="highlight"><pre>captainark.net IN TXT "v=spf1 mx ~all"
|
|
</pre></div>
|
|
|
|
|
|
<p>This DNS record lets other mail servers know that hosts that have a MX record for my domain are also allowed to send emails for it.</p>
|
|
<p>For more information on SPF syntax, you can consult the <a href="http://www.openspf.org/SPF_Record_Syntax">official documentation</a>.</p>
|
|
<p>Without a properly configured SPF record, other mail servers might flag your emails as spam or outright drop them.</p>
|
|
<h3>Checking SPF record for inbound mail</h3>
|
|
<p>Now that we have set up our own SPF record, let's configure Postfix to check that other mail servers communicating with us have done the same.</p>
|
|
<p>First, let's add the two following lines at the end of /etc/postfix-policyd-spf-python/policyd-spf.conf :</p>
|
|
<div class="highlight"><pre>Header_Type = AR
|
|
Authserv_Id = "<server's FQDN>"
|
|
</pre></div>
|
|
|
|
|
|
<p>Then, let's edit the /etc/postfix/master.cf file and add the following lines at the end :</p>
|
|
<div class="highlight"><pre>policy-spf unix - n n - - spawn
|
|
user=nobody argv=/usr/bin/policyd-spf
|
|
</pre></div>
|
|
|
|
|
|
<p>Let's now edit the /etc/postfix/main.cf. In the "smtpd_recipient_restrictions" section, add the "check_policy_service" line as seen below :</p>
|
|
<div class="highlight"><pre>smtpd_recipient_restrictions =
|
|
[...]
|
|
reject_unauth_destination,
|
|
check_policy_service unix:private/policy-spf,
|
|
permit
|
|
</pre></div>
|
|
|
|
|
|
<p>We now have to restart postfix :</p>
|
|
<div class="highlight"><pre>systemctl restart postfix
|
|
</pre></div>
|
|
|
|
|
|
<p>Our server is now checking other mail server's SPF records.</p>
|
|
<p>To make sure that it is working, send yourself an email from another provider. You should see the following header in it :</p>
|
|
<div class="highlight"><pre><span class="nt">Authentication-Results</span><span class="o">:</span> <span class="nt">myserver</span><span class="nc">.captainark.net</span><span class="o">;</span> <span class="nt">spf</span><span class="o">=</span><span class="nt">pass</span> <span class="o">(</span><span class="nt">sender</span> <span class="nt">SPF</span> <span class="nt">authorized</span><span class="o">)</span>
|
|
<span class="cp">[</span><span class="nx">...</span><span class="cp">]</span> <span class="nt">receiver</span><span class="o">=</span><span class="nt">example</span><span class="k">@captainark</span><span class="nc">.net</span><span class="o">)</span>
|
|
</pre></div>
|
|
|
|
|
|
<h2>Configuring OpenDKIM</h2>
|
|
<p><a href="http://www.dkim.org/">DKIM</a> (DomainKeys Identified Mail) is a mechanism that validates a domain name identity for an email through cryptographic authentication.</p>
|
|
<p>While not mandatory, setting up DKIM improves the odds of emails sent from your server not being flagged as spam by other providers.</p>
|
|
<p>With this configuration, OpenDKIM will also check the key for inbound emails.</p>
|
|
<h3>Software side</h3>
|
|
<p>First, let's backup the original configuration file and create a folder for the configuration files :</p>
|
|
<div class="highlight"><pre>mv /etc/opendkim.conf /etc/opendkim.conf.orig
|
|
mkdir /etc/opendkim.d
|
|
</pre></div>
|
|
|
|
|
|
<p>We now have to create a /etc/opendkim.conf file with the following content :</p>
|
|
<div class="highlight"><pre>AutoRestart Yes
|
|
AutoRestartRate 10/1h
|
|
UMask 002
|
|
Syslog yes
|
|
SyslogSuccess Yes
|
|
LogWhy Yes
|
|
|
|
OversignHeaders From
|
|
AlwaysAddARHeader yes
|
|
|
|
Canonicalization relaxed/simple
|
|
|
|
ExternalIgnoreList refile:/etc/opendkim.d/TrustedHosts
|
|
InternalHosts refile:/etc/opendkim.d/dkim/TrustedHosts
|
|
KeyTable refile:/etc/opendkim.d/dkim/KeyTable
|
|
SigningTable refile:/etc/opendkim.d/dkim/SigningTable
|
|
|
|
Mode sv
|
|
PidFile /run/opendkim/opendkim.pid
|
|
SignatureAlgorithm rsa-sha256
|
|
|
|
UserID opendkim:opendkim
|
|
|
|
Socket local:/var/spool/postfix/opendkim/opendkim.sock
|
|
</pre></div>
|
|
|
|
|
|
<p>Let's then create the necessary folders :</p>
|
|
<div class="highlight"><pre>mkdir -p /etc/opendkim.d/keys/captainark.net/
|
|
</pre></div>
|
|
|
|
|
|
<p>Now, we are going to create the /etc/opendkim.d/TrustedHosts file with the following content :</p>
|
|
<div class="highlight"><pre>localhost
|
|
127.0.0.1
|
|
::1
|
|
captainark.net
|
|
</pre></div>
|
|
|
|
|
|
<p>This file contains the hosts and domains for which OpenDKIM should sign emails.</p>
|
|
<p>Next, let's create the /etc/opendkim.d/KeyTable :</p>
|
|
<div class="highlight"><pre>mail._domainkey.captainark.net captainark.net:mail:/etc/opendkim.d/keys/captainark.net/mail.private
|
|
</pre></div>
|
|
|
|
|
|
<p>This file tells OpenDKIM which key it should use for each selector.</p>
|
|
<p>Finally, let's create the /etc/opendkim.d/SigningTable file :</p>
|
|
<div class="highlight"><pre>*@captainark.net mail._domainkey.captainark.net
|
|
</pre></div>
|
|
|
|
|
|
<p>This file tells OpenDKIM which selector it should use for each domain.</p>
|
|
<p>We now have to generate the private/public key pair for our domain :</p>
|
|
<div class="highlight"><pre><span class="nb">cd</span> /etc/opendkim.d/keys/captainark.net/
|
|
opendkim-genkey -s mail -d captainark.net
|
|
</pre></div>
|
|
|
|
|
|
<p>This creates two files ; mail.private contains our private key, mail.txt contains our public key.</p>
|
|
<p>Let's change the permissions on those files :</p>
|
|
<div class="highlight"><pre>chown -R opendkim: /etc/opendkim.d/keys
|
|
chmod -R <span class="m">700</span> /etc/opendkim.d/keys
|
|
chmod <span class="m">600</span> /etc/opendkim.d/captainark.net/*
|
|
</pre></div>
|
|
|
|
|
|
<h3>Postfix integration</h3>
|
|
<p>The last thing we have to do is to configure Postfix to communicate with OpenDKIM.</p>
|
|
<p>First, let's create the necessary folders :</p>
|
|
<div class="highlight"><pre>mkdir /var/spool/postfix/opendkim
|
|
chown opendkim: /var/spool/postfix/opendkim
|
|
</pre></div>
|
|
|
|
|
|
<p>We also have to add the postfix user to the opendkim group :</p>
|
|
<div class="highlight"><pre>useradd -G opendkim postfix
|
|
</pre></div>
|
|
|
|
|
|
<p>Now, let's edit the /etc/postfix/master.cf file, like so :</p>
|
|
<div class="highlight"><pre>smtpd_milters = unix:/opendkim/opendkim.sock
|
|
</pre></div>
|
|
|
|
|
|
<p>We now have to restart OpenDKIM and Postfix :</p>
|
|
<div class="highlight"><pre>systemctl restart opendkim
|
|
systemctl restart postfix
|
|
</pre></div>
|
|
|
|
|
|
<h3>DNS side</h3>
|
|
<p>For DKIM to work, you have to configure a DNS TXT record in your zone. This record was automatically generated by OpenDKIM in the mail.txt file mentioned earlier :</p>
|
|
<div class="highlight"><pre>mail._domainkey IN TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCkJq0CW3tl2XHZ1CN5XdbqRDU7KfXOJ70nlwI09bHmDU63/Yz3J5rl863S0t2ncVHfIudZANj0OaiJe5HRR7WCsjuNIhQFfPFGIWLNClpxqdQVQURI38sAGeyn7Ed/Cor1AiWABzFWzel0kvXILw8K/NTzxaAPeSa9ttwQEgSmowIDAQAB" ; ----- DKIM key mail for captainark.net
|
|
</pre></div>
|
|
|
|
|
|
<p>All you have to do is to copy and paste this record in your DNS zone file.</p>
|
|
<p>To make sure that OpenDKIM is working, you can send an empty email to <a href="mailto:check-auth@verifier.port25.com">check-auth@verifier.port25.com</a>. You should receive a response with the following content :</p>
|
|
<div class="highlight"><pre>==========================================================
|
|
Summary of Results
|
|
==========================================================
|
|
SPF check: pass
|
|
DomainKeys check: neutral
|
|
DKIM check: pass
|
|
Sender-ID check: pass
|
|
SpamAssassin check: ham
|
|
</pre></div>
|
|
|
|
|
|
<h2>Configuring OpenDMARC</h2>
|
|
<p><a href="http://dmarc.org/">DMARC</a> (Domain-based Message Authentication, Reporting & Conformance) standardizes SPF and DKIM authentication mechanisms.</p>
|
|
<p>It lets the owner of a domain name indicate that his email is protected by SPF and/or DKIM and what other providers should do with emails that do not pass those checks.</p>
|
|
<h3>Software side</h3>
|
|
<p>Once again, let's backup the original configuration file :</p>
|
|
<div class="highlight"><pre>mv /etc/opendmarc.conf /etc/opendmarc.conf.orig
|
|
</pre></div>
|
|
|
|
|
|
<p>We now have to create a /etc/opendmarc.conf file with the following content :</p>
|
|
<div class="highlight"><pre>AutoRestart Yes
|
|
AutoRestartRate 10/1h
|
|
UMask 0002
|
|
Syslog true
|
|
|
|
AuthservID "<your server's FQDN>"
|
|
TrustedAuthservIDs "<your server's FQDN>"
|
|
IgnoreHosts /etc/opendkim.d/TrustedHosts
|
|
|
|
RejectFailures false
|
|
|
|
UserID opendmarc:opendmarc
|
|
PidFile /run/opendmarc.pid
|
|
Socket local:/var/spool/postfix/opendmarc/opendmarc.sock
|
|
</pre></div>
|
|
|
|
|
|
<h3>Postfix integration</h3>
|
|
<p>The last thing we have to do is to configure Postfix to communicate with OpenDMARC.</p>
|
|
<p>First, let's create the necessary folders :</p>
|
|
<div class="highlight"><pre>mkdir /var/spool/postfix/opendmarc
|
|
chown opendmarc: /var/spool/postfix/opendmarc
|
|
</pre></div>
|
|
|
|
|
|
<p>We also have to add the postfix user to the opendmarc group :</p>
|
|
<div class="highlight"><pre>useradd -G opendmarc postfix
|
|
</pre></div>
|
|
|
|
|
|
<p>Now, let's edit the /etc/postfix/master.cf file, like so :</p>
|
|
<div class="highlight"><pre>smtpd_milters = unix:/opendkim/opendkim.sock, unix:/opendmarc/opendmarc.sock
|
|
</pre></div>
|
|
|
|
|
|
<p>We now have to restart OpenDMARC and Postfix :</p>
|
|
<div class="highlight"><pre>systemctl restart opendmarc
|
|
systemctl restart postfix
|
|
</pre></div>
|
|
|
|
|
|
<p>You should now see the following headers in your incoming emails :</p>
|
|
<div class="highlight"><pre><span class="nt">Authentication-Results</span><span class="o">:</span> <span class="nt">myserver</span><span class="nc">.captainark.net</span><span class="o">;</span> <span class="nt">dmarc</span><span class="o">=</span><span class="nt">pass</span> <span class="nt">header</span><span class="nc">.from</span><span class="o">=</span><span class="nt">gmail</span><span class="nc">.com</span>
|
|
</pre></div>
|
|
|
|
|
|
<h3>DNS side</h3>
|
|
<p>DMARC, like SPF and DKIM, is based on DNS TXT records.</p>
|
|
<p>Here is how I configured it for the captainark.net domain :</p>
|
|
<div class="highlight"><pre><span class="nt">_dmarc</span> <span class="nt">IN</span> <span class="nt">TXT</span> <span class="s2">"v=DMARC1; p=none; rua=mailto:postmaster@captainark.net; ruf=mailto:postmaster@captainark.net"</span>
|
|
</pre></div>
|
|
|
|
|
|
<p>This tells other providers to not reject or quarantine emails should a SPF or DKIM check fail, but to send a daily report of those checks to postmaster@captainark.net.</p>
|
|
<p>For more information on the DMARC syntax, here is an <a href="https://support.google.com/a/answer/2466563?hl=en">article from Google</a>.</p>
|
|
<h2>Configuring Monit</h2>
|
|
<p><a href="http://mmonit.com/monit/">Monit</a> is a daemon that makes sure that other daemons are running. If they crash, it restarts them automatically. Is is not directly related to a mail server per say, but it's pretty easy to set up.</p>
|
|
<p>First, as always, let's backup the original configuration file :</p>
|
|
<div class="highlight"><pre>mv /etc/monit/monitrc /etc/monit/monitrc.orig
|
|
</pre></div>
|
|
|
|
|
|
<p>We now have to create a new /etc/monit/monitrc file with the following content :</p>
|
|
<div class="highlight"><pre>set daemon 30
|
|
set logfile syslog facility log_daemon
|
|
|
|
set httpd port 2812 and
|
|
use address localhost
|
|
allow localhost
|
|
|
|
set mailserver localhost
|
|
with timeout 30 seconds
|
|
using hostname myserver.captainark.net
|
|
|
|
set mail-format { from: monit@captainark.net }
|
|
|
|
include /etc/monit/conf.d/*
|
|
</pre></div>
|
|
|
|
|
|
<p>Then, we are going to create a /etc/monit/conf.d/mail file with the following content :</p>
|
|
<div class="highlight"><pre>check process postfix
|
|
with pidfile "/var/spool/postfix/pid/master.pid"
|
|
start program = "/bin/systemctl start postfix"
|
|
stop program = "/bin/systemctl stop postfix"
|
|
alert monit@captainark.net
|
|
group mail
|
|
|
|
check process dovecot
|
|
with pidfile "/run/dovecot/master.pid"
|
|
start program = "/bin/systemctl start dovecot"
|
|
stop program = "/bin/systemctl stop dovecot"
|
|
alert monit@captainark.net
|
|
group mail
|
|
depends on postfix
|
|
|
|
check process spamassassin
|
|
with pidfile "/run/spamassassin.pid"
|
|
start program = "/bin/systemctl start spamassassin"
|
|
stop program = "/bin/systemctl stop spamassassin"
|
|
alert monit@captainark.net
|
|
group mail
|
|
depends on postfix, dovecot
|
|
|
|
check process opendkim
|
|
with pidfile "/run/opendkim/opendkim.pid"
|
|
start program = "/bin/systemctl start opendkim"
|
|
stop program = "/bin/systemctl stop opendkim"
|
|
alert monit@captainark.net
|
|
group mail
|
|
depends on postfix, dovecot
|
|
|
|
check process opendmarc
|
|
with pidfile "/run/opendmarc/opendmarc.pid"
|
|
start program = "/bin/systemctl start opendmarc"
|
|
stop program = "/bin/systemctl stop opendmarc"
|
|
alert monit@captainark.net
|
|
group mail
|
|
depends on postfix, dovecot
|
|
</pre></div>
|
|
|
|
|
|
<p>Let's make sure that permissions on the file are correct :</p>
|
|
<div class="highlight"><pre>chown root: /etc/monit/conf.d/mail <span class="o">&&</span> chmod <span class="m">600</span> /etc/monit/conf.d/mail
|
|
</pre></div>
|
|
|
|
|
|
<p>Then, we have to reload the monit daemon :</p>
|
|
<div class="highlight"><pre>monit reload
|
|
</pre></div>
|
|
|
|
|
|
<p>Now, the <code>monit summary</code> command should have the following output :</p>
|
|
<div class="highlight"><pre>The Monit daemon 5.4 uptime: 3d 0h 41m
|
|
|
|
Process 'postfix' Running
|
|
Process 'dovecot' Running
|
|
Process 'spamassassin' Running
|
|
Process 'opendkim' Running
|
|
Process 'opendmarc' Running
|
|
</pre></div>
|
|
|
|
|
|
<h2>Configuring Rainloop</h2>
|
|
<p><a href="http://www.rainloop.net/">Rainloop</a> is a web-based email client. I won't go into details on how to configure it in this tutorial ; here's a link to the <a href="http://www.rainloop.net/docs/installation/">official documentation</a>.</p>
|
|
<p>You'll need a web server with PHP 5.3+ to run Rainloop. You do not have to run Rainloop on the same host as your mail server. No database is required.</p>
|
|
<h2>Conclusion</h2>
|
|
<p>We now have a mail server that should be running pretty smoothly. It could still be improved by setting up things such as greylisting or virus detection.</p>
|
|
<p>If you have found this tutorial useful, if you've found an error in it or if you have any question, please feel free to leave a comment below or to contact me on <a href="https://twitter.com/captainark">Twitter</a>.</p>
|
|
<h2>References</h2>
|
|
<p>Here are the tutorials I used to set up my own mail server :</p>
|
|
<ul>
|
|
<li><a href="http://sealedabstract.com/code/nsa-proof-your-e-mail-in-2-hours/">A complete tutorial on setting up a mail server</a></li>
|
|
<li><a href="https://docs.raccoon.io/mail-server-setup-with-postfix-dovecot/">Another complete tutorial</a></li>
|
|
<li><a href="https://www.digitalocean.com/community/tutorials/how-to-configure-a-mail-server-using-postfix-dovecot-mysql-and-spamassasin">A third tutorial from DigitalOcean</a></li>
|
|
<li><a href="https://www.digitalocean.com/community/tutorials/how-to-install-and-configure-dkim-with-postfix-on-debian-wheezy">A tutorial on setting up OpenDKIM</a></li>
|
|
<li><a href="https://guillaume.vaillant.me/?p=481">A tutorial on setting up OpenDMARC</a> (in french)</li>
|
|
</ul>
|
|
</article>
|
|
|
|
|
|
<hr>
|
|
<div class="sharing">
|
|
</div>
|
|
<hr>
|
|
|
|
<div class="comments">
|
|
<h2>Comments !</h2>
|
|
<div id="disqus_thread"></div>
|
|
<script type="text/javascript">
|
|
var disqus_shortname = 'captainark';
|
|
var disqus_identifier = 'setting-up-a-mail-server.html';
|
|
var disqus_url = 'https://captainark.net/setting-up-a-mail-server.html';
|
|
(function() {
|
|
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
|
|
dsq.src = '//captainark.disqus.com/embed.js';
|
|
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
|
|
})();
|
|
</script>
|
|
<noscript>Please enable JavaScript to view the comments.</noscript>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<hr>
|
|
|
|
<!-- Footer -->
|
|
<footer>
|
|
<div class="container">
|
|
<div class="row">
|
|
<div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1">
|
|
<ul class="list-inline text-center">
|
|
<li>
|
|
<a href="mailto:contact@captainark.net">
|
|
<span class="fa-stack fa-lg">
|
|
<i class="fa fa-circle fa-stack-2x"></i>
|
|
<i class="fa fa-envelope fa-stack-1x fa-inverse"></i>
|
|
</span>
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="https://twitter.com/captainark">
|
|
<span class="fa-stack fa-lg">
|
|
<i class="fa fa-circle fa-stack-2x"></i>
|
|
<i class="fa fa-twitter fa-stack-1x fa-inverse"></i>
|
|
</span>
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="https://github.com/captainark">
|
|
<span class="fa-stack fa-lg">
|
|
<i class="fa fa-circle fa-stack-2x"></i>
|
|
<i class="fa fa-github fa-stack-1x fa-inverse"></i>
|
|
</span>
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="http://www.last.fm/user/captainark">
|
|
<span class="fa-stack fa-lg">
|
|
<i class="fa fa-circle fa-stack-2x"></i>
|
|
<i class="fa fa-lastfm fa-stack-1x fa-inverse"></i>
|
|
</span>
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="https://steamcommunity.com/id/captainark">
|
|
<span class="fa-stack fa-lg">
|
|
<i class="fa fa-circle fa-stack-2x"></i>
|
|
<i class="fa fa-steam fa-stack-1x fa-inverse"></i>
|
|
</span>
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="http://www.twitch.tv/captainark">
|
|
<span class="fa-stack fa-lg">
|
|
<i class="fa fa-circle fa-stack-2x"></i>
|
|
<i class="fa fa-twitch fa-stack-1x fa-inverse"></i>
|
|
</span>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
<p class="copyright text-muted">
|
|
Blog powered by <a href="http://getpelican.com">Pelican</a>,
|
|
which takes great advantage of <a href="http://python.org">Python</a>.
|
|
</p> </div>
|
|
</div>
|
|
</div>
|
|
</footer>
|
|
|
|
<!-- jQuery -->
|
|
<script src="https://captainark.net/theme/js/jquery.js"></script>
|
|
|
|
<!-- Bootstrap Core JavaScript -->
|
|
<script src="https://captainark.net/theme/js/bootstrap.min.js"></script>
|
|
|
|
<!-- Custom Theme JavaScript -->
|
|
<script src="https://captainark.net/theme/js/clean-blog.min.js"></script>
|
|
|
|
<script type="text/javascript">
|
|
var _gaq = _gaq || [];
|
|
_gaq.push(['_setAccount', 'UA-53658366-1']);
|
|
_gaq.push(['_trackPageview']);
|
|
(function() {
|
|
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
|
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
|
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
|
})();
|
|
</script>
|
|
<script type="text/javascript">
|
|
var disqus_shortname = 'captainark';
|
|
(function () {
|
|
var s = document.createElement('script'); s.async = true;
|
|
s.type = 'text/javascript';
|
|
s.src = '//' + disqus_shortname + '.disqus.com/count.js';
|
|
(document.getElementsByTagName('HEAD')[0] || document.getElementsByTagName('BODY')[0]).appendChild(s);
|
|
}());
|
|
</script>
|
|
</body>
|
|
|
|
</html> |