Dynamic DNS with 1&1

I’ve been using afraid.org FreeDNS as my dynamic dns servers for my domains registered with 1&1 for the past few years. They offer great service and have a large variety of options when it comes down to configuration and so forth. With that being said, just recently I started noticing their servers weren’t responding and my site all of a sudden went down since the dns data wasn’t getting updated. I’ve started doing searches on alternatives and found a few, but none as straight forward as FreeDNS from afraid.org … until last night.


I’ve finally come across this post from Rob who has had the same service from afraid.org and was able to make his dynamic dns work with 1&1. So I thought, wait, this is cool, I also have a domain with 1&1, so let me give this a shot and see how it works. To my surprise, it worked indeed (after a few custom changes). So there, if you own a domain with 1&1.com and have a dynamic ip address, you can now too, update your dns information whenever your ip changes! Here’s how:

1. Login to 1&1 and change the dns settings for your domain. You now need to set it so it uses 1&1 dns servers, and under advanced settings, enter the current ip address of your server.

2. If you would like to serve your own mail, you’ll also need to create a glue record and a mx record and then update your settings. 1&1 has the instructions for this (just click on the links I just mentioned.)

Now that you have the config part in 1&1 done you can go ahead and setup the scripts that will update the dns information for you. I’ve downloaded and modified this script from tsaiberspace, which is an automated client that masquerades itself as a web browser to automatically update the IP address associated with a 1&1 domain. For it all to work you’ll need to create a few scripts and install a few dependencies:

1. Install libwww-curl-perl
2. config.cfg (Contains the domain information to be updated)
3. checkdns (checks if the ip address has been changed, if so, runs the update1and1 script)
4. update1and1 (updates 1and1 dns information)

1. config.cfg:

# config file for 1and1 dns update
DOMAIN1=domain.com
LOGIN=www.domain.com
PASS=yourpassword

2. checkdns



#!/bin/bash

echo "Reading config from /etc/dynamic1and1/config.cfg ...." 
echo "IP check date: $(date +%m/%d/%Y-%H:%M)"
source /etc/dynamic1and1/config.cfg
## vars $DOMAIN1 $LOGIN $PASS come from config.cfg above

function update_dns() {
	$HOME/dynamic1and1/update1and1 $address $DOMAIN1 $LOGIN $PASS 

	echo "1AND1 DNS Updated Successfuly"
}

function update_dns2() {
        $HOME/dynamic1and1/update1and1 $address $DOMAIN2 $LOGIN $PASS

        echo "1AND1 DNS Updated Successfuly"
}



