Smörgåsbord

Ambachtelijk bereide beschouwingen.

Quite a while ago, I ranted about Facebook, their email “validation”, and their rather eccentric take on quality assurance. Things have changed at Facebook — you can now sign up using a sub-addressed email account.

Friend suggestions

But there may have been more to it than improper validation. One of the many ways Facebook comes up with suggestions running along the lines of “so-and-so is now on facebook, do you know her? let’s connect!” is to entice you to give your password to third-party services (they call this “add friends from your address book”). So suppose you had mary@jane.com in your Gmail address book at that time, and you let Facebook read this address book, then Facebook stores the mary@jane.com address (possibly indefinately) as one of your “friend candidates”.
Some time later, Mary Jane registers a Facebook account, using her mary@jane.com email address – the one that’s also on record as one of your friend candidates. Ping! Facebook sends you a message, enticing you to connect1.
Of course, this all falls down when Mary registers using a sub-addressed address, e.g., mary+facebook@jane.com. She might want to do this to channel the flood of facebook-originating email into a separate folder. mary+facebook@jane.com is probably not on file with you or anyone else, since if you want to send her an email, you’d send it to mary@jane.com — so that’s what was in your Gmail address book.

Sub-addresses: Bad for business, unless…

That brings me to the following conspiracy theory: Initially, Facebook disallowed sub-addressed email addresses (under the guise of a “broken” validator?) because those interfere with their goal of engaging you with as many people as possible (via friend suggestions), so as to have your eyeballs on their site and in front of their advertisers until they bleed (the eyeballs, not the advertisers).
At a certain moment in the past 28 months, they fixed the improper ‘invalid email address’ designation of sub-addressed accounts. Good for facebookers, bad for business — unless they parse the email address and drop the part between the + and the @. Thing is, sub-addressing is not a standard. On my mailserver, I can specify a character other than + to use as an extension designator. It’s up to the mailserver to do something useful or silly with the sub-addressing. There are no formal semantics. If there is a + in the user-part of an email address, that does not necessarily mean that it is sub-addressed.
My guess is that Facebook made their address matching fuzzy, to account for many possibilities. They’ll plunder your address book and will still figure out that you and Mary Jane are acquainted, no worries.

Tinfoil hat

Well, I do worry.
On the web, your email address is a key to your identity. Your identity is something which many organizations (advertisers, some governments, …) very much like to link across natural domain boundaries. I don’t think that many organizations have updated their address matching algorithms with fuzzyness… yet. So at the moment, it’s still a good idea to sign up to sites and services using unique, subaddressed email accounts. But to be futureproof, you’ll need to defeat fuzzy matchers that take the many forms of sub-addressing into account. It’s probably best to just register a domain and have all email arriving at that domain be delivered to one account. That way, you can easily use any email address at that domain when signing up. If you don’t want to run your own mail server, the one that Google provides you with if you take Google Apps on your domain allows just that. But I’m not so sure that recommendation is solid advice, privacy-wise…

1)Possibly, and hopefully, they do some cross-checking first.


Tags: , , ,

For a little while I’ve been running a DIY dynamic DNS service. My peers think it’s convenient and pretty sweet, so in this post I’ll set things straight — a full disclosure on the mess that it really is ;-)

What is a dynamic DNS service?

Since the dial-up days of yore I have occasionaly been using “dynamic DNS” services. What is a “dynamic DNS” service, you ask? Well, it’s a marketing term, not a technical term, but most providers of such services allow for having some CNAME or A record point to an IP, and this pairing is updatable over HTTP. Usually they use a freemium business model where you can get a couple of subdomains on their second level domains.

Why use one?

An example of a use case: You run a development server on your laptop. You want someone to connect to it. Instead of saying to this person “now just go to… errr… hold on” (and now you pop your shell and run ip a s dev wlan0)1 “yes I’m back, you need to go to 110.34.56.250″ you can just say “go to kleinebeer.ath.cx”. Much easier. And that’s with IPv4. Typing an IPv6 address that someone just yelled at you from across the room is an even less joyful experience.
To enable this use case you configured your machine in such a way that every time your interface aquires a new IP, a program contacts the “dynamic DNS service” to update the ‘kleinebeer’ DNS record to reflect this IP. There are many clients for such services out there. I have even seen domestic ADSL modem/routers on which the factory firmware contains such a client.

