Pages related to the web (HTTP, HTML, CSS, etc.).

Nginx

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 scripts
  • www-servers/spawn-fcgi, a FastCGI manager for spawning fcgiwrap.

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;
  }
}
Posted
Simple Common Gateway Interface

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.

Posted
twirssi

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.
Posted
irssi

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.

Posted
Local IRC server

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.

Posted
HTML5 outlines

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 means Untitled 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 the Table of Contents header.
Posted
AWStats

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
Posted
Serving Git on Gentoo

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.

Postfix

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. 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.

SMTP

Verizon blocks outgoing connections on port 25 (SMTP) unless you are connecting to their outgoing.verizon.net message exchange server. This server requires authentication with your Verzon username/password before it will accept your mail. For the purpose of this example, our Verizon username is jdoe, our Verizon password is YOURPASS, and were sending email from me@example.com to you@target.edu.

$ nc outgoing.verizon.net 25
220 vms173003pub.verizon.net -- Server ESMTP (...)
mail from: <jdoe@example.com>  
550 5.7.1 Authentication Required
quit
221 2.3.0 Bye received. Goodbye.

Because authenticating over an unencrypted connection is a Bad Idea™, I was looking for an encrypted way to send my outgoing email. Unfortunately, Verizon's exchange server does not support STARTTLS for encrypting connections to outgoing.verizon.net:25:

$ nc outgoing.verizon.net 25
220 vms173003pub.verizon.net -- Server ESMTP (...)
ehlo example.com
250-vms173003pub.verizon.net
250-8BITMIME
250-PIPELINING
250-CHUNKING
250-DSN
250-ENHANCEDSTATUSCODES
250-HELP
250-XLOOP E9B7EB199A9B52CF7D936A4DD3199D6F
250-AUTH DIGEST-MD5 PLAIN LOGIN CRAM-MD5
250-AUTH=LOGIN PLAIN
250-ETRN
250-NO-SOLICITING
250 SIZE 20971520
starttls
533 5.7.1 STARTTLS command is not enabled.
quit
221 2.3.0 Bye received. Goodbye.

Verizon recommends pre-STARTTLS approach of wrapping the whole SMTP connection in TLS (SMTPS), which it provides via outgoing.verizon.net:465:

$ python -c 'from base64 import *; print b64encode("\0jdoe@verizon.net\0YOURPASS")'
AGpkb2VAdmVyaXpvbi5uZXQAWU9VUlBBU1M=
$ openssl s_client -connect outgoing.verizon.net:465
...
220 vms173013pub.verizon.net -- Server ESMTP (...)
ehlo example.com
250-vms173013pub.verizon.net
250-8BITMIME
250-PIPELINING
250-CHUNKING
250-DSN
250-ENHANCEDSTATUSCODES
250-HELP
250-XLOOP 9380A5843FE933CF9BD037667F4C950D
250-AUTH DIGEST-MD5 PLAIN LOGIN CRAM-MD5
250-AUTH=LOGIN PLAIN
250-ETRN
250-NO-SOLICITING
250 SIZE 20971520
auth plain AGpkb2VAdmVyaXpvbi5uZXQAWU9VUlBBU1M
235 2.7.0 plain authentication successful.
mail from: <me@example.com>
250 2.5.0 Address Ok.
rcpt to: <you@target.edu>
250 2.1.5 you@target.edu OK.
data
354 Enter mail, end with a single ".".
From: Me <me@example.com>
To: You <you@target.edu>
Subject: testing

hello world 
.
250 2.5.0 Ok, envelope id 4BHMFEZ7PHSETMT6@vms173013.mailsrvcs.net
quit
221 2.3.0 Bye received. Goodbye.
closed

This works, but with the rise of STARTTLS, getting your local Postfix mail server to support SMTPS requires a bit of fancyness with stunnel. The stunnel workaround is not too complicated, but I also wanted to look into the submission protocol (port 587), which adapts SMTP (designed for message transfer) into a similar protocol for message submission. Unfortunately, Verizon does not support STARTTLS here either.

$ nc outgoing.verizon.net 587
220 vms173005.mailsrvcs.net -- Server ESMTP (...)
ehlo example.com
250-vms173005.mailsrvcs.net
250-8BITMIME
250-PIPELINING
250-CHUNKING
250-DSN
250-ENHANCEDSTATUSCODES
250-EXPN
250-HELP
250-XADR
250-XSTA
250-XCIR
250-XGEN
250-XLOOP DA941C5B31BE4B102BB69B809BC66C4A
250-AUTH DIGEST-MD5 PLAIN LOGIN CRAM-MD5
250-AUTH=LOGIN PLAIN
250-NO-SOLICITING
250 SIZE 20971520
starttls
533 5.7.1 STARTTLS command is not enabled.
quit
221 2.3.0 Bye received. Goodbye.

In conclusion, Verizon supports a number of email submission standards, but the only secure approach is to use the outdated SMTPS. See my Postfix post for details on configuring Postfix to use Verizon's server for outgoing mail.

There are a number of good SMTP authentication tutorials out there. I used John Simpson and Erwin Hoffmann's tutorials. For cleaner examples of my testing tools (nc and openssl s_client), see my simple servers post.