function curr_dyn_ip() {
	currdev=$(/sbin/ip route| grep default |head -n 1 |awk '{print $5}')
	currip=$(wget http://www.checkip.net/ -o /dev/null -O /dev/stdout | head -n 3 | tail -n 1 | cut -f2 -d:)
	echo $currip
}

function check_ip() {
	address=$(curr_dyn_ip)
	tmp_data="$HOME/.curr_dyn_ip"
	# see if the address really changed
	if [ -f "$tmp_data" ]
	then
		if [ "$address" = "$(cat $tmp_data)" ]
		then
			echo "IP Address hasn't changed, DNS records are uptodate"
		else
			# update 1&1.com
			update_dns			
			update_dns2
		fi
	else
		# update 1&1.com		
		update_dns	
		update_dns2	
	fi
	echo $address > $tmp_data
}

## This function runs now to check the ipaddress. 
## If the ip has changed, the script will call the update1and1 script and update the dns data.
check_ip





3. update1and1

#!/usr/bin/perl -w

###############################################################################

package Update1and1;

use strict;
use WWW::Curl::Easy;
use URI::Escape;

sub __curldebug()
{
	my (@args) = @_;

	print map { "[$_]\n" } @args;
}

sub new($$)
{
	sub curlsink($$)
	{
		my ($data, $ref) = @_;

		push @$ref, $data;
		return length($data);
	}

	my ($class, $args) = @_;

	my $this = bless {
		curl => WWW::Curl::Easy->new(),
		root => "https://admin.1and1.com",
		jsessionid => "",
		headers => [],
		body => [],
		%{$args},
	};

	my $curl = $this->{curl};

	$curl->setopt(CURLOPT_VERBOSE, $args->{verbose} ? 1 : 0);
	$curl->setopt(CURLOPT_HEADERFUNCTION, \&curlsink);
	$curl->setopt(CURLOPT_WRITEFUNCTION, \&curlsink);

	$this;
}

sub httpOK($)
{
	my ($allheaders) = @_;

	my $finalheaders = "";
	foreach (@$allheaders) {
		if (m@^HTTP/\d+\.\d+ \d+ @) {
			$finalheaders = "";
		}
		$finalheaders .= $_;
	}

	return $finalheaders =~ m#^HTTP/1\.\d 200 OK#;
}

sub Login($)
{
	my ($this, $args) = @_;

	my $curl = $this->{curl};

	$curl->setopt(CURLOPT_URL, "$this->{root}/");
	$curl->setopt(CURLOPT_FOLLOWLOCATION, 1);
	$curl->perform() and die "$curl->errbuf\n";
	$curl->setopt(CURLOPT_FOLLOWLOCATION, 0);

	my $eurl = $curl->getinfo(CURLINFO_EFFECTIVE_URL);
	die "Redirected to [$eurl] - looking for [^$this->{root}(:\\d+)?/]\n"
		if $eurl !~ m#^$this->{root}(:\d+)?/#;

	($this->{jsessionid}) = $eurl =~ m@.*;jsessionid=(.*?)[&\?].*?$@;

	$curl->setopt(CURLOPT_URL, $this->{root}
		. "/xml/logpixel?jsessionid=$this->{jsessionid}");
	$curl->perform() and die "$curl->errbuf\n";

	$curl->setopt(CURLOPT_URL, $this->{root}
		. "/xml/config/TaOverview;"
		. "jsessionid=$this->{jsessionid}");
	$curl->setopt(CURLOPT_FOLLOWLOCATION, 1);
	$curl->setopt(CURLOPT_POST, 1);
	$curl->setopt(CURLOPT_POSTFIELDS, join("&", (
		"__lf=" . uri_escape("HomeFlow"),
		"__sendingauthdata=" . uri_escape("1"),
		"login.SelectContract=" . uri_escape(""),
		"login.User=" . uri_escape($args->{auth}->{customerid}),
		"login.Pass=" . uri_escape($args->{auth}->{password}),
	)));

	($this->{headers}, $this->{body}) = ([], []);
	$curl->setopt(CURLOPT_WRITEHEADER, \@{$this->{headers}});
	$curl->setopt(CURLOPT_FILE, \@{$this->{body}});
	$curl->perform() and die "$curl->errbuf\n";
	$curl->setopt(CURLOPT_FOLLOWLOCATION, 0);
	$curl->setopt(CURLOPT_POST, 0);

	if ($this->{verbose}) {
		my $headers = join("", @{$this->{headers}});
		my $body = join("", @{$this->{body}});
		print "[$headers]\n[$body]\n";
	}

	die "HTTP failure in Login! [@{$this->{headers}}]\n"
		if !httpOK($this->{headers});
}

sub GetDomain($$)
{
	# Return the index of the desired domain.
	my ($this, $args) = @_;

	my $curl = $this->{curl};

	$curl->setopt(CURLOPT_URL, $this->{root}
		. "/xml/config/DomainOverview;"
		. "jsessionid=$this->{jsessionid}");
	$curl->setopt(CURLOPT_FOLLOWLOCATION, 1);
	($this->{headers}, $this->{body}) = ([], []);
	$curl->setopt(CURLOPT_WRITEHEADER, \@{$this->{headers}});
	$curl->setopt(CURLOPT_FILE, \@{$this->{body}});
	$curl->perform() and die "$curl->errbuf\n";
	$curl->setopt(CURLOPT_FOLLOWLOCATION, 0);

	my $body = join("", @{$this->{body}});
	if ($this->{verbose}) {
		my $headers = join("", @{$this->{headers}});
		print "[$headers]\n[$body]\n";
	}

	die "HTTP failure in GetDomain! [@{$this->{headers}}]\n"
		if !httpOK($this->{headers});

	$body =~ tr#\n##d;
	$body =~ s#^.*?(.*)#;
		$url =~ s#&#&#g;
		my ($domainIds) = $url =~ m#&domainOverview.DomainIds=(\d+)#;
		$domainmap{$domain} = {
			index => $ii,
			domain => $domain,
			url => $url,
			domainIds => $domainIds,
		};
		$ii++;
	}

	return undef if !$domainmap{$args->{domain}};
	return $domainmap{$args->{domain}}->{domainIds};
}