Doing it differently

But some time ago I got fed up with all the “account expiration warning” emails I had been getting from the service that I used. And since I’m quite familiar with the components you need for building such a service yourself, I set out to do so. The thing I ended up with is for personal use, specific to my needs, KISS, and sinful.

Sins!

It allows for instant creation/updating of DNS records through a very simple HTTP GET. That is sinful for two reasons:

  1. No authentication and authorization.

    Anyone can create any record. And anyone can update any record he or she knows the name of. You understand how this could be a bad thing. Don’t have such names as MX records unless you don’t mind that mail intended for you ends up on someone else’s server.
    At the same time it is a good thing, as I will show in the use case example below.

  2. The HTTP GET alters state.

    And that’s just plain Bad Taste. If you’re designing a web service you’ll usually try to make it RESTful. GET should be a safe method. But for this application, at the frontend, it is actually desirable — because grandma can GET, as you will see in the use case below.

Use cases

  • Case A:

    Imagine you’re on the phone with your grandma, and after discussing apple pie recipes something else comes up and you need to know her IP (let’s say you need to SSH in and help her with her crontab). She hasn’t registered with any dynamic DNS service, she has a hard time determining her IP, she has a hard time reading it out loud without making mistakes, and you are fed up with typing IPs anyway. But she has a web browser. Wouldn’t it be nice if you could just tell her to open her browser and head over to http://grandma.reg-a-record.tld/reg.php ? Nothing to install, nothing to look up, and something reasonably simple for grandma to do. The script on http://grandma.reg-a-record.tld/reg.php creates a DNS A record named ‘grandma’ on dyndomain.tld, and this record points to your grandma’s IP. Now you can just do ’ssh grandma.dyndomain.tld’.

  • Case B:

    Imagine you and your friends are on a coding binge, all running their Django/Rails/Node.js/Thing-du-jour instances on laptops, in the same room, and you all want to connect to each other’s development servers.
    You could all call out your IPs across the room and let everyone type in everyone else’s IPs. Now you all have N tabs open in your browsers. Who was 123.231.101.45 again?
    Or, everyone could just point their browser (or curl, wget…) to http://theirfirstname.reg-a-record.tld/reg.php2 and be done with it. If you want to view Fred’s progress you just go to fred.dyndomain.tld.

So, sin #1 & #2 allow for use cases that were not possible with the provider I was using.

You don’t have to register the records up front with some service provider. You can make up records as you go, and you want to be able to let anyone create any record. Hence sin #1. But it’s a good thing. You don’t have to reuse records, which is better for your privacy. With the commercial service I could only use a couple of names, so anyone whom I once had told to go to kleinebeer.ath.cx could kinda virtually follow me around and determine whether I was at work, home, uni or girlfriend by resolving this record. When creating new records is incredibly easy, there’s no reason to keep using the same name every time.

On to the code

It’s very, very simple! You need very little code. Things can be made much simpler than I have done. For byzantine reasons I use two webservers, one has a python CGI which creates the actual records, and the other one runs the public-facing PHP script (through mod_php) which you actually connect to.

DNS server

You need a domain for which you run the authoritative name server. I’m using the SheerDNS DNS server package on mine, for no good reason. Here’s a patch I made so that sheerdnshash creates directories with some group permission bits set, something I need in my setup.
You can use other DNS servers, of course. I discovered that the Unbound DNS server has a socket protocol which you can possibly use to add records on the fly. But I use sheerdns now, and here’s a Python module I made to write A-records in sheerdns’s directory structure. Public domain, use it as you see fit. You need to modify the constants to reflect your config, and you can test it by running the module from a shell.

#!/bin/env python
 
