Pages related to the web (HTTP, HTML, CSS, etc.).
Node is a server-side JavaScript engine (i.e. it executes JavaScript without using a browser). This means that JavaScript developers can now develop tools in their native language, so it's not a surprise that the Bootstrap folks use Grunt for their build system. I'm new to the whole Node ecosystem, so here are my notes on how it works.
Start off by installing npm, the Node package manager. On Gentoo, that's:
# USE=npm emerge -av net-libs/nodejs
[Configure npm][npm-config] to make "global" installs in your personal space:
# npm config set prefix ~/.local/
Install the Grunt command line interface for building Bootstrap:
$ npm install -g grunt-cli
That installs the libraries under ~/.local/lib/node_modules
and
drops symlinks to binaries in ~/.local/bin
(which is already in my
PATH
thanks to my dotfiles).
Clone Boostrap and install it's dependencies:
$ git clone git://github.com/twbs/bootstrap.git
$ cd bootstrap
$ npm install
This looks in the local [package.json][] to extract a list of dependencies, and installs each of them under node_modules. Node likes to isolate its packages, so every dependency for a given package is installed underneath that package. This leads to some crazy nesting:
$ find node_modules/ -name graceful-fs
node_modules/grunt/node_modules/glob/node_modules/graceful-fs
node_modules/grunt/node_modules/rimraf/node_modules/graceful-fs
node_modules/grunt-contrib-clean/node_modules/rimraf/node_modules/graceful-fs
node_modules/grunt-contrib-qunit/node_modules/grunt-lib-phantomjs/node_modules/phantomjs/node_modules/rimraf/node_modules/graceful-fs
node_modules/grunt-contrib-watch/node_modules/gaze/node_modules/globule/node_modules/glob/node_modules/graceful-fs
Sometimes the redundancy is due to different version requirements, but sometimes the redundancy is just redundant :p. Let's look with npm ls.
$ npm ls graceful-fs
bootstrap@3.0.0 /home/wking/src/bootstrap
├─┬ grunt@0.4.1
│ ├─┬ glob@3.1.21
│ │ └── graceful-fs@1.2.3
│ └─┬ rimraf@2.0.3
│ └── graceful-fs@1.1.14
├─┬ grunt-contrib-clean@0.5.0
│ └─┬ rimraf@2.2.2
│ └── graceful-fs@2.0.1
├─┬ grunt-contrib-qunit@0.2.2
│ └─┬ grunt-lib-phantomjs@0.3.1
│ └─┬ phantomjs@1.9.2-1
│ └─┬ rimraf@2.0.3
│ └── graceful-fs@1.1.14
└─┬ grunt-contrib-watch@0.5.3
└─┬ gaze@0.4.1
└─┬ globule@0.1.0
└─┬ glob@3.1.21
└── graceful-fs@1.2.3
Regardless of on-disk duplication, Node caches modules so a given module only loads once. If it really bothers you, you can avoid some duplicates by installing duplicated packages higher up in the local tree:
$ rm -rf node_modules
$ npm install graceful-fs@1.1.14
$ npm install
$ npm ls graceful-fs
bootstrap@3.0.0 /home/wking/src/bootstrap
├── graceful-fs@1.1.14 extraneous
├─┬ grunt@0.4.1
│ └─┬ glob@3.1.21
│ └── graceful-fs@1.2.3
├─┬ grunt-contrib-clean@0.5.0
│ └─┬ rimraf@2.2.2
│ └── graceful-fs@2.0.1
└─┬ grunt-contrib-watch@0.5.3
└─┬ gaze@0.4.1
└─┬ globule@0.1.0
└─┬ glob@3.1.21
└── graceful-fs@1.2.3
This is probably not worth the trouble.
Now that we have Grunt and the Bootstrap dependencies, we can build the distributed libraries:
$ ~/src/node_modules/.bin/grunt dist
Running "clean:dist" (clean) task
Cleaning dist...OK
Running "recess:bootstrap" (recess) task
File "dist/css/bootstrap.css" created.
Running "recess:min" (recess) task
File "dist/css/bootstrap.min.css" created.
Original: 121876 bytes.
Minified: 99741 bytes.
Running "recess:theme" (recess) task
File "dist/css/bootstrap-theme.css" created.
Running "recess:theme_min" (recess) task
File "dist/css/bootstrap-theme.min.css" created.
Original: 18956 bytes.
Minified: 17003 bytes.
Running "copy:fonts" (copy) task
Copied 4 files
Running "concat:bootstrap" (concat) task
File "dist/js/bootstrap.js" created.
Running "uglify:bootstrap" (uglify) task
File "dist/js/bootstrap.min.js" created.
Original: 58543 bytes.
Minified: 27811 bytes.
Done, without errors.
Wohoo!
Unfortunately, like all language-specific packing systems, npm has trouble installing packages that aren't written in its native language. This means you get things like:
$ ~/src/node_modules/.bin/grunt
…
Running "jekyll:docs" (jekyll) task
`jekyll build` was initiated.
Jekyll output:
Warning: Command failed: /bin/sh: jekyll: command not found
Use --force to continue.
Aborted due to warnings.
Once everybody wises up and starts writing packages for Gentoo Prefix, we can stop worrying about installation and get back to work developing :p.
I transitioned from Apache to Nginx a week or so ago, since words like “minimal” and “streamlined” are appealing to me ;). I was quite happy with Apache, but it's always nice to try something new. Anyhow here's a quick review my configuration.
On Gentoo, set the the modules you want to install by adding
the following lines to your /etc/make.conf
:
NGINX_MODULES_HTTP="access auth_basic autoindex charset fastcgi gzip gzip_static limit_req map proxy rewrite scgi ssi stub_status"
NGINX_MODULES_MAIL=""
Then install Nginx with:
# emerge -av nginx
Make any adjustments you like to /etc/nginx/mime.types
. I added:
types {
…
application/x-python py;
application/x-shell sh;
…
}
Now it's time to setup /etc/nginx/nginx.conf
. Poking about online
will give you lots of examples. Here are things that were useful to
me, in the order they appear in the http
block of my nginx.conf
.
Redirecting www.example.com
to example.com
This keeps people who accidentally add a www.
prefix to your URL
from matching the wildcard virtual host block defined below.
server {
# www.example.com -> example.com
listen 80;
listen 443 ssl;
server_name www.example.com;
ssl_certificate /etc/ssl/nginx/www.example.com.pem;
ssl_certificate_key /etc/ssl/nginx/www.example.com-key.pem;
rewrite ^/(.*)$ $scheme://example.com/$1 permanent;
}
Gitweb (and general CGI approach)
gitweb server:
server {
listen 80;
server_name git.example.com;
access_log /var/log/nginx/git.example.com.access_log main;
error_log /var/log/nginx/git.example.com.error_log info;
root /usr/share/gitweb/;
index gitweb.cgi;
location /gitweb.cgi {
include fastcgi_params;
fastcgi_pass unix:/var/run/fcgiwrap.sock-1;
}
}
Because Nginx lacks built-in CGI support, we need some tricks to
get gitweb.cgi
working. We use the fcgi module to pass the
requests on to a FastCGI server which wraps gitweb.cgi
. On
Gentoo, I installed the following packages:
www-misc/fcgiwrap
, a FastCGI server for wrapping CGI scriptswww-servers/spawn-fcgi
, a FastCGI manager for spawningfcgiwrap
.
Configure spawn-fcgi
to launch fcgiwrap
with:
# cp /etc/conf.d/spawn-fcgi /etc/conf.d/spawn-fcgi.fcgiwrap
# emacs /etc/conf.d/spawn-fcgi.fcgiwrap
# cat /etc/conf.d/spawn-fcgi.fcgiwrap
FCGI_SOCKET=/var/run/fcgiwrap.sock
FCGI_ADDRESS=
FCGI_PORT=
FCGI_PROGRAM=/usr/sbin/fcgiwrap
FCGI_USER=nginx
FCGI_GROUP=nginx
FCGI_EXTRA_OPTIONS="-M 0700"
ALLOWED_ENV="PATH HOME"
HOME=/
FCGI_CHILDREN=1
FCGI_CHROOT=
# cd /etc/init.d/
# ln -s spawn-fcgi spawn-fcgi.fcgiwrap
Start fcgiwrap
with:
# /etc/init.d/spawn-fcgi.fcgiwrap start
Add it to the default runlevel with:
# rc-update add spawn-fcgi.fcgiwrap default
If you also want a virtual host serving Git over HTTP, you can add a virtual host like:
server {
# http-git.example.com
listen 80;
server_name http-git.example.com;
access_log /var/log/nginx/http-git.example.com.access_log main;
error_log /var/log/nginx/http-git.example.com.error_log info;
location / {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /usr/libexec/git-core/git-http-backend;
fastcgi_param GIT_HTTP_EXPORT_ALL "";
fastcgi_param GIT_PROJECT_ROOT /var/git;
fastcgi_param PATH_INFO $document_uri;
fastcgi_pass unix:/var/run/fcgiwrap.sock-1;
}
}
This uses the same FastCGI server we used for gitweb, but this time
the backend CGI script is git-http-backend
.
Wildcard virtual hosts
To add support for virual hosts stored under /var/www/$host
, use:
server {
listen 80;
#listen 443 ssl;
server_name star.example.com *.example.com;
access_log /var/log/nginx/star.example.com.access_log main;
error_log /var/log/nginx/star.example.com.error_log info;
#ssl_certificate /etc/ssl/nginx/$host.pem;
#ssl_certificate_key /etc/ssl/nginx/$host.key;
root /var/www/$host/htdocs;
# deny access to .htaccess files
location ~ /\.ht {
deny all;
}
}
Then adding a new host is as simple as creating a new entry in
/var/www/
and updating your DNS to get the new name pointed at
your server. Unfortunately, SSL/TLS doesn't work with this
approach. It appears that certificates and keys are loaded when Nginx
starts up, but $host
is only defined after a request is received.
Nginx does support SNI though, so it will work if you write SSL
entries by hand for hosts that need them.
Main host
The configuration for my main host is more complicated, so I'll
intersperse some more comments. I setup both clear-text and SSL in
the same definition using the SSL module. The _
server name
is a special name that matches any requests which haven't already
matched and been handled by an earlier server
.
server {
# catchall virtual host (optional SSL, example.com)
listen 80 default_server;
listen 443 default_server ssl;
server_name _;
ssl_certificate /etc/ssl/nginx/example.com.pem;
ssl_certificate_key /etc/ssl/nginx/example.com-key.pem;
Nothing special with the logging or root.
access_log /var/log/nginx/example.com.access_log main;
error_log /var/log/nginx/example.com.error_log info;
root /var/www/example.com/htdocs;
Turn on SSI, and also use index.shtml
as index pages.
index index.html index.shtml;
ssi on;
Use the proxy module to pass requests for /cookbook/
and
subdirectories on to their underlying Django app.
location /cookbook/ {
proxy_pass http://localhost:33333/cookbook/;
proxy_set_header X-Real-IP $remote_addr;
}
Use the scgi module to pass requests for /gallery/
and
subdirectories on to their underlying SCGI app.
location /gallery/ {
include scgi_params;
scgi_pass localhost:4000;
}
Turn on autoindexing for /RAD/
and subdirectories using the
autoindex module.
location /RAD/ {
autoindex on;
}
Force SSL/TLS for /tree/
and subdirectories, redirecting plain-text
requests to the equivalent HTTPS page. Use the auth_basic
module for authentication, the SSL module for
$ssl_protocol
, and the rewrite module for the
redirection.
location /tree/ {
auth_basic "Family Tree";
auth_basic_user_file /home/jdoe/htpasswd;
if ($ssl_protocol = "") {
rewrite ^ https://example.com$request_uri? permanent;
}
}
Nothing special with the end of this server
block.
# deny access to .htaccess files
location ~ /\.ht {
deny all;
}
}
Basic CGI starts a new process to handle each request. This is simple, but resource hungry. To make things more efficient, a number of protocols have developed that keep a CGI server running in the background. The frontend webserver then passes requests back to the CGI server for processing without having to fire up a new program.
FastCGI is the original CGI server protocol, but here we'll focus
on it's simpler cousin SCGI. I write most of my scripts in
Python, so I use the handy scgi module to write the
server (www-apps/scgi
on Gentoo). There's a great article on
Linux Journal to walk you through things, but in short, you need
something like:
import scgi
import scgi.scgi_server
import urlparse
class YourHandler(scgi.scgi_server.SCGIHandler):
def produce(self, env, bodysize, input, output):
url = env.get('DOCUMENT_URI', None)
if url is None: # fall back to the request URI
url = env.get('REQUEST_URI', None)
data = urlparse.parse_qs(env.get('QUERY_STRING', ''))
output.write('Status: 200 OK\r\n')
output.write('Content-type: text/plain; charset=UTF-8\r\n')
output.write('\r\n')
output.write('URI: {}\n'.format(url))
output.write('data: {}\n'.format(data))
if __name__ == '__main__':
server = scgi.scgi_server.SCGIServer(
handler_class=YourHandler, host='localhost', port=4000)
server.serve()
Fire this script up, point your server at localhost:4000
, and you're
good to go!
I've written up a little script to test your SCGI server from the command line: scgi-test.py:
$ scgi-test.py /your/uri/
Status: 200 OK
Content-type: text/html; charset=UTF-8
<!DOCTYPE html>
…
Configuring your frontend webserver to point to the SCGI server is beyond the scope of this post. I give an Nginx example in my Nginx post.
Twirssi is a Twitter/identi.ca plugin for irssi. Installation is pretty simple, but I've put a Gentoo package in my overlay in case you're picky about one-off installations or don't want to track down all those dependencies by hand.
Usage is also nice and simple. Here's an extract of the full docs:
- Load the script:
/script load twirssi.pl
- Log in to twitter:
/twitter_login <username>
- Post:
/tweet <status>
- Follow:
/twitter_follow [-w] <username>
(with-w
, send their tweets to a window of the same name) - Unfollow:
/twitter_unfollow <username>
- Search:
/twitter_search <keyword>
- Subscribe:
/twitter_subscribe [-w] <keyword>
(-w
same as for/twitter_follow
) - Unsubscribe:
/twitter_unsubscribe <keyword>
Subscriptions are basically repeated searches. For example, if you wanted continuous updates on posts about twirssi, you would use:
/twitter_subscribe #twirssi
Twirssi saves your current settings (follows, subscriptions, window
names, etc.) in ~/.irssi/scripts/twirssi.*
.
You can direct a public tweet towards a user by including
@<username>
in your tweet text. You can also tag the tweet so that
others can search for it with #keyword
.
There is currently no way to list users that you are following
(a.k.a. your friends) or your followers. Here's an excerpt from
#twirssi
on irc.foonetic.net.
--- Log opened Mon Feb 20 10:13:06 2012
10:13 < wking> is there a way to list users you are following (aka friends?) in twirssi?
10:15 <&Gedge> oh, just replied to your(?) tweet
10:16 < wking> ah, thanks. Sorry for reposting ;)
10:17 <&Gedge> Not really (/twirssi_dump is debugging so doesn't count). Perhaps #twirssi needs "/twitter_friends" to list friends.
irssi is a great IRC client. The documentation is excellent, but long, so here is my summary of critical usage.
Connect to freenode
as nickname
:
$ irssi -c irc.freenode.net -n nickname
Get help:
irc> /help
irc> /help <command>
Join the #phys305
channel:
irc> /join phys305
Leave the current channel:
irc> /part
Quit your IRC session:
irc> /bye
Change to window <N>
with Meta-<N>
. The Esc
key is one of a
number of ways to change windows.
Talk by typing whatever you have to say. In a multi-user channel, it
is good practice to preface responses with the nick of the person to
whom you are responding. This makes it easier for them to pick
relevant messages out during parallel conversations. irssi
makes
this easy by autocompleting nicks with Tab
.
You can also look over the beginner section of the IRC mini-HOWTO. If you want to start a new channel, read The New IRC Channel Operator's Guide.
Tonight I've been looking into ways to set up a local chat system for
students in one of my courses. My initial thought was to use the
NetKit talk
at talkd
programs, but I couldn't find an active
homepage. Maybe they are so mature that noone needs to touch the
code, but I expect they are actually just quietly dying in the corners
of assorted harddrives, without any users to love them.
Since part of the goal of my course is to get the students up to speed on common open source development tools, my next thought was to go with a local IRC server. I know that there many wonderful public IRC servers (e.g. freenode), but why use a free service when you can run the service locally? Anyhow, here's my setup.
Local IRC server
There seem to be a number of these out there. I chose ngIRCd,
because it's packaged for Gentoo (net-irc/ngircd
), and it seems to
be both cleanly coded and actively maintained (in Git ☺).
Installation was a simple:
# emerge -av ngircd
# emacs /etc/ngircd/ngircd.conf
# cat /etc/ngircd/ngircd.conf
[Global]
Name = irc.example.com
AdminInfo1 = John Doe
AdminInfo2 = 123 Street Rd.
AdminEMail = jdoe@example.com
Info = bla bla bla
ServerGID = nogroup
ServerUID = ngircd
[Options]
PAM = no
# /etc/init.d/ngircd restart
I didn't add ngircd
to the default runlevel. I'll just start it by
hand whenever I want to use it.
Local connections
Using the excellent irssi client (net-irc/irssi
on Gentoo):
$ irssi -c localhost
When you specify a server on the command line with -c server
,
irssi
won't connect to any other servers for which you've configured
automatic connection. In my case, this is what I want.
Exposing the IRC server on a remote host
Alright, I've got an IRC server running on my laptop, but I'm behind a firewall. I can expose my IRC server to the students by forwarding the IRC port to our department server, where the students all have shell accounts.
$ ssh -R localhost:6667:localhost:6667 physics.uni.edu
If someone else is already using port 6667 on the department server, it's easy to use another one:
$ ssh -R localhost:6668:localhost:6667 physics.uni.edu
Students can then connect by SSHing into the department server
and running irssi
there:
student@home $ ssh physics.uni.edu
student@physics $ irssi -c localhost -p 6668
And that's all there is too it! An easy way to introduce people to a popular tool.
HTML5 has a well-defined outline algorithm (there's a nice overview at html5 doctor). Since browsers don't seem to support an outline view yet, I decided to embed an automatically-generated outline in a webpage using JavaScript. Thanks to the HTML5 outliner (h50) project, this is fairly straightforward. However, I couldn't find an example on their site, so here's a recipe to get you started.
Download the outliner javascript:
$ wget http://h5o.googlecode.com/files/outliner.0.5.0.62.js
or get the most recent version from the h5o download page. Then, start your page off with something like:
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="outliner.0.5.0.62.js"></script>
<script type="text/javascript">
var createLinks = true;
var init=function()
{
var outline = HTML5Outline(document.body);
var html = outline.asHTML(createLinks);
document.getElementById('nav').innerHTML = html;
}
</script>
</head>
<body id="body" onload="init();">
<header>
<h1>PAGE TITLE</h1>
<nav id="nav">
<script type="text/javascript">
document.write('<h2 style="display: none">Table of Contents</h2>')
</script>
</nav>
</header>
<h2 id="s1">FIRST SECTION</h2>
Important points:
- The
<nav>
section is populated using JavaScript. If JavaScript is available, we get a nice table of contents. If JavaScript is not available, we go right from the page header to the first content section. - The
<nav>
title (Table of Contents
) is hidden using CSS (display: none
), since its role is fairly obvious. However, if you leave out the nav header entirely, the<nav>
entry the table of contents will end up with an automatically generated title. For Firefox 8.0, that meansUntitled NAV
. - The
<nav>
title is inserted using document.write, so that browsers which support neither JavaScript nor CSS (e.g. w3m)—and therefore cannot generate the table of contents—will not display theTable of Contents
header.
AWStats is a log analyzer that gives you nice summaries about who's accessing your web server, when they're accessing it, how much bandwidth they're using, etc. Here's a quick run through installing and using AWStats on Gentoo.
Install AWStats:
# emerge -av awstats
Optionally, copy the log files from a remote server:
$ rsync -avz example.net:/var/log/apache2/ example-logs/
Configure AWStats by adjusting the model configuration distributed with AWStats.
$ cp /etc/awstats/awstats.model.conf awstats.example.conf
$ emacs awstats.example.conf
Here's an example config with the options I use:
# AWSTATS CONFIGURE FILE 7.0
Include "awstats.user.conf.common"
LogFile="example-logs/access_log"
LogType=W
LogFormat=1
LogSeparator=" "
SiteDomain="www.example.net"
HostAliases="localhost 127.0.0.1 REGEX[example\.net$]"
DNSLookup=1
DirData="data"
CreateDirDataIfNotExists=1
DirIcons="/usr/share/awstats/wwwroot/icon"
BuildHistoryFormat=text
LoadPlugin="decodeutfkeys"
LoadPlugin="ipv6"
LoadPlugin="hashfiles"
Because awstats.pl
can be invoked interactively through your browser
(depending on how you have it installed), it has a fairly restricted
path. In order to run awstats on files in a random directory
(e.g. for processing your server's logs on another host) you should
use the -configdir
option. Here's an example of parsing the all the
archived logs (my logs are archived as access_log-YYYYMMDD.gz
):
$ for log in example-logs/access_log-*.gz; do awstats.pl -configdir="${PWD}" -config example -LogFile="gzip -cd ${log} |"; done
I also analyze logs from another host, whose logs are archived weekly
as access_log.2.gz
, access_log.3.gz
, etc. To parse the last year
of those archived logs, use something like:
$ for i in $(seq 52 -1 2); do awstats.pl -configdir="${PWD}" -config=example -LogFile="gzip -cd example-logs/access_log.${i}.gz |"; done
After working through the archived logs, you can process the current log file:
$ awstats.pl -configdir="${PWD}" -config=example
Once all the logs have been processed, run
$ awstats.pl -configdir="${PWD}" -config=example -update
To generate the report, run
$ awstats_buildstaticpages.pl -configdir="${PWD}" -config=example -dir=html
Today I decided to host all my public Git repositories on my Gentoo server. Here's a quick summary of what I did.
Gitweb
Re-emerge git
with the cgi
USE flag enabled.
# echo "dev-util/git cgi" >> /etc/portage/package.use/webserver
# emerge -av git
Create a virtual host for running gitweb
:
# cat > /etc/apache2/vhosts.d/20_git.example.net_vhost.conf << EOF
<VirtualHost *:80>
ServerName git.example.net
DocumentRoot /usr/share/gitweb
<Directory /usr/share/gitweb>
Allow from all
AllowOverride all
Order allow,deny
Options ExecCGI
<Files gitweb.cgi>
SetHandler cgi-script
</Files>
</Directory>
DirectoryIndex gitweb.cgi
SetEnv GITWEB_CONFIG /etc/gitweb.conf
</VirtualHost>
EOF
Tell gitweb
where you keep your repos:
# echo "\$projectroot = '/var/git';" > /etc/gitweb.conf
Tell gitweb
where people can pull your repos from:
# echo "@git_base_url_list = ( 'git://example.net', ); >> /etc/gitweb.conf
Restart Apache:
# /etc/init.d/apache2 restart
Add the virtual host to your DNS server.
# emacs /etc/bind/pri/example.net.internal
...
git A 192.168.0.2
...
Restart the DNS server.
# /etc/init.d/named restart
If names aren't showing up in the Owner
column, you can add them to
the user's /etc/passwd
comment with
# usermod -c 'John Doe' jdoe
Thanks to Phil Sergi for his own summary, which I've borrowed from heavily.
Git daemon
Gitweb allows browsing repositories via HTTP, but if you will be
pulling from your repositories using the git://
protocol, you'll
also want to run git-daemon
. On Gentoo, this is really easy, just
edit /etc/conf.d/git-daemon
as you see fit. I added --verbose
,
--base-path=/var/git
and --export-all
to GITDAEMON_OPTS
. Start
the daemon with
# /etc/init.d/git-daemon start
Add it to your default runlevel with
# rc-update add git-daemon default
If you're logging to syslog and running syslog-ng, you can configure the log location using the usual syslog tricks. See my syslog-ng for details.
I spent some time today configuring Postfix so I could send mail from home via SMTPS.
Outgoing mail
Verizon, our ISP, blocks port 25 to external domains, forcing all
outgoing mail through their outgoing.verizon.net
exchange server
(Comcast does the same thing). In order to accept mail,
they also require you authenticate with your Verizon username and
password, so I wanted to use an encrypted connection.
For the purpose of this example, our Verizon username is jdoe
, our
Verizon password is YOURPASS
, you're running a local Postfix server
on mail.example.com
for your site at example.com
, and 12345
is a
free local port.
# cat /etc/postfix/main.cf
myhostname = mail.example.com
relayhost = [127.0.0.1]:12345
smtp_generic_maps = regexp:/etc/postfix/generic
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/saslpass
…
# cat /etc/postfix/generic
/^jdoe@(.*)$/ jdoe@example.com
/^root@(.*)$/ jdoe@example.com
# postmap /etc/postfix/generic
# cat /etc/postfix/saslpass
[127.0.0.1]:12345 jdoe@verizon.net:YOURPASS
# postmap /etc/postfix/saslpass
# cat /etc/stunnel/stunnel.conf
[smtp-tls-wrapper]
accept = 12345
client = yes
connect = outgoing.verizon.net:465
# /etc/init.d/stunnel restart
# postfix reload
Test with:
$ echo 'testing 1 2' | sendmail you@somewhere.com
Here's what's going on:
- You hand an outgoing message to your local Postfix, which decides to
send it via port
12345
on your localhost (127.0.0.1
) (relayhost). - Stunnel picks up the connection from Postfix, encrypts everything,
and forwards the connection to port 465 on
outgoing.verizon.net
(stunnel.conf
). - Postfix identifies itself as
mail.example.com
(myhostname), and authenticates using your Verizon credentials (smtp_sasl_…
). - Because Verizon is picky about the
From
addresses it will accept, we use smtp_generic_maps to map addresses to something simple that we've tested.
And that's it :p. If you're curious, there's more detail about all the Postfix config options in the postconf man page. You might also want to look over the SASL_README and ADDRESS_REWRITING_README.
For the generic mapping, I've used a regexp table, that way I don't have to map a bunch of possible original addresses by hand. By using smtp_generic_maps instead of canonical_maps, we only remap addresses before they head off into the wider world. If we used canonical_maps, we would remap all incoming mail, even mail destined for local delivery.
There's also a blog post by Tim White which I found useful. Because Verizon lacks STARTTLS support, his approach didn't work for me out of the box.
Incoming mail
In case you have trouble with someone blocking your incoming mail, things are a bit trickier. You can always accept mail on different ports (e.g. the submission port 587), with an entry like
submission inet n - n - - smtpd
in /etc/postfix/master.cf
. However, others will not know which port
you selected, because MX records do not allow you to specify
alternate ports. The more modern SRV record allows this, but
mail systems are old-school and don't support SRV. If you have access
to another external server (whose port 25 isn't blocked), you can
point your MX record at that server, and have it forward mail to you
on your strange port.
For the purpose of this example, the remote host has a public IP of
1.2.3.4
, and your local site is example.com
, recieving mail on
port 587. All of the following config files are on the remote host.
# cat /etc/postfix/main.cf
…
proxy_interfaces = 1.2.3.4
relay_domains = example.com
relay_transport = relay:[example.com]:587
…
For futher details (e.g. if you are relaying to more than one target), see the Postfix suggestions for being an MX host for a remote site.