sub EditDNSSettings($$)
{
	# Edit DNS settings for the indexed domain.
	my ($this, $args) = @_;

	my $curl = $this->{curl};

	$curl->setopt(CURLOPT_URL, $this->{root}
		. "/xml/config/DomainOverview;"
		. "jsessionid=$this->{jsessionid}");
	$curl->setopt(CURLOPT_FOLLOWLOCATION, 1);
	$curl->setopt(CURLOPT_POST, 1);
	$curl->setopt(CURLOPT_POSTFIELDS, join("&", (
		"__lf=" . uri_escape("MCPrivacyFlow"),
		"__sendingdata=" . uri_escape("1"),
		"__currentindex%5BDomainOverview%5D=" . uri_escape("0"),
		"__SYNT%3Ad1e502d0%3AdomainFilter.Set=" . uri_escape("true"),
		"__SYNT%3Ad1e502d0%3AextendedDomainFilter.Set="
			. uri_escape("true"),
		"__SYNT%3Ad1e502d0%3A__CMD%5BDomainOverview%5D%3ASELWRP="
			. uri_escape("extendedDomainFilter"),
		"__SYNT%3Ad1e502d0%3A__CMD%5BDomainOverview%5D%3ASELWRP="
			. uri_escape("domainFilter"),
		"foo=" . uri_escape(""),
		"__SYNT%3Ad1e1030d0%3A__pageflow="
			. uri_escape("settings_wizard_flow"),
		"__SYNT%3Ad1e1039d0%3A__pageflow="
			. uri_escape("MCFlow"),
		"__SYNT%3Ad1e1048d0%3A__pageflow="
			. uri_escape("dns_wizard_flow"),
		"__SYNT%3Ad1e1056d0%3A__pageflow="
			. uri_escape("lock_unlock_flow"),
		"__SYNT%3Ad1e1065d0%3A__pageflow="
			. uri_escape("subdomain_delete_flow"),
		"__SYNT%3Ad1e1073d0%3A__pageflow="
			. uri_escape("settings_info_flow"),
		"domainFilter.DomainnameSubstring="
			. uri_escape(""),
		"__SYNT%3Ad1e1106d0%3AdomainFilter.Set="
			. uri_escape("true"),
		"__SYNT%3Ad1e1106d0%3AextendedDomainFilter.Set="
			. uri_escape("true"),
		"__SYNT%3Ad1e1106d0%3A__CMD%5BDomainOverview%5D%3ASELWRP="
			. uri_escape("extendedDomainFilter"),
		"__SYNT%3Ad1e1106d0%3A__CMD%5BDomainOverview%5D%3ASELWRP="
			. uri_escape("domainFilter"),
		"domainFilter.DomainType="
			. uri_escape("all"),
		"extendedDomainFilter.TargetType="
			. uri_escape("all"),
		"extendedDomainFilter.FrontpageStatus="
			. uri_escape("all"),
		"__SYNT%3Ad1e1214d0%3AdomainFilter.Set="
			. uri_escape("true"),
		"__SYNT%3Ad1e1214d0%3AextendedDomainFilter.Set="
			. uri_escape("true"),
		"__SYNT%3Ad1e1214d0%3A__CMD%5BDomainOverview%5D%3ASELWRP="
			. uri_escape("extendedDomainFilter"),
		"__SYNT%3Ad1e1214d0%3A__CMD%5BDomainOverview%5D%3ASELWRP="
			. uri_escape("domainFilter"),
		"__SYNT%3Ad1e1312d0%3AselectDomain.Action="
			. uri_escape("reset"),
		"__SYNT%3Ad1e1312d0%3AdomainOrder.OrderBy="
			. uri_escape("domainname"),
		"__SYNT%3Ad1e1312d0%3AdomainOrder.OrderAscending="
			. uri_escape("true"),
		"__SYNT%3Ad1e1312d0%3A__CMD%5BDomainOverview%5D%3ASELWRP="
			. uri_escape("selectDomain"),
		"__SYNT%3Ad1e1312d0%3A__CMD%5BDomainOverview%5D%3ASELWRP="
			. uri_escape("domainOrder"),
		"__SYNT%3Ad1e1417d0%3AselectDomain.Action="
			. uri_escape("reset"),
		"__SYNT%3Ad1e1417d0%3AdomainOrder.OrderBy="
			. uri_escape("domaintype"),
		"__SYNT%3Ad1e1417d0%3AdomainOrder.OrderAscending="
			. uri_escape("false"),
		"__SYNT%3Ad1e1417d0%3A__CMD%5BDomainOverview%5D%3ASELWRP="
			. uri_escape("selectDomain"),
		"__SYNT%3Ad1e1417d0%3A__CMD%5BDomainOverview%5D%3ASELWRP="
			. uri_escape("domainOrder"),
		"__SYNT%3Ad1e1611d0%3AselectDomain.Action="
			. uri_escape("reset"),
		"__SYNT%3Ad1e1611d0%3AdomainOrder.OrderBy="
			. uri_escape("state"),
		"__SYNT%3Ad1e1611d0%3AdomainOrder.OrderAscending="
			. uri_escape("false"),
		"__SYNT%3Ad1e1611d0%3A__CMD%5BDomainOverview%5D%3ASELWRP="
			. uri_escape("selectDomain"),
		"__SYNT%3Ad1e1611d0%3A__CMD%5BDomainOverview%5D%3ASELWRP="
			. uri_escape("domainOrder"),
		"domainOverview.DomainIds=" . uri_escape($args->{domainIds}),
		"__pageflow=" . uri_escape("dns_flow2"),
	)));

	($this->{headers}, $this->{body}) = ([], []);
	$curl->setopt(CURLOPT_WRITEHEADER, \@{$this->{headers}});
	$curl->setopt(CURLOPT_FILE, \@{$this->{body}});
	$curl->perform() and die "$curl->errbuf\n";
	$curl->setopt(CURLOPT_FOLLOWLOCATION, 0);
	$curl->setopt(CURLOPT_POST, 0);

	if ($this->{verbose}) {
		my $headers = join("", @{$this->{headers}});
		my $body = join("", @{$this->{body}});
		print "[$headers]\n[$body]\n";
	}

	die "HTTP failure in EditDNSSettings! [@{$this->{headers}}]\n"
		if !httpOK($this->{headers});
}