import socket,os,string,subprocess,sys
 
SHEERDNS_DIR="/var/sheerdns"
SHEERDNS_HASH="/usr/sbin/sheerdnshash"
DYNDOMAIN="dyndomain.tld"
ALLOWCHARS=string.ascii_letters + ''.join([ "%d" % i for i in range(10)]) + '-'
 
def mk_A(name,ip):
	#are host and ip allright?
	host = ''.join([ c for c in name if c in ALLOWCHARS ]).lower()
	if not host:
	  return None
	try:
	  socket.inet_aton(ip)
	except socket.error:
	  return None
 
	fqdn = "%s.%s" % (host, DYNDOMAIN)
 
	#set the umask
	oldumask = os.umask(0002)
 
	#get the hash
	hash = subprocess.Popen([SHEERDNS_HASH, fqdn], stdout=subprocess.PIPE).communicate()[0].strip()
 
	#construct the dirpath
	path = os.path.join(SHEERDNS_DIR,hash,fqdn)
 
	#chmod it for everyone to read
	os.chmod(path,0755)
 
	#construct the A-record-filepath
	apath = os.path.join(path,'A')
 
	#open the record file
	with open(apath,'w') as record:
		record.write(ip)
 
	os.umask(oldumask)
	return name
 
if __name__ == '__main__':
        if len(sys.argv) == 3:
          print(mk_A(sys.argv[1],sys.argv[2]))

Web ’service’ to create the records: a CGI

This is a Python CGI which uses the above module. I stick it behind HTTP Basic authentication which is handled by the web server. You call it like this: http://server_that_has/the_CGI?name=therecordname&ip=theip .
There’s no reason that this CGI should be guilty of Sin #2, except laziness on my behalf. If I’d be following every convention even for silly little solo projects I’d never get anything done, all right?
It doesn’t propagate any errors from the sheerdns library, but it prints the name as it is registered — stripped of disallowed characters, or nothing if something failed along the way (invalid IP, for instance).

#!/usr/bin/python
import sheerdns
import cgi
 
print "Content-type: text/plain\n\n";
params = cgi.parse()
name, ip = params.get('name'), params.get('ip')
 
if ip and name:
  dnsname = sheerdns.mk_A(name[0],ip[0])
  print dnsname

The accessible part: reg.php

This is in PHP. It lives in the default virtual host of my Apache config, and for good reasons. I have a CNAME record that points *.reg-a-record.tld to this web server. I then use whatever is in * to construct the name for the dyndomain.tld-record. The wildcard record is essential.
You need to modify this script to reflect your config.

<?php
header('Content-Type: text/plain');
$hostparts = explode('.', $_SERVER['SERVER_NAME'], 2);
$ip = $_SERVER['REMOTE_ADDR'];
print $ip;
 
$cha = curl_init('https://server_that_has/the_CGI?name='.$hostparts[0].'&ip='.$ip);
curl_setopt($cha, CURLOPT_SSL_VERIFYPEER, False);
curl_setopt($cha, CURLOPT_TIMEOUT, 5);
curl_setopt($cha, CURLOPT_RETURNTRANSFER, True);
curl_setopt($cha, CURLOPT_USERPWD, 'bite:me');
$resp = curl_exec($cha);
curl_close($cha);
print($resp);
?>

Presto

If you visit http://kitten.reg-a-record.tld/reg.php, the IP address that the web server sees you coming from gets registered as kitten.dyndomain.tld. If everything went well, the script responds with this mapping.
You could set DirectoryIndex reg.php here, or rename reg.pgp to index.php, if you don’t want users to type ‘reg.php’. I did make it explicit, because web sites on this server come and go and I don’t want people to stumble onto reg.php every time they hit the default vhost because their web site doesn’t exist any more.

1)Let’s assume you’re not NATed.
2)Try to only have friends with distinct names that can be expressed in ASCII.


Tags: , , ,
© 2009-2011 Wicher Minnaard | electronic mail | theme: righteously modified "dark strict"