sub DomainNGDnsUpdate($$)
{
	my ($this, $args) = @_;

	my $curl = $this->{curl};
	my @ipaddr = split(/\./, $args->{ipaddr});

	$curl->setopt(CURLOPT_URL, $this->{root}
		. "/xml/config/DomainNGDnsUpdate;"
		. "jsessionid=$this->{jsessionid}");
	$curl->setopt(CURLOPT_FOLLOWLOCATION, 1);
	$curl->setopt(CURLOPT_POST, 1);
	$curl->setopt(CURLOPT_POSTFIELDS, join("&", (
		"__lf=" . uri_escape("dns_flow2"),
		"__sendingdata=" . uri_escape("1"),
		"__currentindex%5BDomainNGDnsUpdate%5D=" . uri_escape("0"),
		"dnsUpdate.UseCname=" . uri_escape("0"),
		"dnsUpdate.Cname=" . uri_escape(""),
		"dnsUpdate.UseCompanyNameserver=" . uri_escape("1"),
		"dnsUpdate.Nameserver0=" . uri_escape(""),
		"dnsUpdate.UseCompanySecondaryNameserver=" . uri_escape("0"),
		"dnsUpdate.Nameserver1=" . uri_escape(""),
		"dnsUpdate.Nameserver2=" . uri_escape(""),
		"dnsUpdate.Nameserver3=" . uri_escape(""),
		"dnsUpdate.SelectIP=" . uri_escape("2"),
		"dnsUpdate.IP0=" . uri_escape($ipaddr[0]),
		"dnsUpdate.IP1=" . uri_escape($ipaddr[1]),
		"dnsUpdate.IP2=" . uri_escape($ipaddr[2]),
		"dnsUpdate.IP3=" . uri_escape($ipaddr[3]),
		"dnsUpdate.UseCompanyMx=" . uri_escape("1"),
		"dnsUpdate.Mx0=" . uri_escape(""),
		"dnsUpdate.Mx0Prio=" . uri_escape(""),
		"dnsUpdate.Mx1=" . uri_escape(""),
		"dnsUpdate.Mx1Prio=" . uri_escape(""),
		"dnsUpdate.UseBackupMx=" . uri_escape("0"),
		"dnsUpdate.Mx2=" . uri_escape(""),
		"dnsUpdate.Mx2Prio=" . uri_escape(""),
		"dnsUpdate.Mx3=" . uri_escape(""),
		"dnsUpdate.Mx3Prio=" . uri_escape(""),
		"__SYNT%3Ad1e1858d0%3AdnsReset.Reset="
			. uri_escape("true"),
		"__SYNT%3Ad1e1858d0%3A__CMD%5BDomainNGDnsUpdate%5D%3ASELWRP="
			. uri_escape("dnsReset"),
		"__SBMT%3Ad1e1874d0%3A="
			. uri_escape("next"),
	)));

	($this->{headers}, $this->{body}) = ([], []);
	$curl->setopt(CURLOPT_WRITEHEADER, \@{$this->{headers}});
	$curl->setopt(CURLOPT_FILE, \@{$this->{body}});
	$curl->perform() and die "$curl->errbuf\n";
	$curl->setopt(CURLOPT_FOLLOWLOCATION, 0);
	$curl->setopt(CURLOPT_POST, 0);

	if ($this->{verbose}) {
		my $headers = join("", @{$this->{headers}});
		my $body = join("", @{$this->{body}});
		print "[$headers]\n[$body]\n";
	}

	die "HTTP failure in DomainNGDNSUpdate! [@{$this->{headers}}]\n"
		if !httpOK($this->{headers});
}

###############################################################################

package main;

use strict;
use Getopt::Std;

use vars qw($opt_v);
die if !getopts("v");
my $verbose = $opt_v ? 1 : 0;

## Changed the line below. Added new parameters $uname and $upass. Added $ARGV[2] and $ARGV[3]
my ($ip, $domain, $uname, $upass) = ($ARGV[0], $ARGV[1], $ARGV[2], $ARGV[3]);
## Changed line below to also check for !$uname and !$upass
if (!$ip || $ip !~ m#^\d+\.\d+\.\d+\.\d+$# || !$domain || !$uname || !$upass) {
## Changed the usage instructions below to include login and password info
	die <   
Example: $0 111.222.333.444 domainname.net 1and1loginname somepassword
EOM
}

$| = 1;

my $a1 = Update1and1->new({
	verbose => $verbose,
});

## Changed auth array to use the vars assigned above ($uname and $upass) instead of hardcoding them in here
$a1->Login({
	auth => {
#		"customerid" => "someloginname",
#		"password" => "somepassword",
		"customerid" => $uname,
		"password" => $upass,
	},
});
my $domainIds = $a1->GetDomain({
	domain => $domain,
});
die "Couldn't find \"$domain\"!\n" if !defined($domainIds);

$a1->EditDNSSettings({
	domainIds => $domainIds,
});

$a1->DomainNGDnsUpdate({
	ipaddr => $ip,
});

Now you can just copy and paste these and save them to your home folder in the server where you host your site, or just download the zip below and follow the instrcutions in the readme file.
dynamic1and1

**** DON’T FORGET TO CHANGE THE SETTINGS TO MATCH YOUR SITE,LOGIN AND PASSWORD INFORMATION ******

You’ll pretty much have to:

1. Unzip the files to your home folder
2. Copy the config.cfg file to /etc/dynamic1and1/config.cfg and edit the settings accordingly
3. (optional) If you unzipped the app into a folder other then your home folder, edit line 8 in checkdns accordingly — function update_dns()
4. (optional) If you decided to place the config.cfg someplace else, edit line 4 in checkdns accordingly — source /etc/dynamic1and1/config.cfg
4. Create a cron job to execute checkdns as often as you see fit

***** KNOWN ISSUES ******
You should not get any errors when you run the script, however, if you get an error like:

WWW::Curl::Easy=SCALAR(0x7855f0) errbuf

It means you either have an outdated version of ca cert or do not have it installed. Just go ahead and install it and you should be good to go. If you are running ubuntu, just do:

sudo apt-get install ca-certificates

If you running a different flavor, head over to to your distro repository and search for ca-certificate and install it from there.

That’s it, you should now have your dynamic address updated on 1&1 DNS servers whenever it changes. Thank you for stopping by and please share with others, after all, code should be free.foscode.com | because code